From fba86a6fe39a087e1a5f1678fee51ff807a8ff42 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Wed, 11 Sep 2024 15:32:15 -0600 Subject: [PATCH 01/11] first pass --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 33 +++++++ .../ultra/l1c/ultra_l1c_pset_bins.py | 96 ++++++++++++++++++- 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 4a6009f45..60fb68ad1 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -3,8 +3,10 @@ import numpy as np from imap_processing.ultra.l1c.ultra_l1c_pset_bins import ( + bin_space, build_energy_bins, build_spatial_bins, + cartesian_to_spherical, ) @@ -33,3 +35,34 @@ def test_build_spatial_bins(): assert el_bin_edges[0] == -90 assert el_bin_edges[-1] == 90 assert len(el_bin_edges) == 361 + + +def test_cartesian_to_spherical(): + """Tests cartesian_to_spherical function.""" + # Example particle velocity in the pointing frame wrt s/c. + vx_sc = np.array([-186.5575, 508.5697]) + vy_sc = np.array([-707.5707, -516.0282]) + vz_sc = np.array([618.0569, 892.6931]) + + az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) + + # MATLAB code outputs: + assert az == np.array([1.313003856057083, 2.348915185230239]) + assert el == np.array([-0.701366480008680, -0.889015692861197]) + + +def test_bin_space(): + """Tests bin_space function.""" + # Example particle velocity in the pointing frame wrt s/c. + vx_sc = np.array([-186.5575, 508.5697]) + vy_sc = np.array([-707.5707, -516.0282]) + vz_sc = np.array([618.0569, 892.6931]) + + az_midpoint, el_midpoint = bin_space(vx_sc, vy_sc, vz_sc) + az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) + + az_within_tolerance = np.abs(az - az_midpoint) <= 0.25 + el_within_tolerance = np.abs(el - el_midpoint) <= 0.25 + + assert np.all(az_within_tolerance) + assert np.all(el_within_tolerance) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 014765234..c64deab5b 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -26,10 +26,12 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: energy_bin_start = bin_edges[:-1] energy_bin_end = bin_edges[1:] - return energy_bin_start, energy_bin_end + return energy_bin_start, energy_bin_end, az_bin_midpoints, el_bin_midpoints -def build_spatial_bins(spacing: float = 0.5) -> tuple[np.ndarray, np.ndarray]: +def build_spatial_bins( + spacing: float = 0.5, +) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: """ Build spatial bin boundaries for azimuth and elevation. @@ -44,11 +46,99 @@ def build_spatial_bins(spacing: float = 0.5) -> tuple[np.ndarray, np.ndarray]: Array of azimuth bin boundary values. el_bin_edges : np.ndarray Array of elevation bin boundary values. + az_bin_midpoints : np.ndarray + Array of azimuth bin midpoint values. + el_bin_midpoints : np.ndarray + Array of elevation bin midpoint values. """ # Azimuth bins from 0 to 360 degrees. az_bin_edges = np.arange(0, 360 + spacing, spacing) + az_bin_midpoints = az_bin_edges[:-1] + spacing / 2 # Midpoints between edges # Elevation bins from -90 to 90 degrees. el_bin_edges = np.arange(-90, 90 + spacing, spacing) + el_bin_midpoints = el_bin_edges[:-1] + spacing / 2 # Midpoints between edges - return az_bin_edges, el_bin_edges + return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints + + +def cartesian_to_spherical( + vx_dps_sc: np.ndarray, vy_dps_sc: np.ndarray, vz_dps_sc: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: + """ + Convert cartesian coordinates to spherical coordinates. + + Parameters + ---------- + vx_dps_sc : np.ndarray + The x-components of the velocity vector. + vy_dps_sc : np.ndarray + The y-components of the velocity vector. + vz_dps_sc : np.ndarray + The z-components of the velocity vector. + + Returns + ------- + az : np.ndarray + The azimuth angles in degrees. + el : np.ndarray + The elevation angles in degrees. + """ + # Magnitude of the velocity vector + magnitude_v = np.sqrt(vx_dps_sc**2 + vy_dps_sc**2 + vz_dps_sc**2) + + vhat_x = -vx_dps_sc / magnitude_v + vhat_y = -vy_dps_sc / magnitude_v + vhat_z = -vz_dps_sc / magnitude_v + + # Convert from cartesian to spherical coordinates (azimuth, elevation) + # Radius (magnitude) + r = np.sqrt(vhat_x**2 + vhat_y**2 + vhat_z**2) + + # Elevation angle (angle from the z-axis, range: [-pi/2, pi/2]) + el = np.arcsin(vhat_z / r) + + # Azimuth angle (angle in the xy-plane, range: [0, 2*pi]) + az = np.arctan2(vhat_y, vhat_x) + + # Ensure azimuth is from 0 to 2PI + az = az % (2 * np.pi) + + return az, el + + +def bin_space(vx_dps_sc, vy_dps_sc, vz_dps_sc) -> tuple[np.ndarray, np.ndarray]: + """ + Bin particle. + + Parameters + ---------- + vx_dps_sc : float, optional + The bin spacing in degrees (default is 0.5 degrees). + vy_dps_sc : float, optional + The bin spacing in degrees (default is 0.5 degrees). + vz_dps_sc : float, optional + The bin spacing in degrees (default is 0.5 degrees). + + Returns + ------- + az_midpoint : np.ndarray + Array of azimuth bin boundary values. + el_midpoint : np.ndarray + Array of elevation bin boundary values. + """ + az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( + build_spatial_bins() + ) + + az, el = cartesian_to_spherical(vx_dps_sc, vy_dps_sc, vz_dps_sc) + + # Find the appropriate bin index using searchsorted + az_bin_idx = np.searchsorted(az_bin_edges, np.degrees(az), side="right") - 1 + el_bin_idx = np.searchsorted(el_bin_edges, np.degrees(el), side="right") - 1 + + # Assign the corresponding midpoints + az_midpoint = az_bin_midpoints[az_bin_idx] + el_midpoint = el_bin_midpoints[el_bin_idx] + + return az_midpoint, el_midpoint From 1f43556642ffc4b88e9a3e44f27acc44292948ec Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Wed, 11 Sep 2024 15:44:57 -0600 Subject: [PATCH 02/11] added midpoints --- .../tests/ultra/unit/test_ultra_l1c_pset_bins.py | 15 ++++++++++----- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py | 14 +++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 60fb68ad1..6ca2bf97a 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -26,7 +26,8 @@ def test_build_energy_bins(): def test_build_spatial_bins(): """Tests build_spatial_bins function.""" - az_bin_edges, el_bin_edges = build_spatial_bins() + az_bin_edges, el_bin_edges, \ + az_bin_midpoints, el_bin_midpoints = build_spatial_bins() assert az_bin_edges[0] == 0 assert az_bin_edges[-1] == 360 @@ -47,8 +48,12 @@ def test_cartesian_to_spherical(): az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) # MATLAB code outputs: - assert az == np.array([1.313003856057083, 2.348915185230239]) - assert el == np.array([-0.701366480008680, -0.889015692861197]) + np.testing.assert_allclose( + az, np.array([1.31300, 2.34891]), atol=1e-05, rtol=0 + ) + np.testing.assert_allclose( + el, np.array([-0.70136, -0.88901]), atol=1e-05, rtol=0 + ) def test_bin_space(): @@ -61,8 +66,8 @@ def test_bin_space(): az_midpoint, el_midpoint = bin_space(vx_sc, vy_sc, vz_sc) az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) - az_within_tolerance = np.abs(az - az_midpoint) <= 0.25 - el_within_tolerance = np.abs(el - el_midpoint) <= 0.25 + az_within_tolerance = np.abs(np.degrees(az) - az_midpoint) <= 0.25 + el_within_tolerance = np.abs(np.degrees(el) - el_midpoint) <= 0.25 assert np.all(az_within_tolerance) assert np.all(el_within_tolerance) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index c64deab5b..a1a45f814 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -1,9 +1,9 @@ -"""Module to create bins for pointing sets.""" +"""Module to create and populate bins for pointing sets.""" import numpy as np -def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: +def build_energy_bins() -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Build energy bin boundaries. @@ -13,6 +13,8 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: Array of energy bin start values. energy_bin_end : np.ndarray Array of energy bin end values. + energy_bin_midpoints : np.ndarray + Array of energy bin midpoint values. """ alpha = 0.05 # deltaE/E energy_start = 3.5 # energy start for the Ultra grids @@ -26,7 +28,9 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: energy_bin_start = bin_edges[:-1] energy_bin_end = bin_edges[1:] - return energy_bin_start, energy_bin_end, az_bin_midpoints, el_bin_midpoints + energy_bin_midpoints = np.sqrt(energy_bin_start * energy_bin_end) + + return energy_bin_start, energy_bin_end, energy_bin_midpoints def build_spatial_bins( @@ -133,11 +137,11 @@ def bin_space(vx_dps_sc, vy_dps_sc, vz_dps_sc) -> tuple[np.ndarray, np.ndarray]: az, el = cartesian_to_spherical(vx_dps_sc, vy_dps_sc, vz_dps_sc) - # Find the appropriate bin index using searchsorted + # Find the appropriate bin index. az_bin_idx = np.searchsorted(az_bin_edges, np.degrees(az), side="right") - 1 el_bin_idx = np.searchsorted(el_bin_edges, np.degrees(el), side="right") - 1 - # Assign the corresponding midpoints + # Assign the corresponding midpoints. az_midpoint = az_bin_midpoints[az_bin_idx] el_midpoint = el_bin_midpoints[el_bin_idx] From b89426cac076a97796c96f02098c384597aad34f Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 12 Sep 2024 10:15:59 -0600 Subject: [PATCH 03/11] modify tests --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 38 +++++-- .../ultra/l1c/ultra_l1c_pset_bins.py | 102 ++++++++++++------ 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 6ca2bf97a..ca6fbffe5 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -12,22 +12,32 @@ def test_build_energy_bins(): """Tests build_energy_bins function.""" - energy_bin_start, energy_bin_end = build_energy_bins() + energy_bin_start, energy_bin_end, energy_bin_mean = build_energy_bins() assert energy_bin_start[0] == 3.5 assert len(energy_bin_start) == 90 assert len(energy_bin_end) == 90 + assert len(energy_bin_mean) == 90 # Comparison to expected values. np.testing.assert_allclose(energy_bin_end[0], 3.6795, atol=1e-4) np.testing.assert_allclose(energy_bin_start[-1], 299.9724, atol=1e-4) np.testing.assert_allclose(energy_bin_end[-1], 315.3556, atol=1e-4) + # Calculate expected geometric mean for the first bin. + expected_mean_first = np.sqrt(3.5 * 3.6795) + np.testing.assert_allclose(energy_bin_mean[0], expected_mean_first, atol=1e-4) + + # Calculate expected geometric mean for the last bin. + expected_mean_last = np.sqrt(299.9724 * 315.3556) + np.testing.assert_allclose(energy_bin_mean[-1], expected_mean_last, atol=1e-4) + def test_build_spatial_bins(): """Tests build_spatial_bins function.""" - az_bin_edges, el_bin_edges, \ - az_bin_midpoints, el_bin_midpoints = build_spatial_bins() + az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( + build_spatial_bins() + ) assert az_bin_edges[0] == 0 assert az_bin_edges[-1] == 360 @@ -37,6 +47,14 @@ def test_build_spatial_bins(): assert el_bin_edges[-1] == 90 assert len(el_bin_edges) == 361 + assert len(az_bin_midpoints) == 720 + np.testing.assert_allclose(az_bin_midpoints[0], 0.25, atol=1e-4) + np.testing.assert_allclose(az_bin_midpoints[-1], 359.75, atol=1e-4) + + assert len(el_bin_midpoints) == 360 + np.testing.assert_allclose(el_bin_midpoints[0], -89.75, atol=1e-4) + np.testing.assert_allclose(el_bin_midpoints[-1], 89.75, atol=1e-4) + def test_cartesian_to_spherical(): """Tests cartesian_to_spherical function.""" @@ -45,23 +63,21 @@ def test_cartesian_to_spherical(): vy_sc = np.array([-707.5707, -516.0282]) vz_sc = np.array([618.0569, 892.6931]) - az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) + az_sc, el_sc = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) # MATLAB code outputs: + np.testing.assert_allclose(az_sc, np.array([1.31300, 2.34891]), atol=1e-05, rtol=0) np.testing.assert_allclose( - az, np.array([1.31300, 2.34891]), atol=1e-05, rtol=0 - ) - np.testing.assert_allclose( - el, np.array([-0.70136, -0.88901]), atol=1e-05, rtol=0 + el_sc, np.array([-0.70136, -0.88901]), atol=1e-05, rtol=0 ) def test_bin_space(): """Tests bin_space function.""" # Example particle velocity in the pointing frame wrt s/c. - vx_sc = np.array([-186.5575, 508.5697]) - vy_sc = np.array([-707.5707, -516.0282]) - vz_sc = np.array([618.0569, 892.6931]) + vx_sc = np.array([-186.5575, 508.5697, 0]) + vy_sc = np.array([-707.5707, -516.0282, 0]) + vz_sc = np.array([618.0569, 892.6931, -1]) az_midpoint, el_midpoint = bin_space(vx_sc, vy_sc, vz_sc) az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index a1a45f814..88c5d9a03 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -3,17 +3,15 @@ import numpy as np -def build_energy_bins() -> tuple[np.ndarray, np.ndarray, np.ndarray]: +def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: """ Build energy bin boundaries. Returns ------- - energy_bin_start : np.ndarray - Array of energy bin start values. - energy_bin_end : np.ndarray - Array of energy bin end values. - energy_bin_midpoints : np.ndarray + energy_bin_edges : np.ndarray + Array of energy bin edges. + energy_bin_mean : np.ndarray Array of energy bin midpoint values. """ alpha = 0.05 # deltaE/E @@ -24,13 +22,12 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray, np.ndarray]: energy_step = (1 + alpha / 2) / (1 - alpha / 2) # Create energy bins. - bin_edges = energy_start * energy_step ** np.arange(n_bins + 1) - energy_bin_start = bin_edges[:-1] - energy_bin_end = bin_edges[1:] + energy_bin_edges = energy_start * energy_step ** np.arange(n_bins + 1) - energy_bin_midpoints = np.sqrt(energy_bin_start * energy_bin_end) + # Calculate the geometric mean. + energy_bin_mean = np.sqrt(energy_bin_edges[:-1] * energy_bin_edges[1:]) - return energy_bin_start, energy_bin_end, energy_bin_midpoints + return energy_bin_edges, energy_bin_mean def build_spatial_bins( @@ -67,18 +64,18 @@ def build_spatial_bins( def cartesian_to_spherical( - vx_dps_sc: np.ndarray, vy_dps_sc: np.ndarray, vz_dps_sc: np.ndarray + vx: np.ndarray, vy: np.ndarray, vz: np.ndarray ) -> tuple[np.ndarray, np.ndarray]: """ Convert cartesian coordinates to spherical coordinates. Parameters ---------- - vx_dps_sc : np.ndarray + vx : np.ndarray The x-components of the velocity vector. - vy_dps_sc : np.ndarray + vy : np.ndarray The y-components of the velocity vector. - vz_dps_sc : np.ndarray + vz : np.ndarray The z-components of the velocity vector. Returns @@ -89,11 +86,11 @@ def cartesian_to_spherical( The elevation angles in degrees. """ # Magnitude of the velocity vector - magnitude_v = np.sqrt(vx_dps_sc**2 + vy_dps_sc**2 + vz_dps_sc**2) + magnitude_v = np.sqrt(vx**2 + vy**2 + vz**2) - vhat_x = -vx_dps_sc / magnitude_v - vhat_y = -vy_dps_sc / magnitude_v - vhat_z = -vz_dps_sc / magnitude_v + vhat_x = -vx / magnitude_v + vhat_y = -vy / magnitude_v + vhat_z = -vz / magnitude_v # Convert from cartesian to spherical coordinates (azimuth, elevation) # Radius (magnitude) @@ -111,38 +108,79 @@ def cartesian_to_spherical( return az, el -def bin_space(vx_dps_sc, vy_dps_sc, vz_dps_sc) -> tuple[np.ndarray, np.ndarray]: +def bin_space( + vx: np.ndarray, vy: np.ndarray, vz: np.ndarray +) -> tuple[np.ndarray, np.ndarray]: """ - Bin particle. + Bin the particle. Parameters ---------- - vx_dps_sc : float, optional - The bin spacing in degrees (default is 0.5 degrees). - vy_dps_sc : float, optional - The bin spacing in degrees (default is 0.5 degrees). - vz_dps_sc : float, optional - The bin spacing in degrees (default is 0.5 degrees). + vx : np.ndarray + The x-components of the velocity vector. + vy : np.ndarray + The y-components of the velocity vector. + vz : np.ndarray + The z-components of the velocity vector. Returns ------- az_midpoint : np.ndarray - Array of azimuth bin boundary values. + Array of azimuth midpoint values. el_midpoint : np.ndarray - Array of elevation bin boundary values. + Array of elevation midpoint values. """ az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( build_spatial_bins() ) - az, el = cartesian_to_spherical(vx_dps_sc, vy_dps_sc, vz_dps_sc) + az, el = cartesian_to_spherical(vx, vy, vz) + + az_degrees = np.degrees(az) + el_degrees = np.degrees(el) + + # If azimuth is exactly 360 degrees it is placed in last bin. + az_degrees[az_degrees >= az_bin_edges[-1]] = az_bin_midpoints[-1] + # If elevation is exactly 90 degrees it is placed in last bin. + el_degrees[el_degrees >= el_bin_edges[-1]] = el_bin_midpoints[-1] # Find the appropriate bin index. - az_bin_idx = np.searchsorted(az_bin_edges, np.degrees(az), side="right") - 1 - el_bin_idx = np.searchsorted(el_bin_edges, np.degrees(el), side="right") - 1 + az_bin_idx = np.searchsorted(az_bin_edges, az_degrees, side="right") - 1 + el_bin_idx = np.searchsorted(el_bin_edges, el_degrees, side="right") - 1 # Assign the corresponding midpoints. az_midpoint = az_bin_midpoints[az_bin_idx] el_midpoint = el_bin_midpoints[el_bin_idx] return az_midpoint, el_midpoint + + +def bin_energy(energy: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """ + Bin the particle. + + Parameters + ---------- + energy : np.ndarray + Particle energy. + + Returns + ------- + energy_mean : np.ndarray + Mean energy value. + """ + # TODO: Use quality flags to filter out energies beyond threshold. + + energy_bin_edges, energy_bin_mean = build_energy_bins() + + # If energy is exactly equal to the last bin edge it is placed in last bin. + energy[energy >= energy_bin_edges[-1]] = energy_bin_mean[-1] + + # Find the appropriate bin index. + energy_bin_idx = np.searchsorted(energy_bin_edges, energy, side="right") - 1 + + # Assign the corresponding means. + az_mean = energy_bin_mean[energy_bin_idx] + el_mean = energy_bin_mean[energy_bin_idx] + + return az_mean, el_mean From 6a457bc2482e885a1063043710ab3a78d237a7ab Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 12 Sep 2024 10:41:48 -0600 Subject: [PATCH 04/11] update tests --- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index ca6fbffe5..07b2bb107 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -12,7 +12,9 @@ def test_build_energy_bins(): """Tests build_energy_bins function.""" - energy_bin_start, energy_bin_end, energy_bin_mean = build_energy_bins() + energy_bin_edges, energy_bin_mean = build_energy_bins() + energy_bin_start = energy_bin_edges[:-1] + energy_bin_end = energy_bin_edges[1:] assert energy_bin_start[0] == 3.5 assert len(energy_bin_start) == 90 From b3ebb730dcd91b9b6297aed4705446f134bde106 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 12 Sep 2024 14:33:38 -0600 Subject: [PATCH 05/11] needs fix --- .../tests/ultra/unit/test_ultra_l1c_pset_bins.py | 14 ++------------ imap_processing/ultra/l1c/ultra_l1c_pset_bins.py | 9 ++------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 07b2bb107..10db8fdaf 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -12,28 +12,18 @@ def test_build_energy_bins(): """Tests build_energy_bins function.""" - energy_bin_edges, energy_bin_mean = build_energy_bins() + energy_bin_edges = build_energy_bins() energy_bin_start = energy_bin_edges[:-1] energy_bin_end = energy_bin_edges[1:] assert energy_bin_start[0] == 3.5 - assert len(energy_bin_start) == 90 - assert len(energy_bin_end) == 90 - assert len(energy_bin_mean) == 90 + assert len(energy_bin_edges) == 91 # Comparison to expected values. np.testing.assert_allclose(energy_bin_end[0], 3.6795, atol=1e-4) np.testing.assert_allclose(energy_bin_start[-1], 299.9724, atol=1e-4) np.testing.assert_allclose(energy_bin_end[-1], 315.3556, atol=1e-4) - # Calculate expected geometric mean for the first bin. - expected_mean_first = np.sqrt(3.5 * 3.6795) - np.testing.assert_allclose(energy_bin_mean[0], expected_mean_first, atol=1e-4) - - # Calculate expected geometric mean for the last bin. - expected_mean_last = np.sqrt(299.9724 * 315.3556) - np.testing.assert_allclose(energy_bin_mean[-1], expected_mean_last, atol=1e-4) - def test_build_spatial_bins(): """Tests build_spatial_bins function.""" diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 88c5d9a03..6c2c848ff 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -24,10 +24,7 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: # Create energy bins. energy_bin_edges = energy_start * energy_step ** np.arange(n_bins + 1) - # Calculate the geometric mean. - energy_bin_mean = np.sqrt(energy_bin_edges[:-1] * energy_bin_edges[1:]) - - return energy_bin_edges, energy_bin_mean + return energy_bin_edges def build_spatial_bins( @@ -169,9 +166,7 @@ def bin_energy(energy: np.ndarray) -> tuple[np.ndarray, np.ndarray]: energy_mean : np.ndarray Mean energy value. """ - # TODO: Use quality flags to filter out energies beyond threshold. - - energy_bin_edges, energy_bin_mean = build_energy_bins() + energy_bin_edges = build_energy_bins() # If energy is exactly equal to the last bin edge it is placed in last bin. energy[energy >= energy_bin_edges[-1]] = energy_bin_mean[-1] From 31a5209651349215777c229587e3292f00f3e730 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 12 Sep 2024 17:20:40 -0600 Subject: [PATCH 06/11] minor updates --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 20 ++++++++--- .../ultra/l1c/ultra_l1c_pset_bins.py | 35 ++++++++----------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 10db8fdaf..0206da5ec 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -3,6 +3,7 @@ import numpy as np from imap_processing.ultra.l1c.ultra_l1c_pset_bins import ( + bin_energy, bin_space, build_energy_bins, build_spatial_bins, @@ -16,13 +17,14 @@ def test_build_energy_bins(): energy_bin_start = energy_bin_edges[:-1] energy_bin_end = energy_bin_edges[1:] - assert energy_bin_start[0] == 3.5 - assert len(energy_bin_edges) == 91 + assert energy_bin_start[0] == 0 + assert energy_bin_start[1] == 3.385 + assert len(energy_bin_edges) == 25 # Comparison to expected values. - np.testing.assert_allclose(energy_bin_end[0], 3.6795, atol=1e-4) - np.testing.assert_allclose(energy_bin_start[-1], 299.9724, atol=1e-4) - np.testing.assert_allclose(energy_bin_end[-1], 315.3556, atol=1e-4) + np.testing.assert_allclose(energy_bin_end[1], 4.137, atol=1e-4) + np.testing.assert_allclose(energy_bin_start[-1], 279.810, atol=1e-4) + np.testing.assert_allclose(energy_bin_end[-1], 341.989, atol=1e-4) def test_build_spatial_bins(): @@ -79,3 +81,11 @@ def test_bin_space(): assert np.all(az_within_tolerance) assert np.all(el_within_tolerance) + + +def test_bin_energy(): + """Tests bin_energy function.""" + energy = np.array([3.384, 3.385, 341.989, 342]) + bin = bin_energy(energy) + + np.testing.assert_equal(bin, (0, 3.385, 341.989, 341.989)) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 6c2c848ff..93b5b80de 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -1,9 +1,10 @@ -"""Module to create and populate bins for pointing sets.""" +"""Module to create energy bins for pointing sets.""" import numpy as np +from numpy.typing import NDArray -def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: +def build_energy_bins() -> NDArray[np.float64]: """ Build energy bin boundaries. @@ -11,18 +12,19 @@ def build_energy_bins() -> tuple[np.ndarray, np.ndarray]: ------- energy_bin_edges : np.ndarray Array of energy bin edges. - energy_bin_mean : np.ndarray - Array of energy bin midpoint values. """ - alpha = 0.05 # deltaE/E - energy_start = 3.5 # energy start for the Ultra grids - n_bins = 90 # number of energy bins + # TODO: these value will almost certainly change. + alpha = 0.2 # deltaE/E + energy_start = 3.385 # energy start for the Ultra grids + n_bins = 23 # number of energy bins # Calculate energy step energy_step = (1 + alpha / 2) / (1 - alpha / 2) # Create energy bins. energy_bin_edges = energy_start * energy_step ** np.arange(n_bins + 1) + # Add a zero to the left side for outliers and round to nearest 3 decimal places. + energy_bin_edges = np.around(np.insert(energy_bin_edges, 0, 0), 3) return energy_bin_edges @@ -137,9 +139,9 @@ def bin_space( el_degrees = np.degrees(el) # If azimuth is exactly 360 degrees it is placed in last bin. - az_degrees[az_degrees >= az_bin_edges[-1]] = az_bin_midpoints[-1] + az_degrees[az_degrees == az_bin_edges[-1]] = az_bin_midpoints[-1] # If elevation is exactly 90 degrees it is placed in last bin. - el_degrees[el_degrees >= el_bin_edges[-1]] = el_bin_midpoints[-1] + el_degrees[el_degrees == el_bin_edges[-1]] = el_bin_midpoints[-1] # Find the appropriate bin index. az_bin_idx = np.searchsorted(az_bin_edges, az_degrees, side="right") - 1 @@ -152,7 +154,7 @@ def bin_space( return az_midpoint, el_midpoint -def bin_energy(energy: np.ndarray) -> tuple[np.ndarray, np.ndarray]: +def bin_energy(energy: np.ndarray) -> NDArray[np.float64]: """ Bin the particle. @@ -163,19 +165,12 @@ def bin_energy(energy: np.ndarray) -> tuple[np.ndarray, np.ndarray]: Returns ------- - energy_mean : np.ndarray - Mean energy value. + energy_start : np.ndarray + Start of energy bin. """ energy_bin_edges = build_energy_bins() - # If energy is exactly equal to the last bin edge it is placed in last bin. - energy[energy >= energy_bin_edges[-1]] = energy_bin_mean[-1] - # Find the appropriate bin index. energy_bin_idx = np.searchsorted(energy_bin_edges, energy, side="right") - 1 - # Assign the corresponding means. - az_mean = energy_bin_mean[energy_bin_idx] - el_mean = energy_bin_mean[energy_bin_idx] - - return az_mean, el_mean + return energy_bin_edges[energy_bin_idx] From e1ad900e9800fbceb1b205876e8d07f98e967e9d Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 19 Sep 2024 11:28:35 -0600 Subject: [PATCH 07/11] updated cartesian to spherical --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 3 ++- .../ultra/l1c/ultra_l1c_pset_bins.py | 20 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 0206da5ec..84cba7528 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -56,8 +56,9 @@ def test_cartesian_to_spherical(): vx_sc = np.array([-186.5575, 508.5697]) vy_sc = np.array([-707.5707, -516.0282]) vz_sc = np.array([618.0569, 892.6931]) + v = np.column_stack((vx_sc, vy_sc, vz_sc)) - az_sc, el_sc = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) + az_sc, el_sc, r = cartesian_to_spherical(v) # MATLAB code outputs: np.testing.assert_allclose(az_sc, np.array([1.31300, 2.34891]), atol=1e-05, rtol=0) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 93b5b80de..0b73fcbeb 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -62,20 +62,14 @@ def build_spatial_bins( return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints -def cartesian_to_spherical( - vx: np.ndarray, vy: np.ndarray, vz: np.ndarray -) -> tuple[np.ndarray, np.ndarray]: +def cartesian_to_spherical(v: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Convert cartesian coordinates to spherical coordinates. Parameters ---------- - vx : np.ndarray - The x-components of the velocity vector. - vy : np.ndarray - The y-components of the velocity vector. - vz : np.ndarray - The z-components of the velocity vector. + v : tuple[np.ndarray, np.ndarray, np.ndarray] + The x,y,z-components of the velocity vector. Returns ------- @@ -83,7 +77,10 @@ def cartesian_to_spherical( The azimuth angles in degrees. el : np.ndarray The elevation angles in degrees. + r : np.ndarray + The radii, or magnitudes, of the vectors. """ + vx, vy, vz = v[:, 0], v[:, 1], v[:, 2] # Magnitude of the velocity vector magnitude_v = np.sqrt(vx**2 + vy**2 + vz**2) @@ -104,7 +101,7 @@ def cartesian_to_spherical( # Ensure azimuth is from 0 to 2PI az = az % (2 * np.pi) - return az, el + return az, el, r def bin_space( @@ -133,7 +130,8 @@ def bin_space( build_spatial_bins() ) - az, el = cartesian_to_spherical(vx, vy, vz) + v = np.column_stack((vx, vy, vz)) + az, el, _ = cartesian_to_spherical(v) az_degrees = np.degrees(az) el_degrees = np.degrees(el) From 19a52a5c199ffd2338e63db77b4cbf720715e725 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 19 Sep 2024 16:25:29 -0600 Subject: [PATCH 08/11] added bin_id --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 31 +++++++++++++------ .../ultra/l1c/ultra_l1c_pset_bins.py | 24 ++++++-------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 84cba7528..e72f56a57 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -70,18 +70,31 @@ def test_cartesian_to_spherical(): def test_bin_space(): """Tests bin_space function.""" # Example particle velocity in the pointing frame wrt s/c. - vx_sc = np.array([-186.5575, 508.5697, 0]) - vy_sc = np.array([-707.5707, -516.0282, 0]) - vz_sc = np.array([618.0569, 892.6931, -1]) + vx_sc = np.array([-186.5575, 508.5697, 508.5697, 0]) + vy_sc = np.array([-707.5707, -516.0282, -516.0282, 0]) + vz_sc = np.array([618.0569, 892.6931, 892.6931, -1]) - az_midpoint, el_midpoint = bin_space(vx_sc, vy_sc, vz_sc) - az, el = cartesian_to_spherical(vx_sc, vy_sc, vz_sc) + v = np.column_stack((vx_sc, vy_sc, vz_sc)) + # 259200 + bin_id = bin_space(v) + + az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( + build_spatial_bins() + ) + expected_az, expected_el, _ = cartesian_to_spherical(v) + + expected_az_degrees = np.degrees(expected_az) + expected_el_degrees = np.degrees(expected_el) + + # Assert that we can back-calculate the bin. + az_indices = bin_id // len(el_bin_midpoints) + el_indices = bin_id % len(el_bin_midpoints) - az_within_tolerance = np.abs(np.degrees(az) - az_midpoint) <= 0.25 - el_within_tolerance = np.abs(np.degrees(el) - el_midpoint) <= 0.25 + # Make certain this is binned properly. + az_bin = az_bin_midpoints[az_indices] + el_bin = el_bin_midpoints[el_indices] - assert np.all(az_within_tolerance) - assert np.all(el_within_tolerance) + print("hi") def test_bin_energy(): diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 0b73fcbeb..9ebf572a0 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -62,7 +62,9 @@ def build_spatial_bins( return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints -def cartesian_to_spherical(v: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]: +def cartesian_to_spherical( + v: tuple[np.ndarray, np.ndarray, np.ndarray], +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Convert cartesian coordinates to spherical coordinates. @@ -105,19 +107,15 @@ def cartesian_to_spherical(v: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.nd def bin_space( - vx: np.ndarray, vy: np.ndarray, vz: np.ndarray -) -> tuple[np.ndarray, np.ndarray]: + v: tuple[np.ndarray, np.ndarray, np.ndarray], +) -> np.ndarray: """ Bin the particle. Parameters ---------- - vx : np.ndarray - The x-components of the velocity vector. - vy : np.ndarray - The y-components of the velocity vector. - vz : np.ndarray - The z-components of the velocity vector. + v : tuple[np.ndarray, np.ndarray, np.ndarray] + The x,y,z-components of the velocity vector. Returns ------- @@ -130,7 +128,6 @@ def bin_space( build_spatial_bins() ) - v = np.column_stack((vx, vy, vz)) az, el, _ = cartesian_to_spherical(v) az_degrees = np.degrees(az) @@ -145,11 +142,10 @@ def bin_space( az_bin_idx = np.searchsorted(az_bin_edges, az_degrees, side="right") - 1 el_bin_idx = np.searchsorted(el_bin_edges, el_degrees, side="right") - 1 - # Assign the corresponding midpoints. - az_midpoint = az_bin_midpoints[az_bin_idx] - el_midpoint = el_bin_midpoints[el_bin_idx] + bin_id = az_bin_idx * len(el_bin_midpoints) + el_bin_idx + _, counts = np.unique(bin_id, return_counts=True) - return az_midpoint, el_midpoint + return bin_id def bin_energy(energy: np.ndarray) -> NDArray[np.float64]: From b8c84b994ab873e89ed7ddcba169a0d83a772400 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 19 Sep 2024 17:19:19 -0600 Subject: [PATCH 09/11] update test --- .../ultra/unit/test_ultra_l1c_pset_bins.py | 25 +++++++++++-------- .../ultra/l1c/ultra_l1c_pset_bins.py | 17 +++++++------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index e72f56a57..ed80aa56f 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -75,26 +75,29 @@ def test_bin_space(): vz_sc = np.array([618.0569, 892.6931, 892.6931, -1]) v = np.column_stack((vx_sc, vy_sc, vz_sc)) - # 259200 - bin_id = bin_space(v) + bin_id, counts = bin_space(v) az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( build_spatial_bins() ) - expected_az, expected_el, _ = cartesian_to_spherical(v) + actual_az, actual_el, _ = cartesian_to_spherical(v) - expected_az_degrees = np.degrees(expected_az) - expected_el_degrees = np.degrees(expected_el) + # Transform flat index bin_id back to 2D grid coordinates. + az_indices, el_indices = divmod(bin_id, len(el_bin_midpoints)) - # Assert that we can back-calculate the bin. - az_indices = bin_id // len(el_bin_midpoints) - el_indices = bin_id % len(el_bin_midpoints) - - # Make certain this is binned properly. az_bin = az_bin_midpoints[az_indices] el_bin = el_bin_midpoints[el_indices] - print("hi") + az_within_tolerance = ( + np.abs(np.sort(az_bin) - np.degrees(np.unique(actual_az))) <= 0.25 + ) + el_within_tolerance = ( + np.abs(np.sort(el_bin) - np.degrees(np.unique(actual_el))) <= 0.25 + ) + + assert np.all(az_within_tolerance) + assert np.all(el_within_tolerance) + assert np.array_equal(counts, np.array([1, 2, 1])) def test_bin_energy(): diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 9ebf572a0..6fd1ef84a 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -2,6 +2,7 @@ import numpy as np from numpy.typing import NDArray +from typing import Any def build_energy_bins() -> NDArray[np.float64]: @@ -108,7 +109,7 @@ def cartesian_to_spherical( def bin_space( v: tuple[np.ndarray, np.ndarray, np.ndarray], -) -> np.ndarray: +) -> tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]: """ Bin the particle. @@ -119,10 +120,10 @@ def bin_space( Returns ------- - az_midpoint : np.ndarray - Array of azimuth midpoint values. - el_midpoint : np.ndarray - Array of elevation midpoint values. + unique_bin_ids : np.ndarray + Bin ids. + counts : np.ndarray + Event counts. """ az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = ( build_spatial_bins() @@ -142,10 +143,12 @@ def bin_space( az_bin_idx = np.searchsorted(az_bin_edges, az_degrees, side="right") - 1 el_bin_idx = np.searchsorted(el_bin_edges, el_degrees, side="right") - 1 + # Create flattened version of the 2D data array. bin_id = az_bin_idx * len(el_bin_midpoints) + el_bin_idx - _, counts = np.unique(bin_id, return_counts=True) + # Counts for each unique bin_id. + unique_bin_ids, counts = np.unique(bin_id, return_counts=True) - return bin_id + return unique_bin_ids, counts def bin_energy(energy: np.ndarray) -> NDArray[np.float64]: From d3702cded05e10b938b7f99b17adf4e63d2985e6 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 19 Sep 2024 17:33:57 -0600 Subject: [PATCH 10/11] update test --- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 6fd1ef84a..414b0d5dd 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -2,7 +2,6 @@ import numpy as np from numpy.typing import NDArray -from typing import Any def build_energy_bins() -> NDArray[np.float64]: @@ -83,7 +82,7 @@ def cartesian_to_spherical( r : np.ndarray The radii, or magnitudes, of the vectors. """ - vx, vy, vz = v[:, 0], v[:, 1], v[:, 2] + vx, vy, vz = v # Magnitude of the velocity vector magnitude_v = np.sqrt(vx**2 + vy**2 + vz**2) @@ -109,7 +108,7 @@ def cartesian_to_spherical( def bin_space( v: tuple[np.ndarray, np.ndarray, np.ndarray], -) -> tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]: +) -> tuple[np.ndarray, np.ndarray]: """ Bin the particle. From faf5175ff7449b74a469f2dc72adaf3c967baf8b Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Thu, 19 Sep 2024 17:42:59 -0600 Subject: [PATCH 11/11] update test --- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 414b0d5dd..19ffb7289 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -82,7 +82,9 @@ def cartesian_to_spherical( r : np.ndarray The radii, or magnitudes, of the vectors. """ - vx, vy, vz = v + vx, vy, vz = np.hsplit(v, 3) + vx, vy, vz = vx.flatten(), vy.flatten(), vz.flatten() # Flatten the arrays + # Magnitude of the velocity vector magnitude_v = np.sqrt(vx**2 + vy**2 + vz**2)