asteroid_spinprops 0.2.32__py3-none-any.whl → 1.0.1__py3-none-any.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.
Files changed (30) hide show
  1. asteroid_spinprops/ssolib/dataprep.py +0 -346
  2. asteroid_spinprops/ssolib/modelfit.py +143 -252
  3. asteroid_spinprops/ssolib/periodest.py +126 -158
  4. asteroid_spinprops/ssolib/utils.py +164 -211
  5. asteroid_spinprops-1.0.1.dist-info/METADATA +186 -0
  6. asteroid_spinprops-1.0.1.dist-info/RECORD +10 -0
  7. asteroid_spinprops/ssolib/.ruff_cache/.gitignore +0 -2
  8. asteroid_spinprops/ssolib/.ruff_cache/0.13.2/1980339045096230685 +0 -0
  9. asteroid_spinprops/ssolib/.ruff_cache/CACHEDIR.TAG +0 -1
  10. asteroid_spinprops/ssolib/pipetools.py +0 -167
  11. asteroid_spinprops/ssolib/testing/atlas_x_ztf_testing/test_pqfile_1.parquet +0 -0
  12. asteroid_spinprops/ssolib/testing/atlas_x_ztf_testing/test_pqfile_2.parquet +0 -0
  13. asteroid_spinprops/ssolib/testing/ephemeris_testing/2000 WL152 +0 -1702
  14. asteroid_spinprops/ssolib/testing/ephemeris_testing/2001 PC +0 -94
  15. asteroid_spinprops/ssolib/testing/ephemeris_testing/2001 SG276 +0 -111
  16. asteroid_spinprops/ssolib/testing/ephemeris_testing/2008 GX32 +0 -93
  17. asteroid_spinprops/ssolib/testing/ephemeris_testing/2009 BE185 +0 -130
  18. asteroid_spinprops/ssolib/testing/ephemeris_testing/2011 EY17 +0 -101
  19. asteroid_spinprops/ssolib/testing/ephemeris_testing/2134 T-1 +0 -352
  20. asteroid_spinprops/ssolib/testing/ephemeris_testing/Bellmore +0 -2657
  21. asteroid_spinprops/ssolib/testing/ephemeris_testing/Dermott +0 -2971
  22. asteroid_spinprops/ssolib/testing/ephemeris_testing/Duke +0 -2026
  23. asteroid_spinprops/ssolib/testing/ephemeris_testing/Izenberg +0 -2440
  24. asteroid_spinprops/ssolib/testing/ephemeris_testing/Lermontov +0 -2760
  25. asteroid_spinprops/ssolib/testing/ephemeris_testing/Poullain +0 -1272
  26. asteroid_spinprops/ssolib/testing/ephemeris_testing/Sonneberga +0 -2756
  27. asteroid_spinprops/ssolib/testing/testing_ssoname_keys.pkl +0 -0
  28. asteroid_spinprops-0.2.32.dist-info/METADATA +0 -77
  29. asteroid_spinprops-0.2.32.dist-info/RECORD +0 -31
  30. {asteroid_spinprops-0.2.32.dist-info → asteroid_spinprops-1.0.1.dist-info}/WHEEL +0 -0
@@ -1,8 +1,5 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
- import rocks
4
- import matplotlib.pyplot as plt
5
-
6
3
 
7
4
  from astropy.timeseries import LombScargleMultiband, LombScargle
8
5
  from scipy.signal import find_peaks
@@ -11,21 +8,59 @@ from scipy.stats import f as ftest
11
8
 
12
9
 
13
10
  def alias_func(x, i, j, p_feat):
14
- """Return aliases relation
15
-
16
- x: float
17
- Fink period
18
- i: int
19
- Strictly positive integer. Mode.
20
- j: int
21
- Alias
22
- p_feat: float
23
- Frequency of a feature
11
+ """
12
+ Compute the aliasing relation for a periodic signal.
13
+
14
+ Parameters
15
+ ----------
16
+ x : float
17
+ Input period.
18
+ i : int
19
+ Positive integer representing the mode number.
20
+ j : int
21
+ Alias index.
22
+ p_feat : float
23
+ Characteristic frequency of the feature (e.g. 1 day).
24
+
25
+ Returns
26
+ -------
27
+ float
28
+ Value of the aliasing relation for the given inputs.
24
29
  """
25
30
  return (i + 1) * p_feat * x / np.abs(p_feat - j * x)
26
31
 
27
32
 
28
33
  def get_period_estimate(residuals_dataframe, p_min=0.03, p_max=2):
34
+ """
35
+ Estimate significant periods in a time series of residuals using Lomb-Scargle periodograms.
36
+
37
+ Parameters
38
+ ----------
39
+ residuals_dataframe : pandas.DataFrame
40
+ DataFrame containing at least the following columns:
41
+ - 'jd' : observation times
42
+ - 'residuals' : residuals (observed - SHG1G2 modeled magnitudes)
43
+ - 'filters' : filter IDs (int)
44
+ p_min : float, optional
45
+ Minimum period to search (in days). Default is 0.03.
46
+ p_max : float, optional
47
+ Maximum period to search (in days). Default is 2.
48
+
49
+ Returns
50
+ -------
51
+ tuple
52
+ - single_band_results : list
53
+ [frequencies, power, top signal peak frequencies, corresponding powers]
54
+ - window_function_results : list
55
+ [frequencies, power, top window peak frequencies, corresponding powers]
56
+ - noise_level : float
57
+ Estimated noise level in the periodogram (mean + 3*std of power)
58
+
59
+ Notes
60
+ -----
61
+ - Uses `LombScargle` for the window function and `LombScargleMultiband` from the `nifty-ls` implementations
62
+ - The five strongest peaks are returned.
63
+ """
29
64
  period_min, period_max = p_min, p_max
30
65
  period_range = (period_min, period_max)
31
66
 
@@ -90,6 +125,50 @@ def get_period_estimate(residuals_dataframe, p_min=0.03, p_max=2):
90
125
  def get_multiband_period_estimate(
91
126
  residuals_dataframe, p_min=0.03, p_max=2, k_free=True, k_val=None
92
127
  ):
128
+ """
129
+ Estimate the period of a multiband time series using a multiband Lomb-Scargle model.
130
+
131
+ Fits a multiband Lomb-Scargle model to the SHG1G2 residuals across different filters,
132
+ optionally testing multiple base-term complexities (`k`) and selecting the simplest model
133
+ that adequately describes the data.
134
+
135
+ Parameters
136
+ ----------
137
+ residuals_dataframe : pandas.DataFrame
138
+ DataFrame containing:
139
+ - 'jd' : observation times
140
+ - 'residuals' : residual magnitudes (observed - model)
141
+ - 'filters' : filter IDs
142
+ - 'sigma' : observational uncertainties
143
+ p_min : float, optional
144
+ Minimum period to search (days). Default is 0.03.
145
+ p_max : float, optional
146
+ Maximum period to search (days). Default is 2.
147
+ k_free : bool, optional
148
+ If True, automatically scan multiple base-term complexities to choose optimal model. Default True.
149
+ k_val : int, optional
150
+ Fixed number of base terms to use if `k_free=False`. Default None.
151
+
152
+ Returns
153
+ -------
154
+ tuple
155
+ - period_in : float
156
+ Estimated dominant SSO period (2 / f_best) in the time series.
157
+ - k_val : int
158
+ Number of base terms used in the final multiband model.
159
+ - p_rms : float
160
+ RMS of residuals for the chosen model (NaN if k_free=False).
161
+ - signal_peaks : array_like
162
+ Frequencies of the top five peaks in the final multiband periodogram.
163
+ - window_peaks : array_like
164
+ Frequencies of the top five peaks in the single-band window function periodogram.
165
+
166
+ Notes
167
+ -----
168
+ - Uses `LombScargle` for the window function and `LombScargleMultiband` from the `nifty-ls` implementations
169
+ - If `k_free=True`, performs F-test comparisons to select the simplest adequate model.
170
+ """
171
+
93
172
  period_min, period_max = p_min, p_max
94
173
  period_range = (period_min, period_max)
95
174
  results = []
@@ -245,152 +324,41 @@ def get_multiband_period_estimate(
245
324
  return period_in, k_val, p_rms, signal_peaks, window_peaks
246
325
 
247
326
 
248
- def plot_periodograms(signal, window, name=None, axis="frequency"):
249
- if name is not None:
250
- r = rocks.Rock(name, datacloud="spins")
251
- bib_estimates = r.spins["period"].values[pd.notna(r.spins["period"].values)]
252
-
253
- fig, ax = plt.subplots(2, 1, figsize=(12, 6))
254
- if axis == "frequency":
255
- ax[0].plot(signal[0], signal[1], c="red", linewidth=2)
256
- ax[1].plot(window[0], window[1], c="black", alpha=1, linewidth=2)
257
-
258
- for i, n in enumerate(signal[2]):
259
- ax[0].scatter(
260
- n - 0.2,
261
- signal[3][i],
262
- marker="${}$".format(i + 1),
263
- zorder=1000,
264
- s=60,
265
- c="black",
266
- )
267
-
268
- for i in [0, 1]:
269
- for j in range(1, 9):
270
- ax[i].axvline(x=24 / (24 / j), linestyle="-.", c="purple", linewidth=1)
271
- ax[i].set_xlim(
272
- 1 / (2 + 0.5),
273
- )
274
- ax[i].set_ylim(
275
- 0,
276
- )
277
- if name is not None:
278
- for p in bib_estimates:
279
- ax[0].axvline(x=24 / (p / 2), linestyle="-", c="green", linewidth=1.5)
280
-
281
- for k in [
282
- int(24 * 2),
283
- int(12 * 2),
284
- int(8 * 2),
285
- int(6 * 2),
286
- ]:
287
- ax[0].text(
288
- x=24 / (k / 2) - 0.1,
289
- y=max(signal[1]) + 0.05,
290
- s="{}h".format(k),
291
- rotation=90,
292
- fontsize=11,
293
- )
294
-
295
- ax[0].text(
296
- x=0.95,
297
- y=0.95,
298
- s="Signal",
299
- fontsize=15,
300
- transform=ax[0].transAxes,
301
- ha="right",
302
- va="top",
303
- )
304
- ax[1].text(
305
- x=0.95,
306
- y=0.95,
307
- s="Window",
308
- fontsize=15,
309
- transform=ax[1].transAxes,
310
- ha="right",
311
- va="top",
312
- )
313
-
314
- ax[1].set_xlabel(r"Frequency /day$^{-1}$")
315
-
316
- ax[0].set_ylabel("Power")
317
- ax[1].set_ylabel("Power")
318
-
319
- ax[0].set_xticks([])
320
-
321
- plt.tight_layout()
322
-
323
- if axis == "period":
324
- ax[0].plot(2 / signal[0], signal[1], c="red", linewidth=2)
325
- ax[1].plot(2 / window[0], window[1], c="black", alpha=1, linewidth=2)
326
-
327
- for i, n in enumerate(2 / signal[2]):
328
- ax[0].scatter(
329
- 2 / (n - 0.2),
330
- signal[3][i],
331
- marker="${}$".format(i + 1),
332
- zorder=1000,
333
- s=60,
334
- c="black",
335
- )
336
-
337
- # for i in [0, 1]:
338
- # for j in range(1, 9):
339
- # ax[i].axvline(x=24 / (24 / j), linestyle="-.", c="purple", linewidth=1)
340
- # ax[i].set_xlim(
341
- # 1 / (2 + 0.5),
342
- # )
343
- # ax[i].set_ylim(
344
- # 0,
345
- # )
346
- if name is not None:
347
- for p in bib_estimates:
348
- ax[0].axvline(x=p / 24, linestyle="-", c="green", linewidth=1.5)
349
-
350
- for k in [
351
- int(24 * 2),
352
- int(12 * 2),
353
- int(8 * 2),
354
- int(6 * 2),
355
- ]:
356
- ax[0].text(
357
- x=(k - 0.1) / 24,
358
- y=max(signal[1]) + 0.05,
359
- s="{}h".format(k),
360
- rotation=90,
361
- fontsize=11,
362
- )
363
-
364
- ax[0].text(
365
- x=0.95,
366
- y=0.95,
367
- s="Signal",
368
- fontsize=15,
369
- transform=ax[0].transAxes,
370
- ha="right",
371
- va="top",
372
- )
373
- ax[1].text(
374
- x=0.95,
375
- y=0.95,
376
- s="Window",
377
- fontsize=15,
378
- transform=ax[1].transAxes,
379
- ha="right",
380
- va="top",
381
- )
382
-
383
- ax[1].set_xlabel(r"Period /day")
384
-
385
- ax[0].set_ylabel("Power")
386
- ax[1].set_ylabel("Power")
387
-
388
- ax[0].set_xticks([])
389
-
390
- plt.tight_layout()
391
-
392
-
393
327
  def perform_residual_resampling(resid_df, p_min, p_max, k=1):
328
+ """
329
+ Estimate the robustness of a period measurement via bootstrap resampling of residuals.
330
+
331
+ This function resamples the residuals DataFrame multiple times with replacement,
332
+ recomputes the period for each bootstrap sample, and counts how many of the
333
+ resampled periods are within 1% of the original period. It supports both single-band
334
+ (k=1) and multiband (k>1) period estimation.
335
+
336
+ Parameters
337
+ ----------
338
+ resid_df : pandas.DataFrame
339
+ DataFrame of residuals containing columns required for `get_period_estimate`
340
+ or `get_multiband_period_estimate`.
341
+ p_min : float
342
+ Minimum period to search (days).
343
+ p_max : float
344
+ Maximum period to search (days).
345
+ k : int, optional
346
+ Number of base terms in the model; k=1 for single-band, k>1 for multiband. Default is 1.
347
+
348
+ Returns
349
+ -------
350
+ tuple
351
+ - BS_df : pandas.DataFrame
352
+ The last bootstrap-resampled residuals DataFrame.
353
+ - Nbs : int
354
+ Number of bootstrap samples whose estimated period is within 1% of the original period.
355
+
356
+ Notes
357
+ -----
358
+ - Performs 25 bootstrap resamples by default.
359
+ - For k=1, uses `get_period_estimate`; for k>1, uses `get_multiband_period_estimate`.
360
+ """
361
+
394
362
  if k == 1:
395
363
  sg, w, _ = get_period_estimate(resid_df)
396
364
  Pog = 48 / sg[2][0] # in hours