From e8e3052cce2327ea20d09723e158347419c519f0 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:46:56 -0500 Subject: [PATCH 01/16] mask zeros and nans from swapi pseudo parameter fit --- imap_processing/ialirt/l0/process_swapi.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index e987f80ac..cd1751f0e 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -111,18 +111,18 @@ def optimize_pseudo_parameters( 60000 * (initial_speed_guess / 400) ** 2, ] ) + five_point_range = range(max_index - 2, max_index + 2 + 1) + xdata = energy_passbands.take(five_point_range, mode="clip") + ydata = current_sweep_count_rates.take(five_point_range, mode="clip") + sigma = current_sweep_count_rate_errors.take(five_point_range, mode="clip") + mask = (ydata > 0) & (sigma > 0) & np.isfinite(xdata) & np.isfinite(ydata) & np.isfinite(sigma) sol = curve_fit( f=count_rate, - xdata=energy_passbands.take( - range(max_index - 3, max_index + 3), mode="clip" - ), - ydata=current_sweep_count_rates.take( - range(max_index - 3, max_index + 3), mode="clip" - ), - sigma=current_sweep_count_rate_errors.take( - range(max_index - 3, max_index + 3), mode="clip" - ), + xdata=xdata[mask], + ydata=ydata[mask], + sigma=sigma[mask], p0=initial_param_guess, + full_output=True ) solution_dict["pseudo_speed"].append(sol[0][0]) solution_dict["pseudo_density"].append(sol[0][1]) From c2f0729aa9332278b2b5ad8259d93c9399e8968d Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:51:10 -0500 Subject: [PATCH 02/16] add assertion --- imap_processing/ialirt/l0/process_swapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index cd1751f0e..570def253 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -116,6 +116,7 @@ def optimize_pseudo_parameters( ydata = current_sweep_count_rates.take(five_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(five_point_range, mode="clip") mask = (ydata > 0) & (sigma > 0) & np.isfinite(xdata) & np.isfinite(ydata) & np.isfinite(sigma) + assert np.count_nonzero(mask) > 0 sol = curve_fit( f=count_rate, xdata=xdata[mask], From 6519994911b055f43bee62da498dce6500ee7f60 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Sat, 6 Dec 2025 22:04:37 -0500 Subject: [PATCH 03/16] Revert to using 6 points for fitting --- imap_processing/ialirt/l0/process_swapi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 570def253..80feb3fad 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -111,10 +111,10 @@ def optimize_pseudo_parameters( 60000 * (initial_speed_guess / 400) ** 2, ] ) - five_point_range = range(max_index - 2, max_index + 2 + 1) - xdata = energy_passbands.take(five_point_range, mode="clip") - ydata = current_sweep_count_rates.take(five_point_range, mode="clip") - sigma = current_sweep_count_rate_errors.take(five_point_range, mode="clip") + fitting_point_range = range(max_index - 3, max_index + 3) + xdata = energy_passbands.take(fitting_point_range, mode="clip") + ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") + sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") mask = (ydata > 0) & (sigma > 0) & np.isfinite(xdata) & np.isfinite(ydata) & np.isfinite(sigma) assert np.count_nonzero(mask) > 0 sol = curve_fit( From 042d29e234b46576cddb6e5fc5da569f2beb84e3 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 08:01:01 -0500 Subject: [PATCH 04/16] Update imap_processing/ialirt/l0/process_swapi.py Co-authored-by: Greg Lucas --- imap_processing/ialirt/l0/process_swapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 80feb3fad..d169dc272 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -123,7 +123,6 @@ def optimize_pseudo_parameters( ydata=ydata[mask], sigma=sigma[mask], p0=initial_param_guess, - full_output=True ) solution_dict["pseudo_speed"].append(sol[0][0]) solution_dict["pseudo_density"].append(sol[0][1]) From 4b75b36534587c3177bf1c0bdc191712800d9a7f Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:12:10 -0500 Subject: [PATCH 05/16] use nan as solution if less than 3 points available for fitting --- imap_processing/ialirt/l0/process_swapi.py | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 80feb3fad..68c5d9005 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -111,24 +111,31 @@ def optimize_pseudo_parameters( 60000 * (initial_speed_guess / 400) ** 2, ] ) + fitting_point_range = range(max_index - 3, max_index + 3) xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - mask = (ydata > 0) & (sigma > 0) & np.isfinite(xdata) & np.isfinite(ydata) & np.isfinite(sigma) - assert np.count_nonzero(mask) > 0 - sol = curve_fit( - f=count_rate, - xdata=xdata[mask], - ydata=ydata[mask], - sigma=sigma[mask], - p0=initial_param_guess, - full_output=True - ) - solution_dict["pseudo_speed"].append(sol[0][0]) - solution_dict["pseudo_density"].append(sol[0][1]) - solution_dict["pseudo_temperature"].append(sol[0][2]) - + is_valid_data = ((ydata > 0) + & (sigma > 0) + & np.isfinite(xdata) + & np.isfinite(ydata) + & np.isfinite(sigma)) + + if np.count_nonzero(is_valid_data) >= 3: + solution = curve_fit( + f=count_rate, + xdata=xdata[is_valid_data], + ydata=ydata[is_valid_data], + sigma=sigma[is_valid_data], + p0=initial_param_guess + )[0] + else: + solution = [np.nan] * 3 + + solution_dict["pseudo_speed"].append(solution[0]) + solution_dict["pseudo_density"].append(solution[1]) + solution_dict["pseudo_temperature"].append(solution[2]) return solution_dict From 194115e2617cadf895caf27a7f6265c733f858b2 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:27:30 -0500 Subject: [PATCH 06/16] remove unnecessary check on xdata --- imap_processing/ialirt/l0/process_swapi.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 68c5d9005..8bedb78de 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -116,11 +116,8 @@ def optimize_pseudo_parameters( xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ((ydata > 0) - & (sigma > 0) - & np.isfinite(xdata) - & np.isfinite(ydata) - & np.isfinite(sigma)) + is_valid_data = ((ydata > 0) & (sigma > 0) + & np.isfinite(ydata) & np.isfinite(sigma)) if np.count_nonzero(is_valid_data) >= 3: solution = curve_fit( From 0e8ac258702563c277f251370511623952f37bea Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 09:38:50 -0500 Subject: [PATCH 07/16] Revert "remove unnecessary check on xdata" This reverts commit 194115e2617cadf895caf27a7f6265c733f858b2. --- imap_processing/ialirt/l0/process_swapi.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 8bedb78de..68c5d9005 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -116,8 +116,11 @@ def optimize_pseudo_parameters( xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ((ydata > 0) & (sigma > 0) - & np.isfinite(ydata) & np.isfinite(sigma)) + is_valid_data = ((ydata > 0) + & (sigma > 0) + & np.isfinite(xdata) + & np.isfinite(ydata) + & np.isfinite(sigma)) if np.count_nonzero(is_valid_data) >= 3: solution = curve_fit( From 91a3d69bc88de36bd82e6cb206058a7bfcae18de Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:13:01 -0500 Subject: [PATCH 08/16] Add unit tests --- .../tests/ialirt/unit/test_process_swapi.py | 102 +++++++++++++++--- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/imap_processing/tests/ialirt/unit/test_process_swapi.py b/imap_processing/tests/ialirt/unit/test_process_swapi.py index db1e5778a..6f6bea427 100644 --- a/imap_processing/tests/ialirt/unit/test_process_swapi.py +++ b/imap_processing/tests/ialirt/unit/test_process_swapi.py @@ -172,11 +172,10 @@ def test_count_rate(): ) -def test_optimize_parameters(): - """Test that the optimize_pseudo_parameters() function works correctly.""" - +@pytest.fixture +def test_data(): # The following files and values are all validation sets provided by the SWAPI team. - test_data = { + return { "test_set_1": { "file_name": "ialirt_test_data_u_sw_550_n_sw_5_T_sw_100000_v2.csv", "expected_values": { # expected output and acceptable tolerance @@ -203,34 +202,111 @@ def test_optimize_parameters(): }, } + +@pytest.fixture +def energy_passbands(): calibration_test_file = pd.read_csv( f"{imap_module_directory}/tests/ialirt/data/l0/swapi_ialirt_energy_steps.csv" ) energy_passbands = calibration_test_file["Energy"][0:63].to_numpy().astype(float) + return energy_passbands + + +def test_optimize_parameters(test_data, energy_passbands): + """Test that the optimize_pseudo_parameters() function works correctly.""" + + for test_set in test_data: + energy_data = pd.read_csv( + f"{imap_module_directory}/tests/ialirt/data/l0/" + f"{test_data[test_set]['file_name']}", + ) + count_rates = energy_data["Count Rates [Hz]"].to_numpy() + count_rates[0] = 0.0 # set the first count to zero + count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x 2, shape (72,) -> (2, 72) + count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() + count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + result = optimize_pseudo_parameters( + count_rates, count_rates_errors, energy_passbands + ) + + for param in test_data[test_set]["expected_values"]: + np.testing.assert_allclose( + result[param][0], + test_data[test_set]["expected_values"][param][0], + rtol=test_data[test_set]["expected_values"][param][1], + err_msg=f"{param} did not match the expected result within the tolerance." + ) + + +def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): + """Test that the optimize_pseudo_parameters() function works correctly with invalid count and count rate values.""" + for test_set in test_data: + energy_data = pd.read_csv( + f"{imap_module_directory}/tests/ialirt/data/l0/" + f"{test_data[test_set]['file_name']}", + ) + count_rates = energy_data["Count Rates [Hz]"].to_numpy() + count_rates[0] = 0.0 # set the first count to zero + count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x N, shape (72,) -> (N, 72), so that there are N sweeps + count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() + count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + # set some points to have invalid values that should not affect the fit too much + i_peak = count_rates[0].argmax() # invalid values need to be close to the maximum to be detected + + # sweep 0: zero count/count rate + count_rates[0, i_peak + 2] = 0 + count_rates_errors[0, i_peak + 2] = 0 + + # sweep 1: nan count/count rate + count_rates[0, i_peak + 2] = np.nan + count_rates_errors[0, i_peak + 2] = np.nan + + result = optimize_pseudo_parameters( + count_rates, count_rates_errors, energy_passbands + ) + + for param in test_data[test_set]["expected_values"]: + # because invalid values inserted, higher error tolerance by 50% + np.testing.assert_allclose( + result[param], + test_data[test_set]["expected_values"][param][0], + rtol=0.5 + test_data[test_set]["expected_values"][param][1], + err_msg=f"{param} did not match the expected result within the tolerance." + ) + +def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): + """Test that the optimize_pseudo_parameters() function works correctly with invalid energy.""" for test_set in test_data: energy_data = pd.read_csv( f"{imap_module_directory}/tests/ialirt/data/l0/" f"{test_data[test_set]['file_name']}", ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() - count_rates[0] = 0.0 - count_rates = np.tile(count_rates, (2, 1)) + count_rates[0] = 0.0 # set the first count to zero + count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x N, shape (72,) -> (N, 72), so that there are N sweeps count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + # set some points to have invalid values that should not affect the fit too much + i_peak = count_rates[0].argmax() # invalid values need to be close to the maximum to be detected + # set passband energy next to peak to nan + energy_passbands[i_peak + 1] = np.nan + result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands ) for param in test_data[test_set]["expected_values"]: - ( - np.testing.assert_allclose( - result[param][0], - test_data[test_set]["expected_values"][param][0], - rtol=test_data[test_set]["expected_values"][param][1], - ), - f"{param} did not match the expected result within the tolerance.", + # because invalid values inserted, higher error tolerance by 50% + np.testing.assert_allclose( + result[param], + test_data[test_set]["expected_values"][param][0], + rtol=0.5 + test_data[test_set]["expected_values"][param][1], + err_msg=f"{param} did not match the expected result within the tolerance." ) From 898da2e8c2e99f96baf15a65f7d8adb6602e6de6 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:56:18 -0500 Subject: [PATCH 09/16] dynmically determine the sweep number --- imap_processing/ialirt/l0/process_swapi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 68c5d9005..fb29fcb73 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -159,6 +159,9 @@ def process_swapi_ialirt( """ logger.info("Processing SWAPI.") + # TODO: account for potentially multiple sweep table versions in one packet due to it falling on the change date + sweep_table_version = unpacked_data['swapi_version'].values[0] + sci_dataset = unpacked_data.sortby("epoch", ascending=True) met = calculate_time( @@ -219,7 +222,7 @@ def process_swapi_ialirt( # Find the sweep's energy data for the latest time, where sweep_id == 2 subset = calibration_lut_table[ (calibration_lut_table["timestamp"] == calibration_lut_table["timestamp"].max()) - & (calibration_lut_table["Sweep #"] == 2) + & (calibration_lut_table["Sweep #"] == sweep_table_version) ] if subset.empty: energy_passbands = np.full(NUM_IALIRT_ENERGY_STEPS, np.nan, dtype=np.float64) From 7825f9340750d79949d2f931dfb894adee0d6385 Mon Sep 17 00:00:00 2001 From: Laura Sandoval Date: Tue, 9 Dec 2025 10:50:10 -0700 Subject: [PATCH 10/16] Add tests for swapi_product structure and behavior --- ...wapi_esa-unit-conversion_20250626_v001.csv | 289 ++++++++++++++ ...wapi_esa-unit-conversion_20251201_v001.csv | 361 ++++++++++++++++++ .../data/l0/iois_1_packets_2025_322_12_00_56 | Bin 0 -> 11346 bytes .../data/l0/iois_1_packets_2025_343_00_00_17 | Bin 0 -> 11163 bytes imap_processing/tests/ialirt/unit/conftest.py | 36 ++ .../tests/ialirt/unit/test_process_swapi.py | 105 ++++- 6 files changed, 773 insertions(+), 18 deletions(-) create mode 100644 imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20250626_v001.csv create mode 100644 imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20251201_v001.csv create mode 100644 imap_processing/tests/ialirt/data/l0/iois_1_packets_2025_322_12_00_56 create mode 100644 imap_processing/tests/ialirt/data/l0/iois_1_packets_2025_343_00_00_17 diff --git a/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20250626_v001.csv b/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20250626_v001.csv new file mode 100644 index 000000000..ff96dc762 --- /dev/null +++ b/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20250626_v001.csv @@ -0,0 +1,289 @@ +timestamp,ESA Step #,K factor,Voltage,Energy,Sweep #,ESA Index Number,LUT version number +2/11/2025 0:00,0,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,1,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,2,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,3,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,4,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,5,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,6,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,7,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,8,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,9,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,10,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,11,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,12,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,13,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,14,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,15,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,16,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,17,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,18,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,19,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,20,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,21,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,22,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,23,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,24,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,25,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,26,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,27,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,28,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,29,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,30,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,31,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,32,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,33,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,34,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,35,1.88,568,"1,068",0,560,4 +2/11/2025 0:00,36,1.88,522,981,0,576,4 +2/11/2025 0:00,37,1.88,479,901,0,592,4 +2/11/2025 0:00,38,1.88,440,828,0,608,4 +2/11/2025 0:00,39,1.88,404,760,0,624,4 +2/11/2025 0:00,40,1.88,371,698,0,640,4 +2/11/2025 0:00,41,1.88,341,641,0,656,4 +2/11/2025 0:00,42,1.88,313,589,0,672,4 +2/11/2025 0:00,43,1.88,289,544,0,688,4 +2/11/2025 0:00,44,1.88,264,497,0,704,4 +2/11/2025 0:00,45,1.88,244,459,0,720,4 +2/11/2025 0:00,46,1.88,224,421,0,736,4 +2/11/2025 0:00,47,1.88,207,389,0,752,4 +2/11/2025 0:00,48,1.88,189,355,0,768,4 +2/11/2025 0:00,49,1.88,174,326,0,784,4 +2/11/2025 0:00,50,1.88,159,298,0,800,4 +2/11/2025 0:00,51,1.88,146,275,0,816,4 +2/11/2025 0:00,52,1.88,134,252,0,832,4 +2/11/2025 0:00,53,1.88,124,234,0,848,4 +2/11/2025 0:00,54,1.88,114,214,0,864,4 +2/11/2025 0:00,55,1.88,104,195,0,880,4 +2/11/2025 0:00,56,1.88,96,181,0,896,4 +2/11/2025 0:00,57,1.88,89,167,0,912,4 +2/11/2025 0:00,58,1.88,82,153,0,928,4 +2/11/2025 0:00,59,1.88,74,139,0,944,4 +2/11/2025 0:00,60,1.88,69,129,0,960,4 +2/11/2025 0:00,61,1.88,64,120,0,976,4 +2/11/2025 0:00,62,1.88,57,107,0,992,4 +2/11/2025 0:00,63,1.88,Solve,Solve,0,-16,4 +2/11/2025 0:00,64,1.88,Solve,Solve,0,-12,4 +2/11/2025 0:00,65,1.88,Solve,Solve,0,-8,4 +2/11/2025 0:00,66,1.88,Solve,Solve,0,-4,4 +2/11/2025 0:00,67,1.88,Solve,Solve,0,0,4 +2/11/2025 0:00,68,1.88,Solve,Solve,0,4,4 +2/11/2025 0:00,69,1.88,Solve,Solve,0,8,4 +2/11/2025 0:00,70,1.88,Solve,Solve,0,12,4 +2/11/2025 0:00,71,1.88,Solve,Solve,0,16,4 +5/19/2025 0:00,0,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,1,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,2,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,3,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,4,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,5,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,6,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,7,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,8,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,9,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,10,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,11,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,12,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,13,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,14,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,15,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,16,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,17,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,18,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,19,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,20,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,21,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,22,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,23,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,24,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,25,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,26,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,27,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,28,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,29,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,30,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,31,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,32,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,33,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,0,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,0,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,0,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,0,592,6 +5/19/2025 0:00,38,1.93,414.7,800.3650292,0,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,0,624,6 +5/19/2025 0:00,40,1.93,348.7,672.9984993,0,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,0,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,0,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,0,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,0,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,0,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,0,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,0,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,0,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,0,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,0,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,0,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,0,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,0,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,0,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,0,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,0,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,0,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,0,928,6 +5/19/2025 0:00,59,1.93,67.2,129.6913506,0,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,0,960,6 +5/19/2025 0:00,61,1.93,56.5,109.0528461,0,976,6 +5/19/2025 0:00,62,1.93,51.81,100,0,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,0,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,0,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,0,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,0,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,0,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,0,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,0,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,0,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,0,16,6 +5/19/2025 0:00,0,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,1,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,2,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,3,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,4,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,5,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,6,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,7,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,8,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,9,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,10,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,11,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,12,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,13,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,14,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,15,1.93,2994.50,5779.393516,1,243,6 +5/19/2025 0:00,16,1.93,2790.90,5386.444636,1,256,6 +5/19/2025 0:00,17,1.93,2559.22,4939.297624,1,272,6 +5/19/2025 0:00,18,1.93,2346.77,4529.269801,1,288,6 +5/19/2025 0:00,19,1.93,2151.96,4153.279775,1,304,6 +5/19/2025 0:00,20,1.93,1973.32,3808.501955,1,320,6 +5/19/2025 0:00,21,1.93,1809.51,3492.34531,1,336,6 +5/19/2025 0:00,22,1.93,1659.29,3202.433898,1,352,6 +5/19/2025 0:00,23,1.93,1521.55,2936.589015,1,368,6 +5/19/2025 0:00,24,1.93,1395.24,2692.812815,1,384,6 +5/19/2025 0:00,25,1.93,1279.42,2469.273304,1,400,6 +5/19/2025 0:00,26,1.93,1173.21,2264.290564,1,416,6 +5/19/2025 0:00,27,1.93,1075.82,2076.324136,1,432,6 +5/19/2025 0:00,28,1.93,986.51,1903.961438,1,448,6 +5/19/2025 0:00,29,1.93,904.62,1745.907151,1,464,6 +5/19/2025 0:00,30,1.93,829.52,1600.973485,1,480,6 +5/19/2025 0:00,31,1.93,760.66,1468.071254,1,496,6 +5/19/2025 0:00,32,1.93,697.51,1346.201688,1,512,6 +5/19/2025 0:00,33,1.93,639.61,1234.448926,1,528,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,1,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,1,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,1,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,1,592,6 +5/19/2025 0:00,38,1.93,414.70,800.3650292,1,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,1,624,6 +5/19/2025 0:00,40,1.93,348.70,672.9984993,1,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,1,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,1,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,1,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,1,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,1,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,1,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,1,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,1,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,1,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,1,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,1,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,1,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,1,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,1,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,1,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,1,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,1,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,1,928,6 +5/19/2025 0:00,59,1.93,67.20,129.6913506,1,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,1,960,6 +5/19/2025 0:00,61,1.93,56.50,109.0528461,1,976,6 +5/19/2025 0:00,62,1.93,51.81,100,1,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,1,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,1,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,1,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,1,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,1,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,1,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,1,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,1,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,1,16,6 +5/19/2025 0:00,0,1.93,10240,19763.2,2,0,6 +5/19/2025 0:00,1,1.93,10240,19763.2,2,16,6 +5/19/2025 0:00,2,1.93,9389.94292,18122.58984,2,32,6 +5/19/2025 0:00,3,1.93,8610.451958,16618.17228,2,48,6 +5/19/2025 0:00,4,1.93,7895.66918,15238.64152,2,64,6 +5/19/2025 0:00,5,1.93,7240.222941,13973.63028,2,80,6 +5/19/2025 0:00,6,1.93,6639.187515,12813.6319,2,96,6 +5/19/2025 0:00,7,1.93,6088.046074,11749.92892,2,112,6 +5/19/2025 0:00,8,1.93,5582.656751,10774.52753,2,128,6 +5/19/2025 0:00,9,1.93,5119.221508,9880.09751,2,144,6 +5/19/2025 0:00,10,1.93,4694.257593,9059.917155,2,160,6 +5/19/2025 0:00,11,1.93,4304.571373,8307.822749,2,176,6 +5/19/2025 0:00,12,1.93,3947.234325,7618.162247,2,192,6 +5/19/2025 0:00,13,1.93,3619.561035,6985.752798,2,208,6 +5/19/2025 0:00,14,1.93,3319.089016,6405.8418,2,224,6 +5/19/2025 0:00,15,1.93,3043.560196,5874.071178,2,240,6 +5/19/2025 0:00,16,1.93,2790.90,5386.444636,2,256,6 +5/19/2025 0:00,17,1.93,2559.22,4939.297624,2,272,6 +5/19/2025 0:00,18,1.93,2346.77,4529.269801,2,288,6 +5/19/2025 0:00,19,1.93,2151.96,4153.279775,2,304,6 +5/19/2025 0:00,20,1.93,1973.32,3808.501955,2,320,6 +5/19/2025 0:00,21,1.93,1809.51,3492.34531,2,336,6 +5/19/2025 0:00,22,1.93,1659.29,3202.433898,2,352,6 +5/19/2025 0:00,23,1.93,1521.55,2936.589015,2,368,6 +5/19/2025 0:00,24,1.93,1395.24,2692.812815,2,384,6 +5/19/2025 0:00,25,1.93,1279.42,2469.273304,2,400,6 +5/19/2025 0:00,26,1.93,1173.21,2264.290564,2,416,6 +5/19/2025 0:00,27,1.93,1075.82,2076.324136,2,432,6 +5/19/2025 0:00,28,1.93,986.51,1903.961438,2,448,6 +5/19/2025 0:00,29,1.93,904.62,1745.907151,2,464,6 +5/19/2025 0:00,30,1.93,829.52,1600.973485,2,480,6 +5/19/2025 0:00,31,1.93,760.66,1468.071254,2,496,6 +5/19/2025 0:00,32,1.93,697.51,1346.201688,2,512,6 +5/19/2025 0:00,33,1.93,639.61,1234.448926,2,528,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,2,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,2,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,2,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,2,592,6 +5/19/2025 0:00,38,1.93,414.70,800.3650292,2,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,2,624,6 +5/19/2025 0:00,40,1.93,348.70,672.9984993,2,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,2,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,2,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,2,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,2,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,2,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,2,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,2,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,2,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,2,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,2,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,2,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,2,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,2,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,2,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,2,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,2,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,2,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,2,928,6 +5/19/2025 0:00,59,1.93,67.20,129.6913506,2,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,2,960,6 +5/19/2025 0:00,61,1.93,56.50,109.0528461,2,976,6 +5/19/2025 0:00,62,1.93,51.81,100,2,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,2,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,2,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,2,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,2,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,2,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,2,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,2,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,2,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,2,16,6 diff --git a/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20251201_v001.csv b/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20251201_v001.csv new file mode 100644 index 000000000..24da17574 --- /dev/null +++ b/imap_processing/tests/ialirt/data/l0/imap_swapi_esa-unit-conversion_20251201_v001.csv @@ -0,0 +1,361 @@ +timestamp,ESA Step #,K factor,Voltage,Energy,Sweep #,ESA Index Number,LUT version number +2/11/2025 0:00,0,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,1,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,2,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,3,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,4,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,5,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,6,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,7,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,8,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,9,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,10,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,11,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,12,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,13,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,14,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,15,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,16,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,17,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,18,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,19,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,20,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,21,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,22,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,23,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,24,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,25,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,26,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,27,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,28,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,29,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,30,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,31,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,32,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,33,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,34,1.88,619,"1,163",0,544,4 +2/11/2025 0:00,35,1.88,568,"1,068",0,560,4 +2/11/2025 0:00,36,1.88,522,981,0,576,4 +2/11/2025 0:00,37,1.88,479,901,0,592,4 +2/11/2025 0:00,38,1.88,440,828,0,608,4 +2/11/2025 0:00,39,1.88,404,760,0,624,4 +2/11/2025 0:00,40,1.88,371,698,0,640,4 +2/11/2025 0:00,41,1.88,341,641,0,656,4 +2/11/2025 0:00,42,1.88,313,589,0,672,4 +2/11/2025 0:00,43,1.88,289,544,0,688,4 +2/11/2025 0:00,44,1.88,264,497,0,704,4 +2/11/2025 0:00,45,1.88,244,459,0,720,4 +2/11/2025 0:00,46,1.88,224,421,0,736,4 +2/11/2025 0:00,47,1.88,207,389,0,752,4 +2/11/2025 0:00,48,1.88,189,355,0,768,4 +2/11/2025 0:00,49,1.88,174,326,0,784,4 +2/11/2025 0:00,50,1.88,159,298,0,800,4 +2/11/2025 0:00,51,1.88,146,275,0,816,4 +2/11/2025 0:00,52,1.88,134,252,0,832,4 +2/11/2025 0:00,53,1.88,124,234,0,848,4 +2/11/2025 0:00,54,1.88,114,214,0,864,4 +2/11/2025 0:00,55,1.88,104,195,0,880,4 +2/11/2025 0:00,56,1.88,96,181,0,896,4 +2/11/2025 0:00,57,1.88,89,167,0,912,4 +2/11/2025 0:00,58,1.88,82,153,0,928,4 +2/11/2025 0:00,59,1.88,74,139,0,944,4 +2/11/2025 0:00,60,1.88,69,129,0,960,4 +2/11/2025 0:00,61,1.88,64,120,0,976,4 +2/11/2025 0:00,62,1.88,57,107,0,992,4 +2/11/2025 0:00,63,1.88,Solve,Solve,0,-16,4 +2/11/2025 0:00,64,1.88,Solve,Solve,0,-12,4 +2/11/2025 0:00,65,1.88,Solve,Solve,0,-8,4 +2/11/2025 0:00,66,1.88,Solve,Solve,0,-4,4 +2/11/2025 0:00,67,1.88,Solve,Solve,0,0,4 +2/11/2025 0:00,68,1.88,Solve,Solve,0,4,4 +2/11/2025 0:00,69,1.88,Solve,Solve,0,8,4 +2/11/2025 0:00,70,1.88,Solve,Solve,0,12,4 +2/11/2025 0:00,71,1.88,Solve,Solve,0,16,4 +5/19/2025 0:00,0,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,1,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,2,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,3,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,4,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,5,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,6,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,7,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,8,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,9,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,10,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,11,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,12,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,13,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,14,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,15,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,16,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,17,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,18,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,19,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,20,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,21,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,22,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,23,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,24,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,25,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,26,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,27,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,28,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,29,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,30,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,31,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,32,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,33,1.93,622.52,1201.466213,0,533,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,0,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,0,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,0,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,0,592,6 +5/19/2025 0:00,38,1.93,414.7,800.3650292,0,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,0,624,6 +5/19/2025 0:00,40,1.93,348.7,672.9984993,0,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,0,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,0,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,0,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,0,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,0,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,0,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,0,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,0,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,0,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,0,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,0,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,0,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,0,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,0,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,0,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,0,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,0,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,0,928,6 +5/19/2025 0:00,59,1.93,67.2,129.6913506,0,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,0,960,6 +5/19/2025 0:00,61,1.93,56.5,109.0528461,0,976,6 +5/19/2025 0:00,62,1.93,51.81,100,0,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,0,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,0,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,0,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,0,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,0,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,0,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,0,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,0,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,0,16,6 +5/19/2025 0:00,0,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,1,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,2,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,3,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,4,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,5,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,6,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,7,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,8,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,9,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,10,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,11,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,12,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,13,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,14,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,15,1.93,2994.5,5779.393516,1,243,6 +5/19/2025 0:00,16,1.93,2790.9,5386.444636,1,256,6 +5/19/2025 0:00,17,1.93,2559.22,4939.297624,1,272,6 +5/19/2025 0:00,18,1.93,2346.77,4529.269801,1,288,6 +5/19/2025 0:00,19,1.93,2151.96,4153.279775,1,304,6 +5/19/2025 0:00,20,1.93,1973.32,3808.501955,1,320,6 +5/19/2025 0:00,21,1.93,1809.51,3492.34531,1,336,6 +5/19/2025 0:00,22,1.93,1659.29,3202.433898,1,352,6 +5/19/2025 0:00,23,1.93,1521.55,2936.589015,1,368,6 +5/19/2025 0:00,24,1.93,1395.24,2692.812815,1,384,6 +5/19/2025 0:00,25,1.93,1279.42,2469.273304,1,400,6 +5/19/2025 0:00,26,1.93,1173.21,2264.290564,1,416,6 +5/19/2025 0:00,27,1.93,1075.82,2076.324136,1,432,6 +5/19/2025 0:00,28,1.93,986.51,1903.961438,1,448,6 +5/19/2025 0:00,29,1.93,904.62,1745.907151,1,464,6 +5/19/2025 0:00,30,1.93,829.52,1600.973485,1,480,6 +5/19/2025 0:00,31,1.93,760.66,1468.071254,1,496,6 +5/19/2025 0:00,32,1.93,697.51,1346.201688,1,512,6 +5/19/2025 0:00,33,1.93,639.61,1234.448926,1,528,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,1,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,1,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,1,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,1,592,6 +5/19/2025 0:00,38,1.93,414.7,800.3650292,1,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,1,624,6 +5/19/2025 0:00,40,1.93,348.7,672.9984993,1,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,1,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,1,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,1,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,1,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,1,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,1,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,1,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,1,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,1,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,1,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,1,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,1,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,1,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,1,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,1,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,1,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,1,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,1,928,6 +5/19/2025 0:00,59,1.93,67.2,129.6913506,1,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,1,960,6 +5/19/2025 0:00,61,1.93,56.5,109.0528461,1,976,6 +5/19/2025 0:00,62,1.93,51.81,100,1,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,1,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,1,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,1,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,1,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,1,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,1,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,1,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,1,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,1,16,6 +5/19/2025 0:00,0,1.93,10240,19763.2,2,0,6 +5/19/2025 0:00,1,1.93,10240,19763.2,2,16,6 +5/19/2025 0:00,2,1.93,9389.94292,18122.58984,2,32,6 +5/19/2025 0:00,3,1.93,8610.451958,16618.17228,2,48,6 +5/19/2025 0:00,4,1.93,7895.66918,15238.64152,2,64,6 +5/19/2025 0:00,5,1.93,7240.222941,13973.63028,2,80,6 +5/19/2025 0:00,6,1.93,6639.187515,12813.6319,2,96,6 +5/19/2025 0:00,7,1.93,6088.046074,11749.92892,2,112,6 +5/19/2025 0:00,8,1.93,5582.656751,10774.52753,2,128,6 +5/19/2025 0:00,9,1.93,5119.221508,9880.09751,2,144,6 +5/19/2025 0:00,10,1.93,4694.257593,9059.917155,2,160,6 +5/19/2025 0:00,11,1.93,4304.571373,8307.822749,2,176,6 +5/19/2025 0:00,12,1.93,3947.234325,7618.162247,2,192,6 +5/19/2025 0:00,13,1.93,3619.561035,6985.752798,2,208,6 +5/19/2025 0:00,14,1.93,3319.089016,6405.8418,2,224,6 +5/19/2025 0:00,15,1.93,3043.560196,5874.071178,2,240,6 +5/19/2025 0:00,16,1.93,2790.9,5386.444636,2,256,6 +5/19/2025 0:00,17,1.93,2559.22,4939.297624,2,272,6 +5/19/2025 0:00,18,1.93,2346.77,4529.269801,2,288,6 +5/19/2025 0:00,19,1.93,2151.96,4153.279775,2,304,6 +5/19/2025 0:00,20,1.93,1973.32,3808.501955,2,320,6 +5/19/2025 0:00,21,1.93,1809.51,3492.34531,2,336,6 +5/19/2025 0:00,22,1.93,1659.29,3202.433898,2,352,6 +5/19/2025 0:00,23,1.93,1521.55,2936.589015,2,368,6 +5/19/2025 0:00,24,1.93,1395.24,2692.812815,2,384,6 +5/19/2025 0:00,25,1.93,1279.42,2469.273304,2,400,6 +5/19/2025 0:00,26,1.93,1173.21,2264.290564,2,416,6 +5/19/2025 0:00,27,1.93,1075.82,2076.324136,2,432,6 +5/19/2025 0:00,28,1.93,986.51,1903.961438,2,448,6 +5/19/2025 0:00,29,1.93,904.62,1745.907151,2,464,6 +5/19/2025 0:00,30,1.93,829.52,1600.973485,2,480,6 +5/19/2025 0:00,31,1.93,760.66,1468.071254,2,496,6 +5/19/2025 0:00,32,1.93,697.51,1346.201688,2,512,6 +5/19/2025 0:00,33,1.93,639.61,1234.448926,2,528,6 +5/19/2025 0:00,34,1.93,586.51,1131.97314,2,544,6 +5/19/2025 0:00,35,1.93,537.83,1038.004216,2,560,6 +5/19/2025 0:00,36,1.93,493.18,951.8359709,2,576,6 +5/19/2025 0:00,37,1.93,452.24,872.8208434,2,592,6 +5/19/2025 0:00,38,1.93,414.7,800.3650292,2,608,6 +5/19/2025 0:00,39,1.93,380.27,733.9240176,2,624,6 +5/19/2025 0:00,40,1.93,348.7,672.9984993,2,640,6 +5/19/2025 0:00,41,1.93,319.76,617.1306146,2,656,6 +5/19/2025 0:00,42,1.93,293.21,565.9005123,2,672,6 +5/19/2025 0:00,43,1.93,268.87,518.9231943,2,688,6 +5/19/2025 0:00,44,1.93,246.55,475.8456225,2,704,6 +5/19/2025 0:00,45,1.93,226.09,436.3440658,2,720,6 +5/19/2025 0:00,46,1.93,207.32,400.1216672,2,736,6 +5/19/2025 0:00,47,1.93,190.11,366.9062125,2,752,6 +5/19/2025 0:00,48,1.93,174.33,336.4480852,2,768,6 +5/19/2025 0:00,49,1.93,159.85,308.5183902,2,784,6 +5/19/2025 0:00,50,1.93,146.58,282.9072338,2,800,6 +5/19/2025 0:00,51,1.93,134.42,259.4221462,2,816,6 +5/19/2025 0:00,52,1.93,123.26,237.8866352,2,832,6 +5/19/2025 0:00,53,1.93,113.03,218.13886,2,848,6 +5/19/2025 0:00,54,1.93,103.64,200.0304145,2,864,6 +5/19/2025 0:00,55,1.93,95.04,183.4252123,2,880,6 +5/19/2025 0:00,56,1.93,87.15,168.1984643,2,896,6 +5/19/2025 0:00,57,1.93,79.91,154.2357401,2,912,6 +5/19/2025 0:00,58,1.93,73.28,141.432109,2,928,6 +5/19/2025 0:00,59,1.93,67.2,129.6913506,2,944,6 +5/19/2025 0:00,60,1.93,61.62,118.9252324,2,960,6 +5/19/2025 0:00,61,1.93,56.5,109.0528461,2,976,6 +5/19/2025 0:00,62,1.93,51.81,100,2,992,6 +5/19/2025 0:00,63,1.93,Solve,Solve,2,-16,6 +5/19/2025 0:00,64,1.93,Solve,Solve,2,-12,6 +5/19/2025 0:00,65,1.93,Solve,Solve,2,-8,6 +5/19/2025 0:00,66,1.93,Solve,Solve,2,-4,6 +5/19/2025 0:00,67,1.93,Solve,Solve,2,0,6 +5/19/2025 0:00,68,1.93,Solve,Solve,2,4,6 +5/19/2025 0:00,69,1.93,Solve,Solve,2,8,6 +5/19/2025 0:00,70,1.93,Solve,Solve,2,12,6 +5/19/2025 0:00,71,1.93,Solve,Solve,2,16,6 +12/1/2025 20:20,0,1.93,10240,19763.2,3,0,8 +12/1/2025 20:20,1,1.93,10240,19763.2,3,16,8 +12/1/2025 20:20,2,1.93,9389.94292,18122.58984,3,32,8 +12/1/2025 20:20,3,1.93,8610.451958,16618.17228,3,48,8 +12/1/2025 20:20,4,1.93,7895.66918,15238.64152,3,64,8 +12/1/2025 20:20,5,1.93,7240.222941,13973.63028,3,80,8 +12/1/2025 20:20,6,1.93,6639.187515,12813.6319,3,96,8 +12/1/2025 20:20,7,1.93,6088.046074,11749.92892,3,112,8 +12/1/2025 20:20,8,1.93,5582.656751,10774.52753,3,128,8 +12/1/2025 20:20,9,1.93,5119.221508,9880.09751,3,144,8 +12/1/2025 20:20,10,1.93,4694.257593,9059.917155,3,160,8 +12/1/2025 20:20,11,1.93,4304.571373,8307.822749,3,176,8 +12/1/2025 20:20,12,1.93,3947.234325,7618.162247,3,192,8 +12/1/2025 20:20,13,1.93,3619.561035,6985.752798,3,208,8 +12/1/2025 20:20,14,1.93,3319.089016,6405.8418,3,224,8 +12/1/2025 20:20,15,1.93,3043.560196,5874.071178,3,240,8 +12/1/2025 20:20,16,1.93,2790.9,5386.444636,3,256,8 +12/1/2025 20:20,17,1.93,2559.22,4939.297624,3,272,8 +12/1/2025 20:20,18,1.93,2346.77,4529.269801,3,288,8 +12/1/2025 20:20,19,1.93,2151.96,4153.279775,3,304,8 +12/1/2025 20:20,20,1.93,1973.32,3808.501955,3,320,8 +12/1/2025 20:20,21,1.93,1809.51,3492.34531,3,336,8 +12/1/2025 20:20,22,1.93,1659.29,3202.433898,3,352,8 +12/1/2025 20:20,23,1.93,1521.55,2936.589015,3,368,8 +12/1/2025 20:20,24,1.93,1395.24,2692.812815,3,384,8 +12/1/2025 20:20,25,1.93,1279.42,2469.273304,3,400,8 +12/1/2025 20:20,26,1.93,1173.21,2264.290564,3,416,8 +12/1/2025 20:20,27,1.93,1075.82,2076.324136,3,432,8 +12/1/2025 20:20,28,1.93,986.51,1903.961438,3,448,8 +12/1/2025 20:20,29,1.93,904.62,1745.907151,3,464,8 +12/1/2025 20:20,30,1.93,829.52,1600.973485,3,480,8 +12/1/2025 20:20,31,1.93,760.66,1468.071254,3,496,8 +12/1/2025 20:20,32,1.93,697.51,1346.201688,3,512,8 +12/1/2025 20:20,33,1.93,639.61,1234.448926,3,528,8 +12/1/2025 20:20,34,1.93,586.51,1131.97314,3,544,8 +12/1/2025 20:20,35,1.93,537.83,1038.004216,3,560,8 +12/1/2025 20:20,36,1.93,493.18,951.8359709,3,576,8 +12/1/2025 20:20,37,1.93,452.24,872.8208434,3,592,8 +12/1/2025 20:20,38,1.93,414.7,800.3650292,3,608,8 +12/1/2025 20:20,39,1.93,380.27,733.9240176,3,624,8 +12/1/2025 20:20,40,1.93,348.7,672.9984993,3,640,8 +12/1/2025 20:20,41,1.93,319.76,617.1306146,3,656,8 +12/1/2025 20:20,42,1.93,293.21,565.9005123,3,672,8 +12/1/2025 20:20,43,1.93,268.87,518.9231943,3,688,8 +12/1/2025 20:20,44,1.93,246.55,475.8456225,3,704,8 +12/1/2025 20:20,45,1.93,226.09,436.3440658,3,720,8 +12/1/2025 20:20,46,1.93,207.32,400.1216672,3,736,8 +12/1/2025 20:20,47,1.93,190.11,366.9062125,3,752,8 +12/1/2025 20:20,48,1.93,174.33,336.4480852,3,768,8 +12/1/2025 20:20,49,1.93,159.85,308.5183902,3,784,8 +12/1/2025 20:20,50,1.93,146.58,282.9072338,3,800,8 +12/1/2025 20:20,51,1.93,134.42,259.4221462,3,816,8 +12/1/2025 20:20,52,1.93,123.26,237.8866352,3,832,8 +12/1/2025 20:20,53,1.93,113.03,218.13886,3,848,8 +12/1/2025 20:20,54,1.93,103.64,200.0304145,3,864,8 +12/1/2025 20:20,55,1.93,95.04,183.4252123,3,880,8 +12/1/2025 20:20,56,1.93,87.15,168.1984643,3,896,8 +12/1/2025 20:20,57,1.93,79.91,154.2357401,3,912,8 +12/1/2025 20:20,58,1.93,73.28,141.432109,3,928,8 +12/1/2025 20:20,59,1.93,67.2,129.6913506,3,944,8 +12/1/2025 20:20,60,1.93,61.62,118.9252324,3,960,8 +12/1/2025 20:20,61,1.93,56.5,109.0528461,3,976,8 +12/1/2025 20:20,62,1.93,51.81,100,3,992,8 +12/1/2025 20:20,63,1.93,0,0,3,1023,8 +12/1/2025 20:20,64,1.93,0,0,3,1023,8 +12/1/2025 20:20,65,1.93,0,0,3,1023,8 +12/1/2025 20:20,66,1.93,Solve,Solve,3,-40,8 +12/1/2025 20:20,67,1.93,Solve,Solve,3,-24,8 +12/1/2025 20:20,68,1.93,Solve,Solve,3,-8,8 +12/1/2025 20:20,69,1.93,Solve,Solve,3,8,8 +12/1/2025 20:20,70,1.93,Solve,Solve,3,24,8 +12/1/2025 20:20,71,1.93,Solve,Solve,3,40,8 diff --git a/imap_processing/tests/ialirt/data/l0/iois_1_packets_2025_322_12_00_56 b/imap_processing/tests/ialirt/data/l0/iois_1_packets_2025_322_12_00_56 new file mode 100644 index 0000000000000000000000000000000000000000..018b095ba120a0e3473322d8adeb95e2213c36a3 GIT binary patch literal 11346 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 literal 0 HcmV?d00001 diff --git a/imap_processing/tests/ialirt/unit/conftest.py b/imap_processing/tests/ialirt/unit/conftest.py index 8f75a1fb7..844402750 100644 --- a/imap_processing/tests/ialirt/unit/conftest.py +++ b/imap_processing/tests/ialirt/unit/conftest.py @@ -19,6 +19,42 @@ def sc_packet_path(): return packet_path, xtce_ialirt_path +@pytest.fixture +def swapi_presweep_sc_packet_path(): + """Returns the spacecraft packet directory.""" + packet_path = ( + imap_module_directory + / "tests" + / "ialirt" + / "data" + / "l0" + / "iois_1_packets_2025_322_12_00_56" + ) + xtce_ialirt_path = ( + imap_module_directory / "ialirt" / "packet_definitions" / "ialirt.xml" + ) + + return packet_path, xtce_ialirt_path + + +@pytest.fixture +def swapi_postsweep_sc_packet_path(): + """Returns the spacecraft packet directory.""" + packet_path = ( + imap_module_directory + / "tests" + / "ialirt" + / "data" + / "l0" + / "iois_1_packets_2025_343_00_00_17" + ) + xtce_ialirt_path = ( + imap_module_directory / "ialirt" / "packet_definitions" / "ialirt.xml" + ) + + return packet_path, xtce_ialirt_path + + @pytest.fixture def ialirt_mag_test_l1d_data(): """Returns the MAG I-ALiRT calibration dataset.""" diff --git a/imap_processing/tests/ialirt/unit/test_process_swapi.py b/imap_processing/tests/ialirt/unit/test_process_swapi.py index 6f6bea427..f804c854a 100644 --- a/imap_processing/tests/ialirt/unit/test_process_swapi.py +++ b/imap_processing/tests/ialirt/unit/test_process_swapi.py @@ -69,6 +69,28 @@ def sc_xarray_data(sc_packet_path): return sc_xarray_data +@pytest.fixture +def swapi_presweep_sc_xarray_data(swapi_presweep_sc_packet_path): + """Extract spacecraft packet for testing.""" + + packet_path, xtce_ialirt_path = swapi_presweep_sc_packet_path + sc_xarray_data = packet_file_to_datasets( + packet_path, xtce_ialirt_path, use_derived_value=False + )[478] + return sc_xarray_data + + +@pytest.fixture +def swapi_postsweep_sc_xarray_data(swapi_postsweep_sc_packet_path): + """Extract spacecraft packet for testing.""" + + packet_path, xtce_ialirt_path = swapi_postsweep_sc_packet_path + sc_xarray_data = packet_file_to_datasets( + packet_path, xtce_ialirt_path, use_derived_value=False + )[478] + return sc_xarray_data + + @pytest.fixture def ialirt_test_data(): """Extract test data for unit tests below.""" @@ -222,10 +244,12 @@ def test_optimize_parameters(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x 2, shape (72,) -> (2, 72) + count_rates = np.tile( + count_rates, (2, 1) + ) # repeat new first dimension x 2, shape (72,) -> (2, 72) count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() count_rates_errors = np.tile(count_rates_errors, (2, 1)) - + result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands ) @@ -235,12 +259,14 @@ def test_optimize_parameters(test_data, energy_passbands): result[param][0], test_data[test_set]["expected_values"][param][0], rtol=test_data[test_set]["expected_values"][param][1], - err_msg=f"{param} did not match the expected result within the tolerance." + err_msg=f"{param} did not match the expected result " + f"within the tolerance.", ) def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): - """Test that the optimize_pseudo_parameters() function works correctly with invalid count and count rate values.""" + """Test that the optimize_pseudo_parameters() function works correctly + with invalid count and count rate values.""" for test_set in test_data: energy_data = pd.read_csv( f"{imap_module_directory}/tests/ialirt/data/l0/" @@ -248,21 +274,26 @@ def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x N, shape (72,) -> (N, 72), so that there are N sweeps + count_rates = np.tile( + count_rates, (2, 1) + ) # repeat new first dimension x N, shape (72,) -> (N, 72), + # so that there are N sweeps count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() count_rates_errors = np.tile(count_rates_errors, (2, 1)) - + # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates[0].argmax() # invalid values need to be close to the maximum to be detected - + i_peak = count_rates[ + 0 + ].argmax() # invalid values need to be close to the maximum to be detected + # sweep 0: zero count/count rate count_rates[0, i_peak + 2] = 0 count_rates_errors[0, i_peak + 2] = 0 - + # sweep 1: nan count/count rate count_rates[0, i_peak + 2] = np.nan count_rates_errors[0, i_peak + 2] = np.nan - + result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands ) @@ -273,12 +304,14 @@ def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): result[param], test_data[test_set]["expected_values"][param][0], rtol=0.5 + test_data[test_set]["expected_values"][param][1], - err_msg=f"{param} did not match the expected result within the tolerance." + err_msg=f"{param} did not match the expected result " + f"within the tolerance.", ) def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): - """Test that the optimize_pseudo_parameters() function works correctly with invalid energy.""" + """Test that the optimize_pseudo_parameters() + function works correctly with invalid energy.""" for test_set in test_data: energy_data = pd.read_csv( f"{imap_module_directory}/tests/ialirt/data/l0/" @@ -286,16 +319,21 @@ def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile(count_rates, (2, 1)) # repeat new first dimension x N, shape (72,) -> (N, 72), so that there are N sweeps + count_rates = np.tile( + count_rates, (2, 1) + ) # repeat new first dimension x N, shape (72,) -> (N, 72), + # so that there are N sweeps count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() count_rates_errors = np.tile(count_rates_errors, (2, 1)) - + # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates[0].argmax() # invalid values need to be close to the maximum to be detected + i_peak = count_rates[ + 0 + ].argmax() # invalid values need to be close to the maximum to be detected # set passband energy next to peak to nan - energy_passbands[i_peak + 1] = np.nan - + energy_passbands[i_peak + 1] = np.nan + result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands ) @@ -306,7 +344,8 @@ def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): result[param], test_data[test_set]["expected_values"][param][0], rtol=0.5 + test_data[test_set]["expected_values"][param][1], - err_msg=f"{param} did not match the expected result within the tolerance." + err_msg=f"{param} did not match the expected " + f"result within the tolerance.", ) @@ -346,3 +385,33 @@ def test_process_spacecraft_packet(sc_xarray_data): assert swapi_product1[0][key] is not None, ( f"The expected attribute {key} was not filled in the result dict." ) + + +@pytest.mark.external_test_data +def test_process_presweep_spacecraft_packet(swapi_presweep_sc_xarray_data): + """Tests spacecraft packet processing presweep.""" + calibration_file = pd.read_csv( + f"{imap_module_directory}/tests/ialirt/data/l0/" + f"imap_swapi_esa-unit-conversion_20250626_v001.csv" + ) + + swapi_product = process_swapi_ialirt( + swapi_presweep_sc_xarray_data, calibration_file + ) + + assert isinstance(swapi_product, list) + + +@pytest.mark.external_test_data +def test_process_postsweep_spacecraft_packet(swapi_postsweep_sc_xarray_data): + """Tests spacecraft packet processing postsweep.""" + calibration_file = pd.read_csv( + f"{imap_module_directory}/tests/ialirt/data/l0/" + f"imap_swapi_esa-unit-conversion_20251201_v001.csv" + ) + + swapi_product = process_swapi_ialirt( + swapi_postsweep_sc_xarray_data, calibration_file + ) + + assert isinstance(swapi_product, list) From 08d4c0124ab25db7f1b21b2581f11028bc312589 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:01:00 +0000 Subject: [PATCH 11/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- imap_processing/ialirt/l0/process_swapi.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index fb29fcb73..e38d7804e 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -111,16 +111,18 @@ def optimize_pseudo_parameters( 60000 * (initial_speed_guess / 400) ** 2, ] ) - + fitting_point_range = range(max_index - 3, max_index + 3) xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ((ydata > 0) - & (sigma > 0) - & np.isfinite(xdata) - & np.isfinite(ydata) - & np.isfinite(sigma)) + is_valid_data = ( + (ydata > 0) + & (sigma > 0) + & np.isfinite(xdata) + & np.isfinite(ydata) + & np.isfinite(sigma) + ) if np.count_nonzero(is_valid_data) >= 3: solution = curve_fit( @@ -128,7 +130,7 @@ def optimize_pseudo_parameters( xdata=xdata[is_valid_data], ydata=ydata[is_valid_data], sigma=sigma[is_valid_data], - p0=initial_param_guess + p0=initial_param_guess, )[0] else: solution = [np.nan] * 3 @@ -160,7 +162,7 @@ def process_swapi_ialirt( logger.info("Processing SWAPI.") # TODO: account for potentially multiple sweep table versions in one packet due to it falling on the change date - sweep_table_version = unpacked_data['swapi_version'].values[0] + sweep_table_version = unpacked_data["swapi_version"].values[0] sci_dataset = unpacked_data.sortby("epoch", ascending=True) From 9d8bf151f21e195bc5dbc8caa81300d87cc93e93 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:09:34 -0500 Subject: [PATCH 12/16] use a common function to select the first 63 energy passbands --- imap_processing/ialirt/l0/process_swapi.py | 136 +++++++++--------- imap_processing/swapi/l2/swapi_l2.py | 57 ++++---- .../tests/ialirt/unit/test_process_swapi.py | 100 +++++++++---- 3 files changed, 164 insertions(+), 129 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index fb29fcb73..e0ea8957a 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -14,7 +14,7 @@ from imap_processing.ialirt.utils.time import calculate_time from imap_processing.spice.time import met_to_ttj2000ns, met_to_utc from imap_processing.swapi.l1.swapi_l1 import process_sweep_data -from imap_processing.swapi.l2.swapi_l2 import SWAPI_LIVETIME +from imap_processing.swapi.l2.swapi_l2 import SWAPI_LIVETIME, select_first_63_passband_energies logger = logging.getLogger(__name__) @@ -76,6 +76,7 @@ def optimize_pseudo_parameters( Find the pseudo speed (u), density (n) and temperature (T) of solar wind particles. Fit a curve to calculated count rate values as a function of energy passband. + Takes count rates (with errors) and energy passbands for one single sweep as input. Parameters ---------- @@ -90,53 +91,50 @@ def optimize_pseudo_parameters( ------- solution_dict : dict Dictionary containing the optimized speed, density, and temperature values for - each sweep included in the input count_rates array. + the sweep included in the input count_rates array. """ - solution_dict = { # type: ignore - "pseudo_speed": [], - "pseudo_density": [], - "pseudo_temperature": [], - } + assert len(count_rates.shape) == 1 + assert count_rates.shape == count_rate_error.shape == energy_passbands.shape, f'{count_rates.shape} {count_rate_error.shape} {energy_passbands.shape}' + + current_sweep_count_rates = count_rates + current_sweep_count_rate_errors = count_rate_error + # Find the max count rate, and use the 5 points surrounding it + max_index = np.argmax(current_sweep_count_rates) + initial_speed_guess = np.sqrt(energy_passbands[max_index]) * Consts.speed_coeff + initial_param_guess = np.array( + [ + initial_speed_guess, + 5 * (400 / initial_speed_guess) ** 2, + 60000 * (initial_speed_guess / 400) ** 2, + ] + ) + + fitting_point_range = range(max_index - 3, max_index + 3) + xdata = energy_passbands.take(fitting_point_range, mode="clip") + ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") + sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") + is_valid_data = ((ydata > 0) + & (sigma > 0) + & np.isfinite(xdata) + & np.isfinite(ydata) + & np.isfinite(sigma)) + + if np.count_nonzero(is_valid_data) >= 3: + solution = curve_fit( + f=count_rate, + xdata=xdata[is_valid_data], + ydata=ydata[is_valid_data], + sigma=sigma[is_valid_data], + p0=initial_param_guess + )[0] + else: + solution = [np.nan] * 3 - for sweep in np.arange(count_rates.shape[0]): - current_sweep_count_rates = count_rates[sweep, :] - current_sweep_count_rate_errors = count_rate_error[sweep, :] - # Find the max count rate, and use the 5 points surrounding it - max_index = np.argmax(current_sweep_count_rates) - initial_speed_guess = np.sqrt(energy_passbands[max_index]) * Consts.speed_coeff - initial_param_guess = np.array( - [ - initial_speed_guess, - 5 * (400 / initial_speed_guess) ** 2, - 60000 * (initial_speed_guess / 400) ** 2, - ] - ) - - fitting_point_range = range(max_index - 3, max_index + 3) - xdata = energy_passbands.take(fitting_point_range, mode="clip") - ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") - sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ((ydata > 0) - & (sigma > 0) - & np.isfinite(xdata) - & np.isfinite(ydata) - & np.isfinite(sigma)) - - if np.count_nonzero(is_valid_data) >= 3: - solution = curve_fit( - f=count_rate, - xdata=xdata[is_valid_data], - ydata=ydata[is_valid_data], - sigma=sigma[is_valid_data], - p0=initial_param_guess - )[0] - else: - solution = [np.nan] * 3 - - solution_dict["pseudo_speed"].append(solution[0]) - solution_dict["pseudo_density"].append(solution[1]) - solution_dict["pseudo_temperature"].append(solution[2]) - return solution_dict + return { + "pseudo_speed": solution[0], + "pseudo_density": solution[1], + "pseudo_temperature": solution[2] + } def process_swapi_ialirt( @@ -159,9 +157,6 @@ def process_swapi_ialirt( """ logger.info("Processing SWAPI.") - # TODO: account for potentially multiple sweep table versions in one packet due to it falling on the change date - sweep_table_version = unpacked_data['swapi_version'].values[0] - sci_dataset = unpacked_data.sortby("epoch", ascending=True) met = calculate_time( @@ -219,41 +214,38 @@ def process_swapi_ialirt( dtype="datetime64[ns]" ) - # Find the sweep's energy data for the latest time, where sweep_id == 2 - subset = calibration_lut_table[ - (calibration_lut_table["timestamp"] == calibration_lut_table["timestamp"].max()) - & (calibration_lut_table["Sweep #"] == sweep_table_version) - ] - if subset.empty: - energy_passbands = np.full(NUM_IALIRT_ENERGY_STEPS, np.nan, dtype=np.float64) - else: - subset = subset.sort_values(["timestamp", "ESA Step #"]) - energy_passbands = ( - subset["Energy"][:NUM_IALIRT_ENERGY_STEPS].to_numpy().astype(float) - ) - - solution = optimize_pseudo_parameters( - raw_coin_rate, count_rate_error, energy_passbands - ) - swapi_data = [] - for entry in np.arange(0, len(solution["pseudo_speed"])): + # TODO why is it that len(raw_coin_rate) != len(met_values) + for entry in np.arange(0, len(raw_coin_rate)): + entry_met = int(met_values[entry]) + entry_met_in_utc = met_to_utc(entry_met) + sweep_table_version = unpacked_data['swapi_version'].sel(epoch=entry_met, method='nearest').item() + energy_passbands = select_first_63_passband_energies( + calibration_table_df=calibration_lut_table, + time=entry_met_in_utc, + sweep_table_version=sweep_table_version + ) + solution = optimize_pseudo_parameters( + raw_coin_rate[entry], + count_rate_error[entry], + energy_passbands + ) swapi_data.append( { "apid": 478, - "met": int(met_values[entry]), - "met_in_utc": met_to_utc(met_values[entry]).split(".")[0], + "met": entry_met, + "met_in_utc": entry_met_in_utc.split(".")[0], "ttj2000ns": int(met_to_ttj2000ns(met_values[entry])), "instrument": "swapi", "swapi_pseudo_proton_speed": Decimal( - f"{solution['pseudo_speed'][entry]:.3f}" + f"{solution['pseudo_speed']:.3f}" ), "swapi_pseudo_proton_density": Decimal( - f"{solution['pseudo_density'][entry]:.3f}" + f"{solution['pseudo_density']:.3f}" ), "swapi_pseudo_proton_temperature": Decimal( - f"{solution['pseudo_temperature'][entry]:.3f}" + f"{solution['pseudo_temperature']:.3f}" ), } ) diff --git a/imap_processing/swapi/l2/swapi_l2.py b/imap_processing/swapi/l2/swapi_l2.py index a81f89d79..4c02c21ea 100644 --- a/imap_processing/swapi/l2/swapi_l2.py +++ b/imap_processing/swapi/l2/swapi_l2.py @@ -1,6 +1,7 @@ """SWAPI L2 processing module.""" import logging +import select import numpy as np import numpy.typing as npt @@ -68,34 +69,7 @@ def solve_full_sweep_energy( first_63_energies = [] for time, sweep_id in zip(data_time, sweep_table, strict=False): - # Find the sweep's ESA data for the given time and sweep_id - subset = esa_table_df[ - (esa_table_df["timestamp"] <= time) & (esa_table_df["Sweep #"] == sweep_id) - ] - if subset.empty: - # Get the earliest timestamp available - earliest_time = esa_table_df["timestamp"].min() - - # Find the sweep's ESA data for the earliest time and sweep_id - earliest_subset = esa_table_df[ - (esa_table_df["timestamp"] == earliest_time) - & (esa_table_df["Sweep #"] == sweep_id) - ] - if earliest_subset.empty: - raise ValueError( - f"No matching ESA table entry found for sweep ID {sweep_id} " - f"at time {time}, and no entries found for earliest time " - f"{earliest_time}." - ) - subset = earliest_subset - - # Subset data can contain multiple 72 energy values with last 9 fine energies - # with 'Solve' value. We need to sort by time and ESA step to maintain correct - # order. Then take the last group of 72 steps values and select first 63 - # values only. - subset = subset.sort_values(["timestamp", "ESA Step #"]) - grouped = subset["Energy"].values.reshape(-1, NUM_ENERGY_STEPS) - first_63 = grouped[-1, :63] + first_63 = select_first_63_passband_energies(esa_table_df, time, sweep_id) first_63_energies.append(first_63) # Find last 9 fine energy values of all sweeps data @@ -163,6 +137,33 @@ def solve_full_sweep_energy( return sweeps_energy_value +def select_first_63_passband_energies(calibration_table_df, time, sweep_table_version): + """ + Select passband energies from the calibration table based on the time and table version. + + :param calibration_table_df: + DataFrame containing entries for `timestamp`, `Sweep #`, `ESA Step #`, and `Energy`. + Each timestamp has 72 energy steps. + :param time: Time of measurement (calibration from after this time is excluded if earlier is available, otherwise earliest time is used) + :param sweep_table_version: Sweep table version (to select from the `Sweep #` column) + """ + + subset = calibration_table_df[calibration_table_df["Sweep #"] == sweep_table_version] + + table_timestamps = pd.to_datetime(subset["timestamp"], utc=True) + table_timestamps_before = table_timestamps[table_timestamps <= pd.to_datetime(time, utc=True)] + selected_timestamp = (table_timestamps_before.max() # latest one that is not after the input time + if not table_timestamps_before.empty + else table_timestamps.max()) + + subset = subset[table_timestamps == selected_timestamp] + + assert len(subset) == 72, f'{len(subset)} entries in calibration table for time {time}, sweep # {sweep_table_version}; 72 are required' + + subset = subset.sort_values(["timestamp", "ESA Step #"]) + return subset["Energy"][:63].to_numpy().astype(float) + + def swapi_l2( l1_dataset: xr.Dataset, esa_table_df: pd.DataFrame, diff --git a/imap_processing/tests/ialirt/unit/test_process_swapi.py b/imap_processing/tests/ialirt/unit/test_process_swapi.py index f804c854a..7e2661708 100644 --- a/imap_processing/tests/ialirt/unit/test_process_swapi.py +++ b/imap_processing/tests/ialirt/unit/test_process_swapi.py @@ -159,11 +159,14 @@ def test_process_swapi_ialirt( 0 : xarray_data["swapi_flag"].shape[0] ].data - energy_passbands = pd.read_csv( + calibration_lut_table = pd.read_csv( f"{imap_module_directory}/tests/ialirt/data/l0/swapi_ialirt_energy_steps.csv" ) + + # it is 0 in the template data, but there is no #0 in the table + xarray_data['swapi_version'] = (['epoch'], [calibration_lut_table['Sweep #'].max()] * len(xarray_data.epoch)) - swapi_result = process_swapi_ialirt(xarray_data, energy_passbands) + swapi_result = process_swapi_ialirt(xarray_data, calibration_lut_table) key_names = [ "apid", @@ -244,11 +247,11 @@ def test_optimize_parameters(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile( - count_rates, (2, 1) - ) # repeat new first dimension x 2, shape (72,) -> (2, 72) count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() - count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + # select the first 63 only + count_rates = count_rates[:63] + count_rates_errors = count_rates_errors[:63] result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands @@ -256,7 +259,7 @@ def test_optimize_parameters(test_data, energy_passbands): for param in test_data[test_set]["expected_values"]: np.testing.assert_allclose( - result[param][0], + result[param], test_data[test_set]["expected_values"][param][0], rtol=test_data[test_set]["expected_values"][param][1], err_msg=f"{param} did not match the expected result " @@ -264,7 +267,7 @@ def test_optimize_parameters(test_data, energy_passbands): ) -def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): +def test_optimize_parameters_with_zero_count(test_data, energy_passbands): """Test that the optimize_pseudo_parameters() function works correctly with invalid count and count rate values.""" for test_set in test_data: @@ -274,25 +277,58 @@ def test_optimize_parameters_with_invalid_values(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile( - count_rates, (2, 1) - ) # repeat new first dimension x N, shape (72,) -> (N, 72), # so that there are N sweeps count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() - count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + # select the first 63 only + count_rates = count_rates[:63] + count_rates_errors = count_rates_errors[:63] # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates[ - 0 - ].argmax() # invalid values need to be close to the maximum to be detected + i_peak = count_rates.argmax() # invalid values need to be close to the maximum to be detected + + # zero count/count rate + count_rates[i_peak + 2] = 0 + count_rates_errors[i_peak + 2] = 0 + + result = optimize_pseudo_parameters( + count_rates, count_rates_errors, energy_passbands + ) + + for param in test_data[test_set]["expected_values"]: + # because invalid values inserted, higher error tolerance by 50% + np.testing.assert_allclose( + result[param], + test_data[test_set]["expected_values"][param][0], + rtol=0.5 + test_data[test_set]["expected_values"][param][1], + err_msg=f"{param} did not match the expected result " + f"within the tolerance.", + ) - # sweep 0: zero count/count rate - count_rates[0, i_peak + 2] = 0 - count_rates_errors[0, i_peak + 2] = 0 - # sweep 1: nan count/count rate - count_rates[0, i_peak + 2] = np.nan - count_rates_errors[0, i_peak + 2] = np.nan +def test_optimize_parameters_with_nan_count(test_data, energy_passbands): + """Test that the optimize_pseudo_parameters() function works correctly + with invalid count and count rate values.""" + for test_set in test_data: + energy_data = pd.read_csv( + f"{imap_module_directory}/tests/ialirt/data/l0/" + f"{test_data[test_set]['file_name']}", + ) + count_rates = energy_data["Count Rates [Hz]"].to_numpy() + count_rates[0] = 0.0 # set the first count to zero + # so that there are N sweeps + count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() + + # select the first 63 only + count_rates = count_rates[:63] + count_rates_errors = count_rates_errors[:63] + + # set some points to have invalid values that should not affect the fit too much + i_peak = count_rates.argmax() # invalid values need to be close to the maximum to be detected + + # nan count/count rate + count_rates[i_peak + 2] = np.nan + count_rates_errors[i_peak + 2] = np.nan result = optimize_pseudo_parameters( count_rates, count_rates_errors, energy_passbands @@ -319,17 +355,15 @@ def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): ) count_rates = energy_data["Count Rates [Hz]"].to_numpy() count_rates[0] = 0.0 # set the first count to zero - count_rates = np.tile( - count_rates, (2, 1) - ) # repeat new first dimension x N, shape (72,) -> (N, 72), # so that there are N sweeps count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() - count_rates_errors = np.tile(count_rates_errors, (2, 1)) + + # select the first 63 only + count_rates = count_rates[:63] + count_rates_errors = count_rates_errors[:63] # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates[ - 0 - ].argmax() # invalid values need to be close to the maximum to be detected + i_peak = count_rates.argmax() # invalid values need to be close to the maximum to be detected # set passband energy next to peak to nan energy_passbands[i_peak + 1] = np.nan @@ -370,6 +404,9 @@ def test_process_spacecraft_packet(sc_xarray_data): extended_data = np.tile(base_sequence, repeat_times)[:target_length] sc_xarray_data["swapi_seq_number"].data = extended_data + # it is 0 in this example data, but there is no #0 in the table + sc_xarray_data['swapi_version'] = (['epoch'], [calibration_file['Sweep #'].max()] * len(sc_xarray_data.epoch)) + swapi_product1 = process_swapi_ialirt(sc_xarray_data, calibration_file) key_names = [ "apid", @@ -400,7 +437,9 @@ def test_process_presweep_spacecraft_packet(swapi_presweep_sc_xarray_data): ) assert isinstance(swapi_product, list) - + assert len(swapi_product) == 4 + + assert np.isclose(float(swapi_product[0]['swapi_pseudo_proton_speed']), 450, rtol=0.1) @pytest.mark.external_test_data def test_process_postsweep_spacecraft_packet(swapi_postsweep_sc_xarray_data): @@ -415,3 +454,6 @@ def test_process_postsweep_spacecraft_packet(swapi_postsweep_sc_xarray_data): ) assert isinstance(swapi_product, list) + assert len(swapi_product) == 4 + + assert np.isclose(float(swapi_product[0]['swapi_pseudo_proton_speed']), 400, rtol=0.1) \ No newline at end of file From 03423103fb074f927bff49df637eceb8e220ee15 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:13:18 -0500 Subject: [PATCH 13/16] apply formatting changes --- imap_processing/ialirt/l0/process_swapi.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index e0ea8957a..6b7e20154 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -113,11 +113,13 @@ def optimize_pseudo_parameters( xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ((ydata > 0) - & (sigma > 0) - & np.isfinite(xdata) - & np.isfinite(ydata) - & np.isfinite(sigma)) + is_valid_data = ( + (ydata > 0) + & (sigma > 0) + & np.isfinite(xdata) + & np.isfinite(ydata) + & np.isfinite(sigma) + ) if np.count_nonzero(is_valid_data) >= 3: solution = curve_fit( @@ -125,7 +127,7 @@ def optimize_pseudo_parameters( xdata=xdata[is_valid_data], ydata=ydata[is_valid_data], sigma=sigma[is_valid_data], - p0=initial_param_guess + p0=initial_param_guess, )[0] else: solution = [np.nan] * 3 @@ -220,7 +222,7 @@ def process_swapi_ialirt( for entry in np.arange(0, len(raw_coin_rate)): entry_met = int(met_values[entry]) entry_met_in_utc = met_to_utc(entry_met) - sweep_table_version = unpacked_data['swapi_version'].sel(epoch=entry_met, method='nearest').item() + sweep_table_version = unpacked_data["swapi_version"].sel(epoch=entry_met, method="nearest").item() energy_passbands = select_first_63_passband_energies( calibration_table_df=calibration_lut_table, time=entry_met_in_utc, From 255b0f486ab7bfcb130e0fcc68a74a98b6c7d94e Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:37:40 -0500 Subject: [PATCH 14/16] remove unneeded nan/infinite checks --- imap_processing/ialirt/l0/process_swapi.py | 13 ++-- .../tests/ialirt/unit/test_process_swapi.py | 77 ------------------- 2 files changed, 6 insertions(+), 84 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 6b7e20154..8be2636ef 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -113,13 +113,11 @@ def optimize_pseudo_parameters( xdata = energy_passbands.take(fitting_point_range, mode="clip") ydata = current_sweep_count_rates.take(fitting_point_range, mode="clip") sigma = current_sweep_count_rate_errors.take(fitting_point_range, mode="clip") - is_valid_data = ( - (ydata > 0) - & (sigma > 0) - & np.isfinite(xdata) - & np.isfinite(ydata) - & np.isfinite(sigma) - ) + + # exclude points where the count is zero (or rounded to zero) + # because the fitting algorithm cannot handle them + # (since zero count implies sigma=0) + is_valid_data = ydata > 0 if np.count_nonzero(is_valid_data) >= 3: solution = curve_fit( @@ -184,6 +182,7 @@ def process_swapi_ialirt( (grouped_dataset["group"] == group) ] + # TODO change this to the center of the interval met_values.append( int(grouped_dataset["met"][(grouped_dataset["group"] == group).values][0]) ) diff --git a/imap_processing/tests/ialirt/unit/test_process_swapi.py b/imap_processing/tests/ialirt/unit/test_process_swapi.py index 7e2661708..9e8257cfd 100644 --- a/imap_processing/tests/ialirt/unit/test_process_swapi.py +++ b/imap_processing/tests/ialirt/unit/test_process_swapi.py @@ -306,83 +306,6 @@ def test_optimize_parameters_with_zero_count(test_data, energy_passbands): ) -def test_optimize_parameters_with_nan_count(test_data, energy_passbands): - """Test that the optimize_pseudo_parameters() function works correctly - with invalid count and count rate values.""" - for test_set in test_data: - energy_data = pd.read_csv( - f"{imap_module_directory}/tests/ialirt/data/l0/" - f"{test_data[test_set]['file_name']}", - ) - count_rates = energy_data["Count Rates [Hz]"].to_numpy() - count_rates[0] = 0.0 # set the first count to zero - # so that there are N sweeps - count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() - - # select the first 63 only - count_rates = count_rates[:63] - count_rates_errors = count_rates_errors[:63] - - # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates.argmax() # invalid values need to be close to the maximum to be detected - - # nan count/count rate - count_rates[i_peak + 2] = np.nan - count_rates_errors[i_peak + 2] = np.nan - - result = optimize_pseudo_parameters( - count_rates, count_rates_errors, energy_passbands - ) - - for param in test_data[test_set]["expected_values"]: - # because invalid values inserted, higher error tolerance by 50% - np.testing.assert_allclose( - result[param], - test_data[test_set]["expected_values"][param][0], - rtol=0.5 + test_data[test_set]["expected_values"][param][1], - err_msg=f"{param} did not match the expected result " - f"within the tolerance.", - ) - - -def test_optimize_parameters_with_invalid_energy(test_data, energy_passbands): - """Test that the optimize_pseudo_parameters() - function works correctly with invalid energy.""" - for test_set in test_data: - energy_data = pd.read_csv( - f"{imap_module_directory}/tests/ialirt/data/l0/" - f"{test_data[test_set]['file_name']}", - ) - count_rates = energy_data["Count Rates [Hz]"].to_numpy() - count_rates[0] = 0.0 # set the first count to zero - # so that there are N sweeps - count_rates_errors = energy_data["Count Rates Error [Hz]"].to_numpy() - - # select the first 63 only - count_rates = count_rates[:63] - count_rates_errors = count_rates_errors[:63] - - # set some points to have invalid values that should not affect the fit too much - i_peak = count_rates.argmax() # invalid values need to be close to the maximum to be detected - - # set passband energy next to peak to nan - energy_passbands[i_peak + 1] = np.nan - - result = optimize_pseudo_parameters( - count_rates, count_rates_errors, energy_passbands - ) - - for param in test_data[test_set]["expected_values"]: - # because invalid values inserted, higher error tolerance by 50% - np.testing.assert_allclose( - result[param], - test_data[test_set]["expected_values"][param][0], - rtol=0.5 + test_data[test_set]["expected_values"][param][1], - err_msg=f"{param} did not match the expected " - f"result within the tolerance.", - ) - - @pytest.mark.external_test_data def test_process_spacecraft_packet(sc_xarray_data): """Tests spacecraft packet processing.""" From edb6f1ede04ab17f6c384656f9a710abc177ec40 Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:57:29 -0500 Subject: [PATCH 15/16] remove unneeded test --- imap_processing/ialirt/l0/process_swapi.py | 4 +- .../tests/ialirt/unit/test_process_swapi.py | 41 ------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index 8be2636ef..a88df8097 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -217,7 +217,9 @@ def process_swapi_ialirt( swapi_data = [] - # TODO why is it that len(raw_coin_rate) != len(met_values) + if len(met_values) == len(raw_coin_rate): + raise RuntimeError('Inconsistent number of MET times and count sweep bins') + for entry in np.arange(0, len(raw_coin_rate)): entry_met = int(met_values[entry]) entry_met_in_utc = met_to_utc(entry_met) diff --git a/imap_processing/tests/ialirt/unit/test_process_swapi.py b/imap_processing/tests/ialirt/unit/test_process_swapi.py index 9e8257cfd..a37574cd4 100644 --- a/imap_processing/tests/ialirt/unit/test_process_swapi.py +++ b/imap_processing/tests/ialirt/unit/test_process_swapi.py @@ -306,47 +306,6 @@ def test_optimize_parameters_with_zero_count(test_data, energy_passbands): ) -@pytest.mark.external_test_data -def test_process_spacecraft_packet(sc_xarray_data): - """Tests spacecraft packet processing.""" - calibration_file = pd.read_csv( - f"{imap_module_directory}/tests/ialirt/data/l0/swapi_ialirt_energy_steps.csv" - ) - - # Case 1: Not fixing the sequence number attribute, which is all zeros. - swapi_product = process_swapi_ialirt(sc_xarray_data, calibration_file) - assert swapi_product == [] - - # Case 2: Overwriting swapi_seq_number to be an acceptable array of numbers. - # Calculate how many times to tile the sequence to reach length of sc packet - target_length = sc_xarray_data["swapi_seq_number"].shape[0] - base_sequence = np.arange(12) - repeat_times = (target_length // len(base_sequence)) + 1 # Over-repeat - - # Tile the sequence and truncate to target_length - extended_data = np.tile(base_sequence, repeat_times)[:target_length] - sc_xarray_data["swapi_seq_number"].data = extended_data - - # it is 0 in this example data, but there is no #0 in the table - sc_xarray_data['swapi_version'] = (['epoch'], [calibration_file['Sweep #'].max()] * len(sc_xarray_data.epoch)) - - swapi_product1 = process_swapi_ialirt(sc_xarray_data, calibration_file) - key_names = [ - "apid", - "met", - "met_in_utc", - "ttj2000ns", - "swapi_pseudo_proton_density", - "swapi_pseudo_proton_speed", - "swapi_pseudo_proton_temperature", - ] - - for key in key_names: - assert swapi_product1[0][key] is not None, ( - f"The expected attribute {key} was not filled in the result dict." - ) - - @pytest.mark.external_test_data def test_process_presweep_spacecraft_packet(swapi_presweep_sc_xarray_data): """Tests spacecraft packet processing presweep.""" From 8920022254292b3e2200538c479e449914904efb Mon Sep 17 00:00:00 2001 From: Hameedullah Farooki <6323125+hafarooki@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:13:24 -0500 Subject: [PATCH 16/16] fix if statement --- imap_processing/ialirt/l0/process_swapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imap_processing/ialirt/l0/process_swapi.py b/imap_processing/ialirt/l0/process_swapi.py index a88df8097..f64bbe14d 100644 --- a/imap_processing/ialirt/l0/process_swapi.py +++ b/imap_processing/ialirt/l0/process_swapi.py @@ -217,7 +217,7 @@ def process_swapi_ialirt( swapi_data = [] - if len(met_values) == len(raw_coin_rate): + if len(met_values) != len(raw_coin_rate): raise RuntimeError('Inconsistent number of MET times and count sweep bins') for entry in np.arange(0, len(raw_coin_rate)):