comparison mc_rotate.py @ 0:f40d05521dca draft default tip

planemo upload for repository https://github.com/esg-epfl-apc/tools-astro/tree/main/tools commit de01e3c02a26cd6353a6b9b6f8d1be44de8ccd54
author astroteam
date Fri, 25 Apr 2025 19:33:20 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:f40d05521dca
1 import math
2
3 import numpy as np
4
5
6 def mc_rotate(
7 mc_file, jet_half_size_deg, jet_direction_deg, psf_deg=180, rotated_file=None
8 ):
9 if rotated_file is None:
10 rotated_file = f"{mc_file}_{jet_half_size_deg}_{jet_direction_deg}_{psf_deg}"
11
12 jet_half_size = (
13 np.longdouble(jet_half_size_deg) / 180.0 * math.pi
14 ) # jet openning angle
15 jet_direction = (
16 np.longdouble(jet_direction_deg) / 180.0 * math.pi
17 ) # angle between direction to source and jet axes
18
19 if jet_half_size > math.pi or jet_half_size <= 0:
20 jet_half_size = math.pi
21 # here we assume that jet axis is located in y-z plane if angle=0, then jet points towards observer
22 jet_axes = np.array(
23 [0, np.sin(jet_direction), np.cos(jet_direction)], dtype=np.longdouble
24 )
25
26 if jet_direction > math.pi or jet_direction < 0:
27 raise ValueError(
28 "invalid jet_direction_deg param [0, 180] range expected")
29
30 with open(mc_file, "r") as lines, open(rotated_file, "w") as rotated:
31 column_names = []
32 for line in lines:
33 columns = line.split()
34 if len(columns) == 0:
35 continue
36 if columns[0] == "#":
37 if "weight" in columns:
38 column_names = (
39 columns[1:3]
40 + ["Theta", "Phi", "dT_raw/T", "dT_calc/T"]
41 + columns[9:]
42 )
43 print("# " + "\t".join(column_names), file=rotated)
44 else:
45 print(line.strip(), file=rotated)
46 else:
47 particle = np.array(list(map(np.longdouble, columns)))
48 beta = phi = 0 #dTcalc = 0
49
50 r = particle[2:5]
51 dz = r[
52 2
53 # dz is roughly equal to time delay in units of full path (assuming very small deflection angle)
54 ]
55
56 # negative dz may apper in data as a result of rounding errors
57 dz = max(dz, 0) # replacing negative values by zeros
58
59 r[2] = 1.0 - dz # now r is position of particle
60
61 # direction of momentum
62 n = particle[5:8]
63 n[2] = 1.0 - n[2]
64 # compensate rounding errors for n
65 mod_n = np.dot(n, n) ** 0.5
66 n = 1 / mod_n * n
67 t = 0 # calculated delay
68 if dz > 0: # there is some delay
69 r2 = np.dot(r, r)
70 # assert r2 < 1
71 if r2 > 1:
72 r *= 1.0 / np.sqrt(r2)
73 r2 = 1.0
74 # we continue trajectory in the direction of momentun until it reaches the sphere of radius 1.
75 # to find the delay t, we need to solve the following quadratic equation with respect to variable t:
76 # [(x + V_x)*t]^2 + [(y + V_y)*t]^2 + [(z + V_z)*t]^2 = 1
77 # with extra condition t > 0
78 # below is the solution:
79 rV = np.dot(n, r)
80 t = np.sqrt(1.0 - r2 + rV * rV) - rV
81
82 # calculating the end point of the trajectory
83 r = r + t * n
84
85 # normalizing r to 1 to fix the rounding errors
86 modR = np.dot(r, r) ** 0.5
87 r = 1.0 / modR * r
88
89 # now we will construct the rotation of coordinates which converts vector r to 0,0,1
90 # to find the starting direction of the particle momentum
91 dr2 = r[0] ** 2 + r[1] ** 2
92 if dr2 > 0:
93 # building rotation matrix to move x,y,z to 0,0,1
94 norm = 1.0 / (dr2**0.5)
95 # rotation axes:
96 ux = norm * r[1]
97 uy = -norm * r[0]
98 # uz=0
99 cos = r[2]
100 sin = (1 - cos**2) ** 0.5
101
102 toCenter = np.array(
103 [
104 [cos + ux * ux * (1 - cos), ux *
105 uy * (1 - cos), uy * sin],
106 [ux * uy * (1 - cos), cos + uy *
107 uy * (1 - cos), -ux * sin],
108 [-uy * sin, ux * sin, cos],
109 ]
110 )
111
112 zAxes = np.array([0.0, 0.0, 1.0], dtype=np.longdouble)
113 # test
114 error = np.fabs(np.dot(toCenter, r) - zAxes).max()
115 assert error < 1e-7
116 # finding starting direction of the momentum
117 nInitial = np.dot(toCenter, zAxes)
118 norm_nInitial = np.sqrt(np.dot(nInitial, nInitial))
119 nInitial *= (
120 1.0 / norm_nInitial
121 ) # normalize after rotation to avoid rounding errors
122 # calculating angle between starting direction and jet axis
123 startCos = np.dot(nInitial, jet_axes)
124 startAngle = 0
125 if (
126 startCos < 1
127 ): # avoid roundout errors if angle is very close to zero
128 startAngle = np.arccos(startCos)
129 # TODO: here instead of using step function we can modify weight depending
130 # on angular distance from jet axes
131 if (
132 startAngle < jet_half_size
133 ): # if the particle is inside the cone (passes cut)
134 # calculate angle between arrival direction and direction to source
135 cosBeta = np.dot(
136 r, n
137 ) # this angle is invariant (doesn't depend on turns)
138 if (
139 cosBeta < 1
140 ): # avoid rounding errors if angle is very close to zero
141 beta = np.arccos(cosBeta)
142 nFinal = np.dot(toCenter, n).transpose()
143 cosPhi = nFinal[0] / np.sin(
144 beta
145 # second angle (could be useful in assymetric tasks) if beta=0 it is undefined (could be any)
146 )
147 if cosPhi < 1 and cosPhi > -1:
148 phi = np.arccos(
149 cosPhi
150 ) # returns value in interval [0,pi]
151 if nFinal[1] < 0:
152 phi = 2 * math.pi - phi
153
154 # s = math.sin(startAngle+beta)
155 # if s>0.:
156 # dTcalc = (math.sin(startAngle)+math.sin(beta))/s - 1.
157 # dTcalc = (1.-min(modR, 1.))/cosBeta # condition modR > 1 can only occur due to rounding error
158 else:
159 continue
160 elif (
161 jet_direction > jet_half_size
162 ): # no deflection: check if jet is pointing towards observer
163 continue
164
165 if beta * 180.0 / math.pi > psf_deg:
166 continue
167
168 output = [
169 particle[0],
170 particle[1],
171 beta * 180.0 / math.pi,
172 phi * 180.0 / math.pi,
173 dz,
174 t,
175 ] + list(particle[8:])
176 output = ["{0:g}".format(i) for i in output]
177 print(*output, file=rotated)
178
179 return rotated_file