archeo 2.0.0.dev2__tar.gz → 2.0.0.dev4__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.
Files changed (63) hide show
  1. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/PKG-INFO +1 -19
  2. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/README.md +0 -14
  3. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/resampler/assume_independence.py +8 -17
  4. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/resampler/base.py +5 -7
  5. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/resampler/generic.py +8 -11
  6. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/resampler/interface.py +14 -22
  7. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/physics/binary.py +5 -5
  8. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/physics/black_hole.py +6 -3
  9. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/physics/mahapatra.py +2 -2
  10. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo.egg-info/PKG-INFO +1 -19
  11. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo.egg-info/SOURCES.txt +0 -3
  12. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo.egg-info/requires.txt +0 -5
  13. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/pyproject.toml +3 -13
  14. archeo-2.0.0.dev2/archeo/ui/__init__.py +0 -8
  15. archeo-2.0.0.dev2/archeo/ui/app.py +0 -123
  16. archeo-2.0.0.dev2/archeo/ui/visualization.py +0 -15
  17. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/LICENSE +0 -0
  18. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/__init__.py +0 -0
  19. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/__main__.py +0 -0
  20. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/__init__.py +0 -0
  21. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/ancestral_posterior.py +0 -0
  22. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/__init__.py +0 -0
  23. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/bayes_factor_curve.py +0 -0
  24. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/bayesian/importance_sampling/resampler/__init__.py +0 -0
  25. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/constants/__init__.py +0 -0
  26. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/constants/enum.py +0 -0
  27. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/constants/physics.py +0 -0
  28. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/__init__.py +0 -0
  29. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/annotation.py +0 -0
  30. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/bayesian/bayes_factor.py +0 -0
  31. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/distribution.py +0 -0
  32. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/math.py +0 -0
  33. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/physics/__init__.py +0 -0
  34. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/physics/simulation.py +0 -0
  35. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/data_structures/visualization.py +0 -0
  36. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/postprocessing/__init__.py +0 -0
  37. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/postprocessing/dataframe.py +0 -0
  38. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/__init__.py +0 -0
  39. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/cli.py +0 -0
  40. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/forward/__init__.py +0 -0
  41. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/forward/compute_bayes_factor_curve.py +0 -0
  42. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/simulation/__init__.py +0 -0
  43. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/simulation/agnostic.py +0 -0
  44. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/simulation/n_generation.py +0 -0
  45. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/preset/simulation/second_generation.py +0 -0
  46. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/py.typed +0 -0
  47. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/simulation/__init__.py +0 -0
  48. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/simulation/simulate_merger.py +0 -0
  49. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/__init__.py +0 -0
  50. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/decorator.py +0 -0
  51. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/env.py +0 -0
  52. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/fs.py +0 -0
  53. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/logger.py +0 -0
  54. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/utils/parallel.py +0 -0
  55. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/version.py +0 -0
  56. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/visualization/__init__.py +0 -0
  57. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/visualization/animation.py +0 -0
  58. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/visualization/base.py +0 -0
  59. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/visualization/distribution.py +0 -0
  60. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo/visualization/estimation.py +0 -0
  61. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo.egg-info/dependency_links.txt +0 -0
  62. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/archeo.egg-info/top_level.txt +0 -0
  63. {archeo-2.0.0.dev2 → archeo-2.0.0.dev4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archeo
3
- Version: 2.0.0.dev2
3
+ Version: 2.0.0.dev4
4
4
  Summary: Bayesian framework for inferring natal kick, ancestral masses and spins of black holes.
5
5
  Author-email: wyhwong <wyhwong@link.cuhk.edu.hk>
6
6
  License-Expression: MIT
@@ -33,10 +33,6 @@ Requires-Dist: seaborn>=0.13.2
33
33
  Requires-Dist: surfinbh>=1.2.7
34
34
  Requires-Dist: tabulate>=0.10.0
35
35
  Requires-Dist: tqdm>=4.67.3
36
- Provides-Extra: ui
37
- Requires-Dist: nbformat>=5.10.4; extra == "ui"
38
- Requires-Dist: plotly>=6.6.0; extra == "ui"
39
- Requires-Dist: streamlit>=1.55.0; extra == "ui"
40
36
  Dynamic: license-file
41
37
 
42
38
  # ARCHEO (V2)
@@ -196,20 +192,6 @@ archeo.visualize_posterior_estimation({"GW190521": df_posterior}, output_dir="./
196
192
 
197
193
  To import archeo in your Python code, please refer to the documentation page at [https://wyhwong.github.io/archeo/](https://wyhwong.github.io/archeo/).
198
194
 
199
- ## Try our UI
200
-
201
- Archeo also provides a simple web-based user interface to visualize the distributions of remnant properties.
202
- To run the UI locally, simply run the following command:
203
-
204
- ```bash
205
- pip3 install archeo[ui]
206
- python3 -m archeo.ui
207
- ```
208
-
209
- Then the UI will be available at [localhost:8501](http://localhost:8501).
210
-
211
- You may also try our [demo version](https://archeo.streamlit.app/) online, which is hosted on Streamlit Community Cloud.
212
-
213
195
  ## Getting Help
214
196
 
215
197
  The code is maintained by [Henry Wong](https://github.com/wyhwong) under [Juan Calderon Bustillo](https://git.ligo.org/juan.calderonbustillo)'s supervision. You can find the [list of contributors](https://github.com/wyhwong/archeo/graphs/contributors) here. Please report bugs by raising an issue on our [GitHub](https://github.com/wyhwong/archeo) repository.
@@ -155,20 +155,6 @@ archeo.visualize_posterior_estimation({"GW190521": df_posterior}, output_dir="./
155
155
 
156
156
  To import archeo in your Python code, please refer to the documentation page at [https://wyhwong.github.io/archeo/](https://wyhwong.github.io/archeo/).
157
157
 
158
- ## Try our UI
159
-
160
- Archeo also provides a simple web-based user interface to visualize the distributions of remnant properties.
161
- To run the UI locally, simply run the following command:
162
-
163
- ```bash
164
- pip3 install archeo[ui]
165
- python3 -m archeo.ui
166
- ```
167
-
168
- Then the UI will be available at [localhost:8501](http://localhost:8501).
169
-
170
- You may also try our [demo version](https://archeo.streamlit.app/) online, which is hosted on Streamlit Community Cloud.
171
-
172
158
  ## Getting Help
173
159
 
174
160
  The code is maintained by [Henry Wong](https://github.com/wyhwong) under [Juan Calderon Bustillo](https://git.ligo.org/juan.calderonbustillo)'s supervision. You can find the [list of contributors](https://github.com/wyhwong/archeo/graphs/contributors) here. Please report bugs by raising an issue on our [GitHub](https://github.com/wyhwong/archeo) repository.
@@ -33,7 +33,7 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
33
33
  nbins = self.get_nbins(samples.name)
34
34
  return get_histogram_1d(samples, nbins=nbins, bounds=self.bounds[samples.name])
35
35
 
36
- def get_likelihood_samples_1d(self, random_state=42, ztol=1e-8) -> np.ndarray:
36
+ def get_likelihood_samples_1d(self, random_state=42) -> np.ndarray:
37
37
  """Get samples for likelihood function"""
38
38
 
39
39
  weights = np.ones(len(self.posterior_samples))
@@ -42,7 +42,7 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
42
42
  prior_hist = self._get_hist_1d(self.prior_samples[col])
43
43
  rv = rv_histogram(
44
44
  (
45
- self._safe_divide(1.0, prior_hist, ztol=ztol),
45
+ self._safe_divide(1.0, prior_hist),
46
46
  self.get_edges(col_name=col),
47
47
  )
48
48
  )
@@ -55,7 +55,7 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
55
55
  random_state=random_state,
56
56
  )
57
57
 
58
- def _get_posterior_sample_weights_1d(self, col_name: str, ztol=1e-8) -> np.ndarray:
58
+ def _get_posterior_sample_weights_1d(self, col_name: str) -> np.ndarray:
59
59
  """Get the weights for the importance sampling"""
60
60
 
61
61
  weights = np.ones(len(self.posterior_samples))
@@ -63,17 +63,13 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
63
63
  prior_hist = self._get_hist_1d(self.prior_samples[col_name])
64
64
  new_prior_hist = self._get_hist_1d(self.new_prior_samples[col_name])
65
65
  # Avoid division by zero
66
- ratio = self._safe_divide(new_prior_hist, prior_hist, ztol=ztol)
66
+ ratio = self._safe_divide(new_prior_hist, prior_hist)
67
67
  rv = rv_histogram((ratio, self.get_edges(col_name)))
68
68
  weights *= rv.pdf(self.posterior_samples[col_name])
69
69
 
70
70
  return weights
71
71
 
72
- def get_bayes_factor_1d(
73
- self,
74
- bootstrapping: bool = False,
75
- ztol=1e-8,
76
- ) -> float:
72
+ def get_bayes_factor_1d(self, bootstrapping: bool = False) -> float:
77
73
  """Compute the Bayes factor between two models"""
78
74
 
79
75
  if bootstrapping:
@@ -81,14 +77,12 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
81
77
  prior_samples=self.prior_samples.sample(n=len(self.prior_samples), replace=True),
82
78
  posterior_samples=self.posterior_samples.sample(n=len(self.posterior_samples), replace=True),
83
79
  new_prior_samples=self.new_prior_samples.sample(n=len(self.new_prior_samples), replace=True),
84
- ztol=ztol,
85
80
  )
86
81
 
87
82
  return self._get_bayes_factor_1d(
88
83
  prior_samples=self.prior_samples,
89
84
  posterior_samples=self.posterior_samples,
90
85
  new_prior_samples=self.new_prior_samples,
91
- ztol=ztol,
92
86
  )
93
87
 
94
88
  def _get_bayes_factor_1d(
@@ -96,7 +90,6 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
96
90
  prior_samples: pd.DataFrame,
97
91
  posterior_samples: pd.DataFrame,
98
92
  new_prior_samples: pd.DataFrame,
99
- ztol=1e-8,
100
93
  ) -> float:
101
94
  """Compute the Bayes factor between two models
102
95
 
@@ -111,19 +104,17 @@ class ISDataAssumeIndependence(ImportanceSamplingDataBase):
111
104
  prior_hist = self._get_hist_1d(prior_samples[col])
112
105
  posterior_hist = self._get_hist_1d(posterior_samples[col])
113
106
  new_prior_hist = self._get_hist_1d(new_prior_samples[col])
114
- bf *= np.sum(posterior_hist * self._safe_divide(new_prior_hist, prior_hist, ztol=ztol)) * self.get_binwidth(
115
- col
116
- )
107
+ bf *= np.sum(posterior_hist * self._safe_divide(new_prior_hist, prior_hist)) * self.get_binwidth(col)
117
108
 
118
109
  return bf
119
110
 
120
- def get_reweighted_samples_1d(self, ztol=1e-8, random_state=42) -> pd.DataFrame:
111
+ def get_reweighted_samples_1d(self, random_state=42) -> pd.DataFrame:
121
112
  """Get the reweighted samples for the importance sampling"""
122
113
 
123
114
  weights = np.ones(len(self.posterior_samples))
124
115
 
125
116
  for col in self.common_columns:
126
- weights *= self._get_posterior_sample_weights_1d(col_name=col, ztol=ztol)
117
+ weights *= self._get_posterior_sample_weights_1d(col_name=col)
127
118
 
128
119
  reweighted_samples = self.posterior_samples.sample(
129
120
  n=len(self.posterior_samples),
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  import pandas as pd
5
3
  from pydantic import BaseModel, ConfigDict
@@ -21,6 +19,7 @@ class ImportanceSamplingDataBase(BaseModel, frozen=True):
21
19
  new_prior_samples: pd.DataFrame
22
20
  binsize_spin: float = 0.05
23
21
  binsize_mass: float = 1.0
22
+ ztol: float = 1e-8
24
23
 
25
24
  @property
26
25
  def common_columns(self) -> list[str]:
@@ -53,7 +52,7 @@ class ImportanceSamplingDataBase(BaseModel, frozen=True):
53
52
 
54
53
  return bounds
55
54
 
56
- def get_binsize(self, col_name: str) -> Optional[float]:
55
+ def get_binsize(self, col_name: str) -> float:
57
56
 
58
57
  if col_name.startswith("a"):
59
58
  return self.binsize_spin
@@ -63,7 +62,7 @@ class ImportanceSamplingDataBase(BaseModel, frozen=True):
63
62
 
64
63
  raise ValueError(f"Unknown column name {col_name}")
65
64
 
66
- def get_nbins(self, col_name: str) -> Optional[int]:
65
+ def get_nbins(self, col_name: str) -> int:
67
66
  """Get the number of bins for a given column name"""
68
67
 
69
68
  binsize = self.get_binsize(col_name)
@@ -83,8 +82,7 @@ class ImportanceSamplingDataBase(BaseModel, frozen=True):
83
82
  edges = self.get_edges(col_name)
84
83
  return edges[1] - edges[0]
85
84
 
86
- @staticmethod
87
- def _safe_divide(a: np.ndarray, b: np.ndarray, ztol: float = 1e-8) -> np.ndarray:
85
+ def _safe_divide(self, a: np.ndarray, b: np.ndarray) -> np.ndarray:
88
86
  """Safe division to avoid division by zero"""
89
87
 
90
- return np.where(b > ztol, np.exp(np.log(a) - np.log(b)), 0.0)
88
+ return np.where(b > self.ztol, np.exp(np.log(a) - np.log(b)), 0.0)
@@ -37,7 +37,7 @@ class ISDataGeneric(ImportanceSamplingDataBase):
37
37
  samples_array = df_samples.to_numpy()
38
38
  return get_histogram_dd(samples_array, nbins=nbins, bounds=bounds)
39
39
 
40
- def get_likelihood_samples_dd(self, random_state=42, ztol=1e-8) -> np.ndarray:
40
+ def get_likelihood_samples_dd(self, random_state=42) -> np.ndarray:
41
41
  """Get samples for likelihood function"""
42
42
 
43
43
  edges = {}
@@ -45,7 +45,7 @@ class ISDataGeneric(ImportanceSamplingDataBase):
45
45
  edges[col] = self.get_edges(col)
46
46
 
47
47
  prior_hist = self._get_hist_dd(self.prior_samples[self.common_columns])
48
- weights_matrix = self._safe_divide(1.0, prior_hist, ztol=ztol)
48
+ weights_matrix = self._safe_divide(1.0, prior_hist)
49
49
 
50
50
  def _get_pdf(row: pd.Series):
51
51
  idx = tuple(np.searchsorted(edges[col], row[col], side="right") - 1 for col in self.common_columns)
@@ -60,7 +60,7 @@ class ISDataGeneric(ImportanceSamplingDataBase):
60
60
  random_state=random_state,
61
61
  )
62
62
 
63
- def _get_posterior_sample_weights_dd(self, ztol=1e-8) -> pd.Series:
63
+ def _get_posterior_sample_weights_dd(self) -> pd.Series:
64
64
  """Get the weights for the importance sampling"""
65
65
 
66
66
  edges = {}
@@ -70,7 +70,7 @@ class ISDataGeneric(ImportanceSamplingDataBase):
70
70
  prior_hist = self._get_hist_dd(self.prior_samples[self.common_columns])
71
71
  new_prior_hist = self._get_hist_dd(self.new_prior_samples[self.common_columns])
72
72
  # Avoid division by zero
73
- weights_matrix = self._safe_divide(new_prior_hist, prior_hist, ztol=ztol)
73
+ weights_matrix = self._safe_divide(new_prior_hist, prior_hist)
74
74
 
75
75
  def _get_pdf(row: pd.Series):
76
76
  idx = tuple(np.searchsorted(edges[col], row[col], side="right") - 1 for col in self.common_columns)
@@ -78,7 +78,7 @@ class ISDataGeneric(ImportanceSamplingDataBase):
78
78
 
79
79
  return self.posterior_samples.apply(_get_pdf, axis=1)
80
80
 
81
- def get_bayes_factor_dd(self, bootstrapping: bool = False, ztol=1e-8) -> float:
81
+ def get_bayes_factor_dd(self, bootstrapping: bool = False) -> float:
82
82
  """Compute the Bayes factor between two models"""
83
83
 
84
84
  if bootstrapping:
@@ -86,14 +86,12 @@ class ISDataGeneric(ImportanceSamplingDataBase):
86
86
  prior_samples=self.prior_samples.sample(n=len(self.prior_samples), replace=True),
87
87
  posterior_samples=self.posterior_samples.sample(n=len(self.posterior_samples), replace=True),
88
88
  new_prior_samples=self.new_prior_samples.sample(n=len(self.new_prior_samples), replace=True),
89
- ztol=ztol,
90
89
  )
91
90
 
92
91
  return self._get_bayes_factor_dd(
93
92
  prior_samples=self.prior_samples,
94
93
  posterior_samples=self.posterior_samples,
95
94
  new_prior_samples=self.new_prior_samples,
96
- ztol=ztol,
97
95
  )
98
96
 
99
97
  def _get_bayes_factor_dd(
@@ -101,7 +99,6 @@ class ISDataGeneric(ImportanceSamplingDataBase):
101
99
  prior_samples: pd.DataFrame,
102
100
  posterior_samples: pd.DataFrame,
103
101
  new_prior_samples: pd.DataFrame,
104
- ztol=1e-8,
105
102
  ) -> float:
106
103
  """Compute the Bayes factor between two models
107
104
 
@@ -116,14 +113,14 @@ class ISDataGeneric(ImportanceSamplingDataBase):
116
113
  new_prior_hist_bh = self._get_hist_dd(new_prior_samples[[c for c in self.common_columns]])
117
114
  prior_hist_bh = self._get_hist_dd(prior_samples[[c for c in self.common_columns]])
118
115
  posterior_hist_bh = self._get_hist_dd(posterior_samples[[c for c in self.common_columns]])
119
- bf *= np.sum(posterior_hist_bh * self._safe_divide(new_prior_hist_bh, prior_hist_bh, ztol=ztol)) * bin_auc
116
+ bf *= np.sum(posterior_hist_bh * self._safe_divide(new_prior_hist_bh, prior_hist_bh)) * bin_auc
120
117
 
121
118
  return bf
122
119
 
123
- def get_reweighted_samples_dd(self, ztol=1e-8, random_state=42) -> pd.DataFrame:
120
+ def get_reweighted_samples_dd(self, random_state=42) -> pd.DataFrame:
124
121
  """Get the reweighted samples for the importance sampling"""
125
122
 
126
- weights = self._get_posterior_sample_weights_dd(ztol=ztol)
123
+ weights = self._get_posterior_sample_weights_dd()
127
124
 
128
125
  return self.posterior_samples.sample(
129
126
  n=len(self.posterior_samples),
@@ -20,16 +20,16 @@ class ImportanceSamplingData(ISDataGeneric, ISDataAssumeIndependence, Interface)
20
20
  assume_parameter_independence: bool = False
21
21
 
22
22
  @pre_release
23
- def get_likelihood_samples(self, random_state=42, ztol=1e-8) -> np.ndarray:
23
+ def get_likelihood_samples(self, random_state=42) -> np.ndarray:
24
24
  """Get samples for likelihood function"""
25
25
 
26
26
  if self.assume_parameter_independence:
27
- return self.get_likelihood_samples_1d(random_state=random_state, ztol=ztol)
27
+ return self.get_likelihood_samples_1d(random_state=random_state)
28
28
 
29
- return self.get_likelihood_samples_dd(random_state=random_state, ztol=ztol)
29
+ return self.get_likelihood_samples_dd(random_state=random_state)
30
30
 
31
31
  @pre_release
32
- def get_bayes_factor(self, bootstrapping: bool = False, ztol=1e-8) -> float:
32
+ def get_bayes_factor(self, bootstrapping: bool = False) -> float:
33
33
  """Compute the Bayes factor between two models
34
34
 
35
35
  NOTE: In this implementation, the likelihood function remains untouched.
@@ -41,18 +41,12 @@ class ImportanceSamplingData(ISDataGeneric, ISDataAssumeIndependence, Interface)
41
41
  return 0.0
42
42
 
43
43
  if self.assume_parameter_independence:
44
- return self.get_bayes_factor_1d(bootstrapping=bootstrapping, ztol=ztol)
44
+ return self.get_bayes_factor_1d(bootstrapping=bootstrapping)
45
45
 
46
- return self.get_bayes_factor_dd(bootstrapping=bootstrapping, ztol=ztol)
46
+ return self.get_bayes_factor_dd(bootstrapping=bootstrapping)
47
47
 
48
48
  @pre_release
49
- def sample_bayes_factor(
50
- self,
51
- n: int,
52
- ztol=1e-8,
53
- is_parallel: bool = False,
54
- n_threads: int | None = None,
55
- ) -> BayesFactor:
49
+ def sample_bayes_factor(self, n: int, is_parallel: bool = False, n_threads: int | None = None) -> BayesFactor:
56
50
  """Sample the Bayes factor for the importance sampling"""
57
51
 
58
52
  if self.new_prior_samples.empty:
@@ -63,29 +57,27 @@ class ImportanceSamplingData(ISDataGeneric, ISDataAssumeIndependence, Interface)
63
57
  return BayesFactor(
64
58
  samples=multithread_run(
65
59
  func=self.get_bayes_factor_1d,
66
- input_kwargs=[{"bootstrapping": True, "ztol": ztol} for _ in range(n)],
60
+ input_kwargs=[{"bootstrapping": True} for _ in range(n)],
67
61
  n_threads=n_threads,
68
62
  )
69
63
  )
70
- return BayesFactor(
71
- samples=[self.get_bayes_factor_1d(bootstrapping=True, ztol=ztol) for _ in tqdm(range(n))]
72
- )
64
+ return BayesFactor(samples=[self.get_bayes_factor_1d(bootstrapping=True) for _ in tqdm(range(n))])
73
65
 
74
66
  if is_parallel:
75
67
  return BayesFactor(
76
68
  samples=multithread_run(
77
69
  func=self.get_bayes_factor_dd,
78
- input_kwargs=[{"bootstrapping": True, "ztol": ztol} for _ in range(n)],
70
+ input_kwargs=[{"bootstrapping": True} for _ in range(n)],
79
71
  n_threads=n_threads,
80
72
  )
81
73
  )
82
- return BayesFactor(samples=[self.get_bayes_factor_dd(bootstrapping=True, ztol=ztol) for _ in tqdm(range(n))])
74
+ return BayesFactor(samples=[self.get_bayes_factor_dd(bootstrapping=True) for _ in tqdm(range(n))])
83
75
 
84
76
  @pre_release
85
- def get_reweighted_samples(self, ztol=1e-8, random_state=42) -> pd.DataFrame:
77
+ def get_reweighted_samples(self, random_state=42) -> pd.DataFrame:
86
78
  """Get the reweighted samples for the importance sampling"""
87
79
 
88
80
  if self.assume_parameter_independence:
89
- return self.get_reweighted_samples_1d(ztol=ztol, random_state=random_state)
81
+ return self.get_reweighted_samples_1d(random_state=random_state)
90
82
 
91
- return self.get_reweighted_samples_dd(ztol=ztol, random_state=random_state)
83
+ return self.get_reweighted_samples_dd(random_state=random_state)
@@ -1,7 +1,7 @@
1
1
  from typing import TypeAlias
2
2
 
3
3
  import numpy as np
4
- from pydantic import BaseModel
4
+ from pydantic import BaseModel, NonNegativeFloat, PositiveFloat
5
5
 
6
6
  from archeo.data_structures.math import Domain
7
7
  from archeo.data_structures.physics.black_hole import BlackHole, BlackHoleSource
@@ -18,13 +18,13 @@ class Binary(BaseModel, frozen=True):
18
18
  secondary_black_hole: BlackHole
19
19
 
20
20
  @property
21
- def mass_ratio(self) -> float:
21
+ def mass_ratio(self) -> PositiveFloat:
22
22
  """Calculate the mass ratio (q) for the binary."""
23
23
 
24
24
  return self.primary_black_hole.mass / self.secondary_black_hole.mass
25
25
 
26
26
  @property
27
- def precession_spin(self) -> float:
27
+ def precession_spin(self) -> NonNegativeFloat:
28
28
  """Calculate the precession spin parameter (chi_p) for the binary."""
29
29
 
30
30
  q = self.primary_black_hole.mass / self.secondary_black_hole.mass
@@ -33,7 +33,7 @@ class Binary(BaseModel, frozen=True):
33
33
  return np.maximum(a1h, (4 / q + 3) / (3 / q + 4) / q * a2h)
34
34
 
35
35
  @property
36
- def effective_spin(self) -> float:
36
+ def effective_spin(self) -> NonNegativeFloat:
37
37
  """Calculate the effective spin parameter (chi_eff) for the binary."""
38
38
 
39
39
  m1 = self.primary_black_hole.mass
@@ -55,7 +55,7 @@ class BinaryGenerator(BaseModel, frozen=True):
55
55
  is_aligned_spin: bool = False
56
56
  enforce_source_binding: bool = False
57
57
 
58
- def draw(self, size: int = 1) -> list[Binary]:
58
+ def draw(self, size: int = 1) -> Binaries:
59
59
  """Generate a list of binaries based on the specified sources and mass ratio domain."""
60
60
 
61
61
  binaries = []
@@ -101,12 +101,15 @@ class BlackHolePopulation(BaseModel, frozen=True):
101
101
  return np.random.choice(self.black_holes, size=size, replace=True).tolist()
102
102
 
103
103
  @classmethod
104
- def from_simulation_results(cls, df: pd.DataFrame) -> "BlackHolePopulation":
104
+ def from_simulation_results(
105
+ cls,
106
+ df: pd.DataFrame,
107
+ phi_distribution: Distribution = Uniform(low=0, high=2 * np.pi),
108
+ theta_distribution: Distribution = Uniform(low=0, high=np.pi),
109
+ ) -> "BlackHolePopulation":
105
110
  """Create a black hole population from simulation results."""
106
111
 
107
- phi_distribution = Uniform(low=0, high=2 * np.pi)
108
112
  phis = phi_distribution.draw(size=len(df))
109
- theta_distribution = Uniform(low=0, high=np.pi)
110
113
  thetas = theta_distribution.draw(size=len(df))
111
114
 
112
115
  return cls(
@@ -33,7 +33,7 @@ class MahapatraMassFunction(BaseModel, DistributionBase, frozen=True):
33
33
  def probis(self) -> np.ndarray:
34
34
  """Probabilities of the masses."""
35
35
 
36
- probis = self.smoothing_func(self.masses)
36
+ probis = self._smoothing_func(self.masses)
37
37
  probis /= probis.sum()
38
38
  return probis
39
39
 
@@ -43,7 +43,7 @@ class MahapatraMassFunction(BaseModel, DistributionBase, frozen=True):
43
43
  mp = masses - self.mass.low
44
44
  return np.exp(self.dm / mp + self.dm / (mp - self.dm))
45
45
 
46
- def smoothing_func(self, masses: np.ndarray) -> np.ndarray:
46
+ def _smoothing_func(self, masses: np.ndarray) -> np.ndarray:
47
47
  """Smoothing function."""
48
48
 
49
49
  probis = masses.copy()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archeo
3
- Version: 2.0.0.dev2
3
+ Version: 2.0.0.dev4
4
4
  Summary: Bayesian framework for inferring natal kick, ancestral masses and spins of black holes.
5
5
  Author-email: wyhwong <wyhwong@link.cuhk.edu.hk>
6
6
  License-Expression: MIT
@@ -33,10 +33,6 @@ Requires-Dist: seaborn>=0.13.2
33
33
  Requires-Dist: surfinbh>=1.2.7
34
34
  Requires-Dist: tabulate>=0.10.0
35
35
  Requires-Dist: tqdm>=4.67.3
36
- Provides-Extra: ui
37
- Requires-Dist: nbformat>=5.10.4; extra == "ui"
38
- Requires-Dist: plotly>=6.6.0; extra == "ui"
39
- Requires-Dist: streamlit>=1.55.0; extra == "ui"
40
36
  Dynamic: license-file
41
37
 
42
38
  # ARCHEO (V2)
@@ -196,20 +192,6 @@ archeo.visualize_posterior_estimation({"GW190521": df_posterior}, output_dir="./
196
192
 
197
193
  To import archeo in your Python code, please refer to the documentation page at [https://wyhwong.github.io/archeo/](https://wyhwong.github.io/archeo/).
198
194
 
199
- ## Try our UI
200
-
201
- Archeo also provides a simple web-based user interface to visualize the distributions of remnant properties.
202
- To run the UI locally, simply run the following command:
203
-
204
- ```bash
205
- pip3 install archeo[ui]
206
- python3 -m archeo.ui
207
- ```
208
-
209
- Then the UI will be available at [localhost:8501](http://localhost:8501).
210
-
211
- You may also try our [demo version](https://archeo.streamlit.app/) online, which is hosted on Streamlit Community Cloud.
212
-
213
195
  ## Getting Help
214
196
 
215
197
  The code is maintained by [Henry Wong](https://github.com/wyhwong) under [Juan Calderon Bustillo](https://git.ligo.org/juan.calderonbustillo)'s supervision. You can find the [list of contributors](https://github.com/wyhwong/archeo/graphs/contributors) here. Please report bugs by raising an issue on our [GitHub](https://github.com/wyhwong/archeo) repository.
@@ -45,9 +45,6 @@ archeo/preset/simulation/n_generation.py
45
45
  archeo/preset/simulation/second_generation.py
46
46
  archeo/simulation/__init__.py
47
47
  archeo/simulation/simulate_merger.py
48
- archeo/ui/__init__.py
49
- archeo/ui/app.py
50
- archeo/ui/visualization.py
51
48
  archeo/utils/__init__.py
52
49
  archeo/utils/decorator.py
53
50
  archeo/utils/env.py
@@ -11,8 +11,3 @@ seaborn>=0.13.2
11
11
  surfinbh>=1.2.7
12
12
  tabulate>=0.10.0
13
13
  tqdm>=4.67.3
14
-
15
- [ui]
16
- nbformat>=5.10.4
17
- plotly>=6.6.0
18
- streamlit>=1.55.0
@@ -16,7 +16,7 @@ classifiers = [
16
16
  "Operating System :: OS Independent"
17
17
  ]
18
18
  keywords = ["black-holes", "gravitational-waves", "black-hole-archeology"]
19
- version = "2.0.0.dev2"
19
+ version = "2.0.0.dev4"
20
20
  readme = "README.md"
21
21
  requires-python = ">=3.11,<3.14"
22
22
  dependencies = [
@@ -35,13 +35,6 @@ dependencies = [
35
35
  "tqdm>=4.67.3",
36
36
  ]
37
37
 
38
- [project.optional-dependencies]
39
- ui = [
40
- "nbformat>=5.10.4",
41
- "plotly>=6.6.0",
42
- "streamlit>=1.55.0",
43
- ]
44
-
45
38
  [project.urls]
46
39
  homepage = "https://pypi.org/project/archeo/"
47
40
  repository = "https://github.com/wyhwong/archeo"
@@ -52,6 +45,8 @@ dev = [
52
45
  "ipykernel>=6.9.2, <7.0.0",
53
46
  "pre-commit>=4.5.1",
54
47
  "pyarrow>=23.0.1",
48
+ "pyinstrument>=5.1.2",
49
+ "pylint>=4.0.5",
55
50
  "pytest>=9.0.2",
56
51
  "pytest-cov>=7.0.0",
57
52
  ]
@@ -60,11 +55,6 @@ mkdocs = [
60
55
  "mkdocs-jupyter>=0.25.1",
61
56
  "mkdocs-mermaid2-plugin>=1.2.3",
62
57
  ]
63
- ui = [
64
- "nbformat>=5.10.4",
65
- "plotly>=6.6.0",
66
- "streamlit>=1.55.0",
67
- ]
68
58
 
69
59
  [tool.black]
70
60
  line-length = 120
@@ -1,8 +0,0 @@
1
- import subprocess
2
-
3
- import archeo
4
-
5
-
6
- # Run the Streamlit app
7
- dist_path = archeo.__path__[0]
8
- subprocess.run(["streamlit", "run", f"{dist_path}/ui/app.py"]) # pylint: disable=subprocess-run-check
@@ -1,123 +0,0 @@
1
- import plotly.graph_objects as go
2
- import streamlit as st
3
-
4
- from archeo.constants.enum import Fits
5
- from archeo.data_structures.distribution import Uniform
6
- from archeo.data_structures.math import Domain
7
- from archeo.data_structures.physics.binary import BinaryGenerator
8
- from archeo.data_structures.physics.black_hole import BlackHoleGenerator
9
- from archeo.postprocessing.dataframe import convert_simulated_binaries_to_dataframe
10
- from archeo.simulation.simulate_merger import simulate_black_hole_mergers
11
- from archeo.ui.visualization import add_pdf
12
-
13
-
14
- st.set_page_config(page_title="Exploring Distribution of Remnant Properties", layout="wide")
15
-
16
- ###############################################
17
- # Sidebar Content #
18
- ###############################################
19
-
20
- st.sidebar.markdown("## Configure Your Ancestral Priors")
21
-
22
- # Store slider values in session state
23
- prior_name = st.sidebar.text_input("Name of the prior", "My prior")
24
- spin_setting = st.sidebar.selectbox("Spin setting", ["Aligned spin", "Precession spin"])
25
- n_samples = st.sidebar.slider("Number of samples", min_value=1000, max_value=10000, value=1000)
26
- n_workers = st.sidebar.slider("Number of workers", min_value=1, max_value=16, value=1)
27
- q_range = st.sidebar.slider("Mass ratio $q$", min_value=1.0, max_value=6.0, value=(1.0, 6.0))
28
-
29
- st.sidebar.markdown("#### Primary Black Hole Settings")
30
- m1_range = st.sidebar.slider("Mass $m_1$", min_value=5.0, max_value=200.0, value=(5.0, 200.0))
31
- a1_range = st.sidebar.slider("Spin $\\chi_1$", min_value=0.0, max_value=0.99, value=(0.0, 0.99))
32
-
33
- if spin_setting == "Precession spin":
34
- phi1_range = st.sidebar.slider("Azimuthal angle $\\phi_1$", min_value=0.0, max_value=2.0, value=(0.0, 2.0))
35
- theta1_range = st.sidebar.slider("Polar angle $\\theta_1$", min_value=0.0, max_value=1.0, value=(0.0, 1.0))
36
- else:
37
- phi1_range = theta1_range = (0.0, 0.0)
38
-
39
- st.sidebar.markdown("#### Secondary Black Hole Settings")
40
- m2_range = st.sidebar.slider("Mass $m_2$", min_value=5.0, max_value=200.0, value=(5.0, 200.0))
41
- a2_range = st.sidebar.slider("Spin $\\chi_2$", min_value=0.0, max_value=0.99, value=(0.0, 0.99))
42
-
43
- if spin_setting == "Precession spin":
44
- phi2_range = st.sidebar.slider("Azimuthal angle $\\phi_2$", min_value=0.0, max_value=2.0, value=(0.0, 2.0))
45
- theta2_range = st.sidebar.slider("Polar angle $\\theta_2$", min_value=0.0, max_value=1.0, value=(0.0, 1.0))
46
- else:
47
- phi2_range = theta2_range = (0.0, 0.0)
48
-
49
- ###############################################
50
- # Main Content #
51
- ###############################################
52
-
53
- st.markdown(
54
- """
55
- # Exploring Distribution of Remnant Properties
56
-
57
- ## Introduction
58
-
59
- This application allows users to configure the distributions for properties
60
- of black hole binaries and visualize the distribution of their remnant
61
- properties. By adjusting mass, spin, and alignment settings, users can generate
62
- samples and study the properties of remnant black holes.
63
-
64
- If you found hierarchical formation of black holes interesting and want to learn more,
65
- please refer to our paper:
66
-
67
- [1] Carlos Araújo Álvarez, Henry W. Y. Wong, Juan Calderón Bustillo. "Kicking Time
68
- Back in Black Hole Mergers: Ancestral Masses, Spins, Birth Recoils, and
69
- Hierarchical-formation Viability of GW190521." The Astrophysical Journal
70
- 977.2 (2024): 220.
71
- """
72
- )
73
-
74
-
75
- if "figs" not in st.session_state:
76
- st.session_state["figs"] = {}
77
- for col, label in {
78
- "k_f": "Birth Recoil k<sub>f</sub> [km s<sup>-1</sup>]",
79
- "a_f": "Spin χ<sub>f</sub> [-]",
80
- "m_f": "Mass m<sub>f</sub> [M<sub>Sun</sub>]",
81
- }.items():
82
- st.session_state.figs[col] = go.Figure()
83
- st.session_state.figs[col].update_layout(
84
- title="Probability Density Function",
85
- xaxis_title=label,
86
- yaxis_title="Density",
87
- showlegend=True,
88
- barmode="overlay",
89
- )
90
-
91
- if st.sidebar.button("Run"):
92
-
93
- fits = Fits.NRSUR7DQ4REMNANT if spin_setting == "Precession spin" else Fits.NRSUR3DQ8REMNANT
94
- generator_bh1 = BlackHoleGenerator(
95
- mass_distribution=Uniform(low=m1_range[0], high=m1_range[1]),
96
- spin_magnitude_distribution=Uniform(low=a1_range[0], high=a1_range[1]),
97
- phi_distribution=Uniform(low=phi1_range[0], high=phi1_range[1]),
98
- theta_distribution=Uniform(low=theta1_range[0], high=theta1_range[1]),
99
- )
100
- generator_bh2 = BlackHoleGenerator(
101
- mass_distribution=Uniform(low=m2_range[0], high=m2_range[1]),
102
- spin_magnitude_distribution=Uniform(low=a2_range[0], high=a2_range[1]),
103
- phi_distribution=Uniform(low=phi2_range[0], high=phi2_range[1]),
104
- theta_distribution=Uniform(low=theta2_range[0], high=theta2_range[1]),
105
- )
106
- binary_generator = BinaryGenerator(
107
- primary_black_hole_source=generator_bh1,
108
- secondary_black_hole_source=generator_bh2,
109
- mass_ratio_domain=Domain(low=q_range[0], high=q_range[1]),
110
- is_aligned_spin=(spin_setting == "Aligned spin"),
111
- )
112
-
113
- with st.spinner("Running simulation... Please wait..."):
114
-
115
- black_hole_mergers = simulate_black_hole_mergers(binary_generator, fits, n_samples, n_workers)
116
- df = convert_simulated_binaries_to_dataframe(black_hole_mergers)
117
-
118
- st.write("## Visualization of Remnant Properties")
119
- for col in ["k_f", "a_f", "m_f"]:
120
- add_pdf(st.session_state.figs[col], df[col], prior_name)
121
- st.plotly_chart(st.session_state.figs[col])
122
-
123
- st.success("Simulation completed!")
@@ -1,15 +0,0 @@
1
- import pandas as pd
2
- import plotly.graph_objects as go
3
-
4
-
5
- def add_pdf(fig: go.Figure, series: pd.Series, label: str) -> None:
6
- """Plot the distribution of a column in a dataframe."""
7
-
8
- fig.add_trace(
9
- go.Histogram(
10
- x=series,
11
- histnorm="probability density",
12
- opacity=0.75,
13
- name=label,
14
- )
15
- )
File without changes
File without changes
File without changes