diff --git a/autogalaxy/convert.py b/autogalaxy/convert.py index 4b8e8a0b..3364b181 100644 --- a/autogalaxy/convert.py +++ b/autogalaxy/convert.py @@ -1,11 +1,19 @@ +from astropy import units import numpy as np from typing import Tuple -def ell_comps_from(axis_ratio, angle): +def ell_comps_from(axis_ratio: float, angle: float) -> Tuple[float, float]: """ - Convert an input axis ratio (0.0 > q > 1.0) and rotation position angle defined counter clockwise from the - positive x-axis(0.0 > angle > 180) to the (y,x) ellipitical components e1 and e2. + Returns the elliptical components e1 and e2 of a light or mass profile from an input angle in degrees and axis + ratio. + + The elliptical components of a light or mass profile are given by: + + elliptical_component_y = ell_comps[0] = (1-axis_ratio)/(1+axis_ratio) * sin(2 * angle) + elliptical_component_x = ell_comps[1] = (1-axis_ratio)/(1+axis_ratio) * cos(2 * angle) + + Which are the values this function returns. Parameters ---------- @@ -21,18 +29,42 @@ def ell_comps_from(axis_ratio, angle): return (ellip_y, ellip_x) -def axis_ratio_and_angle_from(ell_comps): +def axis_ratio_and_angle_from(ell_comps: Tuple[float, float]) -> Tuple[float, float]: """ - Convert the ellipitical components e1 and e2 to an axis ratio (0.0 > q > 1.0) and rotation position angle - defined counter clockwise from the positive x-axis(0.0 > angle > 180) to . + Returns the axis-ratio and position angle in degrees (-45 < angle < 135.0) from input elliptical components e1 + and e2 of a light or mass profile. + + The elliptical components of a light or mass profile are given by: + + elliptical_component_y = ell_comps[0] = (1-axis_ratio)/(1+axis_ratio) * sin(2 * angle) + elliptical_component_x = ell_comps[1] = (1-axis_ratio)/(1+axis_ratio) * cos(2 * angle) + + The axis-ratio and angle are therefore given by: + + axis_ratio = (1 - fac) / (1 + fac) + angle = 0.5 * arctan(ell_comps[0] / ell_comps[1]) + + where `fac = sqrt(ell_comps[1] ** 2 + ell_comps[0] ** 2). + + This function returns the axis-ratio and angle in degrees. + + An additional check is performed which requires the angle is between -45 and 135 degrees. This ensures that + for certain values of `ell_comps` the angle does not jump from one boundary to another (e.g. without this check + certain values of `ell_comps` return -1.0 degrees and others 179.0 degrees). This ensures that when error + estimates are computed from samples of a lens model via marginalization, the calculation is not biased by the + angle jumping between these two values. Parameters ---------- - ell_comps : (float, float) - The first and second ellipticity components of the elliptical coordinate system. + ell_comps + The elliptical components of the light or mass profile which are converted to an angle. """ angle = np.arctan2(ell_comps[0], ell_comps[1]) / 2 angle *= 180.0 / np.pi + + if abs(angle) > 45 and angle < 0: + angle += 180 + fac = np.sqrt(ell_comps[1] ** 2 + ell_comps[0] ** 2) if fac > 0.999: fac = 0.999 # avoid unphysical solution @@ -41,38 +73,89 @@ def axis_ratio_and_angle_from(ell_comps): return axis_ratio, angle -def axis_ratio_from(ell_comps): +def axis_ratio_from(ell_comps: Tuple[float, float]): """ - Convert the ellipitical components e1 and e2 to an axis ratio (0.0 > q > 1.0) and rotation position angle - defined counter clockwise from the positive x-axis(0.0 > angle > 180) to . + Returns the axis-ratio from input elliptical components e1 and e2 of a light or mass profile. + + The elliptical components of a light or mass profile are given by: + + elliptical_component_y = ell_comps[0] = (1-axis_ratio)/(1+axis_ratio) * sin(2 * angle) + elliptical_component_x = ell_comps[1] = (1-axis_ratio)/(1+axis_ratio) * cos(2 * angle) + + The axis-ratio is therefore given by: + + axis_ratio = (1 - fac) / (1 + fac) + angle = 0.5 * arctan(ell_comps[0] / ell_comps[1]) + + where `fac = sqrt(ell_comps[1] ** 2 + ell_comps[0] ** 2). + + This function returns the axis-ratio. Parameters ---------- - ell_comps : (float, float) - The first and second ellipticity components of the elliptical coordinate system. + ell_comps + The elliptical components of the light or mass profile which are converted to an angle. """ axis_ratio, angle = axis_ratio_and_angle_from(ell_comps=ell_comps) return axis_ratio -def angle_from(ell_comps): +def angle_from(ell_comps: Tuple[float, float]) -> float: """ - Convert the ellipitical components e1 and e2 to an rotation position angle in degrees (0.0 > angle > 18-.0). + Returns the position angle in degrees (-45 < angle < 135.0) from input elliptical components e1 and e2 + of a light or mass profile. + + The elliptical components of a light or mass profile are given by: + + elliptical_component_y = ell_comps[0] = (1-q)/(1+q) * sin(2 * angle) + elliptical_component_x = ell_comps[1] = (1-q)/(1+q) * cos(2 * angle) + + axis_ratio = (1 - fac) / (1 + fac) + angle = 0.5 * arctan(ell_comps[0] / ell_comps[1]) + + where `fac = sqrt(ell_comps[1] ** 2 + ell_comps[0] ** 2). + + This function returns the angle in degrees. + + An additional check is performed which requires the angle is between -45 and 135 degrees. This ensures that + for certain values of `ell_comps` the angle does not jump from one boundary to another (e.g. without this check + certain values of `ell_comps` return -1.0 degrees and others 179.0 degrees). + + This ensures that when error estimates are computed from samples of a lens model via marginalization, the + calculation is not biased by the angle jumping between these two values. Parameters ---------- - ell_comps : (float, float) - The first and second ellipticity components of the elliptical coordinate system. + ell_comps + The elliptical components of the light or mass profile which are converted to an angle. """ axis_ratio, angle = axis_ratio_and_angle_from(ell_comps=ell_comps) + return angle def shear_gamma_1_2_from(magnitude: float, angle: float) -> Tuple[float, float]: """ - :param angle: angel - :param magnitude: ellipticity - :return: + Returns the shear gamma 1 and gamma 2 values an input shear magnitude and angle in degrees. + + The gamma 1 and gamma 2 components of a shear are given by: + + gamma_1 = magnitude * np.cos(2 * angle * np.pi / 180.0) + gamma_2 = magnitude * np.sin(2 * angle * np.pi / 180.0) + + Which are the values this function returns. + + Converting from gamma 1 and gamma 2 to magnitude and angle is given by: + + magnitude = np.sqrt(gamma_1**2 + gamma_2**2) + angle = np.arctan2(gamma_2, gamma_1) / 2 * 180.0 / np.pi + + Parameters + ---------- + axis_ratio + Ratio of light profiles ellipse's minor and major axes (b/a). + angle + Rotation angle of light profile counter-clockwise from positive x-axis. """ gamma_1 = magnitude * np.cos(2 * angle * np.pi / 180.0) gamma_2 = magnitude * np.sin(2 * angle * np.pi / 180.0) @@ -83,22 +166,68 @@ def shear_magnitude_and_angle_from( gamma_1: float, gamma_2: float ) -> Tuple[float, float]: """ - :param e1: ellipticity component - :param e2: ellipticity component - :return: angle and abs value of ellipticity + Returns the shear magnitude and angle in degrees from input shear gamma 1 and gamma 2 values. + + The gamma 1 and gamma 2 components of a shear are given by: + + gamma_1 = magnitude * np.cos(2 * angle * np.pi / 180.0) + gamma_2 = magnitude * np.sin(2 * angle * np.pi / 180.0) + + Converting from gamma 1 and gamma 2 to magnitude and angle is given by: + + magnitude = np.sqrt(gamma_1**2 + gamma_2**2) + angle = np.arctan2(gamma_2, gamma_1) / 2 * 180.0 / np.pi + + Which are the values this function returns. + + Additional checks are performed which requires the angle is between -45 and 135 degrees. This ensures that + for certain values of `gamma_1` and `gamma_2` the angle does not jump from one boundary to another (e.g. without + this check certain values of `gamma_1` and `gamma_2` return -1.0 degrees and others 179.0 degrees). + + This ensures that when error estimates are computed from samples of a lens model via marginalization, the + calculation is not biased by the angle jumping between these two values. + + Parameters + ---------- + gamma_1 + The gamma 1 component of the shear. + gamma_2 + The gamma 2 component of the shear. """ angle = np.arctan2(gamma_2, gamma_1) / 2 * 180.0 / np.pi magnitude = np.sqrt(gamma_1**2 + gamma_2**2) + if angle < 0: - return magnitude, angle + 180.0 + angle += 180.0 + + if abs(angle - 90) > 45 and angle > 90: + angle -= 180 + return magnitude, angle def shear_magnitude_from(gamma_1: float, gamma_2: float) -> float: """ - :param e1: ellipticity component - :param e2: ellipticity component - :return: angle and abs value of ellipticity + Returns the shear magnitude and angle in degrees from input shear gamma 1 and gamma 2 values. + + The gamma 1 and gamma 2 components of a shear are given by: + + gamma_1 = magnitude * np.cos(2 * angle * np.pi / 180.0) + gamma_2 = magnitude * np.sin(2 * angle * np.pi / 180.0) + + Converting from gamma 1 and gamma 2 to magnitude and angle is given by: + + magnitude = np.sqrt(gamma_1**2 + gamma_2**2) + angle = np.arctan2(gamma_2, gamma_1) / 2 * 180.0 / np.pi + + The magnitude value is what this function returns. + + Parameters + ---------- + gamma_1 + The gamma 1 component of the shear. + gamma_2 + The gamma 2 component of the shear. """ magnitude, angle = shear_magnitude_and_angle_from(gamma_1=gamma_1, gamma_2=gamma_2) return magnitude @@ -106,9 +235,96 @@ def shear_magnitude_from(gamma_1: float, gamma_2: float) -> float: def shear_angle_from(gamma_1: float, gamma_2: float) -> float: """ - :param e1: ellipticity component - :param e2: ellipticity component - :return: angle and abs value of ellipticity + Returns the shear magnitude and angle in degrees from input shear gamma 1 and gamma 2 values. + + The gamma 1 and gamma 2 components of a shear are given by: + + gamma_1 = magnitude * np.cos(2 * angle * np.pi / 180.0) + gamma_2 = magnitude * np.sin(2 * angle * np.pi / 180.0) + + Converting from gamma 1 and gamma 2 to magnitude and angle is given by: + + magnitude = np.sqrt(gamma_1**2 + gamma_2**2) + angle = np.arctan2(gamma_2, gamma_1) / 2 * 180.0 / np.pi + + The angle value is what this function returns. + + Parameters + ---------- + gamma_1 + The gamma 1 component of the shear. + gamma_2 + The gamma 2 component of the shear. """ magnitude, angle = shear_magnitude_and_angle_from(gamma_1=gamma_1, gamma_2=gamma_2) return angle + + +def multipole_k_m_and_phi_m_from( + multipole_comps: Tuple[float, float], m: int +) -> Tuple[float, float]: + """ + Returns the multipole normalization value `k_m` and angle `phi` from the multipole component parameters. + + The normalization and angle are given by: + + .. math:: + \phi^{\rm mass}_m = \arctan{\frac{\epsilon_{\rm 2}^{\rm mp}}{\epsilon_{\rm 2}^{\rm mp}}}, \, \, + k^{\rm mass}_m = \sqrt{{\epsilon_{\rm 1}^{\rm mp}}^2 + {\epsilon_{\rm 2}^{\rm mp}}^2} \, . + + The conversion depends on the multipole order `m`, to ensure that all possible rotationally symmetric + multiple mass profiles are available in the conversion for multiple components spanning -inf to inf. + + Additional checks are performed which requires the angle `phi_m` is between -45 and 135 degrees. This ensures that + for certain multipole component values the angle does not jump from one boundary to another (e.g. without + this check certain values of `gamma_1` and `gamma_2` return -1.0 degrees and others 179.0 degrees). + + This ensures that when error estimates are computed from samples of a lens model via marginalization, the + calculation is not biased by the angle jumping between these two values. + + Parameters + ---------- + multipole_comps + The first and second components of the multipole. + + Returns + ------- + The normalization and angle parameters of the multipole. + """ + phi_m = ( + np.arctan2(multipole_comps[0], multipole_comps[1]) * 180.0 / np.pi / float(m) + ) + k_m = np.sqrt(multipole_comps[1] ** 2 + multipole_comps[0] ** 2) + + if phi_m < -90.0 / m: + phi_m += 360.0 / m + + return k_m, phi_m + + +def multipole_comps_from(k_m: float, phi_m: float, m: int) -> Tuple[float, float]: + """ + Returns the multipole component parameters from their normalization value `k_m` and angle `phi`. + + .. math:: + \phi^{\rm mass}_m = \arctan{\frac{\epsilon_{\rm 2}^{\rm mp}}{\epsilon_{\rm 2}^{\rm mp}}}, \, \, + k^{\rm mass}_m = \sqrt{{\epsilon_{\rm 1}^{\rm mp}}^2 + {\epsilon_{\rm 2}^{\rm mp}}^2} \, . + + The conversion depends on the multipole order `m`, to ensure that all possible rotationally symmetric + multiple mass profiles are available in the conversion for multiple components spanning -inf to inf. + + Parameters + ---------- + k_m + The magnitude of the multipole. + phi_m + The angle of the multipole. + + Returns + ------- + The multipole component parameters. + """ + multipole_comp_0 = k_m * np.sin(phi_m * float(m) * units.deg.to(units.rad)) + multipole_comp_1 = k_m * np.cos(phi_m * float(m) * units.deg.to(units.rad)) + + return (multipole_comp_0, multipole_comp_1) diff --git a/autogalaxy/profiles/mass/total/power_law_multipole.py b/autogalaxy/profiles/mass/total/power_law_multipole.py index e9928891..354bdabb 100644 --- a/autogalaxy/profiles/mass/total/power_law_multipole.py +++ b/autogalaxy/profiles/mass/total/power_law_multipole.py @@ -4,6 +4,8 @@ import autoarray as aa +from autogalaxy import convert + from autogalaxy.profiles.mass.abstract.abstract import MassProfile @@ -36,66 +38,6 @@ def radial_and_angle_grid_from( return radial_grid, angle_grid -def multipole_parameters_from( - multipole_comps: Tuple[float, float], m: int -) -> Tuple[float, float]: - """ - Converts the multipole component parameters to their normalizartion value `k_m` and angle `phi`, - which are given by: - - .. math:: - \phi^{\rm mass}_m = \arctan{\frac{\epsilon_{\rm 2}^{\rm mp}}{\epsilon_{\rm 2}^{\rm mp}}}, \, \, - k^{\rm mass}_m = \sqrt{{\epsilon_{\rm 1}^{\rm mp}}^2 + {\epsilon_{\rm 2}^{\rm mp}}^2} \, . - - The conversion depends on the multipole order `m`, to ensure that all possible rotationally symmetric - multiple mass profiles are available in the conversion for multiple components spanning -inf to inf. - - Parameters - ---------- - multipole_comps - The first and second components of the multipole. - - Returns - ------- - The normalization and angle parameters of the multipole. - """ - phi_m = ( - np.arctan2(multipole_comps[0], multipole_comps[1]) * 180.0 / np.pi / float(m) - ) - k_m = np.sqrt(multipole_comps[1] ** 2 + multipole_comps[0] ** 2) - - return k_m, phi_m - - -def multipole_comps_from(k_m: float, phi_m: float, m: int) -> Tuple[float, float]: - """ - Converts the multipole normalizartion value `k_m` and angle `phi` to their multipole component parameters, - which are given by: - - .. math:: - \phi^{\rm mass}_m = \arctan{\frac{\epsilon_{\rm 2}^{\rm mp}}{\epsilon_{\rm 2}^{\rm mp}}}, \, \, - k^{\rm mass}_m = \sqrt{{\epsilon_{\rm 1}^{\rm mp}}^2 + {\epsilon_{\rm 2}^{\rm mp}}^2} \, . - - The conversion depends on the multipole order `m`, to ensure that all possible rotationally symmetric - multiple mass profiles are available in the conversion for multiple components spanning -inf to inf. - - Parameters - ---------- - k_m - The magnitude of the multipole. - phi_m - The angle of the multipole. - - Returns - ------- - The multipole component parameters. - """ - multipole_comp_0 = k_m * np.sin(phi_m * float(m) * units.deg.to(units.rad)) - multipole_comp_1 = k_m * np.cos(phi_m * float(m) * units.deg.to(units.rad)) - - return (multipole_comp_0, multipole_comp_1) - - class PowerLawMultipole(MassProfile): def __init__( self, @@ -179,7 +121,7 @@ def __init__( self.slope = slope self.multipole_comps = multipole_comps - self.k_m, self.angle_m = multipole_parameters_from( + self.k_m, self.angle_m = convert.multipole_k_m_and_phi_m_from( multipole_comps=multipole_comps, m=m ) self.angle_m *= units.deg.to(units.rad) diff --git a/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py b/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py index ff3dcd3e..5ab8a7f8 100644 --- a/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py +++ b/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py @@ -1,6 +1,7 @@ -import autogalaxy as ag import pytest +import autogalaxy as ag + grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) diff --git a/test_autogalaxy/test_convert.py b/test_autogalaxy/test_convert.py index 46117e2d..a74c09e4 100644 --- a/test_autogalaxy/test_convert.py +++ b/test_autogalaxy/test_convert.py @@ -1 +1,173 @@ -pass +import pytest + +import autogalaxy as ag + + +def test__ell_comps_from(): + ell_comps = ag.convert.ell_comps_from(axis_ratio=0.00050025012, angle=0.0) + + assert ell_comps == pytest.approx((0.0, 0.999), 1.0e-4) + + +def test__axis_ratio_and_angle_from(): + axis_ratio, angle = ag.convert.axis_ratio_and_angle_from(ell_comps=(0.0, 1.0)) + + assert axis_ratio == pytest.approx(0.00050025012, 1.0e-4) + assert angle == pytest.approx(0.0, 1.0e-4) + + axis_ratio, angle = ag.convert.axis_ratio_and_angle_from(ell_comps=(1.0, 0.0)) + + assert axis_ratio == pytest.approx(0.00050025012, 1.0e-4) + assert angle == pytest.approx(45.0, 1.0e-4) + + axis_ratio, angle = ag.convert.axis_ratio_and_angle_from(ell_comps=(0.0, -1.0)) + + assert axis_ratio == pytest.approx(0.00050025012, 1.0e-4) + assert angle == pytest.approx(90.0, 1.0e-4) + + axis_ratio, angle = ag.convert.axis_ratio_and_angle_from(ell_comps=(-1.0, 0.0)) + + assert axis_ratio == pytest.approx(0.00050025012, 1.0e-4) + assert angle == pytest.approx(-45.0, 1.0e-4) + + axis_ratio, angle = ag.convert.axis_ratio_and_angle_from(ell_comps=(-1.0, -1.0)) + + assert axis_ratio == pytest.approx(0.00050025012, 1.0e-4) + assert angle == pytest.approx(112.5, 1.0e-4) + + +def test__shear_gamma_1_2_from(): + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=0.0) + + assert gamma_1 == pytest.approx(0.05, 1.0e-4) + assert gamma_2 == pytest.approx(0.0, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=45.0) + + assert gamma_1 == pytest.approx(0.0, 1.0e-4) + assert gamma_2 == pytest.approx(0.05, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=90.0) + + assert gamma_1 == pytest.approx(-0.05, 1.0e-4) + assert gamma_2 == pytest.approx(0.0, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=135.0) + + assert gamma_1 == pytest.approx(0.0, 1.0e-4) + assert gamma_2 == pytest.approx(-0.05, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=180.0) + + assert gamma_1 == pytest.approx(0.05, 1.0e-4) + assert gamma_2 == pytest.approx(0.0, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=225.0) + + assert gamma_1 == pytest.approx(0.0, 1.0e-4) + assert gamma_2 == pytest.approx(0.05, 1.0e-4) + + gamma_1, gamma_2 = ag.convert.shear_gamma_1_2_from(magnitude=0.05, angle=-45.0) + + assert gamma_1 == pytest.approx(0.0, 1.0e-4) + assert gamma_2 == pytest.approx(-0.05, 1.0e-4) + + +def test__shear_magnitude_and_angle_from(): + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.05, gamma_2=0.0 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(0.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.0, gamma_2=0.05 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(45.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=-0.05, gamma_2=0.0 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(90.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.0, gamma_2=-0.05 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(135.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.05, gamma_2=0.0 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(0.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.0, gamma_2=0.05 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + assert angle == pytest.approx(45.0, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.05, gamma_2=0.0 + ) + + assert magnitude == pytest.approx(0.05, 1.0e-4) + + magnitude, angle = ag.convert.shear_magnitude_and_angle_from( + gamma_1=0.05, gamma_2=-0.05 + ) + + assert magnitude == pytest.approx(0.07071067811865, 1.0e-4) + assert angle == pytest.approx(-22.5, 1.0e-4) + + +def test__multipole_k_m_and_phi_m_from(): + k_m, phi = ag.convert.multipole_k_m_and_phi_m_from(multipole_comps=(0.1, 0.0), m=1) + + assert k_m == pytest.approx(0.1, 1e-3) + assert phi == pytest.approx(90.0, 1e-3) + + k_m, phi = ag.convert.multipole_k_m_and_phi_m_from(multipole_comps=(0.0, 0.1), m=1) + + assert k_m == pytest.approx(0.1, 1e-3) + assert phi == pytest.approx(0.0, 1e-3) + + k_m, phi = ag.convert.multipole_k_m_and_phi_m_from(multipole_comps=(0.1, 0.0), m=2) + + assert k_m == pytest.approx(0.1, 1e-3) + assert phi == pytest.approx(45.0, 1e-3) + + k_m, phi = ag.convert.multipole_k_m_and_phi_m_from( + multipole_comps=(-0.1, -0.1), m=2 + ) + + assert k_m == pytest.approx(0.14142135, 1e-3) + assert phi == pytest.approx(112.5, 1e-3) + + +def test__multipole_comps_from(): + + multipole_comps = ag.convert.multipole_comps_from(k_m=0.1, phi_m=90.0, m=1) + + assert multipole_comps == pytest.approx((0.1, 0.0), 1e-3) + + multipole_comps = ag.convert.multipole_comps_from(k_m=0.1, phi_m=0.0, m=1) + + assert multipole_comps == pytest.approx((0.0, 0.1), 1e-3) + + multipole_comps = ag.convert.multipole_comps_from(k_m=0.1, phi_m=45.0, m=2) + + assert multipole_comps == pytest.approx((0.1, 0.0), 1e-3) + + multipole_comps = ag.convert.multipole_comps_from(k_m=0.14142135, phi_m=112.5, m=2) + + assert multipole_comps == pytest.approx((-0.1, -0.1), 1e-3) \ No newline at end of file