cows3 0.0.1__tar.gz

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.
@@ -0,0 +1,118 @@
1
+ # Taken from https://packaging.python.org/en/latest/tutorials/packaging-projects/
2
+ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
3
+
4
+ on: workflow_dispatch
5
+
6
+ jobs:
7
+ build:
8
+ name: Build distribution 📦
9
+ runs-on: ubuntu-latest
10
+
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.11"
17
+ - name: Install pypa/build
18
+ run: >-
19
+ python3 -m
20
+ pip install
21
+ build
22
+ --user
23
+ - name: Build a binary wheel and a source tarball
24
+ run: python3 -m build
25
+ - name: Store the distribution packages
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: python-package-distributions
29
+ path: dist/
30
+
31
+ publish-to-pypi:
32
+ name: >-
33
+ Publish Python 🐍 distribution 📦 to PyPI
34
+ if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
35
+ needs:
36
+ - build
37
+ runs-on: ubuntu-latest
38
+ environment:
39
+ name: pypi
40
+ url: https://pypi.org/p/foutstep # Replace <package-name> with your PyPI project name
41
+ permissions:
42
+ id-token: write # IMPORTANT: mandatory for trusted publishing
43
+
44
+ steps:
45
+ - name: Download all the dists
46
+ uses: actions/download-artifact@v4
47
+ with:
48
+ name: python-package-distributions
49
+ path: dist/
50
+ - name: Publish distribution 📦 to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+
53
+ github-release:
54
+ name: >-
55
+ Sign the Python 🐍 distribution 📦 with Sigstore
56
+ and upload them to GitHub Release
57
+ needs:
58
+ - publish-to-pypi
59
+ runs-on: ubuntu-latest
60
+
61
+ permissions:
62
+ contents: write # IMPORTANT: mandatory for making GitHub Releases
63
+ id-token: write # IMPORTANT: mandatory for sigstore
64
+
65
+ steps:
66
+ - name: Download all the dists
67
+ uses: actions/download-artifact@v4
68
+ with:
69
+ name: python-package-distributions
70
+ path: dist/
71
+ - name: Sign the dists with Sigstore
72
+ uses: sigstore/gh-action-sigstore-python@v2.1.1
73
+ with:
74
+ inputs: >-
75
+ ./dist/*.tar.gz
76
+ ./dist/*.whl
77
+ - name: Create GitHub Release
78
+ env:
79
+ GITHUB_TOKEN: ${{ github.token }}
80
+ run: >-
81
+ gh release create
82
+ '${{ github.ref_name }}'
83
+ --repo '${{ github.repository }}'
84
+ --notes ""
85
+ - name: Upload artifact signatures to GitHub Release
86
+ env:
87
+ GITHUB_TOKEN: ${{ github.token }}
88
+ # Upload to GitHub Release using the `gh` CLI.
89
+ # `dist/` contains the built packages, and the
90
+ # sigstore-produced signatures and certificates.
91
+ run: >-
92
+ gh release upload
93
+ '${{ github.ref_name }}' dist/**
94
+ --repo '${{ github.repository }}'
95
+
96
+ publish-to-testpypi:
97
+ name: Publish Python 🐍 distribution 📦 to TestPyPI
98
+ needs:
99
+ - build
100
+ runs-on: ubuntu-latest
101
+
102
+ environment:
103
+ name: testpypi
104
+ url: https://test.pypi.org/p/foutstep
105
+
106
+ permissions:
107
+ id-token: write # IMPORTANT: mandatory for trusted publishing
108
+
109
+ steps:
110
+ - name: Download all the dists
111
+ uses: actions/download-artifact@v4
112
+ with:
113
+ name: python-package-distributions
114
+ path: dist/
115
+ - name: Publish distribution 📦 to TestPyPI
116
+ uses: pypa/gh-action-pypi-publish@release/v1
117
+ with:
118
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,31 @@
1
+
2
+ name: Run pytest
3
+
4
+ on:
5
+ push:
6
+ branches: [ "main" ]
7
+ pull_request:
8
+ branches: [ "main" ]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ build:
15
+
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - name: Set up Python 3.11
21
+ uses: actions/setup-python@v3
22
+ with:
23
+ python-version: "3.11"
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install pytest .
28
+ - name: Test with pytest
29
+ run: |
30
+ pytest
31
+
cows3-0.0.1/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *egg*
2
+ __pycache__
cows3-0.0.1/LICENSE.md ADDED
@@ -0,0 +1,10 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rodrigo Tenorio, Lorenzo Mirasola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
+
cows3-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.1
2
+ Name: cows3
3
+ Version: 0.0.1
4
+ Summary: Continuous-wave search sensitivity simulator
5
+ Author-email: Rodrigo Tenorio <rodrigo.tenorio.marquez@gmail.com>, Lorenzo Mirasola <lorenzo.mirasola@ca.infn.it>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Rodrigo Tenorio, Lorenzo Mirasola
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+
16
+
17
+ Project-URL: Homepage, https://github.com/Rodrigo-Tenorio/cows3
18
+ Project-URL: Issues, https://github.com/Rodrigo-Tenorio/cows3/issues
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
22
+ Classifier: Topic :: Scientific/Engineering :: Physics
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.md
26
+ Requires-Dist: lalsuite
27
+ Requires-Dist: numpy
28
+ Requires-Dist: scipy
29
+ Requires-Dist: solar_system_ephemerides
30
+ Provides-Extra: dev
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: flake8; extra == "dev"
33
+ Requires-Dist: pytest; extra == "dev"
34
+
35
+ # Continuous-wave search sensitivity simulator (COWS3)
36
+
37
+ A Python package to estimate the sensitivity of general
38
+ continuous gravitational-wave searches.
39
+
40
+ The method should be equivalent to the semi-analytical approach derived in
41
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459) and
42
+ implemented in [Octapps](https://github.com/octapps/octapps), but here we
43
+ implement it in Python to make it more convenient to use.
44
+
45
+ ## Citing this work
46
+
47
+ If COWS3 was useful to your research, we would appreciate if you cited
48
+ [Mirasola & Tenorio (2024)](https://arxiv.org/abs/2405.18934) where this
49
+ implementation was first presented:
50
+ ```
51
+ @article{Mirasola:2024lcq,
52
+ author = "Mirasola, Lorenzo and Tenorio, Rodrigo",
53
+ title = "{Towards a computationally-efficient follow-up pipeline for blind continuous gravitational-wave searches}",
54
+ eprint = "2405.18934",
55
+ archivePrefix = "arXiv",
56
+ primaryClass = "gr-qc",
57
+ reportNumber = "LIGO-P2400221",
58
+ month = "5",
59
+ year = "2024",
60
+ journal = "arXiv e-prints"
61
+ }
62
+ ```
63
+ as well as a Zenodo release of this software.
64
+
65
+ For the semi-analytical sensitivity estimation method you should also cite
66
+ [Wette (2012)](https://arxiv.org/abs/1111.5650) and
67
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459). Also,
68
+ this package makes extensive use of SWIG bindings, so please cite
69
+ [Wette (2021)](https://arxiv.org/abs/2012.09552) as well.
70
+
71
+
72
+ ## Authors
73
+ - Rodrigo Tenorio
74
+ - Lorenzo Mirasola
75
+
76
+
cows3-0.0.1/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Continuous-wave search sensitivity simulator (COWS3)
2
+
3
+ A Python package to estimate the sensitivity of general
4
+ continuous gravitational-wave searches.
5
+
6
+ The method should be equivalent to the semi-analytical approach derived in
7
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459) and
8
+ implemented in [Octapps](https://github.com/octapps/octapps), but here we
9
+ implement it in Python to make it more convenient to use.
10
+
11
+ ## Citing this work
12
+
13
+ If COWS3 was useful to your research, we would appreciate if you cited
14
+ [Mirasola & Tenorio (2024)](https://arxiv.org/abs/2405.18934) where this
15
+ implementation was first presented:
16
+ ```
17
+ @article{Mirasola:2024lcq,
18
+ author = "Mirasola, Lorenzo and Tenorio, Rodrigo",
19
+ title = "{Towards a computationally-efficient follow-up pipeline for blind continuous gravitational-wave searches}",
20
+ eprint = "2405.18934",
21
+ archivePrefix = "arXiv",
22
+ primaryClass = "gr-qc",
23
+ reportNumber = "LIGO-P2400221",
24
+ month = "5",
25
+ year = "2024",
26
+ journal = "arXiv e-prints"
27
+ }
28
+ ```
29
+ as well as a Zenodo release of this software.
30
+
31
+ For the semi-analytical sensitivity estimation method you should also cite
32
+ [Wette (2012)](https://arxiv.org/abs/1111.5650) and
33
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459). Also,
34
+ this package makes extensive use of SWIG bindings, so please cite
35
+ [Wette (2021)](https://arxiv.org/abs/2012.09552) as well.
36
+
37
+
38
+ ## Authors
39
+ - Rodrigo Tenorio
40
+ - Lorenzo Mirasola
41
+
42
+
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "setuptools_scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ authors = [
7
+ {name = "Rodrigo Tenorio", email = "rodrigo.tenorio.marquez@gmail.com"},
8
+ {name = "Lorenzo Mirasola", email = "lorenzo.mirasola@ca.infn.it"},
9
+ ]
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Topic :: Scientific/Engineering :: Astronomy",
14
+ "Topic :: Scientific/Engineering :: Physics"
15
+ ]
16
+ dependencies = [
17
+ "lalsuite",
18
+ "numpy",
19
+ "scipy",
20
+ "solar_system_ephemerides",
21
+ ]
22
+ description = "Continuous-wave search sensitivity simulator"
23
+ dynamic = ["version"]
24
+ license = {file = "LICENSE.md"}
25
+ name = "cows3"
26
+ readme = "README.md"
27
+ requires-python = ">=3.11"
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "black",
32
+ "flake8",
33
+ "pytest"
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/Rodrigo-Tenorio/cows3"
38
+ Issues = "https://github.com/Rodrigo-Tenorio/cows3/issues"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+ include = ["cows3"]
43
+
44
+ [tool.setuptools_scm]
45
+
46
+ [tool.pytest.ini_options]
47
+ log_cli = true
48
+ log_cli_level = "DEBUG"
49
+ log_file_level = "DEBUG"
50
+
cows3-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
cows3-0.0.1/setup.py ADDED
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+ if __name__ == "__main__":
4
+ setup()
File without changes
@@ -0,0 +1,143 @@
1
+ import logging
2
+
3
+ import lal
4
+ import lalpulsar
5
+ import numpy as np
6
+
7
+ from .ephemeris import DEFAULT_EPHEMERIS
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class MultiDetectorStates:
13
+ """
14
+ Python interface to `XLALGetMultiDetectorStates` and
15
+ `XLALGetMultiDetectorStatesFromMultiSFTs`.
16
+
17
+ Parameters
18
+ ----------
19
+ timestamps:
20
+ Dictionary containing the GPS timestamps at which detector
21
+ states will be retrieved.
22
+ Keys MUST be two-character detector names as described in LALSuite;
23
+ values MUST be numpy arrays containing the timestamps.
24
+ E.g. for an observing run from GPS 1 to GPS 5 using LIGO Hanford
25
+ and LIGO Livingston:
26
+ ```
27
+ timestamps = {
28
+ "H1": np.array([1, 2, 3, 4, 5]),
29
+ "L1": np.array([1, 2, 3, 4, 5])
30
+ }
31
+ ```
32
+ T_sft:
33
+ Time period covered for each timestamp. Does not need to coincide
34
+ with the separation between consecutive timestamps. It will be floored
35
+ using `int`.
36
+ t_offset:
37
+ Time offset with respect to the timestamp at which the detector
38
+ state will be retrieved. Defaults to LALSuite's behaviour.
39
+ ephemeris:
40
+ Default uses `solar_system_ephemerides` to get lalsuite's default.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ timestamps: dict[str, np.array],
46
+ T_sft: int,
47
+ t_offset: int | None = None,
48
+ ephemeris: lalpulsar.EphemerisData = DEFAULT_EPHEMERIS,
49
+ ):
50
+ self.timestamps = timestamps
51
+ self.T_sft = T_sft
52
+ self.t_offset = t_offset
53
+ self.ephemeris = ephemeris
54
+
55
+ @property
56
+ def Series(self) -> lalpulsar.MultiDetectorStateSeries:
57
+ """
58
+ Return lalpulsar.MultiDetectorStateSeries constructed using the instance's attributes.
59
+ """
60
+ return lalpulsar.GetMultiDetectorStates(
61
+ multiTS=self.multi_timestamps,
62
+ multiIFO=self.multi_lal_detector,
63
+ edat=self.ephemeris,
64
+ tOffset=self.t_offset,
65
+ )
66
+
67
+ @property
68
+ def velocities(self) -> dict[str, np.ndarray]:
69
+ """
70
+ Extracts detector velocity vectors into numpy arrays.
71
+
72
+ Returns
73
+ -------
74
+ velocities:
75
+ Dictionary. Keys refer to detector's 2-character prefix,
76
+ values are (3, num_timestamps) numpy arrays.
77
+ """
78
+
79
+ mdss = self.Series
80
+
81
+ velocities = {}
82
+
83
+ for ifo_ind in range(mdss.length):
84
+ ifo_name = mdss.data[ifo_ind].detector.frDetector.prefix
85
+ velocities[ifo_name] = np.vstack(
86
+ [data.vDetector for data in mdss.data[ifo_ind].data]
87
+ ).T
88
+
89
+ return velocities
90
+
91
+ @property
92
+ def timestamps(self) -> dict[str, np.ndarray]:
93
+ return self._timestamps
94
+
95
+ @property
96
+ def T_sft(self) -> int:
97
+ return self._T_sft
98
+
99
+ @property
100
+ def t_offset(self) -> int | float:
101
+ return self._t_offset
102
+
103
+ @property
104
+ def multi_lal_detector(self) -> lalpulsar.MultiLALDetector:
105
+ return self._multi_lal_detector
106
+
107
+ @property
108
+ def multi_timestamps(self) -> lalpulsar.MultiLIGOTimeGPSVector:
109
+ return self._multi_timestamps
110
+
111
+ @timestamps.setter
112
+ def timestamps(self, new_timestamps: dict):
113
+
114
+ self._timestamps = new_timestamps
115
+
116
+ self._multi_lal_detector = lalpulsar.MultiLALDetector()
117
+ lalpulsar.ParseMultiLALDetector(self._multi_lal_detector, [*self._timestamps])
118
+
119
+ self._multi_timestamps = lalpulsar.CreateMultiLIGOTimeGPSVector(
120
+ self._multi_lal_detector.length
121
+ )
122
+ for ind, ifo in enumerate(new_timestamps):
123
+ seconds_array = np.floor(new_timestamps[ifo])
124
+ nanoseconds_array = np.floor(1e9 * (new_timestamps[ifo] - seconds_array))
125
+
126
+ self._multi_timestamps.data[ind] = lalpulsar.CreateTimestampVector(
127
+ seconds_array.shape[0]
128
+ )
129
+
130
+ for ts_ind in range(self._multi_timestamps.data[ind].length):
131
+ self._multi_timestamps.data[ind].data[ts_ind] = lal.LIGOTimeGPS(
132
+ int(seconds_array[ts_ind]), int(nanoseconds_array[ts_ind])
133
+ )
134
+
135
+ @T_sft.setter
136
+ def T_sft(self, new_T_sft: int):
137
+ self._T_sft = int(new_T_sft)
138
+ for ifo_ind in range(self.multi_timestamps.length):
139
+ self._multi_timestamps.data[ifo_ind].deltaT = self._T_sft
140
+
141
+ @t_offset.setter
142
+ def t_offset(self, new_t_offset: int | None):
143
+ self._t_offset = new_t_offset if new_t_offset is not None else 0.5 * self.T_sft
@@ -0,0 +1,10 @@
1
+ import logging
2
+ import lalpulsar
3
+ from solar_system_ephemerides import body_ephemeris_path
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ DEFAULT_EPHEMERIS = lalpulsar.InitBarycenter(
8
+ str(body_ephemeris_path("earth")),
9
+ str(body_ephemeris_path("sun")),
10
+ )
@@ -0,0 +1,38 @@
1
+ import numpy as np
2
+ from scipy import integrate, stats
3
+
4
+
5
+ def pfd_Fstatistic(
6
+ twoF_threshold: float | np.ndarray,
7
+ depth: float | np.ndarray,
8
+ num_segments: int | np.ndarray,
9
+ unitD_rho2_bins: np.ndarray,
10
+ unitD_rho2_pdf: np.ndarray,
11
+ ):
12
+ """
13
+ Compute the false-dismissal probability for a semicoherent F-statistic
14
+ for a population of signals at a sensitivity depth `depth` given
15
+ a unit-depth SNR^2 `unitD_rho2_pdf`.
16
+ Integration is performed across the last axis of the arrays.
17
+
18
+ twoF_threshold:
19
+ Threshold at which the false-dismissal probability will be computed.
20
+ This threshold corresponds to the semicoherent twoF statistic,
21
+ which is distributed as a chi-squared distribution with `4 * num_segments`
22
+ degrees of freedom in Gaussian noise.
23
+ depth:
24
+ Depth of the signal population.
25
+ num_segments:
26
+ Number of segments over which the semicoherent F-statistic is computed.
27
+ unitD_rho2_bins:
28
+ Values at which the unit-depth SNR^2 pdf is evaluated
29
+ unitD_rho2_pdf:
30
+ PDF of the unitary-depth signal distribution. This is the
31
+ ``geometric factor'' R in Dreissigacker+ arXiv:1808.024
32
+ """
33
+
34
+ cdf_2F_rho2 = stats.ncx2(df=4 * num_segments, nc=unitD_rho2_bins).cdf(
35
+ twoF_threshold
36
+ )
37
+
38
+ return integrate.simpson(y=unitD_rho2_pdf * cdf_2F_rho2, x=unitD_rho2_pdf)
@@ -0,0 +1,190 @@
1
+ from functools import cache
2
+ import logging
3
+
4
+ import lal
5
+ import lalpulsar
6
+ import numpy as np
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class SignalToNoiseRatio:
12
+ r"""Compute the optimal SNR of a CW signal as expected in Gaussian noise.
13
+
14
+ The definition of SNR (shortcut for "optimal signal-to-noise ratio")
15
+ is taken from Eq. (76) of https://dcc.ligo.org/T0900149-v6/public and is
16
+ such that :math:`\langle 2\mathcal{F}\rangle = 4 + \textrm{SNR}^2`,
17
+ where :math:`\langle 2\mathcal{F}\rangle` represents the expected
18
+ value over noise realizations of twice the F-statistic of a template
19
+ perfectly matched to an existing signal in the data.
20
+
21
+ Computing :math:`\textrm{SNR}^2` requires two quantities:
22
+
23
+ * | The antenna pattern matrix :math:`\mathcal{M}`, which depends on the sky position :math:`\vec{n}`
24
+ | and polarization angle :math:`\psi` and encodes the effect of the detector's antenna pattern response
25
+ | over the course of the observing run.
26
+ * | The JKS amplitude parameters :math:`(\mathcal{A}^0, \mathcal{A}^1, \mathcal{A}^2, \mathcal{A}^3)`
27
+ | [JKS1998]_ which are functions of the CW's amplitude parameters :math:`(h_0,\cos\iota, \psi, \phi_0)` or,
28
+ | alternatively, :math:`(A_{+}, A_{\times}, \psi, \phi_0)`.
29
+
30
+ .. [JKS1998] `Jaranowski, Krolak, Schuz Phys. Rev. D58 063001, 1998 <https://arxiv.org/abs/gr-qc/9804014>`_
31
+
32
+ Parameters
33
+ ----------
34
+ mdss: lalpulsar.MultiDetectorStateSeries
35
+ MultiDetectorStateSeries as produced by DetectorStates.
36
+ Provides the required information to compute the antenna pattern contribution.
37
+ noise_weights: Union[lalpulsar.MultiNoiseWeights, None]
38
+ Optional, incompatible with `assumeSqrtSX`.
39
+ Can be computed from SFTs using `SignalToNoiseRatio.from_sfts`.
40
+ Noise weights to account for a varying noise floor or unequal noise
41
+ floors in different detectors.
42
+ assumeSqrtSX: float
43
+ Optional, incompatible with `noise_weights`.
44
+ Single-sided amplitude spectral density (ASD) of the detector noise.
45
+ This value is used for all detectors, meaning it's not currently possible to manually
46
+ specify different noise floors without creating SFT files.
47
+ (To be improved in the future; developer note:
48
+ will require SWIG constructor for MultiNoiseWeights.)
49
+
50
+ This code is a subset of that under `snr.py` in [PyFstat](https://github.com/PyFstat/PyFstat).
51
+
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ mdss: lalpulsar.MultiDetectorStateSeries,
57
+ noise_weights: lalpulsar.MultiNoiseWeights | None = None,
58
+ assumeSqrtSX: float | None = None,
59
+ ):
60
+
61
+ self.mdss = mdss
62
+ self.noise_weights = noise_weights
63
+ self.assumeSqrtSX = assumeSqrtSX
64
+
65
+ @property
66
+ def mdss(self) -> lalpulsar.MultiDetectorStateSeries:
67
+ return self._mdss
68
+
69
+ @property
70
+ def noise_weights(self) -> lalpulsar.MultiNoiseWeights:
71
+ return self._noise_weights
72
+
73
+ @property
74
+ def assumeSqrtSX(self) -> float | None:
75
+ return self._assumeSqrtSX
76
+
77
+ @mdss.setter
78
+ def mdss(self, new_mdss: lalpulsar.MultiDetectorStateSeries):
79
+ self._mdss = new_mdss
80
+ self._T_sft = new_mdss.data[0].deltaT
81
+
82
+ @noise_weights.setter
83
+ def noise_weights(self, new_weights: lalpulsar.MultiNoiseWeights):
84
+ if (new_weights is not None) and (
85
+ getattr(self, "assumeSqrtSX", None) is not None
86
+ ):
87
+ raise ValueError(
88
+ "Cannot set `noise_weights` if `assumeSqrtSX is already set!"
89
+ )
90
+ self._noise_weights = new_weights
91
+ self._Sinv_Tsft = None
92
+
93
+ @assumeSqrtSX.setter
94
+ def assumeSqrtSX(self, new_sqrtSX: float):
95
+ if getattr(self, "noise_weights", None) is not None:
96
+ raise ValueError(
97
+ "Cannot set `assumeSqrtSX' if `noise_weights` is already set!"
98
+ )
99
+ self._assumeSqrtSX = new_sqrtSX
100
+ self._T_sft = self.mdss.data[0].deltaT
101
+ self._Sinv_Tsft = self._T_sft / new_sqrtSX**2
102
+
103
+ def compute_snr2(
104
+ self,
105
+ Alpha: float,
106
+ Delta: float,
107
+ psi: float,
108
+ phi0: float,
109
+ aPlus: float,
110
+ aCross: float,
111
+ ) -> float:
112
+ r"""
113
+ Compute the :math:`\textrm{SNR}^2` of a CW signal using XLALComputeOptimalSNR2FromMmunu.
114
+ Parameters correspond to the standard ones used to describe a CW
115
+ (see e.g. Eqs. (16), (26), (30) of https://dcc.ligo.org/T0900149-v6/public ).
116
+
117
+ Mind that this function returns *squared* SNR
118
+ (Eq. (76) of https://dcc.ligo.org/T0900149-v6/public ),
119
+ which can be directly related to the expected F-statistic as
120
+ :math:`\langle 2\mathcal{F}\rangle = 4 + \textrm{SNR}^2`.
121
+
122
+ Parameters
123
+ ----------
124
+ Alpha: float
125
+ Right ascension (equatorial longitude) of the signal in radians.
126
+ Delta: float
127
+ Declination (equatorial latitude) of the signal in radians.
128
+ psi: float
129
+ Polarization angle.
130
+ phi0: float
131
+ Initial phase.
132
+ aPlus: float
133
+ Plus polarization amplitude, equal to `0.5 * h0 (1 + cosi^2)`.
134
+ aCross: float
135
+ Cross polarization amplitude, equal to `h0 * cosi`.
136
+
137
+ Returns
138
+ -------
139
+ SNR^2: float
140
+ Squared signal-to-noise ratio of a CW signal consistent
141
+ with the specified parameters in the specified detector
142
+ network.
143
+ """
144
+
145
+ Aphys = lalpulsar.PulsarAmplitudeParams()
146
+ Aphys.psi = psi
147
+ Aphys.phi0 = phi0
148
+ Aphys.aPlus = aPlus
149
+ Aphys.aCross = aCross
150
+
151
+ M = self.compute_Mmunu(Alpha=Alpha, Delta=Delta)
152
+
153
+ return lalpulsar.ComputeOptimalSNR2FromMmunu(Aphys, M)
154
+
155
+ def compute_Mmunu(self, Alpha: float, Delta: float) -> float:
156
+ """
157
+ Compute Mmunu matrix at a specific sky position using the detector states
158
+ (and possible noise weights) given at initialization time.
159
+ If no noise weights were given, unit weights are assumed.
160
+
161
+ Parameters
162
+ ----------
163
+ Alpha: float
164
+ Right ascension (equatorial longitude) of the signal in radians.
165
+ Delta: float
166
+ Declination (equatorial latitude) of the signal in radians.
167
+
168
+ Returns
169
+ -------
170
+ Mmunu: lalpulsar.AntennaPatternMatrix
171
+ Mmunu matrix encoding the response of the given detector network
172
+ to a CW at the specified sky position.
173
+ """
174
+
175
+ sky = lal.SkyPosition()
176
+ sky.longitude = Alpha
177
+ sky.latitude = Delta
178
+ sky.system = lal.COORDINATESYSTEM_EQUATORIAL
179
+ lal.NormalizeSkyPosition(sky.longitude, sky.latitude)
180
+
181
+ Mmunu = lalpulsar.ComputeMultiAMCoeffs(
182
+ multiDetStates=self.mdss,
183
+ multiWeights=self.noise_weights,
184
+ skypos=sky,
185
+ ).Mmunu
186
+
187
+ if self.noise_weights is None:
188
+ Mmunu.Sinv_Tsft = self._Sinv_Tsft
189
+
190
+ return Mmunu
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.1
2
+ Name: cows3
3
+ Version: 0.0.1
4
+ Summary: Continuous-wave search sensitivity simulator
5
+ Author-email: Rodrigo Tenorio <rodrigo.tenorio.marquez@gmail.com>, Lorenzo Mirasola <lorenzo.mirasola@ca.infn.it>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Rodrigo Tenorio, Lorenzo Mirasola
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+
16
+
17
+ Project-URL: Homepage, https://github.com/Rodrigo-Tenorio/cows3
18
+ Project-URL: Issues, https://github.com/Rodrigo-Tenorio/cows3/issues
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
22
+ Classifier: Topic :: Scientific/Engineering :: Physics
23
+ Requires-Python: >=3.11
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.md
26
+ Requires-Dist: lalsuite
27
+ Requires-Dist: numpy
28
+ Requires-Dist: scipy
29
+ Requires-Dist: solar_system_ephemerides
30
+ Provides-Extra: dev
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: flake8; extra == "dev"
33
+ Requires-Dist: pytest; extra == "dev"
34
+
35
+ # Continuous-wave search sensitivity simulator (COWS3)
36
+
37
+ A Python package to estimate the sensitivity of general
38
+ continuous gravitational-wave searches.
39
+
40
+ The method should be equivalent to the semi-analytical approach derived in
41
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459) and
42
+ implemented in [Octapps](https://github.com/octapps/octapps), but here we
43
+ implement it in Python to make it more convenient to use.
44
+
45
+ ## Citing this work
46
+
47
+ If COWS3 was useful to your research, we would appreciate if you cited
48
+ [Mirasola & Tenorio (2024)](https://arxiv.org/abs/2405.18934) where this
49
+ implementation was first presented:
50
+ ```
51
+ @article{Mirasola:2024lcq,
52
+ author = "Mirasola, Lorenzo and Tenorio, Rodrigo",
53
+ title = "{Towards a computationally-efficient follow-up pipeline for blind continuous gravitational-wave searches}",
54
+ eprint = "2405.18934",
55
+ archivePrefix = "arXiv",
56
+ primaryClass = "gr-qc",
57
+ reportNumber = "LIGO-P2400221",
58
+ month = "5",
59
+ year = "2024",
60
+ journal = "arXiv e-prints"
61
+ }
62
+ ```
63
+ as well as a Zenodo release of this software.
64
+
65
+ For the semi-analytical sensitivity estimation method you should also cite
66
+ [Wette (2012)](https://arxiv.org/abs/1111.5650) and
67
+ [Dreissigacker, Prix, Wette (2018)](https://arxiv.org/abs/1808.02459). Also,
68
+ this package makes extensive use of SWIG bindings, so please cite
69
+ [Wette (2021)](https://arxiv.org/abs/2012.09552) as well.
70
+
71
+
72
+ ## Authors
73
+ - Rodrigo Tenorio
74
+ - Lorenzo Mirasola
75
+
76
+
@@ -0,0 +1,20 @@
1
+ .gitignore
2
+ LICENSE.md
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ .github/workflows/pypi.yml
7
+ .github/workflows/pytest.yml
8
+ src/cows3/__init__.py
9
+ src/cows3/detectorstates.py
10
+ src/cows3/ephemeris.py
11
+ src/cows3/sensitivity.py
12
+ src/cows3/snr.py
13
+ src/cows3.egg-info/PKG-INFO
14
+ src/cows3.egg-info/SOURCES.txt
15
+ src/cows3.egg-info/dependency_links.txt
16
+ src/cows3.egg-info/requires.txt
17
+ src/cows3.egg-info/top_level.txt
18
+ tests/test_detectorstates.py
19
+ tests/test_sensitivity.py
20
+ tests/test_snr.py
@@ -0,0 +1,9 @@
1
+ lalsuite
2
+ numpy
3
+ scipy
4
+ solar_system_ephemerides
5
+
6
+ [dev]
7
+ black
8
+ flake8
9
+ pytest
@@ -0,0 +1 @@
1
+ cows3
@@ -0,0 +1,65 @@
1
+ import pytest
2
+ import numpy as np
3
+
4
+ from cows3.detectorstates import MultiDetectorStates
5
+
6
+
7
+ @pytest.fixture
8
+ def timestamps():
9
+ return {
10
+ "L1": 1238166018 + np.arange(0, 10, 3),
11
+ "H1": 1238166018 + np.arange(0, 10, 2),
12
+ }
13
+
14
+
15
+ @pytest.fixture
16
+ def wrong_timestamps():
17
+ return {
18
+ "AB": 1238166018 + np.arange(0, 10, 3),
19
+ }
20
+
21
+
22
+ @pytest.fixture
23
+ def Tsft():
24
+ return 1800
25
+
26
+
27
+ @pytest.fixture
28
+ def time_offset():
29
+ return 0.0
30
+
31
+
32
+ def test_get_multi_detector_states(timestamps, Tsft, time_offset):
33
+ mdss = MultiDetectorStates(
34
+ timestamps=timestamps, T_sft=Tsft, t_offset=time_offset
35
+ ).Series
36
+
37
+ assert mdss.length == len(timestamps)
38
+ for ind, ifo in enumerate(timestamps):
39
+ assert mdss.data[ind].length == timestamps[ifo].size
40
+ assert mdss.data[ind].detector.frDetector.prefix == ifo
41
+ assert mdss.data[ind].deltaT == Tsft
42
+
43
+ for gps_ind in range(mdss.data[ind].length):
44
+ mdss.data[ind].data[gps_ind].tGPS.gpsSeconds == timestamps[ifo][gps_ind]
45
+
46
+
47
+ def test_wrong_timestamps(wrong_timestamps, Tsft, time_offset):
48
+ with pytest.raises(RuntimeError) as e_info:
49
+ mds = MultiDetectorStates(
50
+ timestamps=wrong_timestamps, T_sft=Tsft, t_offset=time_offset
51
+ )
52
+
53
+
54
+ def test_extract_detector_velocities(timestamps, Tsft, time_offset):
55
+ mds = MultiDetectorStates(timestamps=timestamps, T_sft=Tsft, t_offset=time_offset)
56
+
57
+ velocities = mds.velocities
58
+ mdss = mds.Series
59
+
60
+ assert len(velocities) == len(timestamps)
61
+ assert all(key in velocities for key in timestamps)
62
+ for ifo_ind in range(len(timestamps)):
63
+ shape_to_test = velocities[mdss.data[ifo_ind].detector.frDetector.prefix].shape
64
+ assert shape_to_test[0] == 3
65
+ assert shape_to_test[1] == mdss.data[ifo_ind].length
@@ -0,0 +1,21 @@
1
+ import pytest
2
+
3
+ import numpy as np
4
+ from cows3.sensitivity import pfd_Fstatistic
5
+
6
+
7
+ @pytest.fixture
8
+ def unitD_rho2():
9
+ bins = np.arange(1, 11)
10
+ pdf = np.ones(bins.size) / bins.size
11
+ return bins, pdf
12
+
13
+
14
+ def test_pfd_Fstatistic(unitD_rho2):
15
+ pfd_Fstatistic(
16
+ twoF_threshold=100,
17
+ depth=10,
18
+ num_segments=25,
19
+ unitD_rho2_bins=unitD_rho2[0],
20
+ unitD_rho2_pdf=unitD_rho2[1],
21
+ )
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from cows3.detectorstates import MultiDetectorStates
5
+ from cows3.snr import SignalToNoiseRatio
6
+
7
+
8
+ @pytest.fixture
9
+ def signal_params():
10
+ return {
11
+ "aPlus": 0.5 * 1e-23,
12
+ "aCross": 1e-23,
13
+ "psi": 0,
14
+ "phi0": 0,
15
+ "Alpha": 0,
16
+ "Delta": 0,
17
+ }
18
+
19
+
20
+ @pytest.fixture
21
+ def mds():
22
+
23
+ Tsft = 1_800
24
+ tstart = 700_000_000
25
+ ts = np.arange(tstart, tstart + 4 * Tsft, Tsft)
26
+
27
+ return MultiDetectorStates(
28
+ timestamps={detector: ts for detector in ["H1", "L1"]},
29
+ T_sft=Tsft,
30
+ )
31
+
32
+
33
+ @pytest.fixture
34
+ def snr_object(mds):
35
+ return SignalToNoiseRatio(
36
+ mdss=mds.Series,
37
+ assumeSqrtSX=1e-23,
38
+ )
39
+
40
+
41
+ def test_SignalToNoiseRatio(signal_params, snr_object):
42
+ params = {
43
+ "aPlus": 0.5 * 1e-23,
44
+ "aCross": 1e-23,
45
+ "psi": 0,
46
+ "phi0": 0,
47
+ "Alpha": 0,
48
+ "Delta": 0,
49
+ }
50
+
51
+ snr_object.compute_snr2(**signal_params)