@@ -747,10 +747,13 @@ def compute_Xcld(self, fname, num_type='ndump'):
747747 Xcld = fv * rho_cld / rho
748748
749749 if self .__isRprofSet :
750- fv = self .get ('FV' , fname , num_type = num_type , resolution = 'l' )
751- airmu = self .get ('airmu' , fname , num_type = num_type )
752- cldmu = self .get ('cldmu' , fname , num_type = num_type )
753- Xcld = fv / ((1. - fv )* (airmu / cldmu ) + fv )
750+ if 'FV' in self .get_dump (self .get_dump_list ()[0 ]).get_lr_variables ():
751+ fv = self .get ('FV' , fname , num_type = num_type , resolution = 'l' )
752+ airmu = self .get ('airmu' , fname , num_type = num_type )
753+ cldmu = self .get ('cldmu' , fname , num_type = num_type )
754+ Xcld = fv / ((1. - fv )* (airmu / cldmu ) + fv )
755+ else :
756+ Xcld = self .get ('X1' , fname , num_type = num_type , resolution = 'l' )
754757
755758 return Xcld
756759
@@ -1321,13 +1324,15 @@ def bound_rad(self, cycles, r_min, r_max, var='ut', \
13211324 if return_var_scale_height :
13221325 Hv = np .zeros (len (cycle_list ))
13231326
1327+ # 'Y' is the default radial coordinate. 'R' is only used in rprof
1328+ # output from runs in spherical geometry (of the stratification, not of
1329+ # the grid).
1330+ rvar = 'Y'
1331+ if self .__isRprofSet and self .get_geometry () == 'spherical' :
1332+ rvar = 'R'
13241333 # The grid is assumed to be static, so we get the radial
13251334 # scale only once at cycle_list[0].
1326- if self .__isyprofile :
1327- r = self .get ('Y' , cycle_list [0 ], resolution = 'l' )
1328-
1329- if self .__isRprofSet :
1330- r = self .get ('R' , cycle_list [0 ], resolution = 'l' )
1335+ r = self .get (rvar , cycle_list [0 ], resolution = 'l' )
13311336
13321337 idx_r_min = np .argmin (np .abs (r - r_min ))
13331338 idx_r_max = np .argmin (np .abs (r - r_max ))
@@ -1475,20 +1480,32 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
14751480 rt = rb + offset * np .abs (Hv )
14761481 print ('Done.' )
14771482
1483+ geometry = self .get_geometry ()
1484+
1485+ # 'Y' is the default radial coordinate. 'R' is only used in rprof
1486+ # output from runs in spherical geometry (of the stratification, not of
1487+ # the grid).
1488+ rvar = 'Y'
1489+ if self .__isRprofSet and geometry == 'spherical' :
1490+ rvar = 'R'
14781491 # The grid is assumed to be static, so we get the radial
14791492 # scale only once at cycles[0].
1480- if self .__isyprofile :
1481- r = self .get ('Y' , cycles [0 ], resolution = 'l' )
1482-
1483- if self .__isRprofSet :
1484- r = self .get ('R' , cycles [0 ], resolution = 'l' )
1493+ r = self .get (rvar , cycles [0 ], resolution = 'l' )
14851494
14861495 r_cell_top = 0.5 * (np .roll (r , + 1 ) + r )
14871496 r_cell_top [0 ] = r_cell_top [1 ] + (r_cell_top [1 ] - r_cell_top [2 ])
1488- r3_cell_top = r_cell_top ** 3
1489- dr3 = r3_cell_top - np .roll (r3_cell_top , - 1 )
1490- dr3 [- 1 ] = dr3 [- 2 ] + (dr3 [- 2 ] - dr3 [- 3 ])
1491- dV = (4. / 3. )* np .pi * dr3
1497+
1498+ if geometry == 'spherical' :
1499+ r3_cell_top = r_cell_top ** 3
1500+ dr3 = r3_cell_top - np .roll (r3_cell_top , - 1 )
1501+ dr3 [- 1 ] = dr3 [- 2 ] + (dr3 [- 2 ] - dr3 [- 3 ])
1502+ dV = (4. / 3. )* np .pi * dr3
1503+ else :
1504+ dr = r_cell_top - np .roll (r_cell_top , - 1 )
1505+ dr [- 1 ] = dr [- 2 ] + (dr [- 2 ] - dr [- 3 ])
1506+ # We do not know the horizontal dimensions of the Cartesian domain,
1507+ # so we assume the horizontal area is unity.
1508+ dV = dr
14921509
14931510 t = np .zeros (len (cycles ))
14941511 burn_rate = np .zeros (len (cycles ))
@@ -1509,8 +1526,11 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
15091526 rho = self .get ('Rho' , cyc , resolution = 'l' )
15101527
15111528 if self .__isRprofSet :
1512- rho = self .get ('Rho0' , cyc , resolution = 'l' ) + \
1513- self .get ('Rho1' , cyc , resolution = 'l' )
1529+ if 'Rho1' in self .get_dump (self .get_dump_list ()[0 ]).get_all_variables ():
1530+ rho = self .get ('Rho0' , cyc , resolution = 'l' ) + \
1531+ self .get ('Rho1' , cyc , resolution = 'l' )
1532+ else :
1533+ rho = self .get ('RHO' , cyc , resolution = 'l' )
15141534
15151535 if burn_func is not None :
15161536 rhodot = burn_func (cyc , ** burn_args )
@@ -1556,10 +1576,17 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
15561576 f_jph = 0.5 * (f_j + f_jpo )
15571577
15581578 s = (f_jph - f_jmh )/ (r_jph - r_jmh )
1559- V_j = (4. / 3. )* np .pi * (r_jmh ** 3 - r_jph ** 3 )
1560- f0 = f_j - np .pi * (r_jmh ** 4 - r_jph ** 4 )/ V_j * s
1561- mir [i ] += (4. / 3. )* np .pi * (rt [i ]** 3 - r_jph ** 3 )* f0 + \
1562- np .pi * (rt [i ]** 4 - r_jph ** 4 )* s
1579+ if geometry == 'spherical' :
1580+ V_j = (4. / 3. )* np .pi * (r_jmh ** 3 - r_jph ** 3 )
1581+ f0 = f_j - np .pi * (r_jmh ** 4 - r_jph ** 4 )/ V_j * s
1582+ mir [i ] += (4. / 3. )* np .pi * (rt [i ]** 3 - r_jph ** 3 )* f0 + \
1583+ np .pi * (rt [i ]** 4 - r_jph ** 4 )* s
1584+ else :
1585+ # We again assume that the horizontal area is unity in
1586+ # Cartesian geometry.
1587+ V_j = r_jmh - r_jph
1588+ f0 = f_j - 0.5 * s * (r_jmh ** 2 - r_jph ** 2 )/ V_j
1589+ mir [i ] += (rt [i ] - r_jph )* f0 + 0.5 * s * (rt [i ]** 2 - r_jph ** 2 )
15631590
15641591 if burn_func is not None :
15651592 # We have to use the minus sign, because rhodot < 0.
@@ -1574,10 +1601,17 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
15741601 print ('{:d}/{:d} cycles processed.' .format (i + 1 , len (cycles )))
15751602 print ('Done.' )
15761603
1577- mir *= 1e27 / nuconst .m_sun
15781604 # mb = mass burnt
15791605 mb = integrate .cumtrapz (burn_rate , x = t , initial = 0. )
1580- mb *= 1e27 / nuconst .m_sun
1606+
1607+ dimensional = False
1608+ if geometry == 'spherical' :
1609+ dimensional = True
1610+
1611+ if dimensional :
1612+ mir *= 1e27 / nuconst .m_sun
1613+ mb *= 1e27 / nuconst .m_sun
1614+
15811615 mtot = mir + mb
15821616
15831617 fit_idx0 = 0
@@ -1603,21 +1637,38 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
16031637 if show_plots :
16041638 cb = utils .colourblind
16051639 pl .close (ifig0 ); fig1 = pl .figure (ifig0 )
1606- pl .plot (t / 60. , rt , color = cb (5 ), ls = '-' , label = r'r$_\mathrm{top}$' )
1607- pl .plot (t / 60. , rb , color = cb (8 ), ls = '--' , label = r'r$_\mathrm{b}$' )
1608- pl .plot (t [fit_range ]/ 60. , rt_fit , color = cb (4 ), ls = '-' )
1609- pl .plot (t [fit_range ]/ 60. , rb_fit , color = cb (4 ), ls = '-' )
1610- pl .xlabel ('t / min' )
1611- pl .ylabel ('r / Mm' )
1640+ tunit = 1.
1641+ if dimensional :
1642+ tunit = 60.
1643+ if geometry == 'spherical' :
1644+ pl .plot (t / tunit , rt , color = cb (5 ), ls = '-' , label = r'r$_\mathrm{top}$' )
1645+ pl .plot (t / tunit , rb , color = cb (8 ), ls = '--' , label = r'r$_\mathrm{b}$' )
1646+ else :
1647+ pl .plot (t / tunit , rt , color = cb (5 ), ls = '-' , label = r'y$_\mathrm{top}$' )
1648+ pl .plot (t / tunit , rb , color = cb (8 ), ls = '--' , label = r'y$_\mathrm{b}$' )
1649+ pl .plot (t [fit_range ]/ tunit , rt_fit , color = cb (4 ), ls = '-' )
1650+ pl .plot (t [fit_range ]/ tunit , rb_fit , color = cb (4 ), ls = '-' )
1651+ if dimensional :
1652+ pl .xlabel ('t / min' )
1653+ pl .ylabel ('r / Mm' )
1654+ else :
1655+ pl .xlabel ('t' )
1656+ pl .ylabel ('y' )
16121657 xfmt = ScalarFormatter (useMathText = True )
16131658 pl .gca ().xaxis .set_major_formatter (xfmt )
16141659 pl .legend (loc = 0 )
16151660 fig1 .tight_layout ()
16161661
1617- print ('rb is the radius of the convective boundary.' )
1618- print ('drb/dt = {:.2e} km/s\n ' .format (1e3 * rb_fc [0 ]))
1619- print ('rt is the upper limit for mass integration.' )
1620- print ('drt/dt = {:.2e} km/s' .format (1e3 * rt_fc [0 ]))
1662+ if dimensional :
1663+ print ('rb is the radius of the convective boundary.' )
1664+ print ('drb/dt = {:.2e} km/s\n ' .format (1e3 * rb_fc [0 ]))
1665+ print ('rt is the upper limit for mass integration.' )
1666+ print ('drt/dt = {:.2e} km/s' .format (1e3 * rt_fc [0 ]))
1667+ else :
1668+ print ('yb is the vertical coordinate of the convective boundary.' )
1669+ print ('dyb/dt = {:.2e}\n ' .format (rb_fc [0 ]))
1670+ print ('yt is the upper limit for mass integration.' )
1671+ print ('dyt/dt = {:.2e}' .format (rt_fc [0 ]))
16211672
16221673 min_val = np .min ([np .min (mtot ), np .min (mb ), np .min (mtot_fit )])
16231674 max_val = np .max ([np .max (mtot ), np .max (mb ), np .max (mtot_fit )])
@@ -1632,28 +1683,41 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
16321683 parts = mdot_str .split ('e' )
16331684 mantissa = float (parts [0 ])
16341685 exponent = int (parts [1 ])
1635- lbl = (r'$\dot{{\mathrm{{M}}}}_\mathrm{{e}} = {:.2f} '
1636- r'\times 10^{{{:d}}}$ M$_\odot$ s$^{{-1}}$' ).\
1637- format (mantissa , exponent )
1638- pl .plot (t [fit_range ]/ 60. , mtot_fit / 10 ** oom , color = cb (4 ), \
1686+ if dimensional :
1687+ lbl = (r'$\dot{{\mathrm{{M}}}}_\mathrm{{e}} = {:.2f} '
1688+ r'\times 10^{{{:d}}}$ M$_\odot$ s$^{{-1}}$' ).\
1689+ format (mantissa , exponent )
1690+ else :
1691+ lbl = (r'$\dot{{\mathrm{{M}}}}_\mathrm{{e}} = {:.2f} '
1692+ r'\times 10^{{{:d}}}$' ).format (mantissa , exponent )
1693+ pl .plot (t [fit_range ]/ tunit , mtot_fit / 10 ** oom , color = cb (4 ), \
16391694 ls = '-' , label = lbl , zorder = 100 )
16401695
16411696 lbl = ''
16421697 if burn_func is not None :
1643- pl .plot (t / 60. , mir / 10 ** oom , ':' , color = cb (3 ), label = 'present' )
1644- pl .plot (t / 60. , mb / 10 ** oom , '--' , color = cb (6 ), label = 'burnt' )
1698+ pl .plot (t / tunit , mir / 10 ** oom , ':' , color = cb (3 ), label = 'present' )
1699+ pl .plot (t / tunit , mb / 10 ** oom , '--' , color = cb (6 ), label = 'burnt' )
16451700 lbl = 'total'
1646- pl .plot (t / 60. , mtot / 10 ** oom , color = cb (5 ), label = lbl )
1701+ pl .plot (t / tunit , mtot / 10 ** oom , color = cb (5 ), label = lbl )
16471702
16481703
16491704 pl .ylim ((min_val / 10 ** oom , max_val / 10 ** oom ))
1650- pl .xlabel ('t / min' )
1651- sub = 'e'
1652- ylbl = r'M$_{:s}$ / 10$^{{{:d}}}$ M$_\odot$' .format (sub , oom )
1653- if oom == 0. :
1654- ylbl = r'M$_{:s}$ / M$_\odot$' .format (sub )
16551705
1656- pl .ylabel (ylbl )
1706+ if dimensional :
1707+ pl .xlabel ('t / min' )
1708+ sub = 'e'
1709+ ylbl = r'M$_{:s}$ / 10$^{{{:d}}}$ M$_\odot$' .format (sub , oom )
1710+ if oom == 0. :
1711+ ylbl = r'M$_{:s}$ / M$_\odot$' .format (sub )
1712+ pl .ylabel (ylbl )
1713+ else :
1714+ pl .xlabel ('t' )
1715+ sub = 'e'
1716+ ylbl = r'M$_{:s}$ / 10$^{{{:d}}}$' .format (sub , oom )
1717+ if oom == 0. :
1718+ ylbl = r'M$_{:s}$' .format (sub )
1719+ pl .ylabel (ylbl )
1720+
16571721 yfmt = FormatStrFormatter ('%.1f' )
16581722 fig2 .gca ().yaxis .set_major_formatter (yfmt )
16591723 fig2 .tight_layout ()
@@ -1663,7 +1727,10 @@ def entr_rate(self, cycles, r_min, r_max, var='ut', criterion='min_grad', \
16631727
16641728 print ('Resolution: {:d}^3' .format (2 * len (r )))
16651729 print ('mtot_fc = ' , mtot_fc )
1666- print ('Entrainment rate: {:.3e} M_Sun/s' .format (mdot ))
1730+ if dimensional :
1731+ print ('Entrainment rate: {:.3e} M_Sun/s' .format (mdot ))
1732+ else :
1733+ print ('Entrainment rate: {:.3e}' .format (mdot ))
16671734
16681735 if return_time_series :
16691736 return t , mir , mb
@@ -9051,7 +9118,7 @@ class RprofSet(PPMtools):
90519118 of PPMstar 2.0.
90529119 '''
90539120
9054- def __init__ (self , dir_name , verbose = 3 , cache_rprofs = True ):
9121+ def __init__ (self , dir_name , verbose = 3 , cache_rprofs = True , geometry = 'spherical' ):
90559122 '''
90569123 Init method.
90579124
@@ -9061,6 +9128,14 @@ def __init__(self, dir_name, verbose=3, cache_rprofs=True):
90619128 Name of the directory to be searched for .rprof files.
90629129 verbose: integer
90639130 Verbosity level as defined in class Messenger.
9131+ cache_rprofs: boolean
9132+ If true all .rprof files loaded from disk will be put into a
9133+ RAM-based cache.
9134+ geometry: 'spherical' or 'cartesian'
9135+ Geometry of the simulation's 3D computational grid. This flag
9136+ changes the definition of high- and low-resolution data as well as
9137+ the behaviour of some methods (such as boundary search, mass
9138+ integrals).
90649139 '''
90659140
90669141 PPMtools .__init__ (self ,verbose = verbose )
@@ -9090,6 +9165,14 @@ def __init__(self, dir_name, verbose=3, cache_rprofs=True):
90909165 'rprof data by simulation time.' )
90919166 self .__messenger .warning (wrng )
90929167
9168+ if geometry in ('spherical' , 'cartesian' ):
9169+ self .__geometry = geometry
9170+ else :
9171+ wrng = ("Unknown geometry ('{:s}'). Using 'spherical' instead. "
9172+ "Unexpected results may occur." .format (geometry ))
9173+ self .__messenger .warning (wrng )
9174+ self .__geometry = 'spherical'
9175+
90939176 self .__is_valid = True
90949177
90959178 def __find_dumps (self , dir_name ):
@@ -9179,6 +9262,13 @@ def get_run_id(self):
91799262
91809263 return str (self .__run_id )
91819264
9265+ def get_geometry (self ):
9266+ '''
9267+ Returns the geometry identifier.
9268+ '''
9269+
9270+ return str (self .__geometry )
9271+
91829272 def get_history (self ):
91839273 '''
91849274 Returns the RprofHistory object associated with this instance if available
@@ -9224,10 +9314,10 @@ def get_dump(self, dump):
92249314 if dump in self .__rprof_cache :
92259315 rp = self .__rprof_cache [dump ]
92269316 else :
9227- rp = Rprof (file_path )
9317+ rp = Rprof (file_path , geometry = self . __geometry )
92289318 self .__rprof_cache [dump ] = rp
92299319 else :
9230- rp = Rprof (file_path )
9320+ rp = Rprof (file_path , geometry = self . __geometry )
92319321
92329322 return rp
92339323
@@ -9722,7 +9812,7 @@ class Rprof:
97229812 PPMstar 2.0.
97239813 '''
97249814
9725- def __init__ (self , file_path , verbose = 3 ):
9815+ def __init__ (self , file_path , verbose = 3 , geometry = 'spherical' ):
97269816 '''
97279817 Init method.
97289818
@@ -9732,10 +9822,24 @@ def __init__(self, file_path, verbose=3):
97329822 Path to the .rprof file.
97339823 verbose: integer
97349824 Verbosity level as defined in class Messenger.
9825+ geometry: 'spherical' or 'cartesian'
9826+ Geometry of the simulation's 3D computational grid. This flag
9827+ changes the definition of high- and low-resolution data as well as
9828+ the behaviour of some methods (such as boundary search, mass
9829+ integrals).
97359830 '''
97369831
97379832 self .__is_valid = False
97389833 self .__messenger = Messenger (verbose = verbose )
9834+
9835+ if geometry in ('spherical' , 'cartesian' ):
9836+ self .__geometry = geometry
9837+ else :
9838+ wrng = ("Unknown geometry ('{:s}'). Using 'spherical' instead. "
9839+ "Unexpected results may occur." .format (geometry ))
9840+ self .__messenger .warning (wrng )
9841+ self .__geometry = 'spherical'
9842+
97399843 if self .__read_rprof_file (file_path ) != 0 :
97409844 return
97419845
@@ -9842,11 +9946,22 @@ def __read_rprof_file(self, file_path):
98429946 # The first number is always the number of radial zones.
98439947 n = int (sline [0 ])
98449948
9845- # n should equal Nx/2 for standard-resolution columns
9846- # and Nx for high-resolution ones.
9949+ # Is this a table of high-resolution variables?
98479950 is_hr = False
9848- if n > self .__header_data ['Nx' ]/ 2 :
9849- is_hr = True
9951+ if self .__geometry == 'spherical' :
9952+ # Nx is the number of computational cells along every dimension
9953+ # of the cubical PPMstar grid. The number of radial bins is
9954+ # Nx/2 for standard-resolution columns and Nx for
9955+ # high-resolution ones (computed from PPB sub-cell data).
9956+ if n > self .__header_data ['Nx' ]/ 2 :
9957+ is_hr = True
9958+ else :
9959+ # Nx is the number of computational cells along the vertical
9960+ # dimension of the computational grid. The number of vertical
9961+ # bins is Nx for standard-resolution columns and 2*Nx for
9962+ # high-resolution ones (computed from PPB sub-cell data).
9963+ if n > self .__header_data ['Nx' ]:
9964+ is_hr = True
98509965
98519966 # Register the columns, skipping the 1st one (IR) and any
98529967 # that are already registered (some columns appear in
0 commit comments