SpiralMap 0.0.0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- SpiralMap/__init__.py +6 -0
- SpiralMap/datafiles/Drimmel2024_cepheids/ArmAttributes_dyoungW1_bw025.pkl +0 -0
- SpiralMap/datafiles/Drimmel_NIR/Drimmel2armspiral.fits +0 -0
- SpiralMap/datafiles/Drimmel_NIR/Readme_spiral_m2_147.txt +5 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm1_X_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm1_Y_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm2_X_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm2_Y_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm3_X_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm3_Y_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm4_X_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm4_Y_hel.npy +0 -0
- SpiralMap/datafiles/Drimmel_NIR/spiral_m2_147.dat +101 -0
- SpiralMap/datafiles/GaiaPVP_cont_2022/GaiaPVP_cont_2022_pproj_contours.pkl +0 -0
- SpiralMap/datafiles/GaiaPVP_cont_2022/over_dens_grid_threshold_0_003_dens.npy +0 -0
- SpiralMap/datafiles/GaiaPVP_cont_2022/xvalues_dens.npy +0 -0
- SpiralMap/datafiles/GaiaPVP_cont_2022/yvalues_dens.npy +0 -0
- SpiralMap/datafiles/Poggio_cont_2021/Poggio_cont_2021_pproj_contours.pkl +0 -0
- SpiralMap/datafiles/Poggio_cont_2021/overdens_grid_locscale03.npy +0 -0
- SpiralMap/datafiles/Poggio_cont_2021/xvalues.npy +0 -0
- SpiralMap/datafiles/Poggio_cont_2021/yvalues.npy +0 -0
- SpiralMap/datafiles/flim.pkl +0 -0
- SpiralMap/datafiles/flim_all.pkl +0 -0
- SpiralMap/datafiles/spiral.bib +136 -0
- SpiralMap/figdir_primer/polar_proj_multiple_models2.png +0 -0
- SpiralMap/models_.py +1127 -0
- SpiralMap/movie_.gif +0 -0
- SpiralMap/mytools.py +223 -0
- SpiralMap/test.py +297 -0
- SpiralMap/version.py +2 -0
- spiralmap-0.0.0.0.2.dist-info/METADATA +49 -0
- spiralmap-0.0.0.0.2.dist-info/RECORD +34 -0
- spiralmap-0.0.0.0.2.dist-info/WHEEL +4 -0
- spiralmap-0.0.0.0.2.dist-info/licenses/LICENSE.md +21 -0
SpiralMap/models_.py
ADDED
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
#######################################################################
|
|
2
|
+
# SpiralMap: a library of the Milky Way's spiral arms
|
|
3
|
+
# History:
|
|
4
|
+
# May 2025: Prusty (IISER Kolkata) & Shourya Khanna (INAF Torino)
|
|
5
|
+
#######################################################################
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
#--------------------------------------------
|
|
9
|
+
# import utilities package / set root
|
|
10
|
+
import os
|
|
11
|
+
from os.path import dirname
|
|
12
|
+
root_ = dirname(__file__)
|
|
13
|
+
dataloc = root_+'/datafiles'
|
|
14
|
+
exec(open(root_+"/mytools.py").read())
|
|
15
|
+
|
|
16
|
+
#--------------------------------------------
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### TO do:
|
|
20
|
+
#1 consistent colours for similar arms
|
|
21
|
+
####################################
|
|
22
|
+
|
|
23
|
+
class spiral_poggio_maps(object):
|
|
24
|
+
# """
|
|
25
|
+
# Class containing spiral arm models from
|
|
26
|
+
# Poggio_2021: Poggio al. 2021 (EDR3 UMS stars)
|
|
27
|
+
# GaiaPVP_2022: Gaia collaboration et al. 2021 (OB stars)
|
|
28
|
+
|
|
29
|
+
# HISTORY:
|
|
30
|
+
# 09 May 2025: Prusty/Khanna
|
|
31
|
+
# """
|
|
32
|
+
|
|
33
|
+
def __init__(self,model_='GaiaPVP_cont_2022'):
|
|
34
|
+
"""Initialize the list of available spiral arms
|
|
35
|
+
and their corresponding plot colors. """
|
|
36
|
+
self.model_ = model_
|
|
37
|
+
self.loc = dataloc + '/'+model_
|
|
38
|
+
self.getarmlist()
|
|
39
|
+
def getarmlist(self):
|
|
40
|
+
"""Initialize the list of available spiral arms
|
|
41
|
+
and their corresponding plot colors. """
|
|
42
|
+
self.arms = np.array(['all'])
|
|
43
|
+
self.armcolour = {'all': 'black'}
|
|
44
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
45
|
+
def info(self):
|
|
46
|
+
"""Collate arm information """
|
|
47
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
48
|
+
dfmodlist = pd.DataFrame(d)
|
|
49
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
50
|
+
def output_(self,plotattrs):
|
|
51
|
+
|
|
52
|
+
xsun = self.xsun
|
|
53
|
+
|
|
54
|
+
flist1 = fcount(self.loc,flist=True,prnt=False)
|
|
55
|
+
func_ = lambda s: 'grid' in s
|
|
56
|
+
overdens_file = list(filter(func_,flist1))[0]
|
|
57
|
+
func_ = lambda s: 'xval' in s
|
|
58
|
+
xval_file = list(filter(func_,flist1))[0]
|
|
59
|
+
func_ = lambda s: 'yval' in s
|
|
60
|
+
yval_file = list(filter(func_,flist1))[0]
|
|
61
|
+
|
|
62
|
+
# # read overdensity contours
|
|
63
|
+
xvalues_overdens=np.load(self.loc+'/'+xval_file)
|
|
64
|
+
yvalues_overdens=np.load(self.loc+'/'+yval_file)
|
|
65
|
+
over_dens_grid=np.load(self.loc+'/'+overdens_file)
|
|
66
|
+
phi1_dens=np.arctan2(yvalues_overdens, -xvalues_overdens)
|
|
67
|
+
Rvalues_dens=sqrtsum(ds=[xvalues_overdens, yvalues_overdens])
|
|
68
|
+
Rgcvalues_dens=sqrtsum(ds=[xvalues_overdens+xsun, yvalues_overdens])
|
|
69
|
+
|
|
70
|
+
fl = pickleread(self.loc+'/'+self.model_+'_pproj_contours.pkl')
|
|
71
|
+
self.dout = {'xhc':xvalues_overdens,'yhc':yvalues_overdens,'xgc':xvalues_overdens+xsun,'ygc':yvalues_overdens}
|
|
72
|
+
self.dout['phi4'] =fl['phi4'].copy()
|
|
73
|
+
self.dout['glon4'] =fl['glon4'].copy()
|
|
74
|
+
self.dout['rgc'] =fl['rgc'].copy()
|
|
75
|
+
self.dout['dhelio'] =fl['dhelio'].copy()
|
|
76
|
+
|
|
77
|
+
# # # # getangular(self)
|
|
78
|
+
|
|
79
|
+
#----overplot spiral arms in overdens----#
|
|
80
|
+
iniz_overdens= 0
|
|
81
|
+
fin_overdens= 1.5
|
|
82
|
+
N_levels_overdens= 2
|
|
83
|
+
levels_overdens1= np.linspace(iniz_overdens,fin_overdens,N_levels_overdens)
|
|
84
|
+
|
|
85
|
+
if plotattrs['polarproj'] == False:
|
|
86
|
+
useclr = plotattrs['armcolour']
|
|
87
|
+
if plotattrs['armcolour'] == '':
|
|
88
|
+
useclr = 'grey'
|
|
89
|
+
cset1 = plt.contourf(self.dout['x'+plotattrs['coordsys'].lower()],self.dout['y'+plotattrs['coordsys'].lower()],over_dens_grid.T,
|
|
90
|
+
levels=levels_overdens1,alpha=0.05,cmap='Greys')
|
|
91
|
+
iniz_overdens= 0.
|
|
92
|
+
fin_overdens= 1.5
|
|
93
|
+
N_levels_overdens= 4
|
|
94
|
+
levels_overdens2= np.linspace(iniz_overdens,fin_overdens,N_levels_overdens)
|
|
95
|
+
cset2 = plt.contour(self.dout['x'+plotattrs['coordsys'].lower()],self.dout['y'+plotattrs['coordsys'].lower()],over_dens_grid.T,levels=levels_overdens2,colors=useclr,linewidths=plotattrs['markersize'])
|
|
96
|
+
|
|
97
|
+
self.xmin,self.xmax =plt.gca().get_xlim()[0].copy(),plt.gca().get_xlim()[1].copy()
|
|
98
|
+
self.ymin,self.ymax =plt.gca().get_ylim()[0].copy(),plt.gca().get_ylim()[1].copy()
|
|
99
|
+
|
|
100
|
+
plt.xlabel('X$_{'+plotattrs['coordsys']+'}$ [Kpc]')
|
|
101
|
+
plt.ylabel('Y$_{'+plotattrs['coordsys']+'}$ [Kpc]')
|
|
102
|
+
|
|
103
|
+
return cset1, cset2
|
|
104
|
+
else:
|
|
105
|
+
plotattrs['linestyle'] = '.'
|
|
106
|
+
_polarproj(self,plotattrs)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TaylorCordesSpiral(object):
|
|
110
|
+
"""
|
|
111
|
+
Taylor & Cordes (1993) Galactic spiral arm model,
|
|
112
|
+
based on radio pulsar observations. The model defines four major spiral arms.
|
|
113
|
+
"""
|
|
114
|
+
def __init__(self):
|
|
115
|
+
self.getarmlist()
|
|
116
|
+
def getarmlist(self):
|
|
117
|
+
"""Set arm names and colours"""
|
|
118
|
+
|
|
119
|
+
self.arms = np.array(['Arm1','Arm2','Arm3','Arm4'])
|
|
120
|
+
self.armcolour = {'Arm1':'yellow','Arm2':'green','Arm3':'blue','Arm4':'purple'}
|
|
121
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
122
|
+
|
|
123
|
+
self.getparams()
|
|
124
|
+
def info(self):
|
|
125
|
+
|
|
126
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
127
|
+
dfmodlist = pd.DataFrame(d)
|
|
128
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
129
|
+
def getparams(self):
|
|
130
|
+
"""Load original spiral parameters from Taylor & Cordes (1993) Table 1.
|
|
131
|
+
|
|
132
|
+
:return: self.params['Arm1','Arm2','Arm3','Arm4'],
|
|
133
|
+
nested dictionary such that,
|
|
134
|
+
self.params['Arm']['theta_deg'] -> Anchor points in galactic longitude (degrees). \
|
|
135
|
+
self.params['Arm']['r_kpc'] ->Corresponding galactocentric radii (kiloparsecs).
|
|
136
|
+
:rtype: dict
|
|
137
|
+
"""
|
|
138
|
+
self.params = { 'Arm1': {'theta_deg': [164, 200, 240, 280, 290, 315, 330],
|
|
139
|
+
'r_kpc': [3.53, 3.76, 4.44, 5.24, 5.36, 5.81, 5.81]},
|
|
140
|
+
|
|
141
|
+
'Arm2':{'theta_deg': [63, 120, 160, 200, 220, 250, 288],
|
|
142
|
+
'r_kpc': [3.76, 4.56, 4.79, 5.70, 6.49, 7.29, 8.20]},
|
|
143
|
+
|
|
144
|
+
'Arm3':{'theta_deg': [52, 120, 170, 180, 200, 220, 252],
|
|
145
|
+
'r_kpc': [4.90, 6.27, 6.49, 6.95, 8.20, 8.89, 9.57]},
|
|
146
|
+
|
|
147
|
+
'Arm4':{'theta_deg': [20, 70, 100, 160, 180, 200, 223],
|
|
148
|
+
'r_kpc': [5.92, 7.06, 7.86, 9.68, 10.37, 11.39, 12.08]}
|
|
149
|
+
}
|
|
150
|
+
def model_(self, arm_name):
|
|
151
|
+
"""
|
|
152
|
+
Generate arm coordinates using cubic spline interpolation.
|
|
153
|
+
|
|
154
|
+
:param arm_name: Must be one of: 'Arm1', 'Arm2', 'Arm3', 'Arm4'.
|
|
155
|
+
:type arm_name: String
|
|
156
|
+
:return: (x_hc, y_hc, x_gc, y_gc)
|
|
157
|
+
:rtype: tuple
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
self.getparams()
|
|
161
|
+
arm_data = self.params[arm_name]
|
|
162
|
+
theta = np.deg2rad(arm_data['theta_deg']) # Convert to radians
|
|
163
|
+
r = np.array(arm_data['r_kpc'])
|
|
164
|
+
|
|
165
|
+
# Cubic spline interpolation for smooth curve
|
|
166
|
+
cs = CubicSpline(theta, r)
|
|
167
|
+
theta_fine = np.linspace(min(theta), max(theta), 300)
|
|
168
|
+
r_fine = cs(theta_fine)
|
|
169
|
+
|
|
170
|
+
# Convert to Cartesian coordinates (Galacto-Centric)
|
|
171
|
+
|
|
172
|
+
xgc = r_fine * np.sin(theta_fine)
|
|
173
|
+
ygc = -r_fine * np.cos(theta_fine)
|
|
174
|
+
|
|
175
|
+
# rotate by 90 anti-clockwise to match with our convention
|
|
176
|
+
rot_ang = np.radians(90)
|
|
177
|
+
x_gc = (xgc*np.cos(rot_ang)) - (ygc*np.sin(rot_ang) )
|
|
178
|
+
y_gc = (xgc*np.sin(rot_ang)) + (ygc*np.cos(rot_ang) )
|
|
179
|
+
|
|
180
|
+
# Convert to Heliocentric coordinates
|
|
181
|
+
x_hc = x_gc + self.R0 # Sun at (-R0, 0) in GC
|
|
182
|
+
|
|
183
|
+
return x_hc, y_gc, x_gc, y_gc
|
|
184
|
+
def output_(self,arm):
|
|
185
|
+
"""
|
|
186
|
+
Get arm coordinates in structured format.
|
|
187
|
+
|
|
188
|
+
:param arm: Arm identifier (e.g., 'Arm1')
|
|
189
|
+
:type arm: String
|
|
190
|
+
:return: self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
191
|
+
:rtype: dict
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
xsun = self.xsun
|
|
195
|
+
self.R0 = -xsun # Solar Galactocentric radius (kpc)
|
|
196
|
+
xhc,yhc,xgc,ygc = self.model_(arm);
|
|
197
|
+
self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class spiral_houhan(object):
|
|
201
|
+
"""Hou & Han (2014) polynomial-logarithmic spiral arm model
|
|
202
|
+
|
|
203
|
+
Implements the Milky Way spiral structure model from:
|
|
204
|
+
"The spiral structure of the Milky Way from classical Cepheids" (Hou & Han 2014)
|
|
205
|
+
using polynomial-logarithmic spiral functions. Provides 6 major arm segments.
|
|
206
|
+
"""
|
|
207
|
+
def __init__(self):
|
|
208
|
+
self.getarmlist()
|
|
209
|
+
def getarmlist(self):
|
|
210
|
+
"""Set arm names and colours"""
|
|
211
|
+
self.arms = np.array(['Norma','Scutum-Centaurus','Sagittarius-Carina','Perseus','Local','Outer'])
|
|
212
|
+
self.armcolour = {'Norma':'black','Scutum-Centaurus':'red','Sagittarius-Carina':'green','Perseus':'blue','Local':'purple','Outer':'gold'}
|
|
213
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
214
|
+
def info(self):
|
|
215
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
216
|
+
dfmodlist = pd.DataFrame(d)
|
|
217
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
218
|
+
|
|
219
|
+
def getparams(self):
|
|
220
|
+
"""
|
|
221
|
+
Load spiral parameters from Hou & Han (2014) Table 4.
|
|
222
|
+
|
|
223
|
+
:return: params ( Nested dictionary containing for each arm).
|
|
224
|
+
|
|
225
|
+
a, b, c, d: Polynomial coefficients.
|
|
226
|
+
|
|
227
|
+
θ_start: Start angle in degrees (Galactic longitude).
|
|
228
|
+
|
|
229
|
+
θ_end: End angle in degrees.
|
|
230
|
+
:rtype: dict
|
|
231
|
+
"""
|
|
232
|
+
params = {
|
|
233
|
+
'Norma': {'a': 1.1668, 'b': 0.1198, 'c': 0.002557, 'd': 0.0, 'θ_start': 40, 'θ_end': 250},
|
|
234
|
+
'Scutum-Centaurus': {'a': 5.8002, 'b': -1.8188, 'c': 0.2352, 'd': -0.008999, 'θ_start': 275, 'θ_end': 620},
|
|
235
|
+
'Sagittarius-Carina': {'a': 4.2300, 'b': -1.1505, 'c': 0.1561, 'd': -0.005898, 'θ_start': 275, 'θ_end': 570},
|
|
236
|
+
'Perseus': {'a': 0.9744, 'b': 0.1405, 'c': 0.003995, 'd': 0.0, 'θ_start': 280, 'θ_end': 500},
|
|
237
|
+
'Local': {'a': 0.9887, 'b': 0.1714, 'c': 0.004358, 'd': 0.0, 'θ_start': 280, 'θ_end': 475},
|
|
238
|
+
'Outer': {'a': 3.3846, 'b': -0.6554, 'c': 0.08170, 'd': 0.0, 'θ_start': 280, 'θ_end': 355}
|
|
239
|
+
}
|
|
240
|
+
return params
|
|
241
|
+
def polynomial_log_spiral(self, θ, a, b, c, d):
|
|
242
|
+
"""Calculate radius using polynomial-logarithmic spiral equation.
|
|
243
|
+
|
|
244
|
+
Parameters
|
|
245
|
+
----------
|
|
246
|
+
θ : float or ndarray
|
|
247
|
+
Galactic longitude angle in degrees
|
|
248
|
+
a,b,c,d : float
|
|
249
|
+
Polynomial coefficients from Hou & Han Table 4
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
float or ndarray
|
|
254
|
+
Galactocentric radius in kiloparsecs
|
|
255
|
+
|
|
256
|
+
Notes
|
|
257
|
+
-----
|
|
258
|
+
Implements equation:
|
|
259
|
+
R(θ) = exp(a + bθ_rad + cθ_rad² + dθ_rad³)
|
|
260
|
+
where θ_rad = np.radians(θ)
|
|
261
|
+
"""
|
|
262
|
+
return np.exp(a + b*np.radians(θ) + c*np.radians(θ)**2 + d*np.radians(θ)**3)
|
|
263
|
+
|
|
264
|
+
def model_(self, arm_name, n_points=500):
|
|
265
|
+
|
|
266
|
+
params_ = self.getparams()
|
|
267
|
+
params = params_[arm_name]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
θ = np.linspace(params['θ_start'], params['θ_end'], n_points)
|
|
271
|
+
R = self.polynomial_log_spiral(θ, params['a'], params['b'], params['c'], params['d'])
|
|
272
|
+
|
|
273
|
+
# Convert to Cartesian coordinates (Galactocentric)
|
|
274
|
+
x_gc = R*np.cos(np.radians(θ))
|
|
275
|
+
y_gc = R * np.sin(np.radians(θ))
|
|
276
|
+
|
|
277
|
+
# Convert to Heliocentric coordinates
|
|
278
|
+
x_hc = (x_gc + self.R0)
|
|
279
|
+
|
|
280
|
+
return x_hc, y_gc, x_gc, y_gc
|
|
281
|
+
|
|
282
|
+
def output_(self, arm):
|
|
283
|
+
"""
|
|
284
|
+
Get arm coordinates in structured format.
|
|
285
|
+
|
|
286
|
+
:param arm: Arm identifier (e.g., 'Arm1')
|
|
287
|
+
:type arm: String
|
|
288
|
+
:return: self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
289
|
+
:rtype: dict
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
xsun = self.xsun
|
|
293
|
+
self.R0 = -xsun # Solar Galactocentric radius (kpc)
|
|
294
|
+
# Generate spiral arm coordinates
|
|
295
|
+
xhc, yhc, xgc, ygc = self.model_(arm)
|
|
296
|
+
self.dout = {
|
|
297
|
+
'xhc': xhc,
|
|
298
|
+
'yhc': yhc,
|
|
299
|
+
'xgc': xgc,
|
|
300
|
+
'ygc': ygc
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class spiral_levine(object):
|
|
305
|
+
"""
|
|
306
|
+
Levine et al (2006) logarithmic spiral arm model for the Milky Way.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
def __init__(self):
|
|
310
|
+
self.getarmlist()
|
|
311
|
+
|
|
312
|
+
def getarmlist(self):
|
|
313
|
+
"""Set arm names and colours"""
|
|
314
|
+
self.arms = np.array(['Arm1','Arm2','Arm3','Arm4'])
|
|
315
|
+
self.armcolour = {'Arm1':'yellow','Arm2':'green','Arm3':'blue','Arm4':'purple'}
|
|
316
|
+
self.getparams()
|
|
317
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
318
|
+
|
|
319
|
+
def info(self):
|
|
320
|
+
|
|
321
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
322
|
+
dfmodlist = pd.DataFrame(d)
|
|
323
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
324
|
+
|
|
325
|
+
def getparams(self):
|
|
326
|
+
"""
|
|
327
|
+
:return: self.params['Arm1','Arm2','Arm3','Arm4'], nested dictionary such that,
|
|
328
|
+
|
|
329
|
+
self.params['Arm']['pitch'] -> pitch angle
|
|
330
|
+
|
|
331
|
+
self.params['Arm']['phi0'] -> Solar crossing angle)
|
|
332
|
+
:rtype: dict
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
self.arms_model = {
|
|
336
|
+
'Arm1': {'pitch': 24, 'phi0': 56},
|
|
337
|
+
'Arm2': {'pitch': 24, 'phi0': 135},
|
|
338
|
+
'Arm3': {'pitch': 25, 'phi0': 189},
|
|
339
|
+
'Arm4': {'pitch': 21, 'phi0': 234}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def model_(self,arm_name, R_max=25, n_points=1000):
|
|
343
|
+
|
|
344
|
+
"""Generate logarithmic spiral coordinates for specified arm.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
arm_name : str
|
|
349
|
+
Name of arm to model (must be in ['Arm1', 'Arm2', 'Arm3', 'Arm4'])
|
|
350
|
+
R_max : float, optional
|
|
351
|
+
Maximum galactocentric radius to model (kpc), default=25
|
|
352
|
+
n_points : int, optional
|
|
353
|
+
Number of points to sample along the spiral, default=1000
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
tuple
|
|
358
|
+
(x_hc, y_hc, x_gc, y_gc) coordinate arrays where:
|
|
359
|
+
- x_hc, y_hc: Heliocentric coordinates (kpc)
|
|
360
|
+
- x_gc, y_gc: Galactocentric coordinates (kpc)
|
|
361
|
+
|
|
362
|
+
Raises
|
|
363
|
+
------
|
|
364
|
+
ValueError
|
|
365
|
+
If invalid arm_name is provided
|
|
366
|
+
|
|
367
|
+
Notes
|
|
368
|
+
-----
|
|
369
|
+
Implements the logarithmic spiral equation:
|
|
370
|
+
R(φ) = R₀ * exp[(φ - φ₀) * tan(i)]
|
|
371
|
+
where:
|
|
372
|
+
- R₀ is solar galactocentric distance
|
|
373
|
+
- i is pitch angle
|
|
374
|
+
- φ₀ is solar crossing angle
|
|
375
|
+
- φ is the angular coordinate
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
params = self.arms_model[arm_name]
|
|
379
|
+
pitch_rad = np.radians(params['pitch'])
|
|
380
|
+
phi0_rad = np.radians(params['phi0'])
|
|
381
|
+
|
|
382
|
+
# Calculate maximum phi to reach R_max
|
|
383
|
+
phi_max = phi0_rad + (np.log(R_max/self.R0)/np.tan(pitch_rad))
|
|
384
|
+
|
|
385
|
+
# Generate angular range
|
|
386
|
+
phi = np.linspace(phi0_rad, phi_max, n_points) #n_
|
|
387
|
+
|
|
388
|
+
# Logarithmic spiral equation
|
|
389
|
+
R = self.R0 * np.exp((phi - phi0_rad) * np.tan(pitch_rad))
|
|
390
|
+
|
|
391
|
+
# Convert to Cartesian coordinates
|
|
392
|
+
x_gc = R * np.cos(phi)
|
|
393
|
+
y_gc = R * np.sin(phi)
|
|
394
|
+
|
|
395
|
+
# Convert to Heliocentric coordinates
|
|
396
|
+
x_hc = x_gc + self.R0
|
|
397
|
+
|
|
398
|
+
return x_hc, y_gc,x_gc, y_gc
|
|
399
|
+
|
|
400
|
+
def output_(self, arm):
|
|
401
|
+
|
|
402
|
+
"""
|
|
403
|
+
Get arm coordinates in structured format.
|
|
404
|
+
|
|
405
|
+
:param arm: Arm identifier (e.g., 'Arm1')
|
|
406
|
+
:type arm: String
|
|
407
|
+
:return: self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
408
|
+
:rtype: dict
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
xsun = self.xsun
|
|
412
|
+
self.R0 = -xsun
|
|
413
|
+
xhc, yhc, xgc, ygc = self.model_(arm)
|
|
414
|
+
self.dout = {
|
|
415
|
+
'xhc': xhc,
|
|
416
|
+
'yhc': yhc,
|
|
417
|
+
'xgc': xgc,
|
|
418
|
+
'ygc': ygc}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class spiral_drimmel_cepheids(object):
|
|
422
|
+
|
|
423
|
+
def __init__(self):
|
|
424
|
+
self.loc = dataloc+'/Drimmel2024_cepheids'
|
|
425
|
+
self.fname = 'ArmAttributes_dyoungW1_bw025.pkl'
|
|
426
|
+
self.getarmlist()
|
|
427
|
+
def getarmlist(self):
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
self.spirals = pickleread(self.loc+'/'+self.fname)
|
|
431
|
+
self.arms= np.array(list(self.spirals['0']['arm_attributes'].keys()))
|
|
432
|
+
self.armcolour = {'Scutum':'C3','Sag-Car':'C0',
|
|
433
|
+
'Orion':'C1','Perseus':'C2'}
|
|
434
|
+
|
|
435
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
436
|
+
def info(self):
|
|
437
|
+
'''
|
|
438
|
+
here goes basic info for the user about this model
|
|
439
|
+
'''
|
|
440
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
441
|
+
dfmodlist = pd.DataFrame(data=d)
|
|
442
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
443
|
+
|
|
444
|
+
def output_(self,arm):
|
|
445
|
+
|
|
446
|
+
xsun = self.xsun
|
|
447
|
+
rsun = -xsun
|
|
448
|
+
spirals = self.spirals
|
|
449
|
+
arms = self.arms
|
|
450
|
+
|
|
451
|
+
# XY positions
|
|
452
|
+
lnrsun = np.log(rsun)
|
|
453
|
+
|
|
454
|
+
# best phi range:
|
|
455
|
+
phi_range = np.deg2rad(np.sort(self.spirals['1']['phi_range'].copy()))
|
|
456
|
+
maxphi_range = np.deg2rad([60,-120])
|
|
457
|
+
|
|
458
|
+
pang = (spirals['1']['arm_attributes'][arm]['arm_pang_strength']+spirals['1']['arm_attributes'][arm]['arm_pang_prom'])/2.
|
|
459
|
+
lnr0 = (spirals['1']['arm_attributes'][arm]['arm_lgr0_strength']+spirals['1']['arm_attributes'][arm]['arm_lgr0_prom'])/2.
|
|
460
|
+
|
|
461
|
+
phi=(np.arange(51)/50.)*np.diff(phi_range)[0] + phi_range[0]
|
|
462
|
+
lgrarm = lnr0 - np.tan(np.deg2rad(pang))*phi
|
|
463
|
+
|
|
464
|
+
xgc = -np.exp(lgrarm)*np.cos(phi); xhc = xgc - xsun
|
|
465
|
+
ygc = np.exp(lgrarm)*np.sin(phi) ; yhc = ygc
|
|
466
|
+
|
|
467
|
+
# extrapolate the arms
|
|
468
|
+
phi=(np.arange(101)/100.)*np.diff(maxphi_range)[0] + maxphi_range[0]
|
|
469
|
+
lgrarm = lnr0 - np.tan(np.deg2rad(pang))*phi
|
|
470
|
+
|
|
471
|
+
xgc_ex = -np.exp(lgrarm)*np.cos(phi); xhc_ex = xgc_ex - xsun
|
|
472
|
+
ygc_ex = np.exp(lgrarm)*np.sin(phi); yhc_ex = ygc_ex
|
|
473
|
+
lonarm = np.arctan((np.exp(lgrarm)*np.sin(phi))/(rsun - np.exp(lgrarm)*np.cos(phi)))
|
|
474
|
+
|
|
475
|
+
rgc = np.sqrt(xgc**2. + ygc**2.)
|
|
476
|
+
rgc_ex = np.sqrt(xgc_ex**2. + ygc_ex**2.)
|
|
477
|
+
|
|
478
|
+
self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc,'xhc_ex':xhc_ex,'yhc_ex':yhc_ex,'xgc_ex':xgc_ex,'ygc_ex':ygc_ex}
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class spiral_drimmel_nir(object):
|
|
482
|
+
"""Drimmel (2000) Near-Infrared (NIR) spiral arm model
|
|
483
|
+
|
|
484
|
+
Implements the 2-arm spiral structure model from:
|
|
485
|
+
Drimmel, R. (2000) "Evidence for a two-armed spiral in the Milky Way"
|
|
486
|
+
using COBE/DIRBE near-infrared data. Includes main arms and phase-shifted interarms.
|
|
487
|
+
|
|
488
|
+
Attributes
|
|
489
|
+
----------
|
|
490
|
+
arms : ndarray
|
|
491
|
+
Array of arm identifiers ['1_arm', '2_arm', '3_interarm', '4_interarm']
|
|
492
|
+
armcolour : dict
|
|
493
|
+
Color mapping for visualization:
|
|
494
|
+
- Main arms: black
|
|
495
|
+
- Interarms: red
|
|
496
|
+
"""
|
|
497
|
+
def __init__(self):
|
|
498
|
+
"""Initialize Drimmel NIR spiral model with default parameters"""
|
|
499
|
+
|
|
500
|
+
self.loc = dataloc+'/Drimmel_NIR'
|
|
501
|
+
self.fname = 'Drimmel2armspiral.fits'
|
|
502
|
+
self.getarmlist()
|
|
503
|
+
def getarmlist(self):
|
|
504
|
+
"""Set arm names and colours"""
|
|
505
|
+
self.arms = np.array(['1_arm','2_arm','3_interarm','4_interarm'])
|
|
506
|
+
self.armcolour = {'1_arm':'black','2_arm':'black','3_interarm':'red','4_interarm':'red'}
|
|
507
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
508
|
+
def info(self):
|
|
509
|
+
# """Display basic model information and arm components."""
|
|
510
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
511
|
+
dfmodlist = pd.DataFrame(d)
|
|
512
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
513
|
+
|
|
514
|
+
def getdata(self):
|
|
515
|
+
"""Load and preprocess spiral arm data from FITS file.
|
|
516
|
+
|
|
517
|
+
1. Loads base FITS data
|
|
518
|
+
|
|
519
|
+
2. Scales coordinates using solar position
|
|
520
|
+
|
|
521
|
+
3. Adds phase-shifted interarm components
|
|
522
|
+
|
|
523
|
+
4. Calculates galactocentric radii
|
|
524
|
+
|
|
525
|
+
Notes
|
|
526
|
+
-----
|
|
527
|
+
- Original data scaled by solar galactocentric distance
|
|
528
|
+
- Phase-shifted arms loaded from separate numpy files
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
dt = fitsread(self.loc+'/'+self.fname)
|
|
532
|
+
self.data0 = dt.copy()
|
|
533
|
+
|
|
534
|
+
xsun = self.xsun
|
|
535
|
+
|
|
536
|
+
# rescaling to |xsun|
|
|
537
|
+
qnts = ['rgc1','xhc1','yhc1','rgc2','xhc2','yhc2']
|
|
538
|
+
for qnt in qnts:
|
|
539
|
+
dt[qnt] = dt[qnt]*abs(xsun)
|
|
540
|
+
#----- add phase-shifted arms as `3` and `4`
|
|
541
|
+
dloc = self.loc+'/phase_shifted'
|
|
542
|
+
for inum in [3,4]:
|
|
543
|
+
dt['xhc'+str(inum)] = np.load(dloc+'/Arm'+str(inum)+'_X_hel.npy')
|
|
544
|
+
dt['yhc'+str(inum)] = np.load(dloc+'/Arm'+str(inum)+'_Y_hel.npy')
|
|
545
|
+
dt['rgc'+str(inum)] = np.sqrt( ((dt['xhc'+str(inum)] + xsun)**2.) + ((dt['yhc'+str(inum)])**2.) )
|
|
546
|
+
#------------------
|
|
547
|
+
|
|
548
|
+
self.data = dt.copy()
|
|
549
|
+
|
|
550
|
+
return
|
|
551
|
+
def output_(self,arm):
|
|
552
|
+
"""Retrieve spiral arm coordinates in specified format.
|
|
553
|
+
|
|
554
|
+
Parameters
|
|
555
|
+
----------
|
|
556
|
+
arm : str
|
|
557
|
+
Arm identifier or selection mode:
|
|
558
|
+
- '1', '2' for main arms
|
|
559
|
+
- '3', '4' for interarms
|
|
560
|
+
- 'all' for all components
|
|
561
|
+
- 'main' for just main arms
|
|
562
|
+
typ_ : {'cartesian', 'polar', 'polargrid'}, default 'cartesian'
|
|
563
|
+
Output format:
|
|
564
|
+
- cartesian: Returns x,y coordinates
|
|
565
|
+
- polar/polargrid: Generates polar coordinate plots
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
-------
|
|
569
|
+
dict
|
|
570
|
+
For cartesian type contains:
|
|
571
|
+
- xhc, yhc: Heliocentric coordinates (kpc)
|
|
572
|
+
- xgc, ygc: Galactocentric coordinates (kpc)
|
|
573
|
+
|
|
574
|
+
Notes
|
|
575
|
+
-----
|
|
576
|
+
Polar modes create matplotlib plots directly using:
|
|
577
|
+
- phi1: Angle from negative x-axis (GC frame)
|
|
578
|
+
- phi4: Galactic longitude (0-360 degrees)
|
|
579
|
+
"""
|
|
580
|
+
xsun = self.xsun
|
|
581
|
+
self.getdata()
|
|
582
|
+
dt = self.data.copy()
|
|
583
|
+
|
|
584
|
+
numbs = [arm]
|
|
585
|
+
if arm == 'all':
|
|
586
|
+
numbs = self.arms
|
|
587
|
+
elif arm == 'main':
|
|
588
|
+
numbs = ['1','2']
|
|
589
|
+
|
|
590
|
+
self.dused = {}
|
|
591
|
+
self.dused['rgc'] = []
|
|
592
|
+
self.dused['xgc'] = []
|
|
593
|
+
self.dused['yhc'] = []
|
|
594
|
+
self.dused['phi1'] = []
|
|
595
|
+
self.dused['phi4'] = []
|
|
596
|
+
|
|
597
|
+
for numb1 in numbs:
|
|
598
|
+
numb = str(int(numb1.split('_')[0]))
|
|
599
|
+
xhc = dt['xhc'+numb]
|
|
600
|
+
yhc = dt['yhc'+numb]
|
|
601
|
+
rgc = dt['rgc'+numb]
|
|
602
|
+
xgc = xhc + xsun
|
|
603
|
+
ygc = yhc
|
|
604
|
+
self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
class reid_spiral(object):
|
|
608
|
+
"""Reid et al. (2019) kinked logarithmic spiral arm model
|
|
609
|
+
|
|
610
|
+
Implements the Milky Way spiral structure model from:
|
|
611
|
+
"Trigonometric Parallaxes of High Mass Star Forming Regions: The Structure and Kinematics of the Milky Way"
|
|
612
|
+
using kinked logarithmic spirals with varying pitch angles. Models 7 major arm features.
|
|
613
|
+
|
|
614
|
+
Attributes
|
|
615
|
+
----------
|
|
616
|
+
arms : ndarray
|
|
617
|
+
Array of arm identifiers ['3-kpc', 'Norma', 'Sct-Cen', 'Sgr-Car', 'Local', 'Perseus', 'Outer']
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
def __init__(self, kcor=False):
|
|
621
|
+
"""Initialize Reid et al. (2019) spiral model
|
|
622
|
+
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
kcor : bool, optional
|
|
626
|
+
Apply distance correction adjustment to R_kink parameters,
|
|
627
|
+
default=False
|
|
628
|
+
"""
|
|
629
|
+
self.kcor = kcor
|
|
630
|
+
self.getarmlist()
|
|
631
|
+
def getarmlist(self):
|
|
632
|
+
"""Set arm names and colours"""
|
|
633
|
+
self.arms = np.array(['3-kpc','Norma','Sct-Cen','Sgr-Car','Local','Perseus','Outer'])
|
|
634
|
+
self.armcolour = {'3-kpc':'C6','Norma':'C5','Sct-Cen':'C4',
|
|
635
|
+
'Sgr-Car':'C3','Local':'C2','Perseus':'C1',
|
|
636
|
+
'Outer':'C0'}
|
|
637
|
+
self.armcolours= [self.armcolour[ky] for ky in self.arms ]
|
|
638
|
+
def info(self):
|
|
639
|
+
d = {'Arm list': self.arms, 'Colour': self.armcolours}
|
|
640
|
+
dfmodlist = pd.DataFrame(d)
|
|
641
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
642
|
+
|
|
643
|
+
def getparams(self,arm):
|
|
644
|
+
"""Load spiral parameters for specified arm from Reid et al. (2019) Table 4.
|
|
645
|
+
|
|
646
|
+
Parameters
|
|
647
|
+
----------
|
|
648
|
+
arm : str
|
|
649
|
+
Valid arm identifier from class arms list
|
|
650
|
+
|
|
651
|
+
Returns
|
|
652
|
+
-------
|
|
653
|
+
dict
|
|
654
|
+
Dictionary containing:
|
|
655
|
+
- beta_kink: Kink angle position in degrees
|
|
656
|
+
|
|
657
|
+
- pitch_low: Pitch angle before kink (degrees)
|
|
658
|
+
|
|
659
|
+
- pitch_high: Pitch angle after kink (degrees)
|
|
660
|
+
|
|
661
|
+
- R_kink: Galactocentric radius at kink (kpc)
|
|
662
|
+
|
|
663
|
+
- beta_min/max: Angular range in degrees
|
|
664
|
+
|
|
665
|
+
- width: Arm width parameter (kpc)
|
|
666
|
+
|
|
667
|
+
Notes
|
|
668
|
+
-----
|
|
669
|
+
Applies correction to R_kink if kcor=True during initialization
|
|
670
|
+
"""
|
|
671
|
+
if arm == '3-kpc':
|
|
672
|
+
params = {'name':arm,'beta_kink':15,
|
|
673
|
+
'pitch_low':-4.2,'pitch_high':-4.2,
|
|
674
|
+
'R_kink':3.52,'beta_min':15,
|
|
675
|
+
'beta_max':18,'width':0.18}
|
|
676
|
+
if arm == 'Norma':
|
|
677
|
+
params = {'name':arm,'beta_kink':18,'pitch_low':-1.,
|
|
678
|
+
'pitch_high':19.5,'R_kink':4.46,'beta_min':5,
|
|
679
|
+
'beta_max':54,'width':0.14}
|
|
680
|
+
if arm == 'Sct-Cen':
|
|
681
|
+
params = {'name':arm,'beta_kink':23,'pitch_low':14.1,
|
|
682
|
+
'pitch_high':12.1,'R_kink':4.91,'beta_min':0,
|
|
683
|
+
'beta_max':104,'width':0.23}
|
|
684
|
+
if arm == 'Sgr-Car': #'Sgr-Car'
|
|
685
|
+
params = {'name':arm,'beta_kink':24,'pitch_low':17.1,
|
|
686
|
+
'pitch_high':1,'R_kink':6.04,'beta_min':2,
|
|
687
|
+
'beta_max':97,'width':0.27}
|
|
688
|
+
if arm == 'Local':
|
|
689
|
+
params = {'name':arm,'beta_kink':9,'pitch_low':11.4,
|
|
690
|
+
'pitch_high':11.4,'R_kink':8.26,'beta_min':-8,
|
|
691
|
+
'beta_max':34,'width':0.31}
|
|
692
|
+
if arm == 'Perseus':
|
|
693
|
+
params = {'name':arm,'beta_kink':40,'pitch_low':10.3,
|
|
694
|
+
'pitch_high':8.7,'R_kink':8.87,'beta_min':-23,
|
|
695
|
+
'beta_max':115,'width':0.35}
|
|
696
|
+
if arm == 'Outer':
|
|
697
|
+
params = {'name':arm,'beta_kink':18,'pitch_low':3,
|
|
698
|
+
'pitch_high':9.4,'R_kink':12.24,'beta_min':-16,
|
|
699
|
+
'beta_max':71,'width':0.65}
|
|
700
|
+
if self.kcor:
|
|
701
|
+
Rreid = 8.15
|
|
702
|
+
diffval = params['R_kink'] - Rreid
|
|
703
|
+
xsun = get_lsr()['xsun']
|
|
704
|
+
if diffval < 0:
|
|
705
|
+
params['R_kink'] = (-xsun) + diffval
|
|
706
|
+
else:
|
|
707
|
+
params['R_kink'] = (-xsun) + diffval
|
|
708
|
+
return params
|
|
709
|
+
|
|
710
|
+
def model_(self,params):
|
|
711
|
+
"""Generate kinked logarithmic spiral coordinates.
|
|
712
|
+
|
|
713
|
+
Parameters
|
|
714
|
+
----------
|
|
715
|
+
params : dict
|
|
716
|
+
Spiral parameters dictionary from getparams()
|
|
717
|
+
|
|
718
|
+
Returns
|
|
719
|
+
-------
|
|
720
|
+
tuple
|
|
721
|
+
(x, y, x1, y1, x2, y2) coordinate arrays where:
|
|
722
|
+
|
|
723
|
+
- x,y: Arm center coordinates (GC)
|
|
724
|
+
|
|
725
|
+
- x1,y1: Inner arm boundary
|
|
726
|
+
|
|
727
|
+
- x2,y2: Outer arm boundary
|
|
728
|
+
|
|
729
|
+
Notes
|
|
730
|
+
-----
|
|
731
|
+
Implements modified logarithmic spiral equation with pitch angle kink:
|
|
732
|
+
R(β) = R_kink * exp[-(β - β_kink) * tan(pitch)]
|
|
733
|
+
where pitch changes at β_kink
|
|
734
|
+
"""
|
|
735
|
+
|
|
736
|
+
beta_kink = np.radians(params['beta_kink'])
|
|
737
|
+
pitch_low = np.radians(params['pitch_low'])
|
|
738
|
+
pitch_high = np.radians(params['pitch_high'])
|
|
739
|
+
R_kink = params['R_kink']
|
|
740
|
+
beta_min = params['beta_min']
|
|
741
|
+
beta_max = params['beta_max']
|
|
742
|
+
width = params['width']
|
|
743
|
+
|
|
744
|
+
beta = np.linspace(beta_min,beta_max,1000)
|
|
745
|
+
beta_min = np.radians(beta_min)
|
|
746
|
+
beta_max = np.radians(beta_max)
|
|
747
|
+
beta = np.radians(beta)
|
|
748
|
+
|
|
749
|
+
pitch = np.zeros(beta.size) + np.nan
|
|
750
|
+
indl = np.where(beta<beta_kink)[0]; pitch[indl] = pitch_low
|
|
751
|
+
indr = np.where(beta>beta_kink)[0]; pitch[indr] = pitch_high
|
|
752
|
+
|
|
753
|
+
tmp1 = (beta - beta_kink)*(np.tan(pitch))
|
|
754
|
+
tmp2 = np.exp(-tmp1)
|
|
755
|
+
|
|
756
|
+
R = R_kink*tmp2
|
|
757
|
+
x = -R*(np.cos(beta))
|
|
758
|
+
y = R*(np.sin(beta))
|
|
759
|
+
|
|
760
|
+
R2 = (R_kink+(width*0.5))*tmp2
|
|
761
|
+
x2 = -R2*(np.cos(beta))
|
|
762
|
+
y2 = R2*(np.sin(beta))
|
|
763
|
+
|
|
764
|
+
R1 = (R_kink-(width*0.5))*tmp2
|
|
765
|
+
x1 = -R1*(np.cos(beta))
|
|
766
|
+
y1 = R1*(np.sin(beta))
|
|
767
|
+
|
|
768
|
+
return x,y, x1,y1,x2,y2
|
|
769
|
+
def output_(self,arm):
|
|
770
|
+
"""
|
|
771
|
+
Get arm coordinates in structured format.
|
|
772
|
+
|
|
773
|
+
:param arm: Arm identifier (e.g., 'Norma')
|
|
774
|
+
:type arm: String
|
|
775
|
+
:return: self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
776
|
+
:rtype: dict
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
xsun = self.xsun
|
|
780
|
+
params = self.getparams(arm)
|
|
781
|
+
|
|
782
|
+
xgc,ygc,xgc1,ygc1,xgc2,ygc2 = self.model_(params);
|
|
783
|
+
xhc = xgc - xsun
|
|
784
|
+
xhc1 = xgc1 - xsun
|
|
785
|
+
xhc2 = xgc2 - xsun
|
|
786
|
+
yhc = ygc
|
|
787
|
+
self.dout = {'xhc':xhc,'yhc':yhc,'xgc':xgc,'ygc':ygc}
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
class main_(object):
|
|
791
|
+
"""
|
|
792
|
+
The main executor that calls the individual models to grab the spiral traces.
|
|
793
|
+
It is also used to set plot preferences and make plots.
|
|
794
|
+
"""
|
|
795
|
+
def __init__(self,Rsun=8.277,print_=True):
|
|
796
|
+
"""
|
|
797
|
+
Initialize main object.
|
|
798
|
+
|
|
799
|
+
:param Rsun: Optional - Galactocentric R(kpc) of the Sun, default=8.277.
|
|
800
|
+
:type Rsun: float
|
|
801
|
+
:param print\_: Optional - if set to False does not print to screen.
|
|
802
|
+
:type print\_: Boolean
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
self.root_ = root_
|
|
806
|
+
self.dataloc = dataloc
|
|
807
|
+
self.xsun = -Rsun
|
|
808
|
+
self.Rsun = Rsun
|
|
809
|
+
self.listmodels()
|
|
810
|
+
self.getinfo(print_=print_)
|
|
811
|
+
|
|
812
|
+
self.modrec = []
|
|
813
|
+
self.armrec = []
|
|
814
|
+
def listmodels(self):
|
|
815
|
+
"""
|
|
816
|
+
Defines list of available models/maps
|
|
817
|
+
Constructs dictionaries to initialise individual model classes
|
|
818
|
+
"""
|
|
819
|
+
|
|
820
|
+
self.models = ['Taylor_Cordes_1992','Drimmel_NIR_2000',
|
|
821
|
+
'Levine_2006','Hou_Han_2014','Reid_2019',
|
|
822
|
+
'Poggio_cont_2021','GaiaPVP_cont_2022','Drimmel_Ceph_2024']
|
|
823
|
+
self.models_class = {'Reid_2019':reid_spiral(),
|
|
824
|
+
'Levine_2006':spiral_levine(),
|
|
825
|
+
'Poggio_cont_2021':spiral_poggio_maps(model_='Poggio_cont_2021'),
|
|
826
|
+
'GaiaPVP_cont_2022':spiral_poggio_maps(model_='GaiaPVP_cont_2022'),
|
|
827
|
+
'Drimmel_NIR_2000':spiral_drimmel_nir(),
|
|
828
|
+
'Taylor_Cordes_1992':TaylorCordesSpiral(),
|
|
829
|
+
'Hou_Han_2014':spiral_houhan(),
|
|
830
|
+
'Drimmel_Ceph_2024':spiral_drimmel_cepheids()}
|
|
831
|
+
|
|
832
|
+
self.models_desc = ['HII','NIR emission',
|
|
833
|
+
'HI','HII/GMC/Masers','MASER parallax',
|
|
834
|
+
'Upper main sequence (map)','OB stars (map)','Cepheids']
|
|
835
|
+
def getinfo(self,model='',print_=True):
|
|
836
|
+
"""
|
|
837
|
+
prints (model list, tracers) & default plot attributes are defined here.
|
|
838
|
+
|
|
839
|
+
:param model: Optional - '' by default so lists all models. otherwise provide a model (ex: Drimmel_Ceph_2024) to list out all arms and default colours.
|
|
840
|
+
:type model: String
|
|
841
|
+
:param print\_: Optional - if set to False does not print to screen.
|
|
842
|
+
:type print\_: Boolean
|
|
843
|
+
"""
|
|
844
|
+
|
|
845
|
+
if model == '':
|
|
846
|
+
print('try self.getinfo(model) for more details')
|
|
847
|
+
dfmodlist = pd.DataFrame(self.models,columns=['Available models & maps:'])
|
|
848
|
+
d = {'Available models & maps:': self.models, 'Description': self.models_desc}
|
|
849
|
+
dfmodlist = pd.DataFrame(d)
|
|
850
|
+
|
|
851
|
+
if print_:
|
|
852
|
+
print(tabulate(dfmodlist, headers = 'keys', tablefmt = 'psql'))
|
|
853
|
+
|
|
854
|
+
else:
|
|
855
|
+
|
|
856
|
+
try:
|
|
857
|
+
spimod = self.models_class[model]
|
|
858
|
+
print('#####################')
|
|
859
|
+
print('Model = '+model)
|
|
860
|
+
spimod.info()
|
|
861
|
+
except KeyError:
|
|
862
|
+
print(' ')
|
|
863
|
+
print(model+' is not in the library, check name !')
|
|
864
|
+
print(' ')
|
|
865
|
+
|
|
866
|
+
self.plotattrs_default = {'plot':False,
|
|
867
|
+
'markersize':3,
|
|
868
|
+
'coordsys':'HC',
|
|
869
|
+
'linewidth':0.5,
|
|
870
|
+
'linestyle': '-',
|
|
871
|
+
'armcolour':'',
|
|
872
|
+
'markSunGC':True,
|
|
873
|
+
'xmin':'',
|
|
874
|
+
'xmax':'',
|
|
875
|
+
'ymin':'',
|
|
876
|
+
'ymax':'',
|
|
877
|
+
'polarproj':False,
|
|
878
|
+
'polargrid':False,
|
|
879
|
+
'dataloc':dataloc}
|
|
880
|
+
def add2plot(self,plotattrs):
|
|
881
|
+
if plotattrs['coordsys'] =='HC':
|
|
882
|
+
plt.plot(0.,0.,marker=r'$\odot$',markersize=plotattrs['markersize'],color='black')
|
|
883
|
+
plt.plot(-self.xsun,0.,marker='*',markersize=plotattrs['markersize'],color='black')
|
|
884
|
+
if plotattrs['coordsys'] =='GC':
|
|
885
|
+
plt.plot(0.,0.,marker='*',markersize=plotattrs['markersize'],color='black')
|
|
886
|
+
plt.plot(self.xsun,0.,marker=r'$\odot$',markersize=plotattrs['markersize'],color='black')
|
|
887
|
+
def xyplot(self,spimod,plotattrs_):
|
|
888
|
+
if plotattrs_['plot'] and plotattrs_['polarproj']==False :
|
|
889
|
+
plt.plot(spimod.dout['x'+plotattrs_['coordsys'].lower()],
|
|
890
|
+
spimod.dout['y'+plotattrs_['coordsys'].lower()],
|
|
891
|
+
plotattrs_['linestyle'],color=plotattrs_['armcolour'])
|
|
892
|
+
if 'xhc_ex' in spimod.dout.keys():
|
|
893
|
+
plt.plot(spimod.dout['x'+plotattrs_['coordsys'].lower()+'_ex'],
|
|
894
|
+
spimod.dout['y'+plotattrs_['coordsys'].lower()+'_ex'],
|
|
895
|
+
'--',color=plotattrs_['armcolour'])
|
|
896
|
+
|
|
897
|
+
plt.xlabel('X$_{'+plotattrs_['coordsys']+'}$ [Kpc]')
|
|
898
|
+
plt.ylabel('Y$_{'+plotattrs_['coordsys']+'}$ [Kpc]')
|
|
899
|
+
if plotattrs_['xmin'] == '' or plotattrs_['xmax'] == '' or plotattrs_['ymin'] == '' or plotattrs_['ymax'] == '':
|
|
900
|
+
rub=1
|
|
901
|
+
else:
|
|
902
|
+
xmin,xmax = plotattrs_['xmin'],plotattrs_['xmax']
|
|
903
|
+
ymin,ymax = plotattrs_['ymin'],plotattrs_['ymax']
|
|
904
|
+
plt.xlim([xmin,xmax])
|
|
905
|
+
plt.ylim([ymin,ymax])
|
|
906
|
+
|
|
907
|
+
self.xmin,self.xmax =plt.gca().get_xlim()[0].copy(),plt.gca().get_xlim()[1].copy()
|
|
908
|
+
self.ymin,self.ymax =plt.gca().get_ylim()[0].copy(),plt.gca().get_ylim()[1].copy()
|
|
909
|
+
|
|
910
|
+
if plotattrs_['markSunGC']:
|
|
911
|
+
self.add2plot(plotattrs_)
|
|
912
|
+
def readout(self,plotattrs={},model='',arm='',print_=False):
|
|
913
|
+
"""
|
|
914
|
+
reads out individual models/ makes plots etc.
|
|
915
|
+
|
|
916
|
+
:param plotattrs: Optional - if not provided, uses default plot attributes.
|
|
917
|
+
:type plotattrs: dict
|
|
918
|
+
:param model: (required otherwise raises exception)
|
|
919
|
+
:type model: String
|
|
920
|
+
:param arm: Optional - (default = '' so reads all arms)
|
|
921
|
+
:type arm: String
|
|
922
|
+
:param print\_: Optional - if set to False does not print to screen.
|
|
923
|
+
:type print\_: Boolean
|
|
924
|
+
:raise RuntimeError: if no model is provided.
|
|
925
|
+
"""
|
|
926
|
+
if model == '':
|
|
927
|
+
raise RuntimeError('model = blank | no model provided \n try self.getino() for list of available models')
|
|
928
|
+
|
|
929
|
+
self.modrec.append(model)
|
|
930
|
+
spimod = self.models_class[model]
|
|
931
|
+
spimod.xsun = self.xsun
|
|
932
|
+
spimod.getarmlist()
|
|
933
|
+
self.armlist = spimod.arms
|
|
934
|
+
self.arm = arm
|
|
935
|
+
|
|
936
|
+
# in case plot attributes are not provided, or incomplete
|
|
937
|
+
for ky in self.plotattrs_default.keys():
|
|
938
|
+
if ky not in list(plotattrs.keys()):
|
|
939
|
+
plotattrs[ky] = self.plotattrs_default[ky]
|
|
940
|
+
plotattrs1 = plotattrs.copy()
|
|
941
|
+
if 'cont' in model.lower():
|
|
942
|
+
spimod.output_(plotattrs1)
|
|
943
|
+
# self.xmin,self.xmax,self.ymin,self.ymax = spimod.xmin,spimod.xmax,spimod.ymin,spimod.ymax
|
|
944
|
+
if (('cont' not in model.lower())&('all' not in arm)):
|
|
945
|
+
self.armrec.append(arm)
|
|
946
|
+
plotattrs1 = plotattrs.copy()
|
|
947
|
+
spimod.output_(arm)
|
|
948
|
+
getangular(spimod)
|
|
949
|
+
self.dout = spimod.dout.copy()
|
|
950
|
+
if plotattrs1['armcolour'] == '':
|
|
951
|
+
plotattrs1['armcolour'] = spimod.armcolour[arm]
|
|
952
|
+
self.xyplot(spimod,plotattrs1)
|
|
953
|
+
_polarproj(spimod,plotattrs1)
|
|
954
|
+
if (('cont' not in model.lower())&(arm=='all')) :
|
|
955
|
+
for arm_temp in spimod.arms:
|
|
956
|
+
plotattrs1 = plotattrs.copy()
|
|
957
|
+
spimod.output_(arm_temp)
|
|
958
|
+
getangular(spimod)
|
|
959
|
+
if plotattrs1['armcolour'] == '':
|
|
960
|
+
plotattrs1['armcolour'] = spimod.armcolour[arm_temp]
|
|
961
|
+
self.xyplot(spimod,plotattrs1)
|
|
962
|
+
_polarproj(spimod,plotattrs1)
|
|
963
|
+
try:
|
|
964
|
+
add_polargrid(plotattrs1,xmin=self.xmin,xmax=self.xmax,ymin=self.ymin,ymax=self.ymax,modrec=self.modrec,armrec=self.armrec)
|
|
965
|
+
except AttributeError:
|
|
966
|
+
pass
|
|
967
|
+
|
|
968
|
+
class _make_supportfiles(object):
|
|
969
|
+
"""
|
|
970
|
+
was run to save supporting files
|
|
971
|
+
|
|
972
|
+
"""
|
|
973
|
+
|
|
974
|
+
def __init__(self):
|
|
975
|
+
|
|
976
|
+
self.xsun = -8.277
|
|
977
|
+
|
|
978
|
+
# self.prep_poggio_polar()
|
|
979
|
+
self.savelims_all()
|
|
980
|
+
self.savelims()
|
|
981
|
+
def prep_poggio_polar(self):
|
|
982
|
+
'''
|
|
983
|
+
saves the poggio contours for polarprojection
|
|
984
|
+
'''
|
|
985
|
+
|
|
986
|
+
xsun=self.xsun
|
|
987
|
+
usemodels = ['Poggio_cont_2021','GaiaPVP_cont_2022']
|
|
988
|
+
|
|
989
|
+
for usemodel in usemodels:
|
|
990
|
+
|
|
991
|
+
plt.close('all')
|
|
992
|
+
plotattrs = {'plot':True,'coordsys': 'HC','markersize':15,'linewidth':1,'polarproj':False,'armcolour':'black'}
|
|
993
|
+
sp = spiral_poggio_maps(model_=usemodel)
|
|
994
|
+
sp.xsun = xsun
|
|
995
|
+
cset1,cset2 = sp.output_(plotattrs)
|
|
996
|
+
|
|
997
|
+
# # check xy projection
|
|
998
|
+
# plt.ion()
|
|
999
|
+
# plt.close('all')
|
|
1000
|
+
# [[plt.plot(q[:,0],q[:,1], c='C%d'%c) for q in Q] for c,Q in enumerate(cset1.allsegs)]
|
|
1001
|
+
# # # # plt.savefig(root_+'/test_xy.png')
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
tst = [[(q[:,0],q[:,1]) for q in Q] for c,Q in enumerate(cset1.allsegs)]
|
|
1005
|
+
|
|
1006
|
+
plt.ion()
|
|
1007
|
+
plt.close('all')
|
|
1008
|
+
fig, ax = plt.subplots(figsize=(7.5,7.),subplot_kw=dict(projection="polar"))
|
|
1009
|
+
|
|
1010
|
+
dsave = {}
|
|
1011
|
+
dsave['glon4'] = []
|
|
1012
|
+
dsave['dhelio'] = []
|
|
1013
|
+
dsave['phi4'] = []
|
|
1014
|
+
dsave['rgc'] = []
|
|
1015
|
+
for inum,Q in enumerate(cset1.allsegs):
|
|
1016
|
+
xc = [q[:,0] for q in Q]
|
|
1017
|
+
yc = [q[:,1] for q in Q]
|
|
1018
|
+
|
|
1019
|
+
for i in range(len(xc)):
|
|
1020
|
+
glon4 = np.degrees(np.arctan2(yc[i],xc[i]))%360.
|
|
1021
|
+
dhelio = sqrtsum(ds=[xc[i],yc[i]])
|
|
1022
|
+
phi4 = np.degrees(np.arctan2(yc[i],xc[i]+sp.xsun))%360.
|
|
1023
|
+
rgc = sqrtsum(ds=[xc[i]+sp.xsun,yc[i]])
|
|
1024
|
+
# plt.plot(np.radians(phi4),rgc,'.') # gc frame
|
|
1025
|
+
# plt.plot(np.radians(glon4),dhelio,'.') # hc frame
|
|
1026
|
+
dsave['phi4'].append(phi4)
|
|
1027
|
+
dsave['rgc'].append(rgc)
|
|
1028
|
+
dsave['glon4'].append(glon4)
|
|
1029
|
+
dsave['dhelio'].append(dhelio)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
for ky in dsave.keys():
|
|
1033
|
+
dsave[ky] = np.concatenate(dsave[ky]).ravel()
|
|
1034
|
+
|
|
1035
|
+
dsave['ang_gc'] = dsave['phi4'].copy()
|
|
1036
|
+
dsave['ang_hc'] = dsave['glon4'].copy()
|
|
1037
|
+
|
|
1038
|
+
dsave['rad_gc'] = dsave['rgc'].copy()
|
|
1039
|
+
dsave['rad_hc'] = dsave['dhelio'].copy()
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
picklewrite(dsave,usemodel+'_pproj_contours',dataloc+'/'+usemodel)
|
|
1043
|
+
def savelims_all(self):
|
|
1044
|
+
|
|
1045
|
+
print('saving plot limits for all models')
|
|
1046
|
+
xsun=self.xsun
|
|
1047
|
+
|
|
1048
|
+
mylims = {}
|
|
1049
|
+
|
|
1050
|
+
spirals = main_(xsun=xsun)
|
|
1051
|
+
for inum,use_model in enumerate(spirals.models):
|
|
1052
|
+
|
|
1053
|
+
plt.close('all')
|
|
1054
|
+
|
|
1055
|
+
mylims[use_model] = {}
|
|
1056
|
+
|
|
1057
|
+
plotattrs = {'plot':True,'coordsys':'GC','markersize':15,'markSunGC':True,'polargrid':False}
|
|
1058
|
+
|
|
1059
|
+
coordsys = plotattrs['coordsys']
|
|
1060
|
+
|
|
1061
|
+
spirals.getinfo(model=use_model)
|
|
1062
|
+
spirals.readout(plotattrs,model=use_model,arm='all')
|
|
1063
|
+
mylims[use_model]['xmin'+'_'+coordsys] = spirals.xmin
|
|
1064
|
+
mylims[use_model]['xmax'+'_'+coordsys] = spirals.xmax
|
|
1065
|
+
mylims[use_model]['ymin'+'_'+coordsys] = spirals.ymin
|
|
1066
|
+
mylims[use_model]['ymax'+'_'+coordsys] = spirals.ymax
|
|
1067
|
+
|
|
1068
|
+
plt.close('all')
|
|
1069
|
+
plotattrs = {'plot':True,'coordsys':'HC','markersize':15,'markSunGC':True,'polargrid':False}
|
|
1070
|
+
coordsys = plotattrs['coordsys']
|
|
1071
|
+
|
|
1072
|
+
spirals.getinfo(model=use_model)
|
|
1073
|
+
spirals.readout(plotattrs,model=use_model,arm='all')
|
|
1074
|
+
mylims[use_model]['xmin'+'_'+coordsys] = spirals.xmin
|
|
1075
|
+
mylims[use_model]['xmax'+'_'+coordsys] = spirals.xmax
|
|
1076
|
+
mylims[use_model]['ymin'+'_'+coordsys] = spirals.ymin
|
|
1077
|
+
mylims[use_model]['ymax'+'_'+coordsys] = spirals.ymax
|
|
1078
|
+
|
|
1079
|
+
picklewrite(mylims,'flim_all',dataloc)
|
|
1080
|
+
def savelims(self):
|
|
1081
|
+
|
|
1082
|
+
print('saving plot limits for all models')
|
|
1083
|
+
xsun=self.xsun
|
|
1084
|
+
|
|
1085
|
+
mylims = {}
|
|
1086
|
+
|
|
1087
|
+
spirals = main_(xsun=xsun)
|
|
1088
|
+
|
|
1089
|
+
for inum,use_model in enumerate(spirals.models):
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
spimod = spirals.models_class[use_model]
|
|
1093
|
+
spimod.getarmlist()
|
|
1094
|
+
|
|
1095
|
+
mylims[use_model] = {}
|
|
1096
|
+
|
|
1097
|
+
for jnum, arm in enumerate(spimod.arms):
|
|
1098
|
+
|
|
1099
|
+
mylims[use_model][arm] = {}
|
|
1100
|
+
|
|
1101
|
+
plt.close('all')
|
|
1102
|
+
plotattrs = {'plot':True,'coordsys':'GC','markersize':15,'markSunGC':True,'polargrid':False}
|
|
1103
|
+
|
|
1104
|
+
coordsys = plotattrs['coordsys']
|
|
1105
|
+
|
|
1106
|
+
spirals.getinfo(model=use_model)
|
|
1107
|
+
spirals.readout(plotattrs,model=use_model,arm=arm)
|
|
1108
|
+
mylims[use_model][arm]['xmin'+'_'+coordsys] = spirals.xmin
|
|
1109
|
+
mylims[use_model][arm]['xmax'+'_'+coordsys] = spirals.xmax
|
|
1110
|
+
mylims[use_model][arm]['ymin'+'_'+coordsys] = spirals.ymin
|
|
1111
|
+
mylims[use_model][arm]['ymax'+'_'+coordsys] = spirals.ymax
|
|
1112
|
+
|
|
1113
|
+
plt.close('all')
|
|
1114
|
+
plotattrs = {'plot':True,'coordsys':'HC','markersize':15,'markSunGC':True,'polargrid':False}
|
|
1115
|
+
coordsys = plotattrs['coordsys']
|
|
1116
|
+
|
|
1117
|
+
spirals.getinfo(model=use_model)
|
|
1118
|
+
spirals.readout(plotattrs,model=use_model,arm=arm)
|
|
1119
|
+
mylims[use_model][arm]['xmin'+'_'+coordsys] = spirals.xmin
|
|
1120
|
+
mylims[use_model][arm]['xmax'+'_'+coordsys] = spirals.xmax
|
|
1121
|
+
mylims[use_model][arm]['ymin'+'_'+coordsys] = spirals.ymin
|
|
1122
|
+
mylims[use_model][arm]['ymax'+'_'+coordsys] = spirals.ymax
|
|
1123
|
+
|
|
1124
|
+
picklewrite(mylims,'flim',dataloc)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
|