Skip to content

Commit a1dcfa7

Browse files
authored
I-ALiRT - Swapi Rolling Average (#2608)
1 parent feee7a1 commit a1dcfa7

4 files changed

Lines changed: 207 additions & 38 deletions

File tree

imap_processing/ialirt/l0/process_swapi.py

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,60 @@ def optimize_pseudo_parameters(
115115
return sol[0]
116116

117117

118+
def geometric_mean(
119+
swapi_met_list: list,
120+
pseudo_speed_list: list,
121+
pseudo_proton_density_list: list,
122+
pseudo_proton_temperature_list: list,
123+
) -> tuple:
124+
"""
125+
Find moving geometric mean of SWAPI data.
126+
127+
Parameters
128+
----------
129+
swapi_met_list : list
130+
Mission elapsed time for first time measurement of SWAPI sweep.
131+
pseudo_speed_list : list
132+
Pseudo speed for SWAPI sweep.
133+
pseudo_proton_density_list : list
134+
Pseudo proton density for SWAPI sweep.
135+
pseudo_proton_temperature_list : list
136+
Pseudo proton temperature for SWAPI sweep.
137+
138+
Returns
139+
-------
140+
avg_swapi_met : float
141+
Average swapi met value.
142+
avg_proton_density : float
143+
Average proton density value.
144+
avg_pseudo_speed : float
145+
Average pseudo speed value.
146+
avg_proton_temperature : float
147+
Average proton temperature value.
148+
"""
149+
met_arr = np.asarray(swapi_met_list)
150+
151+
# If any of the values are equal to nan then do not include that index.
152+
valid = (
153+
~np.isnan(pseudo_speed_list)
154+
& ~np.isnan(pseudo_proton_density_list)
155+
& ~np.isnan(pseudo_proton_temperature_list)
156+
)
157+
158+
pseudo_speed_arr = np.asarray(pseudo_speed_list)[valid]
159+
avg_pseudo_speed = np.exp(np.mean(np.log(pseudo_speed_arr)))
160+
161+
density_arr = np.asarray(pseudo_proton_density_list)[valid]
162+
avg_proton_density = np.exp(np.mean(np.log(density_arr)))
163+
164+
temperature_arr = np.asarray(pseudo_proton_temperature_list)[valid]
165+
avg_proton_temperature = np.exp(np.mean(np.log(temperature_arr)))
166+
167+
avg_swapi_met = np.mean(met_arr[valid])
168+
169+
return avg_swapi_met, avg_proton_density, avg_pseudo_speed, avg_proton_temperature
170+
171+
118172
def process_swapi_ialirt(
119173
unpacked_data: xr.Dataset, calibration_lut_table: pd.DataFrame
120174
) -> list[dict]:
@@ -145,6 +199,10 @@ def process_swapi_ialirt(
145199
sci_dataset["met"] = met
146200
incomplete_groups = []
147201
swapi_data = []
202+
pseudo_proton_speed_list = []
203+
pseudo_proton_density_list = []
204+
pseudo_proton_temperature_list = []
205+
swapi_met_list = []
148206

149207
# Extract energy values from the calibration lookup table file
150208
calibration_lut_table["timestamp"] = pd.to_datetime(
@@ -210,16 +268,44 @@ def process_swapi_ialirt(
210268
raw_coin_rate.squeeze(), count_rate_error.squeeze(), energy_passbands
211269
)
212270

213-
swapi_data.append(
214-
_populate_instrument_header_items(met)
215-
| {
216-
"instrument": "swapi",
217-
"swapi_epoch": int(met_to_ttj2000ns(mid_measurement)),
218-
"swapi_pseudo_proton_speed": Decimal(f"{pseudo_speed:.3f}"),
219-
"swapi_pseudo_proton_density": Decimal(f"{pseudo_density:.3f}"),
220-
"swapi_pseudo_proton_temperature": Decimal(f"{pseudo_temperature:.3f}"),
221-
}
222-
)
271+
pseudo_proton_speed_list.append(pseudo_speed)
272+
pseudo_proton_density_list.append(pseudo_density)
273+
pseudo_proton_temperature_list.append(pseudo_temperature)
274+
swapi_met_list.append(mid_measurement)
275+
276+
# Begin averaging after 1 minute has passed (5 sweeps) and make certain that
277+
# the data is sequential (~12 s cadence).
278+
if len(swapi_met_list) >= 5 and np.all(
279+
np.isclose(np.diff(swapi_met_list[-5:]), 12.0, atol=0.05)
280+
):
281+
(
282+
avg_swapi_met,
283+
avg_pseudo_proton_density,
284+
avg_pseudo_proton_speed,
285+
avg_pseudo_proton_temperature,
286+
) = geometric_mean(
287+
swapi_met_list[-5:],
288+
pseudo_proton_speed_list[-5:],
289+
pseudo_proton_density_list[-5:],
290+
pseudo_proton_temperature_list[-5:],
291+
)
292+
293+
swapi_data.append(
294+
_populate_instrument_header_items(met)
295+
| {
296+
"instrument": "swapi",
297+
"swapi_epoch": int(met_to_ttj2000ns(avg_swapi_met)),
298+
"swapi_pseudo_proton_speed": Decimal(
299+
f"{avg_pseudo_proton_speed:.3f}"
300+
),
301+
"swapi_pseudo_proton_density": Decimal(
302+
f"{avg_pseudo_proton_density:.3f}"
303+
),
304+
"swapi_pseudo_proton_temperature": Decimal(
305+
f"{avg_pseudo_proton_temperature:.3f}"
306+
),
307+
}
308+
)
223309
if incomplete_groups:
224310
logger.info(
225311
f"The following swapi groups were skipped due to "

imap_processing/tests/external_test_data_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
("iois_1_packets_2025_284_05_53_38", "ialirt/data/l0/"),
127127
("iois_1_packets_2025_284_05_54_39", "ialirt/data/l0/"),
128128
("iois_1_packets_2025_344_05_57_56", "ialirt/data/l0/"),
129+
("iois_1_packets_2025_344_05_59_58", "ialirt/data/l0/"),
129130
("imap_recon_od005_20250925_20251014_v01.bsp", "spice/test_data/"),
130131
("imap_2025_283_2025_284_001.ah.bc", "spice/test_data/"),
131132

imap_processing/tests/ialirt/unit/conftest.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,16 @@ def sc_packet_path():
2222
@pytest.fixture
2323
def swapi_postlaunch_sc_packet_path():
2424
"""Returns the spacecraft packet directory."""
25-
packet_path = (
26-
imap_module_directory
27-
/ "tests"
28-
/ "ialirt"
29-
/ "data"
30-
/ "l0"
31-
/ "iois_1_packets_2025_344_05_57_56"
32-
)
3325
xtce_ialirt_path = (
3426
imap_module_directory / "ialirt" / "packet_definitions" / "ialirt.xml"
3527
)
3628

37-
return packet_path, xtce_ialirt_path
29+
directory = imap_module_directory / "tests" / "ialirt" / "data" / "l0"
30+
filenames = [
31+
"iois_1_packets_2025_344_05_57_56",
32+
"iois_1_packets_2025_344_05_59_58",
33+
]
34+
return tuple(directory / fname for fname in filenames), xtce_ialirt_path
3835

3936

4037
@pytest.fixture

imap_processing/tests/ialirt/unit/test_process_swapi.py

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import numpy as np
44
import pandas as pd
55
import pytest
6+
import xarray as xr
67

78
from imap_processing import imap_module_directory
89
from imap_processing.ialirt.l0.process_swapi import (
910
count_rate,
11+
geometric_mean,
1012
optimize_pseudo_parameters,
1113
process_swapi_ialirt,
1214
)
@@ -257,16 +259,114 @@ def test_optimize_parameters():
257259
)
258260

259261

262+
def test_geometric_mean():
263+
"""Test geometric_mean function."""
264+
265+
swapi_met_list = [12, 24, 36, 48, 60]
266+
267+
pseudo_proton_speed_list = [400, 420, 440, 460, 480]
268+
pseudo_proton_density_list = [5.0, 6.0, 7.0, 8.0, 9.0]
269+
pseudo_proton_temperature_list = [60000, 62000, 64000, 66000, 68000]
270+
271+
avg_swapi_met, avg_density, avg_speed, avg_temperature = geometric_mean(
272+
swapi_met_list,
273+
pseudo_proton_speed_list,
274+
pseudo_proton_density_list,
275+
pseudo_proton_temperature_list,
276+
)
277+
278+
expected_density = np.exp(np.mean(np.log(pseudo_proton_density_list)))
279+
expected_speed = np.exp(np.mean(np.log(pseudo_proton_speed_list)))
280+
expected_temperature = np.exp(np.mean(np.log(pseudo_proton_temperature_list)))
281+
expected_met = np.mean(swapi_met_list)
282+
283+
assert np.isclose(avg_density, expected_density)
284+
assert np.isclose(avg_speed, expected_speed)
285+
assert np.isclose(avg_temperature, expected_temperature)
286+
assert np.isclose(avg_swapi_met, expected_met)
287+
288+
289+
def test_geometric_mean_nan():
290+
"""Test geometric_mean function."""
291+
292+
swapi_met_list = [12, 24, 36, 48, 60]
293+
294+
pseudo_proton_speed_list = [400, 420, 440, 460, np.nan]
295+
pseudo_proton_density_list = [5.0, 6.0, 7.0, 8.0, np.nan]
296+
pseudo_proton_temperature_list = [60000, 62000, 64000, 66000, np.nan]
297+
298+
avg_swapi_met, avg_density, avg_speed, avg_temperature = geometric_mean(
299+
swapi_met_list,
300+
pseudo_proton_speed_list,
301+
pseudo_proton_density_list,
302+
pseudo_proton_temperature_list,
303+
)
304+
305+
expected_density = np.exp(np.mean(np.log(pseudo_proton_density_list[0:4])))
306+
expected_speed = np.exp(np.mean(np.log(pseudo_proton_speed_list[0:4])))
307+
expected_temperature = np.exp(np.mean(np.log(pseudo_proton_temperature_list[0:4])))
308+
expected_met = np.mean(swapi_met_list[0:4])
309+
310+
assert np.isclose(avg_density, expected_density)
311+
assert np.isclose(avg_speed, expected_speed)
312+
assert np.isclose(avg_temperature, expected_temperature)
313+
assert np.isclose(avg_swapi_met, expected_met)
314+
315+
316+
def test_geometric_gaps():
317+
"""Test geometric_mean function."""
318+
319+
swapi_met_list = [0, 12, 24, 36, 240, 252, 264, 272, 284]
320+
321+
bool_check = len(swapi_met_list) >= 5 and np.all(
322+
np.isclose(np.diff(swapi_met_list[-5:]), 12.0, atol=0.05)
323+
)
324+
assert not bool_check
325+
326+
pseudo_proton_speed_list = [400, 420, 440, 460, 480, 500, 520, 540, 560]
327+
pseudo_proton_density_list = [5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0]
328+
pseudo_proton_temperature_list = [
329+
60000,
330+
62000,
331+
64000,
332+
66000,
333+
68000,
334+
70000,
335+
72000,
336+
74000,
337+
76000,
338+
]
339+
340+
avg_swapi_met, avg_density, avg_speed, avg_temperature = geometric_mean(
341+
swapi_met_list[4::],
342+
pseudo_proton_speed_list[4::],
343+
pseudo_proton_density_list[4::],
344+
pseudo_proton_temperature_list[4::],
345+
)
346+
347+
expected_density = np.exp(np.mean(np.log(pseudo_proton_density_list[4::])))
348+
expected_speed = np.exp(np.mean(np.log(pseudo_proton_speed_list[4::])))
349+
expected_temperature = np.exp(np.mean(np.log(pseudo_proton_temperature_list[4::])))
350+
expected_met = np.mean(swapi_met_list[4::])
351+
352+
assert np.isclose(avg_density, expected_density)
353+
assert np.isclose(avg_speed, expected_speed)
354+
assert np.isclose(avg_temperature, expected_temperature)
355+
assert np.isclose(avg_swapi_met, expected_met)
356+
357+
260358
@pytest.mark.external_test_data
261359
def test_process_spacecraft_packet(
262360
esa_unit_conversion_table, swapi_postlaunch_sc_packet_path
263361
):
264362
"""Tests spacecraft packet processing."""
265363

266364
packet_path, xtce_ialirt_path = swapi_postlaunch_sc_packet_path
267-
postlaunch_sc_xarray_data = packet_file_to_datasets(
268-
packet_path, xtce_ialirt_path, use_derived_value=False
269-
)[478]
365+
xarray_data = tuple(
366+
packet_file_to_datasets(packet, xtce_ialirt_path, use_derived_value=False)[478]
367+
for packet in packet_path
368+
)
369+
postlaunch_sc_xarray_data = xr.concat(xarray_data, dim="epoch")
270370

271371
postlaunch_sc_xarray_data["swapi_version"].data = np.full_like(
272372
postlaunch_sc_xarray_data["swapi_version"].data, 2
@@ -275,19 +375,4 @@ def test_process_spacecraft_packet(
275375
postlaunch_sc_xarray_data, esa_unit_conversion_table
276376
)
277377

278-
assert len(swapi_product) == 4
279-
280-
key_names = [
281-
"apid",
282-
"met",
283-
"met_in_utc",
284-
"ttj2000ns",
285-
"swapi_pseudo_proton_density",
286-
"swapi_pseudo_proton_speed",
287-
"swapi_pseudo_proton_temperature",
288-
]
289-
290-
for key in key_names:
291-
assert swapi_product[0][key] is not None, (
292-
f"The expected attribute {key} was not filled in the result dict."
293-
)
378+
assert len(swapi_product) == 0

0 commit comments

Comments
 (0)