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