forked from me-manu/PhotALPsConv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conversion_ICM.py
381 lines (317 loc) · 10.7 KB
/
conversion_ICM.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
"""
Class for the calculation of photon-ALPs conversion in galaxy clusters
History:
- 06/01/12: created
- 07/18/13: cleaned up
"""
__version__=0.02
__author__="M. Meyer // manuel.meyer@physik.uni-hamburg.de"
import numpy as np
from math import ceil
import eblstud.ebl.tau_from_model as Tau
from eblstud.misc.constants import *
import logging
import warnings
from numpy.random import rand, seed
from PhotALPsConv.Bturb import Bgaussian as Bgaus
# --- Conversion without absorption, designed to match values in Clusters -------------------------------------------#
from deltas import *
class PhotALPs_ICM(object):
"""
Class for photon ALP conversion in galaxy clusters and the intra cluster medium (ICM)
Attributes
----------
Lcoh: coherence length / domain size of turbulent B-field in the cluster in kpc
B: field strength of transverse component of the cluster B-field, in muG
r_abell: size of cluster filled with the constant B-field in kpc
g: Photon ALP coupling in 10^{-11} GeV^-1
m: ALP mass in neV
n: thermal electron density in the cluster, in 10^{-3} cm^-3
Nd: number of domains, Lcoh/r_abell
Psin: random angle in domain n between transverse B field and propagation direction
T1: Transfer matrix 1 (3x3xNd)-matrix
T2: Transfer matrix 2 (3x3xNd)-matrix
T3: Transfer matrix 3 (3x3xNd)-matrix
Un: Total transfer matrix in all domains (3x3xNd)-matrix
Dperp: Mixing matrix parameter Delta_perpedicular in n-th domain
Dpar: Mixing matrix parameter Delta_{||} in n-th domain
Dag: Mixing matrix parameter Delta_{a\gamma} in n-th domain
Da: Mixing matrix parameter Delta_{a} in n-th domain
alph: Mixing angle
Dosc: Oscillation Delta
EW1: Eigenvalue 1 of mixing matrix
EW2: Eigenvalue 2 of mixing matrix
EW3: Eigenvalue 3 of mixing matrix
Notes
-----
For Photon - ALP mixing theory see e.g. De Angelis et al. 2011 and also Horns et al. 2012
http://adsabs.harvard.edu/abs/2011PhRvD..84j5030D
http://adsabs.harvard.edu/abs/2012PhRvD..86g5024H
"""
def __init__(self, **kwargs):
"""
init photon axion conversion in intracluster medium
Parameters
----------
Lcoh: coherence length / domain size of turbulent B-field in the cluster in kpc, default: 10 kpc
B: field strength of transverse component of the cluster B-field, in muG, default: 1 muG
r_abell: size of cluster filled with the constant B-field in kpc. default: 1500 * h
g: Photon ALP coupling in 10^{-11} GeV^-1, default: 1.
m: ALP mass in neV, default: 1.
n: thermal electron density in the cluster, in 10^{-3} cm^-3, default: 1.
Bn_const: boolean, if True n and B are constant all over the cluster
if False than B and n are modeled, see notes
Bgauss: boolean, if True, B field calculated from gaussian turbulence spectrum,
if False then domain-like structure is assumed.
kH: float, upper wave number cutoff, should be at at least > 1. / osc. wavelength (default = 200 / (1 kpc))
kL: float, lower wave number cutoff, should be of same size as the system (default = 1 / (r_abell kpc))
q: float, power-law turbulence spectrum (default: q = 11/3 is Kolmogorov type spectrum)
dkType: string, either linear, log, or random. Determine the spacing of the dk intervals
dkSteps: int, number of dkSteps. For log spacing, number of steps per decade / number of decades ~ 10
should be chosen.
r_core: Core radius for n and B modeling in kpc, default: 200 kpc
beta: power of n dependence, default: 2/3
eta: power with what B follows n, see Notes. Typical values: 0.5 <= eta <= 1. default: 1.
Returns
-------
Nothing.
Notes
-----
If Bn_const = False then electron density is modeled according to Carilli & Taylor (2002) Eq. 2:
n_e(r) = n * (1 - (r/r_core)**2.)**(-3/2*beta)
with typical values of r_core = 200 kpc and beta = 2/3.
The magnetic field is supposed to follow n_e(r) with (Feretti et al. 2012, p. 41, section 7.1)
B(r) = B * (n_e(r)/n) ** eta
with typical values 1 muG <= B <= 15muG and 0.5 <= eta <= 1
"""
# --- Set the defaults
kwargs.setdefault('g',1.)
kwargs.setdefault('m',1.)
kwargs.setdefault('B',1.)
kwargs.setdefault('n',1.)
kwargs.setdefault('Lcoh',10.)
kwargs.setdefault('r_abell',100.)
kwargs.setdefault('r_core',200.)
kwargs.setdefault('E_GeV',1.)
kwargs.setdefault('B_gauss',False)
kwargs.setdefault('kL',0.)
kwargs.setdefault('kH',15.)
kwargs.setdefault('q',-11. / 3.)
kwargs.setdefault('dkType','log')
kwargs.setdefault('dkSteps',0)
kwargs.setdefault('Bn_const',True)
kwargs.setdefault('beta',2. / 3.)
kwargs.setdefault('eta',1.)
# --------------------
self.update_params(**kwargs)
super(PhotALPs_ICM,self).__init__()
return
def update_params(self, new_Bn = True, **kwargs):
"""Update all parameters with new values and initialize all matrices
kwargs
------
new_B_n: boolean, if True, recalculate B field and electron density
"""
self.__dict__.update(kwargs)
if self.B_gauss:
if not self.kL:
self.kL = 1. / self.r_abell
kwargs['kL'] = self.kL
self.Lcoh = 1. / self.kH
kwargs['Lcoh'] = self.Lcoh
self.bfield = Bgaus(**kwargs) # init gaussian turbulent field
self.Nd = int(self.r_abell / self.Lcoh) # number of domains, no expansion assumed
self.r = np.linspace(self.Lcoh, self.r_abell + self.Lcoh, int(self.Nd))
if new_Bn:
self.new_B_n()
self.T1 = np.zeros((3,3,self.Nd),np.complex) # Transfer matrices
self.T2 = np.zeros((3,3,self.Nd),np.complex)
self.T3 = np.zeros((3,3,self.Nd),np.complex)
self.Un = np.zeros((3,3,self.Nd),np.complex)
return
def new_B_n(self):
"""
Recalculate Bfield and density, if Kolmogorov turbulence is set to true, new random values for B and Psi are calculated.
"""
if self.B_gauss:
Bt = self.bfield.Bgaus(self.r) # calculate first transverse component
self.bfield.new_random_numbers() # new random numbers
Bu = self.bfield.Bgaus(self.r) # calculate second transverse component
self.B = np.sqrt(Bt ** 2. + Bu ** 2.) # calculate total transverse component
self.Psin = np.arctan2(Bt , Bu) # and angle to x2 (t) axis -- use atan2 to get the quadrants right
if self.Bn_const:
self.n = self.n * np.ones(int(self.Nd)) # assuming a constant electron density over all domains
if not self.B_gauss:
self.B = self.B * np.ones(int(self.Nd)) # assuming a constant B-field over all domains
else:
if np.isscalar(self.n):
n0 = self.n
else:
n0 = self.n[0]
# check for double beta profile
try:
if np.isscalar(self.n2):
n2 = self.n2
else:
n2 = self.n2[0]
try: # check for two different beta values
self.beta2
self.n = n0 * (np.ones(int(self.Nd)) + self.r**2./self.r_core**2.)**(-1.5 * self.beta) +\
n2 * (np.ones(int(self.Nd)) + self.r**2./self.r_core2**2.)**(-1.5 * self.beta2)
self.B = self.B * (self.n / (n0 + n2) )**self.eta
except NameError:
self.n = np.sqrt(n0**2. * (np.ones(int(self.Nd)) + self.r**2./self.r_core**2.)**(-3. * self.beta) +\
n2**2. * (np.ones(int(self.Nd)) + self.r**2./self.r_core2**2.)**(-3. * self.beta) )
self.B = self.B * (self.n / np.sqrt(n0**2. + n2**2.) )**self.eta
except AttributeError:
self.n = n0 * (np.ones(int(self.Nd)) + self.r**2./self.r_core**2.)**(-1.5 * self.beta)
self.B = self.B * (self.n / n0 )**self.eta
return
def new_random_psi(self):
"""
Calculate new random psi values
Parameters:
-----------
None
Returns:
--------
Nothing
"""
self.Psin = 2. * np.pi * rand(1,int(self.Nd))[0] # angle between photon propagation on B-field in i-th domain
return
def __setDeltas(self):
"""
Set Deltas of mixing matrix for each domain
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
self.Dperp = Delta_pl_kpc(self.n,self.E) + 2.*Delta_QED_kpc(self.B,self.E) # np.arrays , self.Nd-dim
self.Dpar = Delta_pl_kpc(self.n,self.E) + 3.5*Delta_QED_kpc(self.B,self.E) # np.arrays , self.Nd-dim
self.Dag = Delta_ag_kpc(self.g,self.B) # np.array, self.Nd-dim
self.Da = Delta_a_kpc(self.m,self.E) * np.ones(int(self.Nd)) # np.ones, so that it is np.array, self.Nd-dim
self.alph = 0.5 * np.arctan2(2. * self.Dag , (self.Dpar - self.Da))
self.Dosc = np.sqrt((self.Dpar - self.Da)**2. + 4.*self.Dag**2.)
return
def __setEW(self):
"""
Set Eigenvalues
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
# Eigen values are all self.Nd-dimensional
self.__setDeltas()
self.EW1 = self.Dperp
self.EW2 = 0.5 * (self.Dpar + self.Da - self.Dosc)
self.EW3 = 0.5 * (self.Dpar + self.Da + self.Dosc)
return
def __setT1n(self):
"""
Set T1 in all domains
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
c = np.cos(self.Psin)
s = np.sin(self.Psin)
self.T1[0,0,:] = c*c
self.T1[0,1,:] = -1. * c*s
self.T1[1,0,:] = self.T1[0,1]
self.T1[1,1,:] = s*s
return
def __setT2n(self):
"""
Set T2 in all domains
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
c = np.cos(self.Psin)
s = np.sin(self.Psin)
ca = np.cos(self.alph)
sa = np.sin(self.alph)
self.T2[0,0,:] = s*s*sa*sa
self.T2[0,1,:] = s*c*sa*sa
self.T2[0,2,:] = -1. * s * sa *ca
self.T2[1,0,:] = self.T2[0,1]
self.T2[1,1,:] = c*c*sa*sa
self.T2[1,2,:] = -1. * c *ca * sa
self.T2[2,0,:] = self.T2[0,2]
self.T2[2,1,:] = self.T2[1,2]
self.T2[2,2,:] = ca * ca
return
def __setT3n(self):
"""
Set T3 in all domains
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
c = np.cos(self.Psin)
s = np.sin(self.Psin)
ca = np.cos(self.alph)
sa = np.sin(self.alph)
self.T3[0,0,:] = s*s*ca*ca
self.T3[0,1,:] = s*c*ca*ca
self.T3[0,2,:] = s*sa*ca
self.T3[1,0,:] = self.T3[0,1]
self.T3[1,1,:] = c*c*ca*ca
self.T3[1,2,:] = c * sa *ca
self.T3[2,0,:] = self.T3[0,2]
self.T3[2,1,:] = self.T3[1,2]
self.T3[2,2,:] = sa*sa
return
def __setUn(self):
"""
Set Transfer Matrix Un in n-th domain
Parameters
----------
None (self only)
Returns
-------
Nothing
"""
self.Un = np.exp(1.j * self.EW1 * self.Lcoh) * self.T1 + \
np.exp(1.j * self.EW2 * self.Lcoh) * self.T2 + \
np.exp(1.j * self.EW3 * self.Lcoh) * self.T3
return
def SetDomainN(self):
"""
Set Transfer matrix in all domains and multiply it
Parameters
----------
None (self only)
Returns
-------
Transfer matrix as 3x3 complex numpy array
"""
if not self.Nd == self.Psin.shape[0]:
raise TypeError("Number of domains (={0:n}) is not equal to number of angles (={1:n})!".format(self.Nd,self.Psin.shape[0]))
self.__setEW()
self.__setT1n()
self.__setT2n()
self.__setT3n()
self.__setUn() # self.Un contains now all 3x3 matrices in all self.Nd domains
# do the martix multiplication
for i in range(self.Un.shape[2]):
if not i:
U = self.Un[:,:,i]
else:
U = np.dot(U,self.Un[:,:,i]) # first matrix on the left
return U