From 27140f221a70ced1ee04fd23fce37466a0e5b3b8 Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 20 May 2025 15:43:24 -0500 Subject: [PATCH 01/24] Adds core tests --- tests/test_core.py | 120 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 9879cb142..d72781842 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,7 +3,17 @@ import numpy as np -from magpylib._src.fields.field_BH_sphere import magnet_sphere_Bfield +from magpylib.core import ( + current_circle_Hfield, + current_polyline_Hfield, + dipole_Hfield, + magnet_cuboid_Bfield, + magnet_cylinder_axial_Bfield, + magnet_cylinder_diametral_Hfield, + magnet_cylinder_segment_Hfield, + magnet_sphere_Bfield, + triangle_Bfield, +) def test_magnet_sphere_Bfield(): @@ -15,3 +25,111 @@ def test_magnet_sphere_Bfield(): ) Btest = np.array([(0, 0, 2 / 3)]) np.testing.assert_allclose(B, Btest) + + +def test_current_circle_Hfield(): + Htest = np.array([[0.09098208, 0.09415448], [0.0, 0.0], [0.07677892, 0.22625335]]) + H = current_circle_Hfield( + r0=np.array([1, 2]), + r=np.array([1, 1]), + z=np.array([1, 2]), + i0=np.array([1, 3]), + ) + np.testing.assert_allclose(H, Htest) + + +def test_current_polyline_Hfield(): + Htest = np.array([[0.0, -2.29720373, 2.29720373], [0.0, 0.59785204, -0.59785204]]) + + H = current_polyline_Hfield( + observers=np.array([(1, 1, 1), (2, 2, 2)]), + segments_start=np.array([(0, 0, 0), (0, 0, 0)]), + segments_end=np.array([(1, 0, 0), (-1, 0, 0)]), + currents=np.array([100, 200]), + ) + np.testing.assert_allclose(H, Htest) + + +def test_dipole_Hfield(): + Htest = np.array( + [ + [2.89501155e-13, 1.53146915e03, 1.53146915e03], + [1.91433644e02, 1.91433644e02, 3.61876444e-14], + ] + ) + H = dipole_Hfield( + observers=np.array([(1, 1, 1), (2, 2, 2)]), + moments=np.array([(1e5, 0, 0), (0, 0, 1e5)]), + ) + np.testing.assert_allclose(H, Htest) + + +def test_magnet_cuboid_Bfield(): + Btest = np.array( + [ + [1.56103722e-02, 1.56103722e-02, -3.53394965e-17], + [7.73243250e-03, 6.54431406e-03, 1.04789520e-02], + ] + ) + B = magnet_cuboid_Bfield( + observers=np.array([(1, 1, 1), (2, 2, 2)]), + dimensions=np.array([(1, 1, 1), (1, 2, 3)]), + polarizations=np.array([(0, 0, 1), (0.5, 0.5, 0)]), + ) + np.testing.assert_allclose(B, Btest) + + +def test_magnet_cylinder_axial_Bfield(): + Btest = np.array([[0.05561469, 0.04117919], [0.0, 0.0], [0.06690167, 0.01805674]]) + B = magnet_cylinder_axial_Bfield( + z0=np.array([1, 2]), + r=np.array([1, 2]), + z=np.array([2, 3]), + ) + np.testing.assert_allclose(B, Btest) + + +def test_magnet_cylinder_diametral_Hfield(): + Btest = np.array( + [ + [-0.020742122169014, 0.007307203574376], + [0.004597868528024, 0.020075245863212], + [0.05533684822464, 0.029118084290573], + ], + ) + B = magnet_cylinder_diametral_Hfield( + z0=np.array([1, 2]), + r=np.array([1, 2]), + z=np.array([2, 3]), + phi=np.array([0.1, np.pi / 4]), + ) + np.testing.assert_allclose(B, Btest) + + +def test_magnet_cylinder_segment_Hfield(): + Btest = np.array( + [ + [-1948.14367497, 32319.94437208, 17616.88571231], + [14167.64961763, 1419.94126065, 17921.6463117], + ] + ) + B = magnet_cylinder_segment_Hfield( + observers=np.array([(1, 1, 2), (0, 0, 0)]), + dimensions=np.array([(1, 2, 0.1, 0.2, -1, 1), (1, 2, 0.3, 0.9, 0, 1)]), + magnetizations=np.array([(1e7, 0.1, 0.2), (1e6, 1.1, 2.2)]), + ) + np.testing.assert_allclose(B, Btest) + + +def test_triangle_Bfield(): + Btest = np.array( + [[7.45158965, 4.61994866, 3.13614132], [2.21345618, 2.67710148, 2.21345618]] + ) + B = triangle_Bfield( + observers=np.array([(2, 1, 1), (2, 2, 2)]), + vertices=np.array( + [[(0, 0, 0), (0, 0, 1), (1, 0, 0)], [(0, 0, 0), (0, 0, 1), (1, 0, 0)]] + ), + polarizations=np.array([(1, 1, 1), (1, 1, 0)]) * 1e3, + ) + np.testing.assert_allclose(B, Btest) From addd3346acaaa38da5ef01f7145b81966770157d Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 6 May 2025 08:16:30 -0500 Subject: [PATCH 02/24] dev From 5db3a42d8a42e1c348346653557edca7a714cb21 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:56:16 -0500 Subject: [PATCH 03/24] Adds array_api_strict dependency to test --- pyproject.toml | 3 ++ tests/test_core.py | 82 ++++++++++++++++++++++++---------------------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 36301bf4a..3b6f44814 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ dependencies = [ "scipy>=1.8", "matplotlib>=3.6", "plotly>=5.16", + "array-api-compat>=1.11.2", + "array-api-extra>=0.7.1", ] [dependency-groups] @@ -68,6 +70,7 @@ test = [ "imageio[tifffile,ffmpeg]", "jupyterlab", "anywidget", + "array-api-strict>=2.3.1", ] binder = [ "jupytext", diff --git a/tests/test_core.py b/tests/test_core.py index d72781842..d4585f740 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,7 @@ # here all core functions should be tested properly - ideally against FEM from __future__ import annotations +import array_api_strict as xp import numpy as np from magpylib.core import ( @@ -19,78 +20,78 @@ def test_magnet_sphere_Bfield(): "magnet_sphere_Bfield test" B = magnet_sphere_Bfield( - observers=np.array([(0, 0, 0)]), - diameters=np.array([1]), - polarizations=np.array([(0, 0, 1)]), + observers=xp.asarray([(0, 0, 0)]), + diameters=xp.asarray([1]), + polarizations=xp.asarray([(0, 0, 1)]), ) - Btest = np.array([(0, 0, 2 / 3)]) + Btest = xp.asarray([(0, 0, 2 / 3)]) np.testing.assert_allclose(B, Btest) def test_current_circle_Hfield(): - Htest = np.array([[0.09098208, 0.09415448], [0.0, 0.0], [0.07677892, 0.22625335]]) + Htest = xp.asarray([[0.09098208, 0.09415448], [0.0, 0.0], [0.07677892, 0.22625335]]) H = current_circle_Hfield( - r0=np.array([1, 2]), - r=np.array([1, 1]), - z=np.array([1, 2]), - i0=np.array([1, 3]), + r0=xp.asarray([1, 2]), + r=xp.asarray([1, 1]), + z=xp.asarray([1, 2]), + i0=xp.asarray([1, 3]), ) np.testing.assert_allclose(H, Htest) def test_current_polyline_Hfield(): - Htest = np.array([[0.0, -2.29720373, 2.29720373], [0.0, 0.59785204, -0.59785204]]) + Htest = xp.asarray([[0.0, -2.29720373, 2.29720373], [0.0, 0.59785204, -0.59785204]]) H = current_polyline_Hfield( - observers=np.array([(1, 1, 1), (2, 2, 2)]), - segments_start=np.array([(0, 0, 0), (0, 0, 0)]), - segments_end=np.array([(1, 0, 0), (-1, 0, 0)]), - currents=np.array([100, 200]), + observers=xp.asarray([(1, 1, 1), (2, 2, 2)]), + segments_start=xp.asarray([(0, 0, 0), (0, 0, 0)]), + segments_end=xp.asarray([(1, 0, 0), (-1, 0, 0)]), + currents=xp.asarray([100, 200]), ) np.testing.assert_allclose(H, Htest) def test_dipole_Hfield(): - Htest = np.array( + Htest = xp.asarray( [ [2.89501155e-13, 1.53146915e03, 1.53146915e03], [1.91433644e02, 1.91433644e02, 3.61876444e-14], ] ) H = dipole_Hfield( - observers=np.array([(1, 1, 1), (2, 2, 2)]), - moments=np.array([(1e5, 0, 0), (0, 0, 1e5)]), + observers=xp.asarray([(1, 1, 1), (2, 2, 2)]), + moments=xp.asarray([(1e5, 0, 0), (0, 0, 1e5)]), ) np.testing.assert_allclose(H, Htest) def test_magnet_cuboid_Bfield(): - Btest = np.array( + Btest = xp.asarray( [ [1.56103722e-02, 1.56103722e-02, -3.53394965e-17], [7.73243250e-03, 6.54431406e-03, 1.04789520e-02], ] ) B = magnet_cuboid_Bfield( - observers=np.array([(1, 1, 1), (2, 2, 2)]), - dimensions=np.array([(1, 1, 1), (1, 2, 3)]), - polarizations=np.array([(0, 0, 1), (0.5, 0.5, 0)]), + observers=xp.asarray([(1, 1, 1), (2, 2, 2)]), + dimensions=xp.asarray([(1, 1, 1), (1, 2, 3)]), + polarizations=xp.asarray([(0, 0, 1), (0.5, 0.5, 0)]), ) np.testing.assert_allclose(B, Btest) def test_magnet_cylinder_axial_Bfield(): - Btest = np.array([[0.05561469, 0.04117919], [0.0, 0.0], [0.06690167, 0.01805674]]) + Btest = xp.asarray([[0.05561469, 0.04117919], [0.0, 0.0], [0.06690167, 0.01805674]]) B = magnet_cylinder_axial_Bfield( - z0=np.array([1, 2]), - r=np.array([1, 2]), - z=np.array([2, 3]), + z0=xp.asarray([1, 2]), + r=xp.asarray([1, 2]), + z=xp.asarray([2, 3]), ) np.testing.assert_allclose(B, Btest) def test_magnet_cylinder_diametral_Hfield(): - Btest = np.array( + Btest = xp.asarray( [ [-0.020742122169014, 0.007307203574376], [0.004597868528024, 0.020075245863212], @@ -98,38 +99,41 @@ def test_magnet_cylinder_diametral_Hfield(): ], ) B = magnet_cylinder_diametral_Hfield( - z0=np.array([1, 2]), - r=np.array([1, 2]), - z=np.array([2, 3]), - phi=np.array([0.1, np.pi / 4]), + z0=xp.asarray([1, 2]), + r=xp.asarray([1, 2]), + z=xp.asarray([2, 3]), + phi=xp.asarray([0.1, np.pi / 4]), ) np.testing.assert_allclose(B, Btest) def test_magnet_cylinder_segment_Hfield(): - Btest = np.array( + Btest = xp.asarray( [ [-1948.14367497, 32319.94437208, 17616.88571231], [14167.64961763, 1419.94126065, 17921.6463117], ] ) B = magnet_cylinder_segment_Hfield( - observers=np.array([(1, 1, 2), (0, 0, 0)]), - dimensions=np.array([(1, 2, 0.1, 0.2, -1, 1), (1, 2, 0.3, 0.9, 0, 1)]), - magnetizations=np.array([(1e7, 0.1, 0.2), (1e6, 1.1, 2.2)]), + observers=xp.asarray([(1, 1, 2), (0, 0, 0)]), + dimensions=xp.asarray([(1, 2, 0.1, 0.2, -1, 1), (1, 2, 0.3, 0.9, 0, 1)]), + magnetizations=xp.asarray([(1e7, 0.1, 0.2), (1e6, 1.1, 2.2)]), ) np.testing.assert_allclose(B, Btest) def test_triangle_Bfield(): - Btest = np.array( + Btest = xp.asarray( [[7.45158965, 4.61994866, 3.13614132], [2.21345618, 2.67710148, 2.21345618]] ) B = triangle_Bfield( - observers=np.array([(2, 1, 1), (2, 2, 2)]), - vertices=np.array( - [[(0, 0, 0), (0, 0, 1), (1, 0, 0)], [(0, 0, 0), (0, 0, 1), (1, 0, 0)]] + observers=xp.asarray([(2.0, 1.0, 1.0), (2.0, 2.0, 2.0)]), + vertices=xp.asarray( + [ + [(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0)], + [(0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0)], + ] ), - polarizations=np.array([(1, 1, 1), (1, 1, 0)]) * 1e3, + polarizations=xp.asarray([(1.0, 1.0, 1.0), (1.0, 1.0, 0.0)]) * 1e3, ) np.testing.assert_allclose(B, Btest) From 10be96afbffe35e3d02589e9256a642a95e07747 Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 20:46:56 -0500 Subject: [PATCH 04/24] Adds array api support for polyline core function --- src/magpylib/_src/fields/field_BH_polyline.py | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_polyline.py b/src/magpylib/_src/fields/field_BH_polyline.py index 7d998a321..21fadafd6 100644 --- a/src/magpylib/_src/fields/field_BH_polyline.py +++ b/src/magpylib/_src/fields/field_BH_polyline.py @@ -5,11 +5,15 @@ # pylint: disable=too-many-positional-arguments from __future__ import annotations +from itertools import zip_longest + +import array_api_extra as xpx import numpy as np -from numpy.linalg import norm +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote def current_vertices_field( @@ -124,72 +128,73 @@ def current_polyline_Hfield( Be careful with magnetic fields of discontinued segments. They are unphysical and can lead to unphysical effects. """ + xp = array_namespace(observers, segments_start, segments_end, currents) + observers, segments_start, segments_end, currents = xp_promote(observers, segments_start, segments_end, currents, force_floating=True, xp=xp) # rename p1, p2, po = segments_start, segments_end, observers - # make dimensionless (avoid all large/small input problems) by introducing # the segment length as characteristic length scale. - norm_12 = norm(p1 - p2, axis=1) + norm_12 = xp.linalg.vector_norm(p1 - p2, axis=-1) p1 = (p1.T / norm_12).T p2 = (p2.T / norm_12).T po = (po.T / norm_12).T # p4 = projection of pos_obs onto line p1-p2 - t = np.sum((po - p1) * (p1 - p2), axis=1) + t = xp.sum((po - p1) * (p1 - p2), axis=1) p4 = p1 + (t * (p1 - p2).T).T # distance of observers from line - norm_o4 = norm(po - p4, axis=1) + norm_o4 = xp.linalg.vector_norm(po - p4, axis=-1) # separate on-line cases (-> B=0) mask1 = norm_o4 < 1e-15 # account for numerical issues # continue only with general off-line cases - if np.any(mask1): - not_mask1 = ~mask1 - po = po[not_mask1] - p1 = p1[not_mask1] - p2 = p2[not_mask1] - p4 = p4[not_mask1] - norm_12 = norm_12[not_mask1] - norm_o4 = norm_o4[not_mask1] - currents = currents[not_mask1] + not_mask1 = ~mask1 + po = po[not_mask1] + p1 = p1[not_mask1] + p2 = p2[not_mask1] + p4 = p4[not_mask1] + norm_12 = norm_12[not_mask1] + norm_o4 = norm_o4[not_mask1] + currents = currents[not_mask1] # determine field direction - cros_ = np.cross(p2 - p1, po - p4) - norm_cros = norm(cros_, axis=1) + cros_ = xp.linalg.cross(p2 - p1, po - p4) + + norm_cros = xp.linalg.vector_norm(cros_, axis=-1) eB = (cros_.T / norm_cros).T # compute angles - norm_o1 = norm( - po - p1, axis=1 + norm_o1 = xp.linalg.vector_norm( + po - p1, axis=-1 ) # improve performance by computing all norms at once - norm_o2 = norm(po - p2, axis=1) - norm_41 = norm(p4 - p1, axis=1) - norm_42 = norm(p4 - p2, axis=1) + norm_o2 = xp.linalg.vector_norm(po - p2, axis=-1) + norm_41 = xp.linalg.vector_norm(p4 - p1, axis=-1) + norm_42 = xp.linalg.vector_norm(p4 - p2, axis=-1) sinTh1 = norm_41 / norm_o1 sinTh2 = norm_42 / norm_o2 - deltaSin = np.empty((len(po),)) + deltaSin = xp.empty((po.shape[0],)) # determine how p1,p2,p4 are sorted on the line (to get sinTH signs) # both points below - mask2 = (norm_41 > 1) * (norm_41 > norm_42) - deltaSin[mask2] = abs(sinTh1[mask2] - sinTh2[mask2]) + mask2 = (norm_41 > 1) & (norm_41 > norm_42) + deltaSin = xpx.at(deltaSin)[mask2].set(xp.abs(sinTh1[mask2] - sinTh2[mask2])) # both points above - mask3 = (norm_42 > 1) * (norm_42 > norm_41) - deltaSin[mask3] = abs(sinTh2[mask3] - sinTh1[mask3]) + mask3 = (norm_42 > 1) & (norm_42 > norm_41) + deltaSin = xpx.at(deltaSin)[mask3].set(xp.abs(sinTh2[mask3] - sinTh1[mask3])) # one above one below or one equals p4 - mask4 = ~mask2 * ~mask3 - deltaSin[mask4] = abs(sinTh1[mask4] + sinTh2[mask4]) + mask4 = ~mask2 & ~mask3 + deltaSin = xpx.at(deltaSin)[mask4].set(xp.abs(sinTh1[mask4] + sinTh2[mask4])) # B = (deltaSin / norm_o4 * eB.T / norm_12 * current * 1e-7).T # avoid array creation if possible - if np.any(mask1): - H = np.zeros_like(observers, dtype=float) - H[~mask1] = (deltaSin / norm_o4 * eB.T / norm_12 * currents / (4 * np.pi)).T - return H - return (deltaSin / norm_o4 * eB.T / norm_12 * currents / (4 * np.pi)).T + H = xp.zeros_like(observers, dtype=xp.float64) + H = xpx.at(H)[~mask1].set( + (deltaSin / norm_o4 * eB.T / norm_12 * currents / (4 * np.pi)).T + ) + return H def BHJM_current_polyline( From 6ad2478dea37a71451c85cf3803397d8385cf7bb Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 20 May 2025 17:31:37 -0500 Subject: [PATCH 05/24] Adds utility functions for array api Taken directly from https://github.com/scipy/scipy/blob/7f75a948bd46d0453885dac81efded3aaa640907/scipy/_lib/_array_api.py --- src/magpylib/_src/array_api_utils.py | 194 +++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/magpylib/_src/array_api_utils.py diff --git a/src/magpylib/_src/array_api_utils.py b/src/magpylib/_src/array_api_utils.py new file mode 100644 index 000000000..89af5adff --- /dev/null +++ b/src/magpylib/_src/array_api_utils.py @@ -0,0 +1,194 @@ +import numpy as np +import numpy.typing as npt +from array_api_compat import ( + is_numpy_namespace as is_numpy, + is_torch_namespace as is_torch, + array_namespace, +) +from typing import Any, TypeAlias, Literal +from types import ModuleType + +Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589) +ArrayLike: TypeAlias = Array | npt.ArrayLike + + +def _check_finite(array: Array, xp: ModuleType) -> None: + """Check for NaNs or Infs.""" + if not xp.all(xp.isfinite(array)): + msg = "array must not contain infs or NaNs" + raise ValueError(msg) + + +def _asarray( + array: ArrayLike, + dtype: Any = None, + order: Literal["K", "A", "C", "F"] | None = None, + copy: bool | None = None, + *, + xp: ModuleType | None = None, + check_finite: bool = False, + subok: bool = False, +) -> Array: + """SciPy-specific replacement for `np.asarray` with `order`, `check_finite`, and + `subok`. + + Memory layout parameter `order` is not exposed in the Array API standard. + `order` is only enforced if the input array implementation + is NumPy based, otherwise `order` is just silently ignored. + + `check_finite` is also not a keyword in the array API standard; included + here for convenience rather than that having to be a separate function + call inside SciPy functions. + + `subok` is included to allow this function to preserve the behaviour of + `np.asanyarray` for NumPy based inputs. + """ + if xp is None: + xp = array_namespace(array) + if is_numpy(xp): + # Use NumPy API to support order + if copy is True: + array = np.array(array, order=order, dtype=dtype, subok=subok) + elif subok: + array = np.asanyarray(array, order=order, dtype=dtype) + else: + array = np.asarray(array, order=order, dtype=dtype) + else: + try: + array = xp.asarray(array, dtype=dtype, copy=copy) + except TypeError: + coerced_xp = array_namespace(xp.asarray(3)) + array = coerced_xp.asarray(array, dtype=dtype, copy=copy) + + if check_finite: + _check_finite(array, xp) + + return array + + +def xp_default_dtype(xp): + """Query the namespace-dependent default floating-point dtype.""" + if is_torch(xp): + # historically, we allow pytorch to keep its default of float32 + return xp.get_default_dtype() + else: + # we default to float64 + return xp.float64 + + +def xp_result_type(*args, force_floating=False, xp): + """ + Returns the dtype that results from applying type promotion rules + (see Array API Standard Type Promotion Rules) to the arguments. Augments + standard `result_type` in a few ways: + + - There is a `force_floating` argument that ensures that the result type + is floating point, even when all args are integer. + - When a TypeError is raised (e.g. due to an unsupported promotion) + and `force_floating=True`, we define a custom rule: use the result type + of the default float and any other floats passed. See + https://github.com/scipy/scipy/pull/22695/files#r1997905891 + for rationale. + - This function accepts array-like iterables, which are immediately converted + to the namespace's arrays before result type calculation. Consequently, the + result dtype may be different when an argument is `1.` vs `[1.]`. + + Typically, this function will be called shortly after `array_namespace` + on a subset of the arguments passed to `array_namespace`. + """ + args = [ + (_asarray(arg, subok=True, xp=xp) if np.iterable(arg) else arg) for arg in args + ] + args_not_none = [arg for arg in args if arg is not None] + if force_floating: + args_not_none.append(1.0) + + if is_numpy(xp) and xp.__version__ < "2.0": + # Follow NEP 50 promotion rules anyway + args_not_none = [ + arg.dtype if getattr(arg, "size", 0) == 1 else arg for arg in args_not_none + ] + return xp.result_type(*args_not_none) + + try: # follow library's preferred promotion rules + return xp.result_type(*args_not_none) + except TypeError: # mixed type promotion isn't defined + if not force_floating: + raise + # use `result_type` of default floating point type and any floats present + # This can be revisited, but right now, the only backends that get here + # are array-api-strict (which is not for production use) and PyTorch + # (due to data-apis/array-api-compat#279). + float_args = [] + for arg in args_not_none: + arg_array = xp.asarray(arg) if np.isscalar(arg) else arg + dtype = getattr(arg_array, "dtype", arg) + if xp.isdtype(dtype, ("real floating", "complex floating")): + float_args.append(arg) + return xp.result_type(*float_args, xp_default_dtype(xp)) + + +def xp_promote(*args, broadcast=False, force_floating=False, xp): + """ + Promotes elements of *args to result dtype, ignoring `None`s. + Includes options for forcing promotion to floating point and + broadcasting the arrays, again ignoring `None`s. + Type promotion rules follow `xp_result_type` instead of `xp.result_type`. + + Typically, this function will be called shortly after `array_namespace` + on a subset of the arguments passed to `array_namespace`. + + This function accepts array-like iterables, which are immediately converted + to the namespace's arrays before result type calculation. Consequently, the + result dtype may be different when an argument is `1.` vs `[1.]`. + + See Also + -------- + xp_result_type + """ + args = [ + (_asarray(arg, subok=True, xp=xp) if np.iterable(arg) else arg) for arg in args + ] # solely to prevent double conversion of iterable to array + + dtype = xp_result_type(*args, force_floating=force_floating, xp=xp) + + args = [ + (_asarray(arg, dtype=dtype, subok=True, xp=xp) if arg is not None else arg) + for arg in args + ] + + if not broadcast: + return args[0] if len(args) == 1 else tuple(args) + + args_not_none = [arg for arg in args if arg is not None] + + # determine result shape + shapes = {arg.shape for arg in args_not_none} + try: + shape = ( + np.broadcast_shapes(*shapes) if len(shapes) != 1 else args_not_none[0].shape + ) + except ValueError as e: + message = "Array shapes are incompatible for broadcasting." + raise ValueError(message) from e + + out = [] + for arg in args: + if arg is None: + out.append(arg) + continue + + # broadcast only if needed + # Even if two arguments need broadcasting, this is faster than + # `broadcast_arrays`, especially since we've already determined `shape` + if arg.shape != shape: + kwargs = {"subok": True} if is_numpy(xp) else {} + arg = xp.broadcast_to(arg, shape, **kwargs) + + # This is much faster than xp.astype(arg, dtype, copy=False) + if arg.dtype != dtype: + arg = xp.astype(arg, dtype) + + out.append(arg) + + return out[0] if len(out) == 1 else tuple(out) From b89c186a9d1341ae174b6b42f1d78dca138d9cf7 Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 23:49:59 -0500 Subject: [PATCH 06/24] Adds array api support for triangle core function --- src/magpylib/_src/fields/field_BH_triangle.py | 143 +++++++++++++----- 1 file changed, 101 insertions(+), 42 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_triangle.py b/src/magpylib/_src/fields/field_BH_triangle.py index c311a330b..925d06068 100644 --- a/src/magpylib/_src/fields/field_BH_triangle.py +++ b/src/magpylib/_src/fields/field_BH_triangle.py @@ -6,13 +6,18 @@ # pylance: disable=Code is unreachable from __future__ import annotations +from functools import partial + +import array_api_extra as xpx import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote -def vcross3(a: np.ndarray, b: np.ndarray) -> np.ndarray: +def vcross3(xp, a: np.ndarray, b: np.ndarray) -> np.ndarray: """ vectorized cross product for 3d vectors. Is ~4x faster than np.cross when arrays are smallish. Only slightly faster for large arrays. @@ -20,29 +25,28 @@ def vcross3(a: np.ndarray, b: np.ndarray) -> np.ndarray: returns: (n, 3) """ # receives nan values at corners - with np.errstate(invalid="ignore"): - result = np.array( - [ - a[:, 1] * b[:, 2] - a[:, 2] * b[:, 1], - a[:, 2] * b[:, 0] - a[:, 0] * b[:, 2], - a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0], - ] - ) + result = xp.asarray( + [ + a[:, 1] * b[:, 2] - a[:, 2] * b[:, 1], + a[:, 2] * b[:, 0] - a[:, 0] * b[:, 2], + a[:, 0] * b[:, 1] - a[:, 1] * b[:, 0], + ] + ) return result.T -def norm_vector(v) -> np.ndarray: +def norm_vector_xp(xp, v) -> np.ndarray: """ Calculates normalized orthogonal vector on a plane defined by three vertices. """ - a = v[:, 1] - v[:, 0] - b = v[:, 2] - v[:, 0] - n = vcross3(a, b) - n_norm = np.linalg.norm(n, axis=-1) - return n / np.expand_dims(n_norm, axis=-1) + a = v[:, 1, ...] - v[:, 0, ...] + b = v[:, 2, ...] - v[:, 0, ...] + n = xp.linalg.cross(a, b) + n_norm = xp.sqrt(xp.vecdot(n, n, axis=-1)) + return n / xp.expand_dims(n_norm, axis=-1) -def solid_angle(R: np.ndarray, r: np.ndarray) -> np.ndarray: +def solid_angle_xp(xp, R: np.ndarray, r: np.ndarray) -> np.ndarray: """ Vectorized computation of the solid angle of triangles. @@ -56,23 +60,40 @@ def solid_angle(R: np.ndarray, r: np.ndarray) -> np.ndarray: Returns: [sangle_a, sangle_b, sangle_c, ...] """ + R0, R1, R2 = R[0, ...], R[1, ...], R[2, ...] + r0, r1, r2 = r[0, ...], r[1, ...], r[2, ...] # Calculates (oriented) volume of the parallelepiped in vectorized form. - N = np.einsum("ij, ij->i", R[2], vcross3(R[1], R[0])) + N = xp.vecdot( + R2, + xp.linalg.cross(R1, R0), + ) D = ( - r[0] * r[1] * r[2] - + np.einsum("ij, ij->i", R[2], R[1]) * r[0] - + np.einsum("ij, ij->i", R[2], R[0]) * r[1] - + np.einsum("ij, ij->i", R[1], R[0]) * r[2] + r0 * r1 * r2 + + xp.vecdot( + R2, + R1, + ) + * r0 + + xp.vecdot( + R2, + R0, + ) + * r1 + + xp.vecdot( + R1, + R0, + ) + * r2 ) - result = 2.0 * np.arctan2(N, D) + result = 2.0 * xp.atan2(N, D) # modulus 2pi to avoid jumps on edges in line # "B = sigma * ((n.T * solid_angle(R, r)) - vcross3(n, PQR).T)" # <-- bad fix :( - return np.where(abs(result) > 6.2831853, 0, result) + return xp.where(xp.abs(result) > 6.2831853, 0, result) def triangle_Bfield( @@ -130,24 +151,37 @@ def triangle_Bfield( Loss of precision when approaching a triangle as (x-edge)**2 :( Loss of precision with distance from the triangle as distance**3 :( """ + xp = array_namespace(observers, vertices, polarizations) + observers, vertices, polarizations = xp_promote( + observers, vertices, polarizations, force_floating=True, xp=xp + ) + norm_vector = partial(norm_vector_xp, xp) + solid_angle = partial(solid_angle_xp, xp) + n = norm_vector(vertices) - sigma = np.einsum("ij, ij->i", n, polarizations) # vectorized inner product + sigma = xp.vecdot( + n, + polarizations, + ) # vectorized inner product # vertex <-> observer - R = np.swapaxes(vertices, 0, 1) - observers - r2 = np.sum(R * R, axis=-1) - r = np.sqrt(r2) + R = xp.permute_dims(vertices, (1, 0, 2)) - observers + r2 = xp.sum(R * R, axis=-1) + r = xp.sqrt(r2) # vertex <-> vertex - L = vertices[:, (1, 2, 0)] - vertices[:, (0, 1, 2)] - L = np.swapaxes(L, 0, 1) - l2 = np.sum(L * L, axis=-1) - l1 = np.sqrt(l2) + L = xp.empty_like(vertices[:, 0:3, :]) + L = xpx.at(L)[:, 0, ...].set(vertices[:, 1, :] - vertices[:, 0, :]) + L = xpx.at(L)[:, 1, ...].set(vertices[:, 2, :] - vertices[:, 1, :]) + L = xpx.at(L)[:, 2, ...].set(vertices[:, 0, :] - vertices[:, 2, :]) + L = xp.permute_dims(L, (1, 0, 2)) + l2 = xp.sum(L * L, axis=-1) + l1 = xp.sqrt(l2) # vert-vert -- vert-obs - b = np.einsum("ijk, ijk->ij", R, L) + b = xp.vecdot(R, L) bl = b / l1 - ind = np.fabs(r + bl) # closeness measure to corner and edge + ind = xp.abs(r + bl) # closeness measure to corner and edge # The computation of ind is the origin of a major numerical instability # when approaching the triangle because r ~ -bl. This number @@ -166,16 +200,41 @@ def triangle_Bfield( # 1/x, # 0 # ) + def B_not_degenerate(l1, l2, r, r2, b, bl, ind): + l_arg1 = xp.sqrt(l2 + 2 * b + r2) + l1 + bl + return 1.0 / l1 * xp.log(l_arg1 / ind) + + def B_edge(l1, r): + lr = xp.abs(l1 - r) + return -(1.0 / l1) * xp.log(lr / r) + + def B_degenerate(l1, l2, r, r2, b, bl, ind): + r_mask = (r != 0.0) & (xp.abs(l1 - r) > 0.0) + return xpx.apply_where(r_mask, (l1, r), B_edge, fill_value=xp.nan) + + ind_mask = ind > 1.0e-12 + r_mask = r == 0.0 + r_copy = xp.asarray(r, copy=True) + lr = xp.abs(l1 - r) + lr_mask = lr == 0.0 + lr = xpx.at(lr)[ind_mask | lr_mask].set(3.0) + ind = xpx.at(ind)[~ind_mask].set(1.0) + r_copy = xpx.at(r_copy)[r_mask].set(1.0) + + I = xpx.apply_where( # noqa: E741 + ind_mask, + (l1, l2, r, r2, b, bl, ind), + B_not_degenerate, + B_degenerate, + # 1.0 / l1 * xp.log(l_arg1 / ind), + # -(1.0 / l1) * xp.log(lr / r_copy), + ) + I = xpx.at(I)[r_mask].set(xp.nan) + I = xpx.at(I)[lr_mask & ~ind_mask].set(xp.nan) - with np.errstate(divide="ignore", invalid="ignore"): - I = np.where( # noqa: E741 - ind > 1.0e-12, - 1.0 / l1 * np.log((np.sqrt(l2 + 2 * b + r2) + l1 + bl) / ind), - -(1.0 / l1) * np.log(np.fabs(l1 - r) / r), - ) - PQR = np.einsum("ij, ijk -> jk", I, L) - B = sigma * (n.T * solid_angle(R, r) - vcross3(n, PQR).T) - B = B / np.pi / 4.0 + PQR = xp.vecdot(I[:, :, xp.newaxis], L, axis=-3) + B = sigma * (n.T * solid_angle(R, r) - xp.linalg.cross(n, PQR).T) + B = B / xp.pi / 4.0 return B.T From 3167d02feadff01034efed9cd96a7dc16c0767a3 Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 19:06:33 -0500 Subject: [PATCH 07/24] Adds array api support for cuboid core function --- src/magpylib/_src/fields/field_BH_cuboid.py | 112 +++++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cuboid.py b/src/magpylib/_src/fields/field_BH_cuboid.py index d79a47b2f..7f3a82e02 100644 --- a/src/magpylib/_src/fields/field_BH_cuboid.py +++ b/src/magpylib/_src/fields/field_BH_cuboid.py @@ -6,9 +6,11 @@ from __future__ import annotations import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote # pylint: disable=too-many-statements @@ -79,9 +81,14 @@ def magnet_cuboid_Bfield( Cichon: IEEE Sensors Journal, vol. 19, no. 7, April 1, 2019, p.2509 """ - pol_x, pol_y, pol_z = polarizations.T - a, b, c = dimensions.T / 2 - x, y, z = np.copy(observers).T + xp = array_namespace(observers, dimensions, polarizations) + observers, dimensions, polarizations = xp_promote( + observers, dimensions, polarizations, force_floating=True, xp=xp + ) + + pol_x, pol_y, pol_z = (polarizations[:, i] for i in range(3)) + a, b, c = ((dimensions / 2)[:, i] for i in range(3)) + x, y, z = (observers[:, i] for i in range(3)) # avoid indeterminate forms by evaluating in bottQ4 only -------- # basic masks @@ -95,10 +102,10 @@ def magnet_cuboid_Bfield( z[maskz] = z[maskz] * -1 # create sign flips for position changes - qsigns = np.ones((len(pol_x), 3, 3)) - qs_flipx = np.array([[1, -1, -1], [-1, 1, 1], [-1, 1, 1]]) - qs_flipy = np.array([[1, -1, 1], [-1, 1, -1], [1, -1, 1]]) - qs_flipz = np.array([[1, 1, -1], [1, 1, -1], [-1, -1, 1]]) + qsigns = xp.ones((pol_x.shape[0], 3, 3), dtype=xp.float32) + qs_flipx = xp.asarray([[1, -1, -1], [-1, 1, 1], [-1, 1, 1]], dtype=xp.float32) + qs_flipy = xp.asarray([[1, -1, 1], [-1, 1, -1], [1, -1, 1]], dtype=xp.float32) + qs_flipz = xp.asarray([[1, 1, -1], [1, 1, -1], [-1, -1, 1]], dtype=xp.float32) # signs flips can be applied subsequently qsigns[maskx] = qsigns[maskx] * qs_flipx qsigns[masky] = qsigns[masky] * qs_flipy @@ -119,59 +126,58 @@ def magnet_cuboid_Bfield( ymb2, ypb2 = ymb**2, ypb**2 zmc2, zpc2 = zmc**2, zpc**2 - mmm = np.sqrt(xma2 + ymb2 + zmc2) - pmp = np.sqrt(xpa2 + ymb2 + zpc2) - pmm = np.sqrt(xpa2 + ymb2 + zmc2) - mmp = np.sqrt(xma2 + ymb2 + zpc2) - mpm = np.sqrt(xma2 + ypb2 + zmc2) - ppp = np.sqrt(xpa2 + ypb2 + zpc2) - ppm = np.sqrt(xpa2 + ypb2 + zmc2) - mpp = np.sqrt(xma2 + ypb2 + zpc2) - - with np.errstate(divide="ignore", invalid="ignore"): - ff2x = np.log((xma + mmm) * (xpa + ppm) * (xpa + pmp) * (xma + mpp)) - np.log( - (xpa + pmm) * (xma + mpm) * (xma + mmp) * (xpa + ppp) - ) + mmm = xp.sqrt(xma2 + ymb2 + zmc2) + pmp = xp.sqrt(xpa2 + ymb2 + zpc2) + pmm = xp.sqrt(xpa2 + ymb2 + zmc2) + mmp = xp.sqrt(xma2 + ymb2 + zpc2) + mpm = xp.sqrt(xma2 + ypb2 + zmc2) + ppp = xp.sqrt(xpa2 + ypb2 + zpc2) + ppm = xp.sqrt(xpa2 + ypb2 + zmc2) + mpp = xp.sqrt(xma2 + ypb2 + zpc2) + + ff2x = xp.log((xma + mmm) * (xpa + ppm) * (xpa + pmp) * (xma + mpp)) - xp.log( + (xpa + pmm) * (xma + mpm) * (xma + mmp) * (xpa + ppp) + ) - ff2y = np.log( - (-ymb + mmm) * (-ypb + ppm) * (-ymb + pmp) * (-ypb + mpp) - ) - np.log((-ymb + pmm) * (-ypb + mpm) * (ymb - mmp) * (ypb - ppp)) + ff2y = xp.log((-ymb + mmm) * (-ypb + ppm) * (-ymb + pmp) * (-ypb + mpp)) - xp.log( + (-ymb + pmm) * (-ypb + mpm) * (ymb - mmp) * (ypb - ppp) + ) - ff2z = np.log( - (-zmc + mmm) * (-zmc + ppm) * (-zpc + pmp) * (-zpc + mpp) - ) - np.log((-zmc + pmm) * (zmc - mpm) * (-zpc + mmp) * (zpc - ppp)) + ff2z = xp.log((-zmc + mmm) * (-zmc + ppm) * (-zpc + pmp) * (-zpc + mpp)) - xp.log( + (-zmc + pmm) * (zmc - mpm) * (-zpc + mmp) * (zpc - ppp) + ) ff1x = ( - np.arctan2((ymb * zmc), (xma * mmm)) - - np.arctan2((ymb * zmc), (xpa * pmm)) - - np.arctan2((ypb * zmc), (xma * mpm)) - + np.arctan2((ypb * zmc), (xpa * ppm)) - - np.arctan2((ymb * zpc), (xma * mmp)) - + np.arctan2((ymb * zpc), (xpa * pmp)) - + np.arctan2((ypb * zpc), (xma * mpp)) - - np.arctan2((ypb * zpc), (xpa * ppp)) + xp.atan2((ymb * zmc), (xma * mmm)) + - xp.atan2((ymb * zmc), (xpa * pmm)) + - xp.atan2((ypb * zmc), (xma * mpm)) + + xp.atan2((ypb * zmc), (xpa * ppm)) + - xp.atan2((ymb * zpc), (xma * mmp)) + + xp.atan2((ymb * zpc), (xpa * pmp)) + + xp.atan2((ypb * zpc), (xma * mpp)) + - xp.atan2((ypb * zpc), (xpa * ppp)) ) ff1y = ( - np.arctan2((xma * zmc), (ymb * mmm)) - - np.arctan2((xpa * zmc), (ymb * pmm)) - - np.arctan2((xma * zmc), (ypb * mpm)) - + np.arctan2((xpa * zmc), (ypb * ppm)) - - np.arctan2((xma * zpc), (ymb * mmp)) - + np.arctan2((xpa * zpc), (ymb * pmp)) - + np.arctan2((xma * zpc), (ypb * mpp)) - - np.arctan2((xpa * zpc), (ypb * ppp)) + xp.atan2((xma * zmc), (ymb * mmm)) + - xp.atan2((xpa * zmc), (ymb * pmm)) + - xp.atan2((xma * zmc), (ypb * mpm)) + + xp.atan2((xpa * zmc), (ypb * ppm)) + - xp.atan2((xma * zpc), (ymb * mmp)) + + xp.atan2((xpa * zpc), (ymb * pmp)) + + xp.atan2((xma * zpc), (ypb * mpp)) + - xp.atan2((xpa * zpc), (ypb * ppp)) ) ff1z = ( - np.arctan2((xma * ymb), (zmc * mmm)) - - np.arctan2((xpa * ymb), (zmc * pmm)) - - np.arctan2((xma * ypb), (zmc * mpm)) - + np.arctan2((xpa * ypb), (zmc * ppm)) - - np.arctan2((xma * ymb), (zpc * mmp)) - + np.arctan2((xpa * ymb), (zpc * pmp)) - + np.arctan2((xma * ypb), (zpc * mpp)) - - np.arctan2((xpa * ypb), (zpc * ppp)) + xp.atan2((xma * ymb), (zmc * mmm)) + - xp.atan2((xpa * ymb), (zmc * pmm)) + - xp.atan2((xma * ypb), (zmc * mpm)) + + xp.atan2((xpa * ypb), (zmc * ppm)) + - xp.atan2((xma * ymb), (zpc * mmp)) + + xp.atan2((xpa * ymb), (zpc * pmp)) + + xp.atan2((xma * ypb), (zpc * mpp)) + - xp.atan2((xpa * ypb), (zpc * ppp)) ) # contributions from x-polarization @@ -193,10 +199,10 @@ def magnet_cuboid_Bfield( by_tot = by_pol_x + by_pol_y + by_pol_z bz_tot = bz_pol_x + bz_pol_y + bz_pol_z - # B = np.c_[bx_tot, by_tot, bz_tot] # faster for 10^5 and more evaluations - B = np.concatenate(((bx_tot,), (by_tot,), (bz_tot,)), axis=0).T + # B = xp.c_[bx_tot, by_tot, bz_tot] # faster for 10^5 and more evaluations + B = xp.stack((bx_tot, by_tot, bz_tot), axis=-1) - B /= 4 * np.pi + B /= 4 * xp.pi return B From 41df3314d6dc9975cd8459f5885198b2e62655ab Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 6 May 2025 18:35:38 -0500 Subject: [PATCH 08/24] Adds array api support for sphere core function --- src/magpylib/_src/fields/field_BH_sphere.py | 42 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_sphere.py b/src/magpylib/_src/fields/field_BH_sphere.py index 585ba1e2d..a9c64c446 100644 --- a/src/magpylib/_src/fields/field_BH_sphere.py +++ b/src/magpylib/_src/fields/field_BH_sphere.py @@ -5,10 +5,13 @@ from __future__ import annotations +import array_api_extra as xpx import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote # CORE @@ -58,12 +61,39 @@ def magnet_sphere_Bfield( The field corresponds to a dipole field on the outside and is 2/3*mag in the inside (see e.g. "Theoretical Physics, Bertelmann"). """ - return BHJM_magnet_sphere( - field="B", - observers=observers, - diameter=diameters, - polarization=polarizations, - ) + xp = array_namespace(observers, diameters, polarizations) + observers, diameters, polarizations = xp_promote(observers, diameters, polarizations, force_floating=True, xp=xp) + r = xp.linalg.vector_norm(observers, axis=-1) + r_sphere = xp.abs(diameters) / 2.0 + + # inside field & allocate + polarization = polarizations + mask_out = r > r_sphere + + mask_in = ~mask_out + + def B_in(polarization): + return 2.0 * polarization / 3.0 + + def B_out(polarization, observers, r_sphere, r): + mask = r == 0.0 + r = xpx.at(r)[mask].set(xp.nan) + return ( + ( + xp.divide( + 3 * xp.sum(polarization * observers, axis=1) * observers.T + - polarization.T * r**2, + r**5, + ) + ) + * r_sphere**3 + / 3.0 + ).T + + B = B_out(polarization, observers, r_sphere[xp.newaxis, :], r[xp.newaxis, :]) + B = xpx.at(B)[mask_in].set(B_in(polarization[mask_in])) + + return B def BHJM_magnet_sphere( From 6347cc489fca6ebe0e4f9f22e7243b998e528f7c Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 6 May 2025 11:12:13 -0500 Subject: [PATCH 09/24] Adds array api support for dipole field core function --- src/magpylib/_src/fields/field_BH_dipole.py | 41 ++++++++++----------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_dipole.py b/src/magpylib/_src/fields/field_BH_dipole.py index 07196bc76..201f008b0 100644 --- a/src/magpylib/_src/fields/field_BH_dipole.py +++ b/src/magpylib/_src/fields/field_BH_dipole.py @@ -4,10 +4,13 @@ from __future__ import annotations +import array_api_extra as xpx import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote # CORE @@ -53,27 +56,23 @@ def dipole_Hfield( ----- The moment of a magnet is given by its volume*magnetization. """ - - x, y, z = observers.T - r = np.sqrt(x**2 + y**2 + z**2) # faster than np.linalg.norm - with np.errstate(divide="ignore", invalid="ignore"): - # 0/0 produces invalid warn and results in np.nan - # x/0 produces divide warn and results in np.inf - H = ( - ( - 3 * np.sum(moments * observers, axis=1) * observers.T / r**5 - - moments.T / r**3 - ).T - / 4 - / np.pi - ) - - # when r=0 return np.inf in all non-zero moments directions - mask1 = r == 0 - if np.any(mask1): - with np.errstate(divide="ignore", invalid="ignore"): - H[mask1] = moments[mask1] / 0.0 - np.nan_to_num(H, copy=False, posinf=np.inf, neginf=-np.inf) + xp = array_namespace(observers, moments) + observers, moments = xp_promote(observers, moments, force_floating=True, xp=xp) + r = xp.linalg.vector_norm(observers, axis=-1, keepdims=True) + + # 0/0 produces invalid warn and results in np.nan + # x/0 produces divide warn and results in np.inf + mask_r = r == 0.0 + r = xpx.at(r)[mask_r].set(1.0) + dotprod = xp.vecdot(moments, observers)[:, xp.newaxis] + + def B(dotprod, observers, moments, r): + A = xp.divide(3 * dotprod * observers, r**5) + B = xp.divide(moments, r**3) + return xp.divide((A - B), 4.0 * xp.pi) + + H = xpx.apply_where(~mask_r, (dotprod, observers, moments, r), B, fill_value=xp.inf) + # H = xpx.at(H)[xp.broadcast_to(mask_r, H.shape)].set(xp.inf) return H From 07054b37337f79d7984b5b354f754700c25454e6 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 14:00:10 -0500 Subject: [PATCH 10/24] Adds array api support for circle core function --- src/magpylib/_src/fields/field_BH_circle.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_circle.py b/src/magpylib/_src/fields/field_BH_circle.py index 9ee4fc062..46c87e32d 100644 --- a/src/magpylib/_src/fields/field_BH_circle.py +++ b/src/magpylib/_src/fields/field_BH_circle.py @@ -8,9 +8,12 @@ import numpy as np from scipy.constants import mu_0 as MU0 +from array_api_compat import array_namespace + from magpylib._src.fields.special_cel import cel_iter from magpylib._src.input_checks import check_field_input from magpylib._src.utility import cart_to_cyl_coordinates, cyl_field_to_cart +from magpylib._src.array_api_utils import xp_promote # CORE @@ -67,7 +70,9 @@ def current_circle_Hfield( efficient expression for the magnetic field of a current loop.", M.Ortner et al, Magnetism 2023, 3(1), 11-31. """ - n5 = len(r) + xp = array_namespace(r0, r, z, i0) + r0, r, z, i0 = xp_promote(r0, r, z, i0, force_floating=True, xp=xp) + n5 = r.shape[0] # express through ratios (make dimensionless, avoid large/small input values, stupid) r = r / r0 @@ -79,23 +84,23 @@ def current_circle_Hfield( k2 = 4 * r / x0 q2 = (z2 + (r - 1) ** 2) / x0 - k = np.sqrt(k2) - q = np.sqrt(q2) + k = xp.sqrt(k2) + q = xp.sqrt(q2) p = 1 + q - pf = k / np.sqrt(r) / q2 / 20 / r0 * 1e-6 * i0 + pf = k / xp.sqrt(r) / q2 / 20 / r0 * 1e-6 * i0 # cel* part cc = k2 * k2 ss = 2 * cc * q / p - Hr = pf * z / r * cel_iter(q, p, np.ones(n5), cc, ss, p, q) + Hr = pf * z / r * cel_iter(q, p, xp.ones(n5), cc, ss, p, q) # cel** part cc = k2 * (k2 - (q2 + 1) / r) ss = 2 * k2 * q * (k2 / p - p / r) - Hz = -pf * cel_iter(q, p, np.ones(n5), cc, ss, p, q) + Hz = -pf * cel_iter(q, p, xp.ones(n5), cc, ss, p, q) # input is I -> output must be H-field - return np.vstack((Hr, np.zeros(n5), Hz)) * 795774.7154594767 # *1e7/4/np.pi + return xp.stack((Hr, xp.zeros(n5), Hz), axis=0) * 795774.7154594767 # *1e7/4/np.pi def BHJM_circle( From 4ced289eac5d03e8a3f23acc77af709ebdc2ad57 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 14:00:16 -0500 Subject: [PATCH 11/24] Adds array api suppport for elliptic functions --- src/magpylib/_src/fields/special_cel.py | 108 +++++++++++-------- src/magpylib/_src/fields/special_elliptic.py | 18 ++++ 2 files changed, 79 insertions(+), 47 deletions(-) create mode 100644 src/magpylib/_src/fields/special_elliptic.py diff --git a/src/magpylib/_src/fields/special_cel.py b/src/magpylib/_src/fields/special_cel.py index 149dc6880..715c5f001 100644 --- a/src/magpylib/_src/fields/special_cel.py +++ b/src/magpylib/_src/fields/special_cel.py @@ -3,7 +3,9 @@ import math as m +import array_api_extra as xpx import numpy as np +from array_api_compat import array_namespace def cel0(kc, p, c, s): @@ -56,63 +58,74 @@ def celv(kc, p, c, s): """ vectorized version of the cel integral above """ + xp = array_namespace(kc, p, c, s) # if kc == 0: # return NaN + # errtol = 0.000001 errtol = 0.000001 - n = len(kc) + n = kc.shape[0] - k = np.abs(kc) - em = np.ones(n, dtype=float) + k = xp.abs(kc) + em = xp.ones(n, dtype=xp.float64) - cc = c.copy() - pp = p.copy() - ss = s.copy() + # cc = xp.asarray(c, copy=True) + # pp = xp.asarray(p, copy=True) + # ss = xp.asarray(s, copy=True) # apply a mask for evaluation of respective cases mask = p <= 0 - # if p>0: - pp[~mask] = np.sqrt(p[~mask]) - ss[~mask] = s[~mask] / pp[~mask] - - # else: - f = kc[mask] * kc[mask] + f = kc * kc q = 1.0 - f - g = 1.0 - pp[mask] - f = f - pp[mask] - q = q * (ss[mask] - c[mask] * pp[mask]) - pp[mask] = np.sqrt(f / g) - cc[mask] = (c[mask] - ss[mask]) / g - ss[mask] = -q / (g * g * pp[mask]) + cc[mask] * pp[mask] - - f = cc.copy() + g = 1.0 - p + f = f - p + q = q * (s - c * p) + pp = xpx.apply_where( + mask, + (p, f, g), + lambda p, f, g: xp.sqrt(f / g), + lambda p, f, g: xp.sqrt(p), + ) + cc = xpx.apply_where( + mask, (c, s, g), lambda c, s, g: (c - s) / g, lambda c, ss, g: c + ) + ss = xpx.apply_where( + mask, + (s, pp, q, g, cc), + lambda s, pp, q, g, cc: -q / (g * g * pp) + cc * pp, + lambda s, pp, q, g, cc: s / pp, + ) + + f = xp.asarray(cc, copy=True) cc = cc + ss / pp g = k / pp ss = 2 * (ss + f * g) pp = g + pp - g = em.copy() + g = xp.asarray(em, copy=True) em = k + em - kk = k.copy() + kk = xp.asarray(k, copy=True) # define a mask that adjusts with every evaluation step so that only # non-converged entries are further iterated. - mask = np.ones(n, dtype=bool) - while np.any(mask): - k[mask] = 2 * np.sqrt(kk[mask]) - kk[mask] = k[mask] * em[mask] - f[mask] = cc[mask] - cc[mask] = cc[mask] + ss[mask] / pp[mask] - g[mask] = kk[mask] / pp[mask] - ss[mask] = 2 * (ss[mask] + f[mask] * g[mask]) - pp[mask] = g[mask] + pp[mask] - g[mask] = em[mask] - em[mask] = k[mask] + em[mask] + mask = xp.ones(n, dtype=xp.bool) + while xp.any(mask): + k = 2 * xp.sqrt(kk) + kk = k * em + f = cc + cc = cc + ss / pp + g = kk / pp + ss = 2 * (ss + f * g) + pp = g + pp + g = em + em = k + em # redefine mask - mask = np.abs(g - k) > g * errtol + err = g - k + tol = g * errtol + mask = xp.abs(err) > tol - return (np.pi / 2) * (ss + cc * em) / (em * (em + pp)) + return (xp.pi / 2) * (ss + cc * em) / (em * (em + pp)) def cel(kcv: np.ndarray, pv: np.ndarray, cv: np.ndarray, sv: np.ndarray) -> np.ndarray: @@ -128,12 +141,12 @@ def ellipticE(x): def ellipticPi(x, y): return elliptic((1-y)**(1/2.), 1-x, 1, 1) """ - n_input = len(kcv) + # n_input = len(kcv) - if n_input < 10: - return np.array( - [cel0(kc, p, c, s) for kc, p, c, s in zip(kcv, pv, cv, sv, strict=False)] - ) + # if n_input < 10: + # return np.array( + # [cel0(kc, p, c, s) for kc, p, c, s in zip(kcv, pv, cv, sv, strict=False)] + # ) return celv(kcv, pv, cv, sv) @@ -146,11 +159,11 @@ def cel_iter(qc, p, g, cc, ss, em, kk): # This cannot happen in core functions # case2: small input vector - loop is faster than vectorized computation - n_input = len(qc) - if n_input < 15: - result = np.zeros(n_input) - for i in range(n_input): - result[i] = cel_iter0(qc[i], p[i], g[i], cc[i], ss[i], em[i], kk[i]) + # n_input = len(qc) + # if n_input < 15: + # result = np.zeros(n_input) + # for i in range(n_input): + # result[i] = cel_iter0(qc[i], p[i], g[i], cc[i], ss[i], em[i], kk[i]) # case3: vectorized evaluation return cel_iterv(qc, p, g, cc, ss, em, kk) @@ -177,8 +190,9 @@ def cel_iterv(qc, p, g, cc, ss, em, kk): """ Iterative part of Bulirsch cel algorithm """ - while np.any(np.fabs(g - qc) >= qc * 1e-8): - qc = 2 * np.sqrt(kk) + xp = array_namespace(qc, p, g, cc, ss, em, kk) + while xp.any(xp.abs(g - qc) >= qc * 1e-8): + qc = 2 * xp.sqrt(kk) kk = qc * em f = cc cc = cc + ss / p diff --git a/src/magpylib/_src/fields/special_elliptic.py b/src/magpylib/_src/fields/special_elliptic.py new file mode 100644 index 000000000..99d2a7c6b --- /dev/null +++ b/src/magpylib/_src/fields/special_elliptic.py @@ -0,0 +1,18 @@ +# from scipy.special import ellipe, ellipk +from __future__ import annotations + +from typing import Any + +import array_api_extra as xpx +import scipy as sp + +type Array = Any + + +def ellipe(m: Array): + # return cel(kc, one, one, kc2) + return xpx.lazy_apply(sp.special.ellipe, m, as_numpy=True) + + +def ellipk(m: Array): + return xpx.lazy_apply(sp.special.ellipk, m, as_numpy=True) From 4399e17949f252ca0d472eb65da8c36a101a8e74 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:59:27 -0500 Subject: [PATCH 12/24] Adds array api support to incomplete elliptic functions --- src/magpylib/_src/fields/special_el3.py | 269 ++++++++++--------- src/magpylib/_src/fields/special_elliptic.py | 16 ++ 2 files changed, 156 insertions(+), 129 deletions(-) create mode 100644 src/magpylib/_src/fields/special_elliptic.py diff --git a/src/magpylib/_src/fields/special_el3.py b/src/magpylib/_src/fields/special_el3.py index e2578db9c..8d1d35298 100644 --- a/src/magpylib/_src/fields/special_el3.py +++ b/src/magpylib/_src/fields/special_el3.py @@ -1,6 +1,7 @@ from __future__ import annotations import numpy as np +from array_api_compat import array_namespace from magpylib._src.fields.special_cel import cel @@ -64,7 +65,7 @@ def el30(x, kc, p): if bo: s = -s u = (u + 1) * 0.5 - return (u - s * h) * np.sqrt(h) * x + u * np.arcsinh(x) + return (u - s * h) * np.sqrt(h) * x + u * np.asinh(x) w = 1 + f if w == 0: @@ -217,7 +218,7 @@ def el30(x, kc, p): break if y < 0.0: l += 1 - e = np.arctan(t / y) + np.pi * l + e = np.atan(t / y) + np.pi * l e = e * (c * t + d) / (t * (t + q)) if bo: h = v / (t + u) @@ -227,9 +228,9 @@ def el30(x, kc, p): z = CB if z < 0.0: m = m + np.sign(h) - s = np.arctan(h / z) + m * np.pi + s = np.atan(h / z) + m * np.pi else: - s = np.arcsinh(ye) if bk else np.log(z) + m * ln2 + s = np.asinh(ye) if bk else np.log(z) + m * ln2 s = s * 0.5 e = (e + np.sqrt(fa) * s) / n return e if (x > 0.0) else -e @@ -249,8 +250,9 @@ def el3v(x, kc, p): # pylint: disable=too-many-branches # pylint: disable=too-many-statements - nnn0 = len(x) - result0 = np.zeros(nnn0) + xp = array_namespace(x, kc, p) + nnn0 = x.shape[0] + result0 = xp.zeros(nnn0) # return 0 when mask0 mask0 = x != 0 @@ -258,30 +260,31 @@ def el3v(x, kc, p): kc = kc[mask0] p = p[mask0] - nnn = len(x) - result = np.zeros(nnn) + nnn = x.shape[0] + result = xp.zeros(nnn) D = 8 CA = 10.0 ** (-D / 2) CB = 10.0 ** (-D - 2) ND = D - 2 - ln2 = np.log(2) + ln2 = xp.log(xp.asarray(2.0)) hh = x * x f = p * hh - s = np.zeros(nnn) + s = xp.zeros(nnn) mask1 = kc == 0 - s[mask1] = CA / (1 + np.abs(x[mask1])) + s[mask1] = CA / (1 + xp.abs(x[mask1])) s[~mask1] = kc[~mask1] t = s * s pm = 0.5 * t e = hh * t - z = np.abs(f) - r = np.abs(p) + z = xp.abs(f) + r = xp.abs(p) h = 1.0 + hh - mask2 = (e < 0.1) * (z < 0.1) * (t < 1) * (r < 1) - if any(mask2): - ra, rb, rr = np.zeros((3, ND - 1, np.sum(mask2))) + mask2 = (e < 0.1) & (z < 0.1) & (t < 1) & (r < 1) + if xp.any(mask2): + R = xp.zeros((3, ND - 1, xp.count_nonzero(mask2))) + ra, rb, rr = (R[i] for i in range(3)) px, pmx, tx, hhx, hx, xx = ( p[mask2], pm[mask2], @@ -302,7 +305,7 @@ def el3v(x, kc, p): pmx = pmx * tx * ra[km2] sx = sx * px + pmx ux = sx * zd - sx = np.copy(ux) + sx = xp.asarray(ux, copy=True) bo = False for k in range(ND, 1, -1): km2 = int(k - 2) @@ -313,7 +316,7 @@ def el3v(x, kc, p): if bo: sx = -sx ux = (ux + 1) * 0.5 - result[mask2] = (ux - sx * hx) * np.sqrt(hx) * xx + ux * np.arcsinh(xx) + result[mask2] = (ux - sx * hx) * xp.sqrt(hx) * xx + ux * xp.asinh(xx) mask2x = ~mask2 p, pm, t, hh, h, x = ( @@ -325,19 +328,20 @@ def el3v(x, kc, p): x[mask2x], ) f, e, z, s = f[mask2x], e[mask2x], z[mask2x], s[mask2x] - ye, k = np.zeros((2, len(p))) - bk = np.zeros(len(p), dtype=bool) + ye_k = xp.zeros((2, (p.shape[0]))) + ye, k = (ye_k[i, ...] for i in range(2)) + bk = xp.zeros((p.shape[0]), dtype=xp.bool) w = 1 + f - if np.any(w == 0): + if xp.any(w == 0): msg = "FAIL" raise RuntimeError(msg) - p1 = np.copy(p) + p1 = xp.asarray(p, copy=True) mask3 = p == 0 p1[mask3] = CB / hh[mask3] - s = np.abs(s) - y = np.abs(x) + s = xp.abs(s) + y = xp.abs(x) g = p1 - 1.0 g[g == 0] = CB f = p1 - t @@ -348,26 +352,27 @@ def el3v(x, kc, p): r = p1 * h fa = g / (f * p1) bo = fa > 0.0 - fa = np.abs(fa) - pz = np.abs(g * f) - de = np.sqrt(pz) - q = np.sqrt(np.abs(p1)) + fa = xp.abs(fa) + pz = xp.abs(g * f) + de = xp.sqrt(pz) + q = xp.sqrt(xp.abs(p1)) mask5 = pm > 0.5 pm[mask5] = 0.5 pm = p1 - pm - u, v, d, c = np.zeros((4, len(pm))) + uvdc = xp.zeros((4, (pm.shape[0]))) + u, v, d, c = (uvdc[i, ...] for i in range(4)) mask6 = pm >= 0.0 - if np.any(mask6): - u[mask6] = np.sqrt(r[mask6] * ap[mask6]) - v[mask6] = y[mask6] * de[mask6] * np.sign(g[mask6]) + if xp.any(mask6): + u[mask6] = xp.sqrt(r[mask6] * ap[mask6]) + v[mask6] = y[mask6] * de[mask6] * xp.sign(g[mask6]) d[mask6] = 1 / q[mask6] c[mask6] = 1.0 mask6x = ~mask6 - if np.any(mask6x): - u[mask6x] = np.sqrt(h[mask6x] * ap[mask6x] * pz[mask6x]) + if xp.any(mask6x): + u[mask6x] = xp.sqrt(h[mask6x] * ap[mask6x] * pz[mask6x]) ye[mask6x] = y[mask6x] * q[mask6x] v[mask6x] = am[mask6x] * ye[mask6x] q[mask6x] = -de[mask6x] / g[mask6x] @@ -375,74 +380,78 @@ def el3v(x, kc, p): c[mask6x] = 0 pz[mask6x] = ap[mask6x] - r[mask6x] - if np.any(bo): + if xp.any(bo): r[bo] = v[bo] / u[bo] z[bo] = 1 k[bo] = 1 - mask7 = bo * (pm < 0) - if np.any(mask7): - h[mask7] = y[mask7] * np.sqrt(h[mask7] / (ap[mask7] * fa[mask7])) + mask7 = bo & (pm < 0) + if xp.any(mask7): + h[mask7] = y[mask7] * xp.sqrt(h[mask7] / (ap[mask7] * fa[mask7])) h[mask7] = 1 / h[mask7] - h[mask7] z[mask7] = h[mask7] - 2 * r[mask7] r[mask7] = 2 + r[mask7] * h[mask7] - mask7a = mask7 * (r == 0) + mask7a = mask7 & (r == 0) r[mask7a] = CB - mask7b = mask7 * (z == 0) + mask7b = mask7 & (z == 0) z[mask7b] = h[mask7b] * CB z[mask7] = r[mask7] / z[mask7] - r[mask7] = np.copy(z[mask7]) + r[mask7] = xp.asarray(z[mask7], copy=True) w[mask7] = pz[mask7] u[bo] = u[bo] / w[bo] v[bo] = v[bo] / w[bo] box = ~bo - if np.any(box): - t[box] = u[box] + np.abs(v[box]) + if xp.any(box): + t[box] = u[box] + xp.abs(v[box]) bk[box] = True - mask8 = box * (p1 < 0) - if np.any(mask8): + mask8 = box & (p1 < 0) + if xp.any(mask8): de[mask8] = v[mask8] / pz[mask8] ye[mask8] = u[mask8] * ye[mask8] ye[mask8] = 2 * ye[mask8] u[mask8] = t[mask8] / pz[mask8] v[mask8] = (-f[mask8] - g[mask8] * e[mask8]) / t[mask8] - t[mask8] = pz[mask8] * np.abs(w[mask8]) + t[mask8] = pz[mask8] * xp.abs(w[mask8]) z[mask8] = ( hh[mask8] * r[mask8] * f[mask8] - g[mask8] * ap[mask8] + ye[mask8] ) / t[mask8] ye[mask8] = ye[mask8] / t[mask8] - mask8x = box * (p1 >= 0) - if np.any(mask8x): + mask8x = box & (p1 >= 0) + if xp.any(mask8x): de[mask8x] = v[mask8x] / w[mask8x] ye[mask8x] = 0 u[mask8x] = (e[mask8x] + p1[mask8x]) / t[mask8x] v[mask8x] = t[mask8x] / w[mask8x] z[mask8x] = 1.0 - mask9 = box * (s > 1) - if np.any(mask9): + mask9 = box & (s > 1) + if xp.any(mask9): h[mask9] = u[mask9] u[mask9] = v[mask9] v[mask9] = h[mask9] y = 1 / y - e = np.copy(s) - n, t = np.ones((2, len(p))) - m, l = np.zeros((2, len(p))) - - mask10 = np.ones(len(p), dtype=bool) # dynamic mask, changed in each loop iteration - while np.any(mask10): + e = xp.asarray(s, copy=True) + nt = xp.ones((2, (p.shape[0]))) + n, t = (nt[i, ...] for i in range(2)) + ml = xp.zeros((2, (p.shape[0]))) + m, l = (ml[i, ...] for i in range(2)) + + mask10 = xp.ones( + (p.shape[0]), dtype=xp.bool + ) # dynamic mask, changed in each loop iteration + while xp.any(mask10): y[mask10] = y[mask10] - e[mask10] / y[mask10] - mask11 = mask10 * (y == 0.0) - y[mask11] = np.sqrt(e[mask11]) * CB + mask11 = mask10 & (y == 0.0) + y[mask11] = xp.sqrt(e[mask11]) * CB f[mask10] = c[mask10] c[mask10] = d[mask10] / q[mask10] + c[mask10] @@ -454,67 +463,67 @@ def el3v(x, kc, p): t[mask10] = s[mask10] + t[mask10] n[mask10] = 2 * n[mask10] m[mask10] = 2 * m[mask10] - bo10 = mask10 * bo - if np.any(bo10): - bo10b = bo10 * (z < 0) + bo10 = mask10 & bo + if xp.any(bo10): + bo10b = bo10 & (z < 0) m[bo10b] = k[bo10b] + m[bo10b] - k[bo10] = np.sign(r[bo10]) + k[bo10] = xp.sign(r[bo10]) h[bo10] = e[bo10] / (u[bo10] * u[bo10] + v[bo10] * v[bo10]) u[bo10] = u[bo10] * (1 + h[bo10]) v[bo10] = v[bo10] * (1 - h[bo10]) - bo10x = np.array(mask10 * ~bo10, dtype=bool) - if np.any(bo10x): + bo10x = xp.asarray(mask10 & ~bo10, dtype=xp.bool) + if xp.any(bo10x): r[bo10x] = u[bo10x] / v[bo10x] h[bo10x] = z[bo10x] * r[bo10x] z[bo10x] = h[bo10x] * z[bo10x] hh[bo10x] = e[bo10x] / v[bo10x] - bo10x_bk = np.array(bo10x * bk, dtype=bool) # if bk - bo10x_bkx = np.array(bo10x * ~bk, dtype=bool) - if np.any(bo10x_bk): + bo10x_bk = xp.asarray(bo10x * bk, dtype=bool) # if bk + bo10x_bkx = xp.asarray(bo10x * ~bk, dtype=bool) + if xp.any(bo10x_bk): de[bo10x_bk] = de[bo10x_bk] / u[bo10x_bk] ye[bo10x_bk] = ye[bo10x_bk] * (h[bo10x_bk] + 1 / h[bo10x_bk]) + de[ bo10x_bk ] * (1 + r[bo10x_bk]) de[bo10x_bk] = de[bo10x_bk] * (u[bo10x_bk] - hh[bo10x_bk]) - bk[bo10x_bk] = np.abs(ye[bo10x_bk]) < 1 - if np.any(bo10x_bkx): - a_crack = np.log(x[bo10x_bkx]) + bk[bo10x_bk] = xp.abs(ye[bo10x_bk]) < 1 + if xp.any(bo10x_bkx): + a_crack = xp.log(x[bo10x_bkx]) k[bo10x_bkx] = (a_crack / ln2).astype(int) + 1 a_crack = a_crack - k[bo10x_bkx] * ln2 - m[bo10x_bkx] = np.exp(a_crack) + m[bo10x_bkx] = xp.exp(a_crack) m[bo10x_bkx] = m[bo10x_bkx] + k[bo10x_bkx] - mask11 = np.abs(g - s) > CA * g - if np.any(mask11): - bo11 = mask11 * bo - if np.any(bo11): + mask11 = xp.abs(g - s) > CA * g + if xp.any(mask11): + bo11 = mask11 & bo + if xp.any(bo11): g[bo11] = (1 / r[bo11] - r[bo11]) * 0.5 hh[bo11] = u[bo11] + v[bo11] * g[bo11] h[bo11] = g[bo11] * u[bo11] - v[bo11] - bo11b = bo11 * (hh == 0) + bo11b = bo11 & (hh == 0) hh[bo11b] = u[bo11b] * CB - bo11c = bo11 * (h == 0) + bo11c = bo11 & (h == 0) h[bo11c] = v[bo11c] * CB z[bo11] = r[bo11] * h[bo11] r[bo11] = hh[bo11] / h[bo11] - bo11x = mask11 * ~bo - if np.any(bo11x): + bo11x = mask11 & ~bo + if xp.any(bo11x): u[bo11x] = u[bo11x] + e[bo11x] / u[bo11x] v[bo11x] = v[bo11x] + hh[bo11x] - s[mask11] = np.sqrt(e[mask11]) + s[mask11] = xp.sqrt(e[mask11]) s[mask11] = 2 * s[mask11] e[mask11] = s[mask11] * t[mask11] l[mask11] = 2 * l[mask11] - mask12 = mask11 * (y < 0) + mask12 = mask11 & (y < 0) l[mask12] = l[mask12] + 1 # break off parts that have completed their iteration @@ -523,33 +532,33 @@ def el3v(x, kc, p): mask12 = y < 0 l[mask12] = l[mask12] + 1 - e = np.arctan(t / y) + np.pi * l + e = xp.atan(t / y) + xp.pi * l e = e * (c * t + d) / (t * (t + q)) - if np.any(bo): + if xp.any(bo): h[bo] = v[bo] / (t[bo] + u[bo]) z[bo] = 1 - r[bo] * h[bo] h[bo] = r[bo] + h[bo] - bob = bo * (z == 0) + bob = bo & (z == 0) z[bob] = CB - boc = bo * (z < 0) - m[boc] = m[boc] + np.sign(h[boc]) + boc = bo & (z < 0) + m[boc] = m[boc] + xp.sign(h[boc]) - s[bo] = np.arctan(h[bo] / z[bo]) + m[bo] * np.pi + s[bo] = xp.atan(h[bo] / z[bo]) + m[bo] * xp.pi box = ~bo - if np.any(box): - box_bk = box * bk - s[box_bk] = np.arcsinh(ye[box_bk]) + if xp.any(box): + box_bk = box & bk + s[box_bk] = xp.asinh(ye[box_bk]) - box_bkx = box * ~bk - s[box_bkx] = np.log(z[box_bkx]) + m[box_bkx] * ln2 + box_bkx = box & ~bk + s[box_bkx] = xp.log(z[box_bkx]) + m[box_bkx] * ln2 s[box] = s[box] * 0.5 - e = (e + np.sqrt(fa) * s) / n - result[~mask2] = np.sign(x) * e + e = (e + xp.sqrt(fa) * s) / n + result[~mask2] = xp.sign(x) * e # include mask0-case result0[mask0] = result @@ -561,10 +570,11 @@ def el3(xv: np.ndarray, kcv: np.ndarray, pv: np.ndarray) -> np.ndarray: """ combine vectorized and non-vectorized implementations for improved performance """ - n_input = len(xv) + xp = array_namespace(xv, kcv, pv) + n_input = xv.shape[0] - if n_input < 10: - return np.array([el30(x, kc, p) for x, kc, p in zip(xv, kcv, pv, strict=False)]) + # if n_input < 10: + # return xp.asarray([el30(x, kc, p) for x, kc, p in zip(xv, kcv, pv, strict=False)]) return el3v(xv, kcv, pv) @@ -584,72 +594,73 @@ def el3_angle(phi: np.ndarray, n: np.ndarray, m: np.ndarray) -> np.ndarray: -> possibly provide a non-vectorized form of this ? (maybe not worth the effort) """ # pylint: disable=too-many-statements - n_vec = len(phi) - results = np.zeros(n_vec) + xp = array_namespace(phi, n, m) + n_vec = phi.shape[0] + results = xp.zeros(n_vec) - kc = np.sqrt(1 - m) + kc = xp.sqrt(1 - m) p = 1 - n D = 8 - n = (phi / np.pi).astype(int) - phi_red = phi - n * np.pi + n = xp.round(phi / xp.pi) + phi_red = phi - n * xp.pi - mask1 = (n <= 0) * (phi_red < -np.pi / 2) - mask2 = (n >= 0) * (phi_red > np.pi / 2) - if np.any(mask1): + mask1 = (n <= 0) & (phi_red < -xp.pi / 2) + mask2 = (n >= 0) & (phi_red > xp.pi / 2) + if xp.any(mask1): n[mask1] = n[mask1] - 1 - phi_red[mask1] = phi_red[mask1] + np.pi + phi_red[mask1] = phi_red[mask1] + xp.pi - if np.any(mask2): + if xp.any(mask2): n[mask2] = n[mask2] + 1 - phi_red[mask2] = phi_red[mask2] - np.pi + phi_red[mask2] = phi_red[mask2] - xp.pi mask3 = n != 0 mask3x = ~mask3 - if np.any(mask3): + if xp.any(mask3): n3, phi3, p3, kc3 = n[mask3], phi[mask3], p[mask3], kc[mask3] phi_red3 = phi_red[mask3] - results3 = np.zeros(np.sum(mask3)) - onez = np.ones(np.sum(mask3)) + results3 = xp.zeros(xp.count_nonzero(mask3)) + onez = xp.ones(xp.count_nonzero(mask3)) cel3_res = cel(kc3, p3, onez, onez) # 3rd kind cel - mask3a = phi_red3 > np.pi / 2 - 10 ** (-D) - mask3b = phi_red3 < -np.pi / 2 + 10 ** (-D) - mask3c = ~mask3a * ~mask3b + mask3a = phi_red3 > xp.pi / 2 - 10 ** (-D) + mask3b = phi_red3 < -xp.pi / 2 + 10 ** (-D) + mask3c = ~mask3a & ~mask3b - if np.any(mask3a): + if xp.any(mask3a): results3[mask3a] = (2 * n3[mask3a] + 1) * cel3_res[mask3a] - if np.any(mask3b): + if xp.any(mask3b): results3[mask3b] = (2 * n3[mask3b] - 1) * cel3_res[mask3b] - if np.any(mask3c): - el33_res = el3(np.tan(phi3[mask3c]), kc3[mask3c], p3[mask3c]) + if xp.any(mask3c): + el33_res = el3(xp.tan(phi3[mask3c]), kc3[mask3c], p3[mask3c]) results3[mask3c] = 2 * n3[mask3c] * cel3_res[mask3c] + el33_res results[mask3] = results3 - if np.any(mask3x): + if xp.any(mask3x): phi_red3x = phi_red[mask3x] - results3x = np.zeros(np.sum(mask3x)) + results3x = xp.zeros(xp.count_nonzero(mask3x)) phi3x, kc3x, p3x = phi[mask3x], kc[mask3x], p[mask3x] - mask3xa = phi_red3x > np.pi / 2 - 10 ** (-D) - mask3xb = phi_red3x < -np.pi / 2 + 10 ** (-D) - mask3xc = ~mask3xa * ~mask3xb + mask3xa = phi_red3x > xp.pi / 2 - 10 ** (-D) + mask3xb = phi_red3x < -xp.pi / 2 + 10 ** (-D) + mask3xc = ~mask3xa & ~mask3xb - if np.any(mask3xa): - onez = np.ones(np.sum(mask3xa)) + if xp.any(mask3xa): + onez = xp.ones(xp.count_nonzero(mask3xa)) results3x[mask3xa] = cel( kc3x[mask3xa], p3x[mask3xa], onez, onez ) # 3rd kind cel - if np.any(mask3xb): - onez = np.ones(np.sum(mask3xb)) + if xp.any(mask3xb): + onez = xp.ones(xp.count_nonzero(mask3xb)) results3x[mask3xb] = -cel( kc3x[mask3xb], p3x[mask3xb], onez, onez ) # 3rd kind cel - if np.any(mask3xc): + if xp.any(mask3xc): results3x[mask3xc] = el3( - np.tan(phi3x[mask3xc]), kc3x[mask3xc], p3x[mask3xc] + xp.tan(phi3x[mask3xc]), kc3x[mask3xc], p3x[mask3xc] ) results[mask3x] = results3x diff --git a/src/magpylib/_src/fields/special_elliptic.py b/src/magpylib/_src/fields/special_elliptic.py new file mode 100644 index 000000000..b4bb9668d --- /dev/null +++ b/src/magpylib/_src/fields/special_elliptic.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Any + +import array_api_extra as xpx +import scipy as sp + +type Array = Any + + +def ellipeinc(phi: Array, m: Array): + return xpx.lazy_apply(sp.special.ellipeinc, phi, m, as_numpy=True) + + +def ellipkinc(phi: Array, m: Array): + return xpx.lazy_apply(sp.special.ellipkinc, phi, m, as_numpy=True) From c81a8f3102b2d8f9b7e0ace473dd2195b7328014 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:59:27 -0500 Subject: [PATCH 13/24] Adds array api support to cylinder segment core function --- .../_src/fields/field_BH_cylinder_segment.py | 1801 +++++++++-------- 1 file changed, 911 insertions(+), 890 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cylinder_segment.py b/src/magpylib/_src/fields/field_BH_cylinder_segment.py index 2d0e775b7..bfb03724e 100644 --- a/src/magpylib/_src/fields/field_BH_cylinder_segment.py +++ b/src/magpylib/_src/fields/field_BH_cylinder_segment.py @@ -6,13 +6,16 @@ # pylint: disable=too-many-positional-arguments from __future__ import annotations +import array_api_extra as xpx import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 -from scipy.special import ellipeinc, ellipkinc from magpylib._src.fields.field_BH_cylinder import BHJM_magnet_cylinder from magpylib._src.fields.special_el3 import el3_angle +from magpylib._src.fields.special_elliptic import ellipeinc, ellipkinc from magpylib._src.input_checks import check_field_input +from magpylib._src.array_api_utils import xp_promote def arctan_k_tan_2(k, phi): @@ -23,16 +26,18 @@ def arctan_k_tan_2(k, phi): can be replaced by non-masked version ? """ + xp = array_namespace(k, phi) - full_periods = np.round(phi / (2.0 * np.pi)) - phi_red = phi - full_periods * 2.0 * np.pi + full_periods = xp.round(phi / (2.0 * xp.pi)) + phi_red = phi - full_periods * 2.0 * xp.pi - result = full_periods * np.pi + result = full_periods * xp.pi - return np.where( - np.abs(phi_red) < np.pi, - result + np.arctan(k * np.tan(phi_red / 2.0)), - result + phi_red / 2.0, + return xpx.apply_where( + xp.abs(phi_red) < xp.pi, + (result, k, phi_red), + lambda result, k, phi_red: result + xp.atan(k * xp.tan(phi_red / 2.0)), + lambda result, k, phi_red: result + phi_red / 2.0, ) @@ -42,7 +47,7 @@ def close(arg1: np.ndarray, arg2: np.ndarray) -> np.ndarray: input: ndarray, shape (n,) or numpy-interpretable scalar output: ndarray, dtype=bool """ - return np.isclose(arg1, arg2, rtol=1e-12, atol=1e-12) + return xpx.isclose(arg1, arg2, rtol=1e-12, atol=1e-12) def determine_cases(r, phi, z, r1, phi1, z1): @@ -60,37 +65,38 @@ def determine_cases(r, phi, z, r1, phi1, z1): 2nd digit: 1:phi-phi1= 2n*pi, 2:phi-phi1=(2n+1)*pi, 3:general 3rd digit: 1:r=r1=0, 2:r=0, 3:r1=0, 4:r=r1>0, 5:general """ - n = len(r) # input length + xp = array_namespace(r, phi, z, r1, phi1, z1) + n = r.shape[0] # input length # allocate result - result = np.ones((3, n)) + result = xp.ones((3, n)) # identify z-case mask_z = close(z, z1) - result[0] = 200 - result[0, mask_z] = 100 + result[0, ...] = 200 + result[0, :][mask_z] = 100 # identify phi-case - mod_2pi = np.abs(phi - phi1) % (2 * np.pi) - mask_phi1 = np.logical_or(close(mod_2pi, 0), close(mod_2pi, 2 * np.pi)) - mod_pi = np.abs(phi - phi1) % np.pi - mask_phi2 = np.logical_or(close(mod_pi, 0), close(mod_pi, np.pi)) - result[1] = 30 - result[1, mask_phi2] = 20 - result[1, mask_phi1] = 10 + mod_2pi = xp.abs(phi - phi1) % (2 * xp.pi) + mask_phi1 = xp.logical_or(close(mod_2pi, 0), close(mod_2pi, 2 * xp.pi)) + mod_pi = xp.abs(phi - phi1) % xp.pi + mask_phi2 = xp.logical_or(close(mod_pi, 0), close(mod_pi, xp.pi)) + result[1, ...] = 30 + result[1, ...][mask_phi2] = 20 + result[1, ...][mask_phi1] = 10 # identify r-case mask_r2 = close(r, 0) mask_r3 = close(r1, 0) mask_r4 = close(r, r1) - mask_r1 = mask_r2 * mask_r3 - result[2] = 5 - result[2, mask_r4] = 4 - result[2, mask_r3] = 3 - result[2, mask_r2] = 2 - result[2, mask_r1] = 1 + mask_r1 = mask_r2 & mask_r3 + result[2, ...] = 5 + result[2, ...][mask_r4] = 4 + result[2, ...][mask_r3] = 3 + result[2, ...][mask_r2] = 2 + result[2, ...][mask_r1] = 1 - return np.array(np.sum(result, axis=0), dtype=int) + return xp.asarray(xp.sum(result, axis=0), dtype=xp.int32) # Implementation of all non-zero field components in every special case @@ -100,384 +106,382 @@ def determine_cases(r, phi, z, r1, phi1, z1): # 112 ############## -def Hphi_zk_case112(r_i, theta_M): - return np.cos(theta_M) * np.log(r_i) +def Hphi_zk_case112(xp, r_i, theta_M): + return xp.cos(theta_M) * xp.log(r_i) -def Hz_ri_case112(phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case112(xp, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) -def Hz_phij_case112(r_i, phi_bar_M, theta_M): - return np.sin(theta_M) * np.sin(phi_bar_M) * np.log(r_i) +def Hz_phij_case112(xp, r_i, phi_bar_M, theta_M): + return xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(r_i) # 113 ############## -def Hphi_zk_case113(r, theta_M): - return -np.cos(theta_M) * np.log(r) +def Hphi_zk_case113(xp, r, theta_M): + return -xp.cos(theta_M) * xp.log(r) -def Hz_phij_case113(r, phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) * np.log(r) +def Hz_phij_case113(xp, r, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(r) # 115 ############## -def Hr_zk_case115(r, r_i, r_bar_i, phi_bar_j, theta_M): +def Hr_zk_case115(xp, r, r_i, r_bar_i, phi_bar_j, theta_M): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - E_coef = np.cos(theta_M) * np.abs(r_bar_i) / r + E_coef = xp.cos(theta_M) * xp.abs(r_bar_i) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - F_coef = -np.cos(theta_M) * (r**2 + r_i**2) / (r * np.abs(r_bar_i)) + F_coef = -xp.cos(theta_M) * (r**2 + r_i**2) / (r * xp.abs(r_bar_i)) return E_coef * E + F_coef * F -def Hphi_zk_case115(r, r_i, r_bar_i, theta_M): +def Hphi_zk_case115(xp, r, r_i, r_bar_i, theta_M): t1 = r_i / r - t1_coef = -np.cos(theta_M) * np.sign(r_bar_i) - t2 = np.log(np.abs(r_bar_i)) * np.sign(r_bar_i) - t2_coef = -np.cos(theta_M) + t1_coef = -xp.cos(theta_M) * xp.sign(r_bar_i) + t2 = xp.log(xp.abs(r_bar_i)) * xp.sign(r_bar_i) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case115(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): - t1 = np.abs(r_bar_i) / r - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case115(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): + t1 = xp.abs(r_bar_i) / r + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.abs(r_bar_i) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.abs(r_bar_i) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) F_coef = ( - -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (r * np.abs(r_bar_i)) + -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (r * xp.abs(r_bar_i)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case115(r_bar_i, phi_bar_M, theta_M): - t1 = np.log(np.abs(r_bar_i)) * np.sign(r_bar_i) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case115(xp, r_bar_i, phi_bar_M, theta_M): + t1 = xp.log(xp.abs(r_bar_i)) * xp.sign(r_bar_i) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 # 122 ############## -def Hphi_zk_case122(r_i, theta_M): - return -np.cos(theta_M) * np.log(r_i) +def Hphi_zk_case122(xp, r_i, theta_M): + return -xp.cos(theta_M) * xp.log(r_i) -def Hz_ri_case122(phi_bar_M, theta_M): - return np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case122(xp, phi_bar_M, theta_M): + return xp.sin(theta_M) * xp.sin(phi_bar_M) -def Hz_phij_case122(r_i, phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) * np.log(r_i) +def Hz_phij_case122(xp, r_i, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(r_i) # 123 ############## -def Hphi_zk_case123(r, theta_M): - return -np.cos(theta_M) * np.log(r) +def Hphi_zk_case123(xp, r, theta_M): + return -xp.cos(theta_M) * xp.log(r) -def Hz_phij_case123(r, phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) * np.log(r) +def Hz_phij_case123(xp, r, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(r) # 124 ############## -def Hphi_zk_case124(r, theta_M): - return np.cos(theta_M) * (1.0 - np.log(2.0 * r)) +def Hphi_zk_case124(xp, r, theta_M): + return xp.cos(theta_M) * (1.0 - xp.log(2.0 * r)) -def Hz_ri_case124(phi_bar_M, theta_M): - return 2.0 * np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case124(xp, phi_bar_M, theta_M): + return 2.0 * xp.sin(theta_M) * xp.sin(phi_bar_M) -def Hz_phij_case124(r, phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) * np.log(2.0 * r) +def Hz_phij_case124(xp, r, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(2.0 * r) # 125 ############## -def Hr_zk_case125(r, r_i, r_bar_i, phi_bar_j, theta_M): +def Hr_zk_case125(xp, r, r_i, r_bar_i, phi_bar_j, theta_M): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - E_coef = np.cos(theta_M) * np.abs(r_bar_i) / r + E_coef = xp.cos(theta_M) * xp.abs(r_bar_i) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - F_coef = -np.cos(theta_M) * (r**2 + r_i**2) / (r * np.abs(r_bar_i)) + F_coef = -xp.cos(theta_M) * (r**2 + r_i**2) / (r * xp.abs(r_bar_i)) return E_coef * E + F_coef * F -def Hphi_zk_case125(r, r_i, theta_M): - return np.cos(theta_M) / r * (r_i - r * np.log(r + r_i)) +def Hphi_zk_case125(xp, r, r_i, theta_M): + return xp.cos(theta_M) / r * (r_i - r * xp.log(r + r_i)) -def Hz_ri_case125(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): +def Hz_ri_case125(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.abs(r_bar_i) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.abs(r_bar_i) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) F_coef = ( - -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (r * np.abs(r_bar_i)) + -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (r * xp.abs(r_bar_i)) ) - return np.sin(theta_M) * np.sin(phi_bar_M) * (r + r_i) / r + E_coef * E + F_coef * F + return xp.sin(theta_M) * xp.sin(phi_bar_M) * (r + r_i) / r + E_coef * E + F_coef * F -def Hz_phij_case125(r, r_i, phi_bar_M, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_M) * np.log(r + r_i) +def Hz_phij_case125(xp, r, r_i, phi_bar_M, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.log(r + r_i) # 132 ############## -def Hr_zk_case132(r_i, phi_bar_j, theta_M): - return np.cos(theta_M) * np.sin(phi_bar_j) * np.log(r_i) +def Hr_zk_case132(xp, r_i, phi_bar_j, theta_M): + return xp.cos(theta_M) * xp.sin(phi_bar_j) * xp.log(r_i) -def Hphi_zk_case132(r_i, phi_bar_j, theta_M): - return np.cos(theta_M) * np.cos(phi_bar_j) * np.log(r_i) +def Hphi_zk_case132(xp, r_i, phi_bar_j, theta_M): + return xp.cos(theta_M) * xp.cos(phi_bar_j) * xp.log(r_i) -def Hz_ri_case132(phi_bar_Mj, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_Mj) +def Hz_ri_case132(xp, phi_bar_Mj, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_Mj) -def Hz_phij_case132(r_i, phi_bar_Mj, theta_M): - return np.sin(theta_M) * np.sin(phi_bar_Mj) * np.log(r_i) +def Hz_phij_case132(xp, r_i, phi_bar_Mj, theta_M): + return xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.log(r_i) # 133 ############## -def Hr_zk_case133(r, phi_bar_j, theta_M): - return -np.cos(theta_M) * np.sin(phi_bar_j) + np.cos(theta_M) * np.sin( +def Hr_zk_case133(xp, r, phi_bar_j, theta_M): + return -xp.cos(theta_M) * xp.sin(phi_bar_j) + xp.cos(theta_M) * xp.sin( phi_bar_j - ) * np.log(r * (1.0 - np.cos(phi_bar_j))) + ) * xp.log(r * (1.0 - xp.cos(phi_bar_j))) -def Hphi_zk_case133(phi_bar_j, theta_M): - return np.cos(theta_M) - np.cos(theta_M) * np.cos(phi_bar_j) * np.arctanh( - np.cos(phi_bar_j) +def Hphi_zk_case133(xp, phi_bar_j, theta_M): + return xp.cos(theta_M) - xp.cos(theta_M) * xp.cos(phi_bar_j) * xp.atanh( + xp.cos(phi_bar_j) ) -def Hz_phij_case133(phi_bar_j, phi_bar_Mj, theta_M): - return -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.arctanh(np.cos(phi_bar_j)) +def Hz_phij_case133(xp, phi_bar_j, phi_bar_Mj, theta_M): + return -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.atanh(xp.cos(phi_bar_j)) # 134 ############## -def Hr_zk_case134(r, phi_bar_j, theta_M): - t1 = np.sin(phi_bar_j) - t1_coef = -np.cos(theta_M) - t2 = np.sin(phi_bar_j) / np.sqrt(1.0 - np.cos(phi_bar_j)) - t2_coef = -np.sqrt(2.0) * np.cos(theta_M) - t3 = np.log( - r * (1.0 - np.cos(phi_bar_j) + np.sqrt(2.0) * np.sqrt(1.0 - np.cos(phi_bar_j))) +def Hr_zk_case134(xp, r, phi_bar_j, theta_M): + t1 = xp.sin(phi_bar_j) + t1_coef = -xp.cos(theta_M) + t2 = xp.sin(phi_bar_j) / xp.sqrt(1.0 - xp.cos(phi_bar_j)) + t2_coef = -xp.sqrt(2.0) * xp.cos(theta_M) + t3 = xp.log( + r * (1.0 - xp.cos(phi_bar_j) + xp.sqrt(2.0) * xp.sqrt(1.0 - xp.cos(phi_bar_j))) ) - t3_coef = np.cos(theta_M) * np.sin(phi_bar_j) - t4 = np.arctanh( - np.sin(phi_bar_j) / (np.sqrt(2.0) * np.sqrt(1.0 - np.cos(phi_bar_j))) - ) - t4_coef = np.cos(theta_M) + t3_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) + t4 = xp.atanh(xp.sin(phi_bar_j) / (xp.sqrt(2.0) * xp.sqrt(1.0 - xp.cos(phi_bar_j)))) + t4_coef = xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 + t3_coef * t3 + t4_coef * t4 -def Hphi_zk_case134(phi_bar_j, theta_M): - return np.sqrt(2) * np.cos(theta_M) * np.sqrt(1 - np.cos(phi_bar_j)) + np.cos( +def Hphi_zk_case134(xp, phi_bar_j, theta_M): + return xp.sqrt(2) * xp.cos(theta_M) * xp.sqrt(1 - xp.cos(phi_bar_j)) + xp.cos( theta_M - ) * np.cos(phi_bar_j) * np.arctanh(np.sqrt((1 - np.cos(phi_bar_j)) / 2)) + ) * xp.cos(phi_bar_j) * xp.atanh(xp.sqrt((1 - xp.cos(phi_bar_j)) / 2)) -def Hz_ri_case134(phi_bar_j, phi_bar_M, theta_M): - t1 = np.sqrt(1.0 - np.cos(phi_bar_j)) - t1_coef = np.sqrt(2.0) * np.sin(theta_M) * np.sin(phi_bar_M) - t2 = np.sin(phi_bar_j) / t1 - t2_coef = -np.sqrt(2.0) * np.sin(theta_M) * np.cos(phi_bar_M) - t3 = np.arctanh(t2 / np.sqrt(2.0)) - t3_coef = np.sin(theta_M) * np.cos(phi_bar_M) +def Hz_ri_case134(xp, phi_bar_j, phi_bar_M, theta_M): + t1 = xp.sqrt(1.0 - xp.cos(phi_bar_j)) + t1_coef = xp.sqrt(2.0) * xp.sin(theta_M) * xp.sin(phi_bar_M) + t2 = xp.sin(phi_bar_j) / t1 + t2_coef = -xp.sqrt(2.0) * xp.sin(theta_M) * xp.cos(phi_bar_M) + t3 = xp.atanh(t2 / xp.sqrt(2.0)) + t3_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) return t1_coef * t1 + t2_coef * t2 + t3_coef * t3 -def Hz_phij_case134(phi_bar_j, phi_bar_Mj, theta_M): +def Hz_phij_case134(xp, phi_bar_j, phi_bar_Mj, theta_M): return ( - np.sin(theta_M) - * np.sin(phi_bar_Mj) - * np.arctanh(np.sqrt((1.0 - np.cos(phi_bar_j)) / 2.0)) + xp.sin(theta_M) + * xp.sin(phi_bar_Mj) + * xp.atanh(xp.sqrt((1.0 - xp.cos(phi_bar_j)) / 2.0)) ) # 135 ############## -def Hr_zk_case135(r, r_i, r_bar_i, phi_bar_j, theta_M): - t1 = np.sin(phi_bar_j) - t1_coef = -np.cos(theta_M) - t2 = np.log( +def Hr_zk_case135(xp, r, r_i, r_bar_i, phi_bar_j, theta_M): + t1 = xp.sin(phi_bar_j) + t1_coef = -xp.cos(theta_M) + t2 = xp.log( r_i - - r * np.cos(phi_bar_j) - + np.sqrt(r_i**2 + r**2 - 2 * r_i * r * np.cos(phi_bar_j)) + - r * xp.cos(phi_bar_j) + + xp.sqrt(r_i**2 + r**2 - 2 * r_i * r * xp.cos(phi_bar_j)) ) - t2_coef = np.cos(theta_M) * np.sin(phi_bar_j) + t2_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - E_coef = np.cos(theta_M) * np.abs(r_bar_i) / r + E_coef = xp.cos(theta_M) * xp.abs(r_bar_i) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / r_bar_i**2) - F_coef = -np.cos(theta_M) * (r**2 + r_i**2) / (r * np.abs(r_bar_i)) + F_coef = -xp.cos(theta_M) * (r**2 + r_i**2) / (r * xp.abs(r_bar_i)) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F -def Hphi_zk_case135(r, r_i, phi_bar_j, theta_M): - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j)) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh((r * np.cos(phi_bar_j) - r_i) / t1) - t2_coef = -np.cos(theta_M) * np.cos(phi_bar_j) +def Hphi_zk_case135(xp, r, r_i, phi_bar_j, theta_M): + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j)) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh((r * xp.cos(phi_bar_j) - r_i) / t1) + t2_coef = -xp.cos(theta_M) * xp.cos(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case135(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): +def Hz_ri_case135(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): t = r_bar_i**2 - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j)) / r - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j)) / r + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.sqrt(t) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sqrt(t) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) - F_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (r * np.sqrt(t)) + F_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (r * xp.sqrt(t)) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case135(r, r_i, phi_bar_j, phi_bar_Mj, theta_M): - t1 = np.arctanh( - (r * np.cos(phi_bar_j) - r_i) - / np.sqrt(r**2 + r_i**2 - 2 * r * r_i * np.cos(phi_bar_j)) +def Hz_phij_case135(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M): + t1 = xp.atanh( + (r * xp.cos(phi_bar_j) - r_i) + / xp.sqrt(r**2 + r_i**2 - 2 * r * r_i * xp.cos(phi_bar_j)) ) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 # 211 ############## -def Hr_phij_case211(phi_bar_M, theta_M, z_bar_k): +def Hr_phij_case211(xp, phi_bar_M, theta_M, z_bar_k): return ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) - * np.log(np.abs(z_bar_k)) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) + * xp.log(xp.abs(z_bar_k)) ) -def Hz_zk_case211(phi_j, theta_M, z_bar_k): - return -np.cos(theta_M) * np.sign(z_bar_k) * phi_j +def Hz_zk_case211(xp, phi_j, theta_M, z_bar_k): + return -xp.cos(theta_M) * xp.sign(z_bar_k) * phi_j # 212 ############## -def Hr_ri_case212(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 2.0 * phi_j * np.cos(phi_bar_M) - t3 = 1.0 / 4.0 * np.sin(phi_bar_M) +def Hr_ri_case212(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 2.0 * phi_j * xp.cos(phi_bar_M) + t3 = 1.0 / 4.0 * xp.sin(phi_bar_M) return t1 * (t2 - t3) -def Hr_phij_case212(r_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hphi_ri_case212(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 4.0 * np.cos(phi_bar_M) - t3 = 1.0 / 2.0 * phi_j * np.sin(phi_bar_M) +def Hphi_ri_case212(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 4.0 * xp.cos(phi_bar_M) + t3 = 1.0 / 2.0 * phi_j * xp.sin(phi_bar_M) return t1 * (-t2 + t3) -def Hphi_zk_case212(r_i, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) - t2 = np.arctanh(t1) - t2_coef = np.cos(theta_M) +def Hphi_zk_case212(xp, r_i, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) + t2 = xp.atanh(t1) + t2_coef = xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case212(r_i, phi_bar_M, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_phij_case212(r_i, phi_bar_M, theta_M, z_bar_k): +def Hz_phij_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k): return ( - np.sin(theta_M) - * np.sin(phi_bar_M) - * np.arctanh(r_i / np.sqrt(r_i**2 + z_bar_k**2)) + xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.atanh(r_i / xp.sqrt(r_i**2 + z_bar_k**2)) ) -def Hz_zk_case212(r_i, phi_j, theta_M, z_bar_k): - t1 = phi_j / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) * z_bar_k +def Hz_zk_case212(xp, r_i, phi_j, theta_M, z_bar_k): + t1 = phi_j / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) * z_bar_k return t1_coef * t1 # 213 ############## -def Hr_phij_case213(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case213(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hphi_zk_case213(r, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(r / t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case213(xp, r, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(r / t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_phij_case213(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(r / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case213(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(r / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case213(phi_bar_j, theta_M, z_bar_k): - t1 = np.sign(z_bar_k) - t1_coef = np.cos(theta_M) * phi_bar_j +def Hz_zk_case213(xp, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sign(z_bar_k) + t1_coef = xp.cos(theta_M) * phi_bar_j return t1_coef * t1 # 214 ############## -def Hr_ri_case214(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): +def Hr_ri_case214(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) - * np.sign(z_bar_k) + xp.sin(theta_M) + * xp.cos(phi_bar_M) + * xp.sign(z_bar_k) * (2.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) * z_bar_k**2 / (2.0 * r**2) + E_coef * E @@ -485,29 +489,29 @@ def Hr_ri_case214(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): ) -def Hr_phij_case214(phi_bar_M, theta_M, z_bar_k): +def Hr_phij_case214(xp, phi_bar_M, theta_M, z_bar_k): return ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) - * np.log(np.abs(z_bar_k)) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) + * xp.log(xp.abs(z_bar_k)) ) -def Hr_zk_case214(r, phi_bar_j, theta_M, z_bar_k): +def Hr_zk_case214(xp, r, phi_bar_j, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.cos(theta_M) * np.abs(z_bar_k) / r + E_coef = xp.cos(theta_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - F_coef = -np.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * np.abs(z_bar_k)) - t = np.sqrt(r**2 + z_bar_k**2) + F_coef = -xp.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * xp.abs(z_bar_k)) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle(phi_bar_j / 2, 2 * r / (r + sign * t), -4 * r**2 / z_bar_k**2) def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) * (t - sign * r) * (r + sign * t) ** 2 ) @@ -522,11 +526,11 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**4 / ( r - * np.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -541,87 +545,87 @@ def Pi2_coef(sign): ) -def Hphi_ri_case214(r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = -np.sin(theta_M) * np.cos(phi_bar_M) * np.sign(z_bar_k) / 2.0 +def Hphi_ri_case214(xp, r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = -xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sign(z_bar_k) / 2.0 t2 = phi_j - t2_coef = np.sin(theta_M) * np.sin(phi_bar_M) * np.sign(z_bar_k) / 2.0 - t3 = np.sign(z_bar_k) * z_bar_k**2 / (2.0 * r**2) - t3_coef = -np.sin(theta_M) * np.cos(phi_bar_M) - t4 = np.log(np.abs(z_bar_k) / (np.sqrt(2.0) * r)) - t4_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * np.sign(z_bar_k) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.sign(z_bar_k) / 2.0 + t3 = xp.sign(z_bar_k) * z_bar_k**2 / (2.0 * r**2) + t3_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) + t4 = xp.log(xp.abs(z_bar_k) / (xp.sqrt(2.0) * r)) + t4_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sign(z_bar_k) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) * (4.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return t1 + t2_coef * t2 + t3_coef * t3 + t4_coef * t4 + E_coef * E + F_coef * F -def Hphi_zk_case214(r, theta_M, z_bar_k): - t1 = np.abs(z_bar_k) - t1_coef = np.cos(theta_M) / r +def Hphi_zk_case214(xp, r, theta_M, z_bar_k): + t1 = xp.abs(z_bar_k) + t1_coef = xp.cos(theta_M) / r return t1_coef * t1 -def Hz_ri_case214(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): +def Hz_ri_case214(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.abs(z_bar_k) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (2.0 * r**2 + z_bar_k**2) - / (r * np.abs(z_bar_k)) + / (r * xp.abs(z_bar_k)) ) return ( - np.sin(theta_M) * np.sin(phi_bar_M) * np.abs(z_bar_k) / r + xp.sin(theta_M) * xp.sin(phi_bar_M) * xp.abs(z_bar_k) / r + E_coef * E + F_coef * F ) -def Hz_zk_case214(r, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case214(xp, r, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle(phi_bar_j / 2, 2 * r / (r + sign * t), -4 * r**2 / z_bar_k**2) - Pi_coef = np.cos(theta_M) * np.sign(z_bar_k) + Pi_coef = xp.cos(theta_M) * xp.sign(z_bar_k) return Pi_coef * Pi(1) + Pi_coef * Pi(-1) # 215 ############## -def Hr_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t2 = np.arctanh(z_bar_k / np.sqrt(r_bar_i**2 + z_bar_k**2)) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) +def Hr_ri_case215(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t2 = xp.atanh(z_bar_k / xp.sqrt(r_bar_i**2 + z_bar_k**2)) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (2.0 * r_i**2 + z_bar_k**2) - / (2 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -629,18 +633,18 @@ def Hr_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (r**2 + r_i**2) * (r + r_i) - / (2.0 * r**2 * r_bar_i * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * r_bar_i * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return ( - -np.sin(theta_M) - * np.sin(phi_bar_M) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2.0 * r**2) + t2_coef * t2 + E_coef * E @@ -649,22 +653,22 @@ def Hr_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): ) -def Hr_phij_case215(r_bar_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r_bar_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case215(xp, r_bar_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r_bar_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hr_zk_case215(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): +def Hr_zk_case215(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) - E_coef = np.cos(theta_M) * np.sqrt(r_bar_i**2 + z_bar_k**2) / r + E_coef = xp.cos(theta_M) * xp.sqrt(r_bar_i**2 + z_bar_k**2) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.cos(theta_M) + -xp.cos(theta_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (r * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) - t = np.sqrt(r**2 + z_bar_k**2) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle( @@ -675,8 +679,8 @@ def Pi1(sign): def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) * (t - sign * r) * (r_i + sign * t) ** 2 ) @@ -694,12 +698,12 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**2 * (r_bar_i**2 + z_bar_k**2) / ( r - * np.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -714,26 +718,26 @@ def Pi2_coef(sign): ) -def Hphi_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(r_bar_i**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) - t1_coef = -np.sin(theta_M) * np.cos(phi_bar_M) - t2 = np.arctanh(z_bar_k / np.sqrt(r_bar_i**2 + z_bar_k**2)) - t2_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) +def Hphi_ri_case215(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(r_bar_i**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) + t1_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) + t2 = xp.atanh(z_bar_k / xp.sqrt(r_bar_i**2 + z_bar_k**2)) + t2_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (2.0 * r**2 + 2.0 * r_i**2 + z_bar_k**2) - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -741,47 +745,47 @@ def Hphi_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (r + r_i) ** 2 - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F + Pi_coef * Pi -def Hphi_zk_case215(r, r_bar_i, theta_M, z_bar_k): - t1 = np.sqrt(r_bar_i**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(r_bar_i / t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case215(xp, r, r_bar_i, theta_M, z_bar_k): + t1 = xp.sqrt(r_bar_i**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(r_bar_i / t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): +def Hz_ri_case215(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): t = r_bar_i**2 + z_bar_k**2 - t1 = np.sqrt(r_bar_i**2 + z_bar_k**2) / r - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) + t1 = xp.sqrt(r_bar_i**2 + z_bar_k**2) / r + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.sqrt(t) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sqrt(t) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(t)) + / (r * xp.sqrt(t)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case215(r_bar_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(r_bar_i / np.sqrt(r_bar_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case215(xp, r_bar_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(r_bar_i / xp.sqrt(r_bar_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case215(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case215(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle( @@ -792,10 +796,10 @@ def Pi(sign): def Pi_coef(sign): return ( - np.cos(theta_M) + xp.cos(theta_M) * z_bar_k * (r_i + sign * t) - / (np.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) + / (xp.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) ) return Pi_coef(1) * Pi(1) + Pi_coef(-1) * Pi(-1) @@ -804,134 +808,134 @@ def Pi_coef(sign): # 221 ############## -def Hr_phij_case221(phi_bar_M, theta_M, z_bar_k): +def Hr_phij_case221(xp, phi_bar_M, theta_M, z_bar_k): return ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) - * np.log(np.abs(z_bar_k)) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) + * xp.log(xp.abs(z_bar_k)) ) -def Hz_zk_case221(phi_j, theta_M, z_bar_k): - return -np.cos(theta_M) * np.sign(z_bar_k) * phi_j +def Hz_zk_case221(xp, phi_j, theta_M, z_bar_k): + return -xp.cos(theta_M) * xp.sign(z_bar_k) * phi_j # 222 ############## -def Hr_ri_case222(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 2.0 * phi_j * np.cos(phi_bar_M) - t3 = 1.0 / 4.0 * np.sin(phi_bar_M) +def Hr_ri_case222(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 2.0 * phi_j * xp.cos(phi_bar_M) + t3 = 1.0 / 4.0 * xp.sin(phi_bar_M) return t1 * (t2 - t3) -def Hr_phij_case222(r_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hphi_ri_case222(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 4.0 * np.cos(phi_bar_M) - t3 = 1.0 / 2.0 * phi_j * np.sin(phi_bar_M) +def Hphi_ri_case222(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 4.0 * xp.cos(phi_bar_M) + t3 = 1.0 / 2.0 * phi_j * xp.sin(phi_bar_M) return t1 * (-t2 + t3) -def Hphi_zk_case222(r_i, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) - t2 = np.arctanh(t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case222(xp, r_i, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) + t2 = xp.atanh(t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case222(r_i, phi_bar_M, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_phij_case222(r_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(r_i / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(r_i / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case222(r_i, phi_j, theta_M, z_bar_k): - t1 = z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) * phi_j +def Hz_zk_case222(xp, r_i, phi_j, theta_M, z_bar_k): + t1 = z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) * phi_j return t1_coef * t1 # 223 ############## -def Hr_phij_case223(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case223(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hphi_zk_case223(r, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(r / t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case223(xp, r, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(r / t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_phij_case223(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(r / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case223(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(r / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case223(r, phi_bar_j, theta_M, z_bar_k): - t1 = arctan_k_tan_2(np.sqrt(r**2 + z_bar_k**2) / np.abs(z_bar_k), 2.0 * phi_bar_j) - t1_coef = np.cos(theta_M) * np.sign(z_bar_k) +def Hz_zk_case223(xp, r, phi_bar_j, theta_M, z_bar_k): + t1 = arctan_k_tan_2(xp.sqrt(r**2 + z_bar_k**2) / xp.abs(z_bar_k), 2.0 * phi_bar_j) + t1_coef = xp.cos(theta_M) * xp.sign(z_bar_k) return t1_coef * t1 # 224 ############## -def Hr_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(4.0 * r**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(4.0 * r**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) - * np.sign(z_bar_k) + xp.sin(theta_M) + * xp.cos(phi_bar_M) + * xp.sign(z_bar_k) * (2.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hr_phij_case224(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(4.0 * r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case224(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(4.0 * r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hr_zk_case224(r, phi_bar_j, theta_M, z_bar_k): +def Hr_zk_case224(xp, r, phi_bar_j, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.cos(theta_M) * np.abs(z_bar_k) / r + E_coef = xp.cos(theta_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - F_coef = -np.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * np.abs(z_bar_k)) - t = np.sqrt(r**2 + z_bar_k**2) + F_coef = -xp.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * xp.abs(z_bar_k)) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle( @@ -940,8 +944,8 @@ def Pi1(sign): def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) * (t - sign * r) * (r + sign * t) ** 2 ) @@ -956,11 +960,11 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**4 / ( r - * np.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -975,94 +979,94 @@ def Pi2_coef(sign): ) -def Hphi_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(4.0 * r**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) - t1_coef = -np.sin(theta_M) * np.cos(phi_bar_M) - t2 = np.arctanh(z_bar_k / np.sqrt(4.0 * r**2 + z_bar_k**2)) - t2_coef = -np.sin(theta_M) * np.cos(phi_bar_M) +def Hphi_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(4.0 * r**2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) + t1_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) + t2 = xp.atanh(z_bar_k / xp.sqrt(4.0 * r**2 + z_bar_k**2)) + t2_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) * (4.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F -def Hphi_zk_case224(r, theta_M, z_bar_k): - t1 = np.sqrt(4.0 * r**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(2.0 * r / t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case224(xp, r, theta_M, z_bar_k): + t1 = xp.sqrt(4.0 * r**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(2.0 * r / t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(4.0 * r**2 + z_bar_k**2) / r - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(4.0 * r**2 + z_bar_k**2) / r + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.abs(z_bar_k) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (2.0 * r**2 + z_bar_k**2) - / (r * np.abs(z_bar_k)) + / (r * xp.abs(z_bar_k)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case224(r, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(2.0 * r / np.sqrt(4.0 * r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case224(xp, r, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(2.0 * r / xp.sqrt(4.0 * r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case224(r, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case224(xp, r, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle( phi_bar_j / 2.0, 2.0 * r / (r + sign * t), -4.0 * r**2 / z_bar_k**2 ) - Pi_coef = np.cos(theta_M) * np.sign(z_bar_k) + Pi_coef = xp.cos(theta_M) * xp.sign(z_bar_k) return Pi_coef * Pi(1) + Pi_coef * Pi(-1) # 225 ############## -def Hr_ri_case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt((r + r_i) ** 2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) - t2 = np.arctanh(z_bar_k / np.sqrt((r + r_i) ** 2 + z_bar_k**2)) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) +def Hr_ri_case225(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt((r + r_i) ** 2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) + t2 = xp.atanh(z_bar_k / xp.sqrt((r + r_i) ** 2 + z_bar_k**2)) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (2.0 * r_i**2 + z_bar_k**2) - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -1070,32 +1074,32 @@ def Hr_ri_case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (r**2 + r_i**2) * (r + r_i) - / (2.0 * r**2 * r_bar_i * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * r_bar_i * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F + Pi_coef * Pi -def Hr_phij_case225(r, r_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt((r + r_i) ** 2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hr_phij_case225(xp, r, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt((r + r_i) ** 2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hr_zk_case225(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): +def Hr_zk_case225(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) - E_coef = np.cos(theta_M) * np.sqrt(r_bar_i**2 + z_bar_k**2) / r + E_coef = xp.cos(theta_M) * xp.sqrt(r_bar_i**2 + z_bar_k**2) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.cos(theta_M) + -xp.cos(theta_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (r * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) - t = np.sqrt(r**2 + z_bar_k**2) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle( @@ -1106,8 +1110,8 @@ def Pi1(sign): def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) * (t - sign * r) * (r_i + sign * t) ** 2 ) @@ -1125,12 +1129,12 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**2 * (r_bar_i**2 + z_bar_k**2) / ( r - * np.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -1145,26 +1149,26 @@ def Pi2_coef(sign): ) -def Hphi_ri_case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt((r + r_i) ** 2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) - t1_coef = -np.sin(theta_M) * np.cos(phi_bar_M) - t2 = np.arctanh(z_bar_k / np.sqrt((r + r_i) ** 2 + z_bar_k**2)) - t2_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) +def Hphi_ri_case225(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt((r + r_i) ** 2 + z_bar_k**2) * z_bar_k / (2.0 * r**2) + t1_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) + t2 = xp.atanh(z_bar_k / xp.sqrt((r + r_i) ** 2 + z_bar_k**2)) + t2_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (2.0 * r**2 + 2.0 * r_i**2 + z_bar_k**2) - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -1172,47 +1176,47 @@ def Hphi_ri_case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (r + r_i) ** 2 - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F + Pi_coef * Pi -def Hphi_zk_case225(r, r_i, theta_M, z_bar_k): - t1 = np.sqrt((r + r_i) ** 2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh((r + r_i) / t1) - t2_coef = -np.cos(theta_M) +def Hphi_zk_case225(xp, r, r_i, theta_M, z_bar_k): + t1 = xp.sqrt((r + r_i) ** 2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh((r + r_i) / t1) + t2_coef = -xp.cos(theta_M) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): +def Hz_ri_case225(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): t = r_bar_i**2 + z_bar_k**2 - t1 = np.sqrt((r + r_i) ** 2 + z_bar_k**2) / r - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) + t1 = xp.sqrt((r + r_i) ** 2 + z_bar_k**2) / r + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.sqrt(t) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sqrt(t) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(t)) + / (r * xp.sqrt(t)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case225(r, r_i, phi_bar_M, theta_M, z_bar_k): - t1 = np.arctanh((r + r_i) / np.sqrt((r + r_i) ** 2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) +def Hz_phij_case225(xp, r, r_i, phi_bar_M, theta_M, z_bar_k): + t1 = xp.atanh((r + r_i) / xp.sqrt((r + r_i) ** 2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) return t1_coef * t1 -def Hz_zk_case225(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case225(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle( @@ -1223,10 +1227,10 @@ def Pi(sign): def Pi_coef(sign): return ( - np.cos(theta_M) + xp.cos(theta_M) * z_bar_k * (r_i + sign * t) - / (np.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) + / (xp.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) ) return Pi_coef(1) * Pi(1) + Pi_coef(-1) * Pi(-1) @@ -1235,119 +1239,119 @@ def Pi_coef(sign): # 231 ############## -def Hr_phij_case231(phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): +def Hr_phij_case231(xp, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): return ( - -np.sin(theta_M) - * np.sin(phi_bar_Mj) - * np.cos(phi_bar_j) - * np.sign(z_bar_k) - * np.log(np.abs(z_bar_k)) + -xp.sin(theta_M) + * xp.sin(phi_bar_Mj) + * xp.cos(phi_bar_j) + * xp.sign(z_bar_k) + * xp.log(xp.abs(z_bar_k)) ) -def Hphi_phij_case231(phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.log(np.abs(z_bar_k)) +def Hphi_phij_case231(xp, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.log(xp.abs(z_bar_k)) t1_coef = ( - np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) * np.sign(z_bar_k) + xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) * xp.sign(z_bar_k) ) return t1_coef * t1 -def Hz_zk_case231(phi_j, theta_M, z_bar_k): - t1 = phi_j * np.sign(z_bar_k) - t1_coef = -np.cos(theta_M) +def Hz_zk_case231(xp, phi_j, theta_M, z_bar_k): + t1 = phi_j * xp.sign(z_bar_k) + t1_coef = -xp.cos(theta_M) return t1_coef * t1 # 232 ############## -def Hr_ri_case232(r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 2.0 * phi_j * np.cos(phi_bar_M) - t3 = 1.0 / 4.0 * np.sin(phi_bar_Mj + phi_bar_j) +def Hr_ri_case232(xp, r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 2.0 * phi_j * xp.cos(phi_bar_M) + t3 = 1.0 / 4.0 * xp.sin(phi_bar_Mj + phi_bar_j) return t1 * (t2 - t3) -def Hr_phij_case232(r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) +def Hr_phij_case232(xp, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) return t1_coef * t1 -def Hr_zk_case232(r_i, phi_bar_j, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) * np.sin(phi_bar_j) - t2 = np.arctanh(t1) - t2_coef = np.cos(theta_M) * np.sin(phi_bar_j) +def Hr_zk_case232(xp, r_i, phi_bar_j, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) * xp.sin(phi_bar_j) + t2 = xp.atanh(t1) + t2_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hphi_ri_case232(r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.sin(theta_M) * z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t2 = 1.0 / 4.0 * np.cos(phi_bar_Mj + phi_bar_j) - t3 = 1.0 / 2.0 * phi_j * np.sin(phi_bar_M) +def Hphi_ri_case232(xp, r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.sin(theta_M) * z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t2 = 1.0 / 4.0 * xp.cos(phi_bar_Mj + phi_bar_j) + t3 = 1.0 / 2.0 * phi_j * xp.sin(phi_bar_M) return t1 * (-t2 + t3) -def Hphi_phij_case232(r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) +def Hphi_phij_case232(xp, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 -def Hphi_zk_case232(r_i, phi_bar_j, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) * np.cos(phi_bar_j) - t2 = np.arctanh(t1) - t2_coef = np.cos(theta_M) * np.cos(phi_bar_j) +def Hphi_zk_case232(xp, r_i, phi_bar_j, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) * xp.cos(phi_bar_j) + t2 = xp.atanh(t1) + t2_coef = xp.cos(theta_M) * xp.cos(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case232(r_i, phi_bar_Mj, theta_M, z_bar_k): - t1 = r_i / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) +def Hz_ri_case232(xp, r_i, phi_bar_Mj, theta_M, z_bar_k): + t1 = r_i / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 -def Hz_phij_case232(r_i, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh(r_i / np.sqrt(r_i**2 + z_bar_k**2)) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) +def Hz_phij_case232(xp, r_i, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh(r_i / xp.sqrt(r_i**2 + z_bar_k**2)) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 -def Hz_zk_case232(r_i, phi_j, theta_M, z_bar_k): - t1 = z_bar_k / np.sqrt(r_i**2 + z_bar_k**2) - t1_coef = -np.cos(theta_M) * phi_j +def Hz_zk_case232(xp, r_i, phi_j, theta_M, z_bar_k): + t1 = z_bar_k / xp.sqrt(r_i**2 + z_bar_k**2) + t1_coef = -xp.cos(theta_M) * phi_j return t1_coef * t1 # 233 ############## -def Hr_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh(z_bar_k / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctan( - z_bar_k * np.cos(phi_bar_j) / np.sin(phi_bar_j) / np.sqrt(r**2 + z_bar_k**2) +def Hr_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh(z_bar_k / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atan( + z_bar_k * xp.cos(phi_bar_j) / xp.sin(phi_bar_j) / xp.sqrt(r**2 + z_bar_k**2) ) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hr_zk_case233(r, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) - t1 = np.sin(phi_bar_j) - t1_coef = -np.cos(theta_M) - t2 = np.log(-r * np.cos(phi_bar_j) + t) - t2_coef = np.cos(theta_M) * np.sin(phi_bar_j) - t3 = np.arctan(r * np.sin(phi_bar_j) / z_bar_k) - t3_coef = np.cos(theta_M) * z_bar_k / r - t4 = arctan_k_tan_2(t / np.abs(z_bar_k), 2.0 * phi_bar_j) +def Hr_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) + t1 = xp.sin(phi_bar_j) + t1_coef = -xp.cos(theta_M) + t2 = xp.log(-r * xp.cos(phi_bar_j) + t) + t2_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) + t3 = xp.atan(r * xp.sin(phi_bar_j) / z_bar_k) + t3_coef = xp.cos(theta_M) * z_bar_k / r + t4 = arctan_k_tan_2(t / xp.abs(z_bar_k), 2.0 * phi_bar_j) t4_coef = -t3_coef def t5(sign): - return arctan_k_tan_2(np.abs(z_bar_k) / np.abs(r + sign * t), phi_bar_j) + return arctan_k_tan_2(xp.abs(z_bar_k) / xp.abs(r + sign * t), phi_bar_j) t5_coef = t3_coef return ( @@ -1360,92 +1364,92 @@ def t5(sign): ) -def Hphi_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) - t1 = np.arctan(z_bar_k * np.cos(phi_bar_j) / (np.sin(phi_bar_j) * t)) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctanh(z_bar_k / t) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) +def Hphi_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) + t1 = xp.atan(z_bar_k * xp.cos(phi_bar_j) / (xp.sin(phi_bar_j) * t)) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atanh(z_bar_k / t) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hphi_zk_case233(r, phi_bar_j, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(r * np.cos(phi_bar_j) / t1) - t2_coef = -np.cos(theta_M) * np.cos(phi_bar_j) +def Hphi_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(r * xp.cos(phi_bar_j) / t1) + t2_coef = -xp.cos(theta_M) * xp.cos(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hz_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh(r * np.cos(phi_bar_j) / np.sqrt(r**2 + z_bar_k**2)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) +def Hz_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh(r * xp.cos(phi_bar_j) / xp.sqrt(r**2 + z_bar_k**2)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 -def Hz_zk_case233(r, phi_bar_j, theta_M, z_bar_k): - t1 = arctan_k_tan_2(np.sqrt(r**2 + z_bar_k**2) / np.abs(z_bar_k), 2.0 * phi_bar_j) - t1_coef = np.cos(theta_M) * np.sign(z_bar_k) +def Hz_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k): + t1 = arctan_k_tan_2(xp.sqrt(r**2 + z_bar_k**2) / xp.abs(z_bar_k), 2.0 * phi_bar_j) + t1_coef = xp.cos(theta_M) * xp.sign(z_bar_k) return t1_coef * t1 # 234 ############## -def Hr_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) * z_bar_k / (2.0 * r**2) +def Hr_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) * z_bar_k / (2.0 * r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) - * np.sign(z_bar_k) + xp.sin(theta_M) + * xp.cos(phi_bar_M) + * xp.sign(z_bar_k) * (2.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hr_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh( - z_bar_k / np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) +def Hr_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh( + z_bar_k / xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) ) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctan( + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atan( z_bar_k - * (1.0 - np.cos(phi_bar_j)) + * (1.0 - xp.cos(phi_bar_j)) / ( - np.sin(phi_bar_j) - * np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) + xp.sin(phi_bar_j) + * xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) ) ) - t2_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) + t2_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hr_zk_case234(r, phi_bar_j, theta_M, z_bar_k): - t1 = np.sin(phi_bar_j) - t1_coef = -np.cos(theta_M) - t2 = np.log( - r * (1.0 - np.cos(phi_bar_j)) - + np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) +def Hr_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sin(phi_bar_j) + t1_coef = -xp.cos(theta_M) + t2 = xp.log( + r * (1.0 - xp.cos(phi_bar_j)) + + xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) ) - t2_coef = np.cos(theta_M) * np.sin(phi_bar_j) - t3 = np.arctan(r * np.sin(phi_bar_j) / z_bar_k) - t3_coef = np.cos(theta_M) * z_bar_k / r + t2_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) + t3 = xp.atan(r * xp.sin(phi_bar_j) / z_bar_k) + t3_coef = xp.cos(theta_M) * z_bar_k / r E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.cos(theta_M) * np.abs(z_bar_k) / r + E_coef = xp.cos(theta_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - F_coef = -np.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * np.abs(z_bar_k)) - t = np.sqrt(r**2 + z_bar_k**2) + F_coef = -xp.cos(theta_M) * (2.0 * r**2 + z_bar_k**2) / (r * xp.abs(z_bar_k)) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle( @@ -1454,15 +1458,15 @@ def Pi1(sign): def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * z_bar_k**2)) * (t - sign * r) * (r + sign * t) ** 2 ) def Pi2(sign): return el3_angle( - arctan_k_tan_2(np.sqrt((4.0 * r**2 + z_bar_k**2) / z_bar_k**2), phi_bar_j), + arctan_k_tan_2(xp.sqrt((4.0 * r**2 + z_bar_k**2) / z_bar_k**2), phi_bar_j), 1.0 - z_bar_k**4 / ((4.0 * r**2 + z_bar_k**2) * (r + sign * t) ** 2), 4.0 * r**2 / (4.0 * r**2 + z_bar_k**2), ) @@ -1470,11 +1474,11 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**4 / ( r - * np.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * (4.0 * r**2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -1492,112 +1496,112 @@ def Pi2_coef(sign): ) -def Hphi_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * z_bar_k / (2.0 * r**2) - t2 = np.arctanh( - z_bar_k / np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) +def Hphi_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * z_bar_k / (2.0 * r**2) + t2 = xp.atanh( + z_bar_k / xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) ) - t2_coef = -np.sin(theta_M) * np.cos(phi_bar_M) + t2_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k**2 - * np.sign(z_bar_k) + * xp.sign(z_bar_k) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) - * np.sign(z_bar_k) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) + * xp.sign(z_bar_k) * (4.0 * r**2 + z_bar_k**2) / (2.0 * r**2) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F -def Hphi_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t = np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) - t1 = np.arctan(z_bar_k * (1.0 - np.cos(phi_bar_j)) / (np.sin(phi_bar_j) * t)) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctanh(z_bar_k / t) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) +def Hphi_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t = xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) + t1 = xp.atan(z_bar_k * (1.0 - xp.cos(phi_bar_j)) / (xp.sin(phi_bar_j) * t)) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atanh(z_bar_k / t) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hphi_zk_case234(r, phi_bar_j, theta_M, z_bar_k): - t1 = np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh(r * (1.0 - np.cos(phi_bar_j)) / t1) - t2_coef = np.cos(theta_M) * np.cos(phi_bar_j) +def Hphi_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh(r * (1.0 - xp.cos(phi_bar_j)) / t1) + t2_coef = xp.cos(theta_M) * xp.cos(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) / r +def Hz_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) / r E = ellipeinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.abs(z_bar_k) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.abs(z_bar_k) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r**2 / z_bar_k**2) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (2.0 * r**2 + z_bar_k**2) - / (r * np.abs(z_bar_k)) + / (r * xp.abs(z_bar_k)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh( +def Hz_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh( r - * (1.0 - np.cos(phi_bar_j)) - / np.sqrt(2.0 * r**2 * (1.0 - np.cos(phi_bar_j)) + z_bar_k**2) + * (1.0 - xp.cos(phi_bar_j)) + / xp.sqrt(2.0 * r**2 * (1.0 - xp.cos(phi_bar_j)) + z_bar_k**2) ) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 -def Hz_zk_case234(r, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle( phi_bar_j / 2.0, 2.0 * r / (r + sign * t), -4.0 * r**2 / z_bar_k**2 ) - Pi_coef = np.cos(theta_M) * np.sign(z_bar_k) + Pi_coef = xp.cos(theta_M) * xp.sign(z_bar_k) return Pi_coef * Pi(1) + Pi_coef * Pi(-1) # 235 ############## -def Hr_ri_case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_M) * z_bar_k / (2.0 * r**2) - t2 = np.arctanh( +def Hr_ri_case235(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_M) * z_bar_k / (2.0 * r**2) + t2 = xp.atanh( z_bar_k - / np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) + / xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) ) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) / 2.0 * (1.0 - r_i**2 / r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (2.0 * r_i**2 + z_bar_k**2) - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -1605,55 +1609,55 @@ def Hr_ri_case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.cos(phi_bar_M) + xp.sin(theta_M) + * xp.cos(phi_bar_M) * z_bar_k * (r**2 + r_i**2) * (r + r_i) - / (2.0 * r**2 * r_bar_i * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * r_bar_i * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F + Pi_coef * Pi -def Hr_phij_case235(r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh( +def Hr_phij_case235(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh( z_bar_k - / np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) + / xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) ) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctan( + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atan( z_bar_k - * (r * np.cos(phi_bar_j) - r_i) + * (r * xp.cos(phi_bar_j) - r_i) / ( r - * np.sin(phi_bar_j) - * np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) + * xp.sin(phi_bar_j) + * xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) ) ) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hr_zk_case235(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): - t1 = np.sin(phi_bar_j) - t1_coef = -np.cos(theta_M) - t2 = np.log( +def Hr_zk_case235(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sin(phi_bar_j) + t1_coef = -xp.cos(theta_M) + t2 = xp.log( r_i - - r * np.cos(phi_bar_j) - + np.sqrt(r_i**2 + r**2 - 2.0 * r_i * r * np.cos(phi_bar_j) + z_bar_k**2) + - r * xp.cos(phi_bar_j) + + xp.sqrt(r_i**2 + r**2 - 2.0 * r_i * r * xp.cos(phi_bar_j) + z_bar_k**2) ) - t2_coef = np.cos(theta_M) * np.sin(phi_bar_j) - t3 = np.arctan(r * np.sin(phi_bar_j) / z_bar_k) - t3_coef = np.cos(theta_M) * z_bar_k / r + t2_coef = xp.cos(theta_M) * xp.sin(phi_bar_j) + t3 = xp.atan(r * xp.sin(phi_bar_j) / z_bar_k) + t3_coef = xp.cos(theta_M) * z_bar_k / r E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) - E_coef = np.cos(theta_M) * np.sqrt(r_bar_i**2 + z_bar_k**2) / r + E_coef = xp.cos(theta_M) * xp.sqrt(r_bar_i**2 + z_bar_k**2) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.cos(theta_M) + -xp.cos(theta_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (r * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) - t = np.sqrt(r**2 + z_bar_k**2) + t = xp.sqrt(r**2 + z_bar_k**2) def Pi1(sign): return el3_angle( @@ -1664,8 +1668,8 @@ def Pi1(sign): def Pi1_coef(sign): return ( - -np.cos(theta_M) - / (r * np.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) + -xp.cos(theta_M) + / (r * xp.sqrt((r**2 + z_bar_k**2) * (r_bar_i**2 + z_bar_k**2))) * (t - sign * r) * (r_i + sign * t) ** 2 ) @@ -1673,7 +1677,7 @@ def Pi1_coef(sign): def Pi2(sign): return el3_angle( arctan_k_tan_2( - np.sqrt(((r_i + r) ** 2 + z_bar_k**2) / (r_bar_i**2 + z_bar_k**2)), + xp.sqrt(((r_i + r) ** 2 + z_bar_k**2) / (r_bar_i**2 + z_bar_k**2)), phi_bar_j, ), 1.0 @@ -1686,12 +1690,12 @@ def Pi2(sign): def Pi2_coef(sign): return ( sign - * np.cos(theta_M) + * xp.cos(theta_M) * z_bar_k**2 * (r_bar_i**2 + z_bar_k**2) / ( r - * np.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) + * xp.sqrt((r**2 + z_bar_k**2) * ((r + r_i) ** 2 + z_bar_k**2)) * (r + sign * t) ) ) @@ -1709,29 +1713,29 @@ def Pi2_coef(sign): ) -def Hphi_ri_case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) - t1_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * z_bar_k / (2.0 * r**2) - t2 = np.arctanh( +def Hphi_ri_case235(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) + t1_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * z_bar_k / (2.0 * r**2) + t2 = xp.atanh( z_bar_k - / np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) + / xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) ) - t2_coef = -np.sin(theta_M) * np.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) + t2_coef = -xp.sin(theta_M) * xp.cos(phi_bar_M) * (r**2 + r_i**2) / (2.0 * r**2) E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) E_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k - * np.sqrt(r_bar_i**2 + z_bar_k**2) + * xp.sqrt(r_bar_i**2 + z_bar_k**2) / (2.0 * r**2) ) F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2)) F_coef = ( - -np.sin(theta_M) - * np.sin(phi_bar_M) + -xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (2.0 * r**2 + 2.0 * r_i**2 + z_bar_k**2) - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) Pi = el3_angle( phi_bar_j / 2.0, @@ -1739,61 +1743,59 @@ def Hphi_ri_case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): -4.0 * r * r_i / (r_bar_i**2 + z_bar_k**2), ) Pi_coef = ( - np.sin(theta_M) - * np.sin(phi_bar_M) + xp.sin(theta_M) + * xp.sin(phi_bar_M) * z_bar_k * (r + r_i) ** 2 - / (2.0 * r**2 * np.sqrt(r_bar_i**2 + z_bar_k**2)) + / (2.0 * r**2 * xp.sqrt(r_bar_i**2 + z_bar_k**2)) ) return t1_coef * t1 + t2_coef * t2 + E_coef * E + F_coef * F + Pi_coef * Pi -def Hphi_phij_case235(r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) - t1 = np.arctan( - z_bar_k * (r * np.cos(phi_bar_j) - r_i) / (r * np.sin(phi_bar_j) * t) - ) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.cos(phi_bar_j) - t2 = np.arctanh(z_bar_k / t) - t2_coef = np.sin(theta_M) * np.sin(phi_bar_Mj) * np.sin(phi_bar_j) +def Hphi_phij_case235(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) + t1 = xp.atan(z_bar_k * (r * xp.cos(phi_bar_j) - r_i) / (r * xp.sin(phi_bar_j) * t)) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.cos(phi_bar_j) + t2 = xp.atanh(z_bar_k / t) + t2_coef = xp.sin(theta_M) * xp.sin(phi_bar_Mj) * xp.sin(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hphi_zk_case235(r, r_i, phi_bar_j, theta_M, z_bar_k): - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) - t1_coef = np.cos(theta_M) / r - t2 = np.arctanh((r * np.cos(phi_bar_j) - r_i) / t1) - t2_coef = -np.cos(theta_M) * np.cos(phi_bar_j) +def Hphi_zk_case235(xp, r, r_i, phi_bar_j, theta_M, z_bar_k): + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) + t1_coef = xp.cos(theta_M) / r + t2 = xp.atanh((r * xp.cos(phi_bar_j) - r_i) / t1) + t2_coef = -xp.cos(theta_M) * xp.cos(phi_bar_j) return t1_coef * t1 + t2_coef * t2 -def Hz_ri_case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): +def Hz_ri_case235(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): t = r_bar_i**2 + z_bar_k**2 - t1 = np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) - t1_coef = np.sin(theta_M) * np.sin(phi_bar_M) / r + t1 = xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) + t1_coef = xp.sin(theta_M) * xp.sin(phi_bar_M) / r E = ellipeinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) - E_coef = np.sin(theta_M) * np.cos(phi_bar_M) * np.sqrt(t) / r + E_coef = xp.sin(theta_M) * xp.cos(phi_bar_M) * xp.sqrt(t) / r F = ellipkinc(phi_bar_j / 2.0, -4.0 * r * r_i / t) F_coef = ( - -np.sin(theta_M) - * np.cos(phi_bar_M) + -xp.sin(theta_M) + * xp.cos(phi_bar_M) * (r**2 + r_i**2 + z_bar_k**2) - / (r * np.sqrt(t)) + / (r * xp.sqrt(t)) ) return t1_coef * t1 + E_coef * E + F_coef * F -def Hz_phij_case235(r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - t1 = np.arctanh( - (r * np.cos(phi_bar_j) - r_i) - / np.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * np.cos(phi_bar_j) + z_bar_k**2) +def Hz_phij_case235(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + t1 = xp.atanh( + (r * xp.cos(phi_bar_j) - r_i) + / xp.sqrt(r**2 + r_i**2 - 2.0 * r * r_i * xp.cos(phi_bar_j) + z_bar_k**2) ) - t1_coef = -np.sin(theta_M) * np.sin(phi_bar_Mj) + t1_coef = -xp.sin(theta_M) * xp.sin(phi_bar_Mj) return t1_coef * t1 -def Hz_zk_case235(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): - t = np.sqrt(r**2 + z_bar_k**2) +def Hz_zk_case235(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k): + t = xp.sqrt(r**2 + z_bar_k**2) def Pi(sign): return el3_angle( @@ -1804,10 +1806,10 @@ def Pi(sign): def Pi_coef(sign): return ( - np.cos(theta_M) + xp.cos(theta_M) * z_bar_k * (r_i + sign * t) - / (np.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) + / (xp.sqrt(r_bar_i**2 + z_bar_k**2) * (r + sign * t)) ) return Pi_coef(1) * Pi(1) + Pi_coef(-1) * Pi(-1) @@ -1823,286 +1825,294 @@ def Pi_coef(sign): # out: ndarray, shape (n,3,3) # (n)vector, (3)r_phi_z, (3)face -def case112(r_i, phi_bar_M, theta_M): - results = np.zeros((len(r_i), 3, 3)) - results[:, 1, 2] = Hphi_zk_case112(r_i, theta_M) - results[:, 2, 0] = Hz_ri_case112(phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case112(r_i, phi_bar_M, theta_M) +def case112(xp, r_i, phi_bar_M, theta_M): + results = xp.zeros(((r_i.shape[0]), 3, 3)) + results[:, 1, 2] = Hphi_zk_case112(xp, r_i, theta_M) + results[:, 2, 0] = Hz_ri_case112(xp, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case112(xp, r_i, phi_bar_M, theta_M) return results -def case113(r, phi_bar_M, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 1, 2] = Hphi_zk_case113(r, theta_M) - results[:, 2, 1] = Hz_phij_case113(r, phi_bar_M, theta_M) +def case113(xp, r, phi_bar_M, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 1, 2] = Hphi_zk_case113(xp, r, theta_M) + results[:, 2, 1] = Hz_phij_case113(xp, r, phi_bar_M, theta_M) return results -def case115(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case115(r, r_i, r_bar_i, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case115(r, r_i, r_bar_i, theta_M) - results[:, 2, 0] = Hz_ri_case115(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case115(r_bar_i, phi_bar_M, theta_M) +def case115(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case115(xp, r, r_i, r_bar_i, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case115(xp, r, r_i, r_bar_i, theta_M) + results[:, 2, 0] = Hz_ri_case115(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case115(xp, r_bar_i, phi_bar_M, theta_M) return results -def case122(r_i, phi_bar_M, theta_M): - results = np.zeros((len(r_i), 3, 3)) - results[:, 1, 2] = Hphi_zk_case122(r_i, theta_M) - results[:, 2, 0] = Hz_ri_case122(phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case122(r_i, phi_bar_M, theta_M) +def case122(xp, r_i, phi_bar_M, theta_M): + results = xp.zeros(((r_i.shape[0]), 3, 3)) + results[:, 1, 2] = Hphi_zk_case122(xp, r_i, theta_M) + results[:, 2, 0] = Hz_ri_case122(xp, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case122(xp, r_i, phi_bar_M, theta_M) return results -def case123(r, phi_bar_M, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 1, 2] = Hphi_zk_case123(r, theta_M) - results[:, 2, 1] = Hz_phij_case123(r, phi_bar_M, theta_M) +def case123(xp, r, phi_bar_M, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 1, 2] = Hphi_zk_case123(xp, r, theta_M) + results[:, 2, 1] = Hz_phij_case123(xp, r, phi_bar_M, theta_M) return results -def case124(r, phi_bar_M, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 1, 2] = Hphi_zk_case124(r, theta_M) - results[:, 2, 0] = Hz_ri_case124(phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case124(r, phi_bar_M, theta_M) +def case124(xp, r, phi_bar_M, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 1, 2] = Hphi_zk_case124(xp, r, theta_M) + results[:, 2, 0] = Hz_ri_case124(xp, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case124(xp, r, phi_bar_M, theta_M) return results -def case125(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case125(r, r_i, r_bar_i, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case125(r, r_i, theta_M) - results[:, 2, 0] = Hz_ri_case125(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case125(r, r_i, phi_bar_M, theta_M) +def case125(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case125(xp, r, r_i, r_bar_i, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case125(xp, r, r_i, theta_M) + results[:, 2, 0] = Hz_ri_case125(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case125(xp, r, r_i, phi_bar_M, theta_M) return results -def case132(r, r_i, phi_bar_j, phi_bar_Mj, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case132(r_i, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case132(r_i, phi_bar_j, theta_M) - results[:, 2, 0] = Hz_ri_case132(phi_bar_Mj, theta_M) - results[:, 2, 1] = Hz_phij_case132(r_i, phi_bar_Mj, theta_M) +def case132(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case132(xp, r_i, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case132(xp, r_i, phi_bar_j, theta_M) + results[:, 2, 0] = Hz_ri_case132(xp, phi_bar_Mj, theta_M) + results[:, 2, 1] = Hz_phij_case132(xp, r_i, phi_bar_Mj, theta_M) return results -def case133(r, phi_bar_j, phi_bar_Mj, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case133(r, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case133(phi_bar_j, theta_M) - results[:, 2, 1] = Hz_phij_case133(phi_bar_j, phi_bar_Mj, theta_M) +def case133(xp, r, phi_bar_j, phi_bar_Mj, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case133(xp, r, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case133(xp, phi_bar_j, theta_M) + results[:, 2, 1] = Hz_phij_case133(xp, phi_bar_j, phi_bar_Mj, theta_M) return results -def case134(r, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case134(r, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case134(phi_bar_j, theta_M) - results[:, 2, 0] = Hz_ri_case134(phi_bar_j, phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case134(phi_bar_j, phi_bar_Mj, theta_M) +def case134(xp, r, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case134(xp, r, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case134(xp, phi_bar_j, theta_M) + results[:, 2, 0] = Hz_ri_case134(xp, phi_bar_j, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case134(xp, phi_bar_j, phi_bar_Mj, theta_M) return results -def case135(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 2] = Hr_zk_case135(r, r_i, r_bar_i, phi_bar_j, theta_M) - results[:, 1, 2] = Hphi_zk_case135(r, r_i, phi_bar_j, theta_M) - results[:, 2, 0] = Hz_ri_case135(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) - results[:, 2, 1] = Hz_phij_case135(r, r_i, phi_bar_j, phi_bar_Mj, theta_M) +def case135(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 2] = Hr_zk_case135(xp, r, r_i, r_bar_i, phi_bar_j, theta_M) + results[:, 1, 2] = Hphi_zk_case135(xp, r, r_i, phi_bar_j, theta_M) + results[:, 2, 0] = Hz_ri_case135(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M) + results[:, 2, 1] = Hz_phij_case135(xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M) return results -def case211(phi_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(phi_j), 3, 3)) - results[:, 0, 1] = Hr_phij_case211(phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case211(phi_j, theta_M, z_bar_k) +def case211(xp, phi_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((phi_j.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case211(xp, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case211(xp, phi_j, theta_M, z_bar_k) return results -def case212(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r_i), 3, 3)) - results[:, 0, 0] = Hr_ri_case212(r_i, phi_j, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 1] = Hr_phij_case212(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 0] = Hphi_ri_case212(r_i, phi_j, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case212(r_i, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case212(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case212(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case212(r_i, phi_j, theta_M, z_bar_k) +def case212(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r_i.shape[0]), 3, 3)) + results[:, 0, 0] = Hr_ri_case212(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 0] = Hphi_ri_case212(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case212(xp, r_i, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case212(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case212(xp, r_i, phi_j, theta_M, z_bar_k) return results -def case213(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 1] = Hr_phij_case213(r, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case213(r, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case213(r, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case213(phi_bar_j, theta_M, z_bar_k) +def case213(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case213(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case213(xp, r, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case213(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case213(xp, phi_bar_j, theta_M, z_bar_k) return results -def case214(r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 0] = Hr_ri_case214(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 1] = Hr_phij_case214(phi_bar_M, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case214(r, phi_bar_j, theta_M, z_bar_k) - results[:, 1, 0] = Hphi_ri_case214(r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case214(r, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case214(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case214(r, phi_bar_j, theta_M, z_bar_k) +def case214(xp, r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 0] = Hr_ri_case214(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case214(xp, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case214(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 1, 0] = Hphi_ri_case214( + xp, r, phi_j, phi_bar_j, phi_bar_M, theta_M, z_bar_k + ) + results[:, 1, 2] = Hphi_zk_case214(xp, r, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case214(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case214(xp, r, phi_bar_j, theta_M, z_bar_k) return results -def case215(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) +def case215(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) results[:, 0, 0] = Hr_ri_case215( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 0, 1] = Hr_phij_case215(r_bar_i, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case215(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case215(xp, r_bar_i, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case215(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) results[:, 1, 0] = Hphi_ri_case215( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 1, 2] = Hphi_zk_case215(r, r_bar_i, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case215(xp, r, r_bar_i, theta_M, z_bar_k) results[:, 2, 0] = Hz_ri_case215( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 2, 1] = Hz_phij_case215(r_bar_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case215(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case215(xp, r_bar_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case215(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) return results -def case221(phi_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(phi_j), 3, 3)) - results[:, 0, 1] = Hr_phij_case221(phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case221(phi_j, theta_M, z_bar_k) +def case221(xp, phi_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((phi_j.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case221(xp, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case221(xp, phi_j, theta_M, z_bar_k) return results -def case222(r_i, phi_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r_i), 3, 3)) - results[:, 0, 0] = Hr_ri_case222(r_i, phi_j, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 1] = Hr_phij_case222(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 0] = Hphi_ri_case222(r_i, phi_j, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case222(r_i, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case222(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case222(r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case222(r_i, phi_j, theta_M, z_bar_k) +def case222(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r_i.shape[0]), 3, 3)) + results[:, 0, 0] = Hr_ri_case222(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 0] = Hphi_ri_case222(xp, r_i, phi_j, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case222(xp, r_i, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case222(xp, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case222(xp, r_i, phi_j, theta_M, z_bar_k) return results -def case223(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 1] = Hr_phij_case223(r, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case223(r, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case223(r, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case223(r, phi_bar_j, theta_M, z_bar_k) +def case223(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case223(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case223(xp, r, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case223(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case223(xp, r, phi_bar_j, theta_M, z_bar_k) return results -def case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 0] = Hr_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 1] = Hr_phij_case224(r, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case224(r, phi_bar_j, theta_M, z_bar_k) - results[:, 1, 0] = Hphi_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case224(r, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case224(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case224(r, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case224(r, phi_bar_j, theta_M, z_bar_k) +def case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 0] = Hr_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case224(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case224(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 1, 0] = Hphi_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case224(xp, r, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case224(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case224(xp, r, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case224(xp, r, phi_bar_j, theta_M, z_bar_k) return results -def case225(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) +def case225(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) results[:, 0, 0] = Hr_ri_case225( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 0, 1] = Hr_phij_case225(r, r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case225(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case225(xp, r, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case225(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) results[:, 1, 0] = Hphi_ri_case225( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 1, 2] = Hphi_zk_case225(r, r_i, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case225(xp, r, r_i, theta_M, z_bar_k) results[:, 2, 0] = Hz_ri_case225( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 2, 1] = Hz_phij_case225(r, r_i, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case225(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case225(xp, r, r_i, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case225(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) return results -def case231(phi_j, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - results = np.zeros((len(phi_j), 3, 3)) - results[:, 0, 1] = Hr_phij_case231(phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 1, 1] = Hphi_phij_case231(phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case231(phi_j, theta_M, z_bar_k) +def case231(xp, phi_j, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + results = xp.zeros(((phi_j.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case231(xp, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 1, 1] = Hphi_phij_case231(xp, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case231(xp, phi_j, theta_M, z_bar_k) return results -def case232(r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): - results = np.zeros((len(r_i), 3, 3)) +def case232(xp, r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): + results = xp.zeros(((r_i.shape[0]), 3, 3)) results[:, 0, 0] = Hr_ri_case232( - r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k + xp, r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k ) - results[:, 0, 1] = Hr_phij_case232(r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case232(r_i, phi_bar_j, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case232(xp, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case232(xp, r_i, phi_bar_j, theta_M, z_bar_k) results[:, 1, 0] = Hphi_ri_case232( - r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k + xp, r_i, phi_j, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k + ) + results[:, 1, 1] = Hphi_phij_case232( + xp, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k ) - results[:, 1, 1] = Hphi_phij_case232(r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case232(r_i, phi_bar_j, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case232(r_i, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case232(r_i, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case232(r_i, phi_j, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case232(xp, r_i, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case232(xp, r_i, phi_bar_Mj, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case232(xp, r_i, phi_bar_Mj, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case232(xp, r_i, phi_j, theta_M, z_bar_k) return results -def case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 1] = Hr_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case233(r, phi_bar_j, theta_M, z_bar_k) - results[:, 1, 1] = Hphi_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case233(r, phi_bar_j, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case233(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case233(r, phi_bar_j, theta_M, z_bar_k) +def case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 1] = Hr_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 1, 1] = Hphi_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case233(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case233(xp, r, phi_bar_j, theta_M, z_bar_k) return results -def case234(r, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) - results[:, 0, 0] = Hr_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 0, 1] = Hr_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case234(r, phi_bar_j, theta_M, z_bar_k) - results[:, 1, 0] = Hphi_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 1, 1] = Hphi_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 1, 2] = Hphi_zk_case234(r, phi_bar_j, theta_M, z_bar_k) - results[:, 2, 0] = Hz_ri_case234(r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) - results[:, 2, 1] = Hz_phij_case234(r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case234(r, phi_bar_j, theta_M, z_bar_k) +def case234(xp, r, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) + results[:, 0, 0] = Hr_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 0, 2] = Hr_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 1, 0] = Hphi_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 1, 1] = Hphi_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 0] = Hz_ri_case234(xp, r, phi_bar_j, phi_bar_M, theta_M, z_bar_k) + results[:, 2, 1] = Hz_phij_case234(xp, r, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case234(xp, r, phi_bar_j, theta_M, z_bar_k) return results -def case235(r, r_i, r_bar_i, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): - results = np.zeros((len(r), 3, 3)) +def case235(xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, phi_bar_Mj, theta_M, z_bar_k): + results = xp.zeros(((r.shape[0]), 3, 3)) results[:, 0, 0] = Hr_ri_case235( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) - results[:, 0, 1] = Hr_phij_case235(r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 0, 2] = Hr_zk_case235(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 0, 1] = Hr_phij_case235( + xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k + ) + results[:, 0, 2] = Hr_zk_case235(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) results[:, 1, 0] = Hphi_ri_case235( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k ) results[:, 1, 1] = Hphi_phij_case235( - r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k + xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k ) - results[:, 1, 2] = Hphi_zk_case235(r, r_i, phi_bar_j, theta_M, z_bar_k) + results[:, 1, 2] = Hphi_zk_case235(xp, r, r_i, phi_bar_j, theta_M, z_bar_k) results[:, 2, 0] = Hz_ri_case235( - r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + xp, r, r_i, r_bar_i, phi_bar_j, phi_bar_M, theta_M, z_bar_k + ) + results[:, 2, 1] = Hz_phij_case235( + xp, r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k ) - results[:, 2, 1] = Hz_phij_case235(r, r_i, phi_bar_j, phi_bar_Mj, theta_M, z_bar_k) - results[:, 2, 2] = Hz_zk_case235(r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) + results[:, 2, 2] = Hz_zk_case235(xp, r, r_i, r_bar_i, phi_bar_j, theta_M, z_bar_k) return results @@ -2146,11 +2156,11 @@ def magnet_cylinder_segment_Hfield( >>> import numpy as np >>> import magpylib as magpy >>> B = magpy.core.magnet_cylinder_segment_Hfield( - ... observers=np.array([(1,1,2), (0,0,0)]), - ... dimensions=np.array([(1,2,.1,.2,-1,1), (1,2,.3,.9,0,1)]), - ... magnetizations=np.array([(1e7,.1,.2), (1e6,1.1,2.2)]), + ... observers=xp.array([(1,1,2), (0,0,0)]), + ... dimensions=xp.array([(1,2,.1,.2,-1,1), (1,2,.3,.9,0,1)]), + ... magnetizations=xp.array([(1e7,.1,.2), (1e6,1.1,2.2)]), ... ) - >>> with np.printoptions(precision=3): + >>> with xp.printoptions(precision=3): ... print(B) [[-1948.144 32319.944 17616.886] [14167.65 1419.941 17921.646]] @@ -2161,22 +2171,28 @@ def magnet_cylinder_segment_Hfield( Materials, Volume 559, 1 October 2022, 169482 """ + xp = array_namespace(observers, dimensions, magnetizations) + observers, dimensions, magnetizations = xp_promote( + observers, dimensions, magnetizations, force_floating=True, xp=xp + ) # tile inputs into 8-stacks (boundary cases) - r, phi, z = np.repeat(observers, 8, axis=0).T - r_i = np.repeat(dimensions[:, :2], 4) - phi_j = np.repeat(np.tile(dimensions[:, 2:4], 2), 2) - z_k = np.ravel(np.tile(dimensions[:, 4:6], 4)) - _, phi_M, theta_M = np.repeat(magnetizations, 8, axis=0).T + rphiz = xp.repeat(observers, 8, axis=0).T + r, phi, z = (rphiz[i, ...] for i in range(3)) + r_i = xp.repeat(dimensions[:, :2], 4) + phi_j = xp.repeat(xp.tile(dimensions[:, 2:4], (2,)), 2) + z_k = xp.reshape(xp.tile(dimensions[:, 4:6], (4,)), (-1,)) + rphiz_M = xp.repeat(magnetizations, 8, axis=0).T + _, phi_M, theta_M = (rphiz_M[i, ...] for i in range(3)) # initialize results array with nan - result = np.empty((len(r), 3, 3)) - result[:] = np.nan + result = xp.empty(((r.shape[0]), 3, 3)) + result[:, ...] = xp.nan # cases to evaluate cases = determine_cases(r, phi, z, r_i, phi_j, z_k) # list of all possible cases - excluding the nan-cases 111, 114, 121, 131 - case_id = np.array( + case_id = xp.asarray( [ 112, 113, @@ -2288,11 +2304,15 @@ def magnet_cylinder_segment_Hfield( for cid, cfkt, cargs in zip(case_id, case_fkt, case_args, strict=False): mask = cases == cid if any(mask): - result[mask] = cfkt(*[allargs[aid][mask] for aid in cargs]) + result[mask] = cfkt(xp, *[allargs[aid][mask] for aid in cargs]) # sum up contributions from different boundary cases (ax1) and different face types (ax3) - result = np.reshape(result, (-1, 8, 3, 3)) - result = np.sum(result[:, (1, 2, 4, 7)] - result[:, (0, 3, 5, 6)], axis=(1, 3)) + result = xp.reshape(result, (-1, 8, 3, 3)) + result0 = result[:, 1, ...] - result[:, 0, ...] + result1 = result[:, 2, ...] - result[:, 3, ...] + result2 = result[:, 4, ...] - result[:, 5, ...] + result3 = result[:, 7, ...] - result[:, 6, ...] + result = xp.sum(result0 + result1 + result2 + result3, axis=2) # multiply with magnetization amplitude result = result.T * magnetizations[:, 0] * 1e-7 / MU0 @@ -2312,8 +2332,9 @@ def BHJM_cylinder_segment_internal( Falls back to magnet_cylinder_field whenever the section angles describe the full 360° cylinder. """ + xp = array_namespace(observers, polarization, dimension) - BHfinal = np.zeros_like(observers, dtype=float) + BHfinal = xp.zeros_like(observers, dtype=float) r1, r2, h, phi1, phi2 = dimension.T @@ -2333,7 +2354,7 @@ def BHJM_cylinder_segment_internal( field=field, observers=observers[mask1x], polarization=polarization[mask1x], - dimension=np.c_[2 * r2[mask1x], h[mask1x]], + dimension=xp.c_[2 * r2[mask1x], h[mask1x]], ) # case2a: hollow cylinder <- should be vectorized together with above @@ -2342,7 +2363,7 @@ def BHJM_cylinder_segment_internal( field=field, observers=observers[mask2], polarization=polarization[mask2], - dimension=np.c_[2 * r1[mask2], h[mask2]], + dimension=xp.c_[2 * r1[mask2], h[mask2]], ) return BHfinal From 8634934c9e8d7651ddc0c8587515267494b18800 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 14:00:16 -0500 Subject: [PATCH 14/24] Adds array api support for cylinder core functions --- src/magpylib/_src/fields/field_BH_cylinder.py | 81 +++++++++++-------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cylinder.py b/src/magpylib/_src/fields/field_BH_cylinder.py index 04fa1a52a..8a8457edc 100644 --- a/src/magpylib/_src/fields/field_BH_cylinder.py +++ b/src/magpylib/_src/fields/field_BH_cylinder.py @@ -6,13 +6,18 @@ # pylint: disable = no-name-in-module from __future__ import annotations +import array_api_extra as xpx import numpy as np + +# from scipy.special import ellipe, ellipk +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 -from scipy.special import ellipe, ellipk from magpylib._src.fields.special_cel import cel +from magpylib._src.fields.special_elliptic import ellipe, ellipk from magpylib._src.input_checks import check_field_input from magpylib._src.utility import cart_to_cyl_coordinates, cyl_field_to_cart +from magpylib._src.array_api_utils import xp_promote # CORE @@ -58,22 +63,27 @@ def magnet_cylinder_axial_Bfield(z0: np.ndarray, r: np.ndarray, z: np.ndarray) - ----- Implementation based on Derby, American Journal of Physics 78.3 (2010): 229-235. """ - n = len(z0) + xp = array_namespace(z0, r, z) + z0, r, z = xp_promote(z0, r, z, force_floating=True, xp=xp) + n = z0.shape[0] + z0 = xp.astype(z0, xp.float64) + r = xp.astype(r, xp.float64) + z = xp.astype(z, xp.float64) # some important quantities zph, zmh = z + z0, z - z0 dpr, dmr = 1 + r, 1 - r - sq0 = np.sqrt(zmh**2 + dpr**2) - sq1 = np.sqrt(zph**2 + dpr**2) + sq0 = xp.sqrt(zmh**2 + dpr**2) + sq1 = xp.sqrt(zph**2 + dpr**2) - k1 = np.sqrt((zph**2 + dmr**2) / (zph**2 + dpr**2)) - k0 = np.sqrt((zmh**2 + dmr**2) / (zmh**2 + dpr**2)) + k1 = xp.sqrt((zph**2 + dmr**2) / (zph**2 + dpr**2)) + k0 = xp.sqrt((zmh**2 + dmr**2) / (zmh**2 + dpr**2)) gamma = dmr / dpr - one = np.ones(n) + one = xp.ones(n, dtype=xp.float64) # radial field (unit polarization) - Br = (cel(k1, one, one, -one) / sq1 - cel(k0, one, one, -one) / sq0) / np.pi + Br = (cel(k1, one, one, -one) / sq1 - cel(k0, one, one, -one) / sq0) / xp.pi # axial field (unit polarization) Bz = ( @@ -83,10 +93,10 @@ def magnet_cylinder_axial_Bfield(z0: np.ndarray, r: np.ndarray, z: np.ndarray) - zph * cel(k1, gamma**2, one, gamma) / sq1 - zmh * cel(k0, gamma**2, one, gamma) / sq0 ) - / np.pi + / xp.pi ) - return np.vstack((Br, np.zeros(n), Bz)) + return xp.stack((Br, xp.zeros(n), Bz)) # CORE @@ -145,11 +155,14 @@ def magnet_cylinder_diametral_Hfield( (unpublished). """ # pylint: disable=too-many-statements + xp = array_namespace(z0, r, z, phi) + z0, r, z, phi = xp_promote(z0, r, z, phi, force_floating=True, xp=xp) - n = len(z0) + n = z0.shape[0] # allocate to treat small r special cases - Hr, Hphi, Hz = np.empty((3, n)) + H = xp.empty((3, n)) + Hr, Hphi, Hz = H[0, ...], H[1, ...], H[2, ...] # compute repeated quantities for all cases zp = z + z0 @@ -162,7 +175,7 @@ def magnet_cylinder_diametral_Hfield( # case small_r: numerical instability of general solution mask_small_r = r < 0.05 mask_general = ~mask_small_r - if np.any(mask_small_r): + if xp.any(mask_small_r): phiX = phi[mask_small_r] zpX, zmX = zp[mask_small_r], zm[mask_small_r] zp2X, zm2X = zp2[mask_small_r], zm2[mask_small_r] @@ -171,8 +184,8 @@ def magnet_cylinder_diametral_Hfield( # taylor series for small r zpp = zp2X + 1 zmm = zm2X + 1 - sqrt_p = np.sqrt(zpp) - sqrt_m = np.sqrt(zmm) + sqrt_p = xp.sqrt(zpp) + sqrt_m = xp.sqrt(zmm) frac1 = zpX / sqrt_p frac2 = zmX / sqrt_m @@ -189,12 +202,12 @@ def magnet_cylinder_diametral_Hfield( * r4X ) - Hr[mask_small_r] = -np.cos(phiX) / 4 * (term1 + 9 * term2 + 25 * term3) + Hr[mask_small_r] = -xp.cos(phiX) / 4 * (term1 + 9 * term2 + 25 * term3) - Hphi[mask_small_r] = np.sin(phiX) / 4 * (term1 + 3 * term2 + 5 * term3) + Hphi[mask_small_r] = xp.sin(phiX) / 4 * (term1 + 3 * term2 + 5 * term3) Hz[mask_small_r] = ( - -np.cos(phiX) + -xp.cos(phiX) / 4 * ( rX * (1 / zpp / sqrt_p - 1 / zmm / sqrt_m) @@ -215,12 +228,12 @@ def magnet_cylinder_diametral_Hfield( # if there are small_r, select the general/case variables # when there are no small_r cases it is not necessary to slice with [True, True, Tue,...] phi = phi[mask_general] - n = len(phi) + n = phi.shape[0] zp, zm = zp[mask_general], zm[mask_general] zp2, zm2 = zp2[mask_general], zm2[mask_general] r, r2 = r[mask_general], r2[mask_general] - if np.any(mask_general): + if xp.any(mask_general): rp = r + 1 rm = r - 1 rp2 = rp**2 @@ -228,8 +241,8 @@ def magnet_cylinder_diametral_Hfield( ap2 = zp2 + rm**2 am2 = zm2 + rm**2 - ap = np.sqrt(ap2) - am = np.sqrt(am2) + ap = xp.sqrt(ap2) + am = xp.sqrt(am2) argp = -4 * r / ap2 argm = -4 * r / am2 @@ -238,24 +251,24 @@ def magnet_cylinder_diametral_Hfield( # result is numerically stable in the vicinity of of r=r0 # so only the special case must be caught (not the surroundings) mask_special = rm == 0 - argc = np.ones(n) * 1e16 # should be np.Inf but leads to 1/0 problems in cel + argc = xp.ones(n) * 1e16 # should be np.Inf but leads to 1/0 problems in cel argc[~mask_special] = -4 * r[~mask_special] / rm2[~mask_special] # special case 1/rm - one_over_rm = np.zeros(n) + one_over_rm = xp.zeros(n) one_over_rm[~mask_special] = 1 / rm[~mask_special] elle_p = ellipe(argp) elle_m = ellipe(argm) ellk_p = ellipk(argp) ellk_m = ellipk(argm) - onez = np.ones(n) - ellpi_p = cel(np.sqrt(1 - argp), 1 - argc, onez, onez) # elliptic_Pi - ellpi_m = cel(np.sqrt(1 - argm), 1 - argc, onez, onez) # elliptic_Pi + onez = xp.ones(n) + ellpi_p = cel(xp.sqrt(1 - argp), 1 - argc, onez, onez) # elliptic_Pi + ellpi_m = cel(xp.sqrt(1 - argm), 1 - argc, onez, onez) # elliptic_Pi # compute fields Hr[mask_general] = ( - -np.cos(phi) - / (4 * np.pi * r2) + -xp.cos(phi) + / (4 * xp.pi * r2) * ( -zm * am * elle_m + zp * ap * elle_p @@ -266,8 +279,8 @@ def magnet_cylinder_diametral_Hfield( ) Hphi[mask_general] = ( - np.sin(phi) - / (4 * np.pi * r2) + xp.sin(phi) + / (4 * xp.pi * r2) * ( +zm * am * elle_m - zp * ap * elle_p @@ -279,8 +292,8 @@ def magnet_cylinder_diametral_Hfield( ) Hz[mask_general] = ( - -np.cos(phi) - / (2 * np.pi * r) + -xp.cos(phi) + / (2 * xp.pi * r) * ( +am * elle_m - ap * elle_p @@ -289,7 +302,7 @@ def magnet_cylinder_diametral_Hfield( ) ) - return np.vstack((Hr, Hphi, Hz)) + return xp.stack((Hr, Hphi, Hz)) def BHJM_magnet_cylinder( From 7866d2b65fb21139fa6f4f882711dd296985661a Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 20:46:56 -0500 Subject: [PATCH 15/24] Adds array api support for polyline BHMJ level and current polyline level --- src/magpylib/_src/fields/field_BH_polyline.py | 68 +++++++++---- tests/test_BHMJ_level.py | 99 +++++++++---------- 2 files changed, 95 insertions(+), 72 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_polyline.py b/src/magpylib/_src/fields/field_BH_polyline.py index 21fadafd6..1d70aeb77 100644 --- a/src/magpylib/_src/fields/field_BH_polyline.py +++ b/src/magpylib/_src/fields/field_BH_polyline.py @@ -20,7 +20,7 @@ def current_vertices_field( field: str, observers: np.ndarray, current: np.ndarray, - vertices: np.ndarray = None, + vertices: np.ndarray | list[np.ndarray] | None = None, segment_start=None, # list of mix3 ndarrays segment_end=None, ) -> np.ndarray: @@ -38,6 +38,13 @@ def current_vertices_field( ### Returns: - B-field (ndarray nx3): B-field vectors at pos_obs in units of T """ + arrs = [observers, current, segment_start, segment_end] + if vertices is np.ndarray: + arrs = arrs + [vertices] + elif vertices is list: + arrs = arrs + vertices + xp = array_namespace(*arrs) + if vertices is None: return BHJM_current_polyline( field=field, @@ -47,29 +54,35 @@ def current_vertices_field( segment_end=segment_end, ) - nvs = np.array([f.shape[0] for f in vertices]) # lengths of vertices sets + nvs = xp.asarray([f.shape[0] for f in vertices]) # lengths of vertices sets if all(v == nvs[0] for v in nvs): # if all vertices sets have the same lengths n0, n1, *_ = vertices.shape BH = BHJM_current_polyline( field=field, - observers=np.repeat(observers, n1 - 1, axis=0), - current=np.repeat(current, n1 - 1, axis=0), - segment_start=vertices[:, :-1].reshape(-1, 3), - segment_end=vertices[:, 1:].reshape(-1, 3), + observers=xp.repeat(observers, n1 - 1, axis=0), + current=xp.repeat(current, n1 - 1, axis=0), + segment_start=xp.reshape(vertices[:, :-1], (-1, 3)), + segment_end=xp.reshape(vertices[:, 1:], (-1, 3)), ) - BH = BH.reshape((n0, n1 - 1, 3)) - BH = np.sum(BH, axis=1) + BH = xp.reshape(BH, (n0, n1 - 1, 3)) + BH = xp.sum(BH, axis=1) else: - split_indices = np.cumsum(nvs - 1)[:-1] # remove last to avoid empty split + split_indices = xp.roll( + xp.cumulative_sum(nvs - 1), shift=1 + ) # remove last to avoid empty split + split_indices[0, ...] = 0 + ind1 = (split_indices[i] for i in range(split_indices.shape[0])) + ind2 = (split_indices[1:][i] for i in range(split_indices.shape[0] - 1)) BH = BHJM_current_polyline( field=field, - observers=np.repeat(observers, nvs - 1, axis=0), - current=np.repeat(current, nvs - 1, axis=0), - segment_start=np.concatenate([vert[:-1] for vert in vertices]), - segment_end=np.concatenate([vert[1:] for vert in vertices]), + observers=xp.repeat(observers, nvs - 1, axis=0), + current=xp.repeat(current, nvs - 1, axis=0), + segment_start=xp.concat([vert[:-1, ...] for vert in vertices]), + segment_end=xp.concat([vert[1:, ...] for vert in vertices]), ) - bh_split = np.split(BH, split_indices) - BH = np.array([np.sum(bh, axis=0) for bh in bh_split]) + bh_split = list(BH[slice(i, j), ...] for i, j in zip_longest(ind1, ind2)) + print(len(bh_split)) + BH = xp.asarray([xp.sum(bh, axis=0) for bh in bh_split]) return BH @@ -211,24 +224,37 @@ def BHJM_current_polyline( # pylint: disable=too-many-statements check_field_input(field) + xp = array_namespace(observers, segment_start, segment_end, current) + + if not xp.isdtype(observers.dtype, kind=("real floating", "complex floating")): + observers = xp.astype(observers, xp.float64) + + if not xp.isdtype(segment_start.dtype, kind=("real floating", "complex floating")): + segment_start = xp.astype(segment_start, xp.float64) + + if not xp.isdtype(segment_end.dtype, kind=("real floating", "complex floating")): + segment_end = xp.astype(segment_end, xp.float64) + + if not xp.isdtype(current.dtype, kind=("real floating", "complex floating")): + current = xp.astype(current, xp.float64) - BHJM = np.zeros_like(observers, dtype=float) + BHJM = xp.zeros_like(observers, dtype=xp.float64) if field in "MJ": return BHJM # Check for zero-length segments (or discontinuous) - mask_nan_start = np.isnan(segment_start).all(axis=1) - mask_nan_end = np.isnan(segment_end).all(axis=1) - mask_equal = np.all(segment_start == segment_end, axis=1) + mask_nan_start = xp.all(xp.isnan(segment_start), axis=1) + mask_nan_end = xp.all(xp.isnan(segment_end), axis=1) + mask_equal = xp.all(segment_start == segment_end, axis=1) mask0 = mask_equal | mask_nan_start | mask_nan_end not_mask0 = ~mask0 # avoid multiple computation of ~mask - if np.all(mask0): + if xp.all(mask0): return BHJM # continue only with non-zero segments - if np.any(mask0): + if xp.any(mask0): current = current[not_mask0] segment_start = segment_start[not_mask0] segment_end = segment_end[not_mask0] diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..6f0e3c93f 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -489,18 +489,18 @@ def test_BHJM_circle(): def test_BHJM_current_polyline(): """Test of current polyline field core function""" - vert = np.array([(-1.5, 0, 0), (-0.5, 0, 0), (0.5, 0, 0), (1.5, 0, 0)]) + vert = xp.asarray([(-1.5, 0, 0), (-0.5, 0, 0), (0.5, 0, 0), (1.5, 0, 0)]) kw = { - "observers": np.array([(0, 0, 1)] * 3), - "current": np.array([1, 1, 1]), - "segment_start": vert[:-1], - "segment_end": vert[1:], + "observers": xp.asarray([(0, 0, 1)] * 3), + "current": xp.asarray([1, 1, 1]), + "segment_start": vert[:-1, ...], + "segment_end": vert[1:, ...], } H, B, M, _ = helper_check_HBMJ_consistency(BHJM_current_polyline, **kw) Btest = ( - np.array( + xp.asarray( [ [0.0, -0.03848367, 0.0], [0.0, -0.08944272, 0.0], @@ -512,7 +512,7 @@ def test_BHJM_current_polyline(): np.testing.assert_allclose(B, Btest, rtol=0, atol=1e-7) Htest = ( - np.array( + xp.asarray( [ [0.0, -30624.33145161, 0.0], [0.0, -71176.25434172, 0.0], @@ -591,10 +591,10 @@ def test_field_loop_specials(): def test_field_line_special_cases(): """test line current for all cases""" - c1 = np.array([1]) - po1 = np.array([(1, 2, 3)]) - ps1 = np.array([(0, 0, 0)]) - pe1 = np.array([(2, 2, 2)]) + c1 = xp.asarray([1]) + po1 = xp.asarray([(1, 2, 3)]) + ps1 = xp.asarray([(0, 0, 0)]) + pe1 = xp.asarray([(2, 2, 2)]) # only normal B1 = ( @@ -607,11 +607,11 @@ def test_field_line_special_cases(): ) * 1e6 ) - x1 = np.array([[0.02672612, -0.05345225, 0.02672612]]) + x1 = xp.asarray([[0.02672612, -0.05345225, 0.02672612]]) assert_allclose(x1, B1, rtol=1e-6) # only on_line - po1b = np.array([(1, 1, 1)]) + po1b = xp.asarray([(1, 1, 1)]) B2 = ( BHJM_current_polyline( field="B", @@ -622,7 +622,7 @@ def test_field_line_special_cases(): ) * 1e6 ) - x2 = np.zeros((1, 3)) + x2 = xp.zeros((1, 3)) assert_allclose(x2, B2, rtol=1e-6) # only zero-segment @@ -636,14 +636,14 @@ def test_field_line_special_cases(): ) * 1e6 ) - x3 = np.zeros((1, 3)) + x3 = xp.zeros((1, 3)) assert_allclose(x3, B3, rtol=1e-6) # only on_line and zero_segment - c2 = np.array([1] * 2) - ps2 = np.array([(0, 0, 0)] * 2) - pe2 = np.array([(0, 0, 0), (2, 2, 2)]) - po2 = np.array([(1, 2, 3), (1, 1, 1)]) + c2 = xp.asarray([1] * 2) + ps2 = xp.asarray([(0, 0, 0)] * 2) + pe2 = xp.asarray([(0, 0, 0), (2, 2, 2)]) + po2 = xp.asarray([(1, 2, 3), (1, 1, 1)]) B4 = ( BHJM_current_polyline( field="B", @@ -654,11 +654,11 @@ def test_field_line_special_cases(): ) * 1e6 ) - x4 = np.zeros((2, 3)) + x4 = xp.zeros((2, 3)) assert_allclose(x4, B4, rtol=1e-6) # normal + zero_segment - po2b = np.array([(1, 2, 3), (1, 2, 3)]) + po2b = xp.asarray([(1, 2, 3), (1, 2, 3)]) B5 = ( BHJM_current_polyline( field="B", @@ -669,11 +669,11 @@ def test_field_line_special_cases(): ) * 1e6 ) - x5 = np.array([[0, 0, 0], [0.02672612, -0.05345225, 0.02672612]]) + x5 = xp.asarray([[0, 0, 0], [0.02672612, -0.05345225, 0.02672612]]) assert_allclose(x5, B5, rtol=1e-6) # normal + on_line - pe2b = np.array([(2, 2, 2)] * 2) + pe2b = xp.asarray([(2, 2, 2)] * 2) B6 = ( BHJM_current_polyline( field="B", @@ -684,14 +684,14 @@ def test_field_line_special_cases(): ) * 1e6 ) - x6 = np.array([[0.02672612, -0.05345225, 0.02672612], [0, 0, 0]]) + x6 = xp.asarray([[0.02672612, -0.05345225, 0.02672612], [0, 0, 0]]) assert_allclose(x6, B6, rtol=1e-6) # normal + zero_segment + on_line - c4 = np.array([1] * 3) - ps4 = np.array([(0, 0, 0)] * 3) - pe4 = np.array([(0, 0, 0), (2, 2, 2), (2, 2, 2)]) - po4 = np.array([(1, 2, 3), (1, 2, 3), (1, 1, 1)]) + c4 = xp.asarray([1] * 3) + ps4 = xp.asarray([(0, 0, 0)] * 3) + pe4 = xp.asarray([(0, 0, 0), (2, 2, 2), (2, 2, 2)]) + po4 = xp.asarray([(1, 2, 3), (1, 2, 3), (1, 1, 1)]) B7 = ( BHJM_current_polyline( field="B", @@ -702,7 +702,7 @@ def test_field_line_special_cases(): ) * 1e6 ) - x7 = np.array([[0, 0, 0], [0.02672612, -0.05345225, 0.02672612], [0, 0, 0]]) + x7 = xp.asarray([[0, 0, 0], [0.02672612, -0.05345225, 0.02672612], [0, 0, 0]]) assert_allclose(x7, B7, rtol=1e-6) @@ -734,19 +734,16 @@ def test_field_loop2(): def test_field_line_from_vert(): """test the Polyline field from vertex input""" - observers = np.array([(1, 2, 2), (1, 2, 3), (-1, 0, -3)]) - current = np.array([1, 5, -3]) - - vertices = np.array( - [ - np.array( - [(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (1, 2, 3), (-3, 4, -5)] - ), - np.array([(0, 0, 0), (3, 3, 3), (-3, 4, -5)]), - np.array([(1, 2, 3), (-2, -3, 3), (3, 2, 1), (3, 3, 3)]), - ], - dtype="object", - ) + observers = xp.asarray([(1, 2, 2), (1, 2, 3), (-1, 0, -3)]) + current = xp.asarray([1, 5, -3]) + + vertices = [ + xp.asarray( + [(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (1, 2, 3), (-3, 4, -5)] + ), + xp.asarray([(0, 0, 0), (3, 3, 3), (-3, 4, -5)]), + xp.asarray([(1, 2, 3), (-2, -3, 3), (3, 2, 1), (3, 3, 3)]), + ] B_vert = current_vertices_field( field="B", @@ -759,10 +756,10 @@ def test_field_line_from_vert(): for obs, vert, curr in zip(observers, vertices, current, strict=False): p1 = vert[:-1] p2 = vert[1:] - po = np.array([obs] * (len(vert) - 1)) - cu = np.array([curr] * (len(vert) - 1)) + po = xp.asarray([obs] * (len(vert) - 1)) + cu = xp.asarray([curr] * (len(vert) - 1)) B += [ - np.sum( + xp.sum( BHJM_current_polyline( field="B", observers=po, @@ -773,17 +770,17 @@ def test_field_line_from_vert(): axis=0, ) ] - B = np.array(B) + B = xp.asarray(B) assert_allclose(B_vert, B) def test_field_line_v4(): """test current_line_Bfield() for all cases""" - cur = np.array([1] * 7) - start = np.array([(-1, 0, 0)] * 7) - end = np.array([(1, 0, 0), (-1, 0, 0), (1, 0, 0), (-1, 0, 0)] + [(1, 0, 0)] * 3) - obs = np.array( + cur = xp.asarray([1] * 7) + start = xp.asarray([(-1, 0, 0)] * 7) + end = xp.asarray([(1, 0, 0), (-1, 0, 0), (1, 0, 0), (-1, 0, 0)] + [(1, 0, 0)] * 3) + obs = xp.asarray( [ (0, 0, 1), (0, 0, 0), @@ -804,7 +801,7 @@ def test_field_line_v4(): ) * 1e6 ) - Btest = np.array( + Btest = xp.asarray( [ [0, -0.14142136, 0], [0, 0.0, 0], From 67119c80fe833ccf3e1c0e07cc7a5e88a2eb4c11 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:59:59 -0500 Subject: [PATCH 16/24] Adds array api support to triangular mesh --- .../_src/fields/field_BH_triangularmesh.py | 171 ++++++++++-------- tests/test_BHMJ_level.py | 14 +- 2 files changed, 104 insertions(+), 81 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_triangularmesh.py b/src/magpylib/_src/fields/field_BH_triangularmesh.py index 0ea1d014f..bb86f47cf 100644 --- a/src/magpylib/_src/fields/field_BH_triangularmesh.py +++ b/src/magpylib/_src/fields/field_BH_triangularmesh.py @@ -10,6 +10,7 @@ import numpy as np import scipy.spatial +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.fields.field_BH_triangle import BHJM_triangle @@ -26,13 +27,14 @@ def calculate_centroid(vertices, faces): Returns: numpy.array: The centroid of the mesh """ + xp = array_namespace(vertices, faces) # Calculate the centroids of each triangle - triangle_centroids = np.mean(vertices[faces], axis=1) + triangle_centroids = xp.mean(vertices[faces], axis=1) # Compute the area of each triangle - triangle_areas = 0.5 * np.linalg.norm( - np.cross( + triangle_areas = 0.5 * xp.linalg.norm( + xp.linalg.cross( vertices[faces[:, 1]] - vertices[faces[:, 0]], vertices[faces[:, 2]] - vertices[faces[:, 0]], ), @@ -40,7 +42,7 @@ def calculate_centroid(vertices, faces): ) # Calculate the centroid of the entire mesh - return np.sum(triangle_centroids.T * triangle_areas, axis=1) / np.sum( + return xp.sum(triangle_centroids.T * triangle_areas, axis=1) / xp.sum( triangle_areas ) @@ -58,17 +60,19 @@ def v_norm_proj(a: np.ndarray, b: np.ndarray) -> np.ndarray: return a_dot_b/|a||b| assuming that |a|, |b| > 0 """ + xp = array_namespace(a, b) ab = a * b ab = ab[..., 0] + ab[..., 1] + ab[..., 2] - return ab / np.sqrt(v_norm2(a) * v_norm2(b)) + return ab / xp.sqrt(v_norm2(a) * v_norm2(b)) def v_cross(a: np.ndarray, b: np.ndarray) -> np.ndarray: """ a x b """ - return np.array( + xp = array_namespace(a, b) + return xp.asarray( ( a[:, 1] * b[:, 2] - a[:, 2] * b[:, 1], a[:, 2] * b[:, 0] - a[:, 0] * b[:, 2], @@ -119,11 +123,12 @@ def get_open_edges(faces: np.ndarray) -> bool: Output: open edges """ - edges = np.concatenate([faces[:, 0:2], faces[:, 1:3], faces[:, ::2]], axis=0) + xp = array_namespace(faces) + edges = xp.concat([faces[:, 0:2], faces[:, 1:3], faces[:, ::2]], axis=0) # unique edge pairs and counts how many edges = np.sort(edges, axis=1) - edges_uniq, edge_counts = np.unique(edges, axis=0, return_counts=True) + edges_uniq, edge_counts = xp.unique(edges, axis=0, return_counts=True) # mesh is closed if each edge exists twice return edges_uniq[edge_counts != 2] @@ -146,27 +151,29 @@ def fix_trimesh_orientation(vertices: np.ndarray, faces: np.ndarray) -> np.ndarr faces: np.ndarray, shape (n,3), dtype int, or faces and 1D array of triples Fixed faces """ + xp = array_namespace(vertices, faces) # use first triangle as a seed, this one needs to be oriented via inside check # compute facet orientation (normalized) inwards_mask = get_inwards_mask(vertices, faces) - new_faces = faces.copy() + new_faces = xp.asarray(faces, copy=True) new_faces[inwards_mask] = new_faces[inwards_mask][:, [0, 2, 1]] return new_faces def is_facet_inwards(face, faces): """Return boolean whether facet is pointing inwards, via ray tracing""" + xp = array_namespace(face, faces) v1 = face[0] - face[1] v2 = face[1] - face[2] - orient = np.cross(v1, v2) - orient /= np.linalg.norm(orient) # for single facet numpy is fine + orient = xp.linalg.cross(v1, v2) + orient /= xp.linalg.norm(orient) # for single facet numpy is fine # create a check point by displacing the facet center in facet orientation direction eps = 1e-5 # unfortunately this must be quite a 'large' number :( check_point = face.mean(axis=0) + orient * eps # find out if first point is inwards - return mask_inside_trimesh(np.array([check_point]), faces)[0] + return mask_inside_trimesh(xp.asarray([check_point]), faces)[0] def get_inwards_mask( @@ -265,23 +272,24 @@ def lines_end_in_trimesh(lines: np.ndarray, faces: np.ndarray) -> np.ndarray: https://www.iue.tuwien.ac.at/phd/ertl/node114.html to check if the extended line would pass through the triangular facet """ + xp = array_namespace(lines, faces) # Part 1 --------------------------- - normals = v_cross(faces[:, 0] - faces[:, 2], faces[:, 1] - faces[:, 2]) - normals = np.tile(normals, (len(lines), 1, 1)) + normals = v_cross(faces[:, 0, :] - faces[:, 2, :], faces[:, 1, :] - faces[:, 2, :]) + normals = xp.tile(normals, ((lines.shape[0]), 1, 1)) - l0 = lines[:, 0][:, np.newaxis] # outside points - l1 = lines[:, 1][:, np.newaxis] # possible inside test-points + l0 = lines[:, 0, ...][:, xp.newaxis, ...] # outside points + l1 = lines[:, 1, ...][:, xp.newaxis, ...] # possible inside test-points # test-point might coincide with chosen in-plane reference point (chosen faces[:,2] here). # this then leads to bad projection computation # --> choose other reference points (faces[:,1]) in those specific cases - ref_pts = np.tile(faces[:, 2], (len(lines), 1, 1)) + ref_pts = xp.tile(faces[:, 2, ...], ((lines.shape[0]), 1, 1)) eps = 1e-16 # note: norm square ! coincide = v_norm2(l1 - ref_pts) < eps - if np.any(coincide): - ref_pts2 = np.tile( - faces[:, 1], (len(lines), 1, 1) + if xp.any(coincide): + ref_pts2 = xp.tile( + faces[:, 1, ...], ((lines.shape[0]), 1, 1) ) # <--inefficient tile !!! only small part needed ref_pts[coincide] = ref_pts2[coincide] @@ -290,19 +298,19 @@ def lines_end_in_trimesh(lines: np.ndarray, faces: np.ndarray) -> np.ndarray: eps = 1e-7 # no need to check proj0 for touch because line init pts are outside - plane_touch = np.abs(proj1) < eps + plane_touch = xp.abs(proj1) < eps # print('plane_touch:') # print(plane_touch) - plane_cross = np.sign(proj0) != np.sign(proj1) + plane_cross = xp.sign(proj0) != xp.sign(proj1) # print('plane_cross:') # print(plane_cross) # Part 2 --------------------------- # signed areas (no 0-problem because ss0 is the outside point) - a = faces[:, 0] - l0 - b = faces[:, 1] - l0 - c = faces[:, 2] - l0 + a = faces[:, 0, ...] - l0 + b = faces[:, 1, ...] - l0 + c = faces[:, 2, ...] - l0 d = l1 - l0 area1 = v_dot_cross3d(a, b, d) area2 = v_dot_cross3d(b, c, d) @@ -310,26 +318,26 @@ def lines_end_in_trimesh(lines: np.ndarray, faces: np.ndarray) -> np.ndarray: eps = 1e-12 pass_through_boundary = ( - (np.abs(area1) < eps) | (np.abs(area2) < eps) | (np.abs(area3) < eps) + (xp.abs(area1) < eps) | (xp.abs(area2) < eps) | (xp.abs(area3) < eps) ) # print('pass_through_boundary:') # print(pass_through_boundary) - area1 = np.sign(area1) - area2 = np.sign(area2) - area3 = np.sign(area3) - pass_through_inside = (area1 == area2) * (area2 == area3) + area1 = xp.sign(area1) + area2 = xp.sign(area2) + area3 = xp.sign(area3) + pass_through_inside = (area1 == area2) & (area2 == area3) # print('pass_through_inside:') # print(pass_through_inside) pass_through = pass_through_boundary | pass_through_inside # Part 3 --------------------------- - result_cross = pass_through * plane_cross - result_touch = pass_through * plane_touch + result_cross = pass_through & plane_cross + result_touch = pass_through & plane_touch - inside1 = np.sum(result_cross, axis=1) % 2 != 0 - inside2 = np.any(result_touch, axis=1) + inside1 = xp.count_nonzero(result_cross, axis=1) % 2 != 0 + inside2 = xp.any(result_touch, axis=1) return inside1 | inside2 @@ -349,6 +357,7 @@ def segments_intersect_facets(segments, facets, eps=1e-6): Point to point tolerance detection. Must be strictly positive, otherwise some triangles may be detected as intersecting themselves. """ + xp = array_namespace(segments, facets) if eps <= 0: # pragma: no cover msg = "eps must be strictly positive" raise ValueError(msg) @@ -356,27 +365,27 @@ def segments_intersect_facets(segments, facets, eps=1e-6): s, t = segments.swapaxes(0, 1), facets.swapaxes(0, 1) # compute the normals to each triangle - normals = np.cross(t[2] - t[0], t[2] - t[1]) - normals /= np.linalg.norm(normals, axis=1, keepdims=True) + normals = xp.linalg.cross(t[2] - t[0], t[2] - t[1]) + normals /= xp.linalg.norm(normals, axis=1, keepdims=True) # get sign of each segment endpoint, if the sign changes then we know this # segment crosses the plane which contains a triangle. If the value is zero # the endpoint of the segment lies on the plane. - g1 = np.sum(normals * (s[0] - t[2]), axis=1) - g2 = np.sum(normals * (s[1] - t[2]), axis=1) + g1 = xp.sum(normals * (s[0] - t[2]), axis=1) + g2 = xp.sum(normals * (s[1] - t[2]), axis=1) # determine segments which cross the plane of a triangle. # -> 1 if the sign of the end points of s is # different AND one of end points of s is not a vertex of t - cross = (np.sign(g1) != np.sign(g2)) * (np.abs(g1) > eps) * (np.abs(g2) > eps) + cross = (xp.sign(g1) != xp.sign(g2)) * (xp.abs(g1) > eps) * (xp.abs(g2) > eps) v = [] # get signed volumes for i, j in zip((0, 1, 2), (1, 2, 0), strict=False): - sv = np.sum((t[i] - s[1]) * np.cross(t[j] - s[1], s[0] - s[1]), axis=1) - v.append(np.sign(sv)) + sv = xp.sum((t[i] - s[1]) * xp.linalg.cross(t[j] - s[1], s[0] - s[1]), axis=1) + v.append(xp.sign(sv)) # same volume if s and t have same sign in v0, v1 and v2 - same_volume = np.logical_and((v[0] == v[1]), (v[1] == v[2])) + same_volume = xp.logical_and((v[0] == v[1]), (v[1] == v[2])) return cross * same_volume @@ -406,28 +415,29 @@ def get_intersecting_triangles(vertices, triangles, r=None, r_factor=1.5, eps=1e Point to point tolerance detection. Must be strictly positive, otherwise some triangles may be detected as intersecting themselves. """ + xp = array_namespace(vertices, triangles) if r_factor < 1: # pragma: no cover msg = "r_factor must be greater or equal to 1" raise ValueError(msg) - vertices = vertices.astype(np.float32) + vertices = xp.astype(vertices, xp.float32) facets = vertices[triangles] - centers = np.mean(facets, axis=1) + centers = xp.mean(facets, axis=1) if r is None: - r = r_factor * np.sqrt(((facets - centers[:, None, :]) ** 2).sum(-1)).max() + r = r_factor * xp.sqrt(((facets - centers[:, None, :]) ** 2).sum(-1)).max() kdtree = scipy.spatial.KDTree(centers) near = kdtree.query_ball_point(centers, r, return_sorted=False, workers=-1) - tria1 = np.concatenate(near) - tria2 = np.repeat(np.arange(len(near)), [len(n) for n in near]) + tria1 = xp.concat(near) + tria2 = xp.repeat(xp.arange(len(near)), [len(n) for n in near]) pairs = np.stack([tria1, tria2], axis=1) pairs = pairs[pairs[:, 0] != pairs[:, 1]] # remove check against itself f1, f2 = facets[pairs[:, 0]], facets[pairs[:, 1]] sums = 0 for inds in [[0, 1], [1, 2], [2, 0]]: sums += segments_intersect_facets(f1[:, inds], f2, eps=eps) - return np.unique(pairs[sums > 0]) + return xp.unique(pairs[sums > 0]) def mask_inside_enclosing_box(points: np.ndarray, vertices: np.ndarray) -> np.ndarray: @@ -444,9 +454,13 @@ def mask_inside_enclosing_box(points: np.ndarray, vertices: np.ndarray) -> np.nd ------- ndarray, boolean, shape (n,) """ - xmin, ymin, zmin = np.min(vertices, axis=0) - xmax, ymax, zmax = np.max(vertices, axis=0) - x, y, z = points.T + xp = array_namespace(points, vertices) + points = xp.astype(points, xp.float64) + Xmin = xp.min(vertices, axis=0) + Xmax = xp.max(vertices, axis=0) + xmin, ymin, zmin = (Xmin[i, ...] for i in range(3)) + xmax, ymax, zmax = (Xmax[i, ...] for i in range(3)) + x, y, z = (points[:, i] for i in range(3)) eps = 1e-12 mx = (x < xmax + eps) & (x > xmin - eps) @@ -474,18 +488,19 @@ def mask_inside_trimesh(points: np.ndarray, faces: np.ndarray) -> np.ndarray: Method: ray-tracing. Faces must form a closed mesh for this to work. """ - vertices = faces.reshape((-1, 3)) + xp = array_namespace(points, faces) + vertices = xp.reshape(faces, (-1, 3)) # test-points inside of enclosing box mask_inside = mask_inside_enclosing_box(points, vertices) pts_in_box = points[mask_inside] # create test-lines from outside to test-points - start_point_outside = np.min(vertices, axis=0) - np.array( + start_point_outside = xp.min(vertices, axis=0) - xp.asarray( [12.0012345, 5.9923456, 6.9932109] ) - test_lines = np.tile(start_point_outside, (len(pts_in_box), 2, 1)) - test_lines[:, 1] = pts_in_box + test_lines = xp.tile(start_point_outside, ((pts_in_box.shape[0]), 2, 1)) + test_lines[:, 1, ...] = pts_in_box # check if test-points are inside using ray tracing mask_inside2 = lines_end_in_trimesh(test_lines, faces) @@ -507,26 +522,30 @@ def BHJM_magnet_trimesh( - Closed meshes are assumed (input comes only from TriangularMesh class) - Field computations via publication: Guptasarma: GEOPHYSICS 1999 64:1, 70-74 """ + xp = array_namespace(observers, mesh, polarization) + polarization = xp.astype(polarization, xp.float64) if field in "BH": if mesh.ndim != 1: # all vertices objects have same number of children n0, n1, *_ = mesh.shape - vertices_tiled = mesh.reshape(-1, 3, 3) - observers_tiled = np.repeat(observers, n1, axis=0) - polarization_tiled = np.repeat(polarization, n1, axis=0) + vertices_tiled = xp.reshape(mesh, (-1, 3, 3)) + observers_tiled = xp.repeat(observers, n1, axis=0) + polarization_tiled = xp.repeat(polarization, n1, axis=0) BHJM = BHJM_triangle( field="B", observers=observers_tiled, vertices=vertices_tiled, polarization=polarization_tiled, ) - BHJM = BHJM.reshape((n0, n1, 3)) - BHJM = np.sum(BHJM, axis=1) + BHJM = xp.reshape(BHJM, (n0, n1, 3)) + BHJM = xp.sum(BHJM, axis=1) else: nvs = [f.shape[0] for f in mesh] # length of vertex set - split_indices = np.cumsum(nvs)[:-1] # remove last to avoid empty split - vertices_tiled = np.concatenate([f.reshape((-1, 3, 3)) for f in mesh]) - observers_tiled = np.repeat(observers, nvs, axis=0) - polarization_tiled = np.repeat(polarization, nvs, axis=0) + split_indices = xp.cumulative_sum(xp.asarray(nvs))[ + :-1 + ] # remove last to avoid empty split + vertices_tiled = xp.concat([xp.reshape(f, (-1, 3, 3)) for f in mesh]) + observers_tiled = xp.repeat(observers, nvs, axis=0) + polarization_tiled = xp.repeat(polarization, nvs, axis=0) BHJM = BHJM_triangle( field="B", observers=observers_tiled, @@ -534,9 +553,9 @@ def BHJM_magnet_trimesh( polarization=polarization_tiled, ) b_split = np.split(BHJM, split_indices) - BHJM = np.array([np.sum(bh, axis=0) for bh in b_split]) + BHJM = xp.asarray([xp.sum(bh, axis=0) for bh in b_split]) else: - BHJM = np.zeros_like(observers, dtype=float) + BHJM = xp.zeros_like(observers, dtype=xp.float64) if field == "H": return BHJM / MU0 @@ -544,22 +563,22 @@ def BHJM_magnet_trimesh( if in_out == "auto": prev_ind = 0 # group similar meshes for inside-outside evaluation and adding B - for new_ind_item, _ in enumerate(BHJM): + for new_ind_item in range(BHJM.shape[0]): new_ind = new_ind_item if ( - new_ind == len(BHJM) - 1 - or mesh[new_ind].shape != mesh[prev_ind].shape - or not np.all(mesh[new_ind] == mesh[prev_ind]) + new_ind == (BHJM.shape[0]) - 1 + or mesh[new_ind, ...].shape != mesh[prev_ind, ...].shape + or not xp.all(mesh[new_ind, ...] == mesh[prev_ind, ...]) ): - if new_ind == len(BHJM) - 1: - new_ind = len(BHJM) + if new_ind == (BHJM.shape[0]) - 1: + new_ind = BHJM.shape[0] mask_inside = mask_inside_trimesh( - observers[prev_ind:new_ind], mesh[prev_ind] + observers[prev_ind:new_ind, ...], mesh[prev_ind, ...] ) # if inside magnet add polarization vector - BHJM[prev_ind:new_ind][mask_inside] += polarization[prev_ind:new_ind][ - mask_inside - ] + BHJM[prev_ind:new_ind, ...][mask_inside] += polarization[ + prev_ind:new_ind, ... + ][mask_inside] prev_ind = new_ind elif in_out == "inside": BHJM += polarization diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..68b7ec775 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -756,11 +756,15 @@ def test_field_line_from_vert(): ) B = [] - for obs, vert, curr in zip(observers, vertices, current, strict=False): - p1 = vert[:-1] - p2 = vert[1:] - po = np.array([obs] * (len(vert) - 1)) - cu = np.array([curr] * (len(vert) - 1)) + for i_obs, vert, i_curr in zip( + range(observers.shape[0]), vertices, range(current.shape[0]), strict=False + ): + obs = observers[i_obs, ...] + curr = current[i_curr, ...] + p1 = vert[:-1, ...] + p2 = vert[1:, ...] + po = xp.asarray([obs] * ((vert.shape[0]) - 1)) + cu = xp.asarray([curr] * ((vert.shape[0]) - 1)) B += [ np.sum( BHJM_current_polyline( From f8166bc1bf85852903c04725a0c2544bfb925061 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:59:34 -0500 Subject: [PATCH 17/24] Adds array api support to tetrahedron --- .../_src/fields/field_BH_tetrahedron.py | 67 ++++++++++--------- tests/test_BHMJ_level.py | 12 ++-- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_tetrahedron.py b/src/magpylib/_src/fields/field_BH_tetrahedron.py index 9e7a2bf0b..8a5184a14 100644 --- a/src/magpylib/_src/fields/field_BH_tetrahedron.py +++ b/src/magpylib/_src/fields/field_BH_tetrahedron.py @@ -6,6 +6,7 @@ from __future__ import annotations import numpy as np +from array_api_compat import array_namespace from scipy.constants import mu_0 as MU0 from magpylib._src.fields.field_BH_triangle import BHJM_triangle @@ -27,17 +28,17 @@ def check_chirality(points: np.ndarray) -> np.ndarray: new list of points, where p2 and p3 are possibly exchanged so that all tetrahedron is given in a right-handed system. """ - - vecs = np.zeros((len(points), 3, 3)) + xp = array_namespace(points) + vecs = xp.zeros(((points.shape[0]), 3, 3)) vecs[:, :, 0] = points[:, 1, :] - points[:, 0, :] vecs[:, :, 1] = points[:, 2, :] - points[:, 0, :] vecs[:, :, 2] = points[:, 3, :] - points[:, 0, :] - dets = np.linalg.det(vecs) + dets = xp.linalg.det(vecs) dets_neg = dets < 0 - if np.any(dets_neg): - points[dets_neg, 2:, :] = points[dets_neg, 3:1:-1, :] + if xp.any(dets_neg): + points[:, 2:, :][dets_neg] = points[:, 3:1:-1, :][dets_neg] return points @@ -47,22 +48,25 @@ def point_inside(points: np.ndarray, vertices: np.ndarray, in_out: str) -> np.nd Takes points, as well as the vertices of a tetrahedra. Returns boolean array indicating whether the points are inside the tetrahedra. """ + xp = array_namespace(points, vertices) if in_out == "inside": - return np.array([True] * len(points)) + return xp.repeat(True, (points.shape[0])) if in_out == "outside": - return np.array([False] * len(points)) - - mat = vertices[:, 1:].swapaxes(0, 1) - vertices[:, 0] - mat = np.transpose(mat.swapaxes(0, 1), (0, 2, 1)) - - tetra = np.linalg.inv(mat) - newp = np.matmul(tetra, np.reshape(points - vertices[:, 0, :], (*points.shape, 1))) - return ( - np.all(newp >= 0, axis=1) - & np.all(newp <= 1, axis=1) - & (np.sum(newp, axis=1) <= 1) - ).flatten() + return xp.repeat(False, (points.shape[0])) + + mat = xp.moveaxis(vertices[:, 1:, ...], (0, 1), (1, 0)) - vertices[:, 0, ...] + mat = xp.moveaxis(mat, (0, 1, 2), (2, 0, 1)) + + mat = xp.astype(mat, xp.float64) + tetra = xp.linalg.inv(mat) + newp = xp.matmul(tetra, xp.reshape(points - vertices[:, 0, :], (*points.shape, 1))) + return xp.reshape( + xp.all(newp >= 0, axis=1) + & xp.all(newp <= 1, axis=1) + & (xp.sum(newp, axis=1) <= 1), + (-1,), + ) def BHJM_magnet_tetrahedron( @@ -79,9 +83,10 @@ def BHJM_magnet_tetrahedron( """ check_field_input(field) + xp = array_namespace(observers, vertices, polarization) # allocate - try not to generate more arrays - BHJM = polarization.astype(float) + BHJM = xp.astype(polarization, (xp.float64)) if field == "J": mask_inside = point_inside(observers, vertices, in_out) @@ -95,27 +100,27 @@ def BHJM_magnet_tetrahedron( vertices = check_chirality(vertices) - tri_vertices = np.concatenate( + tri_vertices = xp.concat( ( - vertices[:, (0, 2, 1), :], - vertices[:, (0, 1, 3), :], - vertices[:, (1, 2, 3), :], - vertices[:, (0, 3, 2), :], + xp.concat(tuple(vertices[:, i : i + 1, :] for i in (0, 2, 1)), axis=1), + xp.concat(tuple(vertices[:, i : i + 1, :] for i in (0, 1, 3)), axis=1), + xp.concat(tuple(vertices[:, i : i + 1, :] for i in (1, 2, 3)), axis=1), + xp.concat(tuple(vertices[:, i : i + 1, :] for i in (0, 3, 2)), axis=1), ), axis=0, ) tri_field = BHJM_triangle( field=field, - observers=np.tile(observers, (4, 1)), + observers=xp.tile(observers, (4, 1)), vertices=tri_vertices, - polarization=np.tile(polarization, (4, 1)), + polarization=xp.tile(polarization, (4, 1)), ) - n = len(observers) + n = observers.shape[0] BHJM = ( # slightly faster than reshape + sum - tri_field[:n] - + tri_field[n : 2 * n] - + tri_field[2 * n : 3 * n] - + tri_field[3 * n :] + tri_field[:n, ...] + + tri_field[n : 2 * n, ...] + + tri_field[2 * n : 3 * n, ...] + + tri_field[3 * n :, ...] ) if field == "H": diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..78bfd01a6 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -321,7 +321,7 @@ def test_BHJM_triangle_BH(): def test_magnet_tetrahedron_field_BH(): """Test of tetrahedron field core function""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -330,7 +330,7 @@ def test_magnet_tetrahedron_field_BH(): (3, 2, 1), # inside ] ) - vert = np.array( + vert = xp.asarray( [ [(0, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, 1)], [(0, 0, 0), (0, 1, 0), (1, 0, 0), (0, 0, 1)], @@ -339,7 +339,7 @@ def test_magnet_tetrahedron_field_BH(): [(-10, 0, -10), (10, 10, -10), (10, -10, -10), (0, 0, 10)], ] ) - obs = np.array( + obs = xp.asarray( [ (1, 1, 1), (1, 1, 1), @@ -424,9 +424,9 @@ def test_BHJM_magnet_trimesh_BH(): [-0.47620221972465515, -0.0791524201631546, 0.8757661581039429], ], ] - mesh = np.array([mesh1, mesh2]) - pol = np.array([(1, 2, 3), (3, 2, 1)]) - obs = np.array([(1, 2, 3), (0, 0, 0)]) + mesh = xp.asarray([mesh1, mesh2]) + pol = xp.asarray([(1, 2, 3), (3, 2, 1)]) + obs = xp.asarray([(1, 2, 3), (0, 0, 0)]) kw = { "observers": obs, "polarization": pol, From 394ec413d191d6f6c5b055e3a7646765a87d9e0a Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 6 May 2025 11:12:13 -0500 Subject: [PATCH 18/24] Adds add array api support for dipole in BHMJ layer --- src/magpylib/_src/fields/field_BH_dipole.py | 15 +++++++++------ tests/test_BHMJ_level.py | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_dipole.py b/src/magpylib/_src/fields/field_BH_dipole.py index 201f008b0..a1602e4fa 100644 --- a/src/magpylib/_src/fields/field_BH_dipole.py +++ b/src/magpylib/_src/fields/field_BH_dipole.py @@ -77,18 +77,21 @@ def B(dotprod, observers, moments, r): return H -def BHJM_dipole( - field: str, - observers: np.ndarray, - moment: np.ndarray, -) -> np.ndarray: +def BHJM_dipole(field: str, observers: np.ndarray, moment: np.ndarray) -> np.ndarray: """ - translate dipole field to BHJM """ check_field_input(field) + xp = array_namespace(observers, moment) + + if not xp.isdtype(observers.dtype, kind=("real floating", "complex floating")): + observers = xp.astype(observers, xp.float64) + + if not xp.isdtype(moment.dtype, kind=("real floating", "complex floating")): + moment = xp.astype(moment, xp.float64) if field in "MJ": - return np.zeros_like(observers, dtype=float) + return xp.zeros_like(observers, dtype=xp.float32) BHJM = dipole_Hfield( observers=observers, diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..712f236c7 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -529,11 +529,11 @@ def test_BHJM_current_polyline(): def test_BHJM_dipole(): """Test of dipole field core function""" - pol = np.array([(0, 0, 1), (1, 0, 1), (-1, 0.321, 0.123)]) + pol = xp.asarray([(0, 0, 1), (1, 0, 1), (-1, 0.321, 0.123)]) kw = { - "observers": np.array([(1, 2, 3), (-1, -2, -3), (3, 3, -1)]), - "moment": pol * 4 * np.pi / 3 / MU0, + "observers": xp.asarray([(1, 2, 3), (-1, -2, -3), (3, 3, -1)]), + "moment": pol * 4 * xp.pi / 3 / MU0, } H, B, M, _ = helper_check_HBMJ_consistency(BHJM_dipole, **kw) From 6a3d35e9c49f08c372ad3b0979040a8b65af09e3 Mon Sep 17 00:00:00 2001 From: purepani Date: Tue, 6 May 2025 18:35:38 -0500 Subject: [PATCH 19/24] Adds array api support for sphere BHMJ level --- src/magpylib/_src/fields/field_BH_sphere.py | 32 +++++++-------------- tests/test_BHMJ_level.py | 6 ++-- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_sphere.py b/src/magpylib/_src/fields/field_BH_sphere.py index a9c64c446..8025baad5 100644 --- a/src/magpylib/_src/fields/field_BH_sphere.py +++ b/src/magpylib/_src/fields/field_BH_sphere.py @@ -109,34 +109,22 @@ def BHJM_magnet_sphere( Would require 2 checks, or forwarding the masks ... both not ideal """ check_field_input(field) - - x, y, z = np.copy(observers.T) - r = np.sqrt(x**2 + y**2 + z**2) # faster than np.linalg.norm - r_sphere = abs(diameter) / 2 - + xp = array_namespace(observers, diameter, polarization) + diameter = xp.astype(diameter, xp.float64) # inside field & allocate - BHJM = polarization.astype(float) + r = xp.sqrt(xp.vecdot(observers, observers)) + r_sphere = xp.divide(xp.abs(diameter), 2.0) + polarization = xp.astype(polarization, xp.float64) out = r > r_sphere if field == "J": - BHJM[out] = 0.0 - return BHJM + polarization[out] = 0.0 + return polarization if field == "M": - BHJM[out] = 0.0 - return BHJM / MU0 - - BHJM *= 2 / 3 - - BHJM[out] = ( - ( - 3 * np.sum(polarization[out] * observers[out], axis=1) * observers[out].T - - polarization[out].T * r[out] ** 2 - ) - / r[out] ** 5 - * r_sphere[out] ** 3 - / 3 - ).T + polarization[out] = 0.0 + return polarization / MU0 + BHJM = magnet_sphere_Bfield(observers, diameter, polarization) if field == "B": return BHJM diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..fad1c3f7e 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -166,7 +166,7 @@ def test_BHJM_magnet_cylinder(): def test_BHJM_magnet_sphere(): """test BHJM_magnet_sphere""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -174,8 +174,8 @@ def test_BHJM_magnet_sphere(): (2, 3, -1), ] ) - dia = np.array([1, 2, 3, 4]) - obs = np.array( + dia = xp.asarray([1, 2, 3, 4]) + obs = xp.asarray( [ (1, 2, 3), (1, -1, 0), From 30338c793f168ff4d27dcc0f367d8d455fbd3ecb Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 14:00:16 -0500 Subject: [PATCH 20/24] Adds array api support for cylinder in BHMJ layer --- src/magpylib/_src/fields/field_BH_cylinder.py | 75 ++++++++++++------- tests/test_BHMJ_level.py | 8 +- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cylinder.py b/src/magpylib/_src/fields/field_BH_cylinder.py index 8a8457edc..f4062c715 100644 --- a/src/magpylib/_src/fields/field_BH_cylinder.py +++ b/src/magpylib/_src/fields/field_BH_cylinder.py @@ -317,10 +317,15 @@ def BHJM_magnet_cylinder( """ check_field_input(field) + xp = array_namespace(observers, dimension, polarization) + observers = xp.astype(observers, xp.float64) + dimension = xp.astype(dimension, xp.float64) + polarization = xp.astype(polarization, xp.float64) # transform to Cy CS -------------------------------------------- r, phi, z = cart_to_cyl_coordinates(observers) - r0, z0 = dimension.T / 2 + dims = dimension.T / 2 + r0, z0 = dims[0, ...], dims[1, ...] # scale invariance (make dimensionless) r = r / r0 @@ -328,10 +333,10 @@ def BHJM_magnet_cylinder( z0 = z0 / r0 # allocate for output - BHJM = polarization.astype(float) + BHJM = xp.astype(polarization, (xp.float64)) # inside/outside - mask_between_bases = np.abs(z) <= z0 # in-between top and bottom plane + mask_between_bases = xp.abs(z) <= z0 # in-between top and bottom plane mask_inside_hull = r <= 1 # inside Cylinder hull plane mask_inside = mask_between_bases & mask_inside_hull @@ -344,17 +349,23 @@ def BHJM_magnet_cylinder( return BHJM / MU0 # SPECIAL CASE 1: on Cylinder edge - mask_on_hull = np.isclose(r, 1, rtol=1e-15, atol=0) # on Cylinder hull plane - mask_on_bases = np.isclose(abs(z), z0, rtol=1e-15, atol=0) # on top or bottom plane + mask_on_hull = xpx.isclose(r, 1, rtol=1e-15, atol=0) # on Cylinder hull plane + mask_on_bases = xpx.isclose( + abs(z), z0, rtol=1e-15, atol=0 + ) # on top or bottom plane mask_not_on_edge = ~(mask_on_hull & mask_on_bases) # axial/transv polarization cases - pol_x, pol_y, pol_z = polarization.T + pol_x, pol_y, pol_z = ( + polarization[..., 0], + polarization[..., 1], + polarization[..., 2], + ) mask_pol_tv = (pol_x != 0) | (pol_y != 0) mask_pol_ax = pol_z != 0 # SPECIAL CASE 2: pol = 0 - mask_pol_not_null = ~((pol_x == 0) * (pol_y == 0) * (pol_z == 0)) + mask_pol_not_null = (pol_x != 0) | (pol_y != 0) | (pol_z != 0) # general case mask_gen = mask_pol_not_null & mask_not_on_edge @@ -367,9 +378,9 @@ def BHJM_magnet_cylinder( BHJM *= 0 # transversal polarization contributions ----------------------- - if any(mask_pol_tv): - pol_xy = np.sqrt(pol_x**2 + pol_y**2)[mask_pol_tv] - tetta = np.arctan2(pol_y[mask_pol_tv], pol_x[mask_pol_tv]) + if xp.any(mask_pol_tv): + pol_xy = xp.sqrt(pol_x**2 + pol_y**2)[mask_pol_tv] + tetta = xp.atan2(pol_y[mask_pol_tv], pol_x[mask_pol_tv]) BHJM[mask_pol_tv] = ( magnet_cylinder_diametral_Hfield( @@ -382,30 +393,42 @@ def BHJM_magnet_cylinder( ).T # axial polarization contributions ---------------------------- - if any(mask_pol_ax): - BHJM[mask_pol_ax] += ( - magnet_cylinder_axial_Bfield( - z0=z0[mask_pol_ax], - r=r[mask_pol_ax], - z=z[mask_pol_ax], - ) - * pol_z[mask_pol_ax] - ).T + if xp.any(mask_pol_ax): + BHJM[mask_pol_ax] = ( + BHJM[mask_pol_ax] + + ( + magnet_cylinder_axial_Bfield( + z0=z0[mask_pol_ax], + r=r[mask_pol_ax], + z=z[mask_pol_ax], + ) + * pol_z[mask_pol_ax] + ).T + ) BHJM[:, 0], BHJM[:, 1] = cyl_field_to_cart(phi, BHJM[:, 0], BHJM[:, 1]) # add/subtract Mag when inside for B/H if field == "B": - mask_tv_inside = mask_pol_tv * mask_inside - if any(mask_tv_inside): # tv computes H-field - BHJM[mask_tv_inside, 0] += pol_x[mask_tv_inside] - BHJM[mask_tv_inside, 1] += pol_y[mask_tv_inside] + mask_tv_inside = mask_pol_tv & mask_inside + mask_tv_inside = xp.broadcast_to(mask_tv_inside[:, xp.newaxis], BHJM.shape) + mask_tv_inside = xpx.at(mask_tv_inside)[:, 2].set(False, copy=True) + + if xp.any(mask_tv_inside): # tv computes H-field + BHJM = xpx.at(BHJM)[mask_tv_inside].set( + BHJM[mask_tv_inside] + polarization[mask_tv_inside] + ) + # BHJM[:, 1] += pol_y * mask_tv_inside return BHJM if field == "H": - mask_ax_inside = mask_pol_ax * mask_inside - if any(mask_ax_inside): # ax computes B-field - BHJM[mask_ax_inside, 2] -= pol_z[mask_ax_inside] + mask_ax_inside = mask_pol_ax & mask_inside + mask_ax_inside = xp.broadcast_to(mask_ax_inside[:, xp.newaxis], BHJM.shape) + mask_ax_inside = xpx.at(mask_ax_inside)[:, :2].set(False, copy=True) + if xp.any(mask_ax_inside): # ax computes B-field + BHJM = xpx.at(BHJM)[mask_ax_inside].set( + BHJM[mask_ax_inside] - polarization[mask_ax_inside] + ) return BHJM / MU0 msg = f"`output_field_type` must be one of ('B', 'H', 'M', 'J'), got {field!r}" diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..c365c1f74 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -108,7 +108,7 @@ def test_BHJM_magnet_cuboid(): def test_BHJM_magnet_cylinder(): """test cylinder field computation""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -116,7 +116,7 @@ def test_BHJM_magnet_cylinder(): (1, 1, 1), ] ) - dim = np.array( + dim = xp.asarray( [ (1, 2), (2, 2), @@ -124,7 +124,7 @@ def test_BHJM_magnet_cylinder(): (3, 3), ] ) - obs = np.array( + obs = xp.asarray( [ (1, 2, 3), (1, -1, 0), @@ -156,7 +156,7 @@ def test_BHJM_magnet_cylinder(): ] np.testing.assert_allclose(H, Htest) - Jtest = np.array([(0, 0, 0), (0, 0, 0), (0, 0, 0), (1, 1, 1)]) + Jtest = xp.asarray([(0, 0, 0), (0, 0, 0), (0, 0, 0), (1, 1, 1)]) np.testing.assert_allclose(J, Jtest) # H_inout = BHJM_magnet_cylinder(field="H", in_out="outside", **kw) From bd375a12e93f63da1e209cbaa46859bb4e0031e6 Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 14:00:10 -0500 Subject: [PATCH 21/24] Adds array api support for circle BHMJ layer --- src/magpylib/_src/fields/field_BH_circle.py | 24 +++++++++------- src/magpylib/_src/utility.py | 15 ++++++---- tests/test_BHMJ_level.py | 32 ++++++++++----------- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_circle.py b/src/magpylib/_src/fields/field_BH_circle.py index 46c87e32d..80a4ecabf 100644 --- a/src/magpylib/_src/fields/field_BH_circle.py +++ b/src/magpylib/_src/fields/field_BH_circle.py @@ -6,9 +6,8 @@ from __future__ import annotations import numpy as np -from scipy.constants import mu_0 as MU0 - from array_api_compat import array_namespace +from scipy.constants import mu_0 as MU0 from magpylib._src.fields.special_cel import cel_iter from magpylib._src.input_checks import check_field_input @@ -114,34 +113,39 @@ def BHJM_circle( - treat special cases """ + xp = array_namespace(observers, diameter, current) + observers = xp.astype(observers, xp.float64) + diameter = xp.astype(diameter, xp.float64) + current = xp.astype(current, xp.float64) + # allocate - BHJM = np.zeros_like(observers, dtype=float) + BHJM = xp.zeros_like(observers, dtype=xp.float64) check_field_input(field) if field in "MJ": return BHJM r, phi, z = cart_to_cyl_coordinates(observers) - r0 = np.abs(diameter / 2) + r0 = xp.abs(diameter / 2) # Special cases: # case1: loop radius is 0 -> return (0,0,0) mask1 = r0 == 0 # case2: at singularity -> return (0,0,0) - mask2 = np.logical_and(abs(r - r0) < 1e-15 * r0, z == 0) + mask2 = xp.logical_and(xp.abs(r - r0) < 1e-15 * r0, z == 0) # case3: r=0 mask3 = r == 0 - if np.any(mask3): - mask4 = mask3 * ~mask1 # only relevant if not also case1 - BHJM[mask4, 2] = ( + if xp.any(mask3): + mask4 = mask3 & ~mask1 # only relevant if not also case1 + BHJM[:, 2][mask4] = ( (r0[mask4] ** 2 / (z[mask4] ** 2 + r0[mask4] ** 2) ** (3 / 2)) * current[mask4] * 0.5 ) # general case - mask5 = ~np.logical_or(np.logical_or(mask1, mask2), mask3) - if np.any(mask5): + mask5 = ~xp.logical_or(xp.logical_or(mask1, mask2), mask3) + if xp.any(mask5): BHJM[mask5] = current_circle_Hfield( r0=r0[mask5], r=r[mask5], diff --git a/src/magpylib/_src/utility.py b/src/magpylib/_src/utility.py index 6d145d0cf..8c24881db 100644 --- a/src/magpylib/_src/utility.py +++ b/src/magpylib/_src/utility.py @@ -13,6 +13,7 @@ from math import log10 import numpy as np +from array_api_compat import array_namespace from magpylib._src.exceptions import MagpylibBadUserInput @@ -328,8 +329,9 @@ def cart_to_cyl_coordinates(observer): cartesian observer positions to cylindrical coordinates observer: ndarray, shape (n,3) """ - x, y, z = observer.T - r, phi = np.sqrt(x**2 + y**2), np.arctan2(y, x) + xp = array_namespace(observer) + x, y, z = observer[..., 0], observer[..., 1], observer[..., 2] + r, phi = xp.sqrt(x**2 + y**2), xp.atan2(y, x) return r, phi, z @@ -337,12 +339,13 @@ def cyl_field_to_cart(phi, Br, Bphi=None): """ transform Br,Bphi to Bx, By """ + xp = array_namespace(phi, Br, Bphi) if Bphi is not None: - Bx = Br * np.cos(phi) - Bphi * np.sin(phi) - By = Br * np.sin(phi) + Bphi * np.cos(phi) + Bx = Br * xp.cos(phi) - Bphi * xp.sin(phi) + By = Br * xp.sin(phi) + Bphi * xp.cos(phi) else: - Bx = Br * np.cos(phi) - By = Br * np.sin(phi) + Bx = Br * xp.cos(phi) + By = Br * xp.sin(phi) return Bx, By diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..baaf2d473 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -453,14 +453,14 @@ def test_BHJM_magnet_trimesh_BH(): def test_BHJM_circle(): """Test of current circle field core function""" kw = { - "observers": np.array([(1, 1, 1), (2, 2, 2), (3, 3, 3)]), - "current": np.array([1, 1, 2]) * 1e3, - "diameter": np.array([2, 4, 6]), + "observers": xp.asarray([(1, 1, 1), (2, 2, 2), (3, 3, 3)]), + "current": xp.asarray([1.0, 1.0, 2.0]) * 1e3, + "diameter": xp.asarray([2, 4, 6]), } H, B, M, _ = helper_check_HBMJ_consistency(BHJM_circle, **kw) Btest = ( - np.array( + xp.asarray( [ [0.06235974, 0.06235974, 0.02669778], [0.03117987, 0.03117987, 0.01334889], @@ -472,7 +472,7 @@ def test_BHJM_circle(): np.testing.assert_allclose(B, Btest) Htest = ( - np.array( + xp.asarray( [ [49624.3033947, 49624.3033947, 21245.41908818], [24812.15169735, 24812.15169735, 10622.70954409], @@ -564,9 +564,9 @@ def test_BHJM_dipole(): def test_field_loop_specials(): """test loop special cases""" - cur = np.array([1, 1, 1, 1, 0, 2]) - dia = np.array([2, 2, 0, 0, 2, 2]) - obs = np.array([(0, 0, 0), (1, 0, 0), (0, 0, 0), (1, 0, 0), (1, 0, 0), (0, 0, 0)]) + cur = xp.asarray([1, 1, 1, 1, 0, 2]) + dia = xp.asarray([2, 2, 0, 0, 2, 2]) + obs = xp.asarray([(0, 0, 0), (1, 0, 0), (0, 0, 0), (1, 0, 0), (1, 0, 0), (0, 0, 0)]) B = ( BHJM_circle( @@ -708,9 +708,9 @@ def test_field_line_special_cases(): def test_field_loop2(): """test if field function accepts correct inputs""" - curr = np.array([1]) - dia = np.array([2]) - obs = np.array([[0, 0, 0]]) + curr = xp.asarray([1]) + dia = xp.asarray([2]) + obs = xp.asarray([[0, 0, 0]]) B = BHJM_circle( field="B", observers=obs, @@ -718,9 +718,9 @@ def test_field_loop2(): diameter=dia, ) - curr = np.array([1] * 2) - dia = np.array([2] * 2) - obs = np.array([[0, 0, 0]] * 2) + curr = xp.asarray([1] * 2) + dia = xp.asarray([2] * 2) + obs = xp.asarray([[0, 0, 0]] * 2) B2 = BHJM_circle( field="B", observers=obs, @@ -728,8 +728,8 @@ def test_field_loop2(): diameter=dia, ) - assert_allclose(B, (B2[0],)) - assert_allclose(B, (B2[1],)) + assert_allclose(B, (B2[0, ...],)) + assert_allclose(B, (B2[1, ...],)) def test_field_line_from_vert(): From 1b913988c764668d23d72e3d2138a847e26de451 Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 19:06:33 -0500 Subject: [PATCH 22/24] Adds array api support for cuboid BHMJ layer --- src/magpylib/_src/fields/field_BH_cuboid.py | 30 +++++++++++++-------- tests/test_BHMJ_level.py | 8 +++--- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cuboid.py b/src/magpylib/_src/fields/field_BH_cuboid.py index 7f3a82e02..c0859eedd 100644 --- a/src/magpylib/_src/fields/field_BH_cuboid.py +++ b/src/magpylib/_src/fields/field_BH_cuboid.py @@ -216,25 +216,33 @@ def BHJM_magnet_cuboid( - translate cuboid core field to BHJM - treat special cases """ + xp = array_namespace(observers, dimension, polarization) + + if not xp.isdtype(observers.dtype, kind=("real floating", "complex floating")): + observers = xp.astype(observers, xp.float64) + + if not xp.isdtype(dimension.dtype, kind=("real floating", "complex floating")): + dimension = xp.astype(dimension, xp.float64) + + if not xp.isdtype(polarization.dtype, kind=("real floating", "complex floating")): + polarization = xp.astype(polarization, xp.float64) RTOL_SURFACE = 1e-15 # relative distance tolerance to be considered on surface check_field_input(field) - pol_x, pol_y, pol_z = polarization.T - a, b, c = np.abs(dimension.T) / 2 - x, y, z = observers.T + pol_x, pol_y, pol_z = (polarization[:, i] for i in range(3)) + a, b, c = ((xp.abs(dimension) / 2)[:, i] for i in range(3)) + x, y, z = (observers[:, i] for i in range(3)) # allocate for output - BHJM = polarization.astype(float) + BHJM = xp.astype(polarization, xp.float64) # SPECIAL CASE 1: polarization = (0,0,0) - mask_pol_not_null = ~( - (pol_x == 0) * (pol_y == 0) * (pol_z == 0) - ) # 2x faster than np.all() + mask_pol_not_null = xp.any(polarization != 0, axis=1) # SPECIAL CASE 2: 0 in dimension - mask_dim_not_null = (a * b * c).astype(bool) + mask_dim_not_null = (a * b * c) != 0 # SPECIAL CASE 3: observer lies on-edge/corner # EPSILON to account for numerical imprecision when e.g. rotating @@ -242,9 +250,9 @@ def BHJM_magnet_cuboid( # a is e.g. EPSILON itself) # on-surface is not a special case - mask_surf_x = abs(x_dist := abs(x) - a) < RTOL_SURFACE * a # on surface - mask_surf_y = abs(y_dist := abs(y) - b) < RTOL_SURFACE * b # on surface - mask_surf_z = abs(z_dist := abs(z) - c) < RTOL_SURFACE * c # on surface + mask_surf_x = xp.abs(x_dist := xp.abs(x) - a) < RTOL_SURFACE * a # on surface + mask_surf_y = xp.abs(y_dist := xp.abs(y) - b) < RTOL_SURFACE * b # on surface + mask_surf_z = xp.abs(z_dist := xp.abs(z) - c) < RTOL_SURFACE * c # on surface # inside-outside mask_inside_x = x_dist < RTOL_SURFACE * a diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..6b7b4a07a 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -41,7 +41,7 @@ def helper_check_HBMJ_consistency(func, **kw): def test_BHJM_magnet_cuboid(): """test cuboid field""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -51,7 +51,7 @@ def test_BHJM_magnet_cuboid(): (1, 2, 3), ] ) - dim = np.array( + dim = xp.asarray( [ (1, 2, 3), (-1, -2, 2), @@ -61,7 +61,7 @@ def test_BHJM_magnet_cuboid(): (3, 3, 3), ] ) - obs = np.array( + obs = xp.asarray( [ (1, 2, 3), (1, -1, 0), @@ -98,7 +98,7 @@ def test_BHJM_magnet_cuboid(): ] np.testing.assert_allclose(H, Htest, rtol=1e-5) - Jtest = np.array([(0, 0, 0)] * 5 + [(1, 2, 3)]) + Jtest = xp.asarray([(0, 0, 0)] * 5 + [(1, 2, 3)]) np.testing.assert_allclose(J, Jtest, rtol=1e-5) # H_inout = BHJM_magnet_cuboid(field="H", in_out="outside", **kw) From fd9aaa8cc5f65c7e2ab2f2654e6202e4fe38b94a Mon Sep 17 00:00:00 2001 From: purepani Date: Mon, 19 May 2025 15:59:27 -0500 Subject: [PATCH 23/24] Adds array api support to cylinder segment BHMJ level --- .../_src/fields/field_BH_cylinder_segment.py | 79 +++++++++++++------ tests/test_BHMJ_level.py | 6 +- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_cylinder_segment.py b/src/magpylib/_src/fields/field_BH_cylinder_segment.py index bfb03724e..ca59aca07 100644 --- a/src/magpylib/_src/fields/field_BH_cylinder_segment.py +++ b/src/magpylib/_src/fields/field_BH_cylinder_segment.py @@ -2379,25 +2379,30 @@ def BHJM_cylinder_segment( - translate cylinder segment field to BHJM - special cases catching """ + xp = array_namespace(observers, dimension, polarization) check_field_input(field) - BHJM = polarization.astype(float) + observers = xp.astype(observers, xp.float64) + dimension = xp.astype(dimension, xp.float64) + polarization = xp.astype(polarization, xp.float64) - r1, r2, h, phi1, phi2 = dimension.T - r1 = abs(r1) - r2 = abs(r2) - h = abs(h) + BHJM = xp.astype(polarization, xp.float64) + + # r1, r2, h, phi1, phi2 = dimension.T + r1 = xp.abs(dimension[:, 0]) + r2 = xp.abs(dimension[:, 1]) + h = xp.abs(dimension[:, 2]) z1, z2 = -h / 2, h / 2 # transform dim deg->rad - phi1 = phi1 / 180 * np.pi - phi2 = phi2 / 180 * np.pi - dim = np.array([r1, r2, phi1, phi2, z1, z2]).T + phi1 = dimension[:, 3] / 180 * xp.pi + phi2 = dimension[:, 4] / 180 * xp.pi + dim = xp.asarray([r1, r2, phi1, phi2, z1, z2]).T # transform obs_pos to Cy CS -------------------------------------------- - x, y, z = observers.T - r, phi = np.sqrt(x**2 + y**2), np.arctan2(y, x) - pos_obs_cy = np.concatenate(((r,), (phi,), (z,)), axis=0).T + x, y, z = (observers[:, i] for i in range(3)) + r, phi = xp.sqrt(x**2 + y**2), xp.atan2(y, x) + pos_obs_cy = xp.concat((r[None, ...], phi[None, ...], z[None, ...]), axis=0).T # determine when points lie inside and on surface of magnet ------------- @@ -2405,7 +2410,7 @@ def BHJM_cylinder_segment( # if in_out == "auto": # phip1 in [-2pi,0], phio2 in [0,2pi] phio1 = phi - phio2 = phi - np.sign(phi) * 2 * np.pi + phio2 = phi - xp.sign(phi) * 2 * xp.pi # phi=phi1, phi=phi2 mask_phi1 = close(phio1, phi1) | close(phio2, phi1) @@ -2413,8 +2418,8 @@ def BHJM_cylinder_segment( # r, phi ,z lies in-between, avoid numerical fluctuations (e.g. due to rotations) by including 1e-14 mask_r_in = (r1 - 1e-14 < r) & (r < r2 + 1e-14) - mask_phi_in = (np.sign(phio1 - phi1) != np.sign(phio1 - phi2)) | ( - np.sign(phio2 - phi1) != np.sign(phio2 - phi2) + mask_phi_in = (xp.sign(phio1 - phi1) != xp.sign(phio1 - phi2)) | ( + xp.sign(phio2 - phi1) != xp.sign(phio2 - phi2) ) mask_z_in = (z1 - 1e-14 < z) & (z < z2 + 1e-14) @@ -2429,13 +2434,13 @@ def BHJM_cylinder_segment( # inside mask_inside = mask_r_in & mask_phi_in & mask_z_in # else: - # mask_inside = np.full(len(observers), in_out == "inside") - # mask_not_on_surf = np.full(len(observers), True) + # mask_inside = xp.full((observers.shape[0]), in_out == "inside") + # mask_not_on_surf = xp.full((observers.shape[0]), True) # WARNING @alex # 1. inside and not_on_surface are not the same! Can't just put to true. # return 0 when all points are on surface - if not np.any(mask_not_on_surf): + if not xp.any(mask_not_on_surf): return BHJM * 0 if field == "J": @@ -2455,19 +2460,21 @@ def BHJM_cylinder_segment( phi = phi[mask_not_on_surf] # transform mag to spherical CS ----------------------------------------- - m = np.sqrt(pol[:, 0] ** 2 + pol[:, 1] ** 2 + pol[:, 2] ** 2) / MU0 # J -> M - phi_m = np.arctan2(pol[:, 1], pol[:, 0]) - th_m = np.arctan2(np.sqrt(pol[:, 0] ** 2 + pol[:, 1] ** 2), pol[:, 2]) - mag_sph = np.concatenate(((m,), (phi_m,), (th_m,)), axis=0).T + m = xp.sqrt(pol[:, 0] ** 2 + pol[:, 1] ** 2 + pol[:, 2] ** 2) / MU0 # J -> M + phi_m = xp.atan2(pol[:, 1], pol[:, 0]) + th_m = xp.atan2(xp.sqrt(pol[:, 0] ** 2 + pol[:, 1] ** 2), pol[:, 2]) + mag_sph = xp.concat((m[None, ...], phi_m[None, ...], th_m[None, ...]), axis=0).T # compute H and transform to cart CS ------------------------------------- H_cy = magnet_cylinder_segment_Hfield( magnetizations=mag_sph, dimensions=dim, observers=pos_obs_cy ) - Hr, Hphi, Hz = H_cy.T - Hx = Hr * np.cos(phi) - Hphi * np.sin(phi) - Hy = Hr * np.sin(phi) + Hphi * np.cos(phi) - BHJM[mask_not_on_surf] = np.concatenate(((Hx,), (Hy,), (Hz,)), axis=0).T + Hr, Hphi, Hz = H_cy[:, 0], H_cy[:, 1], H_cy[:, 2] + Hx = Hr * xp.cos(phi) - Hphi * xp.sin(phi) + Hy = Hr * xp.sin(phi) + Hphi * xp.cos(phi) + BHJM[mask_not_on_surf] = xp.concat( + (Hx[None, ...], Hy[None, ...], Hz[None, ...]), axis=0 + ).T if field == "H": return BHJM @@ -2488,3 +2495,25 @@ def BHJM_cylinder_segment( # field_values=H_all, # mask_inside=mask_inside & mask_not_on_surf, # ) + + # return convert_HBMJ( + # output_field_type=field, + # polarization=polarization, + # input_field_type="H", + # field_values=H_all, + # mask_inside=mask_inside & mask_not_on_surf, + # ) + + # return convert_HBMJ( + # output_field_type=field, + # polarization=polarization, + # input_field_type="H", + # field_values=H_all, + # mask_inside=mask_inside & mask_not_on_surf, + # ) + # output_field_type=field, + # polarization=polarization, + # input_field_type="H", + # field_values=H_all, + # mask_inside=mask_inside & mask_not_on_surf, + # ) diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..3023d528d 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -213,7 +213,7 @@ def test_BHJM_magnet_sphere(): def test_field_cylinder_segment_BH(): """CylinderSegment field test""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -221,7 +221,7 @@ def test_field_cylinder_segment_BH(): (2, 3, -1), ] ) - dim = np.array( + dim = xp.asarray( [ (1, 2, 3, 10, 20), (1, 2, 3, 10, 20), @@ -229,7 +229,7 @@ def test_field_cylinder_segment_BH(): (0.1, 5, 2, 20, 370), ] ) - obs = np.array( + obs = xp.asarray( [ (1, 2, 3), (1, -1, 0), From ad3dc92bc7a09f7ff6a312b5c92a883681e1c3bd Mon Sep 17 00:00:00 2001 From: purepani Date: Wed, 7 May 2025 23:49:59 -0500 Subject: [PATCH 24/24] Adds array api support for triangle BHMJ level --- src/magpylib/_src/fields/field_BH_triangle.py | 16 +++++++++--- tests/test_BHMJ_level.py | 26 +++++++++---------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/magpylib/_src/fields/field_BH_triangle.py b/src/magpylib/_src/fields/field_BH_triangle.py index 925d06068..6109a85ba 100644 --- a/src/magpylib/_src/fields/field_BH_triangle.py +++ b/src/magpylib/_src/fields/field_BH_triangle.py @@ -249,8 +249,18 @@ def BHJM_triangle( - translate triangle core field to BHJM """ check_field_input(field) + xp = array_namespace(observers, vertices, polarization) - BHJM = polarization.astype(float) * 0.0 + if not xp.isdtype(observers.dtype, kind=("real floating", "complex floating")): + observers = xp.astype(observers, xp.float64) + + if not xp.isdtype(vertices.dtype, kind=("real floating", "complex floating")): + vertices = xp.astype(vertices, xp.float64) + + if not xp.isdtype(polarization.dtype, kind=("real floating", "complex floating")): + polarization = xp.astype(polarization, xp.float64) + + BHJM = xp.astype(polarization, xp.float64) * 0.0 if field == "M": return BHJM @@ -259,9 +269,7 @@ def BHJM_triangle( return BHJM BHJM = triangle_Bfield( - observers=observers, - vertices=vertices, - polarizations=polarization, + observers=observers, vertices=vertices, polarizations=polarization ) # new MU0 problem: diff --git a/tests/test_BHMJ_level.py b/tests/test_BHMJ_level.py index 82f6cc8f3..f86795ab1 100644 --- a/tests/test_BHMJ_level.py +++ b/tests/test_BHMJ_level.py @@ -267,7 +267,7 @@ def test_field_cylinder_segment_BH(): def test_BHJM_triangle_BH(): """Test of triangle field core function""" - pol = np.array( + pol = xp.asarray( [ (0, 0, 0), (1, 2, 3), @@ -275,7 +275,7 @@ def test_BHJM_triangle_BH(): (1, -1, 2), ] ) - vert = np.array( + vert = xp.asarray( [ [(0, 0, 0), (0, 1, 0), (1, 0, 0)], [(0, 0, 0), (0, 1, 0), (1, 0, 0)], @@ -283,7 +283,7 @@ def test_BHJM_triangle_BH(): [(1, 2, 2), (0, 1, -1), (3, -1, 1)], ] ) - obs = np.array( + obs = xp.asarray( [ (1, 1, 1), (1, 1, 1), @@ -847,11 +847,11 @@ def test_triangle5(): ] n = 10 - ts = np.linspace(-1, 6, n) - obs1 = np.array([(t, 0, 0) for t in ts]) - obs2 = np.array([(0, t, 0) for t in ts]) - mag = np.array([(111, 222, 333)] * n) - ver = np.array([[(0, 0, 0), (0, 5, 0), (5, 0, 0)]] * n) + ts = xp.linspace(-1, 6, n) + obs1 = xp.asarray([(t, 0, 0) for t in ts]) + obs2 = xp.asarray([(0, t, 0) for t in ts]) + mag = xp.asarray([(111, 222, 333)] * n) + ver = xp.asarray([[(0, 0, 0), (0, 5, 0), (5, 0, 0)]] * n) b1 = ( BHJM_triangle( @@ -877,11 +877,11 @@ def test_triangle5(): def test_triangle6(): """special case tests on corners - result is nan""" - obs1 = np.array([(0, 0, 0)]) - obs2 = np.array([(0, 5, 0)]) - obs3 = np.array([(5, 0, 0)]) - mag = np.array([(1, 2, 3)]) - ver = np.array([[(0, 0, 0), (0, 5, 0), (5, 0, 0)]]) + obs1 = xp.asarray([(0, 0, 0)]) + obs2 = xp.asarray([(0, 5, 0)]) + obs3 = xp.asarray([(5, 0, 0)]) + mag = xp.asarray([(1, 2, 3)]) + ver = xp.asarray([[(0, 0, 0), (0, 5, 0), (5, 0, 0)]]) b1 = BHJM_triangle( field="B", observers=obs1,