Skip to content

Commit 0b3344a

Browse files
author
Menlo Innovations - CAVA Project
committed
Harrison 3029 - PMCL/SWYN - CoDICE Lo direct events: Updated math for computing normalization factor and refactor the computation to its own function
1 parent d49cf2c commit 0b3344a

4 files changed

Lines changed: 152 additions & 64 deletions

File tree

imap_l3_processing/codice/l3/lo/codice_lo_processor.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
calculate_mass, calculate_mass_per_charge, \
1919
rebin_to_counts_by_species_elevation_and_spin_sector, normalize_counts, \
2020
combine_priorities_and_convert_to_rate, rebin_3d_distribution_azimuth_to_elevation, convert_count_rate_to_intensity, \
21-
rebin_direct_events_for_normalization
21+
calculate_normalization_factor
2222
from imap_l3_processing.data_utils import safe_divide
2323
from imap_l3_processing.models import InputMetadata
2424
from imap_l3_processing.processor import Processor
@@ -186,30 +186,18 @@ def process_l3a_direct_event_data_product(self, dependencies: CodiceLoL3aDirectE
186186
]
187187
num_priorities = len(priority_counts)
188188

189-
mass_per_charge = np.full((len(codice_direct_events.epoch), num_priorities, event_buffer),
190-
np.nan)
191-
mass = np.full((len(codice_direct_events.epoch), num_priorities, event_buffer), np.nan)
192-
193189
spin_angle_lut = SpinAngleLookup()
194-
normalization = np.full((len(codice_direct_events.epoch), num_priorities,
195-
num_energies, num_spin_sectors), np.nan)
196-
197-
try:
198-
mass_per_charge = calculate_mass_per_charge(codice_direct_events.energy_per_charge,
199-
codice_direct_events.tof)
200-
mass = calculate_mass(codice_direct_events.apd_energy, codice_direct_events.tof, mass_coefficient_lookup)
201-
202-
direct_events_binned_by_energy_and_spin = rebin_direct_events_for_normalization(
203-
codice_direct_events.num_events,
204-
codice_direct_events.spin_sector,
205-
codice_direct_events.energy_step,
206-
num_spin_sectors,
207-
num_energies)
208-
stacked_priorities = np.stack(priority_counts, axis=1)
209-
numerator = np.concatenate((stacked_priorities, stacked_priorities), axis=3)
210-
normalization = numerator / direct_events_binned_by_energy_and_spin
211-
except Exception as e:
212-
print(e)
190+
191+
mass_per_charge = calculate_mass_per_charge(codice_direct_events.energy_per_charge,
192+
codice_direct_events.tof)
193+
mass = calculate_mass(codice_direct_events.apd_energy, codice_direct_events.tof, mass_coefficient_lookup)
194+
stacked_priorities = np.stack(priority_counts, axis=1)
195+
normalization = calculate_normalization_factor(
196+
stacked_priorities,
197+
codice_direct_events.num_events,
198+
codice_direct_events.spin_sector,
199+
codice_direct_events.energy_step,
200+
)
213201

214202
return CodiceLoL3aDirectEventDataProduct(
215203
input_metadata=self.input_metadata,

imap_l3_processing/codice/l3/lo/science/codice_lo_calculations.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ def calculate_mass_per_charge(energy_per_charge: np.ndarray, tof: np.ndarray) ->
5353
tof ** 2) * CONVERSION_CONSTANT_K
5454

5555

56-
def rebin_direct_events_for_normalization(num_events: np.ndarray, spin_angle: np.ndarray, energy_step: np.ndarray,
56+
def rebin_direct_events_for_normalization(num_events: np.ndarray, spin_sector: np.ndarray, energy_step: np.ndarray,
5757
num_spin_sectors: int,
5858
num_energies: int) -> np.ndarray:
59-
base_counts = rebin_direct_events_by_energy_and_spin_sector(num_events, spin_angle, energy_step, num_spin_sectors, num_energies)
59+
base_counts = rebin_direct_events_by_energy_and_spin_sector(num_events, spin_sector, energy_step, num_spin_sectors, num_energies)
6060
half_spin = num_spin_sectors//2
6161
result = np.zeros_like(base_counts)
6262
result[:,:,:,0:half_spin] = base_counts[:,:,:,0:half_spin] + base_counts[:,:,:,half_spin:num_spin_sectors]
@@ -83,6 +83,20 @@ def rebin_direct_events_by_energy_and_spin_sector(num_events: np.ndarray, spin_s
8383
np.add.at(rebinned_output[time_index, priority_index], (energy_indices, spin_sectors), 1)
8484
return rebinned_output
8585

86+
def calculate_normalization_factor(priority_counts: np.ndarray, num_events: np.ndarray, spin_sectors: np.ndarray, energy_steps: np.ndarray) -> np.ndarray:
87+
numerator = np.concatenate((priority_counts, priority_counts), axis=3)
88+
num_energies = priority_counts.shape[2]
89+
num_spin_sectors = 2 * priority_counts.shape[3]
90+
denominator = rebin_direct_events_for_normalization(num_events, spin_sectors, energy_steps, num_spin_sectors, num_energies)
91+
92+
division_result = np.zeros(numerator.shape, dtype=float)
93+
np.divide(numerator, denominator, out=division_result, where=denominator!=0)
94+
95+
output = np.zeros_like(division_result)
96+
output[denominator!=0] = np.maximum(1.0, division_result[denominator!=0])
97+
output[(denominator==0)&(numerator==0)] = 0.0
98+
output[(denominator==0)&(numerator!=0)] = np.nan
99+
return output
86100

87101
def rebin_to_counts_by_species_elevation_and_spin_sector(num_events: np.ndarray, mass: np.ndarray,
88102
mass_per_charge: np.ndarray,

tests/codice/l3/lo/science/test_codice_lo_calculations.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
calculate_total_number_of_events, calculate_normalization_ratio, calculate_mass, calculate_mass_per_charge, \
1515
rebin_to_counts_by_species_elevation_and_spin_sector, rebin_direct_events_by_energy_and_spin_sector, \
1616
CODICE_LO_NUM_AZIMUTH_BINS, normalize_counts, combine_priorities_and_convert_to_rate, \
17-
rebin_3d_distribution_azimuth_to_elevation, convert_count_rate_to_intensity, rebin_direct_events_for_normalization
17+
rebin_3d_distribution_azimuth_to_elevation, convert_count_rate_to_intensity, rebin_direct_events_for_normalization, \
18+
calculate_normalization_factor
1819
from imap_l3_processing.constants import ONE_SECOND_IN_MICROSECONDS
1920

2021

@@ -316,6 +317,96 @@ def test_rebin_direct_events_for_normalization(self):
316317

317318
np.testing.assert_array_equal(result, expected_rebinned_counts)
318319

320+
def test_calculate_normalization_factor(self):
321+
num_epochs = 2
322+
num_priorities = 7
323+
num_esa_steps = 128
324+
num_l1_spin_sectors = 12
325+
num_l2_spin_sectors = 2*num_l1_spin_sectors
326+
priority_counts = np.zeros((num_epochs, num_priorities, num_esa_steps, num_l1_spin_sectors))
327+
priority_counts[0,0,0,0] = 4
328+
priority_counts[0,0,0,2] = 20
329+
330+
event_buffer_len = 15
331+
num_events = np.zeros((num_epochs, num_priorities), dtype=int)
332+
spin_sectors = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
333+
energy_steps = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
334+
335+
num_events[0, 0] = 3
336+
spin_sectors[0, 0, :3] = [0, 2, 12]
337+
energy_steps[0, 0, :3] = [0, 0, 0]
338+
339+
result = calculate_normalization_factor(priority_counts, num_events, spin_sectors, energy_steps)
340+
341+
expected_shape = (num_epochs, num_priorities, num_esa_steps, num_l2_spin_sectors)
342+
expected = np.full(expected_shape, 0.0)
343+
expected[0,0,0,0] = 4 / 2
344+
expected[0,0,0,0+12] = 4 / 2
345+
expected[0,0,0,2] = 20 / 1
346+
expected[0,0,0,2+12] = 20 / 1
347+
348+
np.testing.assert_equal(result, expected)
349+
350+
def test_calculate_normalization_factor_floor_is_one(self):
351+
num_epochs = 2
352+
num_priorities = 7
353+
num_esa_steps = 128
354+
num_l1_spin_sectors = 12
355+
num_l2_spin_sectors = 2*num_l1_spin_sectors
356+
priority_counts = np.zeros((num_epochs, num_priorities, num_esa_steps, num_l1_spin_sectors))
357+
priority_counts[0,0,0,0] = 1.
358+
priority_counts[0,0,0,2] = 0.
359+
360+
361+
event_buffer_len = 15
362+
num_events = np.zeros((num_epochs, num_priorities), dtype=int)
363+
spin_sectors = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
364+
energy_steps = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
365+
366+
num_events[0, 0] = 3
367+
spin_sectors[0, 0, :3] = [0, 12, 2]
368+
energy_steps[0, 0, :3] = [0, 0, 0]
369+
370+
result = calculate_normalization_factor(priority_counts, num_events, spin_sectors, energy_steps)
371+
372+
expected_shape = (num_epochs, num_priorities, num_esa_steps, num_l2_spin_sectors)
373+
expected = np.full(expected_shape, 0.0)
374+
self.assertEqual(1.0, result[0,0,0,0])
375+
self.assertEqual(1.0, result[0,0,0,0+12])
376+
self.assertEqual(1.0, result[0,0,0,2])
377+
self.assertEqual(1.0, result[0,0,0,2+12])
378+
379+
expected[0, 0, 0, (0, 2, 12, 14)] = 1.0
380+
np.testing.assert_equal(result, expected)
381+
382+
383+
def test_normalization_factor_is_fill_when_priority_nonzero_and_direct_zero(self):
384+
num_epochs = 2
385+
num_priorities = 7
386+
num_esa_steps = 128
387+
num_l1_spin_sectors = 12
388+
num_l2_spin_sectors = 2*num_l1_spin_sectors
389+
priority_counts = np.zeros((num_epochs, num_priorities, num_esa_steps, num_l1_spin_sectors))
390+
priority_counts[0,0,0,0] = 1.
391+
priority_counts[0,0,0,2] = 50.
392+
393+
event_buffer_len = 15
394+
num_events = np.zeros((num_epochs, num_priorities), dtype=int)
395+
spin_sectors = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
396+
energy_steps = np.zeros((num_epochs, num_priorities, event_buffer_len), dtype=int)
397+
398+
result = calculate_normalization_factor(priority_counts, num_events, spin_sectors, energy_steps)
399+
400+
expected_shape = (num_epochs, num_priorities, num_esa_steps, num_l2_spin_sectors)
401+
expected = np.full(expected_shape, 0.0)
402+
np.testing.assert_equal(result[0,0,0,0], np.nan)
403+
np.testing.assert_equal(result[0,0,0,0+12], np.nan)
404+
np.testing.assert_equal(result[0,0,0,2], np.nan)
405+
np.testing.assert_equal(result[0,0,0,2+12], np.nan)
406+
407+
expected[0, 0, 0, (0, 2, 12, 14)] = np.nan
408+
np.testing.assert_equal(result, expected)
409+
319410
def test_normalize_counts(self):
320411
num_epochs = 2
321412
num_priorities = 3

tests/codice/l3/lo/test_codice_lo_processor.py

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,11 @@ def test_process_l3a_partial_densities(self, mock_calculate_partial_densities):
499499
self.assertEqual(fe_hiq_partial_density, result_data.fe_hiq_partial_density),
500500

501501
@patch('imap_l3_processing.codice.l3.lo.codice_lo_processor.SpinAngleLookup')
502-
@patch('imap_l3_processing.codice.l3.lo.codice_lo_processor.rebin_direct_events_for_normalization')
502+
@patch('imap_l3_processing.codice.l3.lo.codice_lo_processor.calculate_normalization_factor')
503503
@patch('imap_l3_processing.codice.l3.lo.codice_lo_processor.calculate_mass_per_charge')
504504
@patch('imap_l3_processing.codice.l3.lo.codice_lo_processor.calculate_mass')
505505
def test_process_l3a_direct_events(self, mock_calculate_mass, mock_calculate_mass_per_charge,
506-
mock_rebin_direct_events_for_normalization,
506+
mock_calculate_normalization_factor,
507507
mock_spin_angle_lookup_class):
508508
rng = np.random.default_rng()
509509

@@ -517,42 +517,39 @@ def test_process_l3a_direct_events(self, mock_calculate_mass, mock_calculate_mas
517517
mock_spin_angle_lookup_class.return_value = mock_spin_angle_lookup
518518

519519
epochs = np.array([datetime.now(), datetime.now() + timedelta(hours=1)])
520+
num_epochs = len(epochs)
520521

521-
priority_counts_variable_shape = (len(epochs), num_energy_bins, num_spin_angle_bins//2)
522+
priority_counts_variable_shape = (num_epochs, num_energy_bins, num_spin_angle_bins // 2)
522523
sw_priority_rates = create_dataclass_mock(CodiceLoL1aSWPriorityRates)
523524
sw_priority_rates.epoch = epochs
524525
sw_priority_rates.p0_tcrs = rng.random(priority_counts_variable_shape)
525526
sw_priority_rates.p1_hplus = rng.random(priority_counts_variable_shape)
526527
sw_priority_rates.p2_heplusplus = rng.random(priority_counts_variable_shape)
527528
sw_priority_rates.p3_heavies = rng.random(priority_counts_variable_shape)
528529
sw_priority_rates.p4_dcrs = rng.random(priority_counts_variable_shape)
529-
sw_priority_rates.half_spin_per_esa_step = rng.random((len(epochs), num_energy_bins))
530-
sw_priority_rates.rgfo_spin_sector = rng.random(len(epochs))
531-
sw_priority_rates.rgfo_esa_step = rng.random(len(epochs))
532-
sw_priority_rates.nso_spin_sector = rng.random(len(epochs))
533-
sw_priority_rates.nso_esa_step = rng.random(len(epochs))
530+
sw_priority_rates.half_spin_per_esa_step = rng.random((num_epochs, num_energy_bins))
531+
sw_priority_rates.rgfo_spin_sector = rng.random(num_epochs)
532+
sw_priority_rates.rgfo_esa_step = rng.random(num_epochs)
533+
sw_priority_rates.nso_spin_sector = rng.random(num_epochs)
534+
sw_priority_rates.nso_esa_step = rng.random(num_epochs)
534535

535536
nsw_priority_rates = create_dataclass_mock(CodiceLoL1aNSWPriorityRates)
536537
nsw_priority_rates.epoch = epochs
537538
nsw_priority_rates.p5_heavies = rng.random(priority_counts_variable_shape)
538539
nsw_priority_rates.p6_hplus_heplusplus = rng.random(priority_counts_variable_shape)
539540

540-
counts_rebinned_by_energy_and_spin = rng.random(
541-
(len(epochs), CODICE_LO_L2_NUM_PRIORITIES, num_energy_bins, num_spin_angle_bins))
542-
mock_rebin_direct_events_for_normalization.return_value = counts_rebinned_by_energy_and_spin
543-
544-
mass_per_charge = rng.random((len(epochs), CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size))
541+
mass_per_charge = rng.random((num_epochs, CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size))
545542
mock_calculate_mass_per_charge.return_value = mass_per_charge
546-
mass = rng.random((len(epochs), CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size))
543+
mass = rng.random((num_epochs, CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size))
547544
mock_calculate_mass.return_value = mass
548545

549-
codice_l2_variables = {f.name: rng.random((len(epochs), CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size)) for f
546+
codice_l2_variables = {f.name: rng.random((num_epochs, CODICE_LO_L2_NUM_PRIORITIES, event_buffer_size)) for f
550547
in fields(CodiceLoL2DirectEventData)}
551548

552549
codice_l2_variables["epoch"] = epochs
553550
codice_l2_variables["spin_angle"] *= 360
554-
codice_l2_variables["data_quality"] = rng.random((len(epochs)))
555-
codice_l2_variables["num_events"] = rng.random((len(epochs)))
551+
codice_l2_variables["data_quality"] = rng.random((num_epochs))
552+
codice_l2_variables["num_events"] = rng.random((num_epochs))
556553

557554
expected_energy_per_charge = codice_l2_variables["energy_per_charge"]
558555
expected_apd_energy = codice_l2_variables["apd_energy"]
@@ -578,6 +575,9 @@ def test_process_l3a_direct_events(self, mock_calculate_mass, mock_calculate_mas
578575
mock_energy_lookup.bin_centers = np.geomspace(14100, 88.082825, 128)
579576
mock_energy_lookup.num_bins = num_energy_bins
580577

578+
expected_normalization = rng.random((num_epochs, num_priorities, num_energy_bins, num_spin_angle_bins))
579+
mock_calculate_normalization_factor.return_value = expected_normalization
580+
581581
dependencies = CodiceLoL3aDirectEventsDependencies(sw_priority_rates, nsw_priority_rates, direct_events, Mock(),
582582
mock_energy_lookup)
583583

@@ -595,12 +595,21 @@ def test_process_l3a_direct_events(self, mock_calculate_mass, mock_calculate_mas
595595
np.testing.assert_equal(mock_calculate_mass_per_charge.call_args.args[0], expected_energy_per_charge)
596596
np.testing.assert_equal(mock_calculate_mass_per_charge.call_args.args[1], expected_tof)
597597

598-
self.assertEqual(1, mock_rebin_direct_events_for_normalization.call_count)
599-
np.testing.assert_equal(mock_rebin_direct_events_for_normalization.call_args.args[0], expected_num_events)
600-
np.testing.assert_equal(mock_rebin_direct_events_for_normalization.call_args.args[1], expected_spin_sector)
601-
np.testing.assert_equal(mock_rebin_direct_events_for_normalization.call_args.args[2], expected_energy_step)
602-
np.testing.assert_equal(mock_rebin_direct_events_for_normalization.call_args.args[3], num_spin_angle_bins)
603-
np.testing.assert_equal(mock_rebin_direct_events_for_normalization.call_args.args[4], num_energy_bins)
598+
self.assertEqual(1, mock_calculate_normalization_factor.call_count)
599+
actual_stacked_priorities = mock_calculate_normalization_factor.call_args.args[0]
600+
601+
np.testing.assert_equal(actual_stacked_priorities[:, 0], sw_priority_rates.p0_tcrs)
602+
np.testing.assert_equal(actual_stacked_priorities[:, 1], sw_priority_rates.p1_hplus)
603+
np.testing.assert_equal(actual_stacked_priorities[:, 2], sw_priority_rates.p2_heplusplus)
604+
np.testing.assert_equal(actual_stacked_priorities[:, 3], sw_priority_rates.p3_heavies)
605+
np.testing.assert_equal(actual_stacked_priorities[:, 4], sw_priority_rates.p4_dcrs)
606+
np.testing.assert_equal(actual_stacked_priorities[:, 5], nsw_priority_rates.p5_heavies)
607+
np.testing.assert_equal(actual_stacked_priorities[:, 6], nsw_priority_rates.p6_hplus_heplusplus)
608+
609+
np.testing.assert_equal(mock_calculate_normalization_factor.call_args.args[1], expected_num_events)
610+
np.testing.assert_equal(mock_calculate_normalization_factor.call_args.args[2], expected_spin_sector)
611+
np.testing.assert_equal(mock_calculate_normalization_factor.call_args.args[3], expected_energy_step)
612+
604613

605614
self.assertIsInstance(l3a_direct_event_data_product, CodiceLoL3aDirectEventDataProduct)
606615
self.assertEqual(input_metadata, l3a_direct_event_data_product.input_metadata)
@@ -613,20 +622,6 @@ def test_process_l3a_direct_events(self, mock_calculate_mass, mock_calculate_mas
613622
np.testing.assert_array_equal(expected_mass_per_charge, l3a_direct_event_data_product.mass_per_charge)
614623
np.testing.assert_array_equal(expected_mass, l3a_direct_event_data_product.mass)
615624

616-
priority_rates = np.stack([
617-
sw_priority_rates.p0_tcrs,
618-
sw_priority_rates.p1_hplus,
619-
sw_priority_rates.p2_heplusplus,
620-
sw_priority_rates.p3_heavies,
621-
sw_priority_rates.p4_dcrs,
622-
nsw_priority_rates.p5_heavies,
623-
nsw_priority_rates.p6_hplus_heplusplus,
624-
], axis=1)
625-
expected_numerator = np.zeros((len(epochs), num_priorities, num_energy_bins, num_spin_angle_bins))
626-
expected_numerator[:,:,:,:12] = priority_rates
627-
expected_numerator[:,:,:,12:] = priority_rates
628-
expected_normalization = expected_numerator / counts_rebinned_by_energy_and_spin
629-
630625
np.testing.assert_array_equal(l3a_direct_event_data_product.normalization,
631626
np.flip(expected_normalization, axis=2))
632627

0 commit comments

Comments
 (0)