|
11 | 11 | from imap_processing.ena_maps.ena_maps import match_coords_to_indices |
12 | 12 | from imap_processing.ena_maps.utils.naming import MapDescriptor |
13 | 13 | from imap_processing.lo.constants import LoConstants |
| 14 | +from imap_processing.lo.l2.lo_l2 import ( |
| 15 | + ANCILLARY_DATA_DIR as PACKAGE_ANCILLARY_DIR, |
| 16 | +) |
14 | 17 | from imap_processing.lo.l2.lo_l2 import ( |
15 | 18 | LoSpinAnglePointingSet, |
16 | 19 | _complete_pointings, |
17 | 20 | _dps_spin_angles, |
18 | 21 | _spin_phase_mask, |
| 22 | + finalize_dataset, |
19 | 23 | lo_l2, |
| 24 | + load_bootstrap_correction_data, |
| 25 | + load_sputter_correction_data, |
20 | 26 | ) |
21 | 27 | from imap_processing.spice.time import met_to_ttj2000ns |
22 | 28 |
|
@@ -189,6 +195,17 @@ def anc_dependencies(): |
189 | 195 | return [ANCILLARY_DIR / "imap_lo_esa-eta-fit-factors_20240101_v001.csv"] |
190 | 196 |
|
191 | 197 |
|
| 198 | +@pytest.fixture |
| 199 | +def shipped_ancillaries(): |
| 200 | + """Read the calibration ancillaries shipped with the package. |
| 201 | +
|
| 202 | + Undoes the autouse ``use_test_geometric_factors`` patch (a more common use-case) |
| 203 | + for tests that need the shipped ancillaries. |
| 204 | + """ |
| 205 | + with patch("imap_processing.lo.l2.lo_l2.ANCILLARY_DATA_DIR", PACKAGE_ANCILLARY_DIR): |
| 206 | + yield |
| 207 | + |
| 208 | + |
192 | 209 | @pytest.fixture |
193 | 210 | def full_map(one_pointing, anc_dependencies): |
194 | 211 | """The full-spin map of one pointing, with the sky pointing mocked.""" |
@@ -505,6 +522,83 @@ def test_missing_product_raises(self, one_pointing): |
505 | 522 | _complete_pointings(dependencies) |
506 | 523 |
|
507 | 524 |
|
| 525 | +class TestCorrectionFactors: |
| 526 | + """Reading the sputter and bootstrap correction ancillaries.""" |
| 527 | + |
| 528 | + def test_sputter_factors_are_selected_by_species_pair( |
| 529 | + self, shipped_ancillaries, tmp_path |
| 530 | + ): |
| 531 | + """Only the rows of the requested pair come back, in ESA step order.""" |
| 532 | + factors = load_sputter_correction_data("o", "h") |
| 533 | + |
| 534 | + assert list(factors.columns) == [ |
| 535 | + "source_species", |
| 536 | + "target_species", |
| 537 | + "esa_step", |
| 538 | + "sputter_factor", |
| 539 | + "sputter_factor_uncertainty", |
| 540 | + ] |
| 541 | + assert not factors.empty |
| 542 | + assert (factors["source_species"] == "o").all() |
| 543 | + assert (factors["target_species"] == "h").all() |
| 544 | + |
| 545 | + with patch("imap_processing.lo.l2.lo_l2.ANCILLARY_DATA_DIR", tmp_path): |
| 546 | + with pytest.raises(ValueError, match="No sputter correction files"): |
| 547 | + load_sputter_correction_data("o", "h") |
| 548 | + |
| 549 | + def test_bootstrap_factors_relate_lower_steps_to_higher( |
| 550 | + self, shipped_ancillaries, tmp_path |
| 551 | + ): |
| 552 | + """Each factor carries a step pair, the source below the target.""" |
| 553 | + factors = load_bootstrap_correction_data() |
| 554 | + |
| 555 | + assert list(factors.columns) == [ |
| 556 | + "esa_step_i", |
| 557 | + "esa_step_k", |
| 558 | + "bootstrap_factor", |
| 559 | + ] |
| 560 | + assert not factors.empty |
| 561 | + assert (factors["esa_step_i"] < factors["esa_step_k"]).all() |
| 562 | + # Steps are 1-based, and step 8 is the virtual E8 channel. |
| 563 | + assert factors["esa_step_i"].min() >= 1 |
| 564 | + assert factors["esa_step_k"].max() <= N_ESA + 1 |
| 565 | + assert (factors["bootstrap_factor"] > 0).all() |
| 566 | + |
| 567 | + with patch("imap_processing.lo.l2.lo_l2.ANCILLARY_DATA_DIR", tmp_path): |
| 568 | + with pytest.raises(ValueError, match="No bootstrap correction factor"): |
| 569 | + load_bootstrap_correction_data() |
| 570 | + |
| 571 | + |
| 572 | +class TestFinalizeDataset: |
| 573 | + """Attaching the CDF attributes to a finished map.""" |
| 574 | + |
| 575 | + def test_attributes_are_filled_in_from_the_descriptor(self): |
| 576 | + """The map is labelled with its descriptor and its variables described.""" |
| 577 | + dataset = xr.Dataset( |
| 578 | + { |
| 579 | + "ena_intensity": ( |
| 580 | + ["epoch", "energy"], |
| 581 | + np.zeros((1, N_ESA)), |
| 582 | + ), |
| 583 | + "not_a_map_variable": ("epoch", np.zeros(1)), |
| 584 | + }, |
| 585 | + coords={"epoch": [0], "energy": ESA_ENERGIES[0]}, |
| 586 | + ) |
| 587 | + |
| 588 | + finalized = finalize_dataset(dataset, FULL_DESCRIPTOR) |
| 589 | + |
| 590 | + assert finalized.attrs["Logical_source"] == f"imap_lo_l2_{FULL_DESCRIPTOR}" |
| 591 | + assert FULL_DESCRIPTOR in finalized.attrs["Data_type"] |
| 592 | + |
| 593 | + # A known map variable picks up its attributes from the enamaps config. |
| 594 | + assert finalized["ena_intensity"].attrs["FIELDNAM"] == "Intensity" |
| 595 | + assert finalized["ena_intensity"].attrs["UNITS"] == "cm -2 s -1 sr -1 keV -1" |
| 596 | + |
| 597 | + # A variable the config says nothing about is left without attributes, |
| 598 | + # rather than failing the map. |
| 599 | + assert finalized["not_a_map_variable"].attrs == {} |
| 600 | + |
| 601 | + |
508 | 602 | class TestUnsupported: |
509 | 603 | """Map flavours the Lo pipeline does not make.""" |
510 | 604 |
|
|
0 commit comments