Source code for sora.occultation.chordlist

import warnings

import numpy as np

from sora.config.list import List
from .chord import Chord

__all__ = ['ChordList']


[docs]class ChordList(List): """Defines a collection of Chord objects associated to an Occultation. This object is not supposed to be defined by the user. It will be automatically defined in Occultation. Attributes ---------- star : `sora.Star` The Star occulted. body : `sora.Body` The occulting Body. time : `astropy.time.Time` The occultation time. """ _allowed_types = (Chord,) # (Chord, AstrometricChord) _set_func = 'add_chord' def __init__(self, *, star, body, time): super().__init__() self._star = star self._body = body self._time = time self._shared_with = {"chord": {"star": self._star, "ephem": self._body.ephem, "time": self._time}, 'occultation': {}} self._method_value = 'geocenter'
[docs] def add_chord(self, *, name=None, chord=None, observer=None, lightcurve=None): """Add a chord to the occultation chord list Parameters ---------- name : `str` The name of the Chord. It must be unique within the list of chords. If not given, it will use the name in chord if chord is directly given or the name in the observer object. If the name in Chord already exists in the list, the name parameter can be given to update the chord name. The `name` parameter is required if given together with observer and lightcurve. It can not be an empty string. chord `sora.occultation.Chord` A chord instance defined by the user. observer : `sora.Observer` An Observer instance defined by the user. It must be given together with a lightcurve. lightcurve : `sora.LightCurve` A lightcurve defined by the user. It must be given together with observer. Examples -------- Ways to add a chord: >>> obj.add_chord(chord) # where the name in chord will be used. >>> obj.add_chord(name, chord) # where the name in chord will be replaced by "name". >>> obj.add_chord(observer, lightcurve) # where the name in observer will be used. >>> obj.add_chord(name, observer, lightcurve) """ if chord and (observer or lightcurve): raise ValueError("User must give only chord or (name and observer and lightcurve)") if chord: name = name or chord.name elif observer and lightcurve: for key in self.keys(): if self[key].lightcurve is lightcurve: raise ValueError('lightcurve is already associated to the chord {}'.format(key)) name = name or observer.name chord = Chord(name=name, observer=observer, lightcurve=lightcurve) else: raise ValueError("User must give chord or (name and observer and lightcurve)") self._add_item(name=name, item=chord) chord._name = name chord._shared_with["chordlist"] = self._shared_with["chord"] chord.lightcurve.set_vel(self._shared_with['occultation']["vel"]) chord.lightcurve.set_dist(self._shared_with['occultation']["dist"]) chord.lightcurve.set_star_diam(self._shared_with['occultation']["star_diam"]) try: chord.lightcurve.calc_magnitude_drop(mag_star=self._star.mag['G'], mag_obj=self._body.apparent_magnitude(self._time)) except: chord.lightcurve.bottom_flux = 0.0 warnings.warn('Magnitude drop was not calculated. Using bottom flux as 0.0.') chord._method = self._method return chord
# This attribute is to modify the way to calculate f and g on chord. @property def _method(self): return self._method_value @_method.setter def _method(self, value): if value not in ['geocenter', 'observer']: raise ValueError('method must be "geocenter" or "observer"') self._method_value = value for name, chord in self.items(): chord._method = value
[docs] def remove_chord(self, *, name): """Remove a chord from the chord list and disassociate it from the Occultation. Parameters ---------- name : `str` The name of the chord. """ del(self[name]._shared_with['chordlist']) del(self[name])
@property def is_able(self): return {name: chord.is_able for name, chord in self.items()}
[docs] def enable(self, *, chord=None, time=None): """Enable a contact point of the curve to be used in the fit. Parameters ---------- chord : `str` Name of the chord to enable. If ``chord=None``, it applies to all chords. time : Non`, `str` If ``time=None``, it will enable all contact points. If 'immersion' or 'emersion', it will enable respective contact point. """ n = 0 for key in self.keys(): if chord is None or chord == key: self[key].enable(time=time) n += 1 if n == 0: raise ValueError('No Chord with name {} is available.'.format(chord))
[docs] def disable(self, *, chord=None, time=None): """Disable a contact point of the curve to be used in the fit. Parameters ---------- chord : `str` Name of the chord to disable. If ``chord=None``, it applies to all chords. time : None, `str` If ``time=None``, it will disable all contact points. If 'immersion' or 'emersion', it will disable respective contact point. """ n = 0 for key in self.keys(): if chord is None or chord == key: self[key].disable(time=time) n += 1 if n == 0: raise ValueError('No Chord with name {} is available.'.format(chord))
[docs] def plot_chords(self, *, segment='standard', ignore_chords=None, only_able=False, ax=None, linestyle='-', **kwargs): """Plots the on-sky path of this chord. Parameters ---------- segment : `str` The segment to plot the chord. The available options are: ``'positive'`` to get the path between the immersion and emersion times if the chord is positive. ``'negative'`` to get the path between the start and end of observation if the chord is negative. ``'standard'`` to get the 'positive' path if the chord is positive or 'negative' if the chord is negative. ``'full'`` to get the path between the start and end of observation independent if the chord is positive or negative. ``'outer'`` to get the path outside the 'positive' path, for instance between the start and immersion times and between the emersion and end times. ``'error'`` to get the path corresponding to the error bars. ignore_chords : `str`, `list` Name of chord or list of names to ignore in the plot. only_able : `bool` Plot only the chords or contact points that are able to be used in the fit. If ``segment='error'`` it will show only the contact points able. If segment is any other, the path will be plotted only if both immersion and emersion are able, or it is a negative chord. ax : `matplotlib.pyplot.Axes` The axes where to make the plot. If None, it will use the default axes. linestyle : `str` Default linestyle used in `matplotlib.pyplot.plot`. The difference is that now it accept ``linestyle='exposure'``, where the plot will be a dashed line corresponding to each exposure. The blank space between the lines can be interpreted as 'dead time'. **kwargs Any other kwarg will be parsed directly by `maplotlip.pyplot.plot`. The only difference is that the default linewidth ``lw=2``. """ n = 0 keys = list(self.keys()) if ignore_chords is not None: ignore_chords = np.array(ignore_chords, ndmin=1) for i in range(len(self)): if ignore_chords is not None and keys[i] in ignore_chords: continue if segment != 'error': kwargs['label'] = keys[i] try: _ = self[i].plot_chord(segment=segment, only_able=only_able, ax=ax, linestyle=linestyle, **kwargs) except ValueError: n += 1 if n == len(self): warnings.warn('Segment "{}" was not found on any chord'.format(segment))
[docs] def summary(self): """Prints a table with the summary of the chords. """ from astropy.table import Table, vstack tables = [] for key in self.keys(): tt = Table() obs = self[key].observer lc = self[key].lightcurve max_val = 0 cols = [] colnames = ['Name', 'Longitude', 'Latitude', 'status', 'time', 'f', 'g'] itens = [key, obs.lon.to_string(), obs.lat.to_string()] row = [] times = {'Initial Time': 'initial_time', 'Immersion': 'immersion', 'Emersion': 'emersion', 'End Time': 'end_time'} for i in times: val = getattr(lc, times[i], None) if val is not None: row.append([i, val.iso, *['{:.2f}'.format(n) for n in self[key].get_fg(time=val)]]) for t in np.array(row).T: itens.append(t.tolist()) for item in itens: v = np.array(item, ndmin=1).tolist() cols.append(v) if len(v) > max_val: max_val = len(v) for i in range(len(cols)): if len(cols[i]) < max_val: for n in range(max_val - len(cols[i])): cols[i].append('') tt[colnames[i]] = cols[i] tables.append(tt) tabela = vstack(tables) tabela.pprint_all()
[docs] def get_impact_param(self, chords='all_chords', center_f=0, center_g=0, verbose=True): """Get the impact parameter, minimal distance between the chord and the centre position. This Chord object must be associated to an Occultation to work, since it needs the position of the star and an ephemeris. Parameters ---------- chords : `int`, `str`, default='all_chords' Index or names of the chords to be considered. center_f : `int`, `float`, default=0 The coordinate in f of the ellipse center. center_g : `int`, `float`, default=0 The coordinate in g of the ellipse center. verbose : `bool` If True, prints the obtained values. Returns ------- impact, sense, chord_name : `list` The impact parameter (in km), the direction of the chord relative the ellipse center, North (N), South (S), East (E) and West (W), and the name of the chord """ impact = np.array([]) sense = np.array([]) names = np.array([]) if chords == 'all_chords': chords = range(len(self)) for i in chords: chord = self[i] im, se = chord.get_impact_param(center_f=center_f, center_g=center_g, verbose=verbose) impact = np.append(impact, im) sense = np.append(sense, se) names = np.append(names, chord.name) return impact, sense, names
[docs] def get_theoretical_times(self, equatorial_radius, chords='all_chords', center_f=0, center_g=0, oblateness=0, position_angle=0, sigma=0, step=1, verbose=True): """Get the theoretical times and chords sizes for a given ellipse. This Chord object must be associated to an Occultation to work, since it needs the position of the star and an ephemeris. Parameters ---------- chords : `int`, `str`, default='all_chords' Index or names of the chords to be considered. equatorial_radius : `int`, `float` The Equatorial radius (semi-major axis) of the ellipse. center_f : `int`, `float`, default=0 The coordinate in f of the ellipse center. center_g : `int`, `float`, default=0 The coordinate in g of the ellipse center. oblateness : `int`, `float`, default=0 The oblateness of the ellipse. position_angle : `int`, `float`, default=0 The pole position angle of the ellipse in degrees. Zero is in the North direction ('g-positive'). Positive clockwise. sigma : `int`, `float` Uncertainty of the expected ellipse, in km. step : `int`, `float` Time resolution of the chord, in seconds. verbose : `bool` If True, prints the obtained values. Returns ------- theory_immersion_time, theory_emersion_time, theory_chord_size, chord_name : `list` The expected immersion time for the given ellipse, the expected emersion time for the given ellipse, the expected chord size for the given ellipse, and the name of the chord. """ theory_immersion_time = np.array([]) theory_emersion_time = np.array([]) theory_chord_size = np.array([]) names = np.array([]) if chords == 'all_chords': chords = range(len(self)) for i in chords: chord = self[i] tit, tet, tcs = chord.get_theoretical_times(equatorial_radius=equatorial_radius, center_f=center_f, center_g=center_g, oblateness=oblateness, position_angle=position_angle, sigma=sigma, step=step, verbose=verbose) theory_immersion_time = np.append(theory_immersion_time, tit) theory_emersion_time = np.append(theory_emersion_time, tet) theory_chord_size = np.append(theory_chord_size, tcs) names = np.append(names, chord.name) return theory_immersion_time, theory_emersion_time, theory_chord_size, names