def _format_abundances(elemental_abundances=None, subtract_solar=False, subtract_metallicity=0): if elemental_abundances is None: return ("0 1", 1) # First-pass to check the abundances and get the number of syntheses. elemental_abundances = elemental_abundances.copy() # Make sure that the abundances are specified as (atomic_number: abundance) for key in list(elemental_abundances.keys()): if isinstance(key, string_types): # It's an element. Convert to atomic number. atomic_number = element_to_atomic_number(key) if atomic_number in elemental_abundances.keys(): raise ValueError( "element {} has abundances specified by element and species"\ .format(key)) elemental_abundances[atomic_number] = elemental_abundances.pop(key) sorted_atomic_numbers, max_synth = sorted( elemental_abundances.keys()), None for atomic_number in sorted_atomic_numbers: abundance = elemental_abundances[atomic_number] try: abundance[0] except (IndexError, TypeError): abundance = [abundance] abundance = np.array(abundance).flatten().copy() # Subtract any compositions. abundance -= subtract_metallicity if subtract_solar: abundance -= solar_composition(atomic_number) elemental_abundances[atomic_number] = abundance if max_synth is None or len(abundance) > max_synth: max_synth = len(abundance) if len(abundance) > 1 and len(abundance) != max_synth: raise ValueError("abundance entries must be fully described " "or have one abundance per element (Z = {})"\ .format(atomic_number)) assert 5 >= max_synth str_format = ["{0} {1}".format(len(sorted_atomic_numbers), max_synth)] for atomic_number in sorted_atomic_numbers: abundance = elemental_abundances[atomic_number] if len(abundance) == 1 and max_synth > 1: abundance = list(abundance) * max_synth _ = " {0:3.0f} ".format(atomic_number) str_format.append(_ + " ".join( ["{0:8.3f}".format(a) for a in np.array(abundance).flatten()])) return ("\n".join(str_format), max_synth)
def _initial_guess(self, spectrum, **kwargs): """ Return an initial guess about the model parameters. :param spectrum: The observed spectrum. """ # Potential parameters: # elemental abundances, continuum coefficients, smoothing kernel, # velocity offset defaults = { "sigma_smooth": self.metadata["manual_sigma_smooth"], #0.1, "vrad": self.metadata["manual_rv"] } p0 = [] for parameter in self.parameter_names: default = defaults.get(parameter, None) if default is not None: p0.append(default) elif parameter.startswith("log_eps"): # The elemental abundances are in log_epsilon format. # If the value was already fit, we use that as the initial guess # Otherwise, we assume a scaled-solar initial value based on the stellar [M/H] # Elemental abundance. element = parameter.split("(")[1].rstrip(")") # Assume scaled-solar composition. default_value = solar_composition(element) + \ self.session.metadata["stellar_parameters"]["metallicity"] try: fitted_result = self.metadata["fitted_result"] except KeyError: p0.append(default_value) else: value = fitted_result[0][parameter] if np.isnan(value): p0.append(default_value) else: p0.append(value) elif parameter.startswith("c"): # Continuum coefficient. p0.append((0, 1)[parameter == "c0"]) else: raise ParallelUniverse("this should never happen") return np.array(p0)
def minimisation_function(stellar_parameters, *args): """The function we want to minimise (e.g., calculates the quadrature sum of all minimizable variables.) stellar_parameters : [teff, vt, logg, feh] """ params_to_optimize, all_sampled_points, total_tolerance, individual_tolerances, use_nlte_grid = args # Old way: #teff, vt, logg, feh = [initial_guess[0], stellar_parameters[0], initial_guess[2], stellar_parameters[1]] # First set all to initial_guess, then replace the ones with a "True" value params_list = initial_guess next_param = 0 for pi, p in enumerate(params_to_optimize): if p == True: params_list[pi] = stellar_parameters[next_param] next_param += 1 teff, vt, logg, feh = params_list photosphere = photosphere_interpolator(teff, logg, feh) photosphere.meta["stellar_parameters"]["microturbulence"] = vt ## TODO: ADJUST ABUNDANCES TO ASPLUND? abundances = rt.abundance_cog(photosphere, transitions, twd=twd) transitions["abundance"] = abundances out = utils.equilibrium_state(transitions[idx_I], ("expot", "reduced_equivalent_width")) dAdchi = out[26.0]['expot'][0] dAdREW = out[26.0]['reduced_equivalent_width'][0] dFe = np.mean(abundances[idx_I]) - np.mean(abundances[idx_II]) # E. Holmbeck changed dM to be w.r.t. FeII abundances. dM = np.mean(abundances[idx_II]) - (feh + solar_composition("Fe")) results = np.array([dAdchi, dAdREW, 0.1 * dFe, 0.1 * dM]) acquired_total_tolerance = np.sum(results**2) point = list(np.array([teff, vt, logg, feh]) * params_to_optimize) + list(results * params_to_optimize) all_sampled_points.append(point) logger.info( "Atmosphere with Teff = {0:.0f} K, vt = {1:.2f} km/s, logg = {2:.2f}, [Fe/H] = {3:.2f}, [alpha/Fe] = {4:.2f}" " yields sum {5:.1e}:\n\t\t\t[{6:.1e}, {7:.1e}, {8:.1e}, {9:.1e}]". format(teff, vt, logg, feh, 0.4, acquired_total_tolerance, *results)) return results[params_to_optimize]
def minimisation_function(stellar_parameters, *args): """The function we want to minimise (e.g., calculates the quadrature sum of all minimizable variables.) stellar_parameters : [teff, logg, vt, feh] """ teff, logg, vt, feh = stellar_parameters sampled_points, total_tolerance, individual_tolerances, use_nlte_grid = args if teff < parameter_ranges["teff"][0] or teff > parameter_ranges["teff"][1] or \ logg < parameter_ranges["logg"][0] or logg > parameter_ranges["logg"][1] or \ vt < parameter_ranges["vt"][0] or vt > parameter_ranges["vt"][1] or \ feh < parameter_ranges["[Fe/H]"][0] or feh > parameter_ranges["[Fe/H]"][1] or \ np.isnan(teff) or np.isnan(logg) or np.isnan(vt) or np.isnan(feh): #return np.array([np.nan, np.nan, np.nan, np.nan]) return np.array([np.inf, np.inf, np.inf, np.inf]) photosphere = photosphere_interpolator(teff, logg, feh, alphafe) photosphere.meta["stellar_parameters"]["microturbulence"] = vt abundances = rt.abundance_cog(photosphere, transitions, twd=twd) transitions["abundance"] = abundances ## Calculate slopes and differences that are being minimized x, y = transitions[idx_expot]["expot"], abundances[idx_expot] dAdchi, b_expot, medy, stdy, stdm_expot, N = utils.fit_line(x, y, None) x, y = transitions[idx_rew]["reduced_equivalent_width"], abundances[ idx_rew] dAdREW, b_rew, medy, stdy, stdm_rew, N = utils.fit_line(x, y, None) dFe = np.mean(abundances[idx_I]) - np.mean(abundances[idx_II]) dM = np.mean(abundances[idx_MH]) - (feh + solar_composition("Fe")) # Some metric has been imposed here. Could make it depend on the slope standard error, but that seems not stable. results = np.array([dAdchi, dAdREW, 0.1 * dFe, 0.1 * dM]) acquired_total_tolerance = np.sum(results**2) point = [teff, logg, vt, feh] + list(results) sampled_points.append(point) acquired_total_tolerance = np.sum(results**2) logger.debug( "Atmosphere with Teff = {0:.0f} K, logg = {2:.2f}, vt = {1:.2f} km/s, [Fe/H] = {3:.2f}, [alpha/Fe] = {4:.2f}" " yields sum {5:.1e}:\n\t\t\t[{6:.1e}, {7:.1e}, {8:.1e}, {9:.1e}]". format(teff, logg, vt, feh, alphafe, acquired_total_tolerance, *results)) return results
def minimisation_function(stellar_parameters, *args): """The function we want to minimise (e.g., calculates the quadrature sum of all minimizable variables.) stellar_parameters : [teff, vt, logg, feh] """ ## "Global" vars: transitions, idx_I, idx_II, photosphere_interpolator teff, vt, logg, feh = stellar_parameters #if teff < parameter_ranges["teff"][0] or teff > parameter_ranges["teff"][1] or \ # vt < parameter_ranges["vt"][0] or vt > parameter_ranges["vt"][1] or \ # logg < parameter_ranges["logg"][0] or logg > parameter_ranges["logg"][1] or \ # feh < parameter_ranges["[Fe/H]"][0] or feh > parameter_ranges["[Fe/H]"][1]: # return np.array([np.nan, np.nan, np.nan, np.nan]) all_sampled_points, total_tolerance, individual_tolerances, use_nlte_grid = args #if not (5 > vt > 0): # return np.array([np.nan, np.nan, np.nan, np.nan]) photosphere = photosphere_interpolator(teff, logg, feh) photosphere.meta["stellar_parameters"]["microturbulence"] = vt ## TODO: ADJUST ABUNDANCES TO ASPLUND? abundances = rt.abundance_cog(photosphere, transitions, twd=twd) transitions["abundance"] = abundances ## Calculate slopes and differences that are being minimized out = utils.equilibrium_state(transitions[idx_I], ("expot", "reduced_equivalent_width")) dAdchi = out[26.0]['expot'][0] dAdREW = out[26.0]['reduced_equivalent_width'][0] dFe = np.mean(abundances[idx_I]) - np.mean(abundances[idx_II]) dM = np.mean(abundances[idx_I]) - (feh + solar_composition("Fe")) results = np.array([dAdchi, dAdREW, 0.1 * dFe, 0.1 * dM]) acquired_total_tolerance = np.sum(results**2) point = [teff, vt, logg, feh] + list(results) all_sampled_points.append(point) logger.info( "Atmosphere with Teff = {0:.0f} K, vt = {1:.2f} km/s, logg = {2:.2f}, [Fe/H] = {3:.2f}, [alpha/Fe] = {4:.2f}" " yields sum {5:.1e}:\n\t\t\t[{6:.1e}, {7:.1e}, {8:.1e}, {9:.1e}]". format(teff, vt, logg, feh, 0.4, acquired_total_tolerance, *results)) return results
def update_stellar_parameter_state_table(self): """ Update the text labels """ ## Note: this uses self.measurement_model to get a good list of measurements. ## So, have to call measurement_model.reset() before doing this ## Note: I have scrapped the Ti I/II in favor of hardcoding. ## Get data # species, abundance, expot, rew acceptable = self.measurement_model.get_data_column("is_acceptable") not_upper_limit = np.logical_not( self.measurement_model.get_data_column("is_upper_limit")) species = self.measurement_model.get_data_column("species") abundance = self.measurement_model.get_data_column("abundances") expot = self.measurement_model.get_data_column("expot") rew = self.measurement_model.get_data_column( "reduced_equivalent_width") ii1 = acceptable & (not_upper_limit) & (np.round(species, 1) == 26.0) ii2 = acceptable & (not_upper_limit) & (np.round(species, 1) == 26.1) chi1, eps1, REW1 = expot[ii1], abundance[ii1], rew[ii1] chi2, eps2, REW2 = expot[ii2], abundance[ii2], rew[ii2] finite = np.isfinite(chi1 * eps1 * REW1) chi1, eps1, REW1 = chi1[finite], eps1[finite], REW1[finite] finite = np.isfinite(chi2 * eps2 * REW2) chi2, eps2, REW2 = chi2[finite], eps2[finite], REW2[finite] ## Fit lines try: mchi1, bchi1, med1, eXH1, emchi1, N1 = utils.fit_line(chi1, eps1) except Exception as e: logger.debug(e) mchi1, bchi1, med1, eXH1, emchi1, N1 = np.nan, np.nan, np.nan, np.nan, np.nan, len( eps1) try: mREW1, bREW1, med1, eXH1, emREW1, N1 = utils.fit_line(REW1, eps1) except Exception as e: logger.debug(e) mREW1, bREW1, med1, eXH1, emREW1, N1 = np.nan, np.nan, np.nan, np.nan, np.nan, len( eps1) try: mchi2, bchi2, med2, eXH2, emchi2, N2 = utils.fit_line(chi2, eps2) except Exception as e: logger.debug(e) mchi2, bchi2, med2, eXH2, emchi2, N2 = np.nan, np.nan, np.nan, np.nan, np.nan, len( eps2) try: mREW2, bREW2, med2, eXH2, emREW2, N2 = utils.fit_line(REW2, eps2) except Exception as e: logger.debug(e) mREW2, bREW2, med2, eXH2, emREW2, N2 = np.nan, np.nan, np.nan, np.nan, np.nan, len( eps2) ## Update table XH1 = med1 - solar_composition(26.0) XH2 = med2 - solar_composition(26.1) self.state_fe1_N.setText(u"Fe I ({})".format(N1)) self.state_fe1_XH.setText(u"{:.2f} ± {:.2f}".format(XH1, eXH1)) self.state_fe1_dAdchi.setText(u"{:.3f} ± {:.3f}".format(mchi1, emchi1)) self.state_fe1_dAdREW.setText(u"{:.3f} ± {:.3f}".format(mREW1, emREW1)) self.state_fe2_N.setText(u"Fe II ({})".format(N2)) self.state_fe2_XH.setText(u"{:.2f} ± {:.2f}".format(XH2, eXH2)) self.state_fe2_dAdchi.setText(u"{:.3f} ± {:.3f}".format(mchi2, emchi2)) self.state_fe2_dAdREW.setText(u"{:.3f} ± {:.3f}".format(mREW2, emREW2)) return None
def abundances_to_solar(self): """ Return [X/H] if fit, else None """ abunds = self.abundances if abunds is None: return None elems = self.elements return [AX - solar_composition(X) for AX, X in zip(abunds, elems)]