Loading [MathJax]/extensions/TeX/AMSsymbols.js
LALPulsar 7.1.1.1-5e288d3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
semicoherent_metric_methods.py
Go to the documentation of this file.
1# Copyright (C) 2019--2023 Benjamin Grace
2#
3# This program is free software; you can redistribute it and/or modify it
4# under the terms of the GNU General Public License as published by the
5# Free Software Foundation; either version 2 of the License, or (at your
6# option) any later version.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11# Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17## \file
18## \ingroup lalpulsar_python_piecewise_model
19"""
20Functions for building the semi-coherent metric for the piecewise model.
21"""
22
23import logging
24
25import numpy as np
26from scipy import integrate
27
28from . import basis_functions as bf
29from . import errors
30
31# In this module we build the methods that generate the semi-coherent metric for our piecewise model. Each piecewise
32# segment being a semi-coherent segment.
33
34# This method checks whether an integral on the lth segment (starting from a segment number 0) will have a non-zero
35# value if we integrate a basis function attached to the parameter with coordinates (i, borc) (ommitting the s value
36# as this does not effect whether this integral is non-zero or not).
37#
38# Further on in this document we loop over all parameters for our signal model and conduct integrals over the basis
39# functions for these parameters on each interval. This list of parameters we loop over have coordinates (i, k),
40# where we by default have borc = 1. This is done because using the coordinates (i, borc, k) has redundancies. For
41# example, the parameter coordinates (1, 0, k) is the same as (0, 1, k). To get rid of these redundancies we
42# implement the new coordinates (i, k) with borc by default set as 1. To then check whether a parameter's basis
43# function has a non-zero integral over the lth interval, if i == l then we know that that parameter is present on
44# that interval. The other possibility is that i = l - 1 but we have borc = 1. We then check these two cases only,
45# if either one mis True then we know that the associated integral will be non-zero.
46#
47#
48# One might also note that if i = l + 1 and borc = 0 then that parameter will also appear on the lth interval and
49# hence lead to a non-zero integral. This is true, however later in this module we have by default set borc = 1,
50# thus if i = l + 1 by default borc = 1 and that parameter will not appear on the lth interval. Rather unintuitively,
51# if borc = 1 by default then the first parameter actually appears on the -1th segment (despite this segment not
52# being 'real').
53def integralzerocheck(i, borc, l):
54 if i == l or (i + 1 == l and borc == 1):
55 return True
56 else:
57 return False
58
59
60# The same as the integralzerocheck method except if we are integrating the product of two basis functions on intervals
61# i1 and i2. Again included only for computational efficiency
62def doubleintegralzerocheck(i1, i2, borc1, borc2, l):
63 if integralzerocheck(i1, borc1, l) and integralzerocheck(i2, borc2, l):
64 return True
65 else:
66 return False
67
68
69# Integrates a basis function on the lth interval corresponding to coordinates l, borc and s and then multiplied by
70# 2pi. For the remainder of this file l will refer to the interval we are working on, whereas a coordinate i will
71# refer to the coordinate of the basis function or parameter
72def integratebasisfunctiononl(pe, l, borc, s, coeffs):
73 basisfunc = lambda t: bf.basisfunctionvalue(t, l, borc, s, coeffs)
74
75 ps = bf.p(l)
76
77 phasederiv = 2 * np.pi * integrate.quad(basisfunc, ps, pe, epsabs=0)[0]
78
79 return phasederiv
80
81
82# Calculates the time average for the part of our phase that belongs to the lth interval for a parameter with
83# coordinates i, borc and k
84def singlephaseaverageonl(l, i, borc, s, coeffs):
85 if integralzerocheck(i, borc, l):
86 if i == l:
87 phasederiv = lambda t: integratebasisfunctiononl(t, l, borc, s, coeffs)
88 else:
89 phasederiv = lambda t: integratebasisfunctiononl(t, l, borc - 1, s, coeffs)
90 else:
91 phasederiv = 0
92
93 # Changed ints to large value because changing of how we generate knots
94 ps = bf.p(l)
95
96 # Case put in for when we generate knots and the l + 1th knot is currently being generated and doesn't exist yet
97 if l + 1 >= len(bf.knotslist):
98 pe = bf.knotslist[-1]
99 else:
100 pe = bf.p(l + 1)
101
102 return 1 / (pe - ps) * integrate.quad(phasederiv, ps, pe)[0]
103
104
105# Calculates the product of two averaged phase functions for the given parameter coordinates on the lth interval
106def singlephaseaveragesquaredonl(l, i1, i2, borc1, borc2, s1, s2, coeffs):
107 if doubleintegralzerocheck(i1, i2, borc1, borc2, l):
108 if i1 == l:
109 parta = singlephaseaverageonl(l, l, borc1, s1, coeffs)
110 else:
111 parta = singlephaseaverageonl(l, l, borc1 - 1, s1, coeffs)
112
113 if i2 == l:
114 partb = singlephaseaverageonl(l, l, borc2, s2, coeffs)
115 else:
116 partb = singlephaseaverageonl(l, l, borc2 - 1, s2, coeffs)
117
118 return parta * partb
119
120 else:
121 return 0
122
123
124# Calculates the average of the product of two phase functions for the given parameter coordinates on the lth interval
125def doublephaseaverageonl(l, i1, i2, borc1, borc2, s1, s2, coeffs):
126 if doubleintegralzerocheck(i1, i2, borc1, borc2, l):
127 if i1 == l:
128 parta = lambda t: integratebasisfunctiononl(t, l, borc1, s1, coeffs)
129 else:
130 parta = lambda t: integratebasisfunctiononl(t, l, borc1 - 1, s1, coeffs)
131
132 if i2 == l:
133 partb = lambda t: integratebasisfunctiononl(t, l, borc2, s2, coeffs)
134 else:
135 partb = lambda t: integratebasisfunctiononl(t, l, borc2 - 1, s2, coeffs)
136
137 # Changed ints to large value because changing of how we generate knots
138 ps = bf.p(l)
139
140 # Case put in for when we generate knots and the l + 1th knot is currently being generated and doesn't exist yet
141 if l + 1 >= len(bf.knotslist):
142 pe = bf.knotslist[-1]
143 else:
144 pe = bf.p(l + 1)
145
146 doubleaverage = lambda x: parta(x) * partb(x)
147
148 return 1 / (pe - ps) * integrate.quad(doubleaverage, ps, pe)[0]
149
150 else:
151 return 0
152
153
154# Calculates the metric element corresponding to the parameter with the given coordinates on the lth interval
155def metricelementonl(l, i1, i2, borc1, borc2, s1, s2, coeffs):
156 singleaveragedsquared = singlephaseaveragesquaredonl(
157 l, i1, i2, borc1, borc2, s1, s2, coeffs
158 )
159 doubleaverage = doublephaseaverageonl(l, i1, i2, borc1, borc2, s1, s2, coeffs)
160
161 return doubleaverage - singleaveragedsquared
162
163
164# Converts from the coordinates (i, k) to (i, borc, k)
166 if i == -1:
167 return [0, 0, s]
168
169 else:
170 return [i, 1, s]
171
172
173# Returns the list of coordinates that correspond to all parameters that will go into the computation of the metric
174# without repetition
176 coords = []
177 ints = len(bf.knotslist) - 1
178 for i in range(-1, ints):
179 for thiss in range(s):
180 coords.append([i, thiss])
181
182 return coords
183
184
185# Calculates the metric associated with the lth interval. The appropriate coordinates for all parameters and
186# coefficients for all basis functions must be given as input. Returns matrix with dimensions equal to the full
187# semicoherent metric. To use the metric with the appropriate dimensions for the given interval, use the method
188# metriconlcorrectdims
189def metriconl(l, coords, coeffs):
190 thismet = []
191
192 newcoords = []
193
194 for c in coords:
195 newcoords.append(twocoordstothreecoords(c[0], c[1]))
196
197 for nc in newcoords:
198 thisrow = []
199 for nnc in newcoords:
200 metelem = metricelementonl(
201 l, nc[0], nnc[0], nc[1], nnc[1], nc[2], nnc[2], coeffs
202 )
203
204 thisrow.append(metelem)
205
206 thismet.append(thisrow)
207
208 return np.array(thismet)
209
210
211# Calculates the semicoherent metric for the given parameters
212def metric(s):
213 coeffs = bf.allcoeffs(s)
214 coords = coordslist(s)
215
216 metsonints = []
217 ints = len(bf.knotslist) - 1
218 for l in range(ints):
219 metsonints.append(metriconl(l, coords, coeffs))
220
221 thismet = np.zeros(np.shape(metsonints[0]))
222
223 for met in metsonints:
224 thismet += met
225
226 return 1 / ints * thismet
227
228
229# Returns the metric component for a single segment of length Tdata, symbolically computed in Mathematica.
230# As the value of the metric should be independent of the start time, we do not need the knot values p0, p1,
231# but only the length of the segment, Tdata. While it may appear that some of the float denominators are not
232# exact (e.g. 1.89189e7), this is not the case and they are the exact value as calculated by Mathematica
234 p1 = float(Tdata)
235 Pi = np.pi
236
237 if s == 3:
238 firstrow = [
239 (5975 * p1**2 * Pi**2) / 63063.0,
240 (13859 * p1**3 * Pi**2) / 630630.0,
241 (475 * p1**4 * Pi**2) / 252252.0,
242 (9071 * p1**2 * Pi**2) / 126126.0,
243 (-23333 * p1**3 * Pi**2) / 1.26126e6,
244 (4259 * p1**4 * Pi**2) / 2.52252e6,
245 ]
246 secondrow = [
247 (13859 * p1**3 * Pi**2) / 630630.0,
248 (2764 * p1**4 * Pi**2) / 525525.0,
249 (321 * p1**5 * Pi**2) / 700700.0,
250 (23333 * p1**3 * Pi**2) / 1.26126e6,
251 (-29713 * p1**4 * Pi**2) / 6.3063e6,
252 (8077 * p1**5 * Pi**2) / 1.89189e7,
253 ]
254 thirdrow = [
255 (475 * p1**4 * Pi**2) / 252252.0,
256 (321 * p1**5 * Pi**2) / 700700.0,
257 (127 * p1**6 * Pi**2) / 3.15315e6,
258 (4259 * p1**4 * Pi**2) / 2.52252e6,
259 (-8077 * p1**5 * Pi**2) / 1.89189e7,
260 (233 * p1**6 * Pi**2) / 6.054048e6,
261 ]
262 fourthrow = [
263 (9071 * p1**2 * Pi**2) / 126126.0,
264 (23333 * p1**3 * Pi**2) / 1.26126e6,
265 (4259 * p1**4 * Pi**2) / 2.52252e6,
266 (5975 * p1**2 * Pi**2) / 63063.0,
267 (-13859 * p1**3 * Pi**2) / 630630.0,
268 (475 * p1**4 * Pi**2) / 252252.0,
269 ]
270 fifthrow = [
271 (-23333 * p1**3 * Pi**2) / 1.26126e6,
272 (-29713 * p1**4 * Pi**2) / 6.3063e6,
273 (-8077 * p1**5 * Pi**2) / 1.89189e7,
274 (-13859 * p1**3 * Pi**2) / 630630.0,
275 (2764 * p1**4 * Pi**2) / 525525.0,
276 (-321 * p1**5 * Pi**2) / 700700.0,
277 ]
278 sixthrow = [
279 (4259 * p1**4 * Pi**2) / 2.52252e6,
280 (8077 * p1**5 * Pi**2) / 1.89189e7,
281 (233 * p1**6 * Pi**2) / 6.054048e6,
282 (475 * p1**4 * Pi**2) / 252252.0,
283 (-321 * p1**5 * Pi**2) / 700700.0,
284 (127 * p1**6 * Pi**2) / 3.15315e6,
285 ]
286
287 metric = [firstrow, secondrow, thirdrow, fourthrow, fifthrow, sixthrow]
288
289 return metric
290 elif s == 2:
291 firstrow = [
292 (583 * p1**2 * Pi**2) / 6300.0,
293 (223 * p1**3 * Pi**2) / 12600.0,
294 (467 * p1**2 * Pi**2) / 6300.0,
295 (-197 * p1**3 * Pi**2) / 12600.0,
296 ]
297 secondrow = [
298 (223 * p1**3 * Pi**2) / 12600.0,
299 (11 * p1**4 * Pi**2) / 3150.0,
300 (197 * p1**3 * Pi**2) / 12600.0,
301 (-41 * p1**4 * Pi**2) / 12600.0,
302 ]
303 thirdrow = [
304 (467 * p1**2 * Pi**2) / 6300.0,
305 (197 * p1**3 * Pi**2) / 12600.0,
306 (583 * p1**2 * Pi**2) / 6300.0,
307 (-223 * p1**3 * Pi**2) / 12600.0,
308 ]
309 fourthrow = [
310 (-197 * p1**3 * Pi**2) / 12600.0,
311 (-41 * p1**4 * Pi**2) / 12600.0,
312 (-223 * p1**3 * Pi**2) / 12600.0,
313 (11 * p1**4 * Pi**2) / 3150.0,
314 ]
315
316 return [firstrow, secondrow, thirdrow, fourthrow]
317
318 else:
319 logging.debug(
320 "Given value of S has not yet been accounted for. Value of S given: "
321 + str(s)
322 )
324
325
327 if s != 3 and s != 2:
328 logging.debug(
329 "Given value of S has not yet been accounted for. Value of S given: "
330 + str(s)
331 )
333
334 segs = len(bf.knotslist) - 1
335
336 segmentmetrics = []
337
338 for seg in range(segs):
339 Tseg = bf.knotslist[seg + 1] - bf.knotslist[seg]
340
341 thismetric = PreCompSingleSegMetric(Tseg, s)
342
343 segmentmetrics.append(thismetric)
344
345 metric = np.zeros((s * (segs + 1), s * (segs + 1)))
346
347 for i, thismetric in enumerate(segmentmetrics):
348
349 prependzeros = [0 for j in range(i * s)]
350 appendzeros = [0 for j in range((segs - 1 - i) * s)]
351 zerorow = [0 for j in range((segs + 1) * s)]
352
353 submetric = []
354
355 prependzerorows = i * s
356 appendzerorows = (segs - 1 - i) * s
357
358 for j in range(prependzerorows):
359 submetric.append(zerorow)
360
361 for row in thismetric:
362 thisrow = []
363
364 thisrow = thisrow + prependzeros
365 thisrow = thisrow + row
366 thisrow = thisrow + appendzeros
367
368 submetric.append(thisrow)
369
370 for j in range(appendzerorows):
371 submetric.append(zerorow)
372
373 metric += np.array(submetric)
374
375 return 1 / segs * metric
def singlephaseaveragesquaredonl(l, i1, i2, borc1, borc2, s1, s2, coeffs)
def doublephaseaverageonl(l, i1, i2, borc1, borc2, s1, s2, coeffs)
def metricelementonl(l, i1, i2, borc1, borc2, s1, s2, coeffs)