def fit_tex(eupper, nupperoverg, verbose=False, plot=False): """ Fit the Boltzmann diagram """ model = modeling.models.Linear1D() #fitter = modeling.fitting.LevMarLSQFitter() fitter = modeling.fitting.LinearLSQFitter() result = fitter(model, eupper, np.log(nupperoverg)) tex = -1. / result.slope * u.K partition_func = specmodel.calculate_partitionfunction( hnco.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot))) if plot: import pylab as pl L, = pl.plot(eupper, np.log10(nupperoverg), 'o') xax = np.array([0, eupper.max().value]) line = (xax * result.slope.value + result.intercept.value) pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(), label='$T={0:0.1f} \log(N)={1:0.1f}$'.format( tex, np.log10(Ntot.value))) return Ntot, tex, result.slope, result.intercept
def sio_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex, 'unit'): tex = tex.value if hasattr(tbg, 'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value if background is not None: tbg = background # assume equal-width channels kwargs = dict(rest=ref_freq) equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz, equiv) - xarr[0].to(u.Hz, equiv)).value velo = xarr.to(u.km / u.s, equiv).value mol_model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = m.calculate_partitionfunction(result.data['States'], temperature=tex)[sio.Id] jnu_bg = lte_molecule.Jnu_cgs(xarr.to(u.Hz).value, tbg) bg_model = np.ones_like(xarr).value * jnu_bg for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) s = np.exp(-(velo - vcen - voff)**2 / (2 * width**2)) * tau_per_dnu / channelwidth jnu_mol = lte_molecule.Jnu_cgs(nu, tex) # the "emission" model is generally zero everywhere, so we can just # add to it as we move along mol_model = mol_model + jnu_mol * (1 - np.exp(-s)) # background is assumed to start as a uniform value, then each # absorption line multiplies to reduce it. s is zero for most velocities, # so this is mostly bg_model *= 1 bg_model *= np.exp(-s) if background: # subtract jnu_bg because we *must* rezero for the case of # having multiple components, otherwise the backgrounds add, # which is nonsense model = bg_model + mol_model - jnu_bg else: model = mol_model return model
def pyspeckitfit(eupper, kkms, frequencies, degeneracies, einsteinAs, verbose=False, plot=False, guess=(150, 1e19)): """ Fit the Boltzmann diagram (but do it right, with no approximations, just direct forward modeling) This doesn't seem to work, though: it can't reproduce its own output """ bandwidth = (1 * u.km / u.s / constants.c) * (frequencies) def model(tex, col, einsteinAs=einsteinAs, eupper=eupper): tex = u.Quantity(tex, u.K) col = u.Quantity(col, u.cm ** -2) eupper = eupper.to(u.erg, u.temperature_energy()) einsteinAs = u.Quantity(einsteinAs, u.Hz) partition_func = specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] return lte_molecule.line_brightness( tex, bandwidth, frequencies, total_column=col, partition_function=Q_rot, degeneracy=degeneracies, energy_upper=eupper, einstein_A=einsteinAs, ) def minmdl(args): tex, col = args return ((model(tex, col).value - kkms) ** 2).sum() from scipy import optimize result = optimize.minimize(minmdl, guess, method="Nelder-Mead") tex, Ntot = result.x if plot: import pylab as pl pl.subplot(2, 1, 1) pl.plot(eupper, kkms, "o") order = np.argsort(eupper) pl.plot(eupper[order], model(tex, Ntot)[order]) pl.subplot(2, 1, 2) xax = np.array([0, eupper.max().value]) pl.plot(eupper, np.log(nupper_of_kkms(kkms, frequencies, einsteinAs, degeneracies).value), "o") partition_func = specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] intercept = np.log(Ntot) - np.log(Q_rot) pl.plot( xax, np.log(xax * tex + intercept), "-", label="$T={0:0.1f} \log(N)={1:0.1f}$".format(tex, np.log10(Ntot)) ) return Ntot, tex, result
def ntot_of_nupper(nupper, eupper, tex, degen=1): partition_func = specmodel.calculate_partitionfunction( vamdc_result.data['States'], temperature=tex.value)[sio.Id] Q_rot = partition_func Ntot = nupper * (Q_rot / degen) * np.exp(eupper / (constants.k_B * tex)) return Ntot
def fit_tex(eupper, nupperoverg, errors=None, verbose=False, plot=False): """ Fit the Boltzmann diagram """ model = modeling.models.Linear1D() #fitter = modeling.fitting.LevMarLSQFitter() fitter = modeling.fitting.LinearLSQFitter() # ignore negatives good = nupperoverg > 0 if good.sum() < len(nupperoverg) / 2.: return 0 * u.cm**-2, 0 * u.K, 0, 0 if errors is not None: weights = 1 / errors**2 else: weights = None result = fitter(model, eupper[good], np.log(nupperoverg[good]), weights=weights[good]) tex = -1. / result.slope * u.K assert not np.isnan(tex) partition_func = specmodel.calculate_partitionfunction( vamdc_result.data['States'], temperature=tex.value)[ch3cn.Id] Q_rot = partition_func Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot))) if plot: import pylab as pl if errors is not None: L, _, _ = pl.errorbar( x=eupper.value, y=np.log10(nupperoverg), marker='o', linestyle='none', yerr=np.array([ np.log10(nupperoverg) - np.log10(nupperoverg - errors), np.log10(nupperoverg + errors) - np.log10(nupperoverg) ])) else: L, = pl.plot(eupper, np.log10(nupperoverg), 'o') xax = np.array([0, eupper.max().value]) line = (xax * result.slope.value + result.intercept.value) pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(), label='$T={0:0.1f} \log(N)={1:0.1f}$'.format( tex, np.log10(Ntot.value))) return Ntot, tex, result.slope, result.intercept
def pyspeckitfit(eupper, kkms, frequencies, degeneracies, einsteinAs, verbose=False, plot=False, guess=(150,1e19)): """ Fit the Boltzmann diagram (but do it right, with no approximations, just direct forward modeling) This doesn't seem to work, though: it can't reproduce its own output """ bandwidth = (1*u.km/u.s/constants.c)*(frequencies) def model(tex, col, einsteinAs=einsteinAs, eupper=eupper): tex = u.Quantity(tex, u.K) col = u.Quantity(col, u.cm**-2) eupper = eupper.to(u.erg, u.temperature_energy()) einsteinAs = u.Quantity(einsteinAs, u.Hz) partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] return lte_molecule.line_brightness(tex, bandwidth, frequencies, total_column=col, partition_function=Q_rot, degeneracy=degeneracies, energy_upper=eupper, einstein_A=einsteinAs) def minmdl(args): tex, col = args return ((model(tex, col).value - kkms)**2).sum() from scipy import optimize result = optimize.minimize(minmdl, guess, method='Nelder-Mead') tex, Ntot = result.x if plot: import pylab as pl pl.subplot(2,1,1) pl.plot(eupper, kkms, 'o') order = np.argsort(eupper) pl.plot(eupper[order], model(tex, Ntot)[order]) pl.subplot(2,1,2) xax = np.array([0, eupper.max().value]) pl.plot(eupper, np.log(nupper_of_kkms(kkms, frequencies, einsteinAs, degeneracies).value), 'o') partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'], temperature=tex) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] intercept = np.log(Ntot) - np.log(Q_rot) pl.plot(xax, np.log(xax*tex + intercept), '-', label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(tex, np.log10(Ntot))) return Ntot, tex, result
def ch3oh_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex,'unit'): tex = tex.value if hasattr(tbg,'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value ckms = constants.c.to(u.km/u.s).value # assume equal-width channels #kwargs = dict(rest=ref_freq) #equiv = u.doppler_radio(**kwargs) #channelwidth = np.abs(xarr[1].to(u.Hz, ) - xarr[0].to(u.Hz, )).value #channelwidth = xarr.as_unit(u.Hz).dxarr #velo = xarr.to(u.km/u.s, equiv).value freq = xarr.to(u.Hz).value # same unit as nu below model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = specmodel.calculate_partitionfunction(result.data['States'], temperature=tex)[ch3oh.Id] for A, g, nu, eu in zip(aij, deg, freqs_, EU): taudnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) width_dnu = width / ckms * nu effective_linewidth_dnu = (2 * np.pi)**0.5 * width_dnu fcen = (1 - vcen/ckms) * nu tauspec = (np.exp(-(freq - fcen)**2 / (2 * width_dnu**2)) * taudnu/effective_linewidth_dnu) jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg)) model = model + jnu*(1-np.exp(-tauspec)) if background is not None: return background-model return model
def model(tex, col, einsteinAs=einsteinAs, eupper=eupper): tex = u.Quantity(tex, u.K) col = u.Quantity(col, u.cm**-2) eupper = eupper.to(u.erg, u.temperature_energy()) einsteinAs = u.Quantity(einsteinAs, u.Hz) partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] return lte_molecule.line_brightness(tex, bandwidth, frequencies, total_column=col, partition_function=Q_rot, degeneracy=degeneracies, energy_upper=eupper, einstein_A=einsteinAs)
def model(tex, col, einsteinAs=einsteinAs, eupper=eupper): tex = u.Quantity(tex, u.K) col = u.Quantity(col, u.cm**-2) eupper = eupper.to(u.erg, u.temperature_energy()) einsteinAs = u.Quantity(einsteinAs, u.Hz) partition_func = specmodel.calculate_partitionfunction(hnco.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] return lte_molecule.line_brightness(tex, bandwidth, frequencies, total_column=col, partition_function=Q_rot, degeneracy=degeneracies, energy_upper=eupper, einstein_A=einsteinAs)
def hnco_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex,'unit'): tex = tex.value if hasattr(tbg,'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value ckms = constants.c.to(u.km/u.s).value # assume equal-width channels #kwargs = dict(rest=ref_freq) #equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz, ) - xarr[0].to(u.Hz, )).value #velo = xarr.to(u.km/u.s, equiv).value freq = xarr.to(u.Hz).value # same unit as nu below model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = specmodel.calculate_partitionfunction(result.data['States'], temperature=tex)[hnco.Id] for A, g, nu, eu in zip(aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) width_dnu = width / ckms * nu s = np.exp(-(freq-(1-vcen/ckms)*nu)**2/(2*width_dnu**2))*tau_per_dnu/channelwidth jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg)) model = model + jnu*(1-np.exp(-s)) if background is not None: return background-model return model
def fit_tex(eupper, nupperoverg, errors=None, verbose=False, plot=False): """ Fit the Boltzmann diagram """ model = modeling.models.Linear1D() #fitter = modeling.fitting.LevMarLSQFitter() fitter = modeling.fitting.LinearLSQFitter() # ignore negatives good = nupperoverg > 0 if good.sum() < len(nupperoverg)/2.: return 0*u.cm**-2, 0*u.K, 0, 0 if errors is not None: weights = 1/errors**2 else: weights=None result = fitter(model, eupper[good], np.log(nupperoverg[good]), weights=weights[good]) tex = -1./result.slope*u.K assert not np.isnan(tex) partition_func = specmodel.calculate_partitionfunction(vamdc_result.data['States'], temperature=tex.value)[ch3cn.Id] Q_rot = partition_func Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, Q_rot={2}".format(tex, Ntot, Q_rot))) if plot: import pylab as pl if errors is not None: L,_,_ = pl.errorbar(x=eupper.value, y=np.log10(nupperoverg), marker='o', linestyle='none', yerr=np.array([np.log10(nupperoverg)-np.log10(nupperoverg-errors), np.log10(nupperoverg+errors)-np.log10(nupperoverg)])) else: L, = pl.plot(eupper, np.log10(nupperoverg), 'o') xax = np.array([0, eupper.max().value]) line = (xax*result.slope.value + result.intercept.value) pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(), label='$T={0:0.1f} \log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value))) return Ntot, tex, result.slope, result.intercept
def ch3cn_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex,'unit'): tex = tex.value if hasattr(tbg,'unit'): tbg = tbg.value if hasattr(column, 'unit'): column = column.value if column < 25: column = 10**column if hasattr(vcen, 'unit'): vcen = vcen.value if hasattr(width, 'unit'): width = width.value # assume equal-width channels kwargs = dict(rest=ref_freq) equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz, equiv) - xarr[0].to(u.Hz, equiv)).value velo = xarr.to(u.km/u.s, equiv).value model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = m.calculate_partitionfunction(result.data['States'], temperature=tex)[ch3cn.Id] for voff, A, g, nu, eu in zip(vdiff, aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10**A) s = np.exp(-(velo-vcen-voff)**2/(2*width**2))*tau_per_dnu/channelwidth jnu = (lte_molecule.Jnu_cgs(nu, tex)-lte_molecule.Jnu_cgs(nu, tbg)) model = model + jnu*(1-np.exp(-s)) if background is not None: return background-model return model
def hnco_model(xarr, vcen, width, tex, column, background=None, tbg=2.73): if hasattr(tex, "unit"): tex = tex.value if hasattr(tbg, "unit"): tbg = tbg.value if hasattr(column, "unit"): column = column.value if column < 25: column = 10 ** column if hasattr(vcen, "unit"): vcen = vcen.value if hasattr(width, "unit"): width = width.value ckms = constants.c.to(u.km / u.s).value # assume equal-width channels # kwargs = dict(rest=ref_freq) # equiv = u.doppler_radio(**kwargs) channelwidth = np.abs(xarr[1].to(u.Hz) - xarr[0].to(u.Hz)).value # velo = xarr.to(u.km/u.s, equiv).value freq = xarr.to(u.Hz).value # same unit as nu below model = np.zeros_like(xarr).value freqs_ = freqs.to(u.Hz).value Q = specmodel.calculate_partitionfunction(result.data["States"], temperature=tex)[hnco.Id] for A, g, nu, eu in zip(aij, deg, freqs_, EU): tau_per_dnu = lte_molecule.line_tau_cgs(tex, column, Q, g, nu, eu, 10 ** A) width_dnu = width / ckms * nu s = np.exp(-(freq - (1 - vcen / ckms) * nu) ** 2 / (2 * width_dnu ** 2)) * tau_per_dnu / channelwidth jnu = lte_molecule.Jnu_cgs(nu, tex) - lte_molecule.Jnu_cgs(nu, tbg) model = model + jnu * (1 - np.exp(-s)) if background is not None: return background - model return model
def fit_tex(eupper, nupperoverg, verbose=False, plot=False, uplims=None, errors=None, min_nupper=1, replace_errors_with_uplims=False, max_uplims='half'): """ Fit the Boltzmann diagram Parameters ---------- max_uplims: str or number The maximum number of upper limits before the fit is ignored completely and instead zeros are returned """ model = modeling.models.Linear1D() #fitter = modeling.fitting.LevMarLSQFitter() fitter = modeling.fitting.LinearLSQFitter() nupperoverg_tofit = nupperoverg.copy() if uplims is not None: upperlim_mask = nupperoverg < uplims # allow this magical keyword 'half' max_uplims = len(nupperoverg)/2. if max_uplims == 'half' else max_uplims if upperlim_mask.sum() > max_uplims: # too many upper limits = bad idea to fit. return 0*u.cm**-2, 0*u.K, 0, 0 if errors is None: # if errors are not specified, we set the upper limits as actual values # (which gives a somewhat useful upper limit on the temperature) nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask] else: # otherwise, we set the values to zero-column and set the errors to # be whatever the upper limits are (hopefully a 1-sigma upper # limit) # 1.0 here becomes 0.0 in log and makes the relative errors meaningful nupperoverg_tofit[upperlim_mask] = 1.0 if replace_errors_with_uplims: errors[upperlim_mask] = uplims[upperlim_mask] # always ignore negatives & really low values good = nupperoverg_tofit > min_nupper # skip any fits that have fewer than 50% good values if good.sum() < len(nupperoverg_tofit)/2.: return 0*u.cm**-2, 0*u.K, 0, 0 if errors is not None: rel_errors = errors / nupperoverg_tofit weights = 1. / rel_errors**2 log.debug("Fitting with data = {0}, weights = {1}, errors = {2}," "relative_errors = {3}" .format(np.log(nupperoverg_tofit[good]), np.log(weights[good]), errors[good], rel_errors[good], )) else: # want log(weight) = 1 weights = np.exp(np.ones_like(nupperoverg_tofit)) result = fitter(model, eupper[good], np.log(nupperoverg_tofit[good]), weights=np.log(weights[good])) tex = -1./result.slope*u.K partition_func = specmodel.calculate_partitionfunction(hnco.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, Q_rot={2}, nuplim={3}".format(tex, Ntot, Q_rot, upperlim_mask.sum()))) if plot: import pylab as pl L, = pl.plot(eupper, np.log10(nupperoverg_tofit), 'ro', markeredgecolor='none', alpha=0.5) L, = pl.plot(eupper, np.log10(nupperoverg), 'bo', alpha=0.2) if errors is not None: yerr = np.array([np.log10(nupperoverg_tofit)-np.log10(nupperoverg_tofit-errors), np.log10(nupperoverg_tofit+errors)-np.log10(nupperoverg_tofit)]) # if lower limit is nan, set to zero yerr[0,:] = np.nan_to_num(yerr[0,:]) if np.any(np.isnan(yerr[1,:])): print("*** Some upper limits are NAN") pl.errorbar(eupper.value, np.log10(nupperoverg_tofit), yerr=yerr, linestyle='none', linewidth=0.5, marker='.', zorder=-5) xax = np.array([0, eupper.max().value]) line = (xax*result.slope.value + result.intercept.value) pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(), alpha=0.3, label='$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value))) pl.ylabel("log N$_u$ (cm$^{-2}$)") pl.xlabel("E$_u$ (K)") if (uplims is not None) and ((errors is None) or replace_errors_with_uplims): # if errors are specified, their errorbars will be better # representations of what's actually being fit pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5, linestyle='none', color='k') return Ntot, tex, result.slope, result.intercept
def fit_tex(eupper, nupperoverg, verbose=False, plot=False, uplims=None, errors=None, min_nupper=1, replace_errors_with_uplims=False, max_uplims='half'): """ Fit the Boltzmann diagram Parameters ---------- max_uplims: str or number The maximum number of upper limits before the fit is ignored completely and instead zeros are returned """ model = modeling.models.Linear1D() #fitter = modeling.fitting.LevMarLSQFitter() fitter = modeling.fitting.LinearLSQFitter() nupperoverg_tofit = nupperoverg.copy() if uplims is not None: upperlim_mask = nupperoverg < uplims # allow this magical keyword 'half' max_uplims = len(nupperoverg)/2. if max_uplims == 'half' else max_uplims if upperlim_mask.sum() > max_uplims: # too many upper limits = bad idea to fit. return 0*u.cm**-2, 0*u.K, 0, 0 if errors is None: # if errors are not specified, we set the upper limits as actual values # (which gives a somewhat useful upper limit on the temperature) nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask] else: # otherwise, we set the values to zero-column and set the errors to # be whatever the upper limits are (hopefully a 1-sigma upper # limit) # 1.0 here becomes 0.0 in log and makes the relative errors meaningful nupperoverg_tofit[upperlim_mask] = 1.0 if replace_errors_with_uplims: errors[upperlim_mask] = uplims[upperlim_mask] # always ignore negatives & really low values good = nupperoverg_tofit > min_nupper # skip any fits that have fewer than 50% good values if good.sum() < len(nupperoverg_tofit)/2.: return 0*u.cm**-2, 0*u.K, 0, 0 if errors is not None: rel_errors = errors / nupperoverg_tofit weights = 1. / rel_errors**2 log.debug("Fitting with data = {0}, weights = {1}, errors = {2}," "relative_errors = {3}" .format(np.log(nupperoverg_tofit[good]), np.log(weights[good]), errors[good], rel_errors[good], )) else: # want log(weight) = 1 weights = np.exp(np.ones_like(nupperoverg_tofit)) result = fitter(model, eupper[good], np.log(nupperoverg_tofit[good]), weights=np.log(weights[good])) tex = -1./result.slope*u.K partition_func = specmodel.calculate_partitionfunction(ch3oh.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, Q_rot={2}, nuplim={3}".format(tex, Ntot, Q_rot, upperlim_mask.sum()))) if plot: import pylab as pl L, = pl.plot(eupper, np.log10(nupperoverg_tofit), 'ro', markeredgecolor='none', alpha=0.5) L, = pl.plot(eupper, np.log10(nupperoverg), 'bo', alpha=0.2) if errors is not None: yerr = np.array([np.log10(nupperoverg_tofit)-np.log10(nupperoverg_tofit-errors), np.log10(nupperoverg_tofit+errors)-np.log10(nupperoverg_tofit)]) # if lower limit is nan, set to zero yerr[0,:] = np.nan_to_num(yerr[0,:]) if np.any(np.isnan(yerr[1,:])): print("*** Some upper limits are NAN") pl.errorbar(eupper.value, np.log10(nupperoverg_tofit), yerr=yerr, linestyle='none', linewidth=0.5, marker='.', zorder=-5) xax = np.array([0, eupper.max().value]) line = (xax*result.slope.value + result.intercept.value) pl.plot(xax, np.log10(np.exp(line)), '-', color=L.get_color(), alpha=0.3, label='$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format(tex, np.log10(Ntot.value))) pl.ylabel("log N$_u$ (cm$^{-2}$)") pl.xlabel("E$_u$ (K)") if (uplims is not None) and ((errors is None) or replace_errors_with_uplims): # if errors are specified, their errorbars will be better # representations of what's actually being fit pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5, linestyle='none', color='k') return Ntot, tex, result.slope, result.intercept
def partfunc(tem): Q = list( specmodel.calculate_partitionfunction(result.data['States'], temperature=tem).values())[0] return Q
def get_molecular_parameters(molecule_name, molecule_name_vamdc=None, tex=50, fmin=1 * u.GHz, fmax=1 * u.THz, line_lists=['SLAIM'], chem_re_flags=0, **kwargs): """ Get the molecular parameters for a molecule from the CDMS database using vamdclib Parameters ---------- molecule_name : string The string name of the molecule (normal name, like CH3OH or CH3CH2OH) molecule_name_vamdc : string or None If specified, gives this name to vamdc instead of the normal name. Needed for some molecules, like CH3CH2OH -> C2H5OH. tex : float Optional excitation temperature (basically checks if the partition function calculator works) fmin : quantity with frequency units fmax : quantity with frequency units The minimum and maximum frequency to search over line_lists : list A list of Splatalogue line list catalogs to search. Valid options include SLAIM, CDMS, JPL. Only a single catalog should be used to avoid repetition of transitions and species chem_re_flags : int An integer flag to be passed to splatalogue's chemical name matching tool Examples -------- >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters(molecule_name='CH2CHCN', ... fmin=220*u.GHz, ... fmax=222*u.GHz, ... molecule_name_vamdc='C2H3CN') >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH3OH', ... fmin=90*u.GHz, ... fmax=100*u.GHz) """ from astroquery.vamdc import load_species_table from astroquery.splatalogue import Splatalogue from vamdclib import nodes from vamdclib import request from vamdclib import specmodel lut = load_species_table.species_lookuptable() species_id_dict = lut.find(molecule_name_vamdc or molecule_name, flags=chem_re_flags) if len(species_id_dict) == 1: species_id = list(species_id_dict.values())[0] elif len(species_id_dict) == 0: raise ValueError("No matches for {0}".format(molecule_name)) else: raise ValueError( "Too many species matched: {0}".format(species_id_dict)) # do this here, before trying to compute the partition function, because # this query could fail tbl = Splatalogue.query_lines(fmin, fmax, chemical_name=molecule_name, line_lists=line_lists, show_upper_degeneracy=True, **kwargs) nl = nodes.Nodelist() nl.findnode('cdms') cdms = nl.findnode('cdms') request = request.Request(node=cdms) query_string = "SELECT ALL WHERE VAMDCSpeciesID='%s'" % species_id request.setquery(query_string) result = request.dorequest() Q = list( specmodel.calculate_partitionfunction(result.data['States'], temperature=tex).values())[0] def partfunc(tem): Q = list( specmodel.calculate_partitionfunction(result.data['States'], temperature=tem).values())[0] return Q freqs = np.array(tbl['Freq-GHz']) * u.GHz aij = tbl['Log<sub>10</sub> (A<sub>ij</sub>)'] deg = tbl['Upper State Degeneracy'] EU = (np.array(tbl['E_U (K)']) * u.K * constants.k_B).to(u.erg).value return freqs, aij, deg, EU, partfunc
import numpy as np import paths import pylab as pl from astroquery.vamdc import Vamdc from vamdclib import specmodel ch3oh = Vamdc.query_molecule('CH3OH') temperatures = np.linspace(10, 300) partition_func = [ specmodel.calculate_partitionfunction(ch3oh.data['States'], temperature=tex)['XCDMS-149'] for tex in temperatures ] pl.matplotlib.rc_file('pubfiguresrc') pl.figure(1).clf() pl.plot(temperatures, partition_func) pl.xlabel("T (K)") pl.ylabel("$Q_{rot}$") pl.savefig(paths.fpath("chemistry/ch3oh_partition_function.png"))
def fit_tex( eupper, nupperoverg, verbose=False, plot=False, uplims=None, errors=None, min_nupper=1, replace_errors_with_uplims=False, molecule=None, color='r', marker='o', max_uplims='half', label='', ): """ Fit the Boltzmann diagram Parameters ---------- max_uplims: str or number The maximum number of upper limits before the fit is ignored completely and instead zeros are returned """ nupperoverg_tofit = nupperoverg.copy().to(u.cm**-2).value if errors is not None: errors = errors.to(u.cm**-2).value if uplims is not None: upperlim_mask = nupperoverg < uplims # allow this magical keyword 'half' max_uplims = len( nupperoverg) / 2. if max_uplims == 'half' else max_uplims if upperlim_mask.sum() > max_uplims: # too many upper limits = bad idea to fit. return 0 * u.cm**-2, 0 * u.K, 0, 0 if errors is None: # if errors are not specified, we set the upper limits as values to # be fitted # (which gives a somewhat useful upper limit on the temperature) nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask] else: # otherwise, we set the values to zero-column and set the errors to # be whatever the upper limits are (hopefully a 1-sigma upper # limit) # 1.0 here becomes 0.0 in log and makes the relative errors meaningful nupperoverg_tofit[upperlim_mask] = 1.0 if replace_errors_with_uplims: errors[upperlim_mask] = uplims[upperlim_mask] else: upperlim_mask = np.ones_like(nupperoverg_tofit, dtype='bool') # always ignore negatives & really low values good = nupperoverg_tofit > min_nupper # skip any fits that have fewer than 50% good values if good.sum() < len(nupperoverg_tofit) / 2.: return 0 * u.cm**-2, 0 * u.K, 0, 0 if errors is not None: rel_errors = errors / nupperoverg_tofit weights = 1. / rel_errors**2 log.debug("Fitting with data = {0}, weights = {1}, errors = {2}," "relative_errors = {3}".format( np.log(nupperoverg_tofit[good]), np.log(weights[good]), errors[good], rel_errors[good], )) else: # want log(weight) = 1 weights = np.exp(np.ones_like(nupperoverg_tofit)) #model = modeling.models.Linear1D() model = simple_lte_model_generator() fitter = modeling.fitting.LevMarLSQFitter() #fitter = modeling.fitting.LinearLSQFitter() #model.slope.bounds = [0, -1000] if good.sum() >= 2: result = fitter(model, eupper[good].to(u.K).value, np.log(nupperoverg_tofit[good]), weights=np.log(weights[good])) #tex = u.Quantity(-1./result.slope, u.K) tex = u.Quantity(result.tem, u.K) if tex < 0 * u.K: tex = 100 * u.K partition_func = specmodel.calculate_partitionfunction( molecule.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] #Ntot = np.exp(result.intercept + np.log(Q_rot)) * u.cm**-2 Ntot = np.exp(result.logcolumn) * Q_rot * u.cm**-2 else: Ntot = 1e10 * u.cm**-2 tex = 100 * u.K Q_rot = 1 result = model if verbose: print(("Tex={0}, Ntot={1}, log(Ntot)={4}, Q_rot={2}, " "nuplim={3}".format( tex, Ntot, Q_rot, upperlim_mask.sum(), np.log10(Ntot.value), ))) if plot: import pylab as pl L, = pl.plot( eupper, np.log10(nupperoverg_tofit), marker=marker, color=color, markeredgecolor='none', alpha=0.5, linestyle='none', #markersize=2, ) if uplims is not None: L, = pl.plot(eupper[upperlim_mask], np.log10(uplims)[upperlim_mask], 'bv', alpha=0.2, markersize=2) #L, = pl.plot(eupper[upperlim_mask], # np.log10(nupperoverg)[upperlim_mask], 'bv', alpha=0.2) if errors is not None: yerr = np.array([ np.log10(nupperoverg_tofit) - np.log10(nupperoverg_tofit - errors), np.log10(nupperoverg_tofit + errors) - np.log10(nupperoverg_tofit) ]) # if lower limit is nan, set to zero yerr[0, :] = np.nan_to_num(yerr[0, :]) if np.any(np.isnan(yerr[1, :])): #raise ValueError("*** Some upper limits are NAN") print(ValueError("*** Some upper limits are NAN")) # use 'good' to exclude plotting errorbars for upper limits pl.errorbar(eupper.value[good], np.log10(nupperoverg_tofit)[good], yerr=yerr[:, good], linestyle='none', linewidth=0.5, color='k', marker='.', zorder=-5, markersize=2) xax = np.array([0, eupper.max().value + 500]) line = (xax * (-1 / result.tem.value) + result.logcolumn.value) pl.plot(xax, np.log10(np.exp(line)), '--', color=color, alpha=0.6, linewidth=1.0, label='{2}$T={0:0.1f}$ $\log(N)={1:0.1f}$'.format( tex, np.log10(Ntot.value), label)) pl.ylabel("log N$_u$/g (cm$^{-2}$)") pl.xlabel("E$_u$ (K)") if (uplims is not None) and ((errors is None) or replace_errors_with_uplims): # if errors are specified, their errorbars will be better # representations of what's actually being fit pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5, linestyle='none', color='k') return Ntot, tex, result.tem, result.logcolumn
def Qrot(temperature): Q = m.calculate_partitionfunction(result.data['States'], temperature=temperature)[h2co.Id] return Q
def get_molecular_parameters(molecule_name, molecule_name_vamdc=None, tex=50, fmin=1*u.GHz, fmax=1*u.THz, line_lists=['SLAIM'], chem_re_flags=0, **kwargs): """ Get the molecular parameters for a molecule from the CDMS database using vamdclib Parameters ---------- molecule_name : string The string name of the molecule (normal name, like CH3OH or CH3CH2OH) molecule_name_vamdc : string or None If specified, gives this name to vamdc instead of the normal name. Needed for some molecules, like CH3CH2OH -> C2H5OH. tex : float Optional excitation temperature (basically checks if the partition function calculator works) fmin : quantity with frequency units fmax : quantity with frequency units The minimum and maximum frequency to search over line_lists : list A list of Splatalogue line list catalogs to search. Valid options include SLAIM, CDMS, JPL. Only a single catalog should be used to avoid repetition of transitions and species chem_re_flags : int An integer flag to be passed to splatalogue's chemical name matching tool Examples -------- >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters(molecule_name='CH2CHCN', ... fmin=220*u.GHz, ... fmax=222*u.GHz, ... molecule_name_vamdc='C2H3CN') >>> freqs, aij, deg, EU, partfunc = get_molecular_parameters('CH3OH', ... fmin=90*u.GHz, ... fmax=100*u.GHz) """ from astroquery.vamdc import load_species_table from astroquery.splatalogue import Splatalogue from vamdclib import nodes from vamdclib import request from vamdclib import specmodel lut = load_species_table.species_lookuptable() species_id_dict = lut.find(molecule_name_vamdc or molecule_name, flags=chem_re_flags) if len(species_id_dict) == 1: species_id = list(species_id_dict.values())[0] elif len(species_id_dict) == 0: raise ValueError("No matches for {0}".format(molecule_name)) else: raise ValueError("Too many species matched: {0}" .format(species_id_dict)) # do this here, before trying to compute the partition function, because # this query could fail tbl = Splatalogue.query_lines(fmin, fmax, chemical_name=molecule_name, line_lists=line_lists, show_upper_degeneracy=True, **kwargs) nl = nodes.Nodelist() nl.findnode('cdms') cdms = nl.findnode('cdms') request = request.Request(node=cdms) query_string = "SELECT ALL WHERE VAMDCSpeciesID='%s'" % species_id request.setquery(query_string) result = request.dorequest() # run it once to test that it works Q = list(specmodel.calculate_partitionfunction(result.data['States'], temperature=tex).values())[0] def partfunc(tem): Q = list(specmodel.calculate_partitionfunction(result.data['States'], temperature=tem).values())[0] return Q freqs = (np.array(tbl['Freq-GHz'])*u.GHz if 'Freq-GHz' in tbl else np.array(tbl['Freq-GHz(rest frame,redshifted)'])*u.GHz) aij = tbl['Log<sub>10</sub> (A<sub>ij</sub>)'] deg = tbl['Upper State Degeneracy'] EU = (np.array(tbl['E_U (K)'])*u.K*constants.k_B).to(u.erg).value return freqs, aij, deg, EU, partfunc
import numpy as np import paths import pylab as pl from astroquery.vamdc import Vamdc from vamdclib import specmodel ch3oh = Vamdc.query_molecule("CH3OH") temperatures = np.linspace(10, 300) partition_func = [ specmodel.calculate_partitionfunction(ch3oh.data["States"], temperature=tex)["XCDMS-149"] for tex in temperatures ] pl.matplotlib.rc_file("pubfiguresrc") pl.figure(1).clf() pl.plot(temperatures, partition_func) pl.xlabel("T (K)") pl.ylabel("$Q_{rot}$") pl.savefig(paths.fpath("chemistry/ch3oh_partition_function.png"))
def partfunc(tem): Q = list(specmodel.calculate_partitionfunction(result.data['States'], temperature=tem).values())[0] return Q
def fit_multi_tex(eupper, nupperoverg, vstate, jstate, vibenergies, rotenergies, verbose=False, plot=False, uplims=None, errors=None, min_nupper=1, replace_errors_with_uplims=False, molecule=None, colors='rgbcmyk', molname=None, collims=(np.log(1e9), np.log(1e17)), rottemlims=(10, 300), vibtemlims=(500, 16000), marker='o', max_uplims='half'): """ Fit the Boltzmann diagram with a vibrational and a rotational temperature Parameters ---------- max_uplims: str or number The maximum number of upper limits before the fit is ignored completely and instead zeros are returned """ nupperoverg_tofit = nupperoverg.copy().to(u.cm**-2).value if errors is not None: errors = errors.to(u.cm**-2).value if uplims is not None: upperlim_mask = nupperoverg < uplims # allow this magical keyword 'half' max_uplims = len( nupperoverg) / 2. if max_uplims == 'half' else max_uplims if upperlim_mask.sum() > max_uplims: # too many upper limits = bad idea to fit. return 0 * u.cm**-2, 0 * u.K, 0, 0 if errors is None: # if errors are not specified, we set the upper limits as values to # be fitted # (which gives a somewhat useful upper limit on the temperature) nupperoverg_tofit[upperlim_mask] = uplims[upperlim_mask] else: # otherwise, we set the values to zero-column and set the errors to # be whatever the upper limits are (hopefully a 1-sigma upper # limit) # 1.0 here becomes 0.0 in log and makes the relative errors meaningful nupperoverg_tofit[upperlim_mask] = 1.0 if replace_errors_with_uplims: errors[upperlim_mask] = uplims[upperlim_mask] else: upperlim_mask = np.ones_like(nupperoverg_tofit, dtype='bool') # always ignore negatives & really low values good = nupperoverg_tofit > min_nupper # skip any fits that have fewer than 50% good values if good.sum() < len(nupperoverg_tofit) / 2.: return 0 * u.cm**-2, 0 * u.K, 0, 0 if errors is not None: rel_errors = errors / nupperoverg_tofit weights = 1. / rel_errors**2 log.debug("Fitting with data = {0}, weights = {1}, errors = {2}," "relative_errors = {3}".format( np.log(nupperoverg_tofit[good]), np.log(weights[good]), errors[good], rel_errors[good], )) else: # want log(weight) = 1 weights = np.exp(np.ones_like(nupperoverg_tofit)) model = rovib_lte_model_generator(vibenergies, rotenergies) #print('vibenergies:',vibenergies, '\nrotenergies:', rotenergies) fitter = modeling.fitting.LevMarLSQFitter() #fitter = modeling.fitting.LinearLSQFitter() model_to_fit = model model_to_fit.logcolumn.bounds = collims model_to_fit.rottem.bounds = rottemlims model_to_fit.vibtem.bounds = vibtemlims #model_to_fit.rottem.fixed = True # check that the model works #first_check = model_to_fit(eupper[good].to(u.K).value) #print("Result of first check: ",first_check) result = fitter(model_to_fit, jstate[good].astype('int'), vstate[good].astype('int'), np.log(nupperoverg_tofit[good]), weights=np.log(weights[good])) print(result) tex = result.rottem #u.Quantity(-1./result.slope, u.K) tvib = result.vibtem partition_func = specmodel.calculate_partitionfunction( molecule.data['States'], temperature=tex.value) assert len(partition_func) == 1 Q_rot = tuple(partition_func.values())[0] print("Q_rot:", Q_rot) Ntot = np.exp(result.logcolumn + np.log(Q_rot)) * u.cm**-2 if verbose: print(("Tex={0}, Ntot={1}, log(Ntot)={4}, Q_rot={2}, " "nuplim={3}".format( tex, Ntot, Q_rot, upperlim_mask.sum(), np.log10(Ntot.value), ))) if plot: import pylab as pl for vib, color in zip(np.arange(vstate.max() + 1), colors): mask = vstate == vib if mask.sum() == 0: continue L, = pl.plot( eupper[mask], np.log10(nupperoverg_tofit[mask]), marker=marker, color=color, markeredgecolor='none', alpha=0.5, linestyle='none', #markersize=2, ) if uplims is not None: L, = pl.plot(eupper[upperlim_mask & mask], np.log10(uplims)[upperlim_mask & mask], 'bv', alpha=0.2, markersize=2) #L, = pl.plot(eupper[upperlim_mask], # np.log10(nupperoverg)[upperlim_mask], 'bv', alpha=0.2) if errors is not None: yerr = np.array([ np.log10(nupperoverg_tofit) - np.log10(nupperoverg_tofit - errors), np.log10(nupperoverg_tofit + errors) - np.log10(nupperoverg_tofit) ]) # if lower limit is nan, set to zero yerr[0, :] = np.nan_to_num(yerr[0, :]) if np.any(np.isnan(yerr[1, :])): print(ValueError("*** Some upper limits are NAN")) # use 'good' to exclude plotting errorbars for upper limits pl.errorbar(eupper.value[good & mask], np.log10(nupperoverg_tofit)[good & mask], yerr=yerr[:, good & mask], linestyle='none', linewidth=0.5, color=L.get_color(), marker='.', zorder=-5, markersize=2) #xax = np.array([0, eupper.max().value+500]) jstates = np.arange(1, np.max(list(rotenergies.keys())) + 1) vstates = np.ones(np.max(list(rotenergies.keys()))) * vib line = result(jstates, vstates) xax = np.array( [rotenergies[ju] + vibenergies[vib] for ju in jstates]) for marker, mask in (('.', (jstates > -1) & (jstates <= 10)), ('s', (jstates > 10) & (jstates <= 20)), ('o', (jstates > 20) & (jstates <= 30)), ('D', (jstates > 30) & (jstates <= 40)), ('p', (jstates > 40) & (jstates <= 50))): pl.plot( xax[mask], np.log10(np.exp(line))[mask], marker=marker, linestyle='none', color=color, alpha=0.3, linewidth=1.0, markersize=4, ) #label='v={0}'.format(vib), #label=('v={2}$T_R={0:0.1f}$ $T_v={3:0.1f}$ $\log(N)={1:0.1f}$' # .format(tex.value, np.log10(Ntot.value), vib, # tvib.value # )) for jj in np.unique(jstate): #jstates = np.arange(1,np.max(list(rotenergies.keys()))+1) #vstates = np.ones(np.max(list(rotenergies.keys())))*vib vstates = np.arange(9) jstates = np.ones(9) * jj line = result(jstates, vstates) xax = np.array( [rotenergies[jj] + vibenergies[vv] for vv in vstates]) pl.plot( xax, np.log10(np.exp(line)), '--', color='k', alpha=0.1, linewidth=1.0, ) #label="Ju={0}".format(jj), #label=('v={2}$T_R={0:0.1f}$ $T_v={3:0.1f}$ $\log(N)={1:0.1f}$' # .format(tex.value, np.log10(Ntot.value), vib, # tvib.value # )) for eu, nu, jj, vv in zip(eupper, nupperoverg_tofit, jstate, vstate): model_nu = result(jj, vv) #print("POINT MATCHING: ",eu, model_nu, np.log(nu), jj, vv) pl.plot(u.Quantity([eu, eu]), np.log10(np.exp([np.log(nu), model_nu])), linestyle='-', alpha=0.3, color='k', linewidth=0.5) pl.ylabel("log N$_u$/g (cm$^{-2}$)") pl.xlabel("E$_u$ (K)") pl.plot( [], [], linestyle='-', color='k', label=('$T_R={0:0.1f}$ $T_v={2:0.1f}$ $\log(N)={1:0.1f}$'.format( tex.value, np.log10(Ntot.value), tvib.value))) if (uplims is not None) and ((errors is None) or replace_errors_with_uplims): # if errors are specified, their errorbars will be better # representations of what's actually being fit pl.plot(eupper, np.log10(uplims), marker='_', alpha=0.5, linestyle='none', color='k') return Ntot, tex, result.rottem, result.vibtem, result.logcolumn