LRO Albedo and Thermal IR (Complex)

Overview

This example demonstrates advanced computation of albedo and thermal infrared accelerations using the object-oriented interface of the pyRTX library.

Key Features

  • Spatially-varying albedo: Uses a grid of albedo values across the planet surface

  • Spatially-varying temperature: Incorporates temperature variations with location

  • Digital elevation model: Represents planetary topography for accurate shadowing

  • OBJ file format: Loads detailed shape models from standard mesh files

This advanced approach is essential for high-precision orbit determination and propagation where planetary radiation pressure effects need to be accurately modeled.

When to Use This Approach

Use this complex method when:

  • High-fidelity force modeling is required

  • Surface property data is available

  • Topographic effects are significant

  • Comparing with the simple uniform model shows non-negligible differences

Code

lro_alb_ir_complex.py
  1### ------------------------------------------------------------------------------------------------------- ###
  2
  3# Example purpose:
  4# Show the object-oriented interface of the pyRTX library
  5#
  6# Example case:
  7# Show how to compute albedo and thermal-ir accelerations
  8# Here we use an albedo and temperature grid for the planet. We also represent the shape of the planet
  9# with a digital elevation model stored in a .obj file.
 10
 11### ------------------------------------------------------------------------------------------------------- ###
 12### IMPORTS
 13
 14import sys, os
 15import numpy as np
 16import spiceypy as sp
 17import xarray as xr
 18import matplotlib.pyplot as plt
 19import timeit
 20
 21from pyRTX.classes.Spacecraft import Spacecraft
 22from pyRTX.classes.Planet import Planet, TemperatureGrid, AlbedoGrid
 23from pyRTX.classes.Radiation import Albedo, Emissivity
 24from pyRTX.classes.Precompute import Precompute
 25from pyRTX.classes.LookUpTable import LookUpTable
 26from pyRTX.core.analysis_utils import epochRange2
 27
 28from numpy import floor, mod
 29
 30### ------------------------------------------------------------------------------------------------------- ###
 31### INPUTS
 32
 33# NOTE: before running this script you must generate the input file 'luts/lro_accel_lut.nc' by running the 
 34# example 'compute_lut.py' using type = 'accel'.
 35
 36ref_epc		= "2010 may 10 09:25:00"
 37duration    = 10000  									  # seconds
 38timestep    = 100
 39METAKR      = '../example_data/LRO/metakernel_lro.tm'     # metakernel
 40obj_path    = '../example_data/LRO/'				      # folder with .obj files
 41lutfile     = 'luts/lro_accel_lut.nc'					  # lookup table file
 42base_flux   =  1361.5
 43ref_radius  =  1737.4
 44n_cores     =  10
 45
 46if not os.path.exists(lutfile):
 47    print(f"Before running this script you must generate the file '{lutfile}' by running the example 'compute_lut.py' setting type = 'accel'.")
 48    sys.exit()
 49    
 50# The spacecraft mass can be a float, int or a xarray with times and values [kg]
 51# You can generate the xarray by running the script 'lro_mass.py'.
 52sc_mass = xr.open_dataset('mass/lro_mass.nc')
 53sc_mass.load()
 54sc_mass.close()
 55
 56# Planet representation (set None for representing the planet as a simple sphere.
 57# Load an obj for representing it with digital elevation models)
 58fromFile = 'moon_obj/fib_1e4_v2.obj'	
 59
 60# Albedo grids
 61# Reference: https://ode.rsl.wustl.edu/moon/pagehelp/Content/Missions_Instruments/LRO/LOLA/GDR/GDRDAM.htm
 62alb_grid = 'grids/bond_albedo.npy'  
 63alb_lon  = 'grids/ldam_4_lon.npy'
 64alb_lat  = 'grids/ldam_4_lat.npy'
 65
 66# Temperature grids
 67# Reference: https://doi.org/10.1016/j.icarus.2016.08.012
 68temp_grid = 'grids/temp.npy'  
 69temp_lon  = 'grids/temp_lon.npy'
 70temp_lat  = 'grids/temp_lat.npy'
 71
 72### ------------------------------------------------------------------------------------------------------- ###
 73### OBJECTS DEFINITION
 74
 75# Time initialization
 76tic = timeit.default_timer()
 77
 78# Load the metakernel containing references to the necessary SPICE frames
 79sp.furnsh(METAKR)
 80
 81# Define epochs
 82epc_et0  = sp.str2et( ref_epc )
 83epc_et1  = epc_et0 + duration
 84epochs   = epochRange2(startEpoch = epc_et0, endEpoch = epc_et1, step = timestep)
 85 
 86# Define the Spacecraft Object (Refer to the class documentation for further details)
 87lro = Spacecraft( name = 'LRO',
 88                 
 89				  base_frame = 'LRO_SC_BUS', 					     # Name of the spacecraft body-fixed frame
 90      
 91                  mass = sc_mass,
 92      
 93				  spacecraft_model = {						         # Define a spacecraft model
 94                          
 95					'LRO_BUS': { 
 96							 'file' : obj_path + 'bus_rotated.obj',	 # .obj file of the spacecraft component
 97							 'frame_type': 'Spice',				     # type of frame (can be 'Spice' or 'UD'
 98							 'frame_name': 'LRO_SC_BUS',			 # Name of the frame
 99							 'center': [0.0,0.0,0.0],			     # Origin of the component
100							 'diffuse': 0.1,				         # Diffuse reflect. coefficient
101							 'specular': 0.3,				         # Specular reflect. coefficient
102							 },
103
104					'LRO_SA': {	
105							'file': obj_path + 'SA_recentred.obj',
106							'frame_type': 'Spice',
107							'frame_name': 'LRO_SA',
108							'center': [-1,-1.1, -0.1],
109							'diffuse': 0,
110							'specular': 0.3,
111							},
112
113
114					'LRO_HGA': { 	
115							'file': obj_path + 'HGA_recentred.obj',
116							'frame_type': 'Spice',
117							'frame_name': 'LRO_HGA',
118							'center':[-0.99,    -0.3,  -3.1],
119							'diffuse': 0.2,
120							'specular': 0.1,
121							},
122					}
123				)
124
125# Define the Moon object
126moon = Planet(  fromFile = fromFile,
127                radius = ref_radius,
128                name = 'Moon',
129                bodyFrame = 'MOON_PA',
130                sunFixedFrame = 'GSE_MOON',
131                units = 'km',
132                subdivs = 5,
133                )
134
135# Define the Albedo grid object
136Lon = np.load(alb_lon)
137Lat = np.load(alb_lat)
138
139ALB = AlbedoGrid(
140		radius      = ref_radius,
141		frame       = 'MOON_PA',
142		planet_name = 'Moon',
143		from_array  = alb_grid,
144		axes        = (Lon, Lat),
145		)
146
147# Define the temperature grid object
148Lon = np.load(temp_lon)
149Lat = np.load(temp_lat)
150
151TEMP = TemperatureGrid(
152		radius      = ref_radius,
153		frame       = 'GSE_MOON',
154		planet_name = 'Moon',
155		from_array  = temp_grid,
156		axes        = (Lon, Lat),
157		)
158
159# Set thermal properties
160moon.emissivity          = 0.9
161moon.albedo              = ALB
162moon.gridded_temperature = TEMP
163
164# Load the Look up table
165LUT  = LookUpTable(lutfile)
166
167# Precomputation object
168prec = Precompute(epochs = epochs,)
169prec.precomputePlanetaryRadiation(lro, moon, LUT.moving_frames, correction='CN')
170prec.dump()
171
172# Create the albedo object
173albedo = Albedo(lro, LUT, moon, precomputation  = prec, baseflux  = base_flux,)
174
175# Create the thermal infrared object
176thermal_ir = Emissivity(lro, LUT, moon, precomputation  = prec, baseflux  = base_flux,)
177
178### ------------------------------------------------------------------------------------------------------- ###
179### COMPUTATIONS
180
181# Both the albedo and emissivity objects have a .compute() method.
182# This method returns the normalized fluxes, direction and albedo values
183# for each of the planet faces contributing to the computation
184# The general formula for computing the acceleration of an elementary face is:
185#
186# acc_i = L * albedo_value/mass * norm_flux
187#
188# where L is the normalized optical response of the spacecraft which can be computed
189# with raytracing setting a unitary radiance of the impacting rays
190# Due to the very high number of rays involved in these computations
191# it is mandatory to precompute L in the form of a lookup table.
192# Here we import the lookup table for LRO which can be computed using the example script
193# 'compute_lut.py'
194
195# Parallel computations
196alb_accel = albedo.compute(epochs, n_cores = n_cores)[0] * 1e3 
197ir_accel  = thermal_ir.compute(epochs, n_cores = n_cores)[0] * 1e3 
198
199# Always unload the SPICE kernels
200sp.unload(METAKR)
201
202### ... Elapsed time
203toc = timeit.default_timer()
204time_min = int(floor((toc-tic)/60))
205time_sec = int(mod((toc-tic), 60))
206print("")
207print("\t Elapsed time: %d min, %d sec" %(time_min, time_sec))
208print("")
209
210### ------------------------------------------------------------------------------------------------------- ###
211### PLOT
212
213epochs  = [float( epc - epc_et0 )/3600 for epc in epochs]
214
215# ALBEDO 
216fig, ax = plt.subplots(3, 1, figsize=(14,8), sharex = True)
217
218ax[0].plot(epochs, alb_accel[:,0], linewidth = 2, color = "tab:blue")
219ax[0].set_ylabel('X [m/s^2]')
220ax[1].plot(epochs, alb_accel[:,1], linewidth = 2, color = "tab:blue")
221ax[1].set_ylabel('Y [m/s^2]')
222ax[2].plot(epochs, alb_accel[:,2], linewidth = 2, color = "tab:blue")
223ax[2].set_ylabel('Z [m/s^2]')
224ax[2].set_xlabel('Hours from CA')
225fig.suptitle('Albedo in S/C body frame')
226plt.tight_layout()
227
228# IR
229fig, ax = plt.subplots(3, 1, figsize=(14,8), sharex = True)
230
231ax[0].plot(epochs, ir_accel[:,0], linewidth = 2, color = "tab:blue")
232ax[0].set_ylabel('X [m/s^2]')
233ax[1].plot(epochs, ir_accel[:,1], linewidth = 2, color = "tab:blue")
234ax[1].set_ylabel('Y [m/s^2]')
235ax[2].plot(epochs, ir_accel[:,2], linewidth = 2, color = "tab:blue")
236ax[2].set_ylabel('Z [m/s^2]')
237ax[2].set_xlabel('Hours from CA')
238fig.suptitle('IR in S/C body frame')
239plt.tight_layout()
240
241plt.show()
242
243### ------------------------------------------------------------------------------------------------------- ###