-
Notifications
You must be signed in to change notification settings - Fork 3
/
isaac.py
1663 lines (1221 loc) · 48.9 KB
/
isaac.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
-----------------------------------------------
Some simple python code to be easily imported from python
-----------------------------------------------
"""
import pynbody
SimArray = pynbody.array.SimArray
pb = pynbody
import copy
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import scipy.interpolate as interp
import warnings
import glob
import os
import datetime
import fnmatch
import logging
self_dir = os.path.dirname(os.path.realpath(__file__))
print os.path.realpath(__file__)
def snapshot_defaults(snapshot):
"""
Applies various defaults to tipsy snapshots of protoplanetary disk
simulations. These include:
-Sets nice units
-Calculates particle smoothing lengths using mass and rho (if available)
Changes the snapshot in place
"""
# Setup units
snapshot['pos'].convert_units('au')
snapshot['mass'].convert_units('Msol')
snapshot['vel'].convert_units('km s**-1')
snapshot['eps'].convert_units('au')
snapshot.g['temp'].convert_units('K')
# Calculate smoothing lengths
if ('rho' in snapshot.g.loadable_keys()) or ('rho' in snapshot.g.keys()):
snapshot.g['rho'].convert_units('Msol au**-3')
if ~(np.any(snapshot.g['rho'] == 0)):
snapshot.g['smooth'] = (snapshot.g['mass']/snapshot.g['rho'])**(1,3)
return
def units_from_param(param):
"""
Figures out the simulation units from a .param file
**ARGUMENTS**
param : str or param dict (see configparser)
Simulation .param file or param dict loaded by configparser
Can also be a list or numpy array of these in which case a list
of units dicts is returned
**RETURNS**
units : dict
A dictionary of the units used in the simulation, returned as
pynbody units
"""
# Define function to load the units from a given param
def _load_units(param):
# Load param if necessary
if isinstance(param, str):
param = configparser(param, 'param')
# Universal G
G = pynbody.units.G
# Load units
dKpcUnit = param['dKpcUnit']
dMsolUnit = param['dMsolUnit']
# Set up pynbody units
m_unit = pynbody.units.Unit('{0} Msol'.format(dMsolUnit))
l_unit = pynbody.units.Unit('{0} kpc'.format(dKpcUnit))
t_unit = (l_unit**3/(G*m_unit))**(1,2)
# Convert the time unit to something sensible
years = t_unit.in_units('yr')
t_unit = pynbody.units.Unit('{0} yr'.format(years))
# Return
outdict = {'l_unit':l_unit, 'm_unit':m_unit, 't_unit':t_unit}
return outdict
# Iterate over param if necessary
if isinstance(param, (list, np.ndarray)):
outlist = []
for par in param:
outlist.append(_load_units(par))
return outlist
else:
# Not iterable
return _load_units(param)
def kepler_pos(pos, vel, t, Mstar, order=10):
"""
Estimate position at future time t assuming an elliptical keplerian orbit
"""
G = SimArray(1.0, 'G')
mu = G*Mstar
r = np.sqrt((pos**2).sum())
v = np.sqrt((vel**2).sum())
# Calculate semi-major axis
a = mu*r/(2*mu - v*v*r)
a.convert_units(r.units)
# Calculate eccentricity vector
ecc = (v*v)*pos/mu - ((pos*vel).sum())*vel/mu - pos/r
ecc.convert_units('1')
# Calculate eccentricity
e = float(np.sqrt((ecc**2).sum()))
# Calculate initial eccentric anomaly
# x1 = a*e^2 + r.e
x1 = a*e**2 + (pos*ecc).sum()
# y1 = |r x e| * sign(r.v)
y1 = np.sqrt((np.cross(pos, ecc)**2).sum())
y1 *= (pos*vel).sum()/abs((pos*vel).sum())
E0 = np.arctan2(y1,x1)
# Calculate mean anomaly
M0 = E0 - e*np.sin(E0)
a3 = np.power(a,3)
M = (np.sqrt(mu/a3)*t).in_units('1') + M0
# Calculate eccentric anomaly
E = E0
for i in range(order):
E = M + e*np.sin(E)
# Calculate (x1, y1) (relative to center of ellipse, not focus)
x1 = (2*a - r) * np.cos(E)
y1 = (2*a - r) * np.sin(E)
# Transform to original coordinates
x1hat = ecc/np.sqrt((ecc**2).sum())
y1hat = np.cross(np.cross(pos, vel), ecc)
y1hat /= np.sqrt((y1hat**2).sum())
pos_f = (x1 - a*e)*x1hat + y1*y1hat
return pos_f
def findfiles(filefilter='*', basedir='.'):
"""
Recursively find files according to filefilter
** ARGUMENTS **
filefilter : str
Filter for finding files. ie, '*.jpg' or 'file.txt'
basedir : str
Base directory to search. Default is the current directory
** RETURNS **
files : list
A list of the full path to all files matching filefilter
"""
matches = []
for root, dirnames, filenames in os.walk(basedir):
for filename in fnmatch.filter(filenames, filefilter):
fname = os.path.join(root, filename)
fname = os.path.realpath(fname)
matches.append(fname)
return matches
def walltime(filename):
"""
Reads walltime information from a ChaNGa .log file.
** ARGUMENTS **
filename : str
Filename of the .log file to load
** RETURNS **
wall_per_step : array
Wall time per step in seconds
"""
log_file = np.genfromtxt(filename, comments='#', delimiter=' ')
wall_per_step = log_file[:,-1]
walltime_total = datetime.timedelta(seconds = wall_per_step.sum())
walltime_avg = datetime.timedelta(seconds = wall_per_step.mean())
print 'Total walltime: '
print str(walltime_total)
print 'Average walltime per step:'
print str(walltime_avg)
return wall_per_step
def load_acc(filename, param_name = None, low_mem = True):
"""
Loads accelerations from a ChaNGa acceleration file (.acc2), ignoring the
star particle.
IF param_name is None, a .param file is searched for, otherwise param_name
should be a string specifying a .param file name
IF no param_file is found, the defaults are used:
length unit: AU
mass unit : Msol
Setting low_mem=True decreases memory usage by about 2x but also increases
readtime by about 2x
"""
if param_name is None:
prefix = filename.split('.')[0]
param_list = glob.glob('*' + prefix +'*param')
if len(param_list) > 0:
param_name = param_list[0]
elif len(glob.glob('*.param')) > 0:
param_name = glob.glob('*.param')[0]
else:
warnings.warn('Could not find .param file. Assuming default units')
if param_name is not None:
# If a param name is set or a param file has been found:
print 'Loading param file: {}'.format(param_name)
param = configparser(param_name, ftype='param')
else:
# Set the default parameters
param = {}
# Assume AU as length unit
param['dKpcUnit'] = pynbody.units.au.ratio('kpc')
# Assume mass units as Msol
param['dMsolUnit'] = 1.0
# Figure out units
G = pynbody.units.G
l_unit = param['dKpcUnit']*pynbody.units.kpc
m_unit = param['dMsolUnit']*pynbody.units.Msol
t_unit = ((l_unit**3) * G**-1 * m_unit**-1)**(1,2)
a_unit = l_unit * t_unit**-2
if low_mem:
acc_file = open(filename, 'r')
n_particles = int(acc_file.readline().strip())
acc = SimArray(np.zeros(3*n_particles, dtype=np.float32), a_unit)
for i, line in enumerate(acc_file):
acc[i] = np.float32(line.strip())
acc_file.close()
return acc.reshape([n_particles, 3], order='F')[0:-1]
else:
# Load acceleration file as numpy array
acc = np.genfromtxt(filename, skip_header=1, dtype=np.float32)
n_particles = len(acc)/3
# Reshape and make it a SimArray with proper units
acc = SimArray(acc.reshape([n_particles, 3], order='F'), a_unit)
return acc
def height(snapshot, bins=100, center_on_star=True):
"""
Calculates the characteristic height (h) of a flared disk as a function
of cylindrical radius (r).
** ARGUMENTS **
snapshot : TipsySnap
Simulation snapshot for a flared disk
bins : int or array_like
Specifies the bins to use. If int, specifies the number of bins. If
array_like, specifies the bin edges
center_on_star : bool
If true (DEFAULT), cylindrical r is calculated relative to the star
** RETURNS **
r_edges : SimArray
Radial bin edges used for calculating h. Length N+1
h : SimArray
Height as a function of r, calculated as the RMS of z over a bin.
Length N
"""
# Center on star
if center_on_star:
star_pos = snapshot.s['pos'].copy()
snapshot['pos'] -= star_pos
else:
star_pos = 0.0*snapshot.s['pos']
# Calculate height
r = snapshot.g['rxy']
z2 = snapshot.g['z']**2
r_edges, z2_mean, err = binned_mean(r, z2, bins=bins, ret_bin_edges=True)
h = np.sqrt(z2_mean)
# Add star_pos back to snapshot
snapshot['pos'] += star_pos
return r_edges, h
def sigma(snapshot, bins=100):
"""
Calculates surface density vs r (relative to the center of mass)
** ARGUMENTS **
snapshot : tipsy snapshot
bins : int, list, array...
Either the number of bins to use or the binedges to use
** RETURNS **
sigma : SimArray
Surface density as a function of r
r_bins : SimArray
Radial bin edges
"""
# Begin by subtracting off the center of mass position
cm = (snapshot['mass'][:,None] * snapshot['pos']).sum()/(snapshot['mass'].sum())
snapshot['pos'] -= cm
r = snapshot.g['rxy']
# particle mass
m_gas = snapshot.gas['mass'][[0]]
N, r_bins = np.histogram(r, bins=bins)
r_bins = match_units(r_bins, r.units)[0]
r_center = (r_bins[1:] + r_bins[0:-1])/2
dr = r_bins[[1]] - r_bins[[0]]
sig = N*m_gas/(2*np.pi*r_center*dr)
# Add star position back to positions
snapshot['pos'] += cm
return sig, r_bins
def Q2(snapshot, molecular_mass = 2.0, bins=100, max_height=None):
# Physical constants
kB = SimArray([1.0],'k')
G = SimArray([1.0],'G')
# Load stuff froms snapshot
v = snapshot.g['vt']
r = snapshot.g['rxy']
z = snapshot.g['z']
T = snapshot.g['temp']
# Calculate sound speed for all particles
m = match_units(molecular_mass,'m_p')[0]
cs = np.sqrt(kB*T/m)
# Calculate surface density
sig_binned, r_edges = sigma(snapshot, bins)
r_cent = (r_edges[1:]+r_edges[0:-1])/2
sig_spl = extrap1d(r_cent, sig_binned)
sig = SimArray(sig_spl(r), sig_binned.units)
# Calculate omega (as a proxy for kappa)
omega = v/r
kappa = omega
#Calculate Q for all particles
print 'kappa',kappa.units
print 'cs',cs.units
print 'sigma', sig.units
Q_all = (kappa*cs/(np.pi*G*sig)).in_units('1')
# Use particles close to midplane
if max_height is not None:
dummy, h = height(snapshot, bins=r_edges)
ind = np.digitize(r, r_edges) - 1
ind[ind<0] = 0
ind[ind >= (len(r_edges)-1)] = len(r_edges)-2
mask = abs(z) < (max_height*h[ind])
Q_all = Q_all[mask]
r = r[mask]
dummy, Q_binned, dummy2 = binned_mean(r, Q_all, binedges=r_edges)
return r_edges, Q_binned
def kappa(f, bins=100):
"""
Estimate the epicyclic frequency from velocity
**ARGUMENTS**
f : TipsySnap
Simulation snapshot
bins : int or array-like
Either the number of bins to use or the bin edges
**RETURNS**
kappa : SimArray
epicyclic frequency
r_edges : SimArray
binedges used
"""
# Require regular spacing of bins
if not isinstance(bins, int):
dr = bins[[1]] - bins[[0]]
eps = np.finfo(bins.dtype).eps
if not np.all(bins[1:] - bins[0:-1] <= dr + 1000*eps):
raise ValueError, 'Bins not uniformly spaced'
r = f.g['rxy']
v = f.g['vt']
r_edges, v_mean, dummy = binned_mean(r, v, bins=bins, ret_bin_edges=True)
dummy, rv_mean, dummy2 = binned_mean(r, r*v, bins=r_edges)
r_cent = (r_edges[1:] + r_edges[0:-1])/2
dr = r_edges[[1]] - r_edges[[0]]
drv_dr = np.gradient(rv_mean, dr)
kappa = np.sqrt(2*v_mean*drv_dr)/r_cent
return kappa, r_edges
def Q(snapshot, molecular_mass = 2.0, bins=100, max_height=None, \
use_velocity=False, use_omega=True):
"""
Calculates the Toomre Q as a function of r, assuming radial temperature
profile and kappa ~= omega
** ARGUMENTS **
snapshot : tipsy snapshot
molecular_mass : float
Mean molecular mass (for sound speed). Default = 2.0
bins : int or array
Either the number of bins or the bin edges
use_velocity : Bool
Determines whether to use the particles' velocities to calculate orbital
velocity. Useful if the circular orbital velocities are set in the
snapshot.
use_omega : Bool
Default=True. Use omega as a proxy for kappa to reduce noise
** RETURNS **
Q : array
Toomre Q as a function of r
r_edges : array
Radial bin edges
"""
# Physical constants
kB = SimArray([1.0],'k')
G = SimArray([1.0],'G')
# Calculate surface density
sig, r_edges = sigma(snapshot, bins)
# Calculate sound speed
m = match_units(molecular_mass,'m_p')[0]
c_s_all = np.sqrt(kB*snapshot.g['temp']/m)
# Bin/average sound speed
dummy, c_s, dummy2 = binned_mean(snapshot.g['rxy'], c_s_all, binedges=r_edges)
if use_omega:
# Calculate keplerian angular velocity (as a proxy for the epicyclic
# frequency, which is a noisy calculation)
if use_velocity:
# Calculate directly from particle's velocity
dummy, omega, dummy2 = binned_mean(snapshot.g['rxy'], \
snapshot.g['vt']/snapshot.g['rxy'], binedges=r_edges)
else:
# Estimate, from forces, using pynbody
p = pynbody.analysis.profile.Profile(snapshot, bins=r_edges)
omega = p['omega']
kappa_calc = omega
else:
if use_velocity:
# Calculate directly from particle's velocities
kappa_calc, dummy = kappa(snapshot, r_edges)
else:
# Estimate, from forces, using pynbody
p = pynbody.analysis.profile.Profile(snapshot, bins=r_edges)
kappa_calc = p['kappa']
return (kappa_calc*c_s/(np.pi*G*sig)).in_units('1'), r_edges
def Q_eff(snapshot, molecular_mass=2.0, bins=100):
"""
Calculates the effective Toomre Q as a function of r, assuming radial temp
profile and kappa ~= omega and scaleheight << wavelength. This assumption
simplifies the calculation of Q_eff (where wavelength is the wavelength of
the disturbances of interest)
** ARGUMENTS **
snapshot : tipsy snapshot
molecular_mass : float
Mean molecular mass (for sound speed). Default = 2.0
bins : int or array
Either the number of bins or the bin edges
** RETURNS **
Qeff : array
Effective Toomre Q as a function of r for scale height << wavelength
r_edges : array
Radial bin edges
"""
# Physical constants
kB = SimArray([1.0],'k')
G = SimArray([1.0],'G')
# Calculate surface density
sig, r_edges = sigma(snapshot, bins)
# Calculate keplerian angular velocity (as a proxy for the epicyclic
# frequency, which is a noisy calculation)
p = pynbody.analysis.profile.Profile(snapshot, bins=r_edges)
omega = p['omega']
# Calculate sound speed
m = match_units(molecular_mass,'m_p')[0]
c_s_all = np.sqrt(kB*snapshot.g['temp']/m)
# Bin/average sound speed
dummy, c_s, dummy2 = binned_mean(snapshot.g['rxy'], c_s_all, binedges=r_edges)
# Calculate scale height
dummy, h = height(snapshot, bins=r_edges, center_on_star=False)
a = np.pi*G*sig
b = (2*a*h/c_s**2).in_units('1')
Q0 = (omega*c_s/a).in_units('1')
return Q0 * np.sqrt(1 + b), r_edges
return ((omega*c_s/a) * np.sqrt(1 + 2*a*h/c_s**2)).in_units('1'), r_edges
def strip_units(x):
"""
Removes the units from a SimArray and returns as a numpy array. Note
that x is copied so that it is not destroyed
x can be a single SimArray or a tuple or list of SimArrays
If any of the inputs are single number, they are returned as a number
USAGE:
array = strip_units(SimArray)
array1, array2, ... = strip_units([SimArray1, SimArray2, ...])
"""
if isinstance(x, (tuple,list)):
# loop through and assign output
x_out = []
for x_i in x:
if np.prod(x_i.shape) == 1:
# There is only one element in x_i. Make sure to return it as
# a number (not an array)
if np.sum(x_i.shape) == 0:
# This is a zero dimensional SimArray
x_out.append(x_i.tolist())
else:
# This is 1 dimensional SimArray
x_out.append(x_i[0])
else:
#This is a multi-element SimArray
x_out.append(np.asarray(x_i.tolist()))
else:
if np.prod(x.shape) == 1:
# There is only one element in x_i. Return as a number
if np.sum(x.shape) == 0:
# This is a 0 dimensional SimArray
x_out = x.tolist()
else:
# This a 1 dimensional SimArray
x_out = x[0]
else:
x_out = np.asarray(x.tolist())
return x_out
def set_units(x, units):
"""
Sets the units of x to units. If x has units, they are ignored.
Does not destroy/alter x
USAGE:
SimArray = set_units(x, units)
SimArray1, SimArray2, ... = set_units([x1, x2, ...], units)
SimArray1, SimArray2, ... = set_units([x1, x2, ...], [units1, units2, ...])
"""
if isinstance(x, (tuple,list)):
x_out = []
if not isinstance(units, (tuple, list)):
units = [units]*len(x)
for i in range(len(x)):
x_i = x[i]
if pynbody.units.has_units(x_i):
x_i_array = strip_units(x_i)
x_out.append(SimArray(x_i_array, units[i]))
else:
x_out.append(SimArray(x_i, units[i]))
else:
if pynbody.units.has_units(x):
x_array = strip_units(x)
x_out = SimArray(x_array, units)
else:
x_out = SimArray(x, units)
return x_out
def setup_param(param, snapshot=None, r_orb=1.0, n_orb=10.0, n_image=None, n_snap=100, \
n_check=None):
"""
Sets up the following for a .param file:
nSteps
dDumpFrameStep
iOutInterval
iCheckInterval
**ARGUMENTS**
param : str or param_dict (see isaac.configparser, configsave)
parameter file for the simulation, must already have dDelta and units
set properly
IF a str, assumed to be a filename
snapshot : str or TipsySnap(see pynbody) or None
Snapshot for the simulation. Needed to estimate the outer orbital
period.
IF a str, assumed to be a filename
IF None, the file pointed to by param is used
r_orb : float
radius to calculate the outer orbital period at as a fraction of the
radius of the farthest out particle. Must be between 0 and 1
n_orb : float
number of outer orbital periods to run simulation for
n_image : int or None
Total number of frames to dump (ie, dDumpFrameStep)
If None, defaults to n_snap
n_snap : int
Total number of simulation outputs
n_check : int or None
Total number of simulation checkpoints. If None, defaults to n_snap
"""
if (r_orb > 1) | (r_orb < 0):
raise ValueError, 'r_orb must be between 0 and 1'
if isinstance(snapshot, str):
# A filename has been passed, not a tipsy snapshot
snapshot = pynbody.load(snapshot)
if isinstance(param, str):
# A filename has been passed. Load the dictionary
param = configparser(param, 'param')
else:
# Copy so as to not overwrite the input dict
param = copy.deepcopy(param)
R_max = r_orb * snapshot.g['rxy'].max()
M_star = snapshot.s['mass']
# Read in .param stuff
l_unit = '{} kpc'.format(param['dKpcUnit'])
m_unit = '{} Msol'.format(SimArray(param['dMsolUnit'], 'Msol'))
# Outer radius and star mass in simulation units
r = float(R_max.in_units(l_unit))
M = float(M_star.in_units(m_unit))
# Calculate the number of time steps to use
dt = param['dDelta']
period = 2*np.pi*np.sqrt(r**3/M)
N = int(np.round(n_orb * period/dt))
param['nSteps'] = N
# Calculate how often to output snapshots, frames, checkpoints
if n_check is None:
n_check = n_snap
if n_image is None:
n_image = n_snap
param['dDumpFrameStep'] = int(N/n_image)
param['iOutInterval'] = int(N/n_snap)
param['iCheckInterval'] = int(N/n_check)
return param
def make_submission_script(param_name, directory=None, nodes=1, walltime=12, changa='ChaNGa_uw_mpi', jobname='changasim', scriptname='subber.sh', backfill=True):
"""
Creates a submission script for qsub. This is highly platform dependent
"""
# Set up simulation directory
if directory is None:
directory = os.getcwd()
# Load param file
param = configparser(param_name, 'param')
fprefix = param['achOutName']
# Format walltime for qsub
seconds = int(walltime*3600)
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
walltime_str = '{0:02d}:{1:02d}:{2:02d}'.format(h,m,s)
# Format walltime for changa
walltime_min = int(walltime*60)
# Open submission script
subber = open(scriptname,'w')
# Write to submission script
subber.write('#!/bin/bash\n\
#PBS -N {0}\n\
#PBS -j oe\n\
#PBS -m be\n\
#PBS -M [email protected]\n\
#PBS -l nodes={1}:ppn=12,feature=12core\n\
#PBS -l walltime={2}\n\
#PBS -V\n'.format(jobname, nodes, walltime_str))
if backfill:
subber.write('#PBS -q bf\n')
subber.write('module load gcc_4.4.7-ompi_1.6.5\n')
subber.write('export MX_RCACHE=0\n')
subber.write('workdir={0}\n'.format(directory))
subber.write('cd $workdir\n')
subber.write('changbin=$(which {0})\n'.format(changa))
subber.write('if [ -e "lastcheckpoint" ]\n\
then\n\
echo "lastcheckpoint exists -- restarting simulation..."\n\
last=`cat lastcheckpoint`\n\
mpirun --mca mtl mx --mca pml cm $changbin +restart {0}.chk$last +balancer MultistepLB_notopo -wall {2} $workdir/{1} >> $workdir/{0}.out 2>&1\n\
else\n\
echo "lastcheckpoint doesnt exist -- starting new simulation..."\n\
mpirun --mca mtl mx --mca pml cm $changbin -D 3 +consph +balancer MultistepLB_notopo -wall {2} $workdir/{1} >& $workdir/{0}.out\n\
fi\n\
'.format(fprefix, param_name, walltime_min))
subber.close()
# Make submission script executable
os.system('chmod a+rwx {}'.format(scriptname))
def make_param(snapshot, filename=None):
"""
Generates a default param dictionary. Can be saved using isaac.configsave
EXAMPLE
snapshot = pynbody.load('snapshot.std') # Load snapshot
param_dict = isaac.make_param(snapshot) # Make default param dict
isaac.configsave(param_dict, 'snapshot.param', ftype='param') # Save
Optionally, the user can set the snapshot filename manually
"""
fname_def = os.path.join(self_dir, 'default.param')
param = configparser(fname_def, ftype='param')
if filename is not None:
param['achInFile'] = filename
param['achOutName'] = os.path.splitext(filename)[0]
elif snapshot.filename != '<created>':
param['achInFile'] = snapshot.filename
param['achOutName'] = os.path.splitext(snapshot.filename)[0]
# Set up the length units
param['dKpcUnit'] = snapshot['pos'].units.ratio('kpc')
# Set up the mass units
param['dMsolUnit'] = snapshot['mass'].units.ratio('Msol')
# Set the mean molecular mass
param['dMeanMolWeight'] = snapshot.gas['mu'][0]
return param
def make_director(sigma_min, sigma_max, r, resolution=1200, filename='snapshot'):
"""
Makes a director dictionary for ChaNGa runs based on the min/max surface
density, maximum image radius, and image resolution for a gaseous
protoplanetary disk. The created dictionary can be saved with
isaac.configsave
The method is to use an example director file (saved as default.director)
which works for one simulation and scale the various parameters accordingly.
default.director should have a commented line in it which reads:
#sigma_max float
where float is the maximum surface density of the simulation in simulation
units.
**ARGUMENTS**
sigma_min : float
The surface density that corresponds to 0 density on the image (ie the
minimum threshold). Required for setting the dynamic range
sigma_max : float
Maximum surface density in the simulation
r : float
Maximum radius to plot out to
resolution : int or float
Number of pixels in image. The image is shape (resolution, resolution)
filename : str
prefix to use for saving the images. Example: if filename='snapshot',
then the outputs will be of form 'snapshot.000000000.ppm'
**RETURNS**
director : dict
A .director dictionary. Can be saved with isaac.configsave
"""
# -----------------------------------------------------------
# Parse defaults to get scale factor for c
# -----------------------------------------------------------
defaults = configparser(os.path.join(self_dir, 'default.director'))
if '#sigma_max' not in defaults:
raise KeyError,'Default .director file should have a line e.g. << #sigma_max 0.01 >>'
sigma_max0 = defaults['#sigma_max']
c0 = defaults['colgas'][3]
n0 = defaults['size'][0]
r0 = defaults['eye'][2]
A = (c0 * float(n0)**2)/(sigma_max0 * r0**2)
# -----------------------------------------------------------
# Create new director dictionary
# -----------------------------------------------------------
director = copy.deepcopy(defaults)
director.pop('#sigma_max', None)
logscale_min = sigma_min/sigma_max
if pynbody.units.has_units(logscale_min):
logscale_min = float(logscale_min.in_units('1'))
c = A * float(sigma_max * r**2 /float(resolution)**2)
director['colgas'][3] = c
director['size'] = [resolution, resolution]
director['eye'][2] = r
director['file'] = filename
return director
def match_units(x, y):
"""
Matches the units of x to y and returns x and y in the same units.
IF x and y don't have units, they are unchanged
IF one of x or y has units, the unitless quantity is returned as a
SimArray (see pynbody.array.SimArray) with the units of the other quantity.
IF both have units, then an attempt is made to convert x into the units of
y. If this is not possible, an error is raised, for example if x is in
units of 'au' and y is in units of 'Msol'
x, y can be: scalar, array, SimArray, pynbody unit (eg pynbody.units.G),
or a unit string (eg 'Msol a**-2')
*** RETURNS ***
x, y are returned as a tuple
"""
# ----------------------------------------------
# Check if either is a string
# ----------------------------------------------
if isinstance(x, str):
x = SimArray(1.0, x)
if isinstance(y,str):
y = SimArray(1.0, y)
# ----------------------------------------------
# Check if one is a pynbody unit
# ----------------------------------------------
# If one is a named unit (eg pynbody.units.G), convert to SimArray
if isinstance(x, pynbody.units.UnitBase):
x = SimArray(1.0, x)
if isinstance(y, pynbody.units.UnitBase):
y = SimArray(1.0, y)
# ----------------------------------------------
# Check the units
# ----------------------------------------------
# If both have units, try to convert x to the units of y
if (pynbody.units.has_units(x)) & (pynbody.units.has_units(y)):
x_out = (x.in_units(y.units))
y_out = y
# If only x has units, make y a SimArray with the units of x
elif (pynbody.units.has_units(x)):
y_out = SimArray(y, x.units)
x_out = x
# If only y has units, make x a SimArray with the units of y