Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b6e6393
Add files via upload
jpkneller Nov 5, 2025
6085250
Rename doc/source/nb/Wright_2017.ipynb to doc/source/nb/pisn/Wright_2…
jpkneller Nov 5, 2025
e98787d
Add files via upload
jpkneller Nov 5, 2025
9cdad25
TypeIa SN models
jpkneller Nov 6, 2025
4b97bcd
Add files via upload
jpkneller Nov 6, 2025
e3d6d47
Add files via upload
jpkneller Nov 6, 2025
96254aa
Rename doc/source/nb/TypeIa.ipynb to doc/source/nb/TypeIa/TypeIa.ipynb
jpkneller Nov 6, 2025
0b21198
Merge pull request #416 from SNEWS2/jpkneller-TypeIa
JostMigenda Jun 10, 2026
66b0724
Update ccsn.py
jpkneller Jun 15, 2026
f504a57
Update base.py
jpkneller Jun 15, 2026
97c6715
Update typeIa_loaders.py
jpkneller Jun 15, 2026
06a505a
Update typeIa.py
jpkneller Jun 15, 2026
ae38d88
Update pisn_loaders.py
jpkneller Jun 15, 2026
08a9f53
Update model_files.yml
jpkneller Jun 15, 2026
e6dd5cc
Merge branch 'main' into jpkneller-pisn-models
jpkneller Jun 15, 2026
d5ae064
Update SNOwGLoBES_models.ipynb
jpkneller Jun 15, 2026
411a35b
Update base.py
jpkneller Jun 15, 2026
c6dcbf7
Update base.py
jpkneller Jun 15, 2026
c3da4d7
Update typeIa.py
jpkneller Jun 15, 2026
aefbb76
Update typeIa_loaders.py
jpkneller Jun 15, 2026
6df4791
Update TypeIa.ipynb
jpkneller Jun 15, 2026
40de9b2
Update base.py
jpkneller Jun 15, 2026
1d27eab
Update typeIa_loaders.py
jpkneller Jun 15, 2026
eb2623f
Update pisn_loaders.py
jpkneller Jun 15, 2026
867c829
Update base.py
jpkneller Jun 15, 2026
72c8421
Update model_files.yml
jpkneller Jun 15, 2026
b95458b
Update model_files.yml
jpkneller Jun 15, 2026
47bd360
Create test_pisn_rates.py
jpkneller Jun 15, 2026
c6500ee
Update test_pisn_rates.py
jpkneller Jun 15, 2026
6eec0fc
Update test_pisn_rates.py
jpkneller Jun 15, 2026
e3f6e81
Update test_pisn_rates.py
jpkneller Jun 15, 2026
40472c1
Update test_pisn_rates.py
jpkneller Jun 15, 2026
3f18a04
Update test_pisn_rates.py
jpkneller Jun 15, 2026
ba5743b
Update test_pisn_rates.py
jpkneller Jun 15, 2026
28fb4fa
Update base.py
jpkneller Jun 17, 2026
61dbfbd
Update base.py
jpkneller Jun 17, 2026
35280e1
Update base.py
jpkneller Jun 17, 2026
46e03da
Update base.py
jpkneller Jun 18, 2026
ad74522
Update base.py
jpkneller Jun 18, 2026
7949db1
Update base.py
jpkneller Jun 18, 2026
a628f16
Update base.py
jpkneller Jun 18, 2026
176c47d
Update base.py
jpkneller Jun 18, 2026
472de1f
Update base.py
jpkneller Jun 18, 2026
aef02fa
Update base.py
jpkneller Jun 19, 2026
ac70072
Update base.py
jpkneller Jun 19, 2026
154efe6
Update base.py
jpkneller Jun 19, 2026
856922b
Update base.py
jpkneller Jun 19, 2026
71ae985
Update base.py
jpkneller Jun 19, 2026
e904771
Update model_files.yml
jpkneller Jun 19, 2026
f871788
Update test_pisn_rates.py
jpkneller Jun 19, 2026
8392556
Update test_pisn_rates.py
jpkneller Jun 19, 2026
a547450
Create test_typeIa_rates.py
jpkneller Jun 19, 2026
cb54312
Merge branch 'main' into jpkneller-pisn-models
jpkneller Jul 9, 2026
d38ee56
Update ccsn_loaders.py
jpkneller Aug 10, 2026
f22d332
Update ccsn.py
jpkneller Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/nb/SNOwGLoBES_models.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"metadata": {},
"outputs": [],
"source": [
"from snewpy.models.ccsn import SNOwGLoBES\n",
"from snewpy.models.base import SNOwGLoBES\n",
"\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt\n",
Expand Down
196 changes: 196 additions & 0 deletions doc/source/nb/TypeIa/TypeIa.ipynb

Large diffs are not rendered by default.

247 changes: 247 additions & 0 deletions doc/source/nb/pisn/Wright_2017.ipynb

Large diffs are not rendered by default.

124 changes: 113 additions & 11 deletions python/snewpy/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
from abc import ABC, abstractmethod

import numpy as np

from astropy import units as u
from astropy.table import Table
from astropy.units import UnitTypeError, get_physical_type
from astropy.units.quantity import Quantity
from scipy.special import loggamma
from snewpy._model_downloader import LocalFileLoader

import tarfile

from scipy import interpolate

import logging

from snewpy._model_downloader import LocalFileLoader
from snewpy.flavor import ThreeFlavor
from snewpy.flavor_transformation import NoTransformation
from functools import wraps
Expand All @@ -23,7 +30,7 @@ def _wrapper(self, *arg, **kwargs):
init(self, *arg, **kwargs)
check(self)
return _wrapper

class SupernovaModel(ABC, LocalFileLoader):
"""Base class defining an interface to a supernova model."""

Expand Down Expand Up @@ -132,10 +139,10 @@ def get_initial_spectra(self, t, E):
A container with the information about the initial neutrino spectra
"""
spectra_dict = self._get_initial_spectra_dict(t, E, flavors=ThreeFlavor)
initial_spectra = flux.Container['1/(MeV*s)'].from_dict(spectra_dict,
time=t,
energy=E,
flavor_scheme=ThreeFlavor)
initial_spectra = flux.Spectrum.from_dict(spectra_dict,
time=t,
energy=E,
flavor_scheme=ThreeFlavor)
return initial_spectra

def get_transformed_spectra(self, t, E, flavor_xform):
Expand All @@ -155,8 +162,8 @@ def get_transformed_spectra(self, t, E, flavor_xform):
flux.Container
A container with the information of the transformed neutrino spectra
"""
initialspectra = self.get_initial_spectra(t, E)
transformed_spectra = flavor_xform.apply_to(initialspectra)
initial_spectra = self.get_initial_spectra(t, E)
transformed_spectra = flavor_xform.apply_to(initial_spectra)
return transformed_spectra

def get_flux (self, t, E, distance, flavor_xform=NoTransformation()):
Expand Down Expand Up @@ -249,7 +256,7 @@ def _get_initial_spectra_dict(self, t, E, flavors=ThreeFlavor):
#Reshape the Energy array to shape [1,len(E)]
E = np.expand_dims(E, axis=0)

initialspectra = {}
initial_spectra = {}

# Estimate L(t), <E_nu(t)> and alpha(t). Express all energies in erg.
E = E.to_value('erg')
Expand Down Expand Up @@ -283,6 +290,101 @@ def _get_initial_spectra_dict(self, t, E, flavors=ThreeFlavor):

#remove unnecessary dimensions, if E or t was scalar:
result = np.squeeze(result)
initialspectra[flavor] = result
initial_spectra[flavor] = result

return initial_spectra

class SNOwGLoBES(SupernovaModel):
"""Subclass for models whose input data is in SNOwBLoBES format"""
def __init__(self, filename, metadata={}):
"""
Parameters
----------
tarfilename: str
Absolute or relative path to tar archive
"""
# Open the requested filename using the model downloader.
datafile = self.request_file(filename)

return initialspectra
tf = tarfile.open(datafile)

# Find the "NoOsc" files.
datafiles = sorted([f.name for f in tf if '.dat' in f.name])
nooscfiles = [df for df in datafiles if 'NoOsc' in df]
nooscfiles.sort(key=len)

# Loop through the NoOsc files and pull out the number fluxes.
self.time = []
self.energy = None
self.initial_spectra = {}
self.interpolation = {}

self._flavorkeys = {ThreeFlavor.NU_E: 'NuE',
ThreeFlavor.NU_E_BAR: 'aNuE',
ThreeFlavor.NU_MU: 'NuMu',
ThreeFlavor.NU_MU_BAR: 'aNuMu',
ThreeFlavor.NU_TAU: 'NuTau',
ThreeFlavor.NU_TAU_BAR: 'aNuTau'}

for nooscfile in nooscfiles:
with tf.extractfile(nooscfile) as f:
logging.debug('Reading {}'.format(nooscfile))
meta = f.readline()
metatext = meta.decode('utf-8')
t = float(metatext.split('TBinMid=')[-1].split('sec')[0])
dt = float(metatext.split('tBinWidth=')[-1].split('s')[0])
dE = float(metatext.split('eBinWidth=')[-1].split('MeV')[0])

data = Table.read(f, format='ascii.commented_header', header_start=-1)
data.meta['t'] = t
data.meta['dt'] = dt
data.meta['dE'] = dE

self.time.append(t)

if self.energy is None:
self.energy = (data['E(GeV)'].data*1000).tolist()

for flavor in ThreeFlavor:
key = self._flavorkeys[flavor]
# convert from flux back to initial spectra: number per /s/erg
spectrum = (data[key].data * (4*np.pi*(u.kpc.to(u.cm,10))**2)/dt/(u.MeV.to(u.erg,dE)) ).tolist()
if flavor in self.initial_spectra:
self.initial_spectra[flavor].append(spectrum)
else:
self.initial_spectra[flavor] = [spectrum]

for flavor in ThreeFlavor:
self.interpolation[flavor] = interpolate.RegularGridInterpolator((self.time, self.energy), self.initial_spectra[flavor], method='cubic')

self.time *= u.s
self.energy *= u.MeV

self.filename = os.path.basename(filename)

def _get_initial_spectra_dict(self, t, E, flavors=ThreeFlavor):
"""Get neutrino spectra/luminosity curves after oscillation.

Parameters
----------
t : astropy.Quantity
Time to evaluate initial spectra.
E : astropy.Quantity or ndarray of astropy.Quantity
Energies to evaluate the initial spectra.
flavors: iterable of snewpy.neutrino.Flavor
Return spectra for these flavors only (default: all)

Returns
-------
spectra : dict
Dictionary of model spectra, keyed by neutrino flavor.
"""
t = u.Quantity(t, ndmin=1).to(u.s).value
E = u.Quantity(E, ndmin=1).to(u.MeV).value
tE_grid = np.stack(np.meshgrid(t, E, indexing='ij'), axis=-1)

initial_spectra = {}
for flavor in ThreeFlavor:
initial_spectra[flavor] = self.interpolation[flavor](tE_grid) / (u.erg * u.s)

return initial_spectra
100 changes: 0 additions & 100 deletions python/snewpy/models/ccsn.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,103 +457,3 @@ def __init__(self, *, Bfield:str, direction:str, rotation:int=None, grav:str=Non
filename = f'{Bfield}_b12_3d_{rotation}deg_snewpy_{direction}.dat'
return super().__init__(filename=filename, metadata=self.metadata)

class SNOwGLoBES:
"""A model that does not inherit from SupernovaModel (yet) and imports a group of SNOwGLoBES files."""

def __init__(self, tarfilename):
"""
Parameters
----------
tarfilename: str
Absolute or relative path to tar archive with SNOwGLoBES files.
"""
self.tfname = tarfilename
tf = tarfile.open(self.tfname)

# For now just pull out the "NoOsc" files.
datafiles = sorted([f.name for f in tf if '.dat' in f.name])
noosc = [df for df in datafiles if 'NoOsc' in df]
noosc.sort(key=len)

# Loop through the noosc files and pull out the number fluxes.
self.time = []
self.energy = None
self.flux = {}
self.fmin = 1e99
self.fmax = -1e99

for nooscfile in noosc:
with tf.extractfile(nooscfile) as f:
logging.debug('Reading {}'.format(nooscfile))
meta = f.readline()
metatext = meta.decode('utf-8')
t = float(metatext.split('TBinMid=')[-1].split('sec')[0])
dt = float(metatext.split('tBinWidth=')[-1].split('s')[0])
dE = float(metatext.split('eBinWidth=')[-1].split('MeV')[0])

data = Table.read(f, format='ascii.commented_header', header_start=-1)
data.meta['t'] = t
data.meta['dt'] = dt
data.meta['dE'] = dE

self.time.append(t)
if self.energy is None:
self.energy = (data['E(GeV)'].data*1000).tolist()

for flavor in ['NuE', 'NuMu', 'NuTau', 'aNuE', 'aNuMu', 'aNuTau']:
if flavor in self.flux:
self.flux[flavor].append(data[flavor].data.tolist())
else:
self.flux[flavor] = [data[flavor].data.tolist()]

# We now have a table with rows=times and columns=energies. Transpose
# so that rows=energy and cols=time.
for k, v in self.flux.items():
self.flux[k] = np.transpose(self.flux[k])
self.fmin = np.minimum(self.fmin, np.min(self.flux[k]))
self.fmax = np.maximum(self.fmax, np.max(self.flux[k]))

def get_fluence(self, t):
"""Return the fluence at a given time t.

Parameters
----------
t : float
Time in seconds.

Returns
-------
fluence : dict
A dictionary giving fluence at time t, keyed by flavor.
"""
# Get index of closest element in the array
idx = np.abs(np.asarray(self.time) - t).argmin()

fluence = {}
for k, fl in self.flux.items():
fluence[k] = fl[:,idx]

return fluence

class Analytic3Species(PinchedModel):
"""An analytical model calculating spectra given total luminosity,
average energy, and rms or pinch, for each species.
"""

param = "There are no input files available for this class. Use `doc/scripts/Analytic.py` in the SNEWPY GitHub repo to create a custom input file."

def get_param_combinations(cls):
print(cls.param)
return []

def __init__(self, filename):
"""
Parameters
----------
filename : str
Absolute or relative path to file with model data.
"""

simtab = Table.read(filename,format='ascii')
self.filename = filename
super().__init__(simtab, metadata={})
23 changes: 23 additions & 0 deletions python/snewpy/models/ccsn_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,3 +1069,26 @@ def __init__(self, filename, metadata={}):
tf.close()

super().__init__(simtab, metadata)

class Analytic3Species(PinchedModel):
"""An analytical model calculating spectra given total luminosity,
average energy, and rms or pinch, for each species.
"""

param = "There are no repository files available for this class. Use `doc/scripts/Analytic.py` in the SNEWPY GitHub repo to create a custom input file."

def get_param_combinations(cls):
print(cls.param)
return []

def __init__(self, filename):
"""
Parameters
----------
filename : str
Absolute or relative path to file with model data.
"""

simtab = Table.read(filename,format='ascii')
self.filename = filename
super().__init__(simtab, metadata={})
15 changes: 13 additions & 2 deletions python/snewpy/models/model_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ config:
- &snewpy "https://github.com/SNEWS2/snewpy/raw/v{snewpy_version}/models/{model}/{filename}"
- &ccsn_repository "https://github.com/SNEWS2/snewpy-models-ccsn/raw/v0.4/models/{model}/{filename}"
- &presn_repository "https://github.com/SNEWS2/snewpy-models-presn/raw/v0.2/models/{model}/{filename}"
- &presn_repository_main "https://github.com/SNEWS2/snewpy-models-presn/raw/main/models/{model}/{filename}"
- &presn_repository_main "https://github.com/SNEWS2/snewpy-models-presn/raw/main/models/{model}/{filename}"
- &pisn_typeIa_repository "https://github.com/SNEWS2/snewpy-models-pisn_typeIa/raw/v0.2/models/{model}/{filename}"

models:
ccsn:
Expand Down Expand Up @@ -82,4 +83,14 @@ models:
repository: *presn_repository

Myers_2026:
repository: *presn_repository_main
repository: *presn_repository_main

pisn:
Wright_2017:
repository: *pisn_typeIa_repository

typeIa:
Wright_2016:
repository: *pisn_typeIa_repository


36 changes: 36 additions & 0 deletions python/snewpy/models/pisn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""
The submodule ``snewpy.models.pisn`` contains models of pair-instability supernova neutrino fluxes,
"""
import logging
import os

from astropy import units as u

from snewpy.models import pisn_loaders as loaders
from .base import SupernovaModel

from snewpy.models.registry_model import RegistryModel, Parameter
from snewpy.models.registry_model import all_models

@RegistryModel(
progenitor_mass = [150, 250] * u.Msun,
eos = ['SFHo', 'Helm']
)
class Wright_2017(loaders.Wright_2017):
"""PISN model described in the paper `Neutrino signal from pair-instability supernovae' by Wright et al.,
Phys. Rev. D96 (2017) 103008 <https://journals.aps.org/prd/abstract/10.1103/PhysRevD.96.103008>`_
"""
def _metadata_from_filename(self, filename:str):
metadata = {
'Progenitor mass': float(filename.split('_')[1].strip('Msun')) * u.Msun,
'EOS': filename.split('_')[2].strip('EOS=')
}
return metadata

def __init__(self, progenitor_mass:u.Quantity, eos:str):
filename=f"PISN_{progenitor_mass.to_value('Msun'):.0f}Msun_EOS={eos}_NeutrinoFlux.tar.bz2"
super().__init__(filename, self.metadata)



14 changes: 14 additions & 0 deletions python/snewpy/models/pisn_loaders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
"""
The submodule ``snewpy.models.pisn_loaders`` contains classes to load pair-instability supernova
models from files stored on disk.
"""

from snewpy.models.base import SNOwGLoBES
from snewpy import _model_downloader

class Wright_2017(SNOwGLoBES):
"""PISN model described in the paper `Neutrino signal from pair-instability supernovae' by Wright et al.,
Phys. Rev. D96 (2017) 103008 <https://journals.aps.org/prd/abstract/10.1103/PhysRevD.96.103008>`
"""
pass
Loading
Loading