arvi 0.1.3__py3-none-any.whl → 0.1.6__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.
- arvi/__init__.py +1 -2
- arvi/dace_wrapper.py +21 -8
- arvi/lbl_wrapper.py +130 -17
- arvi/nasaexo_wrapper.py +137 -0
- arvi/plots.py +37 -12
- arvi/reports.py +12 -8
- arvi/simbad_wrapper.py +16 -3
- arvi/stats.py +21 -2
- arvi/timeseries.py +369 -79
- {arvi-0.1.3.dist-info → arvi-0.1.6.dist-info}/METADATA +2 -14
- arvi-0.1.6.dist-info/RECORD +24 -0
- {arvi-0.1.3.dist-info → arvi-0.1.6.dist-info}/WHEEL +1 -1
- arvi-0.1.3.dist-info/RECORD +0 -23
- {arvi-0.1.3.dist-info → arvi-0.1.6.dist-info}/LICENSE +0 -0
- {arvi-0.1.3.dist-info → arvi-0.1.6.dist-info}/top_level.txt +0 -0
arvi/timeseries.py
CHANGED
|
@@ -55,6 +55,8 @@ class RV:
|
|
|
55
55
|
_raise_on_error: bool = field(init=True, repr=False, default=True)
|
|
56
56
|
|
|
57
57
|
def __repr__(self):
|
|
58
|
+
if self.N == 0:
|
|
59
|
+
return f"RV(star='{self.star}', N=0)"
|
|
58
60
|
if self.time.size == self.mtime.size:
|
|
59
61
|
return f"RV(star='{self.star}', N={self.N})"
|
|
60
62
|
else:
|
|
@@ -73,7 +75,7 @@ class RV:
|
|
|
73
75
|
if self.verbose:
|
|
74
76
|
logger.info(f'querying DACE for {self.__star__}...')
|
|
75
77
|
try:
|
|
76
|
-
self.dace_result = get_observations(self.__star__, self.instrument,
|
|
78
|
+
self.dace_result = get_observations(self.__star__, self.instrument,
|
|
77
79
|
verbose=self.verbose)
|
|
78
80
|
except ValueError as e:
|
|
79
81
|
if self._raise_on_error:
|
|
@@ -96,20 +98,18 @@ class RV:
|
|
|
96
98
|
arrays = get_arrays(self.dace_result, verbose=self.verbose)
|
|
97
99
|
for (inst, pipe, mode), data in arrays:
|
|
98
100
|
child = RV.from_dace_data(self.star, inst, pipe, mode, data, _child=True)
|
|
101
|
+
inst = inst.replace('-', '_')
|
|
99
102
|
setattr(self, inst, child)
|
|
100
103
|
|
|
101
104
|
# build joint arrays
|
|
102
105
|
if not self._child:
|
|
103
106
|
#! sorted?
|
|
104
|
-
self.instruments =
|
|
107
|
+
self.instruments = [
|
|
108
|
+
inst.replace('-', '_')
|
|
109
|
+
for inst in sorted(list(self.dace_result.keys()))
|
|
110
|
+
]
|
|
105
111
|
# self.pipelines =
|
|
106
|
-
|
|
107
|
-
self.obs = np.concatenate(
|
|
108
|
-
[np.full(getattr(self, inst).N, i+1) for i, inst in enumerate(self.instruments)],
|
|
109
|
-
dtype=int
|
|
110
|
-
)
|
|
111
|
-
# mask
|
|
112
|
-
self.mask = np.full_like(self.obs, True, dtype=bool)
|
|
112
|
+
|
|
113
113
|
# all other quantities
|
|
114
114
|
self._build_arrays()
|
|
115
115
|
|
|
@@ -202,6 +202,18 @@ class RV:
|
|
|
202
202
|
|
|
203
203
|
error = sigma # alias!
|
|
204
204
|
|
|
205
|
+
@property
|
|
206
|
+
def _time_sorter(self):
|
|
207
|
+
return np.argsort(self.time)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def _mtime_sorter(self):
|
|
211
|
+
return np.argsort(self.mtime)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def _tt(self):
|
|
215
|
+
return np.linspace(self.mtime.min(), self.mtime.max(), 20*self.N)
|
|
216
|
+
|
|
205
217
|
@classmethod
|
|
206
218
|
def from_dace_data(cls, star, inst, pipe, mode, data, **kwargs):
|
|
207
219
|
s = cls(star, **kwargs)
|
|
@@ -211,9 +223,14 @@ class RV:
|
|
|
211
223
|
s.time = data['rjd'][ind]
|
|
212
224
|
s.vrad = data['rv'][ind]
|
|
213
225
|
s.svrad = data['rv_err'][ind]
|
|
226
|
+
|
|
214
227
|
# mask
|
|
215
228
|
s.mask = np.full_like(s.time, True, dtype=bool)
|
|
216
229
|
s.mask[np.isnan(s.svrad)] = False
|
|
230
|
+
## be careful with bogus values
|
|
231
|
+
s.mask[s.svrad < 0] = False
|
|
232
|
+
|
|
233
|
+
|
|
217
234
|
# all other quantities
|
|
218
235
|
s._quantities = []
|
|
219
236
|
for arr in data.keys():
|
|
@@ -223,11 +240,22 @@ class RV:
|
|
|
223
240
|
setattr(s, 'ccf_mask', data[arr][ind])
|
|
224
241
|
s._quantities.append('ccf_mask')
|
|
225
242
|
else:
|
|
243
|
+
# be careful with bogus values in rhk and rhk_err
|
|
244
|
+
if arr in ('rhk', 'rhk_err'):
|
|
245
|
+
mask99999 = (data[arr] == -99999) | (data[arr] == -99)
|
|
246
|
+
data[arr][mask99999] = np.nan
|
|
247
|
+
|
|
226
248
|
setattr(s, arr, data[arr][ind])
|
|
227
249
|
s._quantities.append(arr)
|
|
228
|
-
|
|
250
|
+
|
|
229
251
|
s._quantities = np.array(s._quantities)
|
|
230
252
|
|
|
253
|
+
# mask out drs_qc = False
|
|
254
|
+
if not s.drs_qc.all():
|
|
255
|
+
n = (~s.drs_qc).sum()
|
|
256
|
+
logger.warning(f'masking {n} points where DRS QC failed for {inst}')
|
|
257
|
+
s.mask &= s.drs_qc
|
|
258
|
+
|
|
231
259
|
s.instruments = [inst]
|
|
232
260
|
s.pipelines = [pipe]
|
|
233
261
|
s.modes = [mode]
|
|
@@ -258,22 +286,119 @@ class RV:
|
|
|
258
286
|
return s
|
|
259
287
|
|
|
260
288
|
@classmethod
|
|
261
|
-
def from_snapshot(cls, file):
|
|
289
|
+
def from_snapshot(cls, file=None, star=None):
|
|
262
290
|
import pickle
|
|
263
291
|
from datetime import datetime
|
|
264
|
-
|
|
265
|
-
|
|
292
|
+
if star is None:
|
|
293
|
+
assert file.endswith('.pkl'), 'expected a .pkl file'
|
|
294
|
+
star, timestamp = file.replace('.pkl', '').split('_')
|
|
295
|
+
else:
|
|
296
|
+
try:
|
|
297
|
+
file = sorted(glob(f'{star}_*.pkl'))[-1]
|
|
298
|
+
except IndexError:
|
|
299
|
+
raise ValueError(f'cannot find any file matching {star}_*.pkl')
|
|
300
|
+
star, timestamp = file.replace('.pkl', '').split('_')
|
|
301
|
+
|
|
266
302
|
dt = datetime.fromtimestamp(float(timestamp))
|
|
267
303
|
logger.info(f'Reading snapshot of {star} from {dt}')
|
|
268
304
|
return pickle.load(open(file, 'rb'))
|
|
269
305
|
|
|
270
|
-
|
|
306
|
+
@classmethod
|
|
307
|
+
def from_rdb(cls, files, star=None, units='ms', **kwargs):
|
|
308
|
+
if isinstance(files, str):
|
|
309
|
+
files = [files]
|
|
310
|
+
|
|
311
|
+
if star is None:
|
|
312
|
+
star_ = np.unique([os.path.splitext(f)[0].split('_')[0] for f in files])
|
|
313
|
+
if star_.size == 1:
|
|
314
|
+
logger.info(f'assuming star is {star_[0]}')
|
|
315
|
+
star = star_[0]
|
|
316
|
+
|
|
317
|
+
instruments = np.array([os.path.splitext(f)[0].split('_')[1] for f in files])
|
|
318
|
+
logger.info(f'assuming instruments: {instruments}')
|
|
319
|
+
|
|
320
|
+
if instruments.size == 1 and len(files) > 1:
|
|
321
|
+
instruments = np.repeat(instruments, len(files))
|
|
322
|
+
|
|
323
|
+
factor = 1e3 if units == 'kms' else 1.0
|
|
324
|
+
|
|
325
|
+
s = cls(star, _child=True, **kwargs)
|
|
326
|
+
|
|
327
|
+
for i, (f, instrument) in enumerate(zip(files, instruments)):
|
|
328
|
+
data = np.loadtxt(f, skiprows=2, usecols=range(3), unpack=True)
|
|
329
|
+
_s = cls(star, _child=True, **kwargs)
|
|
330
|
+
time = data[0]
|
|
331
|
+
_s.time = time
|
|
332
|
+
_s.vrad = data[1] * factor
|
|
333
|
+
_s.svrad = data[2] * factor
|
|
334
|
+
|
|
335
|
+
_quantities = []
|
|
336
|
+
#! hack
|
|
337
|
+
data = np.genfromtxt(f, names=True, dtype=None, comments='--', encoding=None)
|
|
338
|
+
|
|
339
|
+
if 'fwhm' in data.dtype.fields:
|
|
340
|
+
_s.fwhm = data['fwhm']
|
|
341
|
+
if 'sfwhm' in data.dtype.fields:
|
|
342
|
+
_s.fwhm_err = data['sfwhm']
|
|
343
|
+
else:
|
|
344
|
+
_s.fwhm_err = 2 * _s.svrad
|
|
345
|
+
else:
|
|
346
|
+
_s.fwhm = np.zeros_like(time)
|
|
347
|
+
_s.fwhm_err = np.full_like(time, np.nan)
|
|
348
|
+
|
|
349
|
+
_quantities.append('fwhm')
|
|
350
|
+
_quantities.append('fwhm_err')
|
|
351
|
+
|
|
352
|
+
if 'rhk' in data.dtype.fields:
|
|
353
|
+
_s.rhk = data['rhk']
|
|
354
|
+
if 'srhk' in data.dtype.fields:
|
|
355
|
+
_s.rhk_err = data['srhk']
|
|
356
|
+
else:
|
|
357
|
+
_s.rhk = np.zeros_like(time)
|
|
358
|
+
_s.rhk_err = np.full_like(time, np.nan)
|
|
359
|
+
|
|
360
|
+
_quantities.append('rhk')
|
|
361
|
+
_quantities.append('rhk_err')
|
|
362
|
+
|
|
363
|
+
_s.bispan = np.zeros_like(time)
|
|
364
|
+
_s.bispan_err = np.full_like(time, np.nan)
|
|
365
|
+
#! end hack
|
|
366
|
+
|
|
367
|
+
_s.mask = np.ones_like(time, dtype=bool)
|
|
368
|
+
_s.obs = np.full_like(time, i + 1)
|
|
369
|
+
|
|
370
|
+
_s.instruments = [instrument]
|
|
371
|
+
_s._quantities = np.array(_quantities)
|
|
372
|
+
setattr(s, instrument, _s)
|
|
373
|
+
|
|
374
|
+
s._child = False
|
|
375
|
+
s.instruments = list(instruments)
|
|
376
|
+
s._build_arrays()
|
|
377
|
+
|
|
378
|
+
if kwargs.get('do_adjust_means', False):
|
|
379
|
+
s.adjust_means()
|
|
380
|
+
|
|
381
|
+
return s
|
|
382
|
+
|
|
383
|
+
def _check_instrument(self, instrument, strict=False):
|
|
384
|
+
"""
|
|
385
|
+
Check if there are observations from `instrument`.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
instrument (str, None): Instrument name to check
|
|
389
|
+
strict (bool): Whether to match `instrument` exactly
|
|
390
|
+
Returns:
|
|
391
|
+
instruments (list):
|
|
392
|
+
List of instruments matching `instrument`, or None if there
|
|
393
|
+
are no matches.
|
|
394
|
+
"""
|
|
271
395
|
if instrument is None:
|
|
272
396
|
return self.instruments
|
|
397
|
+
if not strict:
|
|
398
|
+
if any([instrument in inst for inst in self.instruments]):
|
|
399
|
+
return [inst for inst in self.instruments if instrument in inst]
|
|
273
400
|
if instrument in self.instruments:
|
|
274
401
|
return [instrument]
|
|
275
|
-
if any([instrument in inst for inst in self.instruments]):
|
|
276
|
-
return [inst for inst in self.instruments if instrument in inst]
|
|
277
402
|
|
|
278
403
|
|
|
279
404
|
def _build_arrays(self):
|
|
@@ -298,6 +423,13 @@ class RV:
|
|
|
298
423
|
[getattr(self, inst).mask for inst in self.instruments]
|
|
299
424
|
)
|
|
300
425
|
|
|
426
|
+
# "observatory" (or instrument id)
|
|
427
|
+
self.obs = np.concatenate(
|
|
428
|
+
[np.full(getattr(self, inst).N, i+1) for i, inst in enumerate(self.instruments)],
|
|
429
|
+
dtype=int
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
301
433
|
# all other quantities
|
|
302
434
|
self._quantities = getattr(self, self.instruments[0])._quantities
|
|
303
435
|
if len(self.instruments) > 1:
|
|
@@ -376,18 +508,19 @@ class RV:
|
|
|
376
508
|
extracted_files = do_download_s2d(files[:limit], directory)
|
|
377
509
|
|
|
378
510
|
|
|
379
|
-
from .plots import plot, plot_fwhm, plot_bis, plot_rhk
|
|
511
|
+
from .plots import plot, plot_fwhm, plot_bis, plot_rhk, plot_quantity
|
|
380
512
|
from .plots import gls, gls_fwhm, gls_bis, gls_rhk
|
|
381
513
|
from .reports import report
|
|
382
514
|
|
|
383
515
|
from .instrument_specific import known_issues
|
|
384
516
|
|
|
385
517
|
|
|
386
|
-
def remove_instrument(self, instrument):
|
|
518
|
+
def remove_instrument(self, instrument, strict=False):
|
|
387
519
|
""" Remove all observations from one instrument
|
|
388
520
|
|
|
389
521
|
Args:
|
|
390
522
|
instrument (str): The instrument for which to remove observations.
|
|
523
|
+
strict (bool): Whether to match `instrument` exactly
|
|
391
524
|
|
|
392
525
|
Note:
|
|
393
526
|
A common name can be used to remove observations for several subsets
|
|
@@ -406,34 +539,37 @@ class RV:
|
|
|
406
539
|
|
|
407
540
|
will remove observations from the specific subset.
|
|
408
541
|
"""
|
|
409
|
-
|
|
542
|
+
instruments = self._check_instrument(instrument, strict)
|
|
543
|
+
|
|
544
|
+
if instruments is None:
|
|
410
545
|
logger.error(f"No data from instrument '{instrument}'")
|
|
411
546
|
logger.info(f'available: {self.instruments}')
|
|
412
547
|
return
|
|
413
548
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
549
|
+
for instrument in instruments:
|
|
550
|
+
ind = self.instruments.index(instrument) + 1
|
|
551
|
+
remove = np.where(self.obs == ind)
|
|
552
|
+
self.obs = np.delete(self.obs, remove)
|
|
553
|
+
self.obs[self.obs > ind] -= 1
|
|
554
|
+
#
|
|
555
|
+
self.time = np.delete(self.time, remove)
|
|
556
|
+
self.vrad = np.delete(self.vrad, remove)
|
|
557
|
+
self.svrad = np.delete(self.svrad, remove)
|
|
558
|
+
#
|
|
559
|
+
self.mask = np.delete(self.mask, remove)
|
|
560
|
+
#
|
|
561
|
+
# all other quantities
|
|
562
|
+
for q in self._quantities:
|
|
563
|
+
if q not in ('rjd', 'rv', 'rv_err'):
|
|
564
|
+
new = np.delete(getattr(self, q), remove)
|
|
565
|
+
setattr(self, q, new)
|
|
566
|
+
#
|
|
567
|
+
self.instruments.remove(instrument)
|
|
568
|
+
#
|
|
569
|
+
delattr(self, instrument)
|
|
434
570
|
|
|
435
|
-
|
|
436
|
-
|
|
571
|
+
if self.verbose:
|
|
572
|
+
logger.info(f"Removed observations from '{instrument}'")
|
|
437
573
|
|
|
438
574
|
if return_self:
|
|
439
575
|
return self
|
|
@@ -453,6 +589,9 @@ class RV:
|
|
|
453
589
|
logger.errors(f'index {index} is out of bounds for N={self.N}')
|
|
454
590
|
return
|
|
455
591
|
|
|
592
|
+
if self.verbose:
|
|
593
|
+
logger.info(f'removing points {index}')
|
|
594
|
+
|
|
456
595
|
self.mask[index] = False
|
|
457
596
|
self._propagate_mask_changes()
|
|
458
597
|
# for i, inst in zip(index, instrument):
|
|
@@ -461,6 +600,45 @@ class RV:
|
|
|
461
600
|
if return_self:
|
|
462
601
|
return self
|
|
463
602
|
|
|
603
|
+
def remove_non_public(self):
|
|
604
|
+
if self.verbose:
|
|
605
|
+
n = (~self.public).sum()
|
|
606
|
+
logger.info(f'masking non-public observations ({n})')
|
|
607
|
+
self.mask = self.mask & self.public
|
|
608
|
+
self._propagate_mask_changes()
|
|
609
|
+
|
|
610
|
+
def remove_single_observations(self):
|
|
611
|
+
""" Remove instruments for which there is a single observation """
|
|
612
|
+
instruments = deepcopy(self.instruments)
|
|
613
|
+
for inst in instruments:
|
|
614
|
+
if getattr(self, inst).mtime.size == 1:
|
|
615
|
+
self.remove_instrument(inst)
|
|
616
|
+
|
|
617
|
+
def remove_prog_id(self, prog_id):
|
|
618
|
+
from glob import has_magic
|
|
619
|
+
if has_magic(prog_id):
|
|
620
|
+
from fnmatch import filter
|
|
621
|
+
matching = np.unique(filter(self.prog_id, prog_id))
|
|
622
|
+
mask = np.full_like(self.time, False, dtype=bool)
|
|
623
|
+
for m in matching:
|
|
624
|
+
mask |= np.isin(self.prog_id, m)
|
|
625
|
+
ind = np.where(mask)[0]
|
|
626
|
+
self.remove_point(ind)
|
|
627
|
+
else:
|
|
628
|
+
if prog_id in self.prog_id:
|
|
629
|
+
ind = np.where(self.prog_id == prog_id)[0]
|
|
630
|
+
self.remove_point(ind)
|
|
631
|
+
else:
|
|
632
|
+
if self.verbose:
|
|
633
|
+
logger.warning(f'no observations for prog_id "{prog_id}"')
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def remove_after_bjd(self, bjd):
|
|
637
|
+
if (self.time > bjd).any():
|
|
638
|
+
ind = np.where(self.time > bjd)[0]
|
|
639
|
+
self.remove_point(ind)
|
|
640
|
+
|
|
641
|
+
|
|
464
642
|
def _propagate_mask_changes(self):
|
|
465
643
|
""" link self.mask with each self.`instrument`.mask """
|
|
466
644
|
masked = np.where(~self.mask)[0]
|
|
@@ -529,31 +707,43 @@ class RV:
|
|
|
529
707
|
if return_self:
|
|
530
708
|
return self
|
|
531
709
|
|
|
532
|
-
def sigmaclip(self, sigma=
|
|
533
|
-
""" Sigma-clip RVs """
|
|
710
|
+
def sigmaclip(self, sigma=5):
|
|
711
|
+
""" Sigma-clip RVs (per instrument!) """
|
|
712
|
+
#from scipy.stats import sigmaclip as dosigmaclip
|
|
713
|
+
from .stats import sigmaclip_median as dosigmaclip
|
|
714
|
+
|
|
534
715
|
if self._child or self._did_sigma_clip:
|
|
535
716
|
return
|
|
536
|
-
from scipy.stats import sigmaclip as dosigmaclip
|
|
537
|
-
result = dosigmaclip(self.vrad, low=sigma, high=sigma)
|
|
538
|
-
n = self.vrad.size - result.clipped.size
|
|
539
717
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
718
|
+
for inst in self.instruments:
|
|
719
|
+
m = self.instrument_array == inst
|
|
720
|
+
result = dosigmaclip(self.vrad[m], low=sigma, high=sigma)
|
|
721
|
+
n = self.vrad[m].size - result.clipped.size
|
|
543
722
|
|
|
544
|
-
|
|
723
|
+
ind = m & ((self.vrad < result.lower) | (self.vrad > result.upper))
|
|
545
724
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
725
|
+
if self.verbose and n > 0:
|
|
726
|
+
s = 's' if (n == 0 or n > 1) else ''
|
|
727
|
+
logger.warning(f'sigma-clip RVs will remove {n} point{s} for {inst}')
|
|
728
|
+
|
|
729
|
+
# # check if going to remove all observations from one instrument
|
|
730
|
+
# if n in self.NN.values(): # all observations
|
|
731
|
+
# # insts = np.unique(self.instrument_array[~ind])
|
|
732
|
+
# # if insts.size == 1: # of the same instrument?
|
|
733
|
+
# if self.verbose:
|
|
734
|
+
# logger.warning(f'would remove all observations from {insts[0]}, skipping')
|
|
735
|
+
# if return_self:
|
|
736
|
+
# return self
|
|
737
|
+
# continue
|
|
738
|
+
|
|
739
|
+
self.mask[ind] = False
|
|
554
740
|
|
|
555
|
-
self.mask[~ind] = False
|
|
556
741
|
self._propagate_mask_changes()
|
|
742
|
+
|
|
743
|
+
if self._did_adjust_means:
|
|
744
|
+
self._did_adjust_means = False
|
|
745
|
+
self.adjust_means()
|
|
746
|
+
|
|
557
747
|
if return_self:
|
|
558
748
|
return self
|
|
559
749
|
|
|
@@ -601,31 +791,38 @@ class RV:
|
|
|
601
791
|
bad_quantities = []
|
|
602
792
|
|
|
603
793
|
for q in s._quantities:
|
|
794
|
+
Q = getattr(s, q)
|
|
795
|
+
|
|
604
796
|
# treat date_night specially, basically doing a group-by
|
|
605
797
|
if q == 'date_night':
|
|
606
798
|
inds = binRV(s.mtime, None, None, binning_indices=True)
|
|
607
|
-
setattr(s, q,
|
|
799
|
+
setattr(s, q, Q[s.mask][inds])
|
|
608
800
|
continue
|
|
609
801
|
|
|
610
|
-
if
|
|
802
|
+
if Q.dtype != np.float64:
|
|
611
803
|
bad_quantities.append(q)
|
|
612
804
|
all_bad_quantities.append(q)
|
|
613
805
|
continue
|
|
614
806
|
|
|
615
|
-
if np.isnan(
|
|
807
|
+
if np.isnan(Q).all():
|
|
616
808
|
yb = np.full_like(tb, np.nan)
|
|
617
809
|
setattr(s, q, yb)
|
|
810
|
+
|
|
618
811
|
elif q + '_err' in s._quantities:
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
812
|
+
Qerr = getattr(s, q + '_err')
|
|
813
|
+
if (Qerr == 0.0).all():
|
|
814
|
+
_, yb = binRV(s.mtime, Q[s.mask], stat='mean', tstat='mean')
|
|
815
|
+
else:
|
|
816
|
+
_, yb, eb = binRV(s.mtime, Q[s.mask], Qerr[s.mask])
|
|
817
|
+
setattr(s, q + '_err', eb)
|
|
818
|
+
|
|
622
819
|
setattr(s, q, yb)
|
|
623
|
-
|
|
820
|
+
|
|
624
821
|
elif not q.endswith('_err'):
|
|
625
822
|
with warnings.catch_warnings():
|
|
626
823
|
warnings.filterwarnings('ignore', category=RuntimeWarning)
|
|
627
824
|
try:
|
|
628
|
-
_, yb = binRV(s.mtime,
|
|
825
|
+
_, yb = binRV(s.mtime, Q[s.mask],
|
|
629
826
|
stat=np.nanmean, tstat=np.nanmean)
|
|
630
827
|
setattr(s, q, yb)
|
|
631
828
|
except TypeError:
|
|
@@ -651,7 +848,33 @@ class RV:
|
|
|
651
848
|
snew._build_arrays()
|
|
652
849
|
return snew
|
|
653
850
|
|
|
851
|
+
def nth_day_mean(self, n=1.0):
|
|
852
|
+
mask = np.abs(self.mtime[:, None] - self.mtime[None, :]) < n
|
|
853
|
+
z = np.full((self.mtime.size, self.mtime.size), np.nan)
|
|
854
|
+
z[mask] = np.repeat(self.mvrad[:, None], self.mtime.size, axis=1)[mask]
|
|
855
|
+
return np.nanmean(z, axis=0)
|
|
856
|
+
|
|
857
|
+
def subtract_mean(self):
|
|
858
|
+
""" Subtract (single) mean RV from all instruments """
|
|
859
|
+
self._meanRV = meanRV = self.mvrad.mean()
|
|
860
|
+
for inst in self.instruments:
|
|
861
|
+
s = getattr(self, inst)
|
|
862
|
+
s.vrad -= meanRV
|
|
863
|
+
self._build_arrays()
|
|
864
|
+
|
|
865
|
+
def _add_back_mean(self):
|
|
866
|
+
""" Add the (single) mean RV removed by self.subtract_mean() """
|
|
867
|
+
if not hasattr(self, '_meanRV'):
|
|
868
|
+
logger.warning("no mean RV stored, run 'self.subtract_mean()'")
|
|
869
|
+
return
|
|
870
|
+
|
|
871
|
+
for inst in self.instruments:
|
|
872
|
+
s = getattr(self, inst)
|
|
873
|
+
s.vrad += self._meanRV
|
|
874
|
+
self._build_arrays()
|
|
875
|
+
|
|
654
876
|
def adjust_means(self, just_rv=False):
|
|
877
|
+
""" Subtract individual mean RVs from each instrument """
|
|
655
878
|
if self._child or self._did_adjust_means:
|
|
656
879
|
return
|
|
657
880
|
|
|
@@ -692,6 +915,42 @@ class RV:
|
|
|
692
915
|
if return_self:
|
|
693
916
|
return self
|
|
694
917
|
|
|
918
|
+
def put_at_systemic_velocity(self):
|
|
919
|
+
"""
|
|
920
|
+
For instruments in which mean(RV) < ptp(RV), "move" RVs to the systemic
|
|
921
|
+
velocity from simbad. This is useful if some instruments are centered
|
|
922
|
+
at zero while others are not, and instead of calling `.adjust_means()`,
|
|
923
|
+
but it only works when the systemic velocity is smaller than ptp(RV).
|
|
924
|
+
"""
|
|
925
|
+
changed = False
|
|
926
|
+
for inst in self.instruments:
|
|
927
|
+
s = getattr(self, inst)
|
|
928
|
+
if s.mask.any():
|
|
929
|
+
if np.abs(s.mvrad.mean()) < s.mvrad.ptp():
|
|
930
|
+
s.vrad += self.simbad.rvz_radvel * 1e3
|
|
931
|
+
changed = True
|
|
932
|
+
else: # all observations are masked, use non-masked arrays
|
|
933
|
+
if np.abs(s.vrad.mean()) < s.vrad.ptp():
|
|
934
|
+
s.vrad += self.simbad.rvz_radvel * 1e3
|
|
935
|
+
changed = True
|
|
936
|
+
if changed:
|
|
937
|
+
self._build_arrays()
|
|
938
|
+
|
|
939
|
+
def sort_instruments(self, by_first_observation=True, by_last_observation=False):
|
|
940
|
+
if by_last_observation:
|
|
941
|
+
by_first_observation = False
|
|
942
|
+
# if by_first_observation and by_last_observation:
|
|
943
|
+
# logger.error("'by_first_observation' and 'by_last_observation' can't both be true")
|
|
944
|
+
# return
|
|
945
|
+
if by_first_observation:
|
|
946
|
+
fun = lambda i: getattr(self, i).time.min()
|
|
947
|
+
self.instruments = sorted(self.instruments, key=fun)
|
|
948
|
+
self._build_arrays()
|
|
949
|
+
if by_last_observation:
|
|
950
|
+
fun = lambda i: getattr(self, i).time.max()
|
|
951
|
+
self.instruments = sorted(self.instruments, key=fun)
|
|
952
|
+
self._build_arrays()
|
|
953
|
+
|
|
695
954
|
#
|
|
696
955
|
|
|
697
956
|
def save(self, directory=None, instrument=None, full=False):
|
|
@@ -707,6 +966,11 @@ class RV:
|
|
|
707
966
|
"""
|
|
708
967
|
star_name = self.star.replace(' ', '')
|
|
709
968
|
|
|
969
|
+
if directory is None:
|
|
970
|
+
directory = '.'
|
|
971
|
+
else:
|
|
972
|
+
os.makedirs(directory, exist_ok=True)
|
|
973
|
+
|
|
710
974
|
files = []
|
|
711
975
|
|
|
712
976
|
for inst in self.instruments:
|
|
@@ -714,26 +978,27 @@ class RV:
|
|
|
714
978
|
if instrument not in inst:
|
|
715
979
|
continue
|
|
716
980
|
|
|
717
|
-
file = f'{star_name}_{inst}.rdb'
|
|
718
|
-
files.append(file)
|
|
719
|
-
|
|
720
|
-
if directory is not None:
|
|
721
|
-
os.makedirs(directory, exist_ok=True)
|
|
722
|
-
file = os.path.join(directory, file)
|
|
723
|
-
|
|
724
981
|
_s = getattr(self, inst)
|
|
725
982
|
|
|
983
|
+
if not _s.mask.any(): # all observations are masked, don't save
|
|
984
|
+
continue
|
|
985
|
+
|
|
726
986
|
if full:
|
|
727
987
|
d = np.c_[
|
|
728
988
|
_s.mtime, _s.mvrad, _s.msvrad,
|
|
729
|
-
_s.fwhm[_s.mask], _s.fwhm_err[_s.mask]
|
|
989
|
+
_s.fwhm[_s.mask], _s.fwhm_err[_s.mask],
|
|
990
|
+
_s.rhk[_s.mask], _s.rhk_err[_s.mask],
|
|
730
991
|
]
|
|
731
|
-
header = 'bjd\tvrad\tsvrad\tfwhm\tsfwhm\n'
|
|
732
|
-
header += '---\t----\t-----\t----\t
|
|
992
|
+
header = 'bjd\tvrad\tsvrad\tfwhm\tsfwhm\trhk\tsrhk\n'
|
|
993
|
+
header += '---\t----\t-----\t----\t-----\t---\t----'
|
|
733
994
|
else:
|
|
734
995
|
d = np.c_[_s.mtime, _s.mvrad, _s.msvrad]
|
|
735
996
|
header = 'bjd\tvrad\tsvrad\n---\t----\t-----'
|
|
736
997
|
|
|
998
|
+
file = f'{star_name}_{inst}.rdb'
|
|
999
|
+
files.append(file)
|
|
1000
|
+
file = os.path.join(directory, file)
|
|
1001
|
+
|
|
737
1002
|
np.savetxt(file, d, fmt='%9.5f', header=header, delimiter='\t', comments='')
|
|
738
1003
|
|
|
739
1004
|
if self.verbose:
|
|
@@ -741,6 +1006,16 @@ class RV:
|
|
|
741
1006
|
|
|
742
1007
|
return files
|
|
743
1008
|
|
|
1009
|
+
def checksum(self, write_to=None):
|
|
1010
|
+
from hashlib import md5
|
|
1011
|
+
d = np.r_[self.time, self.vrad, self.svrad]
|
|
1012
|
+
H = md5(d.data.tobytes()).hexdigest()
|
|
1013
|
+
if write_to is not None:
|
|
1014
|
+
with open(write_to, 'w') as f:
|
|
1015
|
+
f.write(H)
|
|
1016
|
+
return H
|
|
1017
|
+
|
|
1018
|
+
|
|
744
1019
|
#
|
|
745
1020
|
def run_lbl(self, instrument=None, data_dir=None,
|
|
746
1021
|
skysub=False, tell=False, limit=None, **kwargs):
|
|
@@ -796,9 +1071,10 @@ class RV:
|
|
|
796
1071
|
|
|
797
1072
|
run_lbl(self, instrument, files[:limit], **kwargs)
|
|
798
1073
|
|
|
799
|
-
def load_lbl(self, instrument=None, tell=False):
|
|
1074
|
+
def load_lbl(self, instrument=None, tell=False, id=None):
|
|
800
1075
|
if hasattr(self, '_did_load_lbl') and self._did_load_lbl: # don't do it twice
|
|
801
1076
|
return
|
|
1077
|
+
|
|
802
1078
|
from .lbl_wrapper import load_lbl
|
|
803
1079
|
|
|
804
1080
|
if instrument is None:
|
|
@@ -818,11 +1094,25 @@ class RV:
|
|
|
818
1094
|
instruments = instrument
|
|
819
1095
|
|
|
820
1096
|
for inst in instruments:
|
|
821
|
-
|
|
1097
|
+
if self.verbose:
|
|
1098
|
+
logger.info(f'loading LBL data for {inst}')
|
|
822
1099
|
|
|
1100
|
+
load_lbl(self, inst, tell=tell, id=id)
|
|
1101
|
+
# self.instruments.append(f'{inst}_LBL')
|
|
1102
|
+
|
|
1103
|
+
# self._build_arrays()
|
|
823
1104
|
self._did_load_lbl = True
|
|
824
1105
|
|
|
825
1106
|
|
|
1107
|
+
#
|
|
1108
|
+
@property
|
|
1109
|
+
def planets(self):
|
|
1110
|
+
from .nasaexo_wrapper import Planets
|
|
1111
|
+
if not hasattr(self, '_planets'):
|
|
1112
|
+
self._planets = Planets(self)
|
|
1113
|
+
return self._planets
|
|
1114
|
+
|
|
1115
|
+
|
|
826
1116
|
def fit_sine(t, y, yerr, period='gls', fix_period=False):
|
|
827
1117
|
from scipy.optimize import leastsq
|
|
828
1118
|
if period == 'gls':
|