def fit_all_tex(xaxis, cube, cubefrequencies, indices, degeneracies, ecube=None, replace_bad=False): """ Parameters ---------- replace_bad : bool Attempt to replace bad (negative) values with their upper limits? """ tmap = np.empty(cube.shape[1:]) Nmap = np.empty(cube.shape[1:]) yy,xx = np.indices(cube.shape[1:]) pb = ProgressBar(xx.size) count=0 for ii,jj in (zip(yy.flat, xx.flat)): if any(np.isnan(cube[:,ii,jj])): tmap[ii,jj] = np.nan else: if replace_bad: uplims = nupper_of_kkms(replace_bad, cubefrequencies, einsteinAij[indices], degeneracies,).value else: uplims = None nuppers = nupper_of_kkms(cube[:,ii,jj], cubefrequencies, einsteinAij[indices], degeneracies, ) if ecube is not None: nupper_error = nupper_of_kkms(ecube[:,ii,jj], cubefrequencies, einsteinAij[indices], degeneracies,).value uplims = 3 * nupper_error if replace_bad: raise ValueError("replace_bad is ignored now...") else: nupper_error = None fit_result = fit_tex(xaxis, nuppers.value, errors=nupper_error, uplims=uplims) tmap[ii,jj] = fit_result[1].value Nmap[ii,jj] = fit_result[0].value pb.update(count) count+=1 return tmap,Nmap
def test_roundtrip( cubefrequencies=[218.44005, 234.68345, 220.07849, 234.69847, 231.28115] * u.GHz, degeneracies=[9, 9, 17, 11, 21], xaxis=[45.45959683, 60.92357159, 96.61387286, 122.72191958, 165.34856457] * u.K, indices=[3503, 1504, 2500, 116, 3322], ): # integrated line over 1 km/s (see dnu) onekms = 1 * u.km / u.s / constants.c kkms = lte_molecule.line_brightness( tex=100 * u.K, total_column=1e15 * u.cm**-2, partition_function=1185, degeneracy=degeneracies, frequency=cubefrequencies, energy_upper=xaxis.to(u.erg, u.temperature_energy()), einstein_A=einsteinAij[indices], dnu=onekms * cubefrequencies) * u.km / u.s col, tem, slope, intcpt = fit_tex(xaxis, nupper_of_kkms(kkms, cubefrequencies, einsteinAij[indices], degeneracies).value, plot=True) print("temperature = {0} (input was 100)".format(tem)) print("column = {0} (input was 1e15)".format(np.log10(col.value)))
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(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 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(hnco.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 test_roundtrip(cubefrequencies=[218.44005, 234.68345, 220.07849, 234.69847, 231.28115]*u.GHz, degeneracies=[9, 9, 17, 11, 21], xaxis=[45.45959683, 60.92357159, 96.61387286, 122.72191958, 165.34856457]*u.K, indices=[3503, 1504, 2500, 116, 3322], ): # integrated line over 1 km/s (see dnu) onekms = 1*u.km/u.s / constants.c kkms = lte_molecule.line_brightness(tex=100*u.K, total_column=1e15*u.cm**-2, partition_function=1185, degeneracy=degeneracies, frequency=cubefrequencies, energy_upper=xaxis.to(u.erg, u.temperature_energy()), einstein_A=einsteinAij[indices], dnu=onekms*cubefrequencies) * u.km/u.s col, tem, slope, intcpt = fit_tex(xaxis, nupper_of_kkms(kkms, cubefrequencies, einsteinAij[indices], degeneracies).value, plot=True) print("temperature = {0} (input was 100)".format(tem)) print("column = {0} (input was 1e15)".format(np.log10(col.value)))
def fit_each_line_with_a_gaussian(spectra, ampguess, vguess_kms, widthguess, fixed_velo=False, fixed_width=False, wrange_GHz=(0, 0.01), plot=False, separation_tolerance=0.005 * u.GHz, fignum_start=1): assert spectra.unit == 'K' sp = spectra sp.plotter.plotkwargs = {} sp.xarr.convert_to_unit(u.GHz) # assume baselined already # sp.data -= np.nanpercentile(sp.data, 10) #sp.data *= beam.jtok(sp.xarr) #sp.unit = u.K linenamecen = [(x[0], float(x[1].strip('GHz'))) for x in line_to_image_list.line_to_image_list if 'CH3OH' in x[0][:5]] freqs = u.Quantity([nu for ln, nu in linenamecen], u.GHz) # three checks: # 1) is the line in range? # 2) does the line correspond to finite data? # 3) is the closest spectral pixel actually near the line? # (this is to check whether the line falls in SPW gaps) okfreqs = np.array([ sp.xarr.in_range(nu) and np.isfinite(sp.data[sp.xarr.x_to_pix(nu)]) and np.min(np.abs(sp.xarr - nu)) < separation_tolerance for nu in freqs ], dtype='bool') redshift = (1 - vguess_kms / constants.c.to(u.km / u.s).value) guesses = [ x for nu in freqs[okfreqs] for x in (ampguess, nu.value * redshift, widthguess / constants.c.to(u.km / u.s).value * nu.value) ] tied = ['', '', ''] + [ x for nu in freqs[okfreqs][1:] for x in ('', 'p[1]+{0}'.format((nu.value - freqs[okfreqs][0].value) * redshift), 'p[2]') ] #print(list(zip(guesses, tied))) fixed = [False, fixed_velo, fixed_width] * int((len(guesses) / 3)) limited = [(True, True)] * len(guesses) # velos in GHz limits = [(0, 1000), (200, 250), wrange_GHz] * int(len(guesses) / 3) assert len(fixed) == len(guesses) == len(tied) sp.plotter(figure=pl.figure(fignum_start + 1)) sp.specfit(fittype='gaussian', guesses=guesses, tied=tied, fixed=fixed, limited=limited, limits=limits, annotate=False, verbose=True, renormalize=False) velocity_fit = -(spectra.specfit.parinfo[1].value - linenamecen[0][1] ) / linenamecen[0][1] * constants.c.to(u.km / u.s) print("{0}: v={1}".format(spectra.object_name, velocity_fit)) sp.plotter.line_ids(line_names=[ln for ln, nu in linenamecen], line_xvals=[nu * u.GHz for ln, nu in linenamecen], velocity_offset=velocity_fit) qn_to_amp = {} # only one width error b/c of tied #width_error = sp.specfit.parinfo['WIDTH0'].error for (amp, freq, width) in zip(sp.specfit.parinfo[::3], sp.specfit.parinfo[1::3], sp.specfit.parinfo[2::3]): rfreq = freq * (1 + velocity_fit / constants.c.to(u.km / u.s)) closestind = np.argmin(np.abs(rfreq * u.GHz - slaimfreqs)) closest = slaim[closestind] if amp.value < amp.error * 3: amp.value = 0 if abs(rfreq * u.GHz - slaimfreqs[closestind]) < separation_tolerance: qn_to_amp[closest['Resolved QNs']] = ( amp, width, closest['Freq-GHz'], closest['Log<sub>10</sub> (A<sub>ij</sub>)'], closest['E_U (K)'], closest['Upper State Degeneracy'], ) else: print("Skipped {0} for lack of fit. Closest was {1}".format( rfreq, closest['Resolved QNs'])) amps = u.Quantity([x[0].value for x in qn_to_amp.values()], u.K) amp_errs = u.Quantity([x[0].error for x in qn_to_amp.values()], u.K) widths = u.Quantity( [x[1].value / x[2] * constants.c for x in qn_to_amp.values()]) width_errs = u.Quantity( [x[1].error / x[2] * constants.c for x in qn_to_amp.values()]) thesefreqs = u.Quantity([x[2] for x in qn_to_amp.values()], u.GHz) these_aij = u.Quantity([10**x[3] for x in qn_to_amp.values()], u.s**-1) xaxis = u.Quantity([x[4] for x in qn_to_amp.values()], u.K) mydeg = [x[5] for x in qn_to_amp.values()] nupper = nupper_of_kkms(amps * widths * np.sqrt(2 * np.pi), thesefreqs, these_aij, mydeg).value integrated_error = (((amps * width_errs)**2 + (widths * amp_errs)**2) * (2 * np.pi))**0.5 nupper_error = nupper_of_kkms(integrated_error, thesefreqs, these_aij, mydeg).value if plot: pl.figure(fignum_start).clf() log.info("nupper={0}, nupper_error={1}".format(nupper, nupper_error)) result = fit_tex(xaxis, nupper, errors=nupper_error, plot=plot) pl.legend(loc='upper right') if plot: ntot, tex, _, _ = result peakamp = max(sp.specfit.parinfo.values[::3]) width_fit = sp.specfit.parinfo['WIDTH0'] * 2.35 / sp.specfit.parinfo[ 'SHIFT0'] * constants.c.to(u.km / u.s) log.info("vel_fit = {0}, width_fit = {1}".format( velocity_fit, width_fit)) # critical that this is done *before* show_modelfit is called, since # that replaces the model show_gaussian_modelfit( sp, vel=velocity_fit.value, width=width_fit.value, fignum=fignum_start + 3, ylim=(-0.1 * peakamp, peakamp + 2), ) show_modelfit(spectra, vel=velocity_fit.value, width=width_fit.value, tem=tex, col=ntot, fignum=fignum_start + 2, ylim=(-0.1 * peakamp, peakamp + 2)) # because there are 8 CH3OH lines, we can squeeze this into the bottom right ax = pl.subplot(3, 3, 9) result = fit_tex(xaxis, nupper, errors=nupper_error, plot=plot) ax.yaxis.tick_right() ax.yaxis.set_label_position("right") pl.legend(loc='upper right', fontsize=10) return result, qn_to_amp
def fit_all_tex(xaxis, cube, cubefrequencies, indices, degeneracies, ecube=None, replace_bad=False): """ Parameters ---------- replace_bad : bool Attempt to replace bad (negative) values with their upper limits? """ tmap = np.empty(cube.shape[1:]) Nmap = np.empty(cube.shape[1:]) yy, xx = np.indices(cube.shape[1:]) pb = ProgressBar(xx.size) count = 0 for ii, jj in (zip(yy.flat, xx.flat)): if any(np.isnan(cube[:, ii, jj])): tmap[ii, jj] = np.nan else: if replace_bad: uplims = nupper_of_kkms( replace_bad, cubefrequencies, einsteinAij[indices], degeneracies, ).value else: uplims = None nuppers = nupper_of_kkms( cube[:, ii, jj], cubefrequencies, einsteinAij[indices], degeneracies, ) if ecube is not None: nupper_error = nupper_of_kkms( ecube[:, ii, jj], cubefrequencies, einsteinAij[indices], degeneracies, ).value uplims = 3 * nupper_error if replace_bad: raise ValueError("replace_bad is ignored now...") else: nupper_error = None fit_result = fit_tex(xaxis, nuppers.value, errors=nupper_error, uplims=uplims) tmap[ii, jj] = fit_result[1].value Nmap[ii, jj] = fit_result[0].value pb.update(count) count += 1 return tmap, Nmap
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( 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 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( hnco.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
pl.figure(2, figsize=(12, 12)).clf() sample_pos = np.linspace(0, 1, 7)[1:-1] nx = len(sample_pos) ny = len(sample_pos) for ii, (spy, spx) in enumerate(itertools.product(sample_pos, sample_pos)): rdx = int(spx * cube.shape[2]) rdy = int(spy * cube.shape[1]) plotnum = (nx * ny - (2 * (ii // ny) * ny) + ii - ny) + 1 pl.subplot(nx, ny, plotnum) #uplims = nupper_of_kkms(replace_bad, cubefrequencies, # einsteinAij[indices], degeneracies,) nupper_error = nupper_of_kkms( ecube[:, rdy, rdx], cubefrequencies, einsteinAij[indices], degeneracies, ) uplims = 3 * nupper_error Ntot, tex, slope, intcpt = fit_tex( xaxis, nupper_of_kkms(cube[:, rdy, rdx], cubefrequencies, einsteinAij[indices], degeneracies).value, errors=nupper_error.value, uplims=uplims.value, verbose=True, plot=True) pl.ylim(11, 15) pl.xlim(0, 850) #pl.annotate("{0:d},{1:d}".format(rdx,rdy), (0.5, 0.85), xycoords='axes fraction', # horizontalalignment='center')
def fit_each_line_with_a_gaussian(spectra, ampguess, vguess_kms, widthguess, fixed_velo=False, fixed_width=False, wrange_GHz=(0,0.01), plot=False, separation_tolerance=0.005*u.GHz, fignum_start=1): assert spectra.unit == 'K' sp = spectra sp.plotter.plotkwargs = {} sp.xarr.convert_to_unit(u.GHz) # assume baselined already # sp.data -= np.nanpercentile(sp.data, 10) #sp.data *= beam.jtok(sp.xarr) #sp.unit = u.K linenamecen = [(x[0],float(x[1].strip('GHz'))) for x in line_to_image_list.line_to_image_list if 'CH3OH' in x[0][:5]] freqs = u.Quantity([nu for ln,nu in linenamecen], u.GHz) # three checks: # 1) is the line in range? # 2) does the line correspond to finite data? # 3) is the closest spectral pixel actually near the line? # (this is to check whether the line falls in SPW gaps) okfreqs = np.array([sp.xarr.in_range(nu) and np.isfinite(sp.data[sp.xarr.x_to_pix(nu)]) and np.min(np.abs(sp.xarr-nu)) < separation_tolerance for nu in freqs], dtype='bool') redshift = (1-vguess_kms/constants.c.to(u.km/u.s).value) guesses = [x for nu in freqs[okfreqs] for x in (ampguess, nu.value*redshift, widthguess/constants.c.to(u.km/u.s).value*nu.value)] tied = ['','','']+[x for nu in freqs[okfreqs][1:] for x in ('', 'p[1]+{0}'.format((nu.value-freqs[okfreqs][0].value)*redshift), 'p[2]')] #print(list(zip(guesses, tied))) fixed = [False,fixed_velo,fixed_width] * int((len(guesses)/3)) limited=[(True,True)]*len(guesses) # velos in GHz limits=[(0,1000), (200,250), wrange_GHz]*int(len(guesses)/3) assert len(fixed) == len(guesses) == len(tied) sp.plotter(figure=pl.figure(fignum_start+1)) sp.specfit(fittype='gaussian', guesses=guesses, tied=tied, fixed=fixed, limited=limited, limits=limits, annotate=False, verbose=True, renormalize=False) velocity_fit = -(spectra.specfit.parinfo[1].value - linenamecen[0][1])/linenamecen[0][1]*constants.c.to(u.km/u.s) print("{0}: v={1}".format(spectra.object_name, velocity_fit)) sp.plotter.line_ids(line_names=[ln for ln,nu in linenamecen], line_xvals=[nu*u.GHz for ln,nu in linenamecen], velocity_offset=velocity_fit) qn_to_amp = {} # only one width error b/c of tied #width_error = sp.specfit.parinfo['WIDTH0'].error for (amp, freq, width) in zip(sp.specfit.parinfo[::3], sp.specfit.parinfo[1::3], sp.specfit.parinfo[2::3]): rfreq = freq * (1+velocity_fit/constants.c.to(u.km/u.s)) closestind = np.argmin(np.abs(rfreq*u.GHz-slaimfreqs)) closest = slaim[closestind] if amp.value < amp.error*3: amp.value = 0 if abs(rfreq*u.GHz-slaimfreqs[closestind]) < separation_tolerance: qn_to_amp[closest['Resolved QNs']] = (amp, width, closest['Freq-GHz'], closest['Log<sub>10</sub> (A<sub>ij</sub>)'], closest['E_U (K)'], closest['Upper State Degeneracy'], ) else: print("Skipped {0} for lack of fit. Closest was {1}".format(rfreq, closest['Resolved QNs'])) amps = u.Quantity([x[0].value for x in qn_to_amp.values()], u.K) amp_errs = u.Quantity([x[0].error for x in qn_to_amp.values()], u.K) widths = u.Quantity([x[1].value/x[2]*constants.c for x in qn_to_amp.values()]) width_errs = u.Quantity([x[1].error/x[2]*constants.c for x in qn_to_amp.values()]) thesefreqs = u.Quantity([x[2] for x in qn_to_amp.values()], u.GHz) these_aij = u.Quantity([10**x[3] for x in qn_to_amp.values()], u.s**-1) xaxis = u.Quantity([x[4] for x in qn_to_amp.values()], u.K) mydeg = [x[5] for x in qn_to_amp.values()] nupper = nupper_of_kkms(amps*widths*np.sqrt(2*np.pi), thesefreqs, these_aij, mydeg).value integrated_error = (((amps*width_errs)**2+(widths*amp_errs)**2)*(2*np.pi))**0.5 nupper_error = nupper_of_kkms(integrated_error, thesefreqs, these_aij, mydeg).value if plot: pl.figure(fignum_start).clf() log.info("nupper={0}, nupper_error={1}".format(nupper, nupper_error)) result = fit_tex(xaxis, nupper, errors=nupper_error, plot=plot) pl.legend(loc='upper right') if plot: ntot, tex, _,_ = result peakamp = max(sp.specfit.parinfo.values[::3]) width_fit = sp.specfit.parinfo['WIDTH0']*2.35 / sp.specfit.parinfo['SHIFT0'] * constants.c.to(u.km/u.s) log.info("vel_fit = {0}, width_fit = {1}".format(velocity_fit, width_fit)) # critical that this is done *before* show_modelfit is called, since # that replaces the model show_gaussian_modelfit(sp, vel=velocity_fit.value, width=width_fit.value, fignum=fignum_start+3, ylim=(-0.1*peakamp,peakamp+2), ) show_modelfit(spectra, vel=velocity_fit.value, width=width_fit.value, tem=tex, col=ntot, fignum=fignum_start+2, ylim=(-0.1*peakamp,peakamp+2)) # because there are 8 CH3OH lines, we can squeeze this into the bottom right ax = pl.subplot(3,3,9) result = fit_tex(xaxis, nupper, errors=nupper_error, plot=plot) ax.yaxis.tick_right() ax.yaxis.set_label_position("right") pl.legend(loc='upper right', fontsize=10) return result, qn_to_amp
) xaxis,cube,ecube,maps,map_error,energies,cubefrequencies,indices,degeneracies,header = _ pl.figure(2, figsize=(12,12)).clf() sample_pos = np.linspace(0,1,7)[1:-1] nx = len(sample_pos) ny = len(sample_pos) for ii,(spy,spx) in enumerate(itertools.product(sample_pos,sample_pos)): rdx = int(spx*cube.shape[2]) rdy = int(spy*cube.shape[1]) plotnum = (nx*ny-(2*(ii//ny)*ny)+ii-ny)+1 pl.subplot(nx,ny,plotnum) #uplims = nupper_of_kkms(replace_bad, cubefrequencies, # einsteinAij[indices], degeneracies,) nupper_error = nupper_of_kkms(ecube[:,rdy,rdx], cubefrequencies, einsteinAij[indices], degeneracies,) uplims = 3*nupper_error Ntot, tex, slope, intcpt = fit_tex(xaxis, nupper_of_kkms(cube[:,rdy,rdx], cubefrequencies, einsteinAij[indices], degeneracies).value, errors=nupper_error.value, uplims=uplims.value, verbose=True, plot=True) pl.ylim(11, 15) pl.xlim(0, 850) #pl.annotate("{0:d},{1:d}".format(rdx,rdy), (0.5, 0.85), xycoords='axes fraction', # horizontalalignment='center') pl.annotate("T={0:d}".format(int(tex.value)),