├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── pypi_publish.yml ├── .gitignore ├── .readthedocs.yaml ├── CITATION.cff ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── .nojekyll ├── Makefile ├── bibliography.rst ├── conf.py ├── index.rst ├── introduction.rst ├── logo_showermodel.png ├── make.bat ├── package │ ├── atmosphere.rst │ ├── cherenkov.rst │ ├── event.rst │ ├── fluorescence.rst │ ├── image.rst │ ├── observatory.rst │ ├── package.rst │ ├── profile.rst │ ├── projection.rst │ ├── shower.rst │ ├── signal.rst │ ├── telescope.rst │ └── track.rst └── tutorials │ ├── UC0_Playing_around_with_ShowerModel.nblink │ ├── UC1_Track_visualization.nblink │ ├── UC2_Shower_profile.nblink │ ├── UC3_Signal_production_and_ground_distribution.nblink │ ├── UC4_Observatory_events.nblink │ ├── UC5_Camera_images.nblink │ └── index.rst ├── environment.yml ├── extra ├── Edep_profile_1000GeV_1000sh_0deg.dat ├── atm_models.toml ├── averaged_profile_5sh_100TeV.dat ├── mean_annual_global_reference_atmosphere.xlsx ├── showermodel_config.toml └── tel_data.toml ├── logo_showermodel.png ├── notebooks ├── UC0_Playing_around_with_ShowerModel.ipynb ├── UC1_Track_visualization.ipynb ├── UC2_Shower_profile.ipynb ├── UC3_Signal_production_and_ground_distribution.ipynb ├── UC4_Observatory_events.ipynb └── UC5_Camera_images.ipynb ├── postBuild ├── pyproject.toml ├── setup.cfg ├── setup.py └── src └── showermodel ├── __init__.py ├── _dev_version └── __init__.py ├── _tools.py ├── atmosphere.py ├── cherenkov.py ├── constants ├── __init__.py ├── atm_models.toml ├── fluorescence_model.toml ├── showermodel_config.toml └── tel_data.toml ├── event.py ├── fluorescence.py ├── image.py ├── observatory.py ├── profile.py ├── projection.py ├── shower.py ├── signal.py ├── telescope.py ├── tests ├── test_shower.py ├── test_showermodel.py └── test_telescope.py ├── track.py └── version.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '**' 9 | pull_request: 10 | 11 | env: 12 | MPLBACKEND: Agg 13 | PYTEST_ADDOPTS: --color=yes 14 | 15 | jobs: 16 | tests: 17 | strategy: 18 | matrix: 19 | python-version: 20 | - "3.7" 21 | - "3.8" 22 | - "3.9" 23 | - "3.10" 24 | os: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | 29 | runs-on: ${{ matrix.os }} 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v4 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | 39 | - name: Install dependencies 40 | run: | 41 | python -m pip install --upgrade pip 42 | python -m pip install .[tests] 43 | 44 | - name: Test with pytest 45 | run: | 46 | pytest -vv src/showermodel --cov=showermodel --cov-report=xml 47 | 48 | - name: Upload coverage to Codecov 49 | uses: codecov/codecov-action@v3 50 | if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' 51 | with: 52 | file: ./coverage.xml 53 | fail_ci_if_error: true 54 | -------------------------------------------------------------------------------- /.github/workflows/pypi_publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package to PyPi 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | # make sure we have version info 15 | - run: git fetch --tags 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: 3.8 21 | 22 | - name: Install dependencies 23 | run: | 24 | python --version 25 | pip install -U pip setuptools wheel setuptools_scm[toml] 26 | python setup.py sdist bdist_wheel 27 | 28 | - name: Publish package 29 | uses: pypa/gh-action-pypi-publish@master 30 | with: 31 | user: __token__ 32 | password: ${{ secrets.PYPI_PASSWORD }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | src/showermodel/_version.py 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # Docs build 133 | docs/_build 134 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Required 2 | version: 2 3 | 4 | # Build documentation in the docs/ directory with Sphinx 5 | sphinx: 6 | builder: html 7 | configuration: docs/conf.py 8 | 9 | # Python requirements required to build your docs 10 | python: 11 | version: "3.8" 12 | install: 13 | - method: pip 14 | path: . 15 | extra_requirements: 16 | - docs 17 | 18 | # Don't build any extra formats 19 | formats: [] -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.1.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: Rosado 5 | given-names: Jaime 6 | orcid: https://orcid.org/0000-0001-8208-9480 7 | - family-names: Morcuende 8 | given-names: Daniel 9 | orcid: https://orcid.org/0000-0001-9400-0922 10 | title: ShowerModel 11 | doi: 10.5281/zenodo.6773258 12 | version: v0.1.9 13 | date-released: 2022-06-28 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune src/showermodel/_dev_version 2 | prune .github 3 | recursive-include extra *.dat *.toml *.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShowerModel 2 | 3 | [![Build Status](https://github.com/JaimeRosado/ShowerModel/workflows/CI/badge.svg?branch=master)](https://github.com/JaimeRosado/ShowerModel/actions?query=workflow%3ACI+branch%3Amaster) 4 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/JaimeRosado/ShowerModel/master?filepath=notebooks) 5 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6773258.svg)](https://doi.org/10.5281/zenodo.6773258) 6 | [![pypi](https://img.shields.io/pypi/v/ShowerModel.svg)](https://pypi.org/project/ShowerModel) 7 | [![Documentation Status](https://readthedocs.org/projects/showermodel/badge/?version=latest)](https://showermodel.readthedocs.io/en/latest/?badge=latest) 8 | 9 | ![ShowerModel logo](logo_showermodel.png) 10 | 11 | A Python package for modelling cosmic-ray showers, their light production and their detection. 12 | 13 | -------- 14 | * Code : https://github.com/JaimeRosado/ShowerModel 15 | * Docs: https://showermodel.readthedocs.io/ 16 | * License: GPL-3.0 17 | -------- 18 | 19 | ## Install 20 | 21 | * Install miniconda or anaconda first. 22 | 23 | ### As user 24 | It can be installed by doing: 25 | ``` 26 | pip install ShowerModel 27 | ``` 28 | If `pip install ShowerModel` fails as it is, you probably need to use `--user` option. 29 | This may happen in Windows installations. 30 | ``` 31 | pip install --user ShowerModel 32 | ``` 33 | 34 | Although it is optional, it is recommended to create a dedicated conda virtual environment. 35 | 36 | ### As developer 37 | 38 | * Create and activate the conda environment: 39 | ``` 40 | git clone https://github.com/JaimeRosado/ShowerModel.git 41 | cd ShowerModel 42 | conda env create -f environment.yml 43 | conda activate showermodel 44 | ``` 45 | 46 | * To update the environment when dependencies get updated use: 47 | ``` 48 | conda env update -n showermodel -f environment.yml 49 | ``` 50 | 51 | To install `ShowerModel`, run the following command from the ShowerModel root directory 52 | where the `setup.py` file is located: 53 | ``` 54 | pip install -e . 55 | ``` 56 | 57 | Test your installation by running any of the notebooks in this repository. 58 | Otherwise, open an Issue with your error. 59 | 60 | 61 | ## Further information 62 | See our presentation video and contribution to ADASS XXX conference: 63 | * https://drive.google.com/file/d/14AGV91mQXDwecGy2qxgNEmWeIcxKy_I0/view?usp=sharing 64 | * [https://adass2020.es/static/ftp/P4-176/P4-176.pdf](https://www.aspbooks.org/a/volumes/article_details/?paper_id=40528) 65 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaimeRosado/ShowerModel/4c3d35f9594a2460da00ceb3435db1d7bf614b73/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/bibliography.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | 5 | 6 | .. [Heck1998] D. Heck, J. Knapp, J. N. Capdevielle, G. Schatz, T. Thouw, 7 | 1998, ForschungszentrumKarlsruhe, FZKA 6019, 1, 8 | http://bibliothek.fzk.de/zb/berichte/FZKA6019.pdf 9 | .. [Bernlohr2008] K. Bernlöhr, 2008, Astropart. Phys., 30, 149-158 10 | https://doi.org/10.1016/j.astropartphys.2008.07.009 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | import datetime 17 | 18 | # Get configuration information from setup.cfg 19 | from configparser import ConfigParser 20 | 21 | conf = ConfigParser() 22 | conf.read([os.path.join(os.path.dirname(__file__), "..", "setup.cfg")]) 23 | setup_cfg = dict(conf.items("metadata")) 24 | 25 | 26 | # -- Project information ----------------------------------------------------- 27 | project = setup_cfg["name"] 28 | author = setup_cfg["author"] 29 | copyright = "{}. Last updated {}".format( 30 | setup_cfg["author"], datetime.datetime.now().strftime("%d %b %Y %H:%M") 31 | ) 32 | 33 | import showermodel 34 | version = showermodel.__version__ 35 | # The full version, including alpha/beta/rc tags. 36 | release = version 37 | 38 | # -- General configuration --------------------------------------------------- 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | 'sphinx.ext.autodoc', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.githubpages', 47 | 'sphinx.ext.napoleon', 48 | 'nbsphinx', 49 | 'nbsphinx_link' 50 | ] 51 | # Show both the class’ and the __init__ method’s docstring 52 | autoclass_content = 'both' 53 | autodoc_member_order = 'bysource' 54 | 55 | # Add any paths that contain templates here, relative to this directory. 56 | templates_path = ['_templates'] 57 | 58 | # List of patterns, relative to source directory, that match files and 59 | # directories to ignore when looking for source files. 60 | # This pattern also affects html_static_path and html_extra_path. 61 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 62 | 63 | # -- Options for HTML output ------------------------------------------------- 64 | 65 | # The theme to use for HTML and HTML Help pages. See the documentation for 66 | # a list of builtin themes. 67 | # 68 | html_theme = 'sphinx_rtd_theme' 69 | 70 | # Add any paths that contain custom static files (such as style sheets) here, 71 | # relative to this directory. They are copied after the builtin static files, 72 | # so a file named "default.css" will overwrite the builtin "default.css". 73 | # html_static_path = ['_static'] 74 | 75 | # Refer figures 76 | numfig = True 77 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ShowerModel 2 | ======================================= 3 | 4 | .. figure:: logo_showermodel.png 5 | :align: center 6 | 7 | A Python package for modelling cosmic-ray showers, their light production and its detection. 8 | 9 | * Code: https://github.com/JaimeRosado/ShowerModel/ 10 | * Docs: https://showermodel.readthedocs.io/ 11 | * License: BSD-3 12 | * Python 3.7 or later 13 | 14 | .. _showermodel_guide: 15 | .. toctree:: 16 | :maxdepth: 1 17 | :caption: Guide 18 | 19 | introduction 20 | tutorials/index 21 | package/package 22 | bibliography 23 | 24 | 25 | Indices and tables 26 | ================== 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introduction 4 | ************ 5 | 6 | Very-high-energy cosmic rays and gamma rays induce extensive air showers (EAS) when entering the atmosphere. 7 | Cherenkov and fluorescence light emitted by secondary charged particles is used as a proxy for studying the 8 | primary particles that initiate the particle cascades. 9 | 10 | Design, calibration and data analysis of cosmic-ray and gamma-ray observatories strongly rely on Monte Carlo 11 | simulations of both the air shower and detector response. 12 | CORSIKA program [Heck1998]_ is widely used for carrying out the first step of the simulation 13 | whereas the second step depends on the detection technique. 14 | For example, in the case of imaging atmospheric Cherenkov telescopes (IACT), the program `sim_telarray` 15 | [Bernlohr2008]_ is commonly used. These detailed simulations are currently very demanding computationally. 16 | 17 | `ShowerModel` is a python package to easily and quickly compute both Cherenkov and fluorescence light production 18 | in air showers and its detection. This tool can be used to speed up some studies that do not require a full 19 | simulation as well as to cross-check complex Monte Carlo simulations and data analyses. 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/logo_showermodel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaimeRosado/ShowerModel/4c3d35f9594a2460da00ceb3435db1d7bf614b73/docs/logo_showermodel.png -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/package/atmosphere.rst: -------------------------------------------------------------------------------- 1 | .. _atmosphere: 2 | 3 | Atmosphere 4 | =================== 5 | 6 | .. autoclass:: showermodel.atmosphere.Atmosphere 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.atmosphere.Atmosphere.h_to_Xv 11 | :noindex: 12 | .. automethod:: showermodel.atmosphere.Atmosphere.h_to_rho 13 | :noindex: 14 | .. automethod:: showermodel.atmosphere.Atmosphere.Xv_to_h 15 | :noindex: 16 | .. automethod:: showermodel.atmosphere.Atmosphere.Xv_to_rho 17 | :noindex: 18 | .. automethod:: showermodel.atmosphere.Atmosphere.Xv_to_P 19 | :noindex: 20 | .. automethod:: showermodel.atmosphere.Atmosphere.P_to_Xv 21 | :noindex: 22 | .. automethod:: showermodel.atmosphere.Atmosphere.Xv_rho_to_P_T 23 | :noindex: 24 | -------------------------------------------------------------------------------- /docs/package/cherenkov.rst: -------------------------------------------------------------------------------- 1 | .. _cherenkov: 2 | 3 | Cherenkov 4 | =================== 5 | 6 | .. autoclass:: showermodel.cherenkov.Cherenkov 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.cherenkov.Cherenkov.show 11 | :noindex: -------------------------------------------------------------------------------- /docs/package/event.rst: -------------------------------------------------------------------------------- 1 | .. _event: 2 | 3 | Event 4 | =================== 5 | 6 | .. autoclass:: showermodel.event.Event 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.event.Event.show_projection 11 | :noindex: 12 | .. automethod:: showermodel.event.Event.show_profile 13 | :noindex: 14 | .. automethod:: showermodel.event.Event.show_light_production 15 | :noindex: 16 | .. automethod:: showermodel.event.Event.show_signal 17 | :noindex: 18 | .. automethod:: showermodel.event.Event.show_geometry2D 19 | :noindex: 20 | .. automethod:: showermodel.event.Event.show_geometry3D 21 | :noindex: 22 | .. automethod:: showermodel.event.Event.show_distribution 23 | :noindex: 24 | .. automethod:: showermodel.event.Event.make_images 25 | :noindex: 26 | .. automethod:: showermodel.event.Event.show_images 27 | :noindex: 28 | -------------------------------------------------------------------------------- /docs/package/fluorescence.rst: -------------------------------------------------------------------------------- 1 | .. _fluorescence: 2 | 3 | Fluorescence 4 | =================== 5 | 6 | .. autoclass:: showermodel.fluorescence.Fluorescence 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.fluorescence.Fluorescence.show 11 | :noindex: 12 | -------------------------------------------------------------------------------- /docs/package/image.rst: -------------------------------------------------------------------------------- 1 | .. _image: 2 | 3 | Image 4 | =================== 5 | 6 | .. autoclass:: showermodel.image.Image 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.image.Image.show 11 | :noindex: 12 | .. automethod:: showermodel.image.Image.animate 13 | :noindex: 14 | -------------------------------------------------------------------------------- /docs/package/observatory.rst: -------------------------------------------------------------------------------- 1 | .. _observatory: 2 | 3 | Observatory 4 | =================== 5 | 6 | .. autoclass:: showermodel.observatory.Observatory 7 | :show-inheritance: 8 | .. autoclass:: showermodel.observatory.Array25 9 | :show-inheritance: 10 | .. autoclass:: showermodel.observatory.Grid 11 | :show-inheritance: 12 | 13 | *Methods:* 14 | .. automethod:: showermodel.observatory.Observatory.show 15 | :noindex: 16 | .. automethod:: showermodel.observatory.Observatory.append 17 | :noindex: 18 | .. automethod:: showermodel.observatory.Observatory.set_pointing 19 | :noindex: 20 | -------------------------------------------------------------------------------- /docs/package/package.rst: -------------------------------------------------------------------------------- 1 | .. _package: 2 | 3 | ShowerModel package 4 | ==================== 5 | 6 | .. toctree:: 7 | 8 | atmosphere 9 | cherenkov 10 | event 11 | fluorescence 12 | image 13 | observatory 14 | profile 15 | projection 16 | shower 17 | signal 18 | telescope 19 | track -------------------------------------------------------------------------------- /docs/package/profile.rst: -------------------------------------------------------------------------------- 1 | .. _profile: 2 | 3 | Profile 4 | =================== 5 | 6 | .. autoclass:: showermodel.profile.Profile 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.profile.Profile.Fluorescence 11 | :noindex: 12 | .. automethod:: showermodel.profile.Profile.Cherenkov 13 | :noindex: 14 | .. automethod:: showermodel.profile.Profile.show 15 | :noindex: 16 | -------------------------------------------------------------------------------- /docs/package/projection.rst: -------------------------------------------------------------------------------- 1 | .. _projection: 2 | 3 | Projection 4 | =================== 5 | 6 | .. autoclass:: showermodel.projection.Projection 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.projection.Projection.show 11 | :noindex: 12 | .. automethod:: showermodel.projection.Projection.hor_to_FoV 13 | :noindex: 14 | .. automethod:: showermodel.projection.Projection.FoV_to_hor 15 | :noindex: 16 | .. automethod:: showermodel.projection.Projection.theta_phi_to_alt_az 17 | :noindex: 18 | .. automethod:: showermodel.projection.Projection.altaz_to_thetaphi 19 | :noindex: 20 | .. automethod:: showermodel.projection.Projection.spherical 21 | :noindex: 22 | -------------------------------------------------------------------------------- /docs/package/shower.rst: -------------------------------------------------------------------------------- 1 | .. _shower: 2 | 3 | Shower 4 | =================== 5 | 6 | .. autoclass:: showermodel.shower.Shower 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.shower.Shower.copy 11 | :noindex: 12 | .. automethod:: showermodel.shower.Shower.Projection 13 | :noindex: 14 | .. automethod:: showermodel.shower.Shower.Signal 15 | :noindex: 16 | .. automethod:: showermodel.shower.Shower.Event 17 | :noindex: 18 | .. automethod:: showermodel.shower.Shower.show_profile 19 | :noindex: 20 | .. automethod:: showermodel.shower.Shower.show_light_production 21 | :noindex: 22 | .. automethod:: showermodel.shower.Shower.show_projection 23 | :noindex: 24 | .. automethod:: showermodel.shower.Shower.show_signal 25 | :noindex: 26 | .. automethod:: showermodel.shower.Shower.show_geometry2D 27 | :noindex: 28 | .. automethod:: showermodel.shower.Shower.show_geometry3D 29 | :noindex: 30 | .. automethod:: showermodel.shower.Shower.show_distribution 31 | :noindex: 32 | -------------------------------------------------------------------------------- /docs/package/signal.rst: -------------------------------------------------------------------------------- 1 | .. _signal: 2 | 3 | Signal 4 | =================== 5 | 6 | .. autoclass:: showermodel.signal.Signal 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.signal.Signal.show_projection 11 | :noindex: 12 | .. automethod:: showermodel.signal.Signal.show_profile 13 | :noindex: 14 | .. automethod:: showermodel.signal.Signal.show_light_production 15 | :noindex: 16 | .. automethod:: showermodel.signal.Signal.show 17 | :noindex: 18 | .. automethod:: showermodel.signal.Signal.Image 19 | :noindex: 20 | -------------------------------------------------------------------------------- /docs/package/telescope.rst: -------------------------------------------------------------------------------- 1 | .. _telescope: 2 | 3 | Telescope 4 | =================== 5 | 6 | .. autoclass:: showermodel.telescope.Telescope 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.telescope.Telescope.copy 11 | :noindex: 12 | .. automethod:: showermodel.telescope.Telescope.hor_to_FoV 13 | :noindex: 14 | .. automethod:: showermodel.telescope.Telescope.FoV_to_hor 15 | :noindex: 16 | .. automethod:: showermodel.telescope.Telescope.theta_phi_to_alt_az 17 | :noindex: 18 | .. automethod:: showermodel.telescope.Telescope.alt_az_to_theta_phi 19 | :noindex: 20 | .. automethod:: showermodel.telescope.Telescope.spherical 21 | :noindex: 22 | .. automethod:: showermodel.telescope.Telescope.abs_to_rel 23 | :noindex: 24 | .. automethod:: showermodel.telescope.Telescope.distance 25 | :noindex: 26 | -------------------------------------------------------------------------------- /docs/package/track.rst: -------------------------------------------------------------------------------- 1 | .. _track: 2 | 3 | Track 4 | =================== 5 | 6 | .. autoclass:: showermodel.track.Track 7 | :show-inheritance: 8 | 9 | *Methods:* 10 | .. automethod:: showermodel.track.Track.h_to_xyz 11 | :noindex: 12 | .. automethod:: showermodel.track.Track.z_to_t 13 | :noindex: 14 | .. automethod:: showermodel.track.Track.X_to_xyz 15 | :noindex: 16 | .. automethod:: showermodel.track.Track.Projection 17 | :noindex: 18 | .. automethod:: showermodel.track.Track.show_projection 19 | :noindex: -------------------------------------------------------------------------------- /docs/tutorials/UC0_Playing_around_with_ShowerModel.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC0_Playing_around_with_ShowerModel.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/UC1_Track_visualization.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC1_Track_visualization.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/UC2_Shower_profile.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC2_Shower_profile.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/UC3_Signal_production_and_ground_distribution.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC3_Signal_production_and_ground_distribution.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/UC4_Observatory_events.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC4_Observatory_events.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/UC5_Camera_images.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./../../notebooks/UC5_Camera_images.ipynb" 3 | } -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | Tutorials 4 | ************ 5 | 6 | See the following use cases. You can play with them using Binder without any further installation: 7 | 8 | .. image:: https://mybinder.org/badge_logo.svg 9 | :target: https://mybinder.org/v2/gh/JaimeRosado/ShowerModel/master?filepath=notebooks 10 | 11 | .. toctree:: 12 | 13 | UC0_Playing_around_with_ShowerModel 14 | UC1_Track_visualization 15 | UC2_Shower_profile 16 | UC3_Signal_production_and_ground_distribution 17 | UC4_Observatory_events 18 | UC5_Camera_images 19 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: showermodel 2 | channels: 3 | - default 4 | - conda-forge 5 | dependencies: 6 | # core 7 | - python 8 | - jupyter 9 | - ipython 10 | - matplotlib 11 | - numpy 12 | - pandas 13 | - scipy 14 | - toml 15 | -------------------------------------------------------------------------------- /extra/Edep_profile_1000GeV_1000sh_0deg.dat: -------------------------------------------------------------------------------- 1 | # Num_showers: 1000 2 | # E_primary (GeV): 1000 3 | # ID_prim_particle: 1.0 4 | # Seeds: 19 5 | # Theta prim. part. incidence: 0 deg 6 | # Obs level (m): 220000 7 | # Atmosp model: 1 8 | # Cerenk_bunch_size: 10 9 | # Fluor_bunch_size: 5 10 | # Total deposited energy: 981.31 GeV 11 | # 12 | # Depth (g/cm2) | E_dep (GeV) 13 | 5.00 9.497873e-03 14 | 15.00 3.333613e-02 15 | 25.00 7.852014e-02 16 | 35.00 1.549243e-01 17 | 45.00 2.769472e-01 18 | 55.00 4.669559e-01 19 | 65.00 7.300987e-01 20 | 75.00 1.094074e+00 21 | 85.00 1.572849e+00 22 | 95.00 2.162899e+00 23 | 105.00 2.896753e+00 24 | 115.00 3.751382e+00 25 | 125.00 4.754021e+00 26 | 135.00 5.916706e+00 27 | 145.00 7.177527e+00 28 | 155.00 8.552407e+00 29 | 165.00 1.005867e+01 30 | 175.00 1.165912e+01 31 | 185.00 1.329577e+01 32 | 195.00 1.487513e+01 33 | 205.00 1.651595e+01 34 | 215.00 1.809000e+01 35 | 225.00 1.959631e+01 36 | 235.00 2.104782e+01 37 | 245.00 2.240874e+01 38 | 255.00 2.360339e+01 39 | 265.00 2.473791e+01 40 | 275.00 2.569640e+01 41 | 285.00 2.648750e+01 42 | 295.00 2.717476e+01 43 | 305.00 2.762141e+01 44 | 315.00 2.791270e+01 45 | 325.00 2.807644e+01 46 | 335.00 2.804070e+01 47 | 345.00 2.793276e+01 48 | 355.00 2.764829e+01 49 | 365.00 2.726793e+01 50 | 375.00 2.674060e+01 51 | 385.00 2.614333e+01 52 | 395.00 2.547634e+01 53 | 405.00 2.466439e+01 54 | 415.00 2.389814e+01 55 | 425.00 2.303040e+01 56 | 435.00 2.214037e+01 57 | 445.00 2.113998e+01 58 | 455.00 2.012922e+01 59 | 465.00 1.914218e+01 60 | 475.00 1.817566e+01 61 | 485.00 1.721092e+01 62 | 495.00 1.625828e+01 63 | 505.00 1.528029e+01 64 | 515.00 1.434849e+01 65 | 525.00 1.348394e+01 66 | 535.00 1.261466e+01 67 | 545.00 1.180217e+01 68 | 555.00 1.097444e+01 69 | 565.00 1.021752e+01 70 | 575.00 9.511055e+00 71 | 585.00 8.808236e+00 72 | 595.00 8.139209e+00 73 | 605.00 7.513040e+00 74 | 615.00 6.895343e+00 75 | 625.00 6.359041e+00 76 | 635.00 5.823103e+00 77 | 645.00 5.342710e+00 78 | 655.00 4.880717e+00 79 | 665.00 4.462108e+00 80 | 675.00 4.093752e+00 81 | 685.00 3.726242e+00 82 | 695.00 3.398427e+00 83 | 705.00 3.082323e+00 84 | 715.00 2.809296e+00 85 | 725.00 2.558511e+00 86 | 735.00 2.323388e+00 87 | 745.00 2.110865e+00 88 | 755.00 1.902172e+00 89 | 765.00 1.712306e+00 90 | 775.00 1.555700e+00 91 | 785.00 1.399310e+00 92 | 795.00 6.561738e-01 93 | -------------------------------------------------------------------------------- /extra/atm_models.toml: -------------------------------------------------------------------------------- 1 | # CORSIKA atmospheric models (ignoring water vapor) 2 | # They consist in 5 layers. In the first 4 layers, the density follows 3 | # an exponential dependence on the altitude, so the vertical depth is 4 | # X_vert = a + b * exp(-h / c) 5 | # In the fifth layer, X_vert decreases linearly with height 6 | # X_vert = a - b * h / c 7 | # Pressure and temperature are calculated assuming an ideal gas 8 | # (with molar mass of dry air) and constant acceleration of gravity. 9 | 10 | [1] 11 | # Optional paramter 12 | info = "U.S. standard atmosphere as parameterized by Linsley" 13 | 14 | # Lower limit in km of the 5 layers. 15 | # The upper limit of the fifth one is h_limit = a * c / b 16 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 17 | 18 | # Parameter a in g/cm2 19 | a = [-186.555305, -94.919, 0.61289, 0.0, 0.01128292] 20 | 21 | # Parameter b in g/cm2 22 | b = [1222.6562, 1144.9069, 1305.5948, 540.1778, 1.0] 23 | 24 | # Parameter c in cm 25 | c = [994186.38, 878153.55, 636143.04, 772170.16, 1.0e9] 26 | 27 | 28 | [2] 29 | # Optional paramter 30 | info = "AT115 Central European atmosphere for Jan. 15, 1993" 31 | 32 | # Lower limit in km of the 5 layers. 33 | # The upper limit of the fifth one is h_limit = a * c / b 34 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 35 | 36 | # Parameter a in g/cm2 37 | a = [-118.1277, -154.258, 0.4191499, 5.4094056e-4, 0.01128292] 38 | 39 | # Parameter b in g/cm2 40 | b = [1173.9861, 1205.7625, 1386.7807, 555.8935, 1.0] 41 | 42 | # Parameter c in cm 43 | c = [919546.0, 963267.92, 614315.0, 739059.6, 1.0e9] 44 | 45 | 46 | [3] 47 | # Optional paramter 48 | info = "AT223 Central European atmosphere for Feb. 23, 1993" 49 | 50 | # Lower limit in km of the 5 layers. 51 | # The upper limit of the fifth one is h_limit = a * c / b 52 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 53 | 54 | # Parameter a in g/cm2 55 | a = [-195.837264, -50.4128778, 0.345594007, 5.46207e-4, 0.01128292] 56 | 57 | # Parameter b in g/cm2 58 | b = [1240.48, 1117.85, 1210.9, 608.2128, 1.0] 59 | 60 | # Parameter c in cm 61 | c = [933697.0, 765229.0, 636790.0, 733793.8, 1.0e9] 62 | 63 | 64 | [4] 65 | # Optional paramter 66 | info = "AT511 Central European atmosphere for May 11, 1993" 67 | 68 | # Lower limit in km of the 5 layers. 69 | # The upper limit of the fifth one is h_limit = a * c / b 70 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 71 | 72 | # Parameter a in g/cm2 73 | a = [-253.95047, -128.97714, 0.353207, 5.526876e-4, 0.01128292] 74 | 75 | # Parameter b in g/cm2 76 | b = [1285.2782, 1173.1616, 1320.4561, 680.6803, 1.0] 77 | 78 | # Parameter c in cm 79 | c = [1088310.0, 935485.0, 635137.0, 727312.6, 1.0e9] 80 | 81 | 82 | [5] 83 | # Optional paramter 84 | info = "AT616 Central European atmosphere for June 16, 1993" 85 | 86 | # Lower limit in km of the 5 layers. 87 | # The upper limit of the fifth one is h_limit = a * c / b 88 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 89 | 90 | # Parameter a in g/cm2 91 | a = [-208.12899, -120.26179, 0.31167036, 5.591489e-4, 0.01128292] 92 | 93 | # Parameter b in g/cm2 94 | b = [1251.474, 1173.321, 1307.826, 763.1139, 1.0] 95 | 96 | # Parameter c in cm 97 | c = [1032310.0, 925528.0, 645330.0, 720851.4, 1.0e9] 98 | 99 | 100 | [6] 101 | # Optional paramter 102 | info = "AT822 Central European atmosphere for Aug. 22, 1993" 103 | 104 | # Lower limit in km of the 5 layers. 105 | # The upper limit of the fifth one is h_limit = a * c / b 106 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 107 | 108 | # Parameter a in g/cm2 109 | a = [-77.875723, -214.96818, 0.3721868, 5.5309816e-4, 0.01128292] 110 | 111 | # Parameter b in g/cm2 112 | b = [1103.3362, 1226.5761, 1382.6933, 685.6073, 1.0] 113 | 114 | # Parameter c in cm 115 | c = [932077.0, 1109960.0, 630217.0, 726901.3, 1.0e9] 116 | 117 | 118 | [7] 119 | # Optional paramter 120 | info = "AT1014 Central European atmosphere for Oct. 14, 1993" 121 | 122 | # Lower limit in km of the 5 layers. 123 | # The upper limit of the fifth one is h_limit = a * c / b 124 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 125 | 126 | # Parameter a in g/cm2 127 | a = [-242.56651, -103.21398, 0.3349752, 5.527485e-4, 0.01128292] 128 | 129 | # Parameter b in g/cm2 130 | b = [1262.7013, 1139.0249, 1270.2886, 681.4061, 1.0] 131 | 132 | # Parameter c in cm 133 | c = [1059360.0, 888814.0, 639902.0, 727251.8, 1.0e9] 134 | 135 | 136 | [8] 137 | # Optional paramter 138 | info = "AT1224 Central European atmosphere for Dec. 24, 1993" 139 | 140 | # Lower limit in km of the 5 layers. 141 | # The upper limit of the fifth one is h_limit = a * c / b 142 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 143 | 144 | # Parameter a in g/cm2 145 | a = [-195.34842, -71.997323, 0.3378142, 5.48224e-4, 0.01128292] 146 | 147 | # Parameter b in g/cm2 148 | b = [1210.4, 1103.8629, 1215.3545, 629.7611, 1.0] 149 | 150 | # Parameter c in cm 151 | c = [970276.0, 820946.0, 639074.0, 731776.5, 1.0e9] 152 | 153 | 154 | # [9] Not existing. User-defined model in COSRIKA 155 | 156 | # [10] Not existing. User-defined model in COSRIKA 157 | 158 | 159 | [11] 160 | # Optional paramter 161 | info = "South pole atmosphere for March 31, 1997 (MSIS-90-E)" 162 | 163 | # Lower limit in km of the 5 layers. 164 | # The upper limit of the fifth one is h_limit = a * c / b 165 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 166 | 167 | # Parameter a in g/cm2 168 | a = [-137.656, -37.9610, 0.222659, -0.000616201, 0.00207722] 169 | 170 | # Parameter b in g/cm2 171 | b = [1130.74, 1052.05, 1137.21, 442.512, 1.0] 172 | 173 | # Parameter c in cm 174 | c = [867358.0, 741208.0, 633846.0, 759850.0, 5.4303203e9] 175 | 176 | 177 | [12] 178 | # Optional paramter 179 | info = "South pole atmosphere for Jul. 01, 1997 (MSIS-90-E)" 180 | 181 | # Lower limit in km of the 5 layers. 182 | # The upper limit of the fifth one is h_limit = a * c / b 183 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 184 | 185 | # Parameter a in g/cm2 186 | a = [-163.331, -65.3713, 0.402903, -0.000479198, 0.00188667] 187 | 188 | # Parameter b in g/cm2 189 | b = [1183.70, 1108.06, 1424.02, 207.595, 1.0] 190 | 191 | # Parameter c in cm 192 | c = [875221.0, 753213.0, 545846.0, 793043.0, 5.9787908e9] 193 | 194 | 195 | [13] 196 | # Optional paramter 197 | info = "South pole atmosphere for Oct. 01, 1997 (MSIS-90-E)" 198 | 199 | # Lower limit in km of the 5 layers. 200 | # The upper limit of the fifth one is h_limit = a * c / b 201 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 202 | 203 | # Parameter a in g/cm2 204 | a = [-142.801, -70.1538, 1.14855, -0.000910269, 0.00152236] 205 | 206 | # Parameter b in g/cm2 207 | b = [1177.19, 1125.11, 1304.77, 433.823, 1.0] 208 | 209 | # Parameter c in cm 210 | c = [861745.0, 765925.0, 581351.0, 775155.0, 7.4095699e9] 211 | 212 | 213 | [14] 214 | # Optional paramter 215 | info = "South pole atmosphere for Dec. 31, 1997 (MSIS-90-E)" 216 | 217 | # Lower limit in km of the 5 layers. 218 | # The upper limit of the fifth one is h_limit = a * c / b 219 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 220 | 221 | # Parameter a in g/cm2 222 | a = [-128.601, -39.5548, 1.13088, -0.00264960, 0.00192534] 223 | 224 | # Parameter b in g/cm2 225 | b = [1139.99, 1073.82, 1052.96, 492.503, 1.0] 226 | 227 | # Parameter c in cm 228 | c = [861913.0, 744955.0, 675928.0, 829627.0, 5.8587010e9] 229 | 230 | 231 | [15] 232 | # Optional paramter 233 | info = "South pole atmosphere for January after Lipari" 234 | 235 | # Lower limit in km of the 5 layers. 236 | # The upper limit of the fifth one is h_limit = a * c / b 237 | h_low = [0.0, 2.67, 5.33, 8.0, 100.0] 238 | 239 | # Parameter a in g/cm2 240 | a = [-113.139, -79.0635, -54.3888, 0.0, 0.00421033] 241 | 242 | # Parameter b in g/cm2 243 | b = [1133.10, 1101.20, 1085.00, 1098.00, 1.0] 244 | 245 | # Parameter c in cm 246 | c = [861730.0, 826340.0, 790950.0, 682800.0, 2.6798156e9] 247 | 248 | 249 | [16] 250 | # Optional paramter 251 | info = "South pole atmosphere for August after Lipari" 252 | 253 | # Lower limit in km of the 5 layers. 254 | # The upper limit of the fifth one is h_limit = a * c / b 255 | h_low = [0.0, 6.67, 13.33, 20.0, 100.0] 256 | 257 | # Parameter a in g/cm2 258 | a = [-59.0293, -21.5794, -7.14839, 0.0, 0.000190175] 259 | 260 | # Parameter b in g/cm2 261 | b = [1079.00, 1071.90, 1182.00, 1647.10, 1.0] 262 | 263 | # Parameter c in cm 264 | c = [764170.0, 699910.0, 635650.0, 551010.0, 59.329575e9] 265 | 266 | 267 | [17] 268 | # Optional paramter 269 | model = "U.S. standard atmosphere as parameterized by Keilhauer" 270 | 271 | # Lower limit in km of the 5 layers. 272 | # The upper limit of the fifth one is h_limit = a * c / b 273 | h_low = [0.0, 7.0, 11.4, 37.0, 100.0] 274 | 275 | # Parameter a in g/cm2 276 | a = [-149.801663, -57.932486, 0.63631894, 4.3545369e-4, 0.01128292] 277 | 278 | # Parameter b in g/cm2 279 | b = [1183.6071, 1143.0425, 1322.9748, 655.67307, 1.0] 280 | 281 | # Parameter c in cm 282 | c = [954248.34, 800005.34, 629568.93, 737521.77, 1.0e9] 283 | 284 | 285 | [18] 286 | # Optional paramter 287 | model = "Malargüe GDAS model for January after Will/Keilhauer" 288 | 289 | # Lower limit in km of the 5 layers. 290 | # The upper limit of the fifth one is h_limit = a * c / b 291 | h_low = [0.0, 9.4, 15.3, 31.6, 100.0] 292 | 293 | # Parameter a in g/cm2 294 | a = [-136.72575606, -31.636643044, 1.8890234035, 3.9201867984e-4, 0.01128292] 295 | 296 | # Parameter b in g/cm2 297 | b = [1174.8298334, 1204.8233453, 1637.7703583, 735.96095023, 1.0] 298 | 299 | # Parameter c in cm 300 | c = [982815.95248, 754029.87759, 594416.83822, 733974.36972, 1.0e9] 301 | 302 | 303 | [19] 304 | # Optional paramter 305 | model = "Malargüe GDAS model for February after Will/Keilhauer" 306 | 307 | # Lower limit in km of the 5 layers. 308 | # The upper limit of the fifth one is h_limit = a * c / b 309 | h_low = [0.0, 9.2, 15.4, 31.0, 100.0] 310 | 311 | # Parameter a in g/cm2 312 | a = [-137.25655862, -31.793978896, 2.0616227547, 4.1243062289e-4, 0.01128292] 313 | 314 | # Parameter b in g/cm2 315 | b = [1176.0907565, 1197.8951104, 1646.4616955, 755.18728657, 1.0] 316 | 317 | # Parameter c in cm 318 | c = [981369.6125, 756657.65383, 592969.89671, 731345.88332, 1.0e9] 319 | 320 | 321 | [20] 322 | # Optional paramter 323 | model = "Malargüe GDAS model for March after Will/Keilhauer" 324 | 325 | # Lower limit in km of the 5 layers. 326 | # The upper limit of the fifth one is h_limit = a * c / b 327 | h_low = [0.0, 9.6, 15.2, 30.7, 100.0] 328 | 329 | # Parameter a in g/cm2 330 | a = [-132.36885162, -29.077046629, 2.090501509, 4.3534337925e-4, 0.01128292] 331 | 332 | # Parameter b in g/cm2 333 | b = [1172.6227784, 1215.3964677, 1617.0099282, 769.51991638, 1.0] 334 | 335 | # Parameter c in cm 336 | c = [972654.0563, 742769.2171, 595342.19851, 728921.61954, 1.0e9] 337 | 338 | 339 | [21] 340 | # Optional paramter 341 | model = "Malargüe GDAS model for April after Will/Keilhauer" 342 | 343 | # Lower limit in km of the 5 layers. 344 | # The upper limit of the fifth one is h_limit = a * c / b 345 | h_low = [0.0, 10.0, 14.9, 32.6, 100.0] 346 | 347 | # Parameter a in g/cm2 348 | a = [-129.9930412, -21.847248438, 1.5211136484, 3.9559055121e-4, 0.01128292] 349 | 350 | # Parameter b in g/cm2 351 | b = [1172.3291878, 1250.2922774, 1542.6248413, 713.1008285, 1.0] 352 | 353 | # Parameter c in cm 354 | c = [962396.5521, 711452.06673, 603480.61835, 735460.83741, 1.0e9] 355 | 356 | 357 | [22] 358 | # Optional paramter 359 | model = "Malargüe GDAS model for May after Will/Keilhauer" 360 | 361 | # Lower limit in km of the 5 layers. 362 | # The upper limit of the fifth one is h_limit = a * c / b 363 | h_low = [0.0, 10.2, 15.1, 35.9, 100.0] 364 | 365 | # Parameter a in g/cm2 366 | a = [-125.11468467, -14.591235621, 0.93641128677, 3.2475590985e-4, 0.01128292] 367 | 368 | # Parameter b in g/cm2 369 | b = [1169.9511302, 1277.6768488, 1493.5303781, 617.9660747, 1.0] 370 | 371 | # Parameter c in cm 372 | c = [947742.88769, 685089.57509, 609640.01932, 747555.95526, 1.0e9] 373 | 374 | 375 | [23] 376 | # Optional paramter 377 | model = "Malargüe GDAS model for June after Will/Keilhauer" 378 | 379 | # Lower limit in km of the 5 layers. 380 | # The upper limit of the fifth one is h_limit = a * c / b 381 | h_low = [0.0, 10.1, 16.0, 36.7, 100.0] 382 | 383 | # Parameter a in g/cm2 384 | a = [-126.17178851, -7.7289852811, 0.81676828638, 3.1947676891e-4, 0.01128292] 385 | 386 | # Parameter b in g/cm2 387 | b = [1171.0916276, 1295.3516434, 1455.3009344, 595.11713507, 1.0] 388 | 389 | # Parameter c in cm 390 | c = [940102.98842, 661697.57543, 612702.0632, 749976.26832, 1.0e9] 391 | 392 | 393 | [24] 394 | # Optional paramter 395 | model = "Malargüe GDAS model for July after Will/Keilhauer" 396 | 397 | # Lower limit in km of the 5 layers. 398 | # The upper limit of the fifth one is h_limit = a * c / b 399 | h_low = [0.0, 9.6, 16.5, 37.4, 100.0] 400 | 401 | # Parameter a in g/cm2 402 | a = [-126.17216789, -8.6182537514, 0.74177836911, 2.9350702097e-4, 0.01128292] 403 | 404 | # Parameter b in g/cm2 405 | b = [1172.7340688, 1258.9180079, 1450.0537141, 583.07727715, 1.0] 406 | 407 | # Parameter c in cm 408 | c = [934649.58886, 672975.82513, 614888.52458, 752631.28536, 1.0e9] 409 | 410 | 411 | [25] 412 | # Optional paramter 413 | model = "Malargüe GDAS model for August after Will/Keilhauer" 414 | 415 | # Lower limit in km of the 5 layers. 416 | # The upper limit of the fifth one is h_limit = a * c / b 417 | h_low = [0.0, 9.6, 15.9, 36.3, 100.0] 418 | 419 | # Parameter a in g/cm2 420 | a = [-123.27936204, -10.051493041, 0.84187346153, 3.2422546759e-4, 0.01128292] 421 | 422 | # Parameter b in g/cm2 423 | b = [1169.763036, 1251.0219808, 1436.6499372, 627.42169844, 1.0] 424 | 425 | # Parameter c in cm 426 | c = [931569.97625, 678861.75136, 617363.34491, 746739.16141, 1.0e9] 427 | 428 | 429 | [26] 430 | # Optional paramter 431 | model = "Malargüe GDAS model for September after Will/Keilhauer" 432 | 433 | # Lower limit in km of the 5 layers. 434 | # The upper limit of the fifth one is h_limit = a * c / b 435 | h_low = [0.0, 9.5, 16.2, 37.2, 100.0] 436 | 437 | # Parameter a in g/cm2 438 | a = [-126.94494665, -9.5556536981, 0.74939405052, 2.9823116961e-4, 0.01128292] 439 | 440 | # Parameter b in g/cm2 441 | b = [1174.8676453, 1251.5588529, 1440.8257549, 606.31473165, 1.0] 442 | 443 | # Parameter c in cm 444 | c = [936953.91919, 678906.60516, 618132.60561, 750154.67709, 1.0e9] 445 | 446 | 447 | [27] 448 | # Optional paramter 449 | model = "Malargüe GDAS model for October after Will/Keilhauer" 450 | 451 | # Lower limit in km of the 5 layers. 452 | # The upper limit of the fifth one is h_limit = a * c / b 453 | h_low = [0.0, 9.5, 15.5, 36.5, 100.0] 454 | 455 | # Parameter a in g/cm2 456 | a = [-133.13151125, -13.973209265, 0.8378263431, 3.111742176e-4, 0.01128292] 457 | 458 | # Parameter b in g/cm2 459 | b = [1176.9833473, 1244.234531, 1464.0120855, 622.11207419, 1.0] 460 | 461 | # Parameter c in cm 462 | c = [954151.404, 692708.89816, 615439.43936, 747969.08133, 1.0e9] 463 | 464 | 465 | [28] 466 | # Optional paramter 467 | model = "Malargüe GDAS model for November after Will/Keilhauer" 468 | 469 | # Lower limit in km of the 5 layers. 470 | # The upper limit of the fifth one is h_limit = a * c / b 471 | h_low = [0.0, 9.6, 15.3, 34.6, 100.0] 472 | 473 | # Parameter a in g/cm2 474 | a = [-134.72208165, -18.172382908, 1.1159806845, 3.5217025515e-4, 0.01128292] 475 | 476 | # Parameter b in g/cm2 477 | b = [1175.7737972, 1238.9538504, 1505.1614366, 670.64752105, 1.0] 478 | 479 | # Parameter c in cm 480 | c = [964877.07766, 706199.57502, 610242.24564, 741412.74548, 1.0e9] 481 | 482 | 483 | [29] 484 | # Optional paramter 485 | model = "Malargüe GDAS model for December after Will/Keilhauer" 486 | 487 | # Lower limit in km of the 5 layers. 488 | # The upper limit of the fifth one is h_limit = a * c / b 489 | h_low = [0.0, 9.6, 15.6, 33.3, 100.0] 490 | 491 | # Parameter a in g/cm2 492 | a = [-135.40825209, -22.830409026, 1.4223453493, 3.7512921774e-4, 0.01128292] 493 | 494 | # Parameter b in g/cm2 495 | b = [1174.644971, 1227.2753683, 1585.7130562, 691.23389637, 1.0] 496 | 497 | # Parameter c in cm 498 | c = [973884.44361, 723759.74682, 600308.13983, 738390.20525, 1.0e9] 499 | -------------------------------------------------------------------------------- /extra/averaged_profile_5sh_100TeV.dat: -------------------------------------------------------------------------------- 1 | # Num_showers:5 2 | # E_primary (GeV): 100000 3 | # ID_prim_particle: 1 4 | # Seeds: 11, 5 5 | # Theta prim. part. incidence: 30 deg 6 | # Obs level (cm): 220000 7 | # Atmosp model: 1 8 | # Cerenk_bunch_size: 10 9 | # Fluor_bunch_size: 3 10 | # 11 | # Depth(g/cm**2) E_Dep(GeV) 12 | 5 9.731380e-03 13 | 15 2.330881e-02 14 | 25 5.379906e-02 15 | 35 1.928260e-01 16 | 45 4.283024e-01 17 | 55 1.022613e+00 18 | 65 2.081785e+00 19 | 75 4.003883e+00 20 | 85 6.902830e+00 21 | 95 1.223097e+01 22 | 105 1.856271e+01 23 | 115 2.744663e+01 24 | 125 6.336552e+01 25 | 135 5.798032e+01 26 | 145 8.271670e+01 27 | 155 1.101607e+02 28 | 165 1.464398e+02 29 | 175 1.881380e+02 30 | 185 2.395107e+02 31 | 195 2.952046e+02 32 | 205 3.669009e+02 33 | 215 4.436990e+02 34 | 225 5.289737e+02 35 | 235 6.249736e+02 36 | 245 7.310807e+02 37 | 255 8.395838e+02 38 | 265 9.497863e+02 39 | 275 1.064338e+03 40 | 285 1.180073e+03 41 | 295 1.307670e+03 42 | 305 1.428422e+03 43 | 315 1.541367e+03 44 | 325 1.661994e+03 45 | 335 1.773129e+03 46 | 345 1.876050e+03 47 | 355 1.978423e+03 48 | 365 2.074859e+03 49 | 375 2.164609e+03 50 | 385 2.236835e+03 51 | 395 2.303676e+03 52 | 405 2.350807e+03 53 | 415 2.396356e+03 54 | 425 2.434961e+03 55 | 435 2.464022e+03 56 | 445 2.477686e+03 57 | 455 2.486878e+03 58 | 465 2.479722e+03 59 | 475 2.471346e+03 60 | 485 2.458268e+03 61 | 495 2.429990e+03 62 | 505 2.394936e+03 63 | 515 2.354276e+03 64 | 525 2.307338e+03 65 | 535 2.254624e+03 66 | 545 2.190890e+03 67 | 555 2.126190e+03 68 | 565 2.059700e+03 69 | 575 1.986458e+03 70 | 585 1.913006e+03 71 | 595 1.835292e+03 72 | 605 1.755086e+03 73 | 615 1.680962e+03 74 | 625 1.603752e+03 75 | 635 1.523182e+03 76 | 645 1.439007e+03 77 | 655 1.360182e+03 78 | 665 1.276408e+03 79 | 675 1.202321e+03 80 | 685 1.127409e+03 81 | 695 1.058001e+03 82 | 705 9.842260e+02 83 | 715 9.168682e+02 84 | 725 8.529652e+02 85 | 735 7.943462e+02 86 | 745 7.363872e+02 87 | 755 6.764926e+02 88 | 765 6.239040e+02 89 | 775 5.726120e+02 90 | 785 5.210614e+02 91 | 795 4.990564e+03 92 | -------------------------------------------------------------------------------- /extra/mean_annual_global_reference_atmosphere.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaimeRosado/ShowerModel/4c3d35f9594a2460da00ceb3435db1d7bf614b73/extra/mean_annual_global_reference_atmosphere.xlsx -------------------------------------------------------------------------------- /extra/showermodel_config.toml: -------------------------------------------------------------------------------- 1 | [Atmosphere] # For Atmosphere 2 | # Ground level in km above sea level. 3 | h0 = 2.2 # km This parameter has been modified from the original config file!! 4 | 5 | # Upper limit in km above sea level of the atmosphere discretization. 6 | # If commented, the top level of the selected atmospheric model is taken. 7 | #h_top = 112.8292 # km 8 | 9 | # Number of discretization steps. 10 | N_steps = 550 11 | 12 | # CORSIKA atmospheric model (from 1 to 29, except 9 and 10). See atm_models.toml. 13 | atm_model = 1 14 | 15 | # Simple model of water-vapor profile from recommendation ITU-R P.835-6. 16 | # The density is assumed to follow an exponential dependence on the altitude 17 | # rho_w = rho_w_sl * exp(-h / h_scale) 18 | # up to an altitude where the maxing ratio P_w / P = 2.0e-6. 19 | # Above this altitude, the mixing ratio is assumed to be constant. 20 | # Water pessure P_w is calculated from density assuming an ideal gas knowing 21 | # the temperature and the molar mass of water. 22 | # 23 | # Water-vapor density in g/cm3 at sea level. Set to 0 if dry air is assumed. 24 | rho_w_sl = 7.5e-6 # g/cm3 25 | # Scale height in km. 26 | h_scale = 2.0 # km 27 | 28 | 29 | [Shower] # For Track, Profile and Shower 30 | # Zenith angle in degrees of the apparent position of the source. 31 | theta = 0.0 32 | 33 | # Altitude in degrees of the apperent position of the source. 34 | # If commented, theta is used. If given, theta is overwritten. 35 | #alt = 90.0 36 | 37 | # Azimuth angle (from north, clockwise) in degrees of the apparent position of the source. 38 | az = 0.0 39 | 40 | # East and north coordinates in km of shower impact point on ground (z=0). 41 | x0 = 0.0 # km 42 | y0 = 0.0 # km 43 | 44 | # East, north and height coordinates in km of the first interaction point of the shower. 45 | # If zi==None, xi and yi are ignored and the shower impacts at (x0, y0, 0) on ground. 46 | # If zi is given, x0 and y0 are ignored and the shower starts at (xi,yi,zi). 47 | xi = 0.0 # km 48 | yi = 0.0 # km 49 | #zi = 100.0 # km 50 | 51 | # Energy of the primary particle in MeV. 52 | E = 10000000.0 # MeV 53 | 54 | # Profile model: 'Greisen' or 'Gaisser-Hillas' 55 | prf_model = 'Greisen' 56 | 57 | # Slant depth in g/cm2 at shower maximum. 58 | # If None, a typical value of X_max for gamma or proton showers is calculated from the radiation length. 59 | #X_max = 430.0 # g/cm2 60 | 61 | # X0 and Lambda parameters in g/cm2 to be used when prf_model=='Gaisser-Hillas'. 62 | # If None, typical values for the input energy are used. 63 | #X0_GH = 1.5 # g/cm2 64 | #lambda_GH = 77.3 # g/cm2 65 | 66 | 67 | [Signal] # For Signal, Event, Cherenkov and Fluorescence 68 | # Include the atmospheric transmision to transport photons. 69 | atm_trans = true 70 | 71 | # Include the telescope efficiency to calculate the signal. 72 | # If False, 100% efficiency is assumed for a given wavelenght interval. 73 | # For Signal and Event. 74 | tel_eff = true 75 | 76 | # Initial-final wavelengths and step size in nm of the wavelength interval to calculate the light production 77 | # in the Cherenkov and Fluorescence classes, as well as the signal for tel_eff=false in Signal and Event. 78 | wvl_ini = 290.0 # nm 79 | wvl_fin = 430.0 # nm 80 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 81 | 82 | 83 | [Image] # For Image 84 | # Use a NKG lateral profile to spread the signal. 85 | # If False, a linear shower is assumed. 86 | lat_profile = true 87 | 88 | # Night sky background in MHz/m^2/deg^2 (photoelectrons). 89 | NSB = 40.0 # MHz/m^2/deg^2 90 | 91 | 92 | [Telescope] 93 | # East, north and height coordinates of the telescope in km. 94 | x = 0.0 # km 95 | y = 0.0 # km 96 | z = 0.0 # km 97 | 98 | # Zenith angle in degrees of the telescope pointing direction. 99 | theta = 0.0 # deg 100 | 101 | # Altitude in degrees of the telescope pointing direction. 102 | # If commented, theta is used. If given, theta is overwritten. 103 | #alt = 90.0 # deg 104 | 105 | # Azimuth angle (from north, clockwise) in degrees of the telescope pointing direction. 106 | az = 0.0 # deg 107 | 108 | # Type of telescope to be searched from tel_data.toml. 109 | tel_type = 'generic' 110 | 111 | 112 | [Observatory] 113 | # Name given to the observatory. Default to None. 114 | #obs_name = 'Generic' 115 | 116 | 117 | [Array25] 118 | # Name given to the observatory. 119 | obs_name = 'Array25' 120 | 121 | # Type of telescope to be used by default (when telescope=None). 122 | tel_type = 'IACT' 123 | 124 | # East, north and height coordinates in km of the center of the array. 125 | x_c = 0.0 # km 126 | y_c = 0.0 # km 127 | z_c = 0.0 # km 128 | 129 | # Radius in km of the array. 130 | R = 0.341 # km 131 | 132 | # Rotation angle in degrees of the array (clockwise). 133 | rot_angle = 0.0 # deg 134 | 135 | 136 | [Grid] 137 | # Name given to the observatory. 138 | obs_name = 'Grid' 139 | 140 | # Type of telescope to be used by default (when telescope=None). 141 | tel_type = 'GridElement' 142 | 143 | # East, north and height coordinates in km of the center of the grid. 144 | x_c = 0.0 # km 145 | y_c = 0.0 # km 146 | z_c = 0.0 # km 147 | 148 | # Size of the grid in km across the x and y directions. 149 | size_x = 2.0 # km 150 | size_y = 2.0 # km 151 | 152 | # Number of cells across the x and y directions. 153 | N_x = 10 154 | N_y = 10 155 | -------------------------------------------------------------------------------- /extra/tel_data.toml: -------------------------------------------------------------------------------- 1 | # Default telescope data corresponding to tel_type. 2 | # For the optional parameter eff_fluo, efficiency values should be given for the 3 | # 57 fluorescence bands considered in fluorescence_model.toml 4 | # wvl_fluo = [281, 282, 296, 298, 302, 5 | # 308, 312, 314, 316, 318, 6 | # 327, 329, 331, 334, 337, 7 | # 346, 350, 354, 358, 366, 8 | # 367, 371, 376, 381, 386, 9 | # 388, 389, 391, 394, 400, 10 | # 405, 414, 420, 424, 427, 11 | # 428, 434, 436, 442, 449, 12 | # 457, 460, 465, 467, 471, 13 | # 481, 492, 503, 515, 523, 14 | # 531, 545, 570, 575, 586, 15 | # 594, 666] 16 | 17 | [generic] 18 | # Angular diameter in degrees of the telescope field of view. 19 | apert = 10.0 # deg 20 | 21 | # Detection area in m^2 (e.g., mirror area of an IACT). 22 | area = 100.0 # m^2 23 | 24 | # Number of camera pixels. 25 | N_pix = 1500 26 | 27 | # Integration time in microseconds of camera frames. 28 | int_time = 0.01 # us 29 | 30 | # Wavelength interval where efficiency is non zero. 31 | # Array of wavelengths in nm. Discretization step must be constant. 32 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 33 | #wvl = [290.0, ..., 428.0] 34 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 35 | wvl_ini = 290.0 # nm 36 | wvl_fin = 430.0 # nm (not included in wvl) 37 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 38 | 39 | # Detection efficiency in decimal fraction. 40 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 41 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 42 | eff = 1.0 43 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 44 | # If not given, values are interpolated from eff. 45 | # Put zeros if neccesary to match length of wvl_fluo. 46 | #eff_fluo = [1.0, ..., 1.0] 47 | 48 | [GridElement] 49 | # Angular diameter in degrees of the telescope field of view. 50 | apert = 180.0 # deg 51 | 52 | # Detection area in m^2 (e.g., mirror area of an IACT). 53 | area = 100.0 # m^2 54 | 55 | # Number of camera pixels. 56 | N_pix = 1 57 | 58 | # Integration time in microseconds of camera frames. 59 | int_time = 10.0 # us 60 | 61 | # Wavelength interval where efficiency is non zero. 62 | # Array of wavelengths in nm. Discretization step must be constant. 63 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 64 | #wvl = [290.0, ..., 428.0] 65 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 66 | wvl_ini = 290.0 # nm 67 | wvl_fin = 430.0 # nm (not included in wvl) 68 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 69 | 70 | # Detection efficiency in decimal fraction. 71 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 72 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 73 | eff = 1.0 74 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 75 | # If not given, values are interpolated from eff. 76 | # Put zeros if neccesary to match length of wvl_fluo. 77 | #eff_fluo = [1.0, ..., 1.0] 78 | 79 | [IACT] 80 | # Angular diameter in degrees of the telescope field of view. 81 | apert = 8.0 # deg 82 | 83 | # Detection area in m^2 (e.g., mirror area of an IACT). 84 | area = 113.097 # m^2 85 | 86 | # Number of camera pixels. 87 | N_pix = 1800 88 | 89 | # Integration time in microseconds of camera frames. 90 | int_time = 0.01 # us 91 | 92 | # Wavelength interval where efficiency is non zero. 93 | # Array of wavelengths in nm. Discretization step must be constant. 94 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 95 | wvl = [280.0, 283.0, 286.0, 289.0, 292.0, 96 | 295.0, 298.0, 301.0, 304.0, 307.0, 97 | 310.0, 313.0, 316.0, 319.0, 322.0, 98 | 325.0, 328.0, 331.0, 334.0, 337.0, 99 | 340.0, 343.0, 346.0, 349.0, 352.0, 100 | 355.0, 358.0, 361.0, 364.0, 367.0, 101 | 370.0, 373.0, 376.0, 379.0, 382.0, 102 | 385.0, 388.0, 391.0, 394.0, 397.0, 103 | 400.0, 403.0, 406.0, 409.0, 412.0, 104 | 415.0, 418.0, 421.0, 424.0, 427.0, 105 | 430.0, 433.0, 436.0, 439.0, 442.0, 106 | 445.0, 448.0, 451.0, 454.0, 457.0, 107 | 460.0, 463.0, 466.0, 469.0, 472.0, 108 | 475.0, 478.0, 481.0, 484.0, 487.0, 109 | 490.0, 493.0, 496.0, 499.0, 502.0, 110 | 505.0, 508.0, 511.0, 514.0, 517.0, 111 | 520.0, 523.0, 526.0, 529.0, 532.0, 112 | 535.0, 538.0, 541.0, 544.0, 547.0, 113 | 550.0, 553.0, 556.0, 559.0, 562.0, 114 | 565.0, 568.0, 571.0, 574.0, 577.0, 115 | 580.0, 583.0, 586.0, 589.0, 592.0, 116 | 595.0, 598.0] 117 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 118 | wvl_ini = 280.0 # nm 119 | wvl_fin = 560.0 # nm (not included in wvl) 120 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 121 | 122 | # Detection efficiency in decimal fraction. 123 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 124 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 125 | eff = [0.01549109, 0.07929918, 0.12963671, 0.17227160, 0.20737558, 126 | 0.23620218, 0.26303514, 0.28054180, 0.29466943, 0.30534291, 127 | 0.31487235, 0.32109888, 0.32663823, 0.33124247, 0.33485481, 128 | 0.33779549, 0.34029057, 0.34245432, 0.34408696, 0.34525204, 129 | 0.34617290, 0.34677869, 0.34724839, 0.34725518, 0.34704414, 130 | 0.34650661, 0.34635416, 0.34697940, 0.34802544, 0.34980112, 131 | 0.35165961, 0.35340027, 0.35487968, 0.35600359, 0.35627157, 132 | 0.35453785, 0.35290097, 0.35149441, 0.35007389, 0.34852091, 133 | 0.34668034, 0.34418064, 0.34175984, 0.33960103, 0.33737256, 134 | 0.33486841, 0.33231135, 0.32954030, 0.32675191, 0.32395963, 135 | 0.32098053, 0.31707070, 0.31308814, 0.30863957, 0.30418957, 136 | 0.29862994, 0.29259352, 0.28659898, 0.28064450, 0.27502209, 137 | 0.26952477, 0.26466024, 0.25979416, 0.25515796, 0.25052618, 138 | 0.24626667, 0.24225691, 0.23828600, 0.23434073, 0.23003388, 139 | 0.22545024, 0.21959105, 0.21275740, 0.20553806, 0.19688918, 140 | 0.18546885, 0.17399642, 0.16420586, 0.15533591, 0.14750979, 141 | 0.13897085, 0.13244260, 0.12695100, 0.12189394, 0.11705413, 142 | 0.11310926, 0.10968250, 0.10642817, 0.10327383, 0.10030391, 143 | 0.09745325, 0.09444885, 0.09134185, 0.08838148, 0.08553319, 144 | 0.08266862, 0.07978976, 0.07723785, 0.07497205, 0.07239917, 145 | 0.06951917, 0.06666249, 0.06382916, 0.06110683, 0.05851143, 146 | 0.05726153, 0.05755025] 147 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 148 | # If not given, values are interpolated from eff. 149 | # Put zeros if neccesary to match length of wvl_fluo. 150 | eff_fluo = [0.03676045, 0.05802982, 0.24514650, 0.26303514, 0.28525101, 151 | 0.30851939, 0.31902337, 0.32294533, 0.32663823, 0.32970772, 152 | 0.33945887, 0.34101182, 0.34245432, 0.34408696, 0.34525204, 153 | 0.34724839, 0.34718483, 0.34668579, 0.34635416, 0.34920922, 154 | 0.34980112, 0.35223983, 0.35487968, 0.35618225, 0.35399222, 155 | 0.35290097, 0.35243212, 0.35149441, 0.35007389, 0.34668034, 156 | 0.34256678, 0.33570312, 0.33046398, 0.32675191, 0.32395963, 157 | 0.32296660, 0.31574318, 0.31308814, 0.30418958, 0.29059534, 158 | 0.27502209, 0.26952477, 0.26141619, 0.25824876, 0.25207011, 159 | 0.23828600, 0.22154411, 0.19308241, 0.15272720, 0.13244260, 160 | 0.11866740, 0.10228386, 0.07808849, 0.07411443, 0.06382916, 161 | 0.05767816, 0.00000000] 162 | -------------------------------------------------------------------------------- /logo_showermodel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaimeRosado/ShowerModel/4c3d35f9594a2460da00ceb3435db1d7bf614b73/logo_showermodel.png -------------------------------------------------------------------------------- /postBuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python setup.py develop 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools_scm] 6 | write_to = 'src/showermodel/_version.py' 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = ShowerModel 3 | description = Modelling cosmic-ray showers, their light production and its detection 4 | author = Jaime Rosado 5 | author_email = jrosadov@ucm.es 6 | license = GPL-3.0 7 | url=https://github.com/JaimeRosado/ShowerModel 8 | long_description = file: README.md 9 | long_description_content_type = text/markdown 10 | github_project = JaimeRosado/ShowerModel 11 | classifiers = 12 | Intended Audience :: Science/Research 13 | License :: OSI Approved :: GNU General Public License v3 (GPLv3) 14 | Programming Language :: Python :: 3.7 15 | Programming Language :: Python :: 3.8 16 | Programming Language :: Python :: 3.9 17 | Programming Language :: Python :: 3.10 18 | Topic :: Scientific/Engineering :: Astronomy 19 | Topic :: Scientific/Engineering :: Physics 20 | 21 | [options] 22 | packages = find: 23 | package_dir = 24 | = src 25 | python_requires = >=3.7 26 | install_requires = 27 | numpy 28 | pandas 29 | matplotlib 30 | scipy 31 | toml 32 | ipython 33 | openpyxl 34 | 35 | [options.packages.find] 36 | where = src 37 | exclude = showermodel._dev_version 38 | 39 | [options.package_data] 40 | showermodel.constants = *.toml 41 | 42 | [options.extras_require] 43 | tests = 44 | pytest 45 | pytest-cov 46 | 47 | docs = 48 | sphinx ~= 5.0 49 | sphinx_rtd_theme 50 | sphinx_automodapi 51 | sphinx 52 | nbsphinx 53 | nbsphinx-link 54 | numpydoc 55 | jupyter 56 | notebook 57 | graphviz 58 | 59 | dev = 60 | %(tests)s 61 | %(docs)s 62 | setuptools_scm 63 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Empty setup.py file to enable editable installation. 3 | Do not change. Use setup.cfg. 4 | Based on https://github.com/cta-observatory/project-template-python-pure 5 | """ 6 | 7 | from setuptools import setup 8 | 9 | # this is a workaround for an issue in pip that prevents editable installation 10 | # with --user, see https://github.com/pypa/pip/issues/7953 11 | import site 12 | import sys 13 | 14 | 15 | site.ENABLE_USER_SITE = "--user" in sys.argv[1:] 16 | 17 | setup() 18 | -------------------------------------------------------------------------------- /src/showermodel/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from .atmosphere import Atmosphere 4 | from .track import Track 5 | from .profile import Profile 6 | from .fluorescence import Fluorescence 7 | from .cherenkov import Cherenkov 8 | from .shower import Shower 9 | from .telescope import Telescope 10 | from .projection import Projection 11 | from .signal import Signal 12 | from .observatory import Observatory, Array25, Grid 13 | from .event import Event 14 | from .image import Image 15 | 16 | from .version import __version__ 17 | 18 | __all__ = ['__version__'] 19 | -------------------------------------------------------------------------------- /src/showermodel/_dev_version/__init__.py: -------------------------------------------------------------------------------- 1 | # Try to use setuptools_scm to get the current version; this is only used 2 | # in development installations from the git repository. 3 | # see ../version.py for details 4 | try: 5 | from setuptools_scm import get_version 6 | 7 | version = get_version(root="../..", relative_to=__file__) 8 | except Exception as e: 9 | raise ImportError(f"setuptools_scm broken or not installed: {e}") from e 10 | -------------------------------------------------------------------------------- /src/showermodel/_tools.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D 7 | import showermodel.constants as ct 8 | import toml 9 | from pathlib import Path 10 | import warnings 11 | warnings.filterwarnings( 12 | 'ignore', 13 | 'Pandas doesn\'t allow columns to be created via a new attribute name', 14 | UserWarning) 15 | 16 | 17 | def show_projection(projection, profile, shower_Edep, axes, max_theta, X_mark): 18 | """ 19 | Show the projection of the shower track viewed by the telescope in both 20 | horizontal and FoV coordinates systems. 21 | """ 22 | track = projection.track 23 | telescope = projection.telescope 24 | 25 | fig = plt.figure() 26 | # Plot in horizontal coordinates 27 | ax1 = fig.add_subplot(121, projection='polar') 28 | ax1.set_title('Horizontal coordinates alt/az', y=1.1) 29 | 30 | # Plot in FoV coordinates 31 | ax2 = fig.add_subplot(122, projection='polar') 32 | ax2.set_title('FoV coordinates theta/phi', y=1.1) 33 | 34 | # Only available for shower and event 35 | if shower_Edep: 36 | # For the shower track, the point radius is proportional to the shower 37 | # size 38 | shw_Edep = profile.E_dep 39 | shw_Edep = 50. * np.sqrt(shw_Edep / shw_Edep.max()) 40 | else: 41 | shw_Edep = 20 42 | 43 | az = np.array(np.radians(projection.az)) # polar plots use radians 44 | alt = np.array(projection.alt) 45 | phi = np.array(np.radians(projection.phi)) 46 | theta = np.array(projection.theta) 47 | az_line = az.copy() 48 | alt_line = alt.copy() 49 | phi_line = phi.copy() 50 | theta_line = theta.copy() 51 | 52 | if projection.alt_i>projection.alt.max(): # descending shower 53 | az_line = np.append(az_line, np.radians(projection.az_i)) 54 | alt_line = np.append(alt_line, projection.alt_i) 55 | phi_line = np.append(phi_line, np.radians(projection.phi_i)) 56 | theta_line = np.append(theta_line, projection.theta_i) 57 | az_line = np.insert(az_line, 0, np.radians(projection.az_0)) 58 | alt_line = np.insert(alt_line, 0, projection.alt_0) 59 | phi_line = np.insert(phi_line, 0, np.radians(projection.phi_0)) 60 | theta_line = np.insert(theta_line, 0, projection.theta_0) 61 | else: # ascending shower 62 | az_line = np.insert(az_line, 0, np.radians(projection.az_i)) 63 | alt_line = np.insert(alt_line, 0, projection.alt_i) 64 | phi_line = np.insert(phi_line, 0, np.radians(projection.phi_i)) 65 | theta_line = np.insert(theta_line, 0, projection.theta_i) 66 | az_line = np.append(az_line, np.radians(projection.az_top)) 67 | alt_line = np.append(alt_line, projection.alt_top) 68 | phi_line = np.append(phi_line, np.radians(projection.phi_top)) 69 | theta_line = np.append(theta_line, projection.theta_top) 70 | 71 | ax1.scatter(az, alt, c='r', s=shw_Edep, marker='o') 72 | ax1.plot(az_line, alt_line, 'r-') 73 | ax2.scatter(phi, theta, c='r', s=shw_Edep, marker='o') 74 | ax2.plot(phi_line, theta_line, 'r-') 75 | 76 | # Coordinates of the telescope FoV limits in FoV projection 77 | phi_FoV = np.linspace(0., 360., 61) 78 | theta_FoV = np.ones_like(phi_FoV) * telescope.apert / 2. 79 | # Horizontal coordinates system 80 | alt_FoV, az_FoV = telescope.theta_phi_to_alt_az(theta_FoV, phi_FoV) 81 | if len(alt_FoV[alt_FoV<0.])>len(alt_FoV[alt_FoV>0.]): 82 | nadir = True 83 | else: 84 | nadir = False 85 | 86 | ax1.plot(np.radians(az_FoV), alt_FoV, 'g') 87 | ax2.plot(np.radians(phi_FoV), theta_FoV, 'g') 88 | 89 | if axes: 90 | # Coordinates of the limits of a 2*pi solid angle around the telescope 91 | # pointing direction in FoV projection 92 | phi_2pi = phi_FoV # = np.linspace(0., 360., 61) 93 | theta_2pi = np.ones(61) * 90. 94 | # Horizontal coordinates system 95 | alt_2pi, az_2pi = telescope.theta_phi_to_alt_az(theta_2pi, phi_2pi) 96 | ax1.plot(np.radians(az_2pi), alt_2pi, 'g--') 97 | 98 | # Coordinates of the horizon in horizontal projection 99 | az_hor = phi_FoV # = np.linspace(0., 360., 61) 100 | alt_hor = np.zeros(61) 101 | # In FoV projection 102 | theta_hor, phi_hor = telescope.altaz_to_thetaphi(alt_hor, az_hor) 103 | ax2.plot(np.radians(phi_hor), theta_hor, 'g--') 104 | 105 | if nadir: 106 | # Coordinates of north-nadir-south arc in horizontal coordinates 107 | # alt smaller than -90 deg corresponds to az = 180 deg (south) 108 | alt_ns = np.linspace(-180., 0., 61) 109 | else: 110 | # Coordinates of north-zenith-south arc in horizontal coordinates 111 | # alt greater than 90 deg corresponds to az = 180 deg (south) 112 | alt_ns = np.linspace(0., 180., 61) 113 | az_ns = alt_hor # np.zeros(61) 114 | # In FoV projection 115 | theta_ns, phi_ns = telescope.altaz_to_thetaphi(alt_ns, az_ns) 116 | ax2.plot(np.radians(phi_ns), theta_ns, 'g--') 117 | 118 | # Coordinates of east-zenith(or nadir)-west arc in horiziontal coord. 119 | # alt values smaller than -90 deg correspond to az = 270 deg (west) 120 | alt_ew = alt_ns # np.linspace(0., 180., 61) 121 | az_ew = theta_2pi # =np.ones(61) * 90. 122 | # In FoV projection 123 | theta_ew, phi_ew = telescope.altaz_to_thetaphi(alt_ew, az_ew) 124 | ax2.plot(np.radians(phi_ew), theta_ew, 'g--') 125 | 126 | # Coordinates of phi=0 arc (north-pointing-south) in FoV coord. system 127 | theta_phi0 = np.linspace(-90., 90., 61) 128 | # Negative values of theta correspond to phi = 180 degrees 129 | phi_phi0 = az_ns # =np.zeros(61) 130 | # Horizontal coordinates system 131 | alt_phi0, az_phi0 = telescope.theta_phi_to_alt_az(theta_phi0, phi_phi0) 132 | ax1.plot(np.radians(az_phi0), alt_phi0, 'g--') 133 | 134 | # Coordinates of phi=90 arc in FoV coordinates system 135 | theta_phi90 = theta_phi0 # = np.linspace(-90., 90., 61) 136 | # Negative values of theta correspond to phi = 270 degrees 137 | phi_phi90 = theta_2pi # =np.ones(61) * 90. 138 | # Horizontal coordinates system 139 | alt_phi90, az_phi90 = telescope.theta_phi_to_alt_az(theta_phi90, 140 | phi_phi90) 141 | ax1.plot(np.radians(az_phi90), alt_phi90, 'g--') 142 | 143 | if X_mark is not None: 144 | # Absolute coordinates of the shower point at the input 145 | # slanth depth X_mark 146 | # x_mark, y_mark, z_mark = telescope._abs_to_rel( 147 | # *track.X_to_xyz(X_mark)) 148 | x_mark, y_mark, z_mark = track.X_to_xyz(X_mark) 149 | r_mark, alt_mark, az_mark, theta_mark, phi_mark = telescope.spherical( 150 | x_mark, y_mark, z_mark) 151 | 152 | ax1.plot(np.radians(az_mark), alt_mark, 'bx') 153 | ax2.plot(np.radians(phi_mark), theta_mark, 'bx') 154 | 155 | if nadir: 156 | ax1.set_rmin(-90.) 157 | ax1.set_rmax(0.) 158 | else: 159 | ax1.set_rmin(90.) 160 | ax1.set_rmax(0.) 161 | # Polar angle labels in the plot refers to azimuth 162 | ax1.set_theta_zero_location('N') 163 | ax1.set_theta_direction(-1) 164 | ax1.set_rlabel_position(225) 165 | 166 | ax2.set_rmin(0.) 167 | ax2.set_rmax(max_theta) 168 | ax2.set_rlabel_position(-45) 169 | # Offset should be the north direction from right-hand direction 170 | ax2.set_theta_zero_location('E', offset=telescope._phi_right) 171 | ax2.set_theta_direction(-1) 172 | # theta is the name of the axial angle in the plot, not the 173 | # coordinate theta ! 174 | plt.tight_layout() 175 | 176 | return ax1, ax2 177 | 178 | 179 | def show_geometry(obj, observatory, mode, x_min, x_max, y_min, y_max, 180 | X_mark, shower_Edep, signal_size, tel_index, xy_proj, 181 | pointing): 182 | """ 183 | Show a shower track together with the telescope positions of an observatory 184 | in an either 2D or 3D plot. 185 | """ 186 | from .telescope import Telescope 187 | from .observatory import Observatory 188 | if not isinstance(observatory, (Telescope, Observatory)): 189 | raise ValueError('The input observatory is not valid') 190 | elif isinstance(observatory, Telescope): 191 | telescope = observatory 192 | observatory = Observatory() 193 | observatory.append(telescope) 194 | 195 | from .track import Track 196 | track = obj.track if not isinstance(obj, Track) else obj 197 | 198 | # Track points 199 | data_range = ((x_min < track.x) & (track.x < x_max) & (y_min < track.y) 200 | & (track.y < y_max)) 201 | x_track = np.array(track.x[data_range]) 202 | y_track = np.array(track.y[data_range]) 203 | z_track = np.array(track.z[data_range]) 204 | if len(x_track)==0: 205 | raise ValueError('The shower track is outside the plot frame.') 206 | 207 | # Telescope positions 208 | coords = [(telescope.x, telescope.y, telescope.z) for telescope 209 | in observatory] 210 | x_tel, y_tel, z_tel = zip(*coords) 211 | 212 | z_tel_min = min(z_tel) # z coordinate of the most lower telescope 213 | z_tel_max = max(z_tel) # z coordinate of the most upper telescope 214 | 215 | # Track line reaching observation level 216 | x_line = x_track.copy() 217 | y_line = y_track.copy() 218 | z_line = z_track.copy() 219 | # The observation level is lower than the lowest track point 220 | # if z_tel_min < z_track.min(): 221 | # x_line = np.insert(x_line, 0, 222 | # track.x0 + z_tel_min * track.ux / track.uz) 223 | # y_line = np.insert(y_line, 0, 224 | # track.y0 + z_tel_min * track.uy / track.uz) 225 | # z_line = np.insert(z_line, 0, z_tel_min) 226 | # The observation level is higher than the highest track point 227 | # if z_tel_max > z_track.max(): 228 | # x_line = np.append(x_line, track.x0 + z_tel_max * track.ux / track.uz) 229 | # y_line = np.append(y_line, track.y0 + z_tel_max * track.uy / track.uz) 230 | # z_line = np.append(z_line, z_tel_max) 231 | z_min = z_line.min() 232 | z_max = z_line.max() 233 | 234 | if shower_Edep: # Only available for shower and event 235 | # For the shower track, the point radius is proportional to Edep 236 | shw_Edep = obj.profile.E_dep[data_range] 237 | shw_Edep = 50. * np.sqrt(shw_Edep / shw_Edep.max()) 238 | else: 239 | shw_Edep = 20 240 | 241 | if signal_size: # Only available for event, when some signal is produced 242 | # For the telescope positions, the point radius is prportional to 243 | # the signal 244 | signal_size = np.array([signal.Npe_total_sum for signal 245 | in obj.signals]) 246 | signal_size_max = signal_size.max() 247 | if signal_size_max > 0.: 248 | signal_size = 50. * np.sqrt(signal_size / signal_size.max()) 249 | else: 250 | signal_size = 20 251 | else: 252 | signal_size = 20 253 | 254 | if mode == '2d': # 2d projection 255 | fig = plt.figure(figsize=(5, 5)) 256 | ax = fig.add_subplot(111) 257 | # Telescope positions in green 258 | ax.scatter(x_tel, y_tel, c='g', s=signal_size, marker='o') 259 | # Shower track line in red 260 | ax.plot(x_line, y_line, 'r-') 261 | # Shower track points in red 262 | ax.scatter(x_track, y_track, c='r', s=shw_Edep, marker='o') 263 | 264 | if tel_index: 265 | for tel_index, telescope in enumerate(observatory): 266 | ax.annotate(tel_index, (x_tel[tel_index], y_tel[tel_index])) 267 | 268 | if X_mark is not None: 269 | # Coordinates of the shower corresponding to the input slant 270 | # depth X_mark 271 | x_mark, y_mark, z_mark = track.X_to_xyz(X_mark) 272 | ax.scatter(x_mark, y_mark, c='b', marker='x') 273 | 274 | else: # 3d projection 275 | fig = plt.figure(figsize=(7, 5)) 276 | ax = fig.add_subplot(111, projection='3d') 277 | # Telescope positions in green 278 | ax.scatter(x_tel, y_tel, z_tel, c='g', s=signal_size, marker='o') 279 | ax.plot(x_line, y_line, z_line, 'r-') # Shower track line in red 280 | if xy_proj: 281 | # xy projection of shower track line in red 282 | ax.plot(x_line, y_line, 'r--', zs=z_min, zdir='z') 283 | # Shower track points in red 284 | ax.scatter(x_track, y_track, z_track, c='r', s=shw_Edep, marker='o') 285 | 286 | if pointing: 287 | # Arrows pointing to the telescope axis direction 288 | pointing = [(telescope.ux, telescope.uy, telescope.uz) 289 | for telescope in observatory] 290 | ux, uy, uz = zip(*pointing) 291 | ax.quiver(x_tel, y_tel, z_tel, ux, uy, uz, length=0.4) 292 | 293 | if X_mark is not None: 294 | # Coordinates of the shower corresponding to the input slanth 295 | # depth X_mark 296 | x_mark, y_mark, z_mark = track.X_to_xyz(X_mark) 297 | if (z_min < z_mark) & (z_mark < z_max): 298 | ax.scatter(x_mark, y_mark, z_mark, c='b', marker='x') 299 | 300 | # Auto-scale for z 301 | ax.set_zlim(z_min, z_max) 302 | ax.axes.zaxis.set_label_text('z (km)') 303 | 304 | ax.axis([x_min, x_max, y_min, y_max]) 305 | ax.axes.xaxis.set_label_text('x (km)') 306 | ax.axes.yaxis.set_label_text('y (km)') 307 | return ax 308 | 309 | 310 | def rotate(vx, vy, vz, rot_angle, x, y, z): 311 | """ 312 | Rotate the vector (x,y,z) by an angle rot_angle (positive or negative) 313 | around the axis (vx,vy,vz), where v is a unit vector. 314 | """ 315 | ct = np.cos(np.radians(rot_angle)) 316 | st = np.sin(np.radians(rot_angle)) 317 | x_rot = ((ct+vx*vx*(1.-ct)) * x + (vx*vy*(1.-ct)-vz*st) * y 318 | + (vx*vz*(1.-ct)+vy*st) * z) 319 | y_rot = ((vx*vy*(1.-ct)+vz*st) * x + (ct+vy*vy*(1.-ct)) * y 320 | + (vy*vz*(1.-ct)-vx*st) * z) 321 | z_rot = ((vx*vz*(1.-ct)-vy*st) * x + (vy*vz*(1.-ct)+vx*st) * y 322 | + (ct+vz*vz*(1.-ct)) * z) 323 | 324 | return (x_rot, y_rot, z_rot) 325 | 326 | 327 | def zr_to_theta(z, r): 328 | """ 329 | Calculate the angle theta in degrees [0, 180] of a vector with vertical 330 | projection z and modulus r, where theta is defined from the z axis. 331 | """ 332 | try: # For r being a scalar (otherwise r==0 generates an exception) 333 | if r == 0: 334 | theta = 90. * np.ones_like(z) 335 | else: 336 | z_r = np.array(z/r) 337 | z_r[z_r > 1.] = 1. 338 | theta = np.degrees(np.arccos(z_r)) 339 | # If z is also a scalar, the function returns a scalar. 340 | # Otherwise, the output is an array 341 | return 1.*theta 342 | 343 | except Exception: # For r being an array (or a Series) 344 | r = np.array(r) 345 | # For z being a scalar 346 | if not isinstance(z, (tuple, list, np.ndarray, pd.Series)): 347 | # An array of same lenght as r is generated 348 | z = np.ones_like(r) * z 349 | else: 350 | z = np.array(z) 351 | z_r = np.zeros_like(r) 352 | z_r[r != 0] = z[r != 0] / r[r != 0] 353 | z_r[z_r > 1.] = 1. 354 | 355 | theta = 90. * np.ones_like(r) 356 | theta[r != 0] = np.degrees(np.arccos(z_r[r != 0])) 357 | return theta 358 | 359 | 360 | def xy_to_phi(x, y): 361 | """ 362 | Calculate the angle phi in degrees [-90, 270) of the xy projection of a 363 | vector, where phi is defined from the x axis towards the y axis 364 | (anticlockwise). 365 | """ 366 | try: # For x being a scalar (otherwise x==0 generates an exception) 367 | if x == 0: 368 | phi = np.sign(y) * 90. 369 | else: 370 | phi = np.degrees(np.arctan(y/x)) 371 | if x < 0: 372 | phi = phi + 180. 373 | # If y is also a scalar, the function returns a scalar. 374 | # Otherwise, the output is an array 375 | return 1.*phi 376 | 377 | except Exception: # For x being an array (or a Series) 378 | x = np.array(x) 379 | # For r being a scalar 380 | if not isinstance(y, (tuple, list, np.ndarray, pd.Series)): 381 | # An array of same lenght as x is generated 382 | y = np.ones_like(x) * y 383 | else: 384 | y = np.array(y) 385 | 386 | phi = np.zeros_like(x) 387 | phi[x == 0] = np.sign(y[x == 0]) * 90. 388 | phi[x != 0] = np.degrees(np.arctan(y[x != 0] / x[x != 0])) 389 | phi[x < 0] = phi[x < 0] + 180. 390 | return phi 391 | -------------------------------------------------------------------------------- /src/showermodel/atmosphere.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import showermodel.constants as ct 6 | 7 | import warnings 8 | warnings.filterwarnings( 9 | 'ignore', 10 | 'Pandas doesn\'t allow columns to be created via a new attribute name', 11 | UserWarning) 12 | 13 | # Default values for Atmosphere 14 | _Atmosphere__h0 = ct.config['Atmosphere']['h0'] 15 | _Atmosphere__h_top = ct.config['Atmosphere'].get('h_top') # optional parameter 16 | _Atmosphere__N_steps = ct.config['Atmosphere']['N_steps'] 17 | _Atmosphere__atm_model = ct.config['Atmosphere']['atm_model'] 18 | _Atmosphere__rho_w_sl = ct.config['Atmosphere']['rho_w_sl'] 19 | _Atmosphere__h_scale = ct.config['Atmosphere']['h_scale'] 20 | 21 | 22 | # Class ####################################################################### 23 | class Atmosphere(pd.DataFrame): 24 | """ 25 | DataFrame containing an atmosphere discretization. 26 | 27 | Use sm.Atmosphere() to construct the default Atmosphere object. 28 | 29 | Parameters 30 | ---------- 31 | h0 : float, default 0.0 32 | Ground level in km above sea level. 33 | h_top : float or None, default None 34 | Upper limit in km above sea level of the atmosphere 35 | discretization. If None, the top level of the selected 36 | atmospheric model is taken. 37 | N_steps : int, default 550 38 | Number of discretization steps. 39 | atm_model : int or DataFrame, default 1 40 | Atmospheric model assuming dry air. If an int value is given, 41 | atm_model is searched either from CORSIKA atmospheric models 42 | (from 1 to 29) 43 | or a file named atm_models.toml in the working directory 44 | containing user-defined models. If a DataFrame is given, it 45 | should have two columns, one labelled as h with height in km 46 | and other labelled as X_vert or P, depending on whether 47 | vertical depth in g/cm^2 or pressure in hPa is given. 48 | rho_w_sl : float, default 7.5e-6 49 | Water-vapor density in g/cm^3 at sea level to calculate a 50 | simple exponential profile of water-vapor. Set to zero if dry 51 | air is assumed. 52 | h_scale : float, default 2.0 53 | Scale height in km to be used in the water-vapor exponential 54 | profile. 55 | 56 | Attributes 57 | ---------- 58 | h : float 59 | Column 0, height in km above sea level. 60 | X_vert : float 61 | Column 1, vertical depth in g/cm^2. 62 | rho : float 63 | Column 2, mass density in g/cm^3. 64 | temp : float 65 | Column 3, temperature in K. 66 | P : float 67 | Column 4, pressure in hPa. 68 | P_w : float 69 | Column 5, partial pressure of water vapor in hPa. 70 | E_th : float 71 | Column 6, Cherenkov energy threshold in MeV at 350 nm. 72 | r_M : float 73 | Column 7, Moliere radius in km. 74 | h0 : float 75 | Ground level in km above sea level. 76 | h_top : float 77 | Top level of the atmosphere in km above sea level. 78 | N_steps : int 79 | Number of discretization steps. 80 | h_step : float 81 | Size of discretization step in km. 82 | Xv_total : float 83 | Total vertical depth of the atmosphere. 84 | atm_model : int or DataFrame 85 | Atmospheric model assuming dry air. 86 | info : str 87 | Information about the atmospheric model. Set to df if 88 | atm_model is a DataFrame. 89 | rho_w_sl : float 90 | Water-vapor density in g/cm^3 at sea level. 91 | h_scale : float 92 | Scale height in km used in the water-vapor exponential profile. 93 | 94 | Methods 95 | ------- 96 | h_to_Xv() 97 | Get vertical depth from height. 98 | h_to_rho() 99 | Get mass density from height. 100 | Xv_to_h() 101 | Get height from vertical depth. 102 | Xv_to_rho() 103 | Get density from vertical depth. 104 | Xv_to_P() 105 | Calculate pressure from vertical depth assuming constant acceleration 106 | of gravity. 107 | P_to_Xv() 108 | Calculate vertical depth from pressure assuming constant acceleration 109 | of gravity. 110 | Xv_rho_to_P_T() 111 | Calculate pressure and temperature from vertical depth and mass 112 | density assuming constant acceleration of gravity and an ideal gas. 113 | 114 | See also 115 | -------- 116 | Track : DataFrame containing a shower track discretization. 117 | Profile : DataFrame containing a shower profile discretization. 118 | Shower : Make a discretization of a shower. 119 | """ 120 | def __init__(self, h0=__h0, h_top=__h_top, N_steps=__N_steps, 121 | atm_model=__atm_model, rho_w_sl=__rho_w_sl, 122 | h_scale=__h_scale): 123 | super().__init__( 124 | columns=['h', 'X_vert', 'rho', 'temp', 'P', 'P_w', 'E_th', 'r_M']) 125 | _atmosphere(self, h0, h_top, N_steps, atm_model, rho_w_sl, h_scale) 126 | 127 | def h_to_Xv(self, h): 128 | """ 129 | Get vertical depth in g/cm^2 from height in km above sea level. 130 | 131 | Parameters 132 | ---------- 133 | h : float or array_like 134 | Height in km. 135 | 136 | Returns 137 | ------- 138 | Xv : float or array_like 139 | """ 140 | Xv, rho = self._h_to_Xv_rho(h) 141 | return Xv 142 | 143 | def h_to_rho(self, h): 144 | """ 145 | Get mass density in g/cm^3 from height in km above sea level. 146 | 147 | Parameters 148 | ---------- 149 | h : float or array_like 150 | Height in km. 151 | 152 | Returns 153 | ------- 154 | rho : float or array_like 155 | """ 156 | Xv, rho = self._h_to_Xv_rho(h) 157 | return rho 158 | 159 | def _h_to_Xv_rho(self, h): 160 | """ 161 | Get both the vertical depth in g/cm^2 and the mass density in g/cm^3. 162 | from height in km above sea level. 163 | """ 164 | if self._model is None: 165 | # 166 | return _df_Xv_rho(h, self.atm_model) 167 | else: 168 | # _model contais the dictionary with atm_model 169 | return _model_Xv_rho(h, self._model) 170 | 171 | def Xv_to_h(self, Xv): 172 | """ 173 | Get height in km above sea level from vertical depth in g/cm^2. 174 | 175 | Parameters 176 | ---------- 177 | Xv : float or array_like 178 | Vertical depth in g/cm^2. If is outside the range of column 179 | X_vert, return None. 180 | 181 | Returns 182 | ------- 183 | h : float or array_like 184 | """ 185 | h = np.append(self.h0, self.h) 186 | h = np.append(h, self.h_top) 187 | X_vert = np.append(self.Xv_total, self.X_vert) 188 | X_vert = np.append(X_vert, self.Xv_top) 189 | if self.Xv_top==0.: 190 | X_vert[-1] = X_vert[-2] / 1000. # to avoid log(0) 191 | 192 | # An exponential atmosphere is assumed to interpolate h 193 | # The approximation is good enough for the top atmosphere too 194 | # (more or less linear) 195 | # X_vert should be ascending 196 | return 1.*np.interp(np.log(Xv), np.log(X_vert)[::-1], h[::-1], 197 | left=None, right=None) 198 | 199 | def Xv_to_rho(self, Xv): 200 | """ 201 | Get mass density in in g/cm^3 from vertical depth in g/cm^2. 202 | 203 | Parameters 204 | ---------- 205 | Xv : float or array_like 206 | Vertical depth in g/cm^2. If is outside the range of column 207 | X_vert, return None. 208 | 209 | Returns 210 | ------- 211 | rho : float or array_like 212 | """ 213 | rho = np.append(self.rho0, self.rho) 214 | rho = np.append(rho, self.rho_top) 215 | X_vert = np.append(self.Xv_total, self.X_vert) 216 | X_vert = np.append(X_vert, self.Xv_top) 217 | 218 | # An exponential atmosphere is assumed so that rho has a linear 219 | # dependence on X_vert 220 | # X_vert should be ascending 221 | return 1.*np.interp(Xv, X_vert[::-1], rho[::-1], left=None, right=None) 222 | 223 | def Xv_to_P(self, Xv): 224 | """ 225 | Calculate pressure from vertical depth assuming constant acceleration 226 | of gravity. 227 | 228 | Parameters 229 | ---------- 230 | Xv : float or array_like 231 | Vertical depth in g/cm^2. 232 | 233 | Returns 234 | ------- 235 | P : float or array_like 236 | """ 237 | # g_cm: standard acceleration of gravity in cm/s^2 238 | return 1. * ct.g_cm * Xv / 1000. # hPa (1 hPa = 1000 erg/cm^3) 239 | 240 | def P_to_Xv(self, P): 241 | """ 242 | Calculate vertical depth from pressure assuming constant acceleration 243 | of gravity. 244 | 245 | Parameters 246 | ---------- 247 | P : float or array_like 248 | Pressure in hPa. 249 | 250 | Returns 251 | ------- 252 | Xv : float or array_like 253 | """ 254 | # g_cm: standard acceleration of gravity in cm/s^2 255 | return 1. / ct.g_cm * P * 1000. # hPa (1 hPa = 1000 erg/cm^3) 256 | 257 | def Xv_rho_to_P_T(self, Xv, rho): 258 | """ 259 | Calculate pressure and temperature from vertical depth and mass 260 | density assuming constant acceleration of gravity and an ideal gas. 261 | 262 | Parameters 263 | ---------- 264 | Xv : float or array_like 265 | Vertical depth in g/cm^2. 266 | 267 | Returns 268 | ------- 269 | P : float or array_like 270 | T : float or array_like 271 | """ 272 | # M_air: air molar mass in g/mol (dry air) 273 | # R_erg: molar gas constant in erg/K/mol 274 | P = self.Xv_to_P(Xv) 275 | temp = np.zeros_like(rho) 276 | sel = rho>0. 277 | temp[sel] = (ct.M_air * ct.g_cm * self.X_vert[sel] / ct.R_erg / 278 | self.rho[sel]) 279 | 280 | return 1.*P, 1.*temp 281 | 282 | 283 | # Constructor ################################################################# 284 | def _atmosphere(atmosphere, h0, h_top, N_steps, atm_model, rho_w_sl, h_scale): 285 | """ 286 | Constructor of Atmosphere class. 287 | 288 | Parameters 289 | ---------- 290 | atmosphere : Atmosphere object 291 | h0 : float 292 | Ground level in km above sea level. 293 | h_top : float or None 294 | Top level of the atmosphere in km above sea level. 295 | N_steps : int 296 | Number of discretization steps. 297 | atm_model : int or DataFrame 298 | Atmospheric model assuming dry air. 299 | rho_w_sl : float 300 | Water-vapor density in g/cm^3 at sea level to calculate a simple 301 | exponential profile of water-vapor. Set to zero if dry air is assumed. 302 | h_scale : float 303 | Scale height in km to be used in the water-vapor exponential profile. 304 | """ 305 | # The output DataFrame includes the input parameters h0, h_top, N_steps, 306 | # model as attributes 307 | atmosphere.h0 = h0 308 | atmosphere.N_steps = N_steps 309 | atmosphere.atm_model = atm_model 310 | atmosphere.rho_w_sl = rho_w_sl 311 | atmosphere.h_scale = h_scale 312 | 313 | # Load atmospheric model 314 | if isinstance(atm_model, pd.DataFrame): # User-defined model 315 | # Data ordered by h to allow for interpolation 316 | atm_model = atm_model.sort_values(by='h', axis=0, ascending=True) 317 | atmosphere._model = None # to be used in _h_to_Xv_rho 318 | atmosphere.info = 'DataFrame' 319 | 320 | h_top_model = atm_model.h.iloc[-1] 321 | if h_top is None: 322 | h_top = h_top_model 323 | elif h_top>h_top_model: 324 | h_top = h_top_model 325 | 326 | # Array of mid heights of the discretization of the atmosphere. 327 | # h0 represents the ground level. 328 | h = np.linspace(h0, h_top, N_steps+1) 329 | h_step = h[1] - h[0] 330 | h = h - h_step/2. 331 | h[0] = h0 # keep h0 to obtain the total vertical depth Xv_total 332 | h = np.append(h, h_top) # keep h_top to obtain Xv_top 333 | 334 | # Vertical depth in g/cm2 and density in g/cm3 for h from DataFrame 335 | X_vert, rho = _df_Xv_rho(h, atm_model) 336 | 337 | else: # Get atmospheric parameters of the selected model from atm_models 338 | model = ct.atm_models.get(str(atm_model)) 339 | if model is None: 340 | raise ValueError('This atm_model is not implemented.') 341 | atmosphere._model = model 342 | atmosphere.info = model.get('info') 343 | 344 | h_top_model = _model_h_top(model) 345 | if h_top is None: 346 | h_top = h_top_model 347 | elif h_top>h_top_model: 348 | h_top = h_top_model 349 | 350 | # Array of mid heights of the discretization of the atmosphere. 351 | # h0 represents the ground level. 352 | h = np.linspace(h0, h_top, N_steps+1) 353 | h_step = h[1] - h[0] 354 | h = h - h_step/2. 355 | h[0] = h0 # keep h0 to obtain the total vertical depth Xv_total 356 | h = np.append(h, h_top) # keep h_top to obtain Xv_top 357 | 358 | # Vertical depth in g/cm2 and density in g/cm3 for h from the selected model 359 | X_vert, rho = _model_Xv_rho(h, model) 360 | 361 | atmosphere.h_top = h_top 362 | atmosphere.h_step = h_step 363 | atmosphere.Xv_total = X_vert[0] 364 | atmosphere.Xv_top = X_vert[-1] # usually Xv_top=0 365 | atmosphere.rho_0 = rho[0] 366 | atmosphere.rho_top = rho[-1] 367 | h = h[1:-1] # only mid heights are stored in columns 368 | X_vert = X_vert[1:-1] 369 | rho = rho[1:-1] 370 | atmosphere.h = h 371 | atmosphere.X_vert = X_vert 372 | atmosphere.rho = rho 373 | 374 | # It is assumed that air is an ideal gas and that the acceleration of 375 | # gravity is constant 376 | P, temp = atmosphere.Xv_rho_to_P_T(X_vert, rho) 377 | atmosphere.P = P 378 | atmosphere.temp = temp 379 | 380 | if rho_w_sl>0.: 381 | # R_erg: molar gas constant in erg/K/mol 382 | # M_w: water molar mass in g/mol 383 | # Water-vapor density in g/cm3 384 | rho_w = rho_w_sl * np.exp(-h / h_scale) 385 | P_w = ct.R_erg / ct.M_w * rho_w * temp / 1000. # hPa (1 hPa = 1000 erg/cm^3) 386 | P_w_min = 2.e-6 * P 387 | P_w[P_w0. 396 | pw = P_w[sel] 397 | p = P[sel] - pw 398 | t = temp[sel] 399 | delta[sel] = 0.00000001 * ( 400 | 8132.8589 * p / t * (1. + p * 401 | (0.000000579 - 0.0009325 / t + 0.25844 / t**2)) + 402 | 6961.9879 * pw / t * (1. + pw * (1. + 0.00037 * pw) * 403 | (-0.00237321 + 2.23366 / t - 710.792 / t**2 + 0.000775141 / t**3))) 404 | 405 | # Threshold energy for Cherenkov production at 350 nm in air 406 | # mc2: electron mass in MeV 407 | E_th = np.zeros_like(delta) 408 | E_th[sel] = ct.mc2 / np.sqrt(2.*delta[sel]) 409 | atmosphere.E_th = E_th 410 | 411 | # Moliere radius 412 | # E_c: critical energy in air in MeV 413 | # lambda_r: radiation length in air in g/cm2 414 | r_M = np.zeros_like(rho) 415 | r_M[sel] = (4.*ct.pi/ct.alpha)**0.5 * (ct.mc2 / ct.E_c * ct.lambda_r 416 | / rho[sel] * 1.e-5) 417 | atmosphere.r_M = r_M 418 | 419 | # Auxiliary functions ######################################################### 420 | def _model_Xv_rho(h, model): 421 | """ 422 | Get vertical depth in g/cm^2 and mass density in g/cm^3 from height in km 423 | above sea level from an atmospheric model. 424 | 425 | Parameters 426 | ---------- 427 | h : float or array_like 428 | Height in km. 429 | model : dict 430 | CORSIKA atmospheric model. Presently either 1 or 17. More models to 431 | be implemented. 432 | 433 | Returns 434 | ------- 435 | Xv : float or array_like 436 | Vertical depth in g/cm^2. 437 | rho : float or array_like 438 | Mass density in g/cm^3. 439 | """ 440 | h = np.array(h) 441 | Xv = np.zeros_like(h) 442 | rho = np.zeros_like(h) 443 | 444 | # For the atmospheric models used in CORSIKA, all the atmospheric 445 | # layers are exponential except for the top layer, which is linear 446 | # First, take all the layers, except the last one 447 | layers = zip(model['h_low'][:-1], # h_low in km 448 | model['h_low'][1:], # h_up in km 449 | model['a'][:-1], # g/cm2 450 | model['b'][:-1], # g/cm2 451 | model['c'][:-1]) # cm 452 | 453 | # Exponential layers 454 | for h_low, h_up, a, b, c in layers: 455 | layer = (h>=h_low) & (h=h_low) & (hh_df[-1] 520 | Xv = np.exp(np.interp(h, h_df, log_Xv_df, right=-np.inf)) 521 | 522 | # Mass density. Expected to be constant at the top 523 | rho_df = -Xv_df * (np.diff(log_Xv_df, append=0.) / 524 | np.diff(h_df, append=0.) * 1.e-5) 525 | rho_df[-1] = rho_df[-2] 526 | # Xv_df and rho_df must be ascending, but both Xv and rho are descending 527 | # rho=0 for Xv=0 (h>h_df[-1]) 528 | rho = np.interp(Xv, Xv_df[::-1], rho_df[::-1], left=0.) 529 | 530 | # If the input h is a scalar, then the function returns Xv and rho as scalars 531 | return 1.*Xv, 1.*rho 532 | 533 | def _model_h_top(model): 534 | """ 535 | Get the top height of the atmosphere. 536 | """ 537 | # Top layer (lineal) 538 | a = model['a'][-1] 539 | b = model['b'][-1] 540 | c = model['c'][-1] 541 | return a / b * c / 1.e5 542 | 543 | -------------------------------------------------------------------------------- /src/showermodel/cherenkov.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import warnings 6 | import matplotlib.pyplot as plt 7 | import showermodel.constants as ct 8 | 9 | warnings.filterwarnings( 10 | 'ignore', 11 | 'Pandas doesn\'t allow columns to be created via a new attribute name', 12 | UserWarning) 13 | 14 | # Default values for Cherenkov 15 | _Cherenkov__wvl_ini = ct.config['Signal']['wvl_ini'] 16 | _Cherenkov__wvl_fin = ct.config['Signal']['wvl_fin'] 17 | 18 | # Class ####################################################################### 19 | class Cherenkov(pd.DataFrame): 20 | """ 21 | DataFrame containing the Cherenkov light production. 22 | 23 | The Cherenkov light is evaluated in the 290 - 430 nm range. The DataFrame 24 | includes the parameters determining the angular distribution of 25 | Cherenkov emission based on the parameterization described in 26 | F. Nerling et al., Astropart. Phys. 24(2006)241. 27 | 28 | Parameters 29 | ---------- 30 | profile : Profile, mandatory 31 | Profile object to be used. 32 | 33 | Attributes 34 | ---------- 35 | N_ph : int 36 | Column 0, number of Cherenkov photons in the 290 - 430 nm range. 37 | a : float 38 | Column 1, first parameter of the angular distribution of Cherenkov emission. 39 | theta_c : float 40 | Column 2, second parameter (degrees) of the angular distribution. 41 | b : float 42 | Column 3, third parameter of the angular distribution. 43 | theta_cc : float 44 | Column 4, fourth parameter (degrees) of the angular distribution. 45 | profile : Profile 46 | atmosphere : Atmosphere 47 | 48 | Methods 49 | ------- 50 | show() 51 | Show the production of Cherenkov photons as a function of slant depth. 52 | """ 53 | def __init__(self, profile): 54 | super().__init__(columns=['N_ph', 'a', 'theta_c', 'b', 'theta_cc']) 55 | _cherenkov(self, profile) 56 | 57 | def _E_factor(self, s, Eth, Emax): 58 | """ 59 | Calculate a factor enclosing the dependence of the Cherenkov production 60 | on both the Cherenkov energy threshold E_th and the normalized energy 61 | distribution of electrons. 62 | """ 63 | _E_factor(s, Eth, Emax) 64 | 65 | def show(self): 66 | """ 67 | Show the production of Cherenkov photons in the 290 - 430 nm range as a 68 | function of slant depth. 69 | 70 | Returns 71 | ------- 72 | ax : AxesSubplot 73 | """ 74 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5)) 75 | ax.plot(self.profile.X, self.N_ph, 'r-') 76 | ax.axes.xaxis.set_label_text("Slant depth (g/cm$^2$)") 77 | ax.axes.yaxis.set_label_text( 78 | "Cherenkov production (photons·cm$^2$/g)") 79 | return ax 80 | 81 | 82 | # Constructor 83 | def _cherenkov(cherenkov, profile): 84 | """ 85 | Constructor of Cherenkov class. 86 | 87 | Parameters 88 | ---------- 89 | cherenkov : Cherenkov 90 | profile : Profile 91 | """ 92 | # cherenkov = _Cherenkov(columns=['N_ph', 'a', 'theta_c', 'b', 'theta_cc']) 93 | cherenkov.profile = profile 94 | cherenkov.atmosphere = profile.atmosphere 95 | E_th = profile.atmosphere.E_th[profile.index] 96 | C_E = _E_factor(profile.s, E_th, profile.E/2.) 97 | cherenkov.N_ph = (2. * ct.pi * ct.alpha * (1./_Cherenkov__wvl_ini - 98 | 1./_Cherenkov__wvl_fin) 99 | * 1.e12 * C_E * profile.N_ch * profile.dl) 100 | 101 | cherenkov.a = 0.42489 + 0.58371 * profile.s - 0.082373 * profile.s**2 102 | cherenkov.theta_c = np.degrees(0.62694 / E_th**0.6059) 103 | cherenkov.b = 0.055108 - 0.095587 * profile.s + 0.056952 * profile.s**2 104 | theta_cc = np.array((10.509 - 4.9444 * profile.s) * cherenkov.theta_c) 105 | theta_cc[theta_cc < 0.] = 0. 106 | cherenkov.theta_cc = theta_cc 107 | 108 | 109 | # Auxiliary functions ######################################################### 110 | def _E_factor(s, Eth, Emax): 111 | """ 112 | Calculate a factor enclosing the dependence of the Cherenkov production on 113 | both the Cherenkov energy threshold E_th and the normalized energy 114 | distribution of electrons, as they vary with the shower age: 115 | 116 | E_factor = (mc^2)^2 * int( ( 1/Eth^2 - 1/E^2 ) * f(E) * d(ln(E)) ) 117 | where f(E,s) = 1/N(s) * dN(E,s)/d(ln(E)), 118 | with int( f(E,s)*d(ln(E)) ) = 1 119 | See F. Nerling et al. Astropart. Phys. 24(2006)241. 120 | """ 121 | # Parameterization of the energy distribution of secondary electrons as a 122 | # function of the shower age f(E,s)= a0(s) * E / [E+a1(s)] / [E+a2(s)]^s 123 | s = np.array(s) 124 | Eth = np.array(Eth) 125 | a0 = 0.145098 * np.exp(6.20114 * s - 0.596851 * s**2) 126 | a1 = 6.42522 - 1.53183 * s 127 | a2 = 168.168 - 42.1368 * s 128 | 129 | def tail(Eth, E, s): 130 | return E**(-s) * (1. / s / Eth**2 - 1. / (s + 2.) / E**2) 131 | 132 | Etail = min(np.exp(8.), Emax) # exp(8.)=2981 MeV 133 | 134 | factor = np.zeros_like(Eth) 135 | for i in range(0, len(factor)): 136 | if Eth[i] > Emax: 137 | factor[i] = 0. 138 | elif Eth[i] >= Etail: # Etail < Eth < Emax 139 | # For E > Etail: f(E,s) ~= a0/E^s 140 | # factor[i] = a0[i]*2./Eth[i]**(s[i]+2.)/s[i]/(s[i]+2.) 141 | factor[i] = a0[i] * ( 142 | tail(Eth[i], Eth[i], s[i]) - tail(Eth[i], Emax, s[i])) 143 | else: # Eth < Etail <= Emax 144 | lnE = np.arange(np.log(Eth[i]), 8., 0.1) + 0.05 145 | E = np.exp(lnE) 146 | # Normalized electron energy distribution: 147 | # f(E,s) = 1/N(s) * dN(E,s)/d(ln(E)), 148 | # with int( f(E,s)*d(ln(E)) ) = 1 149 | f = a0[i] * E / (E + a1[i]) / (E + a2[i])**s[i] 150 | factor[i] = np.sum((1. / Eth[i]**2 - 1. / E**2) * f) * 0.1 151 | # factor[i] = (factor[i] + a0[i] / Etail**s[i] 152 | # * (1. / s[i] / Eth[i]**2 - 1. / (s[i]+2.) / Etail**2)) 153 | if Etail < Emax: 154 | factor[i] = factor[i] + a0[i] * ( 155 | tail(Eth[i], Etail, s[i]) - tail(Eth[i], Emax, s[i])) 156 | 157 | return ct.mc2**2 * factor 158 | -------------------------------------------------------------------------------- /src/showermodel/constants/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | from scipy import constants as ct 4 | import toml 5 | from pathlib import Path 6 | 7 | # Configuration file with default parameters of classes 8 | try: 9 | config = toml.load("./showermodel_config.toml") # User defined 10 | except Exception: 11 | config_file = Path(__file__).parent.joinpath("./showermodel_config.toml") 12 | config = toml.load(config_file) 13 | 14 | # Atmospheric model parameters 15 | try: 16 | atm_models = toml.load("./atm_models.toml") # User defined 17 | except Exception: 18 | atm_file = Path(__file__).parent.joinpath('./atm_models.toml') 19 | atm_models = toml.load(atm_file) 20 | 21 | # Fluorescence model parameters 22 | fluo_file = Path(__file__).parent.joinpath("./fluorescence_model.toml") 23 | fluo_model = toml.load(fluo_file) 24 | 25 | # Telescope data 26 | try: 27 | tel_data = toml.load("./tel_data.toml") # User defined 28 | except Exception: 29 | tel_file = Path(__file__).parent.joinpath('./tel_data.toml') 30 | tel_data = toml.load(tel_file) 31 | 32 | # Constants 33 | pi = ct.pi # Pi 34 | M_air = 28.9647 # molar mass of air in g/mol 35 | M_w = 18.01528 # molar mass of water in g/mol 36 | g_cm = 100.*ct.g # acceleration of gravity in cm/s^2 37 | R_erg = ct.R / ct.erg # molar gas constant in erg/K/mol 38 | mc2, *foo = ct.physical_constants['electron mass energy equivalent in MeV'] # MeV 39 | alpha = ct.alpha # fine-structure constant 40 | lambda_r = 36.7 # radiation length in g/cm^2 in air 41 | E_c = 81.0 # critial energy in MeV in air 42 | c_km_us = ct.c / 1.e9 # speed of light in km/us 43 | -------------------------------------------------------------------------------- /src/showermodel/constants/atm_models.toml: -------------------------------------------------------------------------------- 1 | # CORSIKA atmospheric models (ignoring water vapor) 2 | # They consist in 5 layers. In the first 4 layers, the density follows 3 | # an exponential dependence on the altitude, so the vertical depth is 4 | # X_vert = a + b * exp(-h / c) 5 | # In the fifth layer, X_vert decreases linearly with height 6 | # X_vert = a - b * h / c 7 | # Pressure and temperature are calculated assuming an ideal gas 8 | # (with molar mass of dry air) and constant acceleration of gravity. 9 | 10 | [1] 11 | # Optional paramter 12 | info = "U.S. standard atmosphere as parameterized by Linsley" 13 | 14 | # Lower limit in km of the 5 layers. 15 | # The upper limit of the fifth one is h_limit = a * c / b 16 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 17 | 18 | # Parameter a in g/cm2 19 | a = [-186.555305, -94.919, 0.61289, 0.0, 0.01128292] 20 | 21 | # Parameter b in g/cm2 22 | b = [1222.6562, 1144.9069, 1305.5948, 540.1778, 1.0] 23 | 24 | # Parameter c in cm 25 | c = [994186.38, 878153.55, 636143.04, 772170.16, 1.0e9] 26 | 27 | 28 | [2] 29 | # Optional paramter 30 | info = "AT115 Central European atmosphere for Jan. 15, 1993" 31 | 32 | # Lower limit in km of the 5 layers. 33 | # The upper limit of the fifth one is h_limit = a * c / b 34 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 35 | 36 | # Parameter a in g/cm2 37 | a = [-118.1277, -154.258, 0.4191499, 5.4094056e-4, 0.01128292] 38 | 39 | # Parameter b in g/cm2 40 | b = [1173.9861, 1205.7625, 1386.7807, 555.8935, 1.0] 41 | 42 | # Parameter c in cm 43 | c = [919546.0, 963267.92, 614315.0, 739059.6, 1.0e9] 44 | 45 | 46 | [3] 47 | # Optional paramter 48 | info = "AT223 Central European atmosphere for Feb. 23, 1993" 49 | 50 | # Lower limit in km of the 5 layers. 51 | # The upper limit of the fifth one is h_limit = a * c / b 52 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 53 | 54 | # Parameter a in g/cm2 55 | a = [-195.837264, -50.4128778, 0.345594007, 5.46207e-4, 0.01128292] 56 | 57 | # Parameter b in g/cm2 58 | b = [1240.48, 1117.85, 1210.9, 608.2128, 1.0] 59 | 60 | # Parameter c in cm 61 | c = [933697.0, 765229.0, 636790.0, 733793.8, 1.0e9] 62 | 63 | 64 | [4] 65 | # Optional paramter 66 | info = "AT511 Central European atmosphere for May 11, 1993" 67 | 68 | # Lower limit in km of the 5 layers. 69 | # The upper limit of the fifth one is h_limit = a * c / b 70 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 71 | 72 | # Parameter a in g/cm2 73 | a = [-253.95047, -128.97714, 0.353207, 5.526876e-4, 0.01128292] 74 | 75 | # Parameter b in g/cm2 76 | b = [1285.2782, 1173.1616, 1320.4561, 680.6803, 1.0] 77 | 78 | # Parameter c in cm 79 | c = [1088310.0, 935485.0, 635137.0, 727312.6, 1.0e9] 80 | 81 | 82 | [5] 83 | # Optional paramter 84 | info = "AT616 Central European atmosphere for June 16, 1993" 85 | 86 | # Lower limit in km of the 5 layers. 87 | # The upper limit of the fifth one is h_limit = a * c / b 88 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 89 | 90 | # Parameter a in g/cm2 91 | a = [-208.12899, -120.26179, 0.31167036, 5.591489e-4, 0.01128292] 92 | 93 | # Parameter b in g/cm2 94 | b = [1251.474, 1173.321, 1307.826, 763.1139, 1.0] 95 | 96 | # Parameter c in cm 97 | c = [1032310.0, 925528.0, 645330.0, 720851.4, 1.0e9] 98 | 99 | 100 | [6] 101 | # Optional paramter 102 | info = "AT822 Central European atmosphere for Aug. 22, 1993" 103 | 104 | # Lower limit in km of the 5 layers. 105 | # The upper limit of the fifth one is h_limit = a * c / b 106 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 107 | 108 | # Parameter a in g/cm2 109 | a = [-77.875723, -214.96818, 0.3721868, 5.5309816e-4, 0.01128292] 110 | 111 | # Parameter b in g/cm2 112 | b = [1103.3362, 1226.5761, 1382.6933, 685.6073, 1.0] 113 | 114 | # Parameter c in cm 115 | c = [932077.0, 1109960.0, 630217.0, 726901.3, 1.0e9] 116 | 117 | 118 | [7] 119 | # Optional paramter 120 | info = "AT1014 Central European atmosphere for Oct. 14, 1993" 121 | 122 | # Lower limit in km of the 5 layers. 123 | # The upper limit of the fifth one is h_limit = a * c / b 124 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 125 | 126 | # Parameter a in g/cm2 127 | a = [-242.56651, -103.21398, 0.3349752, 5.527485e-4, 0.01128292] 128 | 129 | # Parameter b in g/cm2 130 | b = [1262.7013, 1139.0249, 1270.2886, 681.4061, 1.0] 131 | 132 | # Parameter c in cm 133 | c = [1059360.0, 888814.0, 639902.0, 727251.8, 1.0e9] 134 | 135 | 136 | [8] 137 | # Optional paramter 138 | info = "AT1224 Central European atmosphere for Dec. 24, 1993" 139 | 140 | # Lower limit in km of the 5 layers. 141 | # The upper limit of the fifth one is h_limit = a * c / b 142 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 143 | 144 | # Parameter a in g/cm2 145 | a = [-195.34842, -71.997323, 0.3378142, 5.48224e-4, 0.01128292] 146 | 147 | # Parameter b in g/cm2 148 | b = [1210.4, 1103.8629, 1215.3545, 629.7611, 1.0] 149 | 150 | # Parameter c in cm 151 | c = [970276.0, 820946.0, 639074.0, 731776.5, 1.0e9] 152 | 153 | 154 | # [9] Not existing. User-defined model in COSRIKA 155 | 156 | # [10] Not existing. User-defined model in COSRIKA 157 | 158 | 159 | [11] 160 | # Optional paramter 161 | info = "South pole atmosphere for March 31, 1997 (MSIS-90-E)" 162 | 163 | # Lower limit in km of the 5 layers. 164 | # The upper limit of the fifth one is h_limit = a * c / b 165 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 166 | 167 | # Parameter a in g/cm2 168 | a = [-137.656, -37.9610, 0.222659, -0.000616201, 0.00207722] 169 | 170 | # Parameter b in g/cm2 171 | b = [1130.74, 1052.05, 1137.21, 442.512, 1.0] 172 | 173 | # Parameter c in cm 174 | c = [867358.0, 741208.0, 633846.0, 759850.0, 5.4303203e9] 175 | 176 | 177 | [12] 178 | # Optional paramter 179 | info = "South pole atmosphere for Jul. 01, 1997 (MSIS-90-E)" 180 | 181 | # Lower limit in km of the 5 layers. 182 | # The upper limit of the fifth one is h_limit = a * c / b 183 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 184 | 185 | # Parameter a in g/cm2 186 | a = [-163.331, -65.3713, 0.402903, -0.000479198, 0.00188667] 187 | 188 | # Parameter b in g/cm2 189 | b = [1183.70, 1108.06, 1424.02, 207.595, 1.0] 190 | 191 | # Parameter c in cm 192 | c = [875221.0, 753213.0, 545846.0, 793043.0, 5.9787908e9] 193 | 194 | 195 | [13] 196 | # Optional paramter 197 | info = "South pole atmosphere for Oct. 01, 1997 (MSIS-90-E)" 198 | 199 | # Lower limit in km of the 5 layers. 200 | # The upper limit of the fifth one is h_limit = a * c / b 201 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 202 | 203 | # Parameter a in g/cm2 204 | a = [-142.801, -70.1538, 1.14855, -0.000910269, 0.00152236] 205 | 206 | # Parameter b in g/cm2 207 | b = [1177.19, 1125.11, 1304.77, 433.823, 1.0] 208 | 209 | # Parameter c in cm 210 | c = [861745.0, 765925.0, 581351.0, 775155.0, 7.4095699e9] 211 | 212 | 213 | [14] 214 | # Optional paramter 215 | info = "South pole atmosphere for Dec. 31, 1997 (MSIS-90-E)" 216 | 217 | # Lower limit in km of the 5 layers. 218 | # The upper limit of the fifth one is h_limit = a * c / b 219 | h_low = [0.0, 4.0, 10.0, 40.0, 100.0] 220 | 221 | # Parameter a in g/cm2 222 | a = [-128.601, -39.5548, 1.13088, -0.00264960, 0.00192534] 223 | 224 | # Parameter b in g/cm2 225 | b = [1139.99, 1073.82, 1052.96, 492.503, 1.0] 226 | 227 | # Parameter c in cm 228 | c = [861913.0, 744955.0, 675928.0, 829627.0, 5.8587010e9] 229 | 230 | 231 | [15] 232 | # Optional paramter 233 | info = "South pole atmosphere for January after Lipari" 234 | 235 | # Lower limit in km of the 5 layers. 236 | # The upper limit of the fifth one is h_limit = a * c / b 237 | h_low = [0.0, 2.67, 5.33, 8.0, 100.0] 238 | 239 | # Parameter a in g/cm2 240 | a = [-113.139, -79.0635, -54.3888, 0.0, 0.00421033] 241 | 242 | # Parameter b in g/cm2 243 | b = [1133.10, 1101.20, 1085.00, 1098.00, 1.0] 244 | 245 | # Parameter c in cm 246 | c = [861730.0, 826340.0, 790950.0, 682800.0, 2.6798156e9] 247 | 248 | 249 | [16] 250 | # Optional paramter 251 | info = "South pole atmosphere for August after Lipari" 252 | 253 | # Lower limit in km of the 5 layers. 254 | # The upper limit of the fifth one is h_limit = a * c / b 255 | h_low = [0.0, 6.67, 13.33, 20.0, 100.0] 256 | 257 | # Parameter a in g/cm2 258 | a = [-59.0293, -21.5794, -7.14839, 0.0, 0.000190175] 259 | 260 | # Parameter b in g/cm2 261 | b = [1079.00, 1071.90, 1182.00, 1647.10, 1.0] 262 | 263 | # Parameter c in cm 264 | c = [764170.0, 699910.0, 635650.0, 551010.0, 59.329575e9] 265 | 266 | 267 | [17] 268 | # Optional paramter 269 | model = "U.S. standard atmosphere as parameterized by Keilhauer" 270 | 271 | # Lower limit in km of the 5 layers. 272 | # The upper limit of the fifth one is h_limit = a * c / b 273 | h_low = [0.0, 7.0, 11.4, 37.0, 100.0] 274 | 275 | # Parameter a in g/cm2 276 | a = [-149.801663, -57.932486, 0.63631894, 4.3545369e-4, 0.01128292] 277 | 278 | # Parameter b in g/cm2 279 | b = [1183.6071, 1143.0425, 1322.9748, 655.67307, 1.0] 280 | 281 | # Parameter c in cm 282 | c = [954248.34, 800005.34, 629568.93, 737521.77, 1.0e9] 283 | 284 | 285 | [18] 286 | # Optional paramter 287 | model = "Malargüe GDAS model for January after Will/Keilhauer" 288 | 289 | # Lower limit in km of the 5 layers. 290 | # The upper limit of the fifth one is h_limit = a * c / b 291 | h_low = [0.0, 9.4, 15.3, 31.6, 100.0] 292 | 293 | # Parameter a in g/cm2 294 | a = [-136.72575606, -31.636643044, 1.8890234035, 3.9201867984e-4, 0.01128292] 295 | 296 | # Parameter b in g/cm2 297 | b = [1174.8298334, 1204.8233453, 1637.7703583, 735.96095023, 1.0] 298 | 299 | # Parameter c in cm 300 | c = [982815.95248, 754029.87759, 594416.83822, 733974.36972, 1.0e9] 301 | 302 | 303 | [19] 304 | # Optional paramter 305 | model = "Malargüe GDAS model for February after Will/Keilhauer" 306 | 307 | # Lower limit in km of the 5 layers. 308 | # The upper limit of the fifth one is h_limit = a * c / b 309 | h_low = [0.0, 9.2, 15.4, 31.0, 100.0] 310 | 311 | # Parameter a in g/cm2 312 | a = [-137.25655862, -31.793978896, 2.0616227547, 4.1243062289e-4, 0.01128292] 313 | 314 | # Parameter b in g/cm2 315 | b = [1176.0907565, 1197.8951104, 1646.4616955, 755.18728657, 1.0] 316 | 317 | # Parameter c in cm 318 | c = [981369.6125, 756657.65383, 592969.89671, 731345.88332, 1.0e9] 319 | 320 | 321 | [20] 322 | # Optional paramter 323 | model = "Malargüe GDAS model for March after Will/Keilhauer" 324 | 325 | # Lower limit in km of the 5 layers. 326 | # The upper limit of the fifth one is h_limit = a * c / b 327 | h_low = [0.0, 9.6, 15.2, 30.7, 100.0] 328 | 329 | # Parameter a in g/cm2 330 | a = [-132.36885162, -29.077046629, 2.090501509, 4.3534337925e-4, 0.01128292] 331 | 332 | # Parameter b in g/cm2 333 | b = [1172.6227784, 1215.3964677, 1617.0099282, 769.51991638, 1.0] 334 | 335 | # Parameter c in cm 336 | c = [972654.0563, 742769.2171, 595342.19851, 728921.61954, 1.0e9] 337 | 338 | 339 | [21] 340 | # Optional paramter 341 | model = "Malargüe GDAS model for April after Will/Keilhauer" 342 | 343 | # Lower limit in km of the 5 layers. 344 | # The upper limit of the fifth one is h_limit = a * c / b 345 | h_low = [0.0, 10.0, 14.9, 32.6, 100.0] 346 | 347 | # Parameter a in g/cm2 348 | a = [-129.9930412, -21.847248438, 1.5211136484, 3.9559055121e-4, 0.01128292] 349 | 350 | # Parameter b in g/cm2 351 | b = [1172.3291878, 1250.2922774, 1542.6248413, 713.1008285, 1.0] 352 | 353 | # Parameter c in cm 354 | c = [962396.5521, 711452.06673, 603480.61835, 735460.83741, 1.0e9] 355 | 356 | 357 | [22] 358 | # Optional paramter 359 | model = "Malargüe GDAS model for May after Will/Keilhauer" 360 | 361 | # Lower limit in km of the 5 layers. 362 | # The upper limit of the fifth one is h_limit = a * c / b 363 | h_low = [0.0, 10.2, 15.1, 35.9, 100.0] 364 | 365 | # Parameter a in g/cm2 366 | a = [-125.11468467, -14.591235621, 0.93641128677, 3.2475590985e-4, 0.01128292] 367 | 368 | # Parameter b in g/cm2 369 | b = [1169.9511302, 1277.6768488, 1493.5303781, 617.9660747, 1.0] 370 | 371 | # Parameter c in cm 372 | c = [947742.88769, 685089.57509, 609640.01932, 747555.95526, 1.0e9] 373 | 374 | 375 | [23] 376 | # Optional paramter 377 | model = "Malargüe GDAS model for June after Will/Keilhauer" 378 | 379 | # Lower limit in km of the 5 layers. 380 | # The upper limit of the fifth one is h_limit = a * c / b 381 | h_low = [0.0, 10.1, 16.0, 36.7, 100.0] 382 | 383 | # Parameter a in g/cm2 384 | a = [-126.17178851, -7.7289852811, 0.81676828638, 3.1947676891e-4, 0.01128292] 385 | 386 | # Parameter b in g/cm2 387 | b = [1171.0916276, 1295.3516434, 1455.3009344, 595.11713507, 1.0] 388 | 389 | # Parameter c in cm 390 | c = [940102.98842, 661697.57543, 612702.0632, 749976.26832, 1.0e9] 391 | 392 | 393 | [24] 394 | # Optional paramter 395 | model = "Malargüe GDAS model for July after Will/Keilhauer" 396 | 397 | # Lower limit in km of the 5 layers. 398 | # The upper limit of the fifth one is h_limit = a * c / b 399 | h_low = [0.0, 9.6, 16.5, 37.4, 100.0] 400 | 401 | # Parameter a in g/cm2 402 | a = [-126.17216789, -8.6182537514, 0.74177836911, 2.9350702097e-4, 0.01128292] 403 | 404 | # Parameter b in g/cm2 405 | b = [1172.7340688, 1258.9180079, 1450.0537141, 583.07727715, 1.0] 406 | 407 | # Parameter c in cm 408 | c = [934649.58886, 672975.82513, 614888.52458, 752631.28536, 1.0e9] 409 | 410 | 411 | [25] 412 | # Optional paramter 413 | model = "Malargüe GDAS model for August after Will/Keilhauer" 414 | 415 | # Lower limit in km of the 5 layers. 416 | # The upper limit of the fifth one is h_limit = a * c / b 417 | h_low = [0.0, 9.6, 15.9, 36.3, 100.0] 418 | 419 | # Parameter a in g/cm2 420 | a = [-123.27936204, -10.051493041, 0.84187346153, 3.2422546759e-4, 0.01128292] 421 | 422 | # Parameter b in g/cm2 423 | b = [1169.763036, 1251.0219808, 1436.6499372, 627.42169844, 1.0] 424 | 425 | # Parameter c in cm 426 | c = [931569.97625, 678861.75136, 617363.34491, 746739.16141, 1.0e9] 427 | 428 | 429 | [26] 430 | # Optional paramter 431 | model = "Malargüe GDAS model for September after Will/Keilhauer" 432 | 433 | # Lower limit in km of the 5 layers. 434 | # The upper limit of the fifth one is h_limit = a * c / b 435 | h_low = [0.0, 9.5, 16.2, 37.2, 100.0] 436 | 437 | # Parameter a in g/cm2 438 | a = [-126.94494665, -9.5556536981, 0.74939405052, 2.9823116961e-4, 0.01128292] 439 | 440 | # Parameter b in g/cm2 441 | b = [1174.8676453, 1251.5588529, 1440.8257549, 606.31473165, 1.0] 442 | 443 | # Parameter c in cm 444 | c = [936953.91919, 678906.60516, 618132.60561, 750154.67709, 1.0e9] 445 | 446 | 447 | [27] 448 | # Optional paramter 449 | model = "Malargüe GDAS model for October after Will/Keilhauer" 450 | 451 | # Lower limit in km of the 5 layers. 452 | # The upper limit of the fifth one is h_limit = a * c / b 453 | h_low = [0.0, 9.5, 15.5, 36.5, 100.0] 454 | 455 | # Parameter a in g/cm2 456 | a = [-133.13151125, -13.973209265, 0.8378263431, 3.111742176e-4, 0.01128292] 457 | 458 | # Parameter b in g/cm2 459 | b = [1176.9833473, 1244.234531, 1464.0120855, 622.11207419, 1.0] 460 | 461 | # Parameter c in cm 462 | c = [954151.404, 692708.89816, 615439.43936, 747969.08133, 1.0e9] 463 | 464 | 465 | [28] 466 | # Optional paramter 467 | model = "Malargüe GDAS model for November after Will/Keilhauer" 468 | 469 | # Lower limit in km of the 5 layers. 470 | # The upper limit of the fifth one is h_limit = a * c / b 471 | h_low = [0.0, 9.6, 15.3, 34.6, 100.0] 472 | 473 | # Parameter a in g/cm2 474 | a = [-134.72208165, -18.172382908, 1.1159806845, 3.5217025515e-4, 0.01128292] 475 | 476 | # Parameter b in g/cm2 477 | b = [1175.7737972, 1238.9538504, 1505.1614366, 670.64752105, 1.0] 478 | 479 | # Parameter c in cm 480 | c = [964877.07766, 706199.57502, 610242.24564, 741412.74548, 1.0e9] 481 | 482 | 483 | [29] 484 | # Optional paramter 485 | model = "Malargüe GDAS model for December after Will/Keilhauer" 486 | 487 | # Lower limit in km of the 5 layers. 488 | # The upper limit of the fifth one is h_limit = a * c / b 489 | h_low = [0.0, 9.6, 15.6, 33.3, 100.0] 490 | 491 | # Parameter a in g/cm2 492 | a = [-135.40825209, -22.830409026, 1.4223453493, 3.7512921774e-4, 0.01128292] 493 | 494 | # Parameter b in g/cm2 495 | b = [1174.644971, 1227.2753683, 1585.7130562, 691.23389637, 1.0] 496 | 497 | # Parameter c in cm 498 | c = [973884.44361, 723759.74682, 600308.13983, 738390.20525, 1.0e9] 499 | -------------------------------------------------------------------------------- /src/showermodel/constants/fluorescence_model.toml: -------------------------------------------------------------------------------- 1 | # The fluorescence parameterization is described in 2 | # D. Morcuende et al., Astropart. Phys. 107(2019)26 and references therein. 3 | 4 | # Fluorescence yield of the reference 337 nm band 5 | # at pressure P0 and temperature T0 (dry air) 6 | Y0_337 = 7.04 # photons/MeV 7 | 8 | # Reference atmospheric conditions (dry air) 9 | P0 = 800.0 # hPa 10 | T0 = 293.0 # K 11 | 12 | # Fluorescence bands in nm 13 | wvl = [281, 282, 296, 298, 302, 308, 312, 14 | 314, 316, 318, 327, 329, 331, 334, 15 | 337, 346, 350, 354, 358, 366, 367, 16 | 371, 376, 381, 386, 388, 389, 391, 17 | 394, 400, 405, 414, 420, 424, 427, 18 | 428, 434, 436, 442, 449, 457, 460, 19 | 465, 467, 471, 481, 492, 503, 515, 20 | 523, 531, 545, 570, 575, 586, 594, 21 | 666] 22 | 23 | # Relative intensity of each band at reference atmospheric conditions 24 | I_rel = [0.0018, 0.0030, 0.0516, 0.0277, 0.0041, 0.0144, 0.0724, 25 | 0.1105, 0.3933, 0.0046, 0.0080, 0.0380, 0.0215, 0.0402, 26 | 1.0000, 0.0174, 0.0279, 0.2135, 0.6741, 0.0113, 0.0054, 27 | 0.0497, 0.1787, 0.2720, 0.0050, 0.0117, 0.0083, 0.2800, 28 | 0.0336, 0.0838, 0.0807, 0.0049, 0.0175, 0.0104, 0.0708, 29 | 0.0494, 0.0223, 0.0017, 0.0056, 0.0072, 0.0091, 0.0002, 30 | 0.0066, 0.0053, 0.0193, 0.0024, 0.0025, 0.0012, 0.0016, 31 | 0.0033, 0.0006, 0.0003, 0.0001, 0.0003, 0.0005, 0.0001, 32 | 0.0001] 33 | 34 | # Quenching pressure of dry air in hPa 35 | PP0 = [19.00, 20.70, 18.50, 17.30, 21.00, 21.00, 18.70, 36 | 12.27, 11.88, 21.00, 19.00, 20.70, 16.90, 15.50, 37 | 15.89, 21.00, 15.20, 12.70, 15.39, 21.00, 19.00, 38 | 14.80, 12.82, 16.51, 19.00, 7.60, 3.90, 2.94, 39 | 13.70, 13.60, 17.80, 19.00, 13.80, 3.90, 6.38, 40 | 2.89, 15.89, 19.00, 20.70, 12.27, 11.88, 3.90, 41 | 3.90, 15.89, 2.94, 12.27, 11.88, 15.89, 3.90, 42 | 2.94, 11.88, 15.89, 3.90, 3.90, 2.94, 15.89, 43 | 2.94] 44 | 45 | # Quenching pressure of water vapor in hPa 46 | PPw = [0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 47 | 1.20, 1.10, 0.00, 0.00, 0.00, 0.00, 0.00, 48 | 1.28, 0.00, 1.50, 1.27, 1.30, 0.00, 0.00, 49 | 1.30, 1.10, 1.40, 0.00, 0.00, 0.00, 0.33, 50 | 1.20, 1.10, 1.50, 0.00, 0.00, 0.00, 0.00, 51 | 0.60, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 52 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 53 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 54 | 0.00] 55 | 56 | # Correction parameter for temperature dependence 57 | a = [ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 58 | -0.13, -0.19, 0.00, 0.00, 0.00, 0.00, 0.00, 59 | -0.35, 0.00, -0.38, -0.22, -0.35, 0.00, 0.00, 60 | -0.24, -0.17, -0.34, 0.00, 0.00, 0.00, -0.79, 61 | -0.20, -0.20, -0.37, 0.00, 0.00, 0.00, 0.00, 62 | -0.54, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 63 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 64 | 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 65 | 0.00] -------------------------------------------------------------------------------- /src/showermodel/constants/showermodel_config.toml: -------------------------------------------------------------------------------- 1 | [Atmosphere] # For Atmosphere 2 | # Ground level in km above sea level. 3 | h0 = 0.0 # km 4 | 5 | # Upper limit in km above sea level of the atmosphere discretization. 6 | # If commented, the top level of the selected atmospheric model is taken. 7 | #h_top = 112.8292 # km 8 | 9 | # Number of discretization steps. 10 | N_steps = 550 11 | 12 | # CORSIKA atmospheric model (from 1 to 29, except 9 and 10). See atm_models.toml. 13 | atm_model = 1 14 | 15 | # Simple model of water-vapor profile from recommendation ITU-R P.835-6. 16 | # The density is assumed to follow an exponential dependence on the altitude 17 | # rho_w = rho_w_sl * exp(-h / h_scale) 18 | # up to an altitude where the maxing ratio P_w / P = 2.0e-6. 19 | # Above this altitude, the mixing ratio is assumed to be constant. 20 | # Water pessure P_w is calculated from density assuming an ideal gas knowing 21 | # the temperature and the molar mass of water. 22 | # 23 | # Water-vapor density in g/cm3 at sea level. Set to 0 if dry air is assumed. 24 | rho_w_sl = 7.5e-6 # g/cm3 25 | # Scale height in km. 26 | h_scale = 2.0 # km 27 | 28 | 29 | [Shower] # For Track, Profile and Shower 30 | # Zenith angle in degrees of the apparent position of the source. 31 | theta = 0.0 32 | 33 | # Altitude in degrees of the apperent position of the source. 34 | # If commented, theta is used. If given, theta is overwritten. 35 | #alt = 90.0 36 | 37 | # Azimuth angle (from north, clockwise) in degrees of the apparent position of the source. 38 | az = 0.0 39 | 40 | # East and north coordinates in km of shower impact point on ground (z=0). 41 | x0 = 0.0 # km 42 | y0 = 0.0 # km 43 | 44 | # East, north and height coordinates in km of the first interaction point of the shower. 45 | # If zi==None, xi and yi are ignored and the shower impacts at (x0, y0, 0) on ground. 46 | # If zi is given, x0 and y0 are ignored and the shower starts at (xi,yi,zi). 47 | xi = 0.0 # km 48 | yi = 0.0 # km 49 | #zi = 100.0 # km 50 | 51 | # Energy of the primary particle in MeV. 52 | E = 10000000.0 # MeV 53 | 54 | # Profile model: 'Greisen' or 'Gaisser-Hillas' 55 | prf_model = 'Greisen' 56 | 57 | # Slant depth in g/cm2 at shower maximum. 58 | # If None, a typical value of X_max for gamma or proton showers is calculated from the radiation length. 59 | #X_max = 430.0 # g/cm2 60 | 61 | # X0 and Lambda parameters in g/cm2 to be used when prf_model=='Gaisser-Hillas'. 62 | # If None, typical values for the input energy are used. 63 | #X0_GH = 1.5 # g/cm2 64 | #lambda_GH = 77.3 # g/cm2 65 | 66 | 67 | [Signal] # For Signal, Event, Cherenkov and Fluorescence 68 | # Include the atmospheric transmision to transport photons. 69 | atm_trans = true 70 | 71 | # Include the telescope efficiency to calculate the signal. 72 | # If False, 100% efficiency is assumed for a given wavelenght interval. 73 | # For Signal and Event. 74 | tel_eff = true 75 | 76 | # Initial-final wavelengths and step size in nm of the wavelength interval to calculate the light production 77 | # in the Cherenkov and Fluorescence classes, as well as the signal for tel_eff=false in Signal and Event. 78 | wvl_ini = 290.0 # nm 79 | wvl_fin = 430.0 # nm 80 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 81 | 82 | 83 | [Image] # For Image 84 | # Use a NKG lateral profile to spread the signal. 85 | # If False, a linear shower is assumed. 86 | lat_profile = true 87 | 88 | # Night sky background in MHz/m^2/deg^2 (photoelectrons). 89 | NSB = 40.0 # MHz/m^2/deg^2 90 | 91 | 92 | [Telescope] 93 | # East, north and height coordinates of the telescope in km. 94 | x = 0.0 # km 95 | y = 0.0 # km 96 | z = 0.0 # km 97 | 98 | # Zenith angle in degrees of the telescope pointing direction. 99 | theta = 0.0 # deg 100 | 101 | # Altitude in degrees of the telescope pointing direction. 102 | # If commented, theta is used. If given, theta is overwritten. 103 | #alt = 90.0 # deg 104 | 105 | # Azimuth angle (from north, clockwise) in degrees of the telescope pointing direction. 106 | az = 0.0 # deg 107 | 108 | # Type of telescope to be searched from tel_data.toml. 109 | tel_type = 'generic' 110 | 111 | 112 | [Observatory] 113 | # Name given to the observatory. Default to None. 114 | #obs_name = 'Generic' 115 | 116 | 117 | [Array25] 118 | # Name given to the observatory. 119 | obs_name = 'Array25' 120 | 121 | # Type of telescope to be used by default (when telescope=None). 122 | tel_type = 'IACT' 123 | 124 | # East, north and height coordinates in km of the center of the array. 125 | x_c = 0.0 # km 126 | y_c = 0.0 # km 127 | z_c = 0.0 # km 128 | 129 | # Radius in km of the array. 130 | R = 0.341 # km 131 | 132 | # Rotation angle in degrees of the array (clockwise). 133 | rot_angle = 0.0 # deg 134 | 135 | 136 | [Grid] 137 | # Name given to the observatory. 138 | obs_name = 'Grid' 139 | 140 | # Type of telescope to be used by default (when telescope=None). 141 | tel_type = 'GridElement' 142 | 143 | # East, north and height coordinates in km of the center of the grid. 144 | x_c = 0.0 # km 145 | y_c = 0.0 # km 146 | z_c = 0.0 # km 147 | 148 | # Size of the grid in km across the x and y directions. 149 | size_x = 2.0 # km 150 | size_y = 2.0 # km 151 | 152 | # Number of cells across the x and y directions. 153 | N_x = 10 154 | N_y = 10 155 | -------------------------------------------------------------------------------- /src/showermodel/constants/tel_data.toml: -------------------------------------------------------------------------------- 1 | # Default telescope data corresponding to tel_type. 2 | # For the optional parameter eff_fluo, efficiency values should be given for the 3 | # 57 fluorescence bands considered in fluorescence_model.toml 4 | # wvl_fluo = [281, 282, 296, 298, 302, 5 | # 308, 312, 314, 316, 318, 6 | # 327, 329, 331, 334, 337, 7 | # 346, 350, 354, 358, 366, 8 | # 367, 371, 376, 381, 386, 9 | # 388, 389, 391, 394, 400, 10 | # 405, 414, 420, 424, 427, 11 | # 428, 434, 436, 442, 449, 12 | # 457, 460, 465, 467, 471, 13 | # 481, 492, 503, 515, 523, 14 | # 531, 545, 570, 575, 586, 15 | # 594, 666] 16 | 17 | [generic] 18 | # Angular diameter in degrees of the telescope field of view. 19 | apert = 10.0 # deg 20 | 21 | # Detection area in m^2 (e.g., mirror area of an IACT). 22 | area = 100.0 # m^2 23 | 24 | # Number of camera pixels. 25 | N_pix = 1500 26 | 27 | # Integration time in microseconds of camera frames. 28 | int_time = 0.01 # us 29 | 30 | # Wavelength interval where efficiency is non zero. 31 | # Array of wavelengths in nm. Discretization step must be constant. 32 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 33 | #wvl = [290.0, ..., 428.0] 34 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 35 | wvl_ini = 290.0 # nm 36 | wvl_fin = 430.0 # nm (not included in wvl) 37 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 38 | 39 | # Detection efficiency in decimal fraction. 40 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 41 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 42 | eff = 1.0 43 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 44 | # If not given, values are interpolated from eff. 45 | # Put zeros if neccesary to match length of wvl_fluo. 46 | #eff_fluo = [1.0, ..., 1.0] 47 | 48 | [GridElement] 49 | # Angular diameter in degrees of the telescope field of view. 50 | apert = 180.0 # deg 51 | 52 | # Detection area in m^2 (e.g., mirror area of an IACT). 53 | area = 100.0 # m^2 54 | 55 | # Number of camera pixels. 56 | N_pix = 1 57 | 58 | # Integration time in microseconds of camera frames. 59 | int_time = 10.0 # us 60 | 61 | # Wavelength interval where efficiency is non zero. 62 | # Array of wavelengths in nm. Discretization step must be constant. 63 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 64 | #wvl = [290.0, ..., 428.0] 65 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 66 | wvl_ini = 290.0 # nm 67 | wvl_fin = 430.0 # nm (not included in wvl) 68 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 69 | 70 | # Detection efficiency in decimal fraction. 71 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 72 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 73 | eff = 1.0 74 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 75 | # If not given, values are interpolated from eff. 76 | # Put zeros if neccesary to match length of wvl_fluo. 77 | #eff_fluo = [1.0, ..., 1.0] 78 | 79 | [IACT] 80 | # Angular diameter in degrees of the telescope field of view. 81 | apert = 8.0 # deg 82 | 83 | # Detection area in m^2 (e.g., mirror area of an IACT). 84 | area = 113.097 # m^2 85 | 86 | # Number of camera pixels. 87 | N_pix = 1800 88 | 89 | # Integration time in microseconds of camera frames. 90 | int_time = 0.01 # us 91 | 92 | # Wavelength interval where efficiency is non zero. 93 | # Array of wavelengths in nm. Discretization step must be constant. 94 | # If not given, it is calculated from wvl = np.arange(wvl_ini, wvl_fin, wvl_step) 95 | wvl = [280.0, 283.0, 286.0, 289.0, 292.0, 96 | 295.0, 298.0, 301.0, 304.0, 307.0, 97 | 310.0, 313.0, 316.0, 319.0, 322.0, 98 | 325.0, 328.0, 331.0, 334.0, 337.0, 99 | 340.0, 343.0, 346.0, 349.0, 352.0, 100 | 355.0, 358.0, 361.0, 364.0, 367.0, 101 | 370.0, 373.0, 376.0, 379.0, 382.0, 102 | 385.0, 388.0, 391.0, 394.0, 397.0, 103 | 400.0, 403.0, 406.0, 409.0, 412.0, 104 | 415.0, 418.0, 421.0, 424.0, 427.0, 105 | 430.0, 433.0, 436.0, 439.0, 442.0, 106 | 445.0, 448.0, 451.0, 454.0, 457.0, 107 | 460.0, 463.0, 466.0, 469.0, 472.0, 108 | 475.0, 478.0, 481.0, 484.0, 487.0, 109 | 490.0, 493.0, 496.0, 499.0, 502.0, 110 | 505.0, 508.0, 511.0, 514.0, 517.0, 111 | 520.0, 523.0, 526.0, 529.0, 532.0, 112 | 535.0, 538.0, 541.0, 544.0, 547.0, 113 | 550.0, 553.0, 556.0, 559.0, 562.0, 114 | 565.0, 568.0, 571.0, 574.0, 577.0, 115 | 580.0, 583.0, 586.0, 589.0, 592.0, 116 | 595.0, 598.0] 117 | # Initial-final wavelengths and step size in nm. Only used if wvl is not given. 118 | wvl_ini = 280.0 # nm 119 | wvl_fin = 560.0 # nm (not included in wvl) 120 | wvl_step = 3.0 # nm (needed to integrate the Cherenkov signal when atm_trans==true) 121 | 122 | # Detection efficiency in decimal fraction. 123 | # If a float value is given, efficiency is assumed to be constant within the wavelength interval [wvl_ini, wvl_fin]. 124 | # A list of efficiency values can be given instead, making sure that efficiency matches wvl. 125 | eff = [0.01549109, 0.07929918, 0.12963671, 0.17227160, 0.20737558, 126 | 0.23620218, 0.26303514, 0.28054180, 0.29466943, 0.30534291, 127 | 0.31487235, 0.32109888, 0.32663823, 0.33124247, 0.33485481, 128 | 0.33779549, 0.34029057, 0.34245432, 0.34408696, 0.34525204, 129 | 0.34617290, 0.34677869, 0.34724839, 0.34725518, 0.34704414, 130 | 0.34650661, 0.34635416, 0.34697940, 0.34802544, 0.34980112, 131 | 0.35165961, 0.35340027, 0.35487968, 0.35600359, 0.35627157, 132 | 0.35453785, 0.35290097, 0.35149441, 0.35007389, 0.34852091, 133 | 0.34668034, 0.34418064, 0.34175984, 0.33960103, 0.33737256, 134 | 0.33486841, 0.33231135, 0.32954030, 0.32675191, 0.32395963, 135 | 0.32098053, 0.31707070, 0.31308814, 0.30863957, 0.30418957, 136 | 0.29862994, 0.29259352, 0.28659898, 0.28064450, 0.27502209, 137 | 0.26952477, 0.26466024, 0.25979416, 0.25515796, 0.25052618, 138 | 0.24626667, 0.24225691, 0.23828600, 0.23434073, 0.23003388, 139 | 0.22545024, 0.21959105, 0.21275740, 0.20553806, 0.19688918, 140 | 0.18546885, 0.17399642, 0.16420586, 0.15533591, 0.14750979, 141 | 0.13897085, 0.13244260, 0.12695100, 0.12189394, 0.11705413, 142 | 0.11310926, 0.10968250, 0.10642817, 0.10327383, 0.10030391, 143 | 0.09745325, 0.09444885, 0.09134185, 0.08838148, 0.08553319, 144 | 0.08266862, 0.07978976, 0.07723785, 0.07497205, 0.07239917, 145 | 0.06951917, 0.06666249, 0.06382916, 0.06110683, 0.05851143, 146 | 0.05726153, 0.05755025] 147 | # Optional parameter. Detection efficiency at the 57 bands considered in the fluorescence model (see above). 148 | # If not given, values are interpolated from eff. 149 | # Put zeros if neccesary to match length of wvl_fluo. 150 | eff_fluo = [0.03676045, 0.05802982, 0.24514650, 0.26303514, 0.28525101, 151 | 0.30851939, 0.31902337, 0.32294533, 0.32663823, 0.32970772, 152 | 0.33945887, 0.34101182, 0.34245432, 0.34408696, 0.34525204, 153 | 0.34724839, 0.34718483, 0.34668579, 0.34635416, 0.34920922, 154 | 0.34980112, 0.35223983, 0.35487968, 0.35618225, 0.35399222, 155 | 0.35290097, 0.35243212, 0.35149441, 0.35007389, 0.34668034, 156 | 0.34256678, 0.33570312, 0.33046398, 0.32675191, 0.32395963, 157 | 0.32296660, 0.31574318, 0.31308814, 0.30418958, 0.29059534, 158 | 0.27502209, 0.26952477, 0.26141619, 0.25824876, 0.25207011, 159 | 0.23828600, 0.22154411, 0.19308241, 0.15272720, 0.13244260, 160 | 0.11866740, 0.10228386, 0.07808849, 0.07411443, 0.06382916, 161 | 0.05767816, 0.00000000] 162 | -------------------------------------------------------------------------------- /src/showermodel/fluorescence.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import pandas as pd 4 | import warnings 5 | import matplotlib.pyplot as plt 6 | import showermodel.constants as ct 7 | 8 | warnings.filterwarnings( 9 | 'ignore', 10 | 'Pandas doesn\'t allow columns to be created via a new attribute name', 11 | UserWarning) 12 | 13 | # Default values for Fluorescence 14 | _Fluorescence__wvl_ini = ct.config['Signal']['wvl_ini'] 15 | _Fluorescence__wvl_fin = ct.config['Signal']['wvl_fin'] 16 | 17 | # Class ####################################################################### 18 | class Fluorescence(pd.DataFrame): 19 | """ 20 | DataFrame containing the fluorescence light production. 21 | 22 | Fluorescence light is evaluated at each of the 57 bands of the fluorescence 23 | spectrum in the 280 - 670 nm range based on the parameterization described 24 | in D. Morcuende et al., Astropart. Phys. 107(2019)26 and references 25 | therein. 26 | 27 | Parameters 28 | ---------- 29 | profile : Profile, mandatory 30 | Profile object to be used. 31 | 32 | Attributes 33 | ---------- 34 | 281 : float 35 | Column 0, number of fluorescence photons in the band centered at 36 | 281 nm. 37 | 282 : float 38 | Column 1, number of fluorescence photons in the band centered at 39 | 282 nm. 40 | 296 : float 41 | Column 2, number of fluorescence photons in the band centered at 42 | 296 nm. 43 | ... :float 44 | Column ... 45 | 666 : float 46 | Column 56, number of fluorescence photons in the band centered at 47 | 666 nm. 48 | profile : Profile 49 | atmosphere : Atmosphere 50 | 51 | Methods 52 | ------- 53 | show() 54 | Show the production of fluorescence photons in the 290 - 430 nm range 55 | as a function of slant depth. 56 | """ 57 | 58 | def __init__(self, profile): 59 | # All bands, including those outside the wavelength interval 60 | super().__init__(columns=ct.fluo_model['wvl']) 61 | _fluorescence(self, profile) 62 | 63 | def show(self): 64 | """ 65 | Show the production of fluorescence photons in the 290 - 430 nm range 66 | as a function of slant depth. 67 | 68 | Returns 69 | ------- 70 | ax : AxesSubplot 71 | """ 72 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5)) 73 | # Selection of bands within the wavelength interval 74 | fluo = self.loc[:, __wvl_ini:__wvl_fin] 75 | ax.plot(self.profile.X, fluo.sum(axis=1), 'b-') 76 | ax.axes.xaxis.set_label_text("Slant depth (g/cm$^2$)") 77 | ax.axes.yaxis.set_label_text( 78 | "Fluorescence production (Photons·cm$^2$/g)") 79 | return ax 80 | 81 | 82 | # Constructor ################################################################# 83 | def _fluorescence(fluorescence, profile): 84 | """ 85 | Constructor of Fluorescence class. 86 | 87 | Parameters 88 | ---------- 89 | fluorescence : Fluorescence 90 | profile : Profile 91 | """ 92 | # Load fluorescence parameters 93 | Y0_337 = ct.fluo_model['Y0_337'] 94 | P0 = ct.fluo_model['P0'] 95 | T0 = ct.fluo_model['T0'] 96 | bands = zip(ct.fluo_model['wvl'], 97 | ct.fluo_model['I_rel'], 98 | ct.fluo_model['PP0'], 99 | ct.fluo_model['PPw'], 100 | ct.fluo_model['a']) 101 | 102 | # Number of fluorescence photons at 337nm 103 | N0_337 = Y0_337 * profile.E_dep 104 | 105 | # fluorescence = Fluorescence() 106 | fluorescence.profile = profile 107 | fluorescence.atmosphere = profile.atmosphere 108 | 109 | # Only discretization steps where the profile is defined 110 | atm = profile.atmosphere.iloc[profile.index] 111 | P = atm.P - atm.P_w 112 | temp = atm.temp 113 | P_w = atm.P_w 114 | 115 | # Number of emitted photons at wavelength wvl as a function of pressure P, 116 | # temperature T and partial pressure P_w of water vapor: 117 | for wvl, I_rel, PP0, PPw, a in bands: 118 | # For some bands, no information on the quenching contribution from 119 | # water vapor is available 120 | if PPw == 0: 121 | P_PP = P / PP0 122 | else: 123 | P_PP = P / PP0 + P_w / PPw 124 | 125 | fluorescence[wvl] = ( 126 | N0_337 * I_rel * (1. + P0 / PP0) / (1. + P_PP 127 | * (T0 / temp)**(0.5 - a))) 128 | -------------------------------------------------------------------------------- /src/showermodel/image.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import matplotlib.animation as animation 6 | from IPython.display import HTML 7 | import showermodel as sm 8 | import showermodel.constants as ct 9 | 10 | # Default values for Image 11 | _Image__lat_profile = ct.config['Image']['lat_profile'] 12 | _Image__NSB = ct.config['Image']['NSB'] 13 | 14 | # Class ####################################################################### 15 | class Image: 16 | """ 17 | Generate a time-varying shower image from a Signal object. 18 | 19 | The object contains a time-varying shower image in a circular camera with 20 | square pixels of same solid angle. A Nishimura-Kamata-Greisen lateral 21 | profile is used to spread the signal contribution from each shower point to 22 | several pixels. 23 | 24 | Parameters 25 | ---------- 26 | signal : Signal, mandatory 27 | Signal object to be used. 28 | lat_profile : bool, default True 29 | Use a NKG lateral profile to spread the signal. If False, a linear 30 | shower is assumed. 31 | N_pix : int, default None 32 | Number of camera pixels. If not given, the value defined in the 33 | Telescope object is used. 34 | int_time : float, default None 35 | Integration time in microseconds of a camera frame. If not 36 | given, the value defined in the Telescope object is used. 37 | NSB : float, default 40 38 | Night sky background in MHz/m^2/deg^2 (photoelectrons). 39 | 40 | Attributes 41 | ---------- 42 | signal : Signal 43 | lat_profile : bool 44 | Bool indicating whether a NKG lateral profile is used to 45 | spread the signal. If False, a linear shower is assumed. 46 | N_pix : int 47 | Number of camera pixels. 48 | N_pix_r : int 49 | Number of pixels across a camera radius. 50 | sol_angle_pix : float 51 | Solid angle in stereoradians of a single pixel. 52 | int_time : float 53 | Integration time in microseconds of a camera frame. 54 | N_frames : int 55 | Number of frames. 56 | frames : array 57 | Array of size (N_frames, 2*N_pix_r+1, 2*N_pix_r+1) containing the 58 | pixel values at each frame. Array elements not corresponding to any 59 | camera pixel are set to -inf. 60 | NSB : float 61 | Night sky background in MHz/m^2/deg^2. 62 | NSB_pix : float 63 | Mean number of background photoelectrons per pixel and frame. 64 | 65 | Methods 66 | ------- 67 | show() 68 | Show a camera frame or the sum of all them including background. 69 | animate() 70 | Show an animation of camera frames. 71 | """ 72 | def __init__(self, signal, lat_profile=__lat_profile, N_pix=None, 73 | int_time=None, NSB=__NSB): 74 | _image(self, signal, lat_profile, N_pix, int_time, NSB) 75 | 76 | # Methods ################################################################# 77 | def show(self, frame=None, NSB=None, ax=None): 78 | """ 79 | Show a camera frame or the sum of all them including random background. 80 | 81 | Parameters 82 | ---------- 83 | frame : int, default None 84 | Frame number. If None, the sum of frames is shown. 85 | NSB : float, default None 86 | Night sky background in MHz/m^2/deg^2. By default, the one 87 | defined in Image is used. 88 | ax : AxesSubplot, default None 89 | Axes instance where the plot is generated. In not given, a new 90 | AxesSubplot object is created. 91 | 92 | Returns 93 | ------- 94 | axes : AxesSubplot 95 | """ 96 | N_pix = self.N_pix 97 | N_pix_r = self.N_pix_r 98 | N_frames = self.N_frames 99 | frames = self.frames 100 | # Image size 101 | N = 2 * N_pix_r +1 102 | 103 | # If an Axes instance is not given, a new AxesSubplot is created 104 | if ax is None: 105 | fig, ax = plt.subplots(1, 1, figsize=(5, 5)) 106 | 107 | # If no signal, an empty plot is generated 108 | if N_frames == 0: 109 | ax.text(0.4, 0.5, 'No signal') 110 | return ax 111 | 112 | # The NSB defined in image is used 113 | if NSB is None: 114 | NSB_pix = self.NSB_pix 115 | # The input NSB is used 116 | else: 117 | telescope = self.signal.telescope 118 | int_time = self.int_time 119 | sol_angle_pix = self.sol_angle_pix 120 | NSB_pix = (NSB * 180.**2 / ct.pi**2 * telescope.area * 121 | sol_angle_pix * int_time) 122 | 123 | # Sum of frames 124 | if frame is None: 125 | image = frames.sum(0) 126 | # Integrated noise 127 | noise = np.array(np.random.poisson(N_frames*NSB_pix, (N, N)), 128 | float) 129 | image += noise 130 | # A given frame is shown 131 | elif frame < N_frames: 132 | image = frames[frame].copy() 133 | # Noise of a single frame 134 | noise = np.array(np.random.poisson(NSB_pix, (N, N)), float) 135 | image += noise 136 | else: 137 | raise ValueError( 138 | 'The frame number must be lower the number of frames') 139 | 140 | extent = (-N_pix_r-0.5, N_pix_r+0.5, -N_pix_r-0.5, N_pix_r+0.5) 141 | psm = ax.imshow(image, extent=extent) # cmap=viridis 142 | plt.colorbar(psm, ax=ax, label='Photoelectrons') 143 | return ax 144 | 145 | def animate(self, NSB=None): 146 | """ 147 | Show an interactive animation of camera frames. 148 | 149 | Parameters 150 | ---------- 151 | NSB : float, default None 152 | Night sky background in MHz/m^2/deg^2. By defaut, the one 153 | defined in Image is used. 154 | 155 | Returns 156 | ------- 157 | ani : HTML 158 | """ 159 | N_pix = self.N_pix 160 | N_pix_r = self.N_pix_r 161 | N_frames = self.N_frames 162 | frames = self.frames.copy() 163 | # Image size 164 | N = 2 * N_pix_r + 1 165 | 166 | # If no signal, an empty plot is generated 167 | if N_frames == 0: 168 | fig = plt.figure(figsize=(5, 5)) 169 | plt.text(0.4, 0.5, 'No signal') 170 | return fig 171 | 172 | # The NSB defined in image is used 173 | if NSB is None: 174 | NSB_pix = self.NSB_pix 175 | # The input NSB is used 176 | else: 177 | telescope = self.signal.telescope 178 | int_time = self.int_time 179 | sol_angle_pix = self.sol_angle_pix 180 | NSB_pix = (NSB * 180.**2 / ct.pi**2 * telescope.area * 181 | sol_angle_pix * int_time) 182 | 183 | fig = plt.figure() 184 | extent = (-N_pix_r-0.5, N_pix_r+0.5, -N_pix_r-0.5, N_pix_r+0.5) 185 | # Same color scale for all frames 186 | vmax = frames.max()+NSB_pix+NSB_pix**0.5 187 | vmin = 0. 188 | # List of AxesSubplot objects to pass to ArtistAnimation function 189 | ims = [] 190 | for i, f in enumerate(frames): 191 | # Noise of a single frame 192 | noise = np.array( 193 | np.random.poisson(NSB_pix, (N, N)), float) 194 | f += noise 195 | 196 | im = plt.imshow(f, extent=extent, vmin=vmin, vmax=vmax) 197 | ims.append([im]) 198 | 199 | # One only colorbar 200 | cbar = fig.colorbar(im) 201 | cbar.ax.set_ylabel('Photoelectrons'); 202 | 203 | ani = animation.ArtistAnimation(fig, ims, interval=100, blit=True, 204 | repeat_delay=500) 205 | # Interactive HTML object 206 | ani = HTML(ani.to_jshtml()) 207 | return ani 208 | 209 | 210 | # Constructor ################################################################# 211 | def _image(image, signal, lat_profile, N_pix, int_time, NSB): 212 | """ 213 | Generate a time-varying shower image from a Signal object. 214 | 215 | A Nishimura-Kamata-Greisen lateral profile is used to spread the signal 216 | contribution from each shower point to several pixels. A circular camera with 217 | square pixels of same solid angle is assumed. 218 | 219 | Parameters 220 | ---------- 221 | image : Image 222 | signal : Signal 223 | lat_profile : bool, default True 224 | Use a NKG lateral profile to spread the signal. If False, a linear 225 | shower is assumed. 226 | N_pix : int 227 | Number of camera pixels. If not given, the predefined value in the 228 | Telescope object is used. 229 | int_time : float 230 | Integration time in microseconds of a camera frame. If not 231 | given, the predefined value in the Telescope object is used. 232 | NSB : float 233 | Night sky background in MHz/m^2/deg^2. 234 | """ 235 | image.NSB = NSB 236 | image.lat_profile = lat_profile 237 | image.signal = signal 238 | 239 | telescope = signal.telescope 240 | # Camera integration time 241 | if int_time is None: 242 | int_time = telescope.int_time 243 | image.int_time = int_time 244 | 245 | # Number of camera pixel and pixel solid angle 246 | if N_pix is None: 247 | N_pix = telescope.N_pix 248 | sol_angle_pix = telescope.sol_angle_pix 249 | else: 250 | sol_angle_pix = telescope.sol_angle / N_pix 251 | image.N_pix = N_pix 252 | image.sol_angle_pix = sol_angle_pix 253 | 254 | # Side of a square pixel in solid angle projection 255 | Delta_pix = np.sqrt(sol_angle_pix / 2.) 256 | # Number of pixels across a radius within the camera FoV 257 | N_pix_r_exact = np.sqrt(N_pix / ct.pi) 258 | N_pix_r = int(np.ceil(N_pix_r_exact)) 259 | image.N_pix_r = N_pix_r 260 | # Image size 261 | N = 2 * N_pix_r + 1 262 | 263 | # Night sky background per pixel and frame 264 | NSB_pix = (NSB * 180.**2 / ct.pi**2 * telescope.area * sol_angle_pix 265 | * int_time) 266 | image.NSB_pix = NSB_pix 267 | 268 | # Only points included in signal 269 | points = signal.index 270 | if len(points) == 0: 271 | image.N_frames = 0 272 | image.frames = np.zeros((0, N, N)) 273 | return image 274 | 275 | # Parameters of contributing points 276 | track = signal.track 277 | projection = signal.projection 278 | atmosphere = signal.atmosphere 279 | profile = signal.profile 280 | x = np.array(track.x.loc[points]) 281 | y = np.array(track.y.loc[points]) 282 | z = np.array(track.z.loc[points]) 283 | s = np.array(profile.s.loc[points]) 284 | # Shower track half step and apparent size 285 | L_half = track.dl / 2. 286 | L = np.array(L_half * np.sin(np.radians(projection.beta.loc[points]))) 287 | # Moliere radius 288 | R = np.array(atmosphere.r_M.loc[points]) 289 | # FoV coordinates 290 | distance = np.array(projection.distance.loc[points]) 291 | cos_theta = np.array(np.cos(np.radians(projection.theta.loc[points]))) 292 | # phi angle wrt right direction in radians 293 | phi = np.array(np.radians(projection.phi.loc[points] 294 | - telescope._phi_right)) # (-2*pi, 2*pi) 295 | # Total number of photoelectrons 296 | Npe = np.array(signal.Npe_total) 297 | 298 | # Arrival time of photons 299 | time = np.array(projection.time[points]) 300 | # Frame index (>=0) 301 | t0 = time.min() 302 | f_index = np.array((time - t0) // int_time, int) 303 | # Total number of frames 304 | N_frames = f_index.max() + 1 305 | image.N_frames = N_frames 306 | # Initialization of pixel values at each frame 307 | frames = np.zeros((N_frames, N, N)) 308 | # Camera FoV 309 | for pix_y in range(N): 310 | for pix_x in range(N): 311 | if (pix_x-N_pix_r)**2+(pix_y-N_pix_r)**2 > N_pix_r_exact**2: 312 | frames[:, pix_y, pix_x] = -float('inf') # Blanck pixels 313 | 314 | if not lat_profile: 315 | # Radii and x, y indexes of pixels 316 | pix_r = np.sqrt(1. - cos_theta) / Delta_pix 317 | pix_x = np.array(np.round(pix_r * np.cos(phi) + N_pix_r_exact), int) 318 | pix_y = np.array(np.round(pix_r * np.sin(phi) + N_pix_r_exact), int) 319 | for (f_index_p, Npe_p, pix_x_p, pix_y_p) in zip(f_index, Npe, pix_x, 320 | pix_y): 321 | frames[f_index_p, pix_y_p, pix_x_p] += Npe_p 322 | image.frames = frames 323 | return image 324 | 325 | # Approximate theta size of a pixel 326 | Delta_theta = 2. * Delta_pix / np.sqrt(1. + cos_theta) 327 | # Apparent angular size of a cylinder of half length L and radius R coaxial 328 | # to the shower axis at each point 329 | chi1 = 2. * np.arctan(L / distance) 330 | chi2 = 2. * np.arctan(R / distance) 331 | # Apparent size of the cylinder in number of pixels 332 | n1 = np.array(np.round(chi1 / Delta_theta), int) # Along shower axis 333 | n2 = np.array(np.round(chi2 / Delta_theta), int) # Perpendicular 334 | 335 | # Unit coordinate vectors 336 | # Parallel to shower axis (upwards) 337 | ux = track.ux 338 | uy = track.uy 339 | uz = track.uz 340 | # Horizontal plane 341 | vx = track.vx 342 | vy = track.vy 343 | # vz = 0. by definition 344 | # Perpendicular to both u and v 345 | wx = track.wx 346 | wy = track.wy 347 | wz = track.wz 348 | 349 | # Loop over shower points 350 | for point, (f_index_p, n1_p, n2_p, Npe_p) in enumerate(zip(f_index, n1, n2, 351 | Npe)): 352 | # If the apparent size of the cylinder is smaller than 1 pixel 353 | if (n1_p < 2) and (n2_p < 2): 354 | phi_p = phi[point] 355 | # Radii and x, y indexes of pixels 356 | pix_r_p = np.sqrt(1. - cos_theta[point]) / Delta_pix 357 | pix_x_p = int(round(pix_r_p * np.cos(phi_p) + N_pix_r_exact)) 358 | pix_y_p = int(round(pix_r_p * np.sin(phi_p) + N_pix_r_exact)) 359 | frames[f_index_p, pix_y_p, pix_x_p] += Npe_p 360 | continue 361 | 362 | # Lists of length = 1 to allow for loops 363 | x_p = [x[point]] 364 | y_p = [y[point]] 365 | z_p = [z[point]] 366 | 367 | # Contributions beyond one Moliere radius are not included 368 | # Number of photoelectrons are approximately corrected as a function of 369 | # shower age to account for these losses 370 | s_p = s[point] 371 | Npe_ps = Npe_p * (2. * s_p - 4.5) / (s_p - 4.5) 372 | 373 | # Apparent length of cylinder takes several pixels 374 | if n1_p > 1: 375 | # n1_p substeps along the shower axis 376 | L_p = np.linspace(-1., 1., n1_p) * L_half / 2. 377 | # Arrays of length = n1_p 378 | x_p = x_p + L_p * ux 379 | y_p = y_p + L_p * uy 380 | z_p = z_p + L_p * uz 381 | # Number of photoelectrons equally distributed in substeps 382 | Npe_ps = Npe_ps / n1_p 383 | 384 | # Apparent width of cylinder takes one only pixel 385 | if n2_p < 2: 386 | # theta, phi are calculated for all substeps 387 | distance_p, alt_p, az_p, theta_p, phi_p = ( 388 | telescope.spherical(x_p, y_p, z_p)) 389 | cos_theta_p = np.cos(np.radians(theta_p)) 390 | phi_p = np.radians(phi_p - telescope.phi_right) 391 | # Radii and x, y indexes of pixels 392 | pix_r_p = np.sqrt(1. - cos_theta_p) / Delta_pix 393 | pix_x_p = np.array(np.round(pix_r_p * np.cos(phi_p) 394 | + N_pix_r_exact), int) 395 | pix_y_p = np.array(np.round(pix_r_p * np.sin(phi_p) 396 | + N_pix_r_exact), int) 397 | 398 | # Loop over substeps to spread signal over pixels 399 | for (pix_x_ps, pix_y_ps) in zip(pix_x_p, pix_y_p): 400 | # Some indexes may be outside the matrix bounds 401 | if (pix_x_ps in range(N) and pix_y_ps in range(N)): 402 | frames[f_index_p, pix_y_ps, pix_x_ps] += Npe_ps 403 | continue 404 | 405 | # Apparent width of cylinder takes several pixels 406 | delta = 1./n2_p 407 | # n2_p radii are generated 408 | xx = np.arange(delta/2., 1., delta) # In units of Moliere radius 409 | R_p = R[point] * xx 410 | # Each radius is weighted according to the NKG model 411 | weight_p = _NKG(s_p, xx) 412 | Npe_ps = Npe_ps * weight_p / weight_p.sum() 413 | # 5 samples are generated for each radius 414 | Npe_ps = np.array((Npe_ps * np.full((5, n2_p), 1./5.)).flat) 415 | 416 | # Loop over substeps (may be only one) 417 | for (x_ps, y_ps, z_ps) in zip(x_p, y_p, z_p): 418 | # 5 * n2_p random polar angles for each substep 419 | alpha_ps = 2. * ct.pi * np.random.rand(5, n2_p) 420 | # v, w projections of 5 * n2_p vectors 421 | Rv_ps = np.array((R_p * np.cos(alpha_ps)).flat) 422 | Rw_ps = np.array((R_p * np.sin(alpha_ps)).flat) 423 | # 5 * n2_p samples around shower axis 424 | x_ps = x_ps + Rv_ps * vx + Rw_ps * wx 425 | y_ps = y_ps + Rv_ps * vy + Rw_ps * wy 426 | z_ps = z_ps + Rw_ps * wz # vz = 0 by definition 427 | 428 | distance_ps, alt_ps, az_ps, theta_ps, phi_ps = ( 429 | telescope.spherical(x_ps, y_ps, z_ps)) 430 | cos_theta_ps = np.cos(np.radians(theta_ps)) 431 | phi_ps = np.radians(phi_ps - telescope._phi_right) 432 | pix_r_ps = np.sqrt(1. - cos_theta_ps) / Delta_pix 433 | pix_x_ps = np.array(np.round(pix_r_ps * np.cos(phi_ps) 434 | + N_pix_r_exact), int) 435 | pix_y_ps = np.array(np.round(pix_r_ps * np.sin(phi_ps) 436 | + N_pix_r_exact), int) 437 | 438 | # Loop over samples points around shower axis 439 | for (pix_x_pss, pix_y_pss, Npe_pss) in zip(pix_x_ps, pix_y_ps, 440 | Npe_ps): 441 | # Some indexes may be outside the matrix bounds 442 | if (pix_x_pss in range(N) and pix_y_pss in range(N)): 443 | frames[f_index_p, pix_y_pss, pix_x_pss] += Npe_pss 444 | 445 | image.frames = frames 446 | 447 | 448 | # Auxiliary functions ######################################################### 449 | def _NKG(s, x): 450 | """ 451 | Non-normalized distribution of particles per unit radius according to the 452 | NKG model. 453 | """ 454 | s = s if s < 2.25 else 2.24 455 | return x**(s-1.)*(1.+x)**(s-4.5) 456 | -------------------------------------------------------------------------------- /src/showermodel/profile.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import warnings 6 | import showermodel as sm 7 | import showermodel.constants as ct 8 | import matplotlib.pyplot as plt 9 | warnings.filterwarnings( 10 | 'ignore', 11 | 'Pandas doesn\'t allow columns to be created via a new attribute name', 12 | UserWarning) 13 | 14 | # Default values for Profile 15 | _Profile__E = ct.config['Shower']['E'] 16 | _Profile__theta = ct.config['Shower']['theta'] 17 | _Profile__alt = ct.config['Shower'].get('alt') # optional parameter 18 | _Profile__zi = ct.config['Shower'].get('zi') # optional parameter 19 | _Profile__prf_model = ct.config['Shower']['prf_model'] 20 | _Profile__X_max = ct.config['Shower'].get('X_max') # optional parameter 21 | _Profile__X0_GH = ct.config['Shower'].get('X0_GH') # optional parameter 22 | _Profile__lambda_GH = ct.config['Shower'].get('lambda_GH') # optional parameter 23 | _Profile__h0 = ct.config['Atmosphere']['h0'] 24 | _Profile__h_top = ct.config['Atmosphere'].get('h_top') # optional parameter 25 | _Profile__N_steps = ct.config['Atmosphere']['N_steps'] 26 | _Profile__atm_model = ct.config['Atmosphere']['atm_model'] 27 | _Profile__rho_w_sl = ct.config['Atmosphere']['rho_w_sl'] 28 | _Profile__h_scale = ct.config['Atmosphere']['h_scale'] 29 | 30 | 31 | # Class ####################################################################### 32 | class Profile(pd.DataFrame): 33 | """ 34 | DataFrame containing a shower profile discretization. 35 | 36 | Use sm.Profile() to construct the default Profile object. 37 | 38 | Parameters 39 | ---------- 40 | E : float, default 10000000 41 | Energy of the primary particle in MeV. 42 | theta : float, default 0 43 | Zenith angle in degrees of the apparent position of the source. 44 | alt : float, default None 45 | Altitude in degrees of the apparent position of the source. If 46 | None, theta is used. If given, theta is overwritten. 47 | prf_model : 'Greisen', 'Gaisser-Hillas' or DataFrame, default 'Greisen' 48 | Profile model to be used. If 'Greisen', the Greisen function 49 | for electromagnetic showers is used. If 'Gaisser-Hillas', the 50 | Gaisser-Hillas function for hadron-induced showers is used. 51 | If a DataFrame is given, it should have two columns, the first 52 | one with the slant depth in g/cm2 and the second one with dE/dX 53 | in MeV.cm2/g. 54 | X_max : float, default None 55 | Slant depth in g/cm^2 at shower maximum. If None and prf_model 56 | is 'Greisen' or 'Gaisser-Hillas', a typical value of X_max for 57 | gamma or proton showers is calculated from the radiation length 58 | lambda_r = 36.7 g/cm^2 and the critical energy E_c = 81 MeV. 59 | X0_GH : float, default None 60 | X0 parameter in g/cm2 to be used when prf_model=='Gaisser- 61 | Hillas'. If None, a typical value for the input energy is used. 62 | lambda_GH : float, default None 63 | Lambda parameter in g/cm2 to be used when prf_model=='Gaisser- 64 | Hillas'. If None, a typical value for the input energy is used. 65 | zi : float, default None 66 | Height in km of the first interaction point of the shower. If 67 | None, the shower is assumed to begin at the top of the 68 | atmosphere (theta<90) or at ground level (theta>90). 69 | atmosphere : Atmosphere, default None 70 | Atmosphere object to be used. If None, a new Atmosphere object 71 | is generated. If given, h0, h_top, N_steps, atm_model, rho_w_sl 72 | and h_scale are ignored. 73 | h0 : float, default 0 74 | Ground level in km above sea level for the atmosphere 75 | discretization to be generated when atmosphere==None. 76 | h_top : float or None, default None 77 | Upper limit in km above sea level for the atmosphere 78 | discretization to be generated when atmosphere==None. If h_top 79 | is None, the top level of the selected atmospheric model is 80 | taken. 81 | N_steps : int, default 550 82 | Number of discretization steps for the atmosphere discretization 83 | to be generated when atmosphere==None. 84 | atm_model : int or DataFrame, default 1 85 | Atmospheric model used when atmosphere==None. If an int value 86 | is given, atm_model is searched from either CORSIKA atmospheric 87 | models (from 1 to 29) or a file named atm_models.toml in the 88 | working directory containing user-defined models. If a 89 | DataFrame is given, it should have two columns, one labelled as 90 | h with height in km and other labelled as X_vert or P, 91 | depending on whether vertical depth in g/cm^2 or pressure in 92 | hPa is given. 93 | rho_w_sl : float, default 7.5e-6 94 | Water-vapor density in g/cm^3 at sea level to calculate a 95 | simple exponential profile of water-vapor when 96 | atmosphere==None. Set to zero if dry air is assumed. 97 | h_scale : float, default 2.0 98 | Scale height in km to be used in the water-vapor exponential 99 | profile when atmospere==None. 100 | 101 | Attributes 102 | ---------- 103 | X : float 104 | Column 0, slant depth in g/cm^2. 105 | s : float 106 | Column 1, shower age. 107 | dX : float 108 | Column 2, discretization step in g/cm^2 along the shower axis. 109 | E_dep : float 110 | Column 3, energy deposit in MeV at each discretization step. 111 | N_ch : float 112 | Column 4, number of charged particles. 113 | atmosphere : Atmosphere 114 | Atmosphere object. 115 | E : float 116 | Energy of the primary particle. 117 | theta : float 118 | Zenith angle in degrees of the apparent position of the source. 119 | alt : float 120 | Altitude in degrees of the apparent position of the source. 121 | prf_model : {'Greisen', 'Gaisser-Hillas'} or DataFrame. 122 | X_max : float 123 | Slant depth in g/cm^2 at shower maximum. 124 | X0_GH : float 125 | X0 parameter in g/cm2 for prf_model=='Gaisser-Hillas'. 126 | lambda_GH : float 127 | lambda parameter in g/cm2 for prf_model=='Gaisser-Hillas'. 128 | dl : float 129 | Size in km of the discretization step along the shower axis. 130 | 131 | Methods 132 | ------- 133 | Fluorescence() 134 | Calculate the fluorescence light production. 135 | Cherenkov() 136 | Calculate the Cherenkov light production. 137 | show() 138 | Show the shower profile as a function of slant depth. 139 | 140 | See also 141 | -------- 142 | Profile : DataFrame containing a shower profile discretization. 143 | Shower : Make a discretization of a shower. 144 | """ 145 | def __init__(self, E=__E, theta=__theta, alt=__alt, prf_model=__prf_model, 146 | X_max=__X_max, X0_GH=__X0_GH, lambda_GH=__lambda_GH, zi=__zi, 147 | atmosphere=None, h0=__h0, h_top=__h_top, N_steps=__N_steps, 148 | atm_model=__atm_model, rho_w_sl=__rho_w_sl, 149 | h_scale=__h_scale): 150 | super().__init__(columns=['X', 's', 'dX', 'E_dep', 'N_ch']) 151 | _profile(self, E, theta, alt, prf_model, X_max, X0_GH, lambda_GH, zi, 152 | atmosphere, h0, h_top, N_steps, atm_model, rho_w_sl, h_scale) 153 | 154 | def _Greisen_norm(self): 155 | """ 156 | Calculate the normalization constant K that relates a Greisen profile 157 | to the actual shower size N(s). 158 | """ 159 | return _Greisen_norm(self.E, self.X_max) 160 | 161 | def _GH_norm(self): 162 | """ 163 | Calculate the normalization constant K that relates a Gaisser-Hillas 164 | profile to the actual shower size N(s). 165 | """ 166 | return _GH_norm(self.E, self.X_max, self.X0_GH, self.lambda_GH) 167 | 168 | def _alpha(self, s): 169 | """ 170 | Calculate the mean ionization loss rate per electron in MeV/g.cm^2 as a 171 | function of shower age. 172 | """ 173 | return _alpha(s) 174 | 175 | def Fluorescence(self): 176 | """ 177 | Calculate the fluorescence photon production from a shower profile 178 | discretization. 179 | 180 | Returns 181 | ------- 182 | fluorescence : Fluorescence 183 | """ 184 | return sm.Fluorescence(self) 185 | 186 | def Cherenkov(self): 187 | """ 188 | Calculate the Cherenkov light production from a shower profile 189 | discretization. 190 | 191 | Returns 192 | ------- 193 | cherenkov : Cherenkov 194 | """ 195 | return sm.Cherenkov(self) 196 | 197 | def show(self): 198 | """ 199 | Show the shower profile, both number of charged particles and energy 200 | deposit, as a function of slant depth. 201 | 202 | Returns 203 | ------- 204 | (ax1, ax2) : AxesSubplot 205 | """ 206 | # Shower size 207 | fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(10, 5)) 208 | ax1.plot(self.X, self.N_ch, 'r-') 209 | ax1.axes.xaxis.set_label_text("Slant depth (g/cm$^2$)") 210 | ax1.axes.yaxis.set_label_text("Number of charged particles") 211 | 212 | # Energy deposit 213 | ax2.plot(self.X, self.E_dep/self.dX, 'b-') 214 | ax2.axes.xaxis.set_label_text("Slant depth (g/cm$^2$)") 215 | ax2.axes.yaxis.set_label_text("Energy deposit (MeV·cm$^2$/g)") 216 | plt.tight_layout() 217 | return (ax1, ax2) 218 | 219 | 220 | # Constructor ################################################################# 221 | def _profile(profile, E, theta, alt, prf_model, X_max, X0_GH, lambda_GH, zi, 222 | atmosphere, h0, h_top, N_steps, atm_model, rho_w_sl, h_scale): 223 | """ 224 | Constructor of Profile class. 225 | 226 | Parameters 227 | ---------- 228 | profile : Profile 229 | E : float 230 | Energy of the primary particle in MeV. 231 | theta : float 232 | Zenith angle in degrees of the apparent position of the source. 233 | alt : float 234 | Altitude in degrees of the apparent position of the source. If None, 235 | theta is used. If given, theta is overwritten. 236 | prf_model : {'Greisen', 'Gaisser-Hillas'} or DataFrame 237 | If 'Greisen', the Greisen function for electromagnetic showers is used. 238 | If 'Gaisser-Hillas', the Gaisser-Hillas function for hadron-induced 239 | showers is used. If a DataFrame with an energy deposit profile is input, 240 | it must have two columns with the slant depth in g/cm2 and dE/dX in 241 | MeV.cm2/g. 242 | X_max : float 243 | Slant depth in g/cm^2 at shower maximum. If None and prf_model is 244 | 'Greisen' or 'Gaisser-Hillas', a typical value of X_max for gamma or 245 | proton showers is calculated from the radiation length 246 | lambda_r = 36.7 g/cm^2 and the critical energy E_c = 81 MeV. 247 | X0_GH : float 248 | X0 parameter in g/cm2 to be used when prf_model=='Gaisser-Hillas'. 249 | If None, a typical value for the input energy is used. 250 | lambda_GH : float 251 | Lambda parameter in g/cm2 to be used when prf_model=='Gaisser-Hillas'. 252 | If None, a typical value for the input energy is used. 253 | zi : float, default None 254 | Height in km of the first interaction point of the shower. 255 | atmosphere : Atmosphere, default None 256 | If None, a new Atmosphere object is generated with parameters h0, h_top, 257 | N_steps, atm_model. 258 | h0 : float 259 | Ground level in km above sea level. 260 | h_top : float 261 | Top level of the atmosphere in km above sea level. 262 | N_steps : int 263 | Number of discretization steps. 264 | atm_model : int or DataFrame 265 | Atmospheric model assuming dry air. 266 | rho_w_sl : float 267 | Water-vapor density in g/cm^3 at sea level to calculate a simple 268 | exponential profile of water-vapor. Set to zero if dry air is assumed. 269 | h_scale : float 270 | Scale height in km to be used in the water-vapor exponential profile. 271 | """ 272 | from .atmosphere import Atmosphere 273 | if isinstance(atmosphere, Atmosphere): 274 | pass 275 | elif atmosphere is None: 276 | atmosphere = sm.Atmosphere(h0, h_top, N_steps, 277 | atm_model, rho_w_sl, h_scale) 278 | else: 279 | raise ValueError('The input atmosphere is not valid.') 280 | 281 | # The columns of the output DataFrame are: the slant depth in g/cm^2, 282 | # the shower age s, the energy deposit E_dep in MeV, and the number N_ch of 283 | # charged particles with energy above 1MeV 284 | # profile = Profile(columns=['X', 's', 'dX', 'E_dep', 'N_ch']) 285 | profile.atmosphere = atmosphere 286 | 287 | # The input shower parameters along with some geometric parameters are 288 | # included as attributes of the DataFrame 289 | profile.E = E 290 | if alt is None: 291 | alt = 90. - theta 292 | else: 293 | theta = 90. - alt 294 | profile.theta = theta 295 | profile.alt = alt 296 | if theta==180.: 297 | uz = -1. 298 | else: 299 | uz = np.cos(np.radians(theta)) 300 | 301 | #Slant depth in g/cm^2 (following track.X_to_xyz()) 302 | if zi is None: 303 | if uz>0.: 304 | Xv_i = 0. 305 | else: 306 | Xv_i = atmosphere.Xv_total 307 | else: 308 | Xv_i = atmosphere.h_to_Xv(zi + atmosphere.h0) 309 | 310 | X = (atmosphere.X_vert - Xv_i) / uz 311 | points = atmosphere[(X>0.) & (atmosphere.X_vert>0.)].index 312 | profile.X = X[points] 313 | 314 | # Length in km travelled through one atmospheric slice 315 | profile.dl = atmosphere.h_step / abs(uz) 316 | # Depth in g/cm^2 travelled through one atmospheric slice 317 | profile.dX = 100000. * profile.dl * atmosphere.rho[points] 318 | 319 | profile.prf_model = prf_model 320 | 321 | # DataFrame containing dE/dX at steps in X 322 | if isinstance(prf_model, pd.DataFrame): 323 | # Sorted to allow for interpolation 324 | prf_model.sort_index(axis=0, ascending=True, inplace=True) 325 | # The first column must be X in g/cm2 326 | X_model = np.array(prf_model.iloc[:, 0]) 327 | # The second column must be dE_dX in MeV.cm2/g 328 | dE_dX_model = np.array(prf_model.iloc[:, 1]) 329 | # Extreme values are added to allow for extrapolation 330 | X_model = np.insert(X_model, 0, X_model[0] * 1.5 - X_model[1] / 2.) 331 | X_model = np.append(X_model, X_model[-1] * 1.5 - X_model[-2] / 2.) 332 | dE_dX_model = np.insert(dE_dX_model, 0, 0.) 333 | dE_dX_model = np.append(dE_dX_model, 0.) 334 | 335 | index_max = dE_dX_model.argmax() # Index where max of dE_dX is 336 | if X_max is None: 337 | X_max = X_model[index_max] 338 | elif (X_max < X_model[index_max-1]) or (X_max > X_model[index_max+1]): 339 | print(""" 340 | Warning: The input X_max does not match the maximum of the input 341 | energy deposit profile. 342 | """) 343 | 344 | profile.X_max = X_max 345 | s = 3. * profile.X / (profile.X + 2. * X_max) # Shower age 346 | profile.s = s 347 | 348 | profile.X0_GH = None 349 | profile.lambda_GH = None 350 | 351 | dE_dX = np.interp(profile.X, X_model, dE_dX_model, left=0., right=0.) 352 | profile.E_dep = dE_dX * profile.dX 353 | profile.N_ch = dE_dX / profile._alpha(profile.s) 354 | 355 | if E < profile.E_dep.sum(): 356 | profile.E = profile.E_dep.sum() 357 | raise ValueError(""" 358 | The input shower energy is lower than the integrated energy 359 | deposit profile. 360 | """) 361 | elif E > profile.E_dep.sum()*1.2: 362 | print(""" 363 | Warning: The input energy is greater than the integrated energy 364 | deposit profile by more than 20%. 365 | """) 366 | 367 | elif prf_model == 'Greisen': 368 | if X_max is None: 369 | # E_c: critical energy in air in MeV 370 | # lambda_r: radiation length in air in g/cm2 371 | X_max = ct.lambda_r * np.log(E / ct.E_c) 372 | profile.X_max = X_max 373 | s = 3. * profile.X / (profile.X + 2. * X_max) # Shower age 374 | profile.s = s 375 | 376 | profile.X0_GH = None 377 | profile.lambda_GH = None 378 | 379 | # Greisen profile: N_ch = 0.31/sqrt(t_max) * exp[ t * (1-3/2*log(s)) ] 380 | # where t=X/lambda_r , 381 | N_ch = (0.31 / np.sqrt(X_max / ct.lambda_r) 382 | * np.exp(profile.X / ct.lambda_r * (1.-1.5*np.log(s)))) 383 | 384 | # Shower size with an energy cut of 1MeV 385 | profile.N_ch = profile._Greisen_norm() * N_ch 386 | 387 | # Deposited energy in each slice: E_dep = alpha * N_ch * dX, 388 | # where alpha(s) is the mean ionization energy loss per electron for 389 | # an energy cut of 1MeV 390 | profile.E_dep = profile._alpha(s) * profile.N_ch * profile.dX 391 | 392 | elif prf_model == 'Gaisser-Hillas': 393 | # If not given, a typical value according to Heitler model is used 394 | if X_max is None: 395 | # E_c: critical energy in air in MeV 396 | # lambda_r: radiation length in air in g/cm 397 | X_max = ct.lambda_r * np.log(E / 4. / ct.E_c) 398 | profile.X_max = X_max 399 | s = 3. * profile.X / (profile.X + 2. * X_max) # Shower age 400 | profile.s = s 401 | 402 | # If not given, typical values according to Auger data are used 403 | if (X0_GH is None) or (lambda_GH is None): 404 | x = 6. + np.log10(E) 405 | R = 0.26 - 0.04 * (x - 17.9) 406 | L = 226.2 + 7.1 * (x - 17.9) 407 | if X0_GH is None: 408 | X0_GH = X_max - L / R 409 | if lambda_GH is None: 410 | lambda_GH = L * R 411 | profile.X0_GH = X0_GH 412 | profile.lambda_GH = lambda_GH 413 | 414 | # Gaisser-Hillas profile: 415 | # N_ch = N_ch_max * [ (X-X0) / (X_max-X0) ]**( (X_max-X0) / lambda ) 416 | # * exp[ -(X-X_max) / lambda ] 417 | X_min = max(X0_GH, 0.) # X0_GH is expected to be negative 418 | N_ch = 0. * profile.X 419 | N_ch[X>X_min] = ( 420 | ((X[X>X_min]-X0_GH) / (X_max-X0_GH))**((X_max-X0_GH)/lambda_GH) 421 | * np.exp(-(X[X>X_min]-X_max) / lambda_GH)) 422 | # X>X_min prevents from errors for discretization steps with X<=0, 423 | # or with XX0_GH] = (((X[X>X0_GH]-X0_GH)/(X_max-X0_GH))**((X_max-X0_GH)/lambda_GH) 473 | *np.exp(-(X[X>X0_GH]-X_max)/lambda_GH)) 474 | 475 | # Normalization constant K= E / int[alpha(s) * N_GH(s) * dX/ds *ds] 476 | # where dX/ds = (X+2*X_max) / (3-s) 477 | return E / np.sum(_alpha(s) * N_GH * (X+2.*X_max) / (3.-s) * 0.01) 478 | 479 | 480 | def _alpha(s): 481 | """ 482 | Calculate the mean ionization loss rate per electron in MeV/g.cm^2 as a 483 | function of shower age such that: dE/dX = alpha(s) * N(s) 484 | An energy cut of 1MeV on the shower electrons is assumed. 485 | F. Nerling et al., Astropart. Phys. 24(2006)241. 486 | """ 487 | return 3.90883 / (1.05301+s)**9.91717 + 2.41715 + 0.13180 * s 488 | -------------------------------------------------------------------------------- /src/showermodel/projection.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import showermodel.constants as ct 6 | 7 | # Class ####################################################################### 8 | class Projection(pd.DataFrame): 9 | """ 10 | DataFrame containing the projection of a shower track. 11 | 12 | The track is viewed from a telescope position in both horizontal 13 | coordinates system and FoV coordinates system as well as the fraction of 14 | the track within the telescope field of view. 15 | 16 | Parameters 17 | ---------- 18 | telescope : Telescope, mandatory 19 | Telescope object to be used. 20 | track : Track or Shower, mandatory 21 | Track object to be used. 22 | 23 | Attributes 24 | ---------- 25 | distance : float 26 | Column 0, shower-to-telescope distance in km. 27 | alt : float 28 | Column 1, altitude in degrees (from horizon). 29 | az : float 30 | Column 2, azimuth in degrees (from north, clockwise). 31 | theta : float 32 | Column 3, offset angle in degrees relative to the telescope pointing 33 | direction. 34 | phi : float 35 | Column 4, position angle in degrees from north in FoV projection. 36 | beta : float 37 | Column 5, angle in degrees relative to the apparent source position. 38 | time : float 39 | Column 6, arrival time in microseconds of photons emitted at each point 40 | of the shower, where time=0 for photons produced at the top of the 41 | atmosphere. 42 | FoV : bool 43 | Column 7, True if the shower point is within the telescope field of 44 | view, False otherwise. 45 | atmosphere : Atmosphere 46 | Atmosphere object that is used. 47 | track : Track 48 | Track object that is used. 49 | telescope : Telescope 50 | Telescope object that is used. 51 | distance_top, alt_top, ..., beta_top : float or None 52 | Coordinates of the shower point at the top of the atmosphere (if any). 53 | distance_0, alt_0, ..., beta_0 : float or None 54 | Coordinates of the shower impact point at ground (if any). 55 | distance_i, alt_i, ..., beta_i : float or None 56 | Coordinates of the first interaction point of the shower. 57 | distance_min : float 58 | Minimum distance in km to (infinite) line going to the 59 | shower axis. 60 | alt_inf : float 61 | Altitude in degrees of the apparent source position. 62 | az_inf : float 63 | Azimuth in degrees of the apparent source position. 64 | theta_inf : float 65 | Offset angle in degrees of the apparent source position. 66 | phi_inf : float 67 | Position angle in degrees of the apparent source position. 68 | 69 | Methods 70 | ------- 71 | show() 72 | Show the projection of the shower track viewed by the telescope. 73 | hor_to_FoV() 74 | Convert cartesian coordinates from horizontal system to FoV system. 75 | FoV_to_hor() 76 | Convert cartesian coordinates from FoV system to horizontal system. 77 | theta_phi_to_alt_az() 78 | Convert FoV coordinates theta/phi to horizontal coordinates alt/az. 79 | altaz_to_thetaphi() 80 | Convert horizontal coordinates alt/az to FoV coordinates theta/phi. 81 | spherical() 82 | Calculate the spherical coordinates in both horizontal and FoV systems. 83 | """ 84 | def __init__(self, telescope, track): 85 | super().__init__(columns=['distance', 'alt', 'az', 'theta', 'phi', 86 | 'beta', 'time', 'FoV']) 87 | _projection(self, telescope, track) 88 | 89 | def show(self, axes=True, max_theta=30., X_mark=None): 90 | """ 91 | Show the projection of the shower track viewed by the telescope in both 92 | horizontal and FoV coordinates systems. 93 | 94 | Parameters 95 | ---------- 96 | axes : bool, default True 97 | Show the axes of both frames of reference. 98 | max_theta : float, default 30 99 | Maximum offset angle in degrees relative to the telescope 100 | pointing direction. 101 | X_mark : float, default None 102 | Reference slant depth in g/cm^2 of the shower track to be 103 | marked in the figure. If None, no mark is included. 104 | 105 | Returns 106 | ------- 107 | ax1, ax2 : PolarAxesSubplot 108 | """ 109 | from ._tools import show_projection 110 | return show_projection(self, None, False, axes, max_theta, X_mark) 111 | 112 | def altaz_to_thetaphi(self, alt, az): 113 | """ 114 | Convert polar horizontal coordinates alt, az to FoV coordinates 115 | theta, phi. 116 | 117 | Parameters 118 | ---------- 119 | alt, az : float or array_like 120 | 121 | Returns 122 | ------- 123 | theta, phi : float or array-like 124 | 125 | See also 126 | -------- 127 | Projection.hor_to_FoV : Convert cartesian coordinates from horizontal 128 | system to FoV system. 129 | Projection.theta_phi_to_alt_az : Convert FoV coordinates theta, phi to 130 | horizontal coordinates alt, az. 131 | """ 132 | return self.telescope.altaz_to_thetaphi(alt, az) 133 | 134 | def hor_to_FoV(self, x_hor, y_hor, z_hor): 135 | """ 136 | Convert cartesian coordinates from horizontal system to FoV system. 137 | 138 | In the FoV coordinates system, x_FoV grows in the right-hand direction, 139 | y_FoV grows downward and z_FoV grows toward the pointing direction from 140 | the telescope point of view. 141 | 142 | Parameters 143 | ---------- 144 | x_hor, y_hor, z_hor : float or array-like 145 | 146 | Returns 147 | ------- 148 | x_FoV, y_FoV, z_FoV : floar or array_like 149 | 150 | See also 151 | -------- 152 | Projection.FoV_to_hor : Convert cartesian coordinates from FoV system 153 | to horizontal system. 154 | Projection.altaz_to_thetaphi : Convert horizontal coordinates alt, az 155 | to FoV coordinates theta, phi. 156 | """ 157 | return self.telescope.hor_to_FoV(x_hor, y_hor, z_hor) 158 | 159 | def theta_phi_to_alt_az(self, theta, phi): 160 | """ 161 | Convert FoV coordinates theta, phi to horizontal coordinates alt, az. 162 | 163 | Parameters 164 | ---------- 165 | theta, phi : float or array_like 166 | 167 | Returns 168 | ------- 169 | alt, az : float or array_like 170 | 171 | See also 172 | -------- 173 | Projection.FoV_to_hor : Convert cartesian coordinates from FoV system 174 | to horizontal system. 175 | Projection.altaz_to_thetaphi : Convert horizontal coordinates alt, az 176 | to FoV coordinates theta, phi. 177 | """ 178 | return self.telescope.theta_phi_to_alt_az(theta, phi) 179 | 180 | def FoV_to_hor(self, x_FoV, y_FoV, z_FoV): 181 | """ 182 | Convert cartesian coordinates from FoV system to horizontal system. 183 | 184 | In the FoV coordinates system, x_FoV grows in the right-hand direction, 185 | y_FoV grows downward and z_FoV grows toward the pointing direction from 186 | the telescope point of view. 187 | 188 | Parameters 189 | ---------- 190 | x_FoV, y_FoV, z_FoV : float or array_like 191 | 192 | Returns 193 | ------- 194 | x_hor, y_hor, z_hor : float or array_like. 195 | 196 | See also 197 | -------- 198 | Projection.hor_to_FoV : Convert cartesian coordinates from horizontal 199 | system to FoV system. 200 | Projection.theta_phi_to_alt_az : Convert FoV coordinates theta, phi to 201 | horizontal coordinates alt, az. 202 | """ 203 | return self.telescope.FoV_to_hor(x_FoV, y_FoV, z_FoV) 204 | 205 | def spherical(self, x, y, z): 206 | """ 207 | Calculate the spherical coordinates in both horizontal and FoV systems 208 | from the 'absolute' x, y, z coordinates. 209 | 210 | Parameters 211 | ---------- 212 | x, y, z : float or array_like 213 | 214 | Returns 215 | ------- 216 | distance, alt, az, theta, phi : float or array_like 217 | """ 218 | return self.telescope.spherical(x, y, z) 219 | 220 | 221 | # Constructor ################################################################# 222 | def _projection(projection, telescope, track): 223 | """ 224 | Obtain the projection of a shower track viewed from the telescope position 225 | in both horizontal coordinates system (alt/az) and FoV coordinates system 226 | (theta/phi) and determine the fraction of the track within the telescope 227 | field of view. 228 | 229 | Parameters 230 | ---------- 231 | projection : Projection 232 | telescope : Telescope 233 | track : Track or Shower 234 | """ 235 | from .telescope import Telescope 236 | from .track import Track 237 | from .shower import Shower 238 | if isinstance(telescope, Telescope): 239 | pass 240 | # In case the input objects are not ordered correctly. 241 | elif isinstance(telescope, Track): 242 | telescope, track = (track, telescope) 243 | elif isinstance(telescope, Shower): 244 | telescope, shower = (track, telescope) 245 | track = shower.track 246 | else: 247 | raise ValueError('The input telescope is not valid') 248 | if isinstance(track, Track): 249 | pass 250 | elif isinstance(track, Shower): 251 | shower = track 252 | track = shower.track 253 | else: 254 | raise ValueError('The input track is not valid') 255 | 256 | # projection = Projection(columns=['distance', 'alt', 'az', 'theta', 'phi', 257 | # 'beta', 'time', 'FoV']) 258 | projection.atmosphere = track.atmosphere 259 | projection.track = track 260 | projection.telescope = telescope 261 | 262 | # Apparent position of the cosmic-ray source 263 | projection.alt_inf = track.alt 264 | projection.az_inf = track.az 265 | theta_inf, phi_inf = telescope.altaz_to_thetaphi(track.alt, track.az) 266 | projection.theta_inf = theta_inf 267 | projection.phi_inf = phi_inf 268 | 269 | # Shower spherical coordinates in both zenith and camera projections 270 | distance, alt, az, theta, phi = telescope.spherical(track.x, track.y, 271 | track.z) 272 | projection.distance = distance 273 | projection.alt = alt 274 | projection.az = az 275 | projection.theta = theta 276 | projection.phi = phi 277 | 278 | # Coordinates of first interaction point of the shower relative to 279 | # the telescope position 280 | distance_i, alt_i, az_i, theta_i, phi_i = telescope.spherical(track.xi, 281 | track.yi, track.zi) 282 | projection.distance_i = distance_i 283 | projection.alt_i = alt_i 284 | projection.az_i = az_i 285 | projection.theta_i = theta_i 286 | projection.phi_i = phi_i 287 | # Angle formed by the shower axis (backwards) and the vector going 288 | # from the telescope position to the first interaction point 289 | xi, yi, zi = telescope.abs_to_rel(track.xi, track.yi, track.zi) 290 | proj_u_i = xi * track.ux + yi * track.uy + zi * track.uz 291 | beta_i = telescope.zr_to_theta(proj_u_i, distance_i) 292 | 293 | # Coordinates of the shower point at the top of the atmosphere relative to 294 | # the telescope position 295 | if track.z_top is None: 296 | distance_top = None 297 | beta_top = None 298 | projection.alt_top = None 299 | projection.az_top = None 300 | projection.theta_top = None 301 | projection.phi_top = None 302 | elif track.z_top==track.zi: 303 | distance_top = distance_i 304 | proj_u_top = proj_u_i 305 | beta_top = beta_i 306 | projection.alt_top = alt_i 307 | projection.az_top = az_i 308 | projection.theta_top = theta_i 309 | projection.phi_top = phi_i 310 | else: 311 | distance_top, alt_top, az_top, theta_top, phi_top = ( 312 | telescope.spherical(track.x_top, track.y_top, track.z_top)) 313 | projection.alt_top = alt_top 314 | projection.az_top = az_top 315 | projection.theta_top = theta_top 316 | projection.phi_top = phi_top 317 | # Angle formed by the shower axis (backwards) and the vector going 318 | # from the telescope position to the first interaction point 319 | x_top, y_top, z_top = telescope.abs_to_rel(track.x_top, track.y_top, 320 | track.z_top) 321 | proj_u_top = x_top * track.ux + y_top * track.uy + z_top * track.uz 322 | beta_top = telescope.zr_to_theta(proj_u_top, distance_top) 323 | projection.distance_top = distance_top 324 | 325 | # Coordinates of the shower impact point at ground level relative to 326 | # the telescope position and minimum shower-to-telescope distance 327 | if track.z0 is None: 328 | distance_0 = None 329 | beta_0 = None 330 | projection.alt_0 = None 331 | projection.az_0 = None 332 | projection.theta_0 = None 333 | projection.phi_0 = None 334 | # Minimum shower-to-telescope distance 335 | distance_min = distance_i * np.sin(np.radians(beta_i)) 336 | elif track.z0==track.zi: 337 | distance_0 = distance_i 338 | proj_u_0 = proj_u_i 339 | beta_0 = beta_i 340 | projection.alt_0 = alt_i 341 | projection.az_0 = az_i 342 | projection.theta_0 = theta_i 343 | projection.phi_0 = phi_i 344 | # Minimum shower-to-telescope distance 345 | distance_min = distance_i * np.sin(np.radians(beta_i)) 346 | else: 347 | distance_0, alt_0, az_0, theta_0, phi_0 = telescope.spherical(track.x0, 348 | track.y0, track.z0) 349 | projection.alt_0 = alt_0 350 | projection.az_0 = az_0 351 | projection.theta_0 = theta_0 352 | projection.phi_0 = phi_0 353 | # Angle formed by the shower axis (backwards) and the vector going 354 | # from the telescope position to the shower impact point at ground 355 | x0, y0, z0 = telescope.abs_to_rel(track.x0, track.y0, track.z0) 356 | proj_u_0 = x0 * track.ux + y0 * track.uy + z0 * track.uz 357 | beta_0 = telescope.zr_to_theta(proj_u_0, distance_0) 358 | if distance_0 0.)) 398 | -------------------------------------------------------------------------------- /src/showermodel/signal.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | import matplotlib.pyplot as plt 4 | import numpy as np 5 | import pandas as pd 6 | import showermodel as sm 7 | import showermodel.constants as ct 8 | 9 | # Default values for Signal 10 | _Signal__atm_trans = ct.config['Signal']['atm_trans'] 11 | _Signal__tel_eff = ct.config['Signal']['tel_eff'] 12 | _Signal__wvl_ini = ct.config['Signal']['wvl_ini'] 13 | _Signal__wvl_fin = ct.config['Signal']['wvl_fin'] 14 | _Signal__wvl_step = ct.config['Signal']['wvl_step'] 15 | 16 | # Default values for Image 17 | _Image__lat_profile = ct.config['Image']['lat_profile'] 18 | _Image__NSB = ct.config['Image']['NSB'] 19 | 20 | # Class ####################################################################### 21 | class Signal(pd.DataFrame): 22 | """ 23 | DataFrame containing the signal produced by a shower. 24 | 25 | Both Cherenkov light and fluorescence light are transported to the 26 | telescope and the number of photoelectrons (of each light component) is 27 | evaluated from the detection efficiency of the telescope. The atmosphere, 28 | shower, telescope, etc. are stored as object attributes. 29 | 30 | Parameters 31 | ---------- 32 | telescope : Telescope, mandatory 33 | Telescope object to be used. 34 | shower : Shower, mandatory 35 | Shower object to be used. 36 | atm_trans : bool, default True 37 | Include the atmospheric transmission to transport photons. 38 | tel_eff : bool, default True 39 | Include the telescope efficiency to calculate the signals. 40 | If False, 100% efficiency is assumed for a given wavelength interval. 41 | wvl_ini : float, default 290 42 | Initial wavelength in nm of the interval to calculate the signal when 43 | tel_eff==False. 44 | wvl_fin : float, default 430 45 | Final wavelength in nm of the interval to calculate the signal when 46 | tel_eff==False. 47 | wvl_step : float, default 3 48 | Discretization step in nm of the interval to calculate the signal when 49 | tel_eff==False. 50 | 51 | Attributes 52 | ---------- 53 | Npe_cher : float 54 | Column 0, number of photoelectrons per discretization step due to 55 | Cherenkov light. 56 | Npe_fluo : float 57 | Column 1, number of photoelectrons per discretization step due to 58 | fluorescence light. 59 | Npe_total : float 60 | Column 2, total number of photoelectrons per discretization step. 61 | telescope : Telescope 62 | Telescope object. 63 | atm_trans : bool 64 | True if the atmospheric transmission is included. 65 | tel_eff : bool 66 | True if the telescope efficiency is included. 67 | wvl_ini : float 68 | Initial wavelength in nm of the interval. 69 | wvl_fin : float 70 | Final wavelength in nm of the interval. 71 | wvl_step : float 72 | Step size in nm of the interval. 73 | Npe_cher_sum : float 74 | Sum of photoelectrons due to Cherenkov light. 75 | Npe_fluo_sum : float 76 | Sum of photoelectrons due to fluorescence light. 77 | Npe_total_sum : float 78 | Sum of photoelectrons due to both light components. 79 | shower : Shower 80 | Shower object. 81 | projection : Projection 82 | Projection object. 83 | fluorescence : Fluorescence 84 | Fluorescence object. 85 | cherenkov : Cherenkov 86 | Cherenkov object. 87 | profile : Profile 88 | Profile object. 89 | track : Track 90 | Track object. 91 | atmosphere : Atmosphere 92 | Atmosphere object. 93 | 94 | Methods 95 | ------- 96 | show_projection() 97 | Show the projection of the shower track viewed by the telescope. 98 | show_profile() 99 | Show the shower profile as a function of slant depth. 100 | show_light_production() 101 | Show the production of photons as a function of slant depth. 102 | show() 103 | Show the signal evolution. 104 | Image() 105 | Generate a time-varying shower image. 106 | """ 107 | def __init__(self, telescope, shower, projection=None, 108 | atm_trans=__atm_trans, tel_eff=__tel_eff, wvl_ini=__wvl_ini, 109 | wvl_fin=__wvl_fin, wvl_step=__wvl_step): 110 | super().__init__(columns=['Npe_cher', 'Npe_fluo', 'Npe_total']) 111 | _signal(self, telescope, shower, projection, atm_trans, tel_eff, 112 | wvl_ini, wvl_fin, wvl_step) 113 | 114 | def show_projection(self, shower_Edep=True, axes=True, max_theta=30., 115 | X_mark='X_max'): 116 | """ 117 | Show the projection of the shower track viewed by the telescope in both 118 | horizontal and FoV coordinates systems. 119 | 120 | Parameters 121 | ---------- 122 | shower_Edep : bool, default True 123 | Make the radii of the shower track points proportional to 124 | the energy deposited in each step length. 125 | axes : bool, default True 126 | Show the axes of both frames of reference. 127 | max_theta : float, default 30 degrees 128 | Maximum offset angle in degrees relative to the telescope 129 | pointing direction. 130 | X_mark : float 131 | Reference slant depth in g/cm^2 of the shower track to be 132 | marked in the figure. If set to None, no mark is included. 133 | By default, the mark is placed at X_max. 134 | """ 135 | if X_mark == 'X_max': 136 | X_mark = self.shower.X_max 137 | projection = self.projection 138 | profile = self.profile 139 | from ._tools import show_projection 140 | return show_projection(projection, profile, shower_Edep, axes, 141 | max_theta, X_mark) 142 | 143 | def show_profile(self): 144 | """ 145 | Show the shower profile, both number of charged particles and energy 146 | deposit, as a function of slant depth. 147 | 148 | Returns 149 | ------- 150 | (ax1, ax2) : AxesSubplot 151 | """ 152 | return self.profile.show() 153 | 154 | def show_light_production(self): 155 | """ 156 | Show the production of both Cherenkov and fluorescence photons in the 157 | 290 - 430 nm range as a function of slant depth. 158 | 159 | Returns 160 | ------- 161 | (ax1, ax2) : AxesSubplot 162 | """ 163 | return self.shower.show_light_production() 164 | 165 | def show(self): 166 | """ 167 | Show the signal evolution as a function of both time and beta angle 168 | (relative to the shower axis direction). 169 | 170 | The two contributions from Cherenkov and fluorescence light are shown. 171 | 172 | The time scale (us or ns) is adapted depending on the signal pulse 173 | duration. 174 | 175 | Returns 176 | ------- 177 | (ax1, ax2) : AxesSubplot 178 | """ 179 | return _show(self) 180 | 181 | def Image(lat_profile=_Image__lat_profile, N_pix=None, int_time=None, 182 | NSB=_Image__NSB): 183 | """ 184 | Generate a time-varying shower image in a circular camera with square 185 | pixels of same solid angle. A Nishimura-Kamata-Greisen lateral profile 186 | is used to spread the signal contribution from each shower point to 187 | several pixels. 188 | 189 | Parameters 190 | ---------- 191 | lat_profile : bool, default True 192 | Use a NKG lateral profile to spread the signal. If False, a linear 193 | shower is assumed. 194 | N_pix : int, default None 195 | Number of camera pixels. If not given, the value defined in the 196 | Telescope object is used. 197 | int_time : float, default None 198 | Integration time in microseconds of a camera frame. If not 199 | given, the value defined in the Telescope object is used. 200 | NSB : float, default 40 201 | Night sky background in MHz/m^2/deg^2 (photoelectrons). 202 | 203 | Returns 204 | ------- 205 | image : Image 206 | """ 207 | return sm.Image(self, N_pix, int_time, NSB) 208 | 209 | 210 | # Auxiliary functions ######################################################### 211 | def _show(signal): 212 | track = signal.track 213 | projection = signal.projection 214 | 215 | points = signal.index 216 | distance = np.array(projection.distance.loc[points]) 217 | if len(distance) == 0: 218 | print('The shower track is outside the telescope field of view.') 219 | return 220 | beta = np.array(projection.beta.loc[points]) 221 | time = np.array(projection.time.loc[points]) 222 | 223 | # Arrival time interval in microseconds (or nanoseconds) for each 224 | # discretization step. 225 | # c_km_us: speed of light in km/us 226 | Delta_time = track.dl / ct.c_km_us * (1. - np.cos(np.radians(beta))) 227 | ns = True if time.max() < 0.1 else False # Auto-scale 228 | if ns: 229 | time = time * 1000. 230 | Delta_time = Delta_time * 1000. 231 | 232 | # Number of photoelectrons due to each light component (and total) 233 | # per unit time 234 | cher_time = np.array(signal.Npe_cher / Delta_time) 235 | fluo_time = np.array(signal.Npe_fluo / Delta_time) 236 | total_time = cher_time + fluo_time 237 | 238 | # Number of photoelectrons due to each light component (and total) per 239 | # unit of beta angle 240 | Delta_beta = np.degrees(track.dl / distance * np.sin(np.radians(beta))) 241 | cher_beta = np.array(signal.Npe_cher / Delta_beta) 242 | fluo_beta = np.array(signal.Npe_fluo / Delta_beta) 243 | total_beta = cher_beta + fluo_beta 244 | 245 | # Signal evolution as a function of time 246 | fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(10, 5)) 247 | ax1.plot(time, cher_time, 'r--', label='Cherenkov') 248 | ax1.plot(time, fluo_time, 'b--', label='Fluorescence') 249 | ax1.plot(time, total_time, 'k', label='Total') 250 | # Auto-scale 251 | if ns: 252 | ax1.axes.xaxis.set_label_text("Time (ns)") 253 | if signal.tel_eff: 254 | ax1.axes.yaxis.set_label_text("Photoelectrons / ns") 255 | else: 256 | ax1.axes.yaxis.set_label_text("Photons / ns") 257 | else: 258 | ax1.axes.xaxis.set_label_text("Time (us)") 259 | if signal.tel_eff: 260 | ax1.axes.yaxis.set_label_text("Photoelectrons / us") 261 | else: 262 | ax1.axes.yaxis.set_label_text("Photons / us") 263 | ax1.legend(loc=0) 264 | 265 | # Signal evolution as a function of beta 266 | ax2.plot(beta, cher_beta, 'r--', label='Cherenkov') 267 | ax2.plot(beta, fluo_beta, 'b--', label='Fluorescence') 268 | ax2.plot(beta, total_beta, 'k', label='Total') 269 | 270 | ax2.axes.xaxis.set_label_text("Beta (degrees)") 271 | if signal.tel_eff: 272 | ax2.axes.yaxis.set_label_text("Photoelectrons / degree") 273 | else: 274 | ax2.axes.yaxis.set_label_text("Photons / degree") 275 | ax2.legend(loc=0) 276 | plt.tight_layout() 277 | 278 | return ax1, ax2 279 | 280 | 281 | # Constructor ################################################################# 282 | def _signal(signal, telescope, shower, projection, atm_trans, tel_eff, 283 | wvl_ini, wvl_fin, wvl_step): 284 | """ 285 | Calculate the signal produced by a shower detected by a telescope. 286 | 287 | Parameters 288 | ---------- 289 | signal : Signal 290 | telescope : Telescope 291 | shower : Shower 292 | projection : Projection 293 | Only used when called from Event. If None, projection is generated from 294 | telescope and shower. 295 | atm_trans : bool, default True 296 | Include the atmospheric transmission to transport photons. 297 | tel_eff : bool, default True 298 | Include the telescope efficiency to calculate the signal. If False, 299 | 100% efficiency is assumed for a given wavelength interval. 300 | wvl_ini : float, default 290 301 | Initial wavelength in nm of the interval to calculate the signal when 302 | tel_eff==False. 303 | wvl_fin : float, default 430 304 | Final wavelength in nm of the interval to calculate the signal when 305 | tel_eff==False. 306 | wvl_step : float, default 3 307 | Discretization step in nm of the interval to calculate the signal when 308 | tel_eff==False. 309 | """ 310 | if not isinstance(telescope, sm.Telescope): 311 | if not isinstance(telescope, sm.Shower): 312 | raise ValueError('The input telescope is not valid') 313 | else: 314 | telescope, shower = (shower, telescope) 315 | if not isinstance(shower, sm.Shower): 316 | raise ValueError('The input shower is not valid') 317 | 318 | # This function is normally called from Event. If not, projection must be 319 | # generated. 320 | if not isinstance(projection, sm.Projection): 321 | projection = sm.Projection(telescope, shower.track) 322 | atmosphere = shower.atmosphere 323 | track = shower.track 324 | fluorescence = shower.fluorescence 325 | cherenkov = shower.cherenkov 326 | 327 | # signal = Signal() 328 | signal.shower = shower 329 | signal.telescope = telescope 330 | signal.projection = projection 331 | signal.atmosphere = atmosphere 332 | signal.track = track 333 | signal.profile = shower.profile 334 | signal.fluorescence = fluorescence 335 | signal.cherenkov = cherenkov 336 | 337 | signal.atm_trans = atm_trans 338 | signal.tel_eff = tel_eff 339 | 340 | if tel_eff: 341 | # Wavelength range defined in telescope 342 | wvl_ini = telescope.wvl_ini 343 | wvl_fin = telescope.wvl_fin 344 | wvl_step = telescope.wvl_step 345 | wvl_cher = telescope.wvl 346 | eff_fluo = telescope.eff_fluo 347 | eff_cher = telescope.eff 348 | 349 | else: 350 | # User-defined wavelength range 351 | wvl_cher = np.arange(wvl_ini, wvl_fin, wvl_step) 352 | signal.wvl_ini = wvl_ini 353 | signal.wvl_fin = wvl_fin 354 | signal.wvl_step = wvl_step 355 | 356 | # Only discretization points within the telescope field of view contributes 357 | # to the signal. In addition, the very beginning of the shower profile is 358 | # ignored to speed up calculations 359 | points = projection[projection.FoV & (signal.profile.s > 0.01)].index 360 | distance = np.array(projection.distance.loc[points]) 361 | theta = np.radians(projection.theta.loc[points]) 362 | alt = np.radians(projection.alt.loc[points]) 363 | 364 | # Solid angle fraction covered by the telescope area. Only discretization 365 | # points within the telescope field of view contributes to the signal 366 | # area is in m^2 but distance is in km 367 | collection = (telescope.area * np.cos(theta) / 4000000. / ct.pi 368 | / distance**2) 369 | 370 | # Collection efficiency for the angular distribution of Cherenkov light 371 | # See F. Nerling et al., Astropart. Phys. 24(2006)241. 372 | beta = np.radians(projection.beta.loc[points]) 373 | theta_c = np.radians(cherenkov.theta_c.loc[points]) 374 | theta_cc = np.radians(cherenkov.theta_cc.loc[points]) 375 | a = np.array(cherenkov.a.loc[points]) 376 | b = np.array(cherenkov.b.loc[points]) 377 | collection_cher = collection * 2. / np.sin(beta) * ( 378 | a / theta_c * np.exp(-beta / theta_c) 379 | + b / theta_cc * np.exp(-beta / theta_cc)) 380 | 381 | # Relative fluorescence contribution from each shower point at each band 382 | # (between wvl_ini and wvl_fin). The atmospheric transmission is included 383 | # later 384 | rel_fluo = fluorescence.loc[points] 385 | # Selection of bands within the wavelength range 386 | rel_fluo = rel_fluo.loc[:, wvl_ini:wvl_fin] 387 | wvl_fluo = np.array(ct.fluo_model['wvl']) 388 | sel = (wvl_fluo>=wvl_ini) & (wvl_fluo<=wvl_fin) 389 | wvl_fluo = wvl_fluo[sel] 390 | if tel_eff: 391 | eff_fluo = eff_fluo[sel] 392 | rel_fluo *= eff_fluo 393 | 394 | if atm_trans: 395 | # Atmospheric transmission at 350 nm. Only Rayleigh scattering is 396 | # considered 397 | X_vert = np.array(atmosphere.X_vert.loc[points]) 398 | rho = np.array(atmosphere.rho.loc[points]) 399 | thickness = np.array(atmosphere.h_to_Xv(atmosphere.h0 + telescope.z) 400 | - X_vert) 401 | thickness[thickness != 0] = (thickness[thickness != 0] 402 | / np.sin(alt[thickness != 0])) 403 | thickness[thickness == 0] = (100000. * distance[thickness == 0] 404 | * rho[thickness == 0]) 405 | # Only points within the telescope FoV, otherwise trans=0 406 | trans = np.exp(-thickness / 1645.) 407 | 408 | # Relative fluorescence contribution including atmospheric transmission 409 | for wvl in wvl_fluo: 410 | rel_fluo[wvl] *= trans ** ((350. / wvl)**4) 411 | 412 | # Wavelength factor for Cherenkov contribution to signal from each 413 | # shower point 414 | wvl_factor = pd.DataFrame(index=points, columns=wvl_cher) 415 | for wvl in wvl_cher: 416 | wvl_factor[wvl] = trans ** ((350. / wvl)**4) / wvl**2 417 | # wvl**2 -> (wvl**2 - wvl_step**2 / 4.) 418 | if tel_eff: 419 | wvl_factor *= eff_cher 420 | wvl_factor = wvl_factor.sum(axis=1) * wvl_step / (1./_Signal__wvl_ini- 421 | 1./_Signal__wvl_fin) 422 | 423 | elif tel_eff: # If atmospheric transmission is not included 424 | # The wavelength factor of Cherenkov signal is the same for all 425 | # shower points 426 | wvl_factor = eff_cher / wvl_cher**2 427 | # wvl_cher**2 -> (wvl_cher**2 - wvl_step**2 / 4.) 428 | wvl_factor = wvl_factor.sum() * wvl_step / (1./_Signal__wvl_ini- 429 | 1./_Signal__wvl_fin) 430 | 431 | else: # If both atm_trans==False and tel_eff==False 432 | # The wavelength factor only depends on the chosen wavelenth interval. 433 | # Note that the wavelength interval may be different to that defined 434 | # in the Cherenkov class (default one) 435 | wvl_factor = (1./wvl_ini-1./wvl_fin) / (1./_Signal__wvl_ini- 436 | 1./_Signal__wvl_fin) 437 | 438 | # Number of photoelectrons due to fluorescence light emitted from each 439 | # shower point 440 | signal['Npe_fluo'] = rel_fluo.sum(axis=1) * collection 441 | # Number of photoelectrons due to fluorescence light within the FoV 442 | signal.Npe_fluo_sum = signal.Npe_fluo.sum() 443 | # Number of photoelectrons due to Cherenkov light emitted from each shower 444 | # point 445 | signal['Npe_cher'] = (cherenkov.N_ph.loc[points] * collection_cher 446 | * wvl_factor) 447 | # Number of photoelectrons due to Cherenkov light within the FoV 448 | signal.Npe_cher_sum = signal.Npe_cher.sum() 449 | # Total number of photoelectrons from both light components emitted at each 450 | # shower point 451 | signal['Npe_total'] = signal.sum(axis=1) 452 | # Total number of photoelectrons 453 | signal.Npe_total_sum = signal.Npe_cher_sum + signal.Npe_fluo_sum 454 | -------------------------------------------------------------------------------- /src/showermodel/tests/test_shower.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def test_shower(): 4 | from showermodel import Shower 5 | theta = 20.0 6 | # Check the value obtained with the settings below is the following 7 | h_top = 112.8292 8 | shower = Shower(1.e6, theta=theta, az=45.0, x0=0.1, y0=0.2, atm_model=17) 9 | assert shower.alt == 90.0 - theta 10 | assert shower.az == 45.0 11 | assert shower.h_top == h_top 12 | assert np.isclose(shower.atmosphere.P[100], 49.69842905334434) 13 | assert np.isclose(shower.track.t[200], 254.50685251195043) 14 | assert np.isclose(shower.profile.s[10], 1.6504072224436146) 15 | assert np.isclose(shower.cherenkov.N_ph[50], 757147.620313149) 16 | -------------------------------------------------------------------------------- /src/showermodel/tests/test_showermodel.py: -------------------------------------------------------------------------------- 1 | def test_import_showermodel(): 2 | import showermodel 3 | 4 | 5 | def test_import_shower(): 6 | from showermodel import Shower 7 | -------------------------------------------------------------------------------- /src/showermodel/tests/test_telescope.py: -------------------------------------------------------------------------------- 1 | def test_telescope(): 2 | from showermodel.telescope import Telescope 3 | 4 | # Default telescope type: IACT 5 | # Default angular aperture in degrees: 8.0 6 | # Default detection area in m2: 113.097 7 | telescope = Telescope() 8 | assert telescope.tel_type == 'generic' 9 | assert telescope.apert == 10.0 10 | assert telescope.area == 100.0 11 | -------------------------------------------------------------------------------- /src/showermodel/version.py: -------------------------------------------------------------------------------- 1 | # this is adapted from https://github.com/astropy/astropy/blob/main/astropy/version.py 2 | # see https://github.com/astropy/astropy/pull/10774 for a discussion on why this needed. 3 | 4 | try: 5 | try: 6 | from ._dev_version import version 7 | except ImportError: 8 | from ._version import version 9 | except Exception: 10 | import warnings 11 | 12 | warnings.warn( 13 | "Could not determine version; this indicates a broken installation." 14 | " Install from PyPI, using conda or from a local git repository." 15 | " Installing github's autogenerated source release tarballs " 16 | " does not include version information and should be avoided." 17 | ) 18 | del warnings 19 | version = "0.0.0" 20 | 21 | __version__ = version --------------------------------------------------------------------------------