diff --git a/codecov.yml b/codecov.yml index 2050646d5..e0fe79e91 100644 --- a/codecov.yml +++ b/codecov.yml @@ -15,3 +15,5 @@ coverage: ignore: - "**/conftest.py" - "**/test_spacecraft_pset.py" + - "**/test_helio_pset.py" + - "**/*make_helio_index_maps.py" diff --git a/imap_processing/tests/ultra/unit/conftest.py b/imap_processing/tests/ultra/unit/conftest.py index cc60ca571..d5ad9131f 100644 --- a/imap_processing/tests/ultra/unit/conftest.py +++ b/imap_processing/tests/ultra/unit/conftest.py @@ -599,23 +599,23 @@ def random_spin_data(): @pytest.fixture def mock_spacecraft_pointing_lookups(): """Test lookup tables fixture.""" + np.random.seed(42) pix = hp.nside2npix(128) # reduced for testing steps = 2 # Reduced for testing - for_indices_by_spin_phase = np.random.choice( - [True, False], size=(pix, steps), p=[0.1, 0.9] + for_indices_by_spin_phase = xr.DataArray( + np.random.choice([True, False], size=(steps, pix), p=[0.1, 0.9]), + dims=("spin_phase_step", "pixel"), ) - theta_vals = np.random.uniform(-60, 60, size=(pix, steps)) - phi_vals = np.random.uniform(-60, 60, size=(pix, steps)) + theta_vals = np.random.uniform(-60, 60, size=(steps, pix)) + phi_vals = np.random.uniform(-60, 60, size=(steps, pix)) # Ra and Dec pixel shape needs to be the default healpix pixel count - ra_and_dec = np.random.uniform(-80, 80, size=(pix, 2)) - boundary_scale_factors = np.ones((pix, steps)) + ra_and_dec = np.random.uniform(-80, 80, size=(steps, pix)) + boundary_scale_factors = np.ones((steps, pix)) + with ( mock.patch( "imap_processing.ultra.l1c.spacecraft_pset.get_spacecraft_pointing_lookup_tables" ) as mock_lookup, - mock.patch( - "imap_processing.ultra.l1c.helio_pset.get_spacecraft_pointing_lookup_tables" - ) as mock_lookup_helio, ): mock_lookup.return_value = ( for_indices_by_spin_phase, @@ -624,11 +624,60 @@ def mock_spacecraft_pointing_lookups(): ra_and_dec, boundary_scale_factors, ) - mock_lookup_helio.return_value = ( - for_indices_by_spin_phase, - theta_vals, - phi_vals, - ra_and_dec, - boundary_scale_factors, + yield mock_lookup + + +@pytest.fixture +def mock_helio_pointing_lookups(): + """Test lookup tables fixture returning an xarray Dataset.""" + np.random.seed(42) + pix = hp.nside2npix(32) # reduced for testing + steps = 2 # Reduced for testing + energy = 46 + + # Ra and Dec pixel shape needs to be the default healpix pixel count + ra_and_dec = np.random.uniform(-80, 80, size=(steps, pix)) + + index_map = np.random.choice([True, False], size=(steps, energy, pix), p=[0.1, 0.9]) + index_map = index_map.astype(bool) + theta_map = np.random.uniform(-60, 60, size=(steps, energy, pix)) + phi_map = np.random.uniform(-60, 60, size=(steps, energy, pix)) + bsf_map = np.ones((steps, energy, pix)) + with ( + mock.patch( + "imap_processing.ultra.l1c.helio_pset.make_helio_index_maps_with_nominal_kernels" + ) as mock_lookup, + ): + ds = xr.Dataset( + data_vars={ + "index": ( + ["spin_phase_step", "energy", "pixel"], + index_map, + {"long_name": "Pixel in FOV flag"}, + ), + "theta": ( + ["spin_phase_step", "energy", "pixel"], + theta_map, + {"long_name": "Instrument theta angle", "units": "degrees"}, + ), + "phi": ( + ["spin_phase_step", "energy", "pixel"], + phi_map, + {"long_name": "Instrument phi angle", "units": "degrees"}, + ), + "bsf": ( + ["spin_phase_step", "energy", "pixel"], + bsf_map, + {"long_name": "Boundary scale factor", "units": "fractional"}, + ), + "ra_and_dec": (["spin_phase_step", "pixel"], ra_and_dec), + }, + coords={ + "spin_phase_step": np.arange(steps), + "energy": np.arange(energy), + "pixel": np.arange(pix), + }, ) + mock_lookup.return_value = ds + yield mock_lookup diff --git a/imap_processing/tests/ultra/unit/test_helio_pset.py b/imap_processing/tests/ultra/unit/test_helio_pset.py new file mode 100644 index 000000000..7fecdb119 --- /dev/null +++ b/imap_processing/tests/ultra/unit/test_helio_pset.py @@ -0,0 +1,122 @@ +from unittest import mock + +import numpy as np +import pandas as pd +import pytest +import xarray as xr + +from imap_processing import imap_module_directory +from imap_processing.cdf.utils import load_cdf +from imap_processing.tests.conftest import _download_external_data +from imap_processing.ultra.constants import UltraConstants +from imap_processing.ultra.l1c.helio_pset import calculate_helio_pset + +TEST_PATH = imap_module_directory / "tests" / "ultra" / "data" / "l1" + + +@pytest.mark.skip(reason="Long running test for validation purposes.") +def test_validate_exposure_time_and_sensitivities( + ancillary_files, deadtime_datasets, imap_ena_sim_metakernel +): + """Validates exposure time and sensitivities for ebin 0.""" + sens_filename = "SENS-IMAP_ULTRA_90-IMAP_DPS-HELIO-nside32-ebin0.csv" + exposure_filename = "Exposures-IMAP_ULTRA_90-IMAP_DPS-HELIO-nside32-ebin0.csv" + de_filename = "imap_ultra_l1b_90sensor-de_20000101-repoint00000_v000.cdf" + test_data = [ + (sens_filename, "ultra/data/l1/"), + (exposure_filename, "ultra/data/l1/"), + (de_filename, "ultra/data/l1/"), + ] + _download_external_data(test_data) + l1b_de = TEST_PATH / de_filename + l1b_de = load_cdf(l1b_de) + sensitivities_ebin_0 = pd.read_csv(TEST_PATH / sens_filename) + exposure_factor_ebin_0 = pd.read_csv(TEST_PATH / exposure_filename) + + test_deadtimes = ( + pd.read_csv(TEST_PATH / "test_p0_ebin0_deadtimes.csv", header=None) + .to_numpy() + .squeeze() + ) + npix = 12288 # nside 32 + # Create a minimal dataset to pass to the function + dataset = xr.Dataset( + { + "spin_number": (["epoch"], np.array([1, 2, 3])), + } + ) + dataset.attrs["Repointing"] = "repoint00000" + + pointing_range_met = (472374890.0, 582378000.0) + # Create mock spin data that has 5525 nominal spins + # Create DataFrame + nspins = 5522 + nominal_spin_seconds = 15.0 + spin_data = pd.DataFrame( + { + "spin_start_met": np.linspace( + pointing_range_met[0], pointing_range_met[1], nspins + ), + "spin_period_sec": np.full(nspins, nominal_spin_seconds), + "spin_phase_valid": np.ones(nspins), + "spin_period_valid": np.ones(nspins), + } + ) + with ( + # Mock the pointing times + mock.patch( + "imap_processing.ultra.l1c.helio_pset.get_pointing_times_from_id", + return_value=pointing_range_met, + ), + mock.patch( + "imap_processing.ultra.l1c.ultra_l1c_pset_bins.ttj2000ns_to_met", + side_effect=lambda x: x, + ), + # Mock deadtimes to be all ones + mock.patch( + "imap_processing.ultra.l1c.ultra_l1c_pset_bins." + "get_deadtime_ratios_by_spin_phase", + return_value=xr.DataArray(test_deadtimes, dims="spin_phase_step"), + ), + # Mock spin data to match nominal spins in a pointing period + mock.patch( + "imap_processing.ultra.l1c.ultra_l1c_pset_bins.get_spin_data", + return_value=spin_data, + ), + # Mock background rates to be constant 0.1 + mock.patch( + "imap_processing.ultra.l1c.helio_pset.get_spacecraft_background_rates", + return_value=np.ones((46, npix)), + ), + # Mock culling mask (no culling) + mock.patch("imap_processing.ultra.l1c.helio_pset.compute_culling_mask"), + ): + pset = calculate_helio_pset( + l1b_de, + dataset, + deadtime_datasets["rates"], + deadtime_datasets["params"], + "imap_ultra_l1c_90sensor-heliopset", + ancillary_files, + 90, + UltraConstants.TOFXPH_SPECIES_GROUPS["proton"], + ) + + # Validate exposure times for ebin 0 + exposure_times = pset["exposure_factor"][0, 0, :].values + expected_exposure_times = exposure_factor_ebin_0["P0"].to_numpy() + np.testing.assert_allclose( + exposure_times, + expected_exposure_times, + atol=95, # TODO This is due to the helio index map differences + err_msg="Exposure times do not match expected values for ebin 0.", + ) + # Validate sensitivities for ebin 0 + sensitivity = pset["sensitivity"][0, :].values + expected_sensitivity = sensitivities_ebin_0["Sensitivity (cm2)"].to_numpy() + np.testing.assert_allclose( + sensitivity, + expected_sensitivity, + atol=0.0006, # TODO This is due to the helio index map differences + err_msg="Sensitivities times do not match expected values for ebin 0.", + ) diff --git a/imap_processing/tests/ultra/unit/test_l1c_lookup_utils.py b/imap_processing/tests/ultra/unit/test_l1c_lookup_utils.py index 87565b936..ae1e1bbe4 100644 --- a/imap_processing/tests/ultra/unit/test_l1c_lookup_utils.py +++ b/imap_processing/tests/ultra/unit/test_l1c_lookup_utils.py @@ -1,8 +1,10 @@ import astropy_healpix.healpy as hp import numpy as np import pytest +import xarray as xr from imap_processing.ultra.l1c.l1c_lookup_utils import ( + calculate_fwhm_spun_scattering, get_scattering_thresholds_for_energy, get_spacecraft_pointing_lookup_tables, get_static_deadtime_ratios, @@ -25,10 +27,10 @@ def test_get_spacecraft_pointing_lookup_tables(ancillary_files): # Test shapes # There should be 498 spin phase steps. In the real test files there will be 15000 cols = 498 - assert for_indices_by_spin_phase.shape == (npix, cols) - assert theta_vals.shape == (npix, cols) - assert phi_vals.shape == (npix, cols) - assert ra_and_dec.shape == (npix, 2) + assert for_indices_by_spin_phase.shape == (cols, npix) + assert theta_vals.shape == (cols, npix) + assert phi_vals.shape == (cols, npix) + assert ra_and_dec.shape == (2, npix) # Value tests assert for_indices_by_spin_phase.dtype == bool @@ -111,3 +113,47 @@ def test_get_static_deadtime_ratios(ancillary_files): np.testing.assert_array_equal(dt_ratio.shape, (721,)) # Test the values assert np.all((dt_ratio >= 0.0) & (dt_ratio <= 1.0)) + + +def test_calculate_fwhm_spun_scattering(ancillary_files): + """Test calculate_fwhm_spun_scattering function.""" + # Make array with ones (we are only testing the shape here) + for_pixels = np.ones((50, 10)) + theta_vals = np.ones((50, 10)) * 20 # All theta values are 20 + phi_vals = np.ones((50, 5)) * 15 # All phi + with pytest.raises(ValueError, match="Shape mismatch"): + calculate_fwhm_spun_scattering( + for_pixels, theta_vals, phi_vals, ancillary_files, 45 + ) + + +@pytest.mark.external_test_data +def test_calculate_fwhm_spun_scattering_reject(ancillary_files): + """Test calculate_fwhm_spun_scattering function.""" + nside = 8 + pix = hp.nside2npix(nside) + steps = 5 # Reduced for testing + energy_dim = 46 + np.random.seed(42) + mock_theta = np.random.uniform(-60, 60, (steps, energy_dim, pix)) + mock_phi = np.random.uniform(-60, 60, (steps, energy_dim, pix)) + for_pixels = xr.DataArray( + np.zeros((steps, energy_dim, pix)).astype(bool), + dims=("spin_phase_step", "energy", "pixel"), + ) + # Simulate first 100 pixels are in the FOR for all spin phases + inside_inds = 100 + for_pixels[:, :, :inside_inds] = True + valid_spun_pixels, fwhm_theta, fwhm_phi, thresholds = ( + calculate_fwhm_spun_scattering( + for_pixels, + mock_theta, + mock_phi, + ancillary_files, + 45, + reject_scattering=True, + ) + ) + assert valid_spun_pixels.shape == (steps, energy_dim, pix) + # Check that some pixels are rejected + assert not np.array_equal(valid_spun_pixels, for_pixels) diff --git a/imap_processing/tests/ultra/unit/test_make_helio_index_maps.py b/imap_processing/tests/ultra/unit/test_make_helio_index_maps.py new file mode 100644 index 000000000..f9f91b46f --- /dev/null +++ b/imap_processing/tests/ultra/unit/test_make_helio_index_maps.py @@ -0,0 +1,185 @@ +import numpy as np +import pandas as pd +import pytest +import spiceypy as sp + +from imap_processing import imap_module_directory +from imap_processing.spice.geometry import SpiceFrame +from imap_processing.tests.conftest import _download_external_data +from imap_processing.ultra.l1c.make_helio_index_maps import make_helio_index_maps + +TEST_PATH = imap_module_directory / "tests" / "ultra" / "data" / "l1" + + +@pytest.fixture +def helio_index_kernels(furnish_kernels, _download_kernels): + kernels = [ + "imap_sclk_0000.tsc", + "naif0012.tls", + "imap_spk_demo.bsp", + "sim_1yr_imap_attitude.bc", + "imap_001.tf", + "de440s.bsp", + "imap_science_100.tf", + "sim_1yr_imap_pointing_frame.bc", + ] + with furnish_kernels(kernels) as k: + yield k + + +@pytest.mark.skip(reason="Long running test for validation purposes.") +def test_make_helio_index_maps( + helio_index_kernels, use_fake_repoint_data_for_time, spice_test_data_path +): + """Test make_helio_index_maps.""" + # Get coverage window + ck_kernel, _, _, _ = sp.kdata(1, "ck") + ck_cover = sp.ckcov( + ck_kernel, SpiceFrame.IMAP_DPS.value, True, "INTERVAL", 0, "TDB" + ) + et_start, et_end = sp.wnfetd(ck_cover, 0) + + ds = make_helio_index_maps( + nside=32, + spin_duration=15.0, + start_et=et_start, + num_steps=720, + instrument_frame=SpiceFrame.IMAP_ULTRA_90, + compute_bsf=False, + ) + index_file = "IMAP_ULTRA_90-HELIO-IMAP_DPS-nside32-steps720-ebin0-index.csv" + theta_file = "IMAP_ULTRA_90-HELIO-IMAP_DPS-nside32-steps720-ebin0-theta.csv" + phi_file = "IMAP_ULTRA_90-HELIO-IMAP_DPS-nside32-steps720-ebin0-phi.csv" + test_data = [ + (index_file, "ultra/data/l1/"), + (theta_file, "ultra/data/l1/"), + (phi_file, "ultra/data/l1/"), + ] + _download_external_data(test_data) + # Load expected data + expected_index = pd.read_csv( + TEST_PATH / index_file, + header=None, + skiprows=1, + ).to_numpy() + expected_theta = pd.read_csv( + TEST_PATH / theta_file, + header=None, + skiprows=1, + ).to_numpy() + expected_phi = pd.read_csv( + TEST_PATH / phi_file, + header=None, + skiprows=1, + ).to_numpy() + + # Skip ra and dec cols + expected_index_all_steps = expected_index[:, 2:] + expected_theta_all_steps = expected_theta[:, 2:] + expected_phi_all_steps = expected_phi[:, 2:] + + # Replace nans with zero + expected_index_all_steps = np.nan_to_num(expected_index_all_steps, nan=0) + + # Get outputs + index_all_steps = ds.index[:, 0, :].values.T + theta_all_steps = ds.theta[:, 0, :].values.T + phi_all_steps = ds.phi[:, 0, :].values.T + + # Test index mismatch percentage + mismatch_count = np.sum(index_all_steps != expected_index_all_steps) + mismatch_pct = 100 * mismatch_count / index_all_steps.size + assert mismatch_pct < 0.02 + + both_valid_mask = (expected_index_all_steps != 0) & (index_all_steps != 0) + + np.testing.assert_allclose( + theta_all_steps[both_valid_mask], + expected_theta_all_steps[both_valid_mask], + rtol=1e-4, + ) + + np.testing.assert_allclose( + phi_all_steps[both_valid_mask], + expected_phi_all_steps[both_valid_mask], + rtol=1e-4, + atol=0.05, + ) + + +@pytest.mark.skip(reason="Long running test for validation purposes.") +def test_make_helio_index_maps_45(helio_index_kernels, use_fake_repoint_data_for_time): + """Test make_helio_index_maps.""" + ck_kernel, _, _, _ = sp.kdata(1, "ck") + ck_cover = sp.ckcov( + ck_kernel, SpiceFrame.IMAP_DPS.value, True, "INTERVAL", 0, "TDB" + ) + et_start, et_end = sp.wnfetd(ck_cover, 0) + ds = make_helio_index_maps( + nside=32, + spin_duration=15.0, + start_et=et_start, + num_steps=720, + instrument_frame=SpiceFrame.IMAP_ULTRA_45, + compute_bsf=False, + ) + + index_file = "IMAP_ULTRA_45-HELIO-IMAP_DPS-nside32-steps720-ebin0-index.csv" + theta_file = "IMAP_ULTRA_45-HELIO-IMAP_DPS-nside32-steps720-ebin0-theta.csv" + phi_file = "IMAP_ULTRA_45-HELIO-IMAP_DPS-nside32-steps720-ebin0-phi.csv" + test_data = [ + (index_file, "ultra/data/l1/"), + (theta_file, "ultra/data/l1/"), + (phi_file, "ultra/data/l1/"), + ] + _download_external_data(test_data) + # Load expected data + expected_index = pd.read_csv( + TEST_PATH / index_file, + header=None, + skiprows=1, + ).to_numpy() + expected_theta = pd.read_csv( + TEST_PATH / theta_file, + header=None, + skiprows=1, + ).to_numpy() + expected_phi = pd.read_csv( + TEST_PATH / phi_file, + header=None, + skiprows=1, + ).to_numpy() + + # Skip ra and dec cols + expected_index_all_steps = expected_index[:, 2:] + expected_theta_all_steps = expected_theta[:, 2:] + expected_phi_all_steps = expected_phi[:, 2:] + + # Replace nans with zero + expected_index_all_steps = np.nan_to_num(expected_index_all_steps, nan=0) + + # Get outputs + index_all_steps = ds.index[:, 0, :].values.T + theta_all_steps = ds.theta[:, 0, :].values.T + phi_all_steps = ds.phi[:, 0, :].values.T + + # Test index mismatch percentage + mismatch_count = np.sum(index_all_steps != expected_index_all_steps) + mismatch_pct = 100 * mismatch_count / index_all_steps.size + assert mismatch_pct < 0.02 + + both_valid_mask = (expected_index_all_steps != 0) & (index_all_steps != 0) + + np.testing.assert_allclose( + theta_all_steps[both_valid_mask], + expected_theta_all_steps[both_valid_mask], + rtol=1e-4, + atol=0.05, + ) + + np.testing.assert_allclose( + phi_all_steps[both_valid_mask], + expected_phi_all_steps[both_valid_mask], + rtol=1e-4, + atol=0.05, + ) diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c.py b/imap_processing/tests/ultra/unit/test_ultra_l1c.py index 89c295920..76904fc63 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c.py @@ -1,5 +1,6 @@ from unittest import mock +import astropy_healpix.healpy as hp import numpy as np import pandas as pd import pytest @@ -217,7 +218,7 @@ def test_calculate_helio_pset_with_cdf( random_spin_data, ancillary_files, imap_ena_sim_metakernel, - mock_spacecraft_pointing_lookups, + mock_helio_pointing_lookups, deadtime_datasets, use_fake_spin_data_for_time, ): @@ -285,7 +286,8 @@ def test_calculate_helio_pset_with_cdf( "imap_ultra_l1a_45sensor-rates": deadtime_datasets["rates"], "imap_ultra_l1a_45sensor-params": deadtime_datasets["params"], } - mock_eff = np.ones((46, 196608)) + n_pix = hp.nside2npix(32) + mock_eff = np.ones((46, n_pix)) mock_gf = mock_eff * 2 with ( mock.patch( @@ -305,10 +307,7 @@ def test_calculate_helio_pset_with_cdf( output_datasets = ultra_l1c(data_dict, ancillary_files, "45sensor-heliopset") output_datasets[0].attrs["Data_version"] = "999" output_datasets[0].attrs["Repointing"] = f"repoint{pointing + 1:05d}" - # Assert that the cg corrected efficiencies and geometric functions - # are not equal to the mocked ones - assert not np.array_equal(output_datasets[0]["efficiency"], mock_eff) - assert not np.array_equal(output_datasets[0]["geometric_function"], mock_gf) + # Although the arrays are different, their sums should be equal. The helio adjusted # ones are just rebinned. np.testing.assert_array_equal( diff --git a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py index 37e1995af..5f38b02ab 100644 --- a/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +++ b/imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py @@ -19,8 +19,8 @@ calculate_exposure_time, get_deadtime_ratios, get_deadtime_ratios_by_spin_phase, + get_efficiencies_and_geometric_function, get_energy_delta_minus_plus, - get_helio_adjusted_data, get_sectored_rates, get_spacecraft_background_rates, get_spacecraft_count_rate_uncertainty, @@ -32,6 +32,25 @@ TEST_PATH = imap_module_directory / "tests" / "ultra" / "data" / "l1" +@pytest.fixture +def spun_index_data(ancillary_files): + """Spun index test data fixture.""" + nside = 8 + pix = hp.nside2npix(nside) + steps = 500 # Reduced for testing + np.random.seed(42) + mock_theta = np.random.uniform(-60, 60, (steps, pix)) + mock_phi = np.random.uniform(-60, 60, (steps, pix)) + spin_phase_steps = xr.DataArray( + np.zeros((steps, pix)).astype(bool), dims=("spin_phase_step", "pixel") + ) + # Simulate first 100 pixels are in the FOR for all spin phases + inside_inds = 100 + spin_phase_steps[:, :inside_inds] = True + + return mock_theta, mock_phi, spin_phase_steps, inside_inds, pix, steps + + @pytest.fixture def test_data(): """Test data fixture.""" @@ -265,20 +284,12 @@ def test_get_deadtime_interpolator_no_sectored_rates(ancillary_files): @pytest.mark.external_kernel -def test_apply_deadtime_correction(imap_ena_sim_metakernel, ancillary_files): +def test_apply_deadtime_correction( + imap_ena_sim_metakernel, ancillary_files, spun_index_data +): """Tests apply_deadtime_correction function.""" - nside = 8 - pix = hp.nside2npix(nside) - steps = 500 # Reduced for testing - np.random.seed(42) - mock_theta = np.random.uniform(-60, 60, (pix, steps)) - mock_phi = np.random.uniform(-60, 60, (pix, steps)) - spin_phase_steps = np.zeros((pix, steps)).astype(bool) # Spin phase steps 1-15000, - # Simulate first 100 pixels are in the FOR for all spin phases - inside_inds = 100 - spin_phase_steps[:inside_inds, :] = True + mock_theta, mock_phi, spin_phase_steps, inside_inds, pix, steps = spun_index_data deadtime_ratios = xr.DataArray(np.ones(steps), dims="spin_phase_step") - valid_spun_pixels, fwhm_theta, fwhm_phi, thresholds = ( calculate_fwhm_spun_scattering( spin_phase_steps, @@ -296,8 +307,8 @@ def test_apply_deadtime_correction(imap_ena_sim_metakernel, ancillary_files): boundary_sf, apply_bsf=True, ) - # The adjusted exposure should be of shape (1,npix) - np.testing.assert_array_equal(exposure_pointing_adjusted.shape, (1, pix)) + # The adjusted exposure should be of shape (46,npix) + np.testing.assert_array_equal(exposure_pointing_adjusted.shape, (46, pix)) # Check that the pixels inside the FOR have adjusted exposure > 0. assert np.all(exposure_pointing_adjusted[:, :inside_inds] > 0) # Assert that pixels outside the FOR remain at 0. @@ -305,19 +316,13 @@ def test_apply_deadtime_correction(imap_ena_sim_metakernel, ancillary_files): @pytest.mark.external_kernel -def test_apply_deadtime_correction_energy_dep(imap_ena_sim_metakernel, ancillary_files): +def test_apply_deadtime_correction_energy_dep( + imap_ena_sim_metakernel, ancillary_files, spun_index_data +): """Tests apply_deadtime_correction function when scattering rejection is on.""" - nside = 8 - pix = hp.nside2npix(nside) - steps = 500 # Reduced for testing - np.random.seed(42) - mock_theta = np.random.uniform(-60, 60, (pix, steps)) - mock_phi = np.random.uniform(-60, 60, (pix, steps)) - spin_phase_steps = np.zeros((pix, steps)).astype(bool) # Spin phase steps 1-15000, - # Simulate first 100 pixels are in the FOR for all spin phases - inside_inds = 100 - spin_phase_steps[:inside_inds, :] = True + mock_theta, mock_phi, spin_phase_steps, inside_inds, pix, steps = spun_index_data deadtime_ratios = xr.DataArray(np.ones(steps), dims="spin_phase_step") + boundary_sf = xr.DataArray(np.ones((steps, pix)), dims=("spin_phase_step", "pixel")) valid_spun_pixels, fwhm_theta, fwhm_phi, thresholds = ( calculate_fwhm_spun_scattering( @@ -329,14 +334,14 @@ def test_apply_deadtime_correction_energy_dep(imap_ena_sim_metakernel, ancillary reject_scattering=True, ) ) - boundary_sf = xr.DataArray(np.ones((pix, steps)), dims=("pixel", "spin_phase_step")) + exposure_pointing_adjusted = calculate_exposure_time( deadtime_ratios, valid_spun_pixels, boundary_sf, apply_bsf=True, ) - # The adjusted exposure should be of shape (1,npix) + # The adjusted exposure should be of shape (46,npix) np.testing.assert_array_equal(exposure_pointing_adjusted.shape, (46, pix)) # Check that the pixels inside the FOR have adjusted exposure > 0. # Subset the energy dimension to check values in the last energy bin. These @@ -348,6 +353,56 @@ def test_apply_deadtime_correction_energy_dep(imap_ena_sim_metakernel, ancillary assert np.all(exposure_pointing_adjusted[:, inside_inds:] == 0) +@pytest.mark.external_kernel +def test_get_eff_and_gf(imap_ena_sim_metakernel, ancillary_files, spun_index_data): + """Tests apply_deadtime_correction function when scattering rejection is on.""" + nside = 8 + pix = hp.nside2npix(nside) + steps = 5 # Reduced for testing + energy_dim = 46 + np.random.seed(42) + mock_theta = np.random.uniform(-60, 60, (steps, energy_dim, pix)) + mock_phi = np.random.uniform(-60, 60, (steps, energy_dim, pix)) + spin_phase_steps = xr.DataArray( + np.zeros((steps, energy_dim, pix)).astype(bool), + dims=("spin_phase_step", "energy", "pixel"), + ) + # Simulate first 100 pixels are in the FOR for all spin phases + inside_inds = 100 + spin_phase_steps[:, :, :inside_inds] = True + valid_spun_pixels, fwhm_theta, fwhm_phi, thresholds = ( + calculate_fwhm_spun_scattering( + spin_phase_steps, + mock_theta, + mock_phi, + ancillary_files, + 45, + reject_scattering=False, + ) + ) + boundary_sf = xr.DataArray( + np.ones((steps, energy_dim, pix)), dims=("spin_phase_step", "energy", "pixel") + ) + eff, gf = get_efficiencies_and_geometric_function( + valid_spun_pixels, + boundary_sf, + mock_theta, + mock_phi, + npix=pix, + ancillary_files=ancillary_files, + apply_bsf=False, + ) + # The efficiencies should be of shape (energy_dim,npix) + np.testing.assert_array_equal(eff.shape, (energy_dim, pix)) + np.testing.assert_array_equal(gf.shape, (energy_dim, pix)) + # Check that the pixels inside the FOR have efficiencies and geometric factors > 0. + assert np.all(eff[:, :inside_inds] > 0) + assert np.all(gf[:, :inside_inds] > 0) + # Assert that pixels outside the FOR remain at 0. + assert np.all(eff[:, inside_inds:] == 0) + assert np.all(gf[:, inside_inds:] == 0) + + @pytest.mark.external_test_data def test_get_spacecraft_exposure_times( deadtime_datasets, @@ -365,10 +420,12 @@ def test_get_spacecraft_exposure_times( params = deadtime_datasets["params"] pix = 786 - mock_theta = np.random.uniform(-60, 60, (pix, steps)) - mock_phi = np.random.uniform(-60, 60, (pix, steps)) - spin_phase_steps = np.random.randint(0, 2, (pix, steps)).astype( - bool + mock_theta = np.random.uniform(-60, 60, (steps, pix)) + mock_phi = np.random.uniform(-60, 60, (steps, pix)) + np.random.seed(42) + spin_phase_steps = xr.DataArray( + np.random.randint(0, 2, (steps, pix)).astype(bool), + dims=("spin_phase_step", "pixel"), ) # Spin phase steps, random 0 or 1 pixels_below_threshold, fwhm_theta, fwhm_phi, thresholds = ( @@ -376,7 +433,7 @@ def test_get_spacecraft_exposure_times( spin_phase_steps, mock_theta, mock_phi, ancillary_files, 45 ) ) - boundary_sf = xr.DataArray(np.ones((pix, steps)), dims=("pixel", "spin_phase_step")) + boundary_sf = xr.DataArray(np.ones((steps, pix)), dims=("spin_phase_step", "pixel")) exposure_pointing, deadtimes = get_spacecraft_exposure_times( rates, params, @@ -393,38 +450,6 @@ def test_get_spacecraft_exposure_times( np.testing.assert_array_equal(deadtimes.shape, (steps,)) -@pytest.mark.external_kernel -def test_get_helio_exposure_time_and_sensitivity(imap_ena_sim_metakernel): - """Tests get_helio_exposure_times function.""" - - start_time = 829485054.185627 - end_time = 829567884.185627 - - mid_time = np.average([start_time, end_time]) - - _, energy_midpoints, _ = build_energy_bins() - nside = 128 - npix = hp.nside2npix(nside) - shape = (len(energy_midpoints), npix) - exposure = np.ones(shape) - eff = np.ones(shape) - gf = np.ones(shape) - mock_ra = np.random.uniform(-80, 80, (npix)) - mock_dec = np.random.uniform(-80, 80, (npix)) - - helio_exposure, helio_eff, helio_gf = get_helio_adjusted_data( - mid_time, exposure, gf, eff, mock_ra, mock_dec - ) - - for helio_array, array in zip( - [helio_exposure, helio_eff, helio_gf], [exposure, eff, gf], strict=False - ): - total_input = np.sum(array) - total_output = np.sum(total_input) - assert np.allclose(total_input, total_output, atol=1e-6) - assert helio_array.shape == shape - - def test_get_spacecraft_background_rates( rates_l1_test_path, use_fake_spin_data_for_time, ancillary_files ): diff --git a/imap_processing/ultra/constants.py b/imap_processing/ultra/constants.py index e95549a31..f8b920619 100644 --- a/imap_processing/ultra/constants.py +++ b/imap_processing/ultra/constants.py @@ -3,6 +3,10 @@ from dataclasses import dataclass from typing import ClassVar +from imap_processing import imap_module_directory + +SPICE_DATA_SIM_PATH = imap_module_directory / "ultra/l1c/sim_spice_kernels" + @dataclass(frozen=True) class UltraConstants: @@ -157,6 +161,18 @@ class UltraConstants: "non_proton": [20, 21, 22, 23, 24, 25, 26], } - # For FOV calculations + SIM_KERNELS_FOR_HELIO_INDEX_MAPS: ClassVar[list] = [ + str(SPICE_DATA_SIM_PATH / k) + for k in [ + "imap_sclk_0000.tsc", + "naif0012.tls", + "imap_spk_demo.bsp", + "sim_1yr_imap_attitude.bc", + "imap_001.tf", + "imap_science_100.tf", + "sim_1yr_imap_pointing_frame.bc", + ] + ] + FOV_THETA_OFFSET_DEG = 0.0 FOV_PHI_LIMIT_DEG = 60.0 diff --git a/imap_processing/ultra/l1c/helio_pset.py b/imap_processing/ultra/l1c/helio_pset.py index 13add46f8..7bc0bd356 100644 --- a/imap_processing/ultra/l1c/helio_pset.py +++ b/imap_processing/ultra/l1c/helio_pset.py @@ -2,28 +2,30 @@ import logging -import astropy_healpix.healpy as hp import numpy as np import xarray as xr from imap_processing.cdf.utils import parse_filename_like from imap_processing.quality_flags import ImapPSETUltraFlags +from imap_processing.spice.geometry import SpiceFrame from imap_processing.spice.repoint import get_pointing_times_from_id from imap_processing.spice.time import ( met_to_ttj2000ns, ttj2000ns_to_et, ) +from imap_processing.ultra.constants import UltraConstants from imap_processing.ultra.l1b.ultra_l1b_culling import get_de_rejection_mask from imap_processing.ultra.l1c.l1c_lookup_utils import ( build_energy_bins, calculate_fwhm_spun_scattering, - get_spacecraft_pointing_lookup_tables, +) +from imap_processing.ultra.l1c.make_helio_index_maps import ( + make_helio_index_maps_with_nominal_kernels, ) from imap_processing.ultra.l1c.ultra_l1c_culling import compute_culling_mask from imap_processing.ultra.l1c.ultra_l1c_pset_bins import ( get_efficiencies_and_geometric_function, get_energy_delta_minus_plus, - get_helio_adjusted_data, get_spacecraft_background_rates, get_spacecraft_exposure_times, get_spacecraft_histogram, @@ -74,6 +76,8 @@ def calculate_helio_pset( reject_scattering = False # Do not apply boundary scale factor corrections apply_bsf = False + nside = 32 + num_spin_steps = 720 sensor_id = int(parse_filename_like(name)["sensor"][0:2]) pset_dict: dict[str, np.ndarray] = {} # Select only the species we are interested in. @@ -98,20 +102,36 @@ def calculate_helio_pset( species_dataset["velocity_dps_helio"].values / v_mag_helio_spacecraft[:, np.newaxis] ) + # Get the start and stop times of the pointing period + repoint_id = species_dataset.attrs.get("Repointing", None) + if repoint_id is None: + raise ValueError("Repointing ID attribute is missing from the dataset.") + instrument_frame = ( + SpiceFrame.IMAP_ULTRA_90 if sensor_id == 90 else SpiceFrame.IMAP_ULTRA_45 + ) + pointing_range_met = get_pointing_times_from_id(repoint_id) + + logger.info("Generating helio pointing lookup tables.") + + helio_pointing_ds = make_helio_index_maps_with_nominal_kernels( + kernel_paths=UltraConstants.SIM_KERNELS_FOR_HELIO_INDEX_MAPS, + nside=nside, + spin_duration=15.0, + num_steps=num_spin_steps, + instrument_frame=instrument_frame, + compute_bsf=apply_bsf, + ) + boundary_scale_factors = helio_pointing_ds.bsf + theta_vals = helio_pointing_ds.theta + phi_vals = helio_pointing_ds.phi + fov_index = helio_pointing_ds.index + intervals, _, energy_bin_geometric_means = build_energy_bins() - # Get lookup table for FOR indices by spin phase step - ( - for_indices_by_spin_phase, - theta_vals, - phi_vals, - ra_and_dec, - boundary_scale_factors, - ) = get_spacecraft_pointing_lookup_tables(ancillary_files, instrument_id) logger.info("calculating spun FWHM scattering values.") pixels_below_scattering, scattering_theta, scattering_phi, scattering_thresholds = ( calculate_fwhm_spun_scattering( - for_indices_by_spin_phase, + fov_index, theta_vals, phi_vals, ancillary_files, @@ -120,7 +140,6 @@ def calculate_helio_pset( ) ) - nside = hp.npix2nside(for_indices_by_spin_phase.shape[0]) counts, latitude, longitude, n_pix = get_spacecraft_histogram( vhat_dps_helio, species_dataset["energy_heliosphere"].values, @@ -132,13 +151,6 @@ def calculate_helio_pset( ) healpix = np.arange(n_pix) - # Get the start and stop times of the pointing period - repoint_id = species_dataset.attrs.get("Repointing", None) - if repoint_id is None: - raise ValueError("Repointing ID attribute is missing from the dataset.") - - pointing_range_met = get_pointing_times_from_id(repoint_id) - logger.info("Calculating spacecraft exposure times with deadtime correction.") exposure_time, deadtime_ratios = get_spacecraft_exposure_times( rates_dataset, @@ -156,8 +168,8 @@ def calculate_helio_pset( geometric_function, efficiencies = get_efficiencies_and_geometric_function( pixels_below_scattering, boundary_scale_factors, - theta_vals, - phi_vals, + theta_vals.values, + phi_vals.values, n_pix, ancillary_files, apply_bsf, @@ -175,18 +187,6 @@ def calculate_helio_pset( nside=nside, ) - mid_time = ttj2000ns_to_et(met_to_ttj2000ns((np.sum(pointing_range_met)) / 2)) - - logger.info("Adjusting data for helio frame.") - exposure_time, efficiencies, geometric_function = get_helio_adjusted_data( - mid_time, - exposure_time, - geometric_function, - efficiencies, - ra_and_dec[:, 0], - ra_and_dec[:, 1], - nside=nside, - ) sensitivity = efficiencies * geometric_function start: float = np.min(species_dataset["event_times"].values) diff --git a/imap_processing/ultra/l1c/l1c_lookup_utils.py b/imap_processing/ultra/l1c/l1c_lookup_utils.py index cef393c6f..b20388795 100644 --- a/imap_processing/ultra/l1c/l1c_lookup_utils.py +++ b/imap_processing/ultra/l1c/l1c_lookup_utils.py @@ -68,7 +68,7 @@ def mask_below_fwhm_scattering_threshold( def calculate_fwhm_spun_scattering( - for_indices_by_spin_phase: np.ndarray, + for_indices_by_spin_phase: xr.DataArray, theta_vals: np.ndarray, phi_vals: np.ndarray, ancillary_files: dict, @@ -82,14 +82,17 @@ def calculate_fwhm_spun_scattering( Parameters ---------- - for_indices_by_spin_phase : np.ndarray + for_indices_by_spin_phase : xarray.DataArray A 2D boolean array where cols are spin phase steps are rows are HEALPix pixels. True indicates pixels that are within the Field of Regard (FOR) at that spin phase. theta_vals : np.ndarray - A 2D array of theta values for each HEALPix pixel at each spin phase step. + 2D or 3D array of theta values. Shape is either (spin_phase_step, npix) + or (spin_phase_step, energy_bins, npix) when energy-dependent scattering + rejection is used. phi_vals : np.ndarray - A 2D array of phi values for each HEALPix pixel at each spin phase step. + Array of phi values with the same shape as `theta_vals`, giving the + corresponding phi for each pixel (and energy, if present). ancillary_files : dict Dictionary containing ancillary files. instrument_id : int, @@ -114,6 +117,15 @@ def calculate_fwhm_spun_scattering( scattering_thresholds_for_energy_mean : NDArray Scattering thresholds corresponding to each energy bin. """ + # Check shapes of theta phi, and index arrays + index_shape = for_indices_by_spin_phase.shape + if theta_vals.shape != index_shape or phi_vals.shape != index_shape: + raise ValueError( + "Shape mismatch between FOR indices and theta/phi values. " + f"FOR indices shape: {index_shape}, " + f"theta shape: {theta_vals.shape}, " + f"phi shape: {phi_vals.shape}." + ) # Load scattering coefficient lookup table scattering_luts = load_scattering_lookup_tables(ancillary_files, instrument_id) # Get energy bin geometric means @@ -122,16 +134,14 @@ def calculate_fwhm_spun_scattering( scattering_thresholds_for_energy_mean = get_scattering_thresholds_for_energy( energy_bin_geometric_means, ancillary_files ) + n_pix = for_indices_by_spin_phase.sizes["pixel"] # Initialize arrays to accumulate FWHM values for averaging - fwhm_theta_sum = np.zeros( - (len(energy_bin_geometric_means), for_indices_by_spin_phase.shape[0]) - ) + fwhm_theta_sum = np.zeros((len(energy_bin_geometric_means), n_pix)) fwhm_phi_sum = np.zeros_like(fwhm_theta_sum) sample_count = np.zeros_like(fwhm_theta_sum) - steps = for_indices_by_spin_phase.shape[1] + steps = for_indices_by_spin_phase.sizes["spin_phase_step"] energies = energy_bin_geometric_means[np.newaxis, :] - n_pix = for_indices_by_spin_phase.shape[0] # Initialize DataArray to hold boolean of valid pixels at each spin phase step # If reject_scattering if false, this will just be the FOR mask. spun_dims = ("spin_phase_step", "energy", "pixel") @@ -140,46 +150,80 @@ def calculate_fwhm_spun_scattering( np.zeros((steps, len(energy_bin_geometric_means), n_pix), dtype=bool), dims=spun_dims, ) + elif "energy" not in for_indices_by_spin_phase.sizes: + valid_pixels = for_indices_by_spin_phase.expand_dims( + {"energy": len(energy_bin_geometric_means)} + ).transpose(*spun_dims) else: - valid_pixels = xr.DataArray( - for_indices_by_spin_phase.T[:, np.newaxis, :], dims=spun_dims - ) + valid_pixels = for_indices_by_spin_phase # The "for_indices_by_spin_phase" lookup table contains the boolean values of each # pixel at each spin phase step, indicating whether the pixel is inside the FOR. # It starts at Spin-phase = 0, and increments in fine steps (1 ms), spinning the # spacecraft in the despun frame. At each iteration, query for the pixels in the # FOR, and calculate whether the FWHM value is below the threshold at the energy. for i in range(steps): - # Calculate spin phase for the current iteration - for_inds = for_indices_by_spin_phase[:, i] - - # Skip if no pixels in FOR - if not np.any(for_inds): - logger.info(f"No pixels found in FOR at spin phase step {i}") - continue - # Using the lookup table, get the indices of the pixels inside the FOR at - # the current spin phase step. - theta = theta_vals[for_inds, i] - phi = phi_vals[for_inds, i] - theta_coeffs, phi_coeffs = get_scattering_coefficients( - theta, phi, lookup_tables=scattering_luts - ) - # Get a mask for pixels below the FWHM scattering threshold - scattering_mask, fwhm_theta, fwhm_phi = mask_below_fwhm_scattering_threshold( - theta_coeffs, - phi_coeffs, - energies, - scattering_thresholds=scattering_thresholds_for_energy_mean, - ) - # Store results of the scattering mask at the indices corresponding to the - # current spin phase step and the pixels inside the FOR. - if reject_scattering: - valid_pixels[i, :, for_inds] = scattering_mask.T + for_inds = for_indices_by_spin_phase.isel(spin_phase_step=i).values + + if for_inds.ndim > 1: + # Energy dependent FOR indices + for e_ind in range(len(energy_bin_geometric_means)): + for_inds_energy = for_inds[e_ind, :] + + # Skip if no pixels in FOR + if not np.any(for_inds_energy): + continue + + theta = theta_vals[i, e_ind, for_inds_energy] + phi = phi_vals[i, e_ind, for_inds_energy] + theta_coeffs, phi_coeffs = get_scattering_coefficients( + theta.data, phi.data, lookup_tables=scattering_luts + ) + # Calculate scattering mask for specified energy + energy = energy_bin_geometric_means[e_ind : e_ind + 1][np.newaxis, :] + scattering_mask, fwhm_theta, fwhm_phi = ( + mask_below_fwhm_scattering_threshold( + theta_coeffs, + phi_coeffs, + energy, + scattering_thresholds=scattering_thresholds_for_energy_mean[ + e_ind : e_ind + 1 + ], + ) + ) + # If rejecting scattering, store the mask + if reject_scattering: + valid_pixels[i, e_ind, for_inds_energy] = scattering_mask.flatten() + + # Accumulate FWHM values + fwhm_theta_sum[e_ind, for_inds_energy] += fwhm_theta.flatten() + fwhm_phi_sum[e_ind, for_inds_energy] += fwhm_phi.flatten() + sample_count[e_ind, for_inds_energy] += 1 + else: + # Energy independent FOR indices + if not np.any(for_inds): + continue + + theta = theta_vals[i, for_inds] + phi = phi_vals[i, for_inds] + theta_coeffs, phi_coeffs = get_scattering_coefficients( + theta, phi, lookup_tables=scattering_luts + ) + scattering_mask, fwhm_theta, fwhm_phi = ( + mask_below_fwhm_scattering_threshold( + theta_coeffs, + phi_coeffs, + energies, + scattering_thresholds=scattering_thresholds_for_energy_mean, + ) + ) + + if reject_scattering: + valid_pixels[i, :, for_inds] = scattering_mask.T - # Accumulate FWHM values for averaging - fwhm_theta_sum[:, for_inds] += fwhm_theta.T - fwhm_phi_sum[:, for_inds] += fwhm_phi.T - sample_count[:, for_inds] += 1 + # Accumulate FWHM values + fwhm_theta_sum[:, for_inds] += fwhm_theta.T + fwhm_phi_sum[:, for_inds] += fwhm_phi.T + sample_count[:, for_inds] += 1 fwhm_phi_avg = np.zeros_like(fwhm_phi_sum) fwhm_theta_avg = np.zeros_like(fwhm_theta_sum) @@ -195,7 +239,7 @@ def calculate_fwhm_spun_scattering( def get_spacecraft_pointing_lookup_tables( ancillary_files: dict, instrument_id: int -) -> tuple[NDArray, NDArray, NDArray, NDArray, xr.DataArray]: +) -> tuple[xr.DataArray, NDArray, NDArray, NDArray, xr.DataArray]: """ Get indices of pixels in the nominal FOR as a function of spin phase. @@ -212,8 +256,8 @@ def get_spacecraft_pointing_lookup_tables( Returns ------- - for_indices_by_spin_phase : NDArray - A 2D boolean array of shape (npix, n_spin_phase_steps). + for_indices_by_spin_phase : xarray.DataArray + A 2D boolean array of shape (n_spin_phase_steps,npix). True indicates pixels that are within the Field of Regard (FOR) at that spin phase. theta_vals : NDArray @@ -231,27 +275,36 @@ def get_spacecraft_pointing_lookup_tables( index_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-index" bsf_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-bsf" - theta_vals = pd.read_csv( - ancillary_files[theta_descriptor], header=None, skiprows=1 - ).to_numpy(dtype=float)[:, 2:] - phi_vals = pd.read_csv( - ancillary_files[phi_descriptor], header=None, skiprows=1 - ).to_numpy(dtype=float)[:, 2:] - index_grid = pd.read_csv( - ancillary_files[index_descriptor], header=None, skiprows=1 - ).to_numpy(dtype=float) - boundary_scale_factors = pd.read_csv( - ancillary_files[bsf_descriptor], header=None, skiprows=1 - ).to_numpy(dtype=float)[:, 2:] - - ra_and_dec = index_grid[:, :2] # Shape (npix, 2) + theta_vals = ( + pd.read_csv(ancillary_files[theta_descriptor], header=None, skiprows=1) + .to_numpy(dtype=float)[:, 2:] + .T + ) + phi_vals = ( + pd.read_csv(ancillary_files[phi_descriptor], header=None, skiprows=1) + .to_numpy(dtype=float)[:, 2:] + .T + ) + index_grid = ( + pd.read_csv(ancillary_files[index_descriptor], header=None, skiprows=1) + .to_numpy(dtype=float) + .T + ) + boundary_scale_factors = ( + pd.read_csv(ancillary_files[bsf_descriptor], header=None, skiprows=1) + .to_numpy(dtype=float)[:, 2:] + .T + ) + + ra_and_dec = index_grid[:2, :] # Shape (npix, 2) # This array indicates whether each pixel is in the nominal FOR at each spin phase # step (15000 steps for a full rotation with 1 ms resolution). - for_indices_by_spin_phase = np.nan_to_num(index_grid[:, 2:], nan=0).astype( - bool - ) # Shape (npix, 15000) + for_indices_by_spin_phase = xr.DataArray( + np.nan_to_num(index_grid[2:, :], nan=0).astype(bool), + dims=("spin_phase_step", "pixel"), + ) boundary_scale_factors = xr.DataArray( - boundary_scale_factors, dims=("pixel", "spin_phase_step") + boundary_scale_factors, dims=("spin_phase_step", "pixel") ) return ( for_indices_by_spin_phase, diff --git a/imap_processing/ultra/l1c/make_helio_index_maps.py b/imap_processing/ultra/l1c/make_helio_index_maps.py new file mode 100644 index 000000000..8b9c9baee --- /dev/null +++ b/imap_processing/ultra/l1c/make_helio_index_maps.py @@ -0,0 +1,335 @@ +"""Make heliocentric HEALPix index maps for Ultra L1C processing.""" + +import logging + +import astropy_healpix.healpy as hp +import numpy as np +import spiceypy as sp +import xarray as xr + +from imap_processing.spice.geometry import ( + SpiceBody, + SpiceFrame, + get_rotation_matrix, + imap_state, +) +from imap_processing.ultra.constants import UltraConstants +from imap_processing.ultra.l1b.lookup_utils import is_inside_fov +from imap_processing.ultra.l1c.l1c_lookup_utils import build_energy_bins + +logger = logging.getLogger(__name__) + + +def vector_ijk_to_theta_phi( + inst_vecs: np.ndarray, +) -> tuple[np.ndarray, np.ndarray]: + """ + Convert instrument vectors to theta/phi. + + Parameters + ---------- + inst_vecs : np.ndarray + Array of shape (n, 3) with components (x, y, z). + + Returns + ------- + theta : np.ndarray + Declination in radians, range [-π, π]. + phi : np.ndarray + Right ascension in radians, range [-π, π]. + """ + # Extract components + i_comp = inst_vecs[:, 0] # x component + j_comp = inst_vecs[:, 1] # y component + k_comp = inst_vecs[:, 2] # z component + + # Normalize + magnitude = np.linalg.norm(inst_vecs, axis=1) + + # Compute declination and right ascension + theta = np.arcsin(i_comp / magnitude) + phi = np.arctan2(j_comp, k_comp) + + # Wrap to [-π, π] + theta = np.where(theta > np.pi, theta - 2 * np.pi, theta) + phi = np.where(phi > np.pi, phi - 2 * np.pi, phi) + + return theta, phi + + +def make_helio_index_maps_with_nominal_kernels( + kernel_paths: list[str], + nside: int, + spin_duration: float, + num_steps: int, + instrument_frame: SpiceFrame = SpiceFrame.IMAP_ULTRA_90, + compute_bsf: bool = False, + boundary_points: int = 8, +) -> xr.Dataset: + """ + Create index maps with nominal sim kernels. + + This function ensures SPICE kernels are loaded before creating the maps. It uses + a KernelPool context manager to ensure only this function uses the nominal sim + kernels. + + Parameters + ---------- + kernel_paths : list[str] + List of string paths to nominal simulated SPICE kernels. + nside : int + HEALPix nside parameter. + spin_duration : float + Total spin period in seconds. + num_steps : int + Number of spin phase steps. + instrument_frame : SpiceFrame, optional + Instrument frame (default IMAP_ULTRA_90). + compute_bsf : bool, optional + Compute boundary scale factors (default False). + boundary_points : int, optional + Number of boundary points per pixel (default 8). + + Returns + ------- + xr.Dataset + Dataset with helio index maps. + """ + # Get all loaded SPK kernels + spk_kernels = [sp.kdata(i, "spk")[0] for i in range(sp.ktotal("spk"))] + # Find the de440s.bps kernel + de440s_file = next((k for k in spk_kernels if "de440" in k), None) + if de440s_file is None: + raise RuntimeError("de440s.bsp kernel not found in loaded SPK kernels.") + # If found, add to kernel paths + kernel_paths.append(de440s_file) + with sp.KernelPool(kernel_paths): + # calculate the start et of the pointing kernel. + # TODO replace this with a util function + ck_kernel, _, _, _ = sp.kdata(1, "ck") + ck_cover = sp.ckcov( + ck_kernel, SpiceFrame.IMAP_DPS.value, True, "INTERVAL", 0, "TDB" + ) + et_start, _ = sp.wnfetd(ck_cover, 0) + # Call the main function + return make_helio_index_maps( + nside=nside, + spin_duration=spin_duration, + num_steps=num_steps, + start_et=et_start, + instrument_frame=instrument_frame, + compute_bsf=compute_bsf, + boundary_points=boundary_points, + ) + + +def make_helio_index_maps( + nside: int, + spin_duration: float, + num_steps: int, + start_et: float, + instrument_frame: SpiceFrame = SpiceFrame.IMAP_ULTRA_90, + compute_bsf: bool = False, + boundary_points: int = 8, +) -> xr.Dataset: + """ + Create HEALPix index maps for heliocentric observations. + + This function generates exposure maps that account for spacecraft + velocity aberration, multiple energy bins, and spin phase sampling. + + Parameters + ---------- + nside : int + HEALPix nside parameter (determines angular resolution). + spin_duration : float + Total spin period in seconds. + num_steps : int + Number of spin phase steps to sample. + start_et : float + Start ephemeris time. + instrument_frame : SpiceFrame, optional + SpiceFrame of the instrument (default IMAP_ULTRA_90). + compute_bsf : bool, optional + If True, compute boundary scale factors (default False). + boundary_points : int, optional + Number of boundary points to sample per pixel (default 8). + + Returns + ------- + xarray.Dataset + Dataset with dimensions (step, energy, pixel) containing index, + theta, phi, and bsf data variables, plus ra and dec coordinates. + """ + # Get spacecraft velocity at start time + state = imap_state(start_et, ref_frame=SpiceFrame.IMAP_DPS, observer=SpiceBody.SUN) + sc_vel = state[3:6] # Extract [vx, vy, vz] + + logger.info("Spacecraft velocity: %s km/s", sc_vel) + logger.info("Speed: %.2f km/s", np.linalg.norm(sc_vel)) + + # Build energy bins + _, energy_midpoints, energy_bin_geometric_means = build_energy_bins() + num_energy_bins = len(energy_bin_geometric_means) + + # Get number of pixels + npix = hp.nside2npix(nside) + + # Compute RA/Dec for pixel centers + pixel_indices = np.arange(npix) + + # Time parameters + end_et = start_et + spin_duration + dt_step = spin_duration / num_steps + + # Pre-compute all pixel vectors once + pixel_vecs = np.array(hp.pix2vec(nside, pixel_indices, nest=False)).T # (npix, 3) + + # Initialize output arrays + index_map = np.zeros((num_steps, num_energy_bins, npix)) + theta_map = np.zeros((num_steps, num_energy_bins, npix)) + phi_map = np.zeros((num_steps, num_energy_bins, npix)) + bsf_map = np.zeros((num_steps, num_energy_bins, npix)) + + logger.info( + "Processing %d time steps, %d energy bins, %d pixels...", + num_steps, + num_energy_bins, + npix, + ) + if compute_bsf: + logger.info( + "Computing boundary scale factors with %d points per pixel", + boundary_points, + ) + # TODO vectorize loop + time_id = 0 + t = start_et + while t < (end_et - dt_step / 2): + # Get rotation matrix for this time step + rotation_matrix = get_rotation_matrix( + t, + from_frame=SpiceFrame.IMAP_DPS, + to_frame=instrument_frame, + ) + for energy_id in range(num_energy_bins): + # Convert energy to velocity (km/s) + energy_mean = energy_bin_geometric_means[energy_id] + kps = ( + np.sqrt(2 * energy_mean * UltraConstants.KEV_J / UltraConstants.MASS_H) + / 1e3 + ) + + # Transform pixel vectors to heliocentric frame + helio_velocity = ( + sc_vel.reshape(1, 3) + kps * pixel_vecs + ) # Galilean transform + helio_normalized = helio_velocity / np.linalg.norm( + helio_velocity, axis=1, keepdims=True + ) + + # Transform to inst + inst_vecs = helio_normalized @ rotation_matrix.T + theta, phi = vector_ijk_to_theta_phi(inst_vecs) + + phi = np.where(phi > np.pi, phi - 2 * np.pi, phi) + + # Check FOV + in_fov_mask = is_inside_fov(theta, phi) + fov_pixels = np.where(in_fov_mask)[0] + + # Store results for FOV pixels + theta_map[time_id, energy_id, fov_pixels] = np.degrees(theta[fov_pixels]) + phi_map[time_id, energy_id, fov_pixels] = np.degrees(phi[fov_pixels]) + index_map[time_id, energy_id, fov_pixels] = 1.0 + + # Compute boundary scale factor if requested + if compute_bsf: + for pix_id in fov_pixels: + # Get boundary vectors for this pixel + boundary_step = boundary_points // 4 + boundary_vecs = hp.boundaries( + nside, pix_id, step=boundary_step, nest=False + ) + boundary_vecs = boundary_vecs.T + + # Include center pixel + sample_vecs = np.vstack( + [boundary_vecs, pixel_vecs[pix_id : pix_id + 1]] + ) + + # Transform boundary vectors to heliocentric frame + helio_boundary_vel = sc_vel.reshape(1, 3) + kps * sample_vecs + helio_boundary_norm = helio_boundary_vel / np.linalg.norm( + helio_boundary_vel, axis=1, keepdims=True + ) + + # Transform to instrument frame + inst_boundary = helio_boundary_norm @ rotation_matrix + + # Convert to theta/phi + theta_b, phi_b = vector_ijk_to_theta_phi(inst_boundary) + phi_b = np.where(phi_b > np.pi, phi_b - 2 * np.pi, phi_b) + + # Check how many sample points are in FOV + in_fov_boundary = is_inside_fov(theta_b, phi_b) + bsf = np.sum(in_fov_boundary) / len(sample_vecs) + + bsf_map[time_id, energy_id, pix_id] = bsf + + # Increment time + time_id += 1 + t += dt_step + + # Create coordinate arrays + step_indices = np.arange(num_steps) + spin_phases = np.linspace(0, 360, num_steps, endpoint=False) + + # Create xarray Dataset + # Ensure index_map is a boolean type + index_map = index_map.astype(bool) + ds = xr.Dataset( + data_vars={ + "index": ( + ["spin_phase_step", "energy", "pixel"], + index_map, + {"long_name": "Pixel in FOV flag"}, + ), + "theta": ( + ["spin_phase_step", "energy", "pixel"], + theta_map, + {"long_name": "Instrument theta angle", "units": "degrees"}, + ), + "phi": ( + ["spin_phase_step", "energy", "pixel"], + phi_map, + {"long_name": "Instrument phi angle", "units": "degrees"}, + ), + "bsf": ( + ["spin_phase_step", "energy", "pixel"], + bsf_map, + {"long_name": "Boundary scale factor", "units": "fractional"}, + ), + }, + coords={ + "spin_phase_step": (["spin_phase_step"], step_indices), + "energy": ( + ["energy"], + energy_bin_geometric_means, + {"long_name": "Energy bin geometric mean", "units": "keV"}, + ), + "pixel": (["pixel"], pixel_indices), + "spin_phase": ( + ["spin_phase_step"], + spin_phases, + {"long_name": "Spin phase", "units": "degrees"}, + ), + "energy_midpoint": ( + ["energy"], + energy_midpoints, + {"long_name": "Energy bin midpoint", "units": "keV"}, + ), + }, + ) + + return ds diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/imap_001.tf b/imap_processing/ultra/l1c/sim_spice_kernels/imap_001.tf new file mode 100644 index 000000000..4ca51ccaf --- /dev/null +++ b/imap_processing/ultra/l1c/sim_spice_kernels/imap_001.tf @@ -0,0 +1,3105 @@ +KPL/FK + +Interstellar Mapping and Acceleration Probe Frames Kernel +======================================================================== + + This frames kernel contains the current set of coordinate frame + definitions for the Interstellar Mapping and Acceleration Probe + (IMAP) spacecraft, structures, and science instruments. + + This kernel also contains NAIF ID/name mapping for the IMAP + instruments. + + +Version and Date +======================================================================== + + The TEXT_KERNEL_ID stores version information of loaded project text + kernels. Each entry associated with the keyword is a string that + consists of four parts: the kernel name, version, entry date, and + type. For example, the frames kernel might have an entry as follows: + + + TEXT_KERNEL_ID += 'IMAP_FRAMES V1.0.0 2024-XXXX-NN FK' + | | | | + | | | | + KERNEL NAME <-------+ | | | + | | V + VERSION <------+ | KERNEL TYPE + | + V + ENTRY DATE + + + Interstellar Mapping and Acceleration Probe Frames Kernel Version: + + \begindata + + TEXT_KERNEL_ID += 'IMAP_FRAMES V1.0.0 2024-XXXX-NN FK' + + \begintext + + Version 1.0.0 -- XXXX NN, 2024 -- Douglas Rodgers + Lillian Nguyen + Nicholas Dutton + + Initial complete release. Frame/Body codes for thrusters redefined + + Version 0.0.1 -- July 9, 2021 -- Ian Wick Murphy + + Modifying dart_008.tf to add basic IMAP frame components. This + includes IMAP, IMAP_THRUSTER, and CK/SCLK IDs. Also adding a place + holder for the IMAP-Lo instrument with the ID -43001 and IMAP_LO + name. Future work includes adding more detailed instrument frames, + and reaching out to mechanical for an "official" IMAP_SPACECRAFT + frame definition. + + +References +======================================================================== + + 1. "Frames Required Reading" + + 2. "Kernel Pool Required Reading" + + 3. "C-Kernel Required Reading" + + 4. "7516-9067: IMAP Mechanical Interface Control Document", + Johns Hopkins Applied Physics Laboratory + + 5. "7516-9050: IMAP Coordinate Frame & Technical Definitions Doc.", + Johns Hopkins Applied Physics Laboratory + + 6. "7516-0011: IMAP Mechanical Interface Control Drawing", + [EXPORT CONTROLLED], Johns Hopkins Applied Physics Laboratory + + 7. "7523-0008: IMAP ULTRA Mechanical Interface Control Drawing", + [EXPORT CONTROLLED], Johns Hopkins Applied Physics Laboratory + + 8. "058991000: IMAP SWAPI Mechanical Interface Control Drawing", + Princeton University Space Physics + + 9. "GLOWS-CBK-DWG-2020-08-25-019-v4.4: IMAP GLOWS Mechanical + Interface Control Drawing", Centrum Badag Kosmicznych, Polska + Akademia Nauks + + 10. Responses from IMAP instrument teams on their base frame axis + definitions, received in email. + + 11. "Euler angles", Wikimedia Foundation, 2024-04-22, + https://en.wikipedia.org/wiki/Euler_angles + + 12. "7516-9059: IMAP-Lo to Spacecraft Interface Control Document", + [EXPORT CONTROLLED], Johns Hopkins Applied Physics Laboratory + + 13. "DRAFT Rev H: IMAP-Lo Mechanical Interface Control Drawing", + [EXPORT CONTROLLED], Univ. of New Hampshire Space Science Center + + 14. McComas et al, "IMAP: A New NASA Mission", + Space Sci Rev (2018) 214:116 + + 15. "IMAP-HI SENSOR HEAD Mechanical Interface Control Drawing", + [EXPORT CONTROLLED], Los Alamos National Laboratory + + 16. "IMAP-MAG-SENSOR Drawing Rev 6", Imperial College London + + +Contact Information +======================================================================== + + Douglas Rodgers, JHU/APL, Douglas.Rodgers@jhuapl.edu + + Lillian Nguyen, JHU/APL, Lillian.Nguyen@jhuapl.edu + + Nicholas Dutton, JHU/APL, Nicholas.Dutton@jhuapl.edu + + Ian Wick Murphy, JHU/APL, Ian.Murphy@jhuapl.edu + + +Implementation Notes +======================================================================== + + This file is used by the SPICE system as follows: programs that make + use of this frame kernel must `load' the kernel, normally during + program initialization. Loading the kernel associates the data items + with their names in a data structure called the `kernel pool'. The + SPICELIB routine FURNSH loads a kernel into the pool as shown below: + + FORTRAN: (SPICELIB) + + CALL FURNSH ( frame_kernel_name ) + + C: (CSPICE) + + furnsh_c ( frame_kernel_name ); + + IDL: (ICY) + + cspice_furnsh, frame_kernel_name + + MATLAB: (MICE) + + cspice_furnsh ( frame_kernel_name ) + + This file was created and may be updated with a text editor or word + processor. + + +Viewing ASCII Artwork +======================================================================== + + Artwork must be viewed in a text editor with monospaced font and + compact single-spaced lines. The following give the proper aspect + ratio: + + Andale Regular + Menlo Regular + Courier New Regular + PT Mono Regular + + The common monospaced font (at the time of writing) Monaco Regular + gives an aspect ratio that is too tall. Other fonts undoubtedly + will render the diagrams properly or improperly. + + As a guide, the following axis will be square when measured from the + bottom of the lower-most vertical line to the end of each axis. + + | + | + | + |_______ + + +IMAP NAIF ID Codes -- Definitions +======================================================================== + + This section contains name to NAIF ID mappings for the IMAP mission. + Once the contents of this file are loaded into the KERNEL POOL, these + mappings become available within SPICE, making it possible to use + names instead of ID code in high level SPICE routine calls. + + \begindata + + NAIF_BODY_NAME += ( 'IMAP' ) + NAIF_BODY_CODE += ( -43 ) + + NAIF_BODY_NAME += ( 'IMAP_SPACECRAFT' ) + NAIF_BODY_CODE += ( -43000 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_A1' ) + NAIF_BODY_CODE += ( -43010 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_A2' ) + NAIF_BODY_CODE += ( -43011 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_A3' ) + NAIF_BODY_CODE += ( -43012 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_A4' ) + NAIF_BODY_CODE += ( -43013 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R1' ) + NAIF_BODY_CODE += ( -43020 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R2' ) + NAIF_BODY_CODE += ( -43021 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R3' ) + NAIF_BODY_CODE += ( -43022 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R4' ) + NAIF_BODY_CODE += ( -43023 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R5' ) + NAIF_BODY_CODE += ( -43024 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R6' ) + NAIF_BODY_CODE += ( -43025 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R7' ) + NAIF_BODY_CODE += ( -43026 ) + + NAIF_BODY_NAME += ( 'IMAP_THRUSTER_R8' ) + NAIF_BODY_CODE += ( -43027 ) + + NAIF_BODY_NAME += ( 'IMAP_SUN_SENSOR_PZ' ) + NAIF_BODY_CODE += ( -43030 ) + + NAIF_BODY_NAME += ( 'IMAP_SUN_SENSOR_MZ' ) + NAIF_BODY_CODE += ( -43031 ) + + NAIF_BODY_NAME += ( 'IMAP_STAR_TRACKER_PX' ) + NAIF_BODY_CODE += ( -43040 ) + + NAIF_BODY_NAME += ( 'IMAP_STAR_TRACKER_MX' ) + NAIF_BODY_CODE += ( -43041 ) + + NAIF_BODY_NAME += ( 'IMAP_LOW_GAIN_ANTENNA' ) + NAIF_BODY_CODE += ( -43050 ) + + NAIF_BODY_NAME += ( 'IMAP_MED_GAIN_ANTENNA' ) + NAIF_BODY_CODE += ( -43051 ) + + NAIF_BODY_NAME += ( 'IMAP_LO_BASE' ) + NAIF_BODY_CODE += ( -43100 ) + + NAIF_BODY_NAME += ( 'IMAP_LO' ) + NAIF_BODY_CODE += ( -43101 ) + + NAIF_BODY_NAME += ( 'IMAP_LO_STAR_SENSOR' ) + NAIF_BODY_CODE += ( -43102 ) + + NAIF_BODY_NAME += ( 'IMAP_HI_45' ) + NAIF_BODY_CODE += ( -43150 ) + + NAIF_BODY_NAME += ( 'IMAP_HI_90' ) + NAIF_BODY_CODE += ( -43175 ) + + NAIF_BODY_NAME += ( 'IMAP_ULTRA_45' ) + NAIF_BODY_CODE += ( -43200 ) + + NAIF_BODY_NAME += ( 'IMAP_ULTRA_90' ) + NAIF_BODY_CODE += ( -43225 ) + + NAIF_BODY_NAME += ( 'IMAP_MAG_BOOM' ) + NAIF_BODY_CODE += ( -43250 ) + + NAIF_BODY_NAME += ( 'IMAP_MAG_I' ) + NAIF_BODY_CODE += ( -43251 ) + + NAIF_BODY_NAME += ( 'IMAP_MAG_O' ) + NAIF_BODY_CODE += ( -43251 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE' ) + NAIF_BODY_CODE += ( -43300 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_P63' ) + NAIF_BODY_CODE += ( -43301 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_P42' ) + NAIF_BODY_CODE += ( -43302 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_P21' ) + NAIF_BODY_CODE += ( -43303 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_000' ) + NAIF_BODY_CODE += ( -43304 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_M21' ) + NAIF_BODY_CODE += ( -43305 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_M42' ) + NAIF_BODY_CODE += ( -43306 ) + + NAIF_BODY_NAME += ( 'IMAP_SWE_DETECTOR_M63' ) + NAIF_BODY_CODE += ( -43307 ) + + NAIF_BODY_NAME += ( 'IMAP_SWAPI' ) + NAIF_BODY_CODE += ( -433510 ) + + NAIF_BODY_NAME += ( 'IMAP_SWAPI_APERTURE_L' ) + NAIF_BODY_CODE += ( -43351 ) + + NAIF_BODY_NAME += ( 'IMAP_SWAPI_APERTURE_R' ) + NAIF_BODY_CODE += ( -43352 ) + + NAIF_BODY_NAME += ( 'IMAP_SWAPI_SUNGLASSES' ) + NAIF_BODY_CODE += ( -43353 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE' ) + NAIF_BODY_CODE += ( -43400 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_01' ) + NAIF_BODY_CODE += ( -43401 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_02' ) + NAIF_BODY_CODE += ( -43402 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_03' ) + NAIF_BODY_CODE += ( -43403 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_04' ) + NAIF_BODY_CODE += ( -43404 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_05' ) + NAIF_BODY_CODE += ( -43405 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_06' ) + NAIF_BODY_CODE += ( -43406 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_07' ) + NAIF_BODY_CODE += ( -43407 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_08' ) + NAIF_BODY_CODE += ( -43408 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_09' ) + NAIF_BODY_CODE += ( -43409 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_10' ) + NAIF_BODY_CODE += ( -43410 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_11' ) + NAIF_BODY_CODE += ( -43411 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_12' ) + NAIF_BODY_CODE += ( -43412 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_13' ) + NAIF_BODY_CODE += ( -43413 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_14' ) + NAIF_BODY_CODE += ( -43414 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_15' ) + NAIF_BODY_CODE += ( -43415 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_16' ) + NAIF_BODY_CODE += ( -43416 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_17' ) + NAIF_BODY_CODE += ( -43417 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_18' ) + NAIF_BODY_CODE += ( -43418 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_19' ) + NAIF_BODY_CODE += ( -43419 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_20' ) + NAIF_BODY_CODE += ( -43420 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_21' ) + NAIF_BODY_CODE += ( -43421 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_22' ) + NAIF_BODY_CODE += ( -43422 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_23' ) + NAIF_BODY_CODE += ( -43423 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_LO_APERTURE_24' ) + NAIF_BODY_CODE += ( -43424 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_01' ) + NAIF_BODY_CODE += ( -43425 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_02' ) + NAIF_BODY_CODE += ( -43426 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_03' ) + NAIF_BODY_CODE += ( -43427 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_04' ) + NAIF_BODY_CODE += ( -43428 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_05' ) + NAIF_BODY_CODE += ( -43429 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_06' ) + NAIF_BODY_CODE += ( -43430 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_07' ) + NAIF_BODY_CODE += ( -43431 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_08' ) + NAIF_BODY_CODE += ( -43432 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_09' ) + NAIF_BODY_CODE += ( -43433 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_10' ) + NAIF_BODY_CODE += ( -43434 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_11' ) + NAIF_BODY_CODE += ( -43435 ) + + NAIF_BODY_NAME += ( 'IMAP_CODICE_HI_APERTURE_12' ) + NAIF_BODY_CODE += ( -43436 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT' ) + NAIF_BODY_CODE += ( -43500 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_01' ) + NAIF_BODY_CODE += ( -43501 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_02' ) + NAIF_BODY_CODE += ( -43502 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_03' ) + NAIF_BODY_CODE += ( -43503 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_04' ) + NAIF_BODY_CODE += ( -43504 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_05' ) + NAIF_BODY_CODE += ( -43505 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_06' ) + NAIF_BODY_CODE += ( -43506 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_07' ) + NAIF_BODY_CODE += ( -43507 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_08' ) + NAIF_BODY_CODE += ( -43508 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_09' ) + NAIF_BODY_CODE += ( -43509 ) + + NAIF_BODY_NAME += ( 'IMAP_HIT_L1_APERTURE_10' ) + NAIF_BODY_CODE += ( -43510 ) + + NAIF_BODY_NAME += ( 'IMAP_IDEX' ) + NAIF_BODY_CODE += ( -43700 ) + + NAIF_BODY_NAME += ( 'IMAP_IDEX_DETECTOR' ) + NAIF_BODY_CODE += ( -43701 ) + + NAIF_BODY_NAME += ( 'IMAP_IDEX_FULL_SCIENCE' ) + NAIF_BODY_CODE += ( -43702 ) + + + NAIF_BODY_NAME += ( 'IMAP_GLOWS' ) + NAIF_BODY_CODE += ( -43751 ) + + \begintext + +Removed by Tim Plummer due to missing frame definition + NAIF_BODY_NAME += ( 'IMAP_GLOWS_BASE' ) + NAIF_BODY_CODE += ( -43750 ) + +IMAP NAIF ID Codes -- Definitions +======================================================================== + + The ID codes -43900 to -43999 have been reserved for the IMAP dynamic + frames kernel and are not utilized in this file. + + The following frames are defined in this kernel file: + + Frame Name Relative To Type NAIF ID + ========================== =============== ======= ======= + + Spacecraft (000-099) + -------------------------- + IMAP_SPACECRAFT J2000 CK -43000 + IMAP_THRUSTER_A1 IMAP_SPACECRAFT FIXED -43010 + IMAP_THRUSTER_A2 IMAP_SPACECRAFT FIXED -43011 + IMAP_THRUSTER_A3 IMAP_SPACECRAFT FIXED -43012 + IMAP_THRUSTER_A4 IMAP_SPACECRAFT FIXED -43013 + IMAP_THRUSTER_R1 IMAP_SPACECRAFT FIXED -43020 + IMAP_THRUSTER_R2 IMAP_SPACECRAFT FIXED -43021 + IMAP_THRUSTER_R3 IMAP_SPACECRAFT FIXED -43022 + IMAP_THRUSTER_R4 IMAP_SPACECRAFT FIXED -43023 + IMAP_THRUSTER_R5 IMAP_SPACECRAFT FIXED -43024 + IMAP_THRUSTER_R6 IMAP_SPACECRAFT FIXED -43025 + IMAP_THRUSTER_R7 IMAP_SPACECRAFT FIXED -43026 + IMAP_THRUSTER_R8 IMAP_SPACECRAFT FIXED -43027 + IMAP_SUN_SENSOR_PZ IMAP_SPACECRAFT FIXED -43030 + IMAP_SUN_SENSOR_MZ IMAP_SPACECRAFT FIXED -43031 + IMAP_STAR_TRACKER_PX IMAP_SPACECRAFT FIXED -43040 + IMAP_STAR_TRACKER_MX IMAP_SPACECRAFT FIXED -43041 + IMAP_LOW_GAIN_ANTENNA IMAP_SPACECRAFT FIXED -43050 + IMAP_MED_GAIN_ANTENNA IMAP_SPACECRAFT FIXED -43051 + + IMAP-Lo (100-149) + -------------------------- + IMAP_LO_BASE IMAP_SPACECRAFT FIXED -43100 + IMAP_LO IMAP_LO_BASE CK -43101 + IMAP_LO_STAR_SENSOR IMAP_LO FIXED -43102 + + IMAP-Hi (150-199) + -------------------------- + IMAP_HI_45 IMAP_SPACECRAFT FIXED -43150 + IMAP_HI_90 IMAP_SPACECRAFT FIXED -43151 + + IMAP-Ultra (200-249) + -------------------------- + IMAP_ULTRA_45 IMAP_SPACECRAFT FIXED -43200 + IMAP_ULTRA_90 IMAP_SPACECRAFT FIXED -43201 + + MAG (250-299) + -------------------------- + IMAP_MAG_BOOM IMAP_SPACECRAFT FIXED -43250 + IMAP_MAG_I IMAP_MAG_BOOM FIXED -43251 + IMAP_MAG_O IMAP_MAG_BOOM FIXED -43252 + + SWE (300-349) + -------------------------- + IMAP_SWE IMAP_SPACECRAFT FIXED -43300 + IMAP_SWE_DETECTOR_P63 IMAP_SWE FIXED -43301 + IMAP_SWE_DETECTOR_P42 IMAP_SWE FIXED -43302 + IMAP_SWE_DETECTOR_P21 IMAP_SWE FIXED -43303 + IMAP_SWE_DETECTOR_000 IMAP_SWE FIXED -43304 + IMAP_SWE_DETECTOR_M21 IMAP_SWE FIXED -43305 + IMAP_SWE_DETECTOR_M42 IMAP_SWE FIXED -43306 + IMAP_SWE_DETECTOR_M63 IMAP_SWE FIXED -43307 + + SWAPI (350-399) + -------------------------- + IMAP_SWAPI IMAP_SPACECRAFT FIXED -43350 + IMAP_SWAPI_APERTURE_L IMAP_SWAPI FIXED -43351 + IMAP_SWAPI_APERTURE_R IMAP_SWAPI FIXED -43352 + IMAP_SWAPI_SUNGLASSES IMAP_SWAPI FIXED -43353 + + CODICE (400-499) + -------------------------- + IMAP_CODICE IMAP_SPACECRAFT FIXED -43400 + IMAP_CODICE_LO_APERTURE_01 IMAP_CODICE FIXED -43401 + IMAP_CODICE_LO_APERTURE_02 IMAP_CODICE FIXED -43402 + IMAP_CODICE_LO_APERTURE_03 IMAP_CODICE FIXED -43403 + IMAP_CODICE_LO_APERTURE_04 IMAP_CODICE FIXED -43404 + IMAP_CODICE_LO_APERTURE_05 IMAP_CODICE FIXED -43405 + IMAP_CODICE_LO_APERTURE_06 IMAP_CODICE FIXED -43406 + IMAP_CODICE_LO_APERTURE_07 IMAP_CODICE FIXED -43407 + IMAP_CODICE_LO_APERTURE_08 IMAP_CODICE FIXED -43408 + IMAP_CODICE_LO_APERTURE_09 IMAP_CODICE FIXED -43409 + IMAP_CODICE_LO_APERTURE_10 IMAP_CODICE FIXED -43410 + IMAP_CODICE_LO_APERTURE_11 IMAP_CODICE FIXED -43411 + IMAP_CODICE_LO_APERTURE_12 IMAP_CODICE FIXED -43412 + IMAP_CODICE_LO_APERTURE_13 IMAP_CODICE FIXED -43413 + IMAP_CODICE_LO_APERTURE_14 IMAP_CODICE FIXED -43414 + IMAP_CODICE_LO_APERTURE_15 IMAP_CODICE FIXED -43415 + IMAP_CODICE_LO_APERTURE_16 IMAP_CODICE FIXED -43416 + IMAP_CODICE_LO_APERTURE_17 IMAP_CODICE FIXED -43417 + IMAP_CODICE_LO_APERTURE_18 IMAP_CODICE FIXED -43418 + IMAP_CODICE_LO_APERTURE_19 IMAP_CODICE FIXED -43419 + IMAP_CODICE_LO_APERTURE_20 IMAP_CODICE FIXED -43420 + IMAP_CODICE_LO_APERTURE_21 IMAP_CODICE FIXED -43421 + IMAP_CODICE_LO_APERTURE_22 IMAP_CODICE FIXED -43422 + IMAP_CODICE_LO_APERTURE_23 IMAP_CODICE FIXED -43423 + IMAP_CODICE_LO_APERTURE_24 IMAP_CODICE FIXED -43424 + IMAP_CODICE_HI_APERTURE_01 IMAP_CODICE FIXED -43425 + IMAP_CODICE_HI_APERTURE_02 IMAP_CODICE FIXED -43426 + IMAP_CODICE_HI_APERTURE_03 IMAP_CODICE FIXED -43427 + IMAP_CODICE_HI_APERTURE_04 IMAP_CODICE FIXED -43428 + IMAP_CODICE_HI_APERTURE_05 IMAP_CODICE FIXED -43429 + IMAP_CODICE_HI_APERTURE_06 IMAP_CODICE FIXED -43430 + IMAP_CODICE_HI_APERTURE_07 IMAP_CODICE FIXED -43431 + IMAP_CODICE_HI_APERTURE_08 IMAP_CODICE FIXED -43432 + IMAP_CODICE_HI_APERTURE_09 IMAP_CODICE FIXED -43433 + IMAP_CODICE_HI_APERTURE_10 IMAP_CODICE FIXED -43434 + IMAP_CODICE_HI_APERTURE_11 IMAP_CODICE FIXED -43435 + IMAP_CODICE_HI_APERTURE_12 IMAP_CODICE FIXED -43436 + + HIT (500-699) + -------------------------- + IMAP_HIT IMAP_SPACECRAFT FIXED -43500 + IMAP_HIT_L1_APERTURE_01 IMAP_HIT FIXED -43501 + IMAP_HIT_L1_APERTURE_02 IMAP_HIT FIXED -43502 + IMAP_HIT_L1_APERTURE_03 IMAP_HIT FIXED -43503 + IMAP_HIT_L1_APERTURE_04 IMAP_HIT FIXED -43504 + IMAP_HIT_L1_APERTURE_05 IMAP_HIT FIXED -43505 + IMAP_HIT_L1_APERTURE_06 IMAP_HIT FIXED -43506 + IMAP_HIT_L1_APERTURE_07 IMAP_HIT FIXED -43507 + IMAP_HIT_L1_APERTURE_08 IMAP_HIT FIXED -43508 + IMAP_HIT_L1_APERTURE_09 IMAP_HIT FIXED -43509 + IMAP_HIT_L1_APERTURE_10 IMAP_HIT FIXED -43510 + + IDEX (700-749) + -------------------------- + IMAP_IDEX IMAP_SPACECRAFT FIXED -43700 + IMAP_IDEX_DETECTOR IMAP_IDEX FIXED -43701 + IMAP_IDEX_FULL_SCIENCE IMAP_IDEX FIXED -43702 + + GLOWS (750-799) + -------------------------- + IMAP_GLOWS_BASE IMAP_SPACECRAFT FIXED -43750 + IMAP_GLOWS IMAP_GLOWS_BASE FIXED -43751 + + +IMAP Frame Tree +======================================================================== + + The diagram below illustrates the IMAP frame hierarchy: + + J2000 + | + |<---ck + | + IMAP_SPACECRAFT + | + IMAP_THRUSTER_A1 + | + |... + | + IMAP_THRUSTER_A4 + | + IMAP_THRUSTER_R1 + | + |... + | + IMAP_THRUSTER_R8 + | + IMAP_SUN_SENSOR_PZ + | + IMAP_SUN_SENSOR_MZ + | + IMAP_STAR_TRACKER_PX + | + IMAP_STAR_TRACKER_MX + | + IMAP_LOW_GAIN_ANTENNA + | + IMAP_MED_GAIN_ANTENNA + | + IMAP_LO_BASE + | | + | |<---ck + | | + | IMAP_LO + | | + | IMAP_LO_STAR_SENSOR + | + IMAP_HI_45 + | + IMAP_HI_90 + | + IMAP_ULTRA_45 + | + IMAP_ULTRA_90 + | + IMAP_MAG_BOOM + | | + | IMAP_MAP_I + | | + | IMAP_MAP_O + | + IMAP_SWE + | | + | IMAP_SWE_DETECTOR_P63 + | | + | IMAP_SWE_DETECTOR_P42 + | | + | IMAP_SWE_DETECTOR_P21 + | | + | IMAP_SWE_DETECTOR_000 + | | + | IMAP_SWE_DETECTOR_M21 + | | + | IMAP_SWE_DETECTOR_M42 + | | + | IMAP_SWE_DETECTOR_M63 + | + IMAP_SWAPI + | | + | IMAP_SWAPI_APERTURE_L + | | + | IMAP_SWAPI_APERTURE_R + | | + | IMAP_SWAPI_SUNGLASSES + | + IMAP_CODICE + | | + | IMAP_CODICE_LO_APERTURE_01 + | | + | |... + | | + | IMAP_CODICE_LO_APERTURE_24 + | | + | IMAP_CODICE_HI_APERTURE_01 + | | + | |... + | | + | IMAP_CODICE_HI_APERTURE_12 + | + IMAP_HIT + | | + | IMAP_HIT_L1_APERTURE_01 + | | + | |... + | | + | IMAP_HIT_L1_APERTURE_10 + | + IMAP_IDEX + | | + | IMAP_IDEX_DETECTOR + | | + | IMAP_IDEX_FULL_SCIENCE + | + IMAP_GLOWS_BASE + | + IMAP_GLOWS + +IMAP Spacecraft Frame +======================================================================== + + \begindata + + FRAME_IMAP_SPACECRAFT = -43000 + FRAME_-43000_NAME = 'IMAP_SPACECRAFT' + FRAME_-43000_CLASS = 3 + FRAME_-43000_CLASS_ID = -43000 + FRAME_-43000_CENTER = -43 + CK_-43000_SCLK = -43 + CK_-43000_SPK = -43 + + \begintext + + + The orientation of the spacecraft body frame with respect to an + inertial frame, J2000 for IMAP, is provided by a C-kernel (see [3] + for details). + + The spacecraft coordinate frames are defined by the IMAP control + documents (see [4,5], NB, figure 2.2). There are two frames described + there: Observatory Mechanical Design Reference Frame (most relevant) + and Observatory Pointing and Dynamics Reference Frame (less relevant + for this frame kernel). + + + Observatory Mechanical Design Reference Frame (IMAP_SPACECRAFT) + --------------------------------------------------------------------- + + If not explicitly stated, references to 'spacecraft mechanical frame' + 'spacecraft frame', or 'S/C frame' will refer to this frame. + + All instruments and component placements and orientations are defined + using this coordinate frame reference. + + Origin: Center of the launch vehicle adapter ring at the + observatory/launch vehicle interface plane + + +Z axis: Perpendicular to the launch vehicle interface plane pointed + in the direction of the top deck (runs through the center + of the central cylinder structure element) + + +Y axis: Direction of the vector orthogonal to the +Z axis and + parallel to the deployed MAG boom + + +X axis: The third orthogonal axis defined using an X, Y, Z ordered + right hand rule + + NB: The Observatory Pointing and Dynamics Reference Frame is also + defined in [5]. It is identical to the observatory mechanical design + reference frame, but with the origin translated to the observatory + center of mass (which changes with boom deployment and fuel usage). + The offset difference between the mechanical and dynamic frame is + within the uncertainty range of the ephemeris, so the mechanical + design frame is used here for definiteness. + + Three different views [5,6] of the spacecraft with labeled components + are presented below for illustrative purposes. + + + IMAP -Z Bottom View (Figure 3-2 in [5], G-G in [6] rotated 180°) + --------------------------------------------------------------------- + --------- + | +X axis | -------------------- + --------- | +Z axis facing Sun | + . | into page | + /|\ -------------------- + | + | + | + _ + HI 45 /`~~__HI 90 `+ direction of + , = .^ - /_ ``-. '. positive + .+ + `^~/ ./ ~ rotation + ^ + + . -- ' `` \ _-~ \ + _ / ',= ' \~'` \ IMAP \ + ULTRA /' '-_ .~ ' \,.=.. \ LO \|/ + 90 / ~ _,.,_ + + \ ' + / ,~' +' `'+ + + \ + / ~^ .' , = .'. '- ='' -`` --------- + ^/ / , = . + + \ \~'` | +Y axis |-----> + | . + + + + . \ --------- ___ + | | + + ' = ' | \--------------------| | + SWAPI| | ' = ', - . | /--------------------|___| + _+_: ' + + ' / MAG boom + \_ __\__ \ + + / /^*~, + + | SWE '. ' = ' .' ULTRA / + `~-' '~..,___,..~' 45 /~,* + _\ / /~,*` + * / CODICE ^*._/ *` HIT + *\ _/`. / + * / /~ _ _ ,.-^-., _ _ _ / + '=' + + + GLOWS + + + '-.,.-' + IDEX + + + IMAP +X Side View (F-F in [6]) + --------------------------------------------------------------------- + --------- + | +Z axis | + --------- --------------------- + . | +X axis out of page | + /|\ --------------------- + | LGA + __________________|______|^|_________ ___ + SWAPI|__________________|__________________|====================| | + #|-| | | .-==-, | / MAG boom '---' + #|-| {|## | | / \ | | + | {|## | |{ HI 90 }| IMAP LO| + | {|## | _.._ | \ / | _., | + | ULTRA | / \ | `-==-' | / __`',| + | 90 | \ HI 45/ | | \ \_\ ;| + | | '----` | | ~._ + | + '-------------------|----------/--------' + | | \_________O_________/ | | ----------------> + |__| ----------- /_\ --------- + STAR | S/C FRAME | MGA | +Y axis | + TRACKERS | ORIGIN | --------- + ----------- + + + IMAP -X Side View (C-C in [6]) + --------------------------------------------------------------------- + --------- + | +Z axis | + ------------------- --------- + | +X axis into page | . + ------------------- /|\ + LGA | + ___ _________|^|______|__________________ + | |====================|__________________|_____________ __ _|SWAPI + '---' MAG boom \ __ | | | // \ /--|# + |( )=|__|| | | \\__/ \--|# + | HIT | _|_ IDEX | CODICE | + | | ,.' | '., | | + | ____ | [ \ | / ] | SWE| + ULTRA ##',', |,.'|'.,| GLOWS (#)| + 45 ####'. + | + \\(O) |-|| + '----####/----- + | + --------------' + <---------------- | | \______'-.O.-'______/ | | + --------- /_\ ----------- |__| + | +Y axis | MGA | S/C FRAME | STAR + --------- | ORIGIN | TRACKERS + ----------- + + + IMAP Component Location - Azimuth and Elevation + --------------------------------------------------------------------- + + Payload and subsystem component locations are specified[5,6] in the + Observatory Mechanical Design Reference Frame (described above). + Locations are defined in azimuth and elevation (and resultant + direction cosign matrices) of these angles[6] in the same reference + frame. The azimuth and elevation angle diagram is provided below. + + In general, descriptions in this kernel treat the +Z direction as + "up" and the -Z direction as "down." Locations referred to as "above" + are generally closer to the Sun, and vice versa for "below." The + "upper" side of the spacecraft is the plane of the solar panels, + while the "lower" side may refer to the area near the adapter ring. + If ambiguity could arise, more thorough descriptions will be used. + + + Toward Sun + + +Z axis + . + | + . + | + . Component + | Location/ + . Orientation + | @ + Toward . .'| + MAG | +` | + .~ '` Boom S/C . .` \ | + .~ '` FRAME |.` : | + / ~'` ORIGIN O | | + *--- .~ '` \ Elevation + .~ '` \ | | + .~ '` \ ; |~ + .~ '\ \ / | ^~ + +Y axis \ \ + | ^~ + '. '~, \ | ^~ + '~ Azimuth \ | ^~ + '~. `^~-> \| -X axis + ' ~ ., _ _ ,.~ + ``'`` + + + IMAP Component Orientation - Azimuth and Elevation + --------------------------------------------------------------------- + + In addition to the rotation matrices, azimuth and elevation are used + to specify look direction (i.e., boresight) of the science payload + components and thrusters. However, these two angles are not adequate + to specify the complete orientation of the components--a secondary + axis must be specified to complete the rotation. + + The look direction, D, in the frame of the spacecraft for azimuth, az + and elevation, el, is: + + D = [ -cos(el) x sin(az), cos(el) x cos(az), sin(el) ] + + For all practical purposes, the look direction (primary axis) + corresponds to one of the six axis-aligned directions of the local + coordinate system of the instrument: X', Y', Z', -X', -Y', -Z'. While + the azimuth/elevation of the instrument look direction is provided in + the spacecraft MICD[4], the local coordinate axis in which it + corresponds is provided in the instrument's MICD. + + The secondary axis, S, must be perpendicular to D for the following + discussion. It will generally be specified in one of two ways: + + 1) S is one of the six axis-aligned directions of the + spacecraft coordinate system: X, Y, Z, -X, -Y, -Z + + 2) S lies in the plane perpendicular to one of the axes of the + spacecraft coordinate system: X, Y, Z, -X, -Y, -Z + + Similar to the look direction, this direction will then be assigned + to correspond to one of the six instrument directions X', Y', Z', + -X', -Y', -Z'. + + For definiteness, it is assumed that the third axes, N = D x S, + completes the righthanded coordinate system. + + The rotation matrix specifying the component frame, X'Y'Z', in the + spacecraft frame, XYZ, is: + + Ux Uy Uz + + [ X ] [ R11 R12 R13 ] [ X'] + [ ] [ ] [ ] + [ Y ] = [ R21 R22 R23 ] [ Y'] + [ ] [ ] [ ] + [ Z ] [ R31 R32 R33 ] [ Z'] + + with Ux, Uy, Uz specifying the unit column vectors of the rotation. + Because the primary and secondary axes, D and S, lie along the local + axes of the instrument coordinate system (X'Y'Z'), they are simply + the column vectors of the rotation matrix (assuming properly unit). + + + IMAP Component Orientation - Euler Angles + --------------------------------------------------------------------- + + When the orientation is not specified in azimuth/elevation, or the + secondary is not well-defined, we try to deduce the most straight- + forward definition using a simple secondary axis. Sometimes a + single axis-aligned rotation applied BEFORE the general rotation + allows a simple secondary axis to notionally be used to accurately + define the coordinates; see Hi 45 or Hi 90 for this case. + + It is also possible to deduce the Euler angles to produce more + precise rotation matrices. For most components, before final + alignments are calculated, these angles are in whole degrees. + (However, see Hi 45 for a counterexample). + + The spacecraft subsystems such as the star trackers have complete + rotation matrices that fully define the orientation of each + component. These matrices, while complete, are not conducive to + visualizing the orientation of a component on the spacecraft bus. + + As it happens, when applied to rotations, the azimuth and elevation + are nearly identitical to the first two Euler angles of the ZXZ + intrinsic rotation. For the Euler angles (A, B, Y), this is defined + as follows[11]. + + Let xyz represent the coordinate axes of the fixed frame, and XYZ + are the axes of the fully rotated frame expressed in the xyz frame. + Three successive, ordered rotations about the axes are performed: + + 1) Righthanded rotation about z by the angle A ∈ [-π, π); the rotated + frame is defined x'y'z', with z' = z. The new frame x'y'z' is + expressed in the coordinates of the original frame xyz. + + 2) Righthanded rotation about x' by the angle B ∈ [0,π]; the rotated + frame is defined x"y"z", with x" = x'. The new frame x"y"z" is + expressed in the coordinates of the original frame xyz. + + 3) Righthanded rotation about z" by the angle Y ∈ [-π,π); the rotated + frame is defined XYZ, with Z = z". The final frame XYZ is + expressed in the coordinates of the original frame xyz. + + + Euler Angles + Intrinsic ZXZ Rotation + + z axis + . + | Y axis + _._. / + , B ` | / + Z axis ,-` . / + ^, ^ | / + ^, . / + ^, | / + ^, . / + ^, | / _ X axis + ^, . / _ ~ ^ + ^, |/ _ ~ ^ ^ + .~ ~ ^ | + .~ '` \ ^~ ; + .~ '` \ \ ^~ ; + .~ '` ', \ ^~ , + .~ '` ` A \ ^ Y + x axis `^~-> \ , ~ + \ ~` ^~ + \- ^ ^~ + \ y axis + \ + x'=x" axis + + + Comparing the two figures, we see that A = azimuth and B appears to + coincide with elevation. However, while B lies on the range [0,π], + conventionally, elevation ∈ [-π/2,π/2]. This range for elevation does + not capture all possible orientations, e.g., a playing card facing + upward cannot be placed facing downward with elevation ∈ [-π/2,π/2]. + + So, we need to supplement the azimuth and elevation nomenclature with + fully specified Euler angles. + + The technical documents [4,5,6] give rotation matrix elements to six + decimal places, which is not sufficient for accurate pointing in the + SPICE toolkit. The remedy to this inaccuracy is provided below. + + Given an insufficiently-accurate rotation matrix, M, with column + vectors Vx, Vy, Vz: + + Vx Vy Vz + + [ M11 M12 M13 ] + [ ] + M = [ M21 M22 M23 ] + [ ] + [ M31 M32 M33 ] + + A rotation matrix, R, with column unit vectors Ux, Uy, Uz: + + Ux Uy Uz + + [ R11 R12 R13 ] + [ ] + R = [ R21 R22 R23 ] + [ ] + [ R31 R32 R33 ] + + is calculated so that column vectors are orthonormal to within double + precision accuracy (an operation SPICE calls "sharpening"): + + Uz = Vz / |Vz| + + Uy = Uz x (Vx / |Vx|) + + Ux = Uy x Uz + + These calculations are done outside of the SPICE library, but using + numerically stable algorithms as SPICE does. Sharpening by starting + with the X or Y direction, as opposed to Z, can be accomplished by + cyclically permuting x,y,z above. SPICE, for example, starts with X. + + With a precise (though not necessarily accurate) rotation matrix, + the instrinsic ZXZ Euler angles (A, B, Y) are calculated: + + A' = atan2(R13, -R23) + ______________ + B' = atan2(\/ 1 - R33 x R33 , R33) + + Y' = atan2(R31, R32) + + These values are rounded to regain the assumed original orientation: + + A = round(A') to nearest 1/1000th degree + + B = round(B') to nearest 1/1000th degree + + Y = round(Y') to nearest 1/1000th degree + + And finally, the rotation matrix elements are recalculated: + + R11 = c1 x c3 - s1 x c2 x s3 + + R21 = s1 x c3 + c1 x c2 x s3 + + R31 = s2 x s3 + + R12 = -c1 x s3 - s1 x c2 x c3 + + R22 = -s1 x s3 + c1 x c2 x c3 + + R32 = s2 x c3 + + R13 = s1 x s2 + + R23 = -c1 x s2 + + R33 = c2 + + where: + + c1 = cos(A) + + s1 = sin(A) + + c2 = cos(B) + + s2 = sin(B) + + c3 = cos(Y) + + s3 = sin(Y) + + When B = 0, the angles A and Y are degenerate; Y = 0 in this case. + + In the subsequent frames defined below, when Euler angles (A, B, Y) + are referenced without further discussion, they will refer to the + Euler angles as defined here. Otherwise, definitions will be given + inline with the discussion. + + + When Look Direction is Well-Defined + --------------------------------------------------------------------- + + When the look direction is well-defined, but the secondary axis is + not, we replace the column of the imprecise rotation matrix with + the exact look direction, and proceed with the calculations above. + + +IMAP Thruster Frames +======================================================================== + + There are four axial (A) thrusters and eight radial (R) thrusters on + IMAP[6]. The table below shows the thruster positions defined in the + spacecraft frame[6], at the intersection of the thrust axis and the + nozzle exit plane. The unit direction vectors listed in the table + below point in the direction of the thruster exhaust. The positional + information may be captured in the IMAP structure SPK, while the + orientation information is captured here. + + + Thruster ID X (mm) Y (mm) Z (mm) UnitDir (X,Y,Z) + ---------------- ------ -------- -------- ------- --------------- + IMAP_THRUSTER_A1 -43010 1007.28 516.50 1312.40 ( 0, 0, 1 ) + IMAP_THRUSTER_A2 -43011 -1007.28 -516.50 1312.40 ( 0, 0, 1 ) + IMAP_THRUSTER_A3 -43012 -1007.28 -516.50 101.77 ( 0, 0, -1 ) + IMAP_THRUSTER_A4 -43013 1007.28 516.50 101.77 ( 0, 0, -1 ) + IMAP_THRUSTER_R1 -43020 -126.90 1237.78 841.12 (-0.5, 0.866,0) + IMAP_THRUSTER_R2 -43021 126.90 -1237.78 841.12 ( 0.5,-0.866,0) + IMAP_THRUSTER_R3 -43022 -1008.49 728.79 841.12 (-0.5, 0.866,0) + IMAP_THRUSTER_R4 -43023 1008.49 -728.79 841.12 ( 0.5,-0.866,0) + IMAP_THRUSTER_R5 -43024 -126.90 1237.78 447.42 (-0.5, 0.866,0) + IMAP_THRUSTER_R6 -43025 126.90 -1237.78 447.42 ( 0.5,-0.866,0) + IMAP_THRUSTER_R7 -43026 -1008.49 728.79 447.42 (-0.5, 0.866,0) + IMAP_THRUSTER_R8 -43027 1008.49 -728.79 447.42 ( 0.5,-0.866,0) + + + Thruster Locations and Directions + --------------------------------------------------------------------- + + The four axial thrusters[6] are directed along the spacecraft Z axis, + with A1,A2 located on the +Z side of the spacecraft and A3,A4 located + on the -Z side. A1,A2 fire in the +Z direction, while A3,A4 fire in + the -Z direction. A1 and A4 are aligned in the Z direction, while + A2 and A3 are aligned but on the opposite side of the S/C as A1/A4. + + The eight radial thrusters[6] are grouped into four pairs (R1/R5, + R2/R6, R3/R7, R4/R8); each pair is aligned along the Z direction and + fire in the same direction. There are two distinct firing directions, + all perpendicular to the spacecraft Z axis: R1/R5 & R3/R7 fire toward + the +Y direction (with a slight -X component), while R2/R6 & R4/R8 + fire in the -Y direction (with a slight +X component). Thrusters + R1-R4 are located above the center of mass (towards the Sun), while + thrusters R5-R8 are located below the center of mass (away from the + Sun). The table below shows the azimuth of location and direction of + radial thrusters calculated from using thruster table above. + + + Location Azim Direction Azim + -------------- -------------- + R1/R5 5.85° 30.0° + R2/R6 180° + 5.85° 180° + 30.0° + R3/R7 54.15° 30.0° + R4/R8 180° + 54.15° 180° + 30.0° + + + +X axis +Z axis facing Sun + . into page + /|\ + | + | + | A1 (on +Z side) + A4 (on -Z side) + R4/R8 Dir /`~~__ / + '~._ , = .^ - /_ ``-. / + /~._ .+ + `^~/ .\/ + 30°| '~. + . -- ' `` @\ _-~ + - - + - - - -# R4/R8 \~'` \ + /' '-_ . \,.=.. \ + / ~ _,.,_ + + \ + R2/R6 Dir / ,~' +' `'+ + + \ + '~._ / ~^ .' , = .'. '- ='' -`` + /~._ ^/ / , = . + + \ \~'` + 30°| '~. | . + + + + . \ +Y axis -----> + - - + - - - -|# R2/R6 | + + ' = ' | \ + | | ' = ', - . | R1/R5 #._- - - - - + - - + _+_: ' + + ' / '~._ | + \_ __\__ \ + + / /^*~, '~._ / 30° + + | \ '. ' = ' .' / / '~. + `~-' '~..,___,..~' / /~,* R1/R5 Dir + _\ / /~,*` + * / \ ^*._/ *` + *\ _/`. R3/R7 #/._- - - - - + - - + * / /\@_ _ ,.-^-., _ _ _ / '~._ | + '=' | + + '~._ / 30° + | + + '~. + | '-.,.-' R3/R7 Dir + | + A2 (on +Z side) + A3 (on -Z side) + + + Axial Thruster Frames + --------------------------------------------------------------------- + + Each axial thruster has a frame defined so that the thruster exhaust + exits in the +Z' direction. The +Y' axis is chosen to lie in the + direction of the MAG boom. X' = Y' x Z' completes the frame. + + [X] [ 1 0 0 ] [X'] + [Y] = [ 0 1 0 ] [Y'] + [Z]S/C [ 0 0 1 ] [Z']Axial Thrusters A1,A2 + + [X] [ -1 0 0 ] [X'] + [Y] = [ 0 1 0 ] [Y'] + [Z]S/C [ 0 0 -1 ] [Z']Axial Thrusters A3,A4 + + + Axial Thruster + Exhaust Direction + + +Z' axis + | + | + _. -|- ._ + ,' | ', + , | , + | -.,_|_,.- | + ' ' + ' ' + ; ; + ; ; + : ; + , , Toward + ',_,' ^~ MAG + .~ '` ^~ ^~ Boom + .~ '` ^~ ^~ + .~ '` ^~ ^~ + .~ '` ^~ ^~ \ + +X' axis ^~ --* + ^~ + ^~ + +Y' axis + + + \begindata + + FRAME_IMAP_THRUSTER_A1 = -43010 + FRAME_-43010_NAME = 'IMAP_THRUSTER_A1' + FRAME_-43010_CLASS = 4 + FRAME_-43010_CLASS_ID = -43010 + FRAME_-43010_CENTER = -43 + TKFRAME_-43010_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43010_SPEC = 'MATRIX' + TKFRAME_-43010_MATRIX = ( 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 ) + + FRAME_IMAP_THRUSTER_A2 = -43011 + FRAME_-43011_NAME = 'IMAP_THRUSTER_A2' + FRAME_-43011_CLASS = 4 + FRAME_-43011_CLASS_ID = -43011 + FRAME_-43011_CENTER = -43 + TKFRAME_-43011_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43011_SPEC = 'MATRIX' + TKFRAME_-43011_MATRIX = ( 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 ) + + FRAME_IMAP_THRUSTER_A3 = -43012 + FRAME_-43012_NAME = 'IMAP_THRUSTER_A3' + FRAME_-43012_CLASS = 4 + FRAME_-43012_CLASS_ID = -43012 + FRAME_-43012_CENTER = -43 + TKFRAME_-43012_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43012_SPEC = 'MATRIX' + TKFRAME_-43012_MATRIX = ( -1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + -1 ) + + FRAME_IMAP_THRUSTER_A4 = -43013 + FRAME_-43013_NAME = 'IMAP_THRUSTER_A4' + FRAME_-43013_CLASS = 4 + FRAME_-43013_CLASS_ID = -43013 + FRAME_-43013_CENTER = -43 + TKFRAME_-43013_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43013_SPEC = 'MATRIX' + TKFRAME_-43013_MATRIX = ( -1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + -1 ) + + \begintext + + + Radial Thrusters + --------------------------------------------------------------------- + + Each radial thruster has a frame defined so that the thruster exhaust + exits in the +Y' direction. The +Z' axis is chosen to lie along the + spacecraft +Z axis (toward Sun). X' = Y' x Z' completes the frame. + + [X] [ cos( 30) -sin( 30) 0 ] [X'] + [Y] = [ sin( 30) cos( 30) 0 ] [Y'] + [Z]S/C [ 0 0 1 ] [Z']Rad. Thrusters R1,R3,R5,R7 + + [X] [ cos(210) -sin(210) 0 ] [X'] + [Y] = [ sin(210) cos(210) 0 ] [Y'] + [Z]S/C [ 0 0 1 ] [Z']Rad. Thrusters R2,R4,R6,R8 + + + Toward Sun + + +Z' axis + . + | + . + | + . + | + . + Radial Thruster | + Exhaust Direction . + | + .~ '` . + /.~ '` _,,~ ~ ~ ~ ~ ~ ~ ~ | + *-- .;-. \ ~ + ,' '. ~ ^~ + ; \ ~' ^~ + | .~ '`: ~' ^~ + .~ '` | ~' ^~ + ~ '` \ ; _ ~' ^~ + +Y' axis '.,_._;-' ^~ + ^~ + -X' axis + + + \begindata + + FRAME_IMAP_THRUSTER_R1 = -43020 + FRAME_-43020_NAME = 'IMAP_THRUSTER_R1' + FRAME_-43020_CLASS = 4 + FRAME_-43020_CLASS_ID = -43020 + FRAME_-43020_CENTER = -43 + TKFRAME_-43020_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43020_SPEC = 'MATRIX' + TKFRAME_-43020_MATRIX = ( 0.86602540378443865, + 0.50000000000000000, + 0.00000000000000000, + -0.50000000000000000, + 0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R2 = -43021 + FRAME_-43021_NAME = 'IMAP_THRUSTER_R1' + FRAME_-43021_CLASS = 4 + FRAME_-43021_CLASS_ID = -43021 + FRAME_-43021_CENTER = -43 + TKFRAME_-43021_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43021_SPEC = 'MATRIX' + TKFRAME_-43021_MATRIX = ( -0.86602540378443865, + -0.50000000000000000, + 0.00000000000000000, + 0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R3 = -43022 + FRAME_-43022_NAME = 'IMAP_THRUSTER_R3' + FRAME_-43022_CLASS = 4 + FRAME_-43022_CLASS_ID = -43022 + FRAME_-43022_CENTER = -43 + TKFRAME_-43022_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43022_SPEC = 'MATRIX' + TKFRAME_-43022_MATRIX = ( 0.86602540378443865, + 0.50000000000000000, + 0.00000000000000000, + -0.50000000000000000, + 0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R4 = -43023 + FRAME_-43023_NAME = 'IMAP_THRUSTER_R4' + FRAME_-43023_CLASS = 4 + FRAME_-43023_CLASS_ID = -43023 + FRAME_-43023_CENTER = -43 + TKFRAME_-43023_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43023_SPEC = 'MATRIX' + TKFRAME_-43023_MATRIX = ( -0.86602540378443865, + -0.50000000000000000, + 0.00000000000000000, + 0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R5 = -43024 + FRAME_-43024_NAME = 'IMAP_THRUSTER_R5' + FRAME_-43024_CLASS = 4 + FRAME_-43024_CLASS_ID = -43024 + FRAME_-43024_CENTER = -43 + TKFRAME_-43024_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43024_SPEC = 'MATRIX' + TKFRAME_-43024_MATRIX = ( 0.86602540378443865, + 0.50000000000000000, + 0.00000000000000000, + -0.50000000000000000, + 0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R6 = -43025 + FRAME_-43025_NAME = 'IMAP_THRUSTER_R6' + FRAME_-43025_CLASS = 4 + FRAME_-43025_CLASS_ID = -43025 + FRAME_-43025_CENTER = -43 + TKFRAME_-43025_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43025_SPEC = 'MATRIX' + TKFRAME_-43025_MATRIX = ( -0.86602540378443865, + -0.50000000000000000, + 0.00000000000000000, + 0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R7 = -43026 + FRAME_-43026_NAME = 'IMAP_THRUSTER_R7' + FRAME_-43026_CLASS = 4 + FRAME_-43026_CLASS_ID = -43026 + FRAME_-43026_CENTER = -43 + TKFRAME_-43026_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43026_SPEC = 'MATRIX' + TKFRAME_-43026_MATRIX = ( 0.86602540378443865, + 0.50000000000000000, + 0.00000000000000000, + -0.50000000000000000, + 0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_THRUSTER_R8 = -43027 + FRAME_-43027_NAME = 'IMAP_THRUSTER_R6' + FRAME_-43027_CLASS = 4 + FRAME_-43027_CLASS_ID = -43027 + FRAME_-43027_CENTER = -43 + TKFRAME_-43027_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43027_SPEC = 'MATRIX' + TKFRAME_-43027_MATRIX = ( -0.86602540378443865, + -0.50000000000000000, + 0.00000000000000000, + 0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + \begintext + + +IMAP Digital Sun Sensor and Star Tracker Frames +======================================================================== + + There are two digital sun sensors (DSS)[6]: one on the +Z side of the + spacecraft pointing in +Z direction, and one on the -Z side pointing + mostly in the radial direction with a 30° tilt in the -Z direction. + They are approximated aligned along the spacecraft Z axis, though the + origins are offset from absolute alignment by a few centimeters (see + table below). Azimuthally, the sun sensors are located near the SWAPI + instrument approximately 18° off of the Y-Z plane. + + There are two star trackers mounted adjacent to each other on the + underside of the spacecraft close to the -Z digital star sensor[6]. + Their boresights are generally downward (towards -Z), with an angular + separation of 24°. One is angled toward the +X direction, the other + angled towards the -X direction. + + Positional information may be captured in the IMAP structure SPK, + while the orientation information is captured here. + + + Digital Sun Sensor ID X (mm) Y (mm) Z (mm) Loc. Azim + -------------------- ------ -------- -------- -------- --------- + IMAP_SUN_SENSOR_PZ -43030 -364.22 -1121.90 1301.67 162.014° + IMAP_SUN_SENSOR_MZ -43031 -379.11 -1167.77 72.89 162.014° + + + Digital Star Tracker ID X (mm) Y (mm) Z (mm) Loc. Azim + -------------------- ------ -------- -------- -------- --------- + IMAP_STAR_TRACKER_PX -43040 -45.75 -906.66 159.88 177.111° + IMAP_STAR_TRACKER_MX -43041 -188.05 -881.57 142.79 167.959° + + + ##################################################################### + # / _- __.----# + # ,' ~` _.~^' # + # / ~` ,~^ # + # ,' +Z axis facing Sun .` .^ +X axis # + # / into page / .^ . # + # | : /_,-----,_ /|\# + # | ~ ~` ^. | # + # | ^ ^ ^_ | # + # | / / , | # + # | , , ; | # + # | ; ; } | # + # | ___ : : ~ ___# + # -Y axis ___| .` `. | | }/ _# + # <------ |===| ;+X Star; | |. ;/ (` # + # | ;Tracker; | |' ; \ (,_# + # | `, ,` | | ', , \___# + # | '---' : : '-.,_____,.-` _,~# + # | _,;@ ; ; ," # + # /| | @*^^'` : : ; # + # /^' { _,;| ,---, ; ; ^ # + # \ *^^'` | .^ ^. ~ ~ { # + # | SWAPI { _, |-X Star| \ \ | # + # \ _,;*^ \ .Tracker. \ * { # + # | *^^'` \ -Z DSS ^.___.^ ^, `~_ \ # + # \ } \ _} ^_ "~_ ^, # + # ^^'"\\ \*^ ^, '-_ ~_ # + # \ (+Z DSS not visible) "~_ " -, '- # + ##################################################################### + + + Digital Sun Sensors (DSS) + --------------------------------------------------------------------- + + Each DSS has a frame defined so that the look-direction is along the + +Z' axis. The digital image rows and columns are aligned with the X' + and Y' axes of the frame. + + + DSS Look Direction + Local Frame + + +Z' axis + | + | + | + | + | + | + .~|'`^~ + .~ '` | ^~ + .~ '` __,=# | ,_ ^~ + .~ '` __,=#^^^ |@ ^%,_ ^~ + ~ ,=#^^^ | ^%,_ ^~ + | ^~ ,.~^~ ^%,_ ^~ + | ^~ ,.~ '` ^~ ^% ,^ + | ,.^~' @ ^~ .~ '` | + ^~.''` ^~ @^~ '` | + .~ '`` ^~ ^~ .~ '` ^~ | + +X' axis ^~ ^~.~ '` ^~.~ '` + ^~ | .~ '` ^~ + ^~ | .~ '` ^~ + ^~ |.~ '` +Y' axis + + + The rotation matrices orienting each DSS on the spacecraft are + given by [6]: + + [X] [ 0.951057 0.309017 0.000000 ] [X'] + [Y] = [ -0.309017 0.951057 0.000000 ] [Y'] + [Z]S/C [ 0.000000 0.000000 1.000000 ] [Z'] +Z DSS + + [X] [ 0.951078 -0.154380 -0.267616 ] [X'] + [Y] = [ -0.308952 -0.475579 -0.823640 ] [Y'] + [Z]S/C [ -0.000116 0.866025 -0.500000 ] [Z'] -Z DSS + + Using the method described in a previous section, the Euler angles + rounded to 1/1000th of a degree are: + + +Z DSS: (A, B, Y) = ( -18.000°, 0.000°, 0.000° ) + + -Z DSS: (A, B, Y) = ( -18.000°, 120.000°, -0.008° ) + + Using the formulas described in the Euler angles section above, the + rotation matrices have been recalculated to double precision. + + + \begindata + + FRAME_IMAP_SUN_SENSOR_PZ = -43030 + FRAME_-43030_NAME = 'IMAP_SUN_SENSOR_PZ' + FRAME_-43030_CLASS = 4 + FRAME_-43030_CLASS_ID = -43030 + FRAME_-43030_CENTER = -43 + TKFRAME_-43030_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43030_SPEC = 'MATRIX' + TKFRAME_-43030_MATRIX = ( 0.95105651629515350, + -0.30901699437494734, + 0.00000000000000000, + 0.30901699437494734, + 0.95105651629515350, + 0.00000000000000000, + -0.00000000000000000, + -0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_SUN_SENSOR_MZ = -43031 + FRAME_-43031_NAME = 'IMAP_SUN_SENSOR_MZ' + FRAME_-43031_CLASS = 4 + FRAME_-43031_CLASS_ID = -43031 + FRAME_-43031_CENTER = -43 + TKFRAME_-43031_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43031_SPEC = 'MATRIX' + TKFRAME_-43031_MATRIX = ( 0.95107808048040110, + -0.30895059509261280, + -0.00012091995722272, + -0.15437570314113858, + -0.47557140042407403, + 0.86602539534263330, + -0.26761656732981740, + -0.82363910354633210, + -0.49999999999999983 ) + + \begintext + + + Star Trackers + --------------------------------------------------------------------- + + Each star tracker has a frame defined so that the look-direction is + along the +Z' axis. The digital image rows and columns are aligned + with the X' and Y' axes of the frame. + + + Star Tracker Look Direction + Local Frame + + +Z' axis + + | + | + | + | + _. -|- ._ + ,' | ', + | .~ '` ^~ ,| + .~ '` ~ .,_ _,.^~' | + .~ '` | ^~ + .~ '` |, ,| ^~ + +X' axis ' -.,_ _,.- ' ^~ + | | ^~ + | | ^~ + | | +Y' axis + '-.,_ _,.-' + + + + When oriented on the spacecraft: + + - The tracker X' axis mostly points towards the spacecraft -X axis + - The tracker Y' axis mostly points towards the spacecraft +Y axis + - The tracker Z' axis mostly points towards the spacecraft -Z axis + + + ##################################################################### + # { { # + # ) ) # + # @ @ # + # { { # + # _,~--~,_ | | # + # ," ", ,-----,' # + # ; ; | | # + # +X Star / \ | | # + # Tracker { __,.- +Y' '-----' # + # | ..-^" |: | | # + # { ; ;} | | # + # {\ ; / } { { # + # {^, : ,^ ; @ @ # + # . ~_ ; _~ ,` | | # + # `, '~--~" ,^ "' | | # + # '"^--,__ ` ' "^ { { # + # `^ +X' `"` ) ) # + # "' ^' | | # + # ^' '~ { { # + # ^, __,,.~*^# ) ) # + # ', _,.~-'^'`__,,.~*^# | | # + # #-*~^'_,.~-'^'` '" { { # + # #-*~^' "^ @ @ # + # '" `"` | | # + # `^ ^` { { # + # "` _,~^^^~-.,'^ ) )# + # ^' _-" _,~--~,_ ".' ( # + # '^/ ," ", \` \ # + # , ; ;', \ # + # |/ \| # + # { __,.- +Y' Spacecraft Axes # + # -X Star | ..-^" | # + # Tracker { ; } +X # + # \ ; / | # + # ^, : ,^ | # + # ~_ ; _~ | # + # '~--~" | # + # ` x-------- +Y # + # +X' +Z into # + # Page # + ##################################################################### + + + The rotation matrices orienting each star tracker on the spacecraft + are given by [6]: + + [X] [ -0.963287 0.173648 0.204753 ] [X'] + [Y] = [ 0.169854 0.984808 -0.036104 ] [Y'] + [Z]S/C [ -0.207912 0.000000 -0.978148 ] [Z']+X Star Tracker + + + [X] [ -0.963287 0.173648 -0.204753 ] [X'] + [Y] = [ 0.169854 0.984808 0.036104 ] [Y'] + [Z]S/C [ 0.207912 0.000000 -0.978148 ] [Z']-X Star Tracker + + Using the method described in a previous section, the Euler angles + rounded to 1/1000th of a degree are: + + +X Star Tracker: (A, B, Y) = ( 80.000°, 168.000°, -90.000° ) + + -X Star Tracker: (A, B, Y) = ( -100.000°, 168.000°, 90.000° ) + + Use the formulas described in the Euler angles section above, the + rotation matrices have been recalculated to double precision. + + + \begindata + + FRAME_IMAP_STAR_TRACKER_PX = -43040 + FRAME_-43040_NAME = 'IMAP_STAR_TRACKER_PX' + FRAME_-43040_CLASS = 4 + FRAME_-43040_CLASS_ID = -43040 + FRAME_-43040_CENTER = -43 + TKFRAME_-43040_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43040_SPEC = 'MATRIX' + TKFRAME_-43040_MATRIX = ( -0.96328734079294150, + 0.16985354835670569, + -0.20791169081775915, + 0.17364817766693050, + 0.98480775301220800, + 0.00000000000000001, + 0.20475304505920630, + -0.03610348622615415, + -0.97814760073380570 ) + + FRAME_IMAP_STAR_TRACKER_MX = -43041 + FRAME_-43041_NAME = 'IMAP_STAR_TRACKER_MX' + FRAME_-43041_CLASS = 4 + FRAME_-43041_CLASS_ID = -43041 + FRAME_-43041_CENTER = -43 + TKFRAME_-43041_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43041_SPEC = 'MATRIX' + TKFRAME_-43041_MATRIX = ( -0.96328734079294150, + 0.16985354835670533, + 0.20791169081775915, + 0.17364817766693014, + 0.98480775301220800, + 0.00000000000000001, + -0.20475304505920630, + 0.03610348622615410, + -0.97814760073380570 ) + + \begintext + + +IMAP Antenna Frames +======================================================================== + + There are two antennas on the spacecraft. The low gain antenna (LGA) + is located on the +Z side of the spacecraft pointing toward +Z, while + the medium gain antenna (MGA) is located on the -Z side pointing in + the -Z direction. + + + --------- + | +Z axis | + ------------------- --------- + | +X axis into page | #-----# . + ------------------- | LGA | /|\ + #-----# | + ___ _________|^|______|__________________ + | |====================|__________________|_____________ __ _|SWAPI + '---' MAG boom \ __ | | | // \ /--|# + |( )=|__|| | | \\__/ \--|# + | HIT | _|_ IDEX | CODICE | + | | ,.' | '., | | + | ____ | [ \ | / ] | SWE| + ULTRA ##',', |,.'|'.,| GLOWS (#)| + 45 ####'. + | + \\(O) |-|| + '----####/----- + | + --------------' + <---------------- | | \______'-.O.-'______/ | | + --------- /_\ ----------- |__| + | +Y axis | #-----# | S/C FRAME | STAR + --------- | MGA | | ORIGIN | TRACKERS + #-----# ----------- + + + ##################################################################### + # .-----------------------------------------------------.# + # |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|# + # | | | | | | | | | | | | | | | | | | |# + # ,, _,~'-----|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|# + # \ \" ' _,~|___ |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|# + # \ \ " | | | | | | SOLAR PANELS | | | | | | |# + # \ \: |--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|# + # \,' |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|# + # HIT | | | | | | | | | | | | | | | |# + # |--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|# + # |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|# + # \ ___ | | | | | | | | | | |# + # THRUSTER R3 --> ,~\ |# #| |--|--|--|--|--|--|--|--|--|--|# + # ^, |# #| |__|__|__|__|__|__|__|__|__|__|# + # ^~---|---| | | | | | | | | | |# + # Spacecraft Axes | '-----------------------------'# + # | ^/~., ,.~\^ # + # +X #-----# { * `"*,_____,*"` * } # + # # LGA # { * | | * } # + # | #-----# \ * | | * / # + # | ~. * | | * .~ # + # | "|~####|####~|" # + # | # + # +Y --------+ IDEX # + # +Z out # + # of page # + ##################################################################### + + + ##################################################################### + # / #####~._ half of ~` _.~^' # + # / #########~._ ULTRA 45 ~` ,~^_ # + # HIT ,###########/ .` .^ ~ # + # (just out / ########/ / .^ ,` # + # of view) , : / , # + # / ~ ~` | # + # , ^ ^ , # + # / / / , # + # , , , , # + # / +Z into __ ; ; - # + # , page .`##`. : : `- . , _ ___# + # |/ +Y ------x ;#**#; | | / _# + # |\ | `.##.` | | ,.----., / (` # + # ' | | | | _~` `~_\ (,_# + # \ | #-----# | | ~ ~\___# + # ' # MGA # : : ,` `, # + # \ +X #-----# ; ;, , # + # ' : :| | # + # \ _.-----. ; ; , # + # '~ '^, ~ ~ , # + # -| IMAP / \ \ \ , # + # ' | LO | ' \ * - # + # | ' ; \ ^, `~_ _,.` # + # | ; :,_ . ^_ "~_ ~ ^ # + # ' ; | ^, '-_ # + # \ - ; "~_ " -, # + ##################################################################### + + + The LGA frame is coincident with the spacecraft XYZ axis, while the + MGA secondary axis is chosen so that Y' coincides with spacecraft Y. + This selection is identical to the axial thrusters A3,A4. + + [X] [ 1 0 0 ] [X'] + [Y] = [ 0 1 0 ] [Y'] + [Z]S/C [ 0 0 1 ] [Z']Low Gain Antenna + + [X] [ -1 0 0 ] [X'] + [Y] = [ 0 1 0 ] [Y'] + [Z]S/C [ 0 0 -1 ] [Z']Medium Gain Antenna + + + \begindata + + FRAME_IMAP_LOW_GAIN_ANTENNA = -43050 + FRAME_-43050_NAME = 'IMAP_LOW_GAIN_ANTENNA' + FRAME_-43050_CLASS = 4 + FRAME_-43050_CLASS_ID = -43050 + FRAME_-43050_CENTER = -43 + TKFRAME_-43050_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43050_SPEC = 'MATRIX' + TKFRAME_-43050_MATRIX = ( 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 ) + + FRAME_IMAP_MED_GAIN_ANTENNA = -43051 + FRAME_-43051_NAME = 'IMAP_MED_GAIN_ANTENNA' + FRAME_-43051_CLASS = 4 + FRAME_-43051_CLASS_ID = -43051 + FRAME_-43051_CENTER = -43 + TKFRAME_-43051_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43051_SPEC = 'MATRIX' + TKFRAME_-43051_MATRIX = ( -1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + -1 ) + + \begintext + + +IMAP-Lo Frames +======================================================================== + + IMAP-Lo is a single-pixel energetic neutral atom (ENA) imager mounted + on a pivot platform and equipped with a star sensor that pivots with + the ENA sensor [12,13]. The instrument is mounted for imaging in the + radial direction of the rotating spacecraft with the pivot allowing + orientation of the boresight from a polar angle of 60° (slightly + towards the Sun) to 180° (directed away from the Sun). + + + --------- + | +Z axis | + --------- --------------------- + . | +X axis out of page | + /|\ --------------------- + | LGA + __________________|______|^|_________ ___ + SWAPI|__________________|__________________|====================| | + #|-| | | .-==-, | / MAG boom '---' + #|-| {|## | | / \ | | + | {|## | |{ HI 90 }| IMAP LO| _. IMAP LO + | {|## | _.._ | \ / | _., | _.-' BORESIGHT + | ULTRA | / \ | `-==-' | / __`'_.-' + | 90 | \ HI 45/ | | \ \.-';| + | | '----` | | ~._.+ | + '-------------------|----------/--------' + | | \_________O_________/ | | ----------------> + |__| ----------- /_\ --------- + STAR | S/C FRAME | MGA | +Y axis | + TRACKERS | ORIGIN | --------- + ----------- + + + IMAP-Lo Local Frame + + Pivot +Z' axis + Angle | + ,.~'^ ^ ^-| + .-'` | + .` _~-, Star Sensor + .` | ** \___ _ | + Boresight | / \_-'`~~~~~~`'-.- - + . |/___ ,^~~~~~~%##### ', '. + `'. ^~~~~~~%%######### ` '. + `'. /~~~~~~, - - ~~~#####\ . + /. ~~~ / `.~~%###, . + .~~~`'./ .~~### . + .~~~~ `'. |~~~%#" .`. + "~~~~%| O :~~~~ ' . . + |~~~ # . /~~~~~ | . \ + |~~~%##`. /~~~~~ / . | + \~~%### ~`- -'~~~~~~ / . . + +,~%######~~~~~~~~ ,- ~@@@~ . + | ' ~ ######%%%%_,^ ,~@@@~ Rotation Axis + '. - .%##%.- .' . ^~. + .~ '` `. .' .` ^~. + .~ '` ' . _ .' .` ^~. + .~ '` ` '.''`` ,.` +X' axis + -Y' axis `-.,,, . ` + + + The local IMAP-Lo base frame is defined so the sensor pivots about + the +X' axis. When the pivot angle is 90°, the boresight is aligned + with the local -Y' axis. The +Z' axis, from which the pivot angle is + measured, aligns with the spacecraft +Z axis. + + The boresight look-direction is defined for the azimuth-elevation: + + LO (azim, elev) = ( +330°, -90° to +30° ) + + At 0° elevation (90° polar angle), the boresight direction and + primary axis in the spacecraft frame of reference is: + + D = -Y' = [ -cos(0) x sin(330), cos(0) x cos(330), sin(0) ] + + The secondary axis is the +X' local axis, perpendicular to both + the boresight direction D and the spacecraft -Z axis: + + S = +X' = D x -Z = Y' x [ 0, 0, 1 ] + + The tertiary axis is: + + N = D x S = Y' x ( Y' x [ 0, 0, 1 ] ) + + The rotation matrix formed using the column vectors is: + + R = [ +S, -D, +N ] + + From the spacecraft MICD[6], the single-precision rotation matrices + orienting IMAP-Lo on the spacecraft: + + [X] [ -0.866025 -0.500000 0.000000 ] [X'] + [Y] = [ 0.500000 -0.866025 0.000000 ] [Y'] + [Z]S/C [ 0.000000 0.000000 1.000000 ] [Z']IMAP-Lo + + consistent with calculating the matrix R to single precision. + + For reference, the ZYZ intrinsic Euler angles orienting X'Y'Z' in + the spacecraft XYZ coordinate system are: + + IMAP-Lo: (A, B, Y) = ( 150.000°, 0.000°, 0.000° ) + + Using the formulas described in the Euler angles section above, the + rotation matrix generated from these Euler angles is consistent with + the rotation matrix using the azimuth/elevation look direction. + + + IMAP-Lo Orientation + --------------------------------------------------------------------- + + The orientation of IMAP-Lo must be specified in a separate C-kernel. + To facilitate this specification, a base frame representing the fixed + transformation of the local X'Y'Z' frame to the spacecraft frame has + been provided. + + Ideally, the C-kernel will simply specify transformation within the + local IMAP-Lo frame, and be generated using only the pivot angle. + The implementation of this is outside the scope of this kernel. + + + \begindata + + FRAME_IMAP_LO_BASE = -43100 + FRAME_-43100_NAME = 'IMAP_LO_BASE' + FRAME_-43100_CLASS = 4 + FRAME_-43100_CLASS_ID = -43100 + FRAME_-43100_CENTER = -43 + TKFRAME_-43100_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43100_SPEC = 'MATRIX' + TKFRAME_-43100_MATRIX = ( -0.86602540378443865, + 0.50000000000000000, + 0.00000000000000000, + -0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000, + 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000 ) + + FRAME_IMAP_LO = -43101 + FRAME_-43101_NAME = 'IMAP_LO' + FRAME_-43101_CLASS = 3 + FRAME_-43101_CLASS_ID = -43101 + FRAME_-43101_CENTER = -43 + + FRAME_IMAP_LO_STAR_SENSOR = -43102 + FRAME_-43102_NAME = 'IMAP_LO_STAR_SENSOR' + FRAME_-43102_CLASS = 4 + FRAME_-43102_CLASS_ID = -43102 + FRAME_-43102_CENTER = -43 + TKFRAME_-43102_RELATIVE = 'IMAP_LO' + TKFRAME_-43102_SPEC = 'MATRIX' + TKFRAME_-43102_MATRIX = ( 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 ) + + \begintext + + +IMAP-Hi Frames +======================================================================== + + IMAP-Hi consists of two identical, single-pixel high energy neutral + atom (ENA) imagers. Hi 90 is oriented with its boresight + perpendicular to the spacecraft spin axis, while Hi 45 is radially + outward but with the boresight angled 45° from the -Z axis. + + --------- + | +X axis | + --------- -------------------- + . | +Z axis facing Sun | + HI 45 BORESIGHT /|\ | into page | + . | . -------------------- + " .~15°~.| .~15°~." + \ | / HI 90 BORESIGHT + , | , + ; /`~~__ , `+ direction of + , = .^ - /_ ``-. '. positive + .+ + `^~/ ./ ~ rotation + ^ + + . -- ' `` \ _-~ \ + _ / ',= ' \~'` \ IMAP \ + ULTRA /' '-_ .~ ' \,.=.. \ LO \|/ + 90 / ~ _,.,_ + + \ ' + / ,~' +' `'+ + + \ + / ~^ .' , = .'. '- ='' -`` --------- + ^/ / , = . + + \ \~'` | +Y axis |-----> + | . + + + + . \ --------- ___ + | | + + ' = ' | \--------------------| | + SWAPI| | ' = ', - . | /--------------------|___| + _+_: ' + + ' / MAG boom + \_ __\__ \ + + / /^*~, + + | SWE '. ' = ' .' ULTRA / + `~-' '~..,___,..~' 45 /~,* + _\ / /~,*` + * / CODICE ^*._/ *` HIT + *\ _/`. / + * / /~ _ _ ,.-^-., _ _ _ / + '=' + + + GLOWS + + + '-.,.-' + IDEX + + + --------- + | +Z axis | + --------- --------------------- + . | +X axis out of page | + /|\ --------------------- + | LGA + __________________|______|^|_________ ___ + SWAPI|__________________|__________________|====================| | + #|-| | | .-==-, | / MAG boom '---' + #|-| {|## | | / \ | | + | {|## | |{ HI 90 }| IMAP LO| + | {|## | _.._ | \ / | _., | + | ULTRA | / \ | `-==-' | / __`',| + | 90 | \ HI 45/ | | \ \_\ ;| + | | '----` | | ~._ + | + '-------------------|----------/--------' + | | \_________O_________/ | | ----------------> + |__| ----------- /_\ --------- + STAR | S/C FRAME | MGA | +Y axis | + TRACKERS | ORIGIN | --------- + ----------- + + + ##################################################################### + #______________________________________________ # + # / _ | || | IMAP HI 90 # + #----~. / |_| O o | || |==== hidden # + # ULTRA 90 /\ x x = | || | behind s/c # + # / \__________| || || <---- struct here # + # ##### -- #### | || |] # + # ## % ## / \###\ / ___ || |} HI 90 Boresight # + # /## % ##\--|####| |____|*#*| || |}_________________ # + # |## % ##|--|####| | |*#*| || |} # + # |## % ##| |####| | --- || |} # + # |## % ##|--|####| | || |] +Z # + # |## % ##|--|####| \ || || # + # \## % ##/ |####| | || . | # + # ## % ## \ /###/ | || .'. | # + # ##### -- #### | || . /, | # + #--------------- | |.` _~ x------ +X # + # | ,` ,~` `~ +Y into # + # ______ / .` ~` _ \ page # + # .=.=.=.=. |( ) ()| / ___ * -' ~' `', | # + # | | | | | |( ) ()| |____|*#*|| ~ .`_ _ / ~ # + #__#_#_#_#_#__|______| |*#*||` / //// / ~ # + #----------------| .----- / ` ` ' ~ # + # |_ _ _| | | .'_ / '`.,_ ,~' ~. # + # | | | | | | .' -, | _, ` ":. # + #__/_/_/_/___/___|________|______;_\_ ,.-' |:. # + # | | / ":. # + #_______________________________________| | _45° ":. # + #___ ____ __ || || || | |-~" " # + # / / / / // |_____||_____||_____| | HI 45 # + #_/ /___/ /_/ | /|\ \|/ Boresight # + # / ' # + # | --------- # + # / | -Z axis | # + #=========== --------- # + ##################################################################### + + + The local IMAP-Hi frame[15]--identical for both sensors--is defined + with the boresight aligned with the +Y' axis, the rectangular vent + ports aligned with the +Z' axis, and X' = Y' x Z'. + + + IMAP HI 45 + -------------- + + The boresight look-direction is defined for the azimuth-elevation: + + HI 45 (azim, elev) = ( +255°, -45° ) + + The boresight direction is the +Y' local axis of instrument, and the + primary axis in the spacecraft frame of reference is: + + D = +Y' = [ -cos(-45) x sin(255), cos(-45) x cos(255), sin(-45) ] + + The secondary axis is the +Z' local axis, NOTIONALLY perpendicular to + both the boresight direction D and the spacecraft Z axis: + + S = +Z' = D x Z = Y' x [ 0, 0, 1 ] + + The tertiary axis is NOTIONALLY: + + N = D x S = Y' x ( Y' x [ 0, 0, 1 ] ) + + The rotation matrix formed using the column vectors is NOTIONALLY: + + RN = [ +N, +D, +S ] + + HOWEVER, the actual alignment is modified by a rotation about the + local Y' axis by 3° as a consequence of the angular offset of the + mounting inserts by the same amount. This rotation about local Y' is: + + [ cos(3) 0 sin(3) ] + RY' = [ 0 1 0 ] + [ -sin(3) 0 cos(3) ] + + The final rotation that orients HI 45 on the spacecraft is the matrix + multiplication: + + R = RN x RY' + + From the spacecraft MICD[6], the single-precision rotation matrices + orienting IMAP-HI 45 on the spacecraft: + + [X] [ -0.668531 0.683013 -0.294210 ] [X'] + [Y] = [ 0.233315 -0.183013 -0.955024 ] [Y'] + [Z]S/C [ -0.706183 -0.707107 -0.037007 ] [Z']HI 45 + + Using the method described in a Euler discussion section, the Euler + angles rounded to 1/1000th of a degree are: + + HI 45: (A, B, Y) = ( -17.122°, 92.121°, -135.037° ) + + Using the formulas described in the Euler angles section above, the + rotation matrix generated from these Euler angles is consistent with + the rotation matrix using the azimuth/elevation look direction; + however, the full double-precision Euler angles are necessary to + generate the proper precise rotation matrix. + + + \begindata + + FRAME_IMAP_HI_45 = -43150 + FRAME_-43150_NAME = 'IMAP_HI_45' + FRAME_-43150_CLASS = 4 + FRAME_-43150_CLASS_ID = -43150 + FRAME_-43150_CENTER = -43 + TKFRAME_-43150_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43150_SPEC = 'MATRIX' + TKFRAME_-43150_MATRIX = ( -0.66853111450276550, + 0.23331454112339850, + -0.70613771591812640, + 0.68301270189221940, + -0.18301270189221924, + -0.70710678118654750, + -0.29421046547595930, + -0.95502391375634550, + -0.03700710955926802 ) + + \begintext + + + IMAP HI 90 + -------------- + + The boresight look-direction is defined for the azimuth-elevation: + + HI 90 (azim, elev) = ( +285°, 0° ) + + The boresight direction is the +Y' local axis of instrument, and the + primary axis in the spacecraft frame of reference is: + + D = +Y' = [ -cos(0) x sin(285), cos(0) x cos(285), sin(0) ] + + The secondary axis is the +Z' local axis, NOTIONALLY perpendicular to + both the boresight direction D and the spacecraft Z axis: + + S = -Z' = D x Z = -Y' x [ 0, 0, 1 ] + + The tertiary axis is NOTIONALLY: + + N = D x S = Y' x ( Y' x [ 0, 0, 1 ] ) + + The rotation matrix formed using the column vectors is NOTIONALLY: + + RN = [ +N, +D, +S ] + + HOWEVER, the actual alignment is modified by a rotation about the + local Y' axis by 15° as a consequence of the angular offset of the + mounting inserts by the same amount. This rotation about local Y' is: + + [ cos(15) 0 sin(15) ] + RY' = [ 0 1 0 ] + [ -sin(15) 0 cos(15) ] + + The final rotation that orients HI 45 on the spacecraft is the matrix + multiplication: + + R = RN x RY' + + From the spacecraft MICD[6], the single-precision rotation matrices + orienting IMAP-HI 45 on the spacecraft: + + [X] [ 0.066987 0.965926 -0.250000 ] [X'] + [Y] = [ -0.250000 0.258819 0.933013 ] [Y'] + [Z]S/C [ 0.965926 0.000000 0.258819 ] [Z']HI 90 + + Using the method described in a Euler discussion section, the Euler + angles rounded to 1/1000th of a degree are: + + HI 90: (A, B, Y) = ( -165.000°, 75.000°, 90.000° ) + + Using the formulas described in the Euler angles section above, the + rotation matrix generated from these Euler angles is consistent with + the rotation matrix using the azimuth/elevation look direction. + + + \begindata + + FRAME_IMAP_HI_90 = -43151 + FRAME_-43151_NAME = 'IMAP_HI_90' + FRAME_-43151_CLASS = 4 + FRAME_-43151_CLASS_ID = -43151 + FRAME_-43151_CENTER = -43 + TKFRAME_-43151_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43151_SPEC = 'MATRIX' + TKFRAME_-43151_MATRIX = ( 0.06698729810778055, + -0.25000000000000000, + 0.96592582628906829, + 0.96592582628906829, + 0.25881904510252074, + 0.00000000000000000, + -0.25000000000000000, + 0.93301270189221940, + 0.25881904510252074 ) + + \begintext + + +IMAP-Ultra Frames +======================================================================== + + The IMAP-Ultra instrument[7,14] consists of two identical sensors for + imaging the emission of energetic neural atoms (ENAs) produced in the + heliosheath and beyond. Ultra 90 is mounted perpendicular to the IMAP + spin axis (+Z), while Ultra 45 is mounted at 45 degrees from the + anti-sunward spin axis (-Z). + + + --------- + | +X axis | -------------------- + --------- | +Z axis facing Sun | + . | into page | + /|\ -------------------- + | + | + | + _ + HI 45 /`~~__HI 90 `+ direction of + , = .^ - /_ ``-. '. positive + .+ + `^~/ ./ ~ rotation + ULTRA ^ + + . -- ' `` \ _-~ \ + 90 _ / ',= ' \~'` \ IMAP \ + . /' '-_ .~ ' \,.=.. \ LO \|/ + `;. / ~ _,.,_ + + \ ' + / `/ ,~' +' `'+ + + \ + 30° / ~^ .' , = .'. '- ='' -`` --------- + | ^/ / , = . + + \ \~'` | +Y axis |-----> + ---- | . + + + + . \ --------- ___ + | | + + ' = ' | \--------------------| | + SWAPI| | ' = ', - . | /--------------------|___| + _+_: ' + + ' / | MAG boom + \_ __\__ \ + + / /^*~, . + + | SWE '. ' = ' .' ULTRA / 33° + `~-' '~..,___,..~' 45 / ; + _\ / /`., / + * / CODICE ^*._/ `'./ + *\ _/`. / `'. + * / /~ _ _ ,.-^-., _ _ _ / + '=' + + + GLOWS + + + '-.,.-' + IDEX + + + Each sensor comprises two separate assemblies of collector plates. + Each assembly of collector plates is fanned out in a cylindrical + pattern, and the cyclindrical axes of the fanned-out plates are + parallel and offset in the direction perpendicular to the axes. + + The orientations of Ultra 45 and 90 are analogous to IMAP Hi 45 and + 90; see the diagram for IMAP Hi above. Take special note that the + angle with the spacecraft Z axis and the boresights for IMAP Hi are + the same as the angle with the spacecraft Z axis and the "outward" + directions for Ultra. + + + ##################################################################### + # # + # One half of one IMAP Ultra sensor showing # + # assembly of fanned-out collector plates # + # Outward # + # . Assemblies are mirror-symmetric # + # /|\ about the leftmost edge of drawing # + # | # + # | ,--. , # + # || | | ; , 63.42° FOR # + # | | | | ; ; ; 60.31° FOV # + # | | | | : ; ; ; ; # + # \|/ | | | : ; ; ; / # + # ' | ;_ _|_ ; ; ; ; / / . # + # S/C | | ``'''^-,/, / / / .' # + # | | ___ `''., / / . . # + #_________;-|__|_ `'"^~-,._ /^~ `^., / ,' ' . # + #---------'- | |_| `'":., _ `^, . ,' .' # + #--------. ,-| | @ `'~/ \ `'. .` .' , # + # ,'`.' | | @ @ @ @ '~,.;, '. .' .' # + # .',' | | @ @ @ @ @ `;, /~_':' .' ,' # + #.'`,' _| |_ @ @ @ `;, / '. .' ,'` # + # `, |_|-|_| @ @ '. '. .' ,. # + #'-,'. |-| @ @ @ ', `.` ;' # + # '.'-. | | @ @ ;, \,;`' .-`# + # `-.'. | | @ @ ", ', ,.'` ,^# + #_________:'-| | @ @ @ @ :, _,\' .-`` # + #-----------||-|-, @ /~,".' ;'.' # + #=== ||---|@ @ @ @ @ ,\ ' ,.^` # + #___________||_/-~_ _ @ @ _, _,-' ,.-'` # + # @| | | || `- , @ @ \,\' ,'` +Z' # + #----' | | || `~,@ @ ,~`' _,'` # + # | | || ',@ .^\_,'` ,.'` | # + #______'-'__||-@--~-~, \ .;` .'` | # + #___________||/ ~ # ~ | {.'` | # + # |* ||*| + <------------ Collector plate +------ +Y'# + #____ --||\ ~ ~ | axis of symmetry +X' out # + #_ *| ||-@-^~-~^-------| of page # + #*| | ||_______________| # + #___*|_______|_|_|__|__|_|_| | # + ##################################################################### + + + The local IMAP-Ultra frame[14]--identical for both sensors--is + defined with the collector-plate-fan axes of symmetry aligned with + the +X' axis, the cylindrical axes offset in the +Y' axis, and the + Z' axis perpendicular to both and outward as in the diagram below. + + + IMAP ULTRA 45 + -------------- + + The outward look-direction is defined for the azimuth-elevation: + + ULTRA 45 (azim, elev) = ( +33°, -45° ) + + The look-direction is the +Z' local axis of instrument, and the + primary axis in the spacecraft frame of reference is: + + D = +Z' = [ -cos(-45) x sin(33), cos(-45) x cos(33), sin(-45) ] + + The secondary axis is the +X' local axis, lying in the plane spanned + by the look-direction D and the spacecraft Z axis. An equivalent + definition is selecting the secondary axis as the +Y' local axis, + perpendicular to both the look-direction D and the spacecraft Z axis. + + S = +Y' = D x Z = Z' x [ 0, 0, 1 ] + + The tertiary axis is: + + N = D x S = Z' x Y' = Z' x ( Z' x [ 0, 0, 1 ] ) + + The rotation matrix formed using the column vectors is: + + R = [ -N, +S, +D ] + + The rotation matrices orienting the IMAP-Ultra 45 sensor on the + spacecraft is given by [6]: + + [X] [ -0.385118 0.838671 -0.385118 ] [X'] + [Y] = [ 0.593030 0.544639 0.593030 ] [Y'] + [Z]S/C [ 0.707107 0.000000 -0.707107 ] [Z']ULTRA 45 + + Using the method described in a Euler discussion section, the Euler + angles rounded to 1/1000th of a degree are: + + ULTRA 45: (A, B, Y) = ( -147.000°, 135.000°, 90.000° ) + + Using the formulas described in the Euler angles section above, the + rotation matrix generated from these Euler angles is consistent with + the rotation matrix using the azimuth/elevation look direction. + + + \begindata + + FRAME_IMAP_ULTRA_45 = -43200 + FRAME_-43200_NAME = 'IMAP_ULTRA_45' + FRAME_-43200_CLASS = 4 + FRAME_-43200_CLASS_ID = -43200 + FRAME_-43200_CENTER = -43 + TKFRAME_-43200_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43200_SPEC = 'MATRIX' + TKFRAME_-43200_MATRIX = ( -0.38511795495802310, + 0.59302964577578240, + 0.70710678118654760, + 0.83867056794542390, + 0.54463903501502710, + 0.00000000000000000, + -0.38511795495802320, + 0.59302964577578250, + -0.70710678118654750 ) + + \begintext + + + IMAP ULTRA 90 + -------------- + + The outward look-direction is defined for the azimuth-elevation: + + ULTRA 90 (azim, elev) = ( +210°, 0° ) + + The look-direction is the +Z' local axis of instrument, and the + primary axis in the spacecraft frame of reference is: + + D = +Z' = [ -cos(0) x sin(210), cos(0) x cos(210), sin(0) ] + + The secondary axis is the +X' local axis, lying along spacecraft + -Z axis. + + S = +X' = +Z = [ 0, 0, 1 ] + + The tertiary axis is: + + N = D x S = Z' x X' = Z' x [ 0, 0, 1 ] + + The rotation matrix formed using the column vectors is: + + R = [ +S, +N, +D ] + + The rotation matrices orienting the IMAP-Ultra 90 sensor on the + spacecraft is given by [6]: + + [X] [ 0.000000 -0.866025 0.500000 ] [X'] + [Y] = [ 0.000000 -0.500000 -0.866025 ] [Y'] + [Z]S/C [ 1.000000 0.000000 0.000000 ] [Z']ULTRA 90 + + Using the method described in a Euler discussion section, the Euler + angles rounded to 1/1000th of a degree are: + + ULTRA 90: (A, B, Y) = ( 30.000°, 90.000°, 90.000° ) + + Using the formulas described in the Euler angles section above, the + rotation matrix generated from these Euler angles is consistent with + the rotation matrix using the azimuth/elevation look direction. + + + \begindata + + FRAME_IMAP_ULTRA_90 = -43201 + FRAME_-43201_NAME = 'IMAP_ULTRA_90' + FRAME_-43201_CLASS = 4 + FRAME_-43201_CLASS_ID = -43201 + FRAME_-43201_CENTER = -43 + TKFRAME_-43201_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43201_SPEC = 'MATRIX' + TKFRAME_-43201_MATRIX = ( 0.00000000000000000, + 0.00000000000000000, + 1.00000000000000000, + -0.86602540378443865, + -0.50000000000000000, + 0.00000000000000006, + 0.50000000000000000, + -0.86602540378443865, + 0.00000000000000000 ) + + \begintext + + +IMAP Magnetometer (MAG) Frames +======================================================================== + + The IMAP magnetometer (MAG)[7,16] consists of a pair of identical + triaxial fluxgate magnetometers mounted on a ~2.5 meter boom. MAG-O + is positioned at the end of the boom, while MAG-I is mounted ~0.75 + meters from MAG-O. + + + --------- + | +X axis | -------------------- + --------- | +Z axis facing Sun | + . | into page | + /|\ -------------------- + | + | + | + _ + HI 45 /`~~__HI 90 `+ direction of + , = .^ - /_ ``-. '. positive + .+ + `^~/ ./ ~ rotation + ^ + + . -- ' `` \ _-~ \ + _ / ',= ' \~'` \ IMAP \ + ULTRA /' '-_ .~ ' \,.=.. \ LO \|/ + 90 / ~ _,.,_ + + \ ' + / ,~' +' `'+ + + \ + / ~^ .' , = .'. '- ='' -`` + ^/ / , = . + + \ \~'` +Y axis -----> + | . + + + + . \ ___ ___ + | | + + ' = ' | \------------| |---| | + SWAPI| | ' = ', - . | /------------|___|---|___| + _+_: ' + + ' / MAG-I MAG-O + \_ __\__ \ + + / /^*~, + + | SWE '. ' = ' .' ULTRA / MAGS and boom + `~-' '~..,___,..~' 45 /~,* not to scale + _\ / /~,*` + * / CODICE ^*._/ *` HIT + *\ _/`. / + * / /~ _ _ ,.-^-., _ _ _ / + '=' + + + GLOWS + + + '-.,.-' + IDEX + + + ---------------------------- + S/C +Z axis | Deployed Magnetometer Boom | S/C +X axis + . | (approximately to scale) | out of page + /|\ ---------------------------- + | + | S/C +Y axis --------> + @================================================================= + #\ | | | | + \ `'` `'` + Boom Deployment Hinge MAG-I MAG-O + + +X' ------x +Y' into + | page + MAG Local | + Coord System | + + +Z' + + + Each MAG instrument is contained in a cylindrial casing with the + local Z' axis along the cylindrical axis of symmetry. The local X' + axis is along the boom, and the local Y' axis is perp to the boom. + + When deployed, the boom sticks out in the +Y axis of the spacecraft, + with the MAG +X' axis in the -Y direction. The MAG +Z' axis is in the + spacecraft -Z' direction, and +Y' is spacecraft -X. + + [X] [ 0 -1 0 ] [X'] + [Y] = [ -1 0 0 ] [Y'] + [Z]S/C [ 0 0 -1 ] [Z']MAG deployed + + Prior to deployment, the boom is stowed pointing in the -Y direction + of the spacecraft, with the MAG +X' axis in the +Y direction. The MAG + +Z' axis is in the spacecraft +Z' direction, and +Y' is spacecraft -X + + [X] [ 0 +1 0 ] [X'] + [Y] = [ -1 0 0 ] [Y'] + [Z]S/C [ 0 0 +1 ] [Z']MAG undeployed + + To facilitate possible operations prior to the boom deployment, a + frame for the deployed boom is provided; the MAG-I and MAG-O frames + are provided relative to this frame. If needed, the IMAP_MAG_BOOM + can be modified to facilitate arbitrary operational reality. + + + \begindata + + FRAME_IMAP_MAG_BOOM = -43250 + FRAME_-43250_NAME = 'IMAP_MAG_BOOM' + FRAME_-43250_CLASS = 4 + FRAME_-43250_CLASS_ID = -43250 + FRAME_-43250_CENTER = -43 + TKFRAME_-43250_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43250_SPEC = 'MATRIX' + TKFRAME_-43250_MATRIX = ( 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 ) + + FRAME_IMAP_MAG_I = -43251 + FRAME_-43251_NAME = 'IMAP_MAG_I' + FRAME_-43251_CLASS = 4 + FRAME_-43251_CLASS_ID = -43251 + FRAME_-43251_CENTER = -43 + TKFRAME_-43251_RELATIVE = 'IMAP_MAG_BOOM' + TKFRAME_-43251_SPEC = 'MATRIX' + TKFRAME_-43251_MATRIX = ( 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1 ) + + FRAME_IMAP_MAG_O = -43252 + FRAME_-43252_NAME = 'IMAP_MAG_O' + FRAME_-43252_CLASS = 4 + FRAME_-43252_CLASS_ID = -43252 + FRAME_-43252_CENTER = -43 + TKFRAME_-43252_RELATIVE = 'IMAP_MAG_BOOM' + TKFRAME_-43252_SPEC = 'MATRIX' + TKFRAME_-43252_MATRIX = ( 0, + -1, + 0, + -1, + 0, + 0, + 0, + 0, + -1 ) + + \begintext + + +IMAP Solar Wind Electron (SWE) Frames +======================================================================== + + TODO: FIX ME...The orientation of the spacecraft body frame with + respect to an inertial + frame, for IMAP - ECLIPJ2000, is provided by a C-kernel (see [3] + for details). + + This frame specifies the rotating X,Y and pointing Z coordinate body + frame. + + \begindata + + FRAME_IMAP_SWE = -43300 + FRAME_-43300_NAME = 'IMAP_SWE' + FRAME_-43300_CLASS = 4 + FRAME_-43300_CLASS_ID = -43300 + FRAME_-43300_CENTER = -43 + TKFRAME_-43300_SPEC = 'MATRIX' + TKFRAME_-43300_MATRIX = ( 0.453990, + 0.891007, + 0.000000, + -0.891007, + 0.453990, + 0.000000, + 0.000000, + 0.000000, + 1.000000 ) + TKFRAME_-43300_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + +IMAP Solar Wind and Pickup Ion (SWAPI) Frames +======================================================================== + + TODO: add diagrams + + SWAPI has the following nominal alignment to the spacecraft frame, + reference Table 1 of [6]. The azimuth and elevation angles are + illustrated in the 'IMAP I&T Component Placement' section near the top + of this document. + + azimuth | elevation + (deg) | (deg) + ---------+--------- + 168 | 0 + + The SWAPI base frame is defined in the instrument MICD [8] as follows: + + * -Z axis is the axis of symmetry of the instrument, pointing + away from the spacecraft body. + * +Y axis is along the aperture center, in the anti-sunward direction. + + The azimuth and elevation give the outward axis of symmetry, -Z in the + instrument frame: + + -Z = -[ -sin(az) * cos(el), cos(az) * cos(el), sin(el) ] + instr + + The instrument +Y axis is in the sunward direction, towards the + spacecraft +Z axis: + + Y = [ 0 0 1 ] + instr + + Taking the cross product and normalizing, we arrive at the instrumet +X + axis: + Y x Z + X = --------- + instr | Y x Z | + + And adjusting Y: + + Z x X + Y = --------- + instr | Z x X | + + This definition is captured in the keywords below. + + \begindata + + FRAME_IMAP_SWAPI = -43350 + FRAME_-43350_NAME = 'IMAP_SWAPI' + FRAME_-43350_CLASS = 4 + FRAME_-43350_CLASS_ID = -43350 + FRAME_-43350_CENTER = -43 + TKFRAME_-43350_SPEC = 'MATRIX' + TKFRAME_-43350_MATRIX = ( -0.97814760073381, + 0.20791169081776, + 0.00000000000000, + 0.00000000000000, + 0.00000000000000, + 1.00000000000000, + 0.20791169081776, + 0.97814760073381, + 0.00000000000000 ) + TKFRAME_-43350_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + +IMAP Compact Dual Ion Composition Experiment (CoDICE) Frames +======================================================================== + + TODO: FIX ME...The orientation of the spacecraft body frame with + respect to an inertial + frame, for IMAP - ECLIPJ2000, is provided by a C-kernel (see [3] + for details). + + This frame specifies the rotating X,Y and pointing Z coordinate body + frame. + + \begindata + + FRAME_IMAP_CODICE = -43400 + FRAME_-43400_NAME = 'IMAP_CODICE' + FRAME_-43400_CLASS = 4 + FRAME_-43400_CLASS_ID = -43400 + FRAME_-43400_CENTER = -43 + TKFRAME_-43400_SPEC = 'MATRIX' + TKFRAME_-43400_MATRIX = ( 0.694626, + 0.719371, + 0.000000, + -0.719371, + 0.694626, + 0.000000, + 0.000000, + 0.000000, + 1.000000 ) + TKFRAME_-43400_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + +IMAP High-energy Ion Telescope (HIT) Frames +======================================================================== + + TODO: FIX ME...The orientation of the spacecraft body frame with + respect to an inertial + frame, for IMAP - ECLIPJ2000, is provided by a C-kernel (see [3] + for details). + + This frame specifies the rotating X,Y and pointing Z coordinate body + frame. + + \begindata + + FRAME_IMAP_HIT = -43500 + FRAME_-43500_NAME = 'IMAP_HIT' + FRAME_-43500_CLASS = 4 + FRAME_-43500_CLASS_ID = -43500 + FRAME_-43500_CENTER = -43 + TKFRAME_-43500_SPEC = 'MATRIX' + TKFRAME_-43500_MATRIX = ( 0.866025, + 0.500000, + 0.000000, + -0.500000, + 0.866025, + 0.000000, + 0.000000, + 0.000000, + 1.000000 ) + TKFRAME_-43500_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + +IMAP Interstellar Dust Experiment (IDEX) Frames +======================================================================== + + TODO: FIX ME...The orientation of the spacecraft body frame with + respect to an inertial + frame, for IMAP - ECLIPJ2000, is provided by a C-kernel (see [3] + for details). + + This frame specifies the rotating X,Y and pointing Z coordinate body + frame. + + \begindata + + FRAME_IMAP_IDEX = -43700 + FRAME_-43700_NAME = 'IMAP_IDEX' + FRAME_-43700_CLASS = 4 + FRAME_-43700_CLASS_ID = -43700 + FRAME_-43700_CENTER = -43 + TKFRAME_-43700_SPEC = 'MATRIX' + TKFRAME_-43700_MATRIX = ( 0.000000, + 1.000000, + 0.000000, + -0.707107, + 0.000000, + -0.707107, + -0.707107, + 0.000000, + 0.707107 ) + TKFRAME_-43700_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + +IMAP GLObal solar Wind Structure (GLOWS) Frames +======================================================================== + + TODO: add diagrams + + GLOWS has the following nominal alignment to the spacecraft frame, + reference Table 1 of [6]. The azimuth and elevation angles are + illustrated in the 'IMAP I&T Component Placement' section near the top + of this document. + + azimuth | elevation + (deg) | (deg) + ---------+--------- + 127 | 15 + + The GLOWS base frame is defined by the instrument team as follows [10]: + + * +Z axis points in the anti-boresight direction + * +Y axis points in the anti-sunward direction. + + The azimuth and elevation give the outward axis of symmetry, -Z in the + instrument frame: + + Z = -[ -sin(az) * cos(el), cos(az) * cos(el), sin(el) ] + instr + + The instrument +Y axis is in the anti-sunward direction, towards the + spacecraft -Z axis: + + Y = [ 0 0 -1 ] + instr + + Taking the cross product and normalizing, we arrive at the instrumet +X + axis: + Y x Z + X = --------- + instr | Y x Z | + + And adjusting Y: + + Z x X + Y = --------- + instr | Z x X | + + This definition is captured in the keywords below. + + \begindata + + FRAME_IMAP_GLOWS = -43751 + FRAME_-43751_NAME = 'IMAP_GLOWS' + FRAME_-43751_CLASS = 4 + FRAME_-43751_CLASS_ID = -43751 + FRAME_-43751_CENTER = -43 + TKFRAME_-43751_SPEC = 'MATRIX' + TKFRAME_-43751_MATRIX = ( 0.60181502315205, + -0.79863551004729, + 0.00000000000000, + -0.20670208009540, + -0.15576118962056, + -0.96592582628907, + 0.77142266494622, + 0.58130867351132, + -0.25881904510252 ) + TKFRAME_-43751_RELATIVE = 'IMAP_SPACECRAFT' + +\begintext + + Generic axis + + +Z axis + | + | + | + | + | + | + | + | + | + | + | + | + .~ ~ + .~ '` ^~ + .~ '` ^~ + .~ '` ^~ + .~ '` ^~ + +X axis ^~ + ^~ + ^~ + +Y axis + +End of FK file. \ No newline at end of file diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/imap_science_100.tf b/imap_processing/ultra/l1c/sim_spice_kernels/imap_science_100.tf new file mode 100644 index 000000000..b0f16b504 --- /dev/null +++ b/imap_processing/ultra/l1c/sim_spice_kernels/imap_science_100.tf @@ -0,0 +1,1041 @@ +KPL/FK + +Interstellar Mapping and Acceleration Probe (IMAP) Dynamic Frames Kernel +======================================================================== + + This kernel contains SPICE frame definitions to support the + IMAP mission. + + This kernel is composed of primarily dynamic frames, but in general + it holds frame definitions for all instrument-agnostic frames, CK + frames used in science data processing and mapping. + + +Version and Date +------------------------------------------------------------------------ + + The TEXT_KERNEL_ID stores version information of loaded project + text kernels. Each entry associated with the keyword is a string + that consists of four parts: the kernel name, version, entry date, + and type. + + IMAP Dynamic Frame Kernel Version: + + \begindata + + TEXT_KERNEL_ID = 'IMAP_DYNAMIC_FRAMES V0.0.1 2025-JUNE-26 FK' + + \begintext + + Version 0.0.0 -- April 10, 2024 -- Nick Dutton (JHU/APL) + Version 0.0.1 -- June 26, 2025 -- Nick Dutton (JHU/APL) + Version 1.0.0 -- July 8, 2025 -- Nick and Doug (JHU/APL) + + +References +------------------------------------------------------------------------ + + 1. NAIF SPICE `Kernel Pool Required Reading' + + 2. NAIF SPICE `Frames Required Reading' + + 3. "IMAP Coordinate Frame Science.pdf" + + 4. stereo_rtn.tf, at + https://soho.nascom.nasa.gov/solarsoft/stereo/... + ...gen/data/spice/gen/stereo_rtn.tf + + 5. heliospheric.tf, at + https://soho.nascom.nasa.gov/solarsoft/stereo/... + ...gen/data/spice/gen/heliospheric.tf + + 6. "Geophysical Coordinate Transformations", C. T. Russell + + 7. "Heliospheric Coordinate Systems", M. Franz and D. Harper + + 8. "Global observations of the interstellar interaction from the + Interstellar Boundary Explorer (IBEX)", D. J. McComas, et al. + + 9. "Very Local Interstellar Medium Revealed by a Complete Solar + Cycle of Interstellar Neutral Helium Observations with IBEX", + P. Swaczyna, et al. + + 10. Lagrange L1 definition and SPK, Min-Kun Chung, + https://naif.jpl.nasa.gov/pub/naif/... + ...generic_kernels/spk/lagrange_point/ + + +Contact Information +------------------------------------------------------------------------ + + Direct questions, comments, or concerns about the contents of this + kernel to: + + Nick Dutton, JHUAPL, Nicholas.Dutton@jhuapl.edu + + or + + Doug Rodgers, JHUAPL, Douglas.Rodgers@jhuapl.edu + + or + + Lillian Nguyen, JHUAPL, Lillian.Nguyen@jhuapl.edu + + +Implementation Notes +------------------------------------------------------------------------ + + This file is used by the SPICE system as follows: programs that make + use of this frame kernel must `load' the kernel normally during + program initialization. Loading the kernel associates the data items + with their names in a data structure called the `kernel pool'. The + SPICELIB routine FURNSH loads a kernel into the pool as shown below: + + Python: (SpiceyPy) + + spiceypy.furnsh( frame_kernel_name ) + + IDL: (ICY) + + cspice_furnsh, frame_kernel_name + + MATLAB: (MICE) + + cspice_furnsh ( frame_kernel_name ) + + C: (CSPICE) + + furnsh_c ( frame_kernel_name ); + + FORTRAN: (SPICELIB) + + CALL FURNSH ( frame_kernel_name ) + + This file was created, and may be updated with, a text editor or word + processor. + + +IMAP Science Frames +======================================================================== + + This frame kernel defines a series of frames listed in [3] that + support IMAP data reduction and analysis. All of the frame names + assigned an IMAP NAIF ID (beginning with -43) defined by this kernel + are prefixed with 'IMAP_' to avoid conflict with alternative + definitions not specific to the project. + + The project-specific ID codes -43900 to -43999 have been set aside to + support these dynamic frames. + + + Frame Name Relative To Type NAIF ID + ====================== =============== ======== ======= + + IMAP Based Frames: + ---------------------- + IMAP_OMD IMAP_SPACECRAFT FIXED -43900 + IMAP_DPS [n/a] CK -43901 + + Earth Based Frames: + ---------------------- + IMAP_EARTHFIXED IAU_EARTH FIXED -43910 + IMAP_ECLIPDATE J2000 DYNAMIC -43911 + IMAP_MDI ECLIPJ2000 FIXED -43912 + IMAP_MDR J2000 DYNAMIC -43913 + IMAP_GMC IAU_EARTH DYNAMIC -43914 + IMAP_GEI J2000 FIXED -43915 + IMAP_GSE J2000 DYNAMIC -43916 + IMAP_GSM J2000 DYNAMIC -43917 + IMAP_SMD J2000 DYNAMIC -43918 + + Sun Based Frames: + ---------------------- + IMAP_RTN J2000 DYNAMIC -43920 + IMAP_HCI (ie, HGI_J2K) J2000 DYNAMIC -43921 + IMAP_HCD (ie, HGI_D) J2000 DYNAMIC -43922 + IMAP_HGC (ie, HGS_D) IAU_SUN FIXED -43923 + IMAP_HAE ECLIPJ2000 FIXED -43924 + IMAP_HAED IMAP_ECLIPDATE FIXED -43925 + IMAP_HEE J2000 DYNAMIC -43926 + IMAP_HRE J2000 DYNAMIC -43927 + IMAP_HNU J2000 DYNAMIC -43928 + IMAP_GCS GALACTIC FIXED -43929 + + +IMAP Based Frames +======================================================================== + + These dynamic frames are used for analyzing data in a reference + frame tied to the dynamics of IMAP. + + + Observatory Mechanical Design (OMD) Frame ([3]) + --------------------------------------------------------------------- + + Alias for IMAP_SPACECRAFT frame defined in the primary + 'imap_vNNN.tf' frame kernel. From that file: + + Origin: Center of the launch vehicle adapter ring at the + observatory/launch vehicle interface plane + + +Z axis: Perpendicular to the launch vehicle interface plane + pointed in the direction of the top deck (runs through + the center of the central cylinder structure element) + + +Y axis: Direction of the vector orthogonal to the +Z axis and + parallel to the deployed MAG boom + + +X axis: The third orthogonal axis defined using an X, Y, Z + ordered right hand rule + + \begindata + + FRAME_IMAP_EARTHFIXED = -43900 + FRAME_-43900_NAME = 'IMAP_OMD' + FRAME_-43900_CLASS = 4 + FRAME_-43900_CLASS_ID = -43900 + FRAME_-43900_CENTER = -43 + TKFRAME_-43900_RELATIVE = 'IMAP_SPACECRAFT' + TKFRAME_-43900_SPEC = 'MATRIX' + TKFRAME_-43900_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + + \begintext + + + Despun Pointing Sets (DPS) Frame ([3]) + --------------------------------------------------------------------- + + Coordinate frame used for ENA imager data processing and + intentionally designed for use in producing all-sky map products. + + This is provided by a CK file external to this file. Notionally, + the frame is defined: + + +Z axis is parallel to the nominal spin axis of the spacecraft. + The axis is notionally a time-average of the spin axis of the + exact orientation (IMAP_SPACECRAFT or IMAP_OMD). + + Y = Z cross Necliptic where Necliptic is the the unit normal + (North) to the ecliptic plane. + + This is a quasi-inertial reference frame and will have a unique + transformation matrix, valid between repointings of the spacecraft + + \begindata + + FRAME_IMAP_DPS = -43901 + FRAME_-43901_NAME = 'IMAP_DPS' + FRAME_-43901_CLASS = 3 + FRAME_-43901_CLASS_ID = -43901 + FRAME_-43901_CENTER = -43 + CK_-43901_SCLK = -43 + CK_-43901_SPK = -43 + + \begintext + + +Earth Based Frames +======================================================================== + + These dynamic frames are used for analyzing data in a reference + frame tied to the dynamics of Earth. + + + Earth-Fixed Frame (IMAP_EARTHFIXED) + --------------------------------------------------------------------- + + Some of these Earth based dynamic frames reference vectors in an + Earth-fixed frame. To support loading of either rotation model + (IAU_EARTH or ITRF93), the following keywords control which model + is used. The model is enabled by surrounding its keyword-value + block with the \begindata and \begintext markers (currently + IAU_EARTH). + + IAU_EARTH based model is currently employed: + + \begindata + + FRAME_IMAP_EARTHFIXED = -43910 + FRAME_-43910_NAME = 'IMAP_EARTHFIXED' + FRAME_-43910_CLASS = 4 + FRAME_-43910_CLASS_ID = -43910 + FRAME_-43910_CENTER = 399 + TKFRAME_-43910_RELATIVE = 'IAU_EARTH' + TKFRAME_-43910_SPEC = 'MATRIX' + TKFRAME_-43910_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + + \begintext + + Alternately, the more precise ITRF93-based model could be used: + + FRAME_IMAP_EARTHFIXED = -43910 + FRAME_-43910_NAME = 'IMAP_EARTHFIXED' + FRAME_-43910_CLASS = 4 + FRAME_-43910_CLASS_ID = -43910 + FRAME_-43910_CENTER = 399 + TKFRAME_-43910_RELATIVE = 'ITRF93' + TKFRAME_-43910_SPEC = 'MATRIX' + TKFRAME_-43910_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + + However, using the ITRF93 frame requires supplying SPICE with + sufficient binary PCK data to cover the period of interest. + The IAU_EARTH frame just requires a text PCK with Earth data + to be loaded. + + + Mean Ecliptic of Date (IMAP_ECLIPDATE) ([2],[5]) + --------------------------------------------------------------------- + + Mean Ecliptic of Date is the more precise, rotating counterpart + to the inertial Mean Ecliptic and Equinox of J2000 (ECLIPJ2000). + + If computations involving this frame (or frames relative to this) + are too expensive, the user may instruct SPICE to ignore + rotational effects by changing 'ROTATING' to 'INERTIAL'. + + The X axis is the first point in Aries for the mean ecliptic of + date and the Z axis points along the ecliptic north pole. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_ECLIPDATE = -43911 + FRAME_-43911_NAME = 'IMAP_ECLIPDATE' + FRAME_-43911_CLASS = 5 + FRAME_-43911_CLASS_ID = -43911 + FRAME_-43911_CENTER = 399 + FRAME_-43911_RELATIVE = 'J2000' + FRAME_-43911_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43911_FAMILY = 'MEAN_ECLIPTIC_AND_EQUINOX_OF_DATE' + FRAME_-43911_PREC_MODEL = 'EARTH_IAU_1976' + FRAME_-43911_OBLIQ_MODEL = 'EARTH_IAU_1980' + FRAME_-43911_ROTATION_STATE = 'ROTATING' + + \begintext + + + Mission Design Inertial (MDI) Frame ([3]) + --------------------------------------------------------------------- + + Alias for SPICE ECLIPJ2000. + + Primary coordinate frame used to define IMAP's trajectory and + orbit, as well as for some science data products. + + The X axis is the first point in Aries for the mean ecliptic of + J2000 and the Z axis points along the ecliptic north pole. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_MDI = -43912 + FRAME_-43912_NAME = 'IMAP_MDI' + FRAME_-43912_CLASS = 4 + FRAME_-43912_CLASS_ID = -43912 + FRAME_-43912_CENTER = 399 + TKFRAME_-43912_RELATIVE = 'IMAP_EARTHFIXED' + TKFRAME_-43912_SPEC = 'MATRIX' + TKFRAME_-43912_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + + \begintext + + + Mission Design Rotating (MDR) Frame ([3],[10]) + --------------------------------------------------------------------- + + IMAP observatory body coordinate frame. + + The origin of the frame is the L1 point of the Sun and the Earth- + Moon barycenter defined in SPK 'L1_de431.bsp' by reference [10]; + this author assigned the NAIF body code 391 to this L1 point. + + The position of the Earth-Moon barycenter relative to the Sun is + the primary vector: the X axis points from the Sun to the + Earth-Moon barycenter. + + The northern surface normal to the mean ecliptic of date is the + secondary vector: the Z axis is the component of this vector + orthogonal to the X axis. Combined with the definition of the + X axis, this yields a unit vector along the angular momentum + vector of the Earth-Moon barycenter orbiting the Sun. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_MDR = -43913 + FRAME_-43913_NAME = 'IMAP_MDR' + FRAME_-43913_CLASS = 5 + FRAME_-43913_CLASS_ID = -43913 + FRAME_-43913_CENTER = 391 + FRAME_-43913_RELATIVE = 'J2000' + FRAME_-43913_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43913_FAMILY = 'TWO-VECTOR' + FRAME_-43913_PRI_AXIS = 'X' + FRAME_-43913_PRI_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43913_PRI_OBSERVER = 'SUN' + FRAME_-43913_PRI_TARGET = 'EARTH MOON BARYCENTER' + FRAME_-43913_PRI_ABCORR = 'NONE' + FRAME_-43913_SEC_AXIS = 'Z' + FRAME_-43913_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43913_SEC_FRAME = 'IMAP_ECLIPDATE' + FRAME_-43913_SEC_SPEC = 'RECTANGULAR' + FRAME_-43913_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Geomagnetic Coordinate (GMC) Frame (IGRF-14 Modeled Pole) ([6]) + --------------------------------------------------------------------- + + The geomagnetic coordinate (GMC) system is defined so that its + Z-axis is parallel to the magnetic dipole. The geographic + coordinates, D, of the dipole axis are found from the + International Geomagnetic Reference Field. + + The Y-axis of this system is perpendicular to the geographic poles + such that if D is the dipole position and S is the south pole + Y=DxS. The X-axis completes a right-handed orthogonal set. + + The implementation of this frame is complicated in that the + definition of the IGRF dipole is a function of time and the IGRF + model cannot be directly incorporated into SPICE. However, SPICE + does allow one to define time dependent Euler angles. Meaning, you + can define a single Euler angle that rotates the Geocentric + Equatorial Inertial (GEI) system to GMC for a given ephem time t: + + V = r(t) * V + GEI GMC + + where r(t) is a time dependent Euler angle representation of a + rotation. SPICE allows for the time dependence to be represented + by a polynomial expansion. This expansion can be fit using the + IGRF model, thus representing the IGRF dipole axis. + + IGRF-14 (the 14th version) was fit for the period of 1990-2035, + which encompasses the mission and will also make this kernel + useful for performing Magnetic dipole frame transformations for + the 1990's and the 2000's. However, IGRF-14 is not as accurate for + this entire time interval. The years between 1945-2020 are labeled + definitive, although only back to 1990 was used in the polynomial + fit. 2020-2025 is provisional, and may change with IGRF-15. + 2025-2030 was only a prediction. Beyond 2030, the predict is so + far in the future as to not be valid. So to make the polynomials + behave nicely in this region (in case someone does try to use this + frame during that time), the 2030 prediction was extended until + 2035. So for low precision, this kernel can be used for the years + 2025-2035. Any times less than 1990 and greater than 2035 were not + used in the fit, and therefore may be vastly incorrect as the + polynomials may diverge outside of this region. These coefficients + will be refit when IGRF-15 is released. + + Also, since the rest of the magnetic dipole frames are defined + from this one, similar time ranges should be used for those frames + + Definitive Provisional Predict Not Valid + |--------------------------|+++++++++++|###########|???????????| + 1990 2020 2025 2030 2035 + + In addition to the error inherit in the model itself, the + polynomial expansion cannot perfectly be fit the IGRF dipole. The + maximum error on the fit is 0.2 milliradians, or 0.01 degrees, + while the average error is 59 microradians or 0.003 degrees. + + The GMC frame is achieved by first rotating the IAU_EARTH frame + about Z by the longitude degrees, and then rotating about the + Y axis by the amount of latitude. + + NOTE: ITRF93 is much more accurate than IAU_EARTH, if precise + Earth-Fixed coordinates are desired, then ITRF93 should be + incorporated by changing RELATIVE of the IMAP_EARTHFIXED frame. + + \begindata + + FRAME_IMAP_GMC = -43914 + FRAME_-43914_NAME = 'IMAP_GMC' + FRAME_-43914_CLASS = 5 + FRAME_-43914_CLASS_ID = -43914 + FRAME_-43914_CENTER = 399 + FRAME_-43914_RELATIVE = 'IMAP_EARTHFIXED' + FRAME_-43914_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43914_FAMILY = 'EULER' + FRAME_-43914_EPOCH = @2010-JAN-1/00:00:00 + FRAME_-43914_AXES = ( 3, 2, 1 ) + FRAME_-43914_UNITS = 'DEGREES' + FRAME_-43914_ANGLE_1_COEFFS = ( +72.21459071369075 + +2.5468902895893966E-9 + -9.716151847392007E-19 + -1.0433860683683533E-26 + +2.362766949492718E-36 + +3.3213862072412154E-44 + -3.5122239525813096E-54 + -4.264324158308002E-62 + +2.495064964115813E-72 + +1.8605789215176264E-80 ) + FRAME_-43914_ANGLE_2_COEFFS = ( -9.981781660857344 + +1.8136204417470554E-9 + +7.130241121790372E-19 + -2.215929597148403E-27 + -3.900143352851885E-36 + +6.599160686982152E-45 + +8.376429421972708E-54 + -1.07431639798394E-62 + -5.913960690205374E-72 + +6.775302680782905E-81 ) + FRAME_-43914_ANGLE_3_COEFFS = ( 0 ) + + \begintext + + + Geocentric Equatorial Inertial (GEI) Frame ([3],[6]) + --------------------------------------------------------------------- + + Alias for SPICE J2000 frame. + + The Geocentric Equatorial Inertial System (GEI) has its X-axis + pointing from the Earth towards the first point of Aries (the + position of the Sun at the vernal equinox). This direction is the + intersection of the Earth's equatorial plane and the ecliptic + plane and thus the X-axis lies in both planes. The Z-axis is + parallel to the rotation axis of the Earth and Y completes the + right-handed orthogonal set (Y = Z x X). + + \begindata + + FRAME_IMAP_GEI = -43915 + FRAME_-43915_NAME = 'IMAP_GEI' + FRAME_-43915_CLASS = 4 + FRAME_-43915_CLASS_ID = -43915 + FRAME_-43915_CENTER = 399 + TKFRAME_-43915_RELATIVE = 'J2000' + TKFRAME_-43915_SPEC = 'MATRIX' + TKFRAME_-43915_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + + \begintext + + + Geocentric Solar Ecliptic (GSE) Frame ([3],[5]) + --------------------------------------------------------------------- + + Rotating geocentric frame in which Sun and Earth are fixed and the + Z axis is the unit normal to the Ecliptic plane. + + The position of the Sun relative to the Earth is the primary + vector: the X axis points from the Earth to the Sun. + + The northern surface normal to the mean ecliptic of date + (IMAP_ECLIPDATE) is the secondary vector: the Z axis is the + component of this vector orthogonal to the X axis. + + The Y axis is Z cross X, completing the right-handed frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_GSE = -43916 + FRAME_-43916_NAME = 'IMAP_GSE' + FRAME_-43916_CLASS = 5 + FRAME_-43916_CLASS_ID = -43916 + FRAME_-43916_CENTER = 399 + FRAME_-43916_RELATIVE = 'J2000' + FRAME_-43916_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43916_FAMILY = 'TWO-VECTOR' + FRAME_-43916_PRI_AXIS = 'X' + FRAME_-43916_PRI_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43916_PRI_OBSERVER = 'EARTH' + FRAME_-43916_PRI_TARGET = 'SUN' + FRAME_-43916_PRI_ABCORR = 'NONE' + FRAME_-43916_SEC_AXIS = 'Z' + FRAME_-43916_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43916_SEC_FRAME = 'IMAP_ECLIPDATE' + FRAME_-43916_SEC_SPEC = 'RECTANGULAR' + FRAME_-43916_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Geocentric Solar Magnetospheric (GSM) Frame ([3],[5],[6]) + --------------------------------------------------------------------- + + Rotating geocentric frame in which Sun and Earth are fixed and the + XZ plane contains Earth's magnetic dipole moment. Specifically, + the dipole moment will vary in the XZ plane about the Z axis of + this frame. + + The position of the Sun relative to the Earth is the primary + vector: the X axis points from the Earth to the Sun. + + Earth's magnetic dipole axis (the +Z axis of IMAP_GMC) is the + secondary vector: the Z axis is the component of this vector + orthogonal to the X axis. + + The Y axis is Z cross X, completing the right-handed frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_GSM = -43917 + FRAME_-43917_NAME = 'IMAP_GSM' + FRAME_-43917_CLASS = 5 + FRAME_-43917_CLASS_ID = -43917 + FRAME_-43917_CENTER = 399 + FRAME_-43917_RELATIVE = 'J2000' + FRAME_-43917_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43917_FAMILY = 'TWO-VECTOR' + FRAME_-43917_PRI_AXIS = 'X' + FRAME_-43917_PRI_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43917_PRI_OBSERVER = 'EARTH' + FRAME_-43917_PRI_TARGET = 'SUN' + FRAME_-43917_PRI_ABCORR = 'NONE' + FRAME_-43917_SEC_AXIS = 'Z' + FRAME_-43917_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43917_SEC_FRAME = 'IMAP_GMC' + FRAME_-43917_SEC_SPEC = 'RECTANGULAR' + FRAME_-43917_SEC_VECTOR = (0, 0, 1) + + \begintext + + + Solar Magnetic of Date (SMD) Frame ([3],[5],[6]) + --------------------------------------------------------------------- + + Rotating geocentric frame in which the Z axis is aligned with + Earth's magnetic dipole moment, and the XZ plane contains the + Earth-Sun vector. Specifically, the Earth-Sun vector will vary in + the XZ plane about the X axis of this frame. + + Earth's magnetic dipole axis (the +Z axis of IMAP_GMC) is the + primary vector and aligns with the Z axis of this frame. + + The position of the Sun relative to the Earth is the secondary + vector: the X axis is the component of the Earth-Sun vector + orthogonal to the Z axis. + + The Y axis is Z cross X, completing the right-handed frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_SMD = -43918 + FRAME_-43918_NAME = 'IMAP_SMD' + FRAME_-43918_CLASS = 5 + FRAME_-43918_CLASS_ID = -43918 + FRAME_-43918_CENTER = 399 + FRAME_-43918_RELATIVE = 'J2000' + FRAME_-43918_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43918_FAMILY = 'TWO-VECTOR' + FRAME_-43918_PRI_AXIS = 'Z' + FRAME_-43918_PRI_VECTOR_DEF = 'CONSTANT' + FRAME_-43918_PRI_FRAME = 'IMAP_GMC' + FRAME_-43918_PRI_SPEC = 'RECTANGULAR' + FRAME_-43918_PRI_VECTOR = (0, 0, 1) + FRAME_-43918_SEC_AXIS = 'X' + FRAME_-43918_SEC_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43918_SEC_OBSERVER = 'EARTH' + FRAME_-43918_SEC_TARGET = 'SUN' + FRAME_-43918_SEC_ABCORR = 'NONE' + + \begintext + + +Sun Based Frames +======================================================================== + + These dynamic frames are used for analyzing data in a reference + frame tied to the dynamics of the Sun. + + + Heliocentric Radial Tangential Normal (RTN) Frame ([3],[7]) + --------------------------------------------------------------------- + + The position of the spacecraft relative to the Sun is the primary + vector: the X axis points from the Sun center to the spacecraft. + + The solar rotation axis is the secondary vector: the Z axis is + the component of the solar north direction perpendicular to X. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_RTN = -43920 + FRAME_-43920_NAME = 'IMAP_RTN' + FRAME_-43920_CLASS = 5 + FRAME_-43920_CLASS_ID = -43920 + FRAME_-43920_CENTER = 10 + FRAME_-43920_RELATIVE = 'J2000' + FRAME_-43920_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43920_FAMILY = 'TWO-VECTOR' + FRAME_-43920_PRI_AXIS = 'X' + FRAME_-43920_PRI_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43920_PRI_OBSERVER = 'SUN' + FRAME_-43920_PRI_TARGET = 'IMAP' + FRAME_-43920_PRI_ABCORR = 'NONE' + FRAME_-43920_PRI_FRAME = 'IAU_SUN' + FRAME_-43920_SEC_AXIS = 'Z' + FRAME_-43920_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43920_SEC_FRAME = 'IAU_SUN' + FRAME_-43920_SEC_SPEC = 'RECTANGULAR' + FRAME_-43920_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Heliocentric Inertial (HCI) Frame ([3],[5],[7]) + --------------------------------------------------------------------- + + Referred to as "Heliographic Inertial (HGI) frame at epoch J2000" + in [3], but named as in [7] to avoid confusion with HGI of J1900. + + The X-Y Plane lies in the solar equator, +Z axis is parallel to + the Sun's rotation vector. + + The solar rotation axis is the primary vector: the Z axis points + in the solar north direction. + + The ascending node on the Earth ecliptic of J2000 of the solar + equator forms the X axis. This is accomplished by using the +Z + axis of the ecliptic of J2000 as the secondary vector and HCI +Y + as the secondary axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HCI = -43921 + FRAME_-43921_NAME = 'IMAP_HCI' + FRAME_-43921_CLASS = 5 + FRAME_-43921_CLASS_ID = -43921 + FRAME_-43921_CENTER = 10 + FRAME_-43921_RELATIVE = 'J2000' + FRAME_-43921_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43921_FAMILY = 'TWO-VECTOR' + FRAME_-43921_PRI_AXIS = 'Z' + FRAME_-43921_PRI_VECTOR_DEF = 'CONSTANT' + FRAME_-43921_PRI_FRAME = 'IAU_SUN' + FRAME_-43921_PRI_SPEC = 'RECTANGULAR' + FRAME_-43921_PRI_VECTOR = ( 0, 0, 1 ) + FRAME_-43921_SEC_AXIS = 'Y' + FRAME_-43921_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43921_SEC_FRAME = 'ECLIPJ2000' + FRAME_-43921_SEC_SPEC = 'RECTANGULAR' + FRAME_-43921_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Heliocentric of Date (HCD) Frame ([3],[5],[7]) + --------------------------------------------------------------------- + + Referred to as "Heliographic Inertial (HGI) frame true to + reference date" in [3], but named as in [7] without "inertial." + + The X-Y Plane lies in the solar equator, +Z axis is parallel to + the Sun's rotation vector. + + The solar rotation axis is the primary vector: the Z axis points + in the solar north direction. + + The ascending node on the Earth ecliptic of date of the solar + equator forms the X axis. This is accomplished by using the +Z + axis of the ecliptic of date as the secondary vector and HCD +Y + as the secondary axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HCD = -43922 + FRAME_-43922_NAME = 'IMAP_HCD' + FRAME_-43922_CLASS = 5 + FRAME_-43922_CLASS_ID = -43922 + FRAME_-43922_CENTER = 10 + FRAME_-43922_RELATIVE = 'J2000' + FRAME_-43922_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43922_FAMILY = 'TWO-VECTOR' + FRAME_-43922_PRI_AXIS = 'Z' + FRAME_-43922_PRI_VECTOR_DEF = 'CONSTANT' + FRAME_-43922_PRI_FRAME = 'IAU_SUN' + FRAME_-43922_PRI_SPEC = 'RECTANGULAR' + FRAME_-43922_PRI_VECTOR = ( 0, 0, 1 ) + FRAME_-43922_SEC_AXIS = 'Y' + FRAME_-43922_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43922_SEC_FRAME = 'IMAP_ECLIPDATE' + FRAME_-43922_SEC_SPEC = 'RECTANGULAR' + FRAME_-43922_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Heliographic Coordinates (HGC) Frame ([3],[7]) + --------------------------------------------------------------------- + + Cartesian counterpart to the spherical coordinates defined in [3], + "Heliographic Spherical (HGS) coordinate frame true to ref. date". + + Alias for SPICE IAU_SUN (Carrington heliographic coordinates) + in which the frame rotates with the surface of the sun with a + sidereal period of exactly 25.38 days. + + The Z axis is the solar rotation axis. + + The X axis is the intersection of the Carrington prime meridian + and the heliographic equator. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HGC = -43923 + FRAME_-43923_NAME = 'IMAP_HGC' + FRAME_-43923_CLASS = 4 + FRAME_-43923_CLASS_ID = -43923 + FRAME_-43923_CENTER = 10 + TKFRAME_-43923_RELATIVE = 'IAU_SUN' + TKFRAME_-43923_SPEC = 'MATRIX' + TKFRAME_-43923_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + \begintext + + + Heliocentric Aries Ecliptic (HAE) Frame ([3],[7]) + --------------------------------------------------------------------- + + Alias for SPICE ECLIPJ2000. + + The Z axis is the normal to the mean ecliptic at J2000. + + The X axis is the unit vector from Earth to the first point of + Aries at J2000. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HAE = -43924 + FRAME_-43924_NAME = 'IMAP_HAE' + FRAME_-43924_CLASS = 4 + FRAME_-43924_CLASS_ID = -43924 + FRAME_-43924_CENTER = 10 + TKFRAME_-43924_RELATIVE = 'ECLIPJ2000' + TKFRAME_-43924_SPEC = 'MATRIX' + TKFRAME_-43924_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + \begintext + + + Heliocentric Aries Ecliptic of Date (HAED) Frame ([3],[7]) + --------------------------------------------------------------------- + + Same orientation as IMAP_ECLIPDATE, but with Sun at the center + instead of Earth. + + The Z axis is the normal to the mean ecliptic of date. + + The X axis is the unit vector from Earth to the first point of + Aries of date. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HAED = -43925 + FRAME_-43925_NAME = 'IMAP_HAED' + FRAME_-43925_CLASS = 4 + FRAME_-43925_CLASS_ID = -43925 + FRAME_-43925_CENTER = 10 + TKFRAME_-43925_RELATIVE = 'IMAP_ECLIPDATE' + TKFRAME_-43925_SPEC = 'MATRIX' + TKFRAME_-43925_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + \begintext + + + Heliocentric Earth Ecliptic (HEE) Frame ([3],[7]) + --------------------------------------------------------------------- + + The position of the Earth relative to the Sun is the primary + vector: the X axis points from the Sun to the Earth. + + The northern surface normal to the mean ecliptic of date is the + secondary vector: the Z axis is the component of this vector + orthogonal to the X axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + All vectors are geometric: no aberration corrections are used. + + \begindata + + FRAME_IMAP_HEE = -43926 + FRAME_-43926_NAME = 'IMAP_HEE' + FRAME_-43926_CLASS = 5 + FRAME_-43926_CLASS_ID = -43926 + FRAME_-43926_CENTER = 10 + FRAME_-43926_RELATIVE = 'J2000' + FRAME_-43926_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43926_FAMILY = 'TWO-VECTOR' + FRAME_-43926_PRI_AXIS = 'X' + FRAME_-43926_PRI_VECTOR_DEF = 'OBSERVER_TARGET_POSITION' + FRAME_-43926_PRI_OBSERVER = 'SUN' + FRAME_-43926_PRI_TARGET = 'EARTH' + FRAME_-43926_PRI_ABCORR = 'NONE' + FRAME_-43926_SEC_AXIS = 'Z' + FRAME_-43926_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43926_SEC_FRAME = 'IMAP_ECLIPDATE' + FRAME_-43926_SEC_SPEC = 'RECTANGULAR' + FRAME_-43926_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Heliospheric Ram Ecliptic (HRE) Frame ([3],[8],[9]) + --------------------------------------------------------------------- + + This is a heliocentric frame oriented with respect to the current, + nominal ram direction of the Sun's motion relative to the local + interstellar medium and the ecliptic plane, otherwise known as the + heliospheric "nose" direction. + + The nose direction is the primary vector: the X axis points in the + direction [-0.2477, -0.9647, 0.0896] in the ECLIPJ2000 (IMAP_HAE) + frame. + + The northern surface normal to the mean ecliptic of J2000 is the + secondary vector: the Z axis is the component of this vector + orthogonal to the X axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HRE = -43927 + FRAME_-43927_NAME = 'IMAP_HRE' + FRAME_-43927_CLASS = 5 + FRAME_-43927_CLASS_ID = -43927 + FRAME_-43927_CENTER = 10 + FRAME_-43927_RELATIVE = 'J2000' + FRAME_-43927_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43927_FAMILY = 'TWO-VECTOR' + FRAME_-43927_PRI_AXIS = 'X' + FRAME_-43927_PRI_VECTOR_DEF = 'CONSTANT' + FRAME_-43927_PRI_FRAME = 'ECLIPJ2000' + FRAME_-43927_PRI_SPEC = 'RECTANGULAR' + FRAME_-43927_PRI_VECTOR = ( -0.2477, -0.9647, 0.0896 ) + FRAME_-43927_SEC_AXIS = 'Z' + FRAME_-43927_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43927_SEC_FRAME = 'ECLIPJ2000' + FRAME_-43927_SEC_SPEC = 'RECTANGULAR' + FRAME_-43927_SEC_VECTOR = ( 0, 0, 1 ) + + \begintext + + + Heliospheric Nose Upfield (HNU) Frame ([3],[8],[9]) + --------------------------------------------------------------------- + + Heliocentric frame oriented with respect to the current nominal + ram direction of the Sun's motion relative to the local + interstellar medium and the current best estimate of the + unperturbed magnetic field direction in the upstream local + interstellar medium. + + The nominal upfield direction of the ISM B-field is the primary + vector: the Z axis points in the direction + [-0.5583, -0.6046, 0.5681] in the ECLIPJ2000 (IMAP_HAE) frame. + + The nose direction [-0.2477, -0.9647, 0.0896] in the ECLIPJ2000 + (IMAP_HAE) frame is the secondary vector: the X axis is the + component of this vector orthogonal to the Z axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_HNU = -43928 + FRAME_-43928_NAME = 'IMAP_HNU' + FRAME_-43928_CLASS = 5 + FRAME_-43928_CLASS_ID = -43928 + FRAME_-43928_CENTER = 10 + FRAME_-43928_RELATIVE = 'J2000' + FRAME_-43928_DEF_STYLE = 'PARAMETERIZED' + FRAME_-43928_FAMILY = 'TWO-VECTOR' + FRAME_-43928_PRI_AXIS = 'Z' + FRAME_-43928_PRI_VECTOR_DEF = 'CONSTANT' + FRAME_-43928_PRI_FRAME = 'ECLIPJ2000' + FRAME_-43928_PRI_SPEC = 'RECTANGULAR' + FRAME_-43928_PRI_VECTOR = ( -0.5583, -0.6046, 0.5681 ) + FRAME_-43928_SEC_AXIS = 'X' + FRAME_-43928_SEC_VECTOR_DEF = 'CONSTANT' + FRAME_-43928_SEC_FRAME = 'ECLIPJ2000' + FRAME_-43928_SEC_SPEC = 'RECTANGULAR' + FRAME_-43928_SEC_VECTOR = ( -0.2477, -0.9647, 0.0896 ) + + \begintext + + + Galactic Coordinate System (GCS) Frame ([3]) + --------------------------------------------------------------------- + + Alias for SPICE galactic system II frame GALACTIC. + + The primary axis is the normal to the galactic equatorial plane: + Z axis is this unit vector. + + The secondary axis is the vector from the Sun to the galatic + center (represented by Sagittarious): X axis is the component of + this vector orthogonal to the Z axis. + + The Y axis is Z cross X, completing the right-handed reference + frame. + + \begindata + + FRAME_IMAP_GCS = -43929 + FRAME_-43929_NAME = 'IMAP_GCS' + FRAME_-43929_CLASS = 4 + FRAME_-43929_CLASS_ID = -43929 + FRAME_-43929_CENTER = 10 + TKFRAME_-43929_RELATIVE = 'GALACTIC' + TKFRAME_-43929_SPEC = 'MATRIX' + TKFRAME_-43929_MATRIX = ( 1 0 0 + 0 1 0 + 0 0 1 ) + \begintext + + +END OF FILE \ No newline at end of file diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/imap_sclk_0000.tsc b/imap_processing/ultra/l1c/sim_spice_kernels/imap_sclk_0000.tsc new file mode 100644 index 000000000..345cedeb0 --- /dev/null +++ b/imap_processing/ultra/l1c/sim_spice_kernels/imap_sclk_0000.tsc @@ -0,0 +1,156 @@ +\begintext + +FILENAME = "imap_0000.tsc" +CREATION_DATE = "5-January-2021" + + +IMAP Spacecraft Clock Kernel (SCLK) +=========================================================================== + + This file is a SPICE spacecraft clock (SCLK) kernel containing + information required for time conversions involving the on-board + IMAP spacecraft clock. + +Version +-------------------------------------------------------- + + IMAP SCLK Kernel Version: + + IMAP version 0.3 - April 22, 2022 -- Mike Ruffolo + Updated to use NAIF SC ID 43 + + IMAP Version 0.2 - June 2, 2021 -- Caroline Cocca + Updated to use temporary spacecraft id of 225 + + IMAP Version 0.1 - March 6, 2015 -- Eric Melin + Updated text to replace references to RBSP with SPP + + IMAP Version 0.0 - August 7, 2014 -- Eric Melin + The initial SPP spice kernel. + This file was created by using RBSPA initial kernel and + modifying the spacecraft ID. + + +Usage +-------------------------------------------------------- + + This file is used by the SPICE system as follows: programs that + make use of this SCLK kernel must 'load' the kernel, normally + during program initialization. Loading the kernel associates + the data items with their names in a data structure called the + 'kernel pool'. The SPICELIB routine FURNSH loads text kernel + files, such as this one, into the pool as shown below: + + FORTRAN: + + CALL FURNSH ( SCLK_kernel_name ) + + C: + + furnsh_c ( SCLK_kernel_name ); + + Once loaded, the SCLK time conversion routines will be able to + access the necessary data located in this kernel for their + designed purposes. + +References +-------------------------------------------------------- + + 1. "SCLK Required Reading" + +Inquiries +-------------------------------------------------------- + + If you have any questions regarding this file or its usage, + contact: + + Scott Turner + (443)778-1693 + Scott.Turner@jhuapl.edu + +Kernel Data +-------------------------------------------------------- + + The first block of keyword equals value assignments define the + type, parallel time system, and format of the spacecraft clock. + These fields are invariant from SCLK kernel update to SCLK + kernel update. + + The IMAP spacecraft clock is represented by the SPICE + type 1 SCLK kernel. It uses TDT, Terrestrial Dynamical Time, + as its parallel time system. + +\begindata + +SCLK_KERNEL_ID = ( @2009-07-09T12:20:32 ) +SCLK_DATA_TYPE_43 = ( 1 ) +SCLK01_TIME_SYSTEM_43 = ( 2 ) + + +\begintext + + In a particular partition of the IMAP spacecraft clock, + the clock read-out consists of two separate stages: + + 1/18424652:24251 + + The first stage, a 32 bit field, represents the spacecraft + clock seconds count. The second, a 16 bit field, represents + counts of 20 microsecond increments of the spacecraft clock. + + The following keywords and their values establish this structure: + +\begindata + +SCLK01_N_FIELDS_43 = ( 2 ) +SCLK01_MODULI_43 = ( 4294967296 50000 ) +SCLK01_OFFSETS_43 = ( 0 0 ) +SCLK01_OUTPUT_DELIM_43 = ( 2 ) + + +\begintext + + This concludes the invariant portion of the SCLK kernel data. The + remaining sections of the kernel may require updates as the clock + correlation coefficients evolve in time. The first section below + establishes the clock partitions. The data in this section consists + of two parallel arrays, which denote the start and end values in + ticks of each partition of the spacecraft clock. + + SPICE utilizes these two arrays to map from spacecraft clock ticks, + determined with the usual modulo arithmetic, to encoded SCLK--the + internal, monotonically increasing sequence used to tag various + data sources with spacecraft clock. + +\begindata + +SCLK_PARTITION_START_43 = ( 0.00000000000000e+00 ) + +SCLK_PARTITION_END_43 = ( 2.14748364799999e+14 ) + +\begintext + + The remaining section of the SCLK kernel defines the clock correlation + coefficients. Each line contains a 'coefficient triple': + + Encoded SCLK at which Rate is introduced. + Corresponding TDT Epoch at which Rate is introduced. + Rate in TDT (seconds) / most significant clock count (~seconds). + + SPICE uses linear extrapolation to convert between the parallel time + system and encoded SCLK. The triples are stored in the array defined + below. + + The first time triplet below was entered manually and represents the + approximate time (in TDT) at which SCLK = zero. The current plan for + IMAP is that the given epoch will be used for both Observatory I&T + and launch. Note that the conversion from UTC to TDT used 34 leap + seconds. + +\begindata + +SCLK01_COEFFICIENTS_43 = ( + + 0 @01-JAN-2010-00:01:06.184000 1.00000000000 + +) diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/imap_spk_demo.bsp b/imap_processing/ultra/l1c/sim_spice_kernels/imap_spk_demo.bsp new file mode 100644 index 000000000..e8b7578f6 Binary files /dev/null and b/imap_processing/ultra/l1c/sim_spice_kernels/imap_spk_demo.bsp differ diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/naif0012.tls b/imap_processing/ultra/l1c/sim_spice_kernels/naif0012.tls new file mode 100644 index 000000000..caa6a4d13 --- /dev/null +++ b/imap_processing/ultra/l1c/sim_spice_kernels/naif0012.tls @@ -0,0 +1,150 @@ +KPL/LSK + + +LEAPSECONDS KERNEL FILE +=========================================================================== + +Modifications: +-------------- + +2016, Jul. 14 NJB Modified file to account for the leapsecond that + will occur on December 31, 2016. + +2015, Jan. 5 NJB Modified file to account for the leapsecond that + will occur on June 30, 2015. + +2012, Jan. 5 NJB Modified file to account for the leapsecond that + will occur on June 30, 2012. + +2008, Jul. 7 NJB Modified file to account for the leapsecond that + will occur on December 31, 2008. + +2005, Aug. 3 NJB Modified file to account for the leapsecond that + will occur on December 31, 2005. + +1998, Jul 17 WLT Modified file to account for the leapsecond that + will occur on December 31, 1998. + +1997, Feb 22 WLT Modified file to account for the leapsecond that + will occur on June 30, 1997. + +1995, Dec 14 KSZ Corrected date of last leapsecond from 1-1-95 + to 1-1-96. + +1995, Oct 25 WLT Modified file to account for the leapsecond that + will occur on Dec 31, 1995. + +1994, Jun 16 WLT Modified file to account for the leapsecond on + June 30, 1994. + +1993, Feb. 22 CHA Modified file to account for the leapsecond on + June 30, 1993. + +1992, Mar. 6 HAN Modified file to account for the leapsecond on + June 30, 1992. + +1990, Oct. 8 HAN Modified file to account for the leapsecond on + Dec. 31, 1990. + + +Explanation: +------------ + +The contents of this file are used by the routine DELTET to compute the +time difference + +[1] DELTA_ET = ET - UTC + +the increment to be applied to UTC to give ET. + +The difference between UTC and TAI, + +[2] DELTA_AT = TAI - UTC + +is always an integral number of seconds. The value of DELTA_AT was 10 +seconds in January 1972, and increases by one each time a leap second +is declared. Combining [1] and [2] gives + +[3] DELTA_ET = ET - (TAI - DELTA_AT) + + = (ET - TAI) + DELTA_AT + +The difference (ET - TAI) is periodic, and is given by + +[4] ET - TAI = DELTA_T_A + K sin E + +where DELTA_T_A and K are constant, and E is the eccentric anomaly of the +heliocentric orbit of the Earth-Moon barycenter. Equation [4], which ignores +small-period fluctuations, is accurate to about 0.000030 seconds. + +The eccentric anomaly E is given by + +[5] E = M + EB sin M + +where M is the mean anomaly, which in turn is given by + +[6] M = M + M t + 0 1 + +where t is the number of ephemeris seconds past J2000. + +Thus, in order to compute DELTA_ET, the following items are necessary. + + DELTA_TA + K + EB + M0 + M1 + DELTA_AT after each leap second. + +The numbers, and the formulation, are taken from the following sources. + + 1) Moyer, T.D., Transformation from Proper Time on Earth to + Coordinate Time in Solar System Barycentric Space-Time Frame + of Reference, Parts 1 and 2, Celestial Mechanics 23 (1981), + 33-56 and 57-68. + + 2) Moyer, T.D., Effects of Conversion to the J2000 Astronomical + Reference System on Algorithms for Computing Time Differences + and Clock Rates, JPL IOM 314.5--942, 1 October 1985. + +The variable names used above are consistent with those used in the +Astronomical Almanac. + +\begindata + +DELTET/DELTA_T_A = 32.184 +DELTET/K = 1.657D-3 +DELTET/EB = 1.671D-2 +DELTET/M = ( 6.239996D0 1.99096871D-7 ) + +DELTET/DELTA_AT = ( 10, @1972-JAN-1 + 11, @1972-JUL-1 + 12, @1973-JAN-1 + 13, @1974-JAN-1 + 14, @1975-JAN-1 + 15, @1976-JAN-1 + 16, @1977-JAN-1 + 17, @1978-JAN-1 + 18, @1979-JAN-1 + 19, @1980-JAN-1 + 20, @1981-JUL-1 + 21, @1982-JUL-1 + 22, @1983-JUL-1 + 23, @1985-JUL-1 + 24, @1988-JAN-1 + 25, @1990-JAN-1 + 26, @1991-JAN-1 + 27, @1992-JUL-1 + 28, @1993-JUL-1 + 29, @1994-JUL-1 + 30, @1996-JAN-1 + 31, @1997-JUL-1 + 32, @1999-JAN-1 + 33, @2006-JAN-1 + 34, @2009-JAN-1 + 35, @2012-JUL-1 + 36, @2015-JUL-1 + 37, @2017-JAN-1 ) + +\begintext diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_attitude.bc b/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_attitude.bc new file mode 100755 index 000000000..589f2f08f Binary files /dev/null and b/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_attitude.bc differ diff --git a/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_pointing_frame.bc b/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_pointing_frame.bc new file mode 100644 index 000000000..8ab7f76e3 Binary files /dev/null and b/imap_processing/ultra/l1c/sim_spice_kernels/sim_1yr_imap_pointing_frame.bc differ diff --git a/imap_processing/ultra/l1c/spacecraft_pset.py b/imap_processing/ultra/l1c/spacecraft_pset.py index f04e6d033..533578b78 100644 --- a/imap_processing/ultra/l1c/spacecraft_pset.py +++ b/imap_processing/ultra/l1c/spacecraft_pset.py @@ -118,10 +118,11 @@ def calculate_spacecraft_pset( phi_vals, ancillary_files, instrument_id, + reject_scattering, ) ) # Determine nside from the lookup table - nside = hp.npix2nside(len(for_indices_by_spin_phase)) + nside = hp.npix2nside(for_indices_by_spin_phase.sizes["pixel"]) counts, latitude, longitude, n_pix = get_spacecraft_histogram( vhat_dps_spacecraft, species_dataset["energy_spacecraft"].values, diff --git a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py index 700793c5f..c48c5edb3 100644 --- a/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +++ b/imap_processing/ultra/l1c/ultra_l1c_pset_bins.py @@ -9,9 +9,7 @@ from scipy import interpolate from imap_processing.spice.geometry import ( - SpiceFrame, cartesian_to_spherical, - imap_state, ) from imap_processing.spice.spin import ( get_spacecraft_spin_phase, @@ -370,7 +368,7 @@ def calculate_exposure_time( valid_spun_pixels: xr.DataArray, boundary_scale_factors: xr.DataArray, apply_bsf: bool = True, -) -> xr.Dataset: +) -> xr.DataArray: """ Adjust the exposure time at each pixel to account for dead time. @@ -392,7 +390,7 @@ def calculate_exposure_time( Returns ------- - exposure_pointing_adjusted : xarray.Dataset + exposure_pointing: xarray.DataArray Adjusted exposure times accounting for dead time. """ # nominal spin phase step. @@ -500,14 +498,14 @@ def get_spacecraft_exposure_times( ) # Adjust exposure time by the actual number of valid spins in the pointing exposure_pointing_adjusted = n_spins_in_pointing * exposure_time - # Ensure exposure factor is broadcast correctly + if exposure_pointing_adjusted.shape[0] != n_energy_bins: exposure_pointing_adjusted = np.repeat( - exposure_pointing_adjusted.values, + exposure_pointing_adjusted, n_energy_bins, axis=0, ) - return exposure_pointing_adjusted, nominal_deadtime_ratios.values + return exposure_pointing_adjusted.values, nominal_deadtime_ratios.values def get_efficiencies_and_geometric_function( @@ -536,9 +534,12 @@ def get_efficiencies_and_geometric_function( boundary_scale_factors : xarray.DataArray Boundary scale factors for each pixel at each spin phase. theta_vals : np.ndarray - A 2D array of theta values for each HEALPix pixel at each spin phase step. + 2D or 3D array of theta values. Shape is either (spin_phase_step, npix) + or (spin_phase_step, energy_bins, npix) when energy-dependent scattering + rejection is used. phi_vals : np.ndarray - A 2D array of phi values for each HEALPix pixel at each spin phase step. + Array of phi values with the same shape as `theta_vals`, giving the + corresponding phi for each pixel (and energy, if present). npix : int Number of HEALPix pixels. ancillary_files : dict @@ -589,20 +590,9 @@ def get_efficiencies_and_geometric_function( eff_summation = np.zeros((energy_bins, npix)) sample_count = np.zeros((energy_bins, npix)) # Loop through spin phases - spin_steps = valid_spun_pixels.shape[0] + spin_steps = valid_spun_pixels.sizes["spin_phase_step"] for i in range(spin_steps): # Loop through energy bins - # Compute gf and eff for these theta/phi pairs - theta_at_spin = theta_vals[:, i] - phi_at_spin = phi_vals[:, i] - theta_at_spin_clipped = theta_vals_clipped[:, i] - phi_at_spin_clipped = phi_vals_clipped[:, i] - gf_values = get_geometric_factor( - phi=phi_at_spin, - theta=theta_at_spin, - quality_flag=np.zeros(len(phi_at_spin)).astype(np.uint16), - geometric_factor_tables=geometric_lookup_table, - ) # Get valid pixels at this spin phase valid_at_spin = valid_spun_pixels.isel( spin_phase_step=i @@ -610,20 +600,36 @@ def get_efficiencies_and_geometric_function( for energy_bin_idx in range(energy_bins): # Determine pixel indices based on energy dependence - if valid_at_spin.sizes["energy"] == 1: - # No scattering rejection. Same pixels for all energies + if theta_vals.ndim < 3: + # Energy independent calculations # TODO this may cause performance issues. Revisit later. pixel_inds = np.where(valid_at_spin.isel(energy=0))[0] + # Compute gf and eff for these theta/phi pairs + theta_at_spin = theta_vals[i, :] + phi_at_spin = phi_vals[i, :] + theta_at_spin_clipped = theta_vals_clipped[i, :] + phi_at_spin_clipped = phi_vals_clipped[i, :] else: - # Scattering rejection - different pixels per energy + # Energy dependent calculations pixel_inds = np.where(valid_at_spin.isel(energy=energy_bin_idx))[0] + # Compute gf and eff for these theta/phi pairs + theta_at_spin = theta_vals[i, energy_bin_idx, :] + phi_at_spin = phi_vals[i, energy_bin_idx, :] + theta_at_spin_clipped = theta_vals_clipped[i, energy_bin_idx, :] + phi_at_spin_clipped = phi_vals_clipped[i, energy_bin_idx, :] if pixel_inds.size == 0: continue + gf_values = get_geometric_factor( + phi=phi_at_spin[pixel_inds], + theta=theta_at_spin[pixel_inds], + quality_flag=np.zeros(len(phi_at_spin[pixel_inds])).astype(np.uint16), + geometric_factor_tables=geometric_lookup_table, + ) energy = energy_bin_geometric_means[energy_bin_idx] + # Clip energy to calibrated range energy_clipped = np.clip(energy, 3.0, 80.0) - eff_values = get_efficiency( np.full(pixel_inds.size, energy_clipped), phi_at_spin_clipped[pixel_inds], @@ -632,9 +638,13 @@ def get_efficiencies_and_geometric_function( interpolator=eff_interpolator, ) - # Sum - bsfs = boundary_scale_factors[pixel_inds, i] if apply_bsf else 1.0 - gf_summation[energy_bin_idx, pixel_inds] += gf_values[pixel_inds] * bsfs + # Accumulate and sum eff and gf values + bsfs = ( + boundary_scale_factors[pixel_inds, i] + if apply_bsf + else np.ones(len(pixel_inds)) + ) + gf_summation[energy_bin_idx, pixel_inds] += gf_values * bsfs eff_summation[energy_bin_idx, pixel_inds] += eff_values * bsfs sample_count[energy_bin_idx, pixel_inds] += 1 @@ -647,124 +657,6 @@ def get_efficiencies_and_geometric_function( return gf_averaged, eff_averaged -def get_helio_adjusted_data( - time: float, - exposure_time: np.ndarray, - geometric_factor: np.ndarray, - efficiency: np.ndarray, - ra: np.ndarray, - dec: np.ndarray, - nside: int = 128, - nested: bool = False, -) -> tuple[NDArray, NDArray, NDArray]: - """ - Compute 2D (Healpix index, energy) arrays for in the helio frame. - - Build CG corrected exposure, efficiency, and geometric factor arrays. - - Parameters - ---------- - time : float - Median time of pointing in et. - exposure_time : np.ndarray - Spacecraft exposure. Shape = (energy, npix). - geometric_factor : np.ndarray - Geometric factor values. Shape = (energy, npix). - efficiency : np.ndarray - Efficiency values. Shape = (energy, npix). - ra : np.ndarray - Right ascension in the spacecraft frame (degrees). - dec : np.ndarray - Declination in the spacecraft frame (degrees). - nside : int, optional - The nside parameter of the Healpix tessellation (default is 128). - nested : bool, optional - Whether the Healpix tessellation is nested (default is False). - - Returns - ------- - helio_exposure : np.ndarray - A 2D array of shape (n_energy_bins, npix). - helio_efficiency : np.ndarray - A 2D array of shape (n_energy_bins, npix). - helio_geometric_factors : np.ndarray - A 2D array of shape (n_energy_bins, npix). - - Notes - ----- - These calculations are performed once per pointing. - """ - # Get energy midpoints. - _, _, energy_bin_geometric_means = build_energy_bins() - - # The Cartesian state vector representing the position and velocity of the - # IMAP spacecraft. - state = imap_state(time, ref_frame=SpiceFrame.IMAP_DPS) - - # Extract the velocity part of the state vector - spacecraft_velocity = state[3:6] - # Convert (RA, Dec) angles into 3D unit vectors. - # Each unit vector represents a direction in the sky where the spacecraft observed - # and accumulated exposure time. - npix = hp.nside2npix(nside) - unit_dirs = hp.ang2vec(ra, dec, lonlat=True).T # Shape (N, 3) - shape = (len(energy_bin_geometric_means), int(npix)) - if np.any( - [arr.shape != shape for arr in [exposure_time, geometric_factor, efficiency]] - ): - raise ValueError( - f"Input arrays must have the same shape {shape}, but got " - f"{exposure_time.shape}, {geometric_factor.shape}, {efficiency.shape}." - ) - # Initialize output array. - # Each row corresponds to a HEALPix pixel, and each column to an energy bin. - helio_exposure = np.zeros(shape) - helio_efficiency = np.zeros(shape) - helio_geometric_factors = np.zeros(shape) - - # Loop through energy bins and compute transformed exposure. - for i, energy_mean in enumerate(energy_bin_geometric_means): - # Convert the midpoint energy to a velocity (km/s). - # Based on kinetic energy equation: E = 1/2 * m * v^2. - energy_velocity = ( - np.sqrt(2 * energy_mean * UltraConstants.KEV_J / UltraConstants.MASS_H) - / 1e3 - ) - - # Use Galilean Transform to transform the velocity wrt spacecraft - # to the velocity wrt heliosphere. - # energy_velocity * cartesian -> apply the magnitude of the velocity - # to every position on the grid in the despun grid. - helio_velocity = spacecraft_velocity.reshape(1, 3) + energy_velocity * unit_dirs - - # Normalized vectors representing the direction of the heliocentric velocity. - helio_normalized = helio_velocity / np.linalg.norm( - helio_velocity, axis=1, keepdims=True - ) - - # Convert Cartesian heliocentric vectors into spherical coordinates. - # Result: azimuth (longitude) and elevation (latitude) in degrees. - helio_spherical = cartesian_to_spherical(helio_normalized) - az, el = helio_spherical[:, 1], helio_spherical[:, 2] - - # Convert azimuth/elevation directions to HEALPix pixel indices. - hpix_idx = hp.ang2pix(nside, az, el, nest=nested, lonlat=True) - - # Accumulate exposure, eff, and gf values into HEALPix pixels for this energy - # bin. - helio_exposure[i, :] = np.bincount( - hpix_idx, weights=exposure_time[i, :], minlength=npix - ) - helio_efficiency[i, :] = np.bincount( - hpix_idx, weights=efficiency[i, :], minlength=npix - ) - helio_geometric_factors[i, :] = np.bincount( - hpix_idx, weights=geometric_factor[i, :], minlength=npix - ) - - return helio_exposure, helio_efficiency, helio_geometric_factors - - def get_spacecraft_background_rates( rates_dataset: xr.Dataset, sensor_id: int, diff --git a/poetry.lock b/poetry.lock index a72f1673d..512152d65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -807,11 +807,8 @@ files = [ {file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"}, {file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"}, - {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"}, - {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"}, {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"}, {file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"}, {file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"}, diff --git a/pyproject.toml b/pyproject.toml index 10d93b597..663aa571e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,8 @@ version = "0.0.0" description = "IMAP Science Operations Center Processing" authors = ["IMAP SDC Developers "] readme = "README.md" -include = ["imap_processing/_version.py"] +include = ["imap_processing/_version.py", + "imap_processing/ultra/l1c/sim_spice_kernels/*"] license = "MIT" keywords = ["IMAP", "SDC", "SOC", "Science Operations"] classifiers = [