diff --git a/.gitignore b/.gitignore index b660af4044bacc5cadcbfbaa4f7815a78b58b7af..b3ff3663bec2ef29f4d3c59f7c4678295622d002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Patterns .* -*.sh *.png # Folders diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..7b11121f93fb22df688e1890d15ea146bb084932 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "rtasci"] + path = rtasci + url = git@github.com:ambra-dipiano/rtasci.git +[submodule "rtavis"] + path = rtavis + url = git@github.com:ambra-dipiano/rtavis.git +[submodule "rtamock"] + path = rtamock + url = git@github.com:ambra-dipiano/rtamock.git diff --git a/README.md b/README.md index 828505f68894f53d1f9f3db3a374d25b48a40d4e..1f10de76b873dd906dad6b9cbc3bcb467e28a546 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ -# astroRT \ No newline at end of file +# astroRT + +## Environment + +To create a virtual environment with all required dependencies: + +```bash +conda env create -f environment.yml +``` + +Note that you should already have anaconda [anaconda](https://www.anaconda.com/) installed. +Once the environment is created, proceeded to install the software. + +```bash +conda activate astrort +pip install . +``` + +To install editable version use instead: + +```bash +pip install -e . +``` + +# Submodules + +## rtasci + +Simulator of CTA observations. + +## rtavis + +Visibility tool for CTA observations. + +## rtamock + +Scenario mocking software to reproduce CTA real-time observation and analysis conditions. + +## rtadeep [TODO] + +Deep learning solutions for CTA real-time analysis. \ No newline at end of file diff --git a/rtasci/rtasci/__init__.py b/astrort/__init__.py similarity index 100% rename from rtasci/rtasci/__init__.py rename to astrort/__init__.py diff --git a/rtasci/rtasci/cfg/__init__.py b/astrort/configure/__init__.py similarity index 100% rename from rtasci/rtasci/cfg/__init__.py rename to astrort/configure/__init__.py diff --git a/astrort/configure/check_configuration.py b/astrort/configure/check_configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..cf4f9573e21ad541e58cdca690870036bf94604d --- /dev/null +++ b/astrort/configure/check_configuration.py @@ -0,0 +1,45 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +class CheckConfiguration(): + + def __init__(self, configuration): + assert type(configuration) == dict, f'configuration type {type(configuration)} invalid, a python dictionary is required' + self.conf = configuration + pass + + def check(self): + self.check_tags() + self.check_simulator() + self.check_visibility() + return self + + def check_tags(self): + tags = ['simulator', 'visibility'] + assert self.conf.keys() == tags + return self + + def check_simulator(self): + keys = ['name', 'array', 'irf', 'prod', 'pointing', 'duration', 'samples', 'seed', 'model'] + assert self.conf['simulator'].keys() == keys + assert type(self.conf['simulator']['name']) == str + assert type(self.conf['simulator']['array']) in ['lst', 'mst', 'sst', 'cta', 'north', 'south'] + assert type(self.conf['simulator']['irf']) == str + assert type(self.conf['simulator']['prod']) == str + assert (type(self.conf['simulator']['pointing']) == str or type(self.conf['simulator']['pointing']) == dict) + assert type(self.conf['simulator']['duration']) == int + assert type(self.conf['simulator']['samples']) == int + assert type(self.conf['simulator']['seed']) == int + assert type(self.conf['simulator']['model']) == str + return self + + def check_visibility(self): + keys = ['start_time'] + assert self.conf['visibility'].keys() == keys + assert type(self.conf['visibility']['start_time']) == str + return self diff --git a/astrort/configure/test.yml b/astrort/configure/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..5728707099cc9789caae62a95f69d6e9a478933e --- /dev/null +++ b/astrort/configure/test.yml @@ -0,0 +1,15 @@ +simulator: + name: test + array: lst + irf: North_z60_0.5h_LST + prod: prod5 + pointing: random + duration: 300 + samples: 5 + seed: 1 + model: $TEMPLATES$/crab.xml + output: /data01/homes/dipiano/astroRT/ + +visibility: + start_time: '2030-01-01T00:00:00' + diff --git a/rtasci/rtasci/lib/__init__.py b/astrort/simulator/__init__.py similarity index 100% rename from rtasci/rtasci/lib/__init__.py rename to astrort/simulator/__init__.py diff --git a/astrort/simulator/base_simulator.py b/astrort/simulator/base_simulator.py new file mode 100644 index 0000000000000000000000000000000000000000..41672c6a027d5eaa51a2fc6cd2fd7ee1750a6b35 --- /dev/null +++ b/astrort/simulator/base_simulator.py @@ -0,0 +1,22 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import argparse +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation +from rtavis.utils.visibility import Visibility +from astrort.utils.wrap import load_yaml_conf +from astrort.cfg.check_configuration import CheckConfiguration + +parser = argparse.ArgumentParser(description='') +parser.add_argument('-cf', '--conf', type=str, required=True, help="Path of yaml configuration file") +args = parser.parse_args() + +conf = load_yaml_conf(args.conf) + + + diff --git a/astrort/templates/crab.xml b/astrort/templates/crab.xml new file mode 100644 index 0000000000000000000000000000000000000000..47843b8dc4a3668259dae724a082fb609a48bb6f --- /dev/null +++ b/astrort/templates/crab.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/astrort/testing/conftest.py b/astrort/testing/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..4edcb34153b41082da8212600473d4784e99d438 --- /dev/null +++ b/astrort/testing/conftest.py @@ -0,0 +1,21 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +import astrort +from os.path import join, dirname, abspath +from os import makedirs + +@pytest.fixture(scope='function') +def astrort_configuration(): + return join(dirname(abspath(astrort.__file__)), 'configure', 'test.yml') + +@pytest.fixture(scope='function') +def astrort_tmp_folder(): + makedirs(join(dirname(abspath(astrort.__file__)), 'testing', 'tmp'), exist_ok=True) + return join(dirname(abspath(astrort.__file__)), 'testing', 'tmp') diff --git a/astrort/testing/pytest.ini b/astrort/testing/pytest.ini new file mode 100644 index 0000000000000000000000000000000000000000..5c0519734fd9fe1dace6d88900b93819bcf86a99 --- /dev/null +++ b/astrort/testing/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + astrort_configuration: rtadeep test configurtion file \ No newline at end of file diff --git a/astrort/testing/test_astrort/test_configure/test_check_configuration.py b/astrort/testing/test_astrort/test_configure/test_check_configuration.py new file mode 100644 index 0000000000000000000000000000000000000000..903a82db130f17a6360342963fcf715d29d00828 --- /dev/null +++ b/astrort/testing/test_astrort/test_configure/test_check_configuration.py @@ -0,0 +1,42 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +from astrort.utils.wrap import load_yaml_conf +from astrort.configure.check_configuration import CheckConfiguration + +@pytest.mark.astrort_configuration +class TestCheckConfiguration: + + def test_check(self, astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) + try: + CheckConfiguration(configuration=configuration).check() + except AssertionError as e: + type(e) == AssertionError + + def test_check_tags(self, astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) + try: + CheckConfiguration(configuration=configuration).check_tags() + except AssertionError as e: + type(e) == AssertionError + + def test_check_simulator(self, astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) + try: + CheckConfiguration(configuration=configuration).check_simulator() + except AssertionError as e: + type(e) == AssertionError + + def test_check_visibility(self, astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) + try: + CheckConfiguration(configuration=configuration).check_visibility() + except AssertionError as e: + type(e) == AssertionError diff --git a/astrort/testing/test_astrort/test_simulator/test_base_simulator.py b/astrort/testing/test_astrort/test_simulator/test_base_simulator.py new file mode 100644 index 0000000000000000000000000000000000000000..b20924049cf65b31b25d31aa33ad1cb078152307 --- /dev/null +++ b/astrort/testing/test_astrort/test_simulator/test_base_simulator.py @@ -0,0 +1,22 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +from astrort.utils.wrap import load_yaml_conf +from astrort.cfg.check_configuration import CheckConfiguration + +@pytest.mark.rtadeep_configuration +class TestBaseSimulator: + + @pytest.mark.skip('to-do') + def test_base_simulator(self, rtadeep_configuration): + + # get configuration + configuration = load_yaml_conf(rtadeep_configuration) + + diff --git a/astrort/testing/test_astrort/test_utils/test_utils.py b/astrort/testing/test_astrort/test_utils/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a78255bafccb8b23112fb37f8440bed6a8515dd6 --- /dev/null +++ b/astrort/testing/test_astrort/test_utils/test_utils.py @@ -0,0 +1,40 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +from astrort.utils.utils import seeds_to_string_formatter, get_instrument_fov + +@pytest.mark.astrort_tmp_folder +@pytest.mark.parametrize('samples', [3, 5, 8, 10]) +def test_seeds_to_string_formatter(samples, astrort_tmp_folder): + name = seeds_to_string_formatter(samples, astrort_tmp_folder, name='test', seed=1) + + if samples <= 3: + assert name == f"{astrort_tmp_folder}/test_001" + elif samples <= 5: + assert name == f"{astrort_tmp_folder}/test_00001" + elif samples <= 8: + assert name == f"{astrort_tmp_folder}/test_00000001" + else: + assert name == f"{astrort_tmp_folder}/test_1" + +@pytest.mark.parametrize('array', ['lst', 'mst', 'sst', 'cta', 'north', 'south']) +def test_get_instrument_fov(array): + fov = get_instrument_fov(array) + + if array == 'lst': + assert fov == 2.5 + elif array == 'mst': + assert fov == 3.5 + elif array == 'sst': + assert fov == 5 + else: + assert fov == 5 + return fov + + diff --git a/rtavis/setup.py b/astrort/testing/test_astrort/test_utils/test_wrap.py similarity index 51% rename from rtavis/setup.py rename to astrort/testing/test_astrort/test_utils/test_wrap.py index 93996a84b03a2efa26f2fda9bfeed6f7220f3187..04cb39b127a6c65a4572e9720bf6348b603b05a3 100644 --- a/rtavis/setup.py +++ b/astrort/testing/test_astrort/test_utils/test_wrap.py @@ -1,18 +1,15 @@ # ***************************************************************************** -# Copyright (C) 2021 INAF +# Copyright (C) 2023 INAF # This software is distributed under the terms of the BSD-3-Clause license # # Authors: # Ambra Di Piano # ***************************************************************************** -from setuptools import setup, find_packages +import pytest +from astrort.utils.wrap import load_yaml_conf -setup( - name='rtavis', - author='Ambra Di Piano ', - package_dir={'rtasee': 'rtavis'}, - packages=find_packages(), - include_package_data=True, - license='BSD-3-Clause', -) \ No newline at end of file +@pytest.mark.astrort_configuration +def test_load_yaml_conf(astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) + assert type(configuration) == dict \ No newline at end of file diff --git a/rtasci/rtasci/misc/__init__.py b/astrort/utils/__init__.py similarity index 100% rename from rtasci/rtasci/misc/__init__.py rename to astrort/utils/__init__.py diff --git a/astrort/utils/utils.py b/astrort/utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c6723b0b8d78751b03f7b0af5365589d3610c724 --- /dev/null +++ b/astrort/utils/utils.py @@ -0,0 +1,31 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +from os.path import join + +def seeds_to_string_formatter(samples, output, name, seed): + if samples <= 3: + name = join(output, f"{name}_{seed:03d}") + elif samples <= 5: + name = join(output, f"{name}_{seed:05d}") + elif samples <= 8: + name = join(output, f"{name}_{seed:08d}") + else: + name = join(output, f"{name}_{seed}") + return name + +def get_instrument_fov(instrument): + if instrument == 'lst': + fov = 2.5 + elif instrument == 'mst': + fov = 3.5 + elif instrument == 'sst': + fov = 5 + else: + fov = 5 + return fov \ No newline at end of file diff --git a/astrort/utils/wrap.py b/astrort/utils/wrap.py new file mode 100644 index 0000000000000000000000000000000000000000..807fc6f7f48c640f3f0e0bf1203c4c6984db7b1e --- /dev/null +++ b/astrort/utils/wrap.py @@ -0,0 +1,24 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import yaml +from astrort.utils.utils import seeds_to_string_formatter, get_instrument_fov +from astrort.configure.check_configuration import CheckConfiguration + +def load_yaml_conf(yamlfile): + with open(yamlfile) as f: + configuration = yaml.load(f, Loader=yaml.FullLoader) + CheckConfiguration(configuration=configuration) + return configuration + +def configure_simulator(simulator, configuration): + simulator.model = configuration['model'] + simulator.output = seeds_to_string_formatter(configuration['samples'], configuration['output'], configuration['name'], configuration['seed']) + simulator.caldb = configuration['prod'] + simulator.irf = configuration['irf'] + simulator.fov = get_instrument_fov(configuration['array']) diff --git a/environment.yml b/environment.yml index b259179b23d5aa3ec938a7f8de5561a4fe6dd625..9bc21a3995c721a56d47774a1ac236f9365887c2 100644 --- a/environment.yml +++ b/environment.yml @@ -1,5 +1,6 @@ name: astrort channels: + - anaconda - defaults - conda-forge - cta-observatory @@ -8,12 +9,13 @@ dependencies: - _openmp_mutex=4.5=2_kmp_llvm - astropy=5.1=py38h7deecbd_0 - asttokens=2.0.5=pyhd3eb1b0_0 + - attrs=22.1.0=py38h06a4308_0 - backcall=0.2.0=pyhd3eb1b0_0 - blas=1.0=mkl - brotli=1.0.9=h5eee18b_7 - brotli-bin=1.0.9=h5eee18b_7 - c-ares=1.19.1=h5eee18b_0 - - ca-certificates=2023.08.22=h06a4308_0 + - ca-certificates=2023.01.10=h06a4308_0 - cfitsio=3.430=hd130d23_1 - comm=0.1.2=py38h06a4308_0 - contourpy=1.0.5=py38hdb19cb5_0 @@ -127,7 +129,7 @@ dependencies: - pyparsing=3.0.9=py38h06a4308_0 - pyqt=5.15.7=py38h6a678d5_1 - pyqt5-sip=12.11.0=py38h6a678d5_1 - - pytest=7.4.0=py38h06a4308_0 + - pytest=7.1.2=py38h06a4308_0 - pytest-runner=6.0.0=py38h06a4308_0 - python=3.8.17=h955ad1f_0 - python-dateutil=2.8.2=pyhd3eb1b0_0 @@ -162,3 +164,7 @@ dependencies: - zipp=3.11.0=py38h06a4308_0 - zlib=1.2.13=h5eee18b_0 - zstd=1.5.5=hc292b87_0 + - pip: + - astrort==0.0.0 + - rtavis==0.0.0 +prefix: /data01/homes/dipiano/.conda/envs/astrort diff --git a/install.sh b/install.sh new file mode 100755 index 0000000000000000000000000000000000000000..2324587d23a76bb9786dccd27eced8c573044023 --- /dev/null +++ b/install.sh @@ -0,0 +1,19 @@ +#!/bin/bas + +# install astroRT +pip install -e . + +# install rtasci +cd rtasci +pip install -e . +cd .. + +# install rtavis +cd rtavis +pip install -e . +cd .. + +# install rtamock +cd rtamock +pip install -e . +cd .. \ No newline at end of file diff --git a/prod5install.sh b/prod5install.sh deleted file mode 100644 index bc2bee49ad36e51ad740b644a0fae8430d0f298d..0000000000000000000000000000000000000000 --- a/prod5install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -mkdir tmp -cd tmp -wget http://cta.irap.omp.eu/ctools/_downloads/csadd2caldb.py -wget https://zenodo.org/record/5499840/files/cta-prod5-zenodo-fitsonly-v0.1.zip -python csadd2caldb.py debug=yes -cd .. -rm -rf tmp diff --git a/rtamock b/rtamock new file mode 160000 index 0000000000000000000000000000000000000000..e0b346bca408515cbd94787374eadbf511d4708b --- /dev/null +++ b/rtamock @@ -0,0 +1 @@ +Subproject commit e0b346bca408515cbd94787374eadbf511d4708b diff --git a/rtamock/.gitignore b/rtamock/.gitignore deleted file mode 100644 index 4cbb31b6c468ae56b958ae3de9fec46a9349d544..0000000000000000000000000000000000000000 --- a/rtamock/.gitignore +++ /dev/null @@ -1,149 +0,0 @@ -# files -*.yml -*.xml -*.fits -*.png -*.csv -*.reg -loadenv.sh - -# folders -rtamock/analysis_archive - -# exceptions -!rtamock/data/template/* -!rtamock/data/template/* -!rtamock/data/template/* -!rtamock/data/template/* -!rtamock/data/template/* - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - diff --git a/rtamock/LICENSE b/rtamock/LICENSE deleted file mode 100644 index 2c269ef9997d8d2a9db1f7fde72ffaab584113f9..0000000000000000000000000000000000000000 --- a/rtamock/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2022, Ambra Di Piano -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rtamock/MANIFEST.in b/rtamock/MANIFEST.in deleted file mode 100644 index 82e440e9d6c4173f6dd36158f71b118aa04c1f0c..0000000000000000000000000000000000000000 --- a/rtamock/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.md - -recursive-include * *.py \ No newline at end of file diff --git a/rtamock/README.md b/rtamock/README.md deleted file mode 100644 index bce71c8f5e88fb1c1426da93e510863089070537..0000000000000000000000000000000000000000 --- a/rtamock/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Export $VARS and activate environment - -- Activate the environment where the SAG-SCI and the science tool in used by the wrapper are installed -- Export the environment variable corresponding to the wrapper, i.e. RTAPH or GAMMAPY - -```bash -export RTAPH=path/to/sagsci/wrappers/rtaph -export GAMMAPY=path/to/sagsci/wrappers/gammapy -``` - -# Prepare dataset - -- From single observation create time bins -- From target.xml template create bins file and update source name -- From job.xml template create bins file and update values -- From obs.xml template create bins file and update values - -```bash -python prepare_dataset.py -f -``` - -# Execute analysis single run - -- Perform analysis in each bin, either lightcurve (each bin alone) or cumulative (since start of run) and save results -- Move bins and results into the archive - -```bash -python analysis_one_run.py -f -``` - -# Collect run results from single bins - -- From the archive collect all results and create a merged csv data file for easy plotting - -```bash -python collect_bins_results.py -f -``` - -# Plot lightcurves of list of runs - -- Load the csv data file and plot the specified curves - -```bash -python plot_lightcurves.py -f -cp -``` \ No newline at end of file diff --git a/rtamock/pyproject.toml b/rtamock/pyproject.toml deleted file mode 100644 index fa7093a33c048d9db6e0eb3b4d8daa57426754e3..0000000000000000000000000000000000000000 --- a/rtamock/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools>=42"] -build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/rtamock/rtamock/__init__.py b/rtamock/rtamock/__init__.py deleted file mode 100644 index 6069d3dc1bbc4d9b65fde98f779775df244f335e..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -# Import sub modules -from . import * \ No newline at end of file diff --git a/rtamock/rtamock/analysis_one_run.py b/rtamock/rtamock/analysis_one_run.py deleted file mode 100644 index e1b864139025b9e281e42b58d0f629df36d4ac91..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/analysis_one_run.py +++ /dev/null @@ -1,77 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import yaml -import argparse -import numpy as np -from os import listdir, system, makedirs -from os.path import join, isdir, isfile, basename -from sagsci.tools.utils import get_absolute_path -from rtamock.tools.utils import set_logger - -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--configfile", default="myconfig.yml", help="configuration file") -args = parser.parse_args() - -# get YAML configuration -with open(args.configfile) as configuration: - config = yaml.load(configuration, Loader=yaml.FullLoader) -dataset = get_absolute_path(config['dirlist']['data']) -archive_dir = get_absolute_path(config["dirlist"]["archive"].replace("XXX", str(config["run"]["runid"]))) -runid = config['run']['runid'] -wrapper = config['wrapper'].upper() - -# logging -logname = join(get_absolute_path(config["logging"]["folder"]), basename(__file__).replace('.py','.log')) -log = set_logger(filename=logname, level=config["logging"]['level']) -log.info('Logging: ' + logname) - -# get all time bins in run -log.info(f'Collect timebins in run {runid}') -datapath = join(dataset, str(runid)) -subdirs = np.sort([join(datapath, d) for d in listdir(datapath) if isdir(join(datapath, d))]) -log.debug(f"Directories: {subdirs}") - -# loop all time bins -new_dir = join(datapath, f"analysis_{config['run']['nbins']}_bins_{config['run']['type']}") -log.debug(f"New directory: {new_dir}") -system(f'mkdir {new_dir}') -for d in subdirs: - # check all files are there - system(f'cd {datapath}') - if not isfile(join(d, 'obs.xml')): - log.error(f'Missing obs.xml in: {d}') - elif not isfile(join(d, 'job.xml')): - log.error(f'Missing job.xml in: {d}') - elif not isfile(join(d, 'target.xml')): - log.error(f'Missing target.xml in: {d}') - elif not isfile(join(d, basename(d)+'.fits')): - log.error(f'Missing {basename(d)}.fits in: {d}') - - # run time bin analysis - log.info(f'Execute analysis of: {basename(d)}') - log.debug(f"python {get_absolute_path(f'${wrapper.upper()}')}/execute.py -obs {d}/obs.xml -job {d}/job.xml -target {d}/target.xml -evt {d}/{basename(d)}.fits") - system(f"python {get_absolute_path(f'${wrapper.upper()}')}/execute.py -obs {d}/obs.xml -job {d}/job.xml -target {d}/target.xml -evt {d}/{basename(d)}.fits") - - # move bin in new_dir - log.debug(f"Move {d} in {new_dir}") - system(f'mv {d} {new_dir}') - -# move new_dir in archive -if isdir(archive_dir): - system(f"rm -rf {archive_dir}") -makedirs(archive_dir) -log.debug(f"Move {new_dir} in {archive_dir}") -system(f'mv {new_dir} {archive_dir}') - -# close -log.info(f'Analysis of run {runid} completed with {wrapper.upper()} science tool.') - - - diff --git a/rtamock/rtamock/collect_bins_results.py b/rtamock/rtamock/collect_bins_results.py deleted file mode 100644 index 36754083863b0ddc4130a4d4be4b66d3234e58f5..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/collect_bins_results.py +++ /dev/null @@ -1,86 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import yaml -import numpy as np -import argparse -import xml.etree.ElementTree as ET -from os import listdir -from os.path import join, isdir, basename -from sagsci.tools.utils import get_absolute_path -from sagsci.tools.myxml import MyXml -from rtamock.tools.utils import set_logger - - -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--configfile", default="myconfig.yml", help="configuration file") -args = parser.parse_args() - -# get YAML configuration -configuration = open(args.configfile) -config = yaml.load(configuration, Loader=yaml.FullLoader) - -# logging -logname = join(get_absolute_path(config["logging"]["folder"]), basename(__file__).replace('.py','.log')) -log = set_logger(filename=logname, level=config["logging"]['level']) -log.info('Logging: ' + logname) - -# get all time bins in run -log.info(f'Collect timebins in run {config["run"]["runid"]}') -datapath = join(get_absolute_path(config["dirlist"]["archive"]).replace('XXX', str(config['run']['runid'])), f"analysis_{config['run']['nbins']}_bins_{config['run']['type']}") -subdirs = np.sort([join(datapath, d) for d in listdir(datapath) if isdir(join(datapath, d))]) - -datafile = join(datapath, f'run{config["run"]["runid"]}_{config["run"]["type"]}_{config["run"]["nbins"]}bins.csv') - -# create and clear file -f = open(datafile, 'w+') -hdr = "tmin tmax time time_err excess excess_err sigma sigma_err flux flux_err on_counts off_counts alpha emin emax\n" -f.writelines([hdr]) - -# loop all time bins -for idx, d in enumerate(subdirs): - - # get from job configuration - job = join(datapath, d, 'job.xml') - xml = open(job) - jobconf = ET.parse(xml) - tmin = float(jobconf.find('parameter[@name="TimeIntervals"]').attrib['tmin']) - tmax = float(jobconf.find('parameter[@name="TimeIntervals"]').attrib['tmax']) - #tmean = (tmin + tmax)/2 - tmean = tmax - terror = (tmax - tmin)/2 - log.debug(f'time = [{tmin}, {tmax}], exposure = {tmax-tmin}') - log.debug(f'bin center = [{tmean}]') - emin = float(jobconf.find('parameter[@name="Energy"]').attrib['emin']) - emax = float(jobconf.find('parameter[@name="Energy"]').attrib['emax']) - xml.close() - - # get from results.xml file - results = join(datapath, d, 'results', basename(d)+'_results.xml') - xml = MyXml(results) - source = xml.get_source_name() - excess = xml.get_job_results(source=source, parameter='Photometric', attribute='excess') - excess_err = xml.get_job_results(source=source, parameter='Photometric', attribute='excess_err') - off_counts = xml.get_job_results(source=source, parameter='Photometric', attribute='off_counts') - off_err = np.sqrt(off_counts) - alpha = xml.get_job_results(source=source, parameter='Photometric', attribute='alpha') - on_counts = xml.get_job_results(source=source, parameter='Photometric', attribute='on_counts') - on_err = np.sqrt(on_counts) - sigma = xml.get_job_results(source=source, parameter='Significance', attribute='value') - sigma_err = xml.get_job_results(source=source, parameter='Significance', attribute='error') - flux = xml.get_job_results(source=source, parameter='IntegratedFlux', attribute='value') - flux_err = xml.get_job_results(source=source, parameter='IntegratedFlux', attribute='error') - xml.close_xml() - - # write to file - row = f"{tmin} {tmax} {tmean} {terror} {excess} {excess_err} {sigma} {sigma_err} {flux} {flux_err} {on_counts} {off_counts} {alpha} {emin} {emax}\n" - f.writelines([row]) - -f.close() -log.info(f'Results collected in: {datafile}') diff --git a/rtamock/rtamock/collect_results.py b/rtamock/rtamock/collect_results.py deleted file mode 100644 index 67018d02d91099c84a3c47c12d149dd88b09908c..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/collect_results.py +++ /dev/null @@ -1,86 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import csv -import argparse -import numpy as np -import xml.etree.ElementTree as ET -from os import listdir, makedirs -from os.path import join, isdir, isfile -from sagsci.tools.utils import str2bool -from sagsci.tools.myxml import MyXml - -# get line command options -parser = argparse.ArgumentParser() -parser.add_argument('-d', '--directories', default=['data/crab/run1/job1', 'data/crab/run2/job1', 'data/crab/run3/job1', 'data/crab/run4/job1'], help='list of folders of wobble runs to display results from') -parser.add_argument('-s', '--source', type=str, default='Crab', help='target of the observation') -parser.add_argument('-lc', '--lightcurve', type=str, default='true', choices=['true', 'false'], help='collect the lightcurve from results files of single bins') -parser.add_argument('-c', '--cumulative', type=str, default='true', choices=['true', 'false'], help='collect the lightcurve from results files of single bins') -parser.add_argument('-o', '--output', type=str, default='output', help='output folder') -args = parser.parse_args() - -if type(args.directories) is not list: - args.directories = [args.directories] - -if str2bool(args.lightcurve): - if not isdir(args.output): - makedirs(args.output) - f = open(join(args.output, 'lightcurve.txt'), 'w') - w = csv.writer(f, delimiter=' ') - hdr = ['time', 'time_err', 'excess', 'excess_err', 'sigma', 'sigma_err', 'flux', 'flux_err', 'bkg', 'bkg_err', 'emin', 'emax'] - w.writerow(hdr) - f.close() - -if str2bool(args.lightcurve): - if not isdir(args.output): - makedirs(args.output) - f = open(join(args.output, 'cumulative.txt'), 'w+') - w = csv.writer(f, delimiter=' ') - hdr = ['time', 'time_err', 'excess', 'excess_err', 'sigma', 'sigma_err', 'flux', 'flux_err', 'bkg', 'bkg_err', 'emin', 'emax'] - w.writerow(hdr) - f.close() - -# loop directories -if args.lightcurve: - for d in args.directories: - bins = [join(d, b) for b in listdir(d) if isdir(join(d, b))] - for b in bins: - # get from results - try: - results = [join(b, f) for f in listdir(b) if isfile(join(b, f)) and '_results.xml' in f][0] - except IndexError: - continue - xml = MyXml(results) - excess = xml.get_job_results(source=args.source, parameter='Photometric', attribute='excess') - excess_err = xml.get_job_results(source=args.source, parameter='Photometric', attribute='excess_err') - bkg = xml.get_job_results(source=args.source, parameter='Photometric', attribute='off_counts') - bkg_err = np.sqrt(bkg) - sigma = xml.get_job_results(source=args.source, parameter='Significance', attribute='value') - sigma_err = xml.get_job_results(source=args.source, parameter='Significance', attribute='error') - flux = xml.get_job_results(source=args.source, parameter='IntegratedFlux', attribute='value') - flux_err = xml.get_job_results(source=args.source, parameter='IntegratedFlux', attribute='error') - xml.close_xml() - # get from job configuration - job = [join(b, f) for f in listdir(b) if isfile(join(b, f)) and 'job.xml' in f][0] - xml = open(job) - jobconf = ET.parse(xml) - tmin = float(jobconf.find('parameter[@name="TimeIntervals"]').attrib['tmin']) - tmax = float(jobconf.find('parameter[@name="TimeIntervals"]').attrib['tmax']) - tmean = (tmin + tmax)/2 - terror = (tmax - tmin)/2 - emin = float(jobconf.find('parameter[@name="Energy"]').attrib['emin']) - emax = float(jobconf.find('parameter[@name="Energy"]').attrib['emax']) - xml.close() - # write row to file - f = open(join(args.output, 'lightcurve.txt'), 'a') - w = csv.writer(f, delimiter=' ') - row = [tmean, terror, excess, excess_err, sigma, sigma_err, flux, flux_err, bkg, bkg_err, emin, emax] - w.writerow(row) - f.close() - diff --git a/rtamock/rtamock/collect_stack_results.py b/rtamock/rtamock/collect_stack_results.py deleted file mode 100644 index f97f819fa6d86b175d33aa0631f0f99d234b3dfb..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/collect_stack_results.py +++ /dev/null @@ -1,75 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import argparse -import logging -import xml.etree.ElementTree as ET -from os import listdir -from os.path import join, isdir, basename -from sagsci.tools.utils import get_absolute_path -from sagsci.tools.myxml import MyXml - -parser = argparse.ArgumentParser() -parser.add_argument("-d", "--dataset", default="$DATA/analysis_archive", help="directory to dataset") -parser.add_argument("-r", "--run", type=str, required=True, help="runid") -args = parser.parse_args() - -# set logging level -log = logging.getLogger(__name__) -log.setLevel(logging.DEBUG) - -# get all time bins in run -log.info(f'Collect timebins in run {args.run}') -datapath = join(get_absolute_path(args.dataset)) -subdirs = np.sort([join(datapath, d) for d in listdir(datapath) if isdir(join(datapath, d))]) -datafile = join(datapath, f'run{args.run}_stacked_{len(subdirs)}bins.csv') - -# create and clear file -f = open(datafile, 'w+') -hdr = "time time_err excess excess_err sigma sigma_err flux flux_err on_counts off_counts alpha emin emax\n" -f.writelines([hdr]) - -# loop all time bins -for idx, d in enumerate(subdirs): - # get from job configuration - job = join(datapath, d, 'job.xml') - xml = open(job) - jobconf = ET.parse(xml) - emin = float(jobconf.find('parameter[@name="Energy"]').attrib['emin']) - emax = float(jobconf.find('parameter[@name="Energy"]').attrib['emax']) - xml.close() - - # get from results.xml file - results = join(datapath, d, 'results', basename(d)+'_results.xml') - xml = MyXml(results) - source = xml.get_source_name() - excess = xml.get_stacked_results(source=source, parameter='Photometric', attribute='excess') - excess_err = xml.get_stacked_results(source=source, parameter='Photometric', attribute='excess_err') - off_counts = xml.get_stacked_results(source=source, parameter='Photometric', attribute='off_counts') - off_err = np.sqrt(off_counts) - alpha = xml.get_stacked_results(source=source, parameter='Photometric', attribute='alpha') - on_counts = xml.get_stacked_results(source=source, parameter='Photometric', attribute='on_counts') - on_err = np.sqrt(on_counts) - sigma = xml.get_stacked_results(source=source, parameter='Significance', attribute='value') - log.debug(f"sigma: {sigma}") - sigma_err = xml.get_stacked_results(source=source, parameter='Significance', attribute='error') - flux = xml.get_stacked_results(source=source, parameter='IntegratedFlux', attribute='value') - flux_err = xml.get_stacked_results(source=source, parameter='IntegratedFlux', attribute='error') - tmean = xml.get_stacked_attribute(source=source, attribute='livetime') - log.debug(f"tmean:{tmean}") - terror = tmean/2 - xml.close_xml() - - # write to file - row = f"{tmean} {terror} {excess} {excess_err} {sigma} {sigma_err} {flux} {flux_err} {on_counts} {off_counts} {alpha} {emin} {emax}\n" - f.writelines([row]) - -f.close() -log.info(f'Results collected in: {datafile}') diff --git a/rtamock/rtamock/data/targets.txt b/rtamock/rtamock/data/targets.txt deleted file mode 100644 index 0c86a50a784a987677365a02064f3ff7d539404d..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/data/targets.txt +++ /dev/null @@ -1,5 +0,0 @@ -W1 --> Crab -W2 --> Crab -W3 --> Crab -W4 --> Crab -W5 --> GRB RUN0406_ID000126 \ No newline at end of file diff --git a/rtamock/rtamock/explore_dataframe.ipynb b/rtamock/rtamock/explore_dataframe.ipynb deleted file mode 100644 index c33fd60812ced278e9507b73de159d6b37f8badf..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/explore_dataframe.ipynb +++ /dev/null @@ -1,184 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
timetime_errexcessexcess_errsigmasigma_errfluxflux_erron_countsoff_countseminemax
01.616302e+0958.12374416.66666712.8840992.17592627.1172842.250942e-101.740081e-1054.0112.00.031.0
11.616302e+0958.1237443.33333312.4096740.461997685.7007324.501884e-111.676007e-1041.0113.00.031.0
21.616302e+0958.1237444.33333312.4499000.597481405.8971095.852449e-111.681440e-1042.0113.00.031.0
31.616303e+0958.1237440.66666711.4017540.10110212619.0096499.003767e-121.539881e-1033.097.00.031.0
41.616302e+0958.1237448.00000012.4899961.091864115.0873621.080452e-101.686855e-1045.0111.00.031.0
\n", - "
" - ], - "text/plain": [ - " time time_err excess excess_err sigma sigma_err \\\n", - "0 1.616302e+09 58.123744 16.666667 12.884099 2.175926 27.117284 \n", - "1 1.616302e+09 58.123744 3.333333 12.409674 0.461997 685.700732 \n", - "2 1.616302e+09 58.123744 4.333333 12.449900 0.597481 405.897109 \n", - "3 1.616303e+09 58.123744 0.666667 11.401754 0.101102 12619.009649 \n", - "4 1.616302e+09 58.123744 8.000000 12.489996 1.091864 115.087362 \n", - "\n", - " flux flux_err on_counts off_counts emin emax \n", - "0 2.250942e-10 1.740081e-10 54.0 112.0 0.03 1.0 \n", - "1 4.501884e-11 1.676007e-10 41.0 113.0 0.03 1.0 \n", - "2 5.852449e-11 1.681440e-10 42.0 113.0 0.03 1.0 \n", - "3 9.003767e-12 1.539881e-10 33.0 97.0 0.03 1.0 \n", - "4 1.080452e-10 1.686855e-10 45.0 111.0 0.03 1.0 " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import pandas as pd\n", - "\n", - "df = pd.read_csv('run4191_lightcurve_10bins.csv', sep=' ', header=0)\n", - "df.head()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "interpreter": { - "hash": "cdd11fd6dafb22728507bacb81e73a8fccdefb6e5ab546d23f0276d20752413e" - }, - "kernelspec": { - "display_name": "Python 3.7.3 64-bit ('scitools')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/rtamock/rtamock/plot_run_lightcurves.py b/rtamock/rtamock/plot_run_lightcurves.py deleted file mode 100644 index 76ec709cb327f2f8c9baf40af290ae0615ad25ad..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/plot_run_lightcurves.py +++ /dev/null @@ -1,124 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import yaml -import argparse -import numpy as np -import matplotlib.pyplot as plt -import pandas as pd -from os import system -from os.path import join, basename -from sagsci.tools.utils import get_absolute_path, str2bool -from rtamock.tools.utils import set_logger - -# get line command options -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--configfile", default="myconfig.yml", help="configuration YAML file") -parser.add_argument("-cp", "--copyfiles", default="false", help="copy plot files in repository dir") -args = parser.parse_args() - -# get YAML configuration -configuration = open(args.configfile) -config = yaml.load(configuration, Loader=yaml.FullLoader) - -# logging -logname = join(get_absolute_path(config["logging"]["folder"]), basename(__file__).replace('.py','.log')) -log = set_logger(filename=logname, level=config["logging"]['level']) -log.info('Logging: ' + logname) - -def plot_significance(data, xerr=True): - if xerr: - plt.errorbar(x=data['time'], y=data['sigma'], xerr=data['time_err'], fmt='bo', barsabove=True, label='Li&Ma Significance') - else: - plt.errorbar(x=data['time'], y=data['sigma'], fmt='bo', barsabove=True, label='Li&Ma Significance') - plt.ylabel(r'$\sigma$', fontsize=fs) - -def plot_excess(data, xerr=True): - if xerr: - plt.errorbar(x=data['time'], y=data['excess'], xerr=data['time_err'], yerr=data['excess_err'], fmt='bo', barsabove=True, label='excess counts') - else: - plt.errorbar(x=data['time'], y=data['excess'], yerr=data['excess_err'], fmt='bo', barsabove=True, label='excess counts') - plt.ylabel('counts', fontsize=fs) - -def plot_flux(data, xerr=True): - plt.yscale('log') - if xerr: - plt.errorbar(x=data['time'], y=data['flux'], xerr=data['time_err'], yerr=data['flux_err'], fmt='bo', barsabove=True, label='integrated flux') - else: - plt.errorbar(x=data['time'], y=data['flux'], yerr=data['flux_err'], fmt='bo', barsabove=True, label='integrated flux') - plt.ylabel('F (ph/cm2/s)', fontsize=fs) - -def plot_background(data, xerr=True): - if xerr: - plt.errorbar(x=data['time'], y=data['off_counts'], xerr=data['time_err'], yerr=np.sqrt(data['off_counts']), fmt='bo', barsabove=True, label='background') - else: - plt.errorbar(x=data['time'], y=data['off_counts'], yerr=np.sqrt(data['off_counts']), fmt='bo', barsabove=True, label='background') - plt.ylabel('counts', fontsize=fs) - -def plot_counts(data, xerr=True): - if xerr: - plt.errorbar(x=data['time'], y=data['off_counts']/3, xerr=data['time_err'], yerr=np.sqrt(data['off_counts']/3), fmt='bo', barsabove=True, label=r'$\alpha\cdot$Noff') - plt.errorbar(x=data['time'], y=data['on_counts'], xerr=data['time_err'], yerr=np.sqrt(data['on_counts']), fmt='g^', barsabove=True, label='Non') - else: - plt.errorbar(x=data['time'], y=data['off_counts']/3, yerr=np.sqrt(data['off_counts']/3), fmt='bo', barsabove=True, label=r'$\alpha\cdot$Noff') - plt.errorbar(x=data['time'], y=data['on_counts'], yerr=np.sqrt(data['on_counts']), fmt='g^', barsabove=True, label='Non') - plt.ylabel('counts', fontsize=fs) - -datafile = get_absolute_path(config['plot']['data']).replace('XXX', str(config['run']['runid'])).replace('YYY', config['run']['type']).replace('ZZZ', str(config['run']['nbins'])) -data = pd.read_csv(datafile, sep=' ', header=0) -log.debug(f'bins = [{data["time"].min()}, {data["time"].max()}]') -data['time'] = data['time'] - data['time'].min() -log.debug(f'bins = [{data["time"].min()}, {data["time"].max()}]') -log.debug(f'time = [{data["tmin"].min()}, {data["tmax"].max()}]') -log.debug(f'Len table = {len(data)}') - -which = config['plot']['which'] -fs = 16 -for w in which: - fig = plt.figure(figsize=(10, 4)) - plt.title(f"RUN{config['run']['runid']}: {config['source']} E({data['emin'][0]} - {data['emax'][0]}) TeV", fontsize=fs) - plt.tick_params(axis='both', labelsize=fs) - plt.xlabel('time (s)', fontsize=fs) - - if config['run']['type'].lower() == 'cumulative': - xerr = False - else: - xerr = True - - if w.lower() == 'background': - plot_background(data=data, xerr=xerr) - elif w.lower() == 'counts': - plot_counts(data=data, xerr=xerr) - elif w.lower() == 'excess': - plot_excess(data=data, xerr=xerr) - elif w.lower() == 'significance': - plot_significance(data=data, xerr=xerr) - elif w.lower() == 'flux': - plot_flux(data=data, xerr=xerr) - - plt.grid() - plt.legend() - - plt.tight_layout() - outname = get_absolute_path(config['plot']['data']).replace('XXX', str(config['run']['runid'])).replace('YYY', config['run']['type']).replace('ZZZ', str(config['run']['nbins'])).replace('.csv', f'_{w}.png') - plt.savefig(outname) - log.info(f'Saved {w} lightcurve: {outname}') - - if str2bool(args.copyfiles): - log.info('Copy figure to workspace.') - system(f'cp {outname} .') - - - - - - - - - diff --git a/rtamock/rtamock/prepare_dataset.py b/rtamock/rtamock/prepare_dataset.py deleted file mode 100644 index ea7b458c38af00932dce038c371db0175cc654e4..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/prepare_dataset.py +++ /dev/null @@ -1,134 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import yaml -import argparse -import numpy as np -import xml.etree.ElementTree as ET -from os import listdir, system -from os.path import join, isdir, isfile, basename -from sagsci.tools.utils import get_obs_pointing, get_absolute_path -from tools.utils import get_obs_GTI, split_observation, bool2int, set_logger - -# get line command options -parser = argparse.ArgumentParser() -parser.add_argument("-f", "--configfile", default="myconfig.yml", help="configuration YAML file") -args = parser.parse_args() - -# get YAML configuration -configuration = open(args.configfile) -config = yaml.load(configuration, Loader=yaml.FullLoader) -runid = config["run"]["runid"] - -# logging -logname = join(get_absolute_path(config["logging"]["folder"]), basename(__file__).replace('.py','.log')) -log = set_logger(filename=logname, level=config["logging"]['level']) -log.info('Logging: ' + logname) - -# set datapath -datapath = join(get_absolute_path(config['dirlist']['data']), str(runid)) -log.debug(f"Data: {datapath}") - -# remove all subfolders in $DATA directory -log.info(f'Remove all subdirs in run directory') -bins = [join(datapath, f) for f in listdir(datapath) if isdir(join(datapath, f))] -for b in bins: - system(f'rm -r {b}') - -# list all fits inside $DATA directory -log.info(f'Preparing dataset: {datapath}') -bins = [join(datapath, f) for f in listdir(datapath) if isfile(join(datapath, f)) and '.fits' in f] - -# if only one fits in $DATA directory split observation otherwise skip -if len(bins) == 1: - nbins = int(config['run']['nbins']) - log.info(f'Splitting observation in {nbins} time bins') - bins = split_observation(fitsfile=bins[0], nbins=nbins, type=config['run']['type']) - -# create single folders per each fits in $DATA and copy xml files within -for b in bins: - directory = join(datapath, basename(b).replace('.fits', '')) - if not isdir(directory): - system(f'mkdir {directory}') - system(f'mv {b} {directory}') - system(f'cp {join(datapath, "target.xml")} {directory}/.') - system(f'cp data/templates/job.xml {directory}/.') - system(f'cp data/templates/obs.xml {directory}/.') - -# modify job.xml per each bin in $DATA -bins = np.sort([join(datapath, d) for d in listdir(datapath) if isdir(join(datapath, d))]) -log.info('Prepare job.xml files') -for b in bins: - fitsfile = join(b, basename(b)+'.fits') - tstart, tstop = get_obs_GTI(fitsfile=fitsfile) - pointing = get_obs_pointing(filename=fitsfile) - jobfile = join(b, 'job.xml') - with open(jobfile) as job: - jobconf = ET.parse(job) - root = jobconf.getroot() - prm = root.find('parameter[@name="TimeIntervals"]') - prm.set('tmin', str(tstart)) - prm.set('tmax', str(tstop)) - prm = root.find('parameter[@name="DirectoryList"]') - prm.set('job', b) - respath = join(b, 'results') - prm.set('results', respath) - prm.set('jobprefix', f"{basename(b)}") - prm = root.find('parameter[@name="RegionOfInterest"]') - prm.set('ra', str(pointing['ra'])) - prm.set('dec', str(pointing['dec'])) - prm = root.find('parameter[@name="Energy"]') - prm.set('emin', str(config['run']['emin'])) - prm.set('emax', str(config['run']['emax'])) - prm = root.find('parameter[@name="Stack"]') - prm.set('value', str(bool2int(config['run']['stack']))) - prm.set('depth', str(config['run']['maxdepth'])) - prm = root.find('parameter[@name="Logging"]') - prm.set('level', config['logging']['level']) - jobconf.write(jobfile) - -# modify obs.xml per each bin in $DATA -log.info('Prepare obs.xml files') -for b in bins: - obsfile = join(b, 'obs.xml') - with open(obsfile) as obs: - obsconf = ET.parse(obs) - root = obsconf.getroot() - root.set('name', config['source']) - root.set('instrument', 'LST-1') - root.set('id', str(runid)) - prm = root.find('parameter[@name="GoodTimeIntervals"]') - prm.set('tstartreal', str(tstart)) - prm.set('tendreal', str(tstop)) - prm.set('tstartplanned', str(tstart)) - prm.set('tendplanned', str(tstart)) - prm = root.find('parameter[@name="RegionOfInterest"]') - prm.set('ra', str(pointing['ra'])) - prm.set('dec', str(pointing['dec'])) - prm = root.find('parameter[@name="Pointing"]') - prm.set('ra', str(pointing['ra'])) - prm.set('dec', str(pointing['dec'])) - prm = root.find('parameter[@name="Energy"]') - prm.set('emin', str(config['run']['emin'])) - prm.set('emax', str(config['run']['emax'])) - prm = root.find('parameter[@name="Calibration"]') - prm.set('database', config['run']['prod']) - prm.set('response', config['run']['irf']) - obsconf.write(obsfile) - -# modify target.xml per each bin in $DATA -log.info('Prepare target.xml files') -for b in bins: - targetfile = join(b, 'target.xml') - with open(targetfile) as t: - tconf = ET.parse(t) - root = obsconf.getroot() - prm = root.find('source') - root.set('name', config['source']) - tconf.write(targetfile) diff --git a/rtamock/rtamock/tools/__init__.py b/rtamock/rtamock/tools/__init__.py deleted file mode 100644 index 6069d3dc1bbc4d9b65fde98f779775df244f335e..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/tools/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -# Import sub modules -from . import * \ No newline at end of file diff --git a/rtamock/rtamock/tools/utils.py b/rtamock/rtamock/tools/utils.py deleted file mode 100644 index de05fbc6512bf477fd9e8aaf4bc411ecb7fb2bd3..0000000000000000000000000000000000000000 --- a/rtamock/rtamock/tools/utils.py +++ /dev/null @@ -1,93 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2022 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import logging -import numpy as np -from re import compile, escape, DOTALL -from os import system, makedirs, remove -from astropy.io import fits -from sagsci.tools.fits import Fits -from os.path import dirname, isdir, isfile - -def get_level_code(level): - if level.lower() == 'debug': - level = 10 - elif level.lower() == 'info': - level = 20 - elif level.lower() == 'warning': - level = 30 - elif level.lower() == 'error': - level = 40 - elif level.lower() == 'critical': - level = 50 - else: - level = 0 - return level - -def set_logger(filename, level): - if isfile(filename): - remove(filename) - if not isdir(dirname(filename)): - makedirs(dirname(filename)) - log = logging.getLogger() - formatter = logging.Formatter('%(asctime)s|%(filename)s|l%(lineno)d|%(levelname)s| %(message)s') - fileHandler = logging.FileHandler(filename) - fileHandler.setFormatter(formatter) - consoleHandler = logging.StreamHandler() - consoleHandler.setFormatter(formatter) - log.addHandler(fileHandler) - log.addHandler(consoleHandler) - if type(level) == str: - level = get_level_code(level) - log.setLevel(level) - return log - -def bool2int(val): - if val is True: - return 1 - else: - return 0 - -def get_obj_pointing(fitsfile): - with fits.open(fitsfile) as h: - ra = h['EVENTS'].header['RA_OBJ'] - dec = h['EVENTS'].header['DEC_OBJ'] - return {'ra': ra, 'dec': dec} - -def get_obs_GTI(fitsfile): - with fits.open(fitsfile) as h: - GTI = h['GTI'].data[0] - return GTI - -def split_observation(fitsfile, nbins, type='lightcurve'): - GTI = get_obs_GTI(fitsfile=fitsfile) - edges = np.linspace(GTI[0], GTI[1], num=nbins+1) - bins = [] - for i in range(len(edges)-1): - selection = fitsfile.replace(".fits", f"_{edges[i]}_{edges[i+1]}.fits") - system(f'cp {fitsfile} {selection}') - bins.append(selection) - - f = Fits() - pointing = {'ra': f.get_dl3_hdr(selection)['RA_PNT'], 'dec': f.get_dl3_hdr(selection)['DEC_PNT']} - dl3 = f.get_dl3_data(selection) - if type.lower() == 'lightcurve': - dl3_selected = f.selection_cuts(dl3, pointing, trange=[edges[i], edges[i+1]]) - f.set_dl3_data(selection, dl3_selected, GTI=[edges[i], edges[i+1]]) - elif type.lower() == 'cumulative': - dl3_selected = f.selection_cuts(dl3, pointing, trange=[edges[0], edges[i+1]]) - f.set_dl3_data(selection, dl3_selected, GTI=[edges[0], edges[i+1]]) - else: - raise ValueError(f'Type "{type.lower()}" not allowed.') - - return bins - -def multiple_replace(string, rep_dict): - pattern = compile("|".join([escape(k) for k in sorted(rep_dict,key=len,reverse=True)]), flags=DOTALL) - return pattern.sub(lambda x: rep_dict[x.group(0)], string) \ No newline at end of file diff --git a/rtamock/setup.py b/rtamock/setup.py deleted file mode 100644 index 41a7d226ea9c77c282312158900f657b784dde6c..0000000000000000000000000000000000000000 --- a/rtamock/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -# ***************************************************************************** -# Copyright (C) 2022 INAF -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ***************************************************************************** - -import os -import setuptools - -scriptsPath = "rtamock" -scripts = [scriptsPath+file for file in os.listdir(scriptsPath)] -for script in scripts: - print(script) - -setuptools.setup( - name='rtamock', - author='Ambra Di Piano ', - package_dir={'rtamock': 'rtamock'}, - include_package_data=True, - license='BSD-3-Clause', -) \ No newline at end of file diff --git a/rtasci b/rtasci new file mode 160000 index 0000000000000000000000000000000000000000..20ce02eb59e3598b72a14d85424a6e57ea3a3a55 --- /dev/null +++ b/rtasci @@ -0,0 +1 @@ +Subproject commit 20ce02eb59e3598b72a14d85424a6e57ea3a3a55 diff --git a/rtasci/.gitignore b/rtasci/.gitignore deleted file mode 100644 index feba68af4535f9f801c97d5fb581641cb75e6fe8..0000000000000000000000000000000000000000 --- a/rtasci/.gitignore +++ /dev/null @@ -1,53 +0,0 @@ -# files -slurm* -config.yaml -*.xml -env.sh -loadenv.sh -rtasci/cfg/*.yml -rtasci/paper/* -rtasci/cfg/*.sh -rtasci/jobs/*myconfig* -rtasci/jobs -rtasci/paper -rtasci/log -rtasci/cancel_jobs.py -rtasci/deljobs.py -rtasci/wilks.py -rtasci/sphdist.py -rtasci/datamerger.py -rtasci/thesis/config.xml -rtasci/thesis/wilks.xml -rtasci/thesis/rta-test -rtasci/misc/notebooks -rtasci/misc/notebooks/*ipynb -env.sh -loadenv.sh -rtasci/misc/rmfiles.py -rtasci/test* -rtasci/misc/csadd2caldb.py - -# folders -.*/ -./ -/. -__pycache__ -*.egg-info -.vscode -dist/ -build/ -adptest* -test_* -testing/* -phd -rtasci/lib/old* -rtasci/lib/__pycache__/* -rtasci/notebooks -rtasci/jnotebooks -!rtasci/aph -rtasci/adpthesis -rtasci/misc/tmp - -# exceptions -!rtasci/cfg/ - diff --git a/rtasci/LICENSE b/rtasci/LICENSE deleted file mode 100644 index 49d2a32878b0078a1dabdda724e4b542baca238b..0000000000000000000000000000000000000000 --- a/rtasci/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -# ----------------------------------- # -# Copyright 2020-2021 Ambra Di Piano # -# ----------------------------------- # -------------------------------------------- # -# Redistribution and use in source and binary forms, with or without modification, # -# are permitted provided that the following conditions are met: # -# 1. Redistributions of source code must retain the above copyright notice, # -# this list of conditions and the following disclaimer. # -# 2. Redistributions in binary form must reproduce the above copyright notice, # -# this list of conditions and the following disclaimer in the documentation and/or # -# other materials provided with the distribution. # -# 3. Neither the name of the copyright holder nor the names of its contributors # -# may be used to endorse or promote products derived from this software without # -# specific prior written permission. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, # -# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # -# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED # -# OF THE POSSIBILITY OF SUCH DAMAGE. # -# ---------------------------------------------------------------------------------- # \ No newline at end of file diff --git a/rtasci/MANIFEST.in b/rtasci/MANIFEST.in deleted file mode 100644 index e03ceada36eff756130f60e0012db53cf8d91f04..0000000000000000000000000000000000000000 --- a/rtasci/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include README.md - -recursive-include * *.py -recursive-include * *.xml -recursive-include * *.sh \ No newline at end of file diff --git a/rtasci/README.md b/rtasci/README.md deleted file mode 100644 index 1ae0de528e5e25d40bb8b889b7818c49978649b6..0000000000000000000000000000000000000000 --- a/rtasci/README.md +++ /dev/null @@ -1,83 +0,0 @@ -### **Environment** ** - -To create a virtual environment with all required dependencies: - -```bash -conda env create --name --file=environment.yaml -``` - -Note that you should already have anaconda installed: https://www.anaconda.com/ - -## **rtasci** -All code is found in this directory, you can setup the source code as follows: - -```bash -python setup.py develop -``` - -**Sub-directories:** - -- cfg -> configuration class and configuration files -- lib -> classes -- misc -> miscellaneous scripts -- pipelines -> pipelines with different tools and techniques -- timing -> scripts to compare tools and techniques - -### **Calibration database** - -To complete the environment be sure to download and install the correct IRFs (only prod2 comes with ctools installation). Public ones can be found here: https://www.cta-observatory.org/science/cta-performance/ - -### **Configuration file** - -Under cfg you can find an example of configuration file. Description of each parameter is commented within. This file will serve as input when running the code. - -### **CALDB degradation** -Be sure to have your calibration database installed under $CTOOLS/share. You can pass a single CALDB or a list, the degraded version will placed along side the nominal one. It will have the same suffix, replacing "prod" with "degr" (i.e., prod2 --> degr2). - -```bash -python degradation_caldb.py --caldb prod3b pro3b-v2 -``` - -Note: currently the code simply halves the affective area (and consequently renormalise the background rates). - -## rtasci -To extract spectra and lightcurves from the templates (one, a list or the entire sample): - -```bash -python prepareGRBcatalog.py -f cfg/config.yaml -``` - -To run the simulation: - -```bash -python simGRBcatalog.py -f cfg/config.yaml -``` - -To perform the analysis: - -```bash -python pipeline/pipeline_name.py -f cfg/config.yaml -``` -You are required to substitute pipeline_name.py with the chosen script (currently only one pipeline is available but there will be more in the future). - -All steps above may be run together with the following: - -```bash -python rtapipe.py -f cfg/config.yaml -``` -This will first extract all data specified in the configuration file, then simulate the entire sample, finally it will analyse each simulation. Files will be removed (by default) after the simulation so be sure to have enough space to store them. It will avoid running multiple simulation of the same sample if, i.e., you want to perform different types of analysis on it. - -To simulate a single run you can alternatively use - -```bash -python simGRB.py -f cfg/config.yaml -``` - -To simulate a wobble observation (only crab simulations are currently allowed) you can use: - -```bash -python simWobble.py -f cfg/config.yaml -``` - -
-[**] subsceptible to changes \ No newline at end of file diff --git a/rtasci/changelog.md b/rtasci/changelog.md deleted file mode 100644 index 32d0cf902c4a48fa650e8634f1411051f125df6f..0000000000000000000000000000000000000000 --- a/rtasci/changelog.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -## **v.0.1.0** -- script to degrade caldb -- script to extract data GRB afterglow template, normalise the template, apply EBL when required -- scripts to simulate: grb afterglow, crab wobble, empty fields -- ctools pipeline: blind-search + full-FOV unbinned analysis -- simulation scripts now sort events in TIME and reindex them - -#### *Known issues* -- the grb afterglow simulation (simGRBcatalog.py) for serendipitous discovery (onset != 0) is bugged. \ No newline at end of file diff --git a/rtasci/docs/Makefile b/rtasci/docs/Makefile deleted file mode 100644 index d0c3cbf1020d5c292abdedf27627c6abe25e2293..0000000000000000000000000000000000000000 --- a/rtasci/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/rtasci/docs/make.bat b/rtasci/docs/make.bat deleted file mode 100644 index 6247f7e231716482115f34084ac61030743e0715..0000000000000000000000000000000000000000 --- a/rtasci/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/rtasci/docs/source/Config.rst b/rtasci/docs/source/Config.rst deleted file mode 100644 index b8863b38526ca2ebed2e52bc12d2bef1f9f94769..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/Config.rst +++ /dev/null @@ -1,5 +0,0 @@ -Config -====== - -.. automodule:: rtasci.cfg.Config - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTACtoolsAnalysis.rst b/rtasci/docs/source/RTACtoolsAnalysis.rst deleted file mode 100644 index b1199056beaa8f56140044c789261287d9ad662c..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTACtoolsAnalysis.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTACtoolsAnalysis -================= - -.. automodule:: rtasci.lib.RTACtoolsAnalysis - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTACtoolsSimulation.rst b/rtasci/docs/source/RTACtoolsSimulation.rst deleted file mode 100644 index 0032d373a4526e77964654c9648677da4c540a00..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTACtoolsSimulation.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTACtoolsSimulation -=================== - -.. automodule:: rtasci.lib.RTACtoolsSimulation - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAGammapyAnalysis.rst b/rtasci/docs/source/RTAGammapyAnalysis.rst deleted file mode 100644 index 79a91e6cc40b3cb71da78d4e56a05320db229500..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAGammapyAnalysis.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAGammapyAnalysis -================== - -.. automodule:: rtasci.lib.RTAGammapyAnalysis - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAIrfs.rst b/rtasci/docs/source/RTAIrfs.rst deleted file mode 100644 index 7092889aa4f672160b4a003f58b1ddb1fd8a60e8..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAIrfs.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAIrfs -======= - -.. automodule:: rtasci.lib.RTAIrfs - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAManageXml.rst b/rtasci/docs/source/RTAManageXml.rst deleted file mode 100644 index d1f22ae18da4bd89d65c21c403cbeb3aeeea7dda..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAManageXml.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAManageXml -============ - -.. automodule:: rtasci.lib.RTAManageXml - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAStats.rst b/rtasci/docs/source/RTAStats.rst deleted file mode 100644 index 02377a163b65e216c417f25af1e0bcd31220dbf0..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAStats.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAStats -======== - -.. automodule:: rtasci.lib.RTAStats - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAUtils.rst b/rtasci/docs/source/RTAUtils.rst deleted file mode 100644 index 912699bdacb3f04cb1c9531672c1c7eb96a684d2..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAUtils.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAUtils -======== - -.. automodule:: rtasci.lib.RTAUtils - :members: \ No newline at end of file diff --git a/rtasci/docs/source/RTAVisualise.rst b/rtasci/docs/source/RTAVisualise.rst deleted file mode 100644 index c032341378f527b1013ce6b582f506d8c2c6b40a..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/RTAVisualise.rst +++ /dev/null @@ -1,5 +0,0 @@ -RTAVisualise -============ - -.. automodule:: rtasci.lib.RTAVisualise - :members: \ No newline at end of file diff --git a/rtasci/docs/source/conf.py b/rtasci/docs/source/conf.py deleted file mode 100644 index b30ced510385d3459d6f430b859b9b6b3dc223ea..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/conf.py +++ /dev/null @@ -1,61 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('..')) - - -# -- Project information ----------------------------------------------------- - -project = 'cta-sag-sci' -copyright = '2021, Ambra Di Piano ' -author = 'Ambra Di Piano ' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.napoleon'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] -html_context = { - 'css_files': [ - '_static/theme_overrides.css', # override wide tables in RTD theme - ], - } - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Agilepydoc' \ No newline at end of file diff --git a/rtasci/docs/source/configuration.rst b/rtasci/docs/source/configuration.rst deleted file mode 100644 index fed3db82ad744812e4e7ee45451132e6b20a191d..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/configuration.rst +++ /dev/null @@ -1,11 +0,0 @@ -Configuration -============= - -.. toctree:: - :maxdepth: 3 - - Config - - - - diff --git a/rtasci/docs/source/index.rst b/rtasci/docs/source/index.rst deleted file mode 100644 index 87b983fd2d8694489f6560fb690921aa49e733f9..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -Welcome to cta-sag-sci's documentation! -======================================= - -.. toctree:: - :maxdepth: 2 - :caption: testing: - - intro - -.. toctree:: - :maxdepth: 2 - :caption: documentation: - - configuration - library - - - - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/rtasci/docs/source/intro.rst b/rtasci/docs/source/intro.rst deleted file mode 100644 index 7979bcd2e9a047aecaf40df81752f77bdfb9ab52..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/intro.rst +++ /dev/null @@ -1,7 +0,0 @@ -Introduction -============ - -.. toctree:: - :maxdepth: 3 - -Something written here. \ No newline at end of file diff --git a/rtasci/docs/source/library.rst b/rtasci/docs/source/library.rst deleted file mode 100644 index 17a1c4b348ae7203c08b6269f76411ffd702b23a..0000000000000000000000000000000000000000 --- a/rtasci/docs/source/library.rst +++ /dev/null @@ -1,18 +0,0 @@ -Library -======= - -.. toctree:: - :maxdepth: 3 - - RTAIrfs - RTAUtils - RTAStats - RTAVisualise - RTAManageXml - RTACtoolsSimulation - RTACtoolsAnalysis - RTAGammapyAnalysis - - - - diff --git a/rtasci/environment.yaml b/rtasci/environment.yaml deleted file mode 100644 index 03262e54cd052593b09ee471800b19a99172417a..0000000000000000000000000000000000000000 --- a/rtasci/environment.yaml +++ /dev/null @@ -1,15 +0,0 @@ -channels: - - conda-forge - - cta-observatory -dependencies: - - python=3.7 - - ctools=1.7.4 - - astropy - - healpy - - pandas - - lxml - - pyaml - - scipy - - regions - - seaborn - - gammapy \ No newline at end of file diff --git a/rtasci/prod5install.sh b/rtasci/prod5install.sh deleted file mode 100644 index bc2bee49ad36e51ad740b644a0fae8430d0f298d..0000000000000000000000000000000000000000 --- a/rtasci/prod5install.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -mkdir tmp -cd tmp -wget http://cta.irap.omp.eu/ctools/_downloads/csadd2caldb.py -wget https://zenodo.org/record/5499840/files/cta-prod5-zenodo-fitsonly-v0.1.zip -python csadd2caldb.py debug=yes -cd .. -rm -rf tmp diff --git a/rtasci/pyproject.toml b/rtasci/pyproject.toml deleted file mode 100644 index fa7093a33c048d9db6e0eb3b4d8daa57426754e3..0000000000000000000000000000000000000000 --- a/rtasci/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools>=42"] -build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/rtasci/rtasci/aph/__ini__.py b/rtasci/rtasci/aph/__ini__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/rtasci/rtasci/aph/irf.py b/rtasci/rtasci/aph/irf.py deleted file mode 100644 index 16364de3615c8eec73955b1c7f5861a6fac32d27..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/aph/irf.py +++ /dev/null @@ -1,564 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Simone Tampieri -# ******************************************************************************* - -from astropy.io import fits -import numpy as np -from scipy import interpolate, integrate -from rtasci.aph import utils -from astropy.coordinates import SkyCoord -import math - -class IRF: - def __init__(self, filename): - self.filename = filename - self.hdul = fits.open(self.filename) - - def get_extension(self, name): - return self.hdul[name] - - def get_eff_area(self): - return self.get_extension('EFFECTIVE AREA') - - def get_psf_data(self): - return self.get_extension('POINT SPREAD FUNCTION') - -class EffectiveArea: - def __init__(self, irf_filename=None, eff_area_bintable=None): - self.irf_filename = None - self.eff_area = None - - # a sort of cache... - self.energies = None - self.thetas = None - self.aeff_matrix = None - - if irf_filename is not None: - self.irf_filename = irf_filename - irf = IRF(self.irf_filename) - self.eff_area = irf.get_eff_area() - elif eff_area_bintable is not None: - self.eff_area = eff_area_bintable - - if self.eff_area is None: - raise Exception('Need an irf or effective area bintable') - - self.get_data_matrices() - - def columns(self): - return self.eff_area.columns - - def data(self): - return self.eff_area.data - - def get_data_matrices(self): - """ returns aeff data matrix, energy[LO,HI] and theta[LO,HI] - """ - if self.energies is None or self.thetas is None or self.aeff_matrix is None: - data = self.data() - if self.aeff_matrix is None: - self.aeff_matrix = data.field('EFFAREA')[0] - if self.energies is None: - self.energies = np.column_stack((data.field('ENERG_LO')[0], data.field('ENERG_HI')[0])) - if self.thetas is None: - self.thetas = np.column_stack((data.field('THETA_LO')[0], data.field('THETA_HI')[0])) - return self.aeff_matrix, self.energies, self.thetas - - def get_aeff_1d_log(self, offset, energy): - """ - return effective area in [m²] - - Parameters - offset: Angle - energy: [TeV] - - This method does 1D interpolation on energy range, managed as log10. - Theta offset is not interpolated. - """ - offset_angle = utils.get_angle(offset) - aeff_matrix, energy_bins, theta_bins = self.get_data_matrices() - - theta_index = None - for i, tb in enumerate(theta_bins): - if offset_angle.degree >= tb[0] and offset_angle.degree < tb[1]: - theta_index = i - break - if theta_index is None: - raise Exception('Theta offset is out of range ({})'.format(offset)) - - # energy interpolation - energy_mid = [ (e[0]+e[1])/2 for e in np.log10(energy_bins) ] - energy_fn = interpolate.interp1d(x = energy_mid, y = aeff_matrix[theta_index]) - return energy_fn(np.log10(energy)) - - def get_aeff_2d_log(self, input_offset, input_energy): - """ - return effective area in [m²] - - Parameters - offset: Angle - energy: [TeV] - - This method does 2D interpolation and manage energy as log10. - """ - offset_angle = utils.get_angle(input_offset) - aeff_matrix, energy_bins, theta_bins = self.get_data_matrices() - - # energy interpolation - theta_mid = [ (t[0]+t[1])/2 for t in theta_bins ] - energy_mid = [ (e[0]+e[1])/2 for e in np.log10(energy_bins) ] - energy_fn = interpolate.interp2d(x = energy_mid, y = theta_mid, z = aeff_matrix) - return energy_fn(np.log10(input_energy), input_offset)[0] - - def weighted_value_for_region(self, *args): - return self.weighted_aeff_flat_psf_w_powerlaw(*args) - - # FIXME the idea is integrate psf gradually but the offset from center and from pointing works differently - # we need to associate the PSF degradation from source region center - # and the Aeff degradation form the pointing - # def weighted_aeff_psf_w_powerlaw(self, region, pointing, input_energies, pixel_size=0.05, e_index=-2.4): - # ... - - # this method use the psf value plain as it easy for energy weight. doesn't - # consider the spatial component of the PSF - def weighted_aeff_flat_psf_w_powerlaw(self, region, pointing, input_energies, pixel_size=0.05, e_index=-2.4): - """return effective area value [m²] for a specific region - - Parameters - region: { 'ra': ..., 'dec': ..., 'rad': ... } - pointing: { 'ra': ..., 'dec': ... } - energies: a couple of values in TeV (ex: [ 0.025, 1.0 ]) - pixel_size: a value in degree (default: 0.05) - e_index: is the powerlaw index (default: -2.4) - """ - if len(input_energies) != 2: - raise Exception('need two energies') - - psf = PSF(irf_filename=self.irf_filename) - - # create a grid of points - points = self.create_pixel_map(region, pixel_size) - # select the points inside the region - internal_points = self.select_points_in_region(points, region) - # calculate the offsets - offsets = self.get_thetas(pointing, internal_points) - - log_energies = np.log10(input_energies) - # N steps for every unit of log energy - steps = int(np.ceil(log_energies[1]-log_energies[0]) * 10) - energies = 10**np.linspace(log_energies[0], log_energies[1], steps) - powerlaw = lambda x: x**e_index - i_full = integrate.quad(powerlaw, input_energies[0], input_energies[1]) - i_partials = [ integrate.quad(powerlaw, energies[i], energies[i+1]) for i,v in enumerate(energies[:-1]) ] - i_factor = [ p[0]/i_full[0] for p in i_partials ] - energies_middle = (energies[1:]+energies[:-1])/2 - - psf_engines = {} - for en in energies_middle: - psf_engines[en] = psf.get_psf_engine(region, pointing, en) - region_radius_rad = np.deg2rad(region['rad']) - - n_points = len(offsets) - val = 0 - for t in offsets: - for i, en in enumerate(energies_middle): - psf_rate = psf_engines[en](0, region_radius_rad)[0] - val += self.get_aeff_2d_log(t, en) * i_factor[i] * psf_rate / n_points - return val - - # this method use an energy range to evaluate the aeff. - # The energy range is binned and weighted with a powerlaw with index = e_index. - # Each pixel has a radial weigth and a specific column of energies weight. - # Lower energies have more weigth than higher. - # if energy range is small, the effect is trascurable - similar to weighted_value_for_region_single_energy method. - def weighted_value_for_region_w_powerlaw(self, region, pointing, input_energies, pixel_size=0.05, e_index=-2.4): - """return effective area value [m²] for a specific region - - Parameters - region: { 'ra': ..., 'dec': ..., 'rad': ... } - pointing: { 'ra': ..., 'dec': ... } - energies: a couple of values in TeV (ex: [ 0.025, 1.0 ]) - pixel_size: a value in degree (default: 0.05) - e_index: is the powerlaw index (default: -2.4) - """ - if len(input_energies) != 2: - raise Exception('need two energies') - - # create a grid of points - points = self.create_pixel_map(region, pixel_size) - # select the points inside the region - internal_points = self.select_points_in_region(points, region) - # calculate the offsets - offsets = self.get_thetas(pointing, internal_points) - - log_energies = np.log10(input_energies) - # N steps for every unit of log energy - steps = int(np.ceil(log_energies[1]-log_energies[0]) * 10) - energies = 10**np.linspace(log_energies[0], log_energies[1], steps) - powerlaw = lambda x: x**e_index - i_full = integrate.quad(powerlaw, input_energies[0], input_energies[1]) - i_partials = [ integrate.quad(powerlaw, energies[i], energies[i+1]) for i,v in enumerate(energies[:-1]) ] - i_factor = [ p[0]/i_full[0] for p in i_partials ] - energies_middle = (energies[1:]+energies[:-1])/2 - n_points = len(offsets) - val = 0 - for t in offsets: - for i, en in enumerate(energies_middle): - val += self.get_aeff_2d_log(t, en) * i_factor[i] / n_points - return val - - # this method use an energy range to evaluate the aeff. The energy range is - # binned and every matrix cube (pixel distance * energy bin) have the same - # weight. - # no powerlaw here - def weighted_value_for_region_no_powerlaw(self, region, pointing, input_energies, pixel_size=0.05): - """return effective area value [m²] for a specific region - - Parameters - region: { 'ra': ..., 'dec': ..., 'rad': ... } - pointing: { 'ra': ..., 'dec': ... } - energies: a couple of values in TeV (ex: [ 0.025, 1.0 ]) - pixel_size: a value in degree (default: 0.05) - """ - if len(input_energies) != 2: - raise Exception('need two energies') - - # create a grid of points - points = self.create_pixel_map(region, pixel_size) - # select the points inside the region - internal_points = self.select_points_in_region(points, region) - # calculate the offsets - offsets = self.get_thetas(pointing, internal_points) - - log_energies = np.log10(input_energies) - diff = np.ceil(log_energies[1]-log_energies[0]) - steps = int(diff * 10) # N steps for every unit of log energy - energies = 10**np.linspace(log_energies[0], log_energies[1], steps) - n_points = len(offsets) * len(energies) - val = 0 - for t in offsets: - for en in energies: - val += self.get_aeff_2d_log(t, en) / n_points - return val - - # Deprecated 2019-12-11 - # this method is old. just a plain output from an array of points and ONE - # energy (usually the middle point of the energy range) - def weighted_value_for_region_single_energy(self, region, pointing, energy, pixel_size=0.05): - """return effective area value [m²] for a specific region - - Parameters - region: { 'ra': ..., 'dec': ..., 'rad': ... } - pointing: { 'ra': ..., 'dec': ... } - energy: a value in TeV (ex: 0.025, 1.0, 150.0) - pixel_size: a value in degree (default: 0.05) - """ - # create a grid of points - points = self.create_pixel_map(region, pixel_size) - # select the points inside the region - internal_points = self.select_points_in_region(points, region) - # calculate the offsets - offsets = self.get_thetas(pointing, internal_points) - - n_points = len(offsets) - val = 0 - for t in offsets: - val += self.get_aeff_2d_log(t, energy) / n_points - return val # m2 - - # helpers - @staticmethod - def create_pixel_map(region, pixel_side): - for k in ['ra', 'dec', 'rad']: - if k in region: - continue - raise Exception('region data missing {} mandatory key.'.format(k)) - if region['rad'] <= 0: - raise Exception('region radius must be > 0') - if pixel_side <= 0: - raise Exception('pixel side must be > 0') - - region_center = { 'ra': float(region['ra']), 'dec': float(region['dec']) } - region_rad = utils.get_angle(float(region['rad'])) - pixel_side_angle = utils.get_angle(float(pixel_side)) - - # +10% to get a bit of margin - n_pixel_on_rad = 1.1* region_rad / pixel_side_angle - if n_pixel_on_rad <= 1: - n_pixel_on_rad = 1 - - n_pixel_on_axis = float(math.ceil(n_pixel_on_rad)) - multipliers = np.arange(-1*n_pixel_on_axis, n_pixel_on_axis+1) - pixels_midpoint = [] - for i in multipliers: - for j in multipliers: - pixels_midpoint.append({ 'ra': region_center['ra'] + i * pixel_side_angle.deg, - 'dec': region_center['dec'] + j * pixel_side_angle.deg }) - return pixels_midpoint - - @staticmethod - def select_points_in_region(midpoints, region): - for k in ['ra', 'dec', 'rad']: - if k in region: - continue - raise Exception('region data missing {} mandatory key.'.format(k)) - if region['rad'] <= 0: - raise Exception('region radius must be > 0') - if len(midpoints) < 1: - raise Exception('need at least 1 point to check') - - region_center = utils.get_skycoord(region) - region_radius = utils.get_angle(region['rad']) - midpoints_ra = [] - midpoints_dec = [] - for p in midpoints: - midpoints_ra.append(p['ra']) - midpoints_dec.append(p['dec']) - midpoints_coords = SkyCoord(midpoints_ra, midpoints_dec, unit='deg', frame='icrs') - distances = region_center.separation(midpoints_coords) - return np.extract(distances < region_radius, midpoints) - - @staticmethod - def get_thetas(point, midpoints): - for k in ['ra', 'dec']: - if k in point: - continue - raise Exception('point coord {} is missing.'.format(k)) - if len(midpoints) < 1: - raise Exception('need at least 1 point to check') - pnt = utils.get_skycoord(point) - midpoints_ra = [] - midpoints_dec = [] - for p in midpoints: - midpoints_ra.append(p['ra']) - midpoints_dec.append(p['dec']) - midpoints_coords = SkyCoord(midpoints_ra, midpoints_dec, unit='deg', frame='icrs') - return [ ang.degree for ang in pnt.separation(midpoints_coords) ] - -class PSF: - def __init__(self, irf_filename=None, psf_bintable=None): - self.irf_filename = None - self.psf_data = None - self.fields = ('SIGMA_1', 'SIGMA_2', 'SIGMA_3', 'SCALE', 'AMPL_2', 'AMPL_3') - - # a sort of cache... - self.energies = None - self.thetas = None - self.psf_matrix = None - - if irf_filename is not None: - self.irf_filename = irf_filename - irf = IRF(self.irf_filename) - self.psf_data = irf.get_psf_data() - elif psf_bintable is not None: - self.psf_data = psf_bintable - - if self.psf_data is None: - raise Exception('Need an irf or point spread function bintable') - - self.get_data_matrices() - - def columns(self): - return self.psf_data.columns - - def data(self): - return self.psf_data.data - - def get_data_matrices(self): - """ returns psf data matrix, energy[LO,HI] and theta[LO,HI] - """ - if self.energies is None or self.thetas is None or self.psf_matrix is None: - data = self.data() - if self.energies is None: - self.energies = np.column_stack((data.field('ENERG_LO')[0], data.field('ENERG_HI')[0])) - if self.thetas is None: - self.thetas = np.column_stack((data.field('THETA_LO')[0], data.field('THETA_HI')[0])) - - theta_len = len(self.thetas) - energies_len = len(self.energies) - if self.psf_matrix is None: - fmts = ['f8'] * len(self.fields) - self.psf_matrix = np.zeros((theta_len, energies_len), dtype={'names': self.fields, 'formats': tuple(fmts) }) - - for f in self.fields: - self.psf_matrix[f] = data.field(f)[0] - return self.psf_matrix, self.energies, self.thetas - - # maybe a better name: eval_region_psf_rate() or eval_region_rate() - def eval_region_flux_rate(self, region, pointing, energy): - """ - return flux rate in specific source region and integral computation error - - Parameters - region: source region (ra, dec, rad) - pointing: pointing direction (ra, dec) - - This method assumes source region has source as center. Is not general - enough, but at the moment is ok. - The value is the integration from the region center to the edge. If region - contains the full psf (inside 5σ) we return 1.0 . - - 1 1 x - μ - gaussian = ---------- exp( - --- (-------)² ) - sqrt(2πσ²) 2 σ - - prefactor = 1.0 / (2.0 * np.pi * (sigma_1^2 + ampl_2 * sigma_2^2 + ampl_3 * sigma_3^2)) - Note: - scale value ~= 1.0 / (2.0 * np.pi * (sigma_1 + ampl_2 * sigma_2 + ampl_3 * sigma_3)) - """ - region_center = utils.get_skycoord(region) - region_radius = utils.get_angle(region['rad']) - pnt_center = utils.get_skycoord(pointing) - theta = pnt_center.separation(region_center) - - delta_max = self.get_psf_delta_max(theta, energy) - if delta_max <= region_radius.degree: - return (1.0, 0.0) - - sigma_1, sigma_2, sigma_3, scale, ampl_2, ampl_3 = self.get_psf_values(theta, energy) - sigmas2_rad = [ np.deg2rad(s)**2 for s in [sigma_1, sigma_2, sigma_3] ] - prefactor_rad = 1.0 / (2.0 * np.pi * sigmas2_rad[0] + ampl_2 * sigmas2_rad[1] + ampl_3 * sigmas2_rad[2]) - - def psf_value(delta_rad): - d2 = delta_rad**2 - numerator = np.exp( -1/2 * d2 / sigmas2_rad[0] ) - numerator += np.exp( -1/2 * d2 / sigmas2_rad[1] ) * ampl_2 if sigma_2 > 0 else 0 - numerator += np.exp( -1/2 * d2 / sigmas2_rad[2] ) * ampl_3 if sigma_3 > 0 else 0 - return prefactor_rad * numerator - - # integration to 0 to rad on region circumference of psf value - crf_psf_fn = lambda delta: psf_value(delta) * 2.0 * np.pi * np.sin(delta) - return integrate.quad(crf_psf_fn, 0, np.deg2rad(region_radius.degree)) - - def get_psf_engine(self, region, pointing, energy): - """ - return psf engine. The engine function can elaborate the psf rate given - starting and stop angle [rad]. Each engine depends by theta (between - source region and pointing, and energy. - - Parameters - region: source region (ra, dec, rad) - pointing: pointing direction (ra, dec) - energy: energy in TeV - """ - region_center = utils.get_skycoord(region) - region_radius = utils.get_angle(region['rad']) - pnt_center = utils.get_skycoord(pointing) - theta = pnt_center.separation(region_center) - - # Note: delta_max is not the best things to implement in this context - # 'cause the integration distance is variabile and there is no - # "official" source region radius. - # Furthermore, we could implement the delta_max limit in _integrate_psf - # considering as radius the difference between integration params - # and the (eventually) region.radius passed as input. - # delta_max = self.get_psf_delta_max(theta, energy) - # if delta_max <= region_radius.degree: - # return (1.0, 0.0) - - sigma_1, sigma_2, sigma_3, scale, ampl_2, ampl_3 = self.get_psf_values(theta, energy) - sigmas2_rad = [ np.deg2rad(s)**2 for s in [sigma_1, sigma_2, sigma_3] ] - prefactor_rad = 1.0 / (2.0 * np.pi * sigmas2_rad[0] + ampl_2 * sigmas2_rad[1] + ampl_3 * sigmas2_rad[2]) - - def _integrate_psf(start_rad, stop_rad): - # typical params: - # start_rad = 0 - # stop_rad = np.deg2rad(region_radius.degree)) - if start_rad < 0: - raise Exception('The starting angle [rad] must be positive') - def psf_value(delta_rad): - d2 = delta_rad**2 - numerator = np.exp( -1/2 * d2 / sigmas2_rad[0] ) - numerator += np.exp( -1/2 * d2 / sigmas2_rad[1] ) * ampl_2 if sigma_2 > 0 else 0 - numerator += np.exp( -1/2 * d2 / sigmas2_rad[2] ) * ampl_3 if sigma_3 > 0 else 0 - return prefactor_rad * numerator - - # integration to start_rad to stop_rad on region circumference of psf value - crf_psf_fn = lambda delta: psf_value(delta) * 2.0 * np.pi * np.sin(delta) - return integrate.quad(crf_psf_fn, start_rad, stop_rad) - return _integrate_psf - - def get_psf_delta_max(self, offset, energy): - """ - return max delta value [deg] for theta and energy. - - Parameters - offset: Angle - energy: [TeV] - - max delta value is = 5*sigma value. - """ - sigma_1, sigma_2, sigma_3, scale, ampl_2, ampl_3 = self.get_psf_values(offset, energy) - sigma = sigma_1 - if sigma_2 > sigma: - sigma = sigma_2 - if sigma_3 > sigma: - sigma = sigma_3 - return 5.0 * sigma - - def get_psf_values(self, offset, energy): - """ - return psf data array (sigma_1, sigma_2, sigma_3, scale, ampl_2, ampl_3) - - Parameters - offset: Angle - energy: [TeV] - - This method returns plain value. No interpolation. - """ - offset_angle = utils.get_angle(offset) - psf_matrix, energy_bins, theta_bins = self.get_data_matrices() - - theta_index = None - energy_index = None - for i, tb in enumerate(theta_bins): - if offset_angle.degree >= tb[0] and offset_angle.degree < tb[1]: - theta_index = i - break - for j, en in enumerate(energy_bins): - if energy >= en[0] and energy < en[1]: - energy_index = j - break - if theta_index is None: - raise Exception('Theta offset is out of range ({})'.format(offset)) - if energy_index is None: - raise Exception('Energy is out of range ({})'.format(energy)) - return psf_matrix[theta_index, energy_index] - - # this interpolated is a test - def get_psf_1d_log(self, offset, energy): - """ - return psf data array (sigma_1, sigma_2, sigma_3, scale, ampl_2, ampl_3) - - Parameters - offset: Angle - energy: [TeV] - - This method does 1D interpolation on energy range, managed as log10. - Theta offset is not interpolated. - """ - offset_angle = utils.get_angle(offset) - psf_matrix, energy_bins, theta_bins = self.get_data_matrices() - - theta_index = None - for i, tb in enumerate(theta_bins): - if offset_angle.degree >= tb[0] and offset_angle.degree < tb[1]: - theta_index = i - break - if theta_index is None: - raise Exception('Theta offset is out of range ({})'.format(offset)) - - # energy interpolation - energy_mid = [ (e[0]+e[1])/2 for e in np.log10(energy_bins) ] - y_values = [] - for f in self.fields: - y_values.append(psf_matrix[theta_index][f]) - interp_fn = interpolate.interp1d(x = energy_mid, y = y_values) - return tuple(interp_fn(np.log10(energy))) - diff --git a/rtasci/rtasci/aph/photometry.py b/rtasci/rtasci/aph/photometry.py deleted file mode 100644 index 4d1887ca4a388bc06fa6ad067814f097a33a275d..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/aph/photometry.py +++ /dev/null @@ -1,189 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Simone Tampieri -# ******************************************************************************* - -from genericpath import isfile -from astropy.coordinates import SkyCoord, Angle -from astropy.io import fits -from rtasci.aph import utils -from regions import CircleSkyRegion -from regions import write_ds9 -import astropy.units as u -import numpy as np -import logging -import os -logging.basicConfig(level=logging.WARN) - -# Bintable columns: -# 0 name = 'EVENT_ID'; format = '1J'; bscale = 1; bzero = 2147483648 -# name = 'TIME'; format = '1D'; unit = 's' -# name = 'RA'; format = '1E'; unit = 'deg' -# name = 'DEC'; format = '1E'; unit = 'deg' -# name = 'ENERGY'; format = '1E'; unit = 'TeV' -# 5 name = 'DETX'; format = '1E'; unit = 'deg' -# 6 name = 'DETY'; format = '1E'; unit = 'deg' -# name = 'MC_ID'; format = '1J' - -class Photometrics(): - def __init__(self, args): - self.events_data = None - self.events_filename = None - self.mandatory_fields = ['RA', 'DEC', 'ENERGY'] - if 'events_filename' in args: - self.events_filename = args['events_filename'] - self.events_data = self.load_data_from_fits_file(self.events_filename) - elif 'events_list' in args: - self.events_data = args['events_list'] - self.events_list_checks() - logging.info('Events data type: {}'.format(type(self.events_data))) - - def events_list_checks(self): - """Data con be a FITS_rec or a np.recarray - see here: https://docs.astropy.org/en/stable/io/fits/usage/table.html - """ - if self.events_data is None: - raise Exception('Events data is empy. Need a events list.') - if isinstance(self.events_data, fits.fitsrec.FITS_rec): - for f in self.mandatory_fields: - if f not in self.events_data.columns.names: - raise Exception("Events data has no '{}' col".format(f)) - elif isinstance(self.events_data, np.recarray): - for f in self.mandatory_fields: - if f not in self.events_data.dtype.names: - raise Exception("Events data has no '{}' col".format(f)) - else: - raise Exception("Events data must be FITS_rec or np.recarray") - - @staticmethod - def load_data_from_fits_file(filename): - """Load events extension data from a fits file. - - Parameters - ---------- - filename: str - - Returns - ------- - events_bintable data - """ - with fits.open(filename, mode='readonly') as hdul: - data = hdul['EVENTS'].data - return data - - def region_counter(self, input_center, input_radius, emin=None, emax=None, tmin=None, tmax=None): - """Counts photons in an input area""" - region_center = utils.get_skycoord(input_center) - region_radius = utils.get_angle(input_radius) - - # filtering... - condlist = np.full(len(self.events_data.field('ENERGY')), True) - # ... w/ energy boundaries - if emin is not None: - condlist &= self.events_data.field('ENERGY') >= emin - if emax is not None: - condlist &= self.events_data.field('ENERGY') <= emax - # FIXME: TIME needs a better implementation - # atm it consider users that knows the time format in the input fits - if tmin is not None: - condlist &= self.events_data.field('TIME') >= tmin - if tmax is not None: - condlist &= self.events_data.field('TIME') <= tmax - - events_list = np.extract(condlist, self.events_data) - # events coordinates from the selected events list - events_coords = SkyCoord(events_list.field('RA'), events_list.field('DEC'), unit='deg', frame='icrs') - distances = region_center.separation(events_coords) - return np.count_nonzero(distances < region_radius) - - @classmethod - def reflected_regions(cls, input_pointing_center, input_region_center, input_region_radius): - """Find regions with reflected algorithm. - - Parameters - ---------- - input_pointing_center: SkyCoord or dict - input_region_center: SkyCoord or dict - input_region_radius: Angle or float - - Returns - ------- - array of regions - """ - pointing_center = utils.get_skycoord(input_pointing_center) - region_center = utils.get_skycoord(input_region_center) - region_radius = utils.get_angle(input_region_radius) - - # Angular separation of reflected regions. 1.05 factor is to have a margin - region_diameter = 1.05 * 2.0 * region_radius - radius = pointing_center.separation(region_center) - # the numbers_of_reflected regions is the number of center that can stay - # on the circumference, NOT the really computated number of regions. - # the number is floor down - numbers_of_reflected_regions = int(2 * np.pi * radius / region_diameter) - # Indeed, we skip the source region and the two near (see below), so we - # need at least 4 centers to get one off region. - if numbers_of_reflected_regions < 4: - raise Exception('the combination of region radius and coordinates does not allow to compute reflected regions.') - regions_offset_angle = Angle(360, unit='deg') / numbers_of_reflected_regions - - regions = [] - # starting from the source region 0, we skip region 1 and region N, so 2..N-1 - starting_pos_angle = pointing_center.position_angle(region_center) - for i in range(2, numbers_of_reflected_regions-1): - theta = starting_pos_angle + i * regions_offset_angle - coord_pos = pointing_center.directional_offset_by(theta, radius) - regions.append({ 'ra': coord_pos.ra.deg, 'dec': coord_pos.dec.deg, 'rad': region_radius.deg }) - return regions - - @classmethod - def wobble_regions(cls, *args): - return cls.cross_regions(*args) - - @classmethod - def cross_regions(cls, input_pointing_center, input_region_center, input_region_radius): - """Return the three background regions starting from pointing and source one. - - Parameters - ---------- - input_pointing_center: SkyCoord or dict - input_region_center: SkyCoord or dict - input_region_radius: Angle or float - - Returns - ------- - array of regions - """ - # FIXME Wobble algorithm has no check about distance and region radius. - pointing_center = utils.get_skycoord(input_pointing_center) - region_center = utils.get_skycoord(input_region_center) - region_radius = utils.get_angle(input_region_radius) - radius = pointing_center.separation(region_center) - starting_pos_angle = pointing_center.position_angle(region_center) - regions = [] - for i in range(1,4): - theta = starting_pos_angle + i * Angle(90, unit='deg') - coord_pos = pointing_center.directional_offset_by(theta, radius) - regions.append({ 'ra': coord_pos.ra.deg, 'dec': coord_pos.dec.deg, 'rad': region_radius.deg }) - return regions - - @classmethod - def write_region(cls, coords, filename, **kwargs): - try: - iter(coords) - except TypeError as te: - raise Exception('Coords must be iterable') - circles = [] - for coord in coords: - center = utils.get_skycoord(coord) - rad = utils.get_angle(coord['rad']) - circles.append(CircleSkyRegion(center=center, radius=rad, visual=kwargs)) - if isfile(filename): - os.remove(filename) - write_ds9(circles, filename) - diff --git a/rtasci/rtasci/aph/utils.py b/rtasci/rtasci/aph/utils.py deleted file mode 100644 index 4b1b03931a89835445961aa778088d5991f5e4fe..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/aph/utils.py +++ /dev/null @@ -1,162 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Simone Tampieri -# ******************************************************************************* - -import csv -import math -import numpy as np -from astropy.coordinates import SkyCoord, Angle -from rtasci.aph.photometry import Photometrics -from rtasci.aph.irf import EffectiveArea - -def photometrics_counts(events_list, events_type, pointing, true_coords, region_rad=0.2): - phm = Photometrics({events_type: events_list}) - reflected_regions = phm.reflected_regions(pointing, true_coords, region_rad) - on_count = phm.region_counter(true_coords, region_rad) - off_count = 0 - for r in reflected_regions: - off_count += phm.region_counter(r, r['rad']) - alpha = 1 / len(reflected_regions) - return {'on': on_count, 'off': off_count, 'alpha': alpha, 'excess': on_count - alpha * off_count} - -def li_ma (n_on, n_off, alpha): - if n_on <= 0 or n_off <= 0 or alpha == 0: - return np.nan - fc = (1 + alpha) / alpha - fb = n_on / (n_on + n_off) - f = fc * fb - gc = 1 + alpha - gb = n_off / (n_on + n_off) - g = gc * gb - first = n_on * math.log(f) - second = n_off * math.log(g) - fullb = first + second - return math.sqrt(2) * math.sqrt(fullb) - -def get_excess(n_on, n_off, alpha): - return n_on - alpha * n_off - -def get_excess_error(n_on, n_off): - dE = np.sqrt(np.sqrt(n_on)**2 + np.sqrt(n_off)**2) - return dE - -def li_ma_error(n_on, n_off, alpha): - if n_on <= 0 or n_off <= 0 or alpha == 0: - return np.nan - dE = np.sqrt(n_on**2 + n_off**2) - A = n_on / (n_on + n_off) - dA = np.abs(A) * np.sqrt((np.sqrt(n_on)/n_on)**2 + (dE/(n_on+n_off)**2)) - B = n_off / (n_on + n_off) - dB = np.abs(B) * np.sqrt((np.sqrt(n_off)/n_off)**2 + (dE/(n_on+n_off)**2)) - lnA = np.log((alpha+1)/alpha * n_on/(n_on+n_off)) - dlnA = dA / lnA - lnB = np.log((alpha+1)*n_off/(n_on+n_off)) - dlnB = dB / lnB - don = np.abs(n_on*lnA) * np.sqrt((np.sqrt(n_on)/n_on)**2 + (dlnA/lnA)**2) - doff = np.abs(n_off*lnB) * np.sqrt((np.sqrt(n_off)/n_off)**2 + (dlnB/lnB)**2) - dsum = np.sqrt(don**2 + doff**2) - error = 0.5 * np.sqrt(n_on*lnA + n_off*lnB) * dsum/(n_on*lnA + n_off*lnB) - return error - -def read_timeslices_tsv(filename): - ts = [] - with open(filename, mode='r', newline='\n') as fh: - reader = csv.reader(fh, delimiter='\t') - headers = next(reader) - for row in reader: - ts.append(dict(zip(headers, row))) - return ts - -def get_angle(input_angle): - ang = None - if isinstance(input_angle, Angle): - ang = input_angle - elif isinstance(input_angle, float): - ang = Angle(input_angle, unit='deg') - else: - raise Exception('The input parameter must be an Angle or a float for decimal degree.') - return ang - -def get_skycoord(pnt_coord): - coord = None - if isinstance(pnt_coord, SkyCoord): - coord = pnt_coord - elif isinstance(pnt_coord, tuple) or isinstance(pnt_coord, list): - coord = SkyCoord(ra=pnt_coord[0], dec=pnt_coord[1], unit='deg', frame='icrs') - elif isinstance(pnt_coord, dict) and 'ra' in pnt_coord and 'dec' in pnt_coord: - coord = SkyCoord(ra=pnt_coord['ra'], dec=pnt_coord['dec'], unit='deg', frame='icrs') - else: - raise Exception('The input parameter must be a SkyCoord, a { "ra": 12.3, "dec": 45.6 } dictionary, a (12.3, 45.6) tuple or a [12.3, 45.6] list.') - return coord - -def counting(phm, src, rad, off_regions, e_min=None, e_max=None, t_min=None, t_max=None, draconian=False): - on_count = phm.region_counter(src, rad, emin=e_min, emax=e_max, tmin=t_min, tmax=t_max) - off_count = 0 - for r in off_regions: - off_count += phm.region_counter(r, r['rad'], emin=e_min, emax=e_max, tmin=t_min, tmax=t_max) - - alpha = 1 / len(off_regions) - excess = on_count - alpha * off_count - signif = li_ma(on_count, off_count, alpha) - - # !!! here we can implement checks - err_note = None - if on_count < 10 or off_count < 10: - err_note = 'Not compliant with Li & Ma requirements.' - if draconian: - raise Exception(err_note) - - return on_count, off_count, alpha, excess, signif, err_note - -def find_off_regions(phm, algo, src, pnt, rad, verbose=False, save=None): - if not len(pnt) == 2 and len(src) == 2: - raise Exception('need source and pointing coordinates and a region radius to do aperture photometry') - - off_regions = None - if algo.lower() == 'cross': - off_regions = phm.cross_regions(pnt, src, rad) - elif algo.lower() == 'reflection': - off_regions = phm.reflected_regions(pnt, src, rad) - else: - raise Exception('invalid background regions algorithm') - - if verbose > 1: - print('off regions algorithm:', algo) - for i, o in enumerate(off_regions): - print(' off regions #{:02d}:'.format(i), o) - - if save: - phm.write_region(off_regions, save, color='red', dash=True, width=2) - - return off_regions - -def aeff_eval(args, src, pnt): - if not(args.energy_min and args.energy_max and args.pixel_size and args.power_law_index): - raise Exception('need energy min and max, a pixel size to eval the flux') - - aeff = EffectiveArea(irf_filename=args.irf_file) - # these IRFs return value in m², so we need convert - # the source data struct need a 'rad' - source_reg_aeff = aeff.weighted_value_for_region(src, pnt, [args.energy_min, args.energy_max], args.pixel_size, args.power_law_index) * 1e4 # cm2 - return source_reg_aeff - -class ObjectConfig(object): - def __init__(self, d): - self.__dict__ = d - self.__dict__['begin_time'] = float(self.__dict__['begin_time']) - self.__dict__['end_time'] = float(self.__dict__['end_time']) - self.__dict__['source_ra'] = float(self.__dict__['source_ra']) - self.__dict__['source_dec'] = float(self.__dict__['source_dec']) - self.__dict__['region_radius'] = float(self.__dict__['region_radius']) - self.__dict__['verbose'] = float(self.__dict__['verbose']) - self.__dict__['energy_min'] = float(self.__dict__['energy_min']) - self.__dict__['energy_max'] = float(self.__dict__['energy_max']) - self.__dict__['pixel_size'] = float(self.__dict__['pixel_size']) - self.__dict__['power_law_index'] = float(self.__dict__['power_law_index']) - self.__dict__['irf_file'] = str(self.__dict__['irf_file']) diff --git a/rtasci/rtasci/cfg/Config.py b/rtasci/rtasci/cfg/Config.py deleted file mode 100644 index 39a377e980ce3cfcd12624778afc7b7eba3812e4..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/cfg/Config.py +++ /dev/null @@ -1,170 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Leonardo Baroncelli -# ******************************************************************************* - -import os -import yaml - -class ConfigException(Exception): - """Alert for exception.""" - def __init__(self, message): - super().__init__(message) - -class BadConfiguration(ConfigException): - """Alert for bad configuration.""" - def __init__(self, message): - super().__init__(message) - -class ConfigParamNotFound(ConfigException): - """Alert when parameter is not found.""" - def __init__(self, message): - super().__init__(message) - -class Config: - """Configure pipeline.""" - def __init__(self, cfgfile): - configuration = open(cfgfile) - self.cfg = yaml.load(configuration, Loader=yaml.FullLoader) - self.cfgDesc = { - 'sections' : ['setup', 'simulation', 'analysis', 'options', 'path'], - 'setup' : ['simtype', 'runid', 'trials', 'start_count', 'scalefluxfactor'], - 'simulation' : ['caldb', 'irf', 'tobs', 'onset', 'emin', 'emax', 'roi', 'delay', 'offset', 'nruns'], - 'analysis' : ['maxsrc', 'skypix', 'skyroifrac', 'smooth', 'tool', 'type', 'blind', 'binned', 'exposure', 'usepnt', 'sgmthresh', 'cumulative', 'lightcurve', 'index'], - 'options' : ['set_ebl', 'extract_data', 'plotsky'], - 'path' : ['data', 'ebl', 'model', 'catalog'] - } - self.validateCfg() - - def prettyd(self, d=None, indent=0): - s = '' - if d is None: - d = self.cfg - for key, value in d.items(): - s += '\t' * indent + str(key) - if isinstance(value, dict): - s += '\n' + self.prettyd(value, indent+1) - else: - s += '\t' * (indent+1) + str(value) + '\n' - return s - - def __str__(self): - return self.prettyd(self.cfg) - - def get(self, paramName): - for sectionName in self.cfgDesc['sections']: - if paramName in self.cfg[sectionName]: - return self.cfg[sectionName][paramName] - raise ConfigParamNotFound(f"Config param {paramName} not found in configuration.") - - def set(self, paramName, paramValue): - for sectionName in self.cfgDesc['sections']: - if paramName in self.cfg[sectionName]: - self.cfg[sectionName][paramName] = paramValue - return - raise ConfigParamNotFound(f"Config param {paramName} not found in configuration.") - - def validateCfg(self): - self.checkSections( self.cfgDesc['sections']) - self.checkSetupSectionParams( self.cfgDesc['setup']) - self.checkSimulationSectionParams(self.cfgDesc['simulation']) - self.checkOptionsSectionParams( self.cfgDesc['options']) - self.checkAnalysisSectionParams( self.cfgDesc['analysis']) - self.checkPathSectionParams( self.cfgDesc['path']) - - def checkSections(self, sections): - sectionMissing = set(sections) - set(self.cfg.keys()) - if len(sectionMissing) > 0: - raise BadConfiguration(f'Configuration file sections are missing: {sectionMissing}') - - def dump(self, cfgfile_path): - with open(cfgfile_path, 'w') as f: - yaml.dump(self.cfg, f) - - ################# - # Setup section # - ################# - - def checkSetupSectionParams(self, params): - paramsMissing = set(params) - set(self.cfg['setup']) - if len(paramsMissing) > 0: - raise BadConfiguration(f'Configuration file params of "setup" section are missing: {paramsMissing}') - - sectionDict = self.cfg['setup'] - - # Add validations here - simTypeValues = ['grb', 'bkg', 'skip', 'wobble', 'wilks'] - if sectionDict['simtype'] not in simTypeValues: - raise BadConfiguration(f'simtype={sectionDict["simtype"]} is not supported. Available values: {simTypeValues}') - - # Add other validations here.... - - ###################### - # Simulation section # - ###################### - - def checkSimulationSectionParams(self, params): - paramsMissing = set(params) - set(self.cfg['simulation']) - if len(paramsMissing) > 0: - raise BadConfiguration(f'Configuration file params of "simulation" section are missing: {paramsMissing}') - - sectionDict = self.cfg['simulation'] - - # Add other validations here.... - - ################### - # Options section # - ################### - - def checkOptionsSectionParams(self, params): - paramsMissing = set(params) - set(self.cfg['options']) - if len(paramsMissing) > 0: - raise BadConfiguration(f'Configuration file params of "options" section are missing: {paramsMissing}') - - sectionDict = self.cfg['options'] - - # Add other validations here.... - - #################### - # Analysis section # - #################### - - def checkAnalysisSectionParams(self, params): - paramsMissing = set(params) - set(self.cfg['analysis']) - if len(paramsMissing) > 0: - raise BadConfiguration(f'Configuration file params of "analysis" section are missing: {paramsMissing}') - - sectionDict = self.cfg['analysis'] - - # Add other validations here.... - toolTypeValues = ['ctools', 'gammapy', 'rtatool', 'skip'] - if sectionDict['tool'] not in toolTypeValues: - raise BadConfiguration(f'tool={sectionDict["tool"]} is not supported. Available values: {toolTypeValues}') - typeValues = ['1d', '3d'] - if sectionDict['type'] not in typeValues: - raise BadConfiguration(f'type={sectionDict["type"]} is not supported. Available values: {typeValues}') - - ################ - # Path section # - ################ - - def checkPathSectionParams(self, params): - paramsMissing = set(params) - set(self.cfg['path']) - if len(paramsMissing) > 0: - raise BadConfiguration(f'Configuration file params of "path" section are missing: {paramsMissing}') - - sectionDict = self.cfg['path'] - if not sectionDict['data']: - raise BadConfiguration(f'data={sectionDict["data"]} is empty!') - - self.cfg['path']['data'] = os.path.expandvars(self.cfg['path']['data']) - if not os.path.isdir(sectionDict['data']): - raise BadConfiguration(f'data={sectionDict["data"]} is not a folder!') - - # Add other validations here.... - diff --git a/rtasci/rtasci/cfg/config.yaml.default b/rtasci/rtasci/cfg/config.yaml.default deleted file mode 100644 index 5a50fa77ebbc0ede350fe21796df97c52a07f684..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/cfg/config.yaml.default +++ /dev/null @@ -1,52 +0,0 @@ -setup: - simtype: # grb -> src+bkg; bkg -> empty fields; skip -> skip sim; - # wobble -> LST-like runs (str) - runid: # can be all or any template (list or str) in catalog - trials: # realisations per runid (int) - start_count: # starting count for seed (int) - scalefluxfactor: # scale src nominal flux by factor (float) - -simulation: - caldb: # calibration database (str) - irf: # istrument response function (str) - tobs: # total obs time (s) (float) - onset: # time of bkg only a.k.a. delayed onset of burst (s) (float) - delay: # delayed start of observation (s) (float) - emin: # simulation minimum energy (TeV) (float) - emax: # simulation maximum energy (TeV) (float) - roi: # region of interest radius (deg) (float) - offset: # 'gw' -> from alert; value -> otherwise (deg) (str/float) - nruns: # numer of runs (of lenght=tobs) for wobble simtype (int) - -analysis: - skypix: # pixel size in skymap (deg) (float) - skyroifrac: # ratio between skymap axis and roi (float) - smooth: # Gaussian corr. kernel rad. (deg) (float) - maxsrc: # number of hotspot to search for (float) - sgmthresh: 3 # blind-search acc. thresh. in Gaussian sigma (float) - usepnt: yes # use pointing for RA/DEC (bool) - exposure: # exposure times for the analysis (s) (float) - - - - - binned: no # perform binned or unbinned analysis (bool) - blind: yes # requires blind-search (bool) - tool: ctools # which science tool (str) - type: 3d # 1d on/off or 3d full-fov (str) - cumulative: # select events with cumulative exposure time (bool) - lightcurve: # select events to compute lightcurves with fixed time window (bool) - index: # frozen photon index for fitting the PL model (float) - -options: - set_ebl: # if True uses EBL absorbed spectra (bool) - extract_data: # if True extracts lightcurves and spectra (bool) - plotsky: # if True generates skymap plot (bool) - -path: - data: # parent folder (str) - ebl: # file of tau values for EBL (str) - model: # folder of source models (str) - merger: # folder of alerts (str) - bkg: # file of background model (str) - catalog: # folder of source catalog (str) - - diff --git a/rtasci/rtasci/check_templates.py b/rtasci/rtasci/check_templates.py deleted file mode 100644 index 7d2343d66fdf0393490e9b15aefdd9aa94bf3a60..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/check_templates.py +++ /dev/null @@ -1,40 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import argparse -import numpy as np -import matplotlib.pyplot as plt -from rtasci.lib.RTAVisualise import get_template_lc, get_template_spectra - -parser = argparse.ArgumentParser(description='Simulate empty fields.') -parser.add_argument('--runid', type=str, required=True, help="template RUNID") -parser.add_argument('-p', '--path', type=str, default='/Users/iasfbo/Desktop/CTA/DATA/templates/grb_afterglow/GammaCatalogV1.0/', help='absolute path where cat is installed') -args = parser.parse_args() - -time, flux = get_template_lc(runid=args.runid+'.fits', erange=[0.04, 1], path=args.path) -print(np.shape(time), np.shape(flux)) - -plt.plot(time, flux) -plt.xlabel('time') -plt.ylabel('flux') -plt.yscale('log') -plt.xscale('log') -plt.savefig(f'test_lightcurve_{args.runid}.png') -plt.close() - -energy, flux = get_template_spectra(runid=args.runid+'.fits', erange=[0.04, 1], path=args.path) -print(np.shape(energy), np.shape(flux)) - -plt.plot(energy, flux) -plt.xlabel('energy') -plt.ylabel('flux') -plt.yscale('log') -plt.xscale('log') -plt.savefig(f'test_spectra_{args.runid}.png') -plt.close() \ No newline at end of file diff --git a/rtasci/rtasci/degradation_caldb.py b/rtasci/rtasci/degradation_caldb.py deleted file mode 100644 index d03c21d07745a221ae59f2fc81cbf500d9080670..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/degradation_caldb.py +++ /dev/null @@ -1,29 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -from rtasci.lib.RTAIrfs import RTAIrfs - -# files ---! -parser = argparse.ArgumentParser() -parser.add_argument("--caldb", nargs="+", default=['prod3b', 'prod3b-v2']) -args = parser.parse_args() - -# initialise ---! -for db in args.caldb: - print(f'processing {db} degradation') - irf = os.listdir(os.environ.get('CTOOLS') + '/share/caldb/data/cta/' + db + '/bcf/') - for fits in irf: - irfObj = RTAIrfs() - irfObj.irf = fits - irfObj.caldb = db - irfObj.degradeIrf() - -print('caldb degradation completed') \ No newline at end of file diff --git a/rtasci/rtasci/emptyfields.py b/rtasci/rtasci/emptyfields.py deleted file mode 100644 index 419914ed26a69a236aee0412c3fabb651b0dd1c1..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/emptyfields.py +++ /dev/null @@ -1,205 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import os, argparse -from os.path import isdir, isfile, join, expandvars -from rtasci.cfg.Config import Config -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAUtils import get_pointing, get_mergermap -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw -from rtasci.lib.RTAVisualise import plotSkymap -from rtasci.aph.utils import * - -def main(args): - cfg = Config(args.cfgfile) - # general ---! - if cfg.get('simtype').lower() != 'bkg': - raise ValueError('This script should be used only for empty fields simulation and analysis') - trials = cfg.get('trials') # trials - tobs = cfg.get('tobs') # total obs time (s) - runid = cfg.get('runid') - if type(runid) != str or runid == 'all': - raise ValueError('Currently only 1 runid is allowed.') - - # paths ---! - datapath = cfg.get('data') - if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') - if not isdir(join(datapath, 'obs')): # obs parent folder - os.mkdir(join(datapath, 'obs')) - bkgpath = join(datapath, 'obs', f'{runid}_backgrounds') - if not isdir(bkgpath): - os.mkdir(bkgpath) - if not isdir(join(datapath, 'rta_products')): - os.mkdir(join(datapath, 'rta_products')) - rtapath = join(datapath, 'rta_products', f'{runid}_backgrounds') - if not isdir(rtapath): - os.mkdir(rtapath) - if not isdir(join(datapath, 'skymaps')): - os.mkdir(join(datapath, 'skymaps')) - png = join(datapath, 'skymaps', f'{runid}_backgrounds') - if not isdir(join(datapath, 'outputs')): - os.mkdir(join(datapath, 'outputs')) - outpath = join(datapath, 'outputs', f'{runid}_backgrounds') - if not isdir(outpath): - os.mkdir(outpath) - # background model ---! - bkg_model = expandvars(cfg.get('bkg')) # XML background model - logname = join(outpath, f"{cfg.get('caldb')}-{cfg.get('irf')}_seed{cfg.get('start_count')+1:06d}-{cfg.get('start_count')+1+trials:06d}_offset{cfg.get('offset')}.txt") - # true coords ---! - true_coords = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - for i in range(trials): - # initialise ---! - count = cfg.get('start_count') + i + 1 - name = f'bkg{count:06d}' - print(f"Simulate empty fields for runid = {cfg.get('runid')} seed = {count}") - - # setup ---! - sim = RTACtoolsSimulation() - sim.seed = count - sim.pointing = pointing - sim.caldb = cfg.get('caldb') - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - sim.t = [0, tobs] - bkg = join(bkgpath, f'{name}.fits') - sim.model = bkg_model - sim.output = bkg - sim.run_simulation() - - # analysis ---! - for texp in cfg.get('exposure'): - selphlist = bkg.replace(f'{name}', f'texp{texp}s_{name}') - if args.print.lower() == 'true': - print(f"Selection: {selphlist}") - an = RTACtoolsAnalysis() - an.pointing = pointing - an.caldb = cfg.get('caldb') - an.irf = cfg.get('irf') - an.fov = cfg.get('roi') - an.e = [cfg.get('emin'), cfg.get('emax')] - an.t = [0, texp] - an.input = bkg - an.output = selphlist - an.run_selection() - # aperture photometry ---! - if '.fits' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=true_coords, events_type='events_filename') - elif '.xml' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=true_coords, events_type='events_list') - sigma = li_ma(results['on'], results['off'], results['alpha']) - if args.print.lower() == 'true': - print('Photometry counts:', results) - print('Li&Ma significance:', sigma) - # skymap ---! - sky = selphlist.replace(bkgpath, rtapath).replace('.fits', '_sky.fits') - if args.print.lower() == 'true': - print(f"Skymap: {sky}") - an.input = selphlist - an.output = sky - an.max_src = cfg.get('maxsrc') - an.sky_subtraction = 'NONE' - an.run_skymap(wbin=cfg.get('skypix'), roi_factor=cfg.get('skyroifrac')) - # blind-search ---! - candidates = sky.replace('_sky.fits', '_sources.xml') - if args.print.lower() == 'true': - print(f"Candidates: {candidates}") - an.sigma = cfg.get('sgmthresh') - an.corr_rad = cfg.get('smooth') - an.max_src = cfg.get('maxsrc') - an.input = sky - an.output = candidates - an.run_blindsearch() - if cfg.get('plotsky'): - plotSkymap(sky, reg=candidates.replace('.xml', '.reg'), suffix=f'{texp}s', png=png) - # modify model - detection = ManageXml(candidates) - detection.modXml(overwrite=True) - detection.setTsTrue() - detection.parametersFreeFixed(src_free=['Prefactor']) - detection.closeXml() - # fit ---! - fit = candidates.replace('_sources.xml', '_fit.xml') - if args.print.lower() == 'true': - print(f"Fit: {fit}") - an.input = selphlist - an.model = candidates - an.output = fit - an.run_maxlikelihood() - # stats ---! - print(fit) - xml = ManageXml(fit) - try: - coords = xml.getRaDec() - ra = coords[0][0] - dec = coords[1][0] - ts = xml.getTs()[0] - except IndexError: - ts, ra, dec = np.nan, np.nan, np.nan - print('Candidate not found.') - - row = f"{runid} {count} {texp} {ts} {ra} {dec} {results['on']} {results['off']} {results['alpha']} {results['excess']} {sigma} {cfg.get('offset')} {cfg.get('caldb')} {cfg.get('irf')}\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed texp ts ra dec oncounts offcounts alpha excess sigma offset caldb irf\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del an - del sim - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}_backgrounds/*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}_backgrounds/*{name}*") - - - -if __name__=='__main__': - parser = argparse.ArgumentParser(description='Simulate empty fields.') - parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") - parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') - parser.add_argument('--print', type=str, default='false', help='Print out results') - args = parser.parse_args() - - print(args.cfgfile) - - main(args) - print('...done.\n') - diff --git a/rtasci/rtasci/lib/RTACtoolsAnalysis.py b/rtasci/rtasci/lib/RTACtoolsAnalysis.py deleted file mode 100644 index a64ec4053180504de4982a06dc51e1de11b712d7..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTACtoolsAnalysis.py +++ /dev/null @@ -1,576 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import ctools -import cscripts -import os -import numpy as np -from astropy.io import fits - -# count photometry ctools -def onoff_counts(pha): - '''This function returns on, off and excess counts given ctools on/off files.''' - regions = pha.replace('.xml', '_off.reg') - off = pha.replace('.xml', '_pha_off.fits') - on = pha.replace('.xml', '_pha_on.fits') - nreg = 0 - # regions ---! - with open(regions, 'r') as f: - for line in f: - if line.startswith('fk5'): - nreg += 1 - # on counts ---! - with fits.open(on) as h: - oncounts = h[1].data.field('counts') - onsum = 0 - for val in oncounts: - onsum += val - # off counts ---! - with fits.open(off) as h: - offcounts = h[1].data.field('counts') - offsum = 0 - for val in offcounts: - offsum += val - offsum = np.sum(offsum) - # alpha and excess ---! - alpha = 1.0/nreg - excess = onsum - alpha * offsum - return onsum, offsum, excess, alpha - -class RTACtoolsAnalysis() : - ''' - This class contains wrappers for ctools and cscripts tools. - ''' - def __init__(self, on_ram=False): - # files fields ---! - self.__on_ram = on_ram - self.model = str() - self.output, self.input = (str() for i in range(2)) - self.caldb = 'prod2' # caldb (str) ---! - self.irf = 'South_0.5h' # irf (Str) ---! - # condition control ---! - self.set_debug = False # set/unset debug mode for ctools ---! - self.set_log = True # set/unset logfiles for ctools ---! - self.edisp = True # require enegy dispersion ---! - # data ---! - self.e = [0.03, 150.0] # energy range (TeV) ---! - self.roi = 5 # region of interest (deg) ---! - self.t = [0, 1800] # time range (s/MJD) ---! - self.pointing = [83.63, 22.01] # RA/DEC or GLON/GLAT (deg) ---! - self.target = [83.63, 22.51] # RA/DEC or GLON/GLAT (deg) ---! - self.sigma = 3 # Gaussian significance (sigmas) ---! - self.max_src = 10 # Max number of candidates to list during blind-detection ---! - # ctools miscellaneous ---! - self.seed = 1 # MC seed ---! - self.usepnt = True # use pointing coordinates - self.refit = False # refit after initial fit - self.stats = 'DEFAULT' # statistics for likelihood fit - self.accuracy = 0.005 # max like accuracy - self.max_iter = 50 # max iteration max like - self.coord_sys = 'CEL' # coordinate system ---! - self.proj = 'CAR' # projection method ---! - self.sky_subtraction = 'IRF' # skymap subtraction type ---! - self.inexclusion = 'NONE' # exlude sky regions ---! - self.bkg_type = 'irf' # background model ---! - self.src_type = 'POINT' # source model type ---! - self.src_name = 'Src001' # name of source of interest ---! - self.exclrad = 0.5 # radius around candidate to exclude from further search ---! - self.corr_kern = 'GAUSSIAN' # smoothing type ---! - self.corr_rad = 0.1 # radius for skymap smoothing ---! - self.sgmrange = [0, 10] # range of gaussian sigmas ---! - self.confidence = 0.95 # confidence level (%) ---! - self.eref = 1 # energy reference for flux computation ---! - self.sens_type = 'Differential' # sensitivity type ---! - self.nthreads = 1 - self.stack = False - - # ctselect wrapper ---! - def run_selection(self, prefix=None): - '''Wrapper of ctselect.''' - selection = ctools.ctselect() - selection['inobs'] = self.input - selection['outobs'] = self.output - selection['usepnt'] = self.usepnt - if prefix != None: - selection['prefix'] = prefix - selection['rad'] = self.roi - selection['tmin'] = self.t[0] - selection['tmax'] = self.t[1] - selection['emin'] = self.e[0] - selection['emax'] = self.e[1] - # selection["nthreads"] = self.nthreads - selection['logfile'] = self.output.replace('.xml', '.log') - selection['debug'] = self.set_debug - if self.set_log: - selection.logFileOpen() - if not self.__on_ram: - selection.execute() - else: - selection.run() - return - - # ctskymap wrapper ---! - def run_skymap(self, wbin=0.02, roi_factor=1): - '''Wrapper of ctskymap.''' - nbin = int(self.roi*2*roi_factor/wbin) - skymap = ctools.ctskymap() - skymap['inobs'] = self.input - skymap['outmap'] = self.output - skymap['irf'] = self.irf - skymap['caldb'] = self.caldb - skymap['emin'] = self.e[0] - skymap['emax'] = self.e[1] - skymap['usepnt'] = self.usepnt - skymap['nxpix'] = nbin - skymap['nypix'] = nbin - skymap['binsz'] = wbin - skymap['coordsys'] = self.coord_sys.upper() - skymap['proj'] = 'CAR' - skymap['bkgsubtract'] = self.sky_subtraction.upper() - skymap['inexclusion'] = self.inexclusion - # skymap["nthreads"] = self.nthreads - skymap['logfile'] = self.output.replace('.fits', '.log') - skymap['debug'] = self.set_debug - if self.set_log: - skymap.logFileOpen() - if not self.__on_ram: - skymap.execute() - else: - skymap.run() - return - - # cssrcdetect wrapper ---! - def run_blindsearch(self, fit_pos=False, fit_shape=False): - '''Wrapper of cssrcdetect.''' - detection = cscripts.cssrcdetect() - detection['inmap'] = self.input - detection['outmodel'] = self.output - detection['outds9file'] = self.output.replace('xml','reg') - detection['srcmodel'] = self.src_type.upper() - detection['bkgmodel'] = self.bkg_type.upper() - detection['fit_pos'] = fit_pos - detection['fit_shape'] = fit_shape - detection['threshold'] = int(self.sigma) - detection['maxsrcs'] = self.max_src - detection['exclrad'] = self.exclrad - detection['corr_rad'] = self.corr_rad - detection['corr_kern'] = self.corr_kern.upper() - detection['logfile'] = self.output.replace('.xml', '.log') - detection['debug'] = self.set_debug - # detection["nthreads"] = self.nthreads - if self.set_log: - detection.logFileOpen() - if not self.__on_ram: - detection.execute() - else: - detection.run() - return - - # csphagen wrapper ---! - def run_onoff(self, method='reflected', prefix='onoff', maxoffset=2.5, radius=0.2, ebins=40, ebins_alg='LOG', binfile=None, exp=None, use_model_bkg=True, etruemin=0.01, etruemax=0.01, etruebins=30, bkgskip=1, bkgmin=2): - '''Wrapper of csphagen.''' - onoff = cscripts.csphagen() - onoff['inobs'] = self.input - onoff['inmodel'] = self.model - onoff['prefix'] = prefix - onoff['outobs'] = self.output - if '.xml' in self.output: - onoff['outmodel'] = self.output.replace('.xml', '_model.xml') - else: - raise ValueError('output obs must be a .xml file') - onoff['caldb'] = self.caldb - onoff['irf'] = self.irf - onoff['srcname'] = self.src_name - onoff['ebinalg'] = ebins_alg - onoff['emin'] = self.e[0] - onoff['emax'] = self.e[1] - onoff['enumbins'] = ebins - if binfile != None: - onoff['ebinfile'] = binfile - if exp != None: - onoff['ebingamma'] = exp - onoff['coordsys'] = self.coord_sys - if self.coord_sys == 'CEL': - onoff['ra'] = self.target[0] - onoff['dec'] = self.target[1] - else: - onoff['lon'] = self.target[0] - onoff['lat'] = self.target[1] - onoff['rad'] = radius - onoff['srcregfile'] = self.output.replace('.xml', '_on.reg') - onoff['bkgregfile'] = self.output.replace('.xml', '_off.reg') - onoff['bkgmethod'] = method.upper() - onoff['use_model_bkg'] = use_model_bkg - onoff['maxoffset'] = maxoffset - onoff['bkgregmin'] = bkgmin - onoff['bkgregskip'] = bkgskip - onoff['stack'] = self.stack - onoff['etruemin'] = etruemin - onoff['etruemax'] = etruemax - onoff['etruebins'] = etruebins - onoff["nthreads"] = self.nthreads - onoff['logfile'] = self.output.replace('.xml', '.log') - onoff['debug'] = self.set_debug - if self.set_log: - onoff.logFileOpen() - if not self.__on_ram: - onoff.execute() - else: - onoff.run() - return - - # ctbin wrapper ---! - def run_binning(self, prefix='cube_', ebins_alg='LOG', ebins=10, binfile=None, exp=None, nbins=None, wbin=0.02): - '''Wrapper of ctbin.''' - if nbins == None: - nbins = int(self.roi * 2 / wbin) - bins = ctools.ctbin() - bins['inobs'] = self.input - bins['outobs'] = self.output - bins['stack'] = self.stack - if prefix != None: - bins['prefix'] = prefix - bins['ebinalg'] = ebins_alg - bins['emin'] = self.e[0] - bins['emax'] = self.e[1] - bins['enumbins'] = ebins - if binfile != None: - bins['ebinfile'] = binfile - if exp != None: - bins['ebingamma'] = exp - bins['usepnt'] = self.usepnt - bins['nxpix'] = nbins - bins['nypix'] = nbins - bins['binsz'] = wbin - bins['coordsys'] = self.coord_sys - bins['proj'] = self.proj - if self.usepnt is False: - bins['xref'] = self.target[0] - bins['yref'] = self.target[1] - if '.fits' in self.output: - bins['logfile'] = self.output.replace('.fits', '.log') - elif '.xml' in self.output: - bins['logfile'] = self.output.replace('.xml', '.log') - bins['debug'] = self.set_debug - if self.set_log: - bins.logFileOpen() - if not self.__on_ram: - bins.execute() - else: - bins.run() - return - - # ctexpcube wrapper ---! - def run_expcube(self, cube, ebins=10, nbins=None, wbin=0.02, ebin_alg='LOG', ebinfile=None, ebingamma=None, addbounds=False): - '''Wrapper of ctexpcube.''' - if nbins == None: - nbins = int(self.roi * 2 / wbin) - exp = ctools.ctexpcube() - exp['inobs'] = self.input - exp['incube'] = cube - exp['caldb'] = self.caldb - exp['irf'] = self.irf - exp['outcube'] = self.output - exp['ebinalg'] = ebin_alg - exp['emin'] = self.e[0] - exp['emax'] = self.e[1] - exp['enumbins'] = ebins - if ebinfile != None: - exp['ebinfile'] = ebinfile - if ebingamma != None: - exp['ebingamma'] = ebingamma - exp['addbounds'] = addbounds - exp['usepnt'] = self.usepnt - exp['nxpix'] = nbins - exp['nypix'] = nbins - exp['binsz'] = wbin - exp['coordsys'] = self.coord_sys - exp['proj'] = self.proj - if not self.usepnt: - exp['xref'] = self.target[0] - exp['yref'] = self.target[1] - if '.fits' in self.output: - exp['logfile'] = self.output.replace('.fits', '.log') - elif '.xml' in self.output: - exp['logfile'] = self.output.replace('.xml', '.log') - exp['debug'] = self.set_debug - if self.set_log: - exp.logFileOpen() - if not self.__on_ram: - exp.execute() - else: - exp.run() - return - - # ctpsfcube wrapper ---! - def run_psfcube(self, cube, ebins=10, nbins=None, wbin=0.02, amax=0.3, abins=200, ebin_alg='LOG', ebinfile=None, ebingamma=None, addbounds=False): - '''Wrapper of ctpsfcube.''' - if nbins == None: - nbins = int(self.roi * 2 / wbin) - psf = ctools.ctpsfcube() - psf['inobs'] = self.input - psf['incube'] = cube - psf['caldb'] = self.caldb - psf['irf'] = self.irf - psf['outcube'] = self.output - psf['ebinalg'] = ebin_alg - psf['emin'] = self.e[0] - psf['emax'] = self.e[1] - psf['enumbins'] = ebins - if ebinfile != None: - psf['ebinfile'] = ebinfile - if ebingamma != None: - psf['ebingamma'] = ebingamma - psf['addbounds'] = addbounds - psf['usepnt'] = self.usepnt - psf['nxpix'] = nbins - psf['nypix'] = nbins - psf['binsz'] = wbin - psf['coordsys'] = self.coord_sys - psf['proj'] = self.proj - if not self.usepnt: - psf['xref'] = self.target[0] - psf['yref'] = self.target[1] - psf['amax'] = amax - psf['anumbins'] = abins - if '.fits' in self.output: - psf['logfile'] = self.output.replace('.fits', '.log') - elif '.xml' in self.output: - psf['logfile'] = self.output.replace('.xml', '.log') - psf['debug'] = self.set_debug - if self.set_log: - psf.logFileOpen() - if not self.__on_ram: - psf.execute() - else: - psf.run() - return - - # ctedispcube wrapper ---! - def run_edispcube(self, cube, ebins=10, nbins=None, wbin=1.0, migramax=2.0, migrabins=100, ebin_alg='LOG', ebinfile=None, ebingamma=None, addbounds=False): - '''Wrapper of ctedispcube.''' - if nbins == None: - nbins = int(self.roi * 2 / wbin) - edisp = ctools.ctedispcube() - edisp['inobs'] = self.input - edisp['incube'] = cube - edisp['caldb'] = self.caldb - edisp['irf'] = self.irf - edisp['outcube'] = self.output - edisp['ebinalg'] = ebin_alg - edisp['emin'] = self.e[0] - edisp['emax'] = self.e[1] - edisp['enumbins'] = ebins - if ebinfile != None: - edisp['ebinfile'] = ebinfile - if ebingamma != None: - edisp['ebingamma'] = ebingamma - edisp['addbounds'] = addbounds - edisp['usepnt'] = self.usepnt - edisp['nxpix'] = nbins - edisp['nypix'] = nbins - edisp['binsz'] = wbin - edisp['coordsys'] = self.coord_sys - edisp['proj'] = self.proj - if not self.usepnt: - edisp['xref'] = self.target[0] - edisp['yref'] = self.target[1] - edisp['migramax'] = migramax - edisp['migrabins'] = migrabins - if '.fits' in self.output: - edisp['logfile'] = self.output.replace('.fits', '.log') - elif '.xml' in self.output: - edisp['logfile'] = self.output.replace('.xml', '.log') - edisp['debug'] = self.set_debug - if self.set_log: - edisp.logFileOpen() - if not self.__on_ram: - edisp.execute() - else: - edisp.run() - return - - # ctbkgcube wrapper ---! - def run_bkgcube(self, cube, model): - '''Wrapper of ctbkgcube.''' - bkg = ctools.ctbkgcube() - bkg['inobs'] = self.input - bkg['inmodel'] = self.model - bkg['incube'] = cube - bkg['caldb'] = self.caldb - bkg['irf'] = self.irf - bkg['outcube'] = self.output - bkg['outmodel'] = model - if '.fits' in self.output: - bkg['logfile'] = self.output.replace('.fits', '.log') - elif '.xml' in self.output: - bkg['logfile'] = self.output.replace('.xml', '.log') - bkg['debug'] = self.set_debug - if self.set_log: - bkg.logFileOpen() - if not self.__on_ram: - bkg.execute() - else: - bkg.run() - return - - # ctlike wrapper ---! - def run_maxlikelihood(self, binned=False, exp=None, bkg=None, psf=None, edisp=False, edispcube=None, fix_spat_for_ts=True): - '''Wrapper of ctlike.''' - if self.edisp: - edisp = True - like = ctools.ctlike() - like['inobs'] = self.input - like['inmodel'] = self.model - if binned: - like['expcube'] = exp - like['psfcube'] = psf - like['bkgcube'] = bkg - if edisp: - like['edispcube'] = edispcube - if edisp: - like['edisp'] = edisp - like['outmodel'] = self.output - like['caldb'] = self.caldb - like['irf'] = self.irf - like['refit'] = self.refit - like['max_iter'] = self.max_iter - like['like_accuracy'] = self.accuracy - like['fix_spat_for_ts'] = fix_spat_for_ts - like['statistic'] = self.stats - like["nthreads"] = self.nthreads - like['logfile'] = self.output.replace('.xml', '.log') - like['debug'] = self.set_debug - if self.set_log: - like.logFileOpen() - if not self.__on_ram: - like.execute() - else: - like.run() - return - - # cterror wrapper ---! - def run_asymerrors(self, asym_errors): - '''Wrapper of cterror.''' - self.confidence_level=[0.6827, 0.9545, 0.9973] - self.output = [] - for i in range(len(self.confidence_level)): - self.output.append(asym_errors.replace('_errors', '_%2derr' % (self.confidence_level[i] * 100))) - if not os.path.isfile(self.output[i]): - err = ctools.cterror() - err['inobs'] = self.input - err['inmodel'] = self.model - err['srcname'] = self.src_name - err['outmodel'] = self.output[i] - err['caldb'] = self.caldb - err['irf'] = self.irf - err['confidence'] = self.confidence_level[i] - err["nthreads"] = self.nthreads - err['logfile'] = self.output[i].replace('.xml', '.log') - err['debug'] = self.set_debug - if self.set_log: - err.logFileOpen() - if not self.__on_ram: - err.execute() - else: - err.run() - return self.output - - # ctulimit wrapper ---! - def run_uplim(self): - '''Wrapper of ctulimit.''' - uplim = ctools.ctulimit() - uplim['inobs'] = self.input - uplim['inmodel'] = self.model - uplim['srcname'] = self.src_name - uplim['caldb'] = self.caldb - uplim['irf'] = self.irf - uplim['confidence'] = self.confidence - uplim['sigma_min'] = self.sgmrange[0] - uplim['sigma_max'] = self.sgmrange[1] - uplim['eref'] = self.eref # default reference energy for differential limit (in TeV) - uplim['emin'] = self.e[0] # default minimum energy for integral flux limit (in TeV) - uplim['emax'] = self.e[1] # default maximum energy for integral flux limit (in TeV) - uplim["nthreads"] = self.nthreads - uplim['logfile'] = self.model.replace('results.xml', 'flux.log') - uplim['debug'] = self.set_debug - if self.set_log: - uplim.logFileOpen() - if not self.__on_ram: - uplim.execute() - else: - uplim.run() - return - - # ctulimit wrapper ---! - def run_lightcurve(self, nbins=20, bin_type='LIN'): - '''Wrapper of cslightcrv.''' - lc = cscripts.cslightcrv() - lc['inobs'] = self.input - lc['inmodel'] = self.model - lc['srcname'] = self.src_name - lc['caldb'] = self.caldb - lc['irf'] = self.irf - lc['outfile'] = self.output - lc['tbinalg'] = bin_type - lc['tmin'] = self.t[0] - lc['tmax'] = self.t[1] # default reference energy for differential limit (in TeV) - lc['tbins'] = nbins - lc['method'] = '3D' - lc['emin'] = self.e[0] # default minimum energy for integral flux limit (in TeV) - lc['emax'] = self.e[1] # default maximum energy for integral flux limit (in TeV) - lc['enumbins'] = 0 - lc['coordsys'] = self.coord_sys - lc['proj'] = 'CAR' - lc['xref'] = self.pointing[0] - lc['yref'] = self.pointing[1] - lc["nthreads"] = self.nthreads - lc['logfile'] = self.output.replace('.xml', '.log') - lc['debug'] = self.set_debug - if self.set_log: - lc.logFileOpen() - if not self.__on_ram: - lc.execute() - else: - lc.run() - return - - # cssens wrapper ---! - def run_sensitivity(self, bins=20, wbin=0.05, enumbins=0): - '''Wrapper of cssens.''' - sens = cscripts.cssens() - nbin = int(self.roi / wbin) - sens['inobs'] = self.input - sens['inmodel'] = self.model - sens['srcname'] = self.src_name - sens['caldb'] = self.caldb - sens['irf'] = self.irf - sens['outfile'] = self.output - sens['duration'] = self.t[1] - self.t[0] - sens['rad'] = self.roi - sens['emin'] = self.e[0] - sens['emax'] = self.e[1] - sens['bins'] = bins - if enumbins != 0: - sens['enumbins'] = enumbins - sens['npix'] = nbin - sens['binsz'] = wbin - sens['sigma'] = self.sigma - sens['type'] = self.sens_type.capitalize() - sens["nthreads"] = self.nthreads - sens['logfile'] = self.output.replace('.csv', '.log') - sens['debug'] = self.set_debug - if self.set_log: - sens.logFileOpen() - if not self.__on_ram: - sens.execute() - else: - sens.run() - return - diff --git a/rtasci/rtasci/lib/RTACtoolsBase.py b/rtasci/rtasci/lib/RTACtoolsBase.py deleted file mode 100644 index 8540f9eac1aa6c453fee0440566dbacdba2d8606..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTACtoolsBase.py +++ /dev/null @@ -1,27 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Leonardo Baroncelli -# ******************************************************************************* - -from rtasci.cfg.Config import Config - -class RTACtoolsBase: - - def __init__(self): - self.caldb = 'prod2' # production name in calibration database ---! - self.irf = 'South_0.5h' # irf ID name ---! - # data ---! - self.e = [0.03, 150.0] # energy range (TeV) ---! - self.roi = 5 # region of indeterest (deg) ---! - - def configure(self, cfg: Config): - '''Sets common parameters for analysis and simulation: caldb, irf, energy range, field of view.''' - self.caldb = cfg.get('caldb') - self.irf = cfg.get('irf') - self.e = [cfg.get('emin'), cfg.get('emax')] - self.roi = cfg.get('roi') \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTACtoolsSimulation.py b/rtasci/rtasci/lib/RTACtoolsSimulation.py deleted file mode 100644 index bf30779b4d2fadf9b57be3cf74a586e77d27c764..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTACtoolsSimulation.py +++ /dev/null @@ -1,575 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import gammalib -import ctools -import os.path -import csv -import re -import numpy as np -import pandas as pd -from astropy.io import fits -from astropy.table import Table, vstack -from scipy.interpolate import interp1d - -# create observation list with gammalib ---! -def make_obslist(obslist, items, names, instruments='CTA'): - '''Generates an observation list XML file using gammalib.''' - if type(items) != type(list()): - items = [items] - if type(names) != type(list()): - names = [names for i in range(len(items))] - if type(instruments) != type(list()): - instruments = [instruments for i in range(len(items))] - xml = gammalib.GXml() - obslib = xml.append('observation_list title="observation library"') - for i, item in enumerate(items): - obs = obslib.append(f'observation name="{names[i]}" id="{i+1:02d}" instrument="{instruments[i]}"') - obs.append(f'parameter name="EventList" file="{item}"') - xml.save(obslist) - del xml - return - -class RTACtoolsSimulation(): - ''' - This class allows to: 1) compute the EBL absorption from a csv data table and add it to the template; 2) extract spectra, lightcuves and time slices from the template (the flux values can also be normalised by a factor); 3) merge bins of the template simulation in a single photon list; 4) perform simulations using ctoobssim from ctools software package. - ''' - def __init__(self): - # files fields ---! - self.model, self.template, self.table = (str() for i in range(3)) - self.output, self.input = (str() for i in range(2)) - self.caldb = 'prod2' # caldb (str) ---! - self.irf = 'South_0.5h' # irf (Str) ---! - # condition control ---! - self.set_ebl = True # set/unset EBL absorption feature ---! - self.extract_spectrum = False # set/unset spectra extraction feature ---! - self.plot = False # option for retrieving plotting values ---! - self.zfetch = False # set/unset automatic fetching of redshift ---! - self.set_debug = False # set/unset debug mode for ctools ---! - self.set_log = True # set/unset logfiles for ctools ---! - # data ---! - self.e = [0.03, 150.0] # energy range (TeV) ---! - self.fov = 5 # region of interest (deg) ---! - self.tmax = 1800 # maximum exposure time needed (s) ---! - self.t = [0, 1800] # time range (s/MJD) ---! - self.pointing = [83.63, 22.01] # RA/DEC or GLON/GLAT (deg) ---! - # ctools miscellaneous ---! - self.edisp = False # set/unset edisp - self.seed = 1 # MC seed ---! - self.nthreads = 1 # run in parallel - # ebl specifics ---! - self.z = 0.1 # redshift value ---! - self.z_ind = 1 # redshift value index ---! - # fits extension array ---! - self.__time, self.__energy, self.__spectra, self.__ebl = (float() for i in range(4)) - - # open and close the FITS files ---! - def __openFITS(self): - '''Opens FITS file.''' - hdul = fits.open(self.template) - return hdul - def __closeFITS(self, hdul): - '''Closes FITS file.''' - hdul.close() - return - - # retrive FITS data ---! - def __getFitsData(self): - '''Loads time, energy, spectra and (if present) absorbed spectra from a FITS file.''' - hdul = self.__openFITS() - self.__energy = np.array(hdul[1].data) - self.__time = np.array(hdul[2].data) - self.__Nt = len(self.__time) - self.__Ne = len(self.__energy) - self.__spectra = np.array(hdul[3].data) - if self.set_ebl: - try: - self.__ebl = np.array(hdul[4].data) - except: - raise IndexError('Template extensions out of range. Unable to load EBL absorbed spectra.') - self.__closeFITS(hdul) - return - - # check if EBL extention already in template ---! - def checkEBLinFITS(self, ext_name='EBL-ABS. SPECTRA'): - '''Checks if specified extension is present in FITS file.''' - hdul = self.__openFITS() - try: - ext = hdul[ext_name] - return True - except KeyError: - return False - - # load csv table in pandas DataFrame and drop NaN values---! - def __openCSV(self): - '''Opens a CSV data file.''' - df = pd.read_csv(self.table) - df.dropna() - return df - - # retrive csv data ---! - def __getEBLfromCSV(self): - '''Gets optical depth values from a CSV table.''' - df = self.__openCSV() - cols = list(df.columns) - tau_table = np.array(df[cols[self.z_ind]]) - E = np.array(df[cols[0]]) / 1e3 # MeV --> GeV ---! - return tau_table, E - - # retrive csv temporal bin grid of the template in use and return the necessary slice ---! - def getTimeSlices(self, GTI, return_bins=False): - '''Gets the time slices from a GRB afterglow template, within a given interval.''' - self.__getFitsData() - df = self.__openCSV() - cols = list(df.columns) - self.__time = np.append(0, np.array(df[cols[1]])) - #self.__time = np.array(df[cols[1]]) - bin_start = 0 - bin_stop = 1 - for i in range(len(self.__time)): - if self.__time[i] < GTI[0]: - bin_start += 1 - continue - elif self.__time[i] >= GTI[1]: - self.__time[i] = GTI[1] - bin_stop += i - break - if bin_stop <= self.__Nt: - time_slice = slice(bin_start, bin_stop + 1) - else: - time_slice = slice(bin_start, bin_stop) - if not time_slice: - raise ValueError('Invalid GTI: cannot extract time slices') - tgrid = self.__time[time_slice] - tgrid[0] = GTI[0] - if not return_bins: - return tgrid - else: - return tgrid, bin_start, bin_stop - - # compute the EBL absorption ---! - def __addEBL(self, unit='MeV'): - '''Computes the EBL absorption.''' - self.__getFitsData() - tau_table, E = self.__getEBLfromCSV() - if unit == 'GeV': - E *= 1e3 - elif unit == 'TeV': - E *= 1e6 - # interpolate linearly handling NaNs/inf/zeroes ---! - with np.errstate(invalid='raise'): - interp = interp1d(E, tau_table, bounds_error=False) - tau = np.array(interp(self.__energy)) - self.__ebl = np.empty_like(self.__spectra) - # compute absorption ---! - for i in range(self.__Nt): - for j in range(self.__Ne): - self.__ebl[i][j] = self.__spectra[i][j] * np.exp(-tau[j]) - # if required return values to plot ---! - if self.plot: - return E, tau_table, self.__energy, tau - else: - return - - # retrive redshift, find nearest column then access its index ---! - def __zfetch(self): - '''Retrives the optical depth values from a table, according to redshift.''' - hdul = self.__openFITS() - # fetch z from the template and chose the table column with min distance from it - z = hdul[0].header['REDSHIFT'] - with open(self.table, 'r') as f: - reader = csv.reader(f) - hdr = next(reader) - zlist = [] - # load only the redshift columns - for el in hdr: - zlist.append(re.sub('[^0-9,.]', '', el)) - zlist.remove('') - zlist = [float(i) for i in zlist] - # find nearest ---! - self.z = min(zlist, key=lambda x:abs(x-z)) - self.z_ind = zlist.index(self.z) +1 - return - - # add EBL extension to a FITS template ---! - def addEBLtoFITS(self, template_ebl, ext_name='EBL ABS. SPECTRA', unit='MeV'): - '''Adds the EBL absorbed spectra to the tempalte.''' - hdul = self.__openFITS() - if self.zfetch: - self.__zfetch() - # if required retrive values to plot ---! - if self.plot: - x, y, x2, y2 = self.__addEBL(unit=unit) - else: - self.__addEBL(unit=unit) - # update fits ---! - hdu = fits.BinTableHDU(name=ext_name, data=self.__ebl) - header = hdu.header - header.set('UNITS', 'ph/cm2/s/GeV', ' ') - hdu = fits.BinTableHDU(name=ext_name, data=self.__ebl, header=header) - hdul.append(hdu) - # save to new ---! - if os.path.isfile(template_ebl): - os.remove(template_ebl) - hdul.writeto(template_ebl, overwrite=True) - self.__closeFITS(hdul) - # if required return values to plot ---! - if self.plot: - return x, y, x2, y2 - else: - return - - # extract template spectra, create xml model files and time slices csv file ---! - def __extractSpectrumAndModelXML(self, source_name, time_slice_name='time_slices.csv', data_path=None, scalefluxfactor=1): - '''Generates spectra, lightcurves and time slices of a template.''' - # time slices table ---! - if data_path is None: - raise ValueError('please specify a valid path') - table = os.path.join(data_path, time_slice_name) - if os.path.isfile(table): - os.remove(table) - with open(table, 'w+') as tab: - tab.write('#bin,tmax_bin') - # spectra and models ---! - for i in range(self.__Nt): - filename = os.path.join(data_path, f'spec_tbin{i:02d}.out') - if os.path.isfile(filename): - os.remove(filename) - # time slices table ---! - with open(table, 'a') as tab: - tab.write('\n' + str(i) + ', ' + str(self.__time[i][0])) - # spectra ---! - with open(filename, 'a+') as f: - for j in range(self.__Ne): - # write spectral data in E [MeV] and I [ph/cm2/s/MeV] ---! - if self.set_ebl: - f.write(str(self.__energy[j][0] * 1000.0) + ' ' + str(self.__ebl[i][j] / 1000.0 / scalefluxfactor) + "\n") - else: - f.write(str(self.__energy[j][0] * 1000.0) + ' ' + str(self.__spectra[i][j] / 1000.0 / scalefluxfactor) + "\n") - # xml models ---! - os.system('cp ' + str(self.model) + ' ' + str(os.path.join(data_path, f'{source_name}_tbin{i:02d}.xml'))) - s = open(os.path.join(data_path, f'{source_name}_tbin{i:02d}.xml')).read() - s = s.replace('data/spec', f'spec_tbin{i:02d}') - with open(os.path.join(data_path, f'{source_name}_tbin{i:02d}.xml'), 'w') as f: - f.write(s) - return - - # read template and return tbin_stop containing necessary exposure time coverage ---! - def loadTemplate(self, source_name, return_bin=False, data_path=None, scalefluxfactor=1): - '''Loads template data (spectra, lightcurves and time slices).''' - self.__getFitsData() - # time grid ---! - t = [0.0 for x in range(self.__Nt + 1)] - for i in range(self.__Nt - 1): - t[i + 1] = self.__time[i][0] + (self.__time[i + 1][0] - self.__time[i][0]) / 2 - # tmax in last bin ---! - t[self.__Nt] = self.__time[self.__Nt - 1][0] + (self.__time[self.__Nt - 1][0] - t[self.__Nt - 1]) - # stop the second after higher tmax ---! - if self.tmax != None: - tbin_stop = 1 - for bin in range(len(t)): - if t[bin] <= self.tmax: - tbin_stop += 1 - else: - continue - else: - raise ValueError('Total exposure time longer than template temporal evolution.') - # energy grid ---! - en = [1.0 for x in range(self.__Ne + 1)] - for i in range(self.__Ne - 1): - en[i + 1] = self.__energy[i][0] + (self.__energy[i + 1][0] - self.__energy[i][0]) / 2 - # Emax in last bin ---! - en[self.__Ne] = self.__energy[self.__Ne - 1][0] + (self.__energy[self.__Ne - 1][0] - en[self.__Ne - 1]) - # extract spectrum if required ---! - if self.extract_spectrum: - self.__extractSpectrumAndModelXML(source_name=source_name, data_path=data_path, scalefluxfactor=scalefluxfactor) - if return_bin: - return tbin_stop - else: - return - - # get tbin_stop without extracting template data ---! - def getTimeBinStop(self): - '''Gets the last time bin of the template if the observation lasts less than the entire afterglow.''' - self.__getFitsData() - # time grid ---! - t = [0.0 for x in range(self.__Nt + 1)] - for i in range(self.__Nt - 1): - t[i + 1] = self.__time[i][0] + (self.__time[i + 1][0] - self.__time[i][0]) / 2 - # tmax in last bin ---! - t[self.__Nt] = self.__time[self.__Nt - 1][0] + (self.__time[self.__Nt - 1][0] - t[self.__Nt - 1]) - # stop the second after higher tmax ---! - if self.tmax != None: - tbin_stop = 1 - for bin in range(len(t)): - if t[bin] <= self.tmax: - tbin_stop += 1 - else: - continue - else: - raise ValueError('Maximum exposure time (tmax) is larger than the template temporal evolution.') - return tbin_stop - - # get template bins within GTI ---! - def getTimeBins(self, GTI, tgrid): - '''Gets which time bins of the template fall within a give time interval.''' - tbins = [] - for i in range(len(tgrid)): - # if tgrid[i] <= GTI[0]+10 and tgrid[i+1] >= GTI[0]-10: - if tgrid[i] <= GTI[0] and tgrid[i+1] >= GTI[0]: - tbins.append(i) - continue - # if tgrid[i] >= GTI[0]-10 and tgrid[i+1] <= GTI[1]+10: - elif tgrid[i] >= GTI[0] and tgrid[i+1] <= GTI[1]: - tbins.append(i) - continue - # if tgrid[i] >= GTI[1]-10: - elif tgrid[i] >= GTI[1]: - tbins.append(i) - break - tbins = sorted(tbins) - tbins = self.__dropListDuplicates(tbins) - return tbins - - # ctobssim wrapper ---! - def run_simulation(self, inobs=None, prefix=None, startindex=None): - '''Wrapper for ctobssim simulation.''' - self.input = inobs - sim = ctools.ctobssim() - if self.input != None: - sim["inobs"] = self.input - sim["inmodel"] = self.model - sim["outevents"] = self.output - sim["caldb"] = self.caldb - sim["irf"] = self.irf - if self.edisp: - sim["edisp"] = self.edisp - if prefix != None: - sim["prefix"] = prefix - if startindex != None: - sim["startindex"] = startindex - sim["ra"] = self.pointing[0] - sim["dec"] = self.pointing[1] - sim["rad"] = self.fov - sim["tmin"] = self.t[0] - sim["tmax"] = self.t[1] - sim["emin"] = self.e[0] - sim["emax"] = self.e[1] - sim["seed"] = self.seed - sim["nthreads"] = self.nthreads - sim["logfile"] = self.output.replace('.fits', '.log') - sim["debug"] = self.set_debug - if self.set_log: - sim.logFileOpen() - sim.execute() - return - - # dopr duplicates in list ---! - def __dropListDuplicates(self, list): - '''Drops duplicate events in list.''' - new_list = [] - for l in list: - if l not in new_list: - new_list.append(l) - return new_list - - # keep only events within given GTI ---! - def __dropExceedingEvents(self, hdul, GTI): - '''Drops events exceeding GTI.''' - slice_list = [] - times = hdul[1].data.field('TIME') - for i, t in enumerate(times): - if t >= GTI[0] and t <= GTI[1]: - slice_list.append(i) - return slice_list - - # change from GTI of run to min and max of time events ---! - def __newGoodTimeIntervals(self, hdul, GTI): - '''Replaces GTI with min and max time of events.''' - GTI_new = [] - GTI_new.append(min(hdul[1].data.field('TIME'), key=lambda x: abs(x - GTI[0]))) - GTI_new.append(min(hdul[1].data.field('TIME'), key=lambda x: abs(x - GTI[1]))) - hdul[2].data[0][0] = GTI_new[0] - hdul[2].data[0][1] = GTI_new[1] - hdul.flush() - return - - # reindex rows after sorting ---! - def __reindexEvents(self, hdul): - '''Reindexes events.''' - indexes = hdul[1].data.field(0) - for i, ind in enumerate(indexes): - hdul[1].data.field(0)[i] = i + 1 - hdul.flush() - return - - # sort simulated events by time (TIME) instead of source (MC_ID) ---! - def __sortEventsByTime(self, hdul, hdr): - '''Sorts events by time.''' - data = Table(hdul[1].data) - data.sort('TIME') - hdul[1] = fits.BinTableHDU(name='EVENTS', data=data, header=hdr) - hdul.flush() - return - - # check GTI and raise error if bad values are passed ---! - def __checkGTI(self, hdul): - '''Checks that all events fall within the GTI.''' - GTI = hdul[2].data[0] - trange = hdul[1].data.field('TIME') - if GTI[0] > trange.min() or GTI[1] < trange.max(): - raise ValueError ('Bad GTI values passed to photon list append.') - return - - # create single photon list from obs list ---! - def __singlePhotonList(self, sample, filename, GTI, new_GTI=True): - '''Merge segmented simulations into a single photon list, updating all required header keywords.''' - sample = sorted(sample) - n = 0 - for i, f in enumerate(sample): - with fits.open(f) as hdul: - if len(hdul[1].data) == 0: - continue - if n == 0: - # load header and table ---! - hdr1 = hdul[1].header - hdr2 = hdul[2].header - ext1 = Table(hdul[1].data) - ext2 = hdul[2].data - n += 1 - else: - # update header and append table ---! - hdr1['LIVETIME'] += hdul[1].header['LIVETIME'] - hdr1['ONTIME'] += hdul[1].header['ONTIME'] - hdr1['TELAPSE'] += hdul[1].header['TELAPSE'] - hdr1['TSTOP'] = hdul[1].header['TSTOP'] - hdr1['DATE-END'] = hdul[1].header['DATE-END'] - hdr1['TIME-END'] = hdul[1].header['TIME-END'] - ext1 = vstack([ext1, Table(hdul[1].data)]) - hdul.close() - # create output FITS file empty ---! - hdu = fits.PrimaryHDU() - hdul = fits.HDUList([hdu]) - hdul.writeto(filename, overwrite=True) if os.path.isfile(filename) else hdul.writeto(filename) - hdul.close() - # update FITS file ---! - with fits.open(filename, mode='update') as hdul: - hdu1 = fits.BinTableHDU(name='EVENTS', data=ext1, header=hdr1) - hdu2 = fits.BinTableHDU(name='GTI', data=ext2, header=hdr2) - hdul.append(hdu1) - hdul.append(hdu2) - hdul.flush() - # sort table by time ---! - self.__sortEventsByTime(hdul=hdul, hdr=hdr1) - # manipulate fits ---! - with fits.open(filename, mode='update') as hdul: - # drop events exceeding GTI ---! - slice = self.__dropExceedingEvents(hdul=hdul, GTI=GTI) - if len(slice) > 0: - hdul[1].data = hdul[1].data[slice] - hdul.flush() - # modify indexes ---! - self.__reindexEvents(hdul=hdul) - # modify GTI ---! - if new_GTI: - self.__newGoodTimeIntervals(hdul=hdul, GTI=GTI) - else: - hdul[2].data[0][0] = GTI[0] - hdul[2].data[0][1] = GTI[1] - hdul.flush() - return - - # created one FITS table containing all events and GTIs ---! - def appendEventsSinglePhList(self, GTI=None, new_GTI=False): - '''From a list of simulations generates a single photon list.''' - if GTI == None: - GTI = [] - with fits.open(self.input[0]) as hdul: - GTI.append(hdul[2].data[0][0]) - with fits.open(self.input[-1]) as hdul: - GTI.append(hdul[2].data[0][1]) - self.__singlePhotonList(sample=self.input, filename=self.output, GTI=GTI, new_GTI=new_GTI) - return - - # shift times in template simulation to append background before burst ---! - def shiftTemplateTime(self, phlist, time_shift): - '''Shifts events in time.''' - raise Warning('This method is being fixed.') - if phlist is str(): - phlist = list(phlist) - for f in phlist: - print(f) - with fits.open(f, mode='update') as hdul: - # update header ---! - hdul[1].header['TSTART'] += time_shift - hdul[1].header['TSTOP'] += time_shift - # handle date format to add seconds ---! - #hdul[1].header['DATE-OBS'] += - #hdul[1].header['TIME-OBS'] += - #hdul[1].header['DATE-END'] += - #hdul[1].header['TIME-END'] += - # update GTI ---! - hdul[2].data[0][0] += time_shift - hdul[2].data[0][1] += time_shift - # shift events ---! - if len(hdul[1].data) > 0: - times = hdul[1].data.field('TIME') - for i, t, in enumerate(times): - hdul[1].data.field('TIME')[i] = t + time_shift - hdul.flush() - return - - # created a number of observation runs containing all events and GTIs ---! - def appendEventsMultiPhList(self, max_length=None, last=None, r=True, new_GTI=False): - '''This method will be deprecated.''' - exit('This method is outdated') - n = 1 - sample = [] - singlefile = str(self.output) - for j in range(int(last / max_length) + 1): - for i, f in enumerate(self.input): - with fits.open(f) as hdul: - tfirst = hdul[2].data[0][0] - tlast = hdul[2].data[0][1] - if (tfirst >= max_length * (j) and tlast <= max_length * (j + 1)) or \ - (tfirst <= max_length * (j) and tlast > max_length * (j)): - sample.append(f) - elif tlast >= max_length * (j + 1): # or last == max_length * (j + 1): - sample.append(f) - if n == 1: - filename = singlefile.replace('.fits', '_n%03d.fits' % n) - else: - filename = filename.replace('_n%03d.fits' % (n - 1), '_n%03d.fits' % n) - sample = self.__dropListDuplicates(sample) - self.__singlePhotonList(sample=sample, filename=filename, GTI=[max_length * (j), max_length * (j + 1)], new_GTI=new_GTI) - n += 1 - drop = len(sample) - 1 - if drop > 2: - self.input = self.input[drop - 1:] - sample = [f] - break - if r: - return n, singlefile - else: - return - - def sortObsEvents(self, key='TIME'): - '''Sorts simulated events by keyword.''' - with fits.open(self.input, mode='update') as hdul: - data = Table(hdul[1].data) - data.sort(key) - hdr = hdul[1].header - hdul[1] = fits.BinTableHDU(name='EVENTS', data=data, header=hdr) - hdul.flush() - hdul.close() - with fits.open(self.input, mode='update') as hdul: - self.__reindexEvents(hdul=hdul) - hdul.flush() - hdul.close() - return \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAGammapyAnalysis.py b/rtasci/rtasci/lib/RTAGammapyAnalysis.py deleted file mode 100644 index 796ec6887faecbc39c71382e3b55ba7c66f6c0b0..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAGammapyAnalysis.py +++ /dev/null @@ -1,77 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -from astropy.coordinates import SkyCoord -from gammapy.analysis import AnalysisConfig -from gammapy.modeling.models import PowerLawSpectralModel, SkyModel, PointSpatialModel - - -def gammapy_config(cfg, obs, target=None, pointing=None, radius=0.2, rbins=20, etrue=[0.02, 200], tbins=30, maxoffset=2.5, fitflux=False, fbins=30, source='GRB', level='info', stack=False, exclusion=None, safe_mask=['aeff-default', 'offset-max'], save=False, blind=False): - """Wrapper for gammapy configuration class.""" - if target is None and pointing is None: - raise ValueError("Either target or pointing must be not null.") - config = AnalysisConfig() - config.general.log = {'level': level} - config.datasets.type = cfg.get('type') - config.datasets.stack = stack - config.observations.datastore = '' - config.observations.obs_file = obs - # 3d analysis or blind - if cfg.get('type').lower() == "3d" or cfg.get('blind') == True: - #print(f"BLIND {int(cfg.get('roi')*2*cfg.get('skyroifrac'))}") - # type - config.datasets.type = "3d" - # geometry of the map for 3d - config.datasets.geom.wcs.skydir = {'lon': pointing.ra, 'lat': pointing.dec, 'frame': 'icrs'} - config.datasets.geom.wcs.fov = {'width': f"{int(cfg.get('roi')*2*cfg.get('skyroifrac'))} deg", 'height': f"{int(cfg.get('roi')*2*cfg.get('skyroifrac'))} deg"} - config.datasets.geom.wcs.binsize = f"{cfg.get('skypix')} deg" - if not cfg.get('blind'): - config.datasets.background=dict(method="fov_background", exclusion=exclusion) - # 1d analysis - elif cfg.get('type').lower() == "1d": - # type - config.datasets.type = "1d" - # ON region and make sure that PSF leakage is corrected - config.datasets.on_region = dict(frame="icrs", lon=f"{target[0]} deg", lat=f"{target[1]} deg", radius=f"{radius} deg") - # background - config.datasets.background=dict(method="reflected", exclusion=exclusion)#, parameters={'max_region_numberint': 23}) - # what maps to compute - config.datasets.map_selection = ['counts', 'exposure', 'background', 'psf', 'edisp'] - # safe mask and IRF - config.datasets.safe_mask.methods = safe_mask - config.datasets.containment_correction = True - # roi - config.datasets.geom.selection.offset_max = f"{maxoffset} deg" - # energy binning for the spectra - config.datasets.geom.axes.energy = dict(min=f"{cfg.get('emin')} TeV", max=f"{cfg.get('emax')} TeV", nbins=rbins) - config.datasets.geom.axes.energy_true = dict(min=f"{etrue[0]} TeV", max=f"{etrue[1]} TeV", nbins=tbins) - # fit and flux points (currently not used by the analysis) - if fitflux: - config.fit.fit_range = dict(min=f"{cfg.get('emin')} TeV", max=f"{cfg.get('emax')} TeV") - config.flux_points.energy = dict(min=f"{cfg.get('emin')} TeV", max=f"{cfg.get('emax')} TeV", nbins=fbins) - config.flux_points.source = source - # write - if save: - config.write("config1d.yaml", overwrite=True) - return config - -def set_model(target, source='Crab', freeze_spc=['index'], freeze_spt=['lon_0', 'lat_0'], default=True, index=2.4): - """Wrapper to create gammapy model.""" - target = SkyCoord(target[0], target[1], unit='deg', frame='icrs') - spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") - if default: - spectral_model = PowerLawSpectralModel(index=index, amplitude="5.7e-16 cm-2 s-1 MeV-1", reference="1e6 MeV") - else: - raise ValueError('Option default=False not implemented yet.') - for prm in freeze_spt: - spatial_model.parameters[prm].frozen = True - for prm in freeze_spc: - spectral_model.parameters[prm].frozen = True - sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name=source) - return sky_model, spectral_model, spatial_model \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAIrfs.py b/rtasci/rtasci/lib/RTAIrfs.py deleted file mode 100644 index 7532b492ce2d1e17a553af5e793f1b1876c0506c..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAIrfs.py +++ /dev/null @@ -1,215 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import subprocess -import numpy as np -from os.path import join -from astropy.io import fits -from scipy.interpolate import interp1d - -class RTAIrfs: - ''' - This class allows to degrade the CTA instrument response functions. - ''' - def __init__(self): - # location of ctools ---! - self.__CALDB = os.environ.get('CTOOLS') - # files fields ---! - self.caldb = 'prod2' # production name in calibration database ---! - self.irf = 'South_0.5h' # irf ID name ---! - # irf degradation & flux reduction ---! - self.factor = 2 - - - # set CALDB var location ---! - def setCALDB(self, path): - '''Set path to CALDB.''' - self.__CALDB = path - return - - # get CALDB var value ---! - def getCALDB(self): - '''Get path to CALDB.''' - return self.__CALDB - - # initialize paths for caldb degradation: directories and files ---! - def __initCaldbIrf(self): - '''Initialise paths and folders of nominal CALDB and a copy to degrade.''' - nominal_irf = f'{self.__CALDB}/share/caldb/data/cta/{self.caldb}/bcf/{self.irf}/irf_file.fits' - degraded_irf = nominal_irf.replace('prod', 'degr') - caldb_degr = self.caldb.replace('prod', 'degr') - folder = f'{self.__CALDB}/share/caldb/data/cta/' - nominal_cal = join(folder, self.caldb) - degraded_cal = join(folder, caldb_degr) - return folder, nominal_cal, nominal_irf, degraded_cal, degraded_irf - - # updates the degraded caldb index by replacing all "prod" references with "degr" ---! - def __updateCaldbIndex(self, index): - '''Updates the CALDB index.''' - # read content ---! - with open(index, 'r', encoding="ISO-8859-1") as f: - filedata = f.read() - # Replace the target keyword ---! - filedata = filedata.replace('prod', 'degr').replace('PROD', 'DEGR') - # Write the file out again ---! - with open(index, 'w', encoding="ISO-8859-1") as f: - f.write(filedata) - return - - # create copy of caldb and corresponding caldb.inx file ---! - def __mockNominalCaldb(self, nominal_cal, nominal_irf, degraded_cal, degraded_irf): - '''Generates a copy of the nominal CALDB.''' - if not os.path.isdir(degraded_cal): - os.mkdir(degraded_cal) - if not os.path.isfile(join(degraded_cal,'caldb.indx')): - os.system(f"cp {join(nominal_cal, 'caldb.indx')} {join(degraded_cal, 'caldb.indx')}") - # update caldb.indx file ---! - self.__updateCaldbIndex(join(degraded_cal, 'caldb.indx')) - if not os.path.isdir(join(degraded_cal, 'bcf')): - os.mkdir(join(degraded_cal, 'bcf')) - if not os.path.isdir(join(degraded_cal, 'bcf', self.irf)): - os.mkdir(join(degraded_cal, 'bcf', self.irf)) - if os.path.isfile(degraded_irf): - os.system(f'rm {degraded_irf}') - if not os.path.isfile(degraded_irf): - os.system(f'cp {nominal_irf} {degraded_irf}') - return - - # change permission to 777 and ask for password if user id not in idlist param ---! - def __openPermission(self, path, idlist=(0,1126,1001)): - '''Grants writing permission to the CALDB folder.''' - if os.geteuid() in idlist: - subprocess.run(['chmod', '-R', '777', path], check=True) - else: - subprocess.run(['sudo', 'chmod', '-R', '777', path], check=True) - return - - # change permission to 755 and ask for password if user id not in idlist param ---! - def __closePermission(self, path, idlist=(0,1126)): - '''Removes writing permission to the CALDB folder''' - if os.geteuid() in idlist: - subprocess.run(['chmod', '-R', '755', path], check=True) - else: - subprocess.run(['sudo', 'chmod', '-R', '755', path], check=True) - return - - # degrade Aff by self.factor (for now only scalar is implemented) ---! - def __degradeAeff(self, nominal_irf, degraded_irf, r=False): - '''Modifies the AEFF matrix by a factor (scalar).''' - # initialise ---! - inv = 1 / self.factor - extension = 'EFFECTIVE AREA' - field = 4 - with fits.open(nominal_irf) as hdul: - elo = np.array(hdul[extension].data.field(0)[:].astype(float)[0]) - ehi = np.array(hdul[extension].data.field(1)[:].astype(float)[0]) - e = elo + 0.5*(ehi - elo) - tlo = np.array(hdul[extension].data.field(2)[:].astype(float)[0]) - thi = np.array(hdul[extension].data.field(3)[:].astype(float)[0]) - theta = tlo + 0.5*(thi - tlo) - aeff = np.array(hdul[extension].data.field(field)[:].astype(float)[0]) - # effective area multiplied by inv of factor ---! - a = np.where(np.array([i * inv for i in aeff]) is np.nan, 0., np.array([i * inv for i in aeff])) - # degrade and save new ---! - with fits.open(degraded_irf, mode='update') as hdul: - hdul[extension].data.field(field)[:] = a - # save changes ---! - hdul.flush() - # return only if bkg counts must be degraded ---! - if not r: - return - else: - return aeff, a, theta, e - - # degrade bkg counts by normalise for aeff nominal and multiply times aeff degraded ---! - def __degradeBkg(self, nominal_irf, degraded_irf, aeff=True): - '''Modifies the BKG matrix by a factor (scalar).''' - # degrade Aeff (only if True) and get its returns ---! - if not aeff: - tmp = self.factor - self.factor = 1 - aeff_nom, aeff_deg, theta, e_aeff = self.__degradeAeff(nominal_irf=nominal_irf, degraded_irf=degraded_irf, r=True) - if not aeff: - self.factor = tmp - # initialise ---! - extension = 'BACKGROUND' - field = 6 - with fits.open(nominal_irf) as hdul: - xlo = np.array(hdul[extension].data.field(0)[:].astype(float)[0]) - xhi = np.array(hdul[extension].data.field(1)[:].astype(float)[0]) - x = xlo + 0.5*(xhi - xlo) - ylo = np.array(hdul[extension].data.field(2)[:].astype(float)[0]) - yhi = np.array(hdul[extension].data.field(3)[:].astype(float)[0]) - y = ylo + 0.5*(yhi - ylo) - elo = np.array(hdul[extension].data.field(4)[:].astype(float)[0]) - ehi = np.array(hdul[extension].data.field(5)[:].astype(float)[0]) - e_bkg = elo + 0.5*(ehi - elo) - bkg = np.array(hdul[extension].data.field(field)[:].astype(float)[0]) - # spatial pixel/deg conversion factor ---! - conv_factor = (xhi.max() - xlo.min()) / theta.max() - # interpolated Aeff via energy grid ---! - nominal_interp, degraded_interp = ([[]*i for i in range(len(theta))] for i in range(2)) - for i in range(len(theta)): - fnom = interp1d(e_aeff[:], aeff_nom[i,:]) - nominal_interp[i].append(fnom(e_bkg[:])) - fdeg = interp1d(e_aeff[:], aeff_deg[i,:]) - degraded_interp[i].append(fdeg(e_bkg[:])) - # flatten list of theta interpolations (theta array of energy frames) ---! - nominal_interp = np.array([item for sublist in nominal_interp for item in sublist]) - degraded_interp = np.array([item for sublist in degraded_interp for item in sublist]) - # empty copy of bkg tensor ---! - b = np.empty_like(bkg) - for idf, frame in enumerate(bkg[:,0,0]): - for idx, xpix in enumerate(bkg[idf,:,0]): - for idy, ypix in enumerate(bkg[idf,idx,:]): - # find radius in degrees ---! - r = np.sqrt((0 - xpix)**2 + (0 - ypix)**2) - rdegree = r * conv_factor - # find corresponding theta index ---! - angle = min(theta, key=lambda x:abs(x-rdegree)) - idtheta = np.where(np.isin(theta[:], angle)) - # degrade the background count for frame/x/y point ---! - if nominal_interp[idtheta,idf] == 0.: - b[idf, idx, idy] = 0. - else: - b[idf,idx,idy] = bkg[idf,idx,idy] / nominal_interp[idtheta,idf] * degraded_interp[idtheta,idf] - # save to new ---! - with fits.open(degraded_irf, mode='update') as hdul: - hdul[extension].data.field(field)[:] = b - # save changes ---! - hdul.flush() - return - - # degrade IRFs via Effective Area and/or Background ---! - def degradeIrf(self, bkg=True, aeff=True, mod_permission=False): - '''From a nominal CALDB generates a degraded copy.''' - # initialize ---! - folder, nominal_cal, nominal_irf, degraded_cal, degraded_irf = self.__initCaldbIrf() - # open all folder permission ---! - if mod_permission: - self.__openPermission(path=folder) - # create degr caldb path if not existing ---! - self.__mockNominalCaldb(nominal_cal=nominal_cal, nominal_irf=nominal_irf, degraded_cal=degraded_cal, degraded_irf=degraded_irf) - # close all folder permission and open only degraded caldb permission ---! - if mod_permission: - self.__closePermission(path=folder) - self.__openPermission(path=degraded_cal) - # degradation aeff ---! - if not bkg: - self.__degradeAeff(nominal_irf=nominal_irf, degraded_irf=degraded_irf) - # degradation bkg counts ---! - else: - self.__degradeBkg(nominal_irf=nominal_irf, degraded_irf=degraded_irf, aeff=aeff) - # close degraded caldb permission ---! - if mod_permission: - self.__closePermission(degraded_cal) - # update caldb ---! - self.caldb = self.caldb.replace('prod', 'degr') - return \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAManageXml.py b/rtasci/rtasci/lib/RTAManageXml.py deleted file mode 100644 index 4dde844fe2d88dfbccadeb73bfb5c7c03ca0d507..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAManageXml.py +++ /dev/null @@ -1,353 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import xml.etree.ElementTree as ET - -class ManageXml(): - ''' - This class contains methods to handle XML templates. - ''' - - def __init__(self, xml): - self.__xml = xml - self.file = open(self.__xml) - self.src_lib = ET.parse(self.file) - self.root = self.src_lib.getroot() - self.tsv_list = [] - self.pos = [] - self.err = [] - self.spectral = [] - self.sigma = 5 - self.default_model = True - self.instr = 'CTA' - self.bkg_type = 'Irf' - self.src_att = [] - self.bkg_att = [] - self.tscalc = True - self.if_cut = False - - # get source element ---! - def __getSrcObj(self): - '''Returns all sources.''' - src = self.root.findall('source') - return src - - # skip node listed in skip element or filters ---! - def __skipNode(self, cfg): - '''Skip specified nodes.''' - src = self.__getSrcObj() - if src.attrib[cfg.get('idAttribute')] in cfg.get('skip'): - return True - for filter in cfg.get('filters'): - if src.attrib[filter.get('attribute')] == filter.get('value'): - return True - if len(cfg.get('selectors')) == 0: - return False - for select in cfg.get('selectors'): - if src.attrib[select.get('attribute')] == select.get('value'): - return False - return True - - # get TS values ---! - def getTs(self, highest=None): - '''Returns TS values of all sources in library.''' - for src in self.root.findall('source'): - if highest == None: - if 'Background' not in src.attrib['name']: - tsv = float(src.attrib['ts']) - self.tsv_list.append(tsv) - else: - if src.attrib['name'] == highest: - tsv = float(src.attrib['ts']) - self.tsv_list.append(tsv) - return self.tsv_list - - # get RA/DEC values ---! - def getRaDec(self, highest=None): - '''Returns RA and DEC values of all sources in library.''' - ra_list, dec_list = ([] for i in range(2)) - for src in self.root.findall('source'): - if highest == None: - if 'Background' not in src.attrib['name']: - ra = float(src.find('spatialModel/parameter[@name="RA"]').attrib['value']) - dec = float(src.find('spatialModel/parameter[@name="DEC"]').attrib['value']) - ra_list.append(ra) - dec_list.append(dec) - else: - if src.attrib['name'] == highest: - ra = float(src.find('spatialModel/parameter[@name="RA"]').attrib['value']) - dec = float(src.find('spatialModel/parameter[@name="DEC"]').attrib['value']) - ra_list.append(float(ra)) - dec_list.append(float(dec)) - self.pos = [ra_list, dec_list] - return self.pos - - # get Gaussian sigma values ---! - def getConfInt(self, highest=None): - '''Returns spatial errors for all sources in library.''' - ra_list, dec_list = ([] for i in range(2)) - for src in self.root.findall('source'): - if highest == None: - if 'Background' not in src.attrib['name']: - ra = float(src.find('spatialModel/parameter[@name="RA"]').attrib['value']) - dec = float(src.find('spatialModel/parameter[@name="DEC"]').attrib['value']) - ra_list.append(ra) - dec_list.append(dec) - else: - if src.attrib['name'] == highest: - ra = float(src.find('spatialModel/parameter[@name="RA"]').attrib['value']) - dec = float(src.find('spatialModel/parameter[@name="DEC"]').attrib['value']) - ra_list.append(ra) - dec_list.append(dec) - self.err = [ra_list, dec_list] - return self.err - - # get spectral parameter values ---1 - def getSpectral(self, highest=None): - '''Returns spectral parameter values for all sources in library.''' - index_list, pref_list, pivot_list = ([] for i in range(3)) - if self.if_cut is True : - cutoff_list = [] - for src in self.root.findall('source'): - if 'Background' not in src.attrib['name']: - if highest == None: - index = float(src.find('spectrum/parameter[@name="Index"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="Index"]').attrib['scale']) - pref = float(src.find('spectrum/parameter[@name="Prefactor"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="Prefactor"]').attrib['scale']) - pivot = float(src.find('spectrum/parameter[@name="PivotEnergy"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="PivotEnergy"]').attrib['scale']) - index_list.append(index) - pref_list.append(pref) - pivot_list.append(pivot) - if self.if_cut is True : - cutoff = float(src.find('spectrum/parameter[@name="CutoffEnergy"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="CutoffEnergy"]').attrib['scale']) - cutoff_list.append(cutoff) - else: - if src.attrib['name'] == highest: - index = float(src.find('spectrum/parameter[@name="Index"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="Index"]').attrib['scale']) - pref = float(src.find('spectrum/parameter[@name="Prefactor"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="Prefactor"]').attrib['scale']) - pivot = float(src.find('spectrum/parameter[@name="PivotEnergy"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="PivotEnergy"]').attrib['scale']) - index_list.append(index) - pref_list.append(pref) - pivot_list.append(pivot) - if self.if_cut is True: - cutoff = float(src.find('spectrum/parameter[@name="CutoffEnergy"]').attrib['value']) * float( - src.find('spectrum/parameter[@name="CutoffEnergy"]').attrib['scale']) - cutoff_list.append(cutoff) - if self.if_cut is False: - self.spectral = [index_list, pref_list, pivot_list] - else: - self.spectral = [index_list, pref_list, pivot_list, cutoff_list] - return self.spectral - - # get prefact error values ---! - def getPrefError(self, highest=None): - '''Returns Prefactor errors for all sources in library.''' - err_list = [] - for src in self.root.findall('source'): - if highest == None: - if 'Background' not in src.attrib['name']: - err = float(src.find('spectrum/parameter[@name="Prefactor"]').attrib['error']) * float( - src.find('spectrum/parameter[@name="Prefactor"]').attrib['scale']) - err_list.append(err) - else: - if src.attrib['name'] == highest: - err = float(src.find('spectrum/parameter[@name="Prefactor"]').attrib['error']) * float( - src.find('spectrum/parameter[@name="Prefactor"]').attrib['scale']) - err_list.append(err) - self.err = err_list - return self.err - - # save xml files ---! - def __saveXml(self): - '''Saves XML model.''' - self.src_lib.write(self.__xml, encoding="UTF-8", xml_declaration=True) - return - - # set a default spectral model and bkg ---! - def __setModel(self): - '''Sets default XML model for sources (PL) and background (IRF+PL).''' - if self.default_model is True: - att_prefactor = {'name': 'Prefactor', 'scale': '1e-16', 'value': '5.7', 'min': '1e-07', 'max': '1e7', 'free': '1'} - att_index = {'name': 'Index', 'scale': '-1', 'value': '2.48', 'min': '0', 'max': '5.0', 'free': '1'} - att_pivot = {'name': 'PivotEnergy', 'scale': '1e6', 'value': '1.0', 'min': '1e-07', 'max': '1000.0', 'free': '0'} - bkg_prefactor = {'name': 'Prefactor', 'scale': '1', 'value': '1.0', 'min': '1e-03', 'max': '1e+3.0', 'free': '1'} - bkg_index = {'name': 'Index', 'scale': '1.0', 'value': '0.0', 'min': '-5', 'max': '+5.0', 'free': '1'} - bkg_pivot = {'name': 'PivotEnergy', 'scale': '1e6', 'value': '1.0', 'min': '0.01', 'max': '1000.0', 'free': '0'} - - self.src_att = [att_prefactor, att_index, att_pivot] - self.bkg_att = [bkg_prefactor, bkg_index, bkg_pivot] - if self.if_cut is True: - att_cutoff = {'name': 'CutoffEnergy', 'scale': '1e6', 'value': '1.0', 'min': '0.01', 'max': '1000.0', 'free': '1'} - self.src_att.append(att_cutoff) - else: - pass - - # set tscalc to 1 ---! - def setTsTrue(self): - '''Sets tscalc true.''' - for src in self.root.findall('source'): - if 'Background' not in src.attrib['name']: - src.set('tscalc', '1') - self.__saveXml() - return - - def setInstrument(self, instrument='CTA'): - '''Sets Instrument in the XML model.''' - for src in self.root.findall('source'): - if 'Background' not in src.attrib['name']: - src.set('instrument', instrument) - self.__saveXml() - return - - # modify the spectral component of candidate list ---! - def modXml(self, overwrite=True): - '''Modifies the XML model by halving the Prefactor of the spectral model of each subsequent detected candidated.''' - self.__setModel() - # source ---! - i = 0 - for src in self.root.findall('source'): - i += 1 - if src.attrib['name'] not in ('Background', 'CTABackgroundModel'): - src.set('tscalc', '1') if self.tscalc is True else None - # remove spectral component ---! - rm = src.find('spectrum') - src.remove(rm) - # new spectrum ---! - if self.if_cut: - spc = ET.SubElement(src, 'spectrum', attrib={'type': 'ExponentialCutoffPowerLaw'}) - else: - spc = ET.SubElement(src, 'spectrum', attrib={'type': 'PowerLaw'}) - spc.text = '\n\t\t\t'.replace('\t', ' ' * 2) - spc.tail = '\n\t\t'.replace('\t', ' ' * 2) - # new spectral params ---! - for j in range(len(self.src_att)): - prm = ET.SubElement(spc, 'parameter', attrib=self.src_att[j]) - if prm.attrib['name'] == 'Prefactor' and i > 1: - prm.set('value', str(float(prm.attrib['value']) / 2 ** (i - 1))) - prm.tail = '\n\t\t\t'.replace('\t', ' ' * 2) if j < len(self.src_att) else '\n\t\t'.replace('\t', ' ' * 2) - # background ---! - else: - # set bkg attributes ---! - src.set('instrument', '%s' % self.instr.upper()) if self.instr.capitalize() != 'None' else None - if self.bkg_type.capitalize() == 'Aeff' or self.bkg_type.capitalize() == 'Irf': - src.set('type', 'CTA%sBackground' % self.bkg_type.capitalize()) - if self.bkg_type.capitalize() == 'Racc': - src.set('type', 'RadialAcceptance') - # remove spectral component ---! - rm = src.find('spectrum') - src.remove(rm) - # new bkg spectrum ---! - spc = ET.SubElement(src, 'spectrum', attrib={'type': 'PowerLaw'}) - spc.text = '\n\t\t\t'.replace('\t', ' ' * 2) - spc.tail = '\n\t'.replace('\t', ' ' * 2) - # new bkg params ---! - for j in range(len(self.bkg_att)): - prm = ET.SubElement(spc, 'parameter', attrib=self.bkg_att[j]) - prm.tail = '\n\t\t\t'.replace('\t', ' ' * 2) if j < len(self.bkg_att) else '\n\t\t'.replace('\t', ' ' * 2) - # instead of override original xml, save to a new one with suffix "_mod" ---! - if not overwrite: - self.__xml = self.__xml.replace('.xml', '_mod.xml') - self.__saveXml() - return - - # free and fix parameters for max like computation ---! - def parametersFreeFixed(self, src_free=['Prefactor'], bkg_free=['Prefactor', 'Index']): - '''Frees selected parameters and fixed all others, for all sources in library.''' - for src in self.root.findall('source'): - if 'Background' not in src.attrib['name']: - for prm in src.findall('*/parameter'): - if prm.attrib['name'] not in src_free: - prm.set('free', '0') - else: - prm.set('free', '1') - else: - for prm in src.findall('*/parameter'): - if prm.attrib['name'] not in bkg_free: - prm.set('free', '0') - else: - prm.set('free', '1') - self.setTsTrue() if self.tscalc is True else None - self.__saveXml() - return - - # sort candidates by their ts value ---! - def sortSrcTs(self): - '''Sorts the sources by TS in library.''' - src = self.root.findall("*[@ts]") - self.root[:-1] = sorted(src, key=lambda el: (el.tag, el.attrib['ts']), reverse=True) - from_highest = [] - for src in self.root.findall("*[@ts]"): - from_highest.append(src.attrib['name']) - self.__saveXml() - if len(from_highest) == 0: - from_highest = [None] - return from_highest - - # close xml ---! - def closeXml(self): - '''Closes XML.''' - self.file.close() - return - - # find observation filename ---! - def getRunList(self): - '''Gets observation file.''' - filenames = [] - for obs in self.root.findall('observation/parameter[@name="EventList"]'): - filenames.append(obs.attrib['file']) - return filenames - - def setModelParameters(self, parameters=(), values=(), source=None): - '''Sets values of selected model parameters.''' - parameters = tuple(parameters) - values = tuple(values) - for src in self.root.findall('source'): - if 'Background' not in src.attrib['name']: - if source != None: - src.set('name', source) - for i, prm in enumerate(parameters): - p = src.find('*/parameter[@name="%s"]' % prm) - p.set('value', str(values[i])) - self.__saveXml() - return - - # set TS values ---! - def setTsValue(self, value, source=None): - '''Set TS value for a given source (TS = sigma^2).''' - if source == None: - src = self.root.find(f'source') - else: - src = self.root.find(f'source[@name="{source}"]') - src.set('ts', str(value)) - self.__saveXml() - return self.tsv_list - - # add IntegratedFlux ---! - def setIntegratedFlux(self, value, scale=1e-12, source=None): - '''Sets Prefactor equal to -1 and adds IntegratedFlux instead.''' - if source == None: - prms = self.root.findall(f'source/spectrum/parameter') - else: - prms = self.root.findall(f'source[@name="{source}"]/spectrum/parameter') - for p in prms: - p.set('value', str(-1)) - if source == None: - spc = self.root.find(f'source/spectrum') - else: - spc = self.root.find(f'source[@name="{source}"]/spectrum') - prm = ET.SubElement(spc, 'parameter', attrib={'name': 'IntegratedFlux', 'value': str(value/scale), 'scale': str(scale)}) - prm.tail = '\n\t\t'.replace('\t', ' ' * 2) - self.__saveXml() - return \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAStats.py b/rtasci/rtasci/lib/RTAStats.py deleted file mode 100644 index f46bee13040b6846f14f71ede062ebe8532e0dd9..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAStats.py +++ /dev/null @@ -1,881 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import matplotlib.pyplot as plt -import seaborn as sns -import numpy as np -from matplotlib.patches import Rectangle -from scipy import stats -from scipy.stats import rayleigh, norm, chi2 -from matplotlib.lines import Line2D -from matplotlib.patches import Ellipse, Circle -from scipy.interpolate import interp1d - -extra = Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0) -extra2 = Line2D([0], [0], ls='-.', color='k', lw='1') - -def hist1d(x, mean, true=None, nbin=20, hist=True, fit=True, fontsize=20, color='b', xscale='linear', figsize=(15,12), rotation=0, alpha=0.5, lw=3, ls=('--', '-.', ':'), title='gaussian fit', yscale='linear', ax_thresh=None, xlabel='x', ylabel='y', leglabel='data', filename='hist1d_gauss.png', usetex=False, sns_style=False, show=True): - '''Generate multiple 1d histogram in a single plot. Optionally, only the histogram or the fit can be visualised.''' - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111, xscale=xscale, yscale=yscale) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - for index, el in enumerate(x): - if el[0] is list(): - el=el[0] - if fit: - sns.distplot(el, bins=nbin, kde=False, hist=hist, fit=norm, norm_hist=True, fit_kws={"color": color[index], "ls": ls[index]}, color=color[index], hist_kws={'alpha':alpha}) - if mean != None: - plt.axvline(mean[index], c=color[index], ls=ls[index], lw=lw, label=leglabel[index]) - else: - sns.distplot(el, bins=nbin, kde=False, hist=hist, fit=None, norm_hist=True, color=color[index], hist_kws={'alpha':alpha}, label=leglabel[index]) - if true != None and type(true) == list: - plt.axvline(true[index], c='k', ls=ls[index], lw=lw, label=f'{leglabel[index]} (expected)') - if true != None and type(true) == float: - plt.axvline(true, c='k', ls='-', lw=lw, label=f'expected') - plt.title(title, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.legend(fontsize=fontsize) - - plt.grid() if not sns_style else None - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# HIST 1D GAUSSIAN DISTRIBUTION -def hist1d_gauss(x, mean, true=None, loc=0, threshold=1, nbin=20, width=None, hist=True, fontsize=20, figsize=(15,12), color='b', alpha=0.5, lw=3, ls=('--', '-.', ':'), title='gaussian fit', ax_thresh=0.2, xlabel='x', ylabel='y', leglabel='data', rotation=0, filename='hist1d_gauss.png', usetex=False, sns_style=False, show=True): - '''Generate multiple 1d histogram in a single plot. Optionally, only the histogram or the fit can be visualised.''' - - if nbin == None: - if width == None: - raise ValueError('Either nbin or width must be not None') - nbin = int(threshold/width) - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - # plt.plot([],[], color='none', label='wbin=%.2fdeg' %width) - for index, el in enumerate(x): - if el[0] is list(): - el=el[0] - sns.distplot(el, bins=nbin, kde=False, hist=hist, fit=norm, norm_hist=True, fit_kws={"color": color[index], "ls": ls[index], "lw:": lw}, color=color[index], hist_kws={'alpha':alpha, 'range':[loc-threshold, loc+threshold]}, label=leglabel[index]) - plt.axvline(mean[index], c=color[index], ls=ls[index], lw=lw) if mean != None else None - if true != None: - plt.axvline(true, c='k', ls='-', lw=lw, label='true') - plt.axvline(loc, c='k', ls='-', lw=lw, label='true ~ %.3fdeg' %loc) if loc != None else None - plt.title(title, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.legend(fontsize=fontsize) - plt.xlim([loc-ax_thresh, loc+ax_thresh]) - - plt.grid() if not sns_style else None - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# HIST 1D RAYLEIGH DISTRIBUTION -def hist1d_rayleigh(x, mean, rayleigh_prms={'loc':0, 'scale':[1]}, threshold=1, nbin=None, width=None, hist=True, fontsize=15, figsize=(15,12), rotation=0, color='b', alpha=0.5, lw=3, ls=('-', '--', '-.', ':'),title='rayleigh fit', ax_thresh=0.2, xlabel='x', ylabel='y', leglabel='data', filename='hist1d_rayleigh.png', usetex=False, sns_style=False, show=True): - '''Generate multiple 1d histogram in a single plot. Optionally, only the histogram or the fit can be visualised.''' - - if width == None: - width = threshold/nbin - if nbin == None: - nbin = int(threshold/width) - if nbin == None and width == None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - # plt.plot([],[], color='none', label='wbin=%.2fdeg' %width) - for index, el in enumerate(x): - if el[0] is list(): - el=el[0] - sns.distplot(el, bins=nbin, kde=False, hist=hist, fit=rayleigh, norm_hist=True, fit_kws={"color": color[index]}, color=color[index], hist_kws={'alpha':alpha, 'range':[0.0, threshold]}, label=leglabel[index]) - plt.axvline(mean[index], c=color[index], ls=ls[index], lw=lw, label='mean ~ %.3fdeg' %mean[index]) if mean != None else None - if rayleigh_prms['scale'] != None: - plt.axvline(rayleigh_prms['scale'][index], c=color[index], ls='-', lw=lw, label='mode ~ %.3fdeg' %rayleigh_prms['scale'][index]) - plt.title(title, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.legend(fontsize=fontsize) - plt.xlim([rayleigh_prms['loc'], rayleigh_prms['loc']+ax_thresh]) if rayleigh_prms['loc'] != None else None - - plt.grid() if not sns_style else None - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# RAYLEIGH CDF WITH CONFIDENCE INTERVAL -def rayleigh_cdf(x, loc=0, scale=1, if_CI=True, probs=(0.6827, 0.9545, 0.9973, 0.99994), xlabel='x', title='x ~ RA(gamma) CDF', colors=('k', 'r', 'orange', 'm'), fontsize=15, figsize=(15,12), rotation=0, filename='theo_rayleigh_cdf.png', usetex=False, sns_style=False, show=False): - '''Plots the cumulative of Rayleigh distributed data, with given confidence interval.''' - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - ax.plot(np.sort(x), stats.rayleigh.cdf(np.sort(x), loc=loc, scale=scale), ls='-', label='cdf') - ax.axvline(scale, c='maroon', label='gamma') - ax.axvline(np.std(x), c='maroon', ls=':', label='1 std =%.2f' %(np.std(x))) - - if if_CI is True: - x_critical = [] - for i in range(len(probs)): - x_critical.append(stats.rayleigh.ppf(q=probs[i], loc=loc, scale=scale)) - ax.axvline(x_critical[i], c=colors[i], ls='-.', label='x=%.2f, %.2f' %(x_critical[i],probs[i]*100)+'%') - - plt.ylabel('1-alpha', rotation=90, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - ax.set_xlim(left=0) - ax.set_ylim(bottom=0) - plt.legend(loc=0) - - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# RAYLEIGH PDF WITH CONFIDENCE INTERVAL -def rayleigh_pdf(x, loc=0, scale=1, if_CI=True, probs=(0.6827, 0.9545, 0.9973, 0.99994), xlabel='x', title='x ~ RA(gamma) CDF', colors=('k', 'r', 'orange', 'm'), fontsize=15, figsize=(15,12), rotation=0, filename='theo_rayleigh_cdf.png', usetex=False, sns_style=False, show=False): - '''Plots the probability distribution of Rayleigh distributed data, with given confidence interval.''' - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - ax.plot(np.sort(x), stats.rayleigh.pdf(np.sort(x), loc=loc, scale=scale), ls='-', label='cdf') - ax.axvline(scale, c='maroon', label='gamma') - ax.axvline(np.std(x), c='maroon', ls=':', label='1 std =%.2f' %(np.std(x))) - - if if_CI is True: - x_critical = [] - for i in range(len(probs)): - x_critical.append(stats.rayleigh.ppf(q=probs[i], loc=loc, scale=scale)) - ax.axvline(x_critical[i], c=colors[i], ls='-.', label='x=%.2f, %.2f' %(x_critical[i],probs[i]*100)+'%') - - plt.ylabel('counts density', rotation=90, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - ax.set_xlim(left=0) - ax.set_ylim(bottom=0) - plt.legend(loc=0) - - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# 2D HISTOGRAM WITH RAYLEIGH CONFIDENCE INTERVAL -def hist2d_rayleigh_CI(x, y, nbin=None, width=None, rayleigh_prms={'loc':0, 'scale':1}, xcentre=0, ycentre=0, interp=None, threshold=1, probs=(0.6827, 0.9545, 0.9973, 0.99994), colors=('k', 'r', 'orange', 'm'), ls=('-','--','-.',':'), cmap='gist_heat', lw=4, ms=2e2, ax_thresh=0.2, xlabel='x', ylabel='y', title='confidence intervals from theoretical distribution', fontsize=20 , figsize=(12,10), rotation=0, filename='hist2d_CIrayleigh.png', usetex=False, sns_style=False, show=False): - '''Plots a 2d histrogram with Rayleigh confidence regions.''' - - xmean = np.mean(x) - ymean = np.mean(y) - - if width is None: - width = threshold/nbin - if nbin is None: - nbin = int(threshold/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - if interp == None: - h = plt.hist2d(x, y, bins=nbin, cmap=cmap, range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - plt.colorbar(h[3], ax=ax).set_label('counts', fontsize=fontsize) - else: - h, edges = np.histogram2d(x, y, bins=nbin, range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - h = h.T - im = plt.imshow(h, interpolation=interp, cmap=cmap, extent=[xcentre - threshold, xcentre + threshold, ycentre - threshold, ycentre + threshold]) - plt.colorbar(im, ax=ax).set_label('counts', fontsize=fontsize) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - plt.scatter(xcentre, ycentre, c='w', marker='*', s=ms) - plt.plot([], [], c='none', label='Rayleigh') - for i in range(len(probs)): - plt.plot([], [], c=colors[i], ls=ls[i], label='%.2f' % (probs[i] * 100) + '%') - r = stats.rayleigh.ppf(q=probs[i], loc=rayleigh_prms['loc'], scale=rayleigh_prms['scale']) - # q = rayleigh['scale'] * np.sqrt(-2 * np.log(probs[i])) - # r = stats.rayleigh.ppf(q=q, loc=rayleigh['loc'], scale=rayleigh['scale']) - cir = Circle(xy=(float(xmean), float(ymean)), radius=r, color=colors[i], lw=lw, ls=ls[i]) - cir.set_facecolor('none') - ax.add_artist(cir) - - plt.axis([xcentre - ax_thresh, xcentre + ax_thresh, ycentre - ax_thresh, ycentre + ax_thresh], 'equal') if ax_thresh != None else None - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(ncol=3, fontsize=fontsize) - - plt.grid() - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# COVARIANCE EIGENVALUES -def eigsorted(cov): - '''Returns covariance eigenvalues.''' - vals, vecs = np.linalg.eigh(cov) - order = vals.argsort()[::-1] - return vals[order], vecs[:, order] - - -# 2D HISTOGRAM WITH GAUSSIAN COVARIANCE CONFIDENCE INTERVAL -def hist2d_gauss_CI(x, y, nbin=None, width=None, xcentre=0, ycentre=0, threshold=1, nstd=(1, 2, 3, 5), lw=4, ls=('-','--','-.',':'), cmap='gist_heat', colors=('k', 'r', 'orange', 'm'), ax_thresh=0.2, xlabel='x', ylabel='y', interp=None, ms=2e2, title='confidence intervals from theoretical distribution', fontsize=20, figsize=(12,10), rotation=0, filename='hist2d_CIgauss.png', usetex=False, sns_style=False, show=False): - '''Plots a 2d histrogram with Gaussian confidence regions.''' - - xmean = np.mean(x) - ymean = np.mean(y) - - if width is None: - width = threshold/nbin - if nbin is None: - nbin = int(threshold/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - if interp == None: - h = plt.hist2d(x, y, bins=nbin, cmap=cmap, range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - cbar = plt.colorbar(h[3], ax=ax).set_label('counts', fontsize=fontsize) - else: - h, xedges, yedges = np.histogram2d(x, y, bins=nbin, range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - h = h.T - img = plt.imshow(h, interpolation=interp, cmap=cmap, extent=[xcentre - threshold, xcentre + threshold, ycentre - threshold, ycentre + threshold]) - cbar = plt.colorbar(img, ax=ax).set_label('counts', fontsize=fontsize) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - plt.scatter(xcentre, ycentre, c='w', marker='*', s=ms) - plt.plot([], [], c='none', label='gauss') - for i in range(len(nstd)): - plt.plot([], [], c=colors[i], ls=ls[i], label='%d sgm' % (nstd[i])) - cov = np.cov(x, y) - vals, vecs = eigsorted(cov) - theta = np.degrees(np.arctan2(*vecs[:, 0][::-1])) - w, v = 2 * nstd[i] * np.sqrt(vals) - ell = Ellipse(xy=(float(xmean), float(ymean)), width=w, height=v, angle=float(theta), color=colors[i], lw=lw, ls=ls[i]) - ell.set_facecolor('none') - ax.add_artist(ell) - - plt.axis([xcentre - ax_thresh, xcentre + ax_thresh, ycentre - ax_thresh, ycentre + ax_thresh], 'equal') if ax_thresh != None else None - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(ncol=3, fontsize=fontsize) - - plt.grid() - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - -# 2D HISTOGRAM WITH GAUSSIAN COVARIANCE CONFIDENCE INTERVAL -def contour_gauss_CI(x, y, nbin=None, width=None, xcentre=0, ycentre=0, threshold=1, nstd=(1, 2, 3, 5), colors=('k', 'r', 'orange', 'm'), ax_thresh=0.2, xlabel='x', ylabel='y', interp=None, title='confidence intervals from theoretical distribution', fontsize=20, figsize=(10, 8), rotation=0, filename='hist2d_CIgauss.png', usetex=False, sns_style=False, show=False): - '''Plots Gaussian contour map.''' - - xmean = np.mean(x) - ymean = np.mean(y) - - if width is None: - width = threshold/nbin - if nbin is None: - nbin = int(threshold/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - h = plt.hist2d(x, y, bins=nbin, cmap='jet', range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - plt.scatter(xcentre, ycentre, c='w', marker='*', s=1e2) - plt.plot([], [], c='none', label='gauss') - for i in range(len(nstd)): - plt.plot([], [], c=colors[i], label='%d sigma' % (nstd[i])) - cov = np.cov(x, y) - vals, vecs = eigsorted(cov) - theta = np.degrees(np.arctan2(*vecs[:, 0][::-1])) - w, v = 2 * nstd[i] * np.sqrt(vals) - ell = Ellipse(xy=(xmean, ymean), width=w, height=v, angle=theta, color=colors[i], lw=2) - ell.set_facecolor('none') - ax.add_artist(ell) - - cbar = plt.colorbar(h[3], ax=ax).set_label('counts', fontsize=fontsize) - plt.axis([xcentre - ax_thresh, xcentre + ax_thresh, ycentre - ax_thresh, ycentre + ax_thresh], 'equal') if ax_thresh != None else None - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(ncol=3, fontsize=fontsize) - - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - -# 2D HISTOGRAM MAP -def hist2d_map(x, y, nbin=None, width=None, xcentre=0, ycentre=0, threshold=1, ax_thresh=0.2, xlabel='x', ylabel='y', title='probability map', fontsize=20, figsize=(12,10), rotation=0, filename='hist2d_map.png', if_CI=None, rayleigh={'loc':0, 'scale':1}, nstd=(1, 2, 3, 5), colors=('k', 'r', 'orange', 'm'), probs=(0.6827, 0.9545, 0.9973, 0.99994), smooth=True, usetex=False, sns_style=False, show=False): - '''Produces a smoothed count map.''' - - trials = len(x) - if width is None: - width = threshold/nbin - if nbin is None: - nbin = int(threshold/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - h = ax.hist2d(x, y, bins=nbin, cmap='jet', vmin=0.0, vmax=trials, - range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - if smooth: - plt.clf() - heatmap, xedges, yedges = np.histogram2d(x, y, bins=nbin, range=[[xcentre - threshold, xcentre + threshold], [ycentre - threshold, ycentre + threshold]]) - extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]] - ax.imshow(heatmap, extent=extent, cmap='jet', interpolation='gaussian', filterrad=1, filternorm=True, resample=False, origin='lower') - - plt.scatter(xcentre, ycentre, c='w', marker='*', s=1e2) - - if if_CI is None: - pass - - elif if_CI.lower() == 'rayleigh': - xmean = np.mean(x) - ymean = np.mean(y) - plt.plot([], [], c='none', label='Reyleigh') - for i in range(len(probs)): - plt.plot([], [], c=colors[i], label='%.2f' % (probs[i] * 100) + '%') - r = stats.rayleigh.ppf(q=probs[i], loc=rayleigh['loc'], scale=rayleigh['scale']) - cir = Circle(xy=(xmean, ymean), radius=r, color=colors[i], lw=2) - cir.set_facecolor('none') - ax.add_artist(cir) - - elif if_CI.lower() == 'gauss' or if_CI.lower == 'covariance' or if_CI.lower == 'cov': - xmean = np.mean(x) - ymean = np.mean(y) - plt.plot([], [], c='none', label='gauss') - for i in range(len(nstd)): - plt.plot([], [], c=colors[i], label='%.2f' % (nstd[i] * 100) + '%') - cov = np.cov(x, y) - vals, vecs = eigsorted(cov) - theta = np.degrees(np.arctan2(*vecs[:, 0][::-1])) - w, v = 2 * nstd[i] * np.sqrt(vals) - ell = Ellipse(xy=(xmean, ymean), width=w, height=v, angle=theta, color=colors[i], lw=2) - ell.set_facecolor('none') - ax.add_artist(ell) - else: - raise ValueError('Ivalid if_CI parameter value') - - m = plt.cm.ScalarMappable(cmap='jet') - m.set_clim(0., trials/100) - cbar = plt.colorbar(m, boundaries=np.linspace(0, 100, 11)).set_label('cts \%', fontsize=fontsize) - plt.axis([xcentre - ax_thresh, xcentre + ax_thresh, ycentre - ax_thresh, ycentre + ax_thresh], 'equal') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) if title!=None else None - plt.axis('equal') - - plt.tight_layout() - fig.savefig(filename) - # show fig - plt.show() if show == True else None - plt.close() - return fig, ax - - -# WILKS THEOREM DIST FOR EMPTY FIELDS -def ts_wilks(x, df=1, nbin=None, width=None, trials=None, xrange=None, ylim=None, xlim=None, show=False, fontsize=15, figsize=(15,12), rotation=0, xlabel='TS', ylabel='normalised counts', title='TS distribution (empty fields)', filename='wilks_preTrials.png', usetex=False, sns_style=False, overlay=['chi2'], write_data=False, dpi=400, fmt='+', ecolor='red', markersize=1, elinewidth=1, alpha=0.8): - '''Plots a TS distribution comparison with chi2 and chi2/2.''' - assert width == None or nbin == None, 'Define either nbin or width but bot both.' - - x = np.array(x) - filename = str(filename) - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111, yscale='log') - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - - for n, el in enumerate(x): - # checks - if xrange == None: - xrange = (min(el), max(el)) - el = el[(el >= xrange[0]) & (el <= xrange[1])] - if trials == None: - trials = len(el) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - if type(overlay) != list: - overlay = [overlay] - - # compute the histogram - h, edges = np.histogram(el, bins=int(nbin), density=False, range=(xrange[0], xrange[1])) - yerr = np.sqrt(h)/trials - h = h/trials - cbin = (edges[1:] + edges[:-1]) / 2 - xerr = (edges[:-1] - edges[1:]) / 2 - - # plot the histogram - plt.errorbar(cbin, h, yerr=yerr, xerr=xerr, fmt=fmt, ecolor=ecolor, markersize=markersize, elinewidth=elinewidth, alpha=alpha, label=f'hist ({n})') - if 'mplt' in overlay: - plt.hist(el, bins=nbin, density=False, histtype='step', align='mid', range=(xrange[0], xrange[1]), label=f'mplt_{n}') - - # save the istogram - if write_data: - save_hist_on_file(x=cbin, y=h, xerr=xerr, yerr=yerr, filename=filename.replace('.png', f'_{n}.txt')) - - # overlay theoretical dist - if 'chi2' in overlay: - x2 = np.linspace(xrange[0], xrange[1], nbin) - plt.plot(x2, chi2.pdf(x2, df=df), c='orange', lw=1, ls='-.', label=f'chi2(dof={df})') - plt.plot(x2, chi2.pdf(x2, df=df)/2, c='b', lw=1, ls='--', label=f'chi2/2(dof={df})') - - # decorations - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0, fontsize=fontsize) - plt.xlim(xlim) if xlim is not None else None - plt.ylim(ylim) if ylim is not None else None - - plt.grid() - plt.tight_layout() - fig.savefig(filename, dpi=dpi) - - # show fig - plt.show() if show == True else None - plt.close() - - return fig, ax - -# WILKS THEOREM P-VALUES FOR EMPTY FIELDS -def p_values(x, df=1, nbin=None, width=None, trials=None, xrange=None, ylim=None, xlim=None, show=False, fontsize=15, figsize=(15,12), rotation=0, xlabel='h', ylabel='p-values', slabel=None, title='p-value (empty fields)', filename='pvalue_preTrials.png', usetex=False, sns_style=False, overlay=['chi2'], sigma5=True, write_data=False, dpi=400, fmt='+', ecolor='red', markersize=1, elinewidth=1, alpha=0.8, legendprop={"size": 6}, legendloc=0): - '''Plots a p-values distribution comparison with chi2 and chi2/2.''' - assert width == None or nbin == None, 'Define either nbin or width but bot both.' - markers_colors = ['#2ca25f', '#8856a7', '#e34a33', '#4a0a41'] - error_lines_colors = ['#99d8c9', '#9ebcda', '#fdbb84', '#f571e3'] - if len(x) > 4: - print("Warning: colors are optimized for a maximum of three series.") - x = np.array(x) - filename = str(filename) - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111, yscale='log') - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - - if slabel is None: - slabel = list(range(len(x))) - - for n, el in enumerate(x): - # checks - if xrange == None: - xrange = (min(el), max(el)) - el = el[(el >= xrange[0]) & (el <= xrange[1])] - if trials == None: - trials = len(el) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - if type(overlay) != list: - overlay = [overlay] - - # compute pvalues - xerr = np.full((len(range(nbin))), width/2) - h, edges = np.histogram(el, bins=nbin, range=xrange) - cumul = np.cumsum(h[::-1])[::-1] - - p = cumul/trials - yerr = np.sqrt(cumul)/trials - cbin = (edges[1:] + edges[:-1]) / 2 - - # plot the pvalues - plt.errorbar(cbin, p, yerr=yerr, xerr=xerr, fmt=fmt, markerfacecolor=markers_colors[n], markeredgecolor=markers_colors[n], ecolor=error_lines_colors[n], markersize=markersize, elinewidth=elinewidth, alpha=alpha, label=f'p-values ({slabel[n]})') - - if 'mplt' in overlay: - plt.hist(el, bins=nbin, density=True, histtype='step', align='mid', range=(xrange[0], xrange[1]), cumulative=-1, label=f'mplt_{n}') - - # save the pvalues - if write_data: - save_hist_on_file(x=cbin, y=p, xerr=xerr, yerr=yerr, filename=filename.replace('.png', f'_{n}.numpy.txt')) - - # overlay theoretical dist - if 'chi2' in overlay: - x2 = np.linspace(xrange[0], xrange[1], nbin) - plt.plot(x2, (1 - chi2.cdf(x2, df=df)), lw=1, ls='--', c='green', label='chi2(dof=%d)' %df) - plt.plot(x2, (1 - chi2.cdf(x2, df=df))/2, lw=1, ls='-.', c='maroon', label='chi2/2(dof=%d)' %df) - plt.legend(('chi2/2(dof=%d)', 'chi2(dof=%d)', 'ts'), loc=0, fontsize=fontsize) - - # 5 sigma threshold - if sigma5: - plt.axhline(3e-7, c='gray', ls=':', alpha=1, lw=2) - plt.text(xrange[0]*1.2, 3e-7, '5sigma', fontsize=fontsize, alpha=1) - f = interp1d(cbin, p, fill_value="extrapolate", kind='linear') - ts = f(3e-7) - print(f'Significance (5sgm) == {ts}') - - # decorations - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=legendloc, fontsize=fontsize, prop=legendprop, markerscale=10) - plt.xlim(xlim) if xlim is not None else None - plt.ylim(ylim) if ylim is not None else None - - plt.grid() - plt.tight_layout() - fig.savefig(filename, dpi=dpi) - - # show fig - plt.show() if show == True else None - plt.close() - - return fig, ax - -# WILKS THEOREM P-VALUES FOR EMPTY FIELDS -def ts_wilks_cumulative(x, df=1, nbin=None, width=None, trials=None, xrange=None, ylim=None, xlim=None, show=False, fontsize=15, figsize=(15,12), rotation=0, xlabel='h', ylabel='cumulative probability', title='p-value (empty fields)', filename='cumulative_preTrials.png', usetex=False, sns_style=False, overlay=['chi2'], write_data=False, sigma5=True, dpi=400, fmt='+', ecolor='red', markersize=1, elinewidth=1, alpha=0.8): - '''Plots a TS cumulative distribution.''' - assert width == None or nbin == None, 'Define either nbin or width but bot both.' - - x = np.array(x) - filename = str(filename) - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - - ax = plt.subplot(111, yscale='log') - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - - for n, el in enumerate(x): - # checks - if xrange == None: - xrange = (min(el), max(el)) - el = el[(el >= xrange[0]) & (el <= xrange[1])] - if trials == None: - trials = len(el) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - if type(overlay) != list: - overlay = [overlay] - - # compute cumulative - xerr = np.full((len(range(nbin))), width/2) - h, edges = np.histogram(el, bins=nbin, range=xrange) - cumul = np.cumsum(h[::-1])[::-1] - - p = 1 - cumul/trials - yerr = np.sqrt(cumul)/trials - cbin = (edges[1:] + edges[:-1]) / 2 - - # plot the cumulative - plt.errorbar(cbin, p, yerr=yerr, xerr=xerr, fmt=fmt, markersize=markersize, ecolor=ecolor, elinewidth=elinewidth, alpha=alpha, label='cumulative') - if 'mplt' in overlay: - plt.hist(el, bins=nbin, density=True, histtype='step', align='mid', range=(xrange[0], xrange[1]), cumulative=-1, label=f'mplt_{n}') - - # save the pvalues - if write_data: - save_hist_on_file(x=cbin, y=p, xerr=xerr, yerr=yerr, filename=filename.replace('.png', f'_{n}.txt')) - - # overlay theoretical dist - if 'chi2' in overlay: - x2 = np.linspace(xrange[0], xrange[1], nbin) - plt.plot(x2, (1 - chi2.cdf(x2, df=df)), lw=1, ls='--', c='green', label='chi2(dof=%d)' %df) - plt.plot(x2, (1 - chi2.cdf(x2, df=df))/2, lw=1, ls='-.', c='maroon', label='chi2/2(dof=%d)' %df) - plt.legend(('chi2/2(dof=%d)', 'chi2(dof=%d)', 'ts'), loc=0, fontsize=fontsize) - - # 5 sigma threshold - if sigma5: - plt.axhline(1-3e-7, c='gray', ls=':', alpha=1, lw=2) - plt.text(xrange[0]*1.2, 1-3e-7, '5sigma', fontsize=fontsize, alpha=1) - f = interp1d(cbin, p, fill_value="extrapolate", kind='linear') - ts = f(1-3e-7) - print(f'Significance (5sgm) == {ts}') - - # decorations - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0, fontsize=fontsize) - plt.xlim(xlim) if xlim is not None else None - plt.ylim(ylim) if ylim is not None else None - - plt.grid() - plt.tight_layout() - fig.savefig(filename, dpi=dpi) - - # show fig - plt.show() if show == True else None - plt.close() - - return fig, ax - -def chi2_reduced(x, df=1, nbin=None, width=None, var=True, xrange=None): - '''Returns the chi2 and chi2 reduced.''' - trials = len(x) - np.seterr(divide='ignore', invalid='ignore') - - if xrange == None: - xrange = (min(x), max(x)) - if trials == None: - trials = len(x) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - h, edges = np.histogram(x, bins=int(nbin), density=False, range=(0., xrange[1])) - yerr = np.sqrt(h)/trials - h = h/trials - cbin = (edges[1:] + edges[:-1])/2 - p = (1 - stats.chi2.pdf(cbin, df=df))/2 - #p = chi2.pdf(cbin, df=df)/2 - #err = yerr/h - - #print('values', h, '\nerrors', yerr, '\nerror perc', err) - - with np.errstate(invalid='raise'): - if var: - chi2n = 2*np.sum((h[1:] - p[1:])**2/h[1:]) - #chi2n = np.sum((h[1:] - p[1:])**2/err[1:]) - else: - chi2n = 2*np.sum((h[1:] - p[1:])**2/h[1:]) - #chi2n = np.sum((h[1:] - p[1:])**2/err[1:]) - h[1:] = np.array(h[1:]) - N = np.count_nonzero(h[1:]) - chi2r = chi2n / (N - 1) - return chi2n, chi2r - - -# MANUAL NORMALISED HISTOGRAM -def make_hist(x, step=None, nbin=None, width=None, normed=True, write_data=False, xrange=None, trials=None, filename='histogram.txt'): - - x = np.sort(x) - if xrange == None: - xrange = (min(x), max(x)) - if trials == None: - trials = len(x) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - h = np.zeros_like(np.empty(len(np.arange(nbin)))) - cbin, xerr = [], [] - for i in range(nbin): - cbin.append(step*i + step/2) - xerr.append(step/2) - for idx, val in enumerate(x): - if val <= cbin[i]: - h[i] += 1 - x=x[(x>=cbin[i])] - - if normed: - yerr = np.sqrt(h) / trials - h = h/trials - else: - yerr = np.sqrt(h) - - if write_data: - save_hist_on_file(x=cbin, y=h, xerr=xerr, yerr=yerr, filename=filename.replace('.png', '.txt')) - return cbin, h, xerr, yerr - -def normed_hist_plot(x, step=None, nbin=None, width=None, ylim=None, xlim=None, trials=None, xrange=None, show=False, normed=True, xscale='linear', yscale='log', fontsize=15, figsize=(15,12), rotation=0, xlabel='x', ylabel='normalised counts', leglabel='legend', title='normed histogram', usetex=False, sns_style=False, usesns=False, filename='normed_histogram.png', write_data=False): - '''Generates a manually normalised histogram.''' - - x = np.sort(x) - if xrange == None: - xrange = (min(x), max(x)) - if trials == None: - trials = len(x) - if width is None: - width = (xrange[1]-xrange[0])/nbin - if nbin is None: - nbin = int((xrange[1]-xrange[0])/width) - if nbin is None and width is None: - raise ValueError('Either nbin or width must be not None') - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - if sns_style: - sns.set() - else: - plt.grid() - - ax = plt.subplot(111, yscale=yscale, xscale=xscale) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - - h = np.zeros_like(np.empty(len(np.arange(nbin)))) - cbin, xerr = [], [] - for i in range(nbin): - cbin.append(step*i + step/2) - xerr.append(step/2) - for idx, val in enumerate(x): - if val <= cbin[i]: - h[i] += 1 - x=x[(x>=cbin[i])] - h_norm = h/trials - - if normed: - yerr = np.sqrt(h) / trials - plt.errorbar(cbin, h_norm, yerr=yerr, xerr=xerr, fmt='k+', markersize=5, label=leglabel) - else: - yerr = h / trials - plt.errorbar(cbin, h, yerr=yerr, xerr=xerr, fmt='k+', markersize=5, label=leglabel) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0, fontsize=fontsize) - plt.xlim(xlim) if xlim is not None else None - plt.ylim(ylim) if ylim is not None else None - - plt.tight_layout() - fig.savefig(filename) - - if write_data: - save_hist_on_file(x=cbin, y=h, xerr=xerr, yerr=yerr, filename=filename.replace('.png', '.txt')) - - # show fig - plt.show() if show else None - plt.close() - - return fig, ax - - -def save_hist_on_file(x, y, xerr, yerr, filename='histogram.txt', hdr='x xerr y yerr'): - log = open(filename, 'w+') - if hdr != None: - if '\n' not in hdr: - hdr += '\n' - log.write(hdr) - for i in range(len(x)): - log.write(f'{x[i]} {xerr[i]} {y[i]} {yerr[i]}\n') - log.close() - return - -def save_data_on_file(x, filename='histogram.txt', hdr='x xerr y yerr'): - log = open(filename, 'w+') - if hdr != None: - if '\n' not in hdr: - hdr += '\n' - log.write(hdr) - for el in x: - log.write(f'{el}\n') - log.close() - return - -def get_sigma_from_pvalue(pval, decimals=3): - return np.abs(np.round(norm.ppf(pval), decimals)) - -def get_prob_from_sigma(sigma, decimals=8): - return np.round(1-(norm.sf(sigma)*2), decimals) - -def get_prob_from_pvalue(pval, decimals=8): - return np.round(1-pval*2, decimals) - -def get_pvalue_from_sigma(sigma, decimals=8): - p = get_prob_from_sigma(sigma, decimals=decimals) - return np.round((1-p)/2, decimals) \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAUtils.py b/rtasci/rtasci/lib/RTAUtils.py deleted file mode 100644 index 95b62d7521e99acfcc12fab5a4a6d73857932c5b..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAUtils.py +++ /dev/null @@ -1,254 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import numpy as np -from astropy.io import fits -from os.path import join -from scipy import stats -from scipy.interpolate import interp2d - -# center of fov from FITS ---! -def get_pointing(fits_file): - '''Given a template, returns the target coordinates.''' - with fits.open(fits_file) as hdul: - ra = abs(hdul[0].header['RA']) - dec = hdul[0].header['DEC'] - return (ra, dec) - -def increase_exposure(start, stop, function='double'): - '''Increse the exposure time with a given function: double, power2, times10.''' - if function.lower() == 'double': - y = [start] - i = 0 - while y[i] < stop: - y.append(y[i]*2) - i += 1 - elif function.lower() == 'power': - y = [start] - i = 0 - while y[i] < stop: - y.append(y[0]**(i+2)) - i += 1 - elif function.lower() == 'times10': - y = [start] - i = 0 - while y[i] < stop: - y.append(y[i]*10) - i += 1 - elif function.lower() == 'linear': - y = [start] - i = 0 - while y[i] < stop: - y.append(start*(i+2)) - i += 1 # check stop - if y[-1] > stop: - y[-1] = stop - elif y[-1] < stop: - y.append(stop) - return y - -# find base binning for lightcurve -def lightcurve_base_binning(start, stop, exposure): - '''Return lightcurve binning with minimum exposure.''' - y = [start] - i = 0 - while y[i] < stop: - y.append(y[i] + exposure) - i += 1 # check stop - if y[-1] > stop: - y[-1] = stop - elif y[-1] < stop: - y.append(stop) - return y - -# compute integral photon flux for PL model ---! -def phflux_powerlaw(gamma, k0, e0=1, erange=(0.03, 150.0), unit='TeV'): - '''Compute the integrated photon flux for a single power law spectral model.''' - if unit == 'eV': - conv = 1e-6 - elif unit == 'keV': - conv = 1e-3 - elif unit == 'MeV': - conv = 1 - elif unit == 'GeV': - conv = 1e3 - else: - conv = 1e6 - e1 = erange[0] * conv - e2 = erange[1] * conv - delta = gamma + 1 - factor = k0 / (e0**gamma * delta) - flux = factor * (e2**delta - e1**delta) - return flux - -# compute integral energy flux for PL model ---! -def enflux_powerlaw(gamma, k0, e0=1, erange=(0.03, 150.0), unit='TeV'): - '''Compute the integrated energy flux for a single power law spectral model.''' - if unit == 'eV': - conv = 1.60218e-12 - elif unit == 'keV': - conv = 1.60218e-9 - elif unit == 'MeV': - conv = 1.60218e-6 - elif unit == 'GeV': - conv = 1.60218e-3 - else: - conv = 1.60218 - e1 = erange[0] * conv - e2 = erange[1] * conv - k0 *= conv - e0 *= conv - delta = gamma+1 - factor = k0 / (e0**gamma * delta) - flux = factor * (e2**delta - e1**delta) - return flux - -# returns a random total delay time (slew time + gw latency) within given ranges ---! -def totalDelay(slew=(0,50), gw_latency=(0,36000)): - '''Returns random delay accounting for given delay and alert latency within ranges.''' - tslew = np.random.uniform(slew[0], slew[1], 1) - tgw = np.random.uniform(gw_latency[0], gw_latency[1]) - delay = tslew + tgw - return delay - -# get wobble pointing for given target -def wobble_pointing(target, nrun, clockwise=True, offset=0.5): - '''Returns wobble pointing coordinates with given offset (in DEC).''' - if clockwise: - wobble = [(0., offset), (-offset, 0.), (0., -offset), (offset, 0.)] - else: - wobble = [(0., offset), (offset, 0.), (0., -offset), (-offset, 0.)] - wobble_index = nrun % 4 - print(wobble[wobble_index]) - pointing = (target[0] + wobble[wobble_index][0], target[1] + wobble[wobble_index][1]) - return pointing - -# get mergermap file -def get_mergermap(run, path): - '''Gets merger map of runid.''' - runid = run.split('_') - merger = f'{runid[0]}_Merger{runid[1]}_skymap.fits' - if merger in os.listdir(path): - return os.path.join(path, merger) - elif merger+'.gz' in os.listdir(path): - os.system(f"gunzip {join(path, merger)}.gz") - return os.path.join(path, merger) - else: - print(f'Merger map {merger} for not found in path: {path}.') - return None - -def compute_prefactor(flux, erange, gamma=-2.4, e0=1e6, unit='MeV'): - '''Compute the prefactor k0 from photon flux for a simple power law spectral model.''' - if unit == 'eV': - conv = 1e-6 - elif unit == 'keV': - conv = 1e-3 - elif unit == 'MeV': - conv = 1 - elif unit == 'GeV': - conv = 1e3 - else: - conv = 1e6 - - e1 = erange[0] * conv - e2 = erange[1] * conv - delta = gamma + 1 - factor = flux / (e2**delta - e1**delta) - k0 = factor * (e0**gamma * delta) - return k0 - -def compute_phcount(texp, irf, k0, offset=1.638, eTeV=[0.03, 150.0], nbin=1000): - '''Compute the photon count from the prefactor, for given irf, off-axs angle, and energy range.''' - with fits.open(irf) as h: - aeff = h['EFFECTIVE AREA'].data - - elow = aeff['ENERG_LO'][0] # TeV - ehigh = aeff['ENERG_HI'][0] # TeV - thetalo = aeff['THETA_LO'][0] # deg - thetahi = aeff['THETA_HI'][0] # deg - area = aeff['EFFAREA'][0] # m2 - # energy in TeV - x = (elow+ehigh)/2 - y = (thetalo+thetahi)/2 - z = area - # interpolate - f = interp2d(x, y, z, kind='linear') - yref = offset - eref = np.logspace(np.log10(eTeV[0]), np.log10(eTeV[1]), nbin) - s = 0 - for i in range(len(eref)-1): - # aeff at energy bin edges; convert m2 -> cm2 - a1 = f(eref[i], yref) * 10**4 # cm2 - a2 = f(eref[i+1], yref) * 10**4 # cm2 - # delta ph = prefactor * exposure * mean aeff * delta energy (convert TeV -> MeV) - ph = k0 * texp * (a1+a2)/2 * (eref[i+1]-eref[i])*1e6 # dph = ph/cm2/s/MeV * s * * dMeV - # sum photons in bins - s += float(ph) # sum dph - return s - -def phm_options(erange, texp, time_int, target, pointing, irf_file, index=-2.4, bkg_method="reflection", radius=0.2, pixsize=0.05, verbose=0, save_off_reg='.'): - opts = {} - opts['verbose'] = verbose - opts['irf_file'] = irf_file - opts['source_ra'] = target[0] - opts['source_dec'] = target[1] - opts['pointing_ra'] = pointing[0] - opts['pointing_dec'] = pointing[1] - opts['region_radius'] = radius - opts['background_method'] = bkg_method - opts['save_off_regions'] = save_off_reg - opts['energy_min'] = erange[0] - opts['energy_max'] = erange[1] - opts['pixel_size'] = pixsize - opts['begin_time'] = time_int[0] - opts['end_time'] = time_int[1] - opts['step_time'] = texp - opts['power_law_index'] = -np.abs(index) - return opts - -# check energy thresholds agains irf -def check_energy_thresholds(erange, irf): - # minimum energy - if "z60" in irf and erange[0] < 0.11: - erange[0] = 0.011 - elif "z40" in irf and erange[0] < 0.04: - erange[0] = 0.04 - elif "z20" in irf and erange[0] < 0.03: - erange[0] = 0.03 - # maximum energy - if "North" in irf and erange[1] > 30: - erange[1] = 30 - elif "South" in irf and erange[1] > 150: - erange[1] = 150 - return erange - -def get_gamma_r_rayleigh(dist, prob=0.6827): - tmp = 0 - for d in dist: - tmp += d**2 - if len(dist) != 0.0 : - mode = np.sqrt(1/(2*len(dist)) * tmp) - MLE = 0.606/mode - else: - mode = np.nan - MLE = np.nan - gamma = mode - r = stats.rayleigh.ppf(q=prob, loc=0, scale=gamma) - return gamma, r - -def str2bool(v): - if isinstance(v, bool): - return v - if v.lower() in ('yes', 'true', 't', 'y', '1'): - return True - elif v.lower() in ('no', 'false', 'f', 'n', '0'): - return False - else: - raise TypeError('Boolean value expected.') diff --git a/rtasci/rtasci/lib/RTAUtilsGW.py b/rtasci/rtasci/lib/RTAUtilsGW.py deleted file mode 100644 index c1d0f87af6244eda1c15e674763abb1671b557d8..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAUtilsGW.py +++ /dev/null @@ -1,52 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import numpy as np -import healpy as hp -from rtasci.lib.RTAUtils import get_pointing - -# retrieve telescope pointing coordinates from alert probability map ---! -def get_alert_pointing_compressed(merger_map): - '''Given a compressed merger map, it returns the maximum sky localisation probability coordinates.''' - # load map ---! - merger_map = merger_map.replace('.gz','') - os.system(f'gunzip {merger_map}.gz') - map = hp.read_map(merger_map, dtype=None) - pixels = len(map) - axis = hp.npix2nside(pixels) - # search max prob coords ---! - pmax = np.argmax(map) - theta, phi = hp.pix2ang(axis, pmax) - pointing = (np.rad2deg(phi), np.rad2deg(0.5 * np.pi - theta)) - os.system(f'gzip {merger_map}') - return pointing - - -# retrieve telescope pointing coordinates from alert probability map ---! -def get_alert_pointing_gw(merger_map): - '''Given a merger map, it returns the maximum sky localisation probability coordinates.''' - if not os.path.isfile(merger_map): - raise ValueError(f'Merger map {merger_map} not found.') - # load map ---! - map = hp.read_map(merger_map, dtype=None) - pixels = len(map) - axis = hp.npix2nside(pixels) - # search max prob coords ---! - pmax = np.argmax(map) - theta, phi = hp.pix2ang(axis, pmax) - pointing = (np.rad2deg(phi), np.rad2deg(0.5 * np.pi - theta)) - return pointing - -# center of fov from FITS ---! -def get_offset(fits_file, merger_map): - '''Given a template and a merger map, returns the offset (in RA and DEC) between the maximum sky localisation probability and the target coordinates.''' - true = get_pointing(fits_file) - alert = get_alert_pointing_gw(merger_map) - return (true[0]-alert[0], true[1]-alert[1]) \ No newline at end of file diff --git a/rtasci/rtasci/lib/RTAVisualise.py b/rtasci/rtasci/lib/RTAVisualise.py deleted file mode 100644 index aa1630b38603acad0f510f1850925033161a2ac1..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/lib/RTAVisualise.py +++ /dev/null @@ -1,693 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import numpy as np -import seaborn as sns -import matplotlib.pyplot as plt -from os.path import join, isdir, expandvars -from astropy.io import fits -from matplotlib.colors import SymLogNorm -from matplotlib.patches import Rectangle -from astropy import units as u -from astropy.wcs import WCS -from astropy.io import fits -from scipy.interpolate import interp1d -from regions import Regions - - -# handle DS9 regiond (wip) ---! -def handleReg(reg, col='black'): - '''Returns regions from a DS9-like regione file.''' - r = Regions.read('my_regions.reg', format='ds9') - r[0].attr[1]['color'] = col - return r - -def checkPath(png): - if not isdir(png): - png = expandvars('$PWD') - return png - -# plot sky map ---! -def plotSkymap(file, reg='none', col='green', suffix='none', title='', xlabel='R.A. (deg)', ylabel='Dec (deg)', fontsize=25, figsize=(8, 6), rotation=0, show=False, sns_style=False, usetex=False, png=None): - '''Plots skymap with Gaussian smoothing.''' - with fits.open(file) as hdul: - wcs = WCS(hdul[0].header) - data = hdul[0].data - hdr = hdul[0].header - - fig = plt.figure(figsize=figsize) - if usetex: - plt.rc('text', usetex=usetex) - sns.set() if sns_style else None - # plot with projection ---! - ax = plt.subplot(projection=wcs) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - # load region ---! - if reg != 'none': - try: - r = Regions.read(reg).as_imagecoord(hdr) - for i in range(len(r)): - r[i].attr[1]['color'] = col - patch_list, text_list = r.get_mpl_patches_texts() - for p in patch_list: - ax.add_patch(p) - for t in text_list: - ax.add_artist(t) - except ValueError: - pass - plt.imshow(data, cmap='jet', norm=SymLogNorm(1), interpolation='gaussian', zorder=0) - ax.coords[0].set_format_unit(u.deg) - ax.coords[1].set_format_unit(u.deg) - plt.grid(color='white', ls='solid') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.tick_params(axis='both', labelsize=fontsize) - cbar = plt.colorbar().set_label('counts', fontsize=fontsize) - fig.set_tight_layout(True) - # save fig ---! - head, tail = os.path.split(file) - png = checkPath(png) - if suffix != 'none': - fig.savefig(join(png, tail.replace('.fits', '_%s.png' % suffix)), bbox_inches='tight') - else: - fig.savefig(join(png, tail.replace('.fits', '.png')), bbox_inches='tight') - # show fig ---! - plt.show() if show else None - plt.close() - return - -# plot residual count map ---! -def plotResmap(file, reg='none', col='black', suffix='none', title='map redisuals', xlabel='R.A. (deg)', ylabel='Dec (deg)', fontsize=12, show=True, tex=False, png='.'): - '''Plots residual map with Guassian smoothing.''' - - with fits.open(file) as hdul: - data = hdul[0].data - hdr = hdul[0].header - - plt.rc('text', usetex=False) if tex else None - ax = plt.subplot(111) - # load region ---! - if reg != 'none': - r = Regions.read(reg).as_imagecoord(hdr) - for i in range(len(r)): - r[i].attr[1]['color'] = col - patch_list, text_list = r.get_mpl_patches_texts() - for p in patch_list: - ax.add_patch(p) - for t in text_list: - ax.add_artist(t) - # plot ---! - plt.imshow(data, cmap='bwr', interpolation='gaussian', filterrad=0.04) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.colorbar().set_label('Significance $\sigma$') - # save fig ---! - png = checkPath(png) - if suffix != 'none': - plt.savefig(file.replace('.fits', '_%s.png' % suffix)) - else: - plt.savefig(file.replace('.fits', '.png')) - #show fig ---! - plt.show() if show else None - plt.close() - return - -# show spectral residuals ---! -def plotResiduals(file, yscale='log', title='spectral residuals', figsize=(10,8), xlabel='energy (TeV)', ylabel=('counts', 'residuals'), fontsize=12, show=True, tex=False): - '''Plots spectral residuals.''' - - with fits.open(file) as hdul: - data = hdul[1].data - # store data ---! - Emin = data.field(0) - Emax = data.field(1) - cts = data.field(2) - model = data.field(3) - res = data.field(4) - # binning ---! - en_bins = Emax - 0.5 * (Emax - Emin) - - plt.figure(figsize=figsize) - plt.rc('text', usetex=False) if tex else None - if yscale.lower() == 'lin': - ax1 = plt.subplot(211, xscale='log') - else: - ax1 = plt.subplot(211, yscale='log', xscale='log') - # plot ---! - plt.plot(en_bins, cts, 'ro') - plt.step(Emax, cts, 'r-', where='pre', label='cts') - plt.step(Emin, model, 'g-', where='post', label='model') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel[0], fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - # plot ---! - plt.subplot(212, sharex=ax1) - plt.plot(en_bins, res, 'b+', label='cts residuals') - plt.axhline(y=0, c='r', lw='1', ls='--') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel[1], fontsize=fontsize) - plt.grid(True) - # adjust ---! - plt.subplots_adjust(hspace=0.2) - # save fig ---! - plt.savefig(file.replace('.fits', '.png')) - # show fig ---! - plt.show() if show == True else None - plt.close() - return - -# flux butterfly diagram ----! -def plotButterfly(file, flux_pnts=0.0, fluxEn_pnts=0.0, suffix='none', title='flux', fontsize=12, xlabel='Energy (TeV)', ylabel='E $\cdot \\frac{dN}{dE}$ (erg/$cm^2$/s)', show=True, tex=False, png='.'): - '''Plots a butterfly.''' - - data = np.loadtxt(file, delimiter=' ') - energy = data[:, 0] - intensity = data[:, 1] - lerr = data[:, 2] - uerr = data[:, 3] - - # from intensity (ph/cm^2/s/MeV) to flux in (erg/cm^2/s) ---! - (flux, f_lerr, f_uerr) = ((intensity, lerr, uerr) * (energy ** 2)) / (6.4215 * 1e5) - if flux_pnts != 0.0 and fluxEn_pnts != 0.0: - f_pnts = (np.array(flux_pnts) * (np.array(fluxEn_pnts) ** 2)) / (6.4215 * 1e5) - else: - print('without flux points') - - plt.rc('text', usetex=False) if tex else None - ax = plt.subplot(111, yscale='log', xscale='log') - # plot ---! - plt.plot(energy / 1e6, flux, 'b-', alpha=1, label='best fit') - plt.fill_between(energy / 1e6, f_lerr, f_uerr, facecolor='blue', alpha=0.3, label='errors') - if flux_pnts != 0.0 and fluxEn_pnts != 0.0: - plt.scatter(np.array(fluxEn_pnts) / 1e6, f_pnts, marker='o', color='red', label='data flux pnts') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - # save fig ---! - png = checkPath(png) - if suffix != 'none': - plt.savefig(file.replace('.txt', '_%s.png' % suffix)) - else: - plt.savefig(file.replace('.txt', '.png')) - # show fig ---! - plt.show() if show == True else None - plt.close() - return - -# plot spectrum ---! -def plotSpectrum(file, figsize=(8,15), fontsize=12, title=('spectrum with errors', 'spectrum with errors', 'log spectrum'), xlabel='Energy (TeV)', ylabel='Flux (erg/$cm^2$/s)', show=True, tex=False): - '''Plots a spectrum.''' - - with fits.open(file) as hdul: - data = hdul[1].data - # store data ---! - en = data.field(0) - en_down = data.field(1) - en_up = data.field(2) - flux = data.field(3) - err_flux = data.field(4) - # adjusting errors ---! - en_err = (en_up - en_down) / 2 - - plt.rc('text', usetex=False) if tex else None - fig = plt.figure(figsize=figsize) - ax1 = plt.subplot(311, xscale='log') - # plot ---! - plt.errorbar(en, flux, yerr=err_flux, xerr=en_err, fmt='ro', label='data') - plt.step(en, flux, 'r-', where='mid') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title[0], fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - - ax2 = plt.subplot(312, sharex=ax1) - plt.plot(en, flux, 'ro', label='data') - plt.step(en, flux, 'r-', where='mid') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title[1], fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - - ax3 = plt.subplot(313, sharex=ax1, yscale='log') - plt.plot(en, flux, 'ro', label='data') - plt.step(en, flux, 'r-', where='mid') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title[2], fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - # adjust ---! - plt.subplots_adjust(hspace=0.5) - # save fig ---! - extent = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) - fig.savefig(file.replace('.fits', '_errors.png'), bbox_inches=extent.expanded(1.3, 1.3)) - extent = ax2.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) - fig.savefig(file.replace('.fits', '.png'), bbox_inches=extent.expanded(1.3, 1.3)) - extent = ax3.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) - fig.savefig(file.replace('.fits', '_log.png'), bbox_inches=extent.expanded(1.3, 1.3)) - # show fig -- - plt.show() if show else None - plt.close() - return - -# plot cslightcrv output ---! -def plotLightCurveFromFile(file, tsthresh=9, figsize=(15,15), axisLim ='auto', title='lightcurve', yscale=('lin','log'), xscale=('lin','log'), show = True, tex=False): - '''Plots a lightcurve with upperlimit if TS < tsthresh.''' - - fig = plt.figure(figsize=figsize) - plt.rc('text', usetex=False) if tex else None - ax1 = plt.subplot(211, yscale=yscale[0], xscale=xscale[0]) - ax2 = plt.subplot(212, yscale=yscale[1], xscale=xscale[1]) - - for i in range(len(file)): - with fits.open(file) as hdul: - data = hdul[1].data - t_mjd = data.field(0) # days - et_mjd = data.field(1) # days - prefact = data.field(6) # ph/cm^2/s/MeV - e_prefact = data.field(7) # ph/cm^2/s/MeV - index = data.field(8) - e_index = data.field(9) - TS = data.field(10) - diff_uplim = data.field(11) # ph/cm^2/s/MeV - flux_uplim = data.field(12) # ph/cm^2/s - Eflux_uplim = data.field(13) # erg/cm^2/s - - pnts = [] - e_pnts = [] - t_pnts = [] - et_pnts = [] - ul_pnts = [] - eul_pnts = [] - tul_pnts = [] - etul_pnts = [] - # list flux point or upper limit ---! - for el in range(len(data)): - if TS[el] > tsthresh and 2.0*e_prefact[el] < prefact[el]: - pnts.append(prefact[el]) - e_pnts.append(e_prefact[el]) - t_pnts.append(t_mjd[el]) - et_pnts.append(et_mjd[el]) - else: - ul_pnts.append(diff_uplim[el]) - eul_pnts.append(0.5*diff_uplim[el]) - tul_pnts.append(t_mjd[el]) - etul_pnts.append(et_mjd[el]) - - # linear ---! - ax1.errorbar(t_pnts, pnts, xerr=et_pnts, yerr=e_pnts, fmt='o', mec='k', label='data') - ax1.errorbar(tul_pnts, ul_pnts, xerr=[etul_pnts, etul_pnts], yerr=eul_pnts, uplims=True, fmt='bo', mec='k') - ax1.axis(axisLim) if axisLim != 'auto' else None - ax1.grid() - ax1.set_xlabel('t (MJD)') - ax1.set_ylabel('dN/dE (ph/$cm^2$/s/MeV)') - ax1.set_title('lightcurve') if title == 'none' else plt.title(title) - # log ---! - ax2.errorbar(t_pnts, pnts, xerr=et_pnts, yerr=e_pnts, fmt='o', mec='k', label='data') - ax2.errorbar(tul_pnts, ul_pnts, xerr=[etul_pnts, etul_pnts], yerr=eul_pnts, uplims=True, fmt='bo', mec='k') - ax2.axis(axisLim) if axisLim != 'auto' else None - ax2.grid() - ax2.set_xlabel('t (MJD)') - ax2.set_ylabel('dN/dE (ph/$cm^2$/s/MeV)') - ax2.set_title('lightcurve') if title == 'none' else plt.title(title) - ax1.legend() - ax2.legend() - # adjust ---! - plt.subplots_adjust(hspace=0.5) - # save fig ---! - extent = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) - fig.savefig(file.replace('.fits', '.png'), bbox_inches=extent.expanded(1.3, 1.3)) - extent = ax2.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) - fig.savefig(file.replace('.fits', '_log.png'), bbox_inches=extent.expanded(1.3, 1.3)) - # show fig ---! - plt.show() if show else None - plt.close() - return - -# show TS map ---! -def plotTSmap(file, reg='none', col='black', suffix='none', title='TS map', cbar='Significance TSV', xlabel='RA (deg)', ylabel='DEC (deg)', fontsize=12, show=True, tex=False): - '''Plots a TS map.''' - - with fits.open(file) as hdul: - data = hdul[0].data - hdr = hdul[0].header - - plt.rc('text', usetex=False) if tex else None - ax = plt.subplot(111) - # load region ---! - if reg != 'none': - r = Regions.read(reg).as_imagecoord(hdr) - for i in range(len(r)): - r[i].attr[1]['color'] = col - patch_list, text_list = r.get_mpl_patches_texts() - for p in patch_list: - ax.add_patch(p) - for t in text_list: - ax.add_artist(t) - # plot ---! - plt.imshow(data, cmap='bwr') - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title[0], fontsize=fontsize) - plt.colorbar().set_label(cbar, fontsize=fontsize) - # save fig ---! - if suffix != 'none': - plt.savefig(file.replace('.fits', '_%s.png' % suffix)) - else: - plt.savefig(file.replace('.fits', '.png')) - #show fig ---! - plt.show() if show == True else None - plt.close() - return - -# butterfly flux + spectrum ---! -def plotButterflySpectrum(file, spectrum, axisLim='auto', suffix='none', title='butterfly diagram', fontsize=12, xlabel='Energy (TeV)', ylabel='Flux (erg/$cm^2$/s)', show=True, tex=False): - '''Plots spectrum and butterfly diagram together.''' - - data = np.loadtxt(file, delimiter=' ') - energy = data[:, 0] - intensity = data[:, 1] - lerr = data[:, 2] - uerr = data[:, 3] - with fits.open(spectrum) as hdul: - spc = hdul[1].data - en = spc.field(0) - en_down = spc.field(1) - en_up = spc.field(2) - flux_pnt = spc.field(3) - err_flux = spc.field(4) - # adjusting errors ---! - en_err = (en_up - en_down) / 2 - - # from intensity (ph/cm^2/s/MeV) find flux in (erg/cm^2/s) ---! - (flux, f_lerr, f_uerr) = ((intensity, lerr, uerr) * (energy ** 2)) / (6.4215 * 1e5) - - plt.rc('text', usetex=False) if tex else None - ax = plt.subplot(111, yscale='log', xscale='log') - # plot ---! - plt.plot(energy / 1e6, flux, 'b-', alpha=1, label='best fit') - plt.fill_between(energy / 1e6, f_lerr, f_uerr, facecolor='blue', alpha=0.3) - plt.scatter(en, flux_pnt, marker='+', c='r', label='spectrum', alpha=1) - ax.axis(axisLim) if axisLim != 'auto' else None - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0) - plt.grid(True) - # save fig ---! - if suffix != 'none': - plt.savefig(file.replace('.txt', '_%s.png' % suffix)) - else: - plt.savefig(file.replace('.txt', '.png')) - # show fig ---! - plt.show() if show == True else None - plt.close() - return - -# irf degradation via aeff ---! -def degradedIRF_3d(x, y, z, xlabel='x', ylabel='y', zlabel='z', title=None, c=('b'), zscale='linear', tex=False, fontsize=14, figsize=(12,6), rotation=0, zlim=(0,1), alpha=(1), label=None, savefig=None, show=True): - '''Plots IRF 3d.''' - - fig = plt.figure(figsize=figsize) - plt.rc('text', usetex=False) if tex else None - sns.set_style("whitegrid", {'axes.grid': False}) - ax = fig.add_subplot(111, projection='3d', zscale=zscale) - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - - curve = [] - for i in range(len(z)): - ax.plot_surface(x, y, z[i], alpha=alpha[i], color=c[i], label=label[i]) - curve.append(Rectangle((0, 0), 1, 1, fc=c[i], fill=True)) - ax.set_zlim(zlim) - ax.set_xlabel(xlabel, fontsize=fontsize, labelpad=fontsize) - ax.set_ylabel(ylabel, fontsize=fontsize, labelpad=fontsize) - ax.set_zlabel(zlabel, fontsize=fontsize, labelpad=fontsize) - ax.set_title(title, fontsize=fontsize) if title != None else None - ax.legend(curve, label, loc=0, fontsize=fontsize) if label != None else None - plt.tight_layout() - fig.savefig(savefig) if savefig != None else None - plt.show() if show else None - plt.close() - return - -# plot ebl interpolation ---! -def interp_ebl(x, y, savefig, type='linear', xlabel='x', ylabel='y', title='title', label=('y', 'y2'), fontsize=12, show=True, tex=False, sns_style=False): - '''Plots the EBL interpolation.''' - - fig = plt.figure() - plt.rc('text', usetex=False) if tex else None - sns.set() if sns_style else None - ax = plt.subplot(111, xscale='log', yscale='log') - plt.plot(x[0], y[0], '.', label=label[0], c='g') - plt.plot(x[1], y[1], 'o', c='k', markeredgecolor='k', markerfacecolor='none', label=label[1]) - plt.ylabel(ylabel, fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0) - plt.tight_layout() - fig.savefig(savefig) - plt.show() if show else None - plt.close() - return - -# SENSITIVITY ---! -def plotSensitivity(x, y, savefig, xlabel='energy (GeV)', ylabel='sensitivity', label=('nominal', 'nominal/2'), xscale='log', yscale='log', title='', fontsize=12, marker=('.'), ratio=True, show=True, tex=False, sns_style=False): - '''Plots the sensitivity.''' - - fig = plt.figure() - plt.rc('text', usetex=tex) if tex else None - sns.set() if sns_style else None - - if ratio: - ax1 = plt.subplot(211, xscale=xscale, yscale=yscale) - else: - ax1 = plt.subplot(111, xscale=xscale, yscale=yscale) - for i in range(len(y)): - plt.plot(x[i], y[i], marker=marker[i], label=label[i]) - plt.ylabel(ylabel, fontsize=fontsize) - plt.title(title, fontsize=fontsize) - plt.legend(loc=0) - - if ratio: - ax2 = plt.subplot(212, sharex=ax1, yscale='linear') - for i in range(len(y)-1): - plt.plot(x[0], y[0]/y[i+1]) - plt.axhline(0.5, ls='-.', c='r') - plt.ylabel('ratio nom/deg', fontsize=fontsize) - plt.xlabel(xlabel, fontsize=fontsize) - plt.ylim([0.,1.]) - plt.grid() - plt.tight_layout() - fig.savefig(savefig) - plt.show() if show else None - plt.close() - return - -# plot lightcurve from pipeline ---! -def plotLightCurve(flux, t1, uplims, t2, xerr, yerr, filename, temp_t, temp_f, c1=('b'), c2=('r'), lf=('flux'), lup=('upper limit'), alpha=0.5, fontsize=20, figsize=(20, 16), rotation=0, ylim=None, xlim=None, interp=False, tex=False, sns_style=False): - '''Plots lightcurves.''' - - fig = plt.figure(figsize=figsize) - plt.rc('text', usetex=False) if tex else None - sns.set() if sns_style else None - - ax = plt.subplot(111, yscale='log', xscale='log') - plt.xticks(fontsize=fontsize, rotation=rotation) - plt.yticks(fontsize=fontsize, rotation=rotation) - # template ---! - plt.plot(temp_t[20:50], temp_f[20:50], '-g', lw=5, label='expected (30GeV-150TeV)', alpha=1) - # detections ---! - for i, f in enumerate(flux): - plt.errorbar(t1[i], f, xerr=np.array([xerr[i] for i in range(len(t1[i]))]), - # yerr=np.array([yerr[i] for i in range(len(t1[i]))]), - marker='o', c=c1[i], alpha=alpha, label=lf[i], ls='none', ms=10) - plt.fill_between(t1[i][0], - f[0] - np.array([yerr[i] for i in range(len(t1[i]))]), - f[0] + np.array([yerr[i] for i in range(len(t1[i]))]), - alpha=0.3, color=c1[i], interpolate=True) - plt.axvline(t1[i][0][-1], ls='-.', lw=2, c=c1[i]) - plt.text(t1[i][0][-1] + 100, 7e-9 - 1e-9 * (i + 1), '%d s' % t1[i][0][-1], color=c1[i], fontsize=fontsize) - # upper limits ---! - for i, u in enumerate(uplims): - if len(u[0]) > 1: - plt.errorbar(t2[i], u, xerr=np.array([xerr[i] for i in range(len(t2[i]))]), - marker='v', c=c2[i], alpha=alpha, label=lup[i], ls='none', uplims=True) - plt.axhline(np.mean(u), ls='-.', lw=2, c=c2[i]) - plateau = float(np.mean(uplims[i][0])) * 1e9 - plt.text(30, float(np.mean(uplims[i][0])) * 1.1, '%0.2fe-9 ph/cm$^2$/s' % plateau, - color=c2[i], fontsize=fontsize + 5) - - plt.plot(temp_t[20:50], temp_f[20:50], '-g', lw=5, label='expected (30GeV-150TeV)', alpha=1) - plt.ylabel('Flux (ph/cm$^2$/s)', fontsize=fontsize) - plt.xlabel('time (s)', fontsize=fontsize) - plt.legend(fontsize=fontsize) - plt.xlim(xlim) if not xlim else None - plt.ylim(ylim) if not ylim else None - plt.show() - fig.savefig(filename) - plt.close() - return - -def get_template_lc(runid, erange=(0.04, 150), path='/home/ambra/Desktop/CTA/projects/DATA/templates/grb_afterglow/GammaCatalogV1.0', if_ebl=False): - - with fits.open(join(path, runid)) as hdul: - energy=np.array(hdul[1].data) - # timebins [s] - time=np.array(hdul[2].data) - # spectra [fotoni/GeV/cm^2/s] - spectra=np.array(hdul[3].data) - if if_ebl: - # ebl [fotoni/GeV/cm^2/s] - ebl=np.array(hdul[4].data) - Nt=len(time) - Ne=len(energy) - print('Total time bins:', Nt) - print('Total energy bins:', Ne) - # TIME GRID ---! - t=[0.0 for x in range(Nt+1)] - for i in range(Nt-1): - t[i+1]=time[i][0]+(time[i+1][0]-time[i][0])/2 - # last bin - t[Nt]=time[Nt-1][0]+(time[Nt-1][0]-t[Nt-1]) - # ENERGY GRID ---! - en=[1.0 for x in range(Ne+1)] - for i in range(Ne-1): - en[i+1]=energy[i][0]+(energy[i+1][0]-energy[i][0])/2 - # last bin - en[Ne]=energy[Ne-1][0]+(energy[Ne-1][0]-en[Ne-1]) - # instrument range - inst = (min(en, key=lambda x:abs(x-erange[0]*1e3)), min(en, key=lambda x:abs(x-erange[1]*1e3))) - print('Instrument range:', inst) - # FLUX SPECTRA ---! - f, f2 = [], [] - for i in range(Nt): - f.append(0.0) - f2.append(0.0) - for j in range(Ne): - if en[j] <= erange[1]*1e3 and en[j] >= erange[0]*1e3: - #if en[j] <= inst[1] and en[j] >= inst[0]: - f[i]=f[i]+spectra[i][j]*(en[j+1]-en[j]) - if if_ebl: - f2[i]=f2[i]+ebl[i][j]*(en[j+1]-en[j]) - if if_ebl: - return time, f, f2 - else: - return time, f - -def get_template_spectra(runid, time=(0.0, 800), erange=(0.03, 150), path='/home/ambra/Desktop/CTA/projects/DATA/templates/grb_afterglow/GammaCatalogV1.0', if_ebl=False): - - with fits.open(join(path, runid)) as hdul: - energy=np.array(hdul[1].data) - # timebins [s] - time=np.array(hdul[2].data) - # spectra [fotoni/GeV/cm^2/s] - spectra=np.array(hdul[3].data) - if if_ebl: - # ebl [fotoni/GeV/cm^2/s] - ebl=np.array(hdul[4].data) - Nt=len(time) - Ne=len(energy) - print('Total time bins:', Nt) - print('Total energy bins:', Ne) - # TIME GRID ---! - t=[0.0 for x in range(Nt+1)] - for i in range(Nt-1): - t[i+1]=time[i][0]+(time[i+1][0]-time[i][0])/2 - # last bin - t[Nt]=time[Nt-1][0]+(time[Nt-1][0]-t[Nt-1]) - # ENERGY GRID ---! - en=[1.0 for x in range(Ne+1)] - for i in range(Ne-1): - en[i+1]=energy[i][0]+(energy[i+1][0]-energy[i][0])/2 - # last bin - en[Ne]=energy[Ne-1][0]+(energy[Ne-1][0]-en[Ne-1]) - # instrument range - inst = (min(en, key=lambda x:abs(x-erange[0]*1e3)), min(en, key=lambda x:abs(x-erange[1]*1e3))) - # FLUX SPECTRA ---! - f, f2 = [], [] - for j in range(Ne): - f.append(0.0) - f2.append(0.0) - for i in range(Nt): - if en[j] <= inst[1] and en[j] >= inst[0]: - f[j]=f[j]+spectra[i][j]*(en[j+1]-en[j]) - if if_ebl: - f2[j]=f2[j]+ebl[i][j]*(en[j+1]-en[j]) - if if_ebl: - return energy, f, f2 - else: - return energy, f - -def get_template_lc_interp(runid, erange=(0.04, 150), path='/home/ambra/Desktop/CTA/projects/DATA/templates/grb_afterglow/GammaCatalogV1.0'): - - with fits.open(join(path, runid)) as hdul: - energy=np.array(hdul[1].data) - # timebins [s] - time=np.array(hdul[2].data) - # spectra [fotoni/GeV/cm^2/s] - spectra=np.array(hdul[3].data) - # ebl [fotoni/GeV/cm^2/s] - ebl=np.array(hdul[4].data) - Nt=len(time) - Ne=len(energy) - # TIME GRID ---! - t=[0.0 for x in range(Nt+1)] - for i in range(Nt-1): - t[i+1]=time[i][0]+(time[i+1][0]-time[i][0])/2 - # last bin - t[Nt]=time[Nt-1][0]+(time[Nt-1][0]-t[Nt-1]) - # ENERGY GRID ---! - en=[1.0 for x in range(Ne+1)] - for i in range(Ne-1): - en[i+1]=energy[i][0]+(energy[i+1][0]-energy[i][0])/2 - # last bin - en[Ne]=energy[Ne-1][0]+(energy[Ne-1][0]-en[Ne-1]) - # instrument range - x, y = [], [] - xcta, ycta = [], [] - fcta = [] - for i in range(Nt): - xcta.append(erange[0]) - for j in range(Ne): - x.append(en[j]) - y.append(ebl[i][j]) - if en[j] <= erange[1] and en[j] >= erange[0]: - xcta.append(en[j]) - ycta.append(ebl[i][j]) - xcta.append(erange[1]) - - efunc = interp1d(x, y, kind='linear', fill_value='extrapolate') - ycta.insert(0, float(efunc(xcta[0]))) - ycta.append(float(efunc(xcta[1]))) - - fcta.append(0.0) - edge = True - for j in range(Ne): - # CTA - if en[j] <= erange[1] and en[j] >= erange[0]: - if fcta[i] == 0.0: - fcta[i]=fcta[i]+ycta[0]*(en[j]-erange[0]) - else: - fcta[i]=fcta[i]+ebl[i][j]*(en[j+1]-en[j]) - elif edge: - fcta[i]=fcta[i]+ycta[-1]*(erange[1]-en[j]) - edge = False - - return time, fcta \ No newline at end of file diff --git a/rtasci/rtasci/makeConfig_runJobs.py b/rtasci/rtasci/makeConfig_runJobs.py deleted file mode 100644 index c93decf8296705bfa8f8bea71716626dad0b6a84..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/makeConfig_runJobs.py +++ /dev/null @@ -1,113 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Leonardo Baroncelli -# ******************************************************************************* - -import os -import yaml -import argparse -from pathlib import Path - -# ---------------------------------------------------------------------------- ! - -parser = argparse.ArgumentParser() -parser.add_argument('-f', '--infile', type=str, required=True, help='yaml configuration file') -parser.add_argument('--tt', type=int, required=True, help='total trials') -parser.add_argument('--cpus', type=int, required=True, help='number of cpus') -parser.add_argument('--script', type=str, required=True, help='script to run') -parser.add_argument('--env', type=str, required=True, help='environment to activate') -parser.add_argument('--delay', type=float, default=90, help='delay') -parser.add_argument('--off', type=str, default='gw', help='offset') -parser.add_argument('--flux', type=float, default=1, help='flux scaling factor') -parser.add_argument('--print', type=str, default='false', help='print checks and outputs') -parser.add_argument('-out', '--output-dir', type=str, required=False, default="", help='The path to the output directory') -args = parser.parse_args() - -if "DATA" not in os.environ: - raise ValueError("Please, export DATA") -print(f'\nDATA={os.environ["DATA"]}') - -# compose file path -filename = Path(args.infile) -if not filename.is_file(): - raise ValueError('configuration file not found') -# load yaml -with open(filename) as f: - config = yaml.load(f, Loader=yaml.FullLoader) - -output_path = filename.parent -ext = filename.suffix -name = filename.stem - -trials_per_cpu = int(args.tt / args.cpus) - -if args.off == 'gw': - config['simulation']['offset'] = args.off -else: - config['simulation']['offset'] = float(args.off) -config['simulation']['delay'] = args.delay -config['setup']['trials'] = int(trials_per_cpu) -config['setup']['scalefluxfactor'] = args.flux -config['options']['plotsky'] = False -start_count = config['setup']['start_count'] - -print(f"SLURM configuration:\n\tNumber of jobs: {args.cpus}\n\tTotal trials: {args.cpus*trials_per_cpu}\n\tTrials per job: {trials_per_cpu}\n\tStart count: {start_count}\n\tOutput dir: {args.output_dir}") -input("Press any key to start!") - -for i in range(args.cpus): - run = f"job_{i+1:02d}" - job_name = f'trials_{i*trials_per_cpu+1}-{(i+1)*trials_per_cpu}' - #print("\n") - #print(f"run: {run}") - #print(f"job name: {job_name}") - output_dir = output_path.joinpath(run) - output_dir.mkdir(parents=True, exist_ok=True) - - # save new config - config['setup']['start_count'] = start_count + i*trials_per_cpu - config_outname = output_dir.joinpath(f"{name}_{job_name}").with_suffix(ext) - - if config_outname.is_file(): - config_outname.unlink() - with open(config_outname, 'w+') as f: - new_config = yaml.dump(config, f, default_flow_style=False) - - # write bash - sh_outname = output_dir.joinpath(f"sh_{job_name}").with_suffix(".sh") - with open(sh_outname, 'w+') as f: - f. write('#!/bin/bash\n') - f.write(f'\nsource activate {args.env}') - f.write(f'\n\texport DATA={os.environ["DATA"]}') - - script_name = Path(args.script).stem.lower() - if script_name == 'rtapipe': - f.write(f'\n\tpython {args.script}.py -f {config_outname} --print {args.print.lower()}\n') - elif script_name == 'wilks': - f.write(f'\n\tpython {args.script} -f {config_outname}\n') - elif script_name == "simbkg": - f.write(f'\n\tpython {args.script} -f {config_outname} -out {args.output_dir}\n') - else: - raise ValueError(f"Script {script_name} is not supported.") - - # write job - job_outname = output_dir.joinpath(f"job_{job_name}").with_suffix(".sh") - job_outlog = output_dir.joinpath(f"job_{job_name}").with_suffix(".log") - - with open(job_outname, 'w+') as f: - f.write('#!/bin/bash') - f.write(f'\n\n#SBATCH --job-name={script_name}-slurm-job') - f.write(f'\n#SBATCH --output={job_outlog}') - f.write('\n#SBATCH --account=baroncelli') - f.write('\n#SBATCH --partition=large_lc') - f.write(f'\n\nexec sh {str(sh_outname)}\n') - - #print(f"Configuration file={config_outname}") - #print(f"Exec file created={sh_outname}") - #print(f"Slurm configuration file created={job_outname}") - - os.system(f'sbatch {job_outname}') diff --git a/rtasci/rtasci/misc/checkPointing.py b/rtasci/rtasci/misc/checkPointing.py deleted file mode 100644 index 0ed0f191f4d5695199ba9ddf4f0a8fb962556805..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/checkPointing.py +++ /dev/null @@ -1,73 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -from os.path import isdir, join, isfile -from rtasci.lib.RTAUtils import get_pointing -from rtasci.lib.RTAUtilsGW import get_offset -from rtasci.cfg.Config import Config -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw -from astropy import units as u -from astropy.coordinates import SkyCoord - -parser = argparse.ArgumentParser(description='This script allows to find the offset between the target coordinate and the pointing coordinate.') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -args = parser.parse_args() -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') - -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') - -# ------------------------------------------------------- loop runid --- !!! -for runid in runids: - print(f'Processing runid: {runid}') - # grb path ---! - grbpath = join(datapath, 'obs', runid) # folder that will host the phlist - if not isdir(grbpath): - os.mkdir(grbpath) - modelpath = join(datapath, f'extracted_data/{runid}') # bin model folder - if not isdir(modelpath): - raise ValueError(f'Folder {runid} not found in {modelpath}') - tcsv = join(datapath, f'extracted_data/{runid}/time_slices.csv') # times table - if not isfile(tcsv): - raise ValueError(f'Data from {runid} have not been correctly extracted.') - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - template = f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits" - - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - pointing = get_alert_pointing_gw(mergermap) - else: - pointing = list(get_pointing(template)) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - print(f"Pointing: {pointing}") - off = get_offset(template, mergermap) - print(f"Offset: {off}") - target = get_pointing(template) - # find off-axis ---! - offangle = SkyCoord(ra=target[0]*u.deg, dec=target[1]*u.deg, unit='deg', frame='icrs').separation(SkyCoord(ra=pointing[0]*u.deg, dec=pointing[1]*u.deg, unit='deg', frame='icrs')).deg - print(f"Off-axis angle: {offangle}") \ No newline at end of file diff --git a/rtasci/rtasci/misc/chi2distribution.py b/rtasci/rtasci/misc/chi2distribution.py deleted file mode 100644 index 9e9b4b37c38af4b2b732ad3385930fa4e7e654db..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/chi2distribution.py +++ /dev/null @@ -1,19 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -from rtasci.lib.RTAStats import * -from os.path import expandvars, join - -x = np.random.chisquare(1, int(1e7)) -#np.insert(x, 0, 0) - -path = expandvars('$DATA/outputs/LEO') -save_data_on_file(x, filename=join(path, 'chi2sample_s1e7_r0-36_n100.csv'), hdr=None) - diff --git a/rtasci/rtasci/misc/compareCtoolsGammapy.py b/rtasci/rtasci/misc/compareCtoolsGammapy.py deleted file mode 100644 index c5d01f7815f6ee4a4a18105b6188c62caa538fb8..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/compareCtoolsGammapy.py +++ /dev/null @@ -1,59 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import pandas as pd -import seaborn as sns -from lib.RTAVisualise import * -from os.path import join, isdir, isfile -from os import listdir - -datapath = join(os.path.expandvars('$DATA'), 'outputs/crab/timing') -try: - isdir(datapath) -except ValueError: - raise ValueError(f'Folder {datapath} not found.') - -pngpath = join(datapath, 'png') -if not isdir(pngpath): - print('Creating png folder...') - os.mkdir(pngpath) - -tables = [f for f in listdir(datapath) if isfile(join(datapath, f))] -print(f'Tables: {len(tables)}\n') - -for i, table in enumerate(tables): - print(f'Collecting data from {table}') - data = pd.read_csv(join(datapath, table), sep=' ') - data['tools'] = [table.replace('.csv', '') for n in range(len(data))] - #print(f'5 rows {data[:5]}') - if i == 0: - print('start') - total = data - else: - print('add') - total = total.append(data, sort=False) - print(f'data: {len(data)} and keys {len(data.keys())}') - print(f'total: {len(total)} and keys {len(total.keys())}') - - -tools = total['tools'].drop_duplicates() -print(f'\n-----------\nanalysis: {len(tools)}') - -for tool in tools: - d1000 = total[total['texp'] == 1000] - d1000 = d1000[d1000['tools'] == tool] - d100 = total[total['texp'] == 100] - d100 = d100[d100['tools'] == tool] - d10 = total[total['texp'] == 10] - d10 = d10[d10['tools'] == tool] - - -g = sns.FacetGrid(total, row="tools", col="ttotal", height=4, aspect=.5) -g.map(sns.barplot, "texp", "sqrt_ts") diff --git a/rtasci/rtasci/misc/count_mc_events_from_log.py b/rtasci/rtasci/misc/count_mc_events_from_log.py deleted file mode 100644 index bc8c856f6eed8ee979c7249f3d759cd1d556841e..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/count_mc_events_from_log.py +++ /dev/null @@ -1,51 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Leonardo Baroncelli -# ******************************************************************************* - -import os -import argparse - -if __name__=='__main__': - - # get input - parser = argparse.ArgumentParser() - parser.add_argument('-id', '--input-dir', type=str, help='input dir') - args = parser.parse_args() - - background_counts = 0 - events_counts = 0 - - for root, dirs, files in os.walk(args.input_dir): - for f in files: - if ".log" in f and ".skymap" not in f: - f = os.path.join(root, f) - # print(f) - with open(f, "r") as fh: - lines = fh.readlines() - for line in lines: - if ": MC source events ..........: " in line and "[run0406_ID000126]" in line: - #print("\n->",line) - line = line.split("MC source events ..........: ")[1].strip().replace("\n","") - #print("line:",line) - counts = line.split(" [run0406_ID000126]")[0] - #print("counts: ", counts) - counts = int(counts) - #print(f"source counts: {counts}") - events_counts += counts - if ": MC background events ......: " in line: - #print("\n->",line) - line = line.split("MC background events ......: ")[1].strip().replace("\n","") - #print(f"line:{line}") - counts = int(line) - #print("counts: ", counts) - #print(f"bkg counts: {counts}") - background_counts += counts - - print("Background MC simulated events:",background_counts) - print("Source MC simulated counts:",events_counts) - print("Total MC simulated events:",background_counts+events_counts) diff --git a/rtasci/rtasci/misc/datamerger.py b/rtasci/rtasci/misc/datamerger.py deleted file mode 100644 index cd7aec140fd287db147bf76fe930a35daef7c03d..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/datamerger.py +++ /dev/null @@ -1,39 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import pandas as pd -from os import listdir -from os.path import isfile, isdir, join, expandvars - -path = expandvars('$DATA') -folders = [f for f in listdir(path) if isdir(join(path, f))] - -for i, folder in enumerate(folders): - print(folder) - tests = [f for f in listdir(join(path, folder)) if isfile(join(path, folder, f)) and '.txt' in f] - tests = sorted(tests) - for j, test in enumerate(tests): - data = pd.read_csv(join(path, folder, test), sep=' ') - if j == 0: - table = data - else: - table = table.append(data, sort=False) - print(len(table)) - table.to_csv(join(path, folder + '.txt'), index=False, header=True, sep=' ') - if i == 0: - total = table - else: - total = total.append(table, sort=False) -print(len(total)) -total['offset'] = np.where(total['offset'] == 'gw', float(1.6), total['offset']) -#total.sort_index(axis=1, inplace=True) -total.to_csv(join(path, 'all.txt'), index=False, header=True, sep=' ', na_rep=np.nan) - -print('exit') \ No newline at end of file diff --git a/rtasci/rtasci/misc/deljobs.py b/rtasci/rtasci/misc/deljobs.py deleted file mode 100644 index 7e66ecb5828cc5c93f56685a90b4700bf4b0211d..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/deljobs.py +++ /dev/null @@ -1,22 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import sys - -start = int(sys.argv[1]) -if len(sys.argv) > 2: - runs = int(sys.argv[2]) -else: - runs = 20 - -for i in range(runs): - os.system(f'scancel {start+i}') - - diff --git a/rtasci/rtasci/misc/grbSens.py b/rtasci/rtasci/misc/grbSens.py deleted file mode 100644 index 333096f91c3529397ad19c8e67aa0d47930749e3..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/grbSens.py +++ /dev/null @@ -1,104 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import sys -from rtasci.thesis.pkg_blindsearch import * -from os.path import join, expandvars, isdir - -inst = str(sys.argv[1]) - -# files and path ---! -datapath = expandvars('$DATA') -model = join(datapath, 'models/grb.xml') - -# files and path ---! -cfg = xmlConfig('/config.xml') -p = ConfigureXml(cfg=cfg) -outpath = join(datapath, 'irf_degradation/') -if not isdir(outpath): - os.mkdir(outpath) -pngpath = join(outpath, 'png') -if not isdir(pngpath): - os.mkdir(pngpath) -# irf and caldb ---! -caldb_nom = 'prod3b-v2' -caldb_deg = caldb_nom.replace('prod', 'degr') -irf = 'South_z40_0.5h' -p.setRunDir(irf + '/') -# setup ---! -nominal = True -degraded = True -compute = True -plot = True -sens_type = 'Integral' -print('Compute', sens_type, 'sensitivity') - -caldb = [] -if nominal: - caldb.append(caldb_nom) - print('Use nominal cladb') -if degraded: - caldb.append(caldb_deg) - print('Use degraded caldb') - -if inst.lower() == 'magic': - e = [0.3, 1.] - #texp = [42, 40, 70, 151.5, 438.5, 1654] # MAGIC 2 - texp = [38.2, 39.8, 70.28] # MAGIC 1 -elif inst.lower() == 'hess': - e = [0.1, 0.44] - texp = [300, 400, 500, 600] # HESS -elif inst.lower() == 'cta': - e = [0.03, 150.0] - #texp = [5, 10, 50, 100, 200, 300, 500] # CTA - texp = [10, 100] - -target = (33.057, -51.841) # centered on the source -offsets = [0]#, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5] -nbins = 10 # energy bins for sensitivity computation -src_name = 'GRB' - -# INITIALIZE ---! -if compute: - for i in range(len(caldb)): - for offset in offsets: - pointing = (target[0], target[1]-offset) - for j in range(len(texp)): - event = outpath + 'texp%ds_' %texp[j] + caldb[i] + f'_{inst.upper()}sim.fits' - results = outpath + 'texp%ds_' %texp[j] + caldb[i] + f'_{inst.upper()}fit.xml' - output = outpath + 'texp%ds_' %texp[j] + caldb[i] + f'_{inst.upper()}sens.csv' - nObj = Analysis('/config.xml') - nObj.e = e - nObj.t = [0, texp[j]] - nObj.caldb = caldb[i] - nObj.roi = 5 - nObj.irf = irf - nObj.model = model - # NOMINAL SIM ---! - nObj.output = event - nObj.pointing = pointing # (deg) - nObj.eventSim() - print('caldb = ', caldb[i]) - print('texp = ', texp[j], ' s') - print('off = ', offset) - # NOMINAL MAX LIKELIHOOD ---! - nObj.input = event - nObj.output = results - nObj.maxLikelihood() - # NOMINAL SENS ---! - nObj.sens_type = sens_type - nObj.model = results - nObj.output = output - nObj.src_name = src_name - nObj.eventSens(bins=nbins) - - if not isdir(f"{outpath}/off{offset}"): - os.mkdir(f"{outpath}/off{offset}") - os.system(f"mv {outpath}/*csv {outpath}/off{offset}/.") diff --git a/rtasci/rtasci/misc/multihist.py b/rtasci/rtasci/misc/multihist.py deleted file mode 100644 index 43eeff3eb586d147fba62c816eb1b55e9ff9b2a5..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/multihist.py +++ /dev/null @@ -1,30 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Leonardo Baroncelli -# ******************************************************************************* - -import os -import argparse -from os.path import isdir, join -import numpy as np -from rtasci.lib.RTAStats import ts_wilks, p_values, ts_wilks_cumulative - -if __name__=='__main__': - - parser = argparse.ArgumentParser() - parser.add_argument("-f", "--files", nargs='+', type=str, required=True) - parser.add_argument("-o", "--outname", type=str, required=True) - args = parser.parse_args() - - outDir="./tmp" - if not isdir(outDir): - os.mkdir(outDir) - data = [np.genfromtxt(file, usecols=(0), skip_header=0, dtype=float) for file in args.files] - - ts_wilks(data, nbin=100, width=None, filename = join(outDir, f"{args.outname}_wilks.png"), overlay=False, write_data=True) - ts_wilks_cumulative(data, nbin=100, width=None, filename = join(outDir, f"{args.outname}_cumulative.png"), overlay=False, write_data=True) - p_values(data, nbin=100, width=None, filename = join(outDir, f"{args.outname}_pvalues.png"), overlay=False, sigma5=False, write_data=True) diff --git a/rtasci/rtasci/misc/phd_leo_test.py b/rtasci/rtasci/misc/phd_leo_test.py deleted file mode 100644 index 393fafae5481e065e3035671f9b7aa36431df383..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/phd_leo_test.py +++ /dev/null @@ -1,50 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano <.dipiano@inaf.it> -# ******************************************************************************* - -from sagsci.tools.utils import * -from sagsci.wrappers.rtaph.photometry import Photometrics, aeff_eval - -# some parameters -pointing = {'ra': 57, 'dec': 33} -region_radius = 0.2 -offset = 0.5 -max_offset = 2 -livetime = 10 -args = 'the other required stuff' - -# example of data collection -data = {'region': [], 'counts':[], 'flux':[]} - -# define the rings in FOV -while offset <= max_offset: - # define the starting (aka target not necessary source) region - target_region = {'ra': pointing[0]+offset, 'dec': pointing[1], 'rad': region_radius} - - # init photometry - phm = Photometrics(*args) - # get response for current offset - region_eff_resp = aeff_eval(target_region, pointing) - # get ring of regions from starting position - ring_regions = phm.find_off_regions(target_region, pointing, method='reflection', *args) - # add starting position region to dictionary - ring_regions += target_region - - # for each region in ring count events and get flux - for region in ring_regions: - # counts in region within time window - counts = phm.region_counter(region, *args) - # normalised value (flux) in region within time window - flux = counts / livetime / region_eff_resp - # save data somewhere - data['region'].append(region) - data['counts'].append(counts) - data['flux'].append(flux) - - # increment offset to get next offset ring - offset += region_radius*2 diff --git a/rtasci/rtasci/misc/pvalues.py b/rtasci/rtasci/misc/pvalues.py deleted file mode 100644 index 7c38e5228a11c88d763be57b4086bc7e71bf745d..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/pvalues.py +++ /dev/null @@ -1,41 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import pandas as pd -from os.path import join, expandvars -from rtasci.lib.RTAStats import * - -path = expandvars('$DATA/outputs/GIULIANA') -png_path = join(path, 'png/') -if not os.path.isdir(png_path): - os.mkdir(png_path) - - -filename = 'merge_prob.csv' -# load DataFrame and column names ---! -df = pd.read_csv(join(path, filename)) -cols = list(df.columns) -trials = len(df[cols[0]]) -# drop duplicates ---! -df.sort_values(cols[0], inplace=True) -# set arrays ---! -values = np.array(df[cols[0]]) -values.sort() - -nbin = 100 -print(f'min = {min(values)}\nmax = {max(values)}\nlen = {len(values)}') - -# -------------------------------- PLOT ---! - -fig, ax = ts_wilks(values, len(values), nbin=nbin, figsize=(7, 8), xrange=(0,1), title='distribution', show=False, usetex=False, filename=png_path + filename.replace('.csv', '_wilks.png'), overlay=None, write_data=True) - -fig, ax = p_values(values, len(values), nbin=nbin, figsize=(7, 8), xrange=(0,1), ylim=(5e-4,2e-3), title='pvalues', show=False, usetex=False, filename=png_path + filename.replace('.csv', '_pvalues.png'), overlay=None, sigma5=False, write_data=True) - -#fig, ax = ts_wilks_cumulative(values, len(values), nbin=nbin, figsize=(7, 8), xrange=(0,0.3), show=False, usetex=False, title='cumulative', filename=png_path + filename.replace('.csv', '_cumulative.png'), overlay=None) diff --git a/rtasci/rtasci/misc/reformatold.py b/rtasci/rtasci/misc/reformatold.py deleted file mode 100644 index 52cc1f8837cddfd8ab408894980acc3a383ccc0c..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/reformatold.py +++ /dev/null @@ -1,56 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import pandas as pd -from astropy import units as u -from astropy.coordinates import SkyCoord -from os import listdir -from os.path import isfile, isdir, join, expandvars - -# target -trueRA = 33.057 -trueDEC = -51.841 -true_coord = SkyCoord(ra = trueRA*u.deg, dec = trueDEC*u.deg, frame='fk5') - -# data -path = expandvars('$DATA') -folders = [f for f in listdir(path) if isdir(join(path, f)) and 'offGW' in f and 'del50' in f] -folders = sorted(folders) -for i, folder in enumerate(folders): - print(folder) - tests = [f for f in listdir(join(path, folder)) if isfile(join(path, folder, f)) and '.csv' in f] - tests = sorted(tests) - for j, test in enumerate(tests): - data = pd.read_csv(join(path, folder, test), sep=',') - data['scaleflux'] = i+1 - data = data.rename(columns={'#trial': 'runid', 'RA_det': 'ra', 'DEC_det': 'dec', 'flux_ph': 'flux', 'TS': 'sqrt_ts'}) - data['seed'] = [int(seed.replace('ID','')) for seed in data['runid']] - data['sqrt_ts'] = [np.sqrt(float(ts)) for ts in np.array(data['sqrt_ts'])] - data['runid'] = 'run0406_ID000126' - data['offset'] = 1.638 - data['delay'] = 50 - data['caldb'] = 'degr3b-v2' - data['irf'] = 'South_z40_0.5h' - data = data.drop(['Ndet', 'Nsrc', 'RA_fit', 'DEC_fit', 'sigma'], axis=1) - dist = [] - for k in range(len(data)): - dist.append(float(true_coord.separation(SkyCoord(ra=np.array(data['ra'])[k], dec=np.array(data['dec'])[k], unit='deg', frame='fk5')).deg)) - print(len(dist), max(dist), min(dist)) - data['dist'] = dist - #data.sort_index(axis=1, inplace=True) - columnsTitles = ['runid', 'seed', 'texp', 'sqrt_ts', 'flux', 'ra', 'dec', 'dist', 'offset', 'delay', 'scaleflux', 'caldb', 'irf'] - data = data.reindex(columns=columnsTitles) - data.to_csv(join(path, folder, test.replace('.csv', '.txt')), sep=' ', index=False, header=True, na_rep='nan') - if j == 0: - table = data - else: - table = table.append(data, sort=False) - table.to_csv(join(path, folder + '.txt'), sep=' ', index=False, header=True, na_rep='nan') - del table \ No newline at end of file diff --git a/rtasci/rtasci/misc/save_selected_fits.ipynb b/rtasci/rtasci/misc/save_selected_fits.ipynb deleted file mode 100644 index 996e049eaf719e210a419829d912d9c9972529d0..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/save_selected_fits.ipynb +++ /dev/null @@ -1,208 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "/data01/homes/dipiano/.conda/envs/scitools/bin/python\r\n" - ] - } - ], - "source": [ - "! which python" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from sagsci.tools.fits import Fits\n", - "\n", - "phlist = '/data01/homes/dipiano/DATA/obs/backgrounds/bkg000001.fits'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To avoid overwriting the original file, make a copy and save the selected photon list separately" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/data01/homes/dipiano/DATA/obs/backgrounds/bkg000001_selected.fits'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import os\n", - "selected_phlist = phlist.replace(\".fits\", \"_selected.fits\")\n", - "os.system(f'cp {phlist} {selected_phlist}')\n", - "selected_phlist" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- initialise class\n", - "- get DL3 pointing\n", - "- get DL3 data\n", - "- apply selection cuts\n", - "- set DL3 selected data\n", - "- save to new photon list" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "f = Fits()\n", - "pointing = {'ra': f.get_dl3_hdr(selected_phlist)['RA_PNT'], 'dec': f.get_dl3_hdr(selected_phlist)['DEC_PNT']}\n", - "dl3 = f.get_dl3_data(selected_phlist)\n", - "dl3_selected = f.selection_cuts(dl3, pointing, trange=None, erange=[0.04, 1], maproi=1.5)\n", - "f.set_dl3_data(selected_phlist, dl3_selected, GTI=[0, 10])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot the original DL3 and the selected DL3 directly into counts maps." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from sagsci.tools.plotting import SkyImage\n", - "\n", - "im = SkyImage()\n", - "im.set_pointing_from_dict(pointing)\n", - "im.counts_map(phlist, name='skymap.png', figsize=(5, 5), fontsize=12)\n", - "im.counts_map(selected_phlist, name='skymap_selected.png', figsize=(5, 5), fontsize=12)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOydeXxU5bn4v2eZmTPJJJkkk5WQBAiCioiKCooKiksrVax6XVvbX23Va1u1Xqter1prrbWW2t7ae7XailUrVq16xYoVBRUVBSUIKEqWyUIySSbJJJnMembO749hxoRM9oSQ+H4/n3zEmbM8Z5n3eZ/nfRbJMAwDgUAgEAgEkxp5ogUQCAQCgUAweoRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBIIpgFDoAoFAIBBMAYRCFwgEAoFgCiAUukAgEAgEUwCh0AUCgUAgmAIIhS4QCAQCwRRAKHSBQCAQCKYAQqELBAKBQDAFEApdIBAIBAcd5eXlnH322RQXF2O1WsnKymLx4sU8+eSTY77vhx9+yJlnnklaWho2m41ly5bx7rvvJt1227ZtrFy5ksLCQlJSUpg7dy4///nP8fl8iW02btyIJElJ/zZv3tzreF1dXfz0pz/ljDPOICcnB0mS+NnPfjb0G9UDdUR7CQQCgUAwjng8HqZPn84ll1zCtGnT6O7u5qmnnuJb3/oWTqeT//qv/xqTfbds2cLJJ5/McccdxxNPPIFhGPz617/mtNNOY8OGDSxevDix7aeffsoJJ5zAnDlz+N3vfofD4eDtt9/m5z//OR999BEvvfRSLzl++ctfsmzZsl6fzZs3r9f/t7a28qc//YkjjzySlStX8uijj478phkCgUAgEEwSjj/+eGP69Oljtu+ZZ55p5OXlGd3d3YnPOjs7DYfDYZxwwgm9tr3tttsMwKioqOj1+Q9+8AMDMNra2gzDMIwNGzYYgPHss88OKlM0GjWi0ahhGIbR0tJiAMadd945pOvp6Ogw/vnPfyb+X1joAoFAIEhKIBAgFAqNybEMw0CSpF6fWSwWLBbLsI7jcDhobm4ekQzJ9n333Xc5++yzSUlJSXyWlpbGySefzD/+8Q8aGxspKCgAwGQyAZCRkdHrGHa7HVmWMZvNw5Zp/3syHB599FFuuukm/vKXv3DFFVcIl7tAIBAI+hIIBJgxYwYul2tMjmez2fB6vb0+u/POOwddL45Go0SjUdrb23n22Wd57bXXePDBB4d0zqHsGwqFkk4q4p/t2LEjodCvuOIKfve733HNNddw3333kZOTw1tvvcXDDz/MtddeS2pqaq9jXHvttVx88cWkpKSwePFibr/9dpYsWTIk2YfCT37yE/bu3ct3v/tdAoGAUOgCgUAg6EsoFMLlclFXV0d6evqojtXZ2cn06dP7HGso1vm///u/8/DDDwNgNpv57//+b6666qohnXco+x522GFs3ryZaDSKLMfixHVd54MPPgBia9xxSktLef/99znvvPOYNWtW4vMf//jH/O53v0v8f0ZGBtdddx1Lly4lOzubiooK7r//fpYuXcorr7zCmWeeOST5h8KqVavQNI2rr75arKELBAKBoC8dHR0GYHg8rUY0Gh7Vn8fTagBGR0fHsOWoqakxtmzZYrzyyivG1VdfbciybNx///1jtu+f//xnAzCuueYao76+3qitrTW+973vGYqiGICxZs2axLbV1dVGWVmZceKJJxrPPfec8dZbbxm//vWvjfT0dOP//b//N6As7e3tRlFRkTF//vx+txnuGnpPfv7znxtCoQsEAoGgDweLQt+fq6++2lBV1Whubh6zfX/1q18ZNpvNAAzAWLx4sXHz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DOTm5nLmmWdiMBgYNmwYt912G3a73a/t888/z4QJEzAYDMTExJCZmcm9997r+z6Uyf3jjz/mxBNPJCoqipiYGGbNmhVgRfHet507d7JgwQLi4uIYPHgwV111FXV1dW1eW+91+/HHH7nssssCvsvOzmbKlCnodDpSUlK45557Aq57R64ZNNc7HzVqFFqtljFjxvDmm29yxRVXBDwHgwcPZtasWSxfvrzdPgi6Tm+WT503bx4rV65k8eLFrFu3jsmTJ7NgwQLefPPNCPdyYCAUusCPefPmkZGRwbvvvtvmC/PKK6/kySef5Morr+Sjjz7i/PPP57zzzsNsNgdtf/XVV6NWq3nzzTd59NFH2bhxI5deeqnv+w8++IARI0ZwzDHHsGXLFrZs2cIHH3zQpqz79+/n4osv5vXXX2ft2rX89a9/5bHHHuO6667ztbFarcyaNYvy8nKeffZZvvjiC5588knS0tL86qtfccUVLFy4kMmTJ7N69WrefvttzjnnHL/14AcffJAFCxYwZswY3nnnHV5//XUaGho4+eST2bVrl59sTqeTc845h9NOO42PPvqIq666iieeeIJHHnnE1+btt9/mxhtvZNq0aXzwwQd8+OGHLFq0CKvV2ma/33zzTc4991xiY2N56623ePnll6mtrWX69Ols2rQpoP3555/PqFGjeO+997j77rt58803WbRoUZvnAFi7di0qlYpTTjnFb/uuXbs47bTTMJvNrFixguXLl7N161b+85//BBwj3Gv2wgsvcO211zJ+/Hjef/99/vGPf7BkyZKQvgPTp09n8+bNIZ83QeToLZP7Z599xhdffMFzzz3Hddddx4wZM3jxxReZNWsWd9xxB263WJcPoLfLvQn6BosXL5YB+V//+lfI77zs3LlTBuS77rrLr91bb70VUNrz1VdflQH5xhtv9Gv76KOPyoBcWlrq23b00UfL06ZN65T8brdbdjqd8muvvSarVCq5pqZGluXmsquA/OGHH4bc99tvv5UB+b777gvZprCwUJYkSb7lllv8tjc0NMjJycnyBRdc4Nt2+eWXy4D8zjvv+LU988wz5dGjR/v+vvnmm2Wj0dhmvzZs2CAD8oYNG3z9TElJkceNGye73W4/OZKSkuSTTjrJt8173x599FG/Y954442yTqdrt+TuGWecIWdmZgZsv/DCC2W9Xi+XlZX5trlcLjkzM1MG5AMHDsiyHP41c7vdcnJysnzCCSf4tSsoKJDVarU8fPjwABm++OILGZDXrVvXZh8EncdbPhVUskIhdekDqg6XT7366qtlg8EgO51Ov+1vvvmmDMibN2+OdJf7PWKGLvDj/PPPb7fNN998A8AFF1zgt33+/PlIUvDUBuecc47f3+PHjwegoKCgM2ICzSbhc845h8TERFQqFWq1mr/85S+43W5+//13ADIyMoiPj+euu+5i+fLlATNpgHXr1gFw0003hTzX559/jsvl4i9/+Qsul8v30el0TJs2LWAmqVAoOPvsswP63LK/xx9/PGazmQULFvDRRx9RVVXVbp/37t1LSUkJl112GUrlHz9fg8HA+eefT3Z2ts/3wUuwa2+z2aioqGjzXCUlJSQlJQVs37BhA6eddhqDBw/2bVOpVFx44YV+7cK9Znv37qWsrCzgeUpLS2PKlClBZfPKdfDgwTb7IIgEXfdyh+bZdH19vd+n9RJUS3bs2MFRRx0V8E7xvjt27NjRbT3urwiFLvBjyJAh7baprq4G8HuhA0iSRGJiYtB9Wm/XarUANDU1dUZMCgsLOfnkkzl48CBPPfUU3333HT/99BPPPvus33Hj4uL45ptvmDhxIvfeey9HH300KSkpLF682LfmW1lZiUqlIjk5OeT5ysvLAZg8eTJqtdrvs3r16gBlHBUVhU6nC+izzWbz/X3ZZZfxyiuvUFBQwPnnn09SUhInnHACX3zxRUg5vNc+2H1KSUnB4/FQW1vrt72z176pqSmgD14Zgl2r1tvCvWahnqdQ2wCfXJ19fgTto9Fo2vxNdBSvL0lcXJzv89BDD4VsX11dTUJCQsB27zbvcyP4A5EpTuBHOPHmXgVRXl7u5/3scrl67Ef24YcfYrVaef/99xk+fLhv+7Zt2wLajhs3jrfffhtZlvntt99YsWIF//73v9Hr9dx9990MGjQIt9tNWVlZyAGNyWQCYM2aNX7n6ypXXnklV155JVarlW+//ZbFixczZ84cfv/996Dn8V770tLSgO9KSkpQKpXEx8dHRDaTyURNTU1QGcrKygK2t94W7jVr+Ty1d0wvXrm85xBEHp1Ox4EDByIWHijLcsD7xTu4DEVb76PO5sYYyAiFLugwXiep1atX+8VHr1mzBper8/GmWq027BmX98fc8oUgyzIvvvhim/tMmDCBJ554ghUrVvDLL78AcMYZZ/DQQw/x/PPP8+9//zvovqeffjqSJLF///6wliU6SnR0NGeccQYOh4O5c+eyc+fOoEpw9OjRDB06lDfffJPbb7/ddx2sVivvvfeez/M9EmRmZgZN4DJjxgw+/vhjysvLfTNot9vN6tWr/dqFe81Gjx5NcnIy77zzDrfeeqtve2FhId9//z0pKSkB++Tl5QEwZsyYznRNECY6nS6olaYnSExMDDpB8A7mgs3eD3eEQhd0mKOPPpoFCxawbNkyVCoVp556Kjt37mTZsmXExcX5re12BO9MevXq1YwYMQKdTse4ceOCtp01axYajYYFCxZw5513YrPZeP755wPMzWvXruW5555j7ty5jBgxAlmWef/99zGbzcyaNQuAk08+mcsuu4z//Oc/lJeXM2fOHLRaLVu3biUqKopbbrmF9PR0/v3vf3PfffeRl5fH7NmziY+Pp7y8nB9//JHo6GiWLFnSof5ec8016PV6pkyZwpAhQygrK+Ohhx4iLi6OyZMnB91HqVTy6KOPcskllzBnzhyuu+467HY7jz32GGazmYcffrhDMrTF9OnTeeWVV/j9998ZNWqUb/s//vEPPv74Y0499VT+9a9/ERUVxbPPPhvgnR/uNVMqlSxZsoTrrruO+fPnc9VVV2E2m1myZAlDhgwJ+jxlZ2eTmJgY8vkQ9H/GjRvHW2+9hcvl8ltH3759OwBjx47tLdH6Lr3rkyfoK3g9oisrK0N+1xKbzSbfeuutclJSkqzT6eSsrCx5y5YtclxcnLxo0SJfO6+X+08//eS3f2vvbVmW5fz8fPlPf/qTHBMTIwNBvZtb8sknn8gTJkyQdTqdPHToUPmOO+6Q161b53fcPXv2yAsWLJBHjhwp6/V6OS4uTj7++OPlFStW+B3L7XbLTzzxhDx27FhZo9HIcXFx8oknnih/8sknfu0+/PBDecaMGXJsbKys1Wrl4cOHy/Pnz5e//PJLX5vLL79cjo6Obvc6rly5Up4xY4Y8ePBgWaPRyCkpKfIFF1wg//bbb21eJ68cJ5xwgqzT6eTo6Gj5tNNOC/D6DXVPvffE640eirq6OtlgMAR4ycuyLG/evFnOysqStVqtnJycLN9xxx3yCy+8EPS44VwzWZblF154Qc7IyJA1Go08atQo+ZVXXpHPPfdc+ZhjjvFr5/F45OHDhwd4zwsGFp999pkMyG+//bbf9tmzZ8spKSmyy+XqJcn6LgpZluXeGUoIBhrff/89U6ZM4Y033uDiiy/ubXEEEeCWW27hq6++YufOnT2+Zmk2mxk1ahRz587lhRde8G3/6quv+NOf/sTOnTvJzMzsUZkEPcuf/vQncnJyeOSRR8jIyOCtt97ixRdfZNWqVVxyySW9LV6fQyh0Qaf44osv2LJlC8cddxx6vZ5ff/2Vhx9+mLi4OH777bdeW3cTRJby8nJGjRrFyy+/zPz587vtPGVlZTzwwAPMmDGDxMRECgoKeOKJJ9izZw85OTkcffTRvrYzZswgIyOjTX8JwcDAYrFw33338c4771BTU0NmZib33HMPF110UW+L1icRCl3QKX744Qduu+02du3aRUNDAyaTidNPP52HHnoorNA3Qf9h7dq11NbWBk0BGylqa2v5y1/+wk8//URNTQ1RUVFkZWWxZMkSTjjhBL92Tz31FDfeeGPQGHmB4HBGKHSBQCAQCAYAIrGMQCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggGAUOgCgUAgEAwAhEIXCAQCgWAAIBS6QCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggGAUOgCgUAgEAwAhEIXCAQCgWAAIBS6QCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggGAUOgCgUAgEAwAhEIXCAQCgWAAIBS6QCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggGAUOgCgUAgEAwAhEIXCAQCgWAAIBS6QCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggGAUOgCgUAgEAwAhEIXCAQCgWAAIBS6QCAQCAQDAKHQBQKBQCAYAAiFLhAIBALBAEAodIFAIBAIBgBCoQsEAoFAMAAQCl0gEAgEggHA/wfeorJ+WJFLqgAAAABJRU5ErkJggg==\n", - "text/plain": [ - "" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Image\n", - "Image(filename='skymap.png') " - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAYAAADL1t+KAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAA9hAAAPYQGoP6dpAACfmUlEQVR4nOzdeXwTdf4/8NdMJlebtmkSSlvaUtqiqIB1rX6LsloUhVX8ee/igXiAiqioqBRRDlFEEURFRcETPFhvFxQVtWpBVE5FRend0qZt2iZp2lyTmd8ftZXag7aZZJL0/dxHHi7JZOY9aTLv+dyMKIoiCCGEEBLWWLkDIIQQQoj/KKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQgghEYASOiGEEBIBKKETQggJOV9++SWuv/56jBo1CtHR0Rg2bBguvPBC7N69+6jvLSgoAMMw3T527tw54G3bFRYW4rzzzkN8fDy0Wi1GjhyJpUuXdtpm7969uOiii5CcnIyoqCiMGjUKDz74IFpbW7vs74cffsCkSZMQExMDnU6HCRMmYPv27f34tNpw/X4HIYQQEmDPPfccGhoaMGfOHBx//PGor6/HypUrkZubi08//RRnnXXWUfexbNkyTJgwodNzo0eP9mvbN954A9OmTcO///1vvPbaa9DpdCguLkZ1dXXHNr/++itOO+00HHvssVi9ejVMJhO+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e+8hISHhke2noqICb7zxBqKiouDr64tr165hy5YtuHDhAk6ePIlBgwaZ60ZFRSEkJKTZ+RKkDLdv38aaNWuQl5eHqqoqhIWFYdWqVfjVr37l6NAeS0zo1C5///vfsWLFCpw/fx61tbXw8/PDpEmTsHHjxlb99I26jyNHjqC6utrmLWA7S3V1NebMmYMTJ06gqqoKHh4eMBgMSEtLw+jRoy3qvfPOO1iyZInN38gT/ZQxoRMRESkAbyxDRESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArAhE5ERKQATOhEREQKwIRORESkAEzoRERECsCETkREpABM6ERERArwf53A3MGcK8N9AAAAAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Image('skymap_selected.png')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/rtasci/rtasci/misc/sortObsEvents.py b/rtasci/rtasci/misc/sortObsEvents.py deleted file mode 100644 index cabbd745a9cb4596b07fc9a487982b5c4a127873..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/sortObsEvents.py +++ /dev/null @@ -1,18 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import sys -from os.path import expandvars -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation as sim - -events = sim() -events.input = expandvars(sys.argv[1]) -events.t = [0, 1000] -events.sortObsEvents(key='TIME') - diff --git a/rtasci/rtasci/misc/sphdist.py b/rtasci/rtasci/misc/sphdist.py deleted file mode 100644 index 173183e8ac5a018e594c77008d140718aded4299..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/sphdist.py +++ /dev/null @@ -1,37 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import pandas as pd -from astropy import units as u -from astropy.coordinates import SkyCoord -from os.path import join - -path = expandvars('$RESULTS') -total = join(path, 'deg_flux1-3_off2-4_del50-150.txt') -data = pd.read_csv(total, sep=' ') -print(len(data)) - -# SphDistance calc ---! -compute = 'sph_dist' not in data.keys() -print(f"Check if sph. dist. is missing from DF: {compute}") -if compute: - print('add sph. dist. to DF') - trueRA = 33.057 - trueDEC = -51.841 - true_coord = SkyCoord(ra = trueRA*u.deg, dec = trueDEC*u.deg, frame='fk5') - dist = [] - for i in range(len(data)): - print(i+1) - dist.append(float(true_coord.separation(SkyCoord(ra=data['ra'][i]*u.deg, dec=data['dec'][i]*u.deg, frame='fk5')).deg)) - print(len(dist)) - data['sph_dist'] = dist - data.to_csv(join(path, 'deg_flux1-3_off2-4_del50-150_updated.txt'), sep=' ', index=False, header=True) - -print('exit') - diff --git a/rtasci/rtasci/misc/splitObs.py b/rtasci/rtasci/misc/splitObs.py deleted file mode 100644 index 7bcfd9283fc42b7719381f409a6e91b39fd9b720..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/splitObs.py +++ /dev/null @@ -1,47 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import astropy.units as u -import numpy as np -from os.path import join -from astropy.time import Time -from astropy.io import fits -from gammapy.data import DataStore -from os.path import join - -path = os.path.join(os.path.expandvars('$DATA'), 'obs/crab/') -filename = 'crab_offax.fits' -nameroot = 'crab_offax' -fitsfile = join(path, filename) -nbins = 1 - -data = DataStore.from_events_files([fitsfile]) -observations = data.get_observations() -print(data.info()) - -# split -start = observations[0].gti.time_start -duration = observations[0].gti.time_sum -times = start + np.linspace(0*u.s, duration, nbins) -time_intervals = [Time([tstart, tstop]) for tstart, tstop in zip(times[:-1], times[1:])] -bins = observations.select_time(time_intervals) -if not os.path.exists(path): - os.mkdir(path) -#path.mkdir(exist_ok=True) -for i, b in enumerate(bins): - if i >= 1: - break - hdulist = fits.HDUList([fits.PrimaryHDU()]) - hdu = fits.BinTableHDU(b.events.table, name="EVENTS") - hdulist.append(hdu) - hdu = fits.BinTableHDU(b.gti.table, name="GTI") - hdulist.append(hdu) - hdulist.writeto(join(path, f"{nameroot}_texp{round(duration.value/nbins)}s_n{i+1:02d}.fits"), overwrite=True) - print(join(path,f"{nameroot}_texp{round(duration.value/nbins)}s_n{i+1:02d}.fits")) diff --git a/rtasci/rtasci/misc/wilks.py b/rtasci/rtasci/misc/wilks.py deleted file mode 100644 index 55836e51ff31f6f6002685ccf68b4fc08736809d..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/misc/wilks.py +++ /dev/null @@ -1,115 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -from rtasci.lib.RTAStats import * - -dof = 1 -folder = 'tesi_bkg_1e6_nominal_%ddof_fullE_old' % dof -root = '/home/ambra/Desktop/CTA/projects/rta-pipe' -path = root + '/archive_tests/tesi_02_bkg/' + folder + '/run0406_bkg/run0406_ID000126/csv/' -png_path = root + '/archive_tests/paper2021/png/' -if not os.path.isdir(png_path): - os.mkdir(png_path) - -Nchunk = 20 - -texp = [1, 5, 10, 100] -sigma = [5] -chunk = [i + 1 for i in range(Nchunk)] - -# csvName[texp][chunk] -csvName = [[] * i for i in range(len(texp))] -for i in range(len(chunk)): - for j in range(len(texp)): - csvName[j].append('bkg_%ds_chunk%02d.csv' % (texp[j], chunk[i])) - -# merge files ---! -csvMerged = [] -for j in range(len(texp)): - csvMerged.append('bkg_%ds.csv' % texp[j]) - - fout = open(path + csvMerged[j], 'w+') - # first file ---! - for line in open(path + csvName[j][0]): - fout.write(line) - # remaining files ---! - for i in range(len(chunk) - 1): - f = open(path + csvName[j][i + 1]) - next(f) # skip the header ---! - for line in f: - fout.write(line) - f.close() - fout.close() - -print('data files merge completed') - -show = False -fontsize = 18 -for n in range(len(texp)): - filename = csvMerged[n] - print('!======== texp = ', texp[n], '(s) ========!') - # load DataFrame and column names ---! - df = pd.read_csv(path + filename) - cols = list(df.columns) - trials = len(df[cols[0]]) - print('verify trials = ', trials) - # drop duplicates ---! - df.sort_values(cols[0], inplace=True) - # dropping ALL duplicte values - df.drop_duplicates(subset=cols[0], keep='last', inplace=True) - trials = len(df[cols[0]]) - print('verify trials = ', trials) - # drop NaN ---! - # df = df.dropna() - - # set arrays ---! - trial = np.array(df[cols[0]]) - tsv = np.array(df[cols[-1]]) - - tsv.sort() - - wbin = 1 - nbin = int(tsv.max() / wbin) - if nbin == 0: - nbin = 1 - print('ts bin:', nbin) - - # -------------------------------- STATS ---! - - ts = [] - for i in range(trials): - if tsv[i] < 0.0 or tsv[i] == np.nan: - ts.append(0.0) - else: - ts.append(tsv[i]) - - # chi2, chi2r = chi2_reduced(ts, trials, df=dof, nbin=nbin, width=wbin, var=False) - # print('var=False; chi2=', chi2, '; chi2r=', chi2r) - - - # -------------------------------- PLOT ---! - - fig, ax = ts_wilks(ts, len(ts), df=dof, nbin=nbin, width=wbin, figsize=(10, 6), fontsize=fontsize, - title='prod3b-v2: South_z40_0.5h (texp=%ds)' % texp[n], show=True, usetex=False, - filename=png_path + filename.replace('.csv', '_wilks.png'), ylim=(1e-7, 2e0), - xlim=(0.0, 30)) - - fig, ax = p_values(ts, len(ts), df=dof, nbin=nbin, width=wbin, figsize=(10, 6), fontsize=fontsize, - title='prod3b-v2: South_z40_0.5h (texp=%ds)' % texp[n], show=False, usetex=False, - filename=png_path + filename.replace('.csv', '_pvalues.png'), ylim=(1e-7, 2e0), - xlim=(0.0, 30)) - - fig, ax = ts_wilks_cumulative(ts, len(ts), df=dof, nbin=nbin, width=wbin, figsize=(10, 6), - fontsize=fontsize, show=False, usetex=False, - title='prod3b-v2: South_z40_0.5h (texp=%ds)' % texp[n], - filename=png_path + filename.replace('.csv', '_cumulative.png')) - - - diff --git a/rtasci/rtasci/pipelines/__init__.py b/rtasci/rtasci/pipelines/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/rtasci/rtasci/pipelines/ctools1d.py b/rtasci/rtasci/pipelines/ctools1d.py deleted file mode 100644 index 90f2a33885facf5515b8f31a7bb52d09b6a83e6b..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/ctools1d.py +++ /dev/null @@ -1,259 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis, onoff_counts -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * - - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}-{caldb}-{irf}-seed{start_count+1:06d}-{start_count+trials:06d}.txt" - if isfile(logname): - os.remove(logname) - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - if args.print.lower() == 'true': - print(f'Seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - model = join(expandvars(cfg.get('model')), 'grb.xml') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - - # --------------------------------------------------- loop exposure times ---!!! - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - elif cfg.get('lightcurve'): - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = exp - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - if t == len(times) and cfg.get('lightcurve'): - break - # selection ---! - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - grb.input = selphlist - grb.model = model - grb.src_name = 'GRB' - grb.target = target - grb.output = onoff - grb.run_onoff(prefix=onoff.replace('.xml',''), ebins=10, etruemin=0.02, etruemax=200, etruebins=20, ebins_alg='LOG', maxoffset=2.5, bkgskip=0) - # aperture photometry ---! - oncounts, offcounts, excess, alpha = onoff_counts(pha=onoff) - sigma = li_ma(oncounts, offcounts, alpha) - if args.print.lower() == 'true': - print(f'Photometry on={oncounts} off={offcounts} ex={excess} a={alpha}') - print('Li&Ma significance:', sigma) - # fix parameters - onoff_model = onoff.replace('.xml','_model.xml') - xml = ManageXml(onoff_model) - xml.setTsTrue() - xml.parametersFreeFixed(src_free=['Prefactor'], bkg_free=[]) - xml.setModelParameters(parameters=['RA', 'DEC', 'Index'], values=[target[0], target[1], cfg.get('index')]) - xml.closeXml() - # fit ---! - grb.input = onoff - grb.model = onoff_model - grb.output = fit - grb.run_maxlikelihood() - # stats ---! - xml = ManageXml(fit) - try: - # target coords ---! - coords = xml.getRaDec() - ra = coords[0][0] - dec = coords[1][0] - if args.print.lower() == 'true': - print(f'TARGET=[{ra}, {dec}]') - # spectral ---! - spectra = xml.getSpectral() - err = xml.getPrefError()[0] - # ts ---! - ts = xml.getTs()[0] - sqrt_ts = np.sqrt(ts) - # flux ---! - index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] - flux = phflux_powerlaw(index, pref, pivot, grb.e, unit='TeV') - flux_err = phflux_powerlaw(index, err, pivot, grb.e, unit='TeV') - except IndexError: - sqrt_ts = np.nan - print('Candidate not found.') - - if sigma < 5 or grb.t[1] > (cfg.get('tobs')+cfg.get('delay')): - break - - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {texp} {sqrt_ts} {flux} {flux_err} {ra} {dec} {pref} {np.abs(index)} {pivot} {oncounts} {offcounts} {alpha} {excess} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} ctools1d\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec prefactor index scale on off alpha excess sigma offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') \ No newline at end of file diff --git a/rtasci/rtasci/pipelines/ctools1d_blind.py b/rtasci/rtasci/pipelines/ctools1d_blind.py deleted file mode 100644 index cafaac50547b0a7b9a56217e170eaba2db4a191e..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/ctools1d_blind.py +++ /dev/null @@ -1,319 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -tstamp = time.time() -import os -import sys -import argparse -import numpy as np -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * -from astropy.coordinates import SkyCoord - -runtime = time.time() - tstamp - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') - -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - if args.print.lower() == 'true': - print(f'Target true = {target} deg') - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - if args.print.lower() == 'true': - print(f'Pointing = {pointing} deg') - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}-{caldb}-{irf}-seed{start_count+1:06d}-{start_count+trials:06d}.txt" - if isfile(logname): - os.remove(logname) - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - if args.print.lower() == 'true': - print(f'Seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # --------------------------------------------------- loop exposure times ---!!! - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - elif cfg.get('lightcurve'): - print('here') - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = [exp] - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - tcpu = time.time() - if t == len(times) and cfg.get('lightcurve'): - break - # selection ---! - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - - # on/off ---! - if '.fits' in selphlist: - events_type = 'events_filename' - else: - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_filename' - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # skymap ---! - grb.input = selphlist - grb.output = sky - grb.run_skymap(wbin=cfg.get('skypix'), roi_factor=cfg.get('skyroifrac')) - # blind-search ---! - grb.sigma = cfg.get('sgmthresh') - grb.corr_rad = cfg.get('smooth') - grb.max_src = cfg.get('maxsrc') - grb.input = sky - grb.output = candidates - grb.run_blindsearch() - # position ---! - xml = ManageXml(candidates) - try: - target = xml.getRaDec() - ra = target[0][0] - dec = target[1][0] - offset = SkyCoord(ra=ra, dec=dec, unit='deg', frame='icrs').separation(SkyCoord(ra=pointing[0], dec=pointing[1], unit='deg', frame='icrs')).deg - target = (ra, dec) - if args.print.lower() == 'true': - print(f'Target = [{ra}, {dec}]') - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_filename' - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # aperture photometry ---! - phm = Photometrics({events_type: selphlist}) - opts = phm_options(erange=grb.e, texp=texp, time_int=grb.t, target=target, pointing=pointing, index=-2.1, save_off_reg=f"{expandvars(cfg.get('data'))}/rta_products/{runid}/texp{exp}s_{name}_off_regions.reg", irf_file=join(expandvars('$CTOOLS'), f"share/caldb/data/cta/{caldb}/bcf/{irf}/irf_file.fits")) - off_regions = find_off_regions(phm, opts['background_method'], target, pointing, opts['region_radius'], verbose=opts['verbose'], save=opts['save_off_regions']) - on, off, alpha, excess, sigma, err_note = counting(phm, target, opts['region_radius'], off_regions, e_min=opts['energy_min'], e_max=opts['energy_max'], t_min=opts['begin_time'], t_max=opts['end_time'], draconian=False) - if args.print.lower() == 'true': - print(f'Photometry on={on} off={off} ex={excess} a={alpha}') - print('Li&Ma significance:', sigma) - - except KeyError: - if args.print.lower() == 'true': - print('No candidates found.') - ra, dec, on, off, alpha, excess, sigma, offset = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan , np.nan - - if sigma < 5 or offset > cfg.get('roi')-0.5 or grb.t[1] > (cfg.get('tobs')+cfg.get('delay')): - break - - else: - # flux ---! - src = {'ra': target[0], 'dec': target[1], 'rad': opts['region_radius']} - conf = ObjectConfig(opts) - region_eff_resp = aeff_eval(conf, src, {'ra': pointing[0], 'dec': pointing[1]}) - livetime = opts['end_time'] - opts['begin_time'] - flux = excess / region_eff_resp / livetime - gamma = opts['power_law_index'] - if args.print.lower() == 'true': - print(f'Flux={flux}') - - if sigma < 5 and cfg.get('lightcurve'): - if exp < max(cfg.get('exposure')) or grb.t[0] > cfg.get('tobs'): - if args.print.lower() == 'true': - print(f'No significant detection, increase exposure.') - break - else: - sys.exit(f"No significant detection with max. exposure {texp} s.") - - # save results ---! - k0, e0, flux_err, sqrt_ts = np.nan, np.nan, np.nan, np.nan - timing = runtime + time.time() - tcpu - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {grb.t[1]-grb.t[-0]} {sqrt_ts} {flux} {flux_err} {ra} {dec} {k0} {gamma} {e0} {on} {off} {alpha} {excess} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} ctools1d_blind {runtime}\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec prefactor index scale on off alpha excess sigma offset delay scaleflux caldb irf pipe runtime\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb, phm - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') - - diff --git a/rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py b/rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py deleted file mode 100644 index 2ecebfe4e1b35100aca174dcaa73b6f7eae423ec..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py +++ /dev/null @@ -1,222 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -from os.path import isdir, join, isfile -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import phflux_powerlaw, get_pointing, get_mergermap -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw -from rtasci.cfg.Config import Config -from rtasci.lib.RTAVisualise import plotSkymap -from rtasci.aph.utils import * - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('caldb')}-{cfg.get('irf')}_seed{start_count+1:06d}-{start_count+1+trials:06d}_flux{cfg.get('scalefluxfactor')}_offset{offset}_delay{cfg.get('delay')}.txt" - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - if isfile(logname): - os.remove(logname) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - true_coords = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - #print(f'seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # ---------------------------------------------------------- loop exposure times ---!!! - - if cfg.get('cumulative'): - n = int(cfg.get('tobs') / cfg.get('exposure')[0]) - times = [cfg.get('exposure')[0]*(i+1) for i in range(n)] - if times[-1] < cfg.get('tobs'): - times.append(cfg.get('tobs')) - else: - times = cfg.get('exposure') - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - # selection ---! - for texp in times: - selphlist = phlist.replace(f'{name}', f'texp{texp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = cfg.get('caldb') - grb.irf = cfg.get('irf') - grb.roi = cfg.get('roi') - grb.e = [cfg.get('emin'), cfg.get('emax')] - grb.t = [cfg.get('delay'), cfg.get('delay')+texp] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{texp}s_') - grb.run_selection(prefix=prefix) - # aperture photometry ---! - if '.fits' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=true_coords, events_type='events_filename') - elif '.xml' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=true_coords, events_type='events_list') - sigma = li_ma(results['on'], results['off'], results['alpha']) - if args.print.lower() == 'true': - print('Photometry counts:', results) - print('Li&Ma significance:', sigma) - # skymap ---! - grb.input = selphlist - grb.output = sky - grb.run_skymap(wbin=cfg.get('skypix'), roi_factor=cfg.get('skyroifrac')) - # blind-search ---! - grb.sigma = cfg.get('sgmthresh') - grb.corr_rad = cfg.get('smooth') - grb.max_src = cfg.get('maxsrc') - grb.input = sky - grb.output = candidates - grb.run_blindsearch() - if cfg.get('plotsky'): - plotSkymap(sky, reg=candidates.replace('.xml', '.reg'), suffix=f'{texp}s', png=png) - # modify model - detection = ManageXml(candidates) - detection.modXml(overwrite=True) - detection.setTsTrue() - detection.parametersFreeFixed(src_free=['Prefactor']) - detection.closeXml() - # fit ---! - grb.input = selphlist - grb.model = candidates - grb.output = fit - grb.run_maxlikelihood() - # stats ---! - xml = ManageXml(fit) - try: - coords = xml.getRaDec() - ra = coords[0][0] - dec = coords[1][0] - ts = xml.getTs()[0] - sqrt_ts = np.sqrt(ts) - except IndexError: - sqrt_ts = np.nan - print('Candidate not found.') - if sqrt_ts >= 0: - # flux ---! - spectra = xml.getSpectral() - index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] - err = xml.getPrefError()[0] - flux = phflux_powerlaw(index, pref, pivot, grb.e, unit='TeV') - flux_err = phflux_powerlaw(index, err, pivot, grb.e, unit='TeV') - else: - ra, dec, ts, sqrt_ts, flux, flux_err = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan - - row = f"{runid} {count} {texp} {sqrt_ts} {flux} {flux_err} {ra} {dec} {results['on']} {results['off']} {results['alpha']} {results['excess']} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {cfg.get('caldb')} {cfg.get('irf')}\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed texp sqrt_ts flux flux_err ra dec oncounts offcounts alpha excess sigma offset delay scaleflux caldb irf\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') \ No newline at end of file diff --git a/rtasci/rtasci/pipelines/ctools3d_unbinned.py b/rtasci/rtasci/pipelines/ctools3d_unbinned.py deleted file mode 100644 index ef2785ef1fa46aea442d3036e3e669f890a1c7d2..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/ctools3d_unbinned.py +++ /dev/null @@ -1,258 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import sys -import argparse -import numpy as np -from os.path import isdir, join, isfile -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}_offset{offset}_seed{start_count+1:06d}-{start_count+1+trials:06d}.txt" - if isfile(logname): - os.remove(logname) # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - #print(f'seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - model = phlist.replace('.fits', '_grb.xml').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - model = phlist.replace('.xml', '_grb.xml').replace('/obs/', '/rta_products/') - os.system(f"cp {join(expandvars(cfg.get('model')), f'{runid}.xml')} {model}") - fit = model.replace('grb.xml', 'fit.xml') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # -------------------------------------------- loop exposure times ---!!! - - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - if cfg.get('lightcurve'): - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = exp - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - if t == len(times) and cfg.get('lightcurve'): - break - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if texp != exp: - raise ValueError("exp != texp") - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - # aperture photometry ---! - if '.fits' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=target, events_type='events_filename') - elif '.xml' in selphlist: - results = photometrics_counts(selphlist, pointing=pointing, true_coords=target, events_type='events_list') - sigma = li_ma(results['on'], results['off'], results['alpha']) - if args.print.lower() == 'true': - print('Photometry counts:', results) - print('Li&Ma significance:', sigma) - - # modify model - detection = ManageXml(model) - detection.modXml(overwrite=True) - detection.setTsTrue() - detection.parametersFreeFixed(src_free=['Prefactor']) - detection.setModelParameters(parameters=['RA', 'DEC', 'Index'], values=[target[0], target[1], cfg.get('index')]) - detection.closeXml() - # fit ---! - grb.input = selphlist - grb.model = model - grb.output = fit - grb.run_maxlikelihood() - # stats ---! - xml = ManageXml(fit) - try: - coords = xml.getRaDec() - ra = coords[0][0] - dec = coords[1][0] - ts = xml.getTs()[0] - sqrt_ts = np.sqrt(ts) - if args.print.lower() == 'true': - print('TS significance:', sqrt_ts) - except IndexError: - sqrt_ts = np.nan - print('Candidate not found.') - if sqrt_ts >= 0: - # flux ---! - spectra = xml.getSpectral() - index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] - err = xml.getPrefError()[0] - flux = phflux_powerlaw(index, pref, pivot, grb.e, unit='TeV') - flux_err = phflux_powerlaw(index, err, pivot, grb.e, unit='TeV') - else: - ra, dec, ts, sqrt_ts, flux, flux_err = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan - - if sqrt_ts < 5 and cfg.get('lightcurve') and cfg.get('cumulative'): - if exp < max(cfg.get('exposure')): - if args.print.lower() == 'true': - print(f'No significance detection, increase exposure.') - break - else: - sys.exit(f"No significance detection with maximum exposure {texp} s.") - - if sigma < 5 or grb.t[1] > (cfg.get('tobs')+cfg.get('delay')): - break - - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {exp} {sqrt_ts} {flux} {flux_err} {ra} {dec} {results['on']} {results['off']} {results['alpha']} {results['excess']} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} ctools3d_unbinned\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec oncounts offcounts alpha excess sigma offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') \ No newline at end of file diff --git a/rtasci/rtasci/pipelines/gammapy1d.py b/rtasci/rtasci/pipelines/gammapy1d.py deleted file mode 100644 index fedb80bb3f278f2765d33cf877d0a2b293b880db..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/gammapy1d.py +++ /dev/null @@ -1,259 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAGammapyAnalysis import * -from rtasci.lib.RTAUtils import * -from rtasci.lib.RTAUtilsGW import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - girf = load_cta_irfs(f"{expandvars('$CTOOLS')}/share/caldb/data/cta/{caldb}/bcf/{irf}/irf_file.fits") - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}-{caldb}-{irf}-seed{start_count+1:06d}-{start_count+trials:06d}.txt" - if isfile(logname): - os.remove(logname) - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - if args.print.lower() == 'true': - print(f'Seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # --------------------------------------------------- loop exposure times ---!!! - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - elif cfg.get('lightcurve'): - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = exp - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - if t == len(times) and cfg.get('lightcurve'): - break - # selection ---! - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - - # load the event list - events = EventList.read(selphlist, hdu='EVENTS') - gti = GTI.read(selphlist, hdu='GTI') - pointing = events.pointing_radec - observation = Observation.create(pointing=pointing, obs_id=f'{count:02d}', tstart=gti.table['START'] * u.s, tstop=gti.table['STOP'] * u.s, irfs=girf, reference_time=gti.time_ref) - observation._events = events - observations = Observations() - observations.append(observation) - observation.fixed_pointing_info - - # initialise gammapy configuration ---! - config = gammapy_config(cfg=cfg, target=target, obs=selphlist, blind=cfg.get('blind')) - # reduce dataset ---! - grb2 = Analysis(config) - grb2.observations = observations - grb2.get_datasets() - # significance - stats = grb2.datasets.info_table() - sqrt_ts = np.nan - oncounts = stats['counts'][0] - offcounts = stats['counts_off'][0] - excess = stats['excess'][0] - alpha = stats['alpha'][0] - sigma = stats['sqrt_ts'][0] - if args.print.lower() == 'true': - print(f"on = {oncounts}; off={offcounts}; excess={excess}; alpha={alpha}; sigma={sigma}") - - if sigma < 5: - if args.print.lower() == 'true': - print("Sigma < 5 => break") - break - data = grb2.datasets.stack_reduce(name="stacked") - model = set_model(default=True, target=target, source='GRB', index=cfg.get('index')) - data.models = model[0] - # fit ---! - fit = Fit([data]) - result = fit.run() - # flux ---! - phflux = model[1].integral_error(cfg.get('emin')*u.TeV, cfg.get('emax')*u.TeV) - flux = phflux.value[0] - flux_err = phflux.value[1] - # save spectral ---! - k0 = model[1].amplitude.value - gamma = model[1].index.value - e0 = model[1].reference.value - # save target coords ---! - ra = target[0] - dec = target[1] - if args.print.lower() == 'true': - print(f"flux={flux} +/- {flux_err}") - print(f"Spectral k0={k0}; gamma={gamma}; e0={e0}") - - if sigma < 5 or grb.t[1] > (cfg.get('tobs')+cfg.get('delay')): - break - - # save data ---! - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {exp} {sqrt_ts} {flux} {flux_err} {ra} {dec} {k0} {gamma} {e0} {oncounts} {offcounts} {alpha} {excess} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} gammapy1d\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec prefactor index scale on off alpha excess sigma offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") -print('...done.\n') \ No newline at end of file diff --git a/rtasci/rtasci/pipelines/gammapy1d_blind.py b/rtasci/rtasci/pipelines/gammapy1d_blind.py deleted file mode 100644 index 05b84ed5e76243e81c27fc25de3850a4d9c5580b..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/gammapy1d_blind.py +++ /dev/null @@ -1,366 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import sys -import argparse -import numpy as np -import astropy.units as u -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import * -from rtasci.lib.RTAUtilsGW import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from astropy.coordinates import SkyCoord -from astropy.coordinates import SkyCoord -from regions import CircleSkyRegion -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from gammapy.modeling.models import PointSpatialModel, PowerLawSpectralModel, SkyModel - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') - -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - if args.print.lower() == 'true': - print(f'Target True = {target} deg') - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - if args.print.lower() == 'true': - print(f'Pointing = {pointing} deg') - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}-{caldb}-{irf}-seed{start_count+1:06d}-{start_count+trials:06d}.txt" - if isfile(logname): - os.remove(logname) - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - if args.print.lower() == 'true': - print(f'Seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # --------------------------------------------------- loop exposure times ---!!! - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - if cfg.get('lightcurve'): - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = exp - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - if t == len(times) and cfg.get('lightcurve'): - break - # selection ---! - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - - # on/off ---! - if '.fits' in selphlist: - events_type = 'events_filename' - else: - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_filename' - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - - # load the event list - events = EventList.read(selphlist, hdu='EVENTS') - gti = GTI.read(selphlist, hdu='GTI') - point = events.pointing_radec - observation = Observation.create(pointing=point, obs_id=f'{count:02d}', tstart=gti.table['START'] * u.s, tstop=gti.table['STOP'] * u.s, irfs=irf, reference_time=gti.time_ref) - observation._events = events - observations = Observations() - observations.append(observation) - observation.fixed_pointing_info - # initialise gammapy configuration ---! - #config = gammapy_config(cfg=cfg, obs=selphlist, pointing=point) - # - config = AnalysisConfig() - config.observations.datastore = "" - - config.datasets.type = "3d" # Analysis type is 3D - config.datasets.stack = False # We keep track of datasets in all bunches - - config.datasets.geom.wcs.skydir = { - "lon": point.ra, - "lat": point.dec, - "frame": "icrs", - } - config.datasets.geom.wcs.fov = {"width": "10 deg", "height": "10 deg"} - config.datasets.geom.wcs.binsize = "0.02 deg" - - # The FoV radius to use for cutouts - config.datasets.background.method="fov_background" - #config.datasets.background.exclusion=None - config.datasets.geom.selection.offset_max = 2.5 * u.deg - config.datasets.safe_mask.methods = ["aeff-default", "offset-max"] - - # We now fix the energy axis for the counts map - (the reconstructed energy binning) - config.datasets.geom.axes.energy.min = "0.04 TeV" - config.datasets.geom.axes.energy.max = "150 TeV" - config.datasets.geom.axes.energy.nbins = 20 - - # We now fix the energy axis for the IRF maps (exposure, etc) - (the true enery binning) - config.datasets.geom.axes.energy_true.min = "0.02 TeV" - config.datasets.geom.axes.energy_true.max = "200 TeV" - config.datasets.geom.axes.energy_true.nbins = 30 - - - - # reduce dataset ---! - grb2 = Analysis(config) - grb2.observations = observations - grb2.get_datasets() - stacked = grb2.datasets.stack_reduce(name="stacked_3d") - estimator = ExcessMapEstimator(correlation_radius=f"{cfg.get('sgmthresh')} deg", selection_optional=[]) - maps = estimator.run(stacked) - hotspots_table = find_peaks(maps["sqrt_ts"].get_image_by_idx((0,)), threshold=cfg.get('sgmthresh'), min_distance='0.5 deg') - try: - hotspots = SkyCoord(hotspots_table["ra"], hotspots_table["dec"]) - print(hotspots) - ra_gammapy = hotspots.ra[0].deg - dec_gammapy = hotspots.dec[0].deg - if args.print.lower() == 'true': - print(f'Target GAMMAPY = [{ra_gammapy}, {dec_gammapy}]') - - - - - - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_filename' - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # aperture photometry ---! - phm = Photometrics({events_type: selphlist}) - opts = phm_options(erange=grb.e, texp=texp, time_int=grb.t, target=target, pointing=pointing, index=-2.1, irf=irf, caldb=caldb, save_off_reg=f"{expandvars(cfg.get('data'))}/rta_products/{runid}/texp{exp}s_{name}_off_regions.reg", irf_file=join(expandvars('$CTOOLS'), f"share/caldb/data/cta/{caldb}/bcf/{irf}/irf_file.fits")) - off_regions = find_off_regions(phm, opts['background_method'], target, pointing, opts['region_radius'], verbose=opts['verbose'], save=opts['save_off_regions']) - on, off, alpha, excess, sigma, err_note = counting(phm, target, opts['region_radius'], off_regions, e_min=opts['energy_min'], e_max=opts['energy_max'], t_min=opts['begin_time'], t_max=opts['end_time'], draconian=False) - if args.print.lower() == 'true': - print(f'Photometry on={on} off={off} ex={excess} a={alpha}') - print('Li&Ma significance:', sigma) - - except KeyError: - if args.print.lower() == 'true': - print('No candidates found.') - ra, dec, on, off, alpha, excess, sigma, offset = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan , np.nan - - if sigma < 5 or offset.deg > cfg.get('roi')-0.5 or grb.t[1] >= (cfg.get('tobs')+cfg.get('delay')): - break - - else: - # flux ---! - src = {'ra': target[0], 'dec': target[1], 'rad': opts['region_radius']} - conf = ObjectConfig(opts) - region_eff_resp = aeff_eval(conf, src, {'ra': pointing[0], 'dec': pointing[1]}) - livetime = opts['end_time'] - opts['begin_time'] - flux = excess / region_eff_resp / livetime - gamma = opts['power_law_index'] - if args.print.lower() == 'true': - print(f'Flux={flux}') - - if sigma < 5 and cfg.get('lightcurve'): - if exp < max(cfg.get('exposure')) or grb.t[0] >= cfg.get('tobs'): - if args.print.lower() == 'true': - print(f'No significant detection, increase exposure.') - break - else: - sys.exit(f"No significant detection with max. exposure {texp} s.") - - # save results ---! - k0, e0, flux_err, sqrt_ts = np.nan, np.nan, np.nan, np.nan - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {grb.t[1]-grb.t[-0]} {sqrt_ts} {flux} {flux_err} {ra} {dec} {k0} {gamma} {e0} {on} {off} {alpha} {excess} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} ctools1d_blind\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec prefactor index scale on off alpha excess sigma offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb, phm - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') - - diff --git a/rtasci/rtasci/pipelines/rtatool1d.py b/rtasci/rtasci/pipelines/rtatool1d.py deleted file mode 100644 index f9be13981eb7bde0fdfd2cb9c75b38c6b53ca8e5..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/rtatool1d.py +++ /dev/null @@ -1,258 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import sys -import argparse -import time -import numpy as np -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import * -from rtasci.lib.RTAUtilsGW import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# CALDB ---! -if type(cfg.get('caldb')) == str: - caldbs = [cfg.get('caldb')] -else: - caldbs = cfg.get('caldb') -caldbs = sorted(caldbs) - -# IRF ---! -if type(cfg.get('irf')) == str: - irfs = [cfg.get('irf')] -else: - irfs = cfg.get('irf') -irfs = sorted(irfs) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') - -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # ------------------------------------------------------ loop caldb ---!!! - for caldb in caldbs: - if args.print.lower() == 'true': - print(f'Calibration database: {caldb}') - # ------------------------------------------------------ loop irf ---!!! - for irf in irfs: - if args.print.lower() == 'true': - print(f'Instrument response function: {irf}') - erange = check_energy_thresholds(erange=[cfg.get('emin'), cfg.get('emax')], irf=irf) - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('tool')}{cfg.get('type')}-{caldb}-{irf}-seed{start_count+1:06d}-{start_count+trials:06d}.txt" - if isfile(logname): - os.remove(logname) - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - if args.print.lower() == 'true': - print(f'Seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # --------------------------------------------------- loop exposure times ---!!! - for exp in cfg.get('exposure'): - if cfg.get('cumulative'): - times = increase_exposure(start=exp, stop=cfg.get('tobs'), function='linear') - elif cfg.get('lightcurve'): - times = lightcurve_base_binning(start=cfg.get('delay'), stop=cfg.get('tobs'), exposure=exp) - else: - times = exp - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - if len(times) == 0: - times = [times] - - # ---------------------------------------------------------- loop binning ---!!! - for t in times: - if t == len(times) and cfg.get('lightcurve'): - break - # selection ---! - selphlist = phlist.replace(f'{name}', f'texp{exp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = caldb - grb.irf = irf - grb.roi = cfg.get('roi') - grb.e = erange - if cfg.get('lightcurve'): - grb.t = [t, t + exp] - else: - grb.t = [cfg.get('delay'), cfg.get('delay')+t] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - texp = grb.t[1] - grb.t[0] - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{exp}s_') - grb.run_selection(prefix=prefix) - - # on/off ---! - if '.fits' in selphlist: - events_type = 'events_filename' - else: - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - - # aperture photometry ---! - phm = Photometrics({events_type: selphlist}) - pointing = tuple(pointing) - opts = phm_options(erange=grb.e, texp=texp, time_int=grb.t, target=target, pointing=pointing, index=cfg.get('index'), save_off_reg=f"{expandvars(cfg.get('data'))}/rta_products/{runid}/texp{exp}s_{name}_off_regions.reg", irf_file=join(expandvars('$CTOOLS'), f"share/caldb/data/cta/{caldb}/bcf/{irf}/irf_file.fits")) - off_regions = find_off_regions(phm, opts['background_method'], target, pointing, opts['region_radius'], verbose=opts['verbose'], save=opts['save_off_regions']) - oncounts, offcounts, alpha, excess, sigma, err_note = counting(phm, target, opts['region_radius'], off_regions, e_min=opts['energy_min'], e_max=opts['energy_max'], t_min=opts['begin_time'], t_max=opts['end_time'], draconian=False) - if args.print.lower() == 'true': - print(f'Photometry on={oncounts} off={offcounts} ex={excess} a={alpha}') - print('Li&Ma significance:', sigma) - - # flux ---! - src = {'ra': target[0], 'dec': target[1], 'rad': opts['region_radius']} - conf = ObjectConfig(opts) - start = time.time() - region_eff_resp = aeff_eval(conf, src, {'ra': pointing[0], 'dec': pointing[1]}) - print(time.time()-start) - livetime = opts['end_time'] - opts['begin_time'] - flux = excess / region_eff_resp / livetime - k0, e0, flux_err, sqrt_ts = np.nan, np.nan, np.nan, np.nan - ra = target[0] - dec = target[1] - gamma = opts['power_law_index'] - if args.print.lower() == 'true': - print(f'Flux={flux}') - - if sigma < 5 or grb.t[1] > (cfg.get('tobs')+cfg.get('delay')): - break - - elif sigma < 5 and cfg.get('lightcurve'): - if exp < max(cfg.get('exposure')): - if args.print.lower() == 'true': - print(f'No significance detection, increase exposure.') - break - else: - sys.exit(f"No significance detection with maximum exposure {texp} s.") - - # save results ---! - row = f"{runid} {count} {grb.t[0]} {grb.t[1]} {grb.t[1]-grb.t[-0]} {sqrt_ts} {flux} {flux_err} {ra} {dec} {k0} {gamma} {e0} {oncounts} {offcounts} {alpha} {excess} {sigma} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {caldb} {irf} rtatool1d\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed start stop texp sqrt_ts flux flux_err ra dec prefactor index scale on off alpha excess sigma offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb, phm - if args.remove.lower() == 'true': - # remove files ---! - os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') - - diff --git a/rtasci/rtasci/pipelines/rtatool1d_blind.py b/rtasci/rtasci/pipelines/rtatool1d_blind.py deleted file mode 100644 index 17c146ab9dea99156dd2dd3bbdd719054aac19e5..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/pipelines/rtatool1d_blind.py +++ /dev/null @@ -1,311 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -import astropy.units as u -from os.path import isdir, join, isfile, expandvars -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTAUtils import get_pointing, get_mergermap, phm_options -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from astropy.coordinates import SkyCoord -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from rtasci.lib.RTAGammapyAnalysis import * - - -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# general ---! -start_count = cfg.get('start_count') -trials = cfg.get('trials') -if cfg.get('offset') == 'str': - offset = cfg.get('offset').upper() -else: - offset = cfg.get('offset') -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - raise ValueError(f'Missing obs parent folder in {datapath}') -if not isdir(f"{datapath}/outputs"): - os.mkdir(f"{datapath}/outputs") -if not isdir(f"{datapath}/rta_products"): - os.mkdir(f"{datapath}/rta_products") -if not isdir(f"{datapath}/skymaps"): - os.mkdir(f"{datapath}/skymaps") - - -# ------------------------------------------------------ loop runid --- !!! -for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - # outputs - logname = f"{datapath}/outputs/{runid}/{cfg.get('caldb')}-{cfg.get('irf')}_seed{start_count+1:06d}-{start_count+1+trials:06d}_flux{cfg.get('scalefluxfactor')}_offset{offset}_delay{cfg.get('delay')}.txt" - if not isdir(f"{datapath}/outputs/{runid}"): - os.mkdir(f"{datapath}/outputs/{runid}") - if not isdir(f"{datapath}/rta_products/{runid}"): - os.mkdir(f"{datapath}/rta_products/{runid}") - png = f"{datapath}/skymaps/{runid}" - if not isdir(png): - os.mkdir(png) - if isfile(logname): - os.remove(logname) - # grb path ---! - grbpath = join(datapath, 'obs', runid) - if not isdir(grbpath): - raise FileExistsError(f"Directory {runid} not found in {datapath}/obs") - rtapath = f'{datapath}/rta_products/{runid}' - # true coords ---! - - target = get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits") - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # ------------------------------------------------------ loop trials ---!!! - for i in range(trials): - count = start_count + i + 1 - #print(f'seed = {count:06d}') - name = f'ebl{count:06d}' - if args.merge.lower() == 'true': - events_type = 'events_filename' - phlist = join(grbpath, name+'.fits') - sky = phlist.replace('.fits', '_sky.fits').replace('/obs/', '/rta_products/') - else: - events_type = 'events_list' - phlist = join(grbpath, f'{name}.xml') - sky = phlist.replace('.xml', '_sky.fits').replace('/obs/', '/rta_products/') - candidates = sky.replace('_sky.fits', '_sources.xml') - fit = candidates.replace('sources', 'fit') - if args.print.lower() == 'true': - print(f'Input observation: {phlist}') - if not isfile(phlist): - print(f'Missing observation {phlist}. \nSkip runid {runid}.') - break - - # ---------------------------------------------------------- loop exposure times ---!!! - - if cfg.get('cumulative'): - n = int(cfg.get('tobs') / cfg.get('exposure')[0]) - times = [cfg.get('exposure')[0]*(i+1) for i in range(n)] - if times[-1] < cfg.get('tobs'): - times.append(cfg.get('tobs')) - else: - times = cfg.get('exposure') - if args.print.lower() == 'true': - print(f"Time selections = {times} s") - # selection ---! - for texp in times: - # load irf - irf = load_cta_irfs(f"{expandvars('$CTOOLS')}/share/caldb/data/cta/{cfg.get('caldb')}/bcf/{cfg.get('irf')}/irf_file.fits") - obs_id = count - if args.print.lower() == 'true': - print(f"Exposure = {texp} s") - selphlist = phlist.replace(f'{name}', f'texp{texp}s_{name}') - # initialise ---! - grb = RTACtoolsAnalysis() - grb.caldb = cfg.get('caldb') - grb.irf = cfg.get('irf') - grb.roi = cfg.get('roi') - grb.e = [cfg.get('emin'), cfg.get('emax')] - grb.t = [cfg.get('delay'), cfg.get('delay')+texp] - if args.print.lower() == 'true': - print(f"Selection t = {grb.t} s") - grb.input = phlist - grb.output = selphlist - if args.merge.lower() == 'true': - grb.run_selection() - else: - prefix = join(grbpath, f'texp{texp}s_') - grb.run_selection(prefix=prefix) - - # ------------------------------------ GAMMAPY !!! - # load the event list - events = EventList.read(selphlist, hdu='EVENTS') - gti = GTI.read(selphlist, hdu='GTI') - point = events.pointing_radec - observation = Observation.create(pointing=point, obs_id=f'{obs_id:02d}', tstart=gti.table['START'] * u.s, tstop=gti.table['STOP'] * u.s, irfs=irf, reference_time=gti.time_ref) - observation._events = events - observations = Observations() - observations.append(observation) - observation.fixed_pointing_info - # initialise gammapy configuration ---! - #config = gammapy_config(cfg=cfg, obs=selphlist, pointing=point) - # - config = AnalysisConfig() - config.observations.datastore = "" - - config.datasets.type = "3d" # Analysis type is 3D - config.datasets.stack = False # We keep track of datasets in all bunches - - config.datasets.geom.wcs.skydir = { - "lon": point.ra, - "lat": point.dec, - "frame": "icrs", - } - config.datasets.geom.wcs.fov = {"width": "10 deg", "height": "10 deg"} - config.datasets.geom.wcs.binsize = "0.02 deg" - - # The FoV radius to use for cutouts - config.datasets.background.method="fov_background" - #config.datasets.background.exclusion=None - config.datasets.geom.selection.offset_max = 2.5 * u.deg - config.datasets.safe_mask.methods = ["aeff-default", "offset-max"] - - # We now fix the energy axis for the counts map - (the reconstructed energy binning) - config.datasets.geom.axes.energy.min = "0.04 TeV" - config.datasets.geom.axes.energy.max = "150 TeV" - config.datasets.geom.axes.energy.nbins = 20 - - # We now fix the energy axis for the IRF maps (exposure, etc) - (the true enery binning) - config.datasets.geom.axes.energy_true.min = "0.02 TeV" - config.datasets.geom.axes.energy_true.max = "200 TeV" - config.datasets.geom.axes.energy_true.nbins = 30 - - - - # reduce dataset ---! - grb2 = Analysis(config) - grb2.observations = observations - grb2.get_datasets() - stacked = grb2.datasets.stack_reduce(name="stacked_3d") - estimator = ExcessMapEstimator(correlation_radius=f"{cfg.get('sgmthresh')} deg", selection_optional=[]) - maps = estimator.run(stacked) - hotspots_table = find_peaks(maps["sqrt_ts"].get_image_by_idx((0,)), threshold=cfg.get('sgmthresh'), min_distance='0.5 deg') - try: - hotspots = SkyCoord(hotspots_table["ra"], hotspots_table["dec"]) - print(hotspots) - ra_gammapy = hotspots.ra[0].deg - dec_gammapy = hotspots.dec[0].deg - if args.print.lower() == 'true': - print(f'Target GAMMAPY = [{ra_gammapy}, {dec_gammapy}]') - - # aperture photometry ---! - phm = Photometrics({events_type: selphlist}) - opts = phm_options(cfg, texp=texp, start=grb.t[0], stop=grb.t[1], caldb=cfg.get('caldb'), irf=cfg.get('irf'), target=(ra_gammapy, dec_gammapy), pointing=pointing, runid=runid, prefix=f"texp{texp}s_{name}_") - off_regions = find_off_regions(phm, opts['background_method'], (ra_gammapy, dec_gammapy), pointing, opts['region_radius'], verbose=opts['verbose'], save=opts['save_off_regions']) - on_gammapy, off_gammapy, a_gammapy, exc_gammapy, sigma_gammapy, err_note = counting(phm, target, opts['region_radius'], off_regions, e_min=opts['energy_min'], e_max=opts['energy_max'], t_min=opts['begin_time'], t_max=opts['end_time'], draconian=False) - if args.print.lower() == 'true': - print(f'Photometry on={on_gammapy} off={off_gammapy} ex={exc_gammapy} a={a_gammapy}') - print('Li&Ma significance:', sigma_gammapy) - except KeyError: - if args.print.lower() == 'true': - print('No candidates found.') - ra_gammapy, dec_gammapy, on_gammapy, off_gammapy, a_gammapy, exc_gammapy, sigma_gammapy = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan - - - # ----------------------------------------------- CTOOLS !!! - # skymap ---! - grb.input = selphlist - grb.output = sky - grb.run_skymap(wbin=cfg.get('skypix'), roi_factor=cfg.get('skyroifrac')) - # blind-search ---! - grb.sigma = cfg.get('sgmthresh') - grb.corr_rad = cfg.get('smooth') - grb.max_src = cfg.get('maxsrc') - grb.input = sky - grb.output = candidates - grb.run_blindsearch() - # position ---! - xml = ManageXml(candidates) - try: - coords = xml.getRaDec() - ra_ctools = coords[0][0] - dec_ctools = coords[1][0] - if args.print.lower() == 'true': - print(f'Target CTOOLS = [{ra_ctools}, {dec_ctools}]') - # on/off ---! - if '.fits' in selphlist: - onoff = selphlist.replace('.fits', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_filename' - else: - onoff = selphlist.replace('.xml', '_cspha.xml').replace('/obs/', '/rta_products/') - events_type = 'events_list' - filenames = ManageXml(selphlist) - run_list = filenames.getRunList() - filenames.closeXml() - del filenames - selphlist = Photometrics.load_data_from_fits_file(run_list[0]) - for file in run_list[1:]: - selphlist = np.append(selphlist, Photometrics.load_data_from_fits_file(file)) - # aperture photometry ---! - phm = Photometrics({events_type: selphlist}) - opts = phm_options(cfg, texp=texp, start=grb.t[0], stop=grb.t[1], caldb=cfg.get('caldb'), irf=cfg.get('irf'), target=(ra_ctools, dec_ctools), pointing=pointing, runid=runid, prefix=f"texp{texp}s_{name}_") - off_regions = find_off_regions(phm, opts['background_method'], (ra_ctools, dec_ctools), pointing, opts['region_radius'], verbose=opts['verbose'], save=opts['save_off_regions']) - on_ctools, off_ctools, a_ctools, exc_ctools, sigma_ctools, err_note = counting(phm, target, opts['region_radius'], off_regions, e_min=opts['energy_min'], e_max=opts['energy_max'], t_min=opts['begin_time'], t_max=opts['end_time'], draconian=False) - if args.print.lower() == 'true': - print(f'Photometry on={on_ctools} off={off_ctools} ex={exc_ctools} a={a_ctools}') - print('Li&Ma significance:', sigma_ctools) - except KeyError: - if args.print.lower() == 'true': - print('No candidates found.') - ra_ctools, dec_ctools, on_ctools, off_ctools, a_ctools, exc_ctools, sigma_ctools = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan - - # save results ---! - row = f"{runid} {count} {texp} {target[0]} {target[1]} {ra_ctools} {dec_ctools} {on_ctools} {off_ctools} {a_ctools} {exc_ctools} {sigma_ctools} {ra_gammapy} {dec_gammapy} {on_gammapy} {off_gammapy} {a_gammapy} {exc_gammapy} {sigma_gammapy} {offset} {cfg.get('delay')} {cfg.get('scalefluxfactor')} {cfg.get('caldb')} {cfg.get('irf')} rtatool1d\n" - if args.print.lower() == 'true': - print(f"Results: {row}") - if not isfile(logname): - hdr = 'runid seed texp ra_true dec_true ra_ctools dec_ctools on_ctools off_ctools alpha_ctools excess_ctools sigma_ctools ra_gammapy dec_gammapy on_gammapy off_gammapy alpha_gammapy excess_gammapy sigma_gammapy offset delay scaleflux caldb irf pipe\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() - else: - log = open(logname, 'a') - log.write(row) - log.close() - - del grb - if args.remove.lower() == 'true': - # remove files ---! - #os.system(f"rm {datapath}/obs/{runid}/texp*{name}*") - os.system(f"rm {datapath}/rta_products/{runid}/*{name}*") -print('...done.\n') - - diff --git a/rtasci/rtasci/prepareGRBcatalog.py b/rtasci/rtasci/prepareGRBcatalog.py deleted file mode 100644 index ec762ff95512f5e655b8459ba9cff8853801bc03..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/prepareGRBcatalog.py +++ /dev/null @@ -1,103 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -from os.path import isdir, join -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.lib.RTAUtils import get_pointing -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.cfg.Config import Config - -parser = argparse.ArgumentParser(description='This script extracts spectra and lightcurves from the GRB templates, in order to prepare all required files for the simulation.') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# GRB ---! -if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if '_ebl' not in f and os.path.isfile(join(cfg.get('catalog'), f))] -elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] -else: - runids = cfg.get('runid') -runids = sorted(runids) - -# check scalefluxfactor ---! -if cfg.get('scalefluxfactor') == None: - raise ValueError('The parameter "scalefluxfactor" must be int or float. If you want to use the nominal template, please set scalefluxfactor=1.') - -# conditions control ---! -set_ebl = cfg.get('set_ebl') # uses the EBL absorbed template -# paths ---! -if '$' in cfg.get('data'): - datapath = os.path.expandvars(cfg.get('data')) -else: - datapath = cfg.get('data') -if not isdir(datapath): - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): - os.mkdir(join(datapath, 'obs')) - -# global files ---! -ebl_table = os.path.expandvars(cfg.get('ebl')) -pl_template = join(os.path.expandvars(cfg.get('model')), 'grb_file_model.xml' ) -if not os.path.isfile(pl_template): - raise ValueError(f'PL template {pl_template} not found') -if not os.path.isfile(ebl_table): - raise ValueError(f'EBL table {ebl_table} not found') - - -# ------------------------------------------------ loop over runids - -for runid in runids: - print(f'Processing runid: {runid}') - # grb path ---! - grbpath = join(datapath, 'obs', runid) # folder that will host the phlist - if not isdir(grbpath): - os.mkdir(grbpath) - - # grb files ---! - template = join(os.path.expandvars(cfg.get('catalog')), f'{runid}.fits') # grb FITS template data - model_pl = pl_template.replace('grb_file_model.xml', f'{runid}.xml') # grb XML template model - if not isdir(join(datapath, f'extracted_data')): - os.mkdir(join(datapath, f'extracted_data')) - tcsv = join(datapath, f'extracted_data/{runid}/time_slices.csv') # grb template time grid - if not os.path.isfile(template): - raise ValueError(f'Template {runid} FITS not found') - if not isdir(join(datapath, f'extracted_data/{runid}')): - os.mkdir(join(datapath, f'extracted_data/{runid}')) - if not os.path.isfile(model_pl): - print(f'Creating {runid} XML model') - os.system(f'cp {pl_template} {model_pl}') - os.system(f'chmod +wr {model_pl}') - model_xml = ManageXml(xml=model_pl) - model_xml.setModelParameters(source=runid, parameters=('RA', 'DEC'), values=get_pointing(template)) - del model_xml - - sim = RTACtoolsSimulation() - sim.template = template - sim.model = model_pl - # add EBL to template ---! - if set_ebl: - sim.table = ebl_table - sim.zfetch = True - if not sim.checkEBLinFITS(): - print('Computing EBL absorption') - sim.set_ebl = False - sim.addEBLtoFITS(template, ext_name='EBL-ABS. SPECTRA') - sim.template = template - sim.set_ebl = set_ebl - # load template ---! - if cfg.get('extract_data'): - sim.extract_spectrum = True - print('Creating lightcurves and spectra') - sim.loadTemplate(source_name=runid, return_bin=False, data_path=join(datapath, f'extracted_data/{runid}'), scalefluxfactor=cfg.get('scalefluxfactor')) - diff --git a/rtasci/rtasci/rtapipe.py b/rtasci/rtasci/rtapipe.py deleted file mode 100644 index 447367534f6135b45d8439770cec3c3a05eaae11..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/rtapipe.py +++ /dev/null @@ -1,57 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -from rtasci.cfg.Config import Config - -# configure -parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -parser.add_argument('--merge', type=str, default='true', help='Merge in single phlist (true) or use observation library (false)') -parser.add_argument('--remove', type=str, default='true', help='Keep only outputs') -parser.add_argument('--print', type=str, default='false', help='Print out results') -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# simulations -if cfg.get('simtype').lower() == 'grb': - if cfg.get('extract_data'): - print('\nPreparing GRB catalog...\n') - os.system(f'python3 prepareGRBcatalog.py -f {args.cfgfile}') - print('\nRun simulations...\n') - os.system(f'python3 simGRBcatalog.py -f {args.cfgfile} --merge {args.merge.lower()} --remove {args.remove.lower()} --print {args.print.lower()}') -elif cfg.get('simtype').lower() == 'bkg': - print('\nComputing BKG-ONLY simulations is work in progress\n') -elif cfg.get('simtype').lower() == 'wilks': - print("\nRun empty fields simulation + analysis") - os.system(f"python3 emptyfields.py -f {args.cfgfile} --remove {args.remove.lower()} --print {args.print.lower()}") -elif cfg.get('simtype').lower() == 'skip': - pass -else: - raise ValueError('Invalid "simtype" value') - -# analysis -if cfg.get('tool') not in ('ctools', 'gammapy', 'rtatool'): - raise ValueError('Invalit "tool" selection.') -else: - print(f'\nRun analysis...\n') - pipeline = f"{cfg.get('tool')}{cfg.get('type')}" - if cfg.get('blind'): - pipeline += '_blind' - if not cfg.get('binned'): - pipeline += '_unbinned' - pipeline += '.py' - print(f'Pipeline: {pipeline}') - os.system(f"python3 pipelines/{pipeline} -f {args.cfgfile} --merge {args.merge.lower()} --remove {args.remove.lower()} --print {args.print.lower()}") - -if "_trials" in args.cfgfile: - os.system(f"rm {args.cfgfile}") -print('\n\nExit\n\n') \ No newline at end of file diff --git a/rtasci/rtasci/simBkg.py b/rtasci/rtasci/simBkg.py deleted file mode 100644 index 409c0d9f3d4961f0a6c023bdda308862da2d72c1..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/simBkg.py +++ /dev/null @@ -1,143 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import os, argparse -import time -from time import time -from shutil import copy -from pathlib import Path -from datetime import datetime -from multiprocessing import Pool -from os.path import isdir, expandvars -from rtasci.cfg.Config import Config -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.lib.RTAUtils import get_pointing, get_mergermap, str2bool -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw - -def main(args): - cfg = Config(args.cfgfile) - # general ---! - if cfg.get('simtype') != 'bkg': - raise ValueError('This script only allows bakground simulations') - trials = cfg.get('trials') # trials - tobs = cfg.get('tobs') # total obs time (s) - runid = cfg.get('runid') - if type(runid) != str or runid == 'all': - raise ValueError('Currently only 1 runid is allowed.') - - # paths ---! - datapath = cfg.get('data') - if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') - if args.output_dir: - bkgpath = Path(args.output_dir) - else: - bkgpath = Path(datapath).joinpath('obs', 'backgrounds') - bkgpath.mkdir(parents=True, exist_ok=True) - - # background model ---! - bkg_model = expandvars(cfg.get('bkg')) # XML background model - - # ------------------------------------------------------- loop runid --- !!! - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset') == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - raise ValueError(f'Merger map of runid {runid} not found. ') - pointing = get_alert_pointing_gw(mergermap) - else: - if runid == 'crab': - pointing = [83.6331, 22.0145] - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # Dumping the Conf object to txt file - dumpedConfig = os.path.join(bkgpath, "config.yaml") - if not os.path.isfile(dumpedConfig): - copy(args.cfgfile, str(dumpedConfig)) - - # ---------------------------------------------------- loop trials ---!!! - if args.mp_enabled: - with Pool(args.mp_threads) as p: - times = p.map(simulateTrial, [ (i, cfg, pointing, bkg_model, bkgpath, tobs, args.remove) for i in range(trials)]) - else: - for i in range(trials): - times = simulateTrial((i, cfg, pointing, bkg_model, bkgpath, tobs, args.remove)) - # time ---! - if args.print: - if len(times) > 1: - print(f"Trial elapsed time (mean): {np.array(times).mean()}") - else: - print(f"Trial elapsed time: {times[0]}") - print('\n... done.\n') - - -def simulateTrial(trial_args): - start_t = time() - i=trial_args[0] - cfg=trial_args[1] - pointing=trial_args[2] - bkg_model=trial_args[3] - bkgpath=trial_args[4] - tobs=trial_args[5] - remove_logs=trial_args[6] - # initialise ---! - count = cfg.get('start_count') + i + 1 - name = f'bkg{count:08d}' - # setup ---! - sim = RTACtoolsSimulation() - sim.seed = count - sim.pointing = pointing - sim.caldb = cfg.get('caldb') - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - - - print(f"[{datetime.now().strftime('%d/%m/%Y_%H:%M:%S')}] Simulate empty fields for runid = {cfg.get('runid')} with seed = {count}", flush=True) - sim.seed = count - sim.t = [0, tobs] - bkg = os.path.join(bkgpath, f'{name}.fits') - sim.model = bkg_model - sim.output = bkg - if args.print: - print(f"Simulation {bkg}") - sim.run_simulation() - if remove_logs: - Path(sim.output).with_suffix('.log').unlink() - sim.input = bkg - sim.sortObsEvents() - del sim - # time ---! - elapsed_t = time()-start_t - if args.print: - print(f"Trial {count} took {elapsed_t} seconds.") - print(f".. done [{datetime.now().strftime('%d/%m/%Y_%H:%M:%S')}]", flush=True) - return (count, elapsed_t) - - -if __name__=='__main__': - parser = argparse.ArgumentParser(description='Simulate empty fields.') - parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") - parser.add_argument('--print', type=str2bool, default='false', help='Print out results') - parser.add_argument('--remove', type=str2bool, default='true', help='Keep only .fits files and not .log') - parser.add_argument('-mp', '--mp-enabled', type=str2bool, default='false', help='To parallelize trials loop') - parser.add_argument('-mpt', '--mp-threads', type=int, default=4, help='The size of the threads pool') - parser.add_argument('-out', '--output-dir', type=str, required=False, default="", help='The path to the output directory') - args = parser.parse_args() - - main(args) diff --git a/rtasci/rtasci/simGRBcatalog.py b/rtasci/rtasci/simGRBcatalog.py deleted file mode 100644 index f48a12d38c6117fda28b32866dfdcdf3164aa74c..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/simGRBcatalog.py +++ /dev/null @@ -1,242 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -from time import time -from shutil import copy -from astropy.io import fits -from multiprocessing import Pool -from os.path import isdir, join, isfile -from rtasci.cfg.Config import Config -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.lib.RTAUtils import get_mergermap, get_pointing, str2bool -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw - -def main(): - - parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') - parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") - parser.add_argument('--merge', type=str2bool, default=True, help='Merge in single phlist (true) or use observation library (false)') - parser.add_argument('--remove', type=str2bool, default=True, help='Keep only outputs') - parser.add_argument('--print', type=str2bool, default=False, help='Print out results') - parser.add_argument('-mp', '--mp-enabled', type=str2bool, default=False, help='To parallelize trials loop') - parser.add_argument('-mpt', '--mp-threads', type=int, default=4, help='The size of the threads pool') - args = parser.parse_args() - - if args.remove and not args.merge: - raise ValueError('Keyword "remove" cannot be True if keyword "merge" is False.') - - cfg = Config(args.cfgfile) - # GRB ---! - if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] - elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] - else: - runids = cfg.get('runid') - runids = sorted(runids) - - # general ---! - trials = cfg.get('trials') - tmax = cfg.get('tobs')-cfg.get('onset')+cfg.get('delay') - - # paths ---! - datapath = cfg.get('data') - if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') - if not isdir(join(datapath, 'obs')): # obs parent folder - os.mkdir(join(datapath, 'obs')) - # background model ---! - bkg_model = cfg.get('bkg') - - # ------------------------------------------------------- loop runid --- !!! - for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - # grb path ---! - grbpath = join(datapath, 'obs', runid) # folder that will host the phlist - if not isdir(grbpath): - os.mkdir(grbpath) - modelpath = join(datapath, f'extracted_data/{runid}') # bin model folder - if not isdir(modelpath): - raise ValueError(f'Folder {runid} not found in {modelpath}') - tcsv = join(datapath, f'extracted_data/{runid}/time_slices.csv') # times table - if not isfile(tcsv): - raise ValueError(f'Data from {runid} have not been correctly extracted.') - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - print(f'Skip runid {runid}. ') - continue - - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - pointing = get_alert_pointing_gw(mergermap) - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # Dumping the Conf object to txt file - dumpedConfig = os.path.join(grbpath, "config.yaml") - if not os.path.isfile(dumpedConfig): - copy(args.cfgfile, str(dumpedConfig)) - - # ---------------------------------------------------- loop trials ---!!! - if args.mp_enabled: - with Pool(args.mp_threads) as p: - times = p.map(simulateTrial, [ (i, cfg, pointing, tmax, datapath, runid, tcsv, grbpath, bkg_model, args.print, args.merge, args.remove) for i in range(trials)]) - else: - for i in range(trials): - times = simulateTrial((i, cfg, pointing, tmax, datapath, runid, tcsv, grbpath, bkg_model, args.print, args.merge, args.remove)) - # time ---! - if args.print: - if len(times) > 1: - print(f"Trial elapsed time (mean): {np.array(times).mean()}") - else: - print(f"Trial elapsed time: {times[0]}") - print('\n... done.\n') - - -def simulateTrial(trial_args): - start_t = time() - i=trial_args[0] - cfg=trial_args[1] - pointing=trial_args[2] - tmax=trial_args[3] - datapath=trial_args[4] - runid=trial_args[5] - tcsv=trial_args[6] - grbpath=trial_args[7] - bkg_model=trial_args[8] - verbose=trial_args[9] - merge=trial_args[10] - remove=trial_args[11] - - # initialise ---! - count = cfg.get('start_count') + i + 1 - name = f'ebl{count:06d}' - # setup ---! - sim = RTACtoolsSimulation() - if type(cfg.get('caldb')) == list: - sim.caldb = cfg.get('caldb')[0] - else: - sim.caldb = cfg.get('caldb') - if type(cfg.get('irf')) == list: - sim.irf = cfg.get('irf')[0] - else: - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - sim.seed = count - sim.set_ebl = cfg.get('set_ebl') - sim.pointing = pointing - if verbose: - print(f'Pointing = {sim.pointing} s') - sim.tmax = tmax - - # get time grid ---! - sim.template = join(os.path.expandvars(cfg.get('catalog')).replace(cfg.get('data'), datapath), f'{runid}.fits') - event_bins = [] - sim.table = tcsv - tgrid, tbin_start, tbin_stop = sim.getTimeSlices(GTI=(cfg.get('delay'), tmax), return_bins=True) - - # -------------------------------------------------------- simulate ---!!! - print(f'Simulate template seed={sim.seed}') - for j in range(tbin_stop-tbin_start-1): - sim.t = [tgrid[j]+cfg.get('onset'), tgrid[j + 1]+cfg.get('onset')] - if verbose: - print(f'GTI (bin) = {sim.t} s') - sim.model = join(datapath, f'extracted_data/{runid}/{runid}_tbin{tbin_start+j:02d}.xml') - event = join(grbpath, f'{name}_tbin{tbin_start+j:02d}.fits') - event_bins.append(event) - sim.output = event - sim.run_simulation() - # -------------------------------------------- shift time --- !!! - if cfg.get('onset') != 0: - if cfg.get('delay') != 0: - raise ValueError('Bad configuration. Either "onset" or "delay" must be equal to 0.') - # ------------------------------------ add background --- !!! - print('Simulate bkg to append before the burst') - bkg = os.path.join(grbpath, f'bkg{count:06d}.fits') - event_bins.insert(0, bkg) - sim.t = [0, cfg.get('onset')] - if verbose: - print(f"GTI (bkg) = {sim.t} s") - sim.model = bkg_model - sim.output = bkg - sim.run_simulation() - - # ---------------------------------------- gather bins ---!!! - if merge: - print('Merge in photon-list') - phlist = join(grbpath, f'{name}.fits') - sim.input = event_bins - sim.output = phlist - sim.appendEventsSinglePhList(GTI=[cfg.get('delay'), cfg.get('delay')+cfg.get('tobs')]) - if verbose: - h = fits.open(phlist) - print('Check GTI and EVENTS time range:') - print('************') - print(h[2].data) - print(h[1].data.field('TIME').min(), h[1].data.field('TIME').max()) - print('************') - h.close() - else: - # observation list ---! - obslist = join(grbpath, f'{name}.xml') - if os.path.isfile(obslist): - os.remove(obslist) - make_obslist(obslist=obslist, items=event_bins, names=name) - - sim.input = phlist - sim.sortObsEvents() - del sim - - # selections ---! - """for texp in cfg.get('exposure'): - selphlist = phlist.replace(f'{name}', f'texp{texp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = cfg.get('caldb') - grb.irf = cfg.get('irf') - grb.roi = cfg.get('roi') - grb.e = [cfg.get('emin'), cfg.get('emax')] - grb.t = [cfg.get('delay'), cfg.get('delay')+texp] - if verbose: - print(f"Selection t = {grb.t} s") - grb.input = phlist - grb.output = selphlist - if merge: - grb.run_selection() - else: - prefix = join(grbpath, f'texp{texp}s_') - grb.run_selection(prefix=prefix) """ - - # remove files ---! - if remove and merge: - # remove bins ---! - os.system('rm ' + join(grbpath, f'{name}*tbin*')) - if cfg.get('onset') != 0: - # remove bkg ---! - os.system('rm ' + join(grbpath, f'{name.replace("ebl", "bkg")}*')) - - # time ---! - elapsed_t = time()-start_t - if verbose: - print(f"Trial {count} took {elapsed_t} seconds") - return (count, elapsed_t) - - -if __name__=='__main__': - main() diff --git a/rtasci/rtasci/simGRBcatalogWithBackgrounds.py b/rtasci/rtasci/simGRBcatalogWithBackgrounds.py deleted file mode 100644 index 2d6950075e08c66b48506d859729b6f1bc575507..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/simGRBcatalogWithBackgrounds.py +++ /dev/null @@ -1,248 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -import numpy as np -from time import time -from shutil import copy -from astropy.io import fits -from multiprocessing import Pool -from os.path import isdir, join, isfile -from rtasci.cfg.Config import Config -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.lib.RTAUtils import get_mergermap, get_pointing, str2bool -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw - - - -def main(args): - cfg = Config(args.cfgfile) - # GRB ---! - if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] - elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] - else: - runids = cfg.get('runid') - runids = sorted(runids) - - # general ---! - trials = cfg.get('trials') - tmax = cfg.get('tobs')-cfg.get('onset')+cfg.get('delay') - - # paths ---! - datapath = cfg.get('data') - if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') - if not isdir(join(datapath, 'obs')): # obs parent folder - os.mkdir(join(datapath, 'obs')) - # background model ---! - bkg_model = cfg.get('bkg') - - # ------------------------------------------------------- loop runid --- !!! - for runid in runids: - print(f"{'-'*50} #\nProcessing runid: {runid}") - # grb path ---! - grbpath = join(datapath, 'obs', runid) # folder that will host the phlist - if not isdir(grbpath): - os.mkdir(grbpath) - modelpath = join(datapath, f'extracted_data/{runid}') # bin model folder - if not isdir(modelpath): - raise ValueError(f'Folder {runid} not found in {modelpath}') - tcsv = join(datapath, f'extracted_data/{runid}/time_slices.csv') # times table - if not isfile(tcsv): - raise ValueError(f'Data from {runid} have not been correctly extracted.') - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - if mergermap == None: - print(f'Skip runid {runid}. ') - continue - - # get alert pointing - if type(cfg.get('offset')) == str and cfg.get('offset').lower() == 'gw': - pointing = get_alert_pointing_gw(mergermap) - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -cfg.get('offset') - else: - pointing[0] += 0.0 - pointing[1] += cfg.get('offset') - - # Dumping the Conf object to txt file - dumpedConfig = os.path.join(grbpath, "config.yaml") - if not os.path.isfile(dumpedConfig): - copy(args.cfgfile, str(dumpedConfig)) - - # ---------------------------------------------------- loop trials ---!!! - if args.mp_enabled: - with Pool(args.mp_threads) as p: - times = p.map(simulateTrial, [ (i, cfg, pointing, tmax, datapath, runid, tcsv, grbpath, bkg_model) for i in range(trials)]) - else: - for i in range(trials): - times = simulateTrial((i, cfg, pointing, tmax, datapath, runid, tcsv, grbpath, bkg_model)) - # time ---! - if args.print: - if len(times) > 1: - print(f"Trial elapsed time (mean): {np.array(times).mean()}") - else: - print(f"Trial elapsed time: {times[0]}") - print('\n... done.\n') - - -def simulateTrial(trial_args): - start_t = time() - i=trial_args[0] - cfg=trial_args[1] - pointing=trial_args[2] - tmax=trial_args[3] - datapath=trial_args[4] - runid=trial_args[5] - tcsv=trial_args[6] - grbpath=trial_args[7] - bkg_model=trial_args[8] - # initialise ---! - count = cfg.get('start_count') + i + 1 - name = f'ebl{count:06d}' - # setup ---! - sim = RTACtoolsSimulation() - if type(cfg.get('caldb')) == list: - sim.caldb = cfg.get('caldb')[0] - else: - sim.caldb = cfg.get('caldb') - if type(cfg.get('irf')) == list: - sim.irf = cfg.get('irf')[0] - else: - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - sim.seed = count - sim.set_ebl = cfg.get('set_ebl') - sim.pointing = pointing - if args.print: - print(f'Pointing = {sim.pointing} s') - sim.tmax = tmax - - # get time grid ---! - sim.template = join(os.path.expandvars(cfg.get('catalog')).replace(cfg.get('data'), datapath), f'{runid}.fits') - event_bins = [] - sim.table = tcsv - tgrid, tbin_start, tbin_stop = sim.getTimeSlices(GTI=(cfg.get('delay'), tmax), return_bins=True) - - # -------------------------------------------------------- simulate ---!!! - print(f'Simulate template seed={sim.seed}') - for j in range(tbin_stop-tbin_start-1): - sim.t = [tgrid[j]+cfg.get('onset'), tgrid[j + 1]+cfg.get('onset')] - if args.print: - print(f'GTI (bin) = {sim.t} s') - sim.model = join(datapath, f'extracted_data/{runid}/{runid}_tbin{tbin_start+j:02d}.xml') - event = join(grbpath, f'{name}_tbin{tbin_start+j:02d}.fits') - event_bins.append(event) - sim.output = event - sim.run_simulation() - # -------------------------------------------------------- background ---!!! - print(f'Simulate background seed={sim.seed}') - sim.t = [cfg.get('onset'), cfg.get('onset')+cfg.get('tobs')] - if args.print: - print(f'GTI (bin) = {sim.t} s') - sim.model = bkg_model - event = join(grbpath, f'{name}_background.fits') - event_bins.append(event) - sim.output = event - sim.run_simulation() - # -------------------------------------------- shift time --- !!! - if cfg.get('onset') != 0: - if cfg.get('delay') != 0: - raise ValueError('Bad configuration. Either "onset" or "delay" must be equal to 0.') - # ------------------------------------ add background --- !!! - print('Simulate bkg to append before the burst') - bkg = os.path.join(grbpath, f'bkg{count:06d}.fits') - event_bins.insert(0, bkg) - sim.t = [0, cfg.get('onset')] - if args.print: - print(f"GTI (bkg) = {sim.t} s") - sim.model = bkg_model - sim.output = bkg - sim.run_simulation() - - # ---------------------------------------- gather bins ---!!! - if args.merge: - print('Merge in photon-list') - phlist = join(grbpath, f'{name}.fits') - sim.input = event_bins - sim.output = phlist - sim.appendEventsSinglePhList(GTI=[cfg.get('delay'), cfg.get('delay')+cfg.get('tobs')]) - if args.print: - h = fits.open(phlist) - print('Check GTI and EVENTS time range:') - print('************') - print(h[2].data) - print(h[1].data.field('TIME').min(), h[1].data.field('TIME').max()) - print('************') - h.close() - else: - # observation list ---! - obslist = join(grbpath, f'{name}.xml') - if os.path.isfile(obslist): - os.remove(obslist) - make_obslist(obslist=obslist, items=event_bins, names=name) - - sim.input = phlist - sim.sortObsEvents() - del sim - - # selections ---! - """for texp in cfg.get('exposure'): - selphlist = phlist.replace(f'{name}', f'texp{texp}s_{name}') - grb = RTACtoolsAnalysis() - grb.caldb = cfg.get('caldb') - grb.irf = cfg.get('irf') - grb.roi = cfg.get('roi') - grb.e = [cfg.get('emin'), cfg.get('emax')] - grb.t = [cfg.get('delay'), cfg.get('delay')+texp] - if args.print: - print(f"Selection t = {grb.t} s") - grb.input = phlist - grb.output = selphlist - if args.merge: - grb.run_selection() - else: - prefix = join(grbpath, f'texp{texp}s_') - grb.run_selection(prefix=prefix) """ - - # remove files ---! - if args.remove and args.merge: - # remove bins ---! - os.system('rm ' + join(grbpath, f'{name}*tbin*')) - if cfg.get('onset') != 0: - # remove bkg ---! - os.system('rm ' + join(grbpath, f'{name.replace("ebl", "bkg")}*')) - - # time ---! - elapsed_t = time()-start_t - if args.print: - print(f"Trial {count} took {elapsed_t} seconds") - return (count, elapsed_t) - - -if __name__=='__main__': - parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') - parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") - parser.add_argument('--merge', type=str2bool, default=True, help='Merge in single phlist (true) or use observation library (false)') - parser.add_argument('--remove', type=str2bool, default=True, help='Keep only outputs') - parser.add_argument('--print', type=str2bool, default=False, help='Print out results') - parser.add_argument('-mp', '--mp-enabled', type=str2bool, default=False, help='To parallelize trials loop') - parser.add_argument('-mpt', '--mp-threads', type=int, default=4, help='The size of the threads pool') - args = parser.parse_args() - - if args.remove and not args.merge: - raise ValueError('Keyword "remove" cannot be True if keyword "merge" is False.') - main(args) diff --git a/rtasci/rtasci/simGRBcatalogWithRandomization.py b/rtasci/rtasci/simGRBcatalogWithRandomization.py deleted file mode 100644 index 7355d64dc1069ce1f0d1da006980b8b783ed4fa0..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/simGRBcatalogWithRandomization.py +++ /dev/null @@ -1,369 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Leonardo Baroncelli -# ******************************************************************************* - -import os -import random -import argparse -import numpy as np -from time import time -from pathlib import Path -from astropy.io import fits -from datetime import datetime -from multiprocessing import Pool -from os.path import join, isfile -from shutil import move, rmtree -from rtasci.cfg.Config import Config -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.lib.RTAUtils import get_alert_pointing_gw, get_mergermap, get_pointing, str2bool - -class TrialOutput: - def __init__(self, trial_id, run_id, elapsed_time, errors=False, error_msg=''): - self.trial_id = trial_id - self.run_id = run_id - self.elapsed_time = elapsed_time - self.errors = errors - self.error_msg = error_msg - def __str__(self): - return f"Trial {self.trial_id} (run {self.run_id}) took {self.elapsed_time:.2f} seconds. Error message: {self.error_msg}" - -def is_randomizable(val): - if isinstance(val, str) and "random" in val: - return True - return False - -def randomize(val): - _min, _max = val.replace("random$","").split("-") - return round(random.uniform(float(_min), float(_max)), 2) - -def config_runid(cfg): - # GRB ---! - if cfg.get('runid') == 'all': - runids = [f.replace('.fits', '') for f in os.listdir(cfg.get('catalog')) if isfile(join(cfg.get('catalog'), f))] - elif type(cfg.get('runid')) == str: - runids = [cfg.get('runid')] - else: - runids = cfg.get('runid') - return sorted(runids) - -def create_output_dirs(output_dir, cfg, runids): - start = time() - # Create output dirs and dump the configuration file inside - for runid in runids: - output_path = Path(output_dir).joinpath(runid) - if not output_path.exists(): - output_path.mkdir(parents=True, exist_ok=True) - cfg.set("runid", runid) # it could be a list or "all" but I want a single runid - cfg.dump(output_path.joinpath("config.yaml")) - print(f"Output dirs created in {round(time()-start, 3)} seconds") - -def now(): - return datetime.now().strftime('%d/%m/%Y_%H:%M:%S') - -def get_pointing_utility(runid, cfg): - - # get alert pointing - pointing = None - offset = cfg.get('offset') - - if isinstance(offset, str) and offset.lower() == 'gw': - mergerpath = os.path.expandvars(cfg.get('merger')) - mergermap = get_mergermap(runid, mergerpath) - pointing = get_alert_pointing_gw(mergermap) - - else: - pointing = list(get_pointing(f"{os.path.expandvars(cfg.get('catalog'))}/{runid}.fits")) - if pointing[1] < 0: - pointing[0] += 0.0 - pointing[1] += -offset - else: - pointing[0] += 0.0 - pointing[1] += offset - - return pointing - -def simulate_trial_bkg(input_args): - - runid, trial_id, cfg, args = input_args - - start_t = time() - - sim_output_path = args.output_dir.joinpath("backgrounds") - sim_output_path.mkdir(parents=True, exist_ok=True) - - offset = cfg.get('offset') - tobs = cfg.get('tobs') - bkg_model = cfg.get('bkg') - - if args.print: - print(f"start_count: {cfg.get('start_count')} trial id: {trial_id}") - - count = cfg.get('start_count') + trial_id + 1 - name = f'runid_notemplate_trial_{count:010d}_simtype_{cfg.get("simtype")}_onset_0_delay_0_offset_{offset}_tobs_{tobs}.fits' - - try: - pointing = get_pointing_utility(runid, cfg) - except Exception as e: - return TrialOutput(trial_id, runid, time()-start_t, True, str(e)) - - # setup ---! - sim = RTACtoolsSimulation() - sim.seed = count - sim.pointing = pointing - sim.caldb = cfg.get('caldb') - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - - if args.print: - print(f"[{now()}] Simulate empty fields for runid = {cfg.get('runid')} with seed = {count}") - - sim.seed = count - sim.t = [0, tobs] - bkg = str(sim_output_path.joinpath(name)) - sim.model = bkg_model - sim.output = bkg - sim.run_simulation() - #if remove_logs: - # Path(sim.output).with_suffix('.log').unlink() - sim.input = bkg - sim.sortObsEvents() - del sim - # time ---! - elapsed_t = time()-start_t - if args.print: - print(f"Trial {count} took {elapsed_t} seconds.") - print(f".. done [{now()}]", flush=True) - - # time ---! - elapsed_t = time()-start_t - - return TrialOutput(trial_id, runid, elapsed_t) - -def simulate_trial_grb(input_args): - - runid, trial_id, cfg, args = input_args - - start_t = time() - - sim_output_path_base = args.output_dir.joinpath(runid) - sim_output_path = sim_output_path_base.joinpath(f"trial_{trial_id}") # folder that will host the phlist - - if not sim_output_path.exists(): - sim_output_path.mkdir(parents=True, exist_ok=True) - - datapath = Path(cfg.get('data')) - - modelpath = datapath.joinpath("extracted_data", runid) # bin model folder - if not modelpath.exists(): - return TrialOutput(trial_id, runid, 0, True, f'Folder {runid} not found in {modelpath}') - - tcsv = datapath.joinpath("extracted_data", runid, "time_slices.csv") # times table - if not tcsv.exists(): - return TrialOutput(trial_id, runid, time()-start_t, True, f"File {tcsv} not found") - - onset = cfg.get('onset') - delay = cfg.get('delay') - offset = cfg.get('offset') - - # add more if needed - if(is_randomizable(cfg.get('onset'))): - onset = randomize(cfg.get('onset')) - - if(is_randomizable(cfg.get('delay'))): - delay = randomize(cfg.get('delay')) - - if(is_randomizable(cfg.get('offset'))): - offset = randomize(cfg.get('offset')) - - try: - pointing = get_pointing_utility(runid, cfg) - except Exception as e: - return TrialOutput(trial_id, runid, time()-start_t, True, str(e)) - - tmax = cfg.get('tobs')- onset + delay - bkg_model = cfg.get('bkg') - - # initialise ---! - print(f"start_count: {cfg.get('start_count')} trial id: {trial_id}") - count = cfg.get('start_count') + trial_id + 1 - name = f'runid_{runid}_trial_{count:010d}_simtype_{cfg.get("simtype")}_onset_{onset}_delay_{delay}_offset_{offset}' - - # setup ---! - sim = RTACtoolsSimulation() - if type(cfg.get('caldb')) == list: - sim.caldb = cfg.get('caldb')[0] - else: - sim.caldb = cfg.get('caldb') - if type(cfg.get('irf')) == list: - sim.irf = cfg.get('irf')[0] - else: - sim.irf = cfg.get('irf') - sim.fov = cfg.get('roi') - sim.e = [cfg.get('emin'), cfg.get('emax')] - sim.seed = count - sim.set_ebl = cfg.get('set_ebl') - sim.pointing = pointing - if args.print: - print(f'Pointing = {sim.pointing} s') - sim.tmax = tmax - - # get time grid ---! - sim.template = join(os.path.expandvars(cfg.get('catalog')).replace(cfg.get('data'), str(datapath)), f'{runid}.fits') - event_bins = [] - sim.table = tcsv - tgrid, tbin_start, tbin_stop = sim.getTimeSlices(GTI=(delay, tmax), return_bins=True) - - # -------------------------------------------------------- simulate ---!!! - print(f'Simulate template seed={sim.seed}') - for j in range(tbin_stop-tbin_start-1): - sim.t = [tgrid[j]+onset, tgrid[j + 1]+onset] - if args.print: - print(f'GTI (bin) = {sim.t} s') - sim.model = join(datapath, f'extracted_data/{runid}/{runid}_tbin{tbin_start+j:02d}.xml') - event = join(sim_output_path, f'{name}_tbin{tbin_start+j:02d}.fits') - event_bins.append(event) - sim.output = event - try: - sim.run_simulation() - except Exception as e: - rmtree(sim_output_path) - return TrialOutput(trial_id, runid, time()-start_t, True, f"Simulation failed: {e}") - - # -------------------------------------------- shift time --- !!! - if onset != 0: - if delay != 0: - return TrialOutput(trial_id, runid, time()-start_t, True, f'Bad configuration. Either "onset" or "delay" must be equal to 0.') - - # ------------------------------------ add background --- !!! - print('Simulate bkg to append before the burst') - bkg_name = f'bkg_{count:08d}_onset_{onset}_delay_{delay}_offset_{offset}' - bkg = os.path.join(sim_output_path, bkg_name) - event_bins.insert(0, bkg) - sim.t = [0, onset] - if args.print: - print(f"GTI (bkg) = {sim.t} s") - sim.model = bkg_model - sim.output = bkg - sim.run_simulation() - - # ---------------------------------------- gather bins ---!!! - if args.merge: - print('Merge in photon-list') - phlist = join(sim_output_path, f'{name}.fits') - sim.input = event_bins - sim.output = phlist - sim.appendEventsSinglePhList(GTI=[delay, delay+cfg.get('tobs')]) - if args.print: - h = fits.open(phlist) - print('Check GTI and EVENTS time range:') - print('************') - print(h[2].data) - print(h[1].data.field('TIME').min(), h[1].data.field('TIME').max()) - print('************') - h.close() - else: - # observation list ---! - obslist = join(sim_output_path, f'{name}.xml') - if os.path.isfile(obslist): - os.remove(obslist) - make_obslist(obslist=obslist, items=event_bins, names=name) - - sim.input = phlist - sim.sortObsEvents() - del sim - - # move final file and remove the temporary directory ---! - if args.remove and args.merge: - move(phlist, sim_output_path_base.joinpath(f'{name}.fits')) - rmtree(sim_output_path) - - # time ---! - elapsed_t = time()-start_t - - return TrialOutput(trial_id, runid, elapsed_t) - -def yield_batch(batch_size, runids, trials, cfg, args): - trials_batch = [] - for runid in runids: - for trial in range(trials): - trials_batch.append((runid, trial, cfg, args)) - if batch_size == len(trials_batch): - yield trials_batch - trials_batch = [] - if len(trials_batch) > 0: - yield trials_batch - -def stats(output_dir, trials_outputs): - trials_elapsed_times_mean = np.array([trial_output.elapsed_time for trial_output in trials_outputs if not trial_output.errors]).mean() - trials_elapsed_times_std = np.array([trial_output.elapsed_time for trial_output in trials_outputs if not trial_output.errors]).std() - trials_with_errors = [trial_output for trial_output in trials_outputs if trial_output.errors] - print(f"Trials elapsed time (mean): {trials_elapsed_times_mean} +- {trials_elapsed_times_std}\nNumber of trials with errors: {len(trials_with_errors)}", flush=True) - with open(output_dir.joinpath("trials_with_errors.txt"), "a") as f: - for trial_output in trials_with_errors: - f.write(f"\n{trial_output}") - -def main(): - - parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') - parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") - parser.add_argument('--merge', type=str2bool, default=True, help='Merge in single phlist (true) or use observation library (false)') - parser.add_argument('--remove', type=str2bool, default=True, help='Keep only outputs') - parser.add_argument('--output-dir', type=str, default=None, help='Output directory') - parser.add_argument('--print', type=str2bool, default=False, help='Print out results') - parser.add_argument('-mpt', '--mp-threads', type=int, default=4, help='The size of the threads pool') - args = parser.parse_args() - - if args.remove and not args.merge: - raise ValueError('Keyword "remove" cannot be True if keyword "merge" is False.') - - cfg = Config(args.cfgfile) - runids = config_runid(cfg) - trials = cfg.get('trials') - - args.output_dir = Path(args.output_dir) - if not args.output_dir.exists(): - args.output_dir.mkdir(parents=True, exist_ok=True) - - print(f"root output dir: {args.output_dir}") - print(f"total runids: {len(runids)}") - print(f"total trials: {trials}") - print(f"total simulations: {len(runids)*trials}") - print(f"Threads: {args.mp_threads}") - - if cfg.get('simtype') == "bkg": - simulate_trial = simulate_trial_bkg - else: - create_output_dirs(args.output_dir, cfg, runids) - simulate_trial = simulate_trial_grb - - pool = Pool(args.mp_threads) - - count = 0 - for trial_batch in yield_batch(args.mp_threads, runids, trials, cfg, args): - trials_outputs = pool.map(simulate_trial, trial_batch) - count += len(trials_outputs) - print(f"\n[{now()}] Batch processed, computing statistics..", flush=True) - stats(args.output_dir, trials_outputs) - print(f"Processed a total of {count} trials", flush=True) - - """Profiling run - #import cProfile, pstats - #profiler = cProfile.Profile() - #profiler.enable() - #trial_output = simulate_trial(('run0406_ID000126', 1, cfg, args)) - #profiler.disable() - #stats = pstats.Stats(profiler).sort_stats('tottime') - #stats.print_stats()""" - - print(f'\n...[{now()}] done.\n') - - -if __name__=='__main__': - main() diff --git a/rtasci/rtasci/simWobble.py b/rtasci/rtasci/simWobble.py deleted file mode 100644 index 8d2154c3c202b389273b14f2d2efec3ffb70da6a..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/simWobble.py +++ /dev/null @@ -1,90 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import os -import argparse -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.lib.RTAUtils import wobble_pointing -from rtasci.cfg.Config import Config -from os.path import join, isdir - -parser = argparse.ArgumentParser(description='This scripts allows to simulate wobble observation') -parser.add_argument('-f', '--cfgfile', type=str, required=True, help="Path to the yaml configuration file") -args = parser.parse_args() - -cfg = Config(args.cfgfile) - -# source ---! -if cfg.get('simtype').lower() != 'wobble': - raise ValueError('Invalid simtype.') -runid = cfg.get('runid').lower() -if runid not in ['crab', 'bkg']: - raise ValueError('Currenlty only "crab" and "bkg" runid are contemplated for wobble simulation.') - -# general ---! -trials = cfg.get('trials') -start_count = cfg.get('start_count') -# sim parameters ---! -caldb = cfg.get('caldb') -irf = cfg.get('irf') -nruns = cfg.get('nruns') -trun = cfg.get('tobs') -emin = cfg.get('emin') -emax = cfg.get('emax') -roi = cfg.get('roi') -seed = start_count+1 - -# paths ---! -datapath = cfg.get('data') -if not isdir(datapath): # main data folder - raise ValueError('Please specify a valid path') -if not isdir(join(datapath, 'obs')): # obs parent folder - os.mkdir(join(datapath, 'obs')) -obspath = os.path.join(datapath, 'obs', runid) - -# check folders and create missing ones ---! -if not os.path.isdir(obspath): - if not os.path.isdir(obspath.replace(runid, '')): - os.mkdir(obspath.replace(runid, '')) - os.mkdir(obspath) -# files ---! -if runid == 'bkg': - model = os.path.join(datapath, f'models/CTAIrfBackground.xml') -else: - model = os.path.join(datapath, f'models/{runid}.xml') # grb XML template model -obsfile = os.path.join(obspath, 'wobble.xml') - -# ---------------------------------------------------- trials --- !!! -target = (83.6331, 22.0145) -runs = list() -for count in range(nruns): - name = f'{cfg.get("runid")}{start_count:06d}' - print(f'Name: {name}') - wobble_index = count % 4 - pointing = wobble_pointing(target, nrun=count, clockwise=True, offset=cfg.get('offset')) - print(f'Simulating run {count+1}/{nruns} at pointing: {pointing} deg') - - # setup ---! - sim = RTACtoolsSimulation() - sim.seed = seed - sim.pointing = pointing - sim.roi = roi - sim.e = [emin, emax] - sim.t = [trun*count, trun*(count+1)] - sim.caldb = caldb - sim.irf = irf - sim.model = model - run = os.path.join(obspath, f'{name}_run{count+1:02d}.fits') - runs.append(run) - sim.output = run - sim.run_simulation() - sim.input = run - sim.sortObsEvents() - -make_obslist(obsfile, runs, runid) \ No newline at end of file diff --git a/rtasci/rtasci/timing/__init__.py b/rtasci/rtasci/timing/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/rtasci/rtasci/timing/make_sh.py b/rtasci/rtasci/timing/make_sh.py deleted file mode 100644 index b92c83ad76b1bda62335ad4ef3013889b7d244f7..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/make_sh.py +++ /dev/null @@ -1,57 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import sys -import os -from os import listdir -from os.path import isfile, join - - -if len(sys.argv) > 1: - N = int(sys.argv[1]) -else: - N = 10 - -rootpath = str(os.path.dirname(os.path.abspath(__file__))) -pypath = f'{rootpath}' -jobpath = f"{rootpath.replace('timing', 'jobs')}" -scripts = [f for f in listdir(pypath) if isfile(join(pypath, f)) and 'time_' in f] - -print(scripts) - -for script in scripts: - shname = f'{jobpath}/{script.replace(".py", ".sh")}' - shrun = f'{jobpath}/job_{script.replace(".py", ".sh")}' - - sh = open(shname, 'w+') - sh.write('#!/bin/bash\n') - sh.write('\nsource activate scitools') - - for i in range(N): - if i == 0: - sh.write(f'\n\tpython3 {pypath}/{script} 1000 {True}') - sh.write(f'\n\tpython3 {pypath}/{script} 100 {False}') - sh.write(f'\n\tpython3 {pypath}/{script} 10 {False}') - else: - sh.write(f'\n\tpython3 {pypath}/{script} 1000 {False}') - sh.write(f'\n\tpython3 {pypath}/{script} 100 {False}') - sh.write(f'\n\tpython3 {pypath}/{script} 10 {False}') - - sh.write('\n') - sh.close() - - sh = open(shrun, 'w+') - sh.write('#!/bin/bash\n') - sh.write(f'\n#SBATCH --job-name=ambra') - sh.write(f'\n#SBATCH --output=slurm-{script.replace(".py", ".out")}') - sh.write(f'\n#SBATCH --account=ambra') - sh.write(f'\n#SBATCH --nodes=1') - sh.write(f'\n#SBATCH --cpus-per-task=1\n\n') - sh.write(f'exec sh {shname}\n') - sh.close() diff --git a/rtasci/rtasci/timing/time_ctools1d_binned_fit.py b/rtasci/rtasci/timing/time_ctools1d_binned_fit.py deleted file mode 100644 index 95fa72a83a54c87cfffa3e69afc29b1d143ae4ba..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools1d_binned_fit.py +++ /dev/null @@ -1,116 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAUtils import * -from rtasci.lib.RTAManageXml import ManageXml -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -edisp = True -rootpath = "/data01/homes/cta/gammapy_integration" -obspath = f'{rootpath}/DATA/obs/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -modelpath = f'{rootpath}/DATA/models/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -model = f'{modelpath}crab.xml' -onoff_obs = filename.replace(obspath,rtapath).replace('.fits','_cspha.xml') -onoff_model = onoff_obs.replace('.xml','_model.xml') -fitname = onoff_obs.replace('.xml','_fit.xml') -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# set model -t = time.time() -xml = ManageXml(model) -xml.setTsTrue() -xml.parametersFreeFixed(src_free=['Prefactor']) -target = xml.getRaDec() -xml.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# onoff -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.nthreads = 1 -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.t = [0, texp] -analysis.roi = 5 -analysis.input = filename -analysis.model = model -analysis.src_name = 'Crab' -analysis.target = [target[0][0], target[1][0]] -analysis.output = onoff_obs -analysis.run_onoff(prefix=onoff_obs.replace('.xml',''), ebins=30, etruemin=0.03, etruemax=30, etruebins=40, ebins_alg='LOG', maxoffset=2.5) -tonoff = time.time() - t -print(f'Onoff: {tonoff} s\n') - -# onoff -t = time.time() -analysis.input = onoff_obs -analysis.model = onoff_model -analysis.output = fitname -analysis.run_maxlikelihood(edisp=edisp) -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - ts = results.getTs()[0] -except IndexError: - raise Warning('Target not found.') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -print(analysis.e) -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools1d_binned_fit.csv' -row = f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tmodel} {tonoff} {tfit} {tstat} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tmodel tonoff tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print(row) \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_ctools1d_fit.py b/rtasci/rtasci/timing/time_ctools1d_fit.py deleted file mode 100644 index 9558da26aa30d6dc4669be686f2032e46f52aca7..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools1d_fit.py +++ /dev/null @@ -1,110 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -from lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from lib.RTAUtils import * -from lib.RTAManageXml import ManageXml -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci', '') -obspath = f'{rootpath}/DATA/selections/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -modelpath = f'{rootpath}/DATA/models/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -model = f'{modelpath}crab.xml' -onoff_obs = filename.replace(obspath,rtapath).replace('.fits','_cspha.xml') -onoff_model = onoff_obs.replace('.xml','_model.xml') -fitname = onoff_obs.replace('.xml','_fit.xml') -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# set model -t = time.time() -xml = ManageXml(model) -xml.setTsTrue() -xml.parametersFreeFixed(src_free=['Prefactor']) -target = xml.getRaDec() -xml.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# onoff -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.nthreads = 1 -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.input = filename -analysis.model = model -analysis.src_name = 'Crab' -analysis.target = [target[0][0], target[1][0]] -analysis.output = onoff_obs -analysis.run_onoff(prefix=onoff_obs.replace('.xml',''), ebins=1) -tonoff = time.time() - t -print(f'Onoff: {tonoff} s\n') - -# onoff -t = time.time() -analysis.input = onoff_obs -analysis.model = onoff_model -analysis.output = fitname -analysis.run_maxlikelihood() -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - ts = results.getTs()[0] -except IndexError: - raise Warning('Target not found.') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools1d_fit.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tmodel tonoff tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tmodel} {tonoff} {tfit} {tstat} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tmodel} {tonoff} {tfit} {tstat} {tflux}\n') - log.close() diff --git a/rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py b/rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py deleted file mode 100644 index 0ba296add184367f4ed908915b4f706259e7170c..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py +++ /dev/null @@ -1,181 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import numpy as np -from lib.RTACtoolsAnalysis import RTACtoolsAnalysis, make_obslist -from lib.RTAUtils import phflux_powerlaw -from lib.RTAManageXml import ManageXml -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci', '') -obspath = f'{rootpath}/DATA/selections/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -skyname = filename.replace(obspath,rtapath).replace('.fits', '_skymap.fits') -detname = skyname.replace('_skymap.fits',f'_model.xml') -obslist = filename.replace('.fits', '_obs.xml') -cube = filename.replace(obspath,rtapath).replace('.fits','_cube.xml') -expcube = cube.replace('.xml', '_exp.xml') -psfcube = cube.replace('.xml', '_psf.xml') -bkgcube = cube.replace('.xml', '_bkg.xml') -outmodel = cube.replace('.xml', '_model.xml') -fitname = filename.replace(obspath,rtapath).replace('.fits', '_fit.xml') -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# observaition -t = time.time() -make_obslist(obslist, items=filename, names='Crab', instruments='CTA') -tobs = time.time() - t -print(f'Observation: {tobs} s\n') - -# initialise + skymap -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.nthreads = 1 -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.input = filename -analysis.output = skyname -analysis.run_skymap() -tsky = time.time() - t -print(f'Skymap: {tsky} s\n') - -# initialise -t = time.time() -analysis.sigma = 3 -analysis.max_src = 1 -analysis.input = skyname -analysis.output = detname -analysis.run_blindsearch() -tblind = time.time() - t -print(f'Blind-search: {tblind} s\n') - -# get candidate and modify model -t = time.time() -detection = ManageXml(detname) -detection.modXml(overwrite=True) -detection.setTsTrue() -detection.setInstrument() -detection.parametersFreeFixed(src_free=['Prefactor']) -try: - coords = detection.getRaDec() - ra = coords[0][0] - dec = coords[1][0] -except IndexError: - raise Warning('No candidates found.') -print(f'Hotspots:{coords}\n') -detection.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -analysis.usepnt = False -analysis.target = (ra, dec) -analysis.input = obslist -analysis.output = cube -analysis.run_binning(prefix=cube.replace('.xml','_'), ebins=10) -tcube = time.time() - t -print(f'Binning: {tcube} s\n') - -# exp cube -t = time.time() -analysis.input = obslist -analysis.output = expcube -analysis.run_expcube(cube=cube.replace('.xml','_cta.fits'), ebins=10) -texpcube = time.time() - t -print(f'Exp. cube: {texpcube} s\n') - -# psf cube -t = time.time() -analysis.input = obslist -analysis.output = psfcube -analysis.run_psfcube(cube=cube.replace('.xml','_cta.fits'), ebins=10) -tpsfcube = time.time() - t -print(f'Psf. cube: {tpsfcube} s\n') - -# bkg cube -t = time.time() -analysis.input = obslist -analysis.model = detname -analysis.output = bkgcube -analysis.run_bkgcube(cube=cube.replace('.xml','_cta.fits'), model=outmodel) -tbkgcube = time.time() - t -print(f'Bkg. cube: {tbkgcube} s\n') - -# set model -t = time.time() -xml = ManageXml(outmodel) -xml.setTsTrue() -xml.parametersFreeFixed(src_free=['Prefactor']) -xml.closeXml() -tmodel += time.time() - t -print(f'Modelling2: {tmodel} s\n') - -# fitting -analysis.input = cube -analysis.model = outmodel -analysis.output = fitname -analysis.run_maxlikelihood(binned=True, exp=expcube, psf=psfcube, bkg=bkgcube) -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - ts = results.getTs()[0] -except IndexError: - raise Warning('No candidates found.') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools3d_binned_blindfit.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ra dec ttotal timport tsetup tobs tsky tblind tcube texpcube tpsfcube tbkgcube tmodel tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ra} {dec} {ttotal} {timport} {tsetup} {tobs} {tsky} {tblind} {tcube} {texpcube} {tpsfcube} {tbkgcube} {tmodel} {tfit} {tstat} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ra} {dec} {ttotal} {timport} {tsetup} {tobs} {tsky} {tblind} {tcube} {texpcube} {tpsfcube} {tbkgcube} {tmodel} {tfit} {tstat} {tflux}\n') - log.close() - - - - diff --git a/rtasci/rtasci/timing/time_ctools3d_binned_fit.py b/rtasci/rtasci/timing/time_ctools3d_binned_fit.py deleted file mode 100644 index acfdc02a0aea6b54d356f5a2134c9ebcbbdc4b6e..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools3d_binned_fit.py +++ /dev/null @@ -1,171 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -from rtasci.lib.RTACtoolsSimulation import make_obslist -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAUtils import * -from rtasci.lib.RTAManageXml import ManageXml -from rtasci.lib.RTACtoolsBase import * -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -edisp = True -rootpath = "/data01/homes/cta/gammapy_integration" -obspath = f'{rootpath}/DATA/obs/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -modelpath = f'{rootpath}/DATA/models/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -obslist = filename.replace('.fits', '_obs.xml') -cube = filename.replace(obspath,rtapath).replace('.fits','_cube.xml') -expcube = cube.replace('.xml', '_exp.fits') -psfcube = cube.replace('.xml', '_psf.fits') -edispcube = cube.replace('.xml', '_edisp.fits') -bkgcube = cube.replace('.xml', '_bkg.fits') -inmodel = f'{modelpath}crab.xml' -outmodel = cube.replace('.xml', '_model.xml') -fitname = filename.replace(obspath,rtapath).replace('.fits', '_fit.xml') -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -t = time.time() -make_obslist(obslist, filename, 'Crab') -tobs = time.time() - t -print(f'Observation: {tobs} s\n') - -# initialise + binning -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.t = [0, texp] -analysis.roi = 2.5 -analysis.usepnt = True -analysis.input = obslist -analysis.output = cube -analysis.run_binning(prefix=cube.replace('.xml','_'), ebins=30, wbin=0.02) -tcube = time.time() - t -print(f'Binning: {tcube} s\n') - -# exp cube -t = time.time() -analysis.input = obslist -analysis.output = expcube -if edisp: - analysis.run_expcube(cube=cube.replace('.xml','_cta_01.fits'), ebins=30, wbin=0.02) -else: - analysis.run_expcube(cube=cube.replace('.xml','_cta.fits'), ebins=30, wbin=0.02) -texpcube = time.time() - t -print(f'Exp. cube: {texpcube} s\n') - -# psf cube -t = time.time() -analysis.input = obslist -analysis.output = psfcube -if edisp: - analysis.run_psfcube(cube=cube.replace('.xml','_cta_01.fits'), ebins=30, wbin=0.5) -else: - analysis.run_psfcube(cube=cube.replace('.xml','_cta.fits'), ebins=30, wbin=0.5) -tpsfcube = time.time() - t -print(f'Psf. cube: {tpsfcube} s\n') - -if edisp: - # edisp cube - t = time.time() - analysis.input = obslist - analysis.output = edispcube - analysis.run_edispcube(cube=cube.replace('.xml','_cta_01.fits'), ebins=30, wbin=0.5) - tedispcube = time.time() - t - print(f'Edisp. cube: {tedispcube} s\n') -else: - tedispcube = np.nan - edispcube=None - -# bkg cube -t = time.time() -analysis.input = obslist -analysis.model = inmodel -analysis.output = bkgcube -if edisp: - analysis.run_bkgcube(cube=cube.replace('.xml','_cta_01.fits'), model=outmodel) -else: - analysis.run_bkgcube(cube=cube.replace('.xml','_cta.fits'), model=outmodel) -tbkgcube = time.time() - t -print(f'Bkg. cube: {tbkgcube} s\n') - -# set model -t = time.time() -xml = ManageXml(outmodel) -xml.setTsTrue() -xml.parametersFreeFixed(src_free=['Prefactor'], bkg_free=['Prefactor', 'Index']) -xml.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -analysis.input = cube -analysis.model = outmodel -analysis.output = fitname -analysis.run_maxlikelihood(binned=True, edisp=edisp, exp=expcube, psf=psfcube, bkg=bkgcube, edispcube=edispcube) -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - ts = results.getTs()[0] -except IndexError: - raise Warning('Target not found.') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -print(f'pref={pref}') -print(analysis.e) -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools3d_binned_fit.csv' -row = f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tobs} {tcube} {texpcube} {tpsfcube} {tedispcube} {tbkgcube} {tmodel} {tfit} {tstat} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tobs tcube texpcube tpsfcube tedispcube tbkgcube tmodel tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print(row) - diff --git a/rtasci/rtasci/timing/time_ctools3d_blindfit.py b/rtasci/rtasci/timing/time_ctools3d_blindfit.py deleted file mode 100644 index b12f4f7dfb968eed8c5e7543545ea422d296bc89..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools3d_blindfit.py +++ /dev/null @@ -1,124 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import numpy as np -from rtasci.lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.lib.RTAUtils import phflux_powerlaw -from rtasci.lib.RTAManageXml import ManageXml -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = "/data01/homes/cta/gammapy_integration" -obspath = f'{rootpath}/DATA/obs/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -skyname = filename.replace(obspath,rtapath).replace('.fits', '_skymap.fits') -detname = skyname.replace('_skymap.fits',f'_model.xml') -fitname = detname.replace('_model.xml','_fit.xml') -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# initialise + skymap -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.nthreads = 1 -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.t = [0, texp] -analysis.roi = 5 -analysis.input = filename -analysis.output = skyname -analysis.run_skymap() -tsky = time.time() - t -print(f'Skymap: {tsky} s\n') - -# initialise -t = time.time() -analysis.sigma = 3 -analysis.max_src = 1 -analysis.input = skyname -analysis.output = detname -analysis.run_blindsearch() -tblind = time.time() - t -print(f'Blind-search: {tblind} s\n') - -# get candidate and modify model -t = time.time() -detection = ManageXml(detname) -detection.modXml(overwrite=True) -detection.setTsTrue() -detection.parametersFreeFixed(src_free=['Prefactor']) -detection.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -t = time.time() -analysis.input = filename -analysis.model = detname -analysis.output = fitname -analysis.run_maxlikelihood() -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - coords = results.getRaDec() - ra = coords[0][0] - dec = coords[1][0] - ts = results.getTs()[0] -except IndexError: - raise Warning('No candidates found.') -print(f'Hotspots:{coords}\n') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools3d_blindfit.csv' -row = f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ra} {dec} {ttotal} {timport} {tsetup} {tsky} {tblind} {tmodel} {tfit} {tstat} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ra dec ttotal timport tsetup tsky tblind tmodel tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print(row) \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_ctools3d_fit.py b/rtasci/rtasci/timing/time_ctools3d_fit.py deleted file mode 100644 index aad46262b9ffcf2b2154aef52bbfd2b64e8c377e..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_ctools3d_fit.py +++ /dev/null @@ -1,96 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -from lib.RTACtoolsAnalysis import RTACtoolsAnalysis -from lib.RTAUtils import * -from lib.RTAManageXml import ManageXml -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci', '') -obspath = f'{rootpath}/DATA/selections/crab/' -rtapath = f'{rootpath}/DATA/rta_products/crab/' -modelpath = f'{rootpath}/DATA/models/' -filename = f'{obspath}crab_offax_texp{texp}s_n01.fits' -fitname = filename.replace(obspath,rtapath).replace('.fits', '_fit.xml') -model = f'{modelpath}crab.xml' -print(f'Fits: {filename.replace(obspath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# set model -t = time.time() -xml = ManageXml(model) -xml.setTsTrue() -xml.parametersFreeFixed(src_free=['Prefactor']) -xml.closeXml() -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# initialise + fitting -t = time.time() -analysis = RTACtoolsAnalysis() -analysis.nthreads = 1 -analysis.caldb = 'prod3b-v2' -analysis.irf = 'South_z20_0.5h' -analysis.e = [0.05, 20] -analysis.input = filename -analysis.model = model -analysis.output = fitname -analysis.run_maxlikelihood() -tfit = time.time() - t -print(f'Fitting: {tfit} s\n') - -# statistics -t = time.time() -results = ManageXml(fitname) -try: - ts = results.getTs()[0] -except IndexError: - raise Warning('Target not found.') -print(f'sqrt_ts: {np.sqrt(ts)}') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# flux -t = time.time() -spectra = results.getSpectral() -index, pref, pivot = spectra[0][0], spectra[1][0], spectra[2][0] -err = results.getPrefError()[0] -phflux = phflux_powerlaw(index, pref, pivot, analysis.e, unit='TeV') -phflux_err = phflux_powerlaw(index, err, pivot, analysis.e, unit='TeV') -print(f'PH-FLUX {phflux} +/- {phflux_err}\n') -tflux = time.time() - t -print(f'Flux points : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/ctools3d_fit.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tmodel tfit tstat tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tmodel} {tfit} {tstat} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {np.sqrt(ts)} {phflux} {phflux_err} {ttotal} {timport} {tsetup} {tmodel} {tfit} {tstat} {tflux}\n') - log.close() diff --git a/rtasci/rtasci/timing/time_gammapy1d_binned_fit.py b/rtasci/rtasci/timing/time_gammapy1d_binned_fit.py deleted file mode 100644 index 9e070a5182e090cb06a5f6b4ac099404f74dbde4..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy1d_binned_fit.py +++ /dev/null @@ -1,150 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -#from astropy.coordinates import SkyCoord -from astropy import units as u -from astropy.coordinates import SkyCoord -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.modeling.models import PowerLawSpectralModel, SkyModel, PointSpatialModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/obs/crab/crab_offax_texp{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{obs_id:02d}', tstart=gti.table['START'] * u.s, - tstop=gti.table['STOP'] * u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -# target -#target = SkyCoord(pointing.ra, pointing.dec - 0.5 * u.deg, unit='deg', frame='icrs') -target = {'ra': 83.6331, 'dec': 22.0145} -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 1d analysis -t = time.time() -config_1d = AnalysisConfig() -config_1d.general.log = {'level': 'warning'} -config_1d.datasets.type = "1d" -config_1d.datasets.stack = False -# define the ON region and make sure that PSF leakage is corrected -config_1d.datasets.on_region = dict(frame="icrs", lon='%s deg' %target['ra'], lat='%s deg' %target['dec'], radius='0.1 deg') -config_1d.datasets.containment_correction = True -# background -config_1d.datasets.background=dict(method="reflected", exclusion=None) -#config_1d.datasets.safe_mask.methods = ["edisp-bias"] -# define the energy binning for the spectra -config_1d.datasets.geom.axes.energy = dict(min='0.05 TeV', max='20 TeV', nbins=30) -config_1d.datasets.geom.axes.energy_true = dict(min='0.03 TeV', max='30 TeV', nbins=40) -config_1d.datasets.geom.selection.offset_max = '2.5 deg' -# fit -#config_1d.fit.fit_range = dict(min='0.03 TeV', max='30 TeV') -#config_1d.flux_points.energy = dict(min='0.03 TeV', max='30 TeV', nbins=3) -#config_1d.flux_points.source = 'Crab' -# write -#config_1d.write("config1d.yaml", overwrite=True) -#config_1d = AnalysisConfig.read("config1d.yaml") -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') -print(target) -# instantiate data reduction passing directly the config object -t = time.time() -analysis_1d = Analysis(config_1d) -analysis_1d.observations = observations -analysis_1d.get_datasets() -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# statistics -t = time.time() -stats = analysis_1d.datasets.info_table() -print(stats['sqrt_ts'], '\n') -tstat = time.time() - t -print(f'Statistics : {tstat} s\n') - -# prepare models -t = time.time() -stacked_1d = analysis_1d.datasets.stack_reduce(name="stacked") -target = SkyCoord(target['ra'], target['dec'], unit='deg', frame='icrs') -spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") -spectral_model = PowerLawSpectralModel(index=2.48, amplitude=2e-12 * u.Unit("1 / (cm2 s TeV)")) -spectral_model.parameters['index'].frozen = True -spatial_model.parameters['lon_0'].frozen = True -spatial_model.parameters['lat_0'].frozen = True -sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name="Crab") -stacked_1d.models = sky_model -tmodel = time.time() - t -print(f'Modelling : {tmodel} s\n') - -# fitting -t = time.time() -fit_1d = Fit([stacked_1d]) -result_1d = fit_1d.run() -#print(result_1d.parameters.to_table(), '\n') -tfit = time.time() - t -print(f'Fitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/gammapy1d_binned_fit.csv' -row = f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tobs tconf tred tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print (row) \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_gammapy1d_fit.py b/rtasci/rtasci/timing/time_gammapy1d_fit.py deleted file mode 100644 index 9412c38b2c7d5b3ff058eeb63a145753c81ce96f..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy1d_fit.py +++ /dev/null @@ -1,139 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -clock0 = time.time() -t = time.time() -#from astropy.coordinates import SkyCoord -from astropy import units as u -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.modeling.models import PowerLawSpectralModel, SkyModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/selections/crab/crab_offax_texp{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{obs_id:02d}', tstart=gti.table['START'] * u.s, - tstop=gti.table['STOP'] * u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -# target -#target = SkyCoord(pointing.ra, pointing.dec - 0.5 * u.deg, unit='deg', frame='icrs') -target = {'ra': pointing.ra.value, 'dec': pointing.dec.value - 0.5} -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 1d analysis -t = time.time() -config_1d = AnalysisConfig() -config_1d.general.log = {'level': 'warning'} -config_1d.datasets.type = "1d" -config_1d.datasets.stack = False -# define the ON region and make sure that PSF leakage is corrected -config_1d.datasets.on_region = dict(frame="icrs", lon='%s deg' %target['ra'], lat='%s deg' %target['dec'], radius='0.2 deg') -config_1d.datasets.containment_correction = True -# background -config_1d.datasets.background=dict(method="reflected", exclusion=None) -#config_1d.datasets.safe_mask.methods = ["edisp-bias"] -#config_1d.datasets.safe_mask.parameters = dict(edisp_bias=10) -# define the energy binning for the spectra -config_1d.datasets.geom.axes.energy = dict(min='0.05 TeV', max='20 TeV', nbins=1) -config_1d.datasets.geom.axes.energy_true = dict(min='0.03 TeV', max='30 TeV', nbins=1) -config_1d.datasets.geom.selection.offset_max = '3 deg' -# write -#config_1d.write("config1d.yaml", overwrite=True) -#config_1d = AnalysisConfig.read("config1d.yaml") -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') - -# instantiate data reduction passing directly the config object -t = time.time() -analysis_1d = Analysis(config_1d) -analysis_1d.observations = observations -analysis_1d.get_datasets() -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# statistics -t = time.time() -stats = analysis_1d.datasets.info_table() -print(stats['sqrt_ts'], '\n') -tstat = time.time() - t -print(f'Statistics : {tstat} s\n') - -# prepare models -t = time.time() -stacked_1d = analysis_1d.datasets.stack_reduce(name="stacked") -spectral_model = PowerLawSpectralModel(index=2.3, amplitude="2e-12 TeV-1 cm-2 s-1") -spectral_model.parameters['index'].frozen = True -sky_model=SkyModel(spectral_model=spectral_model, name="Crab") -stacked_1d.models = sky_model -tmodel = time.time() - t -print(f'Modelling : {tmodel} s\n') - -# fitting -t = time.time() -fit_1d = Fit([stacked_1d]) -result_1d = fit_1d.run() -#print(result_1d.parameters.to_table(), '\n') -tfit = time.time() - t -print(f'Fitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/gammapy1d_fit.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tobs tconf tred tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() diff --git a/rtasci/rtasci/timing/time_gammapy3d_binned_blindfit.py b/rtasci/rtasci/timing/time_gammapy3d_binned_blindfit.py deleted file mode 100644 index 685de6e355d2e06e0d6e837950d8e0f3900d3234..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy3d_binned_blindfit.py +++ /dev/null @@ -1,176 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import astropy.units as u -from astropy.coordinates import SkyCoord -from regions import CircleSkyRegion -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from gammapy.modeling.models import PointSpatialModel, PowerLawSpectralModel, SkyModel, FoVBackgroundModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -print(rootpath) -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/obs/crab/crab_offax_texp{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -#print('Pointing :', pointing) -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{1:02d}', tstart=gti.table['START']*u.s, - tstop=gti.table['STOP']*u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 3d analysis -t = time.time() -config_3d = AnalysisConfig() -config_3d.general.log = {'level': 'warning'} -config_3d.observations.datastore = '' -config_3d.observations.obs_file = filename -# reduction type -config_3d.datasets.type = '3d' # Analysis type is 3D -config_3d.datasets.stack = False # We keep track of datasets in all bunches -# geometry of the map for 3d -config_3d.datasets.geom.wcs.skydir = {'lon': pointing.ra, 'lat': pointing.dec, 'frame': 'icrs'} -config_3d.datasets.geom.wcs.fov = {'width': '10 deg', 'height': '10 deg'} -config_3d.datasets.geom.wcs.binsize = '0.02 deg' -# The FoV radius to use for cutouts -config_3d.datasets.geom.selection.offset_max = 2.5 * u.deg -# reconstructed energy axis for the counts map -config_3d.datasets.geom.axes.energy = dict(min= "0.05 TeV", max="20 TeV", nbins=30) -# true energy axis for the IRF maps (should always be wider range and larger nbins) -config_3d.datasets.geom.axes.energy_true = dict(min= "0.03 TeV", max="30 TeV", nbins=40) -# backgroun -config_3d.datasets.background = {'method': 'fov_background', 'exclusion': None} -# safe mask from IRF and max offset -config_3d.datasets.safe_mask.methods = ['aeff-default', 'offset-max'] -# what maps to compute -config_3d.datasets.map_selection = ['counts', 'exposure', 'background', 'psf', 'edisp'] -# save the configuration for later and overwrite if already existing -#config_3d.write(filepath + 'tests/prototype3d.yaml', overwrite=True) -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') - -# instantiate data reduction passing directly the config object -t = time.time() -analysis_3d = Analysis(config_3d) -# set observation (single - no list) -analysis_3d.observations = observations -# perform data reduction -analysis_3d.get_datasets() -#print(analysis_3d.get_datasets()) -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# stack and hotspot search -t = time.time() -stacked_3d = analysis_3d.datasets.stack_reduce(name="stacked_3d") -estimator = ExcessMapEstimator(correlation_radius='0.1 deg', selection_optional=[]) -maps = estimator.run(stacked_3d) -hotspots_table = find_peaks(maps["sqrt_ts"].get_image_by_idx((0,)), threshold=3, min_distance='0.5 deg') -try: - hotspots = SkyCoord(hotspots_table["ra"], hotspots_table["dec"]) - ra = hotspots.ra[0].deg - dec = hotspots.dec[0].deg -except KeyError: - raise Warning('No candidates found.') -print(f'Hotsposts: {ra, dec}\n') -tblind = time.time() - t -print(f'Blind search: {tblind} s\n') - -# target significance -t = time.time() -target = SkyCoord(ra, dec, unit='deg', frame='icrs') -target_region = CircleSkyRegion(target.icrs, 0.1 * u.deg) -stats = analysis_3d.datasets.info_table(cumulative=False) -print(stats['sqrt_ts'], '\n') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# modelling -t = time.time() -spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") -spectral_model = PowerLawSpectralModel(index=2.48, amplitude=2e-12 * u.Unit("1 / (cm2 s TeV)"), reference=1 * u.TeV) -spectral_model.parameters['index'].frozen = True -spatial_model.parameters['lon_0'].frozen = True -spatial_model.parameters['lat_0'].frozen = True -sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name="Crab") -bkg_model = FoVBackgroundModel(dataset_name="stacked_3d") -bkg_model.parameters['norm'].frozen = False -stacked_3d.models = [bkg_model, sky_model] -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -t = time.time() -fit = Fit([stacked_3d]) -result = fit.run() -#print(result.parameters.to_table()) -tfit = time.time() - t -print(f'\nFitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/gammapy3d_binned_blindfit.csv' -row = f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ra} {dec} {ttotal} {timport} {tsetup} {tconf} {tred} {tblind} {tstat} {tmodel} {tfit} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ra dec ttotal timport tsetup tobs tconf tred tblind tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print(row) \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_gammapy3d_binned_fit.py b/rtasci/rtasci/timing/time_gammapy3d_binned_fit.py deleted file mode 100644 index ed4431cd4a616c88b0921159b6be0ae8bb41831f..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy3d_binned_fit.py +++ /dev/null @@ -1,164 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import astropy.units as u -from astropy.coordinates import SkyCoord -from regions import CircleSkyRegion -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from gammapy.modeling.models import PointSpatialModel, PowerLawSpectralModel, SkyModel, FoVBackgroundModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/obs/crab/crab_offax_texp{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -#print('Pointing :', pointing) -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{1:02d}', tstart=gti.table['START']*u.s, - tstop=gti.table['STOP']*u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 3d analysis -t = time.time() -config_3d = AnalysisConfig() -config_3d.general.log = {'level': 'warning'} -config_3d.observations.datastore = '' -config_3d.observations.obs_file = filename -# reduction type -config_3d.datasets.type = '3d' # Analysis type is 3D -config_3d.datasets.stack = False # We keep track of datasets in all bunches -# geometry of the map for 3d -config_3d.datasets.geom.wcs.skydir = {'lon': pointing.ra, 'lat': pointing.dec, 'frame': 'icrs'} -config_3d.datasets.geom.wcs.fov = {'width': '10 deg', 'height': '10 deg'} -config_3d.datasets.geom.wcs.binsize = '0.02 deg' -# The FoV radius to use for cutouts -config_3d.datasets.geom.selection.offset_max = 2.5 * u.deg -# reconstructed energy axis for the counts map -config_3d.datasets.geom.axes.energy = dict(min= "0.05 TeV", max="20 TeV", nbins=30) -# true energy axis for the IRF maps (should always be wider range and larger nbins) -config_3d.datasets.geom.axes.energy_true = dict(min= "0.03 TeV", max="30 TeV", nbins=40) -# backgroun -config_3d.datasets.background = {'method': 'fov_background', 'exclusion': None} -# safe mask from IRF and max offset -#config_3d.datasets.safe_mask.methods = ['aeff-default', 'offset-max'] -# what maps to compute -config_3d.datasets.map_selection = ['counts', 'exposure', 'background', 'psf', 'edisp'] -# save the configuration for later and overwrite if already existing -#config_3d.write(filepath + 'tests/prototype3d.yaml', overwrite=True) -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') - -#print(config_3d) - -# instantiate data reduction passing directly the config object -t = time.time() -analysis_3d = Analysis(config_3d) -# set observation (single - no list) -analysis_3d.observations = observations -# perform data reduction -analysis_3d.get_datasets() -#print(analysis_3d.get_datasets()) -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# target significance -t = time.time() -target = {'ra': 83.6331, 'dec': 22.0145} -target = SkyCoord(target['ra'], target['dec'], unit='deg', frame='icrs') -target_region = CircleSkyRegion(target.icrs, 0.1 * u.deg) -stats = analysis_3d.datasets.info_table(cumulative=False) -print(stats['sqrt_ts']) -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# modelling -t = time.time() -stacked_3d = analysis_3d.datasets.stack_reduce(name="stacked_3d") -spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") -spectral_model = PowerLawSpectralModel(index=2.48, amplitude=2e-12 * u.Unit("1 / (cm2 s TeV)"), reference=1 * u.TeV) -spectral_model.parameters['index'].frozen = True -spatial_model.parameters['lon_0'].frozen = True -spatial_model.parameters['lat_0'].frozen = True -sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name="Crab") -bkg_model = FoVBackgroundModel(dataset_name="stacked_3d") -bkg_model.parameters['norm'].frozen = False -stacked_3d.models = [bkg_model, sky_model] -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -t = time.time() -fit = Fit([stacked_3d]) -result = fit.run() -#print(result.parameters.to_table()) -tfit = time.time() - t -print(f'\nFitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/gammapy3d_binned_fit.csv' -row = f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tobs tconf tred tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(row) - log.close() -else: - log = open(logname, 'a') - log.write(row) - log.close() - -print(row) \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_gammapy3d_blindfit.py b/rtasci/rtasci/timing/time_gammapy3d_blindfit.py deleted file mode 100644 index b44d99cd98001b58a927b94ec6eb965c6667f244..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy3d_blindfit.py +++ /dev/null @@ -1,173 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import astropy.units as u -from astropy.coordinates import SkyCoord -from regions import CircleSkyRegion -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from gammapy.modeling.models import PointSpatialModel, PowerLawSpectralModel, SkyModel, FoVBackgroundModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/selections/crab/crab_offax{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -#print('Pointing :', pointing) -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{1:02d}', tstart=gti.table['START']*u.s, - tstop=gti.table['STOP']*u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 3d analysis -t = time.time() -config_3d = AnalysisConfig() -config_3d.general.log = {'level': 'warning'} -config_3d.observations.datastore = '' -config_3d.observations.obs_file = filename -# reduction type -config_3d.datasets.type = '3d' # Analysis type is 3D -config_3d.datasets.stack = False # We keep track of datasets in all bunches -# geometry of the map for 3d -config_3d.datasets.geom.wcs.skydir = {'lon': pointing.ra, 'lat': pointing.dec, 'frame': 'icrs'} -config_3d.datasets.geom.wcs.fov = {'width': '6 deg', 'height': '6 deg'} -config_3d.datasets.geom.wcs.binsize = '0.02 deg' -# The FoV radius to use for cutouts -config_3d.datasets.geom.selection.offset_max = 5 * u.deg -# reconstructed energy axis for the counts map -config_3d.datasets.geom.axes.energy = dict(min= "0.05 TeV", max="10 TeV", nbins=1) -# true energy axis for the IRF maps (should always be wider range and larger nbins) -config_3d.datasets.geom.axes.energy_true = dict(min= "0.03 TeV", max="30 TeV", nbins=1) -# backgroun -config_3d.datasets.background = {'method': 'fov_background', 'exclusion': None} -# safe mask from IRF and max offset -config_3d.datasets.safe_mask.methods = ['aeff-default', 'offset-max'] -# what maps to compute -config_3d.datasets.map_selection = ['counts', 'exposure', 'background', 'psf', 'edisp'] -# save the configuration for later and overwrite if already existing -#config_3d.write(filepath + 'tests/prototype3d.yaml', overwrite=True) -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') - -# instantiate data reduction passing directly the config object -t = time.time() -analysis_3d = Analysis(config_3d) -# set observation (single - no list) -analysis_3d.observations = observations -# perform data reduction -analysis_3d.get_datasets() -#print(analysis_3d.get_datasets()) -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# stack and hotspot search -t = time.time() -stacked_3d = analysis_3d.datasets.stack_reduce(name="stacked_3d") -estimator = ExcessMapEstimator(correlation_radius='0.1 deg', selection_optional=[]) -maps = estimator.run(stacked_3d) -hotspots_table = find_peaks(maps["sqrt_ts"].get_image_by_idx((0,)), threshold=9, min_distance='0.5 deg') -try: - hotspots = SkyCoord(hotspots_table["ra"], hotspots_table["dec"]) - print(hotspots) - ra = hotspots.ra[0].deg - dec = hotspots.dec[0].deg -except KeyError: - raise Warning('No candidates found.') -print(f'Hotsposts: {ra, dec}\n') -tblind = time.time() - t -print(f'Blind search: {tblind} s\n') - -# target significance -t = time.time() -target = SkyCoord(ra, dec, unit='deg', frame='icrs') -target_region = CircleSkyRegion(target.icrs, 0.1 * u.deg) -stats = analysis_3d.datasets.info_table(cumulative=False) -print(stats['sqrt_ts'], '\n') -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# modelling -t = time.time() -spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") -spectral_model = PowerLawSpectralModel(index=2.3, amplitude=2e-12 * u.Unit("1 / (cm2 s TeV)"), reference=1 * u.TeV) -spectral_model.parameters['index'].frozen = True -spatial_model.parameters['lon_0'].frozen = True -spatial_model.parameters['lat_0'].frozen = True -sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name="Crab") -bkg_model = FoVBackgroundModel(dataset_name="stacked_3d") -bkg_model.parameters['norm'].frozen = False -stacked_3d.models = [bkg_model, sky_model] -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -t = time.time() -fit = Fit([stacked_3d]) -result = fit.run() -#print(result.parameters.to_table()) -tfit = time.time() - t -print(f'\nFitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/prova.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ra dec ttotal timport tsetup tobs tconf tred tblind tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ra} {dec} {ttotal} {timport} {tsetup} {tconf} {tred} {tblind} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ra} {dec} {ttotal} {timport} {tsetup} {tconf} {tred} {tblind} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() \ No newline at end of file diff --git a/rtasci/rtasci/timing/time_gammapy3d_fit.py b/rtasci/rtasci/timing/time_gammapy3d_fit.py deleted file mode 100644 index b09f835cf6e67c6de87ed7a8c2e70331c49a3995..0000000000000000000000000000000000000000 --- a/rtasci/rtasci/timing/time_gammapy3d_fit.py +++ /dev/null @@ -1,160 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import time -import sys -import os -texp = sys.argv[1] -first = sys.argv[2] - -# start timing -t = time.time() -clock0 = time.time() -import astropy.units as u -from astropy.coordinates import SkyCoord -from regions import CircleSkyRegion -from gammapy.analysis import Analysis, AnalysisConfig -from gammapy.data import EventList, GTI, Observation, Observations -from gammapy.irf import load_cta_irfs -from gammapy.modeling import Fit -from gammapy.estimators import ExcessMapEstimator -from gammapy.estimators.utils import find_peaks -from gammapy.modeling.models import PointSpatialModel, PowerLawSpectralModel, SkyModel, FoVBackgroundModel -timport = time.time() - t -print(f'Imports : {timport} s\n') - -t = time.time() -rootpath = str(os.path.dirname(os.path.abspath(__file__))).replace('cta-sag-sci/rtasci/timing', '') -caldb = f'{rootpath}/caldb/data/cta/prod3b-v2/bcf/South_z20_0.5h/irf_file.fits' -irfs = load_cta_irfs(caldb) -filename = f'{rootpath}/DATA/selections/crab/crab_onax_texp{texp}s_n01.fits' -obs_id = 1 -print(f'Fits: {filename.replace(rootpath, "")}\n') -tsetup = time.time() - t -print(f'Setup : {tsetup} s\n') - - -# read phlist -t = time.time() -events = EventList.read(filename, hdu='EVENTS') -# get GTI -gti = GTI.read(filename, hdu='GTI') -# get pointing -pointing = events.pointing_radec -#print('Pointing :', pointing) -# create observation -observation = Observation.create( - pointing=pointing, obs_id=f'{1:02d}', tstart=gti.table['START']*u.s, - tstop=gti.table['STOP']*u.s, irfs=irfs, reference_time=gti.time_ref) -observation._events = events -#print(observation.gti) -observations = Observations() -observations.append(observation) -# fix pointing info -observation.fixed_pointing_info -tobs = time.time() - t -print(f'Create observation : {tobs} s\n') - -# configure a 3d analysis -t = time.time() -config_3d = AnalysisConfig() -config_3d.general.log = {'level': 'warning'} -config_3d.observations.datastore = '' -config_3d.observations.obs_file = filename -# reduction type -config_3d.datasets.type = '3d' # Analysis type is 3D -config_3d.datasets.stack = False # We keep track of datasets in all bunches -# geometry of the map for 3d -config_3d.datasets.geom.wcs.skydir = {'lon': pointing.ra, 'lat': pointing.dec, 'frame': 'icrs'} -config_3d.datasets.geom.wcs.fov = {'width': '6 deg', 'height': '6 deg'} -config_3d.datasets.geom.wcs.binsize = '0.02 deg' -# The FoV radius to use for cutouts -config_3d.datasets.geom.selection.offset_max = 2.5 * u.deg -# reconstructed energy axis for the counts map -config_3d.datasets.geom.axes.energy = dict(min= "0.05 TeV", max="10 TeV", nbins=1) -# true energy axis for the IRF maps (should always be wider range and larger nbins) -config_3d.datasets.geom.axes.energy_true = dict(min= "0.03 TeV", max="30 TeV", nbins=1) -# backgroun -config_3d.datasets.background = {'method': 'fov_background', 'exclusion': None} -# safe mask from IRF and max offset -config_3d.datasets.safe_mask.methods = ['aeff-default', 'offset-max'] -# what maps to compute -config_3d.datasets.map_selection = ['counts', 'exposure', 'background', 'psf', 'edisp'] -# save the configuration for later and overwrite if already existing -#config_3d.write(filepath + 'tests/prototype3d.yaml', overwrite=True) -tconf = time.time() - t -print(f'Configuration : {tconf} s\n') - -#print(config_3d) - -# instantiate data reduction passing directly the config object -t = time.time() -analysis_3d = Analysis(config_3d) -# set observation (single - no list) -analysis_3d.observations = observations -# perform data reduction -analysis_3d.get_datasets() -#print(analysis_3d.get_datasets()) -tred = time.time() - t -print(f'Data Reduction : {tred} s\n') - -# target significance -t = time.time() -target = SkyCoord(pointing.ra.deg, pointing.dec.deg, unit='deg', frame='icrs') -target_region = CircleSkyRegion(target.icrs, 0.1 * u.deg) -stats = analysis_3d.datasets.info_table(cumulative=False) -print(stats['sqrt_ts']) -tstat = time.time() - t -print(f'Statistics: {tstat} s\n') - -# modelling -t = time.time() -stacked_3d = analysis_3d.datasets.stack_reduce(name="stacked_3d") -spatial_model = PointSpatialModel(lon_0=target.ra, lat_0=target.dec, frame="icrs") -spectral_model = PowerLawSpectralModel(index=2.3, amplitude=2e-12 * u.Unit("1 / (cm2 s TeV)"), reference=1 * u.TeV) -spectral_model.parameters['index'].frozen = True -spatial_model.parameters['lon_0'].frozen = True -spatial_model.parameters['lat_0'].frozen = True -sky_model = SkyModel(spatial_model=spatial_model, spectral_model=spectral_model, name="Crab") -bkg_model = FoVBackgroundModel(dataset_name="stacked_3d") -bkg_model.parameters['norm'].frozen = False -stacked_3d.models = [bkg_model, sky_model] -tmodel = time.time() - t -print(f'Modelling: {tmodel} s\n') - -# fitting -t = time.time() -fit = Fit([stacked_3d]) -result = fit.run() -#print(result.parameters.to_table()) -tfit = time.time() - t -print(f'\nFitting : {tfit} s\n') - -# flux -t = time.time() -phflux_err = spectral_model.integral_error(0.05 * u.TeV, 20 * u.TeV) -print(f'\nPH-FLUX {phflux_err.value[0]} +/- {phflux_err.value[1]}') -tflux = time.time() - t -print(f'\nFlux : {tflux} s\n') - -ttotal = time.time() - clock0 -print(f'Total time: {ttotal} s\n') -print('\n\n-----------------------------------------------------\n\n') - -logname = f'{rootpath}/DATA/outputs/crab/gammapy3d_fit.csv' -if first == 'True': - hdr = 'texp sqrt_ts flux flux_err ttotal timport tsetup tobs tconf tred tstat tmodel tfit tflux\n' - log = open(logname, 'w+') - log.write(hdr) - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() -else: - log = open(logname, 'a') - log.write(f'{texp} {stats["sqrt_ts"][0]} {phflux_err.value[0]} {phflux_err.value[1]} {ttotal} {timport} {tsetup} {tconf} {tred} {tstat} {tmodel} {tfit} {tflux}\n') - log.close() \ No newline at end of file diff --git a/rtasci/setup.py b/rtasci/setup.py deleted file mode 100644 index a0a08c761ca194b50088faafbcfcc0401c58245b..0000000000000000000000000000000000000000 --- a/rtasci/setup.py +++ /dev/null @@ -1,26 +0,0 @@ -# ***************************************************************************** -# Copyright (C) 2020 INAF -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# Leonardo Baroncelli -# ***************************************************************************** - -from setuptools import setup, find_packages - -entry_points = { - 'console_scripts': [ - 'simGRBcatalogWithRandomization = rtasci.simGRBcatalogWithRandomization:main', - 'simGRBcatalog = rtasci.simGRBcatalog:main' - ] -} - -setup(name='rtasci', - author='Ambra Di Piano', - author_email='ambra.dipiano@inaf.it', - package_dir={'rtasci': 'rtasci'}, - packages=find_packages(), - include_package_data=True, - license='BSD-3-Clause', -) diff --git a/rtavis b/rtavis new file mode 160000 index 0000000000000000000000000000000000000000..1d895dca2694f40c4b63ea2714c76d08cfce1d41 --- /dev/null +++ b/rtavis @@ -0,0 +1 @@ +Subproject commit 1d895dca2694f40c4b63ea2714c76d08cfce1d41 diff --git a/rtavis/.gitignore b/rtavis/.gitignore deleted file mode 100644 index 0286189c746528d086469020578f06ec8b0428c2..0000000000000000000000000000000000000000 --- a/rtavis/.gitignore +++ /dev/null @@ -1,132 +0,0 @@ -# my ignores -*.svg - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ diff --git a/rtavis/LICENSE b/rtavis/LICENSE deleted file mode 100644 index 59523307fb74267b84be94cb4cd687b394660c06..0000000000000000000000000000000000000000 --- a/rtavis/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2021, Ambra Di Piano - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/rtavis/README.md b/rtavis/README.md deleted file mode 100644 index 6bb0c15fa217bbefd019de7bfad86ab737c6113f..0000000000000000000000000000000000000000 --- a/rtavis/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# rtavis - -This repository hosts a custom visibility tool `rtavis` for real-time analysis. - - -# Environment and package installation - -To create a virtual environment with all required dependencies: - -```bash -conda env create --name --file=environment.yaml -``` - -Note that you should already have anaconda installed: https://www.anaconda.com/ - -You can then proceed to install the software: - -```bash -pip install . -``` - -for editable installation: - -```bash -pip install -e . -``` - -## Instrument Response Functions - -To complete the environment be sure to download and install the correct IRFs (only prod2 comes with ctools installation). Public IRFs can be downloaded from here: https://www.cta-observatory.org/science/cta-performance/ - -## Configuration - -Under `cfg` you can find a sample configuration file. Description of each parameter is commented within. This file will serve as input when running the code. - -## Compute source visibility - -After adjusting the configuration file to your needs, you can run the code as follows: - -```bash -python runCatVisibility.py -f cfg/config.yaml -``` - -### Reading the visibility table - -The output is saved via numpy as a binary NPY file. You can run an example of how to access data like this: - -```bash -python readVisTable -f path/to/output.npy -``` - -### Notebook: plot the visibility -A notebook for useful plot and checks on the visibility is provided in the *notebooks* folder. diff --git a/rtavis/environment.yaml b/rtavis/environment.yaml deleted file mode 100644 index 4a7036dff9790f07e49b89b003eb7807016623b8..0000000000000000000000000000000000000000 --- a/rtavis/environment.yaml +++ /dev/null @@ -1,13 +0,0 @@ -channels: - - conda-forge - - cta-observatory -dependencies: - - python=3.7 - - gammalib=1.7.3 - - ctools=1.7.3 - - astropy - - pyyaml - - scipy - # optional (i.e. for notebook) - - matplotlib - - pandas diff --git a/rtavis/pyproject.toml b/rtavis/pyproject.toml deleted file mode 100644 index fa7093a33c048d9db6e0eb3b4d8daa57426754e3..0000000000000000000000000000000000000000 --- a/rtavis/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools>=42"] -build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/rtavis/rtavis/cfg/config.yaml b/rtavis/rtavis/cfg/config.yaml deleted file mode 100644 index 9cc5123e8c181f28a0b54cfe0bb7450aaa4342ed..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/cfg/config.yaml +++ /dev/null @@ -1,64 +0,0 @@ -# path configuration -path: - # template (or list of templates) --> null if you want to run an entire catalog - filename: template.fits - # folder where templates are stored - catalog: path/to/templates/folder - # output where visibility table will be saved (used also as input for signigficance calculation) - output: path/to/visibility/output.npy - # input XML model --> null if you want to run an entire catalog - xml_filename: model_Event*.xml - # folder where input XML models are stored (the program looks for xml files even in subdirectories) - xml_dir: path/to/xml_models/folder - #output where significance table will be saved - sigmaoutput: path/to/significance/output.npy -setup: - # sub altitude below which starts the night (for astronomical darkness set -18) - twilight: -18 - # min angular separation from Moon --> null if moon should not be considered - moon_sep: 30 - # max moon phase (range 0-1) threshold of observability - moon_pha: 0.8 - # FoV radius (min angular separation will be added to this); set 0 if moon_sep wrt pointing - fov_rad: 3 - # altitude (lower) thresholds for IRFs ranges - thresholds: - - 10 - - 36 - - 57 - # relative IRFs zenith angle - zenith_angles: - - 60 - - 40 - - 20 -# all sites -sites_list: - North: Roque de los Muchachos - South: Paranal -# total grid points (only if use_visibility_table: no) -total_points: 100 -# grid points within visibility window -window_points: 10 -# set ephemeris (if jpl -> install them beforehand) -ephemeris: default -#parameters to use for simulation with ctools -ctools: - #maximum value of energy up to wich simualte (TeV) - emax : 10.0 - #simulation radius - rad: 5.0 - #calibration database - caldb: prod3b-v2 - #number of times the simulation will be iterated - iterations: 1 - #offset of on and off regions from the pointing center - offset: 0.75 - #number of off regions - off_regions: 5 - #repointing time delay - pointing_delay: 30. - #stop iterating at N sigma? - 3sigma_stop: no - 5sigma_stop: no - - diff --git a/rtavis/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy b/rtavis/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy deleted file mode 100644 index 757455db5636d6267f60c0ce00e10c01ff4a7fe2..0000000000000000000000000000000000000000 Binary files a/rtavis/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy and /dev/null differ diff --git a/rtavis/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy b/rtavis/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy deleted file mode 100644 index ec262ca2323de453bfd63bcbeed23bd9d16385a2..0000000000000000000000000000000000000000 Binary files a/rtavis/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy and /dev/null differ diff --git a/rtavis/rtavis/notebooks/plotSignificance.ipynb b/rtavis/rtavis/notebooks/plotSignificance.ipynb deleted file mode 100644 index a5864b111f7b360ba2542d376189b5864216bf0e..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/notebooks/plotSignificance.ipynb +++ /dev/null @@ -1,357 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "71082ff8", - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import pyplot as plt\n", - "from matplotlib.lines import Line2D\n", - "import numpy as np\n" - ] - }, - { - "cell_type": "markdown", - "id": "adb3d2a6", - "metadata": {}, - "source": [ - "In the examples directory you can find the npy tables of two events: Event54 and Event751. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "1a7522e0", - "metadata": {}, - "outputs": [], - "source": [ - "summed={}\n", - "event_id= 'Event751'#name of the event you want to plot (usefull when using catalog tables of significance)\n", - "phase= 'afterglow' #can be either afterglow or prompt" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5272aa72", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "data= np.load('examples/Evt751.noMoon.thresh10.npy', #the plotted one is Event751 whose table is in examples dir \n", - " allow_pickle=True, encoding='latin1', fix_imports=True).flat[0]\n", - "\n", - "events = list(data.keys())\n", - "sites = list(data[events[0]].keys())" - ] - }, - { - "cell_type": "markdown", - "id": "c328a863", - "metadata": {}, - "source": [ - "The following lines generate a custom legend for the plots." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b12f6f5b", - "metadata": {}, - "outputs": [], - "source": [ - "custom_lines = [Line2D([0], [0], color='red', lw=4, alpha=0.3),\n", - " Line2D([0], [0], color='yellow', lw=4,alpha=0.3),\n", - " Line2D([0], [0], color='green', lw=4,alpha=0.3),\n", - " Line2D([0], [0], marker='o', color='red', markerfacecolor='red', markersize=5),\n", - " Line2D([0], [0], marker='o', color='g', markerfacecolor='g', markersize=5)]" - ] - }, - { - "cell_type": "markdown", - "id": "d5989893", - "metadata": {}, - "source": [ - "Here the event significance, in a separate way for EACH site, is plot as a function of time." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "66fb4eab", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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//vfmTjxw+y//0F87nNnsm3btp3uR5IkqbfoZqtbzOvk9j/cbE2/fv246655XHbZ1xg6dJd2t1xTU8OCBQspLx/MkUdO2pkgd3DNNddx0EEf4K233moo+/d//zoXX/wlTjvtVM4773xmz/4FX/ziOXntV5IkqadyJLmNSktLOffcaVx99bU71K1YsYLJkz/O2LGHceyxn+SVV14BYNq0L3LeeeczceKHOfXUM7j++l9w9dXXbfdY6scee4Ijj/wo++13IHfccVez/V9++XcbHj291177Mn36FwBYtWoVv/3t/Xz+89Mb9k0pMX/+Aj7zmU8DMHXq2dxzz715uxaSJEk9nUlyO5x//ue59dbb2bhx43blF154MVOnnsXy5c9w5pmf5Utf+kpD3apVr/Hkk49y112/5rzzpnPxxReydOni7COpYfXqNSxc+Aj33XcPM2d+neZ8+9tfZ+nSxSxY8BBDhuzGBRd8EYCLLrqUH/zge/Tp8+4/ZWXlmwwePIjS0swfCkaM2IvXXns9b9dBkiSppzNJboeBAwdyzjlncu21P9uufNGipzjjjNMAOPvs01i48MmGus9+9hRKSkqabfPkkz9Bnz59GD36INaseaPF/lNKnHXWNL7ylS9z2GGHct99D7D77sM47LBDd+KsJEmSlKubzUk+sZPbX9/qHhdddCGHHjqB6dOntanFXXZpef5yv379Gt6nlFrc94orvsOIEXsxffpUAJ544inuvfe3/M//PMjWrVt56623OOusadxyy/9hw4aN1NTUUFpayqpVr7HXXu9tU7ySJElyJLndhgwZwqmnforZs+c0lB155Ie4/fZfA3Drrb/m6KOPavLYXXct5+23qzrU77x59/Pww/O59tqrG8q+971vsmrV31ix4iVuv/0WJk8+hl/+cg4Rwcc+9tGGOc433XQLJ53U2V8wJEmSeg6T5A645JILWLduXcP2ddddzS9+cTNjxx7GLbf8imuu+VGTx5144vHcfffc7W7ca6urrvoJr732OhMmHMX48Udw+eXfanH/73//u1x11TXsv/9BVFa+yYwZ01vcX5IkSe/qZtMtiqeq6k1gCwB77LE7mzdvyNZsYeTIkcyf/2DDNrwHgDlzftrwHuCAA/Zn+fJnGrYzN+9tyemjaY88ct92bb3bV8Yxx3yUY475aMP2fvvtx9NPP9Hs/pIk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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "for event in events:\n", - " summed[event]={}\n", - " if event == event_id:\n", - " for site in sites:\n", - "\n", - " summed[event][site]={'t_start':[],'t_stop':[],'irf':[],'significance':[], 'variance':[]}\n", - "\n", - " for night in data[event][site]:\n", - " if night != 'first_night_start':\n", - " if type(data[event][site][night]['irf']) == float:\n", - " print(f'\\tThis contains NaNs event---> '\n", - " f'the source is not observable at the site {site} during {night}.')\n", - " summed[event][site]['significance']=0.0\n", - " else:\n", - " summed[event][site]['t_stop'].append(data[event][site][night]['t_stop'])\n", - " summed[event][site]['t_start'].append(data[event][site][night]['t_start'])\n", - " summed[event][site]['irf'].append(data[event][site][night]['irf'])\n", - " if type(summed[event][site]['significance'])==float:\n", - " summed[event][site]['significance']=[]\n", - " summed[event][site]['significance'].append(data[event][site][night]['significance'])\n", - " summed[event][site]['variance'].append(data[event][site][night]['variance'])\n", - "\n", - "\n", - "\n", - " if '3sigma' in data[event][site][night].keys() and '3sigma' not in summed[event][site].keys():\n", - " summed[event][site]['3sigma']=[data[event][site][night]['3sigma'][0],\n", - " data[event][site][night]['3sigma'][1]]\n", - " if '5sigma' in data[event][site][night].keys() and '5sigma' not in summed[event][site].keys():\n", - " summed[event][site]['5sigma']=[data[event][site][night]['5sigma'][0],\n", - " data[event][site][night]['5sigma'][1]]\n", - "\n", - " if type(summed[event][site]['significance']) != float:\n", - " fig, axes = plt.subplots(1 ,figsize=(12,6))\n", - " summed[event][site]['t_stop'] = np.array([ elem for singleList in summed[event][site]['t_stop'] \n", - " for elem in singleList])\n", - " summed[event][site]['t_start'] = np.array([ elem for singleList in summed[event][site]['t_start'] \n", - " for elem in singleList])\n", - " summed[event][site]['irf'] = np.array([ elem for singleList in summed[event][site]['irf'] \n", - " for elem in singleList])\n", - " summed[event][site]['significance'] = np.array([ elem for singleList \n", - " in summed[event][site]['significance'] \n", - " for elem in singleList])\n", - " summed[event][site]['variance'] = np.array([ elem for singleList \n", - " in summed[event][site]['variance'] \n", - " for elem in singleList])\n", - "\n", - " color_min = min(summed[event][site]['significance']) - max(summed[event][site]['variance'])\n", - " color_max = max(summed[event][site]['significance']) + max(summed[event][site]['variance'])\n", - "\n", - "\n", - " for i in range(len(summed[event][site]['irf'])):\n", - " t_start= summed[event][site]['t_start'][i]\n", - " t_stop = summed[event][site]['t_stop'][i]\n", - "\n", - "\n", - " if 'z20' in summed[event][site]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop], color_min, color_max, color='red', alpha=0.1,\n", - " label=f'IRF z20')\n", - " elif 'z40' in summed[event][site]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop], color_min, color_max, color='yellow', alpha=0.1,\n", - " label='IRF z40')\n", - " elif 'z60' in summed[event][site]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop], color_min, color_max, color='green', alpha=0.1,\n", - " label='IRF z60')\n", - "\n", - "\n", - " axes.errorbar (summed[event][site]['t_stop'], summed[event][site]['significance'], \n", - " yerr= summed[event][site]['variance'], color='#607c8e',fmt='o')\n", - " if '3sigma' in summed[event][site].keys():\n", - " axes.errorbar (summed[event][site]['3sigma'][0], summed[event][site]['3sigma'][1],\n", - " color='red',fmt='o')\n", - " if '5sigma' in summed[event][site].keys(): \n", - " axes.errorbar (summed[event][site]['5sigma'][0], summed[event][site]['5sigma'][1],\n", - " color='g',fmt='o')\n", - "\n", - " axes.set_xlabel(r'Time since \"explosion\"(s)')\n", - " axes.set_ylabel(r'$\\sigma_{Li&Ma}$')\n", - " axes.set_xscale('log')\n", - " axes.legend(custom_lines, [f'{site}_z20', f'{site}_z40', f'{site}_z60',r'$3\\sigma$', r'$5\\sigma$'])\n", - " axes.set_title(f'{event} - {site} - {phase}')\n", - " #plt.savefig(f'examples/{event}-{site}-{phase}.png') \n", - "\n", - "\n", - "\n", - "plt.show()\n" - ] - }, - { - "cell_type": "markdown", - "id": "0f5dc1e1", - "metadata": {}, - "source": [ - "Event751 has been simulated 100 times with 100 different seed values, randomly generated. Variance here reported is reffered to the different values obtained for each seed. \n", - "Event54 has been simulated only once, hence variance is null.\n", - "The last part of the code plots the detailed behaviour of significane at each single night." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3ee0b157", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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95k27ZdMm/ZLrrjiZ+3SryRJUlfmiHQ769mzJ5MmncvVV0//yLXly5czduwxlJWN4eijj+H1118HMiPO5513PqNGHc0pp5zBddf9hquvvpYRIw7jz39+DIBHH/0zY8Z8gX32+VSzo9NTp17OiBGHMWLEYQwa9AkmTPgGAwcO5OCDDwJgxx135IAD9mfVqlUAzJ49h3POyeyPc845Z3H33fe06+chSZLUVZlI58H555/HLbfcRmVlZb3yCy/8LueccxZLlz7BmWeezre//b3aaytXruKJJx7kzjtv57zzvsF3v3shixcv5LOf/QwAq1f/g8ceu597772bKVP+tcm+f/zjS1m8eCHz589ll10+xgUXfKve9eXLl/Pss8sYNWokAG+++RYDB2Y2ltxjjz1488232uUzkCRJ6upMpPNgp5124utfP5Pp0/+7XvmCBU9yxhmnAXD22Wfy2GNP1F772tfGUVLS9M6BX/7yifTo0YOhQw/YZrKbUuKss8bzve99h0MOObi2fP369Ywbdxq/+tXP2GmnnT5yX0S41J0kSVILdc050ieckL+26+5s2IyLLrqQgw8eyYQJ41vU7A47ND+fuk+fPrXHKaVm61522U8YPHgQEyacU1u2ZcsWxo07lTPPPI2vfvXE2vLdd9+N1atXM3DgQFavXs1uuw1oUbySJEndnSPSebLLLrtwyilfYcaMmbVlY8Yczm233Q7ALbfcymc/e0Sj9+64Yz/ee299Tv3OmXMvDz00j+nTr64tSykxceI3OeCAT/O9711Ur/6JJx7PjTf+HoAbb/w9J52Ux19CJEmSuhAT6Ty6+OILWLt2be35tddeze9+dxNlZWO4+eZbuOaa/2j0vhNO+BJ33TW73suGLXXVVdNZtervjBx5BCNGHMbUqZfz+ONPcPPNtzBv3vzsi4if4Y9/vA+AKVO+z9y5D7HffkN56KE/MWXK93N/YEmSpG6ka07tKKL169fVHu+++25s2PBO7fmQIUOYN+8BYCOwfW35zJm/zR5tBGD//fdn6dKna6/XvHBYc71uHw09/PCDjZanVHc6yof9l5aW8qc/PdDcI0mSJKkRjkhLkiRJOXBEupNatuw5zj57Qr2yPn168eSTTzRxhyRJktqTiXQnNWzYgSxevLBB6caixCJJktQdObVDkiRJyoGJtCRJkpQDE2lJkiQpBybSkqR6ps2Yxy13LC92GJLU4ZlIt7OIPlx88Q9qz6+88iouu+wnrWpj/vxHeOKJBbXn48efyx133Nnm2KqrqznooJEcf/yptWWvvfYao0Z9hn33PYBTTz2TzZs3t7kfSZKk7qCLrtoxJ39N99gMfL7Jy3369OHOO+/mkkt+wK677tDq5quqqpg//1H69duBMWNGtyHQj7rmmms54IBP8+6779SW/fCH/8p3v/ttTjvtFM4773xmzPgd3/rWN9u1X0mSpK7IEel21rNnTyZNOperr57+kWvLly9n7NhjKCsbw9FHH8Prr78OZEaczzvvfEaNOppTTjmD6677DVdffW29LcIfffTPjBnzBfbZ51PNjk5PnXp5dhvwwxg06BNMmPANAFauXMkf/nAf55774drTKSXmzZvPySd/FYBzzjmbu+++p90+C0mSpK7MRDoPzj//PG655TYqKyvrlV944Xc555yzWLr0Cc4883S+/e3v1V5buXIVTzzxIHfeeTvnnfcNvvvdC1m8eGHt9uCrV/+Dxx67n3vvvZspU/61yb5//ONLWbx4IfPnz2WXXT7GBRd8C4CLLprML37xc3r0+PBbXlFRwc4796dnz8wfJgYPHsSqVX9vt89BkiSpKzORzoOddtqJr3/9TKZP/+965QsWPMkZZ5wGwNlnn8ljj324C+HXvjaOkpKSJtv88pdPpEePHgwdegBvvvlWs/2nlDjrrPF873vf4ZBDDubee//AbrsN4JBDDm7DU0mSJKmuLjpH+oT8Nb11E/DBNqtddNGFHHzwSCZMGN+iZnfYofn51H369Kk9Tik1W/eyy37C4MGDmDDhHAAef3wB99zzB/74xwfYtGkT7777LmedNZ6bb/4d77xTSVVVFT179mTlylUMGvTxFsUrSZLU3TkinSe77LILp5zyFWbMmFlbNmbM4dx22+0A3HLLrXz2s0c0eu+OO/bjvffW59TvnDn38tBD85g+/erasp///N9ZufJvLF/+MrfddjNjxx7J738/k4jgc5/759o51zfeeDMnnZTHX0IkSZK6EBPpPLr44gtYu3Zt7fm1117N7353E2VlY7j55lu45pr/aPS+E074EnfdNbvey4YtddVV01m16u+MHHkEI0YcxtSplzdb/4orfspVV13DvvseQEXFOiZOnNBsfUmSJGUUbGpHRJQAi4BVKaXjG1zr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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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YovNOeecb6y3f+7cN3jhhVmMGDEcgIUL32bgwIEAbL311ixc+HZJroEkSVJn1znXSB91VBkbL/xCloZtuummnH76iVx99a/YaKMN68qffPJpJk++A4DTTjuF73zn4rp9X/3qKLp3b/ybA4899mi6devGzjt/ptmwm1Li1FPP4JvfPJ8999yjrrympoZRo07n5z+/gk033fRjx0WEt7qTJElqIWeky+SCC/4fN9xwEx98sLxF9TfZZJMm9/fq1avucUqpybqXXvpDBg8exJgxo+vKVq9ezahRJ3DKKV/lK185tq58q622ZMGCBQAsWLCALbccUL85SZIkNcAgXSZbbLE5xx8/ihtuuKmubL/99uH223Mz0pMm3caBB+7b4LF9+vTm/fdrMvU7ZcoDPPzwNK6++qq6spQSZ575dXba6TN885vnrFf/6KOP5OabfwPAzTf/hmOOKedsviRJUudhkC6jiy66gMWLF9dtX3PNVdx44y0MG7Ynt946iV/8YmKDxx111Ejuvvv3633YsKWuvPJXzJ//T4YP35+qqr2ZMOEyHn/8CW69dRLTps2gquoAqqr25v77HwBg/PhvM3Xqw3zqUzvz8MN/Yvz4b2c/YUmSpC6kc66RbkM1NUvrHm+11VYsX/5ufmsFQ4YMYdq0hwpqrwDgppuuX6+NHXfcgdmzn6vbrv3AYUN91Dd9+n3ARh8rT6l2XfeK9fb369ePP/3poY/VlyRJUtOckZYkSZIycEa6g5oz50VOO23MemW9evXi6aenttGIJEmSuhaDdAe16667UF39bAN7VlR8LJIkSV2RSzskSZKkDAzSkiRJUgYGaUmSJCkDg3SJRfTioou+U7d9xRVXcumlPyyqjRkzHuWJJ56s2z7jjLO4887JrR7b2rVr2X33AznyyGPryv7+978zYsQB7LDDTpxwwimsWrWq1f1IkiR1BZ30w4ZTytj2KuBfGt3bq1cvJk++h4svPo/+/QcX3fqaNWuYMeMxevfuy377NfzNh1n94hfXsNNOn+a99z6oK/vud7/PhReex4knHs+4cWdzww038o1vfL2k/UqSJHVGZZ+RjohfR8TbEfFiI/sjIq6OiNcjYnZE7FHuMZVTjx49GDv2LK666j8/tm/u3LkccsjhDBu2J4ceejj/+MebQG7Gedy4sxkx4gCOP/5krr32Rq666pr1vtnw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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtsAAAGFCAYAAAAsHD+yAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA46UlEQVR4nO3de5hV5X33//eXAaGChgh4AgU1YEEOw9GYGCUqv5BgJE+0iVEiEpGaq9qk2j7ll6SE2DTFpLFVa2LUpBqNpqlRPKFGDoIxioCCJ6oSlQgRBMTDYBCYuZ8/9mayGWaGOa3Zs2fer+uai73vda+1vmvPYs9n7rnX2pFSQpIkSVLL61TsAiRJkqT2yrAtSZIkZcSwLUmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JajERMTsibm2lfR0ZERURUdaAvgMiIkVE59aoTZJ2M2xLalci4rWI+FM+hO3++s+M9zk+ItbVaLuuRg0fRMR7BcsfiYjtBctfLFh2WETcExF/zAfEAc2sr19E/DoiNkfEOxHxXESc35xt5re713G3ppTSH1JKPVJKlc3dVm2/JERE14j4WUS8GxEbIuLSgmX7RcQd+fMtRcT45tYgqX0ybEtqjz6bD2G7vy5u7QJSShcV1gDcDvxPjW4XF/Q5tqC9CngQOLOFyrkFeB3oD/QCvgxsbKFtt2ezgYHkXrdPAv83IiYWLP8tMAXY0PqlSSoVhm1JHUJ+lPLtiBha0NYnPwp+cP756RGxMt/vdxExvKDvaxHx9xHxTH50+L8joltEdAceAA4vGKU+vMa+u5MLzjc3pNaU0saU0o+AZS1w6ABjgZtSSttSSrtSSk+nlB4oqO+MiHg+f9yPRMTggmUpIj5S8PymiPjuPo57v4j4eUS8l9/umKYWnq/nnyPisfz2fhMRvfPL9pgaEhFHRcSSfL/5EXFtLVNazo2IP+RH+b+ZX28i8A3gi/njWJXvOxX455TS1pTSauAG4HyAlNKOlNJ/pJR+CzR7ZF1S+2XYltQhpJQ+AO4EvlTQ/AVgcUrpzYgYCfwM+Gtyo78/Ae6JiK41+k8EjgKGA+enlLYBnwb+WDBK/ccauz8T2AQsqdH+r/nQ91jG0xCeAK6NiLMj4sjCBRExiNyo+9eBPsA84N6I2K++De7juM8Afgn0BO4BmjuN5xxgGnAwsB/w93X0uw14ktz3bza5EfyaTgSOBU4FZkXE4JTSg8D3gP/OH8eIiPgwcBiwqmDdVcBxzTwWSR2MYVtSezQ3P0q7++vCfPttwNkF/c7JtwHMAH6SUlqaUqpMKd0MfAB8tKD/1SmlP6aU3gLuBcobWM9U4OcppVTQ9o/A0UBf4HpyAfeYRhxjY/wV8CjwT8Cr+dH7sfllXwTuTyk9nFLaCfwb8BfAx5qxv9+mlObl51LfAoxoxrYA/iul9FJK6U/Ar6jldc//EjEWmJUfdf4tuaBf03dSSn9KKa0iF57rqq1H/t93CtreAQ5o4jFI6qAM25Lao8+llHoWfN2Qb18E7B8Rx+cvOiwH7sov6w9cVhjSgSOAwikhhXNz3+fPgaxO+RA4Hvh5YXs+1L+XUvogH+wfAz7TyOMkIs4tmMbxQG198tMgZqaUjgMOAVaS+4UkyB3f2oK+VeTmd/dtbC0Far5O3aKWu4BExDcKar+uEdur7XU/HHgrpfR+QdvrTdwWQEX+3wML2g4E3qulryTVybAtqcPIj7T+itxUki8B96WUdoen14F/qRHS908p3d6QTdez7MvAYymlVxqwjWjAvvZcKaVfFEzj+HQD+m8mN3p9OHAQ8Edyv2gAkA/gRwDr803vA/sXbOLQGjU3WUrpewW1X9ScbQFvAAdFRGGtRzSmnBq1bc1vs3DkewTwfJMrlNQhGbYldTS3kZs6cS5/nkICuYvfLsqPekdEdI+ISRHRkGkDG4FeEfGhWpadB9xU2BARPSPiU/kLLDtHxLnASeTuQLK7Tzdg93zxrvnnTRIRV0TE0Py+DgC+CqxJKW0h98vHpIg4NSK6AJeRmz7zu/zqK4FzIqIsfyHhyQ087laVUloLLAdmR+62fCcAn23EJjYCAyKi8Ofiz4FvRcSHI+IvgQsp+F5G7qLb3d+X/fLfz0b/wiSpfTNsS2qP7o0973G9e6oIKaWlwDZyI7sPFLQvJxem/hPYCqwhf+eJfUkp/S+5iwxfyU9BORwgH/j6sfct/7oA3yV30eRm4BJyU19eKujzJ/48leF/88+ban9y02XeBl4hN5J9Rr72F8ndvu6afC2fJXfrxB35db+Wb3ub3C8oc/d13EV0LnACsIXc6/vf5H5xaIjd36MtEfFU/vG3gd+Tm2azGPhB/mLK3V4k933pCzyUf9wfSSoQe16vI0lS+xAR/w38b0rp28WuRVLH5ci2JKldiIixEXFMRHTKT3mZTMFIvCQVw15Xh0uSVKIOJXcv9V7AOuCrKaWni1uSpI7OaSSSJElSRpxGIkmSJGWk3U4j6d27dxowYEBR9r2jcgc7KnfQuVPu5X1r6zZSSvQ6qPbPTthVtau6b30a2q+l1mup9Vtrm62x7WLuqy3tuy3WUVNbratQKdRYqNTqranU6y/Uno6lUHs9rpo6ynG2tk7Rif3K9ivKvlesWLE5pdSntmXt9js9YMAAli9fXpR9v7T5JV7d+irDDh0GwI23L6Rixza+PrX2W74+u+HZ6r71aWi/llqvpdZvrW22xraLua+2tO+2WEdNbbWuQqVQY6FSq7emUq+/UHs6lkLt9bhq6ijH2doqPqhgUO9BRdl3RKyta5nTSCRJkqSMGLYlSZKkjBi2JUmSpIy02znbkiRJHVHlrkoqNldQuaOy2KW0qqpUxepNqzPdR7du3ejXrx9dunRp8DqGbUmSpHakYnMFH/7Qh/nwQR8mIopdTqupqqqiW5dumW0/pcSWLVtYt24dRx11VIPXcxqJJElSO1K5o7LDBe3WEBH06tWL7du3N2o9w7YkSVI7Y9DORlNeV8O2JEmSlBHDtiRJkpQRw7YkSZJa3JzvzaF8eDmjR45m7OixPLn0yUZvY/Eji3n8d49XP5/+lenc+es7m1zT66+/zic/+UmGDBnCcccdx1VXXVW97K233mLChAkMHDiQCRMmsHXr1ibvp5B3I5EkSWqH4t77Mt1++uzpdS574vEnmHf/PJYuW0rXrl3ZvHkzO3bsaPQ+lixeQvce3TnhYyc0p9RqnTt35oc//CGjRo3ivffeY/To0UyYMIEhQ4YwZ84cTj31VGbOnMmcOXOYM2cOV1xxRbP36ci2JEmSWtSGDRvo1bsXXbt2BaB3794cfvjhLFywkHFjxjGqfBQzps/ggw8+AGDQMYPYvHkzACuWr2DCKRN47bXXuOH6G7jmqmsYO3osv330twA8+uijnHziyRw78Nh6R7lnzZpFeXk55eXl9O3bl2nTpnHYYYcxatQoAA444AAGDx7M+vXrAbj77ruZOnUqAFOnTmXu3Lkt8loYtiVJktSiTptwGuvWreO4wcdxycWXsGTxErZv386FF1zIrbfdylMrn2LXrl385Lqf1LmNAQMGcOGMC7nka5ewbMUyTvzEiQBseGMDi5YsYu7dc/nmN75Z5/qXX345K1eu5JFHHuGggw7i4osv3mP5a6+9xtNPP83xxx8PwMaNGznssMMAOPTQQ9m4cWNzXwbAsC1JkqQW1qNHD5548gl+9OMf0ad3H6acM4Ubrr+BAQMGMGjQIACmfHlK9Wh1Y5wx+Qw6derE4CGDeXPjm/X2TSkxZcoULr30UkaPHl3dXlFRwZlnnsl//Md/cOCBB+61XkS02O0TnbMtSZLUDtU3p7o1lJWVcfL4kzl5/MkMHTaU6350Xd19O5dRVVUFsM8Pjdk9NQVyYbo+s2fPpl+/fkybNq26befOnZx55pmce+65fP7zn69uP+SQQ3jjjTc47LDDeOONNzj44IPr3XZDObItSZKkFvXiiy/y8ssvVz9ftXIVRx9zNGvXrmXNmjUA3PaL2/jESZ8AoH///jy14ikA7rrzrur1ehzQg4r3KppUw7333sv8+fO5+uqrq9tSSlxwwQUMHjyYSy+9dI/+Z5xxBjfffDMAN998M5MnT27SfmsybEuSJKlFbavYxvRp0xkxbASjR45m9erVfPd73+X6G6/nnLPPYVT5KDp16sSMv54BwLf+6VtcdullnHD8CZSVlVVvZ9Lpk7j77rv3uECyoa688krWr1/PuHHjKC8vZ9asWTz22GPccsstLFy4sPriyXnz5gEwc+ZMHn74YQYOHMj8+fOZOXNmi7wWTiORJElSixo1ehSLf7t4r/ZTTj2FJ5fvfb/tEz9xIs+vfn6v9kGDBrHi6RV79Cv01jtv1VnDokWLam2va+pJr169WLBgQZ3baypHtiVJkqSMOLItSZKkkvXcs88x7fxpkKi+g0jXrl1ZunRpkSvLMWxLkiSpZA0dNpRlK5ZRVVVFty7dil3OXpxGIkmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsJ2BX9zxOAseeq3YZUiSJBXNnO/NoXx4OaNHjmbs6LE8uXTv+2vvy+JHFvP47x6vfj79K9O589d3Nru2yspKRo4cyemn//kj7V999VWOP/54PvKRj/DFL36RHTt2NHs/4N1IJEmS2qXgvky3nzi9zmVPPP4E8+6fx9JlS+natSubN29uUnhdsngJ3Xt054SPndCcUvdy1VVXMXjwYN59993qtn/8x3/k7/7u7zj77LO56KKL+OlPf8pXv/rVZu/LkW1JkiS1qA0bNtCrdy+6du0KQO/evTn88MNZuGAh48aMY1T5KGZMn8EHH3wAwKBjBrF582YAVixfwYRTJvDaa69xw/U3cM1V1+zxce2PPvooJ594MscOPLbeUe5Zs2ZVfyR73759mTZtGgDr1q3j/vvvZ/r06dV9U0osXLiQs846C4CpU6cyd+7cFnktDNuSJElqUadNOI1169Zx3ODjuOTiS1iyeAnbt2/nwgsu5NbbbuWplU+xa9cufnLdT+rcxoABA7hwxoVc8rVLWLZiWfVHtW94YwOLlixi7t1z+eY3vlnn+pdffjkrV67kkUce4aCDDuLiiy8G4Otf/zrf//736dTpzzF4y5Yt9OzZk86dc5M++vXrx/r161vipTBsF9uNty90frckSWpXevTowRNPPsGPfvwj+vTuw5RzpnDD9TcwYMAABg0aBMCUL0+pHq1ujDMmn0GnTp0YPGQwb258s96+KSWmTJnCpZdeyujRo7nvvvs4+OCDGT16dJOOqymcsy1JktQO1TenujWUlZVx8viTOXn8yQwdNpTrfnRd3X07l1FVVQXA9u3b693u7qkpkAvT9Zk9ezb9+vWrnkLy2GOPcc899zBv3jy2b9/Ou+++y5QpU7jlllt4++232bVrF507d2bdunX07du3oYdaL0e2JUmS1KJefPFFXn755ernq1au4uhjjmbt2rWsWbMGgNt+cRufOOkTAPTv35+nVjwFwF133lW9Xo8DelDxXkWTarj33nuZP38+V199dXXbv/7rv7Ju3Tpee+01fvnLX3LKKadw6623EhF88pOf5I477gDg5ptvZvLkyU3ab02GbUmSJLWobRXbmD5tOiOGjWD0yNGsXr2a737vu1x/4/Wcc/Y5jCofRadOnZjx1zMA+NY/fYvLLr2ME44/gbKysurtTDp9EnffffceF0g21JVXXsn69esZN24c5eXlzJo1q97+V1xxBVdeeSUf+chH2LJlCxdccEHjD7wWTiORJElSixo1ehSLf7t4r/ZTTj2FJ5fvfb/tEz9xIs+vfn6v9kGDBrHi6RV79Cv01jtv1VnDokWL6q1x/PjxjB8/vvr50UcfzZNPNv5e4PviyLYkSZKUkTYxsh0RPwNOB95MKQ2tZXkAVwGfAd4Hzk8pPdW6VUqSJKmtee7Z55h2/jRIkIuMuYsoly5dWuTKctpE2AZuAv4T+Hkdyz8NDMx/HQ/8OP+vJEmSOrChw4aybMUyqqqq6NalW7HL2UubmEaSUloC1D3pBiYDP085TwA9I+Kw1qlOkiRJapo2EbYboC/wesHzdfm2PUTEjIhYHhHLN23a1GrFSZIkSbUplbDdICml61NKY1JKY/r06VPsciRJktTBlUrYXg8cUfC8X75NkiRJarNKJWzfA5wXOR8F3kkpvVHsoiRJklS7Od+bQ/nwckaPHM3Y0WN5cmnj72G9+JHFPP67x6ufT//KdO789Z3Nquvtt9/mrLPO4i//8i8ZPHgwjz+e2/5bb73FhAkTGDhwIBMmTGDr1q3N2s9ubeJuJBFxOzAe6B0R64BvA10AUkrXAfPI3fZvDblb/00rTqWSJEml4b6X7st0+6cPOr3OZU88/gTz7p/H0mVL6dq1K5s3b2bHjh2N3seSxUvo3qM7J3zshOaUuoevfe1rTJw4kTvuuIMdO3bw/vvvAzBnzhxOPfVUZs6cyZw5c5gzZw5XXHFFs/fXJka2U0pfSikdllLqklLql1L6aUrpunzQJn8Xkr9JKR2TUhqWUlpe7JolSZJUuw0bNtCrdy+6du0KQO/evTn88MNZuGAh48aMY1T5KGZMn8EHH3wAwKBjBrF582YAVixfwYRTJvDaa69xw/U3cM1V1+zxce2PPvooJ594MscOPLbeUe5Zs2ZRXl5OeXk5ffv2Zdq0abzzzjssWbKk+qPY99tvP3r27AnA3XffzdSpUwGYOnUqc+fObZHXok2EbUmSJLUfp004jXXr1nHc4OO45OJLWLJ4Cdu3b+fCCy7k1ttu5amVT7Fr1y5+ct1P6tzGgAEDuHDGhVzytUtYtmJZ9Ue1b3hjA4uWLGLu3XP55je+Wef6l19+OStXruSRRx7hoIMO4uKLL+bVV1+lT58+TJs2jZEjRzJ9+nS2bdsGwMaNGznssNydpQ899FA2btzYIq+FYVuSJEktqkePHjzx5BP86Mc/ok/vPkw5Zwo3XH8DAwYMYNCgQQBM+fKU6tHqxjhj8hl06tSJwUMG8+bGN+vtm1JiypQpXHrppYwePZpdu3bx1FNP8dWvfpWnn36a7t27M2fOnL3Wi4jqT6NsrjYxZ1uSJEktq7451a2hrKyMk8efzMnjT2bosKFc96Pr6u7buYyqqioAtm/fXu92d09NgVyYrs/s2bPp168f06blLvfr168f/fr14/jjcx9EftZZZ1WH7UMOOYQ33niDww47jDfeeIODDz543wfZAI5sS5IkqUW9+OKLvPzyy9XPV61cxdHHHM3atWtZs2YNALf94jY+cdInAOjfvz9PrXgKgLvuvKt6vR4H9KDivYom1XDvvfcyf/58rr766uq2Qw89lCOOOIIXX3wRgAULFjBkyBAAzjjjDG6++WYAbr75ZiZPntyk/dZk2JYkSVKL2laxjenTpjNi2AhGjxzN6tWr+e73vsv1N17POWefw6jyUXTq1IkZfz0DgG/907e47NLLOOH4EygrK6vezqTTJ3H33XfvcYFkQ1155ZWsX7+ecePGUV5ezqxZswC45pprOPfccxk+fDgrV67kG9/4BgAzZ87k4YcfZuDAgcyfP5+ZM2e2yGvhNJIScePtC6nYsY1hU4cVuxRJkqR6jRo9isW/XbxX+ymnnsKTy/e+3/aJnziR51c/v1f7oEGDWPH0ij36FXrrnbfqrGHRokW1tpeXl7N8+d43tuvVqxcLFiyoc3tN5ci2JEmSlBFHtiVJklSynnv2OaadPw0S1XcQ6dq1K0uXLi1yZTmGbUmSJJWsocOGsmzFMqqqqujWpVuxy9mL00gkSZKkjBi2JUmSpIwYtiVJkqSMGLYlSZKkjBi2JUmSpIwYtiVJktSitm/fzsc/+nHGjBpD+fByLp99ebFLKhpv/SdJktSRVVYSDzxIrFxFKh9B+vREKPjI9Kbo2rUrD81/iB49erBz504+edIn+dTET3H8R49voaJLhyPbkiRJHVVlJZ0/PYnOU86j7DuX03nKeXT+9CSorGzWZiOCHj16ALBz50527tpZ/YEzz6x6hlPHn8qIYSPo1qUbXTt35Tvf/k6zD6WtcmRbkiSpnSq79DJi5TN1d9iyhVi9mqiqyj2vqIBHFtN51Fjo1avWVVL5cCqv/OE+911ZWclHx32U36/5PRd99SLGHT+O7du3c+455/Kz//oZY8eNZfas2Wzfvp1Zs2c15fBKgiPbkiRJHVVFBewO2rtVVeXam6msrIxlK5bxytpXWL5sOc8/9zwL5i9g5MiRjB03FoBhw4exdevW6lHv9siRbUmSpHZqXyPQcd/9dJ5y3p7hunt3Kq/6d9Lpk1qkhp49e3Ly+JN56KGHqNxVydChQ6uXPf3U05SPLG+R/bRVjmxLkiR1UOnTE0njxpK6dydF5P49flzuIslm2LRpE2+//TYAf/rTn1gwfwHHHnssB/U6iGeffRaAl156iblz5/KFL36huYfRpjmyLUmS1FGVlbHrgftzdyNZ9QxpxPAWuRvJhjc2cMFXLqCyspKqqirOOussJp0+iYqKCu679z5GjhhJ7169ueXWW+hVx9zw9sKwLUmS1JGVlZFOn9Ri00YgNxf7yeVP7tXeo0cP7rr7rhbbTylwGokkSZKUEcO2JEmSlBHDtiRJkpQRw7YkSZKUEcO2JEmSlBHDtiRJkpQRw7bqdePtC1nw0GvFLkOSJKkkGbYlSZKkjPihNpIkSWpxg44ZRI8DelBWVkbnzp15fOnjxS6pKAzbkiRJHVhlVSUP/v5BVm1YxYhDRzDxmImUdWrex7Xv9pv5v6F3794tsq1S5TSSds4515IkqS6VVZVMum0S5915Hpcvvpzz7jyPSbdNorKqMrN9PrPqGU4dfyojho2gW5dudO3cle98+zuZ7a/Y2sTIdkRMBK4CyoAbU0pzaiw/ErgZ6JnvMzOlNK+165QkSSoll/3mMp7Z8Eydy7f8aQurN6+mKlUBULGzgsVrFzP2hrH0+oteta4z/NDh/PD/++G+dx4w6dOTiAimXzid6RdOZ/v27Zx7zrn87L9+xthxY5k9azbbt29n1uxZTTq+UlD0sB0RZcC1wARgHbAsIu5JKb1Q0O1bwK9SSj+OiCHAPGBAqxerFnHj7Qup2LGNYVOHFbsUSZI6tIodFdVBe7eqVEXFjoo6w3ZDLVq8iL59+/Lmm2/ymYmf4dhjj+Xdd99l5MiRjB03FoBhw4fxm4d+Q0Q0a19tWdHDNjAOWJNSegUgIn4JTAYKw3YCDsw//hDwx1atUJIkqQTtawT6/pfv57w7z6NiZ0V1W/cu3fn3if/OpIGTmrXvvn37AnDwwQczefJkli1bRuWuSoYOHVrd5+mnnqZ8ZHmz9tPWtYU5232B1wuer8u3FZoNTImIdeRGtS9pndIkSZLar4nHTGRs37F079KdIOjepTvj+o5j4jETm7Xdbdu28d5771U/nv/wfI477jgO6nUQzz77LAAvvfQSc+fO5Qtf/EKzj6Mtawsj2w3xJeCmlNIPI+IE4JaIGJrSnn/3iIgZwAyAI488sghlSpIklY6yTmXcf879PPj7B3lmwzMMP3R4i9yNZOPGjXzhrFyI3rVrF2effTafmvgpKioquO/e+xg5YiS9e/XmlltvoVev5k1XaevaQtheDxxR8Lxfvq3QBcBEgJTS4xHRDegNvFnYKaV0PXA9wJgxY1JWBUuSJLUXZZ3KmDRwUrOnjRQ6+uijWf7U8r3ae/TowV1339Vi+ykFbWEayTJgYEQcFRH7AWcD99To8wfgVICIGAx0Aza1apUqCd7qUJIktSVFD9sppV3AxcBDwGpydx15PiIuj4gz8t0uAy6MiFXA7cD5KSVHriVJktSmtYVpJOTvmT2vRtusgscvAB9v7bqk+ngLQ0lSW5VSate30yuWpoz1Fn1kW5IkSS2nbL8ytr61tUnBUHVLKbFlyxa6devWqPXaxMi2JEmSWkaP3j3YunkrmzdtLnYpraoqVdGlrEum++jWrRv9+vVr1DqGbUmSpHakrHMZHzr0Q8Uuo9VVfFDBoN6Dil3GXpxGIkmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC2VoBtvX8iCh14rdhmSJGkfDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRgzbGVv5wlr+8MctbNr4Pj+47l5WvrC22CVJkiSplRi2M7TyhbXMfXAZlZVVALz97vvMfXCZgVvtxo23L2TBQ68VuwxJktosw3aGHl7yDDt3Ve7RtnNXJQ8veaZIFUmSJKk1GbYz9Pa77zeqXZIkSY134+0L+cUdjxe7jFoZtjPU88D9G9UuSZKk9sWwnaEJJw2nS+eyPdq6dC5jwknDi1SRJEmSWlPnYhfQnpUP6Q/AnQ88SWVlFT0P3J8JJw2vbpckSVL7ZtjOWPmQ/ixf9Xsqdmzj61M/W+xyJEmS1IqcRiJJkiRlxLAtSZIkZcSwLUmSJGWkTYTtiJgYES9GxJqImFlHny9ExAsR8XxE3NbaNUqSJEmNVfQLJCOiDLgWmACsA5ZFxD0ppRcK+gwE/n/g4ymlrRFxcHGqlSRJkhquLYxsjwPWpJReSSntAH4JTK7R50Lg2pTSVoCU0putXKMkSZLUaG0hbPcFXi94vi7fVmgQMCgiHouIJyJiYm0biogZEbE8IpZv2rQpo3IltXU33r6QBQ+9VuwyJElqE2G7IToDA4HxwJeAGyKiZ81OKaXrU0pjUkpj+vTp07oVSpIkSTW0hbC9Hjii4Hm/fFuhdcA9KaWdKaVXgZfIhW9JkiSpzWoLYXsZMDAijoqI/YCzgXtq9JlLblSbiOhNblrJK61YoyRJktRoRQ/bKaVdwMXAQ8Bq4Fcppecj4vKIOCPf7SFgS0S8ACwC/iGltKU4FUuSJEkNU/Rb/wGklOYB82q0zSp4nIBL81+SJEkl6cbbF1KxYxvDpg4rdilqJUUf2ZYkSZLaK8O2JBWBtyeUpI7BsN3CHl+1mvUb3mbTxvf5wXX3svKFtXX2XfnCWv7wxy0N6itJkqTSY9huQY+vWs1Nd8+nsrIKgLfffZ+5Dy7jvW3b9+q78oW1zH1w2V59DdyS2iJH4pU1zzG1V4btFvTrhx9jx85de7Tt3FXJ1ne27dX34SXPsHNX5V59H17yTKY1SpIkqfUYtlvQlnfeq7V99+h1obfffb/WvnW1S5Karj2NmranY5E6AsN2C+r1oQNqbS8r2/tl7nng/rX2ratdkiRJpcew3YLOnPBx9uuy563Lu3Qu48Mf6r5X3wknDadL57K9+k44afhefb2QUpIkqTQZtlvQCSMGc/7k06pHsnseuD+fmziWA7p326tv+ZD+fG7i2L36lg/pv0c/L6SUJEkqXYbtFnbCiMH0PbQnfQ7Zn3+46LN7hedC5UP6c+Thvert64WUkiRJpcuw3cZ5IaUkSVLpMmy3cV5IKUmSVLoM221cYy6klCRJUtvSed9dVEy753Hf+cCTVFZW0fPA/Zlw0vB654JLkiSpbTBsl4DyIf1Zvur3VOzYxtenfrbY5UiSJKmBnEYiSZIkZcSwLUmSJGXEsC1JkiRlxLAtSZIkZaRRF0hGxD8BHwPWA0+nlK7NpCpJkiSpHWjsyHYv4AngX4BjW74ctXcrX1jLH/64hU0b3+cH193LyhfWFrskSZKkzDQ2bG8FyoA3gbdavhy1ZytfWMvcB5dRWVkF5D5yfu6DywzckiSp3WpU2E4pfQe4DrgaeCeTitRimjuK3NKj0A8veYaduyr3aNu5q5KHlzzTrO0WgyP0kiSpIRo8ZzsiFgJPASuAK1JKL2VWlZqtrlFkoEGfPtnc9Wvz9rvvN6q9rcritZEkSe1TY0a25wM98+tMiYjbM6lILaK5o8hZjEL3PHD/RrU3VmuNNrenEXpJkpStBoftlNL3gH8GPgqsTil9KbOq1GzNHUXOYhR6wknD6dK5bI+2Lp3LmHDS8CZvc7fWnA/eXkboJUlS9hoctiPidOAcoAr4fESU7WMVFVFzR5GzGIUuH9Kfz00cS1lZp+ptfW7i2BaZetGao81Zj9BLkqT2ozHTSK4FhpG79d+3UkqV++ivImruKHJWo9DlQ/pz5OG96HPI/vzDRZ9tsTnOrTnanOUIvSRJal8afIFkSql/RPQDxgLnRMSQlNJfZVeammN3iL3zgSeprKyi54H7M+Gk4Q0Ot81dv7X1PHD/WoN1FqPNpfbaSJKk4mnsrf/WAYMA/15eApo7ipzVKHQWWnu0uZivjbcdlCSpdDRmzvYX8g+vBY4A/pBJRVITZDkfvC3xg4EkSSotjRnZPjEiugE/BP4B2JhNSVLTlNJIfFN520FJkkpLY8J2ADcBFeRGtgdlUZCkunnbQUmSSktjLpC8BCAihgKnAf+UVVGSateaF4JKkqTma8yc7csj4nvAEGBeSumN7MqSVBtvOyhJUmlpzCdIzgKuAt4B/k9E3NBSRUTExIh4MSLWRMTMevqdGREpIsa01L6lUtJRLgSVJKm9aPA0EoCU0kbgIeChiDipJQrIfxLltcAEYB2wLCLuSSm9UKPfAcDXgKUtsV+pVJUP6c/yVb+nYsc2vj71s8UuR5Ik1aNR99mu4awWqmEcsCal9EpKaQfwS2ByLf3+GbgC2N5C+5UkSZIy1Zg52/dExFURMTV/kWSjRsXr0Rd4veD5unxb4b5HAUeklO7fR40zImJ5RCzftGlTC5UnSZIkNc0+w3ZEzAZIKZ0BXAm8C5wNtMok0YjolN/vZfvqm1K6PqU0JqU0pk+fPtkXJ0mSJNWjIaPTsyLiL4CDgKeAX6aU7mrBGtaTu2/3bv3ybbsdAAwFHokIgEOBeyLijJTS8hasQ1Ij7P7Y+MrKKn5w3b1MOGm4F2pKklRDQ6aRJHLzpB8iF4p/FxEjWrCGZcDAiDgqIvYjN2p+T/XOU3onpdQ7pTQgpTQAeAIwaEtF5MfGS5LUMA0J2/+bUvp2SumOlNI3yF28+O8tVUBKaRdwMbkwvxr4VUrp+fx9vc9oqf1Iajl+bLwkqa3Y/ZfWP6x/i7//txt5fNXqYpe0h4ZMI9kcEaNTSisAUkovRUSLTohOKc0D5tVom1VH3/EtuW9JjefHxkuS2oKaf2nd8s573HT3fABOGDG4mKVVa8jI9t8Ct0bErRHxjxHxC+DVjOuS1IbV9fHwfmy8JKk11faX1h07d/Hrhx8rUkV722fYTimtAsqB2/NNi4AvZViTpDbOj42XJLUFdf1Fdcs777VyJXVr0L2yU0ofAPfnvyR1cLvvOnLnA09SWVlFzwP3924kkqRW1/PA/WsN3L0+dEARqqldcz5BUlIHVj6kP0ce3os+h+zPP1z0WYO2JKnV1faX1v26dObMCR8vUkV7M2xLald2X5W+aeP7/OC6e70doaQ2w/enllc+pD+fmziWsrJcpO31oQM4f/JpbebiSGi5j1yXpKKr6/7fgCPvkorK96fslA/pz/JVv6eyqpLvXDS12OXsxZFtSe2G9/+W1Fb5/tRxGbYltRve/1tSW+X7U8dl2JbUbnj/b0ltle9PHZdhW1K7USr3/y61i6RKrd6aSr3+jqAjfI9K5f1JLc8LJCW1G6Vw/+9Su0iq1OqtqdTr7wg6yveoFN6flA1HtiW1K239/t+ldpFUqdVbU6nX3xF0pO9RW39/UjYM25LUikrtIqlSq7emUq+/I/B7pPbOsC1JrajULpIqtXprKvX6a2qPc5vb2/dIqsmwLUmtqNQukiq1emsq9foL1TW3udQDd3v6Hkm1MWxLUiuq+dHCPQ/cn89NHNtm526WWr01lXr9hdrr3Ob29D2SauPdSCSple3+aOGKHdv4+tTPFrucfSq1emsq9fp3a89zm9vL90iqjSPbkiSVAOc2S6XJsC1JUglwbrNUmpxGIklSCfBDUaTSZNiWJKlEOLdZKj1OI5EkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMtImwnZETIyIFyNiTUTMrGX5pRHxQkQ8ExELIqJ/MeqUJEmSGqPoYTsiyoBrgU8DQ4AvRcSQGt2eBsaklIYDdwDfb90qJUmSpMYretgGxgFrUkqvpJR2AL8EJhd2SCktSim9n3/6BNCvlWuUJEmSGq0thO2+wOsFz9fl2+pyAfBAbQsiYkZELI+I5Zs2bWrBEiVJkqTGawthu8EiYgowBvhBbctTStenlMaklMb06dOndYuTJEmSauhc7AKA9cARBc/75dv2EBGnAd8ETk4pfdBKtUmSJElN1hZGtpcBAyPiqIjYDzgbuKewQ0SMBH4CnJFSerMINUqSJEmNVvSwnVLaBVwMPASsBn6VUno+Ii6PiDPy3X4A9AD+JyJWRsQ9dWxOkiRJajPawjQSUkrzgHk12mYVPD6t1YuSJEmSmqnoI9uSJElSe2XYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMtIm7rMtlaLpXzqFZzc8W+wyJElSG+bItiRJkpQRR7bV6hwRbj5fQ0mSSoMj25IkSVJGHNluBY5CSpIkdUyObEuSJEkZMWxLkiRJGXEaSYlwKkrD+DpJkqS2xJFtSZIkKSOObLdzjvQqS55fktQ4vm92PI5sS5IkSRlxZFuS1O45mii1b9O/dAoVH1QUu4xaObItSZIkZcSRbdXL0SCVIs/bludrqqx5jqm9cmRbkiRJyogj25JUBI7iSVLH4Mi2JEmSlBHDtiRJkpQRp5EUmX9KliRJar8c2ZYkSZIy4si2JEklxL+ISqXFkW1JkiQpI45sZ+Dcs07g1a2vFrsMSZIkFZkj25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRtpE2I6IiRHxYkSsiYiZtSzvGhH/nV++NCIGFKFMSZIkqVGKHrYjogy4Fvg0MAT4UkQMqdHtAmBrSukjwL8DV7RulZIkSVLjFT1sA+OANSmlV1JKO4BfApNr9JkM3Jx/fAdwakREK9YoSZIkNVpbCNt9gdcLnq/Lt9XaJ6W0C3gH6FVzQxExIyKWR8TyTZs2ZVSuJEmS1DBtIWy3mJTS9SmlMSmlMX369Cl2OZIkSerg2kLYXg8cUfC8X76t1j4R0Rn4ELClVaqTJEmSmqgthO1lwMCIOCoi9gPOBu6p0eceYGr+8VnAwpRSasUaJUmSpEbrXOwCUkq7IuJi4CGgDPhZSun5iLgcWJ5Sugf4KXBLRKwB3iIXyCVJkqQ2rehhGyClNA+YV6NtVsHj7cBftXZdkiRJUnO0hWkkkiRJUrtk2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJS1LAdEQdFxMMR8XL+3w/X0qc8Ih6PiOcj4pmI+GIxapUkSZIaq9gj2zOBBSmlgcCC/POa3gfOSykdB0wE/iMierZeiZIkSVLTFDtsTwZuzj++GfhczQ4ppZdSSi/nH/8ReBPo01oFSpIkSU1V7LB9SErpjfzjDcAh9XWOiHHAfsDvsy5MkiRJaq7OWe8gIuYDh9ay6JuFT1JKKSJSPds5DLgFmJpSqqqjzwxgBsCRRx7Z5JolSZKklpB52E4pnVbXsojYGBGHpZTeyIfpN+vodyBwP/DNlNIT9ezreuB6gDFjxtQZ3CVJkqTWUOxpJPcAU/OPpwJ31+wQEfsBdwE/Tynd0Yq1SZIkSc1S7LA9B5gQES8Dp+WfExFjIuLGfJ8vACcB50fEyvxXeVGqlSRJkhoh82kk9UkpbQFOraV9OTA9//hW4NZWLk2SJElqtmKPbEuSJEntlmFbkiRJyohhW5IkScqIYVuSJEnKiGFbkiRJyohhW5IkScqIYVuSJEnKiGFbkiRJyohhW5IkScqIYVuSJEnKiGFbkiRJyohhW5IkScqIYVuSJEnKiGFbkiRJyohhW5IkScqIYVuSJEnKiGFbkiRJyohhW5IkScqIYVuSJEnKSKSUil1DJiJiE7C2KDsvYz/K6EIVu6rbOtF5j+eF6lvWlH4ttV5Lrd9a22zItqs4gE681yr7ylox990W66ipGHU19vxqq69dXUqt3ppKpf6GnEelciyN1V6Pq6YsjrOlf76VokSikh1F2nv/lFKf2ha027At1SYirk8pzSh2HWqfPL/UEjyP1BSeN22X00jU0dxb7ALUrnl+qSV4HqkpPG/aKEe2JUmSpIw4si1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JkiRlxLAtNVFEdI+I5RFxerFrUWnzXFKWPL/UWJ4zLcuwrTYnIo6IiEUR8UJEPB8RX6uj39ci4rl8n683c58/i4g3I+K5Gu0TI+LFiFgTETNrrPaPwK+as1+1DRHRLSKejIhV+fPpO83YVq3nUn5ZXeeT51IJauh5U9850YR9+l5V4hpy3jT052AD9+d7UpEZttUW7QIuSykNAT4K/E1EDCnsEBFDgQuBccAI4PSI+EiNPgdHxAE12vboU+AmYGKNvmXAtcCngSHAl3bXERETgBeAN5tygGpzPgBOSSmNAMqBiRHx0cIOjTifbqLGuZTvW+v55LlU0vZ53uTdRC3nxG6+V3U4DTlvGvJz0PekEmHYVpuTUnojpfRU/vF7wGqgb41ug4GlKaX3U0q7gMXA52v0ORmYGxFdASLiQuCaOva5BHirRvM4YE1K6ZWU0g7gl8Dk/LLx5N4AzwEujAj/L5WwlFORf9ol/1XzQwgadD7VcS5B3efTeDyXSlIDz5v6zondfK/qQBpy3jTw56DvSSWic7ELkOoTEQOAkcDSGoueA/4lInoBfwI+Aywv7JBS+p+IOAr474j4H+ArwIRG7L4v8HrB83XA8fltfzNf3/nA5pRSVSO2qzYoP8qzAvgIcG1KaY9zLqvzKaV0cX7/5+O5VHL2dd40hO9VHU9jzpu6fg76nlQ6DNtqsyKiB/Br4OsppXcLl6WUVkfEFcBvgG3ASqCy5jZSSt+PiF8CPwaOKRhNaBEppZtacnsqnpRSJVAeET2BuyJiaErpuRp9MjufPJdKU0POmwZux/eqDqSh5019Pwfz2/E9qQT4pwG1SRHRhdwbzC9SSnfW1iel9NOU0uiU0knAVuClWrbzCWAocBfw7UaWsR44ouB5v3yb2rGU0tvAImqf4+j5pFrVd940hOdWx7SP95t9/hz0vCkNhm21ORERwE+B1SmlK+vpd3D+3yPJzde+rcbykcD15OagTQN6RcR3G1HKMmBgRBwVEfsBZwP3NOZYVBoiok9+hImI+Atyf4r93xp9PJ+0h4acNw3cjudWB9LA95t9/hz0vCkdhm21RR8HvgycEhEr81+fAYiIeRFxeL7fryPiBeBe4G/yIwSF9ge+kFL6fX7O2XnA2tp2GBG3A48Dx0bEuoi4IH/h5cXAQ+QuTvlVSun5lj1UtRGHAYsi4hlyP4AeTindV6NPg86n2s4lAM+ndqnO86bwvaquc6KA71UdS0POmzp/DhbwPalEREp7XTgtSZIkqQU4si1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JNUREr4J7226IiPX5xxUR8aNWqmFMRFyd4fbHR8RNEXF+RMxu4W2fHxH/2YT1mnXMEfFaRAyIiEcK2kZGxE/rWadPRDzY1H1K0r50LnYBktTWpJS2AOUA+SBakVL6t1auYTmwvDX3WWwZHfM3gDo/VS+ltCki3oiIj6eUHmvhfUuSI9uS1FD50eDdn/Q2OyJujohHI2JtRHw+Ir4fEc9GxIMR0SXfb3RELI6IFRHxUEQcVst2/yoinouIVRGxpI59/SwiHomIVyLibwvWPS8insmve0u+rU9E/DoiluW/Pl7L4ewA3gH+BFTUt15E3B0R5+Uf/3VE/CL/+JGIuCo/6v9cRIyr5dgGRMTCfI0LIuLIBh7zQRExN7/eExExfF+vBbAJqATeyvc9ABieUlqVf35ywV8sns4vB5gLnFvPt16SmsyRbUlqumOATwJDyH0c8pkppf8bEXcBkyLifuAaYHJ+BPWLwL8AX6mxnVnAp1JK6yOiZx37+sv8vg4AXoyIHwODgG8BH0spbY6Ig/J9rwL+PaX023y4fQgYXLixlNLvgN/V2Edd680AHouIV4HLgI8WrLN/Sqk8Ik4CfgYMrbHNa4CbU0o3R8RXgKuBzzXgmL8DPJ1S+lxEnAL8nPxfG2p7LVJKO1NKY/PLP5//dwzwXME2/x74m5TSYxHRA9ieb19OPaPfktQchm1JaroHUko7I+JZoAzYPff3WWAAcCy58PlwRJDv80Yt23kMuCkifgXcWce+7k8pfQB8EBFvAocApwD/k1LaDJBSeivf9zRgSH6fAAdGRI+UUsU+jqeu9TZGxCxgEfB/CvYDcHt+30si4sBagvMJ/Dn83gJ8v4HHfCJwZn7bCyM3j/7Ael6LdbVs4zByo927PQZcmR+ZvzOltHudN4HDa1lfkprNsC1JTfcBQEqpKiJ2ppRSvr2K3PtrAM+nlE6obyMppYsi4nhgErAiIkbXta+8Sup//+4EfDSltL2ePo1dbxiwhb1DadrH81o18Jjr0tDX4k9At4J9zsn/teEz5EbqP5VS+t98nz81Yv+S1GDO2Zak7LwI9ImIEwAioktEHFezU0Qck1JamlKaRW4k9ogGbn8h8FcR0Su/nd3TSH4DXFKw/fIGbq/W9fJzsT8NjAT+PiKOKljni/k+JwLvpJTeqbHN3wFn5x+fCzya77+vY34035+IGA9sTim928Dj2G018JGC4zkmpfRsSukKYBm56SiQm47zXC3rS1KzGbYlKSMppR3AWcAVEbEKWAl8rJauP4jchZXPkQunqxq4/efJzQFfnN/+lflFfwuMyV9c+AJwUQNL3mu9iOgK3AB8JaX0R3Jztn8Wf55rsj0ingauAy6oZZuXANMi4hngy8DXGnjMs4HR+fXmAFMbeAzV8qPWHyq4EPLr+YsynwF2Ag/k2z8J3N/Y7UtSQ8Sf/+opSVLDRe5+1n+fv2VfmxQRfwe8l1K6sZ4+S8hdxLq19SqT1FE4si1Jas9+zJ5zvPcQEX2AKw3akrLiyLYkSZKUEUe2JUmSpIwYtiVJkqSMGLYlSZKkjBi2JUmSpIwYtiVJkqSM/D+COOS7AXTOPQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "for event in events:\n", - " #print (summed[event].keys())\n", - " if event == event_id:\n", - " for site in sites:\n", - " for night in data[event][site]:\n", - " if night != 'first_night_start':\n", - "\n", - " if type(data[event][site][night]['irf']) == float:\n", - " print(f'\\tThis contains NaNs event--->' \n", - " f'the source is not observable at the site {site} during {night}.')\n", - " else:\n", - "\n", - " fig, axes = plt.subplots(1 ,figsize=(12,6))\n", - " color_min = min(summed[event][site]['significance'])-max(summed[event][site]['variance'])\n", - " color_max = max(summed[event][site]['significance'])+max(summed[event][site]['variance'])\n", - " for i in range(len(data[event][site][night]['irf'])):\n", - " t_start= data[event][site][night]['t_start'][i]\n", - " t_stop = data[event][site][night]['t_stop'][i]\n", - " \n", - " \n", - " if 'z20' in data[event][site][night]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop],color_min,color_max,color='red',alpha=0.1) \n", - "\n", - " elif 'z40' in data[event][site][night]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop],color_min,color_max,color='yellow',alpha=0.1)\n", - "\n", - " elif 'z60' in data[event][site][night]['irf'][i]:\n", - " axes.fill_between([t_start,t_stop], color_min, color_max,color='green',alpha=0.1)\n", - "\n", - "\n", - " axes.errorbar(data[event][site][night]['t_stop'],data[event][site][night]['significance'], \n", - " yerr= data[event][site][night]['variance'], color='#607c8e',fmt='o')\n", - " if '3sigma' in data[event][site][night].keys():\n", - " axes.errorbar (data[event][site][night]['3sigma'][0], \n", - " data[event][site][night]['3sigma'][1], \n", - " color='red',fmt='o')\n", - " if '5sigma' in data[event][site][night].keys():\n", - " axes.errorbar (data[event][site][night]['5sigma'][0], \n", - " data[event][site][night]['5sigma'][1], \n", - " color='g',fmt='o')\n", - " axes.set_xlabel(r'Time since \"explosion\"(s)')\n", - " axes.set_ylabel(r'$\\sigma_{Li&Ma}$')\n", - " axes.set_xscale('log')\n", - " axes.set_ylim(color_min, color_max)\n", - " axes.legend(custom_lines, \n", - " [f'{site}_z20', f'{site}_z40', f'{site}_z60',r'$3\\sigma$', r'$5\\sigma$'])\n", - " axes.set_title(f'{event} - {site} - {night}')\n", - " plt.savefig(f'examples/{event}-{site}-{night}-{phase}.png') \n", - "\n", - "\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf8c09b6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19fb9d6e", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/rtavis/rtavis/notebooks/plotVisibility.ipynb b/rtavis/rtavis/notebooks/plotVisibility.ipynb deleted file mode 100644 index df811028ca31aa5d0a3a9fdf36b910935fcb7f1e..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/notebooks/plotVisibility.ipynb +++ /dev/null @@ -1,294 +0,0 @@ -{ - "metadata": { - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3-final" - }, - "orig_nbformat": 2, - "kernelspec": { - "name": "python37364bitscitoolscondac19a7fc295d54bb084b63b5051939beb", - "display_name": "Python 3.7.3 64-bit ('scitools': conda)" - }, - "metadata": { - "interpreter": { - "hash": "496f86081a2f0cd251e9ea8fa672f16ca71d852d0d70dc270fc1363e8207bc07" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2, - "cells": [ - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "({'night01': {'start': 2468103.786605,\n", - " 'stop': 2468103.907567,\n", - " 'irfs': {'start': array([-9.]),\n", - " 'stop': array([-9.]),\n", - " 'zref': array([-9.])}},\n", - " 'night02': {'start': 2468104.478985,\n", - " 'stop': 2468104.61995,\n", - " 'irfs': {'start': array([2468104.478985, 2468104.508555, 2468104.579304]),\n", - " 'stop': array([2468104.508555, 2468104.579304, 2468104.61995 ]),\n", - " 'zref': array([20, 40, 60])}}},\n", - " {'night01': {'start': 2468104.385232,\n", - " 'stop': 2468104.61995,\n", - " 'irfs': {'start': array([2468104.385232, 2468104.417285]),\n", - " 'stop': array([2468104.417285, 2468104.504078]),\n", - " 'zref': array([40, 60])}}})" - ] - }, - "metadata": {}, - "execution_count": 14 - } - ], - "source": [ - "import sys\n", - "import warnings\n", - "import matplotlib.pyplot as plt\n", - "import astropy.units as u\n", - "import pandas as pd\n", - "from astropy.visualization import astropy_mpl_style, quantity_support\n", - "from astropy.time import Time\n", - "from astropy.io import fits\n", - "from astropy.table import Table\n", - "from astropy.coordinates import SkyCoord\n", - "from os.path import join\n", - "sys.path.append('../lib')\n", - "from visibility import *\n", - "plt.style.use(astropy_mpl_style)\n", - "quantity_support()\n", - "\n", - "# add catalog folder\n", - "catalog = 'path/to/your/catalog_folder'\n", - "# add table file\n", - "table = 'path/to/your/visibility_table.npy'\n", - "\n", - "# grb template name\n", - "grb = 'Event998'\n", - "# site (check independently north and south!!!)\n", - "site = 'South'\n", - "\n", - "# check data from table to compare later with independent results\n", - "data = np.load(table, allow_pickle=True, encoding='latin1', fix_imports=True).flat[0]\n", - "data[grb]['South'], data[grb]['North']" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - " True Twilight Event \n JD JD JD \n------------------ ------------------ ------------------\n 2468104.385242233 2468104.385242233 2468104.0783962044\n2468104.5040800646 2468104.7094532535 2468104.5040800646\n2468104.4789870027 2468104.4789870027 2468104.214002172\n 2468104.65946042 2468104.9078309033 2468104.65946042\nok\n" - ] - } - ], - "source": [ - "# initialise template quantities\n", - "filename = join(catalog, grb+'.fits')\n", - "with fits.open(filename) as hdul:\n", - " hdr = hdul[0].header\n", - " source_radec = SkyCoord(ra=hdr['RA'] * u.deg, dec=hdr['DEC'] * u.deg, frame='icrs') # source coordinates\n", - " t_trigger = Time(hdr['GRBJD'] * u.day, format='jd')\n", - " visibility_table = Table.read(hdul, hdu=1) # visibility table\n", - " times = np.array(hdul['TIMES (AFTERGLOW)'].data.tolist())\n", - "print(visibility_table)\n", - " \n", - "# event total duration\n", - "afterglow_duration = Time((times[-1] - times[1])[0] / 86400, format='jd')\n", - "# time windows [THIS SHOULD BE CHANGED ACCORDING TO THE TEMPLATE FORMAT]\n", - "t = [Time(visibility_table['True'].data, format='jd')] # Doesn't have more >1 visibility window to test\n", - "t_true = []\n", - "for row in t:\n", - " t_true.append(row[0:2])\n", - "# minimum altitude\n", - "min_altitude = visibility_table.meta['MIN_ALT'] * u.deg\n", - "# set IRF altitude thresholds\n", - "thresholds = tuple(sorted([10, 36, 57])) # zenith 80 - 54 - 33 - 0\n", - "# set IRF reference zenith angles\n", - "zenith_angles = tuple(sorted([20, 40, 60], reverse=True))\n", - "\n", - "print('ok')" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "*** 2468103.786605299 2468105.4532496277\n", - "\n", - "Event full duration\n", - "nights: {'start': array([2468103.7866053 , 2468104.47897963]), 'stop': array([2468103.90757595, 2468104.90784112])}\n", - "Night 1 of 2\n", - "Night 2 of 2\n", - "IRFs 2468104.47897963 2468104.65945762 [20 40 60]\n" - ] - }, - { - "output_type": "display_data", - "data": { - "text/plain": "
", - "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-04-26T10:18:16.591883\n image/svg+xml\n \n \n Matplotlib v3.3.4, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3oAAAN0CAYAAADiWVoIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAEAAElEQVR4nOzdd3zM9x/A8ddlb5GJxB4VWiv2jNaIqhJ7ltbuQJVfh1Ft0eqipa09Y29aVVqCUtVQrRKqRRBkILL3/f74ViqVyF1yd9/L5f18PPKQu/t8vp/3fb+Ry/v7WRqtVqtFCCGEEEIIIYTFsFI7ACGEEEIIIYQQhiWJnhBCCCGEEEJYGEn0hBBCCCGEEMLCSKInhBBCCCGEEBZGEj0hhBBCCCGEsDCS6AkhhBBCCCGEhZFETwghhBBFFhYWhkajYenSpWqHIoQQ4gGS6AkhhHjI/T/eC/oaPHiw2iHm2rZtGzNmzCjw9aVLl1K/fn0cHBzw8vJi8ODBREVFFbtsaGgozZo1w93dHQ8PD1q0aMG6desM8ZbytW/fPjp37oy/vz8ODg74+fnRvn173nnnHaO1eV9OTg4zZsxgx44dRm9LCCGEYWhkw3QhhBD/FRYWRvv27Rk+fDhBQUEPvV6tWjVatmxp+sDyMXjwYNauXUt+H2dTp05l1qxZdOrUiWeffZaYmBg+//xzypYtS3h4OB4eHkUqO2vWLKZOnUqnTp3o3r07mZmZhIaGEh4ezscff8xrr71m0Pc4b948Xn31VerXr0///v3x8vLi6tWrHD9+nB9++IHs7GyDtvdfWVlZ2NraMnToUFauXJnntfs/K0uWLGHEiBFGjUMIIYTubNQOQAghhPlq3ry5WfXe6ePmzZvMmTOH4OBgvv3229znn3nmGZo3b86HH37IBx98oHdZgM8++4zAwED27t2LRqMBYNSoUVSvXp3ly5cbNNHLyspixowZBAYG8tNPP2Fra5vn9YJ6HIUQQpRuMnRTCCFEkX322WdoNBrCw8Mfeu3w4cNoNBoWL16c+1xWVhZz5syhTp06eYZHXr9+PU/dYcOGodFoiImJYdiwYZQtWxZXV1f69evH3bt3c8sFBQWxdu1agDxDS69cucJPP/1EVlbWQ4lqkyZNqF27dm49QK+yAImJiZQrVy43yQNwdHSkbNmyODs763r6dBIXF8e9e/do3br1Q0kegJ+f30PPHT9+nE6dOuHm5oazszOtWrXKk8ACXLlyBY1G81APHSjn9X5P7pUrV3LbXbVqVe45zq+nd+HChdSoUQN7e3saNGjAwYMH9X/DQgghDEJ69IQQQhQoKSmJuLi4h553dXXF3t6e/v3789prr7F27VoaN26cp8y6deuws7Ojd+/eAGi1Wvr06cOePXsYNmwY48ePJyoqigULFnD48GF+/fVXPD098xyja9euVKlShdmzZxMREcEXX3yBnZ0da9asAWDKlClkZmZy7Nix3OcAvL29ycjIAMDJyemh+J2dnTl37hzR0dH4+vrqVRbgySef5Ntvv2XevHl0796drKwsli5dyp9//snmzZt1Pr+68PHxwdnZmT179jB58uR8E7sHHT16lKeeegovLy8mT56Mo6MjK1asoGvXrmzYsIG+ffvq1b63tzerVq1i6NChtGnThlGjRgHknov7Fi1aRGJiIiNHjsTW1jb33ERGRlK2bFn93rQQQoji0wohhBD/cfDgQS1Q4NeKFStyy3bq1Elbvnx5bXZ2du5zGRkZWk9PT2337t1zn9u4caMW0O7evTtPW6dOndJaW1trp0yZkvvc0KFDtYB2/Pjxecq+8sorWmtra+29e/dynxs0aJA2v4+zU6dO5XuMmJgYrZOTkxbQnjx5Uu+yWq1WGxUVpQ0KCspzTtzc3LS7du3K93wW1+zZs7WA1s7OTvvkk09qp06dqt29e7c2JSXlobJNmjTROjk5aSMjI3Ofi4+P11aqVElbrlw5bUZGhlar1WovX7780LW8r127dtp27drlPs7MzNQC2qFDhz5U9v7Pir+/vzYxMTH3+ZMnT2oB7RdffFH0Ny6EEKLIZOimEEKIAk2cOJH9+/c/9NW5c+fcMoMGDeLmzZt5hul999133L59m0GDBuU+t2HDBvz9/WnevDlxcXG5XxUrVqRatWr88MMPD7X/4osv5nncvn17srOziYyMLDT2hg0b0rJlS7788kvmzZvHpUuXOH78OD179iQzMxOAlJQUvcuCMkzzscce4/nnn2fjxo2EhobStGlT+vTpw86dO3U5tXp588032bRpE61bt+bo0aPMnDmTbt26UaFChTzbGkRHR/PLL78wYMAAKlWqlPt8mTJlGDNmDLdu3eKXX34xeHwAQ4YMwcXFJfdxo0aNcHNz4++//zZKe0II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afPmzbr55pvr/BmaNGmiCy64QO3atVPPnj119913a/Xq1Tpw4IB+//vfV1v3lltuqfYzAQDq78cff1RRUVGtIenNN9/UkCFDzrkipLKyUjt27NDRo0f1m9/8RnfddZfmzJmjiooKSVKnTp3Url07rV27VpWVlVqzZo0iIyPVoUOHOusqLy/XE088ofT0dM2ePVu/+c1v9Nprr2nv3r2SpO3btys2NlaPPPKIsrOz9dRTT+nQoUN1vi5BDwAQtP76179qwIABGjlypIqKilRUVKQrr7yyxvWfeOIJ3XDDDdqwYYOGDh2qcePG6bbbbtOQIUO0fv163XjjjRo3bpzrkpiysjJlZmbqp59+0gcffKD169frhhtu0JAhQ7R169Ya28nLy1O7du2UmJjYoJ8rKSlJY8aM0aJFi1RZWel6vl+/fjp48GCtbQMAznXq1Cm9+OKLysjIULt27dyus2PHDn3zzTe6/vrrz1n2448/ym63a/Xq1Zo2bZqeeuop7dq1S4sXL5YkhYWFKSMjQy+88ILGjBmjF154QdnZ2fW6smTdunVq1aqVMjMzFR4erosuukh9+/bVF198Icm4LDQvL0933HGH/va3v6l169b661//WufrMkYPABC0mjdvrkaNGrnOitXltttu0x133CFJmjZtml5++WV17txZd955pyRj8P1LL72kzz//XEOHDtXbb7+to0eP6u2331ZEhLHLfPjhh/Xxxx9r5syZev755922s2vXrhr/kKivSy65REePHtWhQ4dcl/o4j0Lv3LnTNfgfAELVU089pW3btkkyJl6RpPfff1+S1L17d/3ud7+TZJyNe+mllxQREaEJEya4fa3Kykq99tpruvPOO6tNvuLUqFEjSdL111+vuLg4SdLQoUO1ePFi3Xbbbfr66681b948TZ06VR07dtTOnTv19NNP66GHHtKFF15Y689RXFys7du3a/z48a7n7Ha7MjIyJEmRkZHq06ePOnfuLEkaMWKEJk6cqOPHjys6OrrG1yXoAQBCRs+ePV3/btWqlcLDw3XZZZe5nouLi1OjRo1cY+PWrFmjAwcOnHMJz4kTJ6pNlHK2srKy8x4f6HA4JKnaGD/na5aVlZ3XawOAFTiDnOR+MhbJ+F36yiuv6MiRI3rooYdcB+3OVlZWpp07d7oO4Dmvprjrrrt07733KiUlRfHx8TXWUlBQoJSUFHXq1EmSMSavc+fO2rhxY51BLyEhQT169HBN1HW25OTkavsC57+d+4maEPQAACEjMjKyzudsNptrB19ZWamUlBS9884752xX21HUVq1aadWqVedV66ZNm9SiRYtqf1iUlJS4Xh8AULdXX31V+/bt0yOPPOI6K+dOdHS0XnnlFdf3hw4d0sMPP6wnn3xSzZo1k2TMlLxs2TJdfvnlCg8P1/vvv6/U1FRJxhi9d999V7t379aFF16oXbt2adu2bbr22mslGfuTU6dOyW63y+FwqKKiQmFhYYqIiFBqaqrmz5+v5cuXu4Yf7N69W1FRUUpKStKgQYP07LPP6mc/+5mSkpK0aNEide/eXTExMbX+7AQ9AEBQa9SokdtpsL2hd+/emjt3rpo1a1brVNxnS01N1YwZM1RRUVHrHxY1KSws1Lx585SVlaWwsKrh9Bs3blR4eHi97gEFAKGuuLhYH330kSIjIzVp0iTX89nZ2RowYIAOHTqk++67T88++6wSEhKqXb3hnGSlefPmrks5hw8frqNHj+q3v/2tIiMj1b9/f91yyy2SpB49emjEiBF69tlndeTIETVr1kw333yz60qSrVu36rHHHnO9/tixY9WjRw9NnTpVTZo00cMPP6y5c+dq7ty5cjgcSk5O1rhx4yQZl/LfdtttevLJJ1VRUaFu3brp7rvvrvPntznqOucHAEAA+/Wvf61PP/1US5YsUfPmzdW8eXNFRkaec8N0dzdQj4iI0KuvvuoaoycZl0e+9NJLmjhxosrLy9W7d281adJETzzxhLp27aqDBw/qk08+UUpKSo2zav7www9KTEzU//73Pw0YMMD1vLv76LVr107PPPOMKisrdejQIS1fvlx/+ctf1KJFC+Xl5SkhIcG1/cMPP6yVK1e6ZhYFAKAmzLoJAAhq999/vxISEtSzZ0+1atVKK1eu9NprR0VFKS8vT71799b48ePVtWtXDR8+XF9++aWSk5Nr3C4uLk633Xab3njjjWrPV1ZWnjM+ZP78+Wrbtq2Sk5N19dVXKycnR/fff7/Wrl1bLeQ5HA7Nnz9fkydP9trPBwCwLs7oAQDgAzt27FDv3r21efNmJSYmym63q2nTpnr55ZddM396YsGCBfrTn/6kr776yu2McAAAnIkxegAA+ECnTp00c+ZM7dq1S6dOndI//vEPnTx5UoMGDWrQ6504cUKvv/46IQ8AUC+c0QMAwMciIyPVqVMnTZ06VaNGjTK7HABACCDoAQAAAIDFMBkLAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxUSYXUBD5eTkmF0CAAAAAJgqKyvL7fNBG/Skmn+o85Gbm6tBgwZ5/XXhX/SjNdCP1kA/WgP9aA30o3XQl9Zwvv1Y28kvLt0EAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDEEPQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDERZhdgNVFRUTpx4oTZZfhMo0aN9Ic//MEv7Tz00EM+bwewqr/85S+qqKjweTt//vOf62xnxowZyszM9Hkt8C36sXbsHwEEGs7oeZmVQ54kv/zh6M92AKviswr4F585AIGGoAcAaJChQ4dq5syZHm2zZcsWbdmyxUcVBX777sycOdPj9xEAgLpw6SYAoEHS0tKUlpamyZMn13ublJQUH1YU+O27M2nSJEny6H0EAKAunNEDAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDFenYxl2bJlysvL0549e5Senq4pU6acs05OTo4WLlyohx9+WJdddpkkyeFwaP78+frkk08kSZmZmRozZoxsNps3ywMAAACAkODVoBcXF6fhw4drw4YNbu/zcuDAAa1evVpxcXHVnv/oo4+0Zs0aTZ8+XTabTY8//rjatGmjIUOGeLM8AAAAAAgJXr10s1+/furTp49iY2PdLn/99dc1ZswYRURUz5fLly/X0KFDFR8fr5YtW2ro0KHKzc31ZmkAAAAAEDL8dh+9zz//XBEREerVq9c5y/bu3avk5GTX98nJySosLKzzNX0RBktLS8/rdWfMmOG9YgJUYmKiX9o5n344335EYKAfG65r165+a6uu33tJSUnnrGP270qz23cnEGs6k7t+RHXsH+FP9KU1+LIf/RL0ysvL9dZbb+nhhx+ucXl0dLTr++joaJWXl8vhcNQ6Tm/QoEHeLlW5ubnn9bqZmZneKyZAPfroo35pZ9SoUQ3e9nz7EYGBfmy4adOm+a2tBx54oNblM2bMcK1z//3312sbXzG7fXcCsSZ3zuxHuMf+Ef5EX1rD+fZjTk5Ojcv8MuvmggULNGDAALVu3drt8qioKJWVlbm+LysrU1RUFJOxAAAAAEAD+OWM3qZNm1RSUqL//e9/kqSjR4/q+eef10033aSbbrpJ7du3V0FBgTp37ixJKigoUFJSkj9KAwAAAADL8WrQs9vtstvtqqysVGVlpSoqKhQeHq5HHnlEdrvdtd4f/vAHjR071jVeLyMjQ0uXLnV9v3TpUl1//fXeLA0A4GWzZs3SzJkzPdpmzJgxPqomONp3Jy0tzewSAAAW5NWgt3jxYi1cuND1/YoVKzRixAhlZWVVWy8sLEwxMTGKioqSJA0ePFgHDx50Xft/9dVXa/Dgwd4sDQDgZUVFRVq3bp1H28yfP99H1QRH++54+h4CAFAfXg16WVlZ54Q6d1566aVq39tsNt1+++26/fbbvVkOAAAAAIQkv0zGAgCwnqFDh3p86eaWLVu0ZcsWH1UU+O27M3PmTI/fRwAA6uK3++gBAKwlLS1NaWlpmjx5cr23SUlJ8WFFgd++O5MmTZIkj95HAADqQtADADTIkiVLtGTJEo+2eeONN3xUTXC0744z6AEA4E0EPQBAg6xbt06zZ8/2aJtx48b5qJrgaN8dT99DAADqgzF6AAAAAGAxBD0AQIOkpqYqOzvbo23mzp2ruXPn+qiiwG/fnezsbI/fRwAA6kLQAwA0yLBhwzRr1iyPthk7dqzGjh3ro4oCv313Zs2a5fH7CABAXQh6AAAAAGAxBD0AAAAAsBiCHgAAAABYDEEPAAAAACyGoAcAAAAAFkPQAwAAAACLIegBAAAAgMUQ9AAAAADAYgh6AAAAAGAxBD0AAAAAsBiCHgAAAABYTITZBQAAgtOSJUu0ZMkSj7Z54403fFRNcLTvzqRJk8wuAQBgQQQ9AECDrFu3TrNnz/Zom3HjxvmomuBo3x1P30MAAOqDSzcBAAAAwGIIegCABklNTVV2drZH28ydO1dz5871UUWB37472dnZHr+PAADUhaAHAGiQYcOGadasWR5tM3bsWI0dO9ZHFQV+++7MmjXL4/cRAIC6MEYPANAg+fn5ys/P92ibrVu3+qia4GjfHUIeAMAXCHoAgAZZunSpHn30UY+26dGjh2+KCZL23Zk8ebLZJQAALIhLNwEAAADAYgh6AIAGadu2rVJTUz3aZvTo0Ro9erSPKgr89t1JTU31+H0EAKAuXr10c9myZcrLy9OePXuUnp6uKVOmSJIKCwv1t7/9TQcPHpQkdezYUePHj1dSUpIkKScnR++8844iIqrKefrpp9WmTRtvlgcA8KJJkyZp0qRJstls9d5m3rx5kqT58+f7qqyAbt8d5zhHT95HAADq4tWgFxcXp+HDh2vDhg2qqKio9vy9996rVq1ayeFw6L///a/++te/6umnn3at079/f919993eLAcAAAAAQpJXg16/fv0kSTt27FBJSYnr+ZiYGMXExEiSKisrFRYWpgMHDnizaQAAAADAaX6ddXP8+PEqLy+Xw+FQVlZWtWX5+fmaMGGC4uLidN111+naa6/1Z2kAAAAAYBl+DXqvv/66ysvLlZeXp1atWrme79+/v6655hq1aNFC27dv17PPPquYmBilp6fX+nq5ubler7G0tPS8XnfGjBneKyZAJSYm+qWd8+mH8+1HBAb6seG6du3qt7bq+r2XlJR0zjpm/640u313ArGmM7nrR1TH/hH+RF9agy/70eZwOBzeftG33npLJSUlrslYzlZZWans7Gw9++yzat68+TnL//3vf2vHjh26//77a2wjJyfnnLOC3pCbm6tBgwY1ePtQGEzv6X2zGmrq1KkN3vZ8+xGBgX5suGnTpvm8DedntK7fezNmzNADDzwgSXLucsz6XWl2++4EYk3unNmPcI/9I/yJvrSG8+3H2jKRKbdXcDgcOnHiRLVxfGey2WzyQf4EAAAAgJDg1aBnt9tVUVGhyspKVVZWqqKiQna7XV9//bV27dqlyspKHT9+XHPnzlVsbKzatWsnSVqzZo1KS0vlcDj03Xff6YMPPlCfPn28WRoAAAAAhAyvjtFbvHixFi5c6Pp+xYoVGjFihJKSkvT666/r8OHDatSokTp16qSHHnpIjRo1kiStWrVKr7zyik6ePKn4+HjddNNNGjhwoDdLAwAAAICQ4dWgl5WVVeM1ov37969xu3vuucebZQAAAABASDNljB4AAAAAwHcIegAAAABgMX69jx4AwDry8/OVn5/v0TZbt271UTXB0b47s2bNMrsEAIAFEfQAAA2ydOlSj+8b1qNHD98UEyTtuzN58mSzSwAAWBCXbgIAAACAxRD0AAAN0rZtW6Wmpnq0zejRozV69GgfVRT47buTmprq8fsIAEBdCHoAgAaZNGmSx2P05s2bp3nz5vmoosBv352GjHUEAKAujNEDADTI/v37VVRU5NE2x44d81E1wdG+O4Q8AIAvcEYPANAgs2fPVu/evT3aJjY2VrGxsT6qKPDbd6d3794ev48AANSFoAcAAAAAFkPQAwAAAACLIegBABpk6tSpcjgcHm3jcDg83sabzG7fnUCsCQAQ/Ah6AAAAAGAxBD0AAAAAsBiCHgAAAABYDEEPAAAAACyGoAcAAAAAFkPQAwAAAACLIegBAAAAgMUQ9AAAAADgbMePS599Jj37rHTbbVJhodkVeSTC7AIAAAAAwFR2u7R1q/Tll9Lq1cbXjRuN553GjJGSksyr0UMEPQAAAACh5fvvjUD3xRfG48svpdLSquUtW0pDhkh9+1Y9WrUyr94GIOgBAAAAsK6TJ6UNG6pC3eefSzt3Vi2PjJR69ZKuuMIIdP36SZ06STabeTV7AUEPANAg+/fvV1FRkUfbHDt2zEfVBEf77uTn55tdAgBYy6FDRphbtcr4+uWXUllZ1fIOHaSRI41g17+/dPnlUlSUaeX6CkEPANAgs2fP1qOPPurRNrGxsb4pJkjad6d3795mlwAAwauyUtq2TVq50gh2q1ZJ335btbxxYyktTbrySiPUXXGFlJhoXr1+RNADAAAAEBzKyqS1a41g99lnRrD74Yeq5YmJ0ogRRqi78krjkszGjc2r10QEPQAAAACB6fDhqlD32WdGyDt50lhms0mXXiqNGiWlpxvBLjk56MfWeQtBDwDQIFOnTtXUqVNl82CH6nA4JMmjbbzJ7PbdCcSaAMA0BQVGoFuxwnhs2VK1LDpauuoq45GeblyG2by5ebUGOK8GvWXLlikvL0979uxRenq6pkyZIkkqLCzU3/72Nx08eFCS1LFjR40fP15Jp+9D4XA4NH/+fH3yySeSpMzMTI0ZM4adHgAAAGBVDocxvm7FCmn5cuOxd2/V8oQE6eabpQEDjHDXq5cxQybqxatBLy4uTsOHD9eGDRtUUVFR7fl7771XrVq1ksPh0H//+1/99a9/1dNPPy1J+uijj7RmzRpNnz5dNptNjz/+uNq0aaMhQ4Z4szwAgBdNmzbN48lYzD6AZ3b77gRiTQDgE3a7cZuD5curztgVF1ct79hRGjeuKth168ZlmOfBq0GvX79+kqQdO3aopKTE9XxMTIxiYmIkSZWVlQoLC9OBAwdcy5cvX66hQ4cqPj5ekjR06FB9/PHHBD0AAAAgWJ08KeXnG8EuL8+4JPPo0arlPXpIt94qZWQY4e701X7wDr+O0Rs/frzKy8vlcDiUlZXlen7v3r1KTk52fZ+cnKzCwsI6Xy83N9frNZaWlp7X686YMcN7xQSoRD9NSXs+/XC+/YjAQD82XNeuXf3STn1+5yUlJYXE70arox/rxv4R/hSIfWmrqFCzb75R8w0b1GLDBjXftEnh5eWSJEdYmEo7ddKP116rIz176sill+rkmePrvvvOeIQYX/ajX4Pe66+/rvLycuXl5alVq1au58vLyxUdHe36Pjo62hUIa7ukZdCgQV6vMTc397xeNzMz03vFBChPL9VqqFGjRjV42/PtRwQG+rHhpk2b5vM2srOzVVRUVOd94GbMmKEHHnhAkrFDk8y7n53Z7buzdu1aSYF/P70z+xHusX+EPwVEX5aXGzcjz801ztitWmU8J0nh4VLv3tLAgdLAgbKlp6tp8+ZqKqm9mTUHmPPtx5ycnBqX+X3WzaioKA0ZMkTZ2dl69tln1bx5c0VFRansjLvVl5WVKSoqinELABDAEhMTPT6D4byM3yxmt+9OWlqa2SUAQP2Ul0tffFEV7D7/XDpxwlgWGSn17WsEu0GDjPvYBdBBtVBkyu0VHA6HTpw4oZKSEjVv3lzt27dXQUGBOnfuLEkqKChwzcgJAAAAwATl5dLq1Uaw+/RTI+Q5g12jRsbtDZzB7oorjNsfIGB4NejZ7XbZ7XZVVlaqsrJSFRUVCg8P1+bNm9W0aVMlJyervLxcb7/9tmJjY9WuXTtJUkZGhpYuXapevXpJkpYuXarrr7/em6UBAAAAqM2JE9WD3Zln7JzBbtCgqmDXpImJxaIuXg16ixcv1sKFC13fr1ixQiNGjFBSUpJef/11HT58WI0aNVKnTp300EMPqVGjRpKkwYMH6+DBg65r/6+++moNHjzYm6UBAAAAOFNFRdUYu08/rT7GLjKyKthlZhLsgpBXg15WVla12TTP1L9//xq3s9lsuv3223X77bd7sxwAAAAATidPSmvWVAW7lSsl5zwZzjF2mZlVY+y4FDOomTJGDwAAAICPnTolrVtnhLpPPzXuY3fsmLEsIkLq06cq2F15pRSAE1ah4Qh6AAAAgBXY7dKGDVXBbvly6aefjGVhYcbtDpzB7qqrmBXT4gh6AAAAQDCqrJQ2bqwe7H780Vhms0m9ehnBLjNTGjBAatbM1HLhXwQ9AAAAIBg4HNLmzdKnn+rinBzj3yUlVct79pTuvNM4Y5eRIcXFmVUpAgBBDwAAAAhEDoe0bVvVGbu8PKm4WJLUSpIuuUQaPdo4YzdwoBQfb2q5CCwEPQAAACAQOBzS9u1Vs2Lm5koHDlQtT0mRRoyQMjO1MiJC6bfcYlalCAIEPQAAAMAMDof03XdVoS43VyoqqlrepYv0859XTaBywQWuRSdzc/1cLIINQQ8A0CDTpk3To48+6tE2NpvNN8UESfvuBGJNAHzEGeycoS43V9q/v2p5587SxIlVl2K2a2dSobACgh4AAADgC2deipmba4yxOzPYXXSR9MtfVgW7pCSzKoUFEfQAAAAAb3BOnpKXV/U481LMzp2NYDdokBHs2rc3rVRYH0EPANAg2dnZGjp0qHr37l3vbUpLSyVJsSbdpNfs9t1Zu3atJHn0PgIIEJWVxi0OnKFu+XLp+++rlnfqZFyK6Qx2nLGDHxH0AAANkpiYqMTERI+2iYmJ8VE1wdG+O2lpaWaXAKC+Tp2SNmwwAt3y5dKKFdLhw1XLu3eXbrnFCHUZGYyxg6kIegCABpk1a5Zmzpzp0TZjxozxUTXB0b47BD0ggJ04Ia1dWxXsVq6Ufvqpavkll0i33VYV7Nq0Ma9W4CwEPQBAgxQVFWndunUebTN//nwfVRMc7bvj6XsIwId++klatco4U/fZZ9Lq1VJ5ubEsLExKTTUCXUaGdNVV3KAcAY2gBwAAgNB08KAR6FasMB5ffWWMu5OkRo2kvn2rgt2VV0pNm5paLuAJgh4AoEGGDh2qtm3bavLkyfXeZsuWLZKkHj16+KqsgG7fHeflr568jwAawOGQvvnGuPzys8+Mx3ffVS1v1ky69lppwADj0aePFBVlXr3AeSLoAQAaJC0tTWlpaR4FlJSUFB9WFPjtuzNp0iRJBD3A68rLpXXrjGDnfBw6VLU8MVHKyjIuwRwwQLrsMik83Lx6AS8j6AEAACD4HTggff65EehWrZLy86WKiqrll1wi3XqrEezS06ULL5RsNtPKBXyNoAcAAIDgcuqU9PXX0hdfGOFu1Spp586q5dHRxpi69HSpf3/j33Fx5tULmICgBwAAgMB28GBVqPviC2nNGun48arl7dtLv/iFEeyuvNK4DDMy0rx6gQBA0AMAAEDgKCuT1q83bm3w5ZfG1127qpY3biylpUlXXGGcrbviCikpybx6gQBF0AMAAIA5Kiulb7+tCnSrV0sbNhiXZjpdeKFxtq5/f+PRs6cR9gDUiqAHAAAA33M4pD17jMsunY/8fOno0ap1mjWTBg2S+vUzHn37Sm3amFYyEMwIegAAAPAuh0Pat88Ics7HmjVScXHVOo0aGWfn+vQxHv36Sd26SWFh5tUNWAhBDwAAAA3nLtTl5xsTqDiFhUkpKdKNNxpn6fr0kS69lEswAR8i6AEAAKB+Kiul774zJktZt874un599RuRO0PdddcZk6akpUmXXy7FxJhWNhCKCHoAAAA4V1mZtGmTMTnKV18Zjw0bpNLSqnUiIqQePYwzdamphDoggHg16C1btkx5eXnas2eP0tPTNWXKFEnSt99+qwULFmjnzp0KCwtTjx49NH78eMWdvnFlTk6O3nnnHUVEVJXz9NNPqw2DbwEAAHzLeenlxo1GkHMGu2+/Nc7gOTVpYoyp69XLeKSmShdfLEVFmVY6gJp5NejFxcVp+PDh2rBhgyoqKlzPHzt2TNdcc43uu+8+hYeHa86cOXr55Zf1hz/8wbVO//79dffdd3uzHACAD82aNUszZ870aJsxY8b4qJrgaN+dtLQ0s0tACAk/dkxatcoIdWc+fvih+oqJicall5dfboS7nj2lLl2k8HBT6gbgOa8GvX79+kmSduzYoZKSEtfzvXr1qrbeddddp2nTpnmzaQCAnxUVFWndunUebTN//nwfVRMc7bvj6XsI1Mvx49LWrdLmzcZj0yZp82YNKCiovl5UlHFW7tJLjYcz1CUkmFM3AK+xORwOh7df9K233lJJSYnr0s2zvffee1q1apWeeOIJScalm++9957CwsIUFxen6667Ttdee22tbeTk5KhVq1beLl2lpaWKjY1t8Pb5+flerCYwJSYm+qWdtm3bNnjb8+1HBAb6seGKior80s7+/fvrXCcpKUmFhYV+qAa+RD/WzYz9Y/jx44ouKFB0QYFi9uwxvu7eraiiItnO+BPPYbOpLDFRR9u3V3nXrjp20UUq7dhRZe3acZYuSLGPtIbz7cfi4mJlZWW5Xeb3yVgKCgq0aNEiPfjgg67n+vfvr2uuuUYtWrTQ9u3b9eyzzyomJkbp6em1vtagQYO8Xl9ubu55vW5mZqb3iglQjz76qF/aGTVqVIO3Pd9+RGCgHxvOH1dNDB06VD/99JMmT55c63ozZszQAw88IEnasmWLJKlHjx4+r88ds9t3x3n5a13vo9nO7Ee457P9o8Oh2NJSJRw6pPhDhzSoUyfjbN3WrZK78N2xozR0qHGm7vTD1r27ops00Zf8XrUM9pHWcL79mJOTU+Myvwa9AwcO6C9/+YvuvPNOpaSkuJ5PSkpy/btbt2762c9+pi+++KLOoAcAME9aWprS0tI8Cihn/u43g9ntuzNp0iRJgR/04HsRFRVqWVKihMOHXaEu/vS/G58x94GxcoTUubN0yy3GrJcpKcajWzdmvAQgyY9Br7i4WI8//rhuvfVWZWRk1LquzWaTD64oBQB40ZIlS7RkyRKPtnnjjTd8VE1wtO+OM+ghNISfOqW4H35Qy8OHFX/4sOJLSlz/bvbTT+esXxYVpe9bt9bh+HgdTkjQofh4/eLRR42QFxnp/x8AQNDwatCz2+2y2+2qrKxUZWWlKioqFB4eriNHjuhPf/qTrr32Wg0ZMuSc7dasWaOUlBTFxMRox44d+uCDD87rsj0AgO+tW7dOs2fP9mibcePG+aia4GjfHU/fQwS+RidOKK6kRC1/+EEtS0qMf5eUKO6HH9T8yBHZzlr/VHi4Slq21P7ERJXEx+vQGaHueEyMZDtriwA8Mw0g8Hg16C1evFgLFy50fb9ixQqNGDFCknTw4EEtXLiw2vK5c+dKklatWqVXXnlFJ0+eVHx8vG666SYNHDjQm6UBAAB4RZikJEkXnfHoKClj9mzF/fCDYo4fP2ebU+Hh+iEuTtu7dFFJy5YqiY83ztK1bKmjzZvLERbm158BgPV5NehlZWXVOOtLTc9L0j333OPNMgAAfpCamqrs7GyPzkg5D/CZdWbN7Pbdyc7OlsSZvUBik5Qo6cIzHh3P+HcHSe4umiw/dEg/xMWpIDlZP7RsaQS6uDiVtGypn5o1I8wB8Cu/z7oJALCGYcOGadiwYR4FlLFjx0oyL2iZ3b47s2bNkkTQ86fGMs7IJbt5dJDUXlIjN9udkFQg6VNJOyXtOv3V+fjtQw/5unQAqDeCHgAAsIwwSRfICGvORwdVBbgOktrUsG25pD2S8mQEul2Sdp9+7JJ0QBJTxQEIFgQ9AAAQFMJlhLSkMx7tz/jaXsYllzX9cVMsaa+kz09/LTjjsUfS9yLIAbAOgh4AADBdE0ntang4Q11bGWHPnSOSCiV9dPproYzwtkdGqNsrqcx35QNAwCHoAQAAn7FJai0jsCWqKryd+e92klrW8hqHJe2T9LWqQty+01/3nv567h3oACC0EfQAAECDNFP10JZ41r/byRgvV9NtvU9JKpL0rYzg5nwUnvU9Z+IAwHMEPQAAUE0jGZdJnnn2LWPJEv1L1UNcTC2vUSIjpG05/XW/qoKb89/fS6r0zY8AACGPoAcAQAhpqepj3868fNIZ4Fq52zAvT31knF3bJ2mtjMB2doBzPsp9+UMAAOpE0AMAwALCVH1GyjMnMTkz0DWpYXu7pIMybiWwSueefRt9//2695ln9KPPfgIAgDcR9AAACHDOCU3a1/BIUu23FTgmI6x9rupj384cE3dQRtiryXVt2xLyACCIEPQAAAgAKap+U+8zH+1ljJtzx91tBc5+HPFl4QCAgETQAwDAx5pJuvD0I/msr05b3GxXJuM+cMtV/X5whWf8m9sKAADcIegBABpkyZIlWrJkiUfbvPHGGz6qxtz2o2SEtotOf+14xuNC1XyPuGJJuydNUomkXEkFqrrJ9x5Jh3xSLQAgFBD0AAANsm7dOs2ePdujbcaNG+ejanzffitJnWSEOedX57/b1bDN95K2S9olY5KT3TLCnPNxXJI8fA8BAKgPgh4AAKddIKnzWY8up782c7P+MUk7JK2RtPP0Y7eqgt1xXxcMAEANCHoAgAZJTU1Vdna2R2f15s6dK8m8M3tz585VpKRnxo1TV+mcR1M323wvaZOk72SEOudj5+ll5ys7O1uSPD47CgBAbQh6AIAGGTZsmIYNG+ZRQBk7dqwk3wc9m4zZKruf9Rh0uv3bzmq/WNLXMi6z3C4j1DkfR31aqTRr1ixJBD0AgHcR9AAADZKfn6/8/HyPttm6datXa4iQcVlljzMeKZK66dwbg5dJKt+6VRWS/irp29OP7ZJ+8GpVnnEGPQAAvImgBwBokKVLl+rRRx/1aJsePXo0qK1wGWPlLpF0sYxAd7GMyy0jz1r3e0lfStp2+rH19Nc9khwNbN+XJk+ebHYJAAALIugBAAJKB0mXygh1zkeKpMZnrVco6VMZ959zPrZKKvFbpQAABC6CHgCgQdq2bavU1FStW7eu3tuMHj1akjR//nxFywhxPSVddsbX5mdtUyxppaSNMiZF2Swj1B1pQM1nth8oUlNTJcmj9xEAgLoQ9AAADTJp0iRNmjRJNput9hUdDrWV1EvSvHnzJEmPzp+vTpLCzljtuIwg97WMUOcMdsVerNnZfiAFPec4xzrfRwAAPEDQAwB4jU3G5CipMoLd5ZIGPPqo7j9rvUaSlkraICPYfS1jhstKv1UKAIC1EfQAAOdltKS0049eqn5j8VOSfmjWTAuPHdN6Sc+dfv5Cv1YIAEDoIegBAOrmcCiupETt9u9X4r59Sty/X5o6VZI07/Qq5TLO0K07/Vgv49LLJ+6/Xw888ICkqqAHAAB8i6AHADhH06NH1W7fPleoS9y/X03Ky13LT0ZU7T7Gywh2W2ScwQMAAOYj6AFAiGt04oTa7t+vpH371K6wUO327VOzn35yLbeHhen71q21pV077UtM1P527fR9q1b64+nl/zClagAAUBuCHgCEkspKtTp0SEl79yrpdKhrVVysMIfDtcqh+HhtuOwy7WvXTvvbtdOBNm1kjzz7tuQAACCQeTXoLVu2THl5edqzZ4/S09M1ZcoUSdK3336rBQsWaOfOnQoLC1OPHj00fvx4xcXFSZIcDofmz5+vTz75RJKUmZmpMWPGMNU0AJynqLIytSssVPvCQrUrLFRSYaGiTpxwLT8WHa3vOnfWvqQk7WvXTvvatVN5kyYmVgwAALzBq0EvLi5Ow4cP14YNG1RRUeF6/tixY7rmmmt03333KTw8XHPmzNHLL7+sP/zhD5Kkjz76SGvWrNH06dNls9n0+OOPq02bNhoyZIg3ywMAa3M4pO3bpZUrNew//1H7PXvU6tAh1+JKm00HLrhAhUlJKmzfXoVJSfohLk7ioBoAAJbj1aDXr18/SdKOHTtUUlLier5Xr17V1rvuuus0bdo01/fLly/X0KFDFR8fL0kaOnSoPv74Y4IeANSmvFzKz5dWrjQeq1ZJp4Ndqoyzddu6dVNh+/bam5SkosREnWzUyNyaAQCAX5gyRm/r1q1KSkpyfb93714lJye7vk9OTlZhYWGdr5Obm+v12kpLS8/rdWfMmOG9YgJUYmKiX9o5n344335EYKAfq4soLVWzTZvUfONGNd+4Uc22bVPYyZOu5ccuvFBH+vXTkUsu0Xdt2qi0TRvX2booSR19VFddv/eSkpLOWcfs35Vmt+9OINZ0Jnf9iOrYP8Kf6Etr8GU/+j3oFRQUaNGiRXrwwQddz5WXlys6Otr1fXR0tMrLy+VwOGodpzdo0CCv15ebm3ter5uZmem9YgLUo48+6pd2Ro0a1eBtz7cfERhCvh/37ZNWrDAen30mbdxoXJ4pSVFR0pVXSunpxqN/f8XExSlGUqKkBdOmSUeP+qVM5z3yajJjxgzXOvfff3+9tvEVs9t3JxBrcufMfoR77B/hT/SlNZxvP+bk5NS4zK9B78CBA/rLX/6iO++8UykpKa7no6KiVFZW5vq+rKxMUVFRTMYCIHQ4HNLOndLy5VWPnTurlsfFSUOHSlddJQ0YIKWmSo0bm1evpPz8fOXn53u0zdatW31UTXC0786sWbPMLgEAYEF+C3rFxcV6/PHHdeuttyojI6Pasvbt26ugoECdO3eWZJz1O/PSTgCwHIdD2rZNysurCnb79lUtb9dOGjVKysgwgl1KihQWZl69bixdutTjMxg9evTwTTFB0r47kydPNrsEAIAFeTXo2e122e12VVZWqrKyUhUVFQoPD9eRI0f0pz/9Sddee63bCVYyMjK0dOlS16QtS5cu1fXXX+/N0gDAXA6HtGWLEexyc42v339ftbxTJ2n8eCPYZWRIHTsyGyYAAGgwrwa9xYsXa+HCha7vV6xYoREjRkiSDh48qIULF1ZbPnfuXEnS4MGDdfDgQde1/1dffbUGDx7szdIAwL+cwS43tyrYFRdXLe/WTbrlFmngQCPYtWtnVqUN1rZtW6WmpmrdunX13mb06NGSpPnz5/uqrIBu353U1FRJ8uh9BACgLl4NellZWcrKyqpxWU1sNptuv/123X777d4sBwD8x+GQvv1W+vRT45GbW/2MXUqKNGKEEewGDpQuuMC0Ur1l0qRJmjRpkkfjqefNmyfJvKBldvvuOMc5Mi4dAOBNptxeAQAsYdcu6ZNPjEdurrR/f9Wybt2k4cOlzEwj2LVpY1qZvrJ//34VFRV5tM2xY8d8VE1wtO+OpxPaAABQHwQ9AKiv/fuNs3XOcLd7d9WyTp2kiRONYDdokOSn+2mZafbs2R5PxhIbG+ubYoKkfXd69+5tdgkAAAsi6AFATUpKqge7bduqliUlSXfcIV19tRHu2rc3r04AAICzEPQAwOn4cePG5B99JH38sbR+fdUNyhMSpJEjjWB39dVS587MigkAAAIWQQ9A6Dp1SlqzpirYff65VFFhLIuNlW64QbrmGuNxySUBdx87s02dOlVTp071aBIRx+ngbNbEI2a3704g1gQACH4EPQChw+GQtm6tCna5udLRo8ayRo2k/v2rgl2fPlJkpKnlAgAANBRBD4C17dtnhLqPPjIezlkibTapV6+qYDdggBQdbW6tAAAAXkLQA2AtR48aNyf/8EMj2G3dWrWsUydp8mQj2GVmGuPuAAAALIigByC4nTwprV5thLoPPzT+bbcbyxISpF/8Qho82Ah3HTuaWysAAICfEPQABBeHw7jNwYcfGo/cXKm01FgWFWUEusGDjUfPnkygAgAAQhJBD0DgO3DAGGfnvBxz3z7jeZtNSk2Vhgwxgl16uhH2AAAAQhxBD0DgOXZMWrFCnebMkX7zG2njxqplF14oZWcb4e7qq6X4eNPKBAAACFQEPQDms9uldeuqLsdctUqqqFB7SWrRQho+3Ah2Q4YYE6oAAACgVgQ9AObYsaPqUsxPPpF++MF4PjJSuvJKacgQ5bdsqbRJk6TwcHNrBQAACDIEPQD+cfiwEeics2Pu2lW17JJLpDvvNMbZZWRIsbGSpJ9ycwl5AAAADUDQA+Ab5eXSypVVwW7dOmPGTElq21YaN864FPOaa4zvAQAA4DUEPQDeUVkpbdhQFexWrDDCnmScobvxRiPUDRki9ehhzJiJoLZ//34VFRV5tM2xY8d8VE1wtO9Ofn6+2SUAACyIoAeg4XbtMoLdRx8Ztz84fNh4PjxcuuKKqvvZ9etnjL2DpcyePVuPPvqoR9vEnr4s1yxmt+9O7969zS4BAGBBBD0A9XfmOLuPPpJ27qxa1qOHNHq0ccZu4ECpWTPz6gQAAAhxBD0ANTt9Pzt9/LER7DZsqBpnl5hojLMbPNi4JDMx0dxaAQAA4ELQA1Dl5Elp9Woj2H38sfTFF8ZzknGG7uc/rxpn160b4+xC3NSpUzV16lTZPPh/4Dh9oMCTbbzJ7PbdCcSaAADBj6AHhDK73ThL98knRrBbscI4iydJjRtLAwYYwe6aa6S0NCmCXxkAAADBgL/agFDicEjbtlUFu9zcqhuVh4VJvXtLV19tBLv0dKlJE1PLRWCbNm2ax5OxmH3Wyuz23QnEmgAAwY+gB1iZwyHt2GEEu08/NYLdgQNVyy+91Bhnd/XVxgQqzZubVioAAAC8h6AHWM3u3Uaocz4KC6uWdelijLO7+mpp0CCpTRuzqgQAAIAPEfSAYLd7t3GmzvkoKKhaduGF0vjxUmam8UhKMqVEWFN2draGDh3q0X3gSktLJZl3Pzuz23dn7dq1krifHgDAuwh6QDBxOIxgl5fnPti1b29cijlwoHHW7sILzakTISExMVGJHt5WIyYmxkfVBEf77qSlpZldAgDAgrwa9JYtW6a8vDzt2bNH6enpmjJliiTp1KlTeuGFF7Rz504VFxfrj3/8oy6++GLXdjk5OXrnnXcUccaMfk8//bTacFkZQp3DIX3zjbR8edVj796q5R06GMFu0CDjceGF3PIAAAAA3g16cXFxGj58uDZs2KCKiopqy7p166YbbrhBzz33nNtt+/fvr7vvvtub5QDBx26XNm6UPvvMOGu3fLn0/fdVyy+6SLrzTikjw7gUkzN2AAAAcMOrQa9fv36SpB07dqikpKSqkYgI3XjjjZKksLAwbzYJBLcTJ6Q1a4z7161YIa1aJR05UrW8e3fp5puNSzEHDDAuzQQAAADqEDBj9PLz8zVhwgTFxcXpuuuu07XXXlvnNrm5uV6vo7S09Lxed8aMGd4rJkB5Oianoc6nH863H30l4qef1GzzZjXftEnNN25Us61bFXbypCTJERam0k6ddGTwYB259FL9eOmlOtmyZdXGO3YYjxASqP0YDLp27eq3tur6vZeUlHTOOmb/rjS7fXcCsaYzuetHVMf+Ef5EX1qDL/vR5nA4HN5+0bfeekslJSWuMXpnuuuuu/T//t//qzZGr7CwUNHR0WrRooW2b9+uZ599VuPGjVN6enqNbeTk5CgrK8vbpSs3N1eDBg1q8PahcONbT2+Q3FBTp05t8Lbn249e4XBIO3dKK1dWPTZvrlreuLHUr59xpm7AAKl/f6lZM/PqDUAB0Y9Batq0aT5vw/kZrev33owZM/TAAw9Ikpy7HLN+V5rdvjuBWJM7Z/Yj3GP/CH+iL63hfPuxtkwUEGf0ks6Y8r1bt2762c9+pi+++KLWoAcEnPJyKT9f+vxz47FqVfWbkyckSDfdJKWnG4+0NCPsAQAAAF4WEEHvbDabTT440Qh4j8Nh3Ij8zFC3fr10+jJMSVK3btKNN1YFuy5dmBETAAAAfuHVoGe322W321VZWanKykpVVFQoPDxc4eHhOnnypCu8nTp1ShUVFYqMjJTNZtOaNWuUkpKimJgY7dixQx988IFGjRrlzdKA81Naapyt++ILafVq47F/f9Xy2Fjj8ssrrzQuwezXT4qPN69eAAAAhDSvBr3Fixdr4cKFru9XrFihESNGKCsrS/fee6+Ki4slSX/+858lSS+++KJat26tVatW6ZVXXtHJkycVHx+vm266SQMHDvRmaUD92e3Sli3GbJirVxvhbtMmqbKyap2uXaWxY6uC3SWXSOHh5tUMAAAAnMGrQS8rK6vGwYAvvfRSjdvdc8893iwDqD/nhClr1lQ98vOl48er1mnZUrruOuMs3RVXSH37SnFx5tUMAAAA1CEgx+gBPuFwSAUFRpBzPtaulc6456OioqRevaQ+fYxHv35S586MrQMAAEBQIejBmpxn6tatqwp169ZJhw9XrRMRIV18sXTrrVXB7uKLpchI8+oGgsi0adM8nk7e7FsImN2+O4FYEwAg+BH0EPxOnZK2bjVmvTz9uGrtWunYsap1IiKMcXQ332zc1iAtTbrsMuMMHgAAAGAxBD0Elx9/lL7+WtqwoeqxcaN04kTVOo0b6/iFF6pZRoZxGSahDgAAACGGoIeAZLPbjbN0mzZVD3Z79lRfsXlzY4KUXr2qHt27a93KlRo0aJAptQOhIjs7W0OHDlXv3r3rvU1paakkKTY21ldlBXT77qxdu1aSPHofAQCoC0EP5nI41OzoUbX+/nu1PnjQ+Pr992pVXCz96U9V69lsUqdOxni6nj2rHh06MFEKYJLExEQlJiZ6tE1MTIyPqgmO9t1JS0szuwQAgAUR9OAfpwNdq+JitTod5JyPqDMvu5R0vEkT7W3fXh2HDTPG1V16qfEIoCPwAKRZs2Zp5syZHm0zZswYH1UTHO27Q9ADAPgCQQ9eZbPbFffDD0o4dEitDh1S/KFDalVcrIRDh84JdCcaNVJxq1YqbtVK37dpo4OtW+v71q11LDZWstk0depUk34KAPVRVFSkdevWebTN/PnzfVRNcLTvjqfvIQAA9UHQQ4NElZUp/vBhxR86pPjDh5Vw6JASTv87vLKy2rpnBrri1q31/el/H23enMsuAQAAAB8g6KFGUZI6n350kdTt9KPX9OmKOX78nPWPNm2qguRkHUpIMB6tWulQQoJ+atqUQAdY0NChQ9W2bVtNnjy53tts2bJFktSjRw9flRXQ7bvjvPzVk/cRAIC6EPRCXLSkiyR1khHmuqgq2LV3s/4xSUebNdOujh11OD7eeCQk6HB8vE5w+wIgpKSlpSktLc2jgJKSkuLDigK/fXcmTZokiaAHAPAugl4IaCOpo4wwd/bjAjfrn5C0Q9K7krZL+u70128k7Zc09Ve/8n3RAAAAABqMoGcBzSUlywhzHWWcoet4xiPazTY/yQhzq05/3aGqQFcoqdLNNgAAAACCA0EvCLSUEeScjwvPerRws02ljMC2RtJOSbtOf3WGumJfFgwAAADAVAQ9k0VKaidjPFyH04/ks77WdPe4IklbJe2WVHD6qzPU7ZFU4buyAQAAAAQwgp4PhckYH9deUtLpr85A5/x6wen1znZK0j5J62SEtoLTX3effuyRVO7L4gEAAAAELYKeN61YofmqCnSJMs7YuVMiaa+k/NNf95z+6gx0+yXZfV0vAAAAAEsi6HnTgQMaJWOik72SPjn9tfD0V+e/98i4TQEAAAAA+AJBz5uGDlVzSUfNrgMAAABASHM3PAwN1aQJIQ8AAACA6Qh6AAAAAGAxBD0AAAAAsBjG6AEAGmTWrFmaOXOmR9uMGTPGR9UER/vupKWlmV0CAMCCCHoAgAYpKirSunXrPNpm/vz5PqomONp3x9P3EACA+uDSTQAAAACwGIIeAKBBhg4d6vGlm1u2bNGWLVt8VFHgt+/OzJkzPX4fAQCoC5duAgAaJC0tTWlpaZo8eXK9t0lJSfFhRYHfvjuTJk2SJI/eRwAA6uLVoLds2TLl5eVpz549Sk9P15QpUyRJp06d0gsvvKCdO3equLhYf/zjH3XxxRe7tnM4HJo/f74++eQTSVJmZqbGjBkjm83mzfIAAF60ZMkSLVmyxKNt3njjDR9VExztu+MMegAAeJNXg15cXJyGDx+uDRs2qKKiotqybt266YYbbtBzzz13znYfffSR1qxZo+nTp8tms+nxxx9XmzZtNGTIEG+WBwDwonXr1mn27NkebTNu3DgfVRMc7bvj6XsIAEB9eHWMXr9+/dSnTx/FxsZWez4iIkI33nijunfvrrCwc5tcvny5hg4dqvj4eLVs2VJDhw5Vbm6uN0sDAAAAgJAREJOx7N27V8nJya7vk5OTVVhYaGJFAIC6pKamKjs726Nt5s6dq7lz5/qoosBv353s7GyP30cAAOoSEJOxlJeXKzo62vV9dHS0ysvL5XA4ah2n54uzfqWlpef1ujNmzPBeMQEqMTHRL+2cTz+cbz8iMNCPDde1a1eftzFs2DANGzZM3bp1q3W9pKQk1+/GsWPHSpK+//57n9fnjtntu3P//fdLUp3vo9nO7Ee4x/4R/kRfWoMv+zEggl5UVJTKyspc35eVlSkqKqrOyVgGDRrk9Vpyc3PP63UzMzO9V0yAevTRR/3SzqhRoxq87fn2IwID/dhw06ZN81tbDzzwQK3LZ8yY4VrHGWrq2sZXzG7fnUCsyZ0z+xHusX+EP9GX1nC+/ZiTk1PjsoC4dLN9+/YqKChwfV9QUKCkpCQTKwIAAACA4OXVoGe321VRUaHKykpVVlaqoqJCdrtdknTy5EnXTJynTp1SRUWFHA6HJCkjI0NLly5VSUmJSkpKtHTpUo5QAAAAAEADefXSzcWLF2vhwoWu71esWKERI0YoKytL9957r4qLiyVJf/7znyVJL774olq3bq3Bgwfr4MGDrktCrr76ag0ePNibpQEAAABAyPBq0MvKylJWVpbbZS+99FKN29lsNt1+++26/fbbvVkOAAAAAISkgBijBwAAAADwHoIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxXj19goAgNCxZMkSLVmyxKNt3njjDR9VExztuzNp0iSzSwAAWBBBDwDQIOvWrdPs2bM92mbcuHE+qiY42nfH0/cQAID64NJNAAAAALAYgh4AoEFSU1OVnZ3t0TZz587V3LlzfVRR4LfvTnZ2tsfvIwAAdSHoAQAaZNiwYZo1a5ZH24wdO1Zjx471UUWB3747s2bN8vh9BACgLozRAwA0SH5+vvLz8z3aZuvWrT6qJjjad4eQBwDwBYIeAKBBli5dqkcffdSjbXr06OGbYoKkfXcmT55sdgkAAAvi0k0AAAAAsBiCHgCgQdq2bavU1FSPthk9erRGjx7to4oCv313UlNTPX4fAQCoC0EPANAgkyZN8niM3rx58zRv3jwfVRT47bvTkLGOAADUhaAHAAAAABZD0AMAAAAAiyHoAQAAAIDFEPQAAAAAwGIIegAAAABgMQQ9AAAAALAYgh4AAAAAWAxBDwAAAAAshqAHAAAAABZD0AMAAAAAiyHoAQAAAIDFRJhdAAAgOOXn5ys/P9+jbbZu3eqjaoKjfXdmzZpldgkAAAvyatBbtmyZ8vLytGfPHqWnp2vKlCmuZRs3btScOXN06NAhde7cWVOmTFGrVq0kSTk5OXrnnXcUEVFVztNPP602bdp4szwAgBctXbpUjz76qEfb9OjRwzfFBEn77kyePNnsEgAAFuTVoBcXF6fhw4drw4YNqqiocD1/9OhRPfPMM5o8ebLS0tK0YMECPf/883riiSdc6/Tv31933323N8sBAAAAgJDk1TF6/fr1U58+fRQbG1vt+S+//FLt27dX//791ahRI40YMUIFBQXat2+fN5sHAPhR27ZtlZqa6tE2o0eP1ujRo31UUeC3705qaqrH7yMAAHXxyxi9wsJCJScnu76PiopSmzZtVFhYqHbt2kkyxnpMmDBBcXFxuu6663TttdfW+bq5ubler7W0tPS8XnfGjBneKyZAJSYm+qWd8+mH8+1HBAb6seG6du3q8zZGjRqlSZMm6Zlnnql1vaSkJNfvxvvvv1+STAs2ZrfvjrOmut5Hs53Zj3CP/SP8ib60Bl/2o1+CXnl5uZo1a1btuejoaJWVlUkyLtu85ppr1KJFC23fvl3PPvusYmJilJ6eXuvrDho0yOu15ubmntfrZmZmeq+YAOXpmJyGGjVqVIO3Pd9+RGCgHxtu2rRpPm9j//79Kioq0gMPPFDrejNmzHCt86tf/UqS6tzGV8xu3x3n//FAqsmdM/sR7rF/hD/Rl9Zwvv2Yk5NT4zK/3F4hKipKx48fr/ZcWVmZmjRpIsk4StiyZUuFhYWpW7du+tnPfqYvvvjCH6UBABpo9uzZ6t27t0fbxMbGnnN5vz+Z3b47vXv39vh9BACgLn4JeklJSSooKHB9X15eroMHDyopKcnt+jabTQ6Hwx+lAQAAAIDleDXo2e12VVRUqLKyUpWVlaqoqJDdblffvn21d+9erV69WhUVFVq0aJE6dOjgGp+3Zs0alZaWyuFw6LvvvtMHH3ygPn36eLM0AAAAAAgZXh2jt3jxYi1cuND1/YoVKzRixAhlZWXpvvvu0+uvv64XX3xRXbp00T333ONab9WqVXrllVd08uRJxcfH66abbtLAgQO9WRoAwMumTp2qqVOnymaz1Xsb59UanmzjTWa3704g1gQACH5eDXpZWVnKyspyu+yyyy7Tc88953bZmaEPAAAAAHB+/DJGDwAAAADgPwQ9AAAAALAYgh4AAAAAWAxBDwAAAAAshqAHAAAAABZD0AMAAAAAiyHoAQAAAIDFEPQAAAAAwGIIegAAAABgMQQ9AAAAALCYCLMLAAAEp/3796uoqMijbY4dO+ajaoKjfXfy8/PNLgEAYEGc0QMANMjs2bPVu3dvj7aJjY1VbGysjyoK/Pbd6d27t8fvIwAAdSHoAQAAAIDFEPQAAAAAwGIIegCABpk6daocDodH2zgcDo+38Saz23cnEGsCAAQ/gh4AAAAAWAxBDwDQINOmTZPNZvNoG5vN5vE23mR2++4EYk0AgOBH0POyxo0bm12CTzVq1MhS7QBWxWcV8C8+cwACDffR87Ly8nKzSwAAPfTQQ35pZ+rUqXWuk5ubyxg0C6AfASC4cEYPAAAAACyGoAcAAAAAFkPQAwAAAACLIegBAAAAgMUQ9AAAAADAYgh6AAAAAGAxBD0AAAAAsBiCHgAAAABYjFdvmL5s2TLl5eVpz549Sk9P15QpU1zLNm7cqDlz5ujQoUPq3LmzpkyZolatWkmSHA6H5s+fr08++USSlJmZqTFjxshms3mzPAAAAAAICV49oxcXF6fhw4crMzOz2vNHjx7VM888o5EjR+q1115Tp06d9Pzzz7uWf/TRR1qzZo2mT5+up59+WuvWrdNHH33kzdIAAAAAIGR4Nej169dPffr0UWxsbLXnv/zyS7Vv3179+/dXo0aNNGLECBUUFGjfvn2SpOXLl2vo0KGKj49Xy5YtNXToUOXm5nqzNAAAAAAIGV69dLMmhYWFSk5Odn0fFRWlNm3aqLCwUO3atdPevXurLU9OTlZhYWGdr+uLMFhaWkrItAD60Rrox8A2aNAgSXX/Lj6zH+u7ja+Y3b47gViTO3werYF+tA760hp82Y9+CXrl5eVq1qxZteeio6NVVlbmWh4dHV1tWXl5uRwOR63j9Jw7R2/Kzc31yevCv+hHa6Afg0NdfeSuH83uV7PbdycQazoTn0droB+tg760hvPtx5ycnBqX+SXoRUVF6fjx49WeKysrU5MmTVzLnaHPuSwqKqrOyVhq+8HOh69eF/5FP1oD/WgN9KM10I/WQD9aB31pDb7qR78EvaSkJOXl5bm+Ly8v18GDB5WUlCRJat++vQoKCtS5c2dJUkFBgWtZTbKysnxXMAAAAAAEMa9OxmK321VRUaHKykpVVlaqoqJCdrtdffv21d69e7V69WpVVFRo0aJF6tChg9q1aydJysjI0NKlS1VSUqKSkhItXbqUU9EAAAAA0EA2h8Ph8NaL5eTkaOHChdWeGzFihLKysvT111/r9ddfV3Fxsbp06aK77rpLrVu3lmTcR2/evHmu++hdffXV3EcPAAAAABrIq0EPAAAAAGA+r166CQAAAAAwH0EPAAAAACyGoAcAAAAAFuOX2yuY6cUXX9SmTZt04sQJtWjRQsOGDdM111xzznqzZ8/WihUrXN/b7XZFRETon//8pyRp2rRp2r59u8LCjGzcsmVLPf/88375GVClqKhIDz74oPr166e7777b7Trvvfee3n33XVVUVKhfv36aOHGiIiMjJUmlpaV65ZVX9PXXX6tp06YaNWqUrrrqKn/+CFDd/ZiXl6cPPvhABw4cUJMmTZSenq5Ro0YpPDxcEp/HQFFXP+bm5uqVV15Ro0aNXM/97ne/08UXXyyJz2Mgqasv2UcGNk/ef/aRgau+/cg+MrDV9/33xz7S8kHv5ptv1q9+9StFRkZq3759mjZtmjp27KiLLrqo2nrZ2dnKzs52ff/3v//9nFk/x48f7zYkwn/mzJmjTp061bj8q6++0rvvvqtHHnlEcXFxeuaZZ5STk6PRo0dLkl577TVFRERo1qxZ2r17t5588kklJyerffv2/voRoLr78cSJE7rjjjvUpUsXHT16VNOnT9eSJUt08803u9bh82i+uvpRkrp27arHHnvM7TI+j4Gjrr5kHxn46vP+s48MfPXpR/aRga++77+v95GWv3Szffv2riNVNptNNptNBw8erHWb8vJyrV69WgMHDvRHiainlStXKjo6WpdcckmN6yxfvlyZmZlq3769YmNjNXz4cOXm5kqq6teRI0cqKipK3bt3V+/evasdpYbv1acfr732WqWkpCgiIkItW7bUVVddpW+++caPVaIu9enH2vB5DBye9iX7yODFPtIa2Edan7c+j5YPepL06quvauzYsbr33nvVokUL9erVq9b1V69erWbNmiklJaXa82+++aYmTpyoRx55RJs3b/ZlyTjL8ePHlZOTo7Fjx9a63t69e5WcnOz6Pjk5WUeOHNFPP/2koqIihYWFKTExsdryvXv3+qxuVFfffjzb1q1bzzmCxefRPJ704+7duzVx4kT99re/1aJFi2S32yWJz2OAaMhnkn1kYKrP+88+MvA15HPEPjLw1Pf99/U+0vKXbkrSxIkTNWHCBH377bfavHmzIiJq/7GXL1+ujIyMapeljB49WklJSYqIiNCqVas0ffp0PfXUU7rgggt8XT4kLViwQJmZmUpISKh1vRMnTig6Otr1vfPfZWVlKi8vr7bMuby8vNz7BcOt+vbjmT799FPt3LlTkydPdj3H59Fc9e3HlJQUzZgxQwkJCSosLNTzzz+vsLAw3XLLLXweA0RDPpPsIwNPfd9/9pGBrSGfI/aRgae+778/9pEhcUZPksLCwtS9e3eVlJToww8/rHG9Q4cOacuWLcrIyKj2fJcuXdSkSRNFRkZq4MCB6tatm9avX+/rsiHjaMfGjRt144031rlu48aNVVZW5vre+e8mTZooKiqq2jLJOJodFRXl3YLhlif96LRmzRq9+eabeuihh9SsWTPX83wezeNJP7Zp00atW7dWWFiYOnTooFtvvVWrV6+WJD6PAaAhn0n2kYGpvu8/+8jA5unniH1kYKrv+++PfWRInNE7k91ur3WM3vLly9W1a1e1adOm1tc5exA6fGfz5s0qLi7WlClTJBnXLVdWVqqwsFBPPfVUtXXbt2+vgoIC9e/fX5JUUFCg5s2bq2nTpoqMjJTdbldRUZHatm3rWs4gc//wpB8lY9KAmTNn6ve//706dOhQ62vzefQfT/vxTDabTQ6HQ5LUtm1bPo8ma0hfso8MDjW9/+wjg0ttnyP2kcGjvu+/L/aRlj6jd+TIEa1cudK18/rqq6+0atUq17Sl7ixfvlyDBg2q9tyxY8f01VdfqaKiQna7XStWrNDWrVvVs2dPH/8EkKTBgwfrhRde0PTp0zV9+nQNGTJEqampevjhh89ZNyMjQ5988okKCwtVWlqqxYsXu/ozKipKffv21YIFC1ReXq5t27Zp7dq1GjBggJ9/otDkST9u2rRJL774ou677z517ty52jI+j+bypB/Xr1+vH3/8UZK0b98+LVq0SL1795bE5zEQeNKXTuwjA48n7z/7yMDlST+yjwxcnrz//thHWvqMns1m04cffqhXX31VDodDCQkJuuOOO9SnTx8dOnRI9913n5599lnX2IRvv/1WJSUluuKKK6q9jt1u19tvv639+/e7BkY+8MAD1QZIwncaN26sxo0bThNotwAABpVJREFUu76PiopSZGSkmjVrdk4/Xn755fr5z3+uxx57TBUVFerbt6+ysrJc206cOFEvv/yyJk2apNjYWE2cOJGjlX7iST8uWrRIx48f15NPPulaPyUlRQ899BCfR5N50o+bNm3Syy+/rPLycjVv3lwDBgzQLbfc4tqWz6O5POlLiX1koKrt/WcfGTw86Uf2kYHLk370xz7S5nCeIwQAAAAAWIKlL90EAAAAgFBE0AMAAAAAiyHoAQAAAIDFEPQAAAAAwGIIegAAAABgMQQ9AAAAALAYS99HDwAAAEBoOnnypF577TVt3LhRpaWluuCCC3TbbbepV69edW772GOPafPmzZo/f77Cw8Ndz69cuVILFy7U4cOH1aJFC911111KSUmRJH3++edasGCBSkpKFB8fr1GjRqlPnz6SjBvdL1q0SLt27VJsbKxeeuklj36WnTt36p///Kd27dqlqKgo3Xzzzbrhhhtq3YagBwAAACCo5eTkSJKysrJcz9ntdsXHx2vq1KlKSEjQ+vXr9fzzz+vpp59W69ata3ytFStWyG63n/P8119/rfnz5+uee+5R586d9eOPP7qWlZSU6MUXX9SDDz6oyy+/XOvXr9dzzz2nl156Sc2bN1dUVJQyMzOVnp6uf//73x79bEePHtVf/vIXjRs3TldccYVOnTqlw4cP17kdl24CAAAAsJyoqChlZWWpdevWCgsLU1pamlq3bq1du3bVuM3x48e1aNEijRkz5pxlOTk5uvXWW9W1a1eFhYWpZcuWatmypSTp8OHDiomJUa9evWSz2ZSamqrGjRvr4MGDkqTOnTsrIyOjxoC5b98+Pf7445owYYJ++9vf6vPPP3cte++999SzZ08NGDBAkZGRatKkiZKSkur8+Ql6AAAAACzvxx9/VFFRUa0h6c0339SQIUPUokWLas9XVlZqx44dOnr0qH7zm9/orrvu0pw5c1RRUSFJ6tSpk9q1a6e1a9eqsrJSa9asUWRkpDp06FBnXeXl5XriiSeUnp6u2bNn6ze/+Y1ee+017d27V5K0fft2xcbG6pFHHlF2draeeuopHTp0qM7XJegBAAAAsLRTp07pxRdfVEZGhtq1a+d2nR07duibb77R9ddff86yH3/8UXa7XatXr9a0adP01FNPadeuXVq8eLEkKSwsTBkZGXrhhRc0ZswYvfDCC8rOzlZUVFSdta1bt06tWrVSZmamwsPDddFFF6lv37764osvJBmXhebl5emOO+7Q3/72N7Vu3Vp//etf63xdxugBAAAACDpPPfWUtm3bJsmYeEWS3n//fUlS9+7d9bvf/U6ScTbupZdeUkREhCZMmOD2tSorK/Xaa6/pzjvvrDb5ilOjRo0kSddff73i4uIkSUOHDtXixYt122236euvv9a8efM0depUdezYUTt37tTTTz+thx56SBdeeGGtP0dxcbG2b9+u8ePHu56z2+3KyMiQJEVGRqpPnz7q3LmzJGnEiBGaOHGijh8/rujo6Bpfl6AHAAAAIOg4g5zkfjIWSXI4HHrllVd05MgRPfTQQ4qIcB9/ysrKtHPnTj3//POSjOAnSXfddZfuvfdepaSkKD4+vsZaCgoKlJKSok6dOkkyxuR17txZGzdurDPoJSQkqEePHvq///s/t8uTk5Nls9lc3zv/7XA4an1dLt0EAAAAYEmvvvqq9u3bp9/97neus3LuREdH65VXXtH06dM1ffp0/f73v5ckPfnkk+rSpYskadCgQVq2bJmOHDmi0tJSvf/++0pNTZVkjNHbtm2bdu/eLUnatWuXtm3b5hqjV1lZqYqKCtntdjkcDlVUVOjUqVOSpNTUVBUVFWn58uU6deqUTp06pe+++06FhYWudtesWaPdu3fr1KlTWrRokbp3766YmJhaf3abo64oCAAAAAABzN0ZveLiYv2///f/FBkZqbCwqvNb2dnZGjBggA4dOqT77rtPzz77rBISEqq93vfff6+777672n30Tp06pX/84x9auXKlIiMj1b9/f40ZM8YVIJctW6b3339fR44cUbNmzXTttddq2LBhkqTNmzfrscceq9ZGjx49NHXqVEnS/v37NXfuXH333XdyOBxKTk7WuHHjXGcD//e//2nx4sWqqKhQt27d9Mtf/vKcms9G0AMAAAAAi+HSTQAAAACwGIIeAAAAAFgMQQ8AAAAALIagBwAAAAAWQ9ADAAAAAIsh6AEAAACAxRD0AAAAAMBiCHoAAAAAYDH/H6DZ8d0tycbuAAAAAElFTkSuQmCC\n" - }, - "metadata": {} - } - ], - "source": [ - "if_moon = False\n", - "if_markers = True\n", - "twilight = -18\n", - "moon_sep = 30\n", - "\n", - "sites = {'North': 'Roque de los Muchachos', 'South': 'Paranal'}\n", - "# initialise site coordinates\n", - "north_site_coords = EarthLocation.of_site(sites['North'])\n", - "south_site_coords = EarthLocation.of_site(sites['South'])\n", - "if site == 'North':\n", - " site_coords = north_site_coords\n", - "else:\n", - " site_coords = south_site_coords\n", - "\n", - "# initialise visibility\n", - "t_start = t_trigger\n", - "#duration = Time(2, format='jd') \n", - "duration = afterglow_duration\n", - "print('***', t_trigger, t_trigger.value + afterglow_duration.value)\n", - "total_points = 100\n", - "night_points = 10\n", - "\n", - "fig = plt.figure(figsize=(15,15))\n", - "ax1 = fig.add_subplot(211)\n", - "ax2 = fig.add_subplot(212)\n", - "\n", - "# ignore warnings\n", - "with warnings.catch_warnings():\n", - " warnings.filterwarnings('ignore')\n", - " print('\\nEvent full duration')\n", - " # initialise\n", - " visibility = Visibility()\n", - " # visibility points in JD and AltAz\n", - " visibility.visibility_points(t_start, duration, total_points)\n", - " visibility.visibility_altaz(source_radec, sites[site])\n", - " # find nights\n", - " nights_twilight = visibility.get_nighttime(0)\n", - " nights_astronomical = visibility.get_nighttime(-18)\n", - " if moon_sep == 0:\n", - " nights = visibility.get_nighttime(twilight=twilight)\n", - " else:\n", - " nights = visibility.get_nighttime_moonlight(twilight=twilight, moon_sep=moon_sep)\n", - " print('nights:', nights)\n", - " # plot source\n", - " #ax1.plot(visibility.vis_points.value, visibility.altaz.alt.value, 'bo', label='Target (CTA %s)' % site)\n", - " ax1.plot(visibility.vis_points.value, visibility.altaz.alt.value, 'b-', label='Target (CTA %s)' % site)\n", - " # plot min altitude\n", - " ax1.fill_between(visibility.vis_points.value, 0, min_altitude.value, color='k', zorder=1)\n", - " # plot sun\n", - " ax1.plot(visibility.vis_points.value, visibility.sun_altaz.alt.value, color='r', label='Sun')\n", - " # plot moon\n", - " if if_moon:\n", - " visibility.moon_position()\n", - " ax1.plot(visibility.vis_points.value, visibility.moon_altaz.alt, color='gray', ls='--', label='Moon')\n", - " # plot irf\n", - " ax1.fill_between(visibility.vis_points.value, thresholds[2], 90, color='yellow', alpha=0.2, label='IRF z20')\n", - " ax1.fill_between(visibility.vis_points.value, thresholds[1], thresholds[2], color='orange', alpha=0.2, label='IRF z40')\n", - " ax1.fill_between(visibility.vis_points.value, thresholds[0], thresholds[1], color='red', alpha=0.2, label='IRF z60') \n", - " # compute moon sky distance\n", - " moon_dist = visibility.moon_altaz.separation(visibility.altaz)\n", - " ax2.plot(visibility.vis_points.value, moon_dist.deg, color='r')\n", - " # clear\n", - " del visibility\n", - "\n", - " # within each night find IRFs\n", - " for i in range(len(nights['start'])):\n", - " print('Night', i+1, 'of', len(nights['start']))\n", - " # plot nights\n", - " ax1.fill_between([nights_twilight['start'][i], nights_twilight['stop'][i]], 0, 90, color='0.5', zorder=0)\n", - " ax2.fill_between([nights_twilight['start'][i], nights_twilight['stop'][i]], moon_dist.deg.min()-10, moon_dist.deg.max()+10, color='0.5', zorder=0)\n", - " if len(nights_astronomical['start']) > i:\n", - " ax1.fill_between([nights_astronomical['start'][i], nights_astronomical['stop'][i]], 0, 90, color='k', zorder=0)\n", - " ax2.fill_between([nights_astronomical['start'][i], nights_astronomical['stop'][i]], moon_dist.deg.min()-10, moon_dist.deg.max()+10, color='k', zorder=0)\n", - "\n", - " # initialise night visibility\n", - " t_start = Time(nights['start'][i], format='jd')\n", - " duration = Time(nights['stop'][i] - nights['start'][i], format='jd')\n", - " visibility = Visibility()\n", - " # visibility points in JD and AltAz\n", - " visibility.visibility_points(t_start, duration, total_points)\n", - " visibility.visibility_altaz(source_radec, sites[site])\n", - " # IRFs and relative time intervals\n", - " irfs = visibility.associate_irf_zenith_angle(thresholds, zenith_angles)\n", - " # plot irf intervals\n", - " if if_markers and irfs['start'][0] > 0.:\n", - " for j in range(len(irfs['zref'])):\n", - " ax1.axvline(irfs['start'][j], color='white', ls='-.', lw=2)\n", - " ax1.axvline(irfs['stop'][j], color='white', ls='-.', lw=2)\n", - " ax2.axvline(irfs['start'][j], color='white', ls='-.', lw=2)\n", - " ax2.axvline(irfs['stop'][j], color='white', ls='-.', lw=2)\n", - " # complete IRFs\n", - " print('IRFs', irfs['start'][0], irfs['stop'][-1], irfs['zref'])\n", - " del visibility\n", - "\n", - " # complete plot ax1\n", - " ax1.set_ylim(0, 90)\n", - " ax1.set_xlabel('time (JD)')\n", - " ax1.set_ylabel('altitude (deg)')\n", - " ax1.title.set_text(f'{grb} - {site}')\n", - " ax1.legend()\n", - " suffix = site\n", - "\n", - " # complete plot ax2\n", - " if if_moon:\n", - " ax2.set_ylim([moon_dist.deg.min()-10, moon_dist.deg.max()+10])\n", - " ax2.set_xlabel('time (JD)')\n", - " ax2.set_ylabel('moon sky distance (deg)')\n", - " ax2.set_title(f'{grb} - {site}')\n", - " ax2.legend()\n", - "\n", - " if not if_moon:\n", - " suffix += '_noMoon'\n", - " if if_markers:\n", - " suffix += '_mark'\n", - " #plt.savefig(f'{grb}_{suffix}.png')\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ] -} \ No newline at end of file diff --git a/rtavis/rtavis/readVisTable.py b/rtavis/rtavis/readVisTable.py deleted file mode 100644 index 7784fa4768a1f962b8b277b5d32d95ed14611e73..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/readVisTable.py +++ /dev/null @@ -1,50 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import numpy as np -import argparse - -# parse command line inputs -parser = argparse.ArgumentParser(description='This script is a simple example on how to read a NPY binary file.') -parser.add_argument('-f', '--file', required=True, type=str, help='configuration yaml file') -# configuration file -filename = parser.parse_args().file - -data = np.load(filename, allow_pickle=True, encoding='latin1', fix_imports=True).flat[0] - -print(f'These are the parent keywords: \n\t{sorted(data.keys())}') -events = list(data.keys()) -print(f'Inside each of the parent keyword you will find the following keys: \n\t{sorted(data[events[0]].keys())}') -sites = list(data[events[0]].keys()) -print(f'Each key is a dictionary: \n\t{data[events[0]][sites[0]].keys()}') - -print(f'In example you can access data with a nested cycle:') -for n, event in enumerate(events): - for site in sites: - print(f'Template {event} - Site {site} - Visibility Intervals:') - if type(data[event][site]) == float: - print(f'\tThis contains NaNs ---> the source is not observable due to daylight or moon.') - else: - print(f"\tThis contains the start and stop of each night.") - for night in data[event][site]: - print(f"\t{night}") - print(f"\tstart: {data[event][site][night]['start']}") - print(f"\tstop: {data[event][site][night]['stop']}") - print(f"\n\tEach night contains the irfs intervals under the 'irfs' keyword.") - if type(data[event][site][night]['irfs']['zref']) == float: - print(f'\tThis contains NaNs ---> the source is not observable at the site.') - else: - for i in range(len(data[event][site][night]['irfs']['zref'])): - print(f'\t\tstart: {data[event][site][night]["irfs"]["start"][i]}') - print(f'\t\tstop: {data[event][site][night]["irfs"]["stop"][i]}') - print(f'\t\tzref: {data[event][site][night]["irfs"]["zref"][i]}') - - if n > 2: - print("Let's stop after 3 templates.") - break \ No newline at end of file diff --git a/rtavis/rtavis/runCatVisibility.py b/rtavis/rtavis/runCatVisibility.py deleted file mode 100644 index 920d139fc399dec0ee2d4b1972b8cfe4b4ef2ff0..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/runCatVisibility.py +++ /dev/null @@ -1,169 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2021 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import argparse -import warnings -import yaml -import os -import logging -from os.path import join, isfile, isdir -import numpy as np -import astropy.units as u -from astropy.coordinates import SkyCoord -from astropy.time import Time -from astropy.io import fits -from rtavis.utils.visibility import Visibility -from astropy.coordinates import solar_system_ephemeris - -# parse command line inputs -parser = argparse.ArgumentParser(description='This software runs the CTA visibility for a given configuration pass via YAML configuration file. The output is saved as NPY binary file.') -parser.add_argument('-f', '--config', required=True, type=str, help='configuration yaml file') -# configuration file -cf = parser.parse_args().config -# load params configuration from cf -with open(cf) as f: - cfg = yaml.load(f, Loader=yaml.FullLoader) - -# set ephemeris -if cfg['ephemeris'] == 'jpl': - solar_system_ephemeris.set('jpl') -elif cfg['ephemeris'] not in ('default', 'jpl'): - raise ValueError("selected ephemeris not implemented, please use either default or jpl.") - -# ----------------------------------------------------------------------------- catalog -if '$' in cfg['path']['catalog']: - catalog = os.path.expandvars(cfg['path']['catalog']) -else: - catalog = cfg['path']['catalog'] -if cfg['path']['output'] == None: - output = 'visibility_output.npy' -elif '$' in cfg['path']['output']: - output = os.path.expandvars(cfg['path']['output']) -else: - output = cfg['path']['output'] - -if not isdir(catalog): - raise ValueError('Please correctly select a catalog folder') - -if cfg['path']['filename'] == None: - runids = [f for f in os.listdir(catalog) if '.fits' in f and isfile(join(catalog, f))] - if len(runids) == 0: - raise ValueError('No valid FITS file found') -elif type(cfg['path']['filename']) == str: - if not isfile(join(catalog, cfg['path']['filename'])): - raise ValueError(f'Specified template does not exist in catalog') - runids = [cfg['path']['filename']] -else: - runids = cfg['path']['filename'] - for runid in runids: - if not isfile(join(catalog, runid)): - raise ValueError(f'Specified template {runid} does not exist in catalog') -runids = sorted(runids) - -# -------------------------------------------------------------------------log the configuration - -logname = output.replace('.npy','.log') -logging.basicConfig(filename=logname, filemode='w+', level=logging.DEBUG, format='%(asctime)s %(message)s') -logging.info('#################') -logging.info('# CONFIGURATION #') -logging.info(f'#################\n\n{yaml.dump(cfg)}') -logging.info('##############') -logging.info('# VISIBILITY #') -logging.info('##############') - -# ----------------------------------------------------------------------------- loop runid - -data = {} -# ignore warnings -with warnings.catch_warnings(): - warnings.filterwarnings('ignore') - for runid in runids: - logging.info('------------------------------------------------------------------ #') - print(f'Processing {runid}') - logging.info(f'Processing {runid}') - logging.info('----------') - data[f'{runid.replace(".fits", "")}'] = {} - # load template - with fits.open(join(catalog, runid)) as hdul: - hdr = hdul[0].header - # source coordinates - source_radec = SkyCoord(ra=hdr['RA'] * u.deg, dec=hdr['DEC'] * u.deg, frame='icrs') - # source trigger time and afterglow duration - try: - trigger = Time(hdr['GRBJD'] * u.day, format='jd') - except KeyError: - raise ValueError('This catalog cannot be processed. The headers do not contain a "GRBJD" trigger time keyword.') - - try: - times = np.array(hdul['TIMES (AFTERGLOW)'].data.tolist()) - except KeyError: - times = np.array(hdul['TIMES'].data.tolist()) - afterglow_duration = Time((times[-1] - times[1])[0] / 86400, format='jd') - - # --------------------------------------------------------------------------- loop sites - for site in cfg['sites_list']: - logging.info(f'{site} site') - # initialise - visibility = Visibility() - # set ephemeris - if cfg['ephemeris'] == 'jpl': - visibility.set_jpl_ephemeris() - # visibility points in JD and AltAz - visibility.visibility_points(trigger, afterglow_duration, cfg['total_points']) - visibility.visibility_altaz(source_radec, cfg['sites_list'][site]) - # find nights account for Moon (use default Moon thresholds) - if cfg['setup']['moon_sep'] == None: - nights = visibility.get_nighttime(twilight=cfg['setup']['twilight']) - else: - nights = visibility.get_nighttime_moonlight(twilight=cfg['setup']['twilight'], moon_sep=cfg['setup']['moon_sep'], fov_rad=cfg['setup']['fov_rad'], moonpha=0, max_moonpha=cfg['setup']['moon_pha']) - #del visibility - # within each night find IRFs - #print(nights) - if nights['start'][0] < 0: - logging.info('................Not visible either to moonlight or daylight conditions') - #del nights, irfs, site_coords - - logging.info('Observability windows:') - data[f'{runid.replace(".fits", "")}'][f'{site}'] = {} - for i in range(len(nights['start'])): - #print('Night', i+1, 'of', len(nights['start'])) - logging.info(f'................Night {i+1} of {len(nights["start"])} in [{nights["start"][i]}, {nights["stop"][i]}]') - data[f'{runid.replace(".fits", "")}'][f'{site}'][f'night{i+1:02d}'] = {'start': nights["start"][i], 'stop': nights["stop"][i]} - #print(nights['start'][i], nights['stop'][i]) - t_start = Time(nights['start'][i], format='jd') - night_duration = Time(nights['stop'][i] - nights['start'][i], format='jd') - # initialise - visibility = Visibility() - # set ephemeris - if cfg['ephemeris'] == 'jpl': - visibility.set_jpl_ephemeris() - # visibility points in JD and AltAz - visibility.visibility_points(t_start, night_duration, cfg['window_points']) - visibility.visibility_altaz(source_radec, cfg['sites_list'][site]) - # IRFs and relative time intervals - irfs = visibility.associate_irf_zenith_angle(cfg['setup']['thresholds'], cfg['setup']['zenith_angles']) - if irfs['start'][0] < 0: - logging.info('................Not visible due to low altitude') - data[f'{runid.replace(".fits", "")}'][f'{site}'][f'night{i+1:02d}']['irfs'] = irfs - else: - logging.info('................Altitude intervals:') - for n in range(len(irfs['zref'])): - logging.info(f'................Zenith Ref. {irfs["zref"][n]} in [{irfs["start"][n]}, {irfs["stop"][n]}]') - data[f'{runid.replace(".fits", "")}'][f'{site}'][f'night{i+1:02d}']['irfs'] = irfs - #del visibility - #print(irfs) - #print(irfs['start'][0], irfs['stop'][-1]) - #del nights, irfs, site_coords, night_duration - #del afterglow_duration -np.save(output, data) - - -print("\n\nExit\n\n") - -#os.system(f"cat {logname}") \ No newline at end of file diff --git a/rtavis/rtavis/utils/__init__.py b/rtavis/rtavis/utils/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/rtavis/rtavis/utils/functions.py b/rtavis/rtavis/utils/functions.py deleted file mode 100644 index d1fd210d0dd0a62c58696b80cb860f7cacd9f586..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/utils/functions.py +++ /dev/null @@ -1,80 +0,0 @@ -import os -from os.path import join, isfile -import glob - -def read_input_file(catalog_path,file_name,visibility_cat): - ''' - Function that reads paths from configuration file and choses how to deal with files - Output values are - - path to the directory containing XML files (or their directories in case of multiple sources) - - path to the visibility catalog, *.npy, generated by runCatVisibility - - the list of xml files - ''' - - if '$' in catalog_path: - catalog = os.path.expandvars(catalog_path) - else: - catalog = catalog_path - - if visibility_cat == None: - output = 'visibility_output.npy' - elif '$' in visibility_cat: - output = os.path.expandvars(visibility_cat) - else: - output = visibility_cat - - if file_name == None: - runids = glob.glob(catalog_path + '/**/*.xml', recursive=True) - if len(runids) == 0: - raise ValueError('No valid XML file found') - - elif type(file_name) == str: - if not isfile(join(catalog, file_name)): - raise ValueError(f'Specified template does not exist in catalog') - runids = [file_name] - else: - runids = file_name - for runid in runids: - if not isfile(join(catalog, runid)): - raise ValueError(f'Specified template {runid} does not exist in catalog') - runids = sorted(runids) - return (catalog,output,runids) - -# ---------------------------------------------------------------------------------------------------- -# --------------function to append new line to txt file -def append_new_line(file_name, text_to_append): - """Append given text as a new line at the end of file""" - # Open the file in append & read mode ('a+') - with open(file_name, "a+") as file_object: - # Move read cursor to the start of file. - file_object.seek(0) - # If file is not empty then append '\n' - data = file_object.read(100) - if len(data) > 0: - file_object.write("\n") - # Append text at the end of file - file_object.write(text_to_append) - -# ------------------------------------------------------------------------------------------------- -def irf_selection(site,z,delta_t): - if delta_t < 94.9 * 60: #times are converted in SECONDS - irf_duration = '0.5h' - elif 94.9 * 60 < delta_t < 15.8 * 60 * 60: - irf_duration = '5h' - elif delta_t > 15.8 * 60 * 60: - irf_duration = '50h' - - if z == 60: - energy = 0.110 #energies are in GeV - #sim_e_max = 5.6234 - - elif z == 40: - energy = 0.04 - #sim_e_max = 5.6234 - else: - energy = 0.03 - #sim_e_max = 10. - - - name = (f'{site}_z{z}_{irf_duration}') - return [name , energy] \ No newline at end of file diff --git a/rtavis/rtavis/utils/visibility.py b/rtavis/rtavis/utils/visibility.py deleted file mode 100644 index 3abae6bbada50531fac932b58de6b228a739dd43..0000000000000000000000000000000000000000 --- a/rtavis/rtavis/utils/visibility.py +++ /dev/null @@ -1,380 +0,0 @@ -# ******************************************************************************* -# Copyright (C) 2020 INAF -# -# This software is distributed under the terms of the BSD-3-Clause license -# -# Authors: -# Ambra Di Piano -# ******************************************************************************* - -import logging -import astropy.units as u -import numpy as np -from scipy.interpolate import interp1d -from astropy.coordinates import AltAz, EarthLocation, get_sun, get_moon, SkyCoord -from astropy.coordinates import solar_system_ephemeris - -def complete_irf_name(irfs, site, exposure, azimuth=None): - """ - Given a list of IRFs zenith angles, the chosen site, exposure and NBS, it returns the completed name of the IRFs - :param irfs: str (list of str) - :param site: (str) - :param exposure: <100s|0.5h|5h|50h> (str) - :param azimuth: (str or None) - :return: list of completed IRF names - """ - new_irfs = [] - for irf in irfs: - if azimuth is None: - new_irfs.append(site + '_z' + str(irf) + '_' + exposure) - else: - new_irfs.append(site + '_z' + str(irf) + '_' + str(azimuth) + '_' + exposure) - return new_irfs - - -class Visibility: - """ - This class contains methods that compute the source visibility and extracts information on which IRF to use per time interval. This class heavily relies on astropy. - """ - - def __init__(self): - pass - - def set_jpl_ephemeris(self): - solar_system_ephemeris.set('jpl') - return - - def visibility_points(self, trigger, duration, num_points=10, unit='jd'): - """ - It creates a time grid. - :param trigger: start time or trigger time (astropy Time object) - :param duration: duration of the event or the visibility window (astropy Time object) - :param num_points: number of time grid points (int) - :param unit: (str) - :return: - """ - self.trigger = trigger - if unit.lower() != 'jd': - raise Warning('Time formats other than JD are not implemented yet') - self.vis_points = np.linspace(0, duration.value, num_points) * u.d - self.vis_points += trigger - return self - - def return_to_trigger_frame(self, unit='s'): - """ - Converts the time grid back to the trigger (or start time) frame. - :param unit: (str) - :return: - """ - if unit.lower() != 's': - raise Warning('Time formats other than seconds are not implemented yet') - self.vis_points -= self.trigger - self.vis_points = self.vis_points * u.s - return self - - def visibility_altaz(self, source_radec, site, hardcoded=True): - """ - To each time grid point associates AltAz coordinates. - :param source_radec: source coordinates (astropy SkyCoord object) - :param site: (str) - :return: - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hardcoded: - site_coords = EarthLocation.of_site(site) - else: - if site.lower() in ('north', 'roque de los muchachos'): - #site_coords = EarthLocation.from_geodetic('342.1184', '28.7606', 2326. * u.meter) - site_coords = EarthLocation.from_geocentric(5327285.09211954, -1718777.11250295, 3051786.7327476, unit="m") - - elif site.lower() in ('south', 'paranal'): - #site_coords = EarthLocation.from_geodetic('289.5972', '-24.6253', 2635. * u.meter) - site_coords = EarthLocation.from_geocentric(1946635.7979987, -5467633.94561753, -2642498.5212285, unit="m") - else: - raise Warning(f"{site} is not a valid site choice") - self.altaz = source_radec.transform_to(AltAz(obstime=self.vis_points, location=site_coords)) - return self - - - def sun_position(self): - """ - Finds the position of the Sun for the observation site, given a time grid. - :return: - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - sun = get_sun(self.vis_points) - self.sun_altaz = sun.transform_to(self.altaz) - return self - - def moon_position(self): - """ - [WIP] Finds the position of the Moon at the observation site, given a time grid. - :return: - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - moon = get_moon(self.vis_points) - self.moon_altaz = moon.transform_to(self.altaz) - return self - - def get_nighttime(self, twilight=-18, digits=8): - """Given a twilight altitute threshold for the Sun, it returns twilight and dawn time for each night covering the event duration. - :param twilight: <0|-6|-12|-18> civil, naval or astronomical twilight or night thresholds (int). Default -18. - :return: dictionary containing 'twilight' and 'dawn' time of the Sun for each nighttime window of the event - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - self.sun_position() - windows = {'start': [], 'stop': []} - current = None - for idx, t in enumerate(self.vis_points.value): - previous = current - # night condition - if self.sun_altaz[idx].alt.value < twilight: - current = True - else: - current = False - - if idx == 0 and current is True: - windows['start'].append(self.vis_points[idx].value) - continue - elif idx == len(self.vis_points.value) - 1 and current is True: - windows['stop'].append(self.vis_points[idx].value) - break - elif previous != current: - x = [self.sun_altaz[idx - 1].alt.value, self.sun_altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - f = interp1d(np.array(x), np.array(y)) - if previous is False and current is True: - windows['start'].append(f(twilight)) - elif previous is True and current is False: - windows['stop'].append(f(twilight)) - - if len(windows['start']) != 0: - windows['stop'] = np.concatenate(np.around(windows['stop'], digits), axis=None) - windows['start'] = np.concatenate(np.around(windows['start'], digits), axis=None) - else: - windows['start'] = np.array([-9.0]) - windows['stop'] = np.array([-9.0]) - return windows - - def get_nighttime_moon_veto(self, twilight=-18, moon_alt_max=-0.5, digits=8): - """Given a twilight altitute threshold for the Sun, it returns twilight and dawn time for each night covering the event duration with a veto on the Moon presence. - :param twilight: <0|-6|-12|-18|integer> civil, naval or astronomical twilight or night thresholds (int). Default -18 deg (integer). - :return: dictionary containing 'start' and 'stop' time of the Sun for each nighttime window of the event - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - self.sun_position() - self.moon_position() - windows = {'start': [], 'stop': []} - current = None - for idx, t in enumerate(self.vis_points.value): - previous = current - # visibility conditions - sun_cond = np.array(self.sun_altaz.alt.value < twilight) - moon_cond = np.array(self.moon_altaz.alt.value < moon_alt_max) - if sun_cond[idx] and moon_cond[idx]: - current = True - else: - current = False - if idx == 0 and current is True: - windows['start'].append(self.vis_points[idx].value) - continue - elif idx == len(self.vis_points.value) - 1 and current is True: - windows['stop'].append(self.vis_points[idx].value) - break - elif previous != current and previous != None: - use = None - # assign x and y arrays - if sun_cond[idx] != sun_cond[idx - 1]: - x = [self.sun_altaz[idx - 1].alt.value, self.sun_altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - use = 'sun' - elif moon_cond[idx] != moon_cond[idx - 1]: - x = [self.moon_altaz[idx - 1].alt.deg, self.moon_altaz[idx].alt.deg] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - use = 'moon' - # interpolate - f = interp1d(np.array(x), np.array(y)) - if previous is False and current is True: - if use == 'sun': - windows['start'].append(f(twilight)) - elif use == 'moon': - windows['start'].append(f(moon_alt_max)) - elif previous is True and current is False: - if use == 'sun': - windows['stop'].append(f(twilight)) - elif use == 'moon': - windows['stop'].append(f(moon_alt_max)) - - - if len(windows['start']) != 0: - windows['stop'] = np.concatenate(np.around(windows['stop'], digits), axis=None) - windows['start'] = np.concatenate(np.around(windows['start'], digits), axis=None) - else: - windows['start'] = np.array([-9.0]) - windows['stop'] = np.array([-9.0]) - return windows - - - def get_nighttime_moonlight(self, twilight=-18, moon_sep=30, fov_rad=0, moonpha=0, max_moonpha=0.8, digits=8): - """Given a twilight altitute threshold for the Sun and a minimum separation from the Moon position, it returns twilight and dawn time for each night covering the event duration. - :param twilight: <0|-6|-12|-18|integer> civil, naval or astronomical twilight or night thresholds (int). Default -18 deg (integer). - :param moon_sep: minimum angular separation between moon and FoV border. Default 30 deg. - :param fov_rad: radius of FoV in degrees. Default 2.5. - :return: dictionary containing 'start' and 'stop' time of the Sun for each nighttime window of the event - """ - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - self.sun_position() - self.moon_position() - dist = self.altaz.separation(self.moon_altaz) - # add FoV radius to minimum separation from Moon - moon_sep += fov_rad - windows = {'start': [], 'stop': []} - current = None - for idx, t in enumerate(self.vis_points.value): - previous = current - # visibility conditions - sun_cond = np.array(self.sun_altaz.alt.value < twilight) - moon_cond = np.array(dist.deg > moon_sep) - if sun_cond[idx] and moon_cond[idx]: - current = True - else: - current = False - if idx == 0 and current is True: - windows['start'].append(self.vis_points[idx].value) - continue - elif idx == len(self.vis_points.value) - 1 and current is True: - windows['stop'].append(self.vis_points[idx].value) - break - elif previous != current and previous != None: - use = None - # assign x and y arrays - if sun_cond[idx] != sun_cond[idx - 1]: - x = [self.sun_altaz[idx - 1].alt.value, self.sun_altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - use = 'sun' - elif moon_cond[idx] != moon_cond[idx - 1]: - x = [dist[idx - 1].deg, dist[idx].deg] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - use = 'moon' - # interpolate - f = interp1d(np.array(x), np.array(y)) - if previous is False and current is True: - if use == 'sun': - windows['start'].append(f(twilight)) - elif use == 'moon': - windows['start'].append(f(moon_sep)) - elif previous is True and current is False: - if use == 'sun': - windows['stop'].append(f(twilight)) - elif use == 'moon': - windows['stop'].append(f(moon_sep)) - - - if len(windows['start']) != 0: - windows['stop'] = np.concatenate(np.around(windows['stop'], digits), axis=None) - windows['start'] = np.concatenate(np.around(windows['start'], digits), axis=None) - else: - windows['start'] = np.array([-9.0]) - windows['stop'] = np.array([-9.0]) - return windows - - def associate_irf_zenith_angle(self, thresholds=(10, 25, 55), zenith_angles=(60, 40, 20), digits=8): - """ - Given altitude lower bounds and the respective IRFs zenith angles, it returns which IRF zenith - angle to use when. - :param thresholds: (tuple or list of int) - :param zenith_angles: (tuple or list of int) - :return: dictionary containing 'start' time and 'stop' time associated to each 'zref' zenith angle - """ - if len(thresholds) != len(zenith_angles): - raise AttributeError('thresholds length must be equal to zenith_angles length') - if not hasattr(self, 'vis_points'): - raise AttributeError('Must invoke visibility_points() before using this method') - if not hasattr(self, 'altaz'): - raise AttributeError('Must invoke visibility_altaz() before using this method') - thresholds = sorted(thresholds) - zenith_angles = sorted(zenith_angles, reverse=True) - windows = {'start': [], 'stop': [], 'zref': []} - previous, current = None, None - for idx, alt in enumerate(self.altaz.alt.value): - for j, z in enumerate(thresholds): - if alt < z: - if j == 0: - current = None - else: - current = zenith_angles[j - 1] - break - elif j == len(thresholds) - 1: - current = zenith_angles[j] - - if idx == 0 and previous != current and current is not None: - windows['start'].append(self.vis_points[idx].value) - windows['zref'].append(current) - elif idx == len(self.altaz) - 1 and current is not None: - windows['stop'].append(self.vis_points[idx].value) - elif previous != current and previous is None: - x = [self.altaz[idx - 1].alt.value, self.altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - f = interp1d(x, y) - windows['start'].append(f(thresholds[0])) - windows['zref'].append(current) - elif previous != current and current is None: - x = [self.altaz[idx - 1].alt.value, self.altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - f = interp1d(x, y) - windows['stop'].append(f(thresholds[0])) - elif previous != current and (current and previous) is not None: - x = [self.altaz[idx - 1].alt.value, self.altaz[idx].alt.value] - y = [self.vis_points[idx - 1].value, self.vis_points[idx].value] - f = interp1d(x, y) - if previous > current: - windows['stop'].append(f(thresholds[zenith_angles.index(current)])) - windows['start'].append(f(thresholds[zenith_angles.index(current)])) - else: - windows['stop'].append(f(thresholds[zenith_angles.index(previous)])) - windows['start'].append(f(thresholds[zenith_angles.index(previous)])) - windows['zref'].append(current) - previous = current - if len(windows['zref']) != 0: - windows['start'] = np.concatenate(np.around(windows['start'], digits), axis=None) - windows['stop'] = np.concatenate(np.around(windows['stop'], digits), axis=None) - windows['zref'] = np.concatenate(np.around(windows['zref'], digits), axis=None) - else: - windows['start'] = np.array([-9.0]) - windows['stop'] = np.array([-9.0]) - windows['zref'] = np.array([-9.0]) - return windows - - def associate_irf_one_night(self, source_radec, start_time, duration, site, num_points, - thresholds=(10, 25, 55), zenith_angles=(20, 40, 60)): - """ - Shortcut method, it returns the IRF zenith angle to use when, given the source position, site, start time and duration of the event, number of time grid points and altitude thresholds with associate zenith angles. - :param source_radec: source coordinates (astropy SkyCoord object) - :param start_time: starting time of the night or trigger (astropy Time object) - :param duration: duration of the event or night (astropy Time object) - :param site: (str) - :param num_points: time grid points (int) - :param thresholds: altitude lower bounds for the IRFs (list of int) - :param zenith_angles: associated zenith angles (list of int) - :return irfs: dictionary containing 'start' time and 'stop' time associated to each 'zref' zenith angle - """ - self.visibility_points(start_time, duration, num_points) - self.visibility_altaz(source_radec, site) - irfs = self.associate_irf_zenith_angle(thresholds, zenith_angles) - return irfs \ No newline at end of file diff --git a/setup.py b/setup.py index 1ac545fcf184aac14c9c526b80f49e1bde085250..d40ff4c9e529ab3a4be69881853bbb04b232b6e4 100644 --- a/setup.py +++ b/setup.py @@ -9,9 +9,9 @@ from setuptools import setup, find_packages setup( - name='rtavis', + name='astrort', author='Ambra Di Piano ', - package_dir={'visibility': 'rtavis/rtavis', 'mocking': 'rtamock/rtamock', 'analysis': 'rtasci/rtasci'}, + package_dir={'astrort': 'astrort'}, packages=find_packages(), include_package_data=True, license='BSD-3-Clause',