1- #!/usr/bin/env python
1+ #!/usr/bin/env python3
22
33import argparse
44import matplotlib .pyplot as plt
88from uncertainties import unumpy
99
1010# Set some fontsize
11- SMALL_SIZE = 18
12- MEDIUM_SIZE = 24
13- BIGGER_SIZE = 28
11+ SMALL_SIZE = 16
12+ MEDIUM_SIZE = 18
13+ BIGGER_SIZE = 20
1414
1515plt .rc ('font' , size = SMALL_SIZE ) # controls default text sizes
1616plt .rc ('axes' , titlesize = SMALL_SIZE ) # fontsize of the axes title
2424plt .rc ('ytick.major' , size = 7 , width = 2 )
2525plt .rc ('ytick.minor' , size = 5 , width = 1 )
2626
27- parser = argparse .ArgumentParser (description = '''This script uses output
28- from front_tracker.py to plot the time evolution of flame front θ.
29- ''' )
30-
31- parser .add_argument ('tracking_fnames' , nargs = '+' , type = str ,
32- help = 'cvs file generated from front_tracker.py to track flame front position' )
33- parser .add_argument ('--tmin' , default = 0.0 , type = float ,
34- help = 'minimum time for curve fitting. Note that this will not affect plotting' )
35- parser .add_argument ('--tmax' , type = float ,
36- help = 'maximum time for both plotting and curve fitting' )
37-
38- args = parser .parse_args ()
39-
40- all_data = []
41-
42- # Loop over all tracking files and append them
43- for fname in args .tracking_fnames :
44- df = pd .read_csv (fname )
45- all_data .append (df )
46-
47- # concatenate all files together
48- tracking_data = pd .concat (all_data , ignore_index = True )
49-
50- # sort by the time
51- tracking_data = tracking_data .sort_values (by = 'time[ms]' )
52-
53- # data has columns: fname, time, front_theta, theta_max_avg, max_avg, theta_max, max_val.
54- # Get time and theta, these should already be time-sorted
55- times = tracking_data ['time[ms]' ]
56- front_thetas = tracking_data ['flame_theta' ]
57-
58- # Only plot up to tmax
59- if args .tmax is not None :
60- cond = times < args .tmax
61- times = times [cond ]
62- front_thetas = front_thetas [cond ]
63-
64- # Now do a curve fit to the data.
65- # Now apply tmin so that we ignore the transient phase during fitting
66- fit_times = times [args .tmin <= times ]
67- fit_front_thetas = front_thetas [args .tmin <= times ]
68-
6927# Use tanh + quadratic fit
7028def tanh_func (t , a0 , v0 , x0 , a , b , c ):
7129 return 0.5 * a0 * t ** 2 + v0 * t + x0 + a * np .tanh (t / b + c )
@@ -74,52 +32,206 @@ def tanh_func(t, a0, v0, x0, a, b, c):
7432def utanh_func (t , a0 , v0 , x0 , a , b , c ):
7533 return 0.5 * a0 * t ** 2 + v0 * t + x0 + a * unumpy .tanh (t / b + c )
7634
77- # Give initial guess and solve for different parameters
78- # error is given by the square root of the diagonal of the covariance matrix.
79- init_guess = np .array ([0.0 , 0.0004 , 0.0 , 0.01 , 5 , - 2.0 ])
80- popt , pcov = curve_fit (tanh_func , fit_times , fit_front_thetas , p0 = init_guess , method = "lm" )
81- err = np .sqrt (np .diag (pcov ))
82-
83- # Given the fitted parameters, recreate fitted curve along with error propagation
84- # Error propagation is handled by the uncertainties package.
85- # create fitted params with error
86- fitted_params = unumpy .uarray (popt , err )
87- theta_fit = utanh_func (fit_times , * fitted_params )
88- theta_nominal = unumpy .nominal_values (theta_fit )
89- theta_err = unumpy .std_devs (theta_fit )
90-
91- # Now use the fitted parameter to calculate angular velocity
92- # This is the derivative of utanh_func
93- def angular_velocity (t , a0 , v0 , x0 , a , b , c ):
94- return a0 * t + v0 + a / (b * unumpy .cosh (t / b + c )** 2 )
95-
96- # Get angular velocity in rad / s
97- w_fit = angular_velocity (fit_times , * fitted_params ) * 1e3
98- w_nominal = unumpy .nominal_values (w_fit )
99- w_err = unumpy .std_devs (w_fit )
100-
101- # Now do plotting
102- fig , ax = plt .subplots ()
103- ax .plot (times , front_thetas , 'x' , color = 'k' , label = 'θ: data' )
104- ax .plot (fit_times , theta_nominal , linewidth = 3 , color = 'blue' , linestyle = '--' , label = 'θ: fit' )
105- ax .fill_between (fit_times , theta_nominal - theta_err , theta_nominal + theta_err ,
106- alpha = 0.3 , color = 'skyblue' , label = 'θ: 1σ band' )
107- ax .set_xlabel ("time [ms]" )
108- ax .set_ylabel ("θ [rad]" )
109- ax .set_ylim (0.06 , None )
110-
111- # Create twin ax to plot angular velocity
112- ax_twin = ax .twinx ()
113- ax_twin .plot (fit_times , w_nominal , linewidth = 3 , color = 'red' , linestyle = '-.' , label = 'ω: fit' )
114- ax_twin .fill_between (fit_times , w_nominal - w_err , w_nominal + w_err ,
115- alpha = 0.3 , color = 'salmon' , label = 'ω: 1σ band' )
116- ax_twin .set_ylabel ("ω [rad/s]" )
117-
118- # Combine legend
119- lines1 , labels1 = ax .get_legend_handles_labels ()
120- lines2 , labels2 = ax_twin .get_legend_handles_labels ()
121- ax .legend (lines1 + lines2 , labels1 + labels2 , loc = 'center right' , frameon = False )
122-
123- fig .tight_layout ()
124- fig .set_size_inches (8 , 8 )
125- fig .savefig ("flame_position.png" , bbox_inches = "tight" )
35+ # def angular_velocity(t, a0, v0, x0, a, b, c):
36+ # return a0*t + v0 + a / (b * unumpy.cosh(t/b + c)**2)
37+
38+ def quadratic_func (t , a0 , v0 , x0 ):
39+ return 0.5 * a0 * t ** 2 + v0 * t + x0
40+
41+ def angular_velocity (t , a0 , v0 , x0 ):
42+ return a0 * t + v0
43+
44+ def fit_front (times , thetas , tmin , init_guess ):
45+ # Helper function to fit the front
46+
47+ # Now apply tmin so that we ignore the transient phase during fitting
48+ cond = times >= tmin
49+ fit_times = times [cond ]
50+ fit_thetas = thetas [cond ]
51+
52+ # Do the fitting
53+ # error is given by the square root of the diagonal of the covariance matrix.
54+ popt , pcov = curve_fit (quadratic_func , fit_times , fit_thetas , p0 = init_guess , method = "lm" )
55+ err = np .sqrt (np .diag (pcov ))
56+
57+ # Given the fitted parameters, recreate fitted curve along with error propagation
58+ # Error propagation is handled by the uncertainties package.
59+ # create fitted params with error
60+ fitted_params = unumpy .uarray (popt , err )
61+ theta_fit = quadratic_func (fit_times , * fitted_params )
62+ theta_nominal = unumpy .nominal_values (theta_fit )
63+ theta_err = unumpy .std_devs (theta_fit )
64+
65+ # Get angular velocity in rad / s
66+ w_fit = angular_velocity (fit_times , * fitted_params ) * 1e3
67+ w_nominal = unumpy .nominal_values (w_fit )
68+ w_err = unumpy .std_devs (w_fit )
69+
70+ return {"fit_times" : fit_times ,
71+ "theta_nominal" : theta_nominal , "theta_err" : theta_err ,
72+ "w_nominal" : w_nominal , "w_err" : w_err }
73+
74+ if __name__ == "__main__" :
75+ parser = argparse .ArgumentParser (description = '''This script uses output
76+ from front_tracker.py to plot the time evolution of flame front θ.''' )
77+
78+ parser .add_argument ('tracking_fnames' , nargs = '+' , type = str ,
79+ help = 'cvs file generated from front_tracker.py to track flame front position' )
80+ parser .add_argument ("--figsize" , nargs = 2 , type = float , default = [7 , 9 ],
81+ metavar = ("WIDTH" , "HEIGHT" ), help = "Figure size in inches." )
82+ parser .add_argument ('--ash-tmin' , default = 0.0 , type = float ,
83+ help = 'minimum time for ash front curve fitting. Note that this will not affect plotting' )
84+ parser .add_argument ('--flame-tmin' , default = 0.0 , type = float ,
85+ help = 'minimum time for ash front curve fitting. Note that this will not affect plotting' )
86+ parser .add_argument ('--tmax' , type = float ,
87+ help = 'maximum time for both plotting and curve fitting. This applies to both flame and ash' )
88+ parser .add_argument ('--initial-guess' , nargs = 3 , type = float ,
89+ default = None , metavar = "a0, v0, x0" ,
90+ help = "initial guess for the quadratic front fit" )
91+ parser .add_argument ('--plot-stride' , type = int , default = 1 ,
92+ help = """Interval at which we plot the raw front position data.
93+ Increasing it can make plot look nicer""" )
94+ parser .add_argument ("-o" , "--output" , default = None , type = str , metavar = "FILENAME" ,
95+ help = "Output filename (PNG). If not set, shows interactive plot." )
96+
97+ args = parser .parse_args ()
98+ all_data = []
99+
100+ # Loop over all tracking files and append them
101+ for fname in args .tracking_fnames :
102+ df = pd .read_csv (fname )
103+ all_data .append (df )
104+
105+ # concatenate all files together
106+ # then sort by the time
107+ tracking_data = pd .concat (all_data , ignore_index = True )
108+ tracking_data = tracking_data .sort_values (by = 'time[ms]' )
109+
110+ # Columns available are
111+ # fname,time[ms],flame_theta,theta_max_avg,max_avg_enuc,ash_theta
112+ # Get time and theta, these should already be time-sorted
113+ times = tracking_data ['time[ms]' ]
114+ flame_thetas = tracking_data ['flame_theta' ]
115+ ash_thetas = tracking_data ['ash_theta' ]
116+ t_nuc = tracking_data ['t_nuc' ]
117+ D = tracking_data ['D' ]
118+ ocean_height = tracking_data ['ocean_height' ]
119+ coriolis_param = tracking_data ['coriolis_param' ]
120+ ash_velocity = tracking_data ['ash_velocity' ]
121+
122+ # Only plot up to tmax
123+ if args .tmax is not None :
124+ cond = times < args .tmax
125+ times = times [cond ]
126+ flame_thetas = flame_thetas [cond ]
127+ ash_thetas = ash_thetas [cond ]
128+ t_nuc = t_nuc [cond ]
129+ D = D [cond ]
130+ ocean_height = ocean_height [cond ]
131+ coriolis_param = coriolis_param [cond ]
132+ ash_velocity = ash_velocity [cond ]
133+
134+ # Now since t_nuc and diffusion coefficient, D, will be None when dataset is smallplt
135+ # since it doesn't have enough information. Do filtering on these dataset.
136+ none_mask = D .notna ()
137+ times_vel = times [none_mask ]
138+ t_nuc = t_nuc [none_mask ]
139+ D = D [none_mask ]
140+ ocean_height = ocean_height [none_mask ]
141+ coriolis_param = coriolis_param [none_mask ]
142+ ash_velocity = ash_velocity [none_mask ]
143+
144+ # Now get the fitted front data
145+ # init_guess = np.array([0.0, 0.0004, 0.0, 0.01, 5, -2.0])
146+ init_guess = args .initial_guess
147+
148+ flame_fit = fit_front (times , flame_thetas , args .flame_tmin , init_guess )
149+ ash_fit = fit_front (times , ash_thetas , args .ash_tmin , init_guess )
150+
151+ # Now do plotting
152+ fig , (ax_theta , ax_velocity ) = plt .subplots (2 , 1 , figsize = (8 , 8 ),
153+ sharex = True , constrained_layout = True )
154+
155+ # Raw data, downsample the data so markers show up nicely
156+ stride = args .plot_stride
157+ ax_theta .plot (times [::stride ], flame_thetas [::stride ], '*' , color = 'k' , markersize = 7 , label = 'flame data' )
158+ ax_theta .plot (times [::stride ], ash_thetas [::stride ], '^' , color = 'k' , markersize = 7 , label = 'ash data' )
159+
160+ # Fit data
161+ ax_theta .plot (flame_fit ["fit_times" ], flame_fit ["theta_nominal" ], linewidth = 4 ,
162+ color = 'tab:blue' , linestyle = '--' , label = 'flame fit' )
163+
164+ ax_theta .plot (ash_fit ["fit_times" ], ash_fit ["theta_nominal" ], linewidth = 4 ,
165+ color = 'tab:green' , linestyle = '--' , label = 'ash fit' )
166+
167+ # plot error of the fit
168+ ax_theta .fill_between (flame_fit ["fit_times" ],
169+ flame_fit ["theta_nominal" ] - flame_fit ["theta_err" ],
170+ flame_fit ["theta_nominal" ] + flame_fit ["theta_err" ],
171+ alpha = 0.5 , color = 'tab:blue' )
172+
173+ ax_theta .fill_between (ash_fit ["fit_times" ],
174+ ash_fit ["theta_nominal" ] - ash_fit ["theta_err" ],
175+ ash_fit ["theta_nominal" ] + ash_fit ["theta_err" ],
176+ alpha = 0.5 , color = 'tab:green' )
177+
178+ ax_theta .set_ylabel (r"$\theta$ [rad]" )
179+ ax_theta .set_ylim (0.06 , None )
180+ ax_theta .grid (linestyle = ":" )
181+ ax_theta .tick_params (top = True , bottom = True , left = True , right = True )
182+ ax_theta .legend (frameon = False )
183+ ax_theta .tick_params (axis = "both" ,direction = "in" )
184+
185+ # Then do velocity plotting.
186+ # Show fitting data and velocity from theoretical prediction.
187+
188+ # Compute theoretical speed.
189+ # Conduction Speed (Landau)
190+ v1 = np .sqrt (D / t_nuc ) * 1e-5 # convert to km/s
191+
192+ # Ageotrophic Speed (Spitkovski 2002)
193+ g = 1.5e14
194+ L_R = np .sqrt (g * ocean_height ) / coriolis_param
195+ v2 = L_R / t_nuc * 1e-5
196+
197+ # Conduction + Ageotrophic (Cavecchi 2013)
198+ v3 = 2.5 * np .sqrt (D / t_nuc ) * L_R / ocean_height * 1e-5
199+
200+ # Plot theoretical speeds
201+ ax_velocity .plot (times_vel [::stride ], v1 [::stride ], 'v' , color = 'k' , markersize = 7 , label = r'$\sqrt{D/t_n}$' )
202+ ax_velocity .plot (times_vel [::stride ], v2 [::stride ], 'p' , color = 'k' , markersize = 7 , label = r'$L_R/t_n$' )
203+ ax_velocity .plot (times_vel [::stride ], v3 [::stride ], 'X' , color = 'k' , markersize = 7 , label = r'$L_R/H \sqrt{D/t_n} $' )
204+
205+ # Assume neutron star of radius 11 km, so linear speed is R*omega
206+ R = 11
207+ ax_velocity .plot (flame_fit ["fit_times" ], R * flame_fit ["w_nominal" ], linewidth = 3 ,
208+ color = 'tab:red' , linestyle = '-.' , label = 'flame speed' )
209+
210+ ax_velocity .plot (ash_fit ["fit_times" ], R * ash_fit ["w_nominal" ], linewidth = 3 ,
211+ color = 'tab:orange' , linestyle = '-.' , label = 'ash speed' )
212+
213+ ax_velocity .fill_between (flame_fit ["fit_times" ],
214+ R * (flame_fit ["w_nominal" ] - flame_fit ["w_err" ]),
215+ R * (flame_fit ["w_nominal" ] + flame_fit ["w_err" ]),
216+ alpha = 0.5 , color = 'tab:red' )
217+
218+ ax_velocity .fill_between (ash_fit ["fit_times" ],
219+ R * (ash_fit ["w_nominal" ] - ash_fit ["w_err" ]),
220+ R * (ash_fit ["w_nominal" ] + ash_fit ["w_err" ]),
221+ alpha = 0.5 , color = 'tab:orange' )
222+
223+ ax_velocity .set_ylabel (r"R $\omega$ [km $s^{-1}$]" )
224+ ax_velocity .grid (linestyle = ":" )
225+ ax_velocity .tick_params (top = True , bottom = True , left = True , right = True )
226+ ax_velocity .legend (frameon = False )
227+ ax_velocity .tick_params (axis = "both" ,direction = "in" )
228+
229+ ax_velocity .set_xlabel ("time [ms]" )
230+
231+ fig .tight_layout ()
232+ fig .set_size_inches (* args .figsize )
233+ # Store to output, otherwise show plot
234+ if args .output is not None :
235+ fig .savefig (args .output , format = "png" , bbox_inches = "tight" )
236+ else :
237+ plt .show ()
0 commit comments