5 import matplotlib.pyplot
as plt
6 import matplotlib
as mpl
7 import matplotlib.patches
as patches
8 import matplotlib.patheffects
as PathEffects
9 from matplotlib.collections
import PatchCollection
11 from matplotlib
import cm
12 from matplotlib.patches
import Arrow, FancyBboxPatch
13 from matplotlib.colors
import LogNorm, SymLogNorm
14 from matplotlib.colors
import ListedColormap, LinearSegmentedColormap
15 from matplotlib.animation
import FuncAnimation
16 from wreader
import wreader
18 from nucleus_multiple_class
import nucleus_multiple
24 Class to create an animation of the abundances and their flow for the nuclear reaction network WinNet.
38 separate_fission = True,
39 fission_minflow = 1e-10,
41 flow_adapt_prange = True,
43 flow_adapt_width = True,
44 flow_maxArrowWidth= 2.0,
45 flow_minArrowWidth= 0.3,
46 cmapNameFlow = 'viridis',
51 cmapNameX = 'inferno',
55 addMassBinLabels = True,
57 cmapNameMassBins = 'jet',
58 massBins = [[1,1],[2,71],[72,93],[94,110],[111,144],[145,169],[170,187],[188,205],[206,252],[253,337]],
59 massBinLabels = ['','','1st peak','','2nd peak','rare earths', '', '3rd peak', '', 'fissioning'],
63 additional_plot = 'none',
65 trackedrange = (1e-8, 1),
67 energyrange = (1e10, 1e20),
69 timescalerange = (1e-12, 1e10),
70 timerange = (1e-5 , 1e5),
73 densityrange = (1e-5, 1e12),
74 temperaturerange = (0, 10),
75 yerange = (0.0, 0.55),
82 Path to the WinNet data.
83 fig : matplotlib.figure.Figure
84 Figure to plot the animation on.
86 Folder to save the frames in, default is path/frames.
88 Plot the flow of the abundances.
90 Minimum value for the flow.
92 Maximum value for the flow.
93 separate_fission : bool
94 Separate the fissioning region from the fission products in the flow plot.
95 fission_minflow : float
96 Minimum value to indicate a fission region.
98 Plot the colorbar for the flow.
99 flow_adapt_prange : bool
100 Adapt the color range of the flow to the data.
102 Range (in log10) of the flow.
103 flow_adapt_width : bool
104 Adapt the width of the flow arrows to the flow.
105 flow_maxArrowWidth : float
106 Maximum width of the flow arrows.
107 flow_minArrowWidth : float
108 Minimum width of the flow arrows.
110 Name of the colormap for the flow.
112 Plot the colorbar for the mass fractions.
114 Minimum value for the mass fractions.
116 Maximum value for the mass fractions.
118 Name of the colormap for the mass fractions.
120 Plot the average mass number.
123 addMassBinLabels : bool
124 Add labels to the mass bins.
125 alphaMassBins : float
126 Transparency of the mass bins.
127 cmapNameMassBins : str
128 Name of the colormap for the mass bin color.
132 List of labels for the mass bins.
134 Plot the magic numbers in the abundance plot.
135 additional_plot : str
136 Additional plot to be made, possible values: 'None', 'timescales', 'energy', 'tracked'.
138 Range of the tracked nuclei plot.
140 Range of the energy axis.
141 timescalerange : tuple
142 Range of the timescales.
144 Range of the time axis.
146 Plot the mainout data.
148 Range of the density axis in the mainout plot.
149 temperaturerange : tuple
150 Range of the temperature axis in the mainout plot.
152 Range of the electron fraction axis in the mainout plot.
154 Plot the WinNet logo.
159 if (mpl.__version__ <
'3.8.0'):
160 print(
'Using old version of Matplotlib ('+str(mpl.__version__)+
'), some features may not work.')
161 print(
'Need 3.8 or higher.')
169 if frame_dir
is None:
238 raise ValueError(f
"Additional plot {self.additional_plot} not recognized. Possible values: 'None', 'timescales', 'energy', 'tracked'")
244 self.N_stab, self.
Z_stab = np.loadtxt(os.path.join(self.
__data_path,
'../../../class_files/data/stableiso.dat'),
245 unpack=
True, usecols=(1, 2), dtype=int)
248 sunet_path = os.path.join(self.
__data_path,
"sunet_really_complete")
249 nuclei_names = np.loadtxt(sunet_path,dtype=str)
259 self.
nMagic = [8, 20, 28, 50, 82, 126, 184]
260 self.
zMagic = [8, 20, 28, 50, 82, 114]
265 self.
timescale_entries = [[
'ag',
'ga'],[
'ng',
'gn'],[
'an',
'na'],[
'np',
'pn'],[
'pg',
'gp'],[
'ap',
'pa'],[
"beta"],[
"bfiss"],[
"nfiss"],[
"sfiss"]]
266 self.
timescale_labels = [
r"$\tau_{\alpha,\gamma}$",
r"$\tau_{n,\gamma}$",
r"$\tau_{\alpha,n}$",
r"$\tau_{n,p}$",
r"$\tau_{p,\gamma}$",
267 r"$\tau_{\alpha,p}$",
r"$\tau_{\beta}$",
r"$\tau_{\rm{bfiss}}$",
r"$\tau_{\rm{nfiss}}$",
r"$\tau_{\rm{sfiss}}$"]
270 self.
energy_colors = [
"k",
"C2",
"C3",
"C4",
"C5",
"C6",
"C7",
"C8",
"C9",
"C0"]
271 self.
energy_entries = [
'tot',
'ag_ga',
'ng_gn',
'an_na',
'np_pn',
'pg_gp',
'ap_pa',
'beta',
'fiss']
272 self.
energy_labels = [
'Total',
r"$( \alpha,\gamma )$",
r"$( n,\gamma )$",
r"$( \alpha,n )$",
r"$( n,p )$",
273 r"$( p,\gamma )$",
r"$( \alpha,p )$",
r"$\beta$",
r"$\rm{fission}$"]
278 self.
flow_norm = LogNorm(flow_min, flow_max, clip=
True)
300 Initialize the axes and everything figure related of the plot.
303 mpl.rcParams[
'hatch.linewidth'] = 0.8
307 self.
ax.set_aspect(
'equal')
338 Initialize the axes and everything figure related of the nuclear chart.
341 self.
ax.xaxis.set_visible(
False)
342 self.
ax.yaxis.set_visible(
False)
343 self.
ax.spines[[
'right',
'top',
"bottom",
"left"]].set_visible(
False)
348 Initialize the axes and everything figure related of the mass fraction plot.
351 self.
axAbund = plt.axes([0.15,0.78,0.35,0.15])
352 self.
axAbund.set_xlabel(
r'Mass number $A$')
353 self.
axAbund.set_ylabel(
r'X(A)')
358 self.
axAbund.axvline(np.nan, color=
'tab:red',label=
r"$\bar{A}$")
359 self.
axAbund.legend(loc=
'upper right')
370 Initialize the axes and everything figure related of the timescales plot.
382 Initialize the axes and everything figure related of the tracked nuclei plot.
386 self.
axTracked.set_ylabel(
'Mass fractions')
394 Initialize the axes and everything figure related of the energy plot.
397 self.
axEnergy.set_xlabel(
'Time [s]')
398 self.
axEnergy.set_ylabel(
'Energy [erg/g/s]')
406 Initialize the axes and everything figure related of the mainout plot.
408 def make_patch_spines_invisible(ax):
409 ax.set_frame_on(
True)
410 ax.patch.set_visible(
False)
411 for sp
in ax.spines.values():
412 sp.set_visible(
False)
416 self.
axMainout.set_ylabel(
r'Density [g/cm$^3$]')
441 self.
axMainout_ye.yaxis.set_tick_params(colors=
"tab:blue")
446 Initialize the axes and everything figure related of the WinNet logo.
448 self.
axLogo = plt.axes([0.75,0.45,0.15,0.15])
455 Initialize the data for the plot.
501 abundance_colors = abucmap(np.linspace(0, 1, 256))
503 amount_mass_bins = len(self.
massBins)
504 massbin_colors = massbin_colormap(np.linspace(0, 1, amount_mass_bins))
507 newcolors = np.vstack((massbin_colors, abundance_colors))
510 dist = (np.log10(self.
X_max)-np.log10(self.
X_min))/256.0
511 self.
values = np.linspace(np.log10(self.
X_min)-amount_mass_bins*dist, np.log10(self.
X_max),num=256+amount_mass_bins+1,endpoint=
True)
515 background_Y[:,:] = np.nan
516 for index, mbin
in enumerate(self.
massBins):
540 Initialize the plots.
546 vmax=(max(self.
values)),linewidth=0.0,edgecolor=
"face")
556 cmap=self.
cmapNameFlow, angles=
'xy', scale_units=
'xy', scale=1,
557 units=
'xy', width=0.1, headwidth=3, headlength=4
563 with np.errstate(divide=
'ignore'):
567 a.set_array(self.
flow)
574 fisspos,hatch=
'//////', edgecolor=
'tab:red',facecolor=
'none',
575 linewidth=0.0,zorder=1000
582 fissneg,hatch=
'//////', edgecolor=
'tab:blue',facecolor=
'none',
583 linewidth=0.0,zorder=1000
588 edgecolors = np.full((max(self.
n+1),max(self.
z+1)),
"none")
589 edgecolors[self.N_stab, self.
Z_stab] =
"k"
590 edgecolors = edgecolors.T.ravel()
592 facecolor=
"none",linewidth=0.5,edgecolor=edgecolors)
606 for i,b
in enumerate(self.
massBins):
609 axis_to_data = self.
axAbund.transAxes + self.
axAbund.transData.inverted()
610 data_to_axis = axis_to_data.inverted()
611 trans = data_to_axis.transform((px,py))
613 ha=
'left',va=
'center',clip_on=
False, fontsize=8,color=self.
massbin_colors[i])
614 txt.set_path_effects([PathEffects.withStroke(linewidth=0.5, foreground=
'k')])
627 self.
axMainout.legend(lines, [l.get_label()
for l
in lines],loc=
'upper right')
630 left_side =
"$t$"+
"\n"+rf
"$\rho$"+
"\n"+rf
"$T_9$"+
"\n"+rf
"$Y_e$"
631 right_side = f
"= {self.format_time(self.mainout_time[-1])[0]}\n"+\
632 f
"= {self.to_latex_exponent(self.mainout_density[-1])}\n"+\
633 f
"= {self.to_latex_exponent(self.mainout_temperature[-1])}\n"+\
634 f
"= {self.mainout_ye[-1]:.3f}"
635 units = f
"{self.format_time(self.mainout_time[-1])[1]}\n"+\
642 rect = patches.FancyBboxPatch((0.35, y_pos-0.01), 0.17, 0.135, transform=self.
ax.transAxes, boxstyle=
"round,pad=0.01", ec=
"k", fc=
"lightgrey", zorder=1,alpha=0.5)
643 self.
ax.add_patch(rect)
646 self.
ax.text(0.35, y_pos, left_side, transform=self.
ax.transAxes, fontsize=12,)
647 self.
Mainout_text = self.
ax.text(0.37, y_pos, right_side, transform=self.
ax.transAxes, fontsize=12,)
648 self.
Mainout_units = self.
ax.text(0.48, y_pos, units, transform=self.
ax.transAxes, fontsize=12,)
653 self.
ax.arrow(-8, -8, arrowLength, 0, head_width=2, head_length=2, fc=
'k', ec=
'k')
654 self.
ax.arrow(-8, -8, 0, arrowLength, head_width=2, head_length=2, fc=
'k', ec=
'k')
655 self.
ax.text(arrowLength-8+3,0-8,
'N',horizontalalignment=
'left',verticalalignment=
'center',fontsize=14,clip_on=
True)
656 self.
ax.text(0-8,arrowLength-8+3,
'Z',horizontalalignment=
'center',verticalalignment=
'bottom',fontsize=14,clip_on=
True)
664 self.
axTimescales.legend(loc=
'upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=
True, facecolor=
'white', edgecolor=
'black', framealpha=1.0, fontsize=8)
671 self.
axEnergy.legend(loc=
'upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=
True, facecolor=
'white', edgecolor=
'black', framealpha=1.0, fontsize=8)
678 self.
axTracked.legend(loc=
'upper right', ncol=2, bbox_to_anchor=(1.3, 1.0), frameon=
True, facecolor=
'white', edgecolor=
'black', framealpha=1.0, fontsize=8)
692 self.
ax.text(n,zmin-self.
magic_excess-1,int(n),ha=
'center',va=
'top',clip_on=
True)
702 self.
ax.text(nmin-self.
magic_excess-1,z,int(z),ha=
'right',va=
'center',clip_on=
True)
707 ratio = self.
fig.get_figwidth()/self.
fig.get_figheight()
708 self.
ax.add_patch(mpl.patches.Rectangle((0.88, 0.70), 0.01, 0.01*ratio, fill=
False, transform=self.
ax.transAxes, hatch=
"////", edgecolor=
'tab:blue', lw=1))
709 self.
ax.text(0.9, 0.70,
'Fissioning region', transform=self.
ax.transAxes, fontsize=8, verticalalignment=
'bottom', horizontalalignment=
'left')
710 self.
ax.add_patch(mpl.patches.Rectangle((0.88, 0.67), 0.01, 0.01*ratio, fill=
False, transform=self.
ax.transAxes, hatch=
"////", edgecolor=
'tab:red', lw=1))
711 self.
ax.text(0.9, 0.67,
'Fission products', transform=self.
ax.transAxes, fontsize=8, verticalalignment=
'bottom', horizontalalignment=
'left')
716 Initialize the colorbars.
719 n_cbars = abun_cbar + flow_cbar
724 self.
cax = [self.
fig.add_axes([0.75, 0.88, 0.14, 0.02])]
726 self.
cax = [self.
fig.add_axes([0.58, 0.88, 0.14, 0.02]),
727 self.
fig.add_axes([0.75, 0.88, 0.14, 0.02])]
735 cax=self.
cax[ii], orientation=
'horizontal', label=
'')
736 self.
abun_cbar.ax.set_title(
'Mass fraction')
744 orientation=
'horizontal',
753 Update the data for the plot.
760 mask = xx > self.
X_min
761 self.
abun[self.
N[mask], self.
Z[mask]] = np.log10(xx[mask])
764 self.
Abar = 1.0/np.sum(yy)
768 self.
Xbins[i] = np.sum(xx[mask])
797 fpath=f
'{self.path}/WinNet_data.h5'
799 nin = flow_dict[
'n_in']
800 zin = flow_dict[
'p_in']
801 nout = flow_dict[
'n_out']
802 zout = flow_dict[
'p_out']
803 flow = flow_dict[
'flow']
811 self.
flow = flow[mask]
822 Separate the fissioning region from the fission products in the flow plot.
826 fis_N = self.
flow_N[fis_mask]
827 fis_Z = self.
flow_Z[fis_mask]
828 fis_dn = self.
flow_dn[fis_mask]
829 fis_dz = self.
flow_dz[fis_mask]
830 fis_flow = self.
flow[fis_mask]
838 self.
flow = self.
flow[((~fis_mask) & mask)]
841 for nn, zz, dn, dz, ff
in zip(fis_N, fis_Z, fis_dn, fis_dz, fis_flow):
871 right_side = f
"= {self.to_latex_exponent(self.format_time(self.mainout_time[-1])[0])}\n"+\
872 f
"= {self.to_latex_exponent(self.mainout_density[-1])}\n"+\
873 f
"= {self.to_latex_exponent(self.mainout_temperature[-1])}\n"+\
874 f
"= {self.mainout_ye[-1]:.3f}"
876 units = f
"{self.format_time(self.mainout_time[-1])[1]}\n"+\
898 self.
flow_cbar.mappable.set_clim(vmin=10**lminflow, vmax=10**lmaxflow)
912 with np.errstate(divide=
'ignore'):
916 a.set_array(self.
flow)
933 plt.savefig(f
'{self.frame_dir}/frame_{ii}.png',
934 dpi=300, bbox_inches=
'tight')
941 frames=frames, **kwargs)
947 Convert a number to a latex string with exponent.
951 exponent = np.floor(np.log10(x))
952 mantissa = x / 10**exponent
953 return f
'{mantissa:.2f}'+
r'$\times 10^{'+f
'{int(exponent)}'+
r'}$'
959 Format a time in seconds to a more human readable format.
962 return time*1000,
'ms'
966 return time/60,
'min'
968 return time/3600,
'h'
969 if time < 3600*24*365:
970 return time/86400,
'd'
971 if time < 3600*24*365*1000:
972 return time/31536000,
'y'
973 if time < 3600*24*365*1000*1000:
974 return time/31536000/1000,
'ky'
976 return time/31536000_000_000,
'My'
979 A_unique = np.arange(max(A)+1)
980 X_sum = np.zeros_like(A_unique,dtype=float)
981 for a, x
in zip(A,X):
983 return A_unique, X_sum
989 if __name__ ==
"__main__":
990 run_path =
"../Example_NSM_dyn_ejecta_rosswog_hdf5"
991 fig = plt.figure(figsize=(15, 8))
997 ani = anim.get_funcanimation(interval=10, frames=range(1, anim.n_timesteps))