def calculate_uncertainties(self): """ Calculate the uncertainties of this parameter. """ # If this parameter does not allow uncertainties, set them to None. if not self.uncertain_flag: self.uncertainty = None self.uncertainty_lower = None self.uncertainty_upper = None return # If all uncertainties are empty, reset them to uncertainties provided # by the initial template construction. elif (is_empty(self.uncertainty) and is_empty(self.uncertainty_lower) and is_empty(self.uncertainty_upper) ): self.reset_uncertainties() return # If upper and lower uncertainties are provided, calculate the mean # uncertainty. elif (is_valid(self.uncertainty_lower) and is_valid(self.uncertainty_upper) ): u_hi = Decimal(self.uncertainty_upper) u_lo = Decimal(self.uncertainty_lower) u_avg = (u_lo + u_hi) / 2 self.uncertainty = Decimal(u_avg) # If mean and upper uncertainties are provided, calculate ther lower # uncertainty. elif (is_valid(self.uncertainty) and is_valid(self.uncertainty_upper)): u_hi = Decimal(self.uncertainty_upper) u_avg = Decimal(self.uncertainty) u_lo = (2 * u_avg) - u_hi self.uncertainty_lower = Decimal(u_lo) # If only an upper uncertainty is provided, treat this as the mean. elif (is_empty(self.uncertainty) and is_valid(self.uncertainty_upper)): temp = Decimal(self.uncertainty_upper) self.reset_uncertainties() self.uncertainty = temp # If only a lower uncertainty is provided, treat this as the mean. elif (is_empty(self.uncertainty) and is_valid(self.uncertainty_lower)): temp = Decimal(self.uncertainty_lower) self.reset_uncertainties() self.uncertainty = temp else: return
def calculate_uncertainties(self): """ Calculate the uncertainties of this parameter. """ # If this parameter does not allow uncertainties, set them to None. if not self.uncertain_flag: self.uncertainty = None self.uncertainty_lower = None self.uncertainty_upper = None return # If all uncertainties are empty, reset them to uncertainties provided # by the initial template construction. elif (is_empty(self.uncertainty) and is_empty(self.uncertainty_lower) and is_empty(self.uncertainty_upper)): self.reset_uncertainties() return # If upper and lower uncertainties are provided, calculate the mean # uncertainty. elif (is_valid(self.uncertainty_lower) and is_valid(self.uncertainty_upper)): u_hi = Decimal(self.uncertainty_upper) u_lo = Decimal(self.uncertainty_lower) u_avg = (u_lo + u_hi) / 2 self.uncertainty = Decimal(u_avg) # If mean and upper uncertainties are provided, calculate ther lower # uncertainty. elif (is_valid(self.uncertainty) and is_valid(self.uncertainty_upper)): u_hi = Decimal(self.uncertainty_upper) u_avg = Decimal(self.uncertainty) u_lo = (2 * u_avg) - u_hi self.uncertainty_lower = Decimal(u_lo) # If only an upper uncertainty is provided, treat this as the mean. elif (is_empty(self.uncertainty) and is_valid(self.uncertainty_upper)): temp = Decimal(self.uncertainty_upper) self.reset_uncertainties() self.uncertainty = temp # If only a lower uncertainty is provided, treat this as the mean. elif (is_empty(self.uncertainty) and is_valid(self.uncertainty_lower)): temp = Decimal(self.uncertainty_lower) self.reset_uncertainties() self.uncertainty = temp else: return
def _calculate_rhostar(self): """ If needed, calculate RHOSTAR using MSTAR and RSTAR if available. """ if (is_empty(self.rhostar.value) and is_valid(self.mstar.value) and is_valid(self.rstar.value) ): mstar = Decimal(self.mstar.value) rstar = Decimal(self.rstar.value) density = (mstar * (Decimal(1.41) / (rstar ** 3))) mu = Decimal(self.mstar.uncertainty) ru = Decimal(self.rstar.uncertainty) if is_valid(mu) and is_valid(ru): du = (mu * (Decimal(1.41) / (ru ** 3))) else: du = 'NaN' self.rhostar.value = round(density, len(str(mstar))) self.rhostar.uncertainty = du self.rhostar.reference = "Calculated from MSTAR and RSTAR" self.rhostar.url = str(self.mstar.url)
def set_from_dict(self, attribute_dict): """ Update the attributes of this ExoParameter using a provided dictionary. :param attribute_dict: This module needs a dictionary of attribute / value pairs to set attributes of self. :type attribute_dict: dict """ # Iterate through each attribute / value pair in the dictionary. for attr, value in attribute_dict.items(): # Get the value currently in self.attr. Use None if this is not a # current attribute of self. try: old_value = getattr(self, attr) except AttributeError: old_value = None # Uncertainty values from the GUI will either be None or Decimals. # We want to prevent overwriting "NaN" with None. if (value is None and is_empty(old_value)): continue # Update self. setattr(self, attr, value) # If no value is provided, set to default. if self.value is None: self.value = self.default
def _add_nasa_value(self, frame, attr): """ Add values from a CustomNASA object to an ExoPlanet object. :param frame: The ingested NASA Archive .csv file. :type frame: CustomNASA :param attr: The ExoPlanet attribute to be updated. :type attr: ExoParameter """ # Look up the appropriate field to look for in the CustomNASA object. # If nasa_field is empty, return immediately. nasa_str = attr.nasa_field if is_empty(nasa_str): return # Get the current ExoParameter attached to attr, and update the # new value. attr.value = frame.read_val(nasa_str) if is_valid(attr.value): print("Updated {0} for {1}".format(attr.parameter, self.name.value )) # Update error values for this attribute. hi, lo = frame.read_errors(nasa_str) attr.uncertainty_upper = hi attr.uncertainty_lower = lo # Get the reference and url strings for this attribute. attr.reference, attr.url = frame.read_refs()
def _check_ar(self): """ If the AR value is empty, clear the reference & url parameters. """ if is_empty(self.ar.value): self.ar.reference = "Calculated" self.ar.url = None
def check_constrained(self, limit=None): """ Set the well-constrained flag based on relative uncertainty percent. :param limit: Optionally provide a different acceptable limit tolerance (0.1 by default). :type limit: float """ # Set the 'well-constrained' limit at 10% (arbitrary) if not provided. limit = (Decimal(0.1) if not limit else Decimal(limit)) if is_empty(self.value) or is_empty(self.uncertainty): return False elif self.uncertainty > (Decimal(self.value) * Decimal(limit)): self.well_constrained = False else: self.well_constrained = True
def _add_simbad_coords(self, query): """ Add RA and Dec information from a Simbad query to an ExoPlanet object. :param query: The Simbad query object. :type query: CustomSimbad """ # Get the coordinate values from the query object. ra, dec = query.get_coordinates() # If either coordinate is empty, exit the function. if is_empty(ra) or is_empty(dec): pl = self.name.value print("Could not find Simbad coordinates for {0}".format(pl)) return # Set the ExoPlanet coordinate string values to these figures. self.ra_string.value = str(ra) self.dec_string.value = str(dec) # Use astropy SkyCoord to convert these into coordinates in hours and # degrees. coord = SkyCoord(" ".join([ra, dec]), unit=(u.hourangle, u.deg)) # Round the numerical coordinates to a reasonable length and set the # ExoPlanet attributes. self.ra.value = round(Decimal(coord.ra.hour), 10) self.dec.value = round(Decimal(coord.dec.degree), 10) # Set the COORDREF value to 'Simbad'. self.coordref.value = "Simbad" # Convert special characters in the SIMBADNAME to HTML friendly characters. ident = self.simbadname.value.replace("+", "%2B") ident = ident.replace(" ", "+") # Construct the Simbad target url and update COORDURL. simbad_url = "http://simbad.u-strasbg.fr/simbad/sim-basic?ident=" self.coordurl.value = "".join([simbad_url, ident])
def reset_parameter(self, force=None): """ Reset this parameter to the original template attributes. :param force: Set this flag to force a reset of the given parameter. :type force: bool """ # Set self to a new ExoParameter using the original self.template # dictionary. This will reset a parameter that has a null value or # it can be forced to reset. if (is_empty(self.value) or force): self = ExoParameter(self.parameter, attr_dict=self.template, from_template=True) self.value = self.default
def reset_parameter(self, force=None): """ Reset this parameter to the original template attributes. :param force: Set this flag to force a reset of the given parameter. :type force: bool """ # Set self to a new ExoParameter using the original self.template # dictionary. This will reset a parameter that has a null value or # it can be forced to reset. if (is_empty(self.value) or force): self = ExoParameter(self.parameter, attr_dict=self.template, from_template=True ) self.value = self.default
def check_t0(self): """ If T0 is empty, try to calculate it from a couple different methods if either is available. """ if is_empty(self.t0.value): if (is_valid(self.tt.value) and is_valid(self.om.value) and is_valid(self.ecc.value) ): self._calculate_t0_from_tt() elif (is_valid(self.m0.value) and is_valid(self.reftime.value) and is_valid(self.per.value) and self.ecc.value == Decimal(0) ): self._calculate_t0_from_m0()
def add_nasa_info(self, nasa_frame): """ Add values from a scraped NASA Archive table to self. :param nasa_frame: The scraped NASA Archive data object for this planet. :type nasa_frame: CustomNASA """ # Keep a list of ExoPlanet attributes to update with NASA data. fields_to_update = ["rhostar"] for f in fields_to_update: # Get the current value of this field. current = getattr(self, f) # If there is currently no value listed for this field, look for # one from the provided NASA data object. if is_empty(current.value): self._add_nasa_value(nasa_frame, current)
def check_limits(self): """ Compare the current value attribute to any limits provided for this parameter. (not currently used) """ too_hi = False too_lo = False if is_empty(self.value): return None # Check for limit violations. if is_valid(self.limit_lower): too_lo = (self.value < self.limit_lower) if is_valid(self.limit_upper): too_hi = (self.value > self.limit_upper) # If a limit is violated, return the designated limit action. if too_hi or too_lo: return self.limit_action else: return None
def read_from_nasa(self, nasa_series): """ Construct an ExoPlanet from data scraped from the NASA ExoPlanet Archive. :param nasa_series: This function expects a data pulled for a single matching exoplanet. :type nasa_series: pandas.Series """ # Transform the Pandas Series containing NASA parameters into a dict. nasa_dict = nasa_series.to_dict() # Look for every attribute added during ExoPlanet initialization. for att in self.attributes: # getattr will return an ExoParameter object for this attribute. exo_param = getattr(self, att) # Check for a corresponding data field in the NASA data. nasa_field = exo_param.nasa_field if nasa_field is None: continue # Get the NASA value and apply some changes based on which # ExoParameter we are working on. new_value = nasa_dict[nasa_field] if is_empty(new_value): new_value = exo_param.default # Remove 'd', 'm', 's' from the DEC string and fill with ' '. elif nasa_field == "dec_str": new_value = new_value.replace("d", " ") new_value = new_value.replace("m", " ") new_value = new_value[:-1] # NASA hd_name includes the planet letter, so cut that off. elif nasa_field == "hd_name": new_value = " ".join(new_value.split(" ")[:-1]) # NASA hip_name includes the planet letter, so cut that off. elif nasa_field == "hip_name": new_value = " ".join(new_value.split(" ")[:-1]) # NASA provides transit depth values in percentages, we just want # the decimal value. elif nasa_field == "pl_trandep" and is_valid(new_value): new_value = Decimal(new_value) / 100 # Remove 'h', 'm', 's' from the RA string and fill with ' '. elif nasa_field == "ra_str": new_value = new_value.replace("h", " ") new_value = new_value.replace("m", " ") new_value = new_value[:-1] # If no RV measurements are listed in NASA, we want to use -1 for # our NOBS. elif nasa_field == "st_nrvc" and new_value == "0": new_value = -1 # Try changing the new value into a Decimal. try: exo_param.value = Decimal(new_value) except (InvalidOperation, TypeError): exo_param.value = new_value # Try looking for corresponding uncertainty values in the NASA # data. if exo_param.uncertain_flag: # Create the two error column fields (for + and - uncertainty). nasa_err1 = "".join([nasa_field, "err1"]) nasa_err2 = "".join([nasa_field, "err2"]) # Pull values from these columns if they exist. try: shi = str(nasa_dict[nasa_err1]) exo_param.uncertainty_upper = Decimal(shi) # NASA data stores lower uncertainty as a negative number. slo = str(nasa_dict[nasa_err2]) exo_param.uncertainty_lower = Decimal(slo) * -1 except KeyError: pass # Reset the current ExoPlanet attribute to the now-updated # ExoParameter. setattr(self, att, exo_param)
def verify_pln(self): """ Some last-second tweaks are needed for proper .pln file formatting. """ warnings = [] self._populate_uncertainties() # The transitref and transiturl actually end up stored in the 'transit' # ExoParam due to the ref and url splits. Pull these out and set the # transit entries to the proper pointers. if self.transit.value == 1: if is_empty(self.transit.reference): self.transit.reference = "__TRANSITREF" if is_empty(self.transit.url): self.transit.url = "__TRANSITURL" # If the transit depth is not provided, but an Rp/R* ratio is, # calculate the depth value. if is_empty(self.depth.value) and is_valid(self.rr.value): self.depth.value = self.rr.value ** 2 if isinstance(self.rr.uncertainty, Decimal): self.depth.uncertainty = self.rr.uncertainty * 2 if isinstance(self.rr.uncertainty_upper, Decimal): self.depth.uncertainty_upper = self.rr.uncertainty_upper * 2 self.depth.reference = "Calculated from Rp/R*" self.depth.url = self.rr.reference # If the orbital eccentricity value is 0 and a TT value is provided, # use the same values for T0 as well. if self.ecc.value == Decimal(0) and is_empty(self.om.value): self.om.value = Decimal(90) self.om.reference = "Set to 90 deg with ecc~0" print("set omega to 90") if is_valid(self.tt.value): print("copying TT to T0") self.t0.copy_values(self.tt) # OM may already be set to 90. elif self.ecc.value == 0 and self.om.value == 90: if str(self.tt.value) != "NaN": print("copying TT to T0") self.t0.copy_values(self.tt) # Set the FREEZE_ECC flag if ECC=0 and no uncertainty is provided. if self.ecc.value == 0 and is_empty(self.ecc.uncertainty): self.freeze_ecc.value = 1 # Set the MULT flag if NCOMP is more than 1 planet. if self.ncomp.value > 1: self.mult.value = 1 # Set the TREND flag if a DVDT value is provided. if not is_empty(self.dvdt.value): self.trend.value = 1 # Exclude planets with period uncertainty >10%. self.per.check_constrained(0.1) if not self.per.well_constrained: self.exclude() warnings.append("<uncertain PER>") # Warn of planets with K speeds <2 m/s. if is_valid(self.k.value): if self.k.value < 2: # self.exclude() warnings.append("<low K value>") # Make sure RA string uses spaces. if not is_empty(self.ra_string.value): if "h" in self.ra_string.value: new_value = self.ra_string.value.replace("h", " ") new_value = new_value.replace("m", " ") new_value = new_value.replace("s", "") self.ra_string.value = new_value # Make sure DEC string uses spaces. if not is_empty(self.dec_string.value): if "d" in self.dec_string.value: new_value = self.dec_string.value.replace("d", " ") new_value = new_value.replace("m", " ") new_value = new_value.replace("s", "") self.dec_string.value = new_value # Display warnings generated by final adjustments. if len(warnings) > 0: print("<<<{0} GOT {1} WARNING(S)>>>".format(self.name.value, len(warnings) ) ) [print(x) for x in warnings]
def save_to_pln(self, name=None, dir=None, pref=None, disp=True): """ Save an ExoPlanet object into a .pln text file. A .pln template is needed here to recreate the sections of the text file and where to write each parameter. :param name: The file name for the resulting .pln file. This will be automatically set to the exoplanet name if not provided, but the user may supply a different name for testing. :type name: str :param gui: Flag used to alter the filename if this file is being generated by the GUI. :type gui: bool """ # If name is not provided, use the NAME field value + .pln. if not name: name = ".".join([self.name.value, "pln"]) # If the gui flag is set, prepend a 'gen_' on the filename to specify # this was generated by the GUI. if pref: name = "_".join([pref, name]) # If dir is provided add this directory path to the filename. if dir: name = os.path.join(dir, name) home = os.getcwd() name = os.path.join(home, name) self.pln_filename = name if os.path.isfile(self.pln_filename): os.remove(self.pln_filename) with open(self.pln_filename, 'w') as new_pln: if disp: print("writing to {0}".format(self.pln_filename)) # Use the pln_template dictionary to create the file sections and # look up the names that may be in the parameters dictionary. for section, fields in self.pln_template.items(): # Recreate the section header for each new section. comment = "#* " separator = "=" * 48 new_pln.write("".join([comment, separator, "\n"])) new_pln.write("".join([comment, section, "\n"])) new_pln.write("".join([comment, separator, "\n"])) # Look for attributes that match the field names defined in # the .pln template. for f in fields: try: exo_param = getattr(self, f.lower()) except AttributeError: continue # exo_param is now an ExoParameter object. Add the # keyword & value pair to the .pln file. if is_empty(exo_param.value): exo_param.value = exo_param.default self._write_pln_line(new_pln, f, exo_param.value) # Add additional keywords for uncertainties and references # if they are present in the current ExoParameter. if exo_param.uncertainty: uf = "".join(["U", f]) self._write_pln_line(new_pln, uf, exo_param.uncertainty ) if exo_param.uncertainty_upper: ufd = "".join(["U", f, "D"]) self._write_pln_line(new_pln, ufd, exo_param.uncertainty_upper ) if exo_param.reference: fref = "".join([f, "REF"]) self._write_pln_line(new_pln, fref, exo_param.reference ) if exo_param.url: furl = "".join([f, "URL"]) self._write_pln_line(new_pln, furl, exo_param.url )
def save_to_pln(self, name=None, dir=None, pref=None, disp=True): """ Save an ExoPlanet object into a .pln text file. A .pln template is needed here to recreate the sections of the text file and where to write each parameter. :param name: The file name for the resulting .pln file. This will be automatically set to the exoplanet name if not provided, but the user may supply a different name for testing. :type name: str :param gui: Flag used to alter the filename if this file is being generated by the GUI. :type gui: bool """ # If name is not provided, use the NAME field value + .pln. if not name: name = ".".join([self.name.value, "pln"]) # If the gui flag is set, prepend a 'gen_' on the filename to specify # this was generated by the GUI. if pref: name = "_".join([pref, name]) # If dir is provided add this directory path to the filename. if dir: name = os.path.join(dir, name) home = os.getcwd() name = os.path.join(home, name) self.pln_filename = name if os.path.isfile(self.pln_filename): os.remove(self.pln_filename) with open(self.pln_filename, 'w') as new_pln: if disp: print("writing to {0}".format(self.pln_filename)) # Use the pln_template dictionary to create the file sections and # look up the names that may be in the parameters dictionary. for section, fields in self.pln_template.items(): # Recreate the section header for each new section. comment = "#* " separator = "=" * 48 new_pln.write("".join([comment, separator, "\n"])) new_pln.write("".join([comment, section, "\n"])) new_pln.write("".join([comment, separator, "\n"])) # Look for attributes that match the field names defined in # the .pln template. for f in fields: try: exo_param = getattr(self, f.lower()) except AttributeError: continue # exo_param is now an ExoParameter object. Add the # keyword & value pair to the .pln file. if is_empty(exo_param.value): exo_param.value = exo_param.default self._write_pln_line(new_pln, f, exo_param.value) # Add additional keywords for uncertainties and references # if they are present in the current ExoParameter. if exo_param.uncertainty: uf = "".join(["U", f]) self._write_pln_line(new_pln, uf, exo_param.uncertainty) if exo_param.uncertainty_upper: ufd = "".join(["U", f, "D"]) self._write_pln_line(new_pln, ufd, exo_param.uncertainty_upper) if exo_param.reference: fref = "".join([f, "REF"]) self._write_pln_line(new_pln, fref, exo_param.reference) if exo_param.url: furl = "".join([f, "URL"]) self._write_pln_line(new_pln, furl, exo_param.url)
def verify_pln(self): """ Some last-second tweaks are needed for proper .pln file formatting. """ warnings = [] self._populate_uncertainties() # The transitref and transiturl actually end up stored in the 'transit' # ExoParam due to the ref and url splits. Pull these out and set the # transit entries to the proper pointers. if self.transit.value == 1: if is_empty(self.transit.reference): self.transit.reference = "__TRANSITREF" if is_empty(self.transit.url): self.transit.url = "__TRANSITURL" # If the transit depth is not provided, but an Rp/R* ratio is, # calculate the depth value. if is_empty(self.depth.value) and is_valid(self.rr.value): self.depth.value = self.rr.value**2 if isinstance(self.rr.uncertainty, Decimal): self.depth.uncertainty = self.rr.uncertainty * 2 if isinstance(self.rr.uncertainty_upper, Decimal): self.depth.uncertainty_upper = self.rr.uncertainty_upper * 2 self.depth.reference = "Calculated from Rp/R*" self.depth.url = self.rr.reference # If the orbital eccentricity value is 0 and a TT value is provided, # use the same values for T0 as well. if self.ecc.value == Decimal(0) and is_empty(self.om.value): self.om.value = Decimal(90) self.om.reference = "Set to 90 deg with ecc~0" print("set omega to 90") if is_valid(self.tt.value): print("copying TT to T0") self.t0.copy_values(self.tt) # OM may already be set to 90. elif self.ecc.value == 0 and self.om.value == 90: if str(self.tt.value) != "NaN": print("copying TT to T0") self.t0.copy_values(self.tt) # Set the FREEZE_ECC flag if ECC=0 and no uncertainty is provided. if self.ecc.value == 0 and is_empty(self.ecc.uncertainty): self.freeze_ecc.value = 1 # Set the MULT flag if NCOMP is more than 1 planet. if self.ncomp.value > 1: self.mult.value = 1 # Set the TREND flag if a DVDT value is provided. if not is_empty(self.dvdt.value): self.trend.value = 1 # Exclude planets with period uncertainty >10%. self.per.check_constrained(0.1) if not self.per.well_constrained: self.exclude() warnings.append("<uncertain PER>") # Warn of planets with K speeds <2 m/s. if is_valid(self.k.value): if self.k.value < 2: # self.exclude() warnings.append("<low K value>") # Make sure RA string uses spaces. if not is_empty(self.ra_string.value): if "h" in self.ra_string.value: new_value = self.ra_string.value.replace("h", " ") new_value = new_value.replace("m", " ") new_value = new_value.replace("s", "") self.ra_string.value = new_value # Make sure DEC string uses spaces. if not is_empty(self.dec_string.value): if "d" in self.dec_string.value: new_value = self.dec_string.value.replace("d", " ") new_value = new_value.replace("m", " ") new_value = new_value.replace("s", "") self.dec_string.value = new_value # Display warnings generated by final adjustments. if len(warnings) > 0: print("<<<{0} GOT {1} WARNING(S)>>>".format( self.name.value, len(warnings))) [print(x) for x in warnings]