def jam_axi_vel(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, normpsf=1., pixang=0., pixsize=0., plot=True, vel=None, evel=None, sigmapsf=0., goodbins=None, quiet=False, beta=None, kappa=None, gamma=None, step=0., nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51, **kwargs): """ This procedure calculates a prediction for the projected mean velocity V for an anisotropic axisymmetric galaxy model. It implements the solution of the anisotropic Jeans equations presented in equation (38) of Cappellari (2008, MNRAS, 390, 71). PSF convolution is done as described in the Appendix of that paper: http://adsabs.harvard.edu/abs/2008MNRAS.390...71C CALLING SEQUENCE: velModel, kappa, chi2, flux = jam_axi_vel( surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, normpsf=1, pixang=0, pixsize=0, plot=True, vel=None, evel=None, sigmapsf=0, goodbins=None, quiet=False, beta=None, kappa=None, gamma=None, step=0, nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51) See the file jam_axi_vel.py for detailed documentation. """ if beta is None: beta = np.zeros_like(surf_lum) # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2 (beta=0-->circle) if kappa is None: kappa = np.ones_like(surf_lum) # Anisotropy parameter: V_obs = kappa * V_iso (kappa=1-->circle) if gamma is None: gamma = np.zeros_like(surf_lum) # Anisotropy parameter gamma = 1 - (sig_phi/sig_R)^2 (gamma=0-->circle) if not (surf_lum.size == sigma_lum.size == qobs_lum.size == beta.size == gamma.size == kappa.size): raise ValueError("The luminous MGE components and anisotropies do not match") if not (surf_pot.size == sigma_pot.size == qobs_pot.size): raise ValueError("The total mass MGE components do not match") if xbin.size != ybin.size: raise ValueError("xbin and ybin do not match") if vel is not None: if evel is None: evel = np.full_like(vel, 5.) # Constant 5 km/s errors if goodbins is None: goodbins = np.ones_like(vel, dtype=bool) elif goodbins.dtype != bool: raise ValueError("goodbins must be a boolean vector") if not (xbin.size == vel.size == evel.size == goodbins.size): raise ValueError("(vel, evel, goodbins) and (xbin, ybin) do not match") sigmapsf = np.atleast_1d(sigmapsf) normpsf = np.atleast_1d(normpsf) if sigmapsf.size != normpsf.size: raise ValueError("sigmaPSF and normPSF do not match") pc = distance*np.pi/0.648 # Constant factor to convert arcsec --> pc surf_lum_pc = surf_lum surf_pot_pc = surf_pot sigma_lum_pc = sigma_lum*pc # Convert from arcsec to pc sigma_pot_pc = sigma_pot*pc # Convert from arcsec to pc xbin_pc = xbin*pc # Convert all distances to pc ybin_pc = ybin*pc pixSize_pc = pixsize*pc sigmaPsf_pc = sigmapsf*pc step_pc = step*pc # Add a Gaussian with small sigma and the same total mass as the BH. # The Gaussian provides an excellent representation of the second moments # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH. # The error increses to 14% at 1*sigmaBH, independently of the BH mass. # if mbh > 0: sigmaBH_pc = rbh*pc # Adopt for the BH just a very small size surfBH_pc = mbh/(2*np.pi*sigmaBH_pc**2) surf_pot_pc = np.append(surfBH_pc, surf_pot_pc) # Add Gaussian to potential only! sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc) qobs_pot = np.append(1., qobs_pot) # Make sure vectors do not have extra dimensions qobs_lum = qobs_lum.clip(0, 0.999) qobs_pot = qobs_pot.clip(0, 0.999) t = clock() velModel = _vel(xbin_pc, ybin_pc, inc, surf_lum_pc, sigma_lum_pc, qobs_lum, surf_pot_pc, sigma_pot_pc, qobs_pot, beta, kappa, gamma, sigmaPsf_pc, normpsf, pixSize_pc, pixang, step_pc, nrad, nang, component, nsteps) if not quiet: print('jam_axis_vel elapsed time sec: %.2f' % (clock() - t)) # Analytic convolution of the MGE model with an MGE circular PSF # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400) # lum = surf_lum_pc*qobs_lum*sigma_lum**2 # Luminosity/(2np.pi) of each Gaussian flux = np.zeros_like(xbin) # Total MGE surface brightness for plotting for sigp, norp in zip(sigmapsf, normpsf): # loop over the PSF Gaussians sigmaX = np.sqrt(sigma_lum**2 + sigp**2) sigmaY = np.sqrt((sigma_lum*qobs_lum)**2 + sigp**2) surfConv = lum / (sigmaX*sigmaY) # PSF-convolved in Lsun/pc**2 for srf, sx, sy in zip(surfConv, sigmaX, sigmaY): # loop over the galaxy MGE Gaussians flux += norp*srf*np.exp(-0.5*((xbin/sx)**2 + (ybin/sy)**2)) ####### Output and optional M/L fit # If RMS keyword is not given all this section is skipped if vel is None: chi2 = None else: # Only consider the good bins for the chi**2 estimation # # Scale by having the same angular momentum # in the model and in the galaxy (equation 52) # kappa2 = np.sum(np.abs(vel[goodbins]*xbin[goodbins])) \ / np.sum(np.abs(velModel[goodbins]*xbin[goodbins])) kappa = kappa*kappa2 # Rescale input kappa by the best fit # Measure the scaling one would have from a standard chi^2 fit of the V field. # This value is only used to get proper sense of rotation for the model. # y1 = rms; dy1 = erms (y1 are the data, y2 the model) # scale = total(y1*y2/dy1^2)/total(y2^2/dy1^2) (equation 51) # kappa3 = np.sum(vel[goodbins]*velModel[goodbins]/evel[goodbins]**2) \ / np.sum((velModel[goodbins]/evel[goodbins])**2) velModel *= kappa2*np.sign(kappa3) chi2 = np.sum(((vel[goodbins]-velModel[goodbins])/evel[goodbins])**2) / goodbins.sum() if not quiet: print('inc=%.1f kappa=%.2f beta_z=%.2f BH=%.2e chi2/DOF=%.3g' % (inc, kappa[0], beta[0], mbh, chi2)) mass = 2*np.pi*surf_pot_pc*qobs_pot*sigma_pot_pc**2 print('Total mass MGE %.4g:' % np.sum(mass)) if plot: vel1 = vel.copy() # Only symmetrize good bins vel1[goodbins] = symmetrize_velfield(xbin[goodbins], ybin[goodbins], vel[goodbins], sym=1) if vmax is None: vmax = stats.scoreatpercentile(np.abs(vel1[goodbins]), 99) plt.clf() plt.subplot(121) plot_velfield(xbin, ybin, vel1, vmin=-vmax, vmax=vmax, flux=flux, **kwargs) plt.title(r"Input $V_{\rm los}$") plt.subplot(122) plot_velfield(xbin, ybin, velModel, vmin=-vmax, vmax=vmax, flux=flux, **kwargs) plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white') plt.title(r"Model $V_{\rm los}$") plt.tick_params(labelleft='off') plt.subplots_adjust(wspace=0.03) return velModel, kappa, chi2, flux
def jam_axi_vel(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, normpsf=1., pixang=0., pixsize=0., plot=True, vel=None, evel=None, sigmapsf=0., goodbins=None, quiet=False, beta=None, kappa=None, gamma=None, step=0., nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51, **kwargs): """ This procedure calculates a prediction for the projected mean velocity V for an anisotropic axisymmetric galaxy model. It implements the solution of the anisotropic Jeans equations presented in equation (38) of Cappellari (2008, MNRAS, 390, 71). PSF convolution is done as described in the Appendix of that paper: http://adsabs.harvard.edu/abs/2008MNRAS.390...71C CALLING SEQUENCE: velModel, kappa, chi2, flux = jam_axi_vel( surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, normpsf=1, pixang=0, pixsize=0, plot=True, vel=None, evel=None, sigmapsf=0, goodbins=None, quiet=False, beta=None, kappa=None, gamma=None, step=0, nrad=20, nang=10, rbh=0.01, vmax=None, component='z', nsteps=51) See the file jam_axi_vel.py for detailed documentation. """ if beta is None: beta = np.zeros_like( surf_lum ) # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2 (beta=0-->circle) if kappa is None: kappa = np.ones_like( surf_lum ) # Anisotropy parameter: V_obs = kappa * V_iso (kappa=1-->circle) if gamma is None: gamma = np.zeros_like( surf_lum ) # Anisotropy parameter gamma = 1 - (sig_phi/sig_R)^2 (gamma=0-->circle) if not (surf_lum.size == sigma_lum.size == qobs_lum.size == beta.size == gamma.size == kappa.size): raise ValueError( "The luminous MGE components and anisotropies do not match") if not (surf_pot.size == sigma_pot.size == qobs_pot.size): raise ValueError("The total mass MGE components do not match") if xbin.size != ybin.size: raise ValueError("xbin and ybin do not match") if vel is not None: if evel is None: evel = np.full_like(vel, 5.) # Constant 5 km/s errors if goodbins is None: goodbins = np.ones_like(vel, dtype=bool) elif goodbins.dtype != bool: raise ValueError("goodbins must be a boolean vector") if not (xbin.size == vel.size == evel.size == goodbins.size): raise ValueError( "(vel, evel, goodbins) and (xbin, ybin) do not match") sigmapsf = np.atleast_1d(sigmapsf) normpsf = np.atleast_1d(normpsf) if sigmapsf.size != normpsf.size: raise ValueError("sigmaPSF and normPSF do not match") pc = distance * np.pi / 0.648 # Constant factor to convert arcsec --> pc surf_lum_pc = surf_lum surf_pot_pc = surf_pot sigma_lum_pc = sigma_lum * pc # Convert from arcsec to pc sigma_pot_pc = sigma_pot * pc # Convert from arcsec to pc xbin_pc = xbin * pc # Convert all distances to pc ybin_pc = ybin * pc pixSize_pc = pixsize * pc sigmaPsf_pc = sigmapsf * pc step_pc = step * pc # Add a Gaussian with small sigma and the same total mass as the BH. # The Gaussian provides an excellent representation of the second moments # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH. # The error increses to 14% at 1*sigmaBH, independently of the BH mass. # if mbh > 0: sigmaBH_pc = rbh * pc # Adopt for the BH just a very small size surfBH_pc = mbh / (2 * np.pi * sigmaBH_pc**2) surf_pot_pc = np.append(surfBH_pc, surf_pot_pc) # Add Gaussian to potential only! sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc) qobs_pot = np.append( 1., qobs_pot) # Make sure vectors do not have extra dimensions qobs_lum = qobs_lum.clip(0, 0.999) qobs_pot = qobs_pot.clip(0, 0.999) t = clock() velModel = _vel(xbin_pc, ybin_pc, inc, surf_lum_pc, sigma_lum_pc, qobs_lum, surf_pot_pc, sigma_pot_pc, qobs_pot, beta, kappa, gamma, sigmaPsf_pc, normpsf, pixSize_pc, pixang, step_pc, nrad, nang, component, nsteps) if not quiet: print('jam_axis_vel elapsed time sec: %.2f' % (clock() - t)) # Analytic convolution of the MGE model with an MGE circular PSF # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400) # lum = surf_lum_pc * qobs_lum * sigma_lum**2 # Luminosity/(2np.pi) of each Gaussian flux = np.zeros_like(xbin) # Total MGE surface brightness for plotting for sigp, norp in zip(sigmapsf, normpsf): # loop over the PSF Gaussians sigmaX = np.sqrt(sigma_lum**2 + sigp**2) sigmaY = np.sqrt((sigma_lum * qobs_lum)**2 + sigp**2) surfConv = lum / (sigmaX * sigmaY) # PSF-convolved in Lsun/pc**2 for srf, sx, sy in zip(surfConv, sigmaX, sigmaY): # loop over the galaxy MGE Gaussians flux += norp * srf * np.exp(-0.5 * ((xbin / sx)**2 + (ybin / sy)**2)) ####### Output and optional M/L fit # If RMS keyword is not given all this section is skipped if vel is None: chi2 = None else: # Only consider the good bins for the chi**2 estimation # # Scale by having the same angular momentum # in the model and in the galaxy (equation 52) # kappa2 = np.sum(np.abs(vel[goodbins]*xbin[goodbins])) \ / np.sum(np.abs(velModel[goodbins]*xbin[goodbins])) kappa = kappa * kappa2 # Rescale input kappa by the best fit # Measure the scaling one would have from a standard chi^2 fit of the V field. # This value is only used to get proper sense of rotation for the model. # y1 = rms; dy1 = erms (y1 are the data, y2 the model) # scale = total(y1*y2/dy1^2)/total(y2^2/dy1^2) (equation 51) # kappa3 = np.sum(vel[goodbins]*velModel[goodbins]/evel[goodbins]**2) \ / np.sum((velModel[goodbins]/evel[goodbins])**2) velModel *= kappa2 * np.sign(kappa3) chi2 = np.sum(((vel[goodbins] - velModel[goodbins]) / evel[goodbins])** 2) / goodbins.sum() if not quiet: print('inc=%.1f kappa=%.2f beta_z=%.2f BH=%.2e chi2/DOF=%.3g' % (inc, kappa[0], beta[0], mbh, chi2)) mass = 2 * np.pi * surf_pot_pc * qobs_pot * sigma_pot_pc**2 print('Total mass MGE %.4g:' % np.sum(mass)) if plot: vel1 = vel.copy() # Only symmetrize good bins vel1[goodbins] = symmetrize_velfield(xbin[goodbins], ybin[goodbins], vel[goodbins], sym=1) if vmax is None: vmax = stats.scoreatpercentile(np.abs(vel1[goodbins]), 99) plt.clf() plt.subplot(121) plot_velfield(xbin, ybin, vel1, vmin=-vmax, vmax=vmax, flux=flux, **kwargs) plt.title(r"Input $V_{\rm los}$") plt.subplot(122) plot_velfield(xbin, ybin, velModel, vmin=-vmax, vmax=vmax, flux=flux, **kwargs) plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white') plt.title(r"Model $V_{\rm los}$") plt.tick_params(labelleft='off') plt.subplots_adjust(wspace=0.03) return velModel, kappa, chi2, flux
def jam_axi_rms(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, ml=None, normpsf=1., pixang=0., pixsize=0., plot=True, rms=None, erms=None, sigmapsf=0., goodbins=None, quiet=False, beta=None, step=0., nrad=20, nang=10, rbh=0.01, tensor='zz', vmin=None, vmax=None, **kwargs): """ This procedure calculates a prediction for the projected second velocity moment V_RMS = sqrt(V^2 + sigma^2), or optionally any of the six components of the symmetric proper motion dispersion tensor, for an anisotropic axisymmetric galaxy model. It implements the solution of the anisotropic Jeans equations presented in equation (28) and note 5 of Cappellari (2008, MNRAS, 390, 71). PSF convolution in done as described in the Appendix of that paper. http://adsabs.harvard.edu/abs/2008MNRAS.390...71C CALLING SEQUENCE: rmsModel, ml, chi2, flux = \ jam_axi_rms(surf_lum, sigma_lum, qobs_lum, surf_pot, sigma_pot, qobs_pot, inc, mbh, distance, xbin, ybin, ml=None, normpsf=1, pixang=0, pixsize=0, plot=True, rms=None, erms=None, sigmapsf=0, goodbins=None, quiet=False, beta=None, step=0, nrad=20, nang=10, rbh=0.01, tensor='zz', vmin=None, vmax=None) See the file jam_axi_rms.py for detailed documentation. """ if beta is None: beta = np.zeros_like( surf_lum) # Anisotropy parameter beta = 1 - (sig_z/sig_R)**2 if not (surf_lum.size == sigma_lum.size == qobs_lum.size == beta.size): raise ValueError("The luminous MGE components do not match") if not (surf_pot.size == sigma_pot.size == qobs_pot.size): raise ValueError("The total mass MGE components do not match") if xbin.size != ybin.size: raise ValueError("xbin and ybin do not match") if rms is not None: if erms is None: erms = np.full_like(rms, np.median(rms) * 0.05) # Constant ~5% errors if goodbins is None: goodbins = np.ones_like(rms, dtype=bool) elif goodbins.dtype != bool: raise ValueError("goodbins must be a boolean vector") if not (xbin.size == rms.size == erms.size == goodbins.size): raise ValueError( "(rms, erms, goodbins) and (xbin, ybin) do not match") sigmapsf = np.atleast_1d(sigmapsf) normpsf = np.atleast_1d(normpsf) if sigmapsf.size != normpsf.size: raise ValueError("sigmaPSF and normPSF do not match") pc = distance * np.pi / 0.648 # Constant factor to convert arcsec --> pc surf_lum_pc = surf_lum surf_pot_pc = surf_pot sigma_lum_pc = sigma_lum * pc # Convert from arcsec to pc sigma_pot_pc = sigma_pot * pc # Convert from arcsec to pc xbin_pc = xbin * pc # Convert all distances to pc ybin_pc = ybin * pc pixSize_pc = pixsize * pc sigmaPsf_pc = sigmapsf * pc step_pc = step * pc # Add a Gaussian with small sigma and the same total mass as the BH. # The Gaussian provides an excellent representation of the second moments # of a point-like mass, to 1% accuracy out to a radius 2*sigmaBH. # The error increses to 14% at 1*sigmaBH, independently of the BH mass. # if mbh > 0: sigmaBH_pc = rbh * pc # Adopt for the BH just a very small size surfBH_pc = mbh / (2 * np.pi * sigmaBH_pc**2) surf_pot_pc = np.append(surfBH_pc, surf_pot_pc) # Add Gaussian to potential only! sigma_pot_pc = np.append(sigmaBH_pc, sigma_pot_pc) qobs_pot = np.append( 1., qobs_pot) # Make sure vectors do not have extra dimensions qobs_lum = qobs_lum.clip(0, 0.999) qobs_pot = qobs_pot.clip(0, 0.999) t = clock() rmsModel = _vrms2(xbin_pc, ybin_pc, inc, surf_lum_pc, sigma_lum_pc, qobs_lum, surf_pot_pc, sigma_pot_pc, qobs_pot, beta, tensor, sigmaPsf_pc, normpsf, pixSize_pc, pixang, step_pc, nrad, nang) if not quiet: print('jam_axi_rms elapsed time sec: %.2f' % (clock() - t)) if tensor in ('xx', 'yy', 'zz'): rmsModel = np.sqrt( rmsModel.clip(0)) # Return SQRT and fix possible rounding errors if tensor in ('xy', 'xz'): rmsModel *= np.sign(xbin * ybin) # Calculation was done in positive quadrant # Analytic convolution of the MGE model with an MGE circular PSF # using Equations (4,5) of Cappellari (2002, MNRAS, 333, 400) # lum = surf_lum_pc * qobs_lum * sigma_lum**2 # Luminosity/(2np.pi) of each Gaussian flux = np.zeros_like(xbin) # Total MGE surface brightness for plotting for sigp, norp in zip(sigmapsf, normpsf): # loop over the PSF Gaussians sigmaX = np.sqrt(sigma_lum**2 + sigp**2) sigmaY = np.sqrt((sigma_lum * qobs_lum)**2 + sigp**2) surfConv = lum / (sigmaX * sigmaY) # PSF-convolved in Lsun/pc**2 for srf, sx, sy in zip(surfConv, sigmaX, sigmaY): # loop over the galaxy MGE Gaussians flux += norp * srf * np.exp(-0.5 * ((xbin / sx)**2 + (ybin / sy)**2)) if rms is None: chi2 = None if ml is None: ml = 1. else: rmsModel *= np.sqrt(ml) else: if (ml is None) or (ml <= 0): # y1, dy1 = rms, erms # (y1 are the data, y2 the model) # scale = sum(y1*y2/dy1**2)/sum(y2**2/dy1**2) # (equation 51) # ml = (np.sum(rms[goodbins] * rmsModel[goodbins] / erms[goodbins]**2) / np.sum( (rmsModel[goodbins] / erms[goodbins])**2))**2 rmsModel *= np.sqrt(ml) chi2 = np.sum(((rms[goodbins] - rmsModel[goodbins]) / erms[goodbins])** 2) / goodbins.sum() if not quiet: print('inc=%.1f beta_z=%.2f M/L=%.3g BH=%.2e chi2/DOF=%.3g' % (inc, beta[0], ml, mbh * ml, chi2)) mass = 2 * np.pi * surf_pot_pc * qobs_pot * sigma_pot_pc**2 print('Total mass MGE: %.4g' % np.sum(mass * ml)) if plot: rms1 = rms.copy() # Only symmetrize good bins rms1[goodbins] = symmetrize_velfield(xbin[goodbins], ybin[goodbins], rms[goodbins]) if (vmin is None) or (vmax is None): vmin, vmax = stats.scoreatpercentile( rms1[goodbins], [0.5, 99.5]) # Could use np.percentile in Numpy 1.10 plt.clf() plt.subplot(121) plot_velfield(xbin, ybin, rms1, vmin=vmin, vmax=vmax, flux=flux, **kwargs) plt.title(r"Input $V_{\rm rms}$") plt.subplot(122) plot_velfield(xbin, ybin, rmsModel, vmin=vmin, vmax=vmax, flux=flux, **kwargs) plt.plot(xbin[~goodbins], ybin[~goodbins], 'ok', mec='white') plt.title(r"Model $V_{\rm rms}$") plt.tick_params(labelleft='off') plt.subplots_adjust(wspace=0.03) return rmsModel, ml, chi2, flux
def __init__(self, name, path='.', burnin=0, best='median'): self.data = load(name, path=path) # load model data into class self.ndim = self.data['ndim'] self.nwalkers = self.data['nwalkers'] self.chain = self.data['rst']['chain'] self.goodchains = self.data['rst']['goodchains'] # check good chain fraction if self.goodchains.sum() / float(self.nwalkers) < 0.6: self.goodchains = np.ones_like(self.goodchains, dtype=bool) print('Warning - goodchain fraction less than 0.6') print('Acceptance fraction:') print(self.data['rst']['acceptance_fraction']) self.lnprob = self.data['rst']['lnprobability'] self.flatchain = self.chain[self.goodchains, burnin:, :].reshape(-1, self.ndim) self.flatlnprob = self.lnprob[self.goodchains, burnin:].reshape(-1) # estimate the beset model parameters self.medianPars = estimatePrameters(self.flatchain, flatlnprob=self.flatlnprob) self.meanPars = estimatePrameters(self.flatchain, flatlnprob=self.flatlnprob, method='mean') self.peakPars = estimatePrameters(self.flatchain, flatlnprob=self.flatlnprob, method='peak') self.maxPars = estimatePrameters(self.flatchain, flatlnprob=self.flatlnprob, method='max') # estimate the errors percentile = np.percentile(self.flatchain, [16, 50, 84], axis=0) self.errPars = np.zeros([2, percentile.shape[1]]) self.errPars[0, :] = percentile[0, :] - percentile[1, :] self.errPars[1, :] = percentile[2, :] - percentile[1, :] # choose the best parameter type switch = { 'median': self.medianPars, 'mean': self.meanPars, 'peak': self.peakPars, 'max': self.maxPars } bestPars = switch[best] self.bestPars_list = bestPars self.bestPars = {} for i, key in enumerate(self.data['JAMpars']): self.bestPars[key] = bestPars[i] # model inclination cosinc = self.bestPars.get('cosinc', np.pi / 2.0) self.inc = np.arccos(cosinc) if 'ml' in self.bestPars.keys(): self.ml = self.bestPars['ml'] else: if 'logml' in self.bestPars.keys(): self.ml = 10**self.bestPars['logml'] else: print('Waring - do not find ml parameter, set to 1.0') self.ml = 1.0 # load observational data self.dist = self.data['distance'] self.pc = self.dist * np.pi / 0.648 self.lum2d = self.data['lum2d'] self.pot2d = self.data['pot2d'] self.xbin = self.data['xbin'] self.ybin = self.data['ybin'] self.rms = self.data['rms'].clip(0.0, 600.0) self.goodbins = self.data['goodbins'] self.symetrizedRms = self.rms.copy() self.symetrizedRms[self.goodbins] = \ symmetrize_velfield(self.xbin[self.goodbins], self.ybin[self.goodbins], self.rms[self.goodbins]) # run a JAM model with the choosen best model parameters JAMmodel = self.data['JAM'] # restore JAM model JAMlnprob = self.data['lnprob'] self.rmsModel = JAMlnprob(bestPars, model=self.data, returnType='rmsModel') self.flux = JAMlnprob(bestPars, model=self.data, returnType='flux') self.shape = self.data['shape'] # create stellar mass mge objected (used in mass profile) self.LmMge = util_mge.mge(self.pot2d, inc=self.inc, shape=self.shape, dist=self.dist) # set black hole mge object bh3dmge = JAMmodel.mge_bh if bh3dmge is None: self.BhMge = None else: bh2dmge = util_mge.projection(bh3dmge, inc=self.inc) self.BhMge = util_mge.mge(bh2dmge, inc=self.inc) if self.data['type'] == 'massFollowLight': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M/L}$' ] self.DmMge = None # no dark halo elif self.data['type'] == 'spherical_gNFW': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$' ] # create dark halo mass mge object dh = JAMlnprob(bestPars, model=self.data, returnType='dh') dh_mge3d = dh.mge3d() self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc) elif self.data['type'] == 'spherical_gNFW_logml': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{logM^*/L}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$' ] # create dark halo mass mge object dh = JAMlnprob(bestPars, model=self.data, returnType='dh') dh_mge3d = dh.mge3d() self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc) elif self.data['type'] == 'spherical_gNFW_gas': inc = self.inc Beta = np.zeros(self.lum2d.shape[0]) + bestPars[1] ml = bestPars[2] logrho_s = bestPars[3] rs = bestPars[4] gamma = bestPars[5] dh = util_dm.gnfw1d(10**logrho_s, rs, gamma) dh_mge3d = dh.mge3d() gas3d = self.data['gas3d'] dh_mge3d = np.append(gas3d, dh_mge3d, axis=0) self.rmsModel = JAMmodel.run(inc, Beta, ml=ml, mge_dh=dh_mge3d) self.flux = JAMmodel.flux self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$' ] # create dark halo mass mge object self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc) elif self.data['type'] == 'spherical_gNFW_gradient': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{M^*/L}$', r'$\mathbf{\log \Delta_{IMF}}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$' ] # create dark halo mass mge object dh = JAMlnprob(bestPars, model=self.data, returnType='dh') dh_mge3d = dh.mge3d() self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc) elif self.data['type'] == 'spherical_total_dpl': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$' ] # create dark halo mass mge object dh = JAMlnprob(bestPars, model=self.data, returnType='dh') dh_mge3d = dh.mge3d() self.DmMge = util_mge.mge(dh.mge2d(), inc=self.inc) elif self.data['type'] == 'oblate_total_dpl': self.labels = [ r'$\mathbf{cosi}$', r'$\mathbf{\beta}$', r'$\mathbf{log\ \rho_s}$', r'$\mathbf{r_s}$', r'$\mathbf{\gamma}$', r'$\mathbf{q}$' ] # create dark halo mass mge object dh = JAMlnprob(bestPars, model=self.data, returnType='dh') dh_mge3d = dh.mge3d() self.DmMge = util_mge.mge(dh.mge2d(self.inc), inc=self.inc) else: raise ValueError('model type {} not supported'.format( self.data['type']))