LALInference  4.1.6.1-fe68b98
lalinference_compute_roq_weights.py
Go to the documentation of this file.
1 import numpy as np
2 from math import ceil
3 from optparse import OptionParser
4 import os
5 from scipy.linalg import solve_triangular
6 import lal
7 import lalsimulation
8 
9 #####################################################################
10 
11 def _block_slices(dim_size, block_size):
12  """Generator that yields slice objects for indexing into
13  sequential blocks of an array along a particular axis
14  """
15  count = 0
16  while True:
17  yield slice(count, count + block_size, 1)
18  count += block_size
19  if count > dim_size:
20  return
21 
22 def blockwise_dot(A, B, deltaF, max_elements=int(2**27), out=None):
23  """
24  Computes the dot product of two matrices in a block-wise fashion.
25  Only blocks of `A` with a maximum size of `max_elements` will be
26  processed simultaneously.
27  """
28 
29  m, n = A.shape
30  n1, o = B.shape
31 
32  if n1 != n:
33  raise ValueError('matrices are not aligned')
34 
35  if A.flags.f_contiguous:
36  # prioritize processing as many columns of A as possible
37  max_cols = max(1, max_elements // m)
38  max_rows = max_elements // max_cols
39 
40  else:
41  # prioritize processing as many rows of A as possible
42  max_rows = max(1, max_elements // n)
43  max_cols = max_elements // max_rows
44 
45  if out is None:
46  out = np.empty((m, o), dtype=np.result_type(A, B))
47  elif out.shape != (m, o):
48  raise ValueError('output array has incorrect dimensions')
49 
50  for mm in _block_slices(m, max_rows):
51  out[mm, :] = 0
52  for nn in _block_slices(n, max_cols):
53  A_block = A[mm, nn].copy() # copy to force a read
54  out[mm, :] += np.dot(A_block, B[nn, :]) * 4 * deltaF
55  del A_block
56 
57  return out
58 
59 def ehat(j, length):
60  """Return a unit vector whose j-th component is 1"""
61  ehat = np.zeros(length)
62  ehat[j] = 1.0
63  return ehat
64 
65 def DEIM(basis):
66  """Calculate interpolation nodes following the Algorithm 5 in J Sci Comput
67  (2013) 57:604-637.
68 
69  Parameters
70  ----------
71  basis : ndarray
72  ndarray whose i-th row is the i-th basis vector
73 
74  Return
75  ------
76  nodes : array
77  interpolation nodes
78  B : ndarray
79  ndarray whose i-th row is the weight for the i-th frequency node.
80  """
81  vec_num, vec_len = basis.shape
82  # Step (1)
83  j = abs(basis[0]).argmax()
84  # Step (2)
85  UT = np.zeros(shape=(vec_num, vec_len), dtype=complex)
86  PT = np.zeros(shape=(vec_num, vec_len))
87  UT[0] = basis[0]
88  PT[0] = ehat(j, vec_len)
89  PTU = np.zeros(shape=(vec_num, vec_num), dtype=complex)
90  nodes = [j]
91  # Step (3)
92  for i in range(vec_num - 1):
93  i += 1
94  ei = basis[i]
95  # Step (4)
96  PTU[i-1][:i:] = np.array([UT[k][nodes[i-1]] for k in range(i)])
97  c = solve_triangular(
98  PTU[:i:, :i:], np.array([ei[node] for node in nodes]),
99  lower=True, check_finite=False)
100  # Step (5)
101  r = ei - c.dot(UT[:i:])
102  # Step (6)
103  j = abs(r).argmax()
104  # Step (7)
105  UT[i] = r
106  PT[i] = ehat(j, vec_len)
107  nodes.append(j)
108 
109  # Calculate B
110  VT = basis
111  B = np.linalg.inv(VT.dot(PT.transpose())).dot(VT)
112 
113  # ordering
114  B = np.array([B[i] for i in np.argsort(nodes)])
115  nodes.sort()
116 
117  return nodes, B
118 
119 def construct_nodes(
120  selected_params, flow, fhigh, deltaF, approximant, quadratic
121 ):
122  """Construct frequency nodes and weights from parameter values selected by greedy algorithm.
123 
124  Parameters
125  ----------
126  selected_params : ndarray
127  ndarray whose i-th row is the i-th parameter set selected by greedy
128  algorithm
129  flow : float
130  fhigh : float
131  deltaF : float
132  approximant : string
133  quadratic : bool
134  construct nodes for products of waveforms when this is True
135 
136  Return
137  ------
138  f_nodes : ndarray
139  interpolation frequency nodes
140  B : ndarray
141  ndarray whose i-th roq is the weight for the i-th frequency node.
142  """
143  # generate waveforms at selected parameters
144  start_index = int(flow / deltaF)
145  def _waveform(param):
146  # FIXME: Need to be fixed when we take into account precession.
147  m1, m2, a1z, a2z = param
148  hp, _ = lalsimulation.SimInspiralChooseFDWaveform(
149  m1 * lal.MSUN_SI, m2 * lal.MSUN_SI,
150  0.0, 0.0, a1z, 0.0, 0.0, a2z,
151  1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
152  deltaF, flow, fhigh, flow, None,
153  lalsimulation.GetApproximantFromString(approximant)
154  )
155  hp = hp.data.data[start_index::]
156  if quadratic:
157  return np.conj(hp) * hp
158  else:
159  return hp
160  basis = np.array([_waveform(param) for param in selected_params]).transpose()
161 
162  # orthogonalize waveforms
163  basis, _ = np.linalg.qr(basis)
164  basis = basis.transpose()
165 
166  # discrete empirical interpolation
167  nodes, B = DEIM(basis)
168  return np.array([flow + deltaF * node for node in nodes]), B
169 
170 ######################################################################
171 
172 data_dir = './'
173 # load in data from file
174 
175 parser = OptionParser(usage="usage: %prog [options]",
176  version="%prog")
177 parser.add_option("-d", "--data", type='string',
178  action="append",
179  dest="data_file",
180  help="data file",)
181 parser.add_option("-p", "--psd", type='string',
182  action="append", # optional because action defaults to "store"
183  dest="psd_file",
184  help="psd file",)
185 parser.add_option("-t", "--time_prior", type=float,
186  action="store", # optional because action defaults to "store"
187  dest="dt",
188  help="width of time prior",)
189 parser.add_option("-i", "--ifo", type='string',
190  action="append",
191  dest="IFOs",
192  help="list of ifos",)
193 parser.add_option("-a", "--approx", type='string',
194  action="store",
195  dest="approximant",
196  help="approximant name",)
197 parser.add_option("-s", "--seglen", type=float,
198  action="store",
199  dest="seglen",
200  help="segment length",)
201 parser.add_option("-f", "--fLow", type=float,
202  action="store",
203  dest="fLow",
204  help="low frequency cut off",)
205 parser.add_option("-u", "--fHigh", type=float,
206  action="store",
207  dest="fHigh",
208  help="high frequency cut off",)
209 parser.add_option("-T", "--delta_tc", type=float,
210  action="store",
211  dest="delta_tc",
212  help="width of tc subdomain",)
213 parser.add_option("-B", "--basis-set", type='string',
214  action="store",
215  dest="b_matrix_directory",
216  help="B matrix directory",)
217 parser.add_option("-o", "--out", type='string',
218  action="store",
219  dest="outpath",
220  default="./",
221  help="output path",)
222 
223 (options, args) = parser.parse_args()
224 
225 basis_params = np.loadtxt(os.path.join(options.b_matrix_directory, "params.dat")).T
226 flow_in_params, fhigh_in_params, deltaF_in_params = basis_params[0], basis_params[1], 1. / basis_params[2]
227 
228 B_linear_path = os.path.join(options.b_matrix_directory, "B_linear.npy")
229 B_quadratic_path = os.path.join(options.b_matrix_directory, "B_quadratic.npy")
230 fnodes_linear_path = os.path.join(options.b_matrix_directory, "fnodes_linear.npy")
231 fnodes_quadratic_path = os.path.join(options.b_matrix_directory, "fnodes_quadratic.npy")
232 params_linear_path = os.path.join(options.b_matrix_directory, "selected_params_linear.npy")
233 params_quadratic_path = os.path.join(options.b_matrix_directory, "selected_params_quadratic.npy")
234 if os.path.exists(B_linear_path) and os.path.exists(B_quadratic_path) and \
235  os.path.exists(fnodes_linear_path) and os.path.exists(fnodes_quadratic_path):
236  B_linear = np.load(B_linear_path)
237  B_quadratic = np.load(B_quadratic_path)
238  fnodes_linear = np.load(fnodes_linear_path)
239  fnodes_quadratic = np.load(fnodes_quadratic_path)
240 elif os.path.exists(params_linear_path) and os.path.exists(params_quadratic_path):
241  selected_params_linear = np.load(params_linear_path)
242  selected_params_quadratic = np.load(params_quadratic_path)
243  fnodes_linear, B_linear = construct_nodes(selected_params_linear, flow_in_params, fhigh_in_params, deltaF_in_params, options.approximant, False)
244  fnodes_quadratic, B_quadratic = construct_nodes(selected_params_linear, flow_in_params, fhigh_in_params, deltaF_in_params, options.approximant, True)
245 else:
246  print("No ROQ data found. Please make sure that you have B_(linear, quadratic).npy "
247  "and fnodes_(linear, quadratic).npy, or selected_params_(linear, quadratic).npy")
248  raise
249 
250 def BuildWeights(data, B, deltaF):
251 
252  ''' for a data array and reduced basis compute roq weights
253 
254  B: (reduced basis element)*invV (the inverse Vandermonde matrix)
255  data: data set
256  PSD: detector noise power spectral density (must be same shape as data)
257  deltaF: integration element df
258 
259  '''
260  weights = np.dot(data, B.conjugate()) * deltaF * 4.
261  #weights = np.einsum('ij,jk->ik', B, data.conjugate() * deltaF * 4)
262  return weights
263 ##################################
264 
265 relative_tc_shift = options.seglen - 2.
266 
267 # loop over ifos
268 
269 i=0
270 scale_factor = 0
271 
272 for ifo in options.IFOs:
273 
274  dat_file = np.column_stack( np.loadtxt(options.data_file[i]) )
275  data = dat_file[1] + 1j*dat_file[2]
276  fseries = dat_file[0]
277  if options.seglen:
278  deltaF = 1./options.seglen
279  else:
280  deltaF = fseries[1] - fseries[0]
281  if options.fHigh:
282  fHigh = options.fHigh
283  else:
284  fHigh = fseries[-1]
285  fHigh_index = int(fHigh / deltaF)
286 
287  print('Desired fHigh is', fHigh,', actual fHigh is', fseries[fHigh_index])
288 
289  if options.fLow:
290  fLow = options.fLow
291  scale_factor = flow_in_params / fLow
292 
293  else:
294  fLow = flow_in_params
295  assert fHigh == fhigh_in_params
296  fLow_index = int(fLow / deltaF)
297 
298  print('Desired fLow is', fLow,', actual fLow is', fseries[fLow_index])
299 
300  fseries = fseries[fLow_index:fHigh_index]
301  data = data[fLow_index:fHigh_index]
302 
303  psdfile = np.column_stack( np.loadtxt(options.psd_file[i]) )
304  psd = psdfile[1]
305 
306  psd[-1] = psd[-1 -1 ]
307  psd = psd[fLow_index:fHigh_index]
308  data /= psd
309 
310  # only get frequency components up to fHigh
311  B_linear = B_linear.T[0:(fHigh_index - fLow_index)][:].T
312  B_quadratic = B_quadratic.T[0:(fHigh_index-fLow_index)][:].T
313  print(B_linear.shape[1], B_quadratic.shape[1], len(data), len(psd))
314  assert len(data) == len(psd) == B_linear.shape[1] == B_quadratic.shape[1]
315 
316 
317 
318  #for the dot product, it's convenient to work with transpose of B:
319  B_linear = B_linear.T
320  B_quadratic = B_quadratic.T
321 
322  for k in range(len(data)):
323  if np.isnan(data[k].real):
324  data[k] = 0+0j
325 
326  #0.045 comes from the diameter of the earth in light seconds: the maximum time-delay between earth-based observatories
327  tcs = np.linspace(relative_tc_shift - options.dt - 0.045, relative_tc_shift + options.dt + 0.045, ceil(2.*(options.dt+0.045) / options.delta_tc) )# array of relative time shifts to be applied to the data
328 
329 
330  tc_shifted_data = np.zeros([len(tcs), len(fseries)], dtype=complex) # array to be filled with data, shifted by discrete time tc
331 
332  print("time steps = "+str(len(tcs)))
333  for j in range(len(tcs)):
334 
335  tc = tcs[j]
336 
337  #exp_2pi_i_tc = np.array(np.exp(1j*2.*np.pi*fseries*tc))
338  #data_tc = np.array(data*exp_2pi_i_tc)
339  tc_shifted_data[j] = data * np.exp(1j*2.*np.pi*fseries*tc)
340 
341  #tc_shifted_data = tc_shifted_data.T#np.array(tc_shifted_data).T
342 
343 
344 
345  #*************************************************************************** #
346  print("Computing weights for "+ifo)
347  weights_path_linear = os.path.join(options.outpath,"weights_linear_%s.dat"%ifo)
348  weights_file_linear = open(weights_path_linear, "wb")
349 
350  max_block_gigabytes = 4
351  max_elements = int((max_block_gigabytes * 2 ** 30) / 8) # max number of double complex elements
352 
353  weights_linear = blockwise_dot(tc_shifted_data, B_linear.conjugate(), deltaF, max_elements).T
354 
355  (weights_linear).tofile(weights_file_linear)
356  weights_file_linear.close()
357 
358  del tc_shifted_data
359 
360  #*************************************************************************** #
361  weights_path_quadratic = os.path.join(options.outpath,"weights_quadratic_%s.dat"%ifo)
362  weights_file_quadratic = open(weights_path_quadratic, "wb")
363  weights_quadratic = (BuildWeights(1./psd, B_quadratic, deltaF).T).real
364 
365  (weights_quadratic).tofile(weights_file_quadratic)
366  weights_file_quadratic.close()
367  size_file_path = os.path.join(options.outpath,"roq_sizes.dat")
368 
369  #*************************************************************************** #
370 
371  B_linear = B_linear.T
372  B_quadratic = B_quadratic.T
373 
374  np.savetxt(size_file_path,np.array((len(tcs),B_linear.shape[0],B_quadratic.shape[0],B_linear.shape[1])),fmt='%u')
375  print("Weights have been computed for "+ifo)
376 
377  i += 1
378 
379  #save the fnodes as a dat file if they're not already:
380 
381 if scale_factor:
382  print("scale factor = %f"%scale_factor)
383  fnodes_linear /= scale_factor
384  fnodes_quadratic /= scale_factor
385 
386 
387 fnodes_linear_path = os.path.join(options.outpath,"fnodes_linear.dat")
388 fnodes_linear_file = open(fnodes_linear_path, "wb")
389 
390 fnodes_linear.tofile(fnodes_linear_file)
391 fnodes_linear_file.close()
392 
393 fnodes_quadratic_path = os.path.join(options.outpath,"fnodes_quadratic.dat")
394 fnodes_quadratic_file = open(fnodes_quadratic_path, "wb")
395 
396 fnodes_quadratic.tofile(fnodes_quadratic_file)
397 fnodes_quadratic_file.close()
398 
399 tcs_file_path = os.path.join(options.outpath,"tcs.dat")
400 tcs_file = open(tcs_file_path, "wb")
401 
402 tcs.tofile(tcs_file)
403 tcs_file.close()
static REAL8 dot(const REAL8 v[3], const REAL8 w[3])
#define max(a, b)
def construct_nodes(selected_params, flow, fhigh, deltaF, approximant, quadratic)
Construct frequency nodes and weights from parameter values selected by greedy algorithm.
def DEIM(basis)
Calculate interpolation nodes following the Algorithm 5 in J Sci Comput (2013) 57:604-637.
def BuildWeights(data, B, deltaF)
for a data array and reduced basis compute roq weights
def ehat(j, length)
Return a unit vector whose j-th component is 1.
def blockwise_dot(A, B, deltaF, max_elements=int(2 **27), out=None)
Computes the dot product of two matrices in a block-wise fashion.