@@ -233,15 +233,14 @@ def test_calculate_moment_products(self, mock_calculate_fit_temperature_density_
233233 epoch_delta = np .repeat (timedelta (seconds = 30 ), 2 ),
234234 phase_space_density = np .arange (9 ).reshape (3 , 3 ) + 100 ,
235235 flux = np .arange (9 ).reshape (3 , 3 ),
236- energy = np .array ([2 , 4 , 6 ]),
236+ energy = np .array ([9 , 10 , 12 , 14 , 36 , 54 , 96 , 102 , 112 , 156 ]),
237237 inst_el = np .array ([]),
238238 inst_az_spin_sector = np .arange (10 , 19 ).reshape (3 , 3 ),
239239 acquisition_time = np .array ([]),
240240 )
241241
242- expected_core_breakpoint = 0
243242 expected_breakpoint_1 = (12 , 96 )
244- expected_breakpoint_2 = (16 , 86 )
243+ expected_breakpoint_2 = (14 , 54 )
245244 mock_find_breakpoints .side_effect = [
246245 expected_breakpoint_1 ,
247246 expected_breakpoint_2
@@ -279,7 +278,9 @@ def test_calculate_moment_products(self, mock_calculate_fit_temperature_density_
279278 )
280279 ]
281280
282- mock_regress .side_effect = [sentinel .first_regress_return , sentinel .second_regress_return ]
281+ mock_calculate_fit_temperature_density_velocity .return_value = Mock (density = 10 )
282+
283+ mock_regress .side_effect = [(sentinel .first_regress_return , 0 ), (sentinel .second_regress_return , 0 )]
283284 mock_average_over_look_directions .return_value = np .array ([5 , 10 , 15 ])
284285 input_metadata = InputMetadata ("swe" , "l3" , datetime (2025 , 2 , 21 ),
285286 datetime (2025 , 2 , 22 ), "v001" )
@@ -328,9 +329,8 @@ def test_calculate_moment_products(self, mock_calculate_fit_temperature_density_
328329 np .testing .assert_array_equal (swe_l2_data .phase_space_density [0 ], filter_and_flatten_call_1 .args [2 ])
329330 np .testing .assert_array_equal (maxwellian_weight_factors [0 ],
330331 filter_and_flatten_call_1 .args [3 ])
331- self .assertEqual (expected_core_breakpoint , filter_and_flatten_call_1 .args [4 ])
332- self .assertEqual (expected_breakpoint_1 [1 ] - expected_breakpoint_1 [0 ],
333- filter_and_flatten_call_1 .args [5 ])
332+ self .assertEqual (2 , filter_and_flatten_call_1 .args [4 ])
333+ self .assertEqual (6 , filter_and_flatten_call_1 .args [5 ])
334334
335335 filter_and_flatten_call_2 = mock_filter_and_flatten_regress_parameters .mock_calls [1 ]
336336 np .testing .assert_array_equal (swe_l2_data .energy - expected_breakpoint_2 [0 ],
@@ -340,9 +340,8 @@ def test_calculate_moment_products(self, mock_calculate_fit_temperature_density_
340340 np .testing .assert_array_equal (swe_l2_data .phase_space_density [1 ], filter_and_flatten_call_2 .args [2 ])
341341 np .testing .assert_array_equal (maxwellian_weight_factors [1 ],
342342 filter_and_flatten_call_2 .args [3 ])
343- self .assertEqual (expected_core_breakpoint , filter_and_flatten_call_2 .args [4 ])
344- self .assertEqual (expected_breakpoint_2 [1 ] - expected_breakpoint_2 [0 ],
345- filter_and_flatten_call_2 .args [5 ])
343+ self .assertEqual (3 , filter_and_flatten_call_2 .args [4 ])
344+ self .assertEqual (5 , filter_and_flatten_call_2 .args [5 ])
346345
347346 regress_call_1 = mock_regress .mock_calls [0 ]
348347 np .testing .assert_array_equal (filtered_velocity_vectors [0 ], regress_call_1 .args [0 ])
@@ -356,3 +355,151 @@ def test_calculate_moment_products(self, mock_calculate_fit_temperature_density_
356355
357356 mock_calculate_fit_temperature_density_velocity .assert_has_calls (
358357 [call (sentinel .first_regress_return ), call (sentinel .second_regress_return )])
358+
359+ @patch ('imap_processing.swe.swe_processor.average_over_look_directions' )
360+ @patch ('imap_processing.swe.swe_processor.compute_maxwellian_weight_factors' )
361+ @patch ('imap_processing.swe.swe_processor.calculate_velocity_in_dsp_frame_km_s' )
362+ @patch ('imap_processing.swe.swe_processor.regress' )
363+ @patch ('imap_processing.swe.swe_processor.calculate_fit_temperature_density_velocity' )
364+ @patch ('imap_processing.swe.swe_processor.find_breakpoints' )
365+ @patch ('imap_processing.swe.swe_processor.filter_and_flatten_regress_parameters' )
366+ def test_moment_fit_should_be_retried_until_returned_density_is_valid (self ,
367+ mock_filter_and_flatten_regress_parameters ,
368+ mock_find_breakpoints ,
369+ mock_calculate_fit_temperature_density_velocity ,
370+ mock_regress , mock_calculate_velocity ,
371+ mock_compute_maxwellian_weights ,
372+ _ ):
373+ epochs = datetime .now () + np .arange (1 ) * timedelta (minutes = 1 )
374+ swe_config = build_swe_configuration ()
375+
376+ energies = np .array ([1.2 , 3.4 , 4.6 , 5.9 , 8.7 , 9.1 , 9.2 , 10.5 , 11.9 ])
377+
378+ swe_l2_data = SweL2Data (
379+ epoch = epochs ,
380+ epoch_delta = np .repeat (timedelta (seconds = 30 ), 1 ),
381+ phase_space_density = np .arange (7 * 3 ).reshape (1 , 7 , 3 ) + 100 ,
382+ flux = np .arange (9 ).reshape (3 , 3 ),
383+ energy = energies ,
384+ inst_el = np .array ([]),
385+ inst_az_spin_sector = np .arange (10 , 19 ).reshape (3 , 3 ),
386+ acquisition_time = np .array ([]),
387+ )
388+
389+ core_breakpoint = 3.4
390+ core_halo_breakpoint = 9.2
391+ mock_find_breakpoints .return_value = (core_breakpoint , core_halo_breakpoint )
392+
393+ expected_core_energy_start_index = 1
394+ expected_core_energy_end_index = 6
395+
396+ mock_filter_and_flatten_regress_parameters .side_effect = [
397+ (sentinel .filtered_vecs_1 , sentinel .filtered_weights_1 , sentinel .filtered_yreg_1 ),
398+ (sentinel .filtered_vecs_2 , sentinel .filtered_weights_2 , sentinel .filtered_yreg_2 ),
399+ (sentinel .filtered_vecs_3 , sentinel .filtered_weights_3 , sentinel .filtered_yreg_3 )
400+ ]
401+
402+ mock_regress .side_effect = [(sentinel .first_regress_return , 0 ), (sentinel .second_regress_return , 0 ),
403+ (sentinel .third_regress_return , 0 )]
404+
405+ mock_calculate_fit_temperature_density_velocity .side_effect = [(Mock (density = - 1 )), (Mock (density = 186 )),
406+ (Mock (density = 10 ))]
407+
408+ input_metadata = InputMetadata ("swe" , "l3" , datetime (2025 , 2 , 21 ),
409+ datetime (2025 , 2 , 22 ), "v001" )
410+
411+ swel3_dependency = SweL3Dependencies (swe_l2_data , Mock (), Mock (), swe_config )
412+ swe_processor = SweProcessor (dependencies = [], input_metadata = input_metadata )
413+
414+ swe_processor .calculate_moment_products (swel3_dependency )
415+
416+ self .assertEqual (3 , mock_filter_and_flatten_regress_parameters .call_count )
417+ mock_filter_and_flatten_regress_parameters .assert_has_calls ([
418+ call (NumpyArrayMatcher (swe_l2_data .energy - core_breakpoint ), mock_calculate_velocity .return_value ,
419+ NumpyArrayMatcher (swe_l2_data .phase_space_density [0 ]), mock_compute_maxwellian_weights .return_value ,
420+ expected_core_energy_start_index , expected_core_energy_end_index ),
421+ call (NumpyArrayMatcher (swe_l2_data .energy - core_breakpoint ), mock_calculate_velocity .return_value ,
422+ NumpyArrayMatcher (swe_l2_data .phase_space_density [0 ]), mock_compute_maxwellian_weights .return_value ,
423+ expected_core_energy_start_index , expected_core_energy_end_index - 1 ),
424+ call (NumpyArrayMatcher (swe_l2_data .energy - core_breakpoint ), mock_calculate_velocity .return_value ,
425+ NumpyArrayMatcher (swe_l2_data .phase_space_density [0 ]), mock_compute_maxwellian_weights .return_value ,
426+ expected_core_energy_start_index , expected_core_energy_end_index - 2 )
427+ ])
428+
429+ self .assertEqual (3 , mock_regress .call_count )
430+ mock_regress .assert_has_calls ([
431+ call (sentinel .filtered_vecs_1 , sentinel .filtered_weights_1 , sentinel .filtered_yreg_1 ),
432+ call (sentinel .filtered_vecs_2 , sentinel .filtered_weights_2 , sentinel .filtered_yreg_2 ),
433+ call (sentinel .filtered_vecs_3 , sentinel .filtered_weights_3 , sentinel .filtered_yreg_3 )
434+
435+ ])
436+
437+ self .assertEqual (3 , mock_calculate_fit_temperature_density_velocity .call_count )
438+ mock_calculate_fit_temperature_density_velocity .assert_has_calls ([
439+ call (sentinel .first_regress_return ),
440+ call (sentinel .second_regress_return ),
441+ call (sentinel .third_regress_return )
442+ ])
443+
444+ @patch ('imap_processing.swe.swe_processor.average_over_look_directions' )
445+ @patch ('imap_processing.swe.swe_processor.compute_maxwellian_weight_factors' )
446+ @patch ('imap_processing.swe.swe_processor.calculate_velocity_in_dsp_frame_km_s' )
447+ @patch ('imap_processing.swe.swe_processor.regress' )
448+ @patch ('imap_processing.swe.swe_processor.calculate_fit_temperature_density_velocity' )
449+ @patch ('imap_processing.swe.swe_processor.find_breakpoints' )
450+ @patch ('imap_processing.swe.swe_processor.filter_and_flatten_regress_parameters' )
451+ def test_moment_fit_should_be_ran_while_number_of_energies_between_halo_and_core_is_greater_than_3 (self ,
452+ mock_filter_and_flatten_regress_parameters ,
453+ mock_find_breakpoints ,
454+ mock_calculate_fit_temperature_density_velocity ,
455+ mock_regress ,
456+ mock_calculate_velocity ,
457+ mock_compute_maxwellian_weights ,
458+ _ ):
459+ epochs = datetime .now () + np .arange (1 ) * timedelta (minutes = 1 )
460+ swe_config = build_swe_configuration ()
461+
462+ energies = np .array ([1.2 , 3.4 , 4.6 , 5.9 , 8.7 , 9.2 , 10.5 , 11.9 ])
463+
464+ swe_l2_data = SweL2Data (
465+ epoch = epochs ,
466+ epoch_delta = np .repeat (timedelta (seconds = 30 ), 1 ),
467+ phase_space_density = np .arange (7 * 3 ).reshape (1 , 7 , 3 ) + 100 ,
468+ flux = np .arange (9 ).reshape (3 , 3 ),
469+ energy = energies ,
470+ inst_el = np .array ([]),
471+ inst_az_spin_sector = np .arange (10 , 19 ).reshape (3 , 3 ),
472+ acquisition_time = np .array ([]),
473+ )
474+
475+ core_breakpoint = 3.4
476+ core_halo_breakpoint = 8.7
477+ mock_find_breakpoints .return_value = (core_breakpoint , core_halo_breakpoint )
478+
479+ expected_core_energy_start_index = 1
480+ expected_core_energy_end_index = 4
481+
482+ mock_filter_and_flatten_regress_parameters .return_value = (
483+ sentinel .filtered_vecs_1 , sentinel .filtered_weights_1 , sentinel .filtered_yreg_1 )
484+
485+ mock_regress .return_value = (sentinel .first_regress_return , 0 )
486+
487+ mock_calculate_fit_temperature_density_velocity .side_effect = [Mock (density = - 1 ), Mock (density = 10 )]
488+
489+ input_metadata = InputMetadata ("swe" , "l3" , datetime (2025 , 2 , 21 ),
490+ datetime (2025 , 2 , 22 ), "v001" )
491+
492+ swel3_dependency = SweL3Dependencies (swe_l2_data , Mock (), Mock (), swe_config )
493+ swe_processor = SweProcessor (dependencies = [], input_metadata = input_metadata )
494+
495+ swe_processor .calculate_moment_products (swel3_dependency )
496+
497+ mock_filter_and_flatten_regress_parameters .assert_called_once_with (
498+ NumpyArrayMatcher (swe_l2_data .energy - core_breakpoint ), mock_calculate_velocity .return_value ,
499+ NumpyArrayMatcher (swe_l2_data .phase_space_density [0 ]), mock_compute_maxwellian_weights .return_value ,
500+ expected_core_energy_start_index , expected_core_energy_end_index )
501+
502+ mock_regress .assert_called_once_with (sentinel .filtered_vecs_1 , sentinel .filtered_weights_1 ,
503+ sentinel .filtered_yreg_1 )
504+
505+ mock_calculate_fit_temperature_density_velocity .assert_called_once_with (sentinel .first_regress_return )
0 commit comments