Compute Lookup Table

This example demonstrates lookup table generation when the spacecraft has moveable appendages

compute_lut.py
  1### -------------------------------------------------------------------- ###
  2
  3#					    LOOK UP TABLE OBJECT BUILDING
  4
  5# Example case:
  6# Generate the normalized optical response lookup table for LRO.
  7
  8# The look up table is designed to store the "geometry vector" of a spacecraft.
  9# This vector is representative of how the spacecraft’s shape and surface 
 10# properties interact with radiation coming from a specific direction. 
 11
 12# The lookup table helps to speed up the computation of non-gravitational
 13# accelerations and is MANDATORY for albedo, thermal infrared and drag
 14# acceleration.
 15
 16# To compute a lookup table, we need to define a grid of right ascensions
 17# and declinations that represents the directions from which the radiation 
 18# is coming in the spacecraft body-fixed frame. 
 19
 20# Also, we need to specify every frame that is not fixed with respect to 
 21# the body-fixed frame. These frames are stored in the variable 'moving_frames'. 
 22# If a frame is not specified inside 'moving_frames', it is represented 
 23# fixed with respect to the spacecraft body frame. For every moving_frame, 
 24# a range of euler angles in a specific euler set must be defined.
 25
 26# The LUT have two computational mode:
 27# - If type is 'accel', the LUT computes the acceleration for a 
 28#   normalized radiation flux of 1 W/m**2 and a mass of 1 kg. 
 29#   The LUT values will be 3x1 vectors. This mode is for srp, albedo and 
 30#   thermal infrared acceleration.
 31# - If type is 'cross-section', the LUT computes the cross-section for the
 32#	drag acceleration. The LUT values will be single float. 
 33
 34# NOTE: Here we compute the lookup table for the Lunar Redonnaissance orbiter
 35# by varying the orientation of the solar array. The High Gain Antenna
 36# is considered fixed with respect to the bus. 
 37
 38### -------------------------------------------------------------------- ###
 39### IMPORTS
 40
 41import time
 42import xarray as xr
 43import spiceypy as sp
 44import numpy as np
 45import pickle as pkl
 46
 47from numpy import floor, mod
 48from math import ceil
 49
 50from pyRTX.classes.SRP import SolarPressure
 51from pyRTX.classes.Spacecraft import Spacecraft
 52from pyRTX.classes.PixelPlane import PixelPlane
 53from pyRTX.classes.RayTracer import RayTracer
 54from pyRTX.core.analysis_utils import epochRange2
 55
 56import multiprocessing as mproc
 57
 58import timeit, os, itertools, logging
 59
 60### -------------------------------------------------------------------- ###
 61### INPUTS & LUT CONFIG
 62			  	
 63n_cores    =  10							      # number of cores for parallel computation
 64grid_res   =  20							      # angular resolution for RA, DEC
 65angle_res  =  10						          # angular resolution for moving parts
 66spacing	   =  0.01								  # spacing between rays 
 67
 68ref_epc	    = "2010 may 10 09:25:00"	          # reference epoch
 69duration    = 10000  							  # seconds
 70timestep    = 100
 71
 72sc_mass 	  = 1								  # the sc mass must be 1 for LUT computation
 73base_frame    = 'LRO_SC_BUS'				      # sc body-fixed frame
 74moving_frames = ['LRO_SA',]					      # frames that are not fixed wrt the sc base frame
 75
 76eul_set = (2,1,3)								  # euler representation for moving frames attitude
 77
 78obj_path = '../example_data/LRO/'		          # path for 3D shape elements
 79
 80METAKR = '../example_data/LRO/metakernel_lro.tm'  # metakernel
 81
 82type = 'accel'	                                  # method: 'accel' or 'cross-section'
 83
 84lutfile = 'luts/lro_accel_lut.nc'	    		  # output file
 85
 86### --------------------------------------------------------------------------- ###
 87### LUT LIMITS
 88
 89sp.furnsh(METAKR)
 90
 91# List of right ascension and declination values for the incoming rays
 92RA   = np.linspace(0, 360, int(360/grid_res) + 1) * np.pi / 180
 93DEC  = np.linspace(-90, 90, int(180/grid_res) + 1) * np.pi / 180
 94
 95###  Here we find the limits for every moving frame, in terms of euler angles  ###
 96
 97# Define timespan
 98eul_limits = {frame: {e: [] for e in eul_set} for frame in moving_frames}
 99epc_et0  = sp.str2et( ref_epc )
100epc_et1  = epc_et0 + duration
101epochs   = epochRange2(startEpoch = epc_et0, endEpoch = epc_et1, step = timestep)
102
103# Find euler limits
104for frame in moving_frames:
105    
106	EUL = np.zeros((len(epochs),3))
107
108	for i, epc in enumerate(epochs):
109
110		rot = sp.pxform(frame, base_frame, epc)
111		EUL[i,:] = np.array(sp.m2eul(rot, *eul_set)) * 180 / np.pi
112 
113	for i, e in enumerate(eul_set):
114  
115		if max(abs(EUL[:,i].max()), abs(EUL[:,i].min())) >= grid_res:
116			eul_limits[frame][e] = [EUL[:,i].min() - 0.05, EUL[:,i].max() + 0.05]
117
118sp.unload(METAKR)
119
120### --------------------------------------------------------------------------- ###
121### OBJECT DEFINITION
122
123# Define a spacecraft model
124spacecraft_model = {						        
125                          
126					'LRO_BUS': { 
127							 'file' : obj_path + 'bus_rotated.obj',	 # .obj file of the spacecraft component
128							 'frame_type': 'Spice',				     # type of frame (can be 'Spice' or 'UD'
129							 'frame_name': 'LRO_SC_BUS',			 # Name of the frame
130							 'center': [0.0,0.0,0.0],			     # Origin of the component
131							 'diffuse': 0.1,				         # Diffuse reflect. coefficient
132							 'specular': 0.3,				         # Specular reflect. coefficient
133							 },
134
135					'LRO_SA': {	
136							'file': obj_path + 'SA_recentred.obj',
137							'frame_type': 'Spice',
138							'frame_name': 'LRO_SA',
139							'center': [-1,-1.1, -0.1],
140							'diffuse': 0,
141							'specular': 0.3,
142							},
143
144
145					'LRO_HGA': { 	
146							'file': obj_path + 'HGA_recentred.obj',
147							'frame_type': 'Spice',
148							'frame_name': 'LRO_HGA',
149							'center':[-0.99,    -0.3,  -3.1],
150							'diffuse': 0.2,
151							'specular': 0.1,
152							},
153					}
154 
155 # Elements with moving frames must have an user defined rotation
156for elem in spacecraft_model.keys():
157    if any([spacecraft_model[elem]['frame_name'] == frame for frame in moving_frames]):
158        spacecraft_model[elem]['frame_type'] = 'UD'
159        spacecraft_model[elem]['UD_rotation'] = np.identity(4)
160
161# Define the Spacecraft Object (Refer to the class documentation for further details)
162lro = Spacecraft( name = 'LRO',     
163				  base_frame = 'LRO_SC_BUS', 					     # Name of the spacecraft body-fixed frame
164                  mass = sc_mass,									 # The mass should be 1 for LUT computation 
165				  spacecraft_model = spacecraft_model,
166				)
167
168# Define the Sun rays object
169rays = PixelPlane( spacecraft = lro,
170				   mode = 'Fixed',
171				   width = 15,
172				   height = 15,
173				   ray_spacing = spacing, 
174				   lon = 0,
175				   lat = 0,
176				   distance = 30)
177
178# Define the Ray Tracer
179rtx = RayTracer( lro,                    # Spacecraft object
180				 rays,                   # pixelPlane object
181				 kernel = 'Embree3',     # The RTX kernel to use (use Embree 3)
182				 bounces = 1,            # The number of bounces to account for
183				 diffusion = False,      # Account for secondary diffusion
184				 ) 
185
186# Define the Solar Pressure Object 
187# NOTE: for LUT computation the baseflux must be set to None.
188srp = SolarPressure( lro, rtx, baseflux = None, )
189
190### -------------------------------------------------------------------- ###
191### LUT INITIALIZATION
192
193# Time initialization
194tic = timeit.default_timer()
195
196# Refresh inputs and output directiories
197os.system('rm inputs/*')
198os.system('rm outputs/*')
199
200# Save srp object
201with open('inputs/srp.pkl', 'wb') as f: pkl.dump(srp, f)
202
203# Deactivate trimesh logging
204log = logging.getLogger('trimesh')
205log.disabled = True
206
207print('\n *** Calculating dimension ...')
208
209# Build dimensions and axes
210axes = []
211dims = []
212for name in moving_frames:
213    for ax in eul_set:
214        
215        if not len(eul_limits[name][ax]): continue
216        lb = eul_limits[name][ax][0]
217        ub = eul_limits[name][ax][1]
218        
219        dims.append('%s%d'%(name,ax))
220        axes.append(np.linspace(lb, ub, ceil((ub-lb)/angle_res) + 1)*np.pi/180)	  
221
222# Append ra and dec
223dims.append('ra')
224dims.append('dec')
225dims.append('value')
226axes.append(RA) 
227axes.append(DEC) 
228
229# Build attribute dictionary for xarray
230attrs = {
231	'moving_frames': ",".join(moving_frames),
232	'base_frame': base_frame,
233	'type': type,
234	'ref_epoch': ref_epc,
235	'eul_set': ",".join([str(e) for e in eul_set]),
236	'dims': ",".join(dims),
237	}
238
239# Save attributes dictionary
240with open('inputs/attrs.pkl', 'wb') as f: pkl.dump(attrs, f)
241
242# Compute shape for xarray
243shape = tuple([len(r) for r in axes] + [3])
244
245# Build coordinates
246coords = {dims[i]: vals for i, vals in enumerate(axes)}
247
248### -------------------------------------------------------------------- ###
249### LUT PARALLEL COMPUTATION 
250
251print(f'\n *** LUT size: {shape} ...')
252
253# Init data array
254data  = np.zeros(shape)
255
256# Find all permutations
257SEQ    = list(itertools.product(*axes))
258IDX    = list(itertools.product(*[range(l) for l in shape[:-1]]))
259steps  = [int(i) for i in np.linspace(0,len(SEQ),n_cores)]
260SEQ    = [SEQ[i:j] for (i,j) in zip(steps[:-1],steps[1:])]
261IDX    = [IDX[i:j] for (i,j) in zip(steps[:-1],steps[1:])]
262INPUTS = [(I,S) for I,S in zip(IDX,SEQ)]
263
264# Save inputs file
265with open('inputs/inputs.pkl', 'wb') as f: pkl.dump(INPUTS, f)
266
267### -------------------------------------------------- ###
268### Multiprocessing target function
269
270def process(ID, METAKR):
271	os.system(f'python task_lut.py {ID} {METAKR}')
272	return
273
274### -------------------------------------------------- ###
275
276print('\n *** Filling the LUT values ...')
277
278# --------------------------------- #
279# PYTHON MULTIPROCESSING
280
281p = [0.] * len(INPUTS)
282
283# Process in parallel
284for ID in range(len(INPUTS)):
285	p[ID] = mproc.Process( target=process, args=(ID, METAKR,) )
286
287for ID in range(len(INPUTS)):       
288	p[ID].start()
289	while sum([pi.is_alive() for pi in p]) >= n_cores: time.sleep(0.5)
290
291for ID in range(len(INPUTS)): 
292	while(p[ID].is_alive()): p[ID].join(1)
293
294# --------------------------------- #
295
296# Fill LUT values
297for ID in range(len(INPUTS)):
298    result = np.load(f'outputs/output{ID}.npy')
299    for i, idxs in enumerate(INPUTS[ID][0]):
300        data[tuple(idxs)] = result[i]
301
302print('\n *** Generating the x-array ...')
303
304# Define X-array LUT
305data = xr.Dataset( data_vars = {'look_up_table': (dims, data)}, 
306				   coords    = coords,
307				   attrs     = attrs,)
308
309print(f'\n *** LUT completed!\n')
310
311# Reactivate trimesh logging
312log.disabled = False
313
314# Save
315data.to_netcdf(lutfile, encoding = data.encoding.update({'zlib': True, 'complevel': 1}))
316
317### ... Elapsed time
318toc = timeit.default_timer()
319time_min = int(floor((toc-tic)/60))
320time_sec = int(mod((toc-tic), 60))
321print("")
322print("\t Elapsed time: %d min, %d sec" %(time_min, time_sec))
323print("")
324
325### -------------------------------------------------------------------- ###