├── .github └── workflows │ ├── integration.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── doc ├── Makefile ├── paper.bib ├── paper.md ├── snewpy-flowchart.pdf └── source │ ├── .static │ └── README │ ├── .templates │ └── README │ ├── citing.rst │ ├── conf.py │ ├── contributing.rst │ ├── flux.rst │ ├── gettingstarted.rst │ ├── index.rst │ ├── luminosity-comparison.pdf │ ├── luminosity-comparison.png │ ├── models.rst │ ├── nb │ ├── AnalyticFluence.ipynb │ ├── FlavorTransformation.ipynb │ ├── README.md │ ├── SNOwGLoBES_models.ipynb │ ├── SNOwGLoBES_usage.ipynb │ ├── ccsn │ │ ├── Bollig_2016.ipynb │ │ ├── Bugli_2021.ipynb │ │ ├── Fischer_2020.ipynb │ │ ├── Fornax_2019.ipynb │ │ ├── Fornax_2021.ipynb │ │ ├── Fornax_2022.ipynb │ │ ├── Kuroda_2020.ipynb │ │ ├── Mori_2023.ipynb │ │ ├── Nakazato_2013.ipynb │ │ ├── OConnor_2013.ipynb │ │ ├── OConnor_2015.ipynb │ │ ├── Sukhbold_2015.ipynb │ │ ├── Tamborra_2014.ipynb │ │ ├── Walk_2018.ipynb │ │ ├── Walk_2019.ipynb │ │ ├── Warren_2020.ipynb │ │ └── Zha_2021.ipynb │ ├── dev │ │ ├── Detector_demo.ipynb │ │ ├── ExtendedCoolingTail.ipynb │ │ └── FluxContainer_demo.ipynb │ └── presn │ │ ├── Kato_2017.ipynb │ │ ├── Odrzywolek_2010.ipynb │ │ ├── Patton_2017.ipynb │ │ └── Yoshida_2016.ipynb │ ├── neutrino.rst │ ├── notebooks.rst │ ├── snewpy-logo-bh.png │ ├── snewpy-logo-revival.png │ ├── snewpy-logo.png │ ├── snowglobes.rst │ └── transformations.rst ├── pyproject.toml ├── pytest.ini └── python └── snewpy ├── __init__.py ├── _model_downloader.py ├── flavor.py ├── flavor_transformation ├── TransformationChain.py ├── __init__.py ├── base.py ├── in_earth.py ├── in_sn.py └── in_vacuum.py ├── flux.py ├── models ├── __init__.py ├── base.py ├── ccsn.py ├── ccsn_loaders.py ├── extended.py ├── model_files.yml ├── presn.py ├── presn_loaders.py └── registry_model.py ├── neutrino.py ├── rate_calculator.py ├── scripts ├── Analytic.py ├── Convert_to_ROOT.py ├── SNEWS2.0_rate_table.py ├── SNEWS2.0_rate_table_singleexample.py ├── TimeSeries.py ├── make_rate_table_dict.py ├── old_to_snowglobes.py └── snewpy_to_snewpdag.py ├── snowglobes.py ├── snowglobes_interface.py ├── test ├── __init__.py ├── _rate_crosscheck_table.py ├── simplerate_integrationtest.py ├── test_00_init.py ├── test_01_registry.py ├── test_02_models.py ├── test_03_neutrino.py ├── test_04_xforms.py ├── test_05_snowglobes.py ├── test_flavors.py ├── test_flux_container.py ├── test_presn_rates.py └── test_rate_calculation.py └── utils.py /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | # Workflow that installs SNOwGLoBES and runs an integration test 2 | 3 | name: Integration Tests 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the main branch 7 | on: 8 | push: 9 | branches: 10 | - main 11 | - release_* 12 | pull_request: 13 | branches: 14 | - main 15 | - release_* 16 | types: [opened, synchronize, reopened, ready_for_review] 17 | 18 | jobs: 19 | run: 20 | # Only execute integration tests if PR is *not* a draft 21 | if: github.event.pull_request.draft == false 22 | 23 | strategy: 24 | matrix: 25 | # Test all supported Python versions under Ubuntu 26 | os: [ubuntu-latest] 27 | python-version: ['3.10', '3.11', '3.12', '3.13'] 28 | # Additionally, test one Python version under MacOS and Windows, to detect OS-specific issues 29 | include: 30 | - os: macos-latest 31 | python-version: '3.12' 32 | - os: windows-latest 33 | python-version: '3.12' 34 | 35 | runs-on: ${{ matrix.os }} 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Set up Python ${{ matrix.python-version }} 40 | uses: actions/setup-python@v5 41 | with: 42 | python-version: ${{ matrix.python-version }} 43 | - name: Install dependencies 44 | run: | 45 | python -m pip install --upgrade pip 46 | - name: Install SNEWPY 47 | run: | 48 | pip install ".[dev,docs]" 49 | - name: Run Integration Tests 50 | run: | 51 | python -m unittest python/snewpy/test/simplerate_integrationtest.py 52 | pytest -m 'snowglobes' 53 | - uses: r-lib/actions/setup-pandoc@v2 54 | 55 | - name: Build HTML docs 56 | run: | 57 | cd doc/ 58 | make html 59 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is published 2 | # Based on https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Publish to PyPI 5 | 6 | on: 7 | push: 8 | tags: # Sequence of patterns matched against refs/tags 9 | - 'v*' # Any tag matching v*, e.g. v1.0, v1.2b1 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.11' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build twine 25 | 26 | - name: Check and get Version Number 27 | id: get_version 28 | run: | 29 | pip install . 30 | PYTHON_VERSION=`python -c 'import snewpy; print(snewpy.__version__)'` 31 | echo "PYTHON_VERSION=${PYTHON_VERSION}" 32 | GIT_VERSION=${GITHUB_REF/refs\/tags\//} 33 | echo "GIT_VERSION=${GIT_VERSION}" 34 | if [ v$PYTHON_VERSION != $GIT_VERSION ]; then exit 1; fi 35 | echo "VERSION=${GIT_VERSION}" >> $GITHUB_OUTPUT 36 | 37 | - name: Build and publish 38 | env: 39 | TWINE_USERNAME: __token__ 40 | TWINE_PASSWORD: ${{ secrets.PYPI_JM }} 41 | run: | 42 | python -m build 43 | twine upload dist/* 44 | 45 | - name: Create Draft Release 46 | # Go to https://github.com/SNEWS2/snewpy/releases to edit this draft release and publish it 47 | # Once it is published, the release automatically is pushed to Zenodo: https://doi.org/10.5281/zenodo.4498940 48 | id: create_release 49 | uses: actions/create-release@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | tag_name: ${{ steps.get_version.outputs.VERSION }} 54 | release_name: ${{ steps.get_version.outputs.VERSION }} 55 | body: | 56 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4498940.svg)](https://doi.org/10.5281/zenodo.4498940) 57 | (TODO: This DOI always points to the latest version. Replace it with the DOI for this specific release!) 58 | 59 | List major changes since the last release here: newly added models, new features, etc. 60 | If necessary, also list breaking changes: removed features, renamed command line options, new minimum Python version, etc. 61 | 62 | draft: true 63 | prerelease: false 64 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: tests 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the main branch 7 | on: 8 | push: 9 | branches: 10 | - main 11 | - release_* 12 | pull_request: 13 | branches: 14 | - main 15 | - release_* 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a single job called "build" 20 | build: 21 | strategy: 22 | matrix: 23 | # Test all supported Python versions under Ubuntu 24 | os: [ubuntu-latest] 25 | python-version: ['3.10', '3.11', '3.12', '3.13'] 26 | # Additionally, test one Python version under MacOS and Windows, to detect OS-specific issues 27 | include: 28 | - os: macos-latest 29 | python-version: '3.12' 30 | - os: windows-latest 31 | python-version: '3.12' 32 | 33 | # The type of runner that the job will run on. 34 | runs-on: ${{ matrix.os }} 35 | 36 | # Steps represent a sequence of tasks that will be executed as part of the job 37 | steps: 38 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 39 | - uses: actions/checkout@v4 40 | 41 | # Set up the Python environment and dependencies 42 | - name: Set up Python ${{ matrix.python-version }} 43 | uses: actions/setup-python@v5 44 | with: 45 | python-version: ${{ matrix.python-version }} 46 | - name: Install dependencies 47 | run: | 48 | python -m pip install --upgrade pip 49 | - name: Install SNEWPY 50 | run: | 51 | pip install ".[dev]" 52 | 53 | - name: Run unit tests with pytest 54 | run: | 55 | pytest -m 'not snowglobes' -k 'not EarthMatter' 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled code and build products 2 | __pycache__/ 3 | *.pyc 4 | *.so 5 | build/ 6 | dist/ 7 | *.egg-info/ 8 | 9 | # default directory for downloaded model files 10 | SNEWPY_models/ 11 | 12 | ## SNEWPY output files 13 | # snewpy.snowglobes intermediate results 14 | *_SNOprocessed.tar.gz 15 | *.npy 16 | *.npz 17 | # used for integration tests in test_snowglobes.py 18 | fluence_Bollig_2016_s* 19 | # plots generated by FlavorTransformation.ipynb 20 | doc/source/nb/*_adiabaticmsw*.pdf 21 | 22 | # editor-specific 23 | .ipynb_checkpoints/ 24 | .vscode/ 25 | 26 | #hypothesis package cache 27 | .hypothesis/ 28 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNEWS2/snewpy/dc4c2c76235e3208ff554967180b24fe65173078/.gitmodules -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file. 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details. 4 | 5 | version: 2 6 | 7 | # Build information required. 8 | build: 9 | os: "ubuntu-22.04" 10 | tools: 11 | python: "3.11" 12 | 13 | # Python version and requirements to build docs. 14 | python: 15 | install: 16 | - method: pip 17 | path: . 18 | extra_requirements: 19 | - docs 20 | 21 | # Build documentation in the doc/ directory with sphinx. 22 | sphinx: 23 | configuration: doc/source/conf.py 24 | fail_on_warning: true 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, SNEWS2.0 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SNEWPY: Supernova Neutrino Early Warning Models for Python 2 | 3 | snewpy logo: The word 'snewpy' in a monospace font, with an explosion emoji inside the letter 'p'. 4 | 5 | [![DOI](https://zenodo.org/badge/221705586.svg)](https://zenodo.org/badge/latestdoi/221705586) 6 | [![PyPI](https://img.shields.io/pypi/v/snewpy)](https://pypi.org/project/snewpy/) 7 | ![tests](https://github.com/SNEWS2/snewpy/actions/workflows/tests.yml/badge.svg) 8 | [![Documentation Status](https://readthedocs.org/projects/snewpy/badge/?version=latest)](https://snewpy.readthedocs.io/en/latest/?badge=latest) 9 | 10 | SNEWPY is a Python package for working with supernova neutrinos. It offers … 11 | 12 | * … a simple and unified interface to hundreds of supernova simulations. 13 | * … a large library of flavor transformations that relate neutrino fluxes produced in the supernova to those reaching a detector on Earth. 14 | * … and a Python interface to SNOwGLoBES which lets you estimate and plot event rates in many different neutrino detectors. 15 | 16 | 17 | ## Installation 18 | 19 | Run `pip install snewpy` to install SNEWPY. 20 | 21 | SNEWPY includes a large number of supernova models from different simulation groups. Since these models have a size of several 100 MB, they are not included in the initial install but will be downloaded automatically when needed. 22 | Alternatively, you can run the following command to explicitly download models you want to use to a subdirectory named `SNEWPY-models//` in the current directory: 23 | 24 | `python -c 'import snewpy; snewpy.get_models()'` 25 | 26 | 27 | ## Usage and Documentation 28 | 29 | SNEWPY gives you easy access to hundreds of included SN simulations … 30 | ```Python 31 | import astropy.units as u 32 | from snewpy.models.ccsn import Nakazato_2013, Bollig_2016 33 | 34 | # Initialise two SN models. This automatically downloads the required data files if necessary. 35 | nakazato = Nakazato_2013(progenitor_mass=20*u.solMass, revival_time=100*u.ms, metallicity=0.004, eos='shen') 36 | bollig = Bollig_2016(progenitor_mass=27*u.solMass) 37 | ``` 38 | 39 | … and many flavor transformations that neutrinos could experience on the way to Earth … 40 | ```Python 41 | from snewpy.flavor_transformation import AdiabaticMSW 42 | from snewpy.neutrino import MassHierarchy 43 | 44 | # Adiabatic MSW flavor transformation with normal mass ordering 45 | msw_nmo = AdiabaticMSW(mh=MassHierarchy.NORMAL) 46 | ``` 47 | 48 | … letting you quickly calculate the neutrino flux reaching Earth: 49 | ```Python 50 | times = [0.5, 1] * u.s 51 | energies = range(5,50) * u.MeV 52 | # Assume a SN at the fiducial distance of 10 kpc and normal mass ordering. 53 | flux = bollig.get_flux(times, energies, distance=10*u.kpc, flavor_xform=msw_nmo) 54 | ``` 55 | 56 | You can also calculate the observed event rate in all neutrino detectors supported by SNOwGLoBES, use the included SN models and flavor transformations in third-party code (like event generators), and much more. 57 | 58 | Jupyter notebooks showcasing the downloadable supernova models available through SNEWPY and much of its functionality are available in the `doc/source/nb/` subfolder. 59 | Additional example scripts are in the 60 | `python/snewpy/scripts/` subfolder. 61 | 62 | Papers describing SNEWPY and the underlying physics are published in the Astrophysical Journal ([DOI:10.3847/1538-4357/ac350f](https://dx.doi.org/10.3847/1538-4357/ac350f), [arXiv:2109.08188](https://arxiv.org/abs/2109.08188)) and the Journal of Open Source Software ([DOI:10.21105/joss.03772](https://dx.doi.org/10.21105/joss.03772)). 63 | 64 | For more, see the [full documentation on Read the Docs](https://snewpy.rtfd.io/). 65 | 66 | ## Contributing 67 | 68 | **Your contributions to SNEWPY are welcome!** For minor changes, simply submit a pull request. If you plan larger changes, it’s probably a good idea to open an issue first to coordinate our work. 69 | 70 | We use a [Fork & Pull Request](https://docs.github.com/en/get-started/quickstart/fork-a-repo) workflow, which is common on GitHub. 71 | Please see the [Contributing page](https://snewpy.readthedocs.io/en/stable/contributing.html) in our full documentation for details. -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -W --keep-going -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/snewpy.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/snewpy.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/snewpy" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/snewpy" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc/snewpy-flowchart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNEWS2/snewpy/dc4c2c76235e3208ff554967180b24fe65173078/doc/snewpy-flowchart.pdf -------------------------------------------------------------------------------- /doc/source/.static/README: -------------------------------------------------------------------------------- 1 | Add any paths that contain custom static files (such as style sheets) here, 2 | relative to this directory. They are copied after the builtin static files, so 3 | a file named "default.css" will overwrite the builtin "default.css." 4 | -------------------------------------------------------------------------------- /doc/source/.templates/README: -------------------------------------------------------------------------------- 1 | Add any paths that contain templates here, relative to this directory. 2 | -------------------------------------------------------------------------------- /doc/source/citing.rst: -------------------------------------------------------------------------------- 1 | Citing SNEWPY 2 | ============= 3 | 4 | If you use SNEWPY for your own research, please cite the following two papers: 5 | 6 | * \A. L. Baxter `et al.` (SNEWS Collaboration): 7 | “SNEWPY: A Data Pipeline from Supernova Simulations to Neutrino Signals”. 8 | Journal of Open Source Software 6 (2021) 67, 03772. 9 | [`DOI:10.21105/joss.03772 `_] 10 | 11 | * \A. L. Baxter `et al.` (SNEWS Collaboration): 12 | “SNEWPY: A Data Pipeline from Supernova Simulations to Neutrino Signals”. 13 | Astrophysical Journal 925 (2022) 107. 14 | [`arXiv:2109.08188 `_, `DOI:10.3847/1538-4357/ac350f `_] 15 | 16 | 17 | To refer to a specific version of SNEWPY (e.g. to ensure reproducibility), you 18 | can use the `Zenodo entry for SNEWPY `_. 19 | Zenodo automatically archives all released SNEWPY versions and generates version-specific DOIs. 20 | 21 | 22 | Additionally, SNEWPY lets you access hundreds of supernova models from different modeling groups. 23 | If you use one of these models, please always cite the appropriate reference. 24 | You can find the appropriate reference in the README file downloaded alongside 25 | the model files or in the documentation of the respective :class:`.SupernovaModel` subclass. -------------------------------------------------------------------------------- /doc/source/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing to SNEWPY 2 | ====================== 3 | 4 | Development of SNEWPY happens in `our repository on GitHub `_. 5 | If you already use GitHub, everything works as you’re used to; if you don’t, 6 | check out `GitHub’s documentation `_ if 7 | anything in this section is unclear. 8 | 9 | Feedback or Problems 10 | -------------------- 11 | 12 | The best way to give feedback, request features or report problems is to 13 | `open an issue `_. 14 | 15 | 16 | Contribute Code or Documentation 17 | -------------------------------- 18 | **Your contributions to SNEWPY are welcome!** 19 | 20 | To contribute, please `create your own fork `_ 21 | of the SNEWPY repository [#fn_fork]_, then clone it:: 22 | 23 | git clone git@github.com:/snewpy.git 24 | cd snewpy/ 25 | 26 | Next, make your changes and try them out. Where relevant, run tests or build 27 | documentation as described in the following subsections. 28 | Once you're happy with your changes, please 29 | `create a pull request `_. 30 | If you plan larger changes, it’s probably a good idea to open an issue first 31 | to coordinate our work. 32 | 33 | Running Tests 34 | ~~~~~~~~~~~~~ 35 | 36 | SNEWPY uses the `pytest `_ package for automated testing. 37 | First, to install the necessary packages, use:: 38 | 39 | pip install ".[dev]" 40 | 41 | You can then run the tests using the 42 | 43 | pytest 44 | 45 | command in the SNEWPY root directory. To skip integration tests which depend 46 | on SNOwGLoBES (or to run *only* those tests) you can use one of:: 47 | 48 | pytest -m 'snowglobes' # only run tests that depend on SNOwGLoBES 49 | pytest -m 'not snowglobes' 50 | 51 | Normally, integration tests use the release version of SNOwGLoBES that was 52 | installed as a dependency of SNEWPY. To use custom data files, set the 53 | ``$SNOWGLOBES`` environment variable to the path of your SNOwGLoBES directory. 54 | 55 | Building Documentation 56 | ~~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | SNEWPY uses `Sphinx `_ for documentation. 59 | See the Sphinx website for an `overview over the markup syntax `_. 60 | First, to install the necessary packages, use:: 61 | 62 | pip install ".[docs]" 63 | cd doc/ 64 | 65 | You can then build the documentation and view it in your browser:: 66 | 67 | make html 68 | open build/index.html 69 | 70 | Contribute Supernova Models 71 | --------------------------- 72 | 73 | If you are a supernova modeler and want to allow us to use your models in 74 | SNEWPY, we are happy to hear from you! 75 | Please `open an issue `_ to discuss 76 | your contribution or simply `submit a pull request 77 | `_ with your model files. 78 | 79 | Ideally, your pull request should include a customized SupernovaModel subclass 80 | to read in your model files. See the code of existing models for examples or 81 | let us know in your issue or pull request if you need help. 82 | 83 | 84 | .. rubric:: Footnotes 85 | 86 | .. [#fn_fork] If you are a member of the SNEWS2 organization on GitHub, you 87 | don’t need to create a fork; simply use ``git clone git@github.com:SNEWS2/snewpy.git`` instead. 88 | In that case, please include your user name in the branch name for clarity. -------------------------------------------------------------------------------- /doc/source/flux.rst: -------------------------------------------------------------------------------- 1 | Interface for flux and rate calculation 2 | ======================================= 3 | 4 | .. note:: 5 | Users should consider using high-level interface, provided by :mod:`snewpy.snowglobes` 6 | This low-level interface is not guaranteed to be stable and may change at any time without warning. 7 | 8 | Flux containers 9 | --------------- 10 | .. automodule:: snewpy.flux 11 | 12 | Rate calcuation 13 | --------------- 14 | .. automodule:: snewpy.rate_calculator 15 | -------------------------------------------------------------------------------- /doc/source/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | To use SNEWPY, first install it using pip: 8 | 9 | .. code-block:: console 10 | 11 | $ pip install snewpy 12 | 13 | 14 | .. _sec-download_models: 15 | 16 | Download Supernova Models 17 | ------------------------- 18 | 19 | SNEWPY includes a large number of supernova models from different simulation groups. 20 | Since these models have a size of several 100 MB, they are not included in the initial install. 21 | Instead, SNEWPY automatically loads these files the first time you use a model. 22 | Alternatively, you can run the following command to bulk download model files: 23 | 24 | .. code-block:: console 25 | 26 | $ python -c 'import snewpy; snewpy.get_models()' 27 | 28 | Files are downloaded to a hidden directory given by ``snewpy.model_path``. 29 | 30 | .. note:: 31 | 32 | The documentation for each model includes more information, including a reference to the corresponding publication 33 | (e.g. DOI or arXiv identifier). If you use one of these models, please always cite the appropriate reference. 34 | 35 | 36 | Usage 37 | ----- 38 | 39 | This example script shows how to use SNEWPY to compare the luminosity of two different supernova models: 40 | 41 | .. code-block:: python 42 | 43 | import astropy.units as u 44 | import matplotlib as mpl 45 | import matplotlib.pyplot as plt 46 | 47 | import snewpy 48 | from snewpy.models.ccsn import Nakazato_2013, Bollig_2016 49 | from snewpy.neutrino import Flavor 50 | 51 | mpl.rc('font', size=16) 52 | %matplotlib inline 53 | 54 | # Initialise two different models. This automatically downloads the required data files. 55 | nakazato = Nakazato_2013(progenitor_mass=20*u.solMass, revival_time=100*u.ms, metallicity=0.004, eos='shen') 56 | bollig = Bollig_2016(progenitor_mass=27*u.solMass) 57 | 58 | # Plot luminosity of both models 59 | fig, ax = plt.subplots(1, figsize=(10, 6)) 60 | 61 | for flavor in Flavor: 62 | ax.plot(nakazato.time, nakazato.luminosity[flavor]/1e51, # Report luminosity in units foe/s 63 | label=flavor.to_tex() + ' (Nakazato)', 64 | color='C0' if flavor.is_electron else 'C2', 65 | ls='-' if flavor.is_neutrino else '--', 66 | lw=2) 67 | 68 | for flavor in Flavor: 69 | ax.plot(bollig.time, bollig.luminosity[flavor]/1e51, # Report luminosity in units foe/s 70 | label=flavor.to_tex() + ' (Bollig)', 71 | color='C1' if flavor.is_electron else 'C3', 72 | ls='-' if flavor.is_neutrino else '--', 73 | lw=1) 74 | 75 | ax.set(xlim=(-0.05, 0.5), xlabel=r'$t-t_{\rm bounce}$ [s]', ylabel=r'luminosity [foe s$^{-1}$]') 76 | ax.grid() 77 | ax.legend(loc='upper right', ncol=2, fontsize=18) 78 | 79 | This will generate the following figure: 80 | 81 | .. image:: luminosity-comparison.* 82 | 83 | 84 | The SNEWPY repository contains many Jupyter notebooks in ``doc/source/nb/`` with sample code 85 | showing different models or how to apply flavor transformations to the neutrino fluxes. 86 | 87 | More advanced usage of SNEWPY requires SNOwGLoBES and is described in the following section. 88 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to SNEWPY! 2 | ================== 3 | 4 | .. image:: snewpy-logo.png 5 | :alt: snewpy logo: The word 'snewpy' in a monospace font, with an explosion emoji inside the letter 'p'. 6 | :width: 600px 7 | 8 | SNEWPY is a Python package for working with supernova neutrinos. It offers … 9 | 10 | * … a simple and unified interface to hundreds of supernova simulations. 11 | * … a large library of flavor transformations that relate neutrino fluxes 12 | produced in the supernova to those reaching a detector on Earth. 13 | * … and a Python interface to SNOwGLoBES which lets you estimate and plot event 14 | rates in many different neutrino detectors. 15 | 16 | This documentation describes SNEWPY’s API. For descriptions of the underlying 17 | physics, please see the paper `arXiv:2109.08188 `_. 18 | 19 | Table of Contents 20 | ----------------- 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | gettingstarted 26 | snowglobes 27 | models 28 | transformations 29 | neutrino 30 | flux 31 | contributing 32 | citing 33 | notebooks 34 | 35 | 36 | Indices and Search 37 | ------------------ 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /doc/source/luminosity-comparison.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNEWS2/snewpy/dc4c2c76235e3208ff554967180b24fe65173078/doc/source/luminosity-comparison.pdf -------------------------------------------------------------------------------- /doc/source/luminosity-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNEWS2/snewpy/dc4c2c76235e3208ff554967180b24fe65173078/doc/source/luminosity-comparison.png -------------------------------------------------------------------------------- /doc/source/models.rst: -------------------------------------------------------------------------------- 1 | Supernova Models: ``snewpy.models`` 2 | =================================== 3 | 4 | Base Class for Supernova Models 5 | ------------------------------- 6 | .. autoclass:: snewpy.models.base.SupernovaModel 7 | :members: 8 | 9 | Derived Models 10 | -------------- 11 | 12 | Core-Collapse Supernova Models 13 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | .. automodule:: snewpy.models.ccsn 16 | :members: 17 | :exclude-members: get_param_combinations, SNOwGLoBES, Analytic3Species 18 | 19 | Presupernova Models 20 | ~~~~~~~~~~~~~~~~~~~ 21 | .. automodule:: snewpy.models.presn 22 | :members: 23 | :exclude-members: get_param_combinations 24 | 25 | Other Models 26 | ------------ 27 | .. autoclass:: snewpy.models.ccsn.Analytic3Species 28 | :members: 29 | 30 | .. autoclass:: snewpy.models.ccsn.SNOwGLoBES 31 | :members: 32 | -------------------------------------------------------------------------------- /doc/source/nb/README.md: -------------------------------------------------------------------------------- 1 | # SNEWPY Usage Examples 2 | 3 | The Jupyter notebooks in this directory contain different examples for how to use SNEWPY. 4 | 5 | ## `ccsn` and `presn` Directories 6 | 7 | These directories contain notebooks demonstrating how to use the core-collapse and pre-supernova models available through SNEWPY. 8 | 9 | ## AnalyticFluence 10 | 11 | This notebook demonstrates how to use the `Analytic3Species` class from `snewpy.models` to create an analytic supernova model by specifying the luminosity, mean energy and mean squared energy for three neutrino flavors. 12 | 13 | ## FlavorTransformation 14 | 15 | This notebook demonstrates the flavor transformations available in `snewpy.flavor_transformation`. It was used to produce many of the figures in the SNEWPY ApJ paper. 16 | 17 | ## SNOwGLoBES_models 18 | 19 | This notebook demonstrates how to use the `SNOwGLoBES` class in `snewpy.models`, which can be used with the `Type_Ia` and `PISN` model files that are available for download through SNEWPY. 20 | 21 | ## SNOwGLoBES_usage 22 | 23 | This notebook demonstrates how to use SNEWPY’s `snewpy.snowglobes` module to interact with SNOwGLoBES. 24 | 25 | ## `dev` Directory 26 | 27 | This directory contains notebooks which may be under development or illustrate usage of internal/undocumented APIs. 28 | They are not recommended for general users. -------------------------------------------------------------------------------- /doc/source/nb/SNOwGLoBES_usage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# `snewpy.snowglobes` Usage Example\n", 8 | "\n", 9 | "This notebook demonstrates how to use SNEWPY with SNOwGLoBES.\n", 10 | "\n", 11 | "To start, make sure you have SNOwGLoBES installed and have downloaded one of the models that are part of SNEWPY. Adjust the directory paths in the following cell." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from astropy import units as u\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "import numpy as np\n", 23 | "\n", 24 | "from snewpy import snowglobes, model_path\n", 25 | "\n", 26 | "SNOwGLoBES_path = None # to use custom SNOwGLoBES detector/channel/smearing files, set SNOwGLoBES directory\n", 27 | "SNEWPY_models_base = model_path # directory containing SNEWPY models\n", 28 | "\n", 29 | "# Hack to ensure that the example file used below is downloaded. Will be fixed in v2.0.\n", 30 | "from snewpy.models.ccsn import Zha_2021\n", 31 | "_ = Zha_2021(progenitor_mass=17*u.solMass)" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "Next, we will set up some basic parameters for the supernova we want to simulate." 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "# set distance in kpc\n", 48 | "distance = 10\n", 49 | "\n", 50 | "# set SNOwGLoBES detector to use\n", 51 | "detector = \"icecube\"\n", 52 | "\n", 53 | "# set SNEWPY model type and filename\n", 54 | "modeltype = 'Zha_2021'\n", 55 | "model = 's17'\n", 56 | "\n", 57 | "# set desired flavor transformation\n", 58 | "transformation = 'AdiabaticMSW_NMO'\n", 59 | "\n", 60 | "# Construct file system path of model file and name of output file\n", 61 | "# The output file will be stored in the same directory as the model file.\n", 62 | "modelfile = SNEWPY_models_base + \"/\" + modeltype + \"/\" + model + '.dat'\n", 63 | "outfile = modeltype+\"_\"+model+\"_\"+transformation\n", 64 | "\n", 65 | "# There are three ways to select a time range.\n", 66 | "# Option 1 - don't specify tstart and tend, then the whole model is integrated\n", 67 | "#tstart = None\n", 68 | "#tend = None\n", 69 | "\n", 70 | "# Option 2 - specify single tstart and tend, this makes 1 fluence file integrated over the window\n", 71 | "#tstart = 0.7 * u.s\n", 72 | "#tend = 0.8 * u.s\n", 73 | "\n", 74 | "# Option 3 = specify sequence of time intervals, one fluence file is made for each interval\n", 75 | "window_tstart = 0.742\n", 76 | "window_tend = 0.762\n", 77 | "window_bins = 60\n", 78 | "tstart = np.linspace(window_tstart, window_tend, window_bins, endpoint=False) * u.s\n", 79 | "tend = tstart + (window_tend - window_tstart) / window_bins * u.s\n", 80 | "tmid = (tstart + tend) * 0.5" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "Now that everything’s set up, let’s start using SNOwGLoBES! Be patient—these three steps together may take a few minutes." 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "# snowglobes.generate_fluence integrates the model over the specified time window(s)\n", 97 | "# and generates input files for SNOwGLoBES. It returns the full file path of the output file.\n", 98 | "print(\"Preparing fluences ...\")\n", 99 | "tarredfile = snowglobes.generate_fluence(modelfile, modeltype, transformation, distance, outfile, tstart, tend)\n", 100 | "\n", 101 | "# Next, we run SNOwGLoBES. This will loop over all the fluence files in `tarredfile`.\n", 102 | "print(\"Running SNOwGLoBES ...\")\n", 103 | "snowglobes.simulate(SNOwGLoBES_path, tarredfile, detector_input=detector)\n", 104 | "\n", 105 | "# Finally, we collate SNOwGLoBES’ results into a dictionary\n", 106 | "print(\"Collating results ...\")\n", 107 | "tables = snowglobes.collate(SNOwGLoBES_path, tarredfile, skip_plots=True)" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "Finally, since we chose option 3 above, and calculated the fluence in 60 time bins, we can now plot the event counts over time." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "%matplotlib inline\n", 124 | "nevents = np.zeros(len(tmid))\n", 125 | "for i in range(len(tmid)):\n", 126 | " key = f\"Collated_{outfile}_{i}_{detector}_events_smeared_weighted.dat\"\n", 127 | " for j in range(1,len(tables[key]['header'].split())):\n", 128 | " nevents[i] += sum(tables[key]['data'][j])\n", 129 | "\n", 130 | "# nevents is per bin, convert to per ms\n", 131 | "factor = window_bins / (window_tend - window_tstart) / 1000\n", 132 | "\n", 133 | "plt.plot(tmid - 0.742 * u.s, nevents * factor)\n", 134 | "plt.xlabel(\"$t-t_{2c}$ [s]\")\n", 135 | "plt.ylabel(\"Counts [ms$^{-1}$]\")\n", 136 | "plt.show()\n", 137 | "# compare to Figure 5 of Zha et al. (2021)\n", 138 | "print(\"Total Events:\", sum(nevents))" 139 | ] 140 | } 141 | ], 142 | "metadata": { 143 | "interpreter": { 144 | "hash": "12a4e164d15418f42fe0584c567a58ace9669f8a11b564e22ebd17e6959ef919" 145 | }, 146 | "kernelspec": { 147 | "display_name": "Python 3.9.5 64-bit ('snews': conda)", 148 | "name": "python3" 149 | }, 150 | "language_info": { 151 | "codemirror_mode": { 152 | "name": "ipython", 153 | "version": 3 154 | }, 155 | "file_extension": ".py", 156 | "mimetype": "text/x-python", 157 | "name": "python", 158 | "nbconvert_exporter": "python", 159 | "pygments_lexer": "ipython3", 160 | "version": "3.9.5" 161 | } 162 | }, 163 | "nbformat": 4, 164 | "nbformat_minor": 4 165 | } 166 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Bollig_2016.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Bollig 2016 Models\n", 8 | "\n", 9 | "Data from Mirizzi et al., particular the models that go into Figure 17. Models (s11.2c and s27.0c) taken from the Garching Supernova archive (https://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/Bollig2016/) with permission for use in SNEWS2.0.\n", 10 | "\n", 11 | "Reference: Mirizzi et al. Rivista del Nuovo Cimento Vol 39 N. 1-2 (2016)\n", 12 | "- doi:10.1393/ncr/i2016-10120-8\n", 13 | "- arXiv:1508.00785" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import Bollig_2016\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `Bollig_2016` model. We can use the `param` property to view all physics parameters and their possible values:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "Bollig_2016.param" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "We’ll initialise both of these progenitors. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "m11 = Bollig_2016(progenitor_mass=11.2*u.solMass)\n", 68 | "m27 = Bollig_2016(progenitor_mass=27*u.solMass)\n", 69 | "\n", 70 | "m11" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Bollig_2016` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 87 | "\n", 88 | "for i, model in enumerate([m11, m27]):\n", 89 | " ax = axes[i]\n", 90 | " for flavor in Flavor:\n", 91 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 92 | " label=flavor.to_tex(),\n", 93 | " color='C0' if flavor.is_electron else 'C1',\n", 94 | " ls='-' if flavor.is_neutrino else ':',\n", 95 | " lw=2)\n", 96 | " ax.set(xlim=(0.0, 0.35),\n", 97 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 98 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 99 | " ax.grid()\n", 100 | " ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 101 | "\n", 102 | "axes[0].set(ylabel=r'luminosity [foe s$^{-1}$]');" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "## Initial and Oscillated Spectra\n", 110 | "\n", 111 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 112 | "\n", 113 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "# Adiabatic MSW effect. NMO is used by default.\n", 123 | "xform_nmo = AdiabaticMSW()\n", 124 | "\n", 125 | "# Energy array and time to compute spectra.\n", 126 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 127 | "E = np.linspace(0,100,201) * u.MeV\n", 128 | "t = 50*u.ms\n", 129 | "\n", 130 | "ispec = model.get_initial_spectra(t, E)\n", 131 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 141 | "\n", 142 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 143 | " ax = axes[i]\n", 144 | " for flavor in Flavor:\n", 145 | " ax.plot(E, spec[flavor],\n", 146 | " label=flavor.to_tex(),\n", 147 | " color='C0' if flavor.is_electron else 'C1',\n", 148 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 149 | " alpha=0.7)\n", 150 | "\n", 151 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 152 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 153 | " ax.grid()\n", 154 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 155 | "\n", 156 | "ax = axes[0]\n", 157 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 158 | "\n", 159 | "fig.tight_layout();" 160 | ] 161 | } 162 | ], 163 | "metadata": { 164 | "kernelspec": { 165 | "display_name": "Python 3.9.5 ('snews')", 166 | "language": "python", 167 | "name": "python3" 168 | }, 169 | "language_info": { 170 | "codemirror_mode": { 171 | "name": "ipython", 172 | "version": 3 173 | }, 174 | "file_extension": ".py", 175 | "mimetype": "text/x-python", 176 | "name": "python", 177 | "nbconvert_exporter": "python", 178 | "pygments_lexer": "ipython3", 179 | "version": "3.9.5" 180 | }, 181 | "vscode": { 182 | "interpreter": { 183 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 184 | } 185 | } 186 | }, 187 | "nbformat": 4, 188 | "nbformat_minor": 2 189 | } 190 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Bugli_2021.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Bugli 2021 Models\n", 8 | "\n", 9 | "Data from M. Bugli et al., with permission for use in SNEWS2.0.\n", 10 | "\n", 11 | "Reference: M. Bugli, J. Guilet and M. Obergaulin, \"Three-dimensional core-collapse supernovae with complex magnetic structures: I. Explosion dynamics\", MNRAS 507 (2021) 1\n", 12 | "- https://doi.org/10.1093/mnras/stab2161\n", 13 | "- https://arxiv.org/abs/2105.00665" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "\n", 25 | "from snewpy.neutrino import Flavor\n", 26 | "from snewpy.models.ccsn import Bugli_2021\n", 27 | "\n", 28 | "mpl.rc('font', size=16)\n", 29 | "%matplotlib inline" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "## Initialize Models\n", 37 | "\n", 38 | "To start, let’s see what progenitors are available for the `Bugli_2021` model. We can use the `param` property to view all physics parameters and their possible values:" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "Bugli_2021.param" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "We’ll initialise both of these progenitors. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": null, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "mhydro = Bugli_2021(Bfield='hydro',direction='average') \n", 64 | "mL1 = Bugli_2021(Bfield='L1',direction='average',rotation=90)\n", 65 | "mL2 = Bugli_2021(Bfield='L2',direction='average',grav='A')\n", 66 | "\n", 67 | "mL1" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "Finally, let’s plot the luminosity of different neutrino flavors for this model." 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [ 83 | "fig, axes = plt.subplots(1, 3, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 84 | "\n", 85 | "for i, flavor in enumerate(Flavor):\n", 86 | " if i>2:\n", 87 | " continue\n", 88 | " ax = axes[i]\n", 89 | " for model in [mhydro,mL1, mL2]:\n", 90 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 91 | " label = model.metadata['Bfield'],\n", 92 | " lw=2)\n", 93 | " ax.set(xlim=(0.0, 0.5),\n", 94 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 95 | " title= flavor.to_tex())\n", 96 | " ax.grid()\n", 97 | " ax.legend(loc='upper right', ncol=1, fontsize=18)\n", 98 | "\n", 99 | "axes[0].set(ylabel=r'luminosity [foe s$^{-1}$]');" 100 | ] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 3 (ipykernel)", 106 | "language": "python", 107 | "name": "python3" 108 | }, 109 | "language_info": { 110 | "codemirror_mode": { 111 | "name": "ipython", 112 | "version": 3 113 | }, 114 | "file_extension": ".py", 115 | "mimetype": "text/x-python", 116 | "name": "python", 117 | "nbconvert_exporter": "python", 118 | "pygments_lexer": "ipython3", 119 | "version": "3.12.3" 120 | }, 121 | "vscode": { 122 | "interpreter": { 123 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 124 | } 125 | } 126 | }, 127 | "nbformat": 4, 128 | "nbformat_minor": 4 129 | } 130 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Fischer_2020.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Fischer 2020 Model\n", 8 | "\n", 9 | "CCSN neutrino model from Tobias Fischer\n", 10 | "\n", 11 | "The citation is: *Neutrino signal from proto-neutron star evolution: Effects of opacities from charged-current-neutrino interactions and inverse neutron decay*, Fischer, Tobias ; Guo, Gang ; Dzhioev, Alan A. ; Martínez-Pinedo, Gabriel ; Wu, Meng-Ru ; Lohs, Andreas ; Qian, Yong-Zhong, Physical Review C, Volume 101, Issue 2, article id.025804 (https://ui.adsabs.harvard.edu/link_gateway/2020PhRvC.101b5804F/doi:10.1103/PhysRevC.101.025804), [arXiv:1804.10890](https://arxiv.org/abs/1804.10890)." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import matplotlib as mpl\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "import numpy as np\n", 23 | "\n", 24 | "from astropy import units as u \n", 25 | "from snewpy.neutrino import Flavor\n", 26 | "from snewpy.models.ccsn import Fischer_2020\n", 27 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 28 | "\n", 29 | "mpl.rc('font', size=16)\n", 30 | "%matplotlib inline" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Initialize Models\n", 38 | "\n", 39 | "For the `Fischer_2020` model, there’s just a single progenitor available; so let’s initialize it. If this is the first time you’re using this progenitor, snewpy will automatically download the required data files for you." 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "F2020 = Fischer_2020()\n", 49 | "\n", 50 | "F2020" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "Plot the luminosity of different neutrino flavors for this model. " 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "fig, ax = plt.subplots(1, figsize=(8, 6), tight_layout=False)\n", 67 | "\n", 68 | "for flavor in Flavor:\n", 69 | " if flavor.is_electron == True:\n", 70 | " color='C0'\n", 71 | " else:\n", 72 | " color='C1'\n", 73 | " ls='-' if flavor.is_neutrino else ':'\n", 74 | " lw = 2\n", 75 | " \n", 76 | " ax.plot(F2020.time, F2020.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 77 | " label=flavor.to_tex(), color=color, ls=ls, lw=lw)\n", 78 | " \n", 79 | "ax.set(xlim=(-0.1, 1), xlabel=r'$t-t_{\\rm bounce}$ [s]')\n", 80 | "ax.grid()\n", 81 | "ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 82 | "ax.set(ylabel=r'luminosity [foe s$^{-1}$]');" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "## Initial and Oscillated Spectra\n", 90 | "\n", 91 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 92 | "\n", 93 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "# Adiabatic MSW effect. NMO is used by default.\n", 103 | "xform_nmo = AdiabaticMSW()\n", 104 | "\n", 105 | "# Energy array and time to compute spectra.\n", 106 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 107 | "E = np.linspace(0,100,201) * u.MeV\n", 108 | "t = 50*u.ms\n", 109 | "\n", 110 | "ispec = F2020.get_initial_spectra(t, E)\n", 111 | "ospec_nmo = F2020.get_transformed_spectra(t, E, xform_nmo)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 121 | "\n", 122 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 123 | " ax = axes[i]\n", 124 | " for flavor in Flavor:\n", 125 | " if flavor.is_electron == True:\n", 126 | " color='C0'\n", 127 | " else:\n", 128 | " color='C1'\n", 129 | " ax.plot(E, spec[flavor],\n", 130 | " label=flavor.to_tex(),\n", 131 | " color=color,\n", 132 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 133 | " alpha=0.7)\n", 134 | "\n", 135 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 136 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 137 | " ax.grid()\n", 138 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 139 | "\n", 140 | "ax = axes[0]\n", 141 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 142 | "\n", 143 | "fig.tight_layout();" 144 | ] 145 | } 146 | ], 147 | "metadata": { 148 | "kernelspec": { 149 | "display_name": "Python 3 (ipykernel)", 150 | "language": "python", 151 | "name": "python3" 152 | }, 153 | "language_info": { 154 | "codemirror_mode": { 155 | "name": "ipython", 156 | "version": 3 157 | }, 158 | "file_extension": ".py", 159 | "mimetype": "text/x-python", 160 | "name": "python", 161 | "nbconvert_exporter": "python", 162 | "pygments_lexer": "ipython3", 163 | "version": "3.12.3" 164 | }, 165 | "vscode": { 166 | "interpreter": { 167 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 168 | } 169 | } 170 | }, 171 | "nbformat": 4, 172 | "nbformat_minor": 4 173 | } 174 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Kuroda_2020.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Kuroda 2020 Models\n", 8 | "\n", 9 | "Models from Takami Kuroda. The paper describing the simulations is \"Impact of magnetic field on neutrino-matter interactions in core-collapse supernova\" arXiv:2009.07733.\n", 10 | "Included with SNEWPY with permission of the authors." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import matplotlib as mpl\n", 20 | "import matplotlib.pyplot as plt\n", 21 | "import numpy as np\n", 22 | "\n", 23 | "from astropy import units as u \n", 24 | "\n", 25 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 26 | "from snewpy.models.ccsn import Kuroda_2020\n", 27 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 28 | "\n", 29 | "mpl.rc('font', size=16)\n", 30 | "%matplotlib inline" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Initialize Models\n", 38 | "\n", 39 | "To start, let’s see what progenitors are available for the `Kuroda_2020` model. We can use the `param` property to view all physics parameters and their possible values:" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "Kuroda_2020.param" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "Quite a lot of choice there! However, for this model, not all combinations of these parameters are valid. We can use the `get_param_combinations` function to get a list of all valid combinations or to filter it:" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "# This will print a tuple of all combinations:\n", 65 | "Kuroda_2020.get_param_combinations()" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "We’ll pick one of these progenitors and initialise it. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [ 81 | "model = Kuroda_2020(**Kuroda_2020.get_param_combinations()[1])\n", 82 | "# This is equivalent to:\n", 83 | "# model = Kuroda_2020(rotational_velocity=1*u.rad/u.s, magnetic_field_exponent=12)\n", 84 | "model" 85 | ] 86 | }, 87 | { 88 | "cell_type": "markdown", 89 | "metadata": {}, 90 | "source": [ 91 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Kuroda_2020` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "fig, ax = plt.subplots(1, figsize=(8,6), tight_layout=False)\n", 101 | "\n", 102 | "for flavor in Flavor:\n", 103 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 104 | " label=flavor.to_tex(),\n", 105 | " color = 'C0' if flavor.is_electron else 'C1',\n", 106 | " ls = '-' if flavor.is_neutrino else ':',\n", 107 | " lw = 2 )\n", 108 | "\n", 109 | "ax.set(xlim=(0.0, 0.35),\n", 110 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 111 | " ylabel=r'luminosity [foe s$^{-1}$]')\n", 112 | "ax.grid()\n", 113 | "ax.legend(loc='upper right', ncol=2, fontsize=18);" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "## Initial and Oscillated Spectra\n", 121 | "\n", 122 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 123 | "\n", 124 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "# Adiabatic MSW effect. NMO is used by default.\n", 134 | "xform_nmo = AdiabaticMSW()\n", 135 | "\n", 136 | "# Energy array and time to compute spectra.\n", 137 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 138 | "E = np.linspace(0,100,201) * u.MeV\n", 139 | "t = 50*u.ms\n", 140 | "\n", 141 | "ispec = model.get_initial_spectra(t, E)\n", 142 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 152 | "\n", 153 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 154 | " ax = axes[i]\n", 155 | " for flavor in Flavor:\n", 156 | " ax.plot(E, spec[flavor],\n", 157 | " label=flavor.to_tex(),\n", 158 | " color='C0' if flavor.is_electron else 'C1',\n", 159 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 160 | " alpha=0.7)\n", 161 | "\n", 162 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 163 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 164 | " ax.grid()\n", 165 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 166 | "\n", 167 | "ax = axes[0]\n", 168 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 169 | "\n", 170 | "fig.tight_layout();" 171 | ] 172 | } 173 | ], 174 | "metadata": { 175 | "kernelspec": { 176 | "display_name": "Python 3.9.5 ('snews')", 177 | "language": "python", 178 | "name": "python3" 179 | }, 180 | "language_info": { 181 | "codemirror_mode": { 182 | "name": "ipython", 183 | "version": 3 184 | }, 185 | "file_extension": ".py", 186 | "mimetype": "text/x-python", 187 | "name": "python", 188 | "nbconvert_exporter": "python", 189 | "pygments_lexer": "ipython3", 190 | "version": "3.9.5" 191 | }, 192 | "vscode": { 193 | "interpreter": { 194 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 195 | } 196 | } 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 2 200 | } 201 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Mori_2023.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "04bf2ca6", 6 | "metadata": {}, 7 | "source": [ 8 | "# Mori 2023 2D Models with Axionlike Production\n", 9 | "\n", 10 | "Neutrino spectra from a set of 2D simulations with axionlike particle production. The models are described in the paper [Multi-messenger signals of heavy axionlike particles in core-collapse supernovae: two-dimensional simulations](https://arxiv.org/abs/2304.11360) by K. Mori, T. Takiwaki, K. Kotake and S. Horiuchi, Phys. Rev. D 108:063027, 2023.\n", 11 | "\n", 12 | "The following models are supported:\n", 13 | "\n", 14 | "| Model | Axion mass [MeV] | Coupling g10 | tpb,2000 [ms] | Ediag [1e51 erg] | MPNS [Msun] |\n", 15 | "| ----- | ---------------- | ------------ | ------------- | ---------------- | ---------- |\n", 16 | "| Standard | − | 0 | 390 | 0.40 | 1.78 |\n", 17 | "| (100, 2) | 100 | 2 | 385 | 0.37 | 1.77 |\n", 18 | "| (100, 4) | 100 | 4 | 362 | 0.34 | 1.76 |\n", 19 | "| (100, 10) | 100 | 10 | 395 | 0.36 | 1.77 |\n", 20 | "| (100, 12) | 100 | 12 | 357 | 0.43 | 1.77 |\n", 21 | "| (100, 14) | 100 | 14 | 360 | 0.44 | 1.77 |\n", 22 | "| (100, 16) | 100 | 16 | 367 | 0.51 | 1.77 |\n", 23 | "| (100, 20) | 100 | 20 | 330 | 1.10 | 1.74 |\n", 24 | "| (200, 2) | 200 | 2 | 374 | 0.45 | 1.77 |\n", 25 | "| (200, 4) | 200 | 4 | 376 | 0.45 | 1.76 |\n", 26 | "| (200, 6) | 200 | 6 | 333 | 0.54 | 1.75 |\n", 27 | "| (200, 8) | 200 | 8 | 323 | 0.94 | 1.74 |\n", 28 | "| (200, 10) | 200 | 10 | 319 | 1.61 | 1.73 |\n", 29 | "| (200, 20) | 200 | 20 | 248 | 3.87 | 1.62 |" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "29ceae1a", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "from snewpy.neutrino import Flavor\n", 40 | "from snewpy.models.ccsn import Mori_2023\n", 41 | "\n", 42 | "from astropy import units as u\n", 43 | "from glob import glob\n", 44 | "\n", 45 | "from scipy.interpolate import PchipInterpolator\n", 46 | "\n", 47 | "import numpy as np\n", 48 | "import matplotlib as mpl\n", 49 | "import matplotlib.pyplot as plt" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "1b7566ed", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "mpl.rc('font', size=16)" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "66d13225", 65 | "metadata": {}, 66 | "source": [ 67 | "## Initialize the 2D models\n", 68 | "\n", 69 | "Use the `param` property of the model class to see the available parameters. Models are initialized using the `axion_mass` and `axion_coupling` parameters." 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "id": "f62e690f", 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "Mori_2023.param" 80 | ] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "id": "55479166", 85 | "metadata": {}, 86 | "source": [ 87 | "The model with `axion_mass=0` and `axion_coupling=0` is a standard simulation with no ALP production." 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "id": "649f5ae4", 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "model_std = Mori_2023(axion_mass=0, axion_coupling=0)\n", 98 | "model_std.metadata" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": null, 104 | "id": "6f15de7a", 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "# Initialize a handful of axion models.\n", 109 | "models = {}\n", 110 | "for (am, ac) in ((100*u.MeV, 2e-10/u.GeV), (200*u.MeV,2e-10/u.GeV), (100*u.MeV,10e-10/u.GeV), (100*u.MeV,20e-10/u.GeV), (200*u.MeV,10e-10/u.GeV), (200*u.MeV,20e-10/u.GeV)):\n", 111 | " models[(am,ac)] = Mori_2023(axion_mass=am, axion_coupling=ac)\n", 112 | "\n", 113 | "models" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "3cab5a9c", 119 | "metadata": {}, 120 | "source": [ 121 | "## Plot Model Luminosities\n", 122 | "\n", 123 | "Compare axion model luminosity to the standard 2D simulation.\n", 124 | "\n", 125 | "Higher mass models with stronger coupling constants should produce a decrease in neutrino luminosity at all flavors relative to the reference simulation." 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "id": "8a398333", 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "for (m,c), model in models.items():\n", 136 | " \n", 137 | " fig, axes = plt.subplots(2, 4, figsize=(28, 8), sharex=True,\n", 138 | " gridspec_kw={'height_ratios':[3.2,1], 'hspace':0})\n", 139 | " \n", 140 | " Lmin, Lmax = 1e99, -1e99\n", 141 | " dLmin, dLmax = 1e99, -1e99\n", 142 | " \n", 143 | " for j, (flavor) in enumerate([Flavor.NU_E, Flavor.NU_E_BAR, Flavor.NU_X, Flavor.NU_X_BAR]):\n", 144 | " ax = axes[0][j]\n", 145 | " \n", 146 | " ax.plot(model_std.time, model_std.luminosity[flavor]/1e51, 'k', label=r'$20M_\\odot$ reference') # Report luminosity in [foe/s]\n", 147 | " Lmin = np.minimum(Lmin, np.min(model_std.luminosity[flavor].to_value('1e51 erg/s')))\n", 148 | " Lmax = np.maximum(Lmax, np.max(model_std.luminosity[flavor].to_value('1e51 erg/s')))\n", 149 | " \n", 150 | " modlabel = rf\"{flavor.to_tex()}: $m_a=${m.to_string(format='latex_inline')}\\n $g_{{a\\gamma}}=${c.to_string(format='latex_inline')}\"\n", 151 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 152 | " label=modlabel,\n", 153 | " color='C0' if flavor.is_electron else 'C1',\n", 154 | " ls='-' if flavor.is_neutrino else ':',\n", 155 | " lw=2)\n", 156 | " if j==0:\n", 157 | " ax.set(ylabel=r'luminosity [$10^{51}$ erg s$^{-1}$]')\n", 158 | " \n", 159 | " ax.legend(fontsize=12)\n", 160 | " ax.set(xlim=(model_std.time[0].to_value('s'), model_std.time[-1].to_value('s')))\n", 161 | " \n", 162 | " ax = axes[1][j]\n", 163 | " tmin = np.maximum(model.time[0], model_std.time[0]).to_value('s')\n", 164 | " tmax = np.minimum(model.time[-1], model_std.time[-1]).to_value('s')\n", 165 | " times = np.arange(tmin, tmax, 0.001)*u.s\n", 166 | "\n", 167 | " Lstd = PchipInterpolator(model_std.time, model_std.luminosity[flavor].to_value('1e51 erg/s'))\n", 168 | " Lstd_t = Lstd(times)\n", 169 | " select = Lstd_t != 0\n", 170 | " \n", 171 | " Lmod = PchipInterpolator(model.time, model.luminosity[flavor].to_value('1e51 erg/s'))\n", 172 | " Lmod_t = Lmod(times)\n", 173 | " dL = (Lmod_t[select] - Lstd_t[select]) / Lstd_t[select]\n", 174 | " \n", 175 | " dLmin = np.minimum(dLmin, np.min(dL))\n", 176 | " dLmax = np.maximum(dLmax, np.max(dL))\n", 177 | "\n", 178 | " ax.plot(times[select], dL)\n", 179 | " if j==0:\n", 180 | " ax.set(xlabel='time [s]',\n", 181 | " ylabel=r'$\\Delta L_\\nu/L_\\nu$')\n", 182 | " \n", 183 | " for j in range(4):\n", 184 | " axes[0][j].set(ylim=(Lmin, 1.1*Lmax))\n", 185 | " axes[1][j].set(ylim=(dLmin, dLmax))\n", 186 | " \n", 187 | " fig.suptitle(rf\"Axionlike model: $m_a=${m.to_string(format='latex_inline')}, $g_{{a\\gamma}}=${c.to_string(format='latex_inline')}\")" 188 | ] 189 | } 190 | ], 191 | "metadata": { 192 | "kernelspec": { 193 | "display_name": "Python 3 (ipykernel)", 194 | "language": "python", 195 | "name": "python3" 196 | }, 197 | "language_info": { 198 | "codemirror_mode": { 199 | "name": "ipython", 200 | "version": 3 201 | }, 202 | "file_extension": ".py", 203 | "mimetype": "text/x-python", 204 | "name": "python", 205 | "nbconvert_exporter": "python", 206 | "pygments_lexer": "ipython3", 207 | "version": "3.12.3" 208 | } 209 | }, 210 | "nbformat": 4, 211 | "nbformat_minor": 5 212 | } 213 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/OConnor_2013.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# O'Connor 2013 Models\n", 8 | "\n", 9 | "Data from O'Connor & Ott 2013, 32 progenitors (Woosley and Heger 2007) and 2 EOS (LS220 and HShen) for 500 ms post bounce in spherical symmetry (no explosions)\n", 10 | " \n", 11 | "Reference: O'Connor and Ott ApJ 762 126 2013\n", 12 | "- [doi:10.1088/0004-637X/762/2/126](https://doi.org/10.1088/0004-637X/762/2/126)\n", 13 | "- [arXiv:1207.1100](https://arxiv.org/abs/1207.1100)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import OConnor_2013\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `OConnor_2013` model. We can use the `param` property to view all physics parameters and their possible values:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "OConnor_2013.param" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "Quite a lot of choice there! Let’s initialise all of these progenitors and compare their $\\nu_e$ luminosities. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "models = {}\n", 68 | "for mass in OConnor_2013.param['progenitor_mass']:\n", 69 | " models[int(mass.value)] = OConnor_2013(progenitor_mass=mass, eos='LS220')\n", 70 | "\n", 71 | "for model in models.values():\n", 72 | " plt.plot(model.time, model.luminosity[Flavor.NU_E]/1e51, 'C0', lw=1)\n", 73 | "\n", 74 | "plt.xlabel(r'$t$ [s]')\n", 75 | "plt.ylabel(r'luminosity [foe s$^{-1}$]');" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "Finally, let’s plot the luminosity of different neutrino flavors for two of these progenitors. (Note that the `OConnor_2013` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both flavors have the same luminosity.)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 92 | "\n", 93 | "for i, model in enumerate([models[12], models[20]]):\n", 94 | " ax = axes[i]\n", 95 | " for flavor in Flavor:\n", 96 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 97 | " label=flavor.to_tex(),\n", 98 | " color='C0' if flavor.is_electron else 'C1',\n", 99 | " ls='-' if flavor.is_neutrino else ':',\n", 100 | " lw=2)\n", 101 | " ax.set(xlim=(-0.05, 0.51),\n", 102 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 103 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 104 | " ax.grid()\n", 105 | " ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 106 | "\n", 107 | "axes[0].set(ylabel=r'luminosity [foe s$^{-1}$]');" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "## Initial and Oscillated Spectra\n", 115 | "\n", 116 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 117 | "\n", 118 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": null, 124 | "metadata": {}, 125 | "outputs": [], 126 | "source": [ 127 | "# Adiabatic MSW effect. NMO is used by default.\n", 128 | "xform_nmo = AdiabaticMSW()\n", 129 | "\n", 130 | "# Energy array and time to compute spectra.\n", 131 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 132 | "E = np.linspace(0,100,201) * u.MeV\n", 133 | "t = 400*u.ms\n", 134 | "\n", 135 | "ispec = model.get_initial_spectra(t, E)\n", 136 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 146 | "\n", 147 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 148 | " ax = axes[i]\n", 149 | " for flavor in Flavor:\n", 150 | " ax.plot(E, spec[flavor],\n", 151 | " label=flavor.to_tex(),\n", 152 | " color='C0' if flavor.is_electron else 'C1',\n", 153 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 154 | " alpha=0.7)\n", 155 | "\n", 156 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 157 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 158 | " ax.grid()\n", 159 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 160 | "\n", 161 | "ax = axes[0]\n", 162 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 163 | "\n", 164 | "fig.tight_layout();" 165 | ] 166 | } 167 | ], 168 | "metadata": { 169 | "kernelspec": { 170 | "display_name": "Python 3 (ipykernel)", 171 | "language": "python", 172 | "name": "python3" 173 | }, 174 | "language_info": { 175 | "codemirror_mode": { 176 | "name": "ipython", 177 | "version": 3 178 | }, 179 | "file_extension": ".py", 180 | "mimetype": "text/x-python", 181 | "name": "python", 182 | "nbconvert_exporter": "python", 183 | "pygments_lexer": "ipython3", 184 | "version": "3.7.3" 185 | }, 186 | "vscode": { 187 | "interpreter": { 188 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 189 | } 190 | } 191 | }, 192 | "nbformat": 4, 193 | "nbformat_minor": 2 194 | } 195 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/OConnor_2015.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# O'Connor 2015 Models\n", 8 | "\n", 9 | "Data from O'Connor 2015, black hole forming simulations of a 40 solar mass progenitor from Woosley and Heger 2007 and the LS220 EOS.\n", 10 | " \n", 11 | "Reference: O'Connor ApJS 219 24 2015\n", 12 | "- doi:10.1088/0067-0049/219/2/24\n", 13 | "- arXiv:1411.7058" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import OConnor_2015\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `OConnor_2015` model. We can use the `param` property to view all physics parameters and their possible values:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "OConnor_2015.param" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "We’ll initialise this progenitor. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "model = OConnor_2015(progenitor_mass=40*u.solMass)\n", 68 | "\n", 69 | "model" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `OConnor_2015` simulation didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "fig, ax = plt.subplots(1, figsize=(8, 6), tight_layout=False)\n", 86 | "\n", 87 | "for flavor in Flavor:\n", 88 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 89 | " label=flavor.to_tex(),\n", 90 | " color='C0' if flavor.is_electron else 'C1',\n", 91 | " ls='-' if flavor.is_neutrino else ':',\n", 92 | " lw=2)\n", 93 | "ax.set(xlim=(0, 0.55),\n", 94 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 95 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 96 | "ax.grid()\n", 97 | "ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 98 | "ax.set(ylabel=r'luminosity [foe s$^{-1}$]');\n" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "## Initial and Oscillated Spectra\n", 106 | "\n", 107 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 108 | "\n", 109 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# Adiabatic MSW effect. NMO is used by default.\n", 119 | "xform_nmo = AdiabaticMSW()\n", 120 | "\n", 121 | "# Energy array and time to compute spectra.\n", 122 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 123 | "E = np.linspace(0,100,201) * u.MeV\n", 124 | "t = 400*u.ms\n", 125 | "\n", 126 | "ispec = model.get_initial_spectra(t, E)\n", 127 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 137 | "\n", 138 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 139 | " ax = axes[i]\n", 140 | " for flavor in Flavor:\n", 141 | " ax.plot(E, spec[flavor],\n", 142 | " label=flavor.to_tex(),\n", 143 | " color='C0' if flavor.is_electron else 'C1',\n", 144 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 145 | " alpha=0.7)\n", 146 | "\n", 147 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 148 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 149 | " ax.grid()\n", 150 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 151 | "\n", 152 | "ax = axes[0]\n", 153 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 154 | "\n", 155 | "fig.tight_layout();" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Python 3.9.5 ('snews')", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.9.5" 176 | }, 177 | "vscode": { 178 | "interpreter": { 179 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 180 | } 181 | } 182 | }, 183 | "nbformat": 4, 184 | "nbformat_minor": 2 185 | } 186 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Sukhbold_2015.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Sukhbold 2015 Models\n", 8 | "\n", 9 | "CSN neutrino models from the MPA Garching CCSN archive based on the paper by Sukhbold et al., 2015. The archive is available on\n", 10 | "[their website](https://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/SEWBJ_2015/index.html), but the data are private and available only upon request.\n", 11 | "Note these are the results using the PROMETHEUS-VERTEX code https://ui.adsabs.harvard.edu/abs/2002A%26A...396..361R/abstract.\n", 12 | "The four models are also described in Appendix C of this paper https://arxiv.org/abs/2010.04728\n", 13 | "\n", 14 | "The citation is: *Core-Collapse Supernovae from 9 to 120 Solar Masses Based on Neutrino-powered Explosions*, Tuguldur Sukhbold, T. Ertl, S. E. Woosley, Justin M. Brown, H.-T. Janka, [Astrophys. J. 821 (2016)\n", 15 | "38](http://dx.doi.org/10.3847/0004-637X/821/1/38), [arXiv:1510.04643](http://arxiv.org/abs/1510.04643)." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import matplotlib as mpl\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "import numpy as np\n", 27 | "\n", 28 | "from astropy import units as u \n", 29 | "\n", 30 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 31 | "from snewpy.models.ccsn import Sukhbold_2015\n", 32 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 33 | "\n", 34 | "mpl.rc('font', size=16)\n", 35 | "%matplotlib inline" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## Initialize Models\n", 43 | "\n", 44 | "To start, let’s see what progenitors are available for the `Sukhbold_2015` model. We can use the `param` property to view all physics parameters and their possible values:" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "Sukhbold_2015.param" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "We’ll initialise two of these progenitors. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "mls220 = Sukhbold_2015(progenitor_mass=27*u.solMass, eos='LS220')\n", 70 | "msfho = Sukhbold_2015(progenitor_mass=27*u.solMass, eos='SFHo')\n", 71 | "\n", 72 | "mls220" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Sukhbold_2015` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "\n", 89 | "fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 90 | "\n", 91 | "for i, model in enumerate([mls220, msfho]):\n", 92 | " ax = axes[i]\n", 93 | " for flavor in Flavor:\n", 94 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 95 | " label=flavor.to_tex(),\n", 96 | " color='C0' if flavor.is_electron else 'C1',\n", 97 | " ls='-' if flavor.is_neutrino else ':',\n", 98 | " lw=2)\n", 99 | " ax.set(xlim=(-0.05, 0.65),\n", 100 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 101 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 102 | " ax.grid()\n", 103 | " ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 104 | "\n", 105 | "axes[0].set(ylabel=r'luminosity [foe s$^{-1}$]');" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "## Initial and Oscillated Spectra\n", 113 | "\n", 114 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 115 | "\n", 116 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "# Adiabatic MSW effect. NMO is used by default.\n", 126 | "xform_nmo = AdiabaticMSW()\n", 127 | "\n", 128 | "# Energy array and time to compute spectra.\n", 129 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 130 | "E = np.linspace(0,100,201) * u.MeV\n", 131 | "t = 50*u.ms\n", 132 | "\n", 133 | "ispec = model.get_initial_spectra(t, E)\n", 134 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 144 | "\n", 145 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 146 | " ax = axes[i]\n", 147 | " for flavor in Flavor:\n", 148 | " ax.plot(E, spec[flavor],\n", 149 | " label=flavor.to_tex(),\n", 150 | " color='C0' if flavor.is_electron else 'C1',\n", 151 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 152 | " alpha=0.7)\n", 153 | "\n", 154 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 155 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 156 | " ax.grid()\n", 157 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 158 | "\n", 159 | "ax = axes[0]\n", 160 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 161 | "\n", 162 | "fig.tight_layout();" 163 | ] 164 | } 165 | ], 166 | "metadata": { 167 | "kernelspec": { 168 | "display_name": "Python 3.9.5 ('snews')", 169 | "language": "python", 170 | "name": "python3" 171 | }, 172 | "language_info": { 173 | "codemirror_mode": { 174 | "name": "ipython", 175 | "version": 3 176 | }, 177 | "file_extension": ".py", 178 | "mimetype": "text/x-python", 179 | "name": "python", 180 | "nbconvert_exporter": "python", 181 | "pygments_lexer": "ipython3", 182 | "version": "3.9.5" 183 | }, 184 | "vscode": { 185 | "interpreter": { 186 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 187 | } 188 | } 189 | }, 190 | "nbformat": 4, 191 | "nbformat_minor": 2 192 | } 193 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Tamborra_2014.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Tamborra 2014 Models\n", 8 | "\n", 9 | "Data from Tamborra et al., [Phys. Rev. D90:045032, 2014](http://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/Tamborra2014/). Models (s20.0c and s27.0c) taken from the Garching Supernova archive (https://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/Tamborra2014/) with permission for use in SNEWS2.0.\n", 10 | "\n", 11 | "Reference: I. Tamborra et al., *Neutrino emission characteristics and detection opportunities based on three-dimensional supernova simulations*, Phys. Rev D90:045032, 2014.\n", 12 | "- [doi:10.1103/PhysRevD.90.045032](https://journals.aps.org/prd/abstract/10.1103/PhysRevD.90.045032)\n", 13 | "- [arXiv:1406.0006](https://arxiv.org/abs/1406.0006)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import Tamborra_2014\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `Tamborra_2014` model. We can use the `param` property to view all physics parameters and their possible values. However, for this model, not all combinations of these parameters are valid, so we use the `get_param_combinations` function to get a list of all valid combinations:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "# Tamborra_2014.param\n", 52 | "Tamborra_2014.get_param_combinations()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "We’ll initialise two of these progenitors. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "m20 = Tamborra_2014(progenitor_mass=20*u.solMass, direction=1)\n", 69 | "m27 = Tamborra_2014(progenitor_mass=27*u.solMass, direction=1)\n", 70 | "\n", 71 | "m20" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Tamborra_2014` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "fig, axes = plt.subplots(1, 2, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 88 | "\n", 89 | "for i, model in enumerate([m20, m27]):\n", 90 | " ax = axes[i]\n", 91 | " for flavor in Flavor:\n", 92 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 93 | " label=flavor.to_tex(),\n", 94 | " color='C0' if flavor.is_electron else 'C1',\n", 95 | " ls='-' if flavor.is_neutrino else ':',\n", 96 | " lw=2)\n", 97 | " ax.set(xlim=(0.0, 0.35),\n", 98 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 99 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 100 | " ax.grid()\n", 101 | " ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 102 | "\n", 103 | "axes[0].set(ylabel=r'luminosity [foe s$^{-1}$]');" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "## Initial and Oscillated Spectra\n", 111 | "\n", 112 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 113 | "\n", 114 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "# Adiabatic MSW effect. NMO is used by default.\n", 124 | "xform_nmo = AdiabaticMSW()\n", 125 | "\n", 126 | "# Energy array and time to compute spectra.\n", 127 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 128 | "E = np.linspace(0,100,201) * u.MeV\n", 129 | "t = 50*u.ms\n", 130 | "\n", 131 | "ispec = model.get_initial_spectra(t, E)\n", 132 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 142 | "\n", 143 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 144 | " ax = axes[i]\n", 145 | " for flavor in Flavor:\n", 146 | " ax.plot(E, spec[flavor],\n", 147 | " label=flavor.to_tex(),\n", 148 | " color='C0' if flavor.is_electron else 'C1',\n", 149 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 150 | " alpha=0.7)\n", 151 | "\n", 152 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 153 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 154 | " ax.grid()\n", 155 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 156 | "\n", 157 | "ax = axes[0]\n", 158 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]');" 159 | ] 160 | } 161 | ], 162 | "metadata": { 163 | "kernelspec": { 164 | "display_name": "Python 3.9.5 ('snews')", 165 | "language": "python", 166 | "name": "python3" 167 | }, 168 | "language_info": { 169 | "codemirror_mode": { 170 | "name": "ipython", 171 | "version": 3 172 | }, 173 | "file_extension": ".py", 174 | "mimetype": "text/x-python", 175 | "name": "python", 176 | "nbconvert_exporter": "python", 177 | "pygments_lexer": "ipython3", 178 | "version": "3.9.5" 179 | }, 180 | "vscode": { 181 | "interpreter": { 182 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 183 | } 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 2 188 | } 189 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Walk_2018.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Walk 2018 Models\n", 8 | "\n", 9 | "Models from L. Walk et al., [Phys. Rev. D 98:123001, 2018](https://arxiv.org/abs/1807.02366) (s15.0c) taken from the Garching Supernova archive (https://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/Walk2018/) with permission for use in SNEWPY.\n", 10 | "\n", 11 | "Reference: L. Walk et al., *Identifying rotation in SASI-dominated core-collapse supernovae with a neutrino gyroscope*, Phys. Rev. D 98:123001, 2018\n", 12 | "- [doi:10.1103/PhysRevD.98.123001](https://journals.aps.org/prd/abstract/10.1103/PhysRevD.98.123001)\n", 13 | "- [arXiv:1807.02366](https://arxiv.org/abs/1807.02366)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import Walk_2018\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `Walk_2018` model. We can use the `param` property to view all physics parameters and their possible values:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "Walk_2018.param" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "We’ll initialise this progenitor. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "model = Walk_2018(progenitor_mass=15*u.solMass, rotation='fast', direction=1)\n", 68 | "\n", 69 | "model" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Sukhbold_2015` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "fig, ax = plt.subplots(1, figsize=(8, 6), tight_layout=False)\n", 86 | "\n", 87 | "for flavor in Flavor:\n", 88 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 89 | " label=flavor.to_tex(),\n", 90 | " color='C0' if flavor.is_electron else 'C1',\n", 91 | " ls='-' if flavor.is_neutrino else ':',\n", 92 | " lw=2)\n", 93 | "ax.set(xlim=(0, 0.35),\n", 94 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 95 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 96 | "ax.grid()\n", 97 | "ax.legend(loc='upper right', ncol=2, fontsize=18)\n", 98 | "ax.set(ylabel=r'luminosity [foe s$^{-1}$]');\n" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "## Initial and Oscillated Spectra\n", 106 | "\n", 107 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 108 | "\n", 109 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# Adiabatic MSW effect. NMO is used by default.\n", 119 | "xform_nmo = AdiabaticMSW()\n", 120 | "\n", 121 | "# Energy array and time to compute spectra.\n", 122 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 123 | "E = np.linspace(0,100,201) * u.MeV\n", 124 | "t = 50*u.ms\n", 125 | "\n", 126 | "ispec = model.get_initial_spectra(t, E)\n", 127 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 137 | "\n", 138 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 139 | " ax = axes[i]\n", 140 | " for flavor in Flavor:\n", 141 | " ax.plot(E, spec[flavor],\n", 142 | " label=flavor.to_tex(),\n", 143 | " color='C0' if flavor.is_electron else 'C1',\n", 144 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 145 | " alpha=0.7)\n", 146 | "\n", 147 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 148 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 149 | " ax.grid()\n", 150 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 151 | "\n", 152 | "ax = axes[0]\n", 153 | "ax.set(ylabel=r'flux [s$^{-1}$]')\n", 154 | "\n", 155 | "fig.tight_layout();" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Python 3.9.5 ('snews')", 162 | "language": "python", 163 | "name": "python3" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": { 167 | "name": "ipython", 168 | "version": 3 169 | }, 170 | "file_extension": ".py", 171 | "mimetype": "text/x-python", 172 | "name": "python", 173 | "nbconvert_exporter": "python", 174 | "pygments_lexer": "ipython3", 175 | "version": "3.9.5" 176 | }, 177 | "vscode": { 178 | "interpreter": { 179 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 180 | } 181 | } 182 | }, 183 | "nbformat": 4, 184 | "nbformat_minor": 2 185 | } 186 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Walk_2019.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Walk 2019 Models\n", 8 | "\n", 9 | "Models from L. Walk et al., [Phys. Rev. D 101:123013, 2019](https://arxiv.org/abs/1910.12971) (s15.0c) taken from the Garching Supernova archive (https://wwwmpa.mpa-garching.mpg.de/ccsnarchive/data/Walk2019/) with permission for use in SNEWPY.\n", 10 | "\n", 11 | "Reference: L. Walk et al., *Neutrino emission characteristics of black hole formation in three-dimensional simulations of stellar collapse*, Phys. Rev. D 101:123013, 2019\n", 12 | "- [doi:10.1103/PhysRevD.101.123013](https://journals.aps.org/prd/abstract/10.1103/PhysRevD.101.123013)\n", 13 | "- [arXiv:1910.12971](https://arxiv.org/abs/1910.12971)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import Walk_2019\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `Walk_2019` model. We can use the `param` property to view all physics parameters and their possible values. However, for this model, not all combinations of these parameters are valid, so we use the `get_param_combinations` function to get a list of all valid combinations:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "# Walk_2019.param\n", 52 | "Walk_2019.get_param_combinations()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "We’ll initialise this progenitor. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "model = Walk_2019(progenitor_mass=40*u.solMass, direction=1)\n", 69 | "\n", 70 | "model" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Walk_2019` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "fig, axes = plt.subplots(1, 1, figsize=(12, 5), sharex=True, sharey=True, tight_layout=True)\n", 87 | "\n", 88 | "for flavor in Flavor:\n", 89 | " axes.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 90 | " label=flavor.to_tex(),\n", 91 | " color='C0' if flavor.is_electron else 'C1',\n", 92 | " ls='-' if flavor.is_neutrino else ':',\n", 93 | " lw=2)\n", 94 | "axes.set(xlim=(0, 0.6),\n", 95 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 96 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 97 | "axes.grid()\n", 98 | "axes.legend(loc='upper right', ncol=2, fontsize=18)\n", 99 | "axes.set(ylabel=r'luminosity [foe s$^{-1}$]');" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "## Initial and Oscillated Spectra\n", 107 | "\n", 108 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 109 | "\n", 110 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "# Adiabatic MSW effect. NMO is used by default.\n", 120 | "xform_nmo = AdiabaticMSW()\n", 121 | "\n", 122 | "# Energy array and time to compute spectra.\n", 123 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 124 | "E = np.linspace(0,100,201) * u.MeV\n", 125 | "t = 50*u.ms\n", 126 | "\n", 127 | "ispec = model.get_initial_spectra(t, E)\n", 128 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 138 | "\n", 139 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 140 | " ax = axes[i]\n", 141 | " for flavor in Flavor:\n", 142 | " ax.plot(E, spec[flavor],\n", 143 | " label=flavor.to_tex(),\n", 144 | " color='C0' if flavor.is_electron else 'C1',\n", 145 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 146 | " alpha=0.7)\n", 147 | "\n", 148 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 149 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 150 | " ax.grid()\n", 151 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 152 | "\n", 153 | "ax = axes[0]\n", 154 | "ax.set(ylabel=r'flux [s$^{-1}$]')\n", 155 | "\n", 156 | "fig.tight_layout();" 157 | ] 158 | } 159 | ], 160 | "metadata": { 161 | "kernelspec": { 162 | "display_name": "Python 3.9.5 ('snews')", 163 | "language": "python", 164 | "name": "python3" 165 | }, 166 | "language_info": { 167 | "codemirror_mode": { 168 | "name": "ipython", 169 | "version": 3 170 | }, 171 | "file_extension": ".py", 172 | "mimetype": "text/x-python", 173 | "name": "python", 174 | "nbconvert_exporter": "python", 175 | "pygments_lexer": "ipython3", 176 | "version": "3.9.5" 177 | }, 178 | "vscode": { 179 | "interpreter": { 180 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 181 | } 182 | } 183 | }, 184 | "nbformat": 4, 185 | "nbformat_minor": 2 186 | } 187 | -------------------------------------------------------------------------------- /doc/source/nb/ccsn/Zha_2021.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Zha et al. 2021 Models\n", 8 | "\n", 9 | "Data from Zha et al. 2021, hadron-quark phase transition simulations of a variety of progenitors from Sukhbold et al. 2018 and the STOS EOS with B=145MeV.\n", 10 | " \n", 11 | "Reference: Zha et al. ApJ 911 74 2021\n", 12 | "- [doi:10.3847/1538-4357/abec4c](https://doi.org/10.3847/1538-4357/abec4c)\n", 13 | "- [arXiv:2103.02268](https://arxiv.org/abs/2103.02268)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import matplotlib as mpl\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "import numpy as np\n", 25 | "\n", 26 | "from astropy import units as u \n", 27 | "\n", 28 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 29 | "from snewpy.models.ccsn import Zha_2021\n", 30 | "from snewpy.flavor_transformation import NoTransformation, AdiabaticMSW, ThreeFlavorDecoherence\n", 31 | "\n", 32 | "mpl.rc('font', size=16)\n", 33 | "%matplotlib inline" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "## Initialize Models\n", 41 | "\n", 42 | "To start, let’s see what progenitors are available for the `Zha_2021` model. We can use the `param` property to view all physics parameters and their possible values:" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "Zha_2021.param" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "We’ll initialise two of these progenitors. If this is the first time you’re using a progenitor, snewpy will automatically download the required data files for you." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "m19 = Zha_2021(progenitor_mass=19*u.solMass)\n", 68 | "m19_89 = Zha_2021(progenitor_mass=19.89*u.solMass)\n", 69 | "\n", 70 | "m19" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "Finally, let’s plot the luminosity of different neutrino flavors for this model. (Note that the `Zha_2021` simulations didn’t distinguish between $\\nu_x$ and $\\bar{\\nu}_x$, so both have the same luminosity.) We’ll also add zoomed-in plots to see the phase transition better." 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "fig, axes = plt.subplots(2, 2, figsize=(12, 10), tight_layout=True)\n", 87 | "\n", 88 | "for i, model in enumerate([m19, m19_89]):\n", 89 | " for j in range(2):\n", 90 | " ax = axes[j, i]\n", 91 | " for flavor in Flavor:\n", 92 | " ax.plot(model.time, model.luminosity[flavor]/1e51, # Report luminosity in units foe/s\n", 93 | " label=flavor.to_tex(),\n", 94 | " color='C0' if flavor.is_electron else 'C1',\n", 95 | " ls='-' if flavor.is_neutrino else ':',\n", 96 | " lw=2)\n", 97 | " ax.set(xlim=(-0.05, 0.9) if j==0 else (0.605, 0.665),\n", 98 | " ylim=(0, 600) if j==0 else (0, 125),\n", 99 | " xlabel=r'$t-t_{\\rm bounce}$ [s]',\n", 100 | " ylabel=r'luminosity [foe s$^{-1}$]',\n", 101 | " title=r'{}: {} $M_\\odot$'.format(model.metadata['EOS'], model.metadata['Progenitor mass'].value))\n", 102 | " ax.grid()\n", 103 | " ax.legend(loc='upper right', ncol=2, fontsize=18)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "## Initial and Oscillated Spectra\n", 111 | "\n", 112 | "Plot the neutrino spectra at the source and after the requested flavor transformation has been applied.\n", 113 | "\n", 114 | "### Adiabatic MSW Flavor Transformation: Normal mass ordering" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "# Adiabatic MSW effect. NMO is used by default.\n", 124 | "xform_nmo = AdiabaticMSW()\n", 125 | "\n", 126 | "# Energy array and time to compute spectra.\n", 127 | "# Note that any convenient units can be used and the calculation will remain internally consistent.\n", 128 | "E = np.linspace(0,100,201) * u.MeV\n", 129 | "t = 400*u.ms\n", 130 | "\n", 131 | "ispec = model.get_initial_spectra(t, E)\n", 132 | "ospec_nmo = model.get_transformed_spectra(t, E, xform_nmo)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "fig, axes = plt.subplots(1,2, figsize=(12,5), sharex=True, sharey=True, tight_layout=True)\n", 142 | "\n", 143 | "for i, spec in enumerate([ispec, ospec_nmo]):\n", 144 | " ax = axes[i]\n", 145 | " for flavor in Flavor:\n", 146 | " ax.plot(E, spec[flavor],\n", 147 | " label=flavor.to_tex(),\n", 148 | " color='C0' if flavor.is_electron else 'C1',\n", 149 | " ls='-' if flavor.is_neutrino else ':', lw=2,\n", 150 | " alpha=0.7)\n", 151 | "\n", 152 | " ax.set(xlabel=r'$E$ [{}]'.format(E.unit),\n", 153 | " title='Initial Spectra: $t = ${:.1f}'.format(t) if i==0 else 'Oscillated Spectra: $t = ${:.1f}'.format(t))\n", 154 | " ax.grid()\n", 155 | " ax.legend(loc='upper right', ncol=2, fontsize=16)\n", 156 | "\n", 157 | "ax = axes[0]\n", 158 | "ax.set(ylabel=r'flux [erg$^{-1}$ s$^{-1}$]')\n", 159 | "\n", 160 | "fig.tight_layout();" 161 | ] 162 | } 163 | ], 164 | "metadata": { 165 | "kernelspec": { 166 | "display_name": "Python 3 (ipykernel)", 167 | "language": "python", 168 | "name": "python3" 169 | }, 170 | "language_info": { 171 | "codemirror_mode": { 172 | "name": "ipython", 173 | "version": 3 174 | }, 175 | "file_extension": ".py", 176 | "mimetype": "text/x-python", 177 | "name": "python", 178 | "nbconvert_exporter": "python", 179 | "pygments_lexer": "ipython3", 180 | "version": "3.7.3" 181 | }, 182 | "vscode": { 183 | "interpreter": { 184 | "hash": "e2528887d751495e023d57d695389d9a04f4c4d2e5866aaf6dc03a1ed45c573e" 185 | } 186 | } 187 | }, 188 | "nbformat": 4, 189 | "nbformat_minor": 2 190 | } 191 | -------------------------------------------------------------------------------- /doc/source/nb/dev/ExtendedCoolingTail.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bb730907", 6 | "metadata": {}, 7 | "source": [ 8 | "# Extended Cooling Tail" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "18341676-5496-4da6-a722-7e730791387a", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from snewpy.models.ccsn import Nakazato_2013\n", 19 | "from snewpy.models.extended import ExtendedModel\n", 20 | "from snewpy.neutrino import Flavor, MassHierarchy\n", 21 | "\n", 22 | "from astropy import units as u\n", 23 | "\n", 24 | "import numpy as np\n", 25 | "import matplotlib.pyplot as plt" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "944473f5-309f-4d65-b857-99f07d496a37", 31 | "metadata": {}, 32 | "source": [ 33 | "## Model Initialization\n", 34 | "\n", 35 | "Let's use a model from the `Nakazato_2013` family of simulations. There are many model parameters to choose from:" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "id": "5a329b2c-58be-42d5-be06-70d0afebb633", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "Nakazato_2013.param" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "id": "11432208-7c45-46d7-b1dc-6bf6d52d0b31", 51 | "metadata": {}, 52 | "source": [ 53 | "Since there are many valid and invalid combinations of parameters available, we will generate a list of valid parameters using the class function `get_param_combinations` and then select one of the particular models for plotting.\n", 54 | "\n", 55 | "It is not really important which model we choose, so we'll pick a large metallicity model (high $Z$)." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "id": "62291165-b6db-4dab-b80a-d45058583a83", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "highZ_models = list(params for params in Nakazato_2013.get_param_combinations() if params['metallicity'] == 0.02)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "2d791680-42ec-4910-9c40-07749938156a", 71 | "metadata": {}, 72 | "source": [ 73 | "This should be equivalent to the initialization:\n", 74 | "```\n", 75 | "model = Nakazato_2013(progenitor_mass=20<`_. 6 | When installing SNEWPY, it automatically downloads the detector configurations 7 | from the latest supported SNOwGLoBES version. 8 | 9 | You only need to download SNOwGLoBES manually if you require a custom detector 10 | configuration. In that case, run the following commands: 11 | 12 | .. code-block:: bash 13 | 14 | git clone https://github.com/SNOwGLoBES/snowglobes.git 15 | cd snowglobes 16 | git checkout v1.3 17 | # create custom detector configuration 18 | export SNOWGLOBES=${PWD} # or use `SNOwGLoBESdir` parameter as documented below 19 | 20 | 21 | Usage 22 | ----- 23 | .. automodule:: snewpy.snowglobes 24 | :members: 25 | :member-order: bysource 26 | 27 | Low-level interface 28 | ------------------- 29 | .. automodule:: snewpy.snowglobes_interface 30 | :members: SimpleRate 31 | -------------------------------------------------------------------------------- /doc/source/transformations.rst: -------------------------------------------------------------------------------- 1 | Flavor Transformations: ``snewpy.flavor_transformation`` 2 | ======================================================== 3 | 4 | .. automodule:: snewpy.flavor_transformation -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "snewpy" 7 | dynamic = ["version"] 8 | description = "A Python package for working with supernova neutrinos" 9 | authors = [{ name = "SNEWS Collaboration", email = "snews2.0@lists.bnl.gov" }] 10 | license = { text = "BSD" } 11 | readme = {file = "README.md", content-type = "text/markdown"} 12 | 13 | requires-python = ">=3.9" 14 | 15 | dependencies = [ 16 | "numpy", 17 | "scipy", 18 | "astropy >= 4.3", 19 | "pandas", 20 | "tqdm", 21 | "matplotlib", 22 | "h5py", 23 | "requests", 24 | "pyyaml", 25 | "snowglobes_data == 1.3.2" 26 | ] 27 | 28 | [project.optional-dependencies] 29 | dev = ["hypothesis", "pytest"] 30 | docs = ["numpydoc", "nbsphinx", "ipykernel", "tqdm[notebook]"] 31 | 32 | [project.urls] 33 | "Homepage" = "https://github.com/SNEWS2/snewpy" 34 | "Bug Tracker" = "https://github.com/SNEWS2/snewpy/issues" 35 | 36 | 37 | [tool.setuptools.dynamic] 38 | version = {attr = "snewpy.__version__"} 39 | 40 | [tool.setuptools.packages.find] 41 | where = ["python"] 42 | include = [ 43 | "snewpy", 44 | "snewpy.*", 45 | ] 46 | exclude = [ 47 | "snewpy.scripts", 48 | ] 49 | 50 | [tool.setuptools.package-data] 51 | "snewpy.models" = ["*.yml"] 52 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers= 3 | snowglobes: tests that require SNOwGLoBES installed 4 | crosscheck: integration tests for comparison with reference output 5 | timing: benchmark testing of the function execution time 6 | BEMEWS: use external BEMEWS package for calculation of EarthMatter effect 7 | 8 | 9 | -------------------------------------------------------------------------------- /python/snewpy/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under a 3-clause BSD style license - see LICENSE.rst 2 | # -*- coding: utf-8 -*- 3 | """ 4 | ====== 5 | snewpy 6 | ====== 7 | Front-end for supernova models which provide neutrino luminosity and spectra. 8 | """ 9 | 10 | from sys import exit 11 | import os 12 | 13 | try: 14 | from astropy.config.paths import get_cache_dir 15 | except ModuleNotFoundError: 16 | # when imported by setup.py before dependencies are installed 17 | get_cache_dir = lambda: '.' 18 | 19 | 20 | __version__ = '1.6' 21 | 22 | 23 | src_path = os.path.realpath(__path__[0]) 24 | base_path = os.sep.join(src_path.split(os.sep)[:-2]) 25 | model_path = os.path.join(get_cache_dir(), 'snewpy', 'models') 26 | 27 | def get_models(models=None, download_dir=None): 28 | """Download model files from the snewpy repository. 29 | 30 | Parameters 31 | ---------- 32 | models : list or str 33 | Models to download. Can be 'all', name of a single model or list of model names. 34 | download_dir : str 35 | [Deprecated, do not use.] 36 | """ 37 | from concurrent.futures import ThreadPoolExecutor, as_completed 38 | from warnings import warn 39 | from .models.registry_model import all_models 40 | 41 | if download_dir is not None: 42 | warn("The `download_dir` argument to `get_models` is deprecated and will be removed soon.", FutureWarning, stacklevel=2) 43 | 44 | all_models = {m.__name__: m for m in all_models} 45 | all_model_names = sorted(all_models.keys()) 46 | 47 | if models == "all": 48 | models = all_model_names 49 | elif isinstance(models, str): 50 | models = [models] 51 | elif models is None: 52 | # Select model(s) to download 53 | print(f"Available models in SNEWPY v{__version__}: {all_model_names}") 54 | 55 | selected = input("\nType a model name, 'all' to download all models or to cancel: ").strip() 56 | if selected == "all": 57 | models = all_model_names 58 | elif selected == "": 59 | exit() 60 | elif selected in all_model_names: 61 | models = {selected} 62 | while True: 63 | selected = input("\nType another model name or if you have selected all models you want to download: ").strip() 64 | if selected in all_model_names: 65 | models.add(selected) 66 | elif selected == "": 67 | break 68 | else: 69 | print(f"'{selected}' is not a valid model name. Please check for typos.") 70 | else: 71 | print(f"'{selected}' is not a valid model name. Please check for typos.") 72 | exit() 73 | 74 | print(f"\nYou have selected the models: {models}\n") 75 | 76 | pool = ThreadPoolExecutor(max_workers=8) 77 | results = [] 78 | print(f"Downloading files for {models} to '{model_path}' ...") 79 | for model in models: 80 | for progenitor in all_models[model].get_param_combinations(): 81 | results.append(pool.submit(all_models[model], **progenitor)) 82 | 83 | exceptions = [] 84 | for result in as_completed(results): 85 | if result.exception() is not None: 86 | exceptions.append(result.exception()) 87 | if exceptions: 88 | print(f"ERROR: {len(exceptions)} exceptions occured. ({exceptions})") 89 | print("Please check your internet connection and try again later. If this persists, please report it at https://github.com/SNEWS2/snewpy/issues") 90 | exit(1) 91 | pool.shutdown(wait=False) 92 | -------------------------------------------------------------------------------- /python/snewpy/_model_downloader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Utility module that reads a local YAML file listing the data files 3 | available for the models supported by SNEWPY. These data files can either 4 | be local (in the SNEWPY source tree) or uploaded to a model repository on 5 | Zenodo. The YAML file supports regular expressions to allow matching of all 6 | possible model files. 7 | """ 8 | 9 | import hashlib 10 | import os 11 | import requests 12 | import yaml 13 | 14 | from dataclasses import dataclass 15 | from importlib.resources import open_text 16 | from pathlib import Path 17 | from tqdm.auto import tqdm 18 | 19 | from snewpy import model_path 20 | from snewpy import __version__ as snewpy_version 21 | 22 | import logging 23 | 24 | logger = logging.getLogger("FileHandle") 25 | 26 | 27 | def _md5(fname: str) -> str: 28 | """calculate the md5sum hash of a file.""" 29 | hash_md5 = hashlib.md5() 30 | with open(fname, "rb") as f: 31 | for chunk in iter(lambda: f.read(4096), b""): 32 | hash_md5.update(chunk) 33 | return hash_md5.hexdigest() 34 | 35 | 36 | def _download(src: str, dest: str, chunk_size=8192): 37 | """Download a file from 'src' to 'dest' and show the progress bar.""" 38 | # make sure parent dir exists 39 | Path(dest).parent.mkdir(exist_ok=True, parents=True) 40 | with requests.get(src, stream=True) as r: 41 | r.raise_for_status() 42 | fileSize = int(r.headers.get("content-length", 0)) 43 | with tqdm( 44 | desc=dest.name, 45 | total=fileSize, 46 | unit="iB", 47 | unit_scale=True, 48 | unit_divisor=1024, 49 | ) as bar: 50 | with open(dest, "wb") as f: 51 | for chunk in r.iter_content(chunk_size=chunk_size): 52 | size = f.write(chunk) 53 | bar.update(size) 54 | 55 | 56 | class ChecksumError(FileNotFoundError): 57 | """Raise an exception due to a mismatch in the MD5 checksum.""" 58 | 59 | def __init__(self, path, md5_exp, md5_actual): 60 | super().__init__(f"Checksum error for file {path}: {md5_actual}!={md5_exp}") 61 | 62 | pass 63 | 64 | 65 | class MissingFileError(FileNotFoundError): 66 | """Raise an exception due to a missing file.""" 67 | 68 | pass 69 | 70 | 71 | @dataclass 72 | class FileHandle: 73 | """Object storing local path, remote URL (optional), and MD5 sum 74 | (optional) for a SNEWPY model file. If the requested file is already present 75 | locally, open it. Otherwise, download it from the remote URL to the desired 76 | local path. 77 | """ 78 | 79 | path: Path 80 | remote: str = None 81 | md5: str | None = None 82 | 83 | def check(self) -> None: 84 | """Check if the given file exists locally and has a correct md5 sum. 85 | Raises 86 | ------ 87 | :class:`MissingFileError` 88 | if the local copy of the file is missing 89 | :class:`ChecksumError` 90 | if the local file exists, but the checksum is wrong""" 91 | if not self.path.exists(): 92 | raise MissingFileError(self.path) 93 | if self.md5: 94 | logger.info(f"File {self.path}: checking md5") 95 | md5 = _md5(self.path) 96 | logger.debug(f"{md5} vs expected {self.md5}") 97 | if md5 != self.md5: 98 | raise ChecksumError(self.path, self.md5, md5) 99 | 100 | def load(self) -> Path: 101 | """Make sure that local file exists and has a correct checksum. 102 | Download the file if needed. 103 | """ 104 | try: 105 | self.check() 106 | except FileNotFoundError as e: 107 | logger.info(f"Downloading file {self.path}") 108 | _download(self.remote, self.path) 109 | self.check() 110 | return self.path 111 | 112 | 113 | def _from_zenodo(zenodo_id: str, filename: str): 114 | """Access files on Zenodo. 115 | 116 | Parameters 117 | ---------- 118 | zenodo_id : Zenodo record for model files. 119 | filename : Expected filename storing simulation data. 120 | 121 | Returns 122 | ------- 123 | file_url, md5sum 124 | """ 125 | zenodo_url = f"https://zenodo.org/api/records/{zenodo_id}" 126 | record = requests.get(zenodo_url).json() 127 | # Search for file string in Zenodo request for this record. 128 | file = next((_file for _file in record["files"] if _file["key"] == filename), None) 129 | 130 | # If matched, return a tuple of URL and checksum.Otherwise raise an exception. 131 | if file is not None: 132 | return file["links"]["self"], file["checksum"].split(":")[1] 133 | else: 134 | raise MissingFileError(filename) 135 | 136 | 137 | class ModelRegistry: 138 | """Access model data. Configuration for each model is in a YAML file 139 | distributed with SNEWPY. 140 | """ 141 | 142 | def __init__(self, config_file: Path = None, local_path: str = model_path): 143 | """ 144 | Parameters 145 | ---------- 146 | config_file: YAML configuration file. If None (default) use the 'model_files.yml' from SNEWPY resources 147 | local_path: local installation path (defaults to astropy cache). 148 | """ 149 | if config_file is None: 150 | context = open_text("snewpy.models", "model_files.yml") 151 | else: 152 | context = open(config_file) 153 | with context as f: 154 | self.config = yaml.safe_load(f) 155 | self.models = self.config["models"] 156 | self.local_path = local_path 157 | 158 | def get_file(self, config_path: str, filename: str) -> Path: 159 | """Get the requested data file from the models file repository 160 | 161 | Parameters 162 | ---------- 163 | config_path : dot-separated path of the model in the YAML configuration (e.g. "ccsn.Bollig_2016") 164 | filename : Name of simulation datafile, or a relative path from the model sub-directory 165 | 166 | Returns 167 | ------- 168 | Path of downloaded file. 169 | """ 170 | tokens = config_path.split(".") 171 | config = self.models 172 | for t in tokens: 173 | config = config[t] 174 | # store the model name 175 | model = tokens[-1] 176 | # Get data from GitHub or Zenodo. 177 | repo = config["repository"] 178 | if repo == "zenodo": 179 | url, md5 = _from_zenodo(zenodo_id=config["zenodo_id"], filename=filename) 180 | else: 181 | # format the url directly 182 | params = { 183 | "model": model, 184 | "filename": filename, 185 | "snewpy_version": snewpy_version, 186 | } 187 | params.update( 188 | config 189 | ) # default parameters can be overriden in the model config 190 | url, md5 = repo.format(**params), None 191 | localpath = Path(model_path) / str(model) 192 | localpath.mkdir(exist_ok=True, parents=True) 193 | fh = FileHandle(path=localpath / filename, remote=url, md5=md5) 194 | return fh.load() 195 | 196 | 197 | registry = ModelRegistry() 198 | 199 | 200 | class LocalFileLoader: 201 | @classmethod 202 | def request_file(cls, filename:str)->Path: 203 | "Require that the provided filename exists locally" 204 | path = Path(filename).absolute() 205 | if not path.exists(): 206 | raise FileNotFoundError(path) 207 | return path 208 | 209 | class RegistryFileLoader(LocalFileLoader): 210 | _registry = registry 211 | 212 | @classmethod 213 | def request_file(cls, filename:str)->Path: 214 | "Request file from the model registry" 215 | return cls._registry.get_file(cls._config_path, filename) 216 | 217 | 218 | def get_model_data(model: str, filename: str) -> Path: 219 | """Get the requested data file from the models file repository 220 | 221 | Parameters 222 | ---------- 223 | model : dot-separated path of the model in the YAML configuration (e.g. "ccsn.Bollig_2016") 224 | filename : Absolute path to simulation datafile, or a relative path from the model sub-directory 225 | 226 | Returns 227 | ------- 228 | Path of downloaded file. 229 | """ 230 | if os.path.isabs(filename): 231 | return Path(filename) 232 | else: 233 | return registry.get_file(model, filename) 234 | -------------------------------------------------------------------------------- /python/snewpy/flavor_transformation/TransformationChain.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """TransformationChain 3 | 4 | Class for calculating the combined probability matrix of the neutrino flavor transformation in SN, vacuum and Earth matter.""" 5 | 6 | 7 | from snewpy.flavor import FlavorMatrix 8 | from snewpy.neutrino import MixingParameters, ThreeFlavorMixingParameters, FourFlavorMixingParameters 9 | from .base import FlavorTransformation 10 | from .in_sn import SNTransformation 11 | from .in_vacuum import VacuumTransformation, NoVacuumTransformation 12 | from .in_earth import EarthTransformation, NoEarthMatter 13 | from collections import namedtuple 14 | 15 | #a tuple to hold the transformations list 16 | TransformationsTuple = namedtuple('TransformationsTuple',['in_sn','in_vacuum','in_earth']) 17 | 18 | class TransformationChain(FlavorTransformation): 19 | 20 | r"""This class calculates the probability matrix :math:`P_{\beta\alpha}` of the :math:`\nu_\alpha\to\nu_\beta` flavor transition as multiplication of :math:`P^{SN}` transformation in SN, :math:`P^{Vac}` transformation in vacuum and :math:`P^{Earth}` transformation in Earth: 21 | 22 | .. math:: 23 | 24 | P_{\beta\alpha} = \sum\limits_{i,j}P^{Earth}_{\beta j} \cdot P^{Vac}_{ji} \cdot P^{SN}_{i\alpha} 25 | """ 26 | def __init__(self, 27 | in_sn: SNTransformation, 28 | in_vacuum: VacuumTransformation=NoVacuumTransformation(), 29 | in_earth: EarthTransformation=NoEarthMatter(), 30 | *, 31 | mixing_params:ThreeFlavorMixingParameters|FourFlavorMixingParameters=MixingParameters() 32 | ): 33 | """ 34 | Parameters 35 | ---------- 36 | in_sn 37 | Transformation in Supernova. 38 | in_vacuum 39 | Transformation in Vacuum. 40 | By default NoVacuumTransformation is applied 41 | in_earth 42 | Transformation in Earth. 43 | By default NoEarthTransformation is applied 44 | 45 | Keyword mixing_params 46 | Neutrino mixing parameters (to be applied to all individual transformations in chain) 47 | By default use standard `MixingParameters` with normal neutrino ordering 48 | """ 49 | self.transforms = TransformationsTuple(in_sn, in_vacuum, in_earth) 50 | self.set_mixing_params(mixing_params) 51 | 52 | def set_mixing_params(self, mixing_params): 53 | """Update the mixing parameters in all transformations in chain""" 54 | #set the mixing parameters to all the inner classes 55 | self.mixing_params = mixing_params 56 | for t in self.transforms: 57 | t.mixing_params = mixing_params 58 | 59 | def P_ff(self, t, E)->FlavorMatrix: 60 | return self.transforms.in_earth.P_fm(t,E) @ \ 61 | self.transforms.in_vacuum.P_mm(t,E) @ \ 62 | self.transforms.in_sn.P_mf(t,E) 63 | 64 | def __str__(self): 65 | s = '+'.join([t.__class__.__name__ for t in self.transforms])+'_'+self.mixing_params.mass_order.name 66 | return s 67 | -------------------------------------------------------------------------------- /python/snewpy/flavor_transformation/__init__.py: -------------------------------------------------------------------------------- 1 | r""" This module implements flavor transformations that describe how neutrinos of 2 | different flavors change into each other between production inside the 3 | supernova and detection on Earth. 4 | 5 | Base Class for Flavor Transformations 6 | ------------------------------------- 7 | .. autoclass:: snewpy.flavor_transformation.FlavorTransformation 8 | :members: 9 | 10 | 11 | Available Transformations 12 | ------------------------- 13 | .. autoclass:: snewpy.flavor_transformation.NoTransformation 14 | :members: 15 | 16 | .. autoclass:: snewpy.flavor_transformation.CompleteExchange 17 | :members: 18 | 19 | .. autoclass:: snewpy.flavor_transformation.ThreeFlavorDecoherence 20 | :members: 21 | 22 | .. autoclass:: snewpy.flavor_transformation.TransformationChain 23 | :members: 24 | 25 | Concrete transformations 26 | ------------------------ 27 | .. automodule:: snewpy.flavor_transformation.in_sn 28 | :members: 29 | :member-order: bysource 30 | 31 | .. automodule:: snewpy.flavor_transformation.in_vacuum 32 | :members: 33 | :member-order: bysource 34 | 35 | .. automodule:: snewpy.flavor_transformation.in_earth 36 | :members: 37 | :member-order: bysource 38 | """ 39 | from snewpy.flavor import FlavorMatrix, ThreeFlavor 40 | from . import in_sn, in_earth, in_vacuum 41 | from .base import FlavorTransformation 42 | from .TransformationChain import TransformationChain 43 | from snewpy.neutrino import ThreeFlavorMixingParameters, FourFlavorMixingParameters 44 | 45 | def construct_chain(*transformation_classes): 46 | """Create a function that constructs a chain from given transformation_steps""" 47 | def construct(mixing_params:ThreeFlavorMixingParameters|FourFlavorMixingParameters, 48 | )->TransformationChain: 49 | """Construct a transformation chain with 50 | Parameters 51 | ---------- 52 | mixing_params: 53 | neutrino mixing parameters to be passed to the internal transformation steps 54 | 55 | Returns 56 | ------- 57 | A TransformationChain object with {step_names} transformations 58 | """ 59 | #initialize the transformations 60 | transformations = [value() for value in transformation_classes] 61 | #create the chain 62 | return TransformationChain(*transformations, mixing_params=mixing_params) 63 | #update the docstring 64 | step_names=[cls.__qualname__ for cls in transformation_classes] 65 | construct.__doc__ = construct.__doc__.format(step_names=step_names) 66 | return construct 67 | 68 | # define default values for backward compatibility 69 | AdiabaticMSW = construct_chain(in_sn.AdiabaticMSW) 70 | NonAdiabaticMSWH = construct_chain(in_sn.NonAdiabaticMSWH) 71 | AdiabaticMSWes = construct_chain(in_sn.AdiabaticMSWes) 72 | NonAdiabaticMSWes = construct_chain(in_sn.NonAdiabaticMSWes) 73 | TwoFlavorDecoherence = construct_chain(in_sn.TwoFlavorDecoherence) 74 | NeutrinoDecay = construct_chain(in_sn.AdiabaticMSW, 75 | in_vacuum.NeutrinoDecay) 76 | QuantumDecoherence = construct_chain(in_sn.AdiabaticMSW, 77 | in_vacuum.QuantumDecoherence) 78 | EarthMatter = lambda mixing_params,AltAz: TransformationChain( 79 | in_sn.AdiabaticMSW(), 80 | in_earth=in_earth.EarthMatter(SNAltAz=AltAz), 81 | mixing_params=mixing_params 82 | ) 83 | 84 | 85 | # Phenomenological transformations that cannot be represented as a TransformationChain 86 | class NoTransformation(FlavorTransformation): 87 | """Survival probabilities for no oscillation case.""" 88 | 89 | def P_ff(self, t, E): 90 | r"""This transformation returns the object without transform, 91 | so the transformation probability matrix is unit: 92 | 93 | .. math:: 94 | 95 | P_{\alpha\beta} = I_{\alpha\beta} 96 | """ 97 | p = FlavorMatrix.eye(ThreeFlavor) 98 | return p 99 | 100 | def apply_to(self, flux): 101 | return flux 102 | 103 | 104 | class CompleteExchange(FlavorTransformation): 105 | """Survival probabilities for the case when the electron flavors 106 | are half exchanged with the mu flavors and the half with the tau flavors. 107 | """ 108 | 109 | def P_ff(self, t, E): 110 | @FlavorMatrix.from_function(ThreeFlavor) 111 | def P(f1, f2): 112 | return (f1.is_neutrino == f2.is_neutrino)*(f1 != f2)*0.5 113 | 114 | return P 115 | 116 | 117 | class ThreeFlavorDecoherence(FlavorTransformation): 118 | """Equal mixing of all threen eutrino matter states and antineutrino matter states""" 119 | 120 | def P_ff(self, t, E): 121 | """Equal mixing so Earth matter has no effect""" 122 | @FlavorMatrix.from_function(ThreeFlavor) 123 | def P(f1, f2): 124 | return (f1.is_neutrino == f2.is_neutrino)*1/3. 125 | return P 126 | -------------------------------------------------------------------------------- /python/snewpy/flavor_transformation/base.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod, ABC 2 | 3 | from snewpy.flux import Container 4 | from snewpy.flavor import FlavorMatrix 5 | from snewpy.neutrino import MixingParameters, ThreeFlavorMixingParameters, FourFlavorMixingParameters 6 | 7 | class ThreeFlavorTransformation: 8 | _mixing_params = ThreeFlavorMixingParameters(**MixingParameters()) 9 | 10 | @property 11 | def mixing_params(self): 12 | return self._mixing_params 13 | 14 | @mixing_params.setter 15 | def mixing_params(self, val): 16 | return self._mixing_params.update(**val) 17 | 18 | class FourFlavorTransformation(ThreeFlavorTransformation): 19 | _mixing_params = FourFlavorMixingParameters(**MixingParameters()) 20 | 21 | 22 | class FlavorTransformation(ABC): 23 | """Generic interface to compute neutrino and antineutrino survival probability.""" 24 | 25 | def __str__(self): 26 | return self.__class__.__name__ 27 | 28 | @abstractmethod 29 | def P_ff(self, t, E) -> FlavorMatrix: 30 | r"""Transition probability matrix in flavor basis :math:`P_{\alpha\to\beta}` 31 | 32 | Parameters 33 | ---------- 34 | """ 35 | pass 36 | 37 | def apply_to(self, flux: Container) -> Container: 38 | r"""Apply this transformation to the given flux, return transformaed flux""" 39 | M = self.P_ff(flux.time, flux.energy) 40 | M = (flux.flavor_scheme <FlavorMatrix: 31 | pass 32 | ############################################################################### 33 | 34 | class NoEarthMatter(EarthTransformation): 35 | def __init__(self, mixing_params=None): 36 | self.mixing_params = mixing_params or MixingParameters('NORMAL') 37 | 38 | def P_fm(self, t, E)->FlavorMatrix: 39 | D = self.mixing_params.VacuumMixingMatrix().abs2() 40 | return D 41 | ############################################################################### 42 | 43 | class EarthMatter(ThreeFlavorTransformation, EarthTransformation): 44 | 45 | def __init__(self, SNAltAz, mixing_params=None): 46 | """Initialize flavor transformation 47 | 48 | Parameters 49 | ---------- 50 | mixing_params : ThreeFlavorMixingParameters instance or None 51 | SNAltAz : astropy AltAz object 52 | """ 53 | if BEMEWS is None: 54 | raise ModuleNotFoundError('BEMEWS module is not found. Please make sure BEMEWS is installed to use EarthMatter transformation') 55 | if(mixing_params): 56 | self.mixing_params=mixing_params 57 | self.SNAltAz = SNAltAz 58 | 59 | self.prior_E = None # used to store energy array from previous calls to get_probabilities 60 | self.prior_D = None 61 | 62 | 63 | # Initialize BEMEWS input data object 64 | self.settings = BEMEWS.InputDataBEMEWS() 65 | 66 | self.settings.altitude = self.SNAltAz.alt.deg 67 | self.settings.azimuth = self.SNAltAz.az.deg 68 | 69 | self.settings.densityprofile = str(files(BEMEWS.data).joinpath('PREM.rho.dat')) 70 | self.settings.electronfraction = str(files(BEMEWS.data).joinpath('PREM.Ye.dat')) 71 | self.settings.accuracy = 1.01e-9 72 | self.settings.outputflag = False 73 | self.settings.stepcounterlimit = False 74 | 75 | def _update_settings(self): 76 | """Put the values from mixing_parameters into self.settings""" 77 | self.settings.deltam_21 = self.mixing_params.dm21_2.to_value('eV**2') 78 | self.settings.deltam_32 = self.mixing_params.dm32_2.to_value('eV**2') 79 | self.settings.theta12 = self.mixing_params.theta12.to_value('deg') 80 | self.settings.theta13 = self.mixing_params.theta13.to_value('deg') 81 | self.settings.theta23 = self.mixing_params.theta23.to_value('deg') 82 | self.settings.deltaCP = self.mixing_params.deltaCP.to_value('deg') 83 | 84 | def P_fm(self, t, E): 85 | #update the settings - in case mixing_params were changed 86 | self._update_settings() 87 | 88 | if self.prior_E != None: 89 | # Use cached result if possible 90 | if u.allclose(self.prior_E, E) == True: 91 | return self.prior_D 92 | 93 | self.prior_E = E 94 | 95 | #- Set the input energy bins 96 | E = E.to_value('MeV') 97 | self.settings.NE = len(E) 98 | self.settings.Emin = E[0] 99 | self.settings.Emax = E[-1] 100 | 101 | #run the calculation 102 | Pfm = np.asarray(BEMEWS.Run(self.settings)) 103 | #matrix from BEMEWS needs to be rearranged to match SNEWPY flavor indicii ordering 104 | #Pfm contains P(nu_alpha -> nu_i) index order is (nu/nubar, energy, alpha, i) 105 | #We convert the array dimensions: 106 | Pfm = np.swapaxes(Pfm, 1,3) #(nu/nubar, i, alpha, energy) 107 | 108 | P = FlavorMatrix.zeros( 109 | flavor=self.mixing_params.basis_mass, 110 | from_flavor=self.mixing_params.basis_flavor, 111 | extra_dims=E.shape) 112 | 113 | P["NU","NU"] = Pfm[0] 114 | P["NU_BAR","NU_BAR"] = Pfm[1] 115 | self.prior_D = P 116 | return P 117 | -------------------------------------------------------------------------------- /python/snewpy/flavor_transformation/in_vacuum.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Transformations in vacuum 3 | ========================= 4 | 5 | Transitions of neutrino mass states :math:`\nu_i\to\nu_j` in vacuum 6 | """ 7 | from abc import abstractmethod, ABC 8 | import numpy as np 9 | from astropy import units as u 10 | from astropy import constants as c 11 | 12 | from snewpy.flavor import FlavorMatrix 13 | from .base import ThreeFlavorTransformation, FourFlavorTransformation 14 | from snewpy.neutrino import MassHierarchy 15 | 16 | ############################################################################### 17 | # Vacuum transformations 18 | ############################################################################### 19 | class VacuumTransformation(ABC): 20 | @abstractmethod 21 | def P_mm(self, t, E)->FlavorMatrix: 22 | pass 23 | ############################################################################### 24 | class NoVacuumTransformation(VacuumTransformation): 25 | def P_mm(self, t, E)->FlavorMatrix: 26 | return FlavorMatrix.eye(self.mixing_params.basis_mass) 27 | 28 | class NeutrinoDecay(VacuumTransformation, ThreeFlavorTransformation): 29 | """Decay effect, where the heaviest neutrino decays to the lightest 30 | neutrino. For a description and typical parameters, see A. de Gouvêa et al., 31 | PRD 101:043013, 2020, arXiv:1910.01127. 32 | """ 33 | def __init__(self, mass=1*u.eV/c.c**2, tau=1*u.day, dist=10*u.kpc): 34 | """Initialize transformation matrix. 35 | 36 | Parameters 37 | ---------- 38 | mass : astropy.units.quantity.Quantity 39 | Mass of the heaviest neutrino; expect in eV/c^2. 40 | tau : astropy.units.quantity.Quantity 41 | Lifetime of the heaviest neutrino. 42 | dist : astropy.units.quantity.Quantity 43 | Distance to the supernova. 44 | """ 45 | self.m = mass 46 | self.tau = tau 47 | self.d = dist 48 | 49 | def gamma(self, E): 50 | """Decay width of the heaviest neutrino mass. 51 | 52 | Parameters 53 | ---------- 54 | E : float 55 | Energy of the nu3. 56 | 57 | Returns 58 | ------- 59 | Gamma : float 60 | Decay width of the neutrino mass, in units of 1/length. 61 | 62 | :meta private: 63 | """ 64 | return self.m*c.c / (E*self.tau) 65 | 66 | def P_mm(self, t, E)->FlavorMatrix: 67 | decay_factor = np.exp(-self.gamma(E)*self.d) 68 | PND = FlavorMatrix.eye(self.mixing_params.basis_mass, extra_dims=E.shape) 69 | 70 | if self.mixing_params.mass_order == MassHierarchy.NORMAL: 71 | PND['NU_1','NU_3'] = 1 - decay_factor 72 | PND['NU_3','NU_3'] = decay_factor 73 | 74 | else: 75 | PND['NU_2','NU_2'] = decay_factor 76 | PND['NU_3','NU_2'] = 1 - decay_factor 77 | 78 | PND['NU_BAR','NU_BAR'] = PND['NU','NU'] 79 | return PND 80 | 81 | ############################################################################### 82 | 83 | class QuantumDecoherence(VacuumTransformation, ThreeFlavorTransformation): 84 | """Quantum Decoherence, where propagation in vacuum leads to equipartition 85 | of states. For a description and typical parameters, see M. V. dos Santos et al., 86 | 2023, arXiv:2306.17591. 87 | """ 88 | def __init__(self, Gamma3=1e-27*u.eV, Gamma8=1e-27*u.eV, dist=10*u.kpc, n=0, E0=10*u.MeV): 89 | """Initialize transformation matrix. 90 | 91 | Parameters 92 | ---------- 93 | Gamma3 : astropy.units.quantity.Quantity 94 | Quantum decoherence parameter; expect in eV. 95 | Gamma8 : astropy.units.quantity.Quantity 96 | Quantum decoherence parameter; expect in eV. 97 | dist : astropy.units.quantity.Quantity 98 | Distance to the supernova. 99 | n : float 100 | Exponent of power law for energy dependent quantum decoherence parameters, 101 | i.e. Gamma = Gamma0*(E/E0)**n. If not specified, it is taken as zero. 102 | E0 : astropy.units.quantity.Quantity 103 | Reference energy in the power law Gamma = Gamma0*(E/E0)**n. If not specified, 104 | it is taken as 10 MeV. Note that if n = 0, quantum decoherence parameters are independent 105 | of E0. 106 | """ 107 | self.Gamma3 = (Gamma3 / (c.hbar.to('eV s') * c.c)).to('1/kpc') 108 | self.Gamma8 = (Gamma8 / (c.hbar.to('eV s') * c.c)).to('1/kpc') 109 | self.d = dist 110 | self.n = n 111 | self.E0 = E0 112 | 113 | def P_mm(self, t, E)->FlavorMatrix: 114 | PQD = FlavorMatrix.zeros(self.mixing_params.basis_mass, extra_dims=E.shape) 115 | x = (E/self.E0)**self.n 116 | PQD['NU_1','NU_1'] = 1/3 + 1/2 * np.exp(-(self.Gamma3 + self.Gamma8 / 3) * x * self.d) \ 117 | + 1/6 * np.exp(-self.Gamma8 * x * self.d) 118 | 119 | PQD['NU_1','NU_2'] = 1/3 - 1/2 * np.exp(-(self.Gamma3 + self.Gamma8 / 3) * x * self.d) \ 120 | + 1/6 * np.exp(-self.Gamma8 * x * self.d) 121 | 122 | PQD['NU_1','NU_3'] = 1/3 - 1/3 * np.exp(-self.Gamma8 * x * self.d) 123 | 124 | PQD['NU_2',['NU_1','NU_2','NU_3']] = PQD['NU_1',['NU_2','NU_1','NU_3']] 125 | PQD['NU_2','NU_2'] = PQD['NU_1','NU_1'] 126 | PQD['NU_2','NU_3'] = PQD['NU_1','NU_3'] 127 | 128 | PQD['NU_3','NU_1'] = PQD['NU_1','NU_3'] 129 | PQD['NU_3','NU_2'] = PQD['NU_2','NU_3'] 130 | 131 | PQD['NU_3','NU_3'] = 1/3 + 2/3 * np.exp(-self.Gamma8 * x * self.d) 132 | PQD['NU_BAR','NU_BAR'] = PQD['NU','NU'] 133 | return PQD 134 | -------------------------------------------------------------------------------- /python/snewpy/models/__init__.py: -------------------------------------------------------------------------------- 1 | from warnings import warn 2 | 3 | from . import ccsn 4 | 5 | 6 | def __getattr__(name): 7 | if name in dir(ccsn): 8 | warn(f"{__name__}.{name} is moved to {__name__}.ccsn.{name}", FutureWarning, stacklevel=2) 9 | return getattr(ccsn, name) 10 | raise AttributeError(f"module {__name__} has no attribute {name}") 11 | -------------------------------------------------------------------------------- /python/snewpy/models/extended.py: -------------------------------------------------------------------------------- 1 | from warnings import warn 2 | 3 | import numpy as np 4 | from astropy import units as u 5 | 6 | from snewpy.neutrino import Flavor 7 | from snewpy.models.base import SupernovaModel 8 | 9 | 10 | class ExtendedModel(SupernovaModel): 11 | """Class defining a supernova model with a cooling tail extension.""" 12 | 13 | def __init__(self, base_model): 14 | """Initialize extended supernova model class.""" 15 | if not isinstance(base_model, SupernovaModel): 16 | raise TypeError("ExtendedModel.__init__ requires a SupernovaModel object") 17 | 18 | self.__dict__ = base_model.__dict__.copy() 19 | for method_name in dir(base_model): 20 | if callable(getattr(base_model, method_name)) and method_name[0] != '_': 21 | if method_name == 'get_initial_spectra': 22 | self._get_initial_spectra = getattr(base_model, method_name) 23 | else: 24 | setattr(self, method_name, getattr(base_model, method_name)) 25 | self.t_final = self.time[-1] 26 | self.L_final = {flv: self.luminosity[flv][-1] for flv in Flavor} 27 | 28 | def get_initial_spectra(self, *args, **kwargs): 29 | """Get neutrino spectra/luminosity curves before oscillation""" 30 | return self._get_initial_spectra(*args, **kwargs) 31 | 32 | def get_extended_luminosity(self, t, k=-1., A=None, tau_c=36. * u.s, alpha=2.66, flavor = Flavor.NU_E): 33 | """Get neutrino luminosity from supernova cooling tail luminosity model. 34 | 35 | Parameters 36 | ---------- 37 | t : astropy.Quantity 38 | Time to evaluate luminosity. 39 | k : float 40 | Power law factor (default: -1) 41 | A : astropy.Quantity 42 | Normalization factor (default: None, automatically match original model data) 43 | tau_c : astropy.Quantity 44 | Exponential decay characteristic timescale (default: 36 s) 45 | alpha : float 46 | Exponential decay factor (default: 2.66) 47 | 48 | Returns 49 | ------- 50 | astropy.Quantity 51 | Luminosity calculated from cooling tail model. 52 | """ 53 | if t.value < 0.5: 54 | warn("Extended luminosity model not applicable to early times") 55 | if A is None: 56 | tf = self.t_final 57 | Lf = self.L_final[flavor] 58 | A = Lf / (tf.value**k * np.exp(-(tf/tau_c)**alpha)) 59 | return A * t.value**k * np.exp(-(t/tau_c)**alpha) 60 | 61 | def extend(self, ts, k=-1., A=None, tau_c=36. * u.s, alpha=2.66): 62 | """Extend supernova model to specific times. 63 | 64 | Parameters 65 | ---------- 66 | ts : astropy.Quantity 67 | Times to add to supernova model. 68 | k : float 69 | Power law factor (default: -1) 70 | A : astropy.Quantity 71 | Normalization factor (default: None, automatically match original model data) 72 | tau_c : astropy.Quantity 73 | Exponential decay characteristic timescale (default: 36 s) 74 | alpha : float 75 | Exponential decay factor (default: 2.66) 76 | """ 77 | # Select times after the end of the model 78 | select = ts > self.t_final 79 | 80 | for t in ts[select]: 81 | self.time = np.append(self.time, t) 82 | for flavor in Flavor: 83 | L_ext = self.get_extended_luminosity(t, k = k, A = A, tau_c = tau_c, alpha = alpha, flavor = flavor) 84 | self.luminosity[flavor] = np.append(self.luminosity[flavor], L_ext) 85 | self.meanE[flavor] = np.append(self.meanE[flavor], self.meanE[flavor][-1]) 86 | self.pinch[flavor] = np.append(self.pinch[flavor], self.pinch[flavor][-1]) 87 | -------------------------------------------------------------------------------- /python/snewpy/models/model_files.yml: -------------------------------------------------------------------------------- 1 | # Define remote locations of simulation files for each model. 2 | # 1. Model data on GitHub must provide the SNEWPY release version. 3 | # 2. Model data on Zenodo must provide the Zenodo record ID. 4 | 5 | config: 6 | - &snewpy "https://github.com/SNEWS2/snewpy/raw/v{snewpy_version}/models/{model}/{filename}" 7 | - &ccsn_repository "https://github.com/SNEWS2/snewpy-models-ccsn/raw/v0.3/models/{model}/{filename}" 8 | - &presn_repository "https://github.com/SNEWS2/snewpy-models-presn/raw/v0.2/models/{model}/{filename}" 9 | 10 | models: 11 | ccsn: 12 | Bollig_2016: 13 | repository: *ccsn_repository 14 | 15 | Fornax_2019: 16 | repository: *ccsn_repository 17 | 18 | Fornax_2021: 19 | repository: *ccsn_repository 20 | 21 | Fornax_2022: 22 | repository: *ccsn_repository 23 | 24 | Kuroda_2020: 25 | repository: *ccsn_repository 26 | 27 | Nakazato_2013: 28 | repository: *ccsn_repository 29 | 30 | OConnor_2013: 31 | repository: *ccsn_repository 32 | 33 | OConnor_2015: 34 | repository: *ccsn_repository 35 | 36 | Sukhbold_2015: 37 | repository: *ccsn_repository 38 | 39 | Tamborra_2014: 40 | repository: *ccsn_repository 41 | 42 | Walk_2018: 43 | repository: *ccsn_repository 44 | 45 | Walk_2019: 46 | repository: *ccsn_repository 47 | 48 | Warren_2020: 49 | repository: *ccsn_repository 50 | 51 | Zha_2021: 52 | repository: *ccsn_repository 53 | 54 | Mori_2023: 55 | repository: *ccsn_repository 56 | 57 | Bugli_2021: 58 | repository: *ccsn_repository 59 | 60 | Fischer_2020: 61 | repository: *ccsn_repository 62 | 63 | presn: 64 | 65 | Odrzywolek_2010: 66 | repository: *presn_repository 67 | 68 | Patton_2017: 69 | repository: *presn_repository 70 | 71 | Kato_2017: 72 | repository: *presn_repository 73 | 74 | Yoshida_2016: 75 | repository: *presn_repository 76 | -------------------------------------------------------------------------------- /python/snewpy/models/presn.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | The submodule ``snewpy.models.presn`` contains models of presupernova neutrino fluxes, 4 | derived from the :class:`SupernovaModel` base class. 5 | """ 6 | import snewpy.models.presn_loaders as loaders 7 | from snewpy.models.registry_model import RegistryModel 8 | from astropy import units as u 9 | 10 | @RegistryModel( 11 | progenitor_mass = [15, 25]<`_ 16 | 17 | Dataset available on `Odrzywolek’s website `_ 18 | """ 19 | def __init__(self, progenitor_mass:u.Quantity): 20 | filename=f"s{progenitor_mass.to_value('Msun'):.0f}_nuebar_data.txt" 21 | super().__init__(filename) 22 | 23 | @RegistryModel( 24 | progenitor_mass = [15, 30]<`_ 29 | 30 | Dataset available on Zenodo (`DOI:10.5281/zenodo.2626645 `_) 31 | """ 32 | def __init__(self, progenitor_mass:u.Quantity): 33 | filename=f"totalLuminosity_{progenitor_mass.to_value('Msun'):.0f}SolarMass.dat" 34 | super().__init__(filename) 35 | 36 | @RegistryModel( 37 | progenitor_mass = [12, 15]<`_ 42 | 43 | Dataset available on `Zenodo `__ 44 | """ 45 | def __init__(self, progenitor_mass:u.Quantity): 46 | path=f"pre_collapse/m{progenitor_mass.to_value('Msun'):.0f}" 47 | super().__init__(path) 48 | 49 | 50 | @RegistryModel( 51 | progenitor_mass = [12, 15, 20]<`_ 56 | 57 | Dataset available on `Zenodo `__ 58 | """ 59 | def __init__(self, progenitor_mass:u.Quantity): 60 | path=f"t_spc_m{progenitor_mass.to_value('Msun'):.0f}_1.2.txt" 61 | super().__init__(path) 62 | -------------------------------------------------------------------------------- /python/snewpy/models/presn_loaders.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | The submodule ``snewpy.models.presn_loaders`` contains classes to load pre-supernova 4 | models from files stored on disk. 5 | """ 6 | 7 | import numpy as np 8 | import pandas as pd 9 | from scipy.interpolate import interp1d 10 | from astropy import units as u 11 | from snewpy.models.base import SupernovaModel 12 | from snewpy.neutrino import Flavor 13 | from pathlib import Path 14 | 15 | def _interp_T(t0, v0, dt=1e-3, dv=1e-10, axis=0): 16 | "dilog interpolation" 17 | lt0 = np.log(t0 + dt) 18 | lv0 = np.log(v0 + dv) 19 | lv1 = interp1d(lt0, lv0, axis=axis, fill_value=0, copy=False, bounds_error=False) 20 | return lambda t1: np.exp(lv1(np.log(t1 + dt))) 21 | 22 | 23 | def _interp_E(e0, v0, axis=1): 24 | "linear interpolation" 25 | return interp1d(e0, v0, axis=axis, fill_value=0, copy=False, bounds_error=False) 26 | 27 | 28 | def _interp_TE(times, energies, array, ax_t=1, ax_e=2): 29 | def _f(t, E): 30 | a_t = _interp_T(times, array, axis=ax_t)(t) 31 | a_te = _interp_E(energies, a_t, axis=ax_e)(E) 32 | return a_te 33 | 34 | return _f 35 | 36 | class Odrzywolek_2010(SupernovaModel): 37 | """Set up a presupernova model, based on 38 | [A. Odrzywolek and A. Heger, Acta Phys. Polon. B 41 (2010) 1611.] 39 | """ 40 | 41 | def __init__(self, filename:str): 42 | df = pd.read_csv( 43 | self.request_file(filename), 44 | sep=r'\s+', 45 | skiprows=1, 46 | usecols=[1,6,7,8], 47 | names=["time","a","alpha","b"], 48 | index_col="time", 49 | ) 50 | # interpolated in time 51 | self.df_t = _interp_T(df.index, df) 52 | self.factor = {} 53 | for f in Flavor: 54 | if f.is_electron: 55 | self.factor[f] = 1.0 56 | else: 57 | # nuX/nuE ratio from Odrzywolek paper: (arXiv:astro-ph/0311012) 58 | self.factor[f] = 0.19 59 | time = -df.index.to_numpy() << u.s 60 | super().__init__(time, self.metadata) 61 | 62 | def get_initial_spectra(self, t, E, flavors=Flavor): 63 | # negative t for time before SN 64 | t = -t.to_value("s") 65 | E = E.to_value("MeV") 66 | df = self.df_t(t) 67 | a, alpha, b = df.T 68 | Enu = np.expand_dims(E, 1) 69 | a = np.expand_dims(a, 0) 70 | alpha = np.expand_dims(alpha, 0) 71 | b = np.expand_dims(b, 0) 72 | fluence = a * Enu ** alpha * np.exp(-b * Enu) / (u.MeV * u.s) 73 | result = {f: fluence.T * self.factor[f] for f in flavors} 74 | return result 75 | 76 | 77 | class Patton_2017(SupernovaModel): 78 | """Set up a presupernova model based on 79 | [Kelly M. Patton et al 2017 ApJ 851 6, 80 | https://doi.org/10.5281/zenodo.2598709] 81 | """ 82 | def __init__(self, filename:str): 83 | df = pd.read_csv( 84 | self.request_file(filename), 85 | comment="#", 86 | sep=r'\s+', 87 | names=["time","Enu",Flavor.NU_E,Flavor.NU_E_BAR,Flavor.NU_MU,Flavor.NU_MU_BAR], 88 | usecols=range(6), 89 | ) 90 | 91 | df[Flavor.NU_TAU] = df[Flavor.NU_MU] 92 | df[Flavor.NU_TAU_BAR] = df[Flavor.NU_MU_BAR] 93 | 94 | df = df.set_index(["time", "Enu"]) 95 | times = df.index.levels[0].to_numpy() 96 | energies = df.index.levels[1].to_numpy() 97 | df = df.unstack("Enu") 98 | # make a 3d array 99 | self.array = np.stack([df[f] for f in Flavor], axis=0) 100 | self.interpolated = _interp_TE( 101 | times, energies, self.array, ax_t=1, ax_e=2 102 | ) 103 | super().__init__(-times << u.hour, self.metadata) 104 | 105 | def get_initial_spectra(self, t, E, flavors=Flavor): 106 | t = np.array(-t.to_value("hour"), ndmin=1) 107 | E = np.array(E.to_value("MeV"), ndmin=1) 108 | flux = self.interpolated(t, E) / (u.MeV * u.s) 109 | return {f: flux[f] for f in flavors} 110 | 111 | class Kato_2017(SupernovaModel): 112 | """Set up a presupernova model based on 113 | [Chinami Kato et al 2017 ApJ 848 48] 114 | """ 115 | def __init__(self, path): 116 | fluxes = {} 117 | #reading the time steps values: 118 | times, step = np.loadtxt(self.request_file(f"{path}/total_nue/lightcurve_nue_all.dat"), usecols=[0, 3]).T 119 | 120 | file_base = {Flavor.NU_E: 'total_nue/spe_all', 121 | Flavor.NU_E_BAR: 'total_nueb/spe_all', 122 | Flavor.NU_MU: 'total_nux/spe_sum_mu_nu', 123 | Flavor.NU_MU_BAR: 'total_nux/spe_sum_mu', 124 | Flavor.NU_TAU: 'total_nux/spe_sum_mu_nu', 125 | Flavor.NU_TAU_BAR: 'total_nux/spe_sum_mu' 126 | } 127 | for flv,file_base in file_base.items(): 128 | d2NdEdT = [] 129 | for s in step: 130 | energies, dNdE = np.loadtxt( 131 | self.request_file(f"{path}/{file_base}{s:05.0f}.dat") 132 | ).T 133 | d2NdEdT += [dNdE] 134 | fluxes[flv] = np.stack(d2NdEdT) 135 | self.array = np.stack([fluxes[f] for f in Flavor], axis=0) 136 | self.interpolated = _interp_TE( 137 | times, energies, self.array, ax_t=1, ax_e=2 138 | ) 139 | super().__init__(-times << u.s, self.metadata) 140 | 141 | def get_initial_spectra(self, t, E, flavors=Flavor): 142 | t = np.array(-t.to_value("s"), ndmin=1) 143 | E = np.array(E.to_value("MeV"), ndmin=1) 144 | flux = self.interpolated(t, E) / (u.MeV * u.s) 145 | return {f: flux[f] for f in flavors} 146 | 147 | class Yoshida_2016(SupernovaModel): 148 | """Set up a presupernova model based on 149 | [Yoshida et al. (2016), PRD 93, 123012.] 150 | """ 151 | def __init__(self, filename): 152 | with open(self.request_file(filename)) as f: 153 | data = [] 154 | T = [] 155 | while (line := f.readline()): 156 | if not line: break 157 | T += [float(line.split()[1])] 158 | data += [[np.loadtxt(f, max_rows=100).flatten() for i in range(4)]] 159 | times = np.array(T) 160 | energies = np.concatenate([ 161 | np.linspace(0,10,1001)[1:], 162 | np.linspace(10,20,501)[1:] 163 | ]) 164 | dNdEdT = np.stack(data, axis=1) 165 | #rearrange flavors from NU_E, NU_E_BAR,_NU_X, NU_X_BAR to current 166 | indices = np.argsort([Flavor.NU_E,Flavor.NU_E_BAR, Flavor.NU_X, Flavor.NU_X_BAR]) 167 | dNdEdT = dNdEdT.take(indices, axis=0) 168 | 169 | self.interpolated = _interp_TE( 170 | times, energies, dNdEdT, ax_t=1, ax_e=2 171 | ) 172 | super().__init__(-times << u.s, self.metadata) 173 | 174 | def get_initial_spectra(self, t, E, flavors=Flavor): 175 | t = np.array(-t.to_value("s"), ndmin=1) 176 | E = np.array(E.to_value("MeV"), ndmin=1) 177 | flux = self.interpolated(t, E) / (u.MeV * u.s) 178 | return {f: flux[f] for f in flavors} 179 | -------------------------------------------------------------------------------- /python/snewpy/scripts/Analytic.py: -------------------------------------------------------------------------------- 1 | """This model stub allows one to generate simple analytic models and then 2 | read them into the Analytic3Species model found in model.py which is part of SNEWPY. 3 | """ 4 | 5 | from astropy.table import Table 6 | import numpy as np 7 | 8 | total_energy = (5e52,5e52,2e53) 9 | mean_energy = (15., 15., 15.) 10 | rms_or_pinch = "pinch" 11 | pinch_values = (2.3, 2.3, 2.3) 12 | # Alternative parameters, for rms. Uncomment the following 2 lines: 13 | #rms_or_pinch = "rms" 14 | #rms_energy = (280, 280, 280) 15 | 16 | file_name = "analytic.dat" 17 | 18 | table = Table() 19 | table['TIME'] = np.linspace(0,1,2) 20 | table['L_NU_E'] = np.linspace(1,1,2)*total_energy[0] 21 | table['L_NU_E_BAR'] = np.linspace(1,1,2)*total_energy[1] 22 | table['L_NU_X'] = np.linspace(1,1,2)*total_energy[2] 23 | 24 | table['E_NU_E'] = np.linspace(1,1,2)*mean_energy[0] 25 | table['E_NU_E_BAR'] = np.linspace(1,1,2)*mean_energy[1] 26 | table['E_NU_X'] = np.linspace(1,1,2)*mean_energy[2] 27 | 28 | if rms_or_pinch == "rms": 29 | table['RMS_NU_E'] = np.linspace(1,1,2)*rms_energy[0] 30 | table['RMS_NU_E_BAR'] = np.linspace(1,1,2)*rms_energy[1] 31 | table['RMS_NU_X'] = np.linspace(1,1,2)*rms_energy[2] 32 | table['ALPHA_NU_E'] = (2.0 * table['E_NU_E'] ** 2 - table['RMS_NU_E'] ** 2) / ( 33 | table['RMS_NU_E'] ** 2 - table['E_NU_E'] ** 2) 34 | table['ALPHA_NU_E_BAR'] = (2.0 * table['E_NU_E_BAR'] ** 2 - table['RMS_NU_E_BAR'] ** 2) / ( 35 | table['RMS_NU_E_BAR'] ** 2 - table['E_NU_E_BAR'] ** 2) 36 | table['ALPHA_NU_X'] = (2.0 * table['E_NU_X'] ** 2 - table['RMS_NU_X'] ** 2) / ( 37 | table['RMS_NU_X'] ** 2 - table['E_NU_X'] ** 2) 38 | elif rms_or_pinch == "pinch": 39 | table['ALPHA_NU_E'] = np.linspace(1,1,2)*pinch_values[0] 40 | table['ALPHA_NU_E_BAR'] = np.linspace(1,1,2)*pinch_values[1] 41 | table['ALPHA_NU_X'] = np.linspace(1,1,2)*pinch_values[2] 42 | table['RMS_NU_E'] = (2.0 + table['ALPHA_NU_E'])/(1.0 + table['ALPHA_NU_E'])*table['E_NU_E']**2 43 | table['RMS_NU_E_BAR'] = (2.0 + table['ALPHA_NU_E_BAR'])/(1.0 + table['ALPHA_NU_E_BAR'])*table['E_NU_E_BAR']**2 44 | table['RMS_NU_X'] = (2.0 + table['ALPHA_NU_X'])/(1.0 + table['ALPHA_NU_X'])*table['E_NU_X']**2 45 | else: 46 | print("incorrect second moment method: rms or pinch") 47 | table.write(file_name,format='ascii') 48 | -------------------------------------------------------------------------------- /python/snewpy/scripts/Convert_to_ROOT.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ This module allows to gather the SNOwGLoBES outputs into a ROOT files with 2D histograms. 3 | These 2D histograms show the rate as a function of time and energy for any given interaction channel. 4 | There is one ROOT file per detector, per smearing option, and per weighting option. 5 | Generate a tarball with the input files by running 6 | snowglobes.generate_time_series(...) 7 | snowglobes.simulate(...) 8 | snowglobes.collate(...) 9 | 10 | Then, to run the code, use: 11 | python Convert_to_ROOT.py 12 | """ 13 | 14 | import tarfile 15 | from pathlib import Path 16 | from tempfile import TemporaryDirectory 17 | import argparse 18 | 19 | import numpy as np 20 | 21 | import ROOT 22 | 23 | # Get the path to input files (command line argument). Default is current folder 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument('path', help='Path to tarball containing collated SNEWPY outputs') 26 | args = parser.parse_args() 27 | 28 | tarball_path = args.path 29 | 30 | # Iterate over collated SNEWPY files and gather all time bins 31 | outputnames = {} 32 | hist_dict = {} 33 | tmin,tmax,ntbins = 0,0,0 34 | column_names = [] 35 | with TemporaryDirectory(prefix='snowglobes') as tempdir: 36 | #Extracts data from tarfile and sets up lists of paths and fluxfilenames for later use 37 | with tarfile.open(tarball_path) as tar: 38 | tar.extractall(tempdir) 39 | 40 | flux_files = list(Path(tempdir).glob('*/Collated*.dat')) 41 | first = True 42 | for flux_file in flux_files: 43 | flux_root = flux_file.stem 44 | if first: 45 | # Time boundaries are formatted to be %0.3f in generate_time_series 46 | tmin,tmax,ntbins = flux_root.split(',') 47 | tmin = float(".".join(tmin.split('.')[-2:])) 48 | tmax = float(tmax) 49 | ntbins = int(ntbins.split('-')[0]) 50 | print(f"Time binning is: tmin={tmin}, tmax={tmax}, ntbins={ntbins}") 51 | first = False 52 | # Get column names 53 | with open(flux_file) as fin: 54 | column_names = fin.readline().split()[1:] 55 | # Find general name for flux file as well as value of time bin 56 | flux_start, flux_end = flux_root.split('tbin') 57 | flux_split = flux_end.split('.') 58 | time_bin = int(flux_split[0]) 59 | time_val = (time_bin - 0.5) * (tmax - tmin)/ntbins + tmin 60 | flux_end = ".".join(flux_split[1:]) 61 | flux_root = flux_start + flux_end 62 | # Get data from file 63 | data = np.loadtxt(flux_file, skiprows=2) 64 | energies = data[:, 0] 65 | data = data[:, 1:] 66 | # Initialize histograms and open files 67 | file_name = f"{Path(tarball_path).parent}/{flux_root}.root" 68 | energy_step = 0.5 * (energies[1] - energies[0]) 69 | if file_name not in outputnames.keys(): 70 | print(f"Creating file {file_name}") 71 | rootfile = ROOT.TFile.Open(file_name, "RECREATE") 72 | outputnames[file_name] = rootfile 73 | for channel in column_names: 74 | histname = f"{flux_root}_{channel}" 75 | hist_dict[histname] = ROOT.TH2D(channel, f"Rates for {channel} interaction", 76 | len(energies), energies[0] - energy_step, 77 | energies[-1] + energy_step, 78 | ntbins, tmin, tmax) 79 | hist_dict[histname].SetDirectory(rootfile) 80 | # Fill histogram with table 81 | for energ,line in zip(energies,data): 82 | for l,c in zip(line,column_names): 83 | histname = f"{flux_root}_{c}" 84 | hist_dict[histname].Fill(energ,time_val,l) 85 | # Save data and close ROOT files 86 | print("Closing files...") 87 | for fname in outputnames: 88 | outputnames[fname].Write() 89 | outputnames[fname].Close() 90 | -------------------------------------------------------------------------------- /python/snewpy/scripts/SNEWS2.0_rate_table_singleexample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from snewpy import snowglobes 3 | 4 | SNOwGLoBES_path = None # change to SNOwGLoBES directory if using a custom detector configuration 5 | SNEWPY_model_dir = "/path/to/snewpy/models/" # directory containing model input files 6 | 7 | distance = 10 # Supernova distance in kpc 8 | detector = "wc100kt30prct" #SNOwGLoBES detector for water Cerenkov 9 | modeltype = 'Bollig_2016' # Model type from snewpy.models 10 | model = 's11.2c' # Name of model 11 | transformation = 'AdiabaticMSW_NMO' # Desired flavor transformation 12 | 13 | # Construct file system path of model file and name of output file 14 | model_path = SNEWPY_model_dir + "/" + modeltype + "/" + model 15 | outfile = modeltype + "_" + model + "_" + transformation 16 | 17 | # Now, do the main work: 18 | print("Generating fluence files ...") 19 | tarredfile = snowglobes.generate_fluence(model_path, modeltype, transformation, distance, outfile) 20 | 21 | print("Simulating detector effects with SNOwGLoBES ...") 22 | snowglobes.simulate(SNOwGLoBES_path, tarredfile, detector_input=detector) 23 | 24 | print("Collating results ...") 25 | tables = snowglobes.collate(SNOwGLoBES_path, tarredfile, skip_plots=True) 26 | 27 | 28 | # Use results to print the number of events in different interaction channels 29 | key = f"Collated_{outfile}_{detector}_events_smeared_weighted.dat" 30 | total_events = 0 31 | for i, channel in enumerate(tables[key]['header'].split()): 32 | if i == 0: 33 | continue 34 | n_events = sum(tables[key]['data'][i]) 35 | total_events += n_events 36 | print(f"{channel:10}: {n_events:.3f} events") 37 | 38 | #Super-K has 32kT inner volume 39 | print("Total events in Super-K-like detector:",0.32*total_events) 40 | 41 | -------------------------------------------------------------------------------- /python/snewpy/scripts/TimeSeries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from snewpy import snowglobes 4 | 5 | SNOwGLoBES_path = None # change to SNOwGLoBES directory if using a custom detector configuration 6 | 7 | # arguments for generate_time_series 8 | model_file = "/path/to/snewpy/models/Nakazato_2013/nakazato-LS220-BH-z0.004-s30.0.fits" 9 | modeltype = 'Nakazato_2013' 10 | transformation = 'AdiabaticMSW_NMO' 11 | d = 10 # Supernova distance in kpc 12 | 13 | # Running the modules 14 | outfile = snowglobes.generate_time_series(model_file, modeltype, transformation, d) 15 | snowglobes.simulate(SNOwGLoBES_path, outfile, detector_input="icecube") 16 | snowglobes.collate(SNOwGLoBES_path, outfile) 17 | 18 | # An additional, optional argument in simulate() is the detector name, if one wants to only run 1 detector, rather than all of them. 19 | -------------------------------------------------------------------------------- /python/snewpy/scripts/make_rate_table_dict.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from astropy import units as u 3 | from snewpy import snowglobes 4 | from pprint import pprint 5 | from datetime import datetime 6 | 7 | def calc_total_rate(model_name, model_file,transform,detectors): 8 | #guess model name 9 | model_path = f'models/{model_name}/{model_file}' 10 | path = snowglobes.generate_fluence(model_path,model_name,transform,d=10) 11 | tables = snowglobes.simulate(None,path,detector_input=detectors) 12 | result = {} 13 | for det,table in tables.items(): 14 | table = list(table.values())[0] #take the first and the only time bin 15 | result[det] = table['weighted']['smeared'].values.sum() 16 | return result 17 | 18 | def make_rate_table(detectors=['icecube','wc100kt30prct','ar40kt','novaND','novaFD','halo1','halo2']): 19 | result = {( model_name, model_file, xform): 20 | calc_total_rate(model_name,model_file,xform,detectors) 21 | for xform in ['AdiabaticMSW_NMO','AdiabaticMSW_IMO'] 22 | for model_file in ['s11.2c','s27.0c'] 23 | for model_name in ['Bollig_2016'] 24 | } 25 | return result 26 | 27 | if __name__=='__main__': 28 | result = make_rate_table() 29 | #save the crosscheck table in a file 30 | print(f'#generated by "make_rate_table.py" on {str(datetime.now())}') 31 | print('rate_table= \\') 32 | pprint(result) 33 | -------------------------------------------------------------------------------- /python/snewpy/scripts/old_to_snowglobes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | snewpy.scripts.to_snowglobes 4 | ============================ 5 | 6 | Convert an arbitrary model to SNOwGLoBES format. Based on SNEWPY.py script by 7 | E. O'Connor and J. P. Kneller. 8 | 9 | This version will subsample the times in a supernova model, produce energy 10 | tables expected by SNOwGLoBES, and compress the output into a tarfile. 11 | """ 12 | 13 | import numpy as np 14 | from argparse import ArgumentParser 15 | 16 | import os 17 | import io 18 | import tarfile 19 | 20 | import logging 21 | 22 | from snewpy.models import * 23 | from snewpy.flavor_transformation import * 24 | 25 | def main(options=None): 26 | # Parse command-line arguments. 27 | p = ArgumentParser(description='Convert to SNOwGLoBES format.') 28 | p.add_argument('infile', nargs=1, 29 | help='Supernova model input file (Nakazato only).') 30 | p.add_argument('-o', '--output', default=None, 31 | help='Output tarfile name (if customization desired)') 32 | 33 | tbingroup = p.add_mutually_exclusive_group() 34 | tbingroup.add_argument('-n', '--ntbins', type=int, 35 | help='Number of bins used to sample model') 36 | tbingroup.add_argument('-t', '--deltat', type=float, 37 | help='Time binning used to sample model [sec]') 38 | 39 | p.add_argument('-v', '--verbose', action='store_true', default=False, 40 | help='Activate verbose log for debugging') 41 | 42 | if options is None: 43 | args = p.parse_args() 44 | else: 45 | args = p.parse_args(options) 46 | 47 | # Set verbosity of the log. 48 | if args.verbose: 49 | logging.basicConfig(level=logging.DEBUG) 50 | else: 51 | logging.basicConfig(level=logging.INFO) 52 | 53 | # Load up the model. To do: support more than Nakazato format. 54 | infile = args.infile[0] 55 | snmodel = Nakazato2013(infile, NoTransformation()) 56 | 57 | # Subsample the model time. Default to 30 time slices. 58 | tmin = snmodel.get_time()[0] 59 | tmax = snmodel.get_time()[-1] 60 | if args.deltat is not None: 61 | dt = args.deltat 62 | elif args.ntbins is not None: 63 | dt = (tmax - tmin) / (args.ntbins+1) 64 | else: 65 | dt = (tmax - tmin) / 31 66 | 67 | tedges = np.arange(tmin, tmax, dt) 68 | times = 0.5*(tedges[1:] + tedges[:-1]) 69 | 70 | # Generate output. 71 | if args.output is not None: 72 | tfname = args.output 73 | else: 74 | tfname = infile.replace('.fits', '.SNOformat.tar.bz2') 75 | 76 | with tarfile.open(tfname, 'w:bz2') as tf: 77 | d = 10. *1000.*3.086e+18 # luminosity to fluence 78 | keV = 1e3 * 1.60218e-12 # eV to erg 79 | MeV = 1e6 * 1.60218e-12 80 | GeV = 1e9 * 1.60218e-12 81 | 82 | energy = np.linspace(0, 100, 501) * MeV 83 | 84 | # Loop over sampled times. 85 | for i, t in enumerate(times): 86 | osc_spectra = snmodel.get_oscillatedspectra(t, energy) 87 | osc_fluence = {} 88 | table = [] 89 | 90 | table.append('# TBinMid={:g}sec@(tBinWidth={:g}s)(eBinWidth=0.2MeV) Fluence in Number Neutrinos per cm^2'.format(t, dt)) 91 | table.append('# E(GeV) NuE NuMu NuTau aNuE aNuMu aNuTau') 92 | 93 | # Generate energy + number flux table. 94 | for j, E in enumerate(energy): 95 | for flavor in Flavor: 96 | osc_fluence[flavor] = osc_spectra[flavor][j] * dt * 200.*keV / (4.*np.pi*d**2) 97 | 98 | s = '{:17.8E}'.format(E/GeV) 99 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_e]) 100 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_x]/2) 101 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_x]/2) 102 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_e_bar]) 103 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_x_bar]/2) 104 | s = '{}{:17.8E}'.format(s, osc_fluence[Flavor.nu_x_bar]/2) 105 | table.append(s) 106 | logging.debug(s) 107 | 108 | # Encode energy/flux table and output to file in tar archive. 109 | output = '\n'.join(table).encode('ascii') 110 | 111 | infoname = '{:02d}Tbins/{}-tbin{:02d}.NoOsc.dat'.format( 112 | len(times), 113 | os.path.basename(infile).replace('.fits', ''), 114 | i + 1) 115 | info = tarfile.TarInfo(name=infoname) 116 | info.size = len(output) 117 | 118 | logging.info('Time {:g} s; writing {} to {}'.format(t, infoname, tfname)) 119 | tf.addfile(info, io.BytesIO(output)) 120 | 121 | -------------------------------------------------------------------------------- /python/snewpy/scripts/snewpy_to_snewpdag.py: -------------------------------------------------------------------------------- 1 | 2 | from snewpy import snowglobes 3 | import numpy as np 4 | from astropy import units as u 5 | 6 | SNOwGLoBES_path = None # change to SNOwGLoBES directory if using a custom detector configuration 7 | SNEWPY_models_base = "/location/of/models/" #where models (aka input for to_snowglobes) is located 8 | output_path = "/path/to/output/" #where the output files will be located 9 | 10 | #set distance in kpc 11 | distance=10 12 | 13 | #set SNOwGLoBES detector to use 14 | detector = "icecube" 15 | 16 | #set SNEWPY model type and filename 17 | modeltype = 'Tamborra_2014' 18 | modeldir = SNEWPY_models_base+"/"+modeltype+"/" 19 | model = 's20.0c_3D_dir1' 20 | filename = model 21 | 22 | #set desired flavor transformation prescription 23 | transformation = 'AdiabaticMSW_NMO' 24 | 25 | #snewpy.snowglobes creates a tarred file of snowglobes fluences 26 | #this is stored in the model types directory in snewpy/models 27 | outfile = modeltype+"_"+model+"_"+transformation 28 | 29 | #Specify sequence of time intervals, one fluence 30 | #file is made for each elements with dt=tstart[i]-tend[i] 31 | window_tstart = 0.001 32 | window_tend = 0.331 33 | window_bins = 330 34 | tstart = np.linspace(window_tstart,window_tend,window_bins,endpoint=False)*u.s 35 | tend = tstart + (window_tend-window_tstart)/window_bins*u.s 36 | tmid = (tstart+tend)*0.5 37 | 38 | #Generate fluence file for SNOwGLoBES (there are two options, here we use generate_fluence) 39 | print("Preparing fluences...") 40 | tarredfile = snowglobes.generate_fluence(modeldir+filename, modeltype, transformation, distance, outfile,tstart,tend) 41 | print("Done fluences...") 42 | 43 | print("Running snowglobes...") 44 | #now run SNOwGLoBES, this will loop over all the fluence files in `tarredfile` 45 | snowglobes.simulate(SNOwGLoBES_path, tarredfile, detector_input=detector) 46 | print("Done snowglobes...") 47 | 48 | #now collate results of output of SNOwGLoBES 49 | print("Collating...") 50 | tables = snowglobes.collate(SNOwGLoBES_path, tarredfile, skip_plots=True) 51 | 52 | #read results from SNOwGLoBES and put lightcurve in output file for snewpdag 53 | fout = open(output_path+"snewpy_output_"+detector+"_"+modeltype+"_"+filename+"_1msbin.txt", "a") 54 | nevents = np.zeros(len(tmid)) 55 | for i in range(len(tmid)): 56 | key = "Collated_"+outfile+"_"+str(i)+"_"+detector+"_events_smeared_weighted.dat" 57 | for j in range(1,len(tables[key]['header'].split())): 58 | nevents[i] += sum(tables[key]['data'][j]) 59 | print(i, "\t" , nevents[i], file=fout) 60 | -------------------------------------------------------------------------------- /python/snewpy/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SNEWS2/snewpy/dc4c2c76235e3208ff554967180b24fe65173078/python/snewpy/test/__init__.py -------------------------------------------------------------------------------- /python/snewpy/test/_rate_crosscheck_table.py: -------------------------------------------------------------------------------- 1 | #generated by "make_rate_table.py" on 2023-06-02 14:05:49.504799 2 | rate_table= \ 3 | {('Bollig_2016', 's11.2c', 'AdiabaticMSW_IMO'): {'ar40kt': 2509.8697955822163, 4 | 'halo1': 3.7370536263087892, 5 | 'halo2': 47.30447628238974, 6 | 'icecube': 332587.17107218195, 7 | 'novaFD': 1955.8019842893325, 8 | 'novaND': 41.91004252048569, 9 | 'wc100kt30prct': 12723.21134007678}, 10 | ('Bollig_2016', 's11.2c', 'AdiabaticMSW_NMO'): {'ar40kt': 2696.396378383758, 11 | 'halo1': 4.261668358305819, 12 | 'halo2': 53.94516909247873, 13 | 'icecube': 320766.2999953658, 14 | 'novaFD': 1947.9479073661355, 15 | 'novaND': 41.741740872131466, 16 | 'wc100kt30prct': 12645.788228369147}, 17 | ('Bollig_2016', 's27.0c', 'AdiabaticMSW_IMO'): {'ar40kt': 5192.001233928208, 18 | 'halo1': 8.180298088573792, 19 | 'halo2': 103.54807707055436, 20 | 'icecube': 659449.9649827218, 21 | 'novaFD': 3638.7825760092305, 22 | 'novaND': 77.97391234305493, 23 | 'wc100kt30prct': 23596.070817047243}, 24 | ('Bollig_2016', 's27.0c', 'AdiabaticMSW_NMO'): {'ar40kt': 5520.903869068218, 25 | 'halo1': 9.105675711861647, 26 | 'halo2': 115.2617178716664, 27 | 'icecube': 661517.4858901916, 28 | 'novaFD': 3734.4927394154874, 29 | 'novaND': 80.02484441604615, 30 | 'wc100kt30prct': 24241.28672972421}} 31 | -------------------------------------------------------------------------------- /python/snewpy/test/simplerate_integrationtest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Integration test based on SNEWS2.0_rate_table_singleexample.py 3 | """ 4 | import unittest 5 | from snewpy import snowglobes 6 | from snewpy import model_path 7 | 8 | from snewpy.models import ccsn 9 | import astropy.units as u 10 | 11 | def preload_model(name:str, **parameters): 12 | #initialize the model with given name and parameters 13 | model = ccsn.__dict__[name](**parameters) 14 | return model 15 | 16 | 17 | class TestSimpleRate(unittest.TestCase): 18 | 19 | def test_simplerate(self): 20 | """Integration test based on SNEWS2.0_rate_table_singleexample.py 21 | """ 22 | # Hardcoded paths on GitHub Action runner machines 23 | SNOwGLoBES_path = None 24 | 25 | distance = 10 # Supernova distance in kpc 26 | detector = "wc100kt30prct" #SNOwGLoBES detector for water Cerenkov 27 | modeltype = 'Bollig_2016' # Model type from snewpy.models 28 | model = 's11.2c' # Name of model 29 | transformation = 'AdiabaticMSW_NMO' # Desired flavor transformation 30 | 31 | # Construct file system path of model file and name of output file 32 | model_file_path = f'{model_path}/{modeltype}/{model}' 33 | outfile = f'{modeltype}_{model}_{transformation}' 34 | 35 | #make sure the model files are loaded 36 | preload_model(modeltype, progenitor_mass=11.2*u.Msun) 37 | 38 | # Now, do the main work: 39 | print("Generating fluence files ...") 40 | tarredfile = snowglobes.generate_fluence(model_file_path, modeltype, transformation, distance, outfile) 41 | 42 | print("Simulating detector effects with SNOwGLoBES ...") 43 | snowglobes.simulate(SNOwGLoBES_path, tarredfile, detector_input=detector, detector_effects=True) 44 | 45 | print("Collating results ...") 46 | tables = snowglobes.collate(SNOwGLoBES_path, tarredfile, skip_plots=True, smearing=True) 47 | 48 | # Use results to print the number of events in different interaction channels 49 | key = f"Collated_{outfile}_{detector}_events_unsmeared_weighted.dat" 50 | total_events = 0 51 | for i, channel in enumerate(tables[key]['header'].split()): 52 | if i == 0: 53 | continue 54 | n_events = sum(tables[key]['data'][i]) 55 | total_events += n_events 56 | print(f"{channel:10}: {n_events:.3f} events") 57 | 58 | #Super-K has 32kT inner volume 59 | print("Total events in Super-K-like detector:" , 0.32*total_events) 60 | 61 | # Use results to print the number of events in different interaction channels 62 | # with efficiency and smearing 63 | key = f"Collated_{outfile}_{detector}_events_smeared_weighted.dat" 64 | total_events_smeared = 0 65 | for i, channel in enumerate(tables[key]['header'].split()): 66 | if i == 0: 67 | continue 68 | n_events = sum(tables[key]['data'][i]) 69 | total_events_smeared += n_events 70 | print(f"{channel:10}: {n_events:.3f} events (smeared)") 71 | 72 | #Super-K has 32kT inner volume 73 | print("Total events in Super-K-like detector (with smearing):" , 0.32*total_events_smeared) 74 | 75 | # We do not use the SNOwGLoBES scaling factors but use other constants so we do not 76 | # expect the results to agree to 7 digits. Here sub-permille agreement is good enough. 77 | sk_expected = 4491.783259 78 | sk_expected_smeared = 4065.662374 79 | sk_computed = 0.32 * total_events 80 | sk_computed_smeared = 0.32 * total_events_smeared 81 | discrepancy = abs(sk_computed - sk_expected)/sk_expected 82 | discrepancy_smeared = abs(sk_computed_smeared - sk_expected_smeared)/sk_expected_smeared 83 | 84 | assert discrepancy < 0.001, f"Number of unsmeared events computed for SK is {sk_computed}, should be {sk_expected}" 85 | assert discrepancy_smeared < 0.001, f"Number of smeared events computed for SK is {sk_computed_smeared}, should be {sk_expected_smeared}" 86 | -------------------------------------------------------------------------------- /python/snewpy/test/test_00_init.py: -------------------------------------------------------------------------------- 1 | import snewpy 2 | import unittest 3 | 4 | 5 | class TestInit(unittest.TestCase): 6 | def test_version_exists(self): 7 | self.assertTrue(hasattr(snewpy, '__version__')) -------------------------------------------------------------------------------- /python/snewpy/test/test_03_neutrino.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Unit tests for the neutrino submodule. 3 | """ 4 | 5 | import unittest 6 | 7 | from snewpy.neutrino import Flavor, MassHierarchy, MixingParameters 8 | 9 | from astropy import units as u 10 | 11 | class TestNeutrino(unittest.TestCase): 12 | 13 | def test_flavor(self): 14 | """ 15 | Neutrino flavor types 16 | """ 17 | nue = Flavor.NU_E 18 | self.assertTrue(nue.is_electron) 19 | self.assertTrue(nue.is_neutrino) 20 | self.assertFalse(nue.is_antineutrino) 21 | 22 | numu = Flavor.NU_MU 23 | self.assertFalse(numu.is_electron) 24 | self.assertTrue(numu.is_neutrino) 25 | self.assertFalse(numu.is_antineutrino) 26 | 27 | nueb = Flavor.NU_E_BAR 28 | self.assertTrue(nueb.is_electron) 29 | self.assertFalse(nueb.is_neutrino) 30 | self.assertTrue(nueb.is_antineutrino) 31 | 32 | numub = Flavor.NU_MU_BAR 33 | self.assertFalse(numub.is_electron) 34 | self.assertFalse(numub.is_neutrino) 35 | self.assertTrue(numub.is_antineutrino) 36 | 37 | 38 | def test_mixing_nmo(self): 39 | """ 40 | Mixing parameter values; NMO 41 | """ 42 | # By default, return mixing parameters for NMO. 43 | mixpars = MixingParameters(version="NuFIT5.0") 44 | self.assertEqual(mixpars.theta12, 33.44 * u.deg) 45 | self.assertEqual(mixpars.theta13, 8.57 * u.deg) 46 | self.assertEqual(mixpars.theta23, 49.20 * u.deg) 47 | self.assertEqual(mixpars.deltaCP, 197 * u.deg) 48 | self.assertEqual(mixpars.dm21_2, 7.42e-5 * u.eV**2) 49 | self.assertEqual(mixpars.dm31_2, 2.517e-3 * u.eV**2) 50 | 51 | 52 | def test_mixing_imo(self): 53 | """ 54 | Mixing parameter values; IMO 55 | """ 56 | mixpars = MixingParameters(MassHierarchy.INVERTED, version="NuFIT5.0") 57 | self.assertEqual(mixpars.theta12, 33.45 * u.deg) 58 | self.assertEqual(mixpars.theta13, 8.60 * u.deg) 59 | self.assertEqual(mixpars.theta23, 49.30 * u.deg) 60 | self.assertEqual(mixpars.deltaCP, 282 * u.deg) 61 | self.assertEqual(mixpars.dm21_2, 7.42e-5 * u.eV**2) 62 | self.assertEqual(mixpars.dm32_2, -2.498e-3 * u.eV**2) 63 | -------------------------------------------------------------------------------- /python/snewpy/test/test_05_snowglobes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pathlib import Path 3 | from snewpy.test._rate_crosscheck_table import rate_table 4 | from snewpy import snowglobes, model_path 5 | 6 | from snewpy.models import ccsn 7 | import astropy.units as u 8 | 9 | pytestmark=pytest.mark.snowglobes 10 | 11 | #get available model parameters from table 12 | param_values = list(rate_table.keys()) 13 | #get available detectors from table 14 | detectors = list(list(rate_table.values())[0].keys()) 15 | 16 | #make sure the model files are loaded 17 | model = ccsn.Bollig_2016 18 | for params in model.get_param_combinations(): 19 | model(**params) 20 | 21 | def fluence_calculation(model_name,model_file,transform): 22 | #generating fluence file 23 | model_file_path = f'{model_path}/{model_name}/{model_file}' 24 | print(model_file_path) 25 | return snowglobes.generate_fluence(model_file_path, model_name, transform,d=10) 26 | 27 | def rates_calculation(fluence_file): 28 | tables = snowglobes.simulate(None,fluence_file,detector_input=detectors) 29 | result = {} 30 | for det,table in tables.items(): 31 | table = list(table.values())[0] #take the first and the only time bin 32 | result[det] = table['weighted']['smeared'].values.sum() 33 | return result 34 | 35 | @pytest.mark.xfail(reason="Rate calculation uses `get_transformed_flux`, which is currently hard coded to a TwoFlavor scheme.", raises=AttributeError) 36 | @pytest.mark.parametrize('model_parameters',param_values) 37 | def test_total_rate_equals_table_value(model_parameters): 38 | fluence_file = fluence_calculation(*model_parameters) 39 | calculated_rates = rates_calculation(fluence_file) 40 | for detector in detectors: 41 | expected = pytest.approx(rate_table[model_parameters][detector], rel=0.01) 42 | assert calculated_rates[detector] == expected, f"Crosscheck failed for {detector}" 43 | -------------------------------------------------------------------------------- /python/snewpy/test/test_flavors.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import snewpy.flavor 4 | from snewpy.flavor import TwoFlavor,ThreeFlavor,FourFlavor, FlavorMatrix, FlavorScheme 5 | 6 | flavor_schemes = TwoFlavor,ThreeFlavor,FourFlavor 7 | 8 | class TestFlavorScheme: 9 | @staticmethod 10 | def test_flavor_scheme_lengths(): 11 | assert len(TwoFlavor)==4 12 | assert len(ThreeFlavor)==6 13 | assert len(FourFlavor)==8 14 | 15 | @staticmethod 16 | 17 | def test_getitem_string(): 18 | assert TwoFlavor['NU_E'] == TwoFlavor.NU_E 19 | assert TwoFlavor['NU_X'] == TwoFlavor.NU_X 20 | #short notations 21 | assert ThreeFlavor['E'] == ThreeFlavor['e'] == ThreeFlavor.NU_E 22 | assert ThreeFlavor['MU'] == ThreeFlavor['mu'] == ThreeFlavor.NU_MU 23 | assert ThreeFlavor['MU_BAR'] == ThreeFlavor['mu_bar'] == ThreeFlavor.NU_MU_BAR 24 | with pytest.raises(KeyError): 25 | TwoFlavor['NU_MU'] 26 | with pytest.raises(KeyError): 27 | ThreeFlavor['NU_X'] 28 | @staticmethod 29 | def test_getitem_collective_names(): 30 | assert ThreeFlavor['NU']==(ThreeFlavor.NU_E, ThreeFlavor.NU_MU, ThreeFlavor.NU_TAU) 31 | assert ThreeFlavor['NU']==ThreeFlavor['e','mu','tau'] 32 | assert ThreeFlavor['NU_BAR']==(ThreeFlavor.NU_E_BAR, ThreeFlavor.NU_MU_BAR, ThreeFlavor.NU_TAU_BAR) 33 | assert ThreeFlavor['NU_BAR']==ThreeFlavor['e_bar','mu_bar','tau_bar'] 34 | @staticmethod 35 | def test_getitem_enum(): 36 | assert TwoFlavor[TwoFlavor.NU_E] == TwoFlavor.NU_E 37 | assert TwoFlavor[TwoFlavor.NU_X] == TwoFlavor.NU_X 38 | with pytest.raises(TypeError): 39 | TwoFlavor[ThreeFlavor.NU_E] 40 | 41 | @staticmethod 42 | def test_values_from_different_enums(): 43 | assert TwoFlavor.NU_E==ThreeFlavor.NU_E 44 | assert TwoFlavor.NU_E_BAR==ThreeFlavor.NU_E_BAR 45 | 46 | @staticmethod 47 | def test_makeFlavorScheme(): 48 | TestFlavor = FlavorScheme.from_lepton_names('TestFlavor',leptons=['A','B','C']) 49 | assert len(TestFlavor)==6 50 | assert [f.name for f in TestFlavor]==['NU_A','NU_A_BAR','NU_B','NU_B_BAR','NU_C','NU_C_BAR'] 51 | 52 | @staticmethod 53 | def test_flavor_properties(): 54 | f = ThreeFlavor.NU_E 55 | assert f.is_neutrino 56 | assert f.is_electron 57 | assert not f.is_muon 58 | assert not f.is_tauon 59 | assert f.lepton=='E' 60 | 61 | f = ThreeFlavor.NU_MU 62 | assert f.is_neutrino 63 | assert not f.is_electron 64 | assert f.is_muon 65 | assert not f.is_tauon 66 | assert f.lepton=='MU' 67 | 68 | f = ThreeFlavor.NU_E_BAR 69 | assert not f.is_neutrino 70 | assert f.is_electron 71 | assert not f.is_muon 72 | assert not f.is_tauon 73 | assert f.lepton=='E' 74 | 75 | f = ThreeFlavor.NU_MU_BAR 76 | assert not f.is_neutrino 77 | assert not f.is_electron 78 | assert f.is_muon 79 | assert not f.is_tauon 80 | assert f.lepton=='MU' 81 | 82 | f = ThreeFlavor.NU_TAU 83 | assert f.is_neutrino 84 | assert not f.is_electron 85 | assert not f.is_muon 86 | assert f.is_tauon 87 | assert f.lepton=='TAU' 88 | 89 | f = ThreeFlavor.NU_TAU_BAR 90 | assert not f.is_neutrino 91 | assert not f.is_electron 92 | assert not f.is_muon 93 | assert f.is_tauon 94 | assert f.lepton=='TAU' 95 | 96 | class TestFlavorMatrix: 97 | @staticmethod 98 | def test_init_square_matrix(): 99 | m = FlavorMatrix(array=np.ones(shape=(4,4)), flavor=TwoFlavor) 100 | assert m.shape == (4,4) 101 | assert m.flavor_in == TwoFlavor 102 | assert m.flavor_out == TwoFlavor 103 | 104 | @staticmethod 105 | def test_getitem(): 106 | m = FlavorMatrix.eye(TwoFlavor,TwoFlavor) 107 | assert m[TwoFlavor.NU_E, TwoFlavor.NU_E]==1 108 | assert m['NU_E','NU_E']==1 109 | assert m['NU_E','NU_X']==0 110 | assert np.allclose(m['NU_E'], [1,0,0,0]) 111 | assert np.allclose(m['NU_E'], m['NU_E',:]) 112 | 113 | @staticmethod 114 | def test_getitem_submatrix(): 115 | m = FlavorMatrix.eye(TwoFlavor) 116 | assert np.allclose(m[['e','x'],['e','x']], [[1,0],[0,1]]) 117 | assert np.allclose(m[:,:].array, m.array) 118 | 119 | @staticmethod 120 | def test_getitem_short(): 121 | m = FlavorMatrix.eye(ThreeFlavor,ThreeFlavor) 122 | assert m['NU_E','NU_E']==m['e','e'] 123 | assert m['NU_MU','NU_E']==m['mu','e'] 124 | assert m['NU_E_BAR','NU_E']==m['e_bar','e'] 125 | assert m['NU_TAU_BAR','NU_TAU']==m['tau_bar','tau'] 126 | 127 | @staticmethod 128 | def test_setitem(): 129 | m = FlavorMatrix.eye(TwoFlavor,TwoFlavor) 130 | m['NU_E']=[2,3,4,5] 131 | assert m['NU_E','NU_E']==2 132 | assert m['NU_E','NU_X']==4 133 | #check that nothing changed in other parts 134 | assert m['NU_X','NU_E']==0 135 | assert m['NU_X','NU_X']==1 136 | m['NU_E','NU_E_BAR']=123 137 | assert m['NU_E','NU_E_BAR']==123 138 | 139 | @staticmethod 140 | def test_init_square_matrix_with_wrong_shape_raises_ValueError(): 141 | with pytest.raises(ValueError): 142 | m = FlavorMatrix(array=np.ones(shape=(4,5)), flavor=TwoFlavor) 143 | with pytest.raises(ValueError): 144 | m = FlavorMatrix(array=np.ones(shape=(5,5)), flavor=TwoFlavor) 145 | with pytest.raises(ValueError): 146 | m = FlavorMatrix(array=np.ones(shape=(5,4)), flavor=TwoFlavor) 147 | 148 | @staticmethod 149 | def test_conversion_matrices_for_same_flavor_are_unity(): 150 | for flavor in [TwoFlavor,ThreeFlavor,FourFlavor]: 151 | matrix = flavor>>flavor 152 | assert isinstance(matrix, FlavorMatrix) 153 | assert np.allclose(matrix.array, np.eye(len(flavor))) 154 | 155 | @staticmethod 156 | @pytest.mark.parametrize('flavor_in',flavor_schemes) 157 | @pytest.mark.parametrize('flavor_out',flavor_schemes) 158 | def test_conversion_matrices(flavor_in, flavor_out): 159 | M = flavor_in>>flavor_out 160 | assert M==flavor_out<1) 133 | note(f'Array is {str(f.array)}') 134 | fI = f.integrate(axis) 135 | for a in Axes: 136 | if a!=axis: 137 | assert fI.shape[a] == f.shape[a] 138 | else: 139 | assert fI.shape[a] == 1 140 | #check the resulting array values 141 | assert fI.unit == f.unit*f.axes[axis].unit 142 | 143 | @given(f=random_flux_containers()) 144 | def test_save_and_load(f): 145 | with TemporaryDirectory() as tmpdir: 146 | fname = os.path.join(tmpdir, 'flux.npz') 147 | f.save(fname) 148 | f1 = Container.load(fname) 149 | assert f1 == f -------------------------------------------------------------------------------- /python/snewpy/test/test_presn_rates.py: -------------------------------------------------------------------------------- 1 | import astropy.units as u 2 | import numpy as np 3 | from snewpy.models import presn 4 | from snewpy.neutrino import MassHierarchy, MixingParameters 5 | from snewpy.flavor_transformation import AdiabaticMSW 6 | from snewpy.rate_calculator import RateCalculator 7 | import pytest 8 | 9 | pytestmark=pytest.mark.snowglobes 10 | 11 | rc = RateCalculator() 12 | 13 | distance = 200*u.pc 14 | #SNOwGLoBES detector for water Cerenkov 15 | T = np.geomspace(-1*u.hour, -1*u.min,1000) 16 | E = np.linspace(0,20,100)*u.MeV 17 | 18 | @pytest.mark.xfail(reason="`model.get_flux` uses `get_transformed_flux`, which is currently hard coded to a TwoFlavor scheme.", raises=AttributeError) 19 | @pytest.mark.parametrize('model_class',[presn.Odrzywolek_2010, presn.Kato_2017, presn.Patton_2017, presn.Yoshida_2016]) 20 | @pytest.mark.parametrize('transformation',[AdiabaticMSW(MixingParameters(mh)) for mh in MassHierarchy]) 21 | @pytest.mark.parametrize('detector', ["wc100kt30prct"]) 22 | def test_presn_rate(model_class, transformation, detector): 23 | model = model_class(progenitor_mass=15*u.Msun) 24 | flux = model.get_flux(T, E, distance=distance, flavor_xform=transformation) 25 | rate = rc.run(flux, detector='scint20kt', detector_effects=False)['ibd'] 26 | ibd_events = rate.integrate_or_sum('time').integrate_or_sum('energy').array.squeeze() 27 | assert 10np.ndarray: 4 | """Expand the dimensions of the array, adding dimensions of len=1 to the right, 5 | so total dimensions equal to `ndim`""" 6 | new_shape = (list(a.shape)+[1]*ndim)[:ndim] 7 | return a.reshape(new_shape) --------------------------------------------------------------------------------