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.
Files changed (34) hide show
  1. SpiralMap/__init__.py +6 -0
  2. SpiralMap/datafiles/Drimmel2024_cepheids/ArmAttributes_dyoungW1_bw025.pkl +0 -0
  3. SpiralMap/datafiles/Drimmel_NIR/Drimmel2armspiral.fits +0 -0
  4. SpiralMap/datafiles/Drimmel_NIR/Readme_spiral_m2_147.txt +5 -0
  5. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm1_X_hel.npy +0 -0
  6. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm1_Y_hel.npy +0 -0
  7. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm2_X_hel.npy +0 -0
  8. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm2_Y_hel.npy +0 -0
  9. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm3_X_hel.npy +0 -0
  10. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm3_Y_hel.npy +0 -0
  11. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm4_X_hel.npy +0 -0
  12. SpiralMap/datafiles/Drimmel_NIR/phase_shifted/Arm4_Y_hel.npy +0 -0
  13. SpiralMap/datafiles/Drimmel_NIR/spiral_m2_147.dat +101 -0
  14. SpiralMap/datafiles/GaiaPVP_cont_2022/GaiaPVP_cont_2022_pproj_contours.pkl +0 -0
  15. SpiralMap/datafiles/GaiaPVP_cont_2022/over_dens_grid_threshold_0_003_dens.npy +0 -0
  16. SpiralMap/datafiles/GaiaPVP_cont_2022/xvalues_dens.npy +0 -0
  17. SpiralMap/datafiles/GaiaPVP_cont_2022/yvalues_dens.npy +0 -0
  18. SpiralMap/datafiles/Poggio_cont_2021/Poggio_cont_2021_pproj_contours.pkl +0 -0
  19. SpiralMap/datafiles/Poggio_cont_2021/overdens_grid_locscale03.npy +0 -0
  20. SpiralMap/datafiles/Poggio_cont_2021/xvalues.npy +0 -0
  21. SpiralMap/datafiles/Poggio_cont_2021/yvalues.npy +0 -0
  22. SpiralMap/datafiles/flim.pkl +0 -0
  23. SpiralMap/datafiles/flim_all.pkl +0 -0
  24. SpiralMap/datafiles/spiral.bib +136 -0
  25. SpiralMap/figdir_primer/polar_proj_multiple_models2.png +0 -0
  26. SpiralMap/models_.py +1127 -0
  27. SpiralMap/movie_.gif +0 -0
  28. SpiralMap/mytools.py +223 -0
  29. SpiralMap/test.py +297 -0
  30. SpiralMap/version.py +2 -0
  31. spiralmap-0.0.0.0.2.dist-info/METADATA +49 -0
  32. spiralmap-0.0.0.0.2.dist-info/RECORD +34 -0
  33. spiralmap-0.0.0.0.2.dist-info/WHEEL +4 -0
  34. 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
+