You can interact with this notebook online: Launch notebook

This notebook is not run by nbsphinx as the data is not available but can be used as a guide.

[ ]:
from pathlib import Path

import numpy as np
import pandas as pd
from astropy import units as u
from matplotlib import pyplot as plt
from radioactivedecay.utils import Z_DICT
from scipy import interpolate

from tardis.io.model.readers.snec.snec_input import read_snec_isotope_profile
from tardis.io.model.readers.snec.snec_output import read_snec_output
[ ]:
SNEC_FOLDER_PATH = Path(
    "~/Downloads/tardis-data/MESA_STIR_MESA_SNEC"
)

Convert SNEC to TARDIS configs

[ ]:
input_isotope_fraction = read_snec_isotope_profile(
    SNEC_FOLDER_PATH / "input" / "profile8.data.iso.dat"
)
[ ]:
input_isotope_fraction_da = input_isotope_fraction.to_xr_array()
[ ]:
snec_output = read_snec_output(SNEC_FOLDER_PATH)
[ ]:
# Build the full dataset using the SNECOutput helper
snec_output_ds = snec_output.to_xr_dataset()
[ ]:
snec_output_ds.enclosed_mass
[ ]:
enclosed_mass_snec = snec_output_ds.enclosed_mass.values
interpolated_isotope_fraction = interpolate.interp1d(
    input_isotope_fraction.enclosed_mass,
    input_isotope_fraction.isotope_mass_fraction.values.T,
    bounds_error=False,
    fill_value=np.nan,
)(enclosed_mass_snec)

interpolated_isotope_fraction_df = pd.DataFrame(
    data=interpolated_isotope_fraction,
    index=input_isotope_fraction.isotope_mass_fraction.columns,
)

# Removing Neutron Fraction
interpolated_isotope_fraction_df.drop([(0, 1)], axis=0, inplace=True)

# Normalizing the isotope fractions
interpolated_isotope_fraction_df = (
    interpolated_isotope_fraction_df / interpolated_isotope_fraction_df.sum(axis=0)
)
[ ]:
interpolated_isotope_fraction_df
[ ]:
# Drop the first time step and the innermost cell
snec_output_ds = snec_output_ds.isel(time=slice(1, None), cell_id=slice(0, None))

Explorational Plotting

[ ]:
%matplotlib inline

fig, ax = plt.subplots(figsize=(8, 6))
vel = snec_output_ds.vel
radius = snec_output_ds.radius
for requested_t_idx in range(1, 10):
    radius_au = (radius.isel(time=requested_t_idx).values * u.cm).to(u.AU)[1:]
    vel_kms = (vel.isel(time=requested_t_idx).values * u.cm / u.s).to(u.km / u.s)[1:]
    current_time = snec_output_ds.time.values[requested_t_idx] * u.s
    homology_kms = (radius_au / current_time).to(u.km / u.s)

    ax.plot(
        radius_au.value / radius_au.max(),
        ((vel_kms - homology_kms) / homology_kms).to(1).value,
        label=f"time={current_time.to(u.day):.2f}",
        marker="|",
    )
ax.axhline(0, color="black", linestyle="--", lw=3)
ax.set_xlabel("Radius (normalized)")
ax.set_ylabel("Fractional Difference $(v - v_{hom})/v_{hom}$")
ax.set_title("Fractional Difference: Actual vs Homology Velocity")
ax.legend()
plt.tight_layout()
plt.show()
plt.savefig("homology_reached.pdf")

[ ]:
%matplotlib inline

fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(
    (snec_output_ds.time.values[1:] * u.s).to(u.day),
    (snec_output_ds.vel_photo.values * (u.cm / u.s)).to(u.km / u.s)[1:],
)  # km/s
ax.set_xlabel("Time [days]")
ax.set_ylabel("Photosphere Velocity [km/s]")
ax.set_title("Photosphere Velocity Evolution")

Setting photospheric phase

[ ]:
%matplotlib inline

GAMMA_RAY_ESCAPE_THRESHOLD = 0.2  # used in Lu et al. 2024

fig, ax = plt.subplots(figsize=(8, 6))
fraction_gamma_ray = (
    snec_output_ds.lum_observed.values[1:] - snec_output_ds.lum_photo.values[1:]
) / snec_output_ds.lum_observed.values[1:]
time_days = (snec_output_ds.time.values[1:] * u.s).to(u.day).value
ax.plot(time_days, fraction_gamma_ray, label="Gamma-ray Fraction")

ax.axhline(
    GAMMA_RAY_ESCAPE_THRESHOLD,
    color="black",
    linestyle="--",
    lw=3,
    label="Threshold (20%)",
)
ax.set_xlabel("Time [days]")
ax.set_ylabel("Fraction of Escaping Gamma-ray Luminosity")
ax.set_title("Fraction of Escaping Gamma-ray Luminosity vs Time")

PHOTOSPHERE_START_INDEX = 1
PHOTOSPHERE_END_INDEX = np.abs(fraction_gamma_ray - GAMMA_RAY_ESCAPE_THRESHOLD).argmin()

# Calculate photosphere times in days
photosphere_start_day = (
    (snec_output_ds.time.values[PHOTOSPHERE_START_INDEX] * u.s).to(u.day).value
)
photosphere_end_day = (
    (snec_output_ds.time.values[PHOTOSPHERE_END_INDEX] * u.s).to(u.day).value
)

# Shade the photosphere region with axvspan
ax.axvspan(
    photosphere_start_day,
    photosphere_end_day,
    color="orange",
    alpha=0.2,
    label="Photosphere Region",
)

# Add a textbox in the middle of the shaded region
midpoint = (photosphere_start_day + photosphere_end_day) / 2
ax.text(
    midpoint,
    GAMMA_RAY_ESCAPE_THRESHOLD + 0.05,
    "Photospheric phase",
    ha="center",
    va="bottom",
    fontsize=10,
    bbox=dict(facecolor="orange", alpha=0.2, edgecolor="none"),
)

ax.legend()
plt.tight_layout()
plt.show()
plt.savefig("plots/gamma_ray_escape_photosphere.pdf")

[ ]:
%matplotlib inline
# Find the index in time_days closest to requested_time
requested_time = 120 * u.min
requested_t_idx = np.abs(time_days - requested_time.to(u.day).value).argmin()
x_axis_type = "vel"

if x_axis_type != "vel":
    raise ValueError("does not work - fix the Unit Conversion")
# Get the x-axis (enclosed mass) from the second time index onwards (skip time=0)
x_axis = snec_output_ds[x_axis_type].isel(time=requested_t_idx).values

if x_axis_type == "vel":
    x_axis = (x_axis * u.cm / u.s).to(u.km / u.s).value
    x_axis_label = "Velocity [km/s]"

if x_axis_type == "mass":
    x_axis = (x_axis * u.g).to(u.Msun).value
    x_axis_label = "Enclosed Mass [Msun]"

if x_axis_type == "radius":
    x_axis = (x_axis * u.cm).to(u.Rsun).value
    x_axis_label = "Radius [Rsun]"


# Convert the multi_index of interpolated_isotope_fraction_df to isotope names
df_iso = interpolated_isotope_fraction_df.copy()
df_iso.index = [f"{Z_DICT.get(z, z)}-{mass}" for z, mass in df_iso.index]
plt.clf()
fig, ax = plt.subplots(figsize=(8, 6))
for isotope in df_iso.index:
    fraction = df_iso.loc[isotope]
    ax.plot(x_axis, fraction, label=f"{isotope}", alpha=0.5, marker="|")

photo_location_last = 1e99
# Add photosphere positions as vertical lines with time labels

no_label = True
x_axis_span = x_axis.max() - x_axis.min()
for idx in range(PHOTOSPHERE_START_INDEX + 1, PHOTOSPHERE_END_INDEX + 1):
    snec_time_slice = snec_output_ds.isel(time=idx)
    photosphere_index = int(snec_time_slice.index_photo.values)
    photosphere_location = (
        (
            snec_time_slice.sel(cell_id=photosphere_index)[x_axis_type].values
            * u.cm
            / u.s
        )
        .to(u.km / u.s)
        .value
    )
    t_day = (snec_time_slice.time.values * u.s).to(u.day)
    if np.abs(photo_location_last - photosphere_location) < (x_axis_span / 20):
        continue

    # Find the cell where velocity is closest to v_photo at time=0
    vel0_kms = (vel.isel(time=0).values[1:] * u.cm / u.s).to(u.km / u.s).value

    ax.axvline(
        photosphere_location,
        color="black",
        linestyle="--",
        label="Photosphere position" if no_label else "",
        lw=1,
    )
    no_label = False
    ax.text(
        photosphere_location,
        0.5,
        f"{t_day:.1f}",
        rotation=90,
        color="black",
        va="center",
        ha="right",
        fontsize=8,
        alpha=0.7,
        transform=ax.get_xaxis_transform(),
    )
    photo_location_last = photosphere_location

ax.set_xlabel(x_axis_label)
ax.set_ylabel("Isotope Mass Fraction")
ax.set_title(
    f"{x_axis_label} @time t={requested_time:.2f}[idx={requested_t_idx}] vs Isotope Fraction"
)
ax.legend()
plt.tight_layout()
plt.show()
# plt.semilogy()
# plt.savefig("plots/isotope_fraction_photosphere_enclosed_mass.pdf")