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
estimating_knots.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"""
20Construct a list of knots which gives the maximum allowed spacing between
21them while maintaining the desired accuracy.
22"""
23
24import ast
25import logging
26import os.path
27
28import numpy as np
29
30from . import basis_functions as bf
31from . import mols_for_gte as mols
32from . import errors
33from . import semicoherent_metric_methods as scmm
34
35knotarchivefile = None
36
37
39 global knotarchivefile
40 knotarchivefile = os.path.join(path, "KnotArchive")
41
42
43# In this module, we use the function, max(|f_GTE(t) - F_PP(t)|) - Delta f_i0,
44# to choose the spacing of our knots. The Delta f_i0 is the spacing between the frequency parameter given by the
45# metric, it is equal to sqrt(mu/g_ii). Ideally, we wish for the maximum difference between our piecewise model and
46# the GTE to be minimised and smaller than the spacing of our frequency parameters. When finding the maximum
47# difference between the GTE and our model, max(|f_GTE(t) - F_PP(t)|), we maximise over the value t which falls into
48# some interval [p_i, p_i+1]. If we make interval sizes small the difference will go to zero, however this would
49# dramatically increase the number of intervals we need, and hence most likely increase the template bank size.
50# However, if we make them too large the error becomes greater than our parameter spacing. As such, we wish to
51# find the values [p_i, p_i+1] such that the given function above is zero. This is the longest we can make a segment
52# before the error becomes greater than our parameter spacing. We start by assuming p_0 = 0 and then inductively
53# calculate all of the following knots.
54
55# Working in this module it is important that the knots function, p(i, ints, dur) in the basis_functions file is
56# written correctly. See that file for how it should be defined when using this file. As well as this, this module
57# requires methods written in the mols_for_gte, basis_functions and semicoherent_metric_methods files, all of which rely
58# on the p(i, ints, dur) method. This can be a problem, as for the methods in this module to work, p(i, ints,
59# dur) should simply extract an element from a 'knots list' that we build inductively in this module. Hence,
60# using the knots generated in this module can at times be tricky. The recommendation for using knots built by this
61# file is to either run this file for given model parameter and then use the output as the knots by copying and
62# pasting the output into the basis_functions file to be used as all knots, or in the module you wish to run, run
63# the GlobalVariableDeclarations module which initialises our knot list (by importing this module and using the
64# methods below) and then import the GlobalVariableDeclarations module into the module you wish to run. As an
65# example, if you want to run module A which requires knots, import the GlobalVariableDeclarations module into A,
66# making sure that in the GlobalVariableDecalartions module the knots list is initialised with one of the methods
67# here.
68#
69# Knots generated in this module are saved to the KnotArchive text file.
70
71# Another word of caution, some methods in this module are outdated, being replaced by more efficient methods later
72# in the module. These methods typically run much slower than those which they are replaced by. Further more, many of
73# these methods do not make use of the KnotArchive file which stores all generated values of knots. I have tried to
74# note these outdated methods were appropriate and add an explanation to why they have been rewritten further in the
75# module. They are kept in this module however for completeness and a reference in case they are needed in the
76# future
77
78# When using this module be sure to check that the method p and the knotslist parameter in the basis_functions module
79# are defined correctly or are being properly updated.
80
81# List that we append the maximum parameter spacing for each segment we build knots for in this module. Kept only for
82# inspection, not used in any methods except for printing.
83parameterspacings = []
84
85# Returns the parameter spacing associated with the zeroth derivative at the lth knot. Note, if the knotslist variable
86# in the basis_functions is never updated, then this method will only work for l = 0
87def metricelementspacingonl(l, coeffs, mu):
88
89 metvalue = scmm.metricelementonl(l, l, l, 1, 1, 0, 0, coeffs)
90
91 stepsize = np.sqrt(mu / metvalue)
92
93 return stepsize
94
95
96# This method is an outdated method, replaced later in this module. Chronologiclly this was replaced first by the
97# knotatleff method and then finally by the knotatleffusinggivenknotlist method. No other methods in this module use
98# this method.
99#
100# Calcualtes the time (and hence knot) at which the function, max(|f_GTE(t) - F_PP(t)| - Delta f_i0 is zero. This is
101# achieved by sampling all times t between durmin and durmax and seeing if the appropriate value of that function is
102# within a given tolerance of zero. If not, we reuse this method with an extended duration of durmax to 2 * durmax. This
103# has the obvious flaw that if the correct knot value is below durmin, then this method will not be able to find it.
104# Because this method does not make use of faster algorithms, such as the bisection method which we use later on, it
105# runs very slowly.
106def knotatl(l, s, durmin, durmax, steps, f0, ngte, kgte, mu):
107 errorlists = []
108 paramspacinglist = []
109
110 durlist = np.linspace(durmin, durmax, steps)
111
112 for dur in durlist:
113 basiscoeffs = bf.allcoeffs(s)
114 modelparams = mols.solsbyint(mols.sols(basiscoeffs, 20, s, f0, ngte, kgte), s)
115 paramspacinglist.append(metricelementspacingonl(l, basiscoeffs, mu))
116
117 suberrorlist = []
118
119 for t in np.linspace(durmin, dur, steps):
120 suberrorlist.append(
121 mols.errorvalueatpoint(t, basiscoeffs, modelparams, f0, ngte, kgte)
122 )
123 errorlists.append(suberrorlist)
124
125 for i, dur in enumerate(durlist):
126
127 if np.max(errorlists[i]) - paramspacinglist[i] > 0:
128 return dur
129
130 logging.debug("No root found for specified duration, increasing duration span")
131 return knotatl(l, s, durmax, 2 * durmax, steps, f0, ngte, kgte)
132
133
134# This method is not used in our finalised knot generating method. It is however used in the buildcompleteknotlist
135# method. It was kept because the buildcompelteknotlist method (and this method) do not read or write to any files.
136# As such, if we want to do some experimentation with how we build our knots but not interfere and risk altering our
137# library of knots we can use these methods instead.
138#
139#
140# The same as the knotatl method but now done more efficiently by using a bisection method. Instead of sampling all
141# possible times/knots, we instead sample two possible durations, negdur and posdur. These knots should be chosen
142# such that negdur results in the function, max(|f_GTE(t) - F_PP(t)| - Delta f_i0, being negative and posdur such
143# that it is positive. We then select a time inbetween these two possible choices and calculated that function value.
144# If the value is within a given tolerance of zero we return that time as the new knot value. If not, we recursively use
145# this method by replacing negdur or posdur with this 'inbetween time' in accordance with the bisection method until we
146# find a time that is close enough to zero for us to be confident we can use that as the new knot value. This method
147# has a maximum recursion depth of 10.
148#
149# This method is somewhat sensitive to the initial choices of negdur and posdur. The checkfirst parameter is used
150# such that negdur and posdur have appropriate values. We must check that negdur and posdur result in negative and
151# positive values of the diff function respectively. In certain cases the ill-conditionedness of the basis functions
152# we calculate also influences our results. We check for this too in certain cases.
153#
154# The checkfirst value should always be used when knotatleff is first called and set to True. Once this check has
155# initially been cleared we do not need to check the negdur and posdur values again.
156#
157# In this method we refer to a boolean value fullMOLS, it is defined below.
159 l,
160 s,
161 negdur,
162 posdur,
163 steps,
164 f0,
165 ngte,
166 kgte,
167 mu,
168 checkfirst=False,
169 recursiondepth=0,
170 ps=None,
171 negcounter=0,
172):
173 # logging.debug("Current recursion depth: " + str(counter))
174
175 knotnuma = len(bf.knotslist) - 1
176 knotnumb = knotnuma + 1
177
178 # As negdur is a candidate knot, it should be greater than the last knot we currently have in bf.knotslist.
179 if bf.knotslist[l] >= negdur:
180 logging.debug(
181 "negdur parameter not within appropriate range, adjusting with larger value"
182 )
183 logging.debug(
184 "negdur = " + str(negdur) + ", previous knot = " + str(bf.knotslist[l])
185 )
186 return knotatleff(
187 l, s, bf.knotslist[l] + 1, posdur, steps, f0, ngte, kgte, mu, True, ps=ps
188 )
189
190 if checkfirst:
191 bf.knotslist.append(negdur)
192 basiscoeffsneg = bf.allcoeffs(s)
193
194 # Can't remember why but this sometimes throws a TypeError. I suspect it probably happens when negdur is too
195 # large and the sample points in the sampling_methods module are chosen poorly. Corrected for by using a
196 # smaller negdur value. Don't ever remember seeing this error for the posdur parameter though
197 try:
198 # logging.debug("Prev knot: " + str(ps))
199 if fullMOLS:
200 modelparamsneg = mols.solsbyint(
201 mols.sols(basiscoeffsneg, 20, s, f0, ngte, kgte), s
202 )
203 else:
204 modelparamsneg = mols.solsbyint(
205 mols.solsbetweenknots(
206 knotnuma, knotnumb, basiscoeffsneg, 20, s, f0, ngte, kgte
207 ),
208 s,
209 )
210 except TypeError as error:
211 logging.debug(error)
212 logging.debug("Reducing negdur duration")
213 diff = negdur - ps
214 bf.knotslist.pop()
215 return knotatleff(
216 l, s, ps + diff / 10, posdur, steps, f0, ngte, kgte, mu, True, 0, ps=ps
217 )
218
219 # Calculating the diff function value for negdur and posdur
220 paramspacingneg = metricelementspacingonl(l, basiscoeffsneg, mu)
221
222 errorlistneg = []
223
224 for t in np.linspace(bf.knotslist[-1], negdur, steps):
225 errorlistneg.append(
226 mols.errorvalueatpoint(
227 t, basiscoeffsneg, modelparamsneg, f0, ngte, kgte
228 )
229 )
230
231 bf.knotslist.pop()
232
233 bf.knotslist.append(posdur)
234 basiscoeffspos = bf.allcoeffs(s)
235 if fullMOLS:
236 modelparamspos = mols.solsbyint(
237 mols.sols(basiscoeffspos, 20, s, f0, ngte, kgte), s
238 )
239 else:
240 modelparamspos = mols.solsbyint(
241 mols.solsbetweenknots(
242 knotnuma, knotnumb, basiscoeffspos, 20, s, f0, ngte, kgte
243 ),
244 s,
245 )
246
247 paramspacingpos = metricelementspacingonl(l, basiscoeffspos, mu)
248
249 errorlistpos = []
250
251 for t in np.linspace(bf.knotslist[-1], posdur, steps):
252 errorlistpos.append(
253 mols.errorvalueatpoint(
254 t, basiscoeffspos, modelparamspos, f0, ngte, kgte
255 )
256 )
257
258 bf.knotslist.pop()
259
260 maxerrorneg = np.max(errorlistneg)
261 maxerrorpos = np.max(errorlistpos)
262
263 logging.debug("Neg and pos durs: " + str([negdur, posdur]))
264 logging.debug("Max errors: " + str([maxerrorneg, maxerrorpos]))
265 logging.debug("Metric values: " + str([paramspacingneg, paramspacingpos]))
266 logging.debug()
267
268 diffvalneg = maxerrorneg - paramspacingneg
269 diffvalpos = maxerrorpos - paramspacingpos
270
271 # Checking if posdur and negdur have the appropriate signs for the bisection method and readjusting if not
272 if not (diffvalneg < 0 and diffvalpos > 0):
273 # logging.debug("Diff vals are: " + str([diffvalneg, diffvalpos]))
274 if diffvalpos < 0:
275 # logging.debug("Negative pos val")
276 diff = posdur - ps
277 return knotatleff(
278 l, s, negdur, ps + 2 * diff, steps, f0, ngte, kgte, mu, True, ps=ps
279 )
280 if diffvalneg > 0:
281 # logging.debug("Positive neg val")
282 diff = negdur - ps
283
284 return knotatleff(
285 l,
286 s,
287 ps + diff / 10,
288 posdur,
289 steps,
290 f0,
291 ngte,
292 kgte,
293 mu,
294 True,
295 ps=ps,
296 negcounter=negcounter + 1,
297 )
298
299 # Calculating the diff function value for the time between negdur and posdur
300 halfdur = (posdur + negdur) / 2
301 bf.knotslist.append(halfdur)
302 basiscoeffs = bf.allcoeffs(s)
303 if fullMOLS:
304 modelparams = mols.solsbyint(mols.sols(basiscoeffs, 20, s, f0, ngte, kgte), s)
305 else:
306 modelparams = mols.solsbyint(
307 mols.solsbetweenknots(
308 knotnuma, knotnumb, basiscoeffs, 20, s, f0, ngte, kgte
309 ),
310 s,
311 )
312 paramspacing = metricelementspacingonl(l, basiscoeffs, mu)
313
314 errorlist = []
315
316 for t in np.linspace(bf.knotslist[-1], halfdur, steps):
317 errorlist.append(
318 mols.errorvalueatpoint(t, basiscoeffs, modelparams, f0, ngte, kgte)
319 )
320
321 bf.knotslist.pop()
322
323 maxerror = np.max(errorlist)
324
325 diffval = maxerror - paramspacing
326
327 # Either return halfdur as our new knot value or recursing
328 if -(2**-10) < diffval <= 0:
329 parameterspacings.append(paramspacing)
330 return halfdur
331 elif recursiondepth > 10:
332 logging.debug(
333 "Recursion depth of 10 reached, terminating and returning current knot value"
334 )
335 logging.debug("Function value for this knot is " + str(diffval))
336 parameterspacings.append(paramspacing)
337 return halfdur
338 else:
339 if diffval < 0:
340 return knotatleff(
341 l,
342 s,
343 halfdur,
344 posdur,
345 steps,
346 f0,
347 ngte,
348 kgte,
349 mu,
350 False,
351 recursiondepth + 1,
352 )
353 elif diffval > 0:
354 return knotatleff(
355 l,
356 s,
357 negdur,
358 halfdur,
359 steps,
360 f0,
361 ngte,
362 kgte,
363 mu,
364 False,
365 recursiondepth + 1,
366 )
367
368
369# Same as the knotatleff method but instead of refering to the bf.knotslist parameter we instead include this list of
370# previous knots as the parameter knotslist. The motivation behind this is that previous methods used to generate
371# knots had to start from an initial knot p_0 = 0. This was inconvenient if we instead wanted to calculate p_100 and
372# had already calculated p_99. Instead we carry all generated knots as a parameter in this function. We of course do
373# not need to carry the entire knot list for the model specifications, only the last knot in that list,
374# but we do this anyway.
375#
376# The logic in this method is identical to that of the knotatleff method with only the addition of the knotslist
377# parameter. Read that method's description for more details about the process of this method. This method has
378# a maximum recursion depth of 10.
379#
380# The fullMOLS parameter allows us to decide when considering the worse case scenario whether we want to consider the
381# accuracy of the entire MOLS solution or just the MOLS solution which belongs to the segment we are currently trying
382# to determine the knot for. Typically, if we consider the MOLS solution for the full signal, the knots are spread
383# further apart.
384fullMOLS = True
385
386
388 l,
389 s,
390 negdur,
391 posdur,
392 steps,
393 f0,
394 ngte,
395 kgte,
396 knotslist,
397 mu,
398 checkfirst=False,
399 counter=0,
400 ps=None,
401 negcounter=0,
402 prevdiffvalneg=np.inf,
403):
404 # logging.debug("Current recursion depth: " + str(counter))
405
406 knotnuma = len(knotslist) - 1
407 knotnumb = knotnuma + 1
408
409 # As negdur is a candidate knot, it should be greater than the last knot we currently have in bf.knotslist.
410 bf.knotslist = knotslist
411 if knotslist[l] >= negdur:
412 logging.debug(
413 "negdur parameter not within appropriate range, adjusting with larger value"
414 )
415 logging.debug(
416 "negdur = " + str(negdur) + ", previous knot = " + str(knotslist[l])
417 )
419 l,
420 s,
421 knotslist[l] + 1,
422 posdur,
423 steps,
424 f0,
425 ngte,
426 kgte,
427 knotslist,
428 mu,
429 True,
430 ps=ps,
431 )
432
433 if checkfirst:
434 bf.knotslist.append(negdur)
435 basiscoeffsneg = bf.allcoeffs(s)
436
437 # Can't remember why but this sometimes throws a TypeError. I suspect it probably happens when negdur is too
438 # large and the sample points in the sampling_methods module are chosen poorly. Corrected for by using a
439 # smaller negdur value. Don't ever remember seeing this error for the posdur parameter though
440 try:
441 if fullMOLS:
442 modelparamsneg = mols.solsbyint(
443 mols.sols(basiscoeffsneg, 20, s, f0, ngte, kgte), s
444 )
445 else:
446 modelparamsneg = mols.solsbyint(
447 mols.solsbetweenknots(
448 knotnuma, knotnumb, basiscoeffsneg, 20, s, f0, ngte, kgte
449 ),
450 s,
451 )
452 except TypeError as error:
453 logging.debug(error)
454 logging.debug("Reducing negdur duration")
455 bf.knotslist.pop()
456 diff = negdur - ps
458 l,
459 s,
460 ps + diff / 10,
461 posdur,
462 steps,
463 f0,
464 ngte,
465 kgte,
466 knotslist,
467 mu,
468 True,
469 0,
470 ps=ps,
471 )
472
473 # Calculating the diff function value for negdur and posdur
474 paramspacingneg = metricelementspacingonl(l, basiscoeffsneg, mu)
475
476 errorlistneg = []
477
478 for t in np.linspace(knotslist[-1], negdur, steps):
479 errorlistneg.append(
480 mols.errorvalueatpoint(
481 t, basiscoeffsneg, modelparamsneg, f0, ngte, kgte
482 )
483 )
484
485 bf.knotslist.pop()
486
487 bf.knotslist.append(posdur)
488 basiscoeffspos = bf.allcoeffs(s)
489
490 if fullMOLS:
491 modelparamspos = mols.solsbyint(
492 mols.sols(basiscoeffspos, 20, s, f0, ngte, kgte), s
493 )
494 else:
495 modelparamspos = mols.solsbyint(
496 mols.solsbetweenknots(
497 knotnuma, knotnumb, basiscoeffspos, 20, s, f0, ngte, kgte
498 ),
499 s,
500 )
501
502 paramspacingpos = metricelementspacingonl(l, basiscoeffspos, mu)
503
504 errorlistpos = []
505
506 for t in np.linspace(knotslist[-1], posdur, steps):
507 errorlistpos.append(
508 mols.errorvalueatpoint(
509 t, basiscoeffspos, modelparamspos, f0, ngte, kgte
510 )
511 )
512
513 bf.knotslist.pop()
514
515 maxerrorneg = np.max(errorlistneg)
516 maxerrorpos = np.max(errorlistpos)
517
518 diffvalneg = maxerrorneg - paramspacingneg
519 diffvalpos = maxerrorpos - paramspacingpos
520
521 # Checking if posdur and negdur have the appropriate signs for the bisection method and readjusting if not
522 if not (diffvalneg < 0 and diffvalpos > 0):
523 if diffvalpos < 0:
524 diff = posdur - ps
526 l,
527 s,
528 negdur,
529 ps + 2 * diff,
530 steps,
531 f0,
532 ngte,
533 kgte,
534 knotslist,
535 mu,
536 True,
537 ps=ps,
538 prevdiffvalneg=diffvalneg,
539 )
540 if diffvalneg > 0:
541 diff = negdur - ps
542
543 if diffvalneg > prevdiffvalneg:
545 l,
546 s,
547 ps + 2 * diff,
548 posdur,
549 steps,
550 f0,
551 ngte,
552 kgte,
553 knotslist,
554 mu,
555 True,
556 ps=ps,
557 negcounter=negcounter + 1,
558 prevdiffvalneg=diffvalneg,
559 )
560
561 # if negcounter > 5 or (diff/10) < 1:
562 # logging.debug("Too many negdur reductions")
563 # logging.debug()
564 # raise errors.TooManyNegdurReductions
565
567 l,
568 s,
569 ps + diff / 10,
570 posdur,
571 steps,
572 f0,
573 ngte,
574 kgte,
575 knotslist,
576 mu,
577 True,
578 ps=ps,
579 negcounter=negcounter + 1,
580 prevdiffvalneg=diffvalneg,
581 )
582
583 # Calculating the diff function value for the time between negdur and posdur
584 halfdur = (posdur + negdur) / 2
585 bf.knotslist.append(halfdur)
586 basiscoeffs = bf.allcoeffs(s)
587
588 if fullMOLS:
589 modelparams = mols.solsbyint(mols.sols(basiscoeffs, 20, s, f0, ngte, kgte), s)
590 else:
591 modelparams = mols.solsbyint(
592 mols.solsbetweenknots(
593 knotnuma, knotnumb, basiscoeffs, 20, s, f0, ngte, kgte
594 ),
595 s,
596 )
597
598 paramspacing = metricelementspacingonl(l, basiscoeffs, mu)
599
600 errorlist = []
601
602 for t in np.linspace(knotslist[-1], halfdur, steps):
603 errorlist.append(
604 mols.errorvalueatpoint(t, basiscoeffs, modelparams, f0, ngte, kgte)
605 )
606 bf.knotslist.pop()
607 maxerror = np.max(errorlist)
608
609 diffval = maxerror - paramspacing
610
611 # Either return halfdur as our new knot value or recurse
612 if -(2**-10) < diffval <= 0:
613 parameterspacings.append(paramspacing)
614 return halfdur
615 elif counter > 10:
616 logging.debug(
617 "Recursion depth of 10 reached, terminating and returning current knot value"
618 )
619 logging.debug("Function value for this knot is " + str(diffval))
620 parameterspacings.append(paramspacing)
621 return halfdur
622 else:
623 if diffval < 0:
625 l,
626 s,
627 halfdur,
628 posdur,
629 steps,
630 f0,
631 ngte,
632 kgte,
633 knotslist,
634 mu,
635 counter=counter + 1,
636 )
637 elif diffval > 0:
639 l,
640 s,
641 negdur,
642 halfdur,
643 steps,
644 f0,
645 ngte,
646 kgte,
647 knotslist,
648 mu,
649 counter=counter + 1,
650 )
651
652
653# knotlist = [0, 2.107421875, 4.18438720703125]
654# ints = len(knotlist)
655# logging.debug(knotatleffusinggivenknotlist(ints - 1, 1, ints, 1, 10, 30, knotlist, checkfirst=True, ps=knotlist[-1]))
656
657# Calculates and adds the next knot to the KnotArchive file with the appropriate specifications by using the
658# knotatleffusinggivenknotlist method
659def addnextknottofile(f0, nmax, kgte, s, mu, steps=30):
660 spindownspecification = [f0, nmax, kgte, s, mu, fullMOLS]
661
662 knotarchive = open(knotarchivefile, "r")
663 alllines = knotarchive.readlines()
664 knotarchive.close()
665
666 foundspecifications = False
667 newknot = 0
668
669 for i, line in enumerate(alllines):
670 thisline = ast.literal_eval(line)
671
672 if thisline[0] == spindownspecification:
673 foundspecifications = True
674
675 knotslist = thisline[1]
676 ps = thisline[1][-1]
677 ints = len(knotslist)
678
679 if ps == 0:
680 negdurincrement = 1.1
681 while True:
682 try:
684 ints - 1,
685 s,
686 negdurincrement,
687 10 * negdurincrement,
688 steps,
689 f0,
690 nmax,
691 kgte,
692 knotslist,
693 mu,
694 True,
695 ps=ps,
696 )
697
698 break
700 negdurincrement *= 10
701 else:
702 negdurincrement = 0
703 while True:
704 ordmag = np.floor(np.log10(ps)) + negdurincrement
705 try:
707 ints - 1,
708 s,
709 ps + 10**ordmag,
710 ps + 10 ** (ordmag + 0.5),
711 steps,
712 f0,
713 nmax,
714 kgte,
715 knotslist,
716 mu,
717 True,
718 ps=ps,
719 )
720 break
722 negdurincrement += 0.5
723 thisline[1].append(newknot)
724 updatedline = str(thisline) + "\n"
725 alllines[i] = updatedline
726 logging.info("Knotnum and knot val: " + str(ints) + ", " + str(newknot))
727 break
728
729 if foundspecifications:
730 knotarchive = open(knotarchivefile, "w")
731 knotarchive.writelines(alllines)
732 knotarchive.close()
733 else:
734 newline = [spindownspecification, [0]]
735
736 knotarchive = open(knotarchivefile, "a")
737 knotarchive.write(str(newline) + "\n")
738 knotarchive.close()
739
740 addnextknottofile(f0, nmax, kgte, s, mu, steps=steps)
741
742 return newknot
743
744
745# Calculates and writes all knots up the knotnum th knot to the KnotArchive file. E.g. If we want to know the first 10
746# knots for certain model specifications, if we run this method with knotnum = 10, if the KnotArchive file does not
747# already have those 10 knot values, this method will calculate and add those knot values to that file until all knots
748# up to the 10th knot are present
749def addknottoknotnum(s, knotnum, f0, nmax, kgte, mu, steps=30):
750 spindownspecifications = [f0, nmax, kgte, s, mu, fullMOLS]
751
752 knotarchive = open(knotarchivefile, "r")
753 alllines = knotarchive.readlines()
754 knotarchive.close()
755
756 currentknotnum = 0
757
758 for i, line in enumerate(alllines):
759 thisline = ast.literal_eval(line)
760
761 if thisline[0] == spindownspecifications:
762 currentknotnum = len(thisline[1]) - 1
763 break
764
765 while currentknotnum <= knotnum:
766 addnextknottofile(f0, nmax, kgte, s, mu, steps=steps)
767 currentknotnum += 1
768
769
770# Ad the above method but now instead of calculating up to a given knotnumber instead calculates all knots up to a given
771# signal duration.
772def addknottodur(s, dur, f0, nmax, kgte, mu, steps=30):
773 spindownspecifications = [f0, nmax, kgte, s, mu, fullMOLS]
774
775 knotarchive = open(knotarchivefile, "r")
776 alllines = knotarchive.readlines()
777 knotarchive.close()
778
779 currentdur = 0
780
781 for i, line in enumerate(alllines):
782 thisline = ast.literal_eval(line)
783
784 if thisline[0] == spindownspecifications:
785 currentdur = thisline[1][-1]
786 break
787
788 while currentdur < dur:
789 currentdur = addnextknottofile(f0, nmax, kgte, s, mu, steps=steps)
790
791
792# Calculates and returns all knots either up to the time dur or given knot number from the KnotArchive file. By using
793# this method we no longer need to always recalculate our knots, hopefully speeding up some of our other modules.
794def allidealisedknots(s, dur, steps, f0, nmax, kgte, mu, knotnum=0):
795 spindownspecifications = [f0, nmax, kgte, s, mu, fullMOLS]
796
797 if os.path.exists(knotarchivefile):
798 pass
799 else:
800 with open(knotarchivefile, "w") as _:
801 pass
802
803 if knotnum != 0:
804 addknottoknotnum(s, knotnum, f0, nmax, kgte, mu, steps=steps)
805 else:
806 addknottodur(s, dur, f0, nmax, kgte, mu, steps=steps)
807
808 knotarchive = open(knotarchivefile, "r")
809 alllines = knotarchive.readlines()
810 knotarchive.close()
811
812 for i, line in enumerate(alllines):
813 thisline = ast.literal_eval(line)
814
815 if thisline[0] == spindownspecifications:
816 bf.knotslist = thisline[1]
817
818 knotsbelowdur = []
819
820 if knotnum != 0:
821 knotsbelowdur = thisline[1][0 : knotnum + 1]
822 else:
823 # In case the knots stored in KnotArchive go well past the specified duration, we make sure we only set the knots to go to the duration we require
824 for i, knot in enumerate(bf.knotslist):
825 if knot < dur:
826 knotsbelowdur.append(knot)
827 else:
828 knotsbelowdur.append(dur)
829 break
830
831 bf.knotslist = knotsbelowdur
832
833 return bf.knotslist
def knotatleff(l, s, negdur, posdur, steps, f0, ngte, kgte, mu, checkfirst=False, recursiondepth=0, ps=None, negcounter=0)
def allidealisedknots(s, dur, steps, f0, nmax, kgte, mu, knotnum=0)
def knotatl(l, s, durmin, durmax, steps, f0, ngte, kgte, mu)
def addknottodur(s, dur, f0, nmax, kgte, mu, steps=30)
def addnextknottofile(f0, nmax, kgte, s, mu, steps=30)
def knotatleffusinggivenknotlist(l, s, negdur, posdur, steps, f0, ngte, kgte, knotslist, mu, checkfirst=False, counter=0, ps=None, negcounter=0, prevdiffvalneg=np.inf)
def addknottoknotnum(s, knotnum, f0, nmax, kgte, mu, steps=30)