lisaanalysistools 1.1.6__cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lisaanalysistools might be problematic. Click here for more details.

Files changed (42) hide show
  1. lisaanalysistools/git_version.py +7 -0
  2. lisaanalysistools-1.1.6.dist-info/METADATA +295 -0
  3. lisaanalysistools-1.1.6.dist-info/RECORD +42 -0
  4. lisaanalysistools-1.1.6.dist-info/WHEEL +6 -0
  5. lisaanalysistools-1.1.6.dist-info/licenses/LICENSE +201 -0
  6. lisatools/__init__.py +58 -0
  7. lisatools/_version.py +34 -0
  8. lisatools/analysiscontainer.py +474 -0
  9. lisatools/cutils/__init__.py +126 -0
  10. lisatools/datacontainer.py +312 -0
  11. lisatools/detector.py +704 -0
  12. lisatools/diagnostic.py +990 -0
  13. lisatools/git_version.py.in +7 -0
  14. lisatools/orbit_files/equalarmlength-orbits-best-fit-to-esa.h5 +0 -0
  15. lisatools/orbit_files/equalarmlength-orbits.h5 +0 -0
  16. lisatools/orbit_files/esa-trailing-orbits.h5 +0 -0
  17. lisatools/sampling/__init__.py +0 -0
  18. lisatools/sampling/likelihood.py +882 -0
  19. lisatools/sampling/moves/__init__.py +0 -0
  20. lisatools/sampling/moves/skymodehop.py +110 -0
  21. lisatools/sampling/prior.py +646 -0
  22. lisatools/sampling/stopping.py +320 -0
  23. lisatools/sampling/utility.py +411 -0
  24. lisatools/sensitivity.py +972 -0
  25. lisatools/sources/__init__.py +6 -0
  26. lisatools/sources/bbh/__init__.py +1 -0
  27. lisatools/sources/bbh/waveform.py +106 -0
  28. lisatools/sources/defaultresponse.py +36 -0
  29. lisatools/sources/emri/__init__.py +1 -0
  30. lisatools/sources/emri/waveform.py +79 -0
  31. lisatools/sources/gb/__init__.py +1 -0
  32. lisatools/sources/gb/waveform.py +69 -0
  33. lisatools/sources/utils.py +459 -0
  34. lisatools/sources/waveformbase.py +41 -0
  35. lisatools/stochastic.py +327 -0
  36. lisatools/utils/__init__.py +0 -0
  37. lisatools/utils/constants.py +54 -0
  38. lisatools/utils/exceptions.py +95 -0
  39. lisatools/utils/parallelbase.py +11 -0
  40. lisatools/utils/utility.py +245 -0
  41. lisatools_backend_cpu/git_version.py +7 -0
  42. lisatools_backend_cpu/pycppdetector.cpython-312-aarch64-linux-gnu.so +0 -0
lisatools/detector.py ADDED
@@ -0,0 +1,704 @@
1
+ from __future__ import annotations
2
+ import os
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, List, Tuple, Optional
5
+ from dataclasses import dataclass
6
+ import requests
7
+ from copy import deepcopy
8
+ import h5py
9
+ from scipy import interpolate
10
+
11
+ from .utils.constants import *
12
+ from .utils.utility import get_array_module
13
+
14
+ import numpy as np
15
+
16
+ from .utils.parallelbase import LISAToolsParallelModule
17
+
18
+
19
+ SC = [1, 2, 3]
20
+ LINKS = [12, 23, 31, 13, 32, 21]
21
+
22
+ LINEAR_INTERP_TIMESTEP = 600.00 # sec (0.25 hr)
23
+
24
+
25
+ class Orbits(LISAToolsParallelModule, ABC):
26
+ """LISA Orbit Base Class
27
+
28
+ Args:
29
+ filename: File name. File should be in the style of LISAOrbits
30
+ use_gpu: If ``True``, use a gpu.
31
+ armlength: Armlength of detector.
32
+
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ filename: str,
38
+ use_gpu: bool = False,
39
+ armlength: Optional[float] = 2.5e9,
40
+ **kwargs
41
+ ) -> None:
42
+ self.use_gpu = use_gpu
43
+ self.filename = filename
44
+ self.armlength = armlength
45
+ self._setup()
46
+ self.configured = False
47
+ LISAToolsParallelModule.__init__(self, **kwargs)
48
+
49
+ @property
50
+ def xp(self):
51
+ """numpy or cupy based on self.use_gpu"""
52
+ return self.backend.xp
53
+
54
+ @property
55
+ def armlength(self) -> float:
56
+ """Armlength parameter."""
57
+ return self._armlength
58
+
59
+ @armlength.setter
60
+ def armlength(self, armlength: float) -> None:
61
+ """armlength setter."""
62
+
63
+ if isinstance(armlength, float):
64
+ # TODO: put error check that it is close
65
+ self._armlength = armlength
66
+
67
+ else:
68
+ raise ValueError("armlength must be float.")
69
+
70
+ @property
71
+ def LINKS(self) -> List[int]:
72
+ """Link order."""
73
+ return LINKS
74
+
75
+ @property
76
+ def SC(self) -> List[int]:
77
+ """Spacecraft order."""
78
+ return SC
79
+
80
+ @property
81
+ def link_space_craft_r(self) -> List[int]:
82
+ """Receiver (first) spacecraft"""
83
+ return [int(str(link_i)[0]) for link_i in self.LINKS]
84
+
85
+ @property
86
+ def link_space_craft_e(self) -> List[int]:
87
+ """Sender (second) spacecraft"""
88
+ return [int(str(link_i)[1]) for link_i in self.LINKS]
89
+
90
+ def _setup(self) -> None:
91
+ """Read in orbital data from file and store."""
92
+ with self.open() as f:
93
+ for key in f.attrs.keys():
94
+ setattr(self, key + "_base", f.attrs[key])
95
+
96
+ @property
97
+ def filename(self) -> str:
98
+ """Orbit file name."""
99
+ return self._filename
100
+
101
+ @filename.setter
102
+ def filename(self, filename: str) -> None:
103
+ """Set file name."""
104
+
105
+ assert isinstance(filename, str)
106
+
107
+ if os.path.exists(filename):
108
+ self._filename = filename
109
+
110
+ else:
111
+ # get path
112
+ path_to_this_file = __file__.split("detector.py")[0]
113
+
114
+ # make sure orbit_files directory exists in the right place
115
+ if not os.path.exists(path_to_this_file + "orbit_files/"):
116
+ os.mkdir(path_to_this_file + "orbit_files/")
117
+ path_to_this_file = path_to_this_file + "orbit_files/"
118
+
119
+ if not os.path.exists(path_to_this_file + filename):
120
+ # download files from github if they are not there
121
+ github_file = f"https://github.com/mikekatz04/LISAanalysistools/blob/main/src/lisatools/orbit_files/{filename}"
122
+ r = requests.get(github_file)
123
+
124
+ # if not success
125
+ if r.status_code != 200:
126
+ raise ValueError(
127
+ f"Cannot find {filename} within default files located at github.com/mikekatz04/LISAanalysistools/lisatools/orbit_files/."
128
+ )
129
+ # write the contents to a local file
130
+ with open(path_to_this_file + filename, "wb") as f:
131
+ f.write(r.content)
132
+
133
+ # store
134
+ self._filename = path_to_this_file + filename
135
+
136
+ def open(self) -> h5py.File:
137
+ """Opens the h5 file in the proper mode.
138
+
139
+ Returns:
140
+ H5 file object: Opened file.
141
+
142
+ Raises:
143
+ RuntimeError: If backend is opened for writing when it is read-only.
144
+
145
+ """
146
+ f = h5py.File(self.filename, "r")
147
+ return f
148
+
149
+ @property
150
+ def t_base(self) -> np.ndarray:
151
+ """Time array from file."""
152
+ with self.open() as f:
153
+ t_base = np.arange(self.size_base) * self.dt_base
154
+ return t_base
155
+
156
+ @property
157
+ def ltt_base(self) -> np.ndarray:
158
+ """Light travel times along links from file."""
159
+ with self.open() as f:
160
+ ltt = f["tcb"]["ltt"][:]
161
+ return ltt
162
+
163
+ @property
164
+ def n_base(self) -> np.ndarray:
165
+ """Normal unit vectors towards receiver along links from file."""
166
+ with self.open() as f:
167
+ n = f["tcb"]["n"][:]
168
+ return n
169
+
170
+ @property
171
+ def x_base(self) -> np.ndarray:
172
+ """Spacecraft position from file."""
173
+ with self.open() as f:
174
+ x = f["tcb"]["x"][:]
175
+ return x
176
+
177
+ @property
178
+ def v_base(self) -> np.ndarray:
179
+ """Spacecraft velocities from file."""
180
+ with self.open() as f:
181
+ v = f["tcb"]["v"][:]
182
+ return v
183
+
184
+ @property
185
+ def t(self) -> np.ndarray:
186
+ """Configured time array."""
187
+ self._check_configured()
188
+ return self._t
189
+
190
+ @t.setter
191
+ def t(self, t: np.ndarray):
192
+ """Set configured time array."""
193
+ assert isinstance(t, np.ndarray) and t.ndim == 1
194
+ self._t = t
195
+
196
+ @property
197
+ def ltt(self) -> np.ndarray:
198
+ """Light travel time."""
199
+ self._check_configured()
200
+ return self._ltt
201
+
202
+ @ltt.setter
203
+ def ltt(self, ltt: np.ndarray) -> np.ndarray:
204
+ """Set light travel time."""
205
+ assert ltt.shape[0] == len(self.t)
206
+
207
+ @property
208
+ def n(self) -> np.ndarray:
209
+ """Normal vectors along links."""
210
+ self._check_configured()
211
+ return self._n
212
+
213
+ @n.setter
214
+ def n(self, n: np.ndarray) -> np.ndarray:
215
+ """Set Normal vectors along links."""
216
+ return self._n
217
+
218
+ @property
219
+ def x(self) -> np.ndarray:
220
+ """Spacecraft positions."""
221
+ self._check_configured()
222
+ return self._x
223
+
224
+ @x.setter
225
+ def x(self, x: np.ndarray) -> np.ndarray:
226
+ """Set Spacecraft positions."""
227
+ return self._x
228
+
229
+ @property
230
+ def v(self) -> np.ndarray:
231
+ """Spacecraft velocities."""
232
+ self._check_configured()
233
+ return self._v
234
+
235
+ @v.setter
236
+ def v(self, v: np.ndarray) -> np.ndarray:
237
+ """Set Spacecraft velocities."""
238
+ return self._v
239
+
240
+ def configure(
241
+ self,
242
+ t_arr: Optional[np.ndarray] = None,
243
+ dt: Optional[float] = None,
244
+ linear_interp_setup: Optional[bool] = False,
245
+ ) -> None:
246
+ """Configure the orbits to match the signal response generator time basis.
247
+
248
+ The base orbits will be scaled up or down as needed using Cubic Spline interpolation.
249
+ The higherarchy of consideration to each keyword argument if multiple are given:
250
+ ``linear_interp_setup``, ``t_arr``, ``dt``.
251
+
252
+ If nothing is provided, the base points are used.
253
+
254
+ Args:
255
+ t_arr: New time array.
256
+ dt: New time step. Will take the time duration to be that of the input data.
257
+ linear_interp_setup: If ``True``, it will create a dense grid designed for linear interpolation with a constant time step.
258
+
259
+ """
260
+
261
+ x_orig = self.t_base
262
+
263
+ # everything up base on input
264
+ if linear_interp_setup:
265
+ # setup spline
266
+ make_cpp = True
267
+ dt = LINEAR_INTERP_TIMESTEP
268
+ Tobs = self.t_base[-1]
269
+ Nobs = int(Tobs / dt)
270
+ t_arr = np.arange(Nobs) * dt
271
+ if t_arr[-1] < self.t_base[-1]:
272
+ t_arr = np.concatenate([t_arr, self.t_base[-1:]])
273
+ elif t_arr is not None:
274
+ # check array inputs and fill dt
275
+ assert np.all(t_arr >= self.t_base[0]) and np.all(t_arr <= self.t_base[-1])
276
+ make_cpp = True
277
+ dt = abs(t_arr[1] - t_arr[0])
278
+
279
+ elif dt is not None:
280
+ # fill array based on dt and base t
281
+ make_cpp = True
282
+ Tobs = self.t_base[-1]
283
+ Nobs = int(Tobs / dt)
284
+ t_arr = np.arange(Nobs) * dt
285
+ if t_arr[-1] < self.t_base[-1]:
286
+ t_arr = np.concatenate([t_arr, self.t_base[-1:]])
287
+
288
+ else:
289
+ make_cpp = False
290
+ t_arr = self.t_base
291
+
292
+ x_new = t_arr.copy()
293
+ self.t = t_arr.copy()
294
+
295
+ # use base quantities, and interpolate to prepare new arrays accordingly
296
+ for which in ["ltt", "x", "n", "v"]:
297
+ arr = getattr(self, which + "_base")
298
+ arr_tmp = arr.reshape(self.size_base, -1)
299
+ arr_out_tmp = np.zeros((len(x_new), arr_tmp.shape[-1]))
300
+ for i in range(arr_tmp.shape[-1]):
301
+ arr_out_tmp[:, i] = interpolate.CubicSpline(x_orig, arr_tmp[:, i])(
302
+ x_new
303
+ )
304
+ arr_out = arr_out_tmp.reshape((len(x_new),) + arr.shape[1:])
305
+ setattr(self, "_" + which, arr_out)
306
+
307
+ # make sure base spacecraft and link inormation is ready
308
+ lsr = np.asarray(self.link_space_craft_r).copy().astype(np.int32)
309
+ lse = np.asarray(self.link_space_craft_e).copy().astype(np.int32)
310
+ ll = np.asarray(self.LINKS).copy().astype(np.int32)
311
+
312
+ # indicate this class instance has been configured
313
+ self.configured = True
314
+
315
+ # prepare cpp class args to load when needed
316
+ if make_cpp:
317
+ self.pycppdetector_args = [
318
+ dt,
319
+ len(self.t),
320
+ self.xp.asarray(self.n.flatten().copy()),
321
+ self.xp.asarray(self.ltt.flatten().copy()),
322
+ self.xp.asarray(self.x.flatten().copy()),
323
+ self.xp.asarray(ll),
324
+ self.xp.asarray(lsr),
325
+ self.xp.asarray(lse),
326
+ self.armlength,
327
+ ]
328
+ self.dt = dt
329
+ else:
330
+ self.pycppdetector_args = None
331
+ self.dt = dt
332
+
333
+ @property
334
+ def dt(self) -> float:
335
+ """new time step if it exists"""
336
+ if self._dt is None:
337
+ raise ValueError("dt not available for t_arr only.")
338
+ return self._dt
339
+
340
+ @dt.setter
341
+ def dt(self, dt: float) -> None:
342
+ self._dt = dt
343
+
344
+ @property
345
+ def pycppdetector(self) -> object:
346
+ """C++ class"""
347
+ if self._pycppdetector_args is None:
348
+ raise ValueError(
349
+ "Asking for c++ class. Need to set linear_interp_setup = True when configuring."
350
+ )
351
+ self._pycppdetector = self.backend.pycppDetector(*self._pycppdetector_args)
352
+ return self._pycppdetector
353
+
354
+ @property
355
+ def pycppdetector_args(self) -> tuple:
356
+ """args for the c++ class."""
357
+ return self._pycppdetector_args
358
+
359
+ @pycppdetector_args.setter
360
+ def pycppdetector_args(self, pycppdetector_args: tuple) -> None:
361
+ self._pycppdetector_args = pycppdetector_args
362
+
363
+ @property
364
+ def size(self) -> int:
365
+ """Number of time points."""
366
+ self._check_configured()
367
+ return len(self.t)
368
+
369
+ def _check_configured(self) -> None:
370
+ if not self.configured:
371
+ raise ValueError(
372
+ "Cannot request property. Need to use configure() method first."
373
+ )
374
+
375
+ def get_light_travel_times(
376
+ self, t: float | np.ndarray, link: int | np.ndarray
377
+ ) -> float | np.ndarray:
378
+ """Compute light travel time as a function of time.
379
+
380
+ Computes with the c++ backend.
381
+
382
+ Args:
383
+ t: Time array in seconds.
384
+ link: which link. Must be ``in self.LINKS``.
385
+
386
+ Returns:
387
+ Light travel times.
388
+
389
+ """
390
+ # test and prepare inputs
391
+ if isinstance(t, float) and isinstance(link, int):
392
+ squeeze = True
393
+ t = self.xp.atleast_1d(t)
394
+ link = self.xp.atleast_1d(link).astype(np.int32)
395
+
396
+ elif isinstance(t, self.xp.ndarray) and isinstance(link, int):
397
+ squeeze = False
398
+ t = self.xp.atleast_1d(t)
399
+ link = self.xp.full_like(t, link, dtype=np.int32)
400
+
401
+ elif isinstance(t, self.xp.ndarray) and isinstance(link, self.xp.ndarray):
402
+ squeeze = False
403
+ t = self.xp.asarray(t)
404
+ link = self.xp.asarray(link).astype(np.int32)
405
+ else:
406
+ raise ValueError(
407
+ "(t, link) can be (float, int), (np.ndarray, int), (np.ndarray, np.ndarray)."
408
+ )
409
+
410
+ # buffer array and c computation
411
+ ltt_out = self.xp.zeros_like(t)
412
+ self.pycppdetector.get_light_travel_time_arr_wrap(
413
+ ltt_out, t, link, len(ltt_out)
414
+ )
415
+
416
+ # prepare output
417
+ if squeeze:
418
+ return ltt_out[0]
419
+ return ltt_out
420
+
421
+ def get_pos(self, t: float | np.ndarray, sc: int | np.ndarray) -> np.ndarray:
422
+ """Compute light travel time as a function of time.
423
+
424
+ Computes with the c++ backend.
425
+
426
+ Args:
427
+ t: Time array in seconds.
428
+ sc: which spacecraft. Must be ``in self.SC``.
429
+
430
+ Returns:
431
+ Position of spacecraft.
432
+
433
+ """
434
+ # test and setup inputs accordingly
435
+ if isinstance(t, float) and isinstance(sc, int):
436
+ squeeze = True
437
+ t = self.xp.atleast_1d(t)
438
+ sc = self.xp.atleast_1d(sc).astype(np.int32)
439
+
440
+ elif isinstance(t, self.xp.ndarray) and isinstance(sc, int):
441
+ squeeze = False
442
+ t = self.xp.atleast_1d(t)
443
+ sc = self.xp.full_like(t, sc, dtype=np.int32)
444
+
445
+ elif isinstance(t, self.xp.ndarray) and isinstance(sc, self.xp.ndarray):
446
+ squeeze = False
447
+ t = self.xp.asarray(t)
448
+ sc = self.xp.asarray(sc).astype(np.int32)
449
+
450
+ else:
451
+ raise ValueError(
452
+ "(t, sc) can be (float, int), (np.ndarray, int), (np.ndarray, np.ndarray). If the inputs follow this, make sure the orbits class GPU setting matches the arrays coming in (GPU or CPU)."
453
+ )
454
+
455
+ # buffer arrays for input into c code
456
+ pos_x = self.xp.zeros_like(t)
457
+ pos_y = self.xp.zeros_like(t)
458
+ pos_z = self.xp.zeros_like(t)
459
+
460
+ # c code computation
461
+ self.pycppdetector.get_pos_arr_wrap(pos_x, pos_y, pos_z, t, sc, len(pos_x))
462
+
463
+ # prepare output
464
+ output = self.xp.array([pos_x, pos_y, pos_z]).T
465
+ if squeeze:
466
+ return output.squeeze()
467
+ return output
468
+
469
+ def get_normal_unit_vec(
470
+ self, t: float | np.ndarray, link: int | np.ndarray
471
+ ) -> np.ndarray:
472
+ """Compute link normal vector as a function of time.
473
+
474
+ Computes with the c++ backend.
475
+
476
+ Args:
477
+ t: Time array in seconds.
478
+ link: which link. Must be ``in self.LINKS``.
479
+
480
+ Returns:
481
+ Link normal vectors.
482
+
483
+ """
484
+ # test and prepare inputs
485
+ if isinstance(t, float) and isinstance(link, int):
486
+ squeeze = True
487
+ t = self.xp.atleast_1d(t)
488
+ link = self.xp.atleast_1d(link).astype(np.int32)
489
+
490
+ elif isinstance(t, self.xp.ndarray) and isinstance(link, int):
491
+ squeeze = False
492
+ t = self.xp.atleast_1d(t)
493
+ link = self.xp.full_like(t, link, dtype=np.int32)
494
+
495
+ elif isinstance(t, self.xp.ndarray) and isinstance(link, self.xp.ndarray):
496
+ squeeze = False
497
+ t = self.xp.asarray(t)
498
+ link = self.xp.asarray(link).astype(np.int32)
499
+ else:
500
+ raise ValueError(
501
+ "(t, link) can be (float, int), (np.ndarray, int), (np.ndarray, np.ndarray)."
502
+ )
503
+
504
+ # c code with buffers
505
+ normal_unit_vec_x = self.xp.zeros_like(t)
506
+ normal_unit_vec_y = self.xp.zeros_like(t)
507
+ normal_unit_vec_z = self.xp.zeros_like(t)
508
+
509
+ # c code
510
+ self.pycppdetector.get_normal_unit_vec_arr_wrap(
511
+ normal_unit_vec_x,
512
+ normal_unit_vec_y,
513
+ normal_unit_vec_z,
514
+ t,
515
+ link,
516
+ len(normal_unit_vec_x),
517
+ )
518
+
519
+ # prep outputs
520
+ output = self.xp.array(
521
+ [normal_unit_vec_x, normal_unit_vec_y, normal_unit_vec_z]
522
+ ).T
523
+ if squeeze:
524
+ return output.squeeze()
525
+ return output
526
+
527
+ @property
528
+ def ptr(self) -> int:
529
+ """pointer to c++ class"""
530
+ return self.pycppdetector.ptr
531
+
532
+
533
+ @classmethod
534
+ def supported_backends(cls):
535
+ return ["lisatools_" + _tmp for _tmp in cls.GPU_RECOMMENDED()]
536
+
537
+
538
+
539
+ class EqualArmlengthOrbits(Orbits):
540
+ """Equal Armlength Orbits
541
+
542
+ Orbit file: equalarmlength-orbits.h5
543
+
544
+ Args:
545
+ *args: Arguments for :class:`Orbits`.
546
+ **kwargs: Kwargs for :class:`Orbits`.
547
+
548
+ """
549
+
550
+ def __init__(self, *args: Any, **kwargs: Any):
551
+ super().__init__("equalarmlength-orbits.h5", *args, **kwargs)
552
+
553
+
554
+ class ESAOrbits(Orbits):
555
+ """ESA Orbits
556
+
557
+ Orbit file: esa-trailing-orbits.h5
558
+
559
+ Args:
560
+ *args: Arguments for :class:`Orbits`.
561
+ **kwargs: Kwargs for :class:`Orbits`.
562
+
563
+ """
564
+
565
+ def __init__(self, *args, **kwargs):
566
+ super().__init__("esa-trailing-orbits.h5", *args, **kwargs)
567
+
568
+
569
+ class DefaultOrbits(EqualArmlengthOrbits):
570
+ """Set default orbit class to Equal Armlength orbits for now."""
571
+
572
+ pass
573
+
574
+
575
+ @dataclass
576
+ class LISAModelSettings:
577
+ """Required LISA model settings:
578
+
579
+ Args:
580
+ Soms_d: OMS displacement noise.
581
+ Sa_a: Acceleration noise.
582
+ orbits: Orbital information.
583
+ name: Name of model.
584
+
585
+ """
586
+
587
+ Soms_d: float
588
+ Sa_a: float
589
+ orbits: Orbits
590
+ name: str
591
+
592
+
593
+ class LISAModel(LISAModelSettings, ABC):
594
+ """Model for the LISA Constellation
595
+
596
+ This includes sensitivity information computed in
597
+ :py:mod:`lisatools.sensitivity` and orbital information
598
+ contained in an :class:`Orbits` class object.
599
+
600
+ This class is used to house high-level methods useful
601
+ to various needed computations.
602
+
603
+ """
604
+
605
+ def __str__(self) -> str:
606
+ out = "LISA Constellation Configurations Settings:\n"
607
+ for key, item in self.__dict__.items():
608
+ out += f"{key}: {item}\n"
609
+ return out
610
+
611
+ def lisanoises(
612
+ self,
613
+ f: float | np.ndarray,
614
+ unit: Optional[str] = "relative_frequency",
615
+ ) -> Tuple[float, float]:
616
+ """Calculate both LISA noise terms based on input model.
617
+ Args:
618
+ f: Frequency array.
619
+ unit: Either ``"relative_frequency"`` or ``"displacement"``.
620
+ Returns:
621
+ Tuple with acceleration term as first value and oms term as second value.
622
+ """
623
+
624
+ # TODO: fix this up
625
+ Soms_d_in = self.Soms_d
626
+ Sa_a_in = self.Sa_a
627
+
628
+ frq = f
629
+ ### Acceleration noise
630
+ ## In acceleration
631
+ Sa_a = Sa_a_in * (1.0 + (0.4e-3 / frq) ** 2) * (1.0 + (frq / 8e-3) ** 4)
632
+ ## In displacement
633
+ Sa_d = Sa_a * (2.0 * np.pi * frq) ** (-4.0)
634
+ ## In relative frequency unit
635
+ Sa_nu = Sa_d * (2.0 * np.pi * frq / C_SI) ** 2
636
+ Spm = Sa_nu
637
+
638
+ ### Optical Metrology System
639
+ ## In displacement
640
+ Soms_d = Soms_d_in * (1.0 + (2.0e-3 / f) ** 4)
641
+ ## In relative frequency unit
642
+ Soms_nu = Soms_d * (2.0 * np.pi * frq / C_SI) ** 2
643
+ Sop = Soms_nu
644
+
645
+ if unit == "displacement":
646
+ return Sa_d, Soms_d
647
+ elif unit == "relative_frequency":
648
+ return Spm, Sop
649
+
650
+
651
+ # defaults
652
+ scirdv1 = LISAModel((15.0e-12) ** 2, (3.0e-15) ** 2, DefaultOrbits(), "scirdv1")
653
+ proposal = LISAModel((10.0e-12) ** 2, (3.0e-15) ** 2, DefaultOrbits(), "proposal")
654
+ mrdv1 = LISAModel((10.0e-12) ** 2, (2.4e-15) ** 2, DefaultOrbits(), "mrdv1")
655
+ sangria = LISAModel((7.9e-12) ** 2, (2.4e-15) ** 2, DefaultOrbits(), "sangria")
656
+
657
+ __stock_list_models__ = [scirdv1, proposal, mrdv1, sangria]
658
+ __stock_list_models_name__ = [tmp.name for tmp in __stock_list_models__]
659
+
660
+
661
+ def get_available_default_lisa_models() -> List[LISAModel]:
662
+ """Get list of default LISA models
663
+
664
+ Returns:
665
+ List of LISA models.
666
+
667
+ """
668
+ return __stock_list_models__
669
+
670
+
671
+ def get_default_lisa_model_from_str(model: str) -> LISAModel:
672
+ """Return a LISA model from a ``str`` input.
673
+
674
+ Args:
675
+ model: Model indicated with a ``str``.
676
+
677
+ Returns:
678
+ LISA model associated to that ``str``.
679
+
680
+ """
681
+ if model not in __stock_list_models_name__:
682
+ raise ValueError(
683
+ "Requested string model is not available. See lisatools.detector documentation."
684
+ )
685
+ return globals()[model]
686
+
687
+
688
+ def check_lisa_model(model: Any) -> LISAModel:
689
+ """Check input LISA model.
690
+
691
+ Args:
692
+ model: LISA model to check.
693
+
694
+ Returns:
695
+ LISA Model checked. Adjusted from ``str`` if ``str`` input.
696
+
697
+ """
698
+ if isinstance(model, str):
699
+ model = get_default_lisa_model_from_str(model)
700
+
701
+ if not isinstance(model, LISAModel):
702
+ raise ValueError("model argument not given correctly.")
703
+
704
+ return model