Skip to content

Commit 350b833

Browse files
authored
update on many plotting scripts for xrb_spherical problem (#3343)
1 parent b15b00b commit 350b833

6 files changed

Lines changed: 571 additions & 171 deletions

File tree

Lines changed: 207 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22

33
import argparse
44
import matplotlib.pyplot as plt
@@ -8,9 +8,9 @@
88
from 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

1515
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
1616
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
@@ -24,48 +24,6 @@
2424
plt.rc('ytick.major', size=7, width=2)
2525
plt.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
7028
def 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):
7432
def 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

Comments
 (0)