44import glob
55import yt
66import numpy as np
7+ import argparse
8+ from concurrent .futures import ProcessPoolExecutor , as_completed
79from yt .frontends .boxlib .api import CastroDataset
10+ from yt .units import cm
811
9-
10- def track_flame_front (ds , metric ):
12+ def track_flame_front (ds , args ):
1113 '''
1214 This function tracks the flame front position for a given dataset.
13- It returns a tuple of the form: ( Time (in ms), Theta)
15+ It returns a list of the form: [ Time (in ms), Theta, averaged_max_field, Theta_max, max_field]
1416
1517 Procedure to determine flame front:
16- 1) User selects e a quantity to use as a metric: enuc or Temp
18+ 1) User selects a quantity to use as a metric: enuc or Temp
1719 2) Determine the global max of that quantity
18- 3) Determine the theta position of the cell that contains the global max
20+ 3) Determine the minimum value required to consider averaging zones based on global max
1921 4) Do a radial average of the data set to convert to 1D as a function of theta
20- 5) Determine flame front at theta where the metric quantity drops to
21- metric_number * global_max of that quantity.
22+ 5) Determine flame front at theta where the radially averaged quantity drops to
23+ percent * averaged_max of that quantity.
2224 '''
2325
2426 time = ds .current_time .in_units ("ms" )
27+ rr = ds .domain_right_edge [0 ].in_units ("cm" )
28+ rl = ds .domain_left_edge [0 ].in_units ("cm" )
29+
30+ thetar = ds .domain_right_edge [1 ]
31+ thetal = ds .domain_left_edge [1 ]
32+ dtheta = thetar - thetal
33+
34+ max_level = ds .index .max_level
35+ ref_ratio = int (np .prod (ds .ref_factors [0 :max_level ]))
36+
37+ # Assume default resolution using finest grid
38+ default_res = ds .domain_dimensions * ref_ratio
39+
40+ ###
41+ ### Select data by choosing rays at different theta
42+ ###
43+
44+ # Get different possible cell-centered thetas
45+ thetas = np .linspace (thetal , thetar , default_res [1 ], endpoint = False ) + 0.5 * dtheta / default_res [1 ]
46+
47+ # Container for the radially averaged field quantity, enuc or temp
48+ averaged_field = []
49+
50+ # First determine the global max of field quantity
51+ max_val = ds .all_data ()[args .field ].max ()
52+
53+ # Determine a threshold of selecting zones for the average, i.e. minimum value allowed
54+ min_val = max_val * args .threshold
2555
56+ # track the theta that has the maximum global value
57+ max_theta_loc = 0.0
2658
27- ad = ds .all_data ()
59+ # Loop over different thetas
60+ for theta in thetas :
61+ # simply go from (rmin, theta) -> (rmax, theta). Doesn't need to convert to physical R-Z
62+ ray = ds .ray ((rl , theta , 0 * cm ), (rr , theta , 0 * cm ))
2863
64+ # sort by "t", which goes from 0 to 1 representing the spatial order.
65+ # isrt = np.argsort(ray["t"])
2966
30- # 1) Global max temperature: this can be used to track initial
31- # detonation resulted from the initial temperature perturbation
32- # 2) Use radially averaged enuc then find flame front is determined by
33- # when the averaged enuc drops below some percentage of max global enuc
67+ # Do the tracking
68+ if any (ray [args .field ) == max_val ):
69+ max_theta_loc = theta
3470
71+ # Consider zones that are larger than minimum value
72+ valid_zones = ray [args .field ] > min_val
73+ valid_values = ray [args .field ][valid_zones ]
74+
75+ if len (valid_values ) > 0 :
76+ averaged_field .append (valid_values .mean ())
77+ else :
78+ averaged_field .append (0.0 )
79+
80+ # Now Determine the index of the maximum radially averaged field
81+ max_index = np .argmax (averaged_field )
82+
83+ # Now assuming flame moves forward in theta, find theta such that the field drops below some threshold of the averaged max
84+ loc_index = averaged_field [max_index :] < args .percent * max (averaged_field )
85+
86+ # Find the first theta that the field drops below the threshold.
87+ theta_loc = thetas [max_index :][loc_index ][0 ]
88+
89+ # Returns 5 quantities
90+ # 1) time in ms
91+ # 2) theta that corresponds to the maximum averaged value
92+ # 3) maximum averaged value
93+ # 4) theta that corresponds to the maximum global value
94+ # 5) maximum global value
95+ timeTheta = [time , theta_loc , max (averaged_field ), max_theta_loc , max_val ]
3596
3697 return timeTheta
3798
3899
100+ def process_dataset (fname , args ):
101+ ds = CastroDataset (fname )
102+
103+ # Returns a list [time, theta, max averaged value, theta_max, max value]
104+ return track_flame_front (ds , args )
105+
39106if __name__ == "__main__" :
40107 parser = argparse .ArgumentParser (description = """
41108 This file tracks the flame front and writes them into a txt file.
@@ -47,9 +114,17 @@ def track_flame_front(ds, metric):
47114 metavar = "{enuc, Temp}" ,
48115 help = """field parameter used as metric to determine flame front.
49116 Choose between {enuc, Temp}""" )
50- parser .add_argument ('--percent' , '-p' , default = 0.001 , type = float ,
117+ parser .add_argument ('--percent' , '-p' , default = 1e-3 , type = float ,
51118 help = """Float number between (0, 1]. Representing the percent of
52- the global maximum of the field quantity used to track the flame.""" )
119+ the averaged maximum of the field quantity used to track the flame.""" )
120+ parser .add_argument ('--threshold' , '-t' , default = 1.e-6 , type = float ,
121+ help = """Float number between (0, 1]. Representine the percent of
122+ the global maximum of the field quantity used to select valid zones
123+ for averaging""" )
124+ parser .add_argument ('--jobs' , '-j' , default = 1 , type = int ,
125+ help = """Number of workers to process plot files in parallel"""
126+ parser .add_argument ('--out' , '-o' , default = "front_tracking.dat" , type = str ,
127+ help = """Output filename for the tracking information""" )
53128
54129 args = parser .parse_args ()
55130
@@ -58,20 +133,38 @@ def track_flame_front(ds, metric):
58133 parser .error ("field must be either enuc or Temp" )
59134
60135 if args .percent <= 0.0 or args .percent > 1.0 :
61- parser .error ("metric must be a float between (0, 1]" )
136+ parser .error ("percent must be a float between (0, 1]" )
62137
63- metric = ( args . field , args .percent )
64- timeThetaArray = []
138+ if args . threshold <= 0.0 or args .percent > 1.0 :
139+ parser . error ( "threshold must be a float between (0, 1]" )
65140
66- for fname in args .fnames :
67- ds = CastroDataset (fname )
141+ timeThetaArray = []
68142
69- # Get tuple in form (theta, time)
70- timeTheta = track_flame_front (ds , metric )
71- timeThetaArray .append (timeTheta )
143+ ###
144+ ### Parallelize the loop. Copied from flame_wave/analysis/front_tracker.py
145+ ###
146+ with concurrent .futures .ProcessPoolExecutor (max_workers = args .jobs ) as executor :
147+ future_to_index = {
148+ executor .submit (process_dataset , fname , args ): i
149+ for i , fname in enumerate (args .fnames )
150+ }
151+ try :
152+ for future in as_completed (future_to_index ):
153+ i = future_to_index .pop (future )
154+ try :
155+ timeThetaArray .append (future .result ())
156+ except Exception as exc :
157+ print (f"{ args .fnames [i ]} generated an exception: { exc } " , file = sys .stderr , flush = True )
158+ except KeyboardInterrupt :
159+ print (
160+ "\n *** got ctrl-c, cancelling remaining tasks and waiting for existing ones to finish...\n " ,
161+ flush = True ,
162+ )
163+ executor .shutdown (wait = True , cancel_futures = True )
164+ sys .exit (1 )
72165
73166 # Sort array by time and write to file
74- timeThetaArray .sort ()
167+ timeThetaArray .sort (key = lambda x : x [ 0 ] )
75168 timeThetaArray = np .array (timeThetaArray )
76169
77- np .savetxt ('front_tracking.dat' , timeThetaArray , delimiter = ',' )
170+ np .savetxt (args . out , timeThetaArray , delimiter = ',' )
0 commit comments