TODO: сделать так, чтобы можно было импортировать
In [1]:
%run ../../utils/load_notebook.py
from instabilities import *
import numpy as np
Коэффициент для учета вклада гелия в массу газа (см. Notes):
In [1]:
He_coeff = 1.34
In [ ]:
In [2]:
def flat_end(argument):
'''декоратор для того, чтобы продолжать функцию на уровне последнего значения'''
def real_decorator(function):
def wrapper(*args, **kwargs):
if args[0] < argument:
return function(*args, **kwargs)
else:
return function(argument, *args[1:], **kwargs)
return wrapper
return real_decorator
Для большой оси: $\sigma^2_{maj} = \sigma^2_{\varphi}\sin^2 i + \sigma^2_{z}\cos^2 i$, следовательно примерные ограничения $$\sigma_{maj} < \frac{\sigma_{maj}}{\sqrt{\sin^2 i + 0.49\cos^2 i}}< \sigma_R = \frac{\sigma_{maj}}{\sqrt{f\sin^2 i + \alpha^2\cos^2 i}} ~< \frac{\sigma_{maj}}{\sqrt{0.5\sin^2 i + 0.09\cos^2 i}} < \frac{\sqrt{2}\sigma_{maj}}{\sin i} (или \frac{\sigma_{maj}}{\sqrt{f}\sin i}),$$ или можно более точную оценку дать, если построить $f$ (сейчас $0.5 < f < 1$).
Для малой оси: $\sigma^2_{min} = \sigma^2_{R}\sin^2 i + \sigma^2_{z}\cos^2 i$ и ограничения $$\sigma_{min} < \frac{\sigma_{min}}{\sqrt{\sin^2 i + 0.49\cos^2 i}} < \sigma_R = \frac{\sigma_{min}}{\sqrt{\sin^2 i + \alpha^2\cos^2 i}} ~< \frac{\sigma_{min}}{\sqrt{\sin^2 i + 0.09\cos^2 i}} < \frac{\sigma_{min}}{\sin i}$$
Соответственно имеем 5 оценок из maj и 4 оценки из min.
In [3]:
# sig_maj_lim=None
# spl_maj=None
# @flat_end(sig_maj_lim)
# def sig_R_maj_minmin(r, spl_maj=spl_maj):
# return spl_maj(r).item()
# @flat_end(sig_maj_lim)
# def sig_R_maj_min(r, spl_maj=spl_maj):
# return spl_maj(r).item()/sqrt(sin_i**2 + 0.49*cos_i**2)
# @flat_end(sig_maj_lim)
# def sig_R_maj_max(r, spl_maj=spl_maj):
# return spl_maj(r).item()/sqrt(0.5*sin_i**2 + 0.09*cos_i**2)
# @flat_end(sig_maj_lim)
# def sig_R_maj_maxmax(r, spl_maj=spl_maj):
# return spl_maj(r)*sqrt(2)/sin_i
# @flat_end(sig_maj_lim)
# def sig_R_maj_maxmaxtrue(r, spl_maj=spl_maj):
# return spl_maj(r)/sin_i/sqrt(sigPhi_to_sigR_real(r))
In [4]:
# sig_min_lim=None
# spl_min=None
# @flat_end(sig_min_lim)
# def sig_R_minor_minmin(r, spl_min=spl_min):
# return spl_min(r).item()
# @flat_end(sig_min_lim)
# def sig_R_minor_min(r, spl_min=spl_min):
# return spl_min(r).item()/sqrt(sin_i**2 + 0.49*cos_i**2)
# @flat_end(sig_min_lim)
# def sig_R_minor_max(r, spl_min=spl_min):
# return spl_min(r).item()/sqrt(sin_i**2 + 0.09*cos_i**2)
# @flat_end(sig_min_lim)
# def sig_R_minor_maxmax(r, spl_min=spl_min):
# return spl_min(r)/sin_i
In [6]:
# TODO: move to proper place
def plot_data_lim(ax, data_lim):
'''Вертикальная линия, обозначающая конец данных'''
ax.axvline(x=data_lim, ls='-.', color='black', alpha=0.5)
def plot_disc_scale(scale, ax, text=None):
'''Обозначает масштаб диска'''
ax.plot([scale, scale], [0., 0.05], '-', lw=6., color='black')
if text:
ax.annotate(text, xy=(scale, 0.025), xytext=(scale, 0.065), textcoords='data', arrowprops=dict(arrowstyle="->"))
def plot_Q_levels(ax, Qs, style='--', color='grey', alpha=0.4):
'''Функция, чтобы рисовать горизонтальные линии различных уровней $Q^{-1}$:'''
for Q in Qs:
ax.axhline(y=1./Q, ls=style, color=color, alpha=alpha)
def plot_2f_vs_1f(ax=None, total_gas_data=None, epicycl=None, gas_approx=None, sound_vel=None, scale=None, sigma_max=None, sigma_min=None, star_density_max=None,
star_density_min=None, data_lim=None, color=None, alpha=0.3, disk_scales=[], label=None):
'''Картинка сравнения 2F и 1F критерия для разных фотометрий и величин sig_R,
куда подается весь газ, результат НЕ исправляется за осесимметричные возмущения.'''
invQg, invQs, invQeff_min = zip(*get_invQeff_from_data(gas_data=total_gas_data,
epicycl=epicycl,
gas_approx=gas_approx,
sound_vel=sound_vel,
scale=scale,
sigma=sigma_max,
star_density=star_density_min))
invQg, invQs, invQeff_max = zip(*get_invQeff_from_data(gas_data=total_gas_data,
epicycl=epicycl,
gas_approx=gas_approx,
sound_vel=sound_vel,
scale=scale,
sigma=sigma_min,
star_density=star_density_max))
# invQg = map(lambda l: l*1.6, invQg)
# invQeff_min = map(lambda l: l*1.6, invQeff_min)
# invQeff_max = map(lambda l: l*1.6, invQeff_max)
rr = zip(*total_gas_data)[0]
ax.fill_between(rr, invQeff_min, invQeff_max, color=color, alpha=alpha, label=label)
ax.plot(rr, invQeff_min, 'd-', color=color, alpha=0.6)
ax.plot(rr, invQeff_max, 'd-', color=color, alpha=0.6)
ax.plot(rr, invQg, 'v-', color='b')
ax.set_ylim(0., 1.5)
ax.set_xlim(0., data_lim+50.)
# plot_SF(ax)
plot_data_lim(ax, data_lim)
for h, annot in disk_scales:
plot_disc_scale(h, ax, annot)
plot_Q_levels(ax, [1., 1.5, 2., 3.])
ax.legend()
Для случая бесконечного тонкого диска: $$\kappa=\frac{3}{R}\frac{d\Phi}{dR}+\frac{d^2\Phi}{dR^2}$$ где $\Phi$ - гравпотенциал, однако его знать не надо, т.к. есть проще формула: $$\kappa=\sqrt{2}\frac{\vartheta_c}{R}\sqrt{1+\frac{R}{\vartheta_c}\frac{d\vartheta_c}{dR}}$$
In [1]:
def epicyclicFreq_real(poly_gas, R, resolution):
'''Честное вычисление эпициклической частоты на расстоянии R для сплайна или полинома'''
try:
return sqrt(2.0) * poly_gas(R) * sqrt(1 + R * poly_gas.deriv()(R) / poly_gas(R)) / (R * resolution )
except:
return sqrt(2.0) * poly_gas(R) * sqrt(1 + R * poly_gas.derivative()(R) / poly_gas(R)) / (R * resolution )
In [ ]:
Два других механизма из http://iopscience.iop.org/article/10.1088/0004-6256/148/4/69/pdf:
Schaye (2004), 'cold gas phase': $$\Sigma_g > 6.1 f_g^{0.3} Z^{-0.3} I^{0.23}$$ или при constant metallicity of 0.1 $Z_{sun}$ and interstellar flux of ionizing photons 10^6 cm−2 s−1: $$\Sigma_g > 6.1 \frac{\Sigma_g}{\Sigma_g + \Sigma_s}$$
In [59]:
def Sigma_crit_S04(gas_dens, r_gas, star_surf_dens):
return 6.1 * gas_dens / (gas_dens + star_surf_dens(r_gas))
Hunter et al (1998), 'competition with shear' according to Leroy: $$\Sigma_A = \alpha_A\frac{\sigma_g A}{\pi G}$$
In [2]:
def oort_a(r, gas_vel):
try:
return 0.5 * (gas_vel(r)/r - gas_vel.deriv()(r))
except:
return 0.5 * (gas_vel(r)/r - gas_vel.derivative()(r))
In [3]:
def Sigma_crit_A(r, gas_vel, alpha, sound_vel):
G = 4.32
return alpha * (sound_vel*oort_a(r, gas_vel)) / (np.pi*G)
In [ ]:
Кривая вращения тонкого диска: $$\frac{v^2}{r} = 2\pi G \Sigma_0 \frac{r}{2h} \left[I_0(\frac{r}{2h})K_0(\frac{r}{2h}) - I_1(\frac{r}{2h})K_1(\frac{r}{2h})\right]$$
In [4]:
from scipy.special import i0, i1, k0, k1
def disc_vel(r, Sigma0, h, scale, Sigma0_2=None, h_2=None):
G = 4.3
bessels = i0(0.5*r/h)*k0(0.5*r/h) - i1(0.5*r/h)*k1(0.5*r/h)
if h_2 is None:
return np.sqrt(2*np.pi*G*Sigma0*r*scale * 0.5*r/h * bessels)
else: #двухдисковая модель
bessels2 = i0(0.5*r/h_2)*k0(0.5*r/h_2) - i1(0.5*r/h_2)*k1(0.5*r/h_2)
return np.sqrt(2*np.pi*G*Sigma0*r*scale * 0.5*r/h * bessels + 2*np.pi*G*Sigma0_2*r*scale * 0.5*r/h_2 * bessels2)
In [ ]:
Функция для печати статистики по фотометриям, куда добавлена также информация о полной массе диска $M_d = 2\pi h^2 \Sigma(0)$ (только нужно учесть, что там пк в arcsec надо перевести):
In [3]:
from tabulate import tabulate
import pandas as pd
def show_all_photometry_table(all_photometry, scale):
'''scale in kpc/arcsec'''
copy = [list(l) for l in all_photometry]
#все это дальше из-за того, что бывают двухдисковые модели и их нужно по другому использовать
for entry in copy:
if type(entry[5]) == tuple:
entry[5] = (round(entry[5][0], 2), round(entry[5][1], 2))
else:
entry[5] = round(entry[5], 2)
for entry in copy:
if type(entry[4]) == tuple:
entry[4] = (round(entry[4][0], 2), round(entry[4][1], 2))
else:
entry[4] = round(entry[4], 2)
for entry in copy:
if type(entry[5]) == tuple:
entry.append(2*math.pi*entry[5][0]**2 * entry[-1][0](0) * (scale * 1000.)**2 +
2*math.pi*entry[5][1]**2 * entry[-1][1](0) * (scale * 1000.)**2)
else:
entry.append(2*math.pi*entry[5]**2 * entry[-1](0) * (scale * 1000.)**2)
for entry in copy:
if type(entry[5]) == tuple:
entry.append(entry[7][0](0) + entry[7][1](0))
else:
entry.append(entry[7](0))
df = pd.DataFrame(data=copy, columns=['Name', 'r_eff', 'mu_eff', 'n', 'mu0_d', 'h_disc', 'M/L', 'surf', 'M_d/M_sun', 'Sigma_0'])
df['M/L'] = df['M/L'].apply(lambda l: '%2.2f'%l)
# df['Sigma_0'] = df['surf'].map(lambda l:l(0))
df['Sigma_0'] = df['Sigma_0'].apply(lambda l: '%2.0f' % l)
# df['M_d/M_sun'] = 2*math.pi*df['h_disc']**2 * df['surf'].map(lambda l:l(0)) * (scale * 1000.)**2
df['M_d/M_sun'] = df['M_d/M_sun'].apply(lambda l: '%.2E.' % l)
df.drop('surf', axis=1, inplace=True)
print tabulate(df, headers='keys', tablefmt='psql', floatfmt=".2f")
In [ ]:
Префикс для плохих фотометрий, чтобы их не брать в конце.
In [1]:
BAD_MODEL_PREFIX = 'b:'
In [ ]:
Функция, которая возвращает суммарный профиль плотности для двухдисковой модели, если это необходимо:
In [1]:
def tot_dens(dens):
if type(dens) == tuple:
star_density = lambda l: dens[0](l) + dens[1](l)
else:
star_density = lambda l: dens(l)
return star_density
In [ ]:
Цикл по стилям линий для того, что бы они отличались там, где их много на одной картинке и цвета сливаются.
In [1]:
from itertools import cycle
lines = ["-","--","-.",":"]
linecycler = cycle(lines)
In [ ]:
Раскрашиваем задний фон в "зебру" с некоей периодичностью.
In [1]:
def foreground_zebra(ax, step, alpha):
for i in range(int(ax.get_xlim()[1])+1):
if i%2 == 0:
ax.axvspan(i*step, (i+1)*step, color='grey', alpha=alpha)
In [ ]:
Сравним с оценкой Romeo & Falstad (2013) https://ui.adsabs.harvard.edu/#abs/2013MNRAS.433.1389R/abstract:
$$Q_N^{-1} = \sum_{i=1}^{N}\frac{W_i}{Q_iT_i}$$где
$$Q_i = \frac{\kappa\sigma_{R,i}}{\pi G\Sigma_i}$$$$T_i= \begin{cases} 1 + 0.6(\frac{\sigma_z}{\sigma_R})^2_i, & \mbox{if } 0.0 \le \frac{\sigma_z}{\sigma_R} \le 0.5, \\ 0.8 + 0.7(\frac{\sigma_z}{\sigma_R})_i, & \mbox{if } 0.5 \le \frac{\sigma_z}{\sigma_R} \le 1.0 \end{cases}$$$$W_i = \frac{2\sigma_{R,m}\sigma_{R,i}}{\sigma_{R,m}^2 + \sigma_{R,i}^2},$$$$m:\ index\ of\ min(T_iQ_i)$$В самой развитой модели 3 компонента - HI, CO, stars. В их же модели полагается $(\sigma_z/\sigma_R)_{CO} = (\sigma_z/\sigma_R)_{HI} = 1$, т.е. $T_{CO} = T_{HI} = 1.5$. Скорость звука я буду полагать в обеих средах равной 11 км/c. Звезды у меня в двух крайних случаях бывают с $\alpha$ равным 0.3 и 0.7, соответственно $T_{0.3} = 1.05;\ T_{0.7}=1.29$.
In [3]:
from math import pi
def romeo_Qinv(r=None, epicycl=None, sound_vel=11., sigma_R=None, star_density=None,
HI_density=None, CO_density=None, alpha=None, scale=None, gas_approx=None, verbose=False, show=False):
G = 4.32
kappa = epicycl(gas_approx, r, scale)
Q_star = kappa*sigma_R(r)/(pi*G*star_density(r))
Q_CO = kappa*sound_vel/(pi*G*CO_density)
Q_HI = kappa*sound_vel/(pi*G*HI_density)
T_CO, T_HI = 1.5, 1.5
if alpha > 0 and alpha <= 0.5:
T_star = 1. + 0.6*alpha**2
else:
T_star = 0.8 + 0.7*alpha
# TODO: оставить только show или verbose
if show:
print 'r={:7.3f} Qg={:7.3f} Qs={:7.3f} Qg^-1={:7.3f} Qs^-1={:7.3f}'.format(r, Q_HI, Q_star, 1./Q_HI, 1./Q_star)
dispersions = [sigma_R(r), sound_vel, sound_vel]
QTs = [Q_star*T_star, Q_HI*T_HI, Q_CO*T_CO]
components = ['star', 'HI', 'H2']
index = QTs.index(min(QTs))
if verbose:
print 'QTs: {}'.format(QTs)
print 'min index: {}'.format(index)
print 'min component: {}'.format(components[index])
sig_m = dispersions[index]
def W_i(sig_m, sig_i):
return 2*sig_m*sig_i/(sig_m**2 + sig_i**2)
return W_i(sig_m, dispersions[0])/QTs[0] + W_i(sig_m, dispersions[1])/QTs[1] + W_i(sig_m, dispersions[2])/QTs[2], components[index]
Для тонкого диска:
In [1]:
def romeo_Qinv_thin(r=None, epicycl=None, sound_vel=11., sigma_R=None, star_density=None,
HI_density=None, CO_density=None, alpha=None, scale=None, gas_approx=None, verbose=False, show=False):
G = 4.32
kappa = epicycl(gas_approx, r, scale)
Q_star = kappa*sigma_R(r)/(pi*G*star_density(r))
Q_CO = kappa*sound_vel/(pi*G*CO_density)
Q_HI = kappa*sound_vel/(pi*G*HI_density)
# TODO: оставить только show или verbose
if show:
print 'r={:7.3f} Qg={:7.3f} Qs={:7.3f} Qg^-1={:7.3f} Qs^-1={:7.3f}'.format(r, Q_HI, Q_star, 1./Q_HI, 1./Q_star)
dispersions = [sigma_R(r), sound_vel, sound_vel]
QTs = [Q_star, Q_HI, Q_CO]
components = ['star', 'HI', 'H2']
index = QTs.index(min(QTs))
if verbose:
print 'QTs: {}'.format(QTs)
print 'min index: {}'.format(index)
print 'min component: {}'.format(components[index])
sig_m = dispersions[index]
def W_i(sig_m, sig_i):
return 2*sig_m*sig_i/(sig_m**2 + sig_i**2)
return W_i(sig_m, dispersions[0])/QTs[0] + W_i(sig_m, dispersions[1])/QTs[1] + W_i(sig_m, dispersions[2])/QTs[2], components[index]
Функция-сравнение с наблюдениями:
In [ ]:
def plot_RF13_vs_2F(r_g_dens=None, HI_gas_dens=None, CO_gas_dens=None, epicycl=None, sound_vel=None, sigma_R_max=None, sigma_R_min=None,
star_density=None, alpha_max=None, alpha_min=None, scale=None, gas_approx=None, thin=True, show=False):
'''Плотности газа передаются не скорр. за гелий.'''
if thin:
romeo_Q = romeo_Qinv_thin
else:
romeo_Q = romeo_Qinv
fig = plt.figure(figsize=[20, 5])
ax = plt.subplot(131)
totgas = zip(r_g_dens, [He_coeff*(l[0]+l[1]) for l in zip(HI_gas_dens, CO_gas_dens)])[1:]
if show:
print 'sig_R_max case:'
romeo_min = []
for r, g, co in zip(r_g_dens, HI_gas_dens, CO_gas_dens):
rom, _ = romeo_Q(r=r, epicycl=epicycl, sound_vel=sound_vel, sigma_R=sigma_R_max,
star_density=star_density, HI_density=He_coeff*g, CO_density=He_coeff*co,
alpha=alpha_min, scale=scale, gas_approx=gas_approx, show=show)
romeo_min.append(rom)
if _ == 'star':
color = 'g'
elif _ == 'HI':
color = 'b'
else:
color = 'm'
ax.scatter(r, rom, 10, marker='o', color=color)
invQg, invQs, invQeff_min = zip(*get_invQeff_from_data(gas_data=totgas,
epicycl=epicycl,
gas_approx=gas_approx,
sound_vel=sound_vel,
scale=scale,
sigma=sigma_R_max,
star_density=star_density))
if show:
print 'sig_R_min case:'
romeo_max = []
for r, g, co in zip(r_g_dens, HI_gas_dens, CO_gas_dens):
rom, _ = romeo_Q(r=r, epicycl=epicycl, sound_vel=sound_vel, sigma_R=sigma_R_min,
star_density=star_density, HI_density=He_coeff*g, CO_density=He_coeff*co,
alpha=alpha_max, scale=scale, gas_approx=gas_approx, show=show)
romeo_max.append(rom)
if _ == 'star':
color = 'g'
elif _ == 'HI':
color = 'b'
else:
color = 'm'
ax.scatter(r, rom, 10, marker = 's', color=color)
invQg, invQs, invQeff_max = zip(*get_invQeff_from_data(gas_data=totgas,
epicycl=epicycl,
gas_approx=gas_approx,
sound_vel=sound_vel,
scale=scale,
sigma=sigma_R_min,
star_density=star_density))
ax.plot(r_g_dens[1:], invQeff_min, '-', alpha=0.5, color='r')
ax.plot(r_g_dens[1:], invQeff_max, '-', alpha=0.5, color='r')
plot_Q_levels(ax, [1., 1.5, 2., 3.])
ax.set_xlim(0)
ax.set_ylim(0)
ax.legend([matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='g', mec='none', marker='o'),
matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='b', mec='none', marker='o'),
matplotlib.lines.Line2D([0], [0], linestyle='none', mfc='m', mec='none', marker='o')],
['star', 'HI', 'H2'], numpoints=1, markerscale=1, loc='upper right') #add custom legend
ax.set_title('RF13: major component')
ax = plt.subplot(132)
ax.plot(romeo_min[1:], invQeff_min, 'o')
ax.plot(romeo_max[1:], invQeff_max, 'o', color='m', alpha=0.5)
ax.set_xlabel('Romeo')
ax.set_ylabel('2F')
ax.set_xlim(0., 1.)
ax.set_ylim(0., 1.)
ax.plot(ax.get_xlim(), ax.get_ylim(), '--')
ax = plt.subplot(133)
ax.plot(r_g_dens[1:], [l[1]/l[0] for l in zip(romeo_min[1:], invQeff_min)], 'o-')
ax.plot(r_g_dens[1:], [l[1]/l[0] for l in zip(romeo_max[1:], invQeff_max)], 'o-', color='m', alpha=0.5)
ax.set_xlabel('R')
ax.set_ylabel('[2F]/[Romeo]');
In [ ]:
Функция для исправления центральной поверхностной яркости за наклон (приведение диска к виду "плашмя"). Взята из http://www.astronet.ru/db/msg/1166765/node20.html, (61).
In [2]:
def mu_face_on(mu0d, cos_i):
return mu0d + 2.5*np.log10(1./cos_i)
In [ ]:
Функция для анализа влияния параметров. Берет стандартные параметры, делает из них списки и прогоняет для всех в списке, после чего измеряет среднее и std. Можно варьировать несколько параметров одновременно.
In [1]:
def plot_param_depend(ax=None, N=None, data_lim=None, color=None, alpha=0.3, disk_scales=[], label=None, **kwargs):
params = kwargs.copy()
for p in params.keys():
if p == 'total_gas_data':
depth = lambda L: isinstance(L, list) and max(map(depth, L))+1 #depth of nested lists
if depth(params[p]) == 1:
params[p] = [params[p]]*N
elif type(params[p]) is not list:
params[p] = [params[p]]*N
result = []
for i in range(N):
invQg, invQs, invQeff_min = zip(*get_invQeff_from_data(gas_data=params['total_gas_data'][i],
epicycl=params['epicycl'][i],
gas_approx=params['gas_approx'][i],
sound_vel=params['sound_vel'][i],
scale=params['scale'][i],
sigma=params['sigma_max'][i],
star_density=params['star_density_min'][i]))
invQg, invQs, invQeff_max = zip(*get_invQeff_from_data(gas_data=params['total_gas_data'][i],
epicycl=params['epicycl'][i],
gas_approx=params['gas_approx'][i],
sound_vel=params['sound_vel'][i],
scale=params['scale'][i],
sigma=params['sigma_min'][i],
star_density=params['star_density_max'][i]))
result.append((invQeff_min, invQeff_max))
rr = zip(*params['total_gas_data'][0])[0]
qmins = []
qmaxs = []
for ind, rrr in enumerate(rr):
qmin = [result[l][0][ind] for l in range(len(result))]
qmax = [result[l][1][ind] for l in range(len(result))]
qmins.append((np.mean(qmin), np.std(qmin)))
qmaxs.append((np.mean(qmax), np.std(qmax)))
ax.errorbar(rr, zip(*qmins)[0], fmt='o-', yerr=zip(*qmins)[1], elinewidth=6, alpha=0.3);
ax.errorbar(rr, zip(*qmaxs)[0], fmt='o-', yerr=zip(*qmaxs)[1])
ax.axhline(y=1., ls='-', color='grey')
ax.set_ylim(0.)
ax.set_xlim(0.)
plot_data_lim(ax, data_lim)
plot_Q_levels(ax, [1., 1.5, 2., 3.]);
In [ ]:
In [ ]:
In [ ]: