imap-processing 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (172) hide show
  1. imap_processing/__init__.py +1 -1
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/excel_to_xtce.py +36 -2
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
  6. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +136 -9
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
  9. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
  10. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
  11. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +14 -7
  12. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +577 -235
  13. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
  14. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
  15. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +137 -79
  18. imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
  19. imap_processing/cdf/imap_cdf_manager.py +31 -27
  20. imap_processing/cdf/utils.py +3 -5
  21. imap_processing/cli.py +25 -14
  22. imap_processing/codice/codice_l1a.py +153 -63
  23. imap_processing/codice/constants.py +10 -10
  24. imap_processing/codice/decompress.py +10 -11
  25. imap_processing/codice/utils.py +1 -0
  26. imap_processing/glows/l1a/glows_l1a.py +1 -2
  27. imap_processing/glows/l1b/glows_l1b.py +3 -3
  28. imap_processing/glows/l1b/glows_l1b_data.py +59 -37
  29. imap_processing/glows/l2/glows_l2_data.py +123 -0
  30. imap_processing/hi/l1a/hi_l1a.py +4 -4
  31. imap_processing/hi/l1a/histogram.py +107 -109
  32. imap_processing/hi/l1a/science_direct_event.py +92 -225
  33. imap_processing/hi/l1b/hi_l1b.py +85 -11
  34. imap_processing/hi/l1c/hi_l1c.py +23 -1
  35. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
  36. imap_processing/hi/utils.py +1 -1
  37. imap_processing/hit/hit_utils.py +221 -0
  38. imap_processing/hit/l0/constants.py +118 -0
  39. imap_processing/hit/l0/decom_hit.py +100 -156
  40. imap_processing/hit/l1a/hit_l1a.py +170 -184
  41. imap_processing/hit/l1b/hit_l1b.py +33 -153
  42. imap_processing/ialirt/l0/process_codicelo.py +153 -0
  43. imap_processing/ialirt/l0/process_hit.py +5 -5
  44. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
  45. imap_processing/ialirt/process_ephemeris.py +212 -0
  46. imap_processing/idex/idex_l1a.py +65 -84
  47. imap_processing/idex/idex_l1b.py +192 -0
  48. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
  49. imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
  50. imap_processing/lo/l0/decompression_tables/decompression_tables.py +17 -1
  51. imap_processing/lo/l0/lo_science.py +45 -13
  52. imap_processing/lo/l1a/lo_l1a.py +76 -8
  53. imap_processing/lo/packet_definitions/lo_xtce.xml +8344 -1849
  54. imap_processing/mag/l0/decom_mag.py +4 -3
  55. imap_processing/mag/l1a/mag_l1a.py +12 -13
  56. imap_processing/mag/l1a/mag_l1a_data.py +1 -2
  57. imap_processing/mag/l1b/mag_l1b.py +90 -7
  58. imap_processing/spice/geometry.py +156 -16
  59. imap_processing/spice/time.py +144 -2
  60. imap_processing/swapi/l1/swapi_l1.py +4 -4
  61. imap_processing/swapi/l2/swapi_l2.py +1 -1
  62. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
  63. imap_processing/swe/l1b/swe_l1b_science.py +8 -8
  64. imap_processing/swe/l2/swe_l2.py +134 -17
  65. imap_processing/tests/ccsds/test_data/expected_output.xml +2 -1
  66. imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
  67. imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
  68. imap_processing/tests/codice/conftest.py +1 -17
  69. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  70. imap_processing/tests/codice/test_codice_l0.py +8 -2
  71. imap_processing/tests/codice/test_codice_l1a.py +127 -107
  72. imap_processing/tests/codice/test_codice_l1b.py +1 -0
  73. imap_processing/tests/codice/test_decompress.py +7 -7
  74. imap_processing/tests/conftest.py +100 -58
  75. imap_processing/tests/glows/conftest.py +6 -0
  76. imap_processing/tests/glows/test_glows_l1b.py +9 -9
  77. imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
  78. imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
  79. imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
  80. imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
  81. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  82. imap_processing/tests/hi/test_hi_l1b.py +73 -3
  83. imap_processing/tests/hi/test_hi_l1c.py +10 -2
  84. imap_processing/tests/hi/test_l1a.py +31 -58
  85. imap_processing/tests/hi/test_science_direct_event.py +58 -0
  86. imap_processing/tests/hi/test_utils.py +4 -3
  87. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  88. imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +95 -36
  89. imap_processing/tests/hit/test_hit_l1a.py +299 -179
  90. imap_processing/tests/hit/test_hit_l1b.py +231 -24
  91. imap_processing/tests/hit/test_hit_utils.py +218 -0
  92. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
  93. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
  94. imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
  95. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  96. imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
  97. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
  98. imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
  99. imap_processing/tests/idex/conftest.py +2 -2
  100. imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  101. imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
  102. imap_processing/tests/idex/test_idex_l0.py +4 -4
  103. imap_processing/tests/idex/test_idex_l1a.py +8 -2
  104. imap_processing/tests/idex/test_idex_l1b.py +126 -0
  105. imap_processing/tests/lo/test_lo_l1a.py +7 -16
  106. imap_processing/tests/lo/test_lo_science.py +69 -5
  107. imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
  108. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
  109. imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
  110. imap_processing/tests/mag/test_mag_l1b.py +97 -7
  111. imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
  112. imap_processing/tests/spice/test_geometry.py +115 -9
  113. imap_processing/tests/spice/test_time.py +135 -6
  114. imap_processing/tests/swapi/test_swapi_decom.py +75 -69
  115. imap_processing/tests/swapi/test_swapi_l1.py +4 -4
  116. imap_processing/tests/swe/conftest.py +33 -0
  117. imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
  118. imap_processing/tests/swe/test_swe_l1b.py +29 -8
  119. imap_processing/tests/swe/test_swe_l2.py +64 -8
  120. imap_processing/tests/test_utils.py +2 -2
  121. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  122. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E12.cdf +0 -0
  123. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
  124. imap_processing/tests/ultra/unit/test_de.py +113 -0
  125. imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
  126. imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
  127. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
  128. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +55 -35
  129. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +10 -68
  130. imap_processing/ultra/constants.py +12 -3
  131. imap_processing/ultra/l1b/de.py +168 -30
  132. imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
  133. imap_processing/ultra/l1b/ultra_l1b_extended.py +46 -80
  134. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +60 -144
  135. imap_processing/ultra/utils/spatial_utils.py +221 -0
  136. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +15 -14
  137. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +142 -139
  138. imap_processing/cdf/cdf_attribute_manager.py +0 -322
  139. imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
  140. imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  141. imap_processing/hi/l0/decom_hi.py +0 -24
  142. imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
  143. imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
  144. imap_processing/hit/l0/data_classes/science_packet.py +0 -259
  145. imap_processing/hit/l0/utils/hit_base.py +0 -57
  146. imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
  147. imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  148. imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
  149. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
  150. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
  151. imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
  152. imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
  153. imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
  154. imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
  155. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
  156. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
  157. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
  158. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
  159. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
  160. imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
  161. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
  162. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
  163. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
  164. imap_processing/tests/hi/test_decom.py +0 -55
  165. imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
  166. imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
  167. imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
  168. /imap_processing/{hi/l0/__init__.py → tests/glows/test_glows_l2_data.py} +0 -0
  169. /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
  170. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
  171. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
  172. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,113 @@
1
+ """Tests Extended Raw Events for ULTRA L1b."""
2
+
3
+ from unittest import mock
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import pytest
8
+
9
+ from imap_processing.ultra.constants import UltraConstants
10
+ from imap_processing.ultra.l1b.de import calculate_de
11
+
12
+
13
+ @pytest.fixture()
14
+ def df_filt(de_dataset, events_fsw_comparison_theta_0):
15
+ """Fixture to import test dataset."""
16
+ df = pd.read_csv(events_fsw_comparison_theta_0)
17
+ df_filt = df[df["StartType"] != -1]
18
+ df_filt = df_filt.replace("FILL", 0)
19
+
20
+ return df_filt
21
+
22
+
23
+ @mock.patch("imap_processing.ultra.l1b.de.get_annotated_particle_velocity")
24
+ def test_calculate_de(mock_get_annotated_particle_velocity, de_dataset, df_filt):
25
+ """Tests calculate_de function."""
26
+
27
+ # Mock get_annotated_particle_velocity to avoid needing kernels
28
+ def side_effect_func(event_times, position, ultra_frame, dps_frame, sc_frame):
29
+ """
30
+ Mock behavior of get_annotated_particle_velocity.
31
+
32
+ Returns NaN-filled arrays matching the expected output shape.
33
+ """
34
+ num_events = event_times.size
35
+ return (
36
+ np.full((num_events, 3), np.nan), # sc_velocity
37
+ np.full((num_events, 3), np.nan), # sc_dps_velocity
38
+ np.full((num_events, 3), np.nan), # helio_velocity
39
+ )
40
+
41
+ mock_get_annotated_particle_velocity.side_effect = side_effect_func
42
+
43
+ dataset = calculate_de(de_dataset, "imap_ultra_l1b_45sensor-de")
44
+
45
+ # Front and back positions
46
+ assert np.allclose(dataset["x_front"].data, df_filt["Xf"].astype("float"))
47
+ assert np.allclose(dataset["y_front"], df_filt["Yf"].astype("float"))
48
+ assert np.allclose(dataset["x_back"], df_filt["Xb"].astype("float"))
49
+ assert np.allclose(dataset["y_back"], df_filt["Yb"].astype("float"))
50
+
51
+ # Coincidence positions
52
+ assert np.allclose(dataset["x_coin"], df_filt["Xc"].astype("float"))
53
+
54
+ # Time of flight
55
+ assert np.allclose(dataset["tof_start_stop"], df_filt["TOF"].astype("float"))
56
+ assert np.allclose(dataset["tof_stop_coin"], df_filt["eTOF"].astype("float"))
57
+ assert np.allclose(dataset["tof_corrected"], df_filt["cTOF"].astype("float"))
58
+
59
+ # Distances and path lengths
60
+ assert np.allclose(dataset["front_back_distance"], df_filt["d"].astype("float"))
61
+ assert np.allclose(dataset["path_length"], df_filt["r"].astype("float"))
62
+
63
+ # Coincidence, start, and event types
64
+ assert np.allclose(dataset["coincidence_type"], df_filt["CoinType"].astype("float"))
65
+ assert np.allclose(dataset["start_type"], df_filt["StartType"].astype("float"))
66
+ assert np.allclose(dataset["event_type"], df_filt["StopType"].astype("float"))
67
+
68
+ # Energies and species
69
+ assert np.allclose(dataset["energy"], df_filt["Energy"].astype("float"))
70
+ species_array = dataset["species"][
71
+ np.where(
72
+ (dataset["tof_corrected"] > UltraConstants.CTOF_SPECIES_MIN)
73
+ & (dataset["tof_corrected"] < UltraConstants.CTOF_SPECIES_MAX)
74
+ )[0]
75
+ ]
76
+ assert np.all(species_array == "H")
77
+
78
+ # Velocities in various frames
79
+ test_tof = dataset["tof_start_stop"]
80
+ assert np.allclose(
81
+ dataset["vx_ultra"][test_tof > 0],
82
+ -df_filt["vhatX"].astype("float").values[test_tof > 0],
83
+ rtol=1e-2,
84
+ )
85
+ assert np.allclose(
86
+ dataset["vy_ultra"][test_tof > 0],
87
+ -df_filt["vhatY"].astype("float").values[test_tof > 0],
88
+ rtol=1e-2,
89
+ )
90
+ assert np.allclose(
91
+ dataset["vz_ultra"][test_tof > 0],
92
+ -df_filt["vhatZ"].astype("float").values[test_tof > 0],
93
+ rtol=1e-2,
94
+ )
95
+
96
+ assert dataset["vx_sc"].shape == (len(de_dataset["epoch"]),)
97
+ assert dataset["vy_sc"].shape == (len(de_dataset["epoch"]),)
98
+ assert dataset["vz_sc"].shape == (len(de_dataset["epoch"]),)
99
+
100
+ assert dataset["vx_dps_sc"].shape == (len(de_dataset["epoch"]),)
101
+ assert dataset["vy_dps_sc"].shape == (len(de_dataset["epoch"]),)
102
+ assert dataset["vz_dps_sc"].shape == (len(de_dataset["epoch"]),)
103
+
104
+ assert dataset["vx_dps_helio"].shape == (len(de_dataset["epoch"]),)
105
+ assert dataset["vy_dps_helio"].shape == (len(de_dataset["epoch"]),)
106
+ assert dataset["vz_dps_helio"].shape == (len(de_dataset["epoch"]),)
107
+
108
+ # Event efficiency
109
+ assert np.allclose(
110
+ dataset["event_efficiency"],
111
+ np.full(len(de_dataset["epoch"]), np.nan),
112
+ equal_nan=True,
113
+ )
@@ -0,0 +1,125 @@
1
+ """Test creation of solid angle map."""
2
+
3
+ import numpy as np
4
+ import numpy.testing as npt
5
+ import pytest
6
+
7
+ from imap_processing.ultra.utils import spatial_utils
8
+
9
+ # Parameterize with spacings (degrees here):
10
+ valid_spacings = [0.1, 0.25, 0.5, 1, 5, 10, 20]
11
+ invalid_spacings = [0, -1, 11]
12
+ invalid_spacings_match_str = [
13
+ "Spacing must be positive valued, non-zero.",
14
+ "Spacing must be positive valued, non-zero.",
15
+ "Spacing must divide evenly into pi radians.",
16
+ ]
17
+
18
+
19
+ def test_build_spatial_bins():
20
+ """Tests build_spatial_bins function."""
21
+ az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = (
22
+ spatial_utils.build_spatial_bins()
23
+ )
24
+
25
+ assert az_bin_edges[0] == 0
26
+ assert az_bin_edges[-1] == 360
27
+ assert len(az_bin_edges) == 721
28
+
29
+ assert el_bin_edges[0] == -90
30
+ assert el_bin_edges[-1] == 90
31
+ assert len(el_bin_edges) == 361
32
+
33
+ assert len(az_bin_midpoints) == 720
34
+ np.testing.assert_allclose(az_bin_midpoints[0], 0.25, atol=1e-4)
35
+ np.testing.assert_allclose(az_bin_midpoints[-1], 359.75, atol=1e-4)
36
+
37
+ assert len(el_bin_midpoints) == 360
38
+ np.testing.assert_allclose(el_bin_midpoints[0], -89.75, atol=1e-4)
39
+ np.testing.assert_allclose(el_bin_midpoints[-1], 89.75, atol=1e-4)
40
+
41
+
42
+ @pytest.mark.parametrize("spacing", valid_spacings)
43
+ def test_build_solid_angle_map(spacing):
44
+ """Test build_solid_angle_map function."""
45
+ solid_angle_map_steradians = spatial_utils.build_solid_angle_map(
46
+ spacing, input_degrees=True, output_degrees=False
47
+ )
48
+ assert np.isclose(np.sum(solid_angle_map_steradians), 4 * np.pi, atol=0, rtol=1e-9)
49
+
50
+ solid_angle_map_sqdeg = spatial_utils.build_solid_angle_map(
51
+ np.deg2rad(spacing), input_degrees=False, output_degrees=True
52
+ )
53
+ assert np.isclose(
54
+ np.sum(solid_angle_map_sqdeg), 4 * np.pi * (180 / np.pi) ** 2, atol=0, rtol=1e-9
55
+ )
56
+
57
+
58
+ @pytest.mark.parametrize(
59
+ "spacing, match_str", zip(invalid_spacings, invalid_spacings_match_str)
60
+ )
61
+ def test_build_solid_angle_map_invalid_spacing(spacing, match_str):
62
+ """Test build_solid_angle_map function raises error for invalid spacing."""
63
+ with pytest.raises(ValueError, match=match_str):
64
+ _ = spatial_utils.build_solid_angle_map(
65
+ spacing, input_degrees=True, output_degrees=False
66
+ )
67
+
68
+
69
+ @pytest.mark.parametrize("spacing", valid_spacings)
70
+ def test_build_az_el_grid(spacing):
71
+ """Test build_az_el_grid function."""
72
+ az_range, el_range, az_grid, el_grid = spatial_utils.build_az_el_grid(
73
+ spacing=spacing,
74
+ input_degrees=True,
75
+ output_degrees=True,
76
+ centered_azimuth=False,
77
+ centered_elevation=True,
78
+ )
79
+
80
+ # Size checks
81
+ assert az_range.size == int(360 / spacing)
82
+ assert el_range.size == int(180 / spacing)
83
+ assert az_range.size == az_grid.shape[1]
84
+ assert el_range.size == el_grid.shape[0]
85
+
86
+ # Check grid values
87
+ expected_az_range = np.arange((spacing / 2), 360 + (spacing / 2), spacing)
88
+ expected_el_range = np.arange(-90 + (spacing / 2), 90 + (spacing / 2), spacing)[
89
+ ::-1
90
+ ] # Note el order is reversed
91
+
92
+ npt.assert_allclose(az_range, expected_az_range, atol=1e-12)
93
+ npt.assert_allclose(el_range, expected_el_range, atol=1e-12)
94
+
95
+
96
+ def test_rewrap_even_spaced_el_az_grid_1d():
97
+ """Test rewrap_even_spaced_el_az_grid function, without extra axis."""
98
+ orig_shape = (180 * 12, 360 * 12)
99
+ orig_grid = np.fromfunction(lambda i, j: i**2 + j, orig_shape, dtype=int)
100
+ raveled_values = orig_grid.ravel(order="F")
101
+ rewrapped_grid_infer_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
102
+ raveled_values
103
+ )
104
+ rewrapped_grid_known_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
105
+ raveled_values, shape=orig_shape
106
+ )
107
+
108
+ assert np.array_equal(rewrapped_grid_infer_shape, orig_grid)
109
+ assert np.array_equal(rewrapped_grid_known_shape, orig_grid)
110
+
111
+
112
+ def test_rewrap_even_spaced_el_az_grid_2d():
113
+ """Test rewrap_even_spaced_el_az_grid function, with extra axis."""
114
+ orig_shape = (180 * 12, 360 * 12, 5)
115
+ orig_grid = np.fromfunction(lambda i, j, k: i**2 + j + k, orig_shape, dtype=int)
116
+ raveled_values = orig_grid.reshape(-1, 5, order="F")
117
+ rewrapped_grid_infer_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
118
+ raveled_values, extra_axis=True
119
+ )
120
+ rewrapped_grid_known_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
121
+ raveled_values, shape=orig_shape, extra_axis=True
122
+ )
123
+ assert raveled_values.shape == (180 * 12 * 360 * 12, 5)
124
+ assert np.array_equal(rewrapped_grid_infer_shape, orig_grid)
125
+ assert np.array_equal(rewrapped_grid_known_shape, orig_grid)
@@ -1,3 +1,5 @@
1
+ from unittest import mock
2
+
1
3
  import numpy as np
2
4
  import pytest
3
5
  import xarray as xr
@@ -73,7 +75,7 @@ def test_create_dataset(mock_data_l1b_dict):
73
75
  assert "epoch" in dataset.coords
74
76
  assert dataset.coords["epoch"].dtype == "datetime64[ns]"
75
77
  assert dataset.attrs["Logical_source"] == "imap_ultra_l1b_45sensor-de"
76
- assert dataset["x_front"].attrs["UNITS"] == "mm"
78
+ assert dataset["x_front"].attrs["UNITS"] == "mm / 100"
77
79
  np.testing.assert_array_equal(dataset["x_front"], np.zeros(3))
78
80
 
79
81
 
@@ -99,9 +101,31 @@ def test_ultra_l1b_rates(mock_data_l1a_rates_dict):
99
101
  )
100
102
 
101
103
 
102
- def test_ultra_l1b_de(mock_data_l1a_de_aux_dict):
104
+ @pytest.mark.external_kernel()
105
+ @pytest.mark.use_test_metakernel("imap_ena_sim_metakernel.template")
106
+ @mock.patch("imap_processing.ultra.l1b.de.get_annotated_particle_velocity")
107
+ def test_ultra_l1b_de(mock_get_annotated_particle_velocity, de_dataset):
103
108
  """Tests that L1b data is created."""
104
- output_datasets = ultra_l1b(mock_data_l1a_de_aux_dict, data_version="001")
109
+ data_dict = {}
110
+ data_dict[de_dataset.attrs["Logical_source"]] = de_dataset
111
+ data_dict["imap_ultra_l1a_45sensor-aux"] = de_dataset
112
+
113
+ # Mock get_annotated_particle_velocity to avoid needing kernels
114
+ def side_effect_func(event_times, position, ultra_frame, dps_frame, sc_frame):
115
+ """
116
+ Mock behavior of get_annotated_particle_velocity.
117
+
118
+ Returns NaN-filled arrays matching the expected output shape.
119
+ """
120
+ num_events = event_times.size
121
+ return (
122
+ np.full((num_events, 3), np.nan), # sc_velocity
123
+ np.full((num_events, 3), np.nan), # sc_dps_velocity
124
+ np.full((num_events, 3), np.nan), # helio_velocity
125
+ )
126
+
127
+ mock_get_annotated_particle_velocity.side_effect = side_effect_func
128
+ output_datasets = ultra_l1b(data_dict, data_version="001")
105
129
 
106
130
  assert len(output_datasets) == 1
107
131
  assert output_datasets[0].attrs["Logical_source"] == "imap_ultra_l1b_45sensor-de"
@@ -5,7 +5,9 @@ import pytest
5
5
  import spiceypy as spice
6
6
 
7
7
  from imap_processing.spice.geometry import SpiceFrame
8
- from imap_processing.ultra.l1b.ultra_l1b_annotated import get_particle_velocity
8
+ from imap_processing.ultra.l1b.ultra_l1b_annotated import (
9
+ get_annotated_particle_velocity,
10
+ )
9
11
 
10
12
 
11
13
  @pytest.fixture()
@@ -45,18 +47,37 @@ def test_get_particle_velocity(spice_test_data_path, kernels):
45
47
  times = np.array([start])
46
48
  instrument_velocity = np.array([[41.18609, -471.24467, -832.8784]])
47
49
 
48
- sc_velocity_45, helio_velocity_45 = get_particle_velocity(
49
- times, instrument_velocity, SpiceFrame.IMAP_ULTRA_45, SpiceFrame.IMAP_DPS
50
+ sc_velocity_45, sc_dps_velocity_45, helio_velocity_45 = (
51
+ get_annotated_particle_velocity(
52
+ times,
53
+ instrument_velocity,
54
+ SpiceFrame.IMAP_ULTRA_45,
55
+ SpiceFrame.IMAP_DPS,
56
+ SpiceFrame.IMAP_SPACECRAFT,
57
+ )
50
58
  )
51
- sc_velocity_90, helio_velocity_90 = get_particle_velocity(
52
- times, instrument_velocity, SpiceFrame.IMAP_ULTRA_90, SpiceFrame.IMAP_DPS
59
+ sc_velocity_90, sc_dps_velocity_90, helio_velocity_90 = (
60
+ get_annotated_particle_velocity(
61
+ times,
62
+ instrument_velocity,
63
+ SpiceFrame.IMAP_ULTRA_90,
64
+ SpiceFrame.IMAP_DPS,
65
+ SpiceFrame.IMAP_SPACECRAFT,
66
+ )
53
67
  )
54
68
 
55
69
  # Compute the magnitude of the velocity vectors in both frames
56
- magnitude_45 = np.linalg.norm(sc_velocity_45)
57
- magnitude_90 = np.linalg.norm(sc_velocity_90)
70
+ magnitude_sc_45 = np.linalg.norm(sc_velocity_45)
71
+ magnitude_sc_90 = np.linalg.norm(sc_velocity_90)
72
+ magnitude_dps_45 = np.linalg.norm(sc_dps_velocity_45)
73
+ magnitude_dps_90 = np.linalg.norm(sc_dps_velocity_90)
58
74
  state, lt = spice.spkezr("IMAP", times, "IMAP_DPS", "NONE", "SUN")
59
75
 
60
- assert np.allclose(magnitude_45, magnitude_90, atol=1e-6)
61
- assert np.array_equal((helio_velocity_45 - state[0][3:6]).flatten(), sc_velocity_45)
62
- assert np.array_equal((helio_velocity_90 - state[0][3:6]).flatten(), sc_velocity_90)
76
+ assert np.allclose(magnitude_sc_45, magnitude_sc_90, atol=1e-6)
77
+ assert np.allclose(magnitude_dps_45, magnitude_dps_90, atol=1e-6)
78
+ assert np.array_equal(
79
+ (helio_velocity_45 - state[0][3:6]).flatten(), sc_dps_velocity_45
80
+ )
81
+ assert np.array_equal(
82
+ (helio_velocity_90 - state[0][3:6]).flatten(), sc_dps_velocity_90
83
+ )
@@ -4,24 +4,24 @@ import numpy as np
4
4
  import pandas as pd
5
5
  import pytest
6
6
 
7
+ from imap_processing.ultra.constants import UltraConstants
7
8
  from imap_processing.ultra.l1b.ultra_l1b_extended import (
8
9
  CoinType,
9
10
  StartType,
10
11
  StopType,
11
12
  calculate_etof_xc,
12
- determine_species_pulse_height,
13
- determine_species_ssd,
13
+ determine_species,
14
14
  get_coincidence_positions,
15
15
  get_ctof,
16
16
  get_energy_pulse_height,
17
17
  get_energy_ssd,
18
18
  get_front_x_position,
19
19
  get_front_y_position,
20
- get_particle_velocity,
21
20
  get_path_length,
22
21
  get_ph_tof_and_back_positions,
23
22
  get_ssd_back_position_and_tof_offset,
24
23
  get_ssd_tof,
24
+ get_unit_vector,
25
25
  )
26
26
 
27
27
 
@@ -213,8 +213,8 @@ def test_calculate_etof_xc(de_dataset, yf_fixture):
213
213
  )
214
214
 
215
215
 
216
- def test_get_particle_velocity(de_dataset, yf_fixture):
217
- """Tests get_particle_velocity function."""
216
+ def test_get_unit_vector(de_dataset, yf_fixture):
217
+ """Tests get_unit_vector function."""
218
218
  df_filt, _, _ = yf_fixture
219
219
 
220
220
  ph_indices = np.nonzero(
@@ -229,7 +229,7 @@ def test_get_particle_velocity(de_dataset, yf_fixture):
229
229
  test_d = ph_rows["d"].astype("float").values
230
230
  test_tof = ph_rows["TOF"].astype("float").values
231
231
 
232
- vhat_x, vhat_y, vhat_z = get_particle_velocity(
232
+ vhat_x, vhat_y, vhat_z = get_unit_vector(
233
233
  (test_xf, test_yf),
234
234
  (test_xb, test_yb),
235
235
  test_d,
@@ -254,7 +254,7 @@ def test_get_particle_velocity(de_dataset, yf_fixture):
254
254
  def test_get_ssd_tof(de_dataset, yf_fixture):
255
255
  """Tests get_ssd_tof function."""
256
256
  df_filt, _, _ = yf_fixture
257
- df_ssd = df_filt[df_filt["StopType"].isin(StopType.SSD.value)]
257
+ df_ssd = df_filt[np.isin(df_filt["StopType"], [StopType.SSD.value])]
258
258
  test_xf = df_filt["Xf"].astype("float").values
259
259
 
260
260
  ssd_tof = get_ssd_tof(de_dataset, test_xf)
@@ -267,7 +267,7 @@ def test_get_ssd_tof(de_dataset, yf_fixture):
267
267
  def test_get_energy_ssd(de_dataset, yf_fixture):
268
268
  """Tests get_energy_ssd function."""
269
269
  df_filt, _, _ = yf_fixture
270
- df_ssd = df_filt[df_filt["StopType"].isin(StopType.SSD.value)]
270
+ df_ssd = df_filt[np.isin(df_filt["StopType"], [StopType.SSD.value])]
271
271
  _, _, ssd_number = get_ssd_back_position_and_tof_offset(de_dataset)
272
272
  energy = get_energy_ssd(de_dataset, ssd_number)
273
273
  test_energy = df_ssd["Energy"].astype("float")
@@ -278,7 +278,7 @@ def test_get_energy_ssd(de_dataset, yf_fixture):
278
278
  def test_get_energy_pulse_height(de_dataset, yf_fixture):
279
279
  """Tests get_energy_ssd function."""
280
280
  df_filt, _, _ = yf_fixture
281
- df_ph = df_filt[df_filt["StopType"].isin(StopType.PH.value)]
281
+ df_ph = df_filt[np.isin(df_filt["StopType"], [StopType.PH.value])]
282
282
  ph_indices = np.nonzero(
283
283
  np.isin(de_dataset["STOP_TYPE"], [StopType.Top.value, StopType.Bottom.value])
284
284
  )[0]
@@ -297,46 +297,66 @@ def test_get_energy_pulse_height(de_dataset, yf_fixture):
297
297
  def test_get_ctof(yf_fixture):
298
298
  """Tests get_ctof function."""
299
299
  df_filt, _, _ = yf_fixture
300
+ df_filt = df_filt[df_filt["eTOF"].astype("str") != "FILL"]
301
+ df_filt = df_filt[df_filt["cTOF"].astype("float") > 0]
300
302
 
301
- df_ph_ssd = df_filt[
302
- df_filt["StopType"].isin([StopType.SSD.value, StopType.PH.value])
303
- ]
303
+ df_ph = df_filt[np.isin(df_filt["StopType"], [StopType.PH.value])]
304
+ df_ssd = df_filt[np.isin(df_filt["StopType"], [StopType.SSD.value])]
304
305
 
305
- ctof = get_ctof(
306
- df_ph_ssd["TOF"].astype("float").to_numpy(),
307
- df_ph_ssd["r"].astype("float").to_numpy(),
306
+ ph_ctof, ph_magnitude_v = get_ctof(
307
+ df_ph["TOF"].astype("float").to_numpy(),
308
+ df_ph["r"].astype("float").to_numpy(),
309
+ "PH",
308
310
  )
309
311
 
312
+ ssd_ctof, ssd_magnitude_v = get_ctof(
313
+ df_ssd["TOF"].astype("float").to_numpy(),
314
+ df_ssd["r"].astype("float").to_numpy(),
315
+ "SSD",
316
+ )
317
+
318
+ np.testing.assert_allclose(
319
+ ph_ctof, df_ph["cTOF"].astype("float"), atol=1e-05, rtol=0
320
+ )
321
+ np.testing.assert_allclose(
322
+ ssd_ctof, df_ssd["cTOF"].astype("float"), atol=1e-05, rtol=0
323
+ )
310
324
  np.testing.assert_allclose(
311
- ctof, df_ph_ssd["cTOF"].astype("float"), atol=1e-05, rtol=0
325
+ ph_magnitude_v, df_ph["vmag"].astype("float"), atol=1e-01, rtol=0
326
+ )
327
+ np.testing.assert_allclose(
328
+ ssd_magnitude_v, df_ssd["vmag"].astype("float"), atol=1e-01, rtol=0
312
329
  )
313
330
 
314
331
 
315
- def test_determine_species_ph(yf_fixture):
316
- """Tests determine_species_ph function."""
332
+ def test_determine_species(yf_fixture):
333
+ """Tests determine_species function."""
317
334
  df_filt, _, _ = yf_fixture
318
- df_ph = df_filt[df_filt["StopType"].isin(StopType.PH.value)]
335
+ df_ph = df_filt[np.isin(df_filt["StopType"], [StopType.PH.value])]
336
+ df_ssd = df_filt[np.isin(df_filt["StopType"], [StopType.SSD.value])]
319
337
 
320
- bin = determine_species_pulse_height(
321
- df_ph["Energy"].astype("float").to_numpy(),
338
+ species_bin_ph = determine_species(
322
339
  df_ph["TOF"].astype("float").to_numpy(),
323
340
  df_ph["r"].astype("float").to_numpy(),
341
+ "PH",
324
342
  )
325
-
326
- # TODO: add in bin values.
327
- np.testing.assert_allclose(bin, np.zeros(len(bin)), atol=1e-05, rtol=0)
328
-
329
-
330
- def test_determine_species_ssd(yf_fixture):
331
- """Tests determine_species_ssd function."""
332
- df_filt, _, _ = yf_fixture
333
- df_ssd = df_filt[df_filt["StopType"].isin(StopType.SSD.value)]
334
-
335
- bin = determine_species_ssd(
336
- df_ssd["Energy"].astype("float").to_numpy(),
343
+ species_bin_ssd = determine_species(
337
344
  df_ssd["TOF"].astype("float").to_numpy(),
338
345
  df_ssd["r"].astype("float").to_numpy(),
346
+ "SSD",
339
347
  )
340
348
 
341
- # TODO: add in bin values.
342
- np.testing.assert_allclose(bin, np.zeros(len(bin)), atol=1e-05, rtol=0)
349
+ h_indices_ph = np.where(species_bin_ph == "H")[0]
350
+ ctof_indices_ph = np.where(
351
+ (df_ph["cTOF"].astype("float") > UltraConstants.CTOF_SPECIES_MIN)
352
+ & (df_ph["cTOF"].astype("float") < UltraConstants.CTOF_SPECIES_MAX)
353
+ )[0]
354
+
355
+ h_indices_ssd = np.where(species_bin_ssd == "H")[0]
356
+ ctof_indices_ssd = np.where(
357
+ (df_ssd["cTOF"].astype("float") > UltraConstants.CTOF_SPECIES_MIN)
358
+ & (df_ssd["cTOF"].astype("float") < UltraConstants.CTOF_SPECIES_MAX)
359
+ )[0]
360
+
361
+ np.testing.assert_array_equal(h_indices_ph, ctof_indices_ph)
362
+ np.testing.assert_array_equal(h_indices_ssd, ctof_indices_ssd)
@@ -3,19 +3,17 @@
3
3
  import cdflib
4
4
  import numpy as np
5
5
  import pytest
6
- import spiceypy as spice
7
6
  from cdflib import CDF
8
7
 
9
8
  from imap_processing import imap_module_directory
10
9
  from imap_processing.ultra.l1c.ultra_l1c_pset_bins import (
11
10
  build_energy_bins,
12
- build_spatial_bins,
13
- cartesian_to_spherical,
14
11
  get_helio_exposure_times,
15
12
  get_histogram,
16
13
  get_pointing_frame_exposure_times,
17
14
  get_pointing_frame_sensitivity,
18
15
  )
16
+ from imap_processing.ultra.utils.spatial_utils import build_spatial_bins
19
17
 
20
18
  BASE_PATH = imap_module_directory / "ultra" / "lookup_tables"
21
19
 
@@ -32,33 +30,15 @@ def test_data():
32
30
  return v, energy
33
31
 
34
32
 
35
- @pytest.fixture()
36
- def kernels(spice_test_data_path):
37
- """List SPICE kernels."""
38
- required_kernels = [
39
- "imap_science_0001.tf",
40
- "imap_sclk_0000.tsc",
41
- "sim_1yr_imap_attitude.bc",
42
- "imap_wkcp.tf",
43
- "naif0012.tls",
44
- "sim_1yr_imap_pointing_frame.bc",
45
- "de440s.bsp",
46
- "imap_spk_demo.bsp",
47
- ]
48
- kernels = [str(spice_test_data_path / kernel) for kernel in required_kernels]
49
-
50
- return kernels
51
-
52
-
53
33
  def test_build_energy_bins():
54
34
  """Tests build_energy_bins function."""
55
35
  energy_bin_edges, energy_midpoints = build_energy_bins()
56
- energy_bin_start = energy_bin_edges[:-1]
57
- energy_bin_end = energy_bin_edges[1:]
36
+ energy_bin_start = [interval[0] for interval in energy_bin_edges]
37
+ energy_bin_end = [interval[1] for interval in energy_bin_edges]
58
38
 
59
39
  assert energy_bin_start[0] == 0
60
40
  assert energy_bin_start[1] == 3.385
61
- assert len(energy_bin_edges) == 25
41
+ assert len(energy_bin_edges) == 24
62
42
  assert energy_midpoints[0] == (energy_bin_start[0] + energy_bin_end[0]) / 2
63
43
 
64
44
  # Comparison to expected values.
@@ -67,44 +47,6 @@ def test_build_energy_bins():
67
47
  np.testing.assert_allclose(energy_bin_end[-1], 341.989, atol=1e-4)
68
48
 
69
49
 
70
- def test_build_spatial_bins():
71
- """Tests build_spatial_bins function."""
72
- az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = (
73
- build_spatial_bins()
74
- )
75
-
76
- assert az_bin_edges[0] == 0
77
- assert az_bin_edges[-1] == 360
78
- assert len(az_bin_edges) == 721
79
-
80
- assert el_bin_edges[0] == -90
81
- assert el_bin_edges[-1] == 90
82
- assert len(el_bin_edges) == 361
83
-
84
- assert len(az_bin_midpoints) == 720
85
- np.testing.assert_allclose(az_bin_midpoints[0], 0.25, atol=1e-4)
86
- np.testing.assert_allclose(az_bin_midpoints[-1], 359.75, atol=1e-4)
87
-
88
- assert len(el_bin_midpoints) == 360
89
- np.testing.assert_allclose(el_bin_midpoints[0], -89.75, atol=1e-4)
90
- np.testing.assert_allclose(el_bin_midpoints[-1], 89.75, atol=1e-4)
91
-
92
-
93
- def test_cartesian_to_spherical(test_data):
94
- """Tests cartesian_to_spherical function."""
95
- v, _ = test_data
96
-
97
- az_sc, el_sc, r = cartesian_to_spherical(v)
98
-
99
- # MATLAB code outputs:
100
- np.testing.assert_allclose(
101
- np.unique(np.radians(az_sc)), np.array([1.31300, 2.34891]), atol=1e-05, rtol=0
102
- )
103
- np.testing.assert_allclose(
104
- np.unique(np.radians(el_sc)), np.array([-0.88901, -0.70136]), atol=1e-05, rtol=0
105
- )
106
-
107
-
108
50
  def test_get_histogram(test_data):
109
51
  """Tests get_histogram function."""
110
52
  v, energy = test_data
@@ -119,7 +61,7 @@ def test_get_histogram(test_data):
119
61
  assert hist.shape == (
120
62
  len(az_bin_edges) - 1,
121
63
  len(el_bin_edges) - 1,
122
- len(energy_bin_edges) - 1,
64
+ len(energy_bin_edges),
123
65
  )
124
66
 
125
67
 
@@ -143,10 +85,10 @@ def test_get_pointing_frame_exposure_times():
143
85
 
144
86
 
145
87
  @pytest.mark.external_kernel()
146
- def test_et_helio_exposure_times(kernels):
88
+ @pytest.mark.use_test_metakernel("imap_ena_sim_metakernel.template")
89
+ def test_get_helio_exposure_times():
147
90
  """Tests get_helio_exposure_times function."""
148
91
 
149
- spice.furnsh(kernels)
150
92
  constant_exposure = BASE_PATH / "dps_grid45_compressed.cdf"
151
93
  start_time = 829485054.185627
152
94
  end_time = 829567884.185627
@@ -185,9 +127,9 @@ def test_et_helio_exposure_times(kernels):
185
127
  transposed_exposure = np.transpose(exposure_data, (2, 1, 0))
186
128
  exposures.append(transposed_exposure)
187
129
 
188
- np.array_equal(exposures[0], exposure_3d[:, :, 0])
189
- np.array_equal(exposures[1], exposure_3d[:, :, 1])
190
- np.array_equal(exposures[2], exposure_3d[:, :, 2])
130
+ assert np.array_equal(np.squeeze(exposures[0]), exposure_3d[:, :, 0])
131
+ assert np.array_equal(np.squeeze(exposures[1]), exposure_3d[:, :, 11])
132
+ assert np.array_equal(np.squeeze(exposures[2]), exposure_3d[:, :, 23])
191
133
 
192
134
 
193
135
  def test_get_pointing_frame_sensitivity():
@@ -52,13 +52,22 @@ class UltraConstants:
52
52
  DF: float = 3.39 # Distance from slit to foil [mm]
53
53
 
54
54
  # Derived constants
55
- DMIN: float = (
55
+ DMIN_PH_CTOF: float = (
56
56
  Z_DS - (2**0.5) * DF
57
57
  ) # Minimum distance between front and back detectors [mm]
58
- DMIN_SSD_CTOF: float = (DMIN**2) / (
59
- DMIN - Z_DSTOP
58
+ DMIN_SSD_CTOF: float = (DMIN_PH_CTOF**2) / (
59
+ DMIN_PH_CTOF - Z_DSTOP
60
60
  ) # SSD-specific correction to DMIN [mm]
61
61
 
62
62
  # Conversion factors
63
63
  KEV_J = 1.602180000000000e-16 # 1.6021766339999e-16 # keV to joules
64
64
  MASS_H = 1.6735575e-27 # Mass of a hydrogen atom in kilograms.
65
+
66
+ # Energy bin constants
67
+ ALPHA = 0.2 # deltaE/E
68
+ ENERGY_START = 3.385 # energy start for the Ultra grids
69
+ N_BINS = 23 # number of energy bins
70
+
71
+ # Constants for species determination based on ctof range.
72
+ CTOF_SPECIES_MIN = 50
73
+ CTOF_SPECIES_MAX = 200