From 50fe69f3f34f50389fe55560754a2f9055ce7030 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Mon, 11 Sep 2023 17:13:26 +0200 Subject: [PATCH 01/30] remove one layer of packaging and added rtadeep --- {rtasci/rtasci => rtadeep}/__init__.py | 0 rtamock/{rtamock => }/__init__.py | 0 rtamock/{rtamock => }/analysis_one_run.py | 0 rtamock/{rtamock => }/collect_bins_results.py | 0 rtamock/{rtamock => }/collect_results.py | 0 .../{rtamock => }/collect_stack_results.py | 0 rtamock/{rtamock => }/data/targets.txt | 0 rtamock/{rtamock => }/explore_dataframe.ipynb | 0 rtamock/{rtamock => }/plot_run_lightcurves.py | 0 rtamock/{rtamock => }/prepare_dataset.py | 0 rtamock/{rtamock => }/tools/__init__.py | 0 rtamock/{rtamock => }/tools/utils.py | 0 rtasci/.gitignore | 53 ------- rtasci/LICENSE | 24 ---- rtasci/MANIFEST.in | 5 - rtasci/{rtasci/cfg => }/__init__.py | 0 rtasci/{rtasci => }/aph/__ini__.py | 0 rtasci/{rtasci => }/aph/irf.py | 0 rtasci/{rtasci => }/aph/photometry.py | 0 rtasci/{rtasci => }/aph/utils.py | 0 rtasci/{rtasci => }/cfg/Config.py | 0 rtasci/{rtasci/lib => cfg}/__init__.py | 0 rtasci/{rtasci => }/cfg/config.yaml.default | 0 rtasci/changelog.md | 11 -- rtasci/{rtasci => }/check_templates.py | 2 +- rtasci/{rtasci => }/degradation_caldb.py | 2 +- rtasci/docs/Makefile | 20 --- rtasci/docs/make.bat | 35 ----- rtasci/docs/source/Config.rst | 5 - rtasci/docs/source/RTACtoolsAnalysis.rst | 5 - rtasci/docs/source/RTACtoolsSimulation.rst | 5 - rtasci/docs/source/RTAGammapyAnalysis.rst | 5 - rtasci/docs/source/RTAIrfs.rst | 5 - rtasci/docs/source/RTAManageXml.rst | 5 - rtasci/docs/source/RTAStats.rst | 5 - rtasci/docs/source/RTAUtils.rst | 5 - rtasci/docs/source/RTAVisualise.rst | 5 - rtasci/docs/source/conf.py | 61 -------- rtasci/docs/source/configuration.rst | 11 -- rtasci/docs/source/index.rst | 27 ---- rtasci/docs/source/intro.rst | 7 - rtasci/docs/source/library.rst | 18 --- rtasci/{rtasci => }/emptyfields.py | 12 +- rtasci/environment.yaml | 15 -- rtasci/{rtasci => }/makeConfig_runJobs.py | 0 rtasci/{rtasci => }/misc/__init__.py | 0 rtasci/{rtasci => }/misc/checkPointing.py | 6 +- rtasci/{rtasci => }/misc/chi2distribution.py | 2 +- .../{rtasci => }/misc/compareCtoolsGammapy.py | 2 +- .../misc/count_mc_events_from_log.py | 0 rtasci/{rtasci => }/misc/datamerger.py | 0 rtasci/{rtasci => }/misc/deljobs.py | 0 rtasci/{rtasci => }/misc/grbSens.py | 0 rtasci/{rtasci => }/misc/multihist.py | 2 +- rtasci/{rtasci => }/misc/phd_leo_test.py | 0 rtasci/{rtasci => }/misc/pvalues.py | 2 +- rtasci/{rtasci => }/misc/reformatold.py | 0 .../misc/save_selected_fits.ipynb | 0 rtasci/{rtasci => }/misc/sortObsEvents.py | 2 +- rtasci/{rtasci => }/misc/sphdist.py | 0 rtasci/{rtasci => }/misc/splitObs.py | 0 rtasci/{rtasci => }/misc/wilks.py | 2 +- rtasci/{rtasci => }/pipelines/__init__.py | 0 rtasci/{rtasci => }/pipelines/ctools1d.py | 8 +- .../{rtasci => }/pipelines/ctools1d_blind.py | 8 +- .../pipelines/ctools3d_blind_unbinned.py | 10 +- .../pipelines/ctools3d_unbinned.py | 8 +- rtasci/{rtasci => }/pipelines/gammapy1d.py | 8 +- .../{rtasci => }/pipelines/gammapy1d_blind.py | 8 +- rtasci/{rtasci => }/pipelines/rtatool1d.py | 8 +- .../{rtasci => }/pipelines/rtatool1d_blind.py | 10 +- rtasci/{rtasci => }/prepareGRBcatalog.py | 6 +- rtasci/pyproject.toml | 3 - rtasci/{rtasci => }/rtapipe.py | 0 rtasci/setup.py | 26 ---- rtasci/{rtasci => }/simBkg.py | 6 +- rtasci/{rtasci => }/simGRBcatalog.py | 6 +- .../simGRBcatalogWithBackgrounds.py | 6 +- .../simGRBcatalogWithRandomization.py | 4 +- rtasci/{rtasci => }/simWobble.py | 4 +- rtasci/{rtasci => }/timing/__init__.py | 0 rtasci/{rtasci => }/timing/make_sh.py | 0 .../timing/time_ctools1d_binned_fit.py | 6 +- .../{rtasci => }/timing/time_ctools1d_fit.py | 6 +- .../timing/time_ctools3d_binned_blindfit.py | 6 +- .../timing/time_ctools3d_binned_fit.py | 10 +- .../timing/time_ctools3d_blindfit.py | 6 +- .../{rtasci => }/timing/time_ctools3d_fit.py | 6 +- .../timing/time_gammapy1d_binned_fit.py | 0 .../{rtasci => }/timing/time_gammapy1d_fit.py | 0 .../timing/time_gammapy3d_binned_blindfit.py | 0 .../timing/time_gammapy3d_binned_fit.py | 0 .../timing/time_gammapy3d_blindfit.py | 0 .../{rtasci => }/timing/time_gammapy3d_fit.py | 0 .../lib => utils}/RTACtoolsAnalysis.py | 0 rtasci/{rtasci/lib => utils}/RTACtoolsBase.py | 0 .../lib => utils}/RTACtoolsSimulation.py | 0 .../lib => utils}/RTAGammapyAnalysis.py | 0 rtasci/{rtasci/lib => utils}/RTAIrfs.py | 0 rtasci/{rtasci/lib => utils}/RTAManageXml.py | 0 rtasci/{rtasci/lib => utils}/RTAStats.py | 0 rtasci/{rtasci/lib => utils}/RTAUtils.py | 0 rtasci/{rtasci/lib => utils}/RTAUtilsGW.py | 2 +- rtasci/{rtasci/lib => utils}/RTAVisualise.py | 0 {rtavis/rtavis => rtasci}/utils/__init__.py | 0 rtavis/.gitignore | 132 ------------------ rtavis/LICENSE | 28 ---- rtavis/{rtavis => }/cfg/config.yaml | 0 rtavis/environment.yaml | 13 -- .../examples/Evt343.noMoon.thresh10.npy | Bin .../examples/Evt751.noMoon.thresh10.npy | Bin .../notebooks/plotSignificance.ipynb | 0 .../notebooks/plotVisibility.ipynb | 0 rtavis/pyproject.toml | 3 - rtavis/{rtavis => }/readVisTable.py | 0 rtavis/{rtavis => }/runCatVisibility.py | 0 rtavis/setup.py | 18 --- rtavis/utils/__init__.py | 0 rtavis/{rtavis => }/utils/functions.py | 0 rtavis/{rtavis => }/utils/visibility.py | 0 120 files changed, 88 insertions(+), 643 deletions(-) rename {rtasci/rtasci => rtadeep}/__init__.py (100%) rename rtamock/{rtamock => }/__init__.py (100%) rename rtamock/{rtamock => }/analysis_one_run.py (100%) rename rtamock/{rtamock => }/collect_bins_results.py (100%) rename rtamock/{rtamock => }/collect_results.py (100%) rename rtamock/{rtamock => }/collect_stack_results.py (100%) rename rtamock/{rtamock => }/data/targets.txt (100%) rename rtamock/{rtamock => }/explore_dataframe.ipynb (100%) rename rtamock/{rtamock => }/plot_run_lightcurves.py (100%) rename rtamock/{rtamock => }/prepare_dataset.py (100%) rename rtamock/{rtamock => }/tools/__init__.py (100%) rename rtamock/{rtamock => }/tools/utils.py (100%) delete mode 100644 rtasci/.gitignore delete mode 100644 rtasci/LICENSE delete mode 100644 rtasci/MANIFEST.in rename rtasci/{rtasci/cfg => }/__init__.py (100%) rename rtasci/{rtasci => }/aph/__ini__.py (100%) rename rtasci/{rtasci => }/aph/irf.py (100%) rename rtasci/{rtasci => }/aph/photometry.py (100%) rename rtasci/{rtasci => }/aph/utils.py (100%) rename rtasci/{rtasci => }/cfg/Config.py (100%) rename rtasci/{rtasci/lib => cfg}/__init__.py (100%) rename rtasci/{rtasci => }/cfg/config.yaml.default (100%) delete mode 100644 rtasci/changelog.md rename rtasci/{rtasci => }/check_templates.py (94%) rename rtasci/{rtasci => }/degradation_caldb.py (95%) delete mode 100644 rtasci/docs/Makefile delete mode 100644 rtasci/docs/make.bat delete mode 100644 rtasci/docs/source/Config.rst delete mode 100644 rtasci/docs/source/RTACtoolsAnalysis.rst delete mode 100644 rtasci/docs/source/RTACtoolsSimulation.rst delete mode 100644 rtasci/docs/source/RTAGammapyAnalysis.rst delete mode 100644 rtasci/docs/source/RTAIrfs.rst delete mode 100644 rtasci/docs/source/RTAManageXml.rst delete mode 100644 rtasci/docs/source/RTAStats.rst delete mode 100644 rtasci/docs/source/RTAUtils.rst delete mode 100644 rtasci/docs/source/RTAVisualise.rst delete mode 100644 rtasci/docs/source/conf.py delete mode 100644 rtasci/docs/source/configuration.rst delete mode 100644 rtasci/docs/source/index.rst delete mode 100644 rtasci/docs/source/intro.rst delete mode 100644 rtasci/docs/source/library.rst rename rtasci/{rtasci => }/emptyfields.py (95%) delete mode 100644 rtasci/environment.yaml rename rtasci/{rtasci => }/makeConfig_runJobs.py (100%) rename rtasci/{rtasci => }/misc/__init__.py (100%) rename rtasci/{rtasci => }/misc/checkPointing.py (94%) rename rtasci/{rtasci => }/misc/chi2distribution.py (93%) rename rtasci/{rtasci => }/misc/compareCtoolsGammapy.py (98%) rename rtasci/{rtasci => }/misc/count_mc_events_from_log.py (100%) rename rtasci/{rtasci => }/misc/datamerger.py (100%) rename rtasci/{rtasci => }/misc/deljobs.py (100%) rename rtasci/{rtasci => }/misc/grbSens.py (100%) rename rtasci/{rtasci => }/misc/multihist.py (94%) rename rtasci/{rtasci => }/misc/phd_leo_test.py (100%) rename rtasci/{rtasci => }/misc/pvalues.py (97%) rename rtasci/{rtasci => }/misc/reformatold.py (100%) rename rtasci/{rtasci => }/misc/save_selected_fits.ipynb (100%) rename rtasci/{rtasci => }/misc/sortObsEvents.py (86%) rename rtasci/{rtasci => }/misc/sphdist.py (100%) rename rtasci/{rtasci => }/misc/splitObs.py (100%) rename rtasci/{rtasci => }/misc/wilks.py (99%) rename rtasci/{rtasci => }/pipelines/__init__.py (100%) rename rtasci/{rtasci => }/pipelines/ctools1d.py (98%) rename rtasci/{rtasci => }/pipelines/ctools1d_blind.py (98%) rename rtasci/{rtasci => }/pipelines/ctools3d_blind_unbinned.py (96%) rename rtasci/{rtasci => }/pipelines/ctools3d_unbinned.py (98%) rename rtasci/{rtasci => }/pipelines/gammapy1d.py (98%) rename rtasci/{rtasci => }/pipelines/gammapy1d_blind.py (99%) rename rtasci/{rtasci => }/pipelines/rtatool1d.py (98%) rename rtasci/{rtasci => }/pipelines/rtatool1d_blind.py (98%) rename rtasci/{rtasci => }/prepareGRBcatalog.py (96%) delete mode 100644 rtasci/pyproject.toml rename rtasci/{rtasci => }/rtapipe.py (100%) delete mode 100644 rtasci/setup.py rename rtasci/{rtasci => }/simBkg.py (96%) rename rtasci/{rtasci => }/simGRBcatalog.py (97%) rename rtasci/{rtasci => }/simGRBcatalogWithBackgrounds.py (97%) rename rtasci/{rtasci => }/simGRBcatalogWithRandomization.py (98%) rename rtasci/{rtasci => }/simWobble.py (95%) rename rtasci/{rtasci => }/timing/__init__.py (100%) rename rtasci/{rtasci => }/timing/make_sh.py (100%) rename rtasci/{rtasci => }/timing/time_ctools1d_binned_fit.py (95%) rename rtasci/{rtasci => }/timing/time_ctools1d_fit.py (96%) rename rtasci/{rtasci => }/timing/time_ctools3d_binned_blindfit.py (97%) rename rtasci/{rtasci => }/timing/time_ctools3d_binned_fit.py (95%) rename rtasci/{rtasci => }/timing/time_ctools3d_blindfit.py (95%) rename rtasci/{rtasci => }/timing/time_ctools3d_fit.py (95%) rename rtasci/{rtasci => }/timing/time_gammapy1d_binned_fit.py (100%) rename rtasci/{rtasci => }/timing/time_gammapy1d_fit.py (100%) rename rtasci/{rtasci => }/timing/time_gammapy3d_binned_blindfit.py (100%) rename rtasci/{rtasci => }/timing/time_gammapy3d_binned_fit.py (100%) rename rtasci/{rtasci => }/timing/time_gammapy3d_blindfit.py (100%) rename rtasci/{rtasci => }/timing/time_gammapy3d_fit.py (100%) rename rtasci/{rtasci/lib => utils}/RTACtoolsAnalysis.py (100%) rename rtasci/{rtasci/lib => utils}/RTACtoolsBase.py (100%) rename rtasci/{rtasci/lib => utils}/RTACtoolsSimulation.py (100%) rename rtasci/{rtasci/lib => utils}/RTAGammapyAnalysis.py (100%) rename rtasci/{rtasci/lib => utils}/RTAIrfs.py (100%) rename rtasci/{rtasci/lib => utils}/RTAManageXml.py (100%) rename rtasci/{rtasci/lib => utils}/RTAStats.py (100%) rename rtasci/{rtasci/lib => utils}/RTAUtils.py (100%) rename rtasci/{rtasci/lib => utils}/RTAUtilsGW.py (97%) rename rtasci/{rtasci/lib => utils}/RTAVisualise.py (100%) rename {rtavis/rtavis => rtasci}/utils/__init__.py (100%) delete mode 100644 rtavis/.gitignore delete mode 100644 rtavis/LICENSE rename rtavis/{rtavis => }/cfg/config.yaml (100%) delete mode 100644 rtavis/environment.yaml rename rtavis/{rtavis => }/notebooks/examples/Evt343.noMoon.thresh10.npy (100%) rename rtavis/{rtavis => }/notebooks/examples/Evt751.noMoon.thresh10.npy (100%) rename rtavis/{rtavis => }/notebooks/plotSignificance.ipynb (100%) rename rtavis/{rtavis => }/notebooks/plotVisibility.ipynb (100%) delete mode 100644 rtavis/pyproject.toml rename rtavis/{rtavis => }/readVisTable.py (100%) rename rtavis/{rtavis => }/runCatVisibility.py (100%) delete mode 100644 rtavis/setup.py create mode 100644 rtavis/utils/__init__.py rename rtavis/{rtavis => }/utils/functions.py (100%) rename rtavis/{rtavis => }/utils/visibility.py (100%) diff --git a/rtasci/rtasci/__init__.py b/rtadeep/__init__.py similarity index 100% rename from rtasci/rtasci/__init__.py rename to rtadeep/__init__.py diff --git a/rtamock/rtamock/__init__.py b/rtamock/__init__.py similarity index 100% rename from rtamock/rtamock/__init__.py rename to rtamock/__init__.py diff --git a/rtamock/rtamock/analysis_one_run.py b/rtamock/analysis_one_run.py similarity index 100% rename from rtamock/rtamock/analysis_one_run.py rename to rtamock/analysis_one_run.py diff --git a/rtamock/rtamock/collect_bins_results.py b/rtamock/collect_bins_results.py similarity index 100% rename from rtamock/rtamock/collect_bins_results.py rename to rtamock/collect_bins_results.py diff --git a/rtamock/rtamock/collect_results.py b/rtamock/collect_results.py similarity index 100% rename from rtamock/rtamock/collect_results.py rename to rtamock/collect_results.py diff --git a/rtamock/rtamock/collect_stack_results.py b/rtamock/collect_stack_results.py similarity index 100% rename from rtamock/rtamock/collect_stack_results.py rename to rtamock/collect_stack_results.py diff --git a/rtamock/rtamock/data/targets.txt b/rtamock/data/targets.txt similarity index 100% rename from rtamock/rtamock/data/targets.txt rename to rtamock/data/targets.txt diff --git a/rtamock/rtamock/explore_dataframe.ipynb b/rtamock/explore_dataframe.ipynb similarity index 100% rename from rtamock/rtamock/explore_dataframe.ipynb rename to rtamock/explore_dataframe.ipynb diff --git a/rtamock/rtamock/plot_run_lightcurves.py b/rtamock/plot_run_lightcurves.py similarity index 100% rename from rtamock/rtamock/plot_run_lightcurves.py rename to rtamock/plot_run_lightcurves.py diff --git a/rtamock/rtamock/prepare_dataset.py b/rtamock/prepare_dataset.py similarity index 100% rename from rtamock/rtamock/prepare_dataset.py rename to rtamock/prepare_dataset.py diff --git a/rtamock/rtamock/tools/__init__.py b/rtamock/tools/__init__.py similarity index 100% rename from rtamock/rtamock/tools/__init__.py rename to rtamock/tools/__init__.py diff --git a/rtamock/rtamock/tools/utils.py b/rtamock/tools/utils.py similarity index 100% rename from rtamock/rtamock/tools/utils.py rename to rtamock/tools/utils.py diff --git a/rtasci/.gitignore b/rtasci/.gitignore deleted file mode 100644 index feba68a..0000000 --- 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 49d2a32..0000000 --- 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 e03cead..0000000 --- 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/rtasci/cfg/__init__.py b/rtasci/__init__.py similarity index 100% rename from rtasci/rtasci/cfg/__init__.py rename to rtasci/__init__.py diff --git a/rtasci/rtasci/aph/__ini__.py b/rtasci/aph/__ini__.py similarity index 100% rename from rtasci/rtasci/aph/__ini__.py rename to rtasci/aph/__ini__.py diff --git a/rtasci/rtasci/aph/irf.py b/rtasci/aph/irf.py similarity index 100% rename from rtasci/rtasci/aph/irf.py rename to rtasci/aph/irf.py diff --git a/rtasci/rtasci/aph/photometry.py b/rtasci/aph/photometry.py similarity index 100% rename from rtasci/rtasci/aph/photometry.py rename to rtasci/aph/photometry.py diff --git a/rtasci/rtasci/aph/utils.py b/rtasci/aph/utils.py similarity index 100% rename from rtasci/rtasci/aph/utils.py rename to rtasci/aph/utils.py diff --git a/rtasci/rtasci/cfg/Config.py b/rtasci/cfg/Config.py similarity index 100% rename from rtasci/rtasci/cfg/Config.py rename to rtasci/cfg/Config.py diff --git a/rtasci/rtasci/lib/__init__.py b/rtasci/cfg/__init__.py similarity index 100% rename from rtasci/rtasci/lib/__init__.py rename to rtasci/cfg/__init__.py diff --git a/rtasci/rtasci/cfg/config.yaml.default b/rtasci/cfg/config.yaml.default similarity index 100% rename from rtasci/rtasci/cfg/config.yaml.default rename to rtasci/cfg/config.yaml.default diff --git a/rtasci/changelog.md b/rtasci/changelog.md deleted file mode 100644 index 32d0cf9..0000000 --- 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/rtasci/check_templates.py b/rtasci/check_templates.py similarity index 94% rename from rtasci/rtasci/check_templates.py rename to rtasci/check_templates.py index 7d2343d..e8353df 100644 --- a/rtasci/rtasci/check_templates.py +++ b/rtasci/check_templates.py @@ -10,7 +10,7 @@ import argparse import numpy as np import matplotlib.pyplot as plt -from rtasci.lib.RTAVisualise import get_template_lc, get_template_spectra +from rtasci.utils.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") diff --git a/rtasci/rtasci/degradation_caldb.py b/rtasci/degradation_caldb.py similarity index 95% rename from rtasci/rtasci/degradation_caldb.py rename to rtasci/degradation_caldb.py index d03c21d..0ea5234 100644 --- a/rtasci/rtasci/degradation_caldb.py +++ b/rtasci/degradation_caldb.py @@ -9,7 +9,7 @@ import os import argparse -from rtasci.lib.RTAIrfs import RTAIrfs +from rtasci.utils.RTAIrfs import RTAIrfs # files ---! parser = argparse.ArgumentParser() diff --git a/rtasci/docs/Makefile b/rtasci/docs/Makefile deleted file mode 100644 index d0c3cbf..0000000 --- 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 6247f7e..0000000 --- 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 b8863b3..0000000 --- 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 b119905..0000000 --- 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 0032d37..0000000 --- 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 79a91e6..0000000 --- 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 7092889..0000000 --- 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 d1f22ae..0000000 --- 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 02377a1..0000000 --- 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 912699b..0000000 --- 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 c032341..0000000 --- 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 b30ced5..0000000 --- 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 fed3db8..0000000 --- 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 87b983f..0000000 --- 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 7979bcd..0000000 --- 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 17a1c4b..0000000 --- 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/rtasci/emptyfields.py b/rtasci/emptyfields.py similarity index 95% rename from rtasci/rtasci/emptyfields.py rename to rtasci/emptyfields.py index 419914e..48d2211 100644 --- a/rtasci/rtasci/emptyfields.py +++ b/rtasci/emptyfields.py @@ -11,12 +11,12 @@ 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.utils.RTAManageXml import ManageXml +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation +from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAUtils import get_pointing, get_mergermap +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw +from rtasci.utils.RTAVisualise import plotSkymap from rtasci.aph.utils import * def main(args): diff --git a/rtasci/environment.yaml b/rtasci/environment.yaml deleted file mode 100644 index 03262e5..0000000 --- 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/rtasci/makeConfig_runJobs.py b/rtasci/makeConfig_runJobs.py similarity index 100% rename from rtasci/rtasci/makeConfig_runJobs.py rename to rtasci/makeConfig_runJobs.py diff --git a/rtasci/rtasci/misc/__init__.py b/rtasci/misc/__init__.py similarity index 100% rename from rtasci/rtasci/misc/__init__.py rename to rtasci/misc/__init__.py diff --git a/rtasci/rtasci/misc/checkPointing.py b/rtasci/misc/checkPointing.py similarity index 94% rename from rtasci/rtasci/misc/checkPointing.py rename to rtasci/misc/checkPointing.py index 0ed0f19..674c177 100644 --- a/rtasci/rtasci/misc/checkPointing.py +++ b/rtasci/misc/checkPointing.py @@ -10,10 +10,10 @@ 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.utils.RTAUtils import get_pointing +from rtasci.utils.RTAUtilsGW import get_offset from rtasci.cfg.Config import Config -from rtasci.lib.RTAUtilsGW import get_alert_pointing_gw +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw from astropy import units as u from astropy.coordinates import SkyCoord diff --git a/rtasci/rtasci/misc/chi2distribution.py b/rtasci/misc/chi2distribution.py similarity index 93% rename from rtasci/rtasci/misc/chi2distribution.py rename to rtasci/misc/chi2distribution.py index 9e9b4b3..2c3a221 100644 --- a/rtasci/rtasci/misc/chi2distribution.py +++ b/rtasci/misc/chi2distribution.py @@ -8,7 +8,7 @@ # ******************************************************************************* import numpy as np -from rtasci.lib.RTAStats import * +from rtasci.utils.RTAStats import * from os.path import expandvars, join x = np.random.chisquare(1, int(1e7)) diff --git a/rtasci/rtasci/misc/compareCtoolsGammapy.py b/rtasci/misc/compareCtoolsGammapy.py similarity index 98% rename from rtasci/rtasci/misc/compareCtoolsGammapy.py rename to rtasci/misc/compareCtoolsGammapy.py index c5d01f7..ffac736 100644 --- a/rtasci/rtasci/misc/compareCtoolsGammapy.py +++ b/rtasci/misc/compareCtoolsGammapy.py @@ -10,7 +10,7 @@ import os import pandas as pd import seaborn as sns -from lib.RTAVisualise import * +from utils.RTAVisualise import * from os.path import join, isdir, isfile from os import listdir diff --git a/rtasci/rtasci/misc/count_mc_events_from_log.py b/rtasci/misc/count_mc_events_from_log.py similarity index 100% rename from rtasci/rtasci/misc/count_mc_events_from_log.py rename to rtasci/misc/count_mc_events_from_log.py diff --git a/rtasci/rtasci/misc/datamerger.py b/rtasci/misc/datamerger.py similarity index 100% rename from rtasci/rtasci/misc/datamerger.py rename to rtasci/misc/datamerger.py diff --git a/rtasci/rtasci/misc/deljobs.py b/rtasci/misc/deljobs.py similarity index 100% rename from rtasci/rtasci/misc/deljobs.py rename to rtasci/misc/deljobs.py diff --git a/rtasci/rtasci/misc/grbSens.py b/rtasci/misc/grbSens.py similarity index 100% rename from rtasci/rtasci/misc/grbSens.py rename to rtasci/misc/grbSens.py diff --git a/rtasci/rtasci/misc/multihist.py b/rtasci/misc/multihist.py similarity index 94% rename from rtasci/rtasci/misc/multihist.py rename to rtasci/misc/multihist.py index 43eeff3..ecb78fe 100644 --- a/rtasci/rtasci/misc/multihist.py +++ b/rtasci/misc/multihist.py @@ -11,7 +11,7 @@ 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 +from rtasci.utils.RTAStats import ts_wilks, p_values, ts_wilks_cumulative if __name__=='__main__': diff --git a/rtasci/rtasci/misc/phd_leo_test.py b/rtasci/misc/phd_leo_test.py similarity index 100% rename from rtasci/rtasci/misc/phd_leo_test.py rename to rtasci/misc/phd_leo_test.py diff --git a/rtasci/rtasci/misc/pvalues.py b/rtasci/misc/pvalues.py similarity index 97% rename from rtasci/rtasci/misc/pvalues.py rename to rtasci/misc/pvalues.py index 7c38e52..0c07b9f 100644 --- a/rtasci/rtasci/misc/pvalues.py +++ b/rtasci/misc/pvalues.py @@ -10,7 +10,7 @@ import os import pandas as pd from os.path import join, expandvars -from rtasci.lib.RTAStats import * +from rtasci.utils.RTAStats import * path = expandvars('$DATA/outputs/GIULIANA') png_path = join(path, 'png/') diff --git a/rtasci/rtasci/misc/reformatold.py b/rtasci/misc/reformatold.py similarity index 100% rename from rtasci/rtasci/misc/reformatold.py rename to rtasci/misc/reformatold.py diff --git a/rtasci/rtasci/misc/save_selected_fits.ipynb b/rtasci/misc/save_selected_fits.ipynb similarity index 100% rename from rtasci/rtasci/misc/save_selected_fits.ipynb rename to rtasci/misc/save_selected_fits.ipynb diff --git a/rtasci/rtasci/misc/sortObsEvents.py b/rtasci/misc/sortObsEvents.py similarity index 86% rename from rtasci/rtasci/misc/sortObsEvents.py rename to rtasci/misc/sortObsEvents.py index cabbd74..00caff4 100644 --- a/rtasci/rtasci/misc/sortObsEvents.py +++ b/rtasci/misc/sortObsEvents.py @@ -9,7 +9,7 @@ import sys from os.path import expandvars -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation as sim +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation as sim events = sim() events.input = expandvars(sys.argv[1]) diff --git a/rtasci/rtasci/misc/sphdist.py b/rtasci/misc/sphdist.py similarity index 100% rename from rtasci/rtasci/misc/sphdist.py rename to rtasci/misc/sphdist.py diff --git a/rtasci/rtasci/misc/splitObs.py b/rtasci/misc/splitObs.py similarity index 100% rename from rtasci/rtasci/misc/splitObs.py rename to rtasci/misc/splitObs.py diff --git a/rtasci/rtasci/misc/wilks.py b/rtasci/misc/wilks.py similarity index 99% rename from rtasci/rtasci/misc/wilks.py rename to rtasci/misc/wilks.py index 55836e5..c808901 100644 --- a/rtasci/rtasci/misc/wilks.py +++ b/rtasci/misc/wilks.py @@ -8,7 +8,7 @@ # ******************************************************************************* import os -from rtasci.lib.RTAStats import * +from rtasci.utils.RTAStats import * dof = 1 folder = 'tesi_bkg_1e6_nominal_%ddof_fullE_old' % dof diff --git a/rtasci/rtasci/pipelines/__init__.py b/rtasci/pipelines/__init__.py similarity index 100% rename from rtasci/rtasci/pipelines/__init__.py rename to rtasci/pipelines/__init__.py diff --git a/rtasci/rtasci/pipelines/ctools1d.py b/rtasci/pipelines/ctools1d.py similarity index 98% rename from rtasci/rtasci/pipelines/ctools1d.py rename to rtasci/pipelines/ctools1d.py index 90f2a33..b24a2f9 100644 --- a/rtasci/rtasci/pipelines/ctools1d.py +++ b/rtasci/pipelines/ctools1d.py @@ -11,12 +11,12 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis, onoff_counts +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * +from rtasci.utils.RTAUtilsGW import * parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') diff --git a/rtasci/rtasci/pipelines/ctools1d_blind.py b/rtasci/pipelines/ctools1d_blind.py similarity index 98% rename from rtasci/rtasci/pipelines/ctools1d_blind.py rename to rtasci/pipelines/ctools1d_blind.py index cafaac5..5af8d9b 100644 --- a/rtasci/rtasci/pipelines/ctools1d_blind.py +++ b/rtasci/pipelines/ctools1d_blind.py @@ -14,12 +14,12 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * +from rtasci.utils.RTAUtilsGW import * from astropy.coordinates import SkyCoord runtime = time.time() - tstamp diff --git a/rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py b/rtasci/pipelines/ctools3d_blind_unbinned.py similarity index 96% rename from rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py rename to rtasci/pipelines/ctools3d_blind_unbinned.py index 2ecebfe..72d429f 100644 --- a/rtasci/rtasci/pipelines/ctools3d_blind_unbinned.py +++ b/rtasci/pipelines/ctools3d_blind_unbinned.py @@ -11,12 +11,12 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import phflux_powerlaw, get_pointing, get_mergermap +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw from rtasci.cfg.Config import Config -from rtasci.lib.RTAVisualise import plotSkymap +from rtasci.utils.RTAVisualise import plotSkymap from rtasci.aph.utils import * parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') diff --git a/rtasci/rtasci/pipelines/ctools3d_unbinned.py b/rtasci/pipelines/ctools3d_unbinned.py similarity index 98% rename from rtasci/rtasci/pipelines/ctools3d_unbinned.py rename to rtasci/pipelines/ctools3d_unbinned.py index ef2785e..74331df 100644 --- a/rtasci/rtasci/pipelines/ctools3d_unbinned.py +++ b/rtasci/pipelines/ctools3d_unbinned.py @@ -12,12 +12,12 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * -from rtasci.lib.RTAUtilsGW import * +from rtasci.utils.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") diff --git a/rtasci/rtasci/pipelines/gammapy1d.py b/rtasci/pipelines/gammapy1d.py similarity index 98% rename from rtasci/rtasci/pipelines/gammapy1d.py rename to rtasci/pipelines/gammapy1d.py index fedb80b..4e33688 100644 --- a/rtasci/rtasci/pipelines/gammapy1d.py +++ b/rtasci/pipelines/gammapy1d.py @@ -11,10 +11,10 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAGammapyAnalysis import * +from rtasci.utils.RTAUtils import * +from rtasci.utils.RTAUtilsGW import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * diff --git a/rtasci/rtasci/pipelines/gammapy1d_blind.py b/rtasci/pipelines/gammapy1d_blind.py similarity index 99% rename from rtasci/rtasci/pipelines/gammapy1d_blind.py rename to rtasci/pipelines/gammapy1d_blind.py index 05b84ed..5bc0ed6 100644 --- a/rtasci/rtasci/pipelines/gammapy1d_blind.py +++ b/rtasci/pipelines/gammapy1d_blind.py @@ -13,10 +13,10 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import * +from rtasci.utils.RTAUtilsGW import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * from astropy.coordinates import SkyCoord diff --git a/rtasci/rtasci/pipelines/rtatool1d.py b/rtasci/pipelines/rtatool1d.py similarity index 98% rename from rtasci/rtasci/pipelines/rtatool1d.py rename to rtasci/pipelines/rtatool1d.py index f9be139..9638e6d 100644 --- a/rtasci/rtasci/pipelines/rtatool1d.py +++ b/rtasci/pipelines/rtatool1d.py @@ -13,10 +13,10 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import * +from rtasci.utils.RTAUtilsGW import * from rtasci.cfg.Config import Config from rtasci.aph.utils import * diff --git a/rtasci/rtasci/pipelines/rtatool1d_blind.py b/rtasci/pipelines/rtatool1d_blind.py similarity index 98% rename from rtasci/rtasci/pipelines/rtatool1d_blind.py rename to rtasci/pipelines/rtatool1d_blind.py index 17c146a..6185c82 100644 --- a/rtasci/rtasci/pipelines/rtatool1d_blind.py +++ b/rtasci/pipelines/rtatool1d_blind.py @@ -12,10 +12,10 @@ 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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTAUtils import get_pointing, get_mergermap, phm_options +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw from rtasci.cfg.Config import Config from rtasci.aph.utils import * from astropy.coordinates import SkyCoord @@ -24,7 +24,7 @@ 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 * +from rtasci.utils.RTAGammapyAnalysis import * parser = argparse.ArgumentParser(description='ADD SCRIPT DESCRIPTION HERE') diff --git a/rtasci/rtasci/prepareGRBcatalog.py b/rtasci/prepareGRBcatalog.py similarity index 96% rename from rtasci/rtasci/prepareGRBcatalog.py rename to rtasci/prepareGRBcatalog.py index ec762ff..5ab9870 100644 --- a/rtasci/rtasci/prepareGRBcatalog.py +++ b/rtasci/prepareGRBcatalog.py @@ -10,9 +10,9 @@ 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.utils.RTACtoolsSimulation import RTACtoolsSimulation +from rtasci.utils.RTAUtils import get_pointing +from rtasci.utils.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.') diff --git a/rtasci/pyproject.toml b/rtasci/pyproject.toml deleted file mode 100644 index fa7093a..0000000 --- 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/rtapipe.py b/rtasci/rtapipe.py similarity index 100% rename from rtasci/rtasci/rtapipe.py rename to rtasci/rtapipe.py diff --git a/rtasci/setup.py b/rtasci/setup.py deleted file mode 100644 index a0a08c7..0000000 --- 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/rtasci/rtasci/simBkg.py b/rtasci/simBkg.py similarity index 96% rename from rtasci/rtasci/simBkg.py rename to rtasci/simBkg.py index 409c0d9..c907ed0 100644 --- a/rtasci/rtasci/simBkg.py +++ b/rtasci/simBkg.py @@ -17,9 +17,9 @@ 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 +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation +from rtasci.utils.RTAUtils import get_pointing, get_mergermap, str2bool +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw def main(args): cfg = Config(args.cfgfile) diff --git a/rtasci/rtasci/simGRBcatalog.py b/rtasci/simGRBcatalog.py similarity index 97% rename from rtasci/rtasci/simGRBcatalog.py rename to rtasci/simGRBcatalog.py index f48a12d..e6706dd 100644 --- a/rtasci/rtasci/simGRBcatalog.py +++ b/rtasci/simGRBcatalog.py @@ -16,9 +16,9 @@ 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 +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist +from rtasci.utils.RTAUtils import get_mergermap, get_pointing, str2bool +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw def main(): diff --git a/rtasci/rtasci/simGRBcatalogWithBackgrounds.py b/rtasci/simGRBcatalogWithBackgrounds.py similarity index 97% rename from rtasci/rtasci/simGRBcatalogWithBackgrounds.py rename to rtasci/simGRBcatalogWithBackgrounds.py index 2d69500..55aee72 100644 --- a/rtasci/rtasci/simGRBcatalogWithBackgrounds.py +++ b/rtasci/simGRBcatalogWithBackgrounds.py @@ -16,9 +16,9 @@ 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 +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist +from rtasci.utils.RTAUtils import get_mergermap, get_pointing, str2bool +from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw diff --git a/rtasci/rtasci/simGRBcatalogWithRandomization.py b/rtasci/simGRBcatalogWithRandomization.py similarity index 98% rename from rtasci/rtasci/simGRBcatalogWithRandomization.py rename to rtasci/simGRBcatalogWithRandomization.py index 7355d64..81cad39 100644 --- a/rtasci/rtasci/simGRBcatalogWithRandomization.py +++ b/rtasci/simGRBcatalogWithRandomization.py @@ -20,8 +20,8 @@ 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 +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist +from rtasci.utils.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=''): diff --git a/rtasci/rtasci/simWobble.py b/rtasci/simWobble.py similarity index 95% rename from rtasci/rtasci/simWobble.py rename to rtasci/simWobble.py index 8d2154c..264976f 100644 --- a/rtasci/rtasci/simWobble.py +++ b/rtasci/simWobble.py @@ -9,8 +9,8 @@ import os import argparse -from rtasci.lib.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.lib.RTAUtils import wobble_pointing +from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist +from rtasci.utils.RTAUtils import wobble_pointing from rtasci.cfg.Config import Config from os.path import join, isdir diff --git a/rtasci/rtasci/timing/__init__.py b/rtasci/timing/__init__.py similarity index 100% rename from rtasci/rtasci/timing/__init__.py rename to rtasci/timing/__init__.py diff --git a/rtasci/rtasci/timing/make_sh.py b/rtasci/timing/make_sh.py similarity index 100% rename from rtasci/rtasci/timing/make_sh.py rename to rtasci/timing/make_sh.py diff --git a/rtasci/rtasci/timing/time_ctools1d_binned_fit.py b/rtasci/timing/time_ctools1d_binned_fit.py similarity index 95% rename from rtasci/rtasci/timing/time_ctools1d_binned_fit.py rename to rtasci/timing/time_ctools1d_binned_fit.py index 95fa72a..fb82d7f 100644 --- a/rtasci/rtasci/timing/time_ctools1d_binned_fit.py +++ b/rtasci/timing/time_ctools1d_binned_fit.py @@ -15,9 +15,9 @@ 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 +from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAUtils import * +from rtasci.utils.RTAManageXml import ManageXml timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_ctools1d_fit.py b/rtasci/timing/time_ctools1d_fit.py similarity index 96% rename from rtasci/rtasci/timing/time_ctools1d_fit.py rename to rtasci/timing/time_ctools1d_fit.py index 9558da2..f57b22b 100644 --- a/rtasci/rtasci/timing/time_ctools1d_fit.py +++ b/rtasci/timing/time_ctools1d_fit.py @@ -16,9 +16,9 @@ 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 +from utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from utils.RTAUtils import * +from utils.RTAManageXml import ManageXml timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py b/rtasci/timing/time_ctools3d_binned_blindfit.py similarity index 97% rename from rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py rename to rtasci/timing/time_ctools3d_binned_blindfit.py index 0ba296a..3b01303 100644 --- a/rtasci/rtasci/timing/time_ctools3d_binned_blindfit.py +++ b/rtasci/timing/time_ctools3d_binned_blindfit.py @@ -17,9 +17,9 @@ first = sys.argv[2] 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 +from utils.RTACtoolsAnalysis import RTACtoolsAnalysis, make_obslist +from utils.RTAUtils import phflux_powerlaw +from utils.RTAManageXml import ManageXml timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_ctools3d_binned_fit.py b/rtasci/timing/time_ctools3d_binned_fit.py similarity index 95% rename from rtasci/rtasci/timing/time_ctools3d_binned_fit.py rename to rtasci/timing/time_ctools3d_binned_fit.py index acfdc02..93f79dd 100644 --- a/rtasci/rtasci/timing/time_ctools3d_binned_fit.py +++ b/rtasci/timing/time_ctools3d_binned_fit.py @@ -15,11 +15,11 @@ 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 * +from rtasci.utils.RTACtoolsSimulation import make_obslist +from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAUtils import * +from rtasci.utils.RTAManageXml import ManageXml +from rtasci.utils.RTACtoolsBase import * timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_ctools3d_blindfit.py b/rtasci/timing/time_ctools3d_blindfit.py similarity index 95% rename from rtasci/rtasci/timing/time_ctools3d_blindfit.py rename to rtasci/timing/time_ctools3d_blindfit.py index b12f4f7..c17b250 100644 --- a/rtasci/rtasci/timing/time_ctools3d_blindfit.py +++ b/rtasci/timing/time_ctools3d_blindfit.py @@ -16,9 +16,9 @@ first = sys.argv[2] 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 +from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from rtasci.utils.RTAUtils import phflux_powerlaw +from rtasci.utils.RTAManageXml import ManageXml timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_ctools3d_fit.py b/rtasci/timing/time_ctools3d_fit.py similarity index 95% rename from rtasci/rtasci/timing/time_ctools3d_fit.py rename to rtasci/timing/time_ctools3d_fit.py index aad4626..b847c6c 100644 --- a/rtasci/rtasci/timing/time_ctools3d_fit.py +++ b/rtasci/timing/time_ctools3d_fit.py @@ -16,9 +16,9 @@ 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 +from utils.RTACtoolsAnalysis import RTACtoolsAnalysis +from utils.RTAUtils import * +from utils.RTAManageXml import ManageXml timport = time.time() - t print(f'Imports : {timport} s\n') diff --git a/rtasci/rtasci/timing/time_gammapy1d_binned_fit.py b/rtasci/timing/time_gammapy1d_binned_fit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy1d_binned_fit.py rename to rtasci/timing/time_gammapy1d_binned_fit.py diff --git a/rtasci/rtasci/timing/time_gammapy1d_fit.py b/rtasci/timing/time_gammapy1d_fit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy1d_fit.py rename to rtasci/timing/time_gammapy1d_fit.py diff --git a/rtasci/rtasci/timing/time_gammapy3d_binned_blindfit.py b/rtasci/timing/time_gammapy3d_binned_blindfit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy3d_binned_blindfit.py rename to rtasci/timing/time_gammapy3d_binned_blindfit.py diff --git a/rtasci/rtasci/timing/time_gammapy3d_binned_fit.py b/rtasci/timing/time_gammapy3d_binned_fit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy3d_binned_fit.py rename to rtasci/timing/time_gammapy3d_binned_fit.py diff --git a/rtasci/rtasci/timing/time_gammapy3d_blindfit.py b/rtasci/timing/time_gammapy3d_blindfit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy3d_blindfit.py rename to rtasci/timing/time_gammapy3d_blindfit.py diff --git a/rtasci/rtasci/timing/time_gammapy3d_fit.py b/rtasci/timing/time_gammapy3d_fit.py similarity index 100% rename from rtasci/rtasci/timing/time_gammapy3d_fit.py rename to rtasci/timing/time_gammapy3d_fit.py diff --git a/rtasci/rtasci/lib/RTACtoolsAnalysis.py b/rtasci/utils/RTACtoolsAnalysis.py similarity index 100% rename from rtasci/rtasci/lib/RTACtoolsAnalysis.py rename to rtasci/utils/RTACtoolsAnalysis.py diff --git a/rtasci/rtasci/lib/RTACtoolsBase.py b/rtasci/utils/RTACtoolsBase.py similarity index 100% rename from rtasci/rtasci/lib/RTACtoolsBase.py rename to rtasci/utils/RTACtoolsBase.py diff --git a/rtasci/rtasci/lib/RTACtoolsSimulation.py b/rtasci/utils/RTACtoolsSimulation.py similarity index 100% rename from rtasci/rtasci/lib/RTACtoolsSimulation.py rename to rtasci/utils/RTACtoolsSimulation.py diff --git a/rtasci/rtasci/lib/RTAGammapyAnalysis.py b/rtasci/utils/RTAGammapyAnalysis.py similarity index 100% rename from rtasci/rtasci/lib/RTAGammapyAnalysis.py rename to rtasci/utils/RTAGammapyAnalysis.py diff --git a/rtasci/rtasci/lib/RTAIrfs.py b/rtasci/utils/RTAIrfs.py similarity index 100% rename from rtasci/rtasci/lib/RTAIrfs.py rename to rtasci/utils/RTAIrfs.py diff --git a/rtasci/rtasci/lib/RTAManageXml.py b/rtasci/utils/RTAManageXml.py similarity index 100% rename from rtasci/rtasci/lib/RTAManageXml.py rename to rtasci/utils/RTAManageXml.py diff --git a/rtasci/rtasci/lib/RTAStats.py b/rtasci/utils/RTAStats.py similarity index 100% rename from rtasci/rtasci/lib/RTAStats.py rename to rtasci/utils/RTAStats.py diff --git a/rtasci/rtasci/lib/RTAUtils.py b/rtasci/utils/RTAUtils.py similarity index 100% rename from rtasci/rtasci/lib/RTAUtils.py rename to rtasci/utils/RTAUtils.py diff --git a/rtasci/rtasci/lib/RTAUtilsGW.py b/rtasci/utils/RTAUtilsGW.py similarity index 97% rename from rtasci/rtasci/lib/RTAUtilsGW.py rename to rtasci/utils/RTAUtilsGW.py index c1d0f87..9d0bd89 100644 --- a/rtasci/rtasci/lib/RTAUtilsGW.py +++ b/rtasci/utils/RTAUtilsGW.py @@ -10,7 +10,7 @@ import os import numpy as np import healpy as hp -from rtasci.lib.RTAUtils import get_pointing +from rtasci.utils.RTAUtils import get_pointing # retrieve telescope pointing coordinates from alert probability map ---! def get_alert_pointing_compressed(merger_map): diff --git a/rtasci/rtasci/lib/RTAVisualise.py b/rtasci/utils/RTAVisualise.py similarity index 100% rename from rtasci/rtasci/lib/RTAVisualise.py rename to rtasci/utils/RTAVisualise.py diff --git a/rtavis/rtavis/utils/__init__.py b/rtasci/utils/__init__.py similarity index 100% rename from rtavis/rtavis/utils/__init__.py rename to rtasci/utils/__init__.py diff --git a/rtavis/.gitignore b/rtavis/.gitignore deleted file mode 100644 index 0286189..0000000 --- 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 5952330..0000000 --- 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/rtavis/cfg/config.yaml b/rtavis/cfg/config.yaml similarity index 100% rename from rtavis/rtavis/cfg/config.yaml rename to rtavis/cfg/config.yaml diff --git a/rtavis/environment.yaml b/rtavis/environment.yaml deleted file mode 100644 index 4a7036d..0000000 --- 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/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy b/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy similarity index 100% rename from rtavis/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy rename to rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy diff --git a/rtavis/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy b/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy similarity index 100% rename from rtavis/rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy rename to rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy diff --git a/rtavis/rtavis/notebooks/plotSignificance.ipynb b/rtavis/notebooks/plotSignificance.ipynb similarity index 100% rename from rtavis/rtavis/notebooks/plotSignificance.ipynb rename to rtavis/notebooks/plotSignificance.ipynb diff --git a/rtavis/rtavis/notebooks/plotVisibility.ipynb b/rtavis/notebooks/plotVisibility.ipynb similarity index 100% rename from rtavis/rtavis/notebooks/plotVisibility.ipynb rename to rtavis/notebooks/plotVisibility.ipynb diff --git a/rtavis/pyproject.toml b/rtavis/pyproject.toml deleted file mode 100644 index fa7093a..0000000 --- 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/readVisTable.py b/rtavis/readVisTable.py similarity index 100% rename from rtavis/rtavis/readVisTable.py rename to rtavis/readVisTable.py diff --git a/rtavis/rtavis/runCatVisibility.py b/rtavis/runCatVisibility.py similarity index 100% rename from rtavis/rtavis/runCatVisibility.py rename to rtavis/runCatVisibility.py diff --git a/rtavis/setup.py b/rtavis/setup.py deleted file mode 100644 index 93996a8..0000000 --- a/rtavis/setup.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 -# ***************************************************************************** - -from setuptools import setup, find_packages - -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 diff --git a/rtavis/utils/__init__.py b/rtavis/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rtavis/rtavis/utils/functions.py b/rtavis/utils/functions.py similarity index 100% rename from rtavis/rtavis/utils/functions.py rename to rtavis/utils/functions.py diff --git a/rtavis/rtavis/utils/visibility.py b/rtavis/utils/visibility.py similarity index 100% rename from rtavis/rtavis/utils/visibility.py rename to rtavis/utils/visibility.py -- GitLab From f31309cb5dec59b90ab8d7209fca04b0b3e93e4b Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:01:49 +0200 Subject: [PATCH 02/30] update readme --- README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 828505f..60292ea 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# 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 . +``` + +## 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 + +Deep learning solutions for CTA real-time analysis. \ No newline at end of file -- GitLab From 4f047631ea2ea1bea7059c9222fb24faa0b0fe46 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:02:02 +0200 Subject: [PATCH 03/30] fix packaging --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1ac545f..8585013 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={'rtavis': 'rtavis', 'rtamock': 'rtamock', 'rtasci': 'rtasci', 'rtadeep': 'rtadeep'}, packages=find_packages(), include_package_data=True, license='BSD-3-Clause', -- GitLab From 5a53104285602bfc2247fea0083851c61aaf170f Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:02:21 +0200 Subject: [PATCH 04/30] wrap shortcuts --- rtadeep/utils/wrap.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 rtadeep/utils/wrap.py diff --git a/rtadeep/utils/wrap.py b/rtadeep/utils/wrap.py new file mode 100644 index 0000000..1fa4b60 --- /dev/null +++ b/rtadeep/utils/wrap.py @@ -0,0 +1,14 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import yaml + +def load_yaml_conf(yamlfile): + with open(yamlfile) as f: + configuration = yaml.load(f, Loader=yaml.FullLoader) + return configuration \ No newline at end of file -- GitLab From 7aae91f567917f4e66e0418d8ef5eda23c6ffeb6 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:02:33 +0200 Subject: [PATCH 05/30] verify configuration --- rtadeep/cfg/check_configuration.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 rtadeep/cfg/check_configuration.py diff --git a/rtadeep/cfg/check_configuration.py b/rtadeep/cfg/check_configuration.py new file mode 100644 index 0000000..0bc9af0 --- /dev/null +++ b/rtadeep/cfg/check_configuration.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 +# ***************************************************************************** + +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() + + def check_tags(self): + tags = ['simulator', 'visibility'] + assert self.conf.keys() == tags + + def check_simulator(self): + keys = ['name', 'irf', 'prod', 'coordinates', 'duration', 'samples', 'seed'] + assert self.conf['simulator'].keys() == keys + + def check_visibility(self): + keys = ['start_time'] + assert self.conf['visibility'].keys() == keys -- GitLab From 75ec4d7ad94d7b88110e7aa59af3186476518999 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:02:46 +0200 Subject: [PATCH 06/30] test config --- rtadeep/cfg/test.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 rtadeep/cfg/test.yml diff --git a/rtadeep/cfg/test.yml b/rtadeep/cfg/test.yml new file mode 100644 index 0000000..a1c4afe --- /dev/null +++ b/rtadeep/cfg/test.yml @@ -0,0 +1,12 @@ +simulator: + name: test + irf: North_z60_0.5h_LST + prod: prod5 + coordinates: random + duration: 300 + samples: 5 + seed: 1 + +visibility: + start_time: today + -- GitLab From 8a1224836e7d60b879281c46e292e0a87072059d Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:02:58 +0200 Subject: [PATCH 07/30] simulator base --- rtadeep/base_simulator.py | 23 +++++++++++++++++++++++ rtadeep/utils/__init__.py | 0 2 files changed, 23 insertions(+) create mode 100644 rtadeep/base_simulator.py create mode 100644 rtadeep/utils/__init__.py diff --git a/rtadeep/base_simulator.py b/rtadeep/base_simulator.py new file mode 100644 index 0000000..eaba28b --- /dev/null +++ b/rtadeep/base_simulator.py @@ -0,0 +1,23 @@ +# ***************************************************************************** +# 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 rtadeep.utils.wrap import load_yaml_conf +from rtadeep.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) +CheckConfiguration(conf) + + + diff --git a/rtadeep/utils/__init__.py b/rtadeep/utils/__init__.py new file mode 100644 index 0000000..e69de29 -- GitLab From 17a4d61dd500a68db915c052822cf3e0bfd771f1 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:03:44 +0200 Subject: [PATCH 08/30] add pytest --- environment.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/environment.yml b/environment.yml index b259179..9bc21a3 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 -- GitLab From eb86fce534f3211b49bf2e0259baccca381e8445 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:44:41 +0200 Subject: [PATCH 09/30] init cfg --- rtadeep/cfg/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rtadeep/cfg/__init__.py diff --git a/rtadeep/cfg/__init__.py b/rtadeep/cfg/__init__.py new file mode 100644 index 0000000..e69de29 -- GitLab From 7e94d297adad0fcb6b52109950e3d12ab86eca89 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:45:04 +0200 Subject: [PATCH 10/30] duck typing --- rtadeep/cfg/check_configuration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rtadeep/cfg/check_configuration.py b/rtadeep/cfg/check_configuration.py index 0bc9af0..1722480 100644 --- a/rtadeep/cfg/check_configuration.py +++ b/rtadeep/cfg/check_configuration.py @@ -17,15 +17,19 @@ class CheckConfiguration(): 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', 'irf', 'prod', 'coordinates', 'duration', 'samples', 'seed'] assert self.conf['simulator'].keys() == keys + return self def check_visibility(self): keys = ['start_time'] assert self.conf['visibility'].keys() == keys + return self -- GitLab From e7171accfb424c6b8c91a38602dc5395f86f30d2 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:45:11 +0200 Subject: [PATCH 11/30] add unit tests --- testing/conftest.py | 15 +++++++ testing/pytest.ini | 3 ++ .../test_cfg/test_check_configuration.py | 42 +++++++++++++++++++ testing/test_rtadeep/test_utils/test_wrap.py | 15 +++++++ 4 files changed, 75 insertions(+) create mode 100644 testing/conftest.py create mode 100644 testing/pytest.ini create mode 100644 testing/test_rtadeep/test_cfg/test_check_configuration.py create mode 100644 testing/test_rtadeep/test_utils/test_wrap.py diff --git a/testing/conftest.py b/testing/conftest.py new file mode 100644 index 0000000..f57cd5b --- /dev/null +++ b/testing/conftest.py @@ -0,0 +1,15 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +import rtadeep +from os.path import join, dirname, abspath + +@pytest.fixture(scope='function') +def rtadeep_configuration(): + return join(dirname(abspath(rtadeep.__file__)), 'cfg', 'test.yml') \ No newline at end of file diff --git a/testing/pytest.ini b/testing/pytest.ini new file mode 100644 index 0000000..c780281 --- /dev/null +++ b/testing/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + rtadeep_configuration: rtadeep test configurtion file \ No newline at end of file diff --git a/testing/test_rtadeep/test_cfg/test_check_configuration.py b/testing/test_rtadeep/test_cfg/test_check_configuration.py new file mode 100644 index 0000000..0eb735a --- /dev/null +++ b/testing/test_rtadeep/test_cfg/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 rtadeep.utils.wrap import load_yaml_conf +from rtadeep.cfg.check_configuration import CheckConfiguration + +@pytest.mark.rtadeep_configuration +class TestCheckConfiguration: + + def test_check(self, rtadeep_configuration): + configuration = load_yaml_conf(rtadeep_configuration) + try: + CheckConfiguration(configuration=configuration).check() + except AssertionError as e: + type(e) == AssertionError + + def test_check_tags(self, rtadeep_configuration): + configuration = load_yaml_conf(rtadeep_configuration) + try: + CheckConfiguration(configuration=configuration).check_tags() + except AssertionError as e: + type(e) == AssertionError + + def test_check_simulator(self, rtadeep_configuration): + configuration = load_yaml_conf(rtadeep_configuration) + try: + CheckConfiguration(configuration=configuration).check_simulator() + except AssertionError as e: + type(e) == AssertionError + + def test_check_visibility(self, rtadeep_configuration): + configuration = load_yaml_conf(rtadeep_configuration) + try: + CheckConfiguration(configuration=configuration).check_visibility() + except AssertionError as e: + type(e) == AssertionError diff --git a/testing/test_rtadeep/test_utils/test_wrap.py b/testing/test_rtadeep/test_utils/test_wrap.py new file mode 100644 index 0000000..8a1bd3d --- /dev/null +++ b/testing/test_rtadeep/test_utils/test_wrap.py @@ -0,0 +1,15 @@ +# ***************************************************************************** +# Copyright (C) 2023 INAF +# This software is distributed under the terms of the BSD-3-Clause license +# +# Authors: +# Ambra Di Piano +# ***************************************************************************** + +import pytest +from rtadeep.utils.wrap import load_yaml_conf + +@pytest.mark.rtadeep_configuration +def test_load_yaml_conf(rtadeep_configuration): + configuration = load_yaml_conf(rtadeep_configuration) + assert type(configuration) == dict \ No newline at end of file -- GitLab From 34b2e8567930d8774b4ee0af1b70f740b7609e13 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:46:10 +0200 Subject: [PATCH 12/30] move to simulator --- rtadeep/{ => simulator}/__init__.py | 0 rtadeep/{ => simulator}/base_simulator.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename rtadeep/{ => simulator}/__init__.py (100%) rename rtadeep/{ => simulator}/base_simulator.py (100%) diff --git a/rtadeep/__init__.py b/rtadeep/simulator/__init__.py similarity index 100% rename from rtadeep/__init__.py rename to rtadeep/simulator/__init__.py diff --git a/rtadeep/base_simulator.py b/rtadeep/simulator/base_simulator.py similarity index 100% rename from rtadeep/base_simulator.py rename to rtadeep/simulator/base_simulator.py -- GitLab From f1f31d33406b1fecf7a0130dd1634dc0d905eccd Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:57:33 +0200 Subject: [PATCH 13/30] remove --- prod5install.sh | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 prod5install.sh diff --git a/prod5install.sh b/prod5install.sh deleted file mode 100644 index bc2bee4..0000000 --- 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 -- GitLab From b84b260effcb980fa78e876c4c1b9e5a13804f47 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 13:59:11 +0200 Subject: [PATCH 14/30] install prod5 --- rtasci/prod5install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rtasci/prod5install.sh b/rtasci/prod5install.sh index bc2bee4..7c44ba2 100644 --- a/rtasci/prod5install.sh +++ b/rtasci/prod5install.sh @@ -4,6 +4,7 @@ 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 +unzip cta-prod5-zenodo-fitsonly-v0.1.zip +csadd2caldb cd .. rm -rf tmp -- GitLab From 8015696f32740704f28371da2b975b220a340f5f Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 14:49:11 +0200 Subject: [PATCH 15/30] remove folders to add submodules and moved out rtadeep --- {rtadeep/cfg => astrort}/__init__.py | 0 .../simulator => astrort/cfg}/__init__.py | 0 .../cfg/check_configuration.py | 0 {rtadeep => astrort}/cfg/test.yml | 0 .../utils => astrort/simulator}/__init__.py | 0 .../simulator/base_simulator.py | 5 +- {testing => astrort/testing}/conftest.py | 2 +- {testing => astrort/testing}/pytest.ini | 0 .../test_cfg/test_check_configuration.py | 4 +- .../test_simulator/test_base_simulator.py | 22 + .../test_rtadeep/test_utils/test_wrap.py | 2 +- {rtasci => astrort/utils}/__init__.py | 0 {rtadeep => astrort}/utils/wrap.py | 2 + rtamock/.gitignore | 149 --- rtamock/LICENSE | 29 - rtamock/MANIFEST.in | 3 - rtamock/README.md | 45 - rtamock/__init__.py | 11 - rtamock/analysis_one_run.py | 77 -- rtamock/collect_bins_results.py | 86 -- rtamock/collect_results.py | 86 -- rtamock/collect_stack_results.py | 75 -- rtamock/data/targets.txt | 5 - rtamock/explore_dataframe.ipynb | 184 ---- rtamock/plot_run_lightcurves.py | 124 --- rtamock/prepare_dataset.py | 134 --- rtamock/pyproject.toml | 3 - rtamock/setup.py | 23 - rtamock/tools/__init__.py | 11 - rtamock/tools/utils.py | 93 -- rtasci/README.md | 83 -- rtasci/aph/__ini__.py | 0 rtasci/aph/irf.py | 564 ----------- rtasci/aph/photometry.py | 189 ---- rtasci/aph/utils.py | 162 ---- rtasci/cfg/Config.py | 170 ---- rtasci/cfg/__init__.py | 0 rtasci/cfg/config.yaml.default | 52 -- rtasci/check_templates.py | 40 - rtasci/degradation_caldb.py | 29 - rtasci/emptyfields.py | 205 ---- rtasci/makeConfig_runJobs.py | 113 --- rtasci/misc/__init__.py | 0 rtasci/misc/checkPointing.py | 73 -- rtasci/misc/chi2distribution.py | 19 - rtasci/misc/compareCtoolsGammapy.py | 59 -- rtasci/misc/count_mc_events_from_log.py | 51 - rtasci/misc/datamerger.py | 39 - rtasci/misc/deljobs.py | 22 - rtasci/misc/grbSens.py | 104 --- rtasci/misc/multihist.py | 30 - rtasci/misc/phd_leo_test.py | 50 - rtasci/misc/pvalues.py | 41 - rtasci/misc/reformatold.py | 56 -- rtasci/misc/save_selected_fits.ipynb | 208 ----- rtasci/misc/sortObsEvents.py | 18 - rtasci/misc/sphdist.py | 37 - rtasci/misc/splitObs.py | 47 - rtasci/misc/wilks.py | 115 --- rtasci/pipelines/__init__.py | 0 rtasci/pipelines/ctools1d.py | 259 ----- rtasci/pipelines/ctools1d_blind.py | 319 ------- rtasci/pipelines/ctools3d_blind_unbinned.py | 222 ----- rtasci/pipelines/ctools3d_unbinned.py | 258 ----- rtasci/pipelines/gammapy1d.py | 259 ----- rtasci/pipelines/gammapy1d_blind.py | 366 -------- rtasci/pipelines/rtatool1d.py | 258 ----- rtasci/pipelines/rtatool1d_blind.py | 311 ------- rtasci/prepareGRBcatalog.py | 103 -- rtasci/prod5install.sh | 10 - rtasci/rtapipe.py | 57 -- rtasci/simBkg.py | 143 --- rtasci/simGRBcatalog.py | 242 ----- rtasci/simGRBcatalogWithBackgrounds.py | 248 ----- rtasci/simGRBcatalogWithRandomization.py | 369 -------- rtasci/simWobble.py | 90 -- rtasci/timing/__init__.py | 0 rtasci/timing/make_sh.py | 57 -- rtasci/timing/time_ctools1d_binned_fit.py | 116 --- rtasci/timing/time_ctools1d_fit.py | 110 --- .../timing/time_ctools3d_binned_blindfit.py | 181 ---- rtasci/timing/time_ctools3d_binned_fit.py | 171 ---- rtasci/timing/time_ctools3d_blindfit.py | 124 --- rtasci/timing/time_ctools3d_fit.py | 96 -- rtasci/timing/time_gammapy1d_binned_fit.py | 150 --- rtasci/timing/time_gammapy1d_fit.py | 139 --- .../timing/time_gammapy3d_binned_blindfit.py | 176 ---- rtasci/timing/time_gammapy3d_binned_fit.py | 164 ---- rtasci/timing/time_gammapy3d_blindfit.py | 173 ---- rtasci/timing/time_gammapy3d_fit.py | 160 ---- rtasci/utils/RTACtoolsAnalysis.py | 576 ------------ rtasci/utils/RTACtoolsBase.py | 27 - rtasci/utils/RTACtoolsSimulation.py | 575 ------------ rtasci/utils/RTAGammapyAnalysis.py | 77 -- rtasci/utils/RTAIrfs.py | 215 ----- rtasci/utils/RTAManageXml.py | 353 ------- rtasci/utils/RTAStats.py | 881 ------------------ rtasci/utils/RTAUtils.py | 254 ----- rtasci/utils/RTAUtilsGW.py | 52 -- rtasci/utils/RTAVisualise.py | 693 -------------- rtasci/utils/__init__.py | 0 rtavis/README.md | 53 -- rtavis/cfg/config.yaml | 64 -- .../examples/Evt343.noMoon.thresh10.npy | Bin 26505 -> 0 bytes .../examples/Evt751.noMoon.thresh10.npy | Bin 20361 -> 0 bytes rtavis/notebooks/plotSignificance.ipynb | 357 ------- rtavis/notebooks/plotVisibility.ipynb | 294 ------ rtavis/readVisTable.py | 50 - rtavis/runCatVisibility.py | 169 ---- rtavis/utils/__init__.py | 0 rtavis/utils/functions.py | 80 -- rtavis/utils/visibility.py | 380 -------- 112 files changed, 30 insertions(+), 14202 deletions(-) rename {rtadeep/cfg => astrort}/__init__.py (100%) rename {rtadeep/simulator => astrort/cfg}/__init__.py (100%) rename {rtadeep => astrort}/cfg/check_configuration.py (100%) rename {rtadeep => astrort}/cfg/test.yml (100%) rename {rtadeep/utils => astrort/simulator}/__init__.py (100%) rename {rtadeep => astrort}/simulator/base_simulator.py (83%) rename {testing => astrort/testing}/conftest.py (87%) rename {testing => astrort/testing}/pytest.ini (100%) rename {testing => astrort/testing}/test_rtadeep/test_cfg/test_check_configuration.py (93%) create mode 100644 astrort/testing/test_rtadeep/test_simulator/test_base_simulator.py rename {testing => astrort/testing}/test_rtadeep/test_utils/test_wrap.py (91%) rename {rtasci => astrort/utils}/__init__.py (100%) rename {rtadeep => astrort}/utils/wrap.py (79%) delete mode 100644 rtamock/.gitignore delete mode 100644 rtamock/LICENSE delete mode 100644 rtamock/MANIFEST.in delete mode 100644 rtamock/README.md delete mode 100644 rtamock/__init__.py delete mode 100644 rtamock/analysis_one_run.py delete mode 100644 rtamock/collect_bins_results.py delete mode 100644 rtamock/collect_results.py delete mode 100644 rtamock/collect_stack_results.py delete mode 100644 rtamock/data/targets.txt delete mode 100644 rtamock/explore_dataframe.ipynb delete mode 100644 rtamock/plot_run_lightcurves.py delete mode 100644 rtamock/prepare_dataset.py delete mode 100644 rtamock/pyproject.toml delete mode 100644 rtamock/setup.py delete mode 100644 rtamock/tools/__init__.py delete mode 100644 rtamock/tools/utils.py delete mode 100644 rtasci/README.md delete mode 100644 rtasci/aph/__ini__.py delete mode 100644 rtasci/aph/irf.py delete mode 100644 rtasci/aph/photometry.py delete mode 100644 rtasci/aph/utils.py delete mode 100644 rtasci/cfg/Config.py delete mode 100644 rtasci/cfg/__init__.py delete mode 100644 rtasci/cfg/config.yaml.default delete mode 100644 rtasci/check_templates.py delete mode 100644 rtasci/degradation_caldb.py delete mode 100644 rtasci/emptyfields.py delete mode 100644 rtasci/makeConfig_runJobs.py delete mode 100644 rtasci/misc/__init__.py delete mode 100644 rtasci/misc/checkPointing.py delete mode 100644 rtasci/misc/chi2distribution.py delete mode 100644 rtasci/misc/compareCtoolsGammapy.py delete mode 100644 rtasci/misc/count_mc_events_from_log.py delete mode 100644 rtasci/misc/datamerger.py delete mode 100644 rtasci/misc/deljobs.py delete mode 100644 rtasci/misc/grbSens.py delete mode 100644 rtasci/misc/multihist.py delete mode 100644 rtasci/misc/phd_leo_test.py delete mode 100644 rtasci/misc/pvalues.py delete mode 100644 rtasci/misc/reformatold.py delete mode 100644 rtasci/misc/save_selected_fits.ipynb delete mode 100644 rtasci/misc/sortObsEvents.py delete mode 100644 rtasci/misc/sphdist.py delete mode 100644 rtasci/misc/splitObs.py delete mode 100644 rtasci/misc/wilks.py delete mode 100644 rtasci/pipelines/__init__.py delete mode 100644 rtasci/pipelines/ctools1d.py delete mode 100644 rtasci/pipelines/ctools1d_blind.py delete mode 100644 rtasci/pipelines/ctools3d_blind_unbinned.py delete mode 100644 rtasci/pipelines/ctools3d_unbinned.py delete mode 100644 rtasci/pipelines/gammapy1d.py delete mode 100644 rtasci/pipelines/gammapy1d_blind.py delete mode 100644 rtasci/pipelines/rtatool1d.py delete mode 100644 rtasci/pipelines/rtatool1d_blind.py delete mode 100644 rtasci/prepareGRBcatalog.py delete mode 100644 rtasci/prod5install.sh delete mode 100644 rtasci/rtapipe.py delete mode 100644 rtasci/simBkg.py delete mode 100644 rtasci/simGRBcatalog.py delete mode 100644 rtasci/simGRBcatalogWithBackgrounds.py delete mode 100644 rtasci/simGRBcatalogWithRandomization.py delete mode 100644 rtasci/simWobble.py delete mode 100644 rtasci/timing/__init__.py delete mode 100644 rtasci/timing/make_sh.py delete mode 100644 rtasci/timing/time_ctools1d_binned_fit.py delete mode 100644 rtasci/timing/time_ctools1d_fit.py delete mode 100644 rtasci/timing/time_ctools3d_binned_blindfit.py delete mode 100644 rtasci/timing/time_ctools3d_binned_fit.py delete mode 100644 rtasci/timing/time_ctools3d_blindfit.py delete mode 100644 rtasci/timing/time_ctools3d_fit.py delete mode 100644 rtasci/timing/time_gammapy1d_binned_fit.py delete mode 100644 rtasci/timing/time_gammapy1d_fit.py delete mode 100644 rtasci/timing/time_gammapy3d_binned_blindfit.py delete mode 100644 rtasci/timing/time_gammapy3d_binned_fit.py delete mode 100644 rtasci/timing/time_gammapy3d_blindfit.py delete mode 100644 rtasci/timing/time_gammapy3d_fit.py delete mode 100644 rtasci/utils/RTACtoolsAnalysis.py delete mode 100644 rtasci/utils/RTACtoolsBase.py delete mode 100644 rtasci/utils/RTACtoolsSimulation.py delete mode 100644 rtasci/utils/RTAGammapyAnalysis.py delete mode 100644 rtasci/utils/RTAIrfs.py delete mode 100644 rtasci/utils/RTAManageXml.py delete mode 100644 rtasci/utils/RTAStats.py delete mode 100644 rtasci/utils/RTAUtils.py delete mode 100644 rtasci/utils/RTAUtilsGW.py delete mode 100644 rtasci/utils/RTAVisualise.py delete mode 100644 rtasci/utils/__init__.py delete mode 100644 rtavis/README.md delete mode 100644 rtavis/cfg/config.yaml delete mode 100644 rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy delete mode 100644 rtavis/notebooks/examples/Evt751.noMoon.thresh10.npy delete mode 100644 rtavis/notebooks/plotSignificance.ipynb delete mode 100644 rtavis/notebooks/plotVisibility.ipynb delete mode 100644 rtavis/readVisTable.py delete mode 100644 rtavis/runCatVisibility.py delete mode 100644 rtavis/utils/__init__.py delete mode 100644 rtavis/utils/functions.py delete mode 100644 rtavis/utils/visibility.py diff --git a/rtadeep/cfg/__init__.py b/astrort/__init__.py similarity index 100% rename from rtadeep/cfg/__init__.py rename to astrort/__init__.py diff --git a/rtadeep/simulator/__init__.py b/astrort/cfg/__init__.py similarity index 100% rename from rtadeep/simulator/__init__.py rename to astrort/cfg/__init__.py diff --git a/rtadeep/cfg/check_configuration.py b/astrort/cfg/check_configuration.py similarity index 100% rename from rtadeep/cfg/check_configuration.py rename to astrort/cfg/check_configuration.py diff --git a/rtadeep/cfg/test.yml b/astrort/cfg/test.yml similarity index 100% rename from rtadeep/cfg/test.yml rename to astrort/cfg/test.yml diff --git a/rtadeep/utils/__init__.py b/astrort/simulator/__init__.py similarity index 100% rename from rtadeep/utils/__init__.py rename to astrort/simulator/__init__.py diff --git a/rtadeep/simulator/base_simulator.py b/astrort/simulator/base_simulator.py similarity index 83% rename from rtadeep/simulator/base_simulator.py rename to astrort/simulator/base_simulator.py index eaba28b..41672c6 100644 --- a/rtadeep/simulator/base_simulator.py +++ b/astrort/simulator/base_simulator.py @@ -9,15 +9,14 @@ import argparse from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation from rtavis.utils.visibility import Visibility -from rtadeep.utils.wrap import load_yaml_conf -from rtadeep.cfg.check_configuration import CheckConfiguration +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) -CheckConfiguration(conf) diff --git a/testing/conftest.py b/astrort/testing/conftest.py similarity index 87% rename from testing/conftest.py rename to astrort/testing/conftest.py index f57cd5b..6e519d8 100644 --- a/testing/conftest.py +++ b/astrort/testing/conftest.py @@ -12,4 +12,4 @@ from os.path import join, dirname, abspath @pytest.fixture(scope='function') def rtadeep_configuration(): - return join(dirname(abspath(rtadeep.__file__)), 'cfg', 'test.yml') \ No newline at end of file + return join(dirname(abspath(astrort.__file__)), 'cfg', 'test.yml') \ No newline at end of file diff --git a/testing/pytest.ini b/astrort/testing/pytest.ini similarity index 100% rename from testing/pytest.ini rename to astrort/testing/pytest.ini diff --git a/testing/test_rtadeep/test_cfg/test_check_configuration.py b/astrort/testing/test_rtadeep/test_cfg/test_check_configuration.py similarity index 93% rename from testing/test_rtadeep/test_cfg/test_check_configuration.py rename to astrort/testing/test_rtadeep/test_cfg/test_check_configuration.py index 0eb735a..fdc053e 100644 --- a/testing/test_rtadeep/test_cfg/test_check_configuration.py +++ b/astrort/testing/test_rtadeep/test_cfg/test_check_configuration.py @@ -7,8 +7,8 @@ # ***************************************************************************** import pytest -from rtadeep.utils.wrap import load_yaml_conf -from rtadeep.cfg.check_configuration import CheckConfiguration +from astrort.utils.wrap import load_yaml_conf +from astrort.cfg.check_configuration import CheckConfiguration @pytest.mark.rtadeep_configuration class TestCheckConfiguration: diff --git a/astrort/testing/test_rtadeep/test_simulator/test_base_simulator.py b/astrort/testing/test_rtadeep/test_simulator/test_base_simulator.py new file mode 100644 index 0000000..912c860 --- /dev/null +++ b/astrort/testing/test_rtadeep/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: + + def test_base_simulator(self, rtadeep_configuration): + + # get configuration + configuration = load_yaml_conf(rtadeep_configuration) + CheckConfiguration(configuration=configuration).check() + + diff --git a/testing/test_rtadeep/test_utils/test_wrap.py b/astrort/testing/test_rtadeep/test_utils/test_wrap.py similarity index 91% rename from testing/test_rtadeep/test_utils/test_wrap.py rename to astrort/testing/test_rtadeep/test_utils/test_wrap.py index 8a1bd3d..5eefc31 100644 --- a/testing/test_rtadeep/test_utils/test_wrap.py +++ b/astrort/testing/test_rtadeep/test_utils/test_wrap.py @@ -7,7 +7,7 @@ # ***************************************************************************** import pytest -from rtadeep.utils.wrap import load_yaml_conf +from astrort.utils.wrap import load_yaml_conf @pytest.mark.rtadeep_configuration def test_load_yaml_conf(rtadeep_configuration): diff --git a/rtasci/__init__.py b/astrort/utils/__init__.py similarity index 100% rename from rtasci/__init__.py rename to astrort/utils/__init__.py diff --git a/rtadeep/utils/wrap.py b/astrort/utils/wrap.py similarity index 79% rename from rtadeep/utils/wrap.py rename to astrort/utils/wrap.py index 1fa4b60..0d32fce 100644 --- a/rtadeep/utils/wrap.py +++ b/astrort/utils/wrap.py @@ -7,8 +7,10 @@ # ***************************************************************************** import yaml +from astrort.cfg.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 \ No newline at end of file diff --git a/rtamock/.gitignore b/rtamock/.gitignore deleted file mode 100644 index 4cbb31b..0000000 --- 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 2c269ef..0000000 --- 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 82e440e..0000000 --- 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 bce71c8..0000000 --- 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/__init__.py b/rtamock/__init__.py deleted file mode 100644 index 6069d3d..0000000 --- a/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/analysis_one_run.py b/rtamock/analysis_one_run.py deleted file mode 100644 index e1b8641..0000000 --- a/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/collect_bins_results.py b/rtamock/collect_bins_results.py deleted file mode 100644 index 3675408..0000000 --- a/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/collect_results.py b/rtamock/collect_results.py deleted file mode 100644 index 67018d0..0000000 --- a/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/collect_stack_results.py b/rtamock/collect_stack_results.py deleted file mode 100644 index f97f819..0000000 --- a/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/data/targets.txt b/rtamock/data/targets.txt deleted file mode 100644 index 0c86a50..0000000 --- a/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/explore_dataframe.ipynb b/rtamock/explore_dataframe.ipynb deleted file mode 100644 index c33fd60..0000000 --- a/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/plot_run_lightcurves.py b/rtamock/plot_run_lightcurves.py deleted file mode 100644 index 76ec709..0000000 --- a/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/prepare_dataset.py b/rtamock/prepare_dataset.py deleted file mode 100644 index ea7b458..0000000 --- a/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/pyproject.toml b/rtamock/pyproject.toml deleted file mode 100644 index fa7093a..0000000 --- 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/setup.py b/rtamock/setup.py deleted file mode 100644 index 41a7d22..0000000 --- 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/rtamock/tools/__init__.py b/rtamock/tools/__init__.py deleted file mode 100644 index 6069d3d..0000000 --- a/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/tools/utils.py b/rtamock/tools/utils.py deleted file mode 100644 index de05fbc..0000000 --- a/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/rtasci/README.md b/rtasci/README.md deleted file mode 100644 index 1ae0de5..0000000 --- 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/aph/__ini__.py b/rtasci/aph/__ini__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtasci/aph/irf.py b/rtasci/aph/irf.py deleted file mode 100644 index 16364de..0000000 --- a/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/aph/photometry.py b/rtasci/aph/photometry.py deleted file mode 100644 index 4d1887c..0000000 --- a/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/aph/utils.py b/rtasci/aph/utils.py deleted file mode 100644 index 4b1b039..0000000 --- a/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/cfg/Config.py b/rtasci/cfg/Config.py deleted file mode 100644 index 39a377e..0000000 --- a/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/cfg/__init__.py b/rtasci/cfg/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtasci/cfg/config.yaml.default b/rtasci/cfg/config.yaml.default deleted file mode 100644 index 5a50fa7..0000000 --- a/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/check_templates.py b/rtasci/check_templates.py deleted file mode 100644 index e8353df..0000000 --- a/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.utils.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/degradation_caldb.py b/rtasci/degradation_caldb.py deleted file mode 100644 index 0ea5234..0000000 --- a/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.utils.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/emptyfields.py b/rtasci/emptyfields.py deleted file mode 100644 index 48d2211..0000000 --- a/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.utils.RTAManageXml import ManageXml -from rtasci.utils.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAUtils import get_pointing, get_mergermap -from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw -from rtasci.utils.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/makeConfig_runJobs.py b/rtasci/makeConfig_runJobs.py deleted file mode 100644 index c93decf..0000000 --- a/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/misc/__init__.py b/rtasci/misc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtasci/misc/checkPointing.py b/rtasci/misc/checkPointing.py deleted file mode 100644 index 674c177..0000000 --- a/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.utils.RTAUtils import get_pointing -from rtasci.utils.RTAUtilsGW import get_offset -from rtasci.cfg.Config import Config -from rtasci.utils.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/misc/chi2distribution.py b/rtasci/misc/chi2distribution.py deleted file mode 100644 index 2c3a221..0000000 --- a/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.utils.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/misc/compareCtoolsGammapy.py b/rtasci/misc/compareCtoolsGammapy.py deleted file mode 100644 index ffac736..0000000 --- a/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 utils.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/misc/count_mc_events_from_log.py b/rtasci/misc/count_mc_events_from_log.py deleted file mode 100644 index bc8c856..0000000 --- a/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/misc/datamerger.py b/rtasci/misc/datamerger.py deleted file mode 100644 index cd7aec1..0000000 --- a/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/misc/deljobs.py b/rtasci/misc/deljobs.py deleted file mode 100644 index 7e66ecb..0000000 --- a/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/misc/grbSens.py b/rtasci/misc/grbSens.py deleted file mode 100644 index 333096f..0000000 --- a/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/misc/multihist.py b/rtasci/misc/multihist.py deleted file mode 100644 index ecb78fe..0000000 --- a/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.utils.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/misc/phd_leo_test.py b/rtasci/misc/phd_leo_test.py deleted file mode 100644 index 393fafa..0000000 --- a/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/misc/pvalues.py b/rtasci/misc/pvalues.py deleted file mode 100644 index 0c07b9f..0000000 --- a/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.utils.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/misc/reformatold.py b/rtasci/misc/reformatold.py deleted file mode 100644 index 52cc1f8..0000000 --- a/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/misc/save_selected_fits.ipynb b/rtasci/misc/save_selected_fits.ipynb deleted file mode 100644 index 996e049..0000000 --- a/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+XlZX5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\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/misc/sortObsEvents.py b/rtasci/misc/sortObsEvents.py deleted file mode 100644 index 00caff4..0000000 --- a/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.utils.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/misc/sphdist.py b/rtasci/misc/sphdist.py deleted file mode 100644 index 173183e..0000000 --- a/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/misc/splitObs.py b/rtasci/misc/splitObs.py deleted file mode 100644 index 7bcfd92..0000000 --- a/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/misc/wilks.py b/rtasci/misc/wilks.py deleted file mode 100644 index c808901..0000000 --- a/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.utils.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/pipelines/__init__.py b/rtasci/pipelines/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtasci/pipelines/ctools1d.py b/rtasci/pipelines/ctools1d.py deleted file mode 100644 index b24a2f9..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis, onoff_counts -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.utils.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/pipelines/ctools1d_blind.py b/rtasci/pipelines/ctools1d_blind.py deleted file mode 100644 index 5af8d9b..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.utils.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/pipelines/ctools3d_blind_unbinned.py b/rtasci/pipelines/ctools3d_blind_unbinned.py deleted file mode 100644 index 72d429f..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import phflux_powerlaw, get_pointing, get_mergermap -from rtasci.utils.RTAUtilsGW import get_alert_pointing_gw -from rtasci.cfg.Config import Config -from rtasci.utils.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/pipelines/ctools3d_unbinned.py b/rtasci/pipelines/ctools3d_unbinned.py deleted file mode 100644 index 74331df..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import * -from rtasci.cfg.Config import Config -from rtasci.aph.utils import * -from rtasci.utils.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/pipelines/gammapy1d.py b/rtasci/pipelines/gammapy1d.py deleted file mode 100644 index 4e33688..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAGammapyAnalysis import * -from rtasci.utils.RTAUtils import * -from rtasci.utils.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/pipelines/gammapy1d_blind.py b/rtasci/pipelines/gammapy1d_blind.py deleted file mode 100644 index 5bc0ed6..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import * -from rtasci.utils.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/pipelines/rtatool1d.py b/rtasci/pipelines/rtatool1d.py deleted file mode 100644 index 9638e6d..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import * -from rtasci.utils.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/pipelines/rtatool1d_blind.py b/rtasci/pipelines/rtatool1d_blind.py deleted file mode 100644 index 6185c82..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.RTAUtils import get_pointing, get_mergermap, phm_options -from rtasci.utils.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.utils.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/prepareGRBcatalog.py b/rtasci/prepareGRBcatalog.py deleted file mode 100644 index 5ab9870..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.utils.RTAUtils import get_pointing -from rtasci.utils.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/prod5install.sh b/rtasci/prod5install.sh deleted file mode 100644 index 7c44ba2..0000000 --- a/rtasci/prod5install.sh +++ /dev/null @@ -1,10 +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 -unzip cta-prod5-zenodo-fitsonly-v0.1.zip -csadd2caldb -cd .. -rm -rf tmp diff --git a/rtasci/rtapipe.py b/rtasci/rtapipe.py deleted file mode 100644 index 4473675..0000000 --- a/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/simBkg.py b/rtasci/simBkg.py deleted file mode 100644 index c907ed0..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation -from rtasci.utils.RTAUtils import get_pointing, get_mergermap, str2bool -from rtasci.utils.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/simGRBcatalog.py b/rtasci/simGRBcatalog.py deleted file mode 100644 index e6706dd..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.utils.RTAUtils import get_mergermap, get_pointing, str2bool -from rtasci.utils.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/simGRBcatalogWithBackgrounds.py b/rtasci/simGRBcatalogWithBackgrounds.py deleted file mode 100644 index 55aee72..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.utils.RTAUtils import get_mergermap, get_pointing, str2bool -from rtasci.utils.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/simGRBcatalogWithRandomization.py b/rtasci/simGRBcatalogWithRandomization.py deleted file mode 100644 index 81cad39..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.utils.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/simWobble.py b/rtasci/simWobble.py deleted file mode 100644 index 264976f..0000000 --- a/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.utils.RTACtoolsSimulation import RTACtoolsSimulation, make_obslist -from rtasci.utils.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/timing/__init__.py b/rtasci/timing/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtasci/timing/make_sh.py b/rtasci/timing/make_sh.py deleted file mode 100644 index b92c83a..0000000 --- a/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/timing/time_ctools1d_binned_fit.py b/rtasci/timing/time_ctools1d_binned_fit.py deleted file mode 100644 index fb82d7f..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAUtils import * -from rtasci.utils.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/timing/time_ctools1d_fit.py b/rtasci/timing/time_ctools1d_fit.py deleted file mode 100644 index f57b22b..0000000 --- a/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 utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from utils.RTAUtils import * -from utils.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/timing/time_ctools3d_binned_blindfit.py b/rtasci/timing/time_ctools3d_binned_blindfit.py deleted file mode 100644 index 3b01303..0000000 --- a/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 utils.RTACtoolsAnalysis import RTACtoolsAnalysis, make_obslist -from utils.RTAUtils import phflux_powerlaw -from utils.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/timing/time_ctools3d_binned_fit.py b/rtasci/timing/time_ctools3d_binned_fit.py deleted file mode 100644 index 93f79dd..0000000 --- a/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.utils.RTACtoolsSimulation import make_obslist -from rtasci.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAUtils import * -from rtasci.utils.RTAManageXml import ManageXml -from rtasci.utils.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/timing/time_ctools3d_blindfit.py b/rtasci/timing/time_ctools3d_blindfit.py deleted file mode 100644 index c17b250..0000000 --- a/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.utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from rtasci.utils.RTAUtils import phflux_powerlaw -from rtasci.utils.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/timing/time_ctools3d_fit.py b/rtasci/timing/time_ctools3d_fit.py deleted file mode 100644 index b847c6c..0000000 --- a/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 utils.RTACtoolsAnalysis import RTACtoolsAnalysis -from utils.RTAUtils import * -from utils.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/timing/time_gammapy1d_binned_fit.py b/rtasci/timing/time_gammapy1d_binned_fit.py deleted file mode 100644 index 9e070a5..0000000 --- a/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/timing/time_gammapy1d_fit.py b/rtasci/timing/time_gammapy1d_fit.py deleted file mode 100644 index 9412c38..0000000 --- a/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/timing/time_gammapy3d_binned_blindfit.py b/rtasci/timing/time_gammapy3d_binned_blindfit.py deleted file mode 100644 index 685de6e..0000000 --- a/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/timing/time_gammapy3d_binned_fit.py b/rtasci/timing/time_gammapy3d_binned_fit.py deleted file mode 100644 index ed4431c..0000000 --- a/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/timing/time_gammapy3d_blindfit.py b/rtasci/timing/time_gammapy3d_blindfit.py deleted file mode 100644 index b44d99c..0000000 --- a/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/timing/time_gammapy3d_fit.py b/rtasci/timing/time_gammapy3d_fit.py deleted file mode 100644 index b09f835..0000000 --- a/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/utils/RTACtoolsAnalysis.py b/rtasci/utils/RTACtoolsAnalysis.py deleted file mode 100644 index a64ec40..0000000 --- a/rtasci/utils/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/utils/RTACtoolsBase.py b/rtasci/utils/RTACtoolsBase.py deleted file mode 100644 index 8540f9e..0000000 --- a/rtasci/utils/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/utils/RTACtoolsSimulation.py b/rtasci/utils/RTACtoolsSimulation.py deleted file mode 100644 index bf30779..0000000 --- a/rtasci/utils/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/utils/RTAGammapyAnalysis.py b/rtasci/utils/RTAGammapyAnalysis.py deleted file mode 100644 index 796ec68..0000000 --- a/rtasci/utils/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/utils/RTAIrfs.py b/rtasci/utils/RTAIrfs.py deleted file mode 100644 index 7532b49..0000000 --- a/rtasci/utils/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/utils/RTAManageXml.py b/rtasci/utils/RTAManageXml.py deleted file mode 100644 index 4dde844..0000000 --- a/rtasci/utils/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/utils/RTAStats.py b/rtasci/utils/RTAStats.py deleted file mode 100644 index f46bee1..0000000 --- a/rtasci/utils/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/utils/RTAUtils.py b/rtasci/utils/RTAUtils.py deleted file mode 100644 index 95b62d7..0000000 --- a/rtasci/utils/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/utils/RTAUtilsGW.py b/rtasci/utils/RTAUtilsGW.py deleted file mode 100644 index 9d0bd89..0000000 --- a/rtasci/utils/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.utils.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/utils/RTAVisualise.py b/rtasci/utils/RTAVisualise.py deleted file mode 100644 index aa1630b..0000000 --- a/rtasci/utils/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/utils/__init__.py b/rtasci/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtavis/README.md b/rtavis/README.md deleted file mode 100644 index 6bb0c15..0000000 --- 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/cfg/config.yaml b/rtavis/cfg/config.yaml deleted file mode 100644 index 9cc5123..0000000 --- a/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/notebooks/examples/Evt343.noMoon.thresh10.npy b/rtavis/notebooks/examples/Evt343.noMoon.thresh10.npy deleted file mode 100644 index 757455db5636d6267f60c0ce00e10c01ff4a7fe2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26505 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1Jm(9}_=RiF%TH83aVmF5;y>LuqFrRwFD=9FY678NB{a>W;=Cg3us{4EOE zJ3HD7If4o~HN2T47#SECY6`jh{QSKB|Ns9VOnBRu6mlnZ#uoC_7V<`LFfcHpaBV2j3_EjPRvOx;wn_kfCP1#MWGTXsFi~XRbWAFQ>cm>)C?tsYDvzZ zFy1ZBVCPV%-d3m)RH&Jun4tm^ZMe(L;NnoI)mEq-RHy?H?I`6b3~vl@DAa8$)C(%q zhltL(uu>#zYP3V4L0h3=P@xe-H16_SzKFA_4u!^Tg(g9TrVvpp1$MEUrUed#W^IM$ zL4_6&(O0Vdo%bJ3NxHnaPQH$*G0zv4tKQ5Y-Q>MAo@KWN|3;Y%BB%D)fek?vQJJ z9k@f&q0pzT&^M^i4f+V`H!l5v< ztuQR8FdQPP8Pk90Yfqa)VMJSDWKdxgMD&|RO#h*yeGY}uZG|yGg|QG(1`yzyfZVO*Fr)upp?g5KXkGt*|(#umnxCw5_l#sIVMOw4$xB zGN`Z$O|-hLuqLRm7EQFSt*}0*upu>qlYxODKQBHxzcjC;xUey{unEO$3J!(MZG|mC zg{?>iG=w@7wzULML{-vd z9Sdi-70wANoSPcK1@c;28q9C=VhiV^_)X2Ba6wz)!l1%M2oo4U*>rJR;gX=jrAVS6 zA1`YwTpm=o0!h>%(V=iYuXCe1{JPD65UYjP`JLWa6?exMkLXK zYKOv2ZH1eI3b!DMax^&RiA;m)AKU65>QT%4Jnn^?Fzws4O| zhJJ=0MAQ@_x;M6PA4Ie?LXd%hAuY40xFjB2xg zFe*G;TX;kxLnOBFs73@YqVzX0h&MIJC_IKm>^K&&6IjGfVi7xqMeH;du`^i2&SDWe zhehl>7O@Lh#4chHyM#sTG8VBbSj4Vk5xa&gW(En<>sZ8YU=h2CMeG(9vD;Y0?qCtS zi$&}n7P0$S#2#P~dx%Bs5f-t>sTuOIg->7^Q=lgQ&&p2@g-_cGp9K{@hbB>hn*2Gd zJ~1sW}xc-wv;k&lN_d$gpAfm6X zu}d*;{^U^jv90h^P~m5YsA9U9d(e(g4uxOZ3cm&weuIdrzwh+@yYG`j;rF(}A3=pb zA)>1rcrNfC|Kw2ktF7>NP~jhlC{NntBa<(Dawz=UR`@Td@IORU^is!ziMKyF6frO| zFtimhGBPj(6)`a~Fd(D_Uwm>XVg^gIfTdaC(x*&vc1eBvv~Rfx#nkwB7U&609aZOE^Sop zwuRU3vqO;(SXvk?EdrOmcQ3+0BIL6}ktkSN3@j}Umu3jaG})i?*`Y`REG-F^mV!%5 zh3rh0Yy0d_Bn_690ZYrmr9Z2@J)^tuvqO;_SXv$|tpJyne6mU6MT%f)C9t$I zT$<(0jRV>w%^9;nHXB9AY|<{>7ol04!|?mNtS*`zjpw`PcWwp~x64 zZ331yg-e&TO+UrDw=X)Ca_HC#F|o?}`S|5t}1 z8?dx3SlW(}fgv@+DYnR-k%2)Y<5kAnj1L)~Grnc~%=nY>KMRx;vp@+h3zV#~K#3;{ zlti*X2_Xv<-C3YW%>qST7AS(UK+%u|3hXRUkY<4bE(;VqS)f430(mtH#a7A1)1U z^5sl%C<*{e2ZE)8;L;$M1!hcfC<+Ekhk&I+;nJYS9z)6$hoUgBbU0W#0xn%>%XNKm z{1k_xNU(GiSUMUm4Q|{roS0ZYe%rQ_hzV3&tZaVUxhODBM(6XDWey91^;6eWSB zlflv{aOrjK535AHrZ^O(f~C{I(&=z%ux^(r4n-MY=}fS67F>GP%$YN1+D>sO$_7j4 zfTeTc(p=XU7ucFlaVW|IOXq{73*gdVmm5xTC@KU?7lEaV;nHa!pf$yzs01us3YIQ| zOM`m?An9_jbOl(t5-tsD@0 z6o;azVCiXK>FIE3u;C!-8DQy|VCh+KX>g!|Vr({8dJb56E;z5I#1_p%6KvVRhZJN!P0BM(rYoL*MX(i zgQYiMN^b;9Zvsni#+2Rymfi}M-i9f?9W1>AEWHy`dKXxFH&}WPru1I0^ggikeoW~D zVCjQk=|h;(hr!ZEz|u!CrH_H7kAtO8U`n3^OP>NupT?9v1C~AumOh6meI6`*0W5tH zQ~DBE`Z8Gh3a0c`u=F*s^mR<>8(`_1VCh?!(zn6VcfitjF{ST;rSF5KA7Dy91WP{x zOFzbxegc+$3YLC`Dg7KQ{Q@lg5>xsWSo$?s`VFS^Td?#yu=IOO=?`G(k6`Ifn9`rY z(qF*RUooY>fu+BLrGJ3)YGZ8CPt?46CSBIC=oeVwZ?M8YDCR_D$~qSP1xx<}OaDia zR>_fdEMi~+H)ff@jaepGV-}RtPvpxw7BPdRS-{e)DAGBlvW`V;U}<)+GzW^bO^vK$ z5hqxh3oOlzBJI&4>sZ7CmgWUZ^Pxz$^vOCF@q?uWz|w*!(lN7S9gBp((!yYA5mf2r zvW`WfU}-V1v^a|No^7&@MG|0XNwBmOinPjcS;r!2u(S+VS{7CMrmSO;99UW&EUkbd zedCR+W04|QS_v$zj3Rx7NzSoI1uU%!mR3WNejzRASfmb?)&NUuqDbE{m2)i80!wRy zrFBrGBP`_{i*&)#dSGdN6zL9IImaRcu(Tmq+6YDZjH8@mkug}>1T1ZeBAwzU=U8M0 zmNo}VTcAj@c*{8!S%Rglz|z(z(rf(X9E)tg(zak}I~3^!A##pI_F!oTu(TtJv_+Jh zW04bB+8HeEf+D>pUe2+|6)f!rmUc&x{*Wr?SmXhg_5@3Np-5Zg$T=2ygQb1I(!MCt z0)=vpMSfsuf3S1_iu9W@ImerbAI8 zSUL$Tos23SGvA>o1uUHkmQF*J)?4gQln$28083}0O4}@VD9Qp$XM?44P^FpHI27fA zrSrhj`KZ!6H#ig(fTat;(nYA!CpJ426@#Tqz|y6t(q`Koips#!$AC|V4bUILb0iXz=maMGb@8CZHbSb7DD zG{dHo4n-@$(yPGIt5KzyPB|2<0ZXq1ORodx)!ng0>%n>TRTijUnwqgUwrB&gKq=Db zsd3RpCI*JuqD^20BC$oAL5v8DVbr26ObiTIgtuZB-iBRxJ9gn6*oAju7v6A@ zK8;=Y40hqO*oDtw7e0?&_yRb6%f}X71SL}Fz>USnXPWn!9g8l3Gs0zXM!15=Cl(){ zRf43if~Bv4rLV)K`x_Mny&f<-7To|#-vmqFf=gR{EuFmPA+uxAZLstmu=HKHbg8tt z&DzJzjz#yt()YpA58%=hlJ{K-dB*Hm^bjol2rT^=EegT$#372MlTr;us6SHH{E3ouyu=E?aH0R9n`Cq;>I~KhKOTPn4 zzekZa`N!;7^Z_jW5iI=)E}iU^$z#dN;#l+Lb_A1uwl z3~I_VGQ*ql8W#*0tXUk3n3zE=eP*yU3tU?8){B`p-B=upSi#b4U}<)^wA`HuhMmDI zjzt_`X-=>-7hJmgh}JWKL>9*)Zm={DSeh3uEw_ZXrzoGrv4{^W%@39qfJ=+4PWM^5G*YOmKKIfzcLo@I@QPGSR?|L76nU-!KHuSjFUSupT)6A94svXmX?G|r=LF~ zKW`(8W04eCS{f`Z1DCE?Aj%kZgvGH)7A!3XmX?Q0TO1Ns`+1$ku}A?dtq7J@f=hdU z{r{xq4U1!uGFVy#EUgNcey8l){)~~;u}BRptqzvfUR6-&cCpFg1}q&5mj*Ag z07=JzrQ^ZU321?oc4qO_Xm_X9GVCg)tbUs`f?6EhK9f}IT(uH8@BDgfzV<72buyhGnx)d%A z9+L%0mw~0r!O|6QX|TsY(v@K8DzJ1lTpBd5Q3aB&0ZZ3{rR(6*pvi**kaRs*x&bWR z2$u#Y2at3VSh^W3-2%>xDX~SZh|CD?T7rgb+rSFj!3sOz=D?*p!O~q|>26Hv9t8=Sb8;BdJU%ZTCnswu=ILN z=?!4%jbQ0bn9`fU(p$jNTQQ}#fu*;DrFURT?*vQl0!#14l->iD-V2uAhbg@uEPVhh zeGpUn5Lo&!So#R2^ii<%F|hP;Oz9I~>62jTQ<&1H!O~~I(q}QH&w-`SgQYKEN?!y^ zUjj>C#+1GSmc9y>zJ@7%9V~qVEPWGG`W9IFHdy)&I5RfJ7Trb7j5_6Vjz#yt3h#pz zK0q<&1xWfKSo#rI`Z0=hK&6~x(G#%rQ?T?i6zQI7Ime>sVCffN>6a+dE9&GNi(Y}H zUxTIJph(L!$vGCi1xvpJOTR~vu4$8VEcyVJ{s@--gd+W;Th6iQGg$fySo$l9^pr_* zjz! znSm9WgB4n!n8RRq%Av>-ENumrwnmX|=sx98WCNDA1xwqZO8-0MP-G95b^uE|qDV7L zIPFm61eSINOS_;-d!KPAas^Affu-G1r8Ukv6nTK9J;BmmDAEnu=NyW>!O}ipXTuyg>5v_r%NhoV5RbP!lN7)APk^hJlF5U_M8SULbOcyB z5=HvJvr7&|QDEt4uyhQHG{fi14n?tG={T@-JgT(hRfnPkuyi6=ItfMkfZlb7qGYgi z3RpT7McTpnxD?7r?$R0@_Z151~qNIP7=?od<#maYU#SD{EV ze7WvWR1KD{0ZZ4SNH+-Ha44z+OV@*?8&ISj3~o3SHG-v^z|zgA(*8Fbidw+ZtzhXk z6lsRM8xBS7VCfF9bSH|m!?GI=MO|R&Zm@I@I5X~!E$RhjMo32&)I9_btoEUam6n1x zEClD5mShz5vw((HCxDb74Y?W?O$3Wi0*Pmc#1>5kF~Iu)z}n#Z7K)~@Ffd>fpNd0# z8V>R4IK*e*5TA)dd=?Jz**L`K;1HjSLwp_%@%cE!7vK%r0+z|tGx(y+}PMVr9Vo59js;L@;N9z|Qh(%Zn&+u_o%?H)xtz|uRx(!1c& zuzepzyTQ_Xz|wo+(y$F6Mf5wF;S(DB7C98%1WVrnOW%e|H{a&>uBu<^P;>_@eHSc!4=%kS-e%&J(iILx_rcN+ zz|s%l($5~*RPS22+M(zXSo$$o`UzZ`|KGNS%bC|Z6g>q?KLblYhfAjxn)h<}ZFDGl z0hWFVmVO17miRE8f3xXkhoaYD={I2Mw{YoaS($zumRlW)-hrjxgQY)!leAN8(MM2{ z&H|;(EKvH(0;RGnP+H0YrI;*Gy2t{>eikT>vp_MK1&XgMP^@Hu!afTW!dakj%mRg0 z7AQ=zKt9g``7aCPi!6{^vp_D&0@(}8Ko9Hn-|lcI`UG~?XRx!rfSu(YTl5v=EQmtz zV!N4~4n^O<3crID{(yTAye2J1$D!yaSo#-O`Zrt}Jnp0F>rnIuEd3WO{SPh;*8L{c zq3AzYnt>JECt!v33Bb$c`YRoZm{`Gm0=H2d4#0r*X152~RrNOI45BED1 zae$>c!O~oCY4DiS!buKA++b-Qurx1R8a(EdFvX#W4=l|OmKK0ZgNKDyPIV{}1WOBn zrG??rpv9AE^QSo!iGZa=!O~)IX>g!|vXVGhS^_LB36};BXmQPOD3St8OM|6l;L_mr zuCr!16v=|6<-pSNaB1*Z5zkDAA_cItB3N1pE)5>3E1v04qzsl;0ZXgGrNP6#=Vv+; zsez@{!O|LVY4GT%^el%WO|Y~USXvt{4IU0lo8?fX1D4hWOY6aPiAiX-Ly;+1+6*jh4wnYo9X{Kk$O0^F36{2E1@$OW zVvDR7mly(J6yMd+MF{M4g z(w<;xFHC7~u(S_Y+80yW4=n8umJYy_4g^aFfu(~nr9;5dp1wca4OqGs zQ@Rc;T@RLS04M3j*rG=CB(3I9)C5-83|82JW=^C-Q7c%w4J_S`Ds3^*p{N5a-3gZN zLXmDLKj=`@4VLZ!OZTEkJE%N&DCz@C_k*P;ph$lZVs0_ z^i;6)G!$u;ND0TH>0s#@VCk7C(o@uA9gAjxrDubs=b%XERVg?Y%>_%(153|Gk*@ip z=vcG>EWHpcy$D5`$5Gj_XfarN30Qh5igZq-ieu3-u=H}U^a>Q|st{GjqLpCjRbc7W zDAGcTYK}!~z|w2M((6#9OYW;V7Oe+MZvabgM3LUrqwZL=2`s%CEWHIq`iO#tW6@Ty z^fs{cb`)ue%^HqHJHXOA!P2`>q-FFp9gB8@rT2iP_o7JqE!T7`+6R{250*ZFBK=87 z%dzMnSo#oH`Y?+0zPVbCMMuEWN5Rs^z)5;uY|(M_B(396bONmKBv|1o6mu9DTpfx| zgQd@arO%>D9|(3RItP|M50<`wCLQlkbP+6l2`qgXO*+$|=n7c+Dp>j&s&qqz|yzD(sxj$Uoveh8L+gev``+o9+& zSo#TA`YEb(%tVKxXJF~)VCfgA(m$p+6uksXzXD6YMwMon=}`0rEd3TN{SH;SV6H>a zd$9Beu=Gb%X`Y1+MW4XZpTW{!P^ITAb}0G^mi`8o{*EeLw#=dE2Uz+iSo#;Lblysb zqTgWYKVa#(P*o7sq3rk`bmclM9ja^seXE_Pu(?85rsge@OiWB^KbkWL~)LH^;3 ztzR6948h6P2%Kz<5gDW)|8Qa37l$Gfu(T;y+6*o&%X~pxsN;)6kvUk}0xWF_mo9l# zA#=U!i$jqWSlSvaZ3CBnW%fJw251APEm+zPENu^$Ry@u*TYbtGhav~Cv?EyB2`;Vf zU%T+qtS=5l&R}U5u(T^&di8_<%1;-4aVT;FOS^-mJ>b$j$J!(^*L-m(@&rqJfu+6S z(xOH+Ch6P1I28GSrG3HDekjt<4}Wne@&`)?fTaWB(x;}x^L@DZ#i1w&EFBD%4uMOz ziFb*1JpAHN6bhCO151a)rP*sG#I}C=;!qR;mW~8VN5Q4kss1D1}3 zOB>y`OqAXBa z$pWQ_EKs~>fnqlc6o*-$n92ghM;0j5vp`{+1q#6|P&j3QLL&?0=PZ!VvOw<70=Y5^ z%2hH)43MKz@Zb_i_k6JQ0BV5_C2(oD z$CiSnmw~01!=>SduK-K01WT`iOM}NjKz6SNORoV-uZ2s4kJt&D?NGE1EWI8qy#X!_ zUSkH5-Uyc71eV?mmj(~ffTXv8rMH5mx51^sp$3xP4wl{lmfi`M1`iK~&UPr;1(x0o zmfi!G1`p_gr1yfQ_kpGN!==H7gQO3Dr4NFo55c9uhKI~{C^`(5J_43L3YP|t9)YBf zfu)awrBA@6!ATJ$eG)8v3M_pZE)BLDBz*=feHJWz4lWH2)Zp0;Md!iN7r@dN;nLut zNRad;u=HiH^cA=?*xw-Ot6=GCVCn1N?35B)bOVu{!0lAfGO(Lqg}1;8Z^O-jOWy%Y z-vvwG!<4=cmVN-1euydk2rT^=Ed2yi`YBlY8Cd!`rt}N2^h>bxD@^IvVCgqt>9?5D z@4(XU!O|Zvr9XnDKY^t`V@iJkOMeARf5Vji4wn7_mi~z;{R=Gp8!Y_?Q~EDh`X5;O zKc+MTJE-fx$PVf{FtMZbEI={F43=gAOS581vw@}A!O|R<(wtyvF0eE=rZf*&nink1 zhbheumKFd@3t~zOfu)7P(ju7BqF`w;u(UX)v;0_mH|u4VoJ+_rRBlW z3YgN0U}+_=v@)i&3RqedEUktqtqzvf084AKgF0M|u|-X@eE&fEDVZ znDYfBtp}FY2TL2ENE^)4ax5|gOB;cujZvg0fTT^p(xzZ(GZblt`C5)e=3r?Hu(TzL zbR9_A3M_36mbO8WR$8FtSY!*9wgXGsqe#C4Njre09l_F0sL~6y9E+U6(k@_WR}|?V zi?kez+`!WAU}+B&X{#k#jzykeX)my}H;Qz|QZ2_KAF#A9SlSOox?`D^W05~tIshyk zh$4M#xt3#55Lh}GEFFR(Ew)n2u_zQQ9R`*TN0F{trR7)z>JPUSMS`WHP^AB@)^aS0 z2J4OiOUI%}Pg<+xSQH1Ajt5I8ph)Yi*K#aM1WPA@rIS&l|7_55EJ^`Or-G%^P^1rR z(sC?H2TNywr87~aKWx!*EXo2)XM?44P^5ot({e1z1xx3FrSnmw&30-z78QV{3&GMw zDAFCfwH%9z!O|sQ=~5JFrhQtDMP*>=a4PVCgEbbTv3T&5JFnLCsDK zU$!_D)q)k)ffd%Hn8WaUi$hTZSh^7`-GnO5vDKld87$obmTpCrmfPx3)CQJr2TOOL zN}F$WDCz`DcY&q5QKdt-Iu!MQrF+5BeW=o9TOEq}!O|1J(i2goXKi&Tngo`f43?gP zDt&0HL(x>Q^fa*abX4h2TOEpKfTd@GrDvf^t8H^Anhlno1D2kPDxI>;p=cghdOlcs z0jl)MZ4N~X!P1Ms(u+~0KW}p=S^}0{3YK1mD($-6p=dc+dIeZ|C93rN?G8n&z|yP1 z(rZwq8Fn}ntp!W3152+*mCoGZP_zLoy%8+E303;~4u_)6VCgMj>8+^JUOOF%wt=O$ zgQa(%O7Gk0P_z>)y$dY88&%qBmqXDWu=HNA^gdMSZMz(b_JgGlfTa(jN?YxAC^`g| zJ`9#Vf+~Grw?olUu=Fvo^l?;aw>=I;C&1Dt!P2KtrC;uGC^`+6J_D9M3(iivV~fs# zvJ<462kIt)hXc=}iJ@*(y}%9{?Yjt40~+nS1Y%$r?Yqnl-KdH>+II!J@Kx->*RTs; z$1Z#WyYNly!nd#s-^MO{2fOfH?85i33*W~s`~bV~L+rwjunRxNF8l<$@Kfx<&#((W z$1eN=oQULOi(Z1#0Hg!XFo7+!>pY8N(JOFjdks!)ZxD%b0$b=Ukn~%y^gFQhd$@G} zO|$w{7g!vNK7ge^f~7yfrLBCs8F?>(_CtcDzksE`!lg@#eOB>a0qutbOMeGT|A0$R z*nQby>ow4RNU-!Tu=H=Z^yGTpBV4yY`ys*7f5FoK;L@Qd{8&Zqf%Zd!r5QLt?QTX6 zSi76mdeZ-^k3jn&IY7;FX0S91T$=M^4{zuT(0)j;G#gl&9YuQTJJ5beurw!FnhP$S zyr`~f=2y^uNU$^ySeh3ueekhxUFu)Zen_x1KUi7-F5M&+{qHv`t7DNMSXu}yEew}_ zyGnmWJU^>rkqB5?6f7+Um!5Gn`}-tGR>vZ7u(SkNS`sdu_r1h+h6<}=krY^38Z0dX zm#$ygyR6WF)v-txEG-9?mWNCCtTwe%v}JWHQUFUUf~A$<(myAk|Fpu3)v-t!EUf~T zR)tIV6mOcr6$#o836@p|OKZTTo63*AanE3NEYbu^Yk{S;IY7;Fr`RGL4$wRZDCuQ^ z5?B@}8D)VIOBN_eWPu_+3lzOsph(ODMO79kLb5=?o&^frEKm?;fdVQE6dYL~PiKL= zmId-a7RZ@dAjf2Ztb}Hcf|;5hs#zV2bippt1G`8c>>~HrA_I_%APV6fbVIO0Bd|ha zxX-|YBB1`e30T?`ENupt1`p?eq|L$77GP;hxHNbm3M6d>mbL~<+rXv43z0z5wqR*H zu(Ul~8a%WRG~1!b0W9qZmUeV{FAY2+ex(AXD0!s&jr9gM-C?wnI@I zSUMgoodA~x521mi6T#9+VCiJIG}vyx*$zc1VCht_bQ)Y5>~fHFI#@abES(9L2K(E0 zwnI@CSUMXlodcH!pYrB2+o32IES(3I&WB5bqXZ;f0G2KUOBaFDY)WiVF(S=^T>%<- zC;=-h1uHCrn**0F2TNCgr7JO|tH9FLVCfo6=~}RK9ay>^Q@Q~x-3XR$!jx_XOSgcf zTQQ~Ez|!qt=?+ZkPOx+rSh^cix(6)X3zqJ~l?e53&GNhz|xB`rI&!Emx862VM;Ft zORoS+uf&vI1(se7mR^G?y%sFJ4lKPMQ+fkfdLvkR6Q=ZLu=EzN^j2`1ZHz72hMH!# zfx4R8!3uYP74Aea=Lkr87g%~XSb7hN^fi$5Ua<5&u=IWuX{Li(jztH+(g(rPhft)A zK+=c7(nrA3M^U7cK+?y+(#OHlCs3ppfuv7@rB8vSPoqe49nx|vIs=wI3zj~IB3%TM zJ`a|@0G7UpBK-m+eF-dm87zGTMLOxQmSfRXu=F*s^mP!Phav_}P&<{86V!}l;)FG0!KJk5UWX!Purv!;niW-A zf3HIk8(5khEX{!`?Yq~Zh!ZT$1(xPUmCoDiP{ae4<^@ahp-NBM>rliGmKFd@3!+Nz z+Urmx1eO*CON*dNzuxOmBnp-m151meN-OMhD3Sn6OM<1PP^Dw{ITT5QrDed>VJ+QPsiZnyY0f!<3u(Tmq+6YCuVeSEkB4e<$30T?` zMVjHp0f!m(9}_=RiF%TH83aVmF5;y>LuqFrRwFD=9FY678NB{a>W;=Cg3us{4EOE zJ3HD7If4o~HN2T47#SECY6`jh{QSKB|Ns9VOnBRu6mlnZ#uoC_7V<`LFfcH%LZTI}5L!n1op=VH`7etid zz;0#HcNZNBz1s?Xf(m^hqSrni$&~tc(V@_=1Es;w|O zs4xa1T7BgZJ@kpYVE;$q?v=t@>6(&JM{W8N|S8!c+C`@iEObIGX zg^1dgFmT-Txa?4v)>fDvRG0w~b>gz(>g~PkP?*_Pm=#o*4H2EO_FlxQ8@o=tq5+ug1@6jrnqRt6PTK}0`(y}S0yrmGHx)oq0}L4~yt(T-1F?l7ud zb11B9E36MHY=DTG3iZ~$nSafpu(7SMDX6d+BKo4h@$5l`>kfr2ZH28tg>4Yg!i9DD zeJidz6t=e&b_5l6LPRh6ysL^czu{2W)mGRYRM-O%Wnf6)n)LIALt$@QVP8;TKSY#? zuh6J_+D(VT32lWFg9;}>MCUHsq`A`SmP6s>w!$evg;OD-+ofe}zCFF=P&lovaC%VT z42Wn5S6O(?jN1-{GusMh1r^SQh(6Z8CFmM-$DwdeTjAWG!g&x;sXaaGBZcoe6wYre zTo6>a5F&bIfs4lB+jkub7qt~G4k}y%5w&3Xs@=8voiEh5OnH_XiaofQaspYkeKKj!_rtB3U9U* z-U=$b4H4zKzPP}ai`Aj)0w5{-2P~mfk=$k-L_^>+^zGy3a8C3WRA`0;_heP4(w!$|-g>NCE z5ZgE%3g5LAz7Hz=01*Z058T1!Q24Q}@KaFXXNV|7Ew@AAm$t&ML51HSq7WbRI23+w zEBp~u_!A-u4oC(*hr(ZNg};Le|3E|`?&WtV{M%OeFR1W8s%Q}dBLhQQ5hEi5Lr@VD zBLf4PG&5M51uV^qDa{6!W(P}iU`lg>rMbY;+?dilU};{kG#^|V5=HzDMf_lC0kE_n zrnC@PS{N)Xf+;NumKFm`i(^VlfTbnD(o&ex(qL&Bu(T{(8WQe&4n=ZcX?d`;0wV)M zD!5TvmROVt%8o^fj0_C1MM{he4A4y3e+ZPq9@!TugEgvvHLAkR1t-eukL`=pz|!hq zX$`nEByl~oFVX}{Yk{S;;nI-2{m{Nh2P~}%mezwygNlO!+eh|A`e11Tu(Tmu8j{oQ z+ZP#urH#STCU9v;qIzIoWD1rx152C3r6H;Io_&!8SlSXSZG|aq4VJb6OWVSw3&C!? zYhPpsmbM2=JHVyEIj!}!eUT$r+6gS}43~z)#x46I7qGM|SlSI!+8r$I0hacJOG85K zrhSnYSlSyb?Sm=p3zqf+OZ&s6gTNlYVP6ygmJS3<2f?KwY52N*Q7~9K1S}m2mxko( ztM)}iISD}MTKDLBCvEZrgRBdx)dy3hACYRmaYIxSHh*?c2|L= ztHIJWn9{Xi={m4yn zuXBG`B@*pWGzBa@6)ZgsE}aj`ohQ;Ail&35XMm+=!ll94&$h&&Xcky{HduNNTv`)U zuq4zw6wL)o&jU-(hf52A?B3DlP_zIny$~$D2rlgcGTpe}p=dE!dI?y1DO|c2RG!YC z;!v~L1kxV9!l7srSb8&9dJA0olSWLx)BQCLMO(qr+rZM>;nEzSvi|V~ zhoT)|>78KdU2tiz$9gt96zv8}?*U8ig-e548QIy}9E$dVrT2rS55T2OjE#*yH|}&O zItZ3N1eQJwm)`$XBZmLN9*3eMVCkb^>0@wdP|I<;_5p{Y<6!9%VCj=^X^^*#c@8-g zodQdr21}oTOM{%I&3VM3=qydZ-?E zD7p-mz5bQ3In3oLybF5UM!aL2vv zCmo9JfTiz(rSHL|*B01vg`YX;P;?(G{QxZe5H4K>$_5WlIutzuOFsrnKY>eE+j3oh z{Qaau(NnPWGqCh?xU^>L>%bp;ryPo2fTdr8rC-6NAA(Y<`YDH^*I?;4VClDT>5TqE zUpX93ITXDEOTPz8e}GGyf$~`7DTktuVChd_>CbR!k?V^K_LiJ-DEb1H{tA}<2AA%6 z9k|18!YPNM?_lX4VCkQ5Y2oXO3t~^4awz%*mi`Tv{sS&{xIo2DS{kg}`3o+0{vpa8 zaDkPR=uq?@td)TY)VgG3g0(Ke#nRb&hax5>P~(#sEX@L!2G^~eJq|^zU}-k6G&@`x z6k^w9W;hgafTcOX(p+$98?Nh%KkQoMP{a+E<^fCdqDU7=t#&Bl155LRr3K*9Aj7B6 z-Q-Xt2$mKCOAEuL7l4w=j2#X|B4BAzu(TLl`VOe))VklHNE|FJ0hX48OM?oQ9h(k2 z6iI=lrNPoNaOv%Gt*<}Iop30U1xw3;rRCw$Dlz?su5zArC{h4RD}tq!;L@wyA68AV zy68}(43<^_ORK`Ar_P)?lhf#mLy;O-S{*E{0hhJ_wOF#RI}~YxrM1A)+HmO~|NsA& ze}2oMNCzyf3zpV{OFs+TA!oYmorIW$ZDRAkJ8ZrG+j2|3|Qo+(`VCi(YG$^GCs(f%L$^c7ef~B*V zK%FemD2s7%W_oU7Q8p8(iSDEJd`m5j5M*FrNXslLE{O+^{6hvoi*n)Sf`%>k zT)XH{lm|99A8c*`69WTy&=jo4z^JH@iGiWEs0eJXNNiCth!MeyI>2m@QB(rbhbdf& zUAPRpa5;A23hcs_*oCXG3s++ouE8!`i(R-5yKp^r;Rfu&jo5{ounRY17jD5W+=^Ye z4ZCnVcHs`}!kyTKyRZv)V;Am069!E)7-SUnVi)eiF5Hh@cmj6eiP(iFVHci^U3dz1 z;i=e#r(qYK4$8C{R6c@u2RX4n=do z(sRMm^Wf65#f8$Jdwx0;%?C>_081}~OP54RDeUO~=}@!?EWH>ky#y}(YT2B^os)k$ z6fFfyF9S<2hf6D(ub#&-^QS}63b6D_u=FapwEBLzrJLvfbSPR4mR%r0+;L<#18ZurRe>xOx1WRuMOK*lti!PG2@YwUyp=b+OdMj9Z8;bOa z6F(h_wu7a2fTefBrBAUi3SGSR)1hb=Sb8^DdJkN>tzBC(^Vv^_qP<}0ePHSRaB23p zJ0+)m|LIV404#kFEPV(reOEL&HCO1|^Uq3AYP`VLt7E?oMO5O;Cdzh4eT_rTKk!O{=l(yRVHE9Fx9 z?NIa(Ed2;9{TMFIz_5MRQup5uMNh!ePr=g9;L?rGyPrJH`|VKl94!3;Ed3HLy<%l| z((CEJ9g1ForC)=k-@v8cv8gTJcj&i6(Oa)U?084)aOMik( z|LmR`b4m7(L(ylj^cS%7SGY90K2wXh-yesfZ(!-~VCf%lX)_IjM}`f59EyH|rGJ5? zf5WBu9c30PZ~5a;^am{c7cBh`F70~m#lv?m|2P!=2TLcgc=*7jdBM_rU}=6#X#udbAXr)m zE)AKj;BzPv21|>8rA6V=kO4G4haxesv^ZE=0xk{ep~v*|ITT5PrKP~q(r{_WAQqoP zkqlT`7A!3Xmj?Iw0{I+@wg=ENu;zwt-8-J!T7*wgXGs!==I9a1lO- zA_uUvBUsuAE)5S0s#$uyiI|8lGCRz|z@Z=^RYyT(EQ=SUMk5 zx&SO)2$n9wlr9EKmw=^9L8VL8)Jh1e9 zu=E1BG$hmx+7~SZOD_UTFUFK!0+wD1mR^P_y&NpP0xZ1}Q+gFxdNo*j4O|*LG;Dm> zzGy92dL3ALJ*M;qu=GZ-^d?N{&0y&*VCk)x(%Zn&+riR1Fr{~brFVg)cf+M2spW`$ z(H^k$Ua<5&xHM!Y?x=myez5cbu=GK=G(`6j`=Uc&>BC^@Bbd@h!P3XT(#J8SPk^OQ zf~8MkN}mQxp8-ps#gsk=mOc-bzJMux5iETPEPWYM`U+V3Dp>j&ru22N^bN4|O}I29 zm#nlex&@ZL4VJ!xDSa0#eGe>sA1)2aCF|^q9)P7Ef~6mU3mV82)ni0K^P);*o%)$m z4nFIf5?T>9i! zjhKA%GY&=n!O{#Y;D#^@tRZY`Y;1hF>x@GY6AQQ@43=hrOM`~)=RQB03(lynRqpLZw{2TMzUr6u9g zpqYb5P8S@Cq`=bBU}+h+v_04L#R)SnI26f(rRBiV@^ERM>x&EKe!1XKqyUyy1WPNy zrM(_jiM$H|ji7_2Rlw4!aA|O!*mcpNNDVBl4wlw{O9$+bYpqtjOb`L$D&IPMMhw0W3aRd zT>8)d|Nq~wyyQ@13YInlOPj-`r_Y=@Q*+}bhawBGv?W;D3N8&AN#3>Nl0%U-SlR|G zZ3~xv9=Jnp>VZoRMRs6md$6d&zFl%C@&!x# zfu;Rf7#LEsAahayETHjW@cLBHkanWl2Zy3Su-YK7+F-a*zyJULpRV)4p(q3_9SW8X zgG-0?ANo4Q?1Mv5I9NIYEFB4#)-^UZ4tD+EP!t80js{D|z@?>HUk9>8esCy?1xv?) zrQ_k!lV{GHd9d(zFYgiAjT+#%Ok_R*oJ2rOL;mM(!yPXwintsfnVO2N`)VCizWw0unep`X7#Iuuoa zr7OYGRdDH<1-4x5Zl4^As=?AVVCh=8^y$R~wo21KITY1_rR%}c4RC4pb?y(n-hFZ? zY6MF+fu)<_(pC?vM7q2`I~28mrCY($ZE)#`w6rv?^`9Mz+QHHtVChb{G=J;sz&jFO z9E!TY(%oR`9=P-~jhKG6&Myu{yQFQpEIkD*Jryo36VrcaD#JI2qG@31>0s#@aOo6VuIujI-yDi&f~9AHrDwyXPc1I6 z4V(PUp=b_RdM;Rc9$fn1nKNhh%>L$3G#@Oz04%)_E*;!|=xfcAZw^I^z|xDs(o5jd z_7AH>X083^P_z^*y$me994@^WwA^I-H;1AXVCj`$=~ZxPkiXv@_~uZw8Z5mAEWH*k zJ-xt|Oa1gWhoW_0>Gfdg4RC3{{zG36T>a)yv=J=52`s%CE?xCHaL29(-yDjzfTg#B zrMJPQ*UGiNmVNWhp=di;dIwl~CtTWXhg@qi^LK}$U0~_mVCg;Jf(9}XwHH*-K*pXx z!!fC)rQkg&!TF^n8Abb8Kyym_K}wM3nhc8$fW;4j#4|))XgeNeNw8i?{4n?=X z(zn6Vci_@xJ~=GAw%&Fqx(k-R2bR7Mm%jLtU;NyG+YUt!z|s%F(vRTM%cUh_FJHLr zQ1loq{RAxi6fWHu6D%n3?6yPEGqCh?u=ESK^o$xs?L7>49Ex6orC))iU&EzW9MU#r zQ@rC)^ad>b7A*Y^F0B>e&MxMD$D!ywSo#B4`XgN0heNiUulSBb(I>FZFy221||OaFyS3!C)) zbxpYIQ1lNh{U0pNzzS<)XI?k*NnU-|p@@+c)W&9F1vSB$;nMrp2p{|V=dME$3s{;J zEX@X&F0hXMrXG6Fp@kC?giC)rtz`ds^F4ZEKnNA0>yL|C_b}5v6cmjn=DWaWP!pt3ly4JpfJkY1#)o~$X!_=+p|D+KojhNM{|B{x$jUU2zG)H*a^a5Cv1)_5&<~@q7d9J zp83$eNEED446INb?iI+W@)P?a39z&zSXv4$4c9FVmX-la%fh8W4gN!4AJ`Ykfu-fa z(h6{C_yS@@u(T3bS{YMX1uU%!mR5sHLq-|z*%zsUr8U6Pns8}|%kS71X@RA+!O}W# zX}I0GU}-(Dv_4!KzIfRHENuvuHiAn-2E1?B7a4=4O~BHoaB0Z6#8vwuGqAKdSlR+E z4H=iXVqatlmbL;*TVqPwfTeB0(sr2A_F!oTu(Tsw8Zw%5*}ljLEbR=Ic7aR7?REuA zyMd+M;nHxIdw``q!O~uEY0$Dyk#*PYi@d?oK458IP_lv!Li!<679@-w*cbVO)dqmo z2EvVkr;Q-6bTC*t1TGx|o{_k3Ula5VZcg3@lv^mac$HL-NE~`=Uy)bQM^-8ZHetyap^?3zn{fOT!JX2TM1A zr5iD&o50e|VCfdPG~C~BnYz-qhUMuFB47T8KU z6!n0md%@CuaOnr2Wqye|4n_T7=?P%ziEwFf(&@5vD4GP8o(z_r0+$B+(!#}|XewBG z8d!QdTpFU=*P&`()2$o(1mR<~(1_#TlREMG^VCkh`>1A+faEAJy`=58EWHjay&f(N3e$)0${dO|fTcHrr8mK)L7FvIR5=uF z21{=NOK*isUj%I}+fe6Fv<)o19W1>AEfhSvP_z>)y$dY88!jCW^4Ya!hoU`T z>Ahg-eQ;?BP@$F3?ohNJEPVhheGr_oAj1rY5Ge~>?6WF46deYuJpxvH6mArFXPml& zL(ws?^l`BC3Ai*kYcmHq6rBW1p8`vthD(DN?CT^r6rBM}p9M>wgG+-3a;mmuIuxA; zOJ4v>UxZ8hgHqnjB8Q?&VClFaQ5aEa&H=umV6EPWF! zeG4wV6twNovdy9BHdy)&So$tp8eA}3=yoW&2bR7MmVN-21{VxG6CH{kf~6mUr60qk z!84WZQyhw(fTf>;rJup2!Pzx&hC|VFu=ESC^h>xjIMk-kaVUBPmVOPEegl_Q2PLg3 z3ml5xf~DVqrQgG)!Ks>iu|v@Zu=Gc;^e4D9XvzKN4ND!0K7*ydfTh2}r9ne)l5Hy- zioSuRzk{WJz@@01ZC}Lt`V89~Gj9r)oyD%$uVK(f-?AV1lunTiy z7v{n)%#B@`2fHvYc40p3!u;5U1+WVXViy*|E-cIjYRp^37Kwo33DS~gXmFev&hp2h zNR$oK;}By5^*F@YU>y>W^mmZ71Xx-UEG-3>mK9#~b~@W1hazdPvhBV5|( zHTS)Y8Gjs#oWRn~U}+b)^gTbhtm1Wl9Ex1Q(r#dBcewQbP5w4&$No4Jd4Q!o!O~vf zbh9qD$QzVyvOv+F1&ZV>P*i4tA}k9OEm@$z&jJN;7APRIK*5y-3Y096*Rwz#%mR5M z3*^`=kdv}N7H5HshbBgWJ1^_M{c$Mr0o(2iw%reG`{vjpe~|4Eh46M)09aulSYZ&{ z7jWrduyhDmIutGqs(EAjFWDD`fu+O2(h-=_kzna4uyi!0bPQNJ7AzfyDIE`%P5?_M zVoE20rIW$ZDR60UWo&%ez90G!p+~0X%>3pzs z0j6{zSh@%-T@06o2Wkmex)dy32A77n@yo%|6=3N~xHP!7Ww>BpR0Wo<220n#rNMP| z!CCvFTCj8-Sh^lA4VuCGs&U4?r~xe92$pVwOM@C2;2uIVSh@u)-3m%K&<r+}rW!lfa0Z?P|$29}-3Nva z^TE;!z|spbr5Ayv7lWmjU`j6qOD_XUFNaG*;%AS2(F(BiO0e`QOzG8N=`~>KwQy-j z^zE}RS_hV150>75DZLRay$LM68B=--Sb8g1dK;$ncChpgu=Gy2G$hQA*ca^rOYa6t z?*S(rNDpo=BI#@d%_XF?I~45$tKAP)djM_}=oBf2ymp78gJ9`HVClnf>0O|$Sat0V zMMuEWN5Rs^;L>YBhEHpEC^`<7J^_|K36};<*0yeKcPKgqmOc%ZJ_DC_12q|Mw>uP_ z1xud;OP_~Jp96JOI654PE`X&kf~7CPr8{2-?vS(Za45PAmc9a(zKSYc+u=}j4J>^f zEPVqmodw$ceW=5s=q6bD7FhZ=TzW33AH?73P;>_@eHSc!4=xS1JGIlH=ssBb0a*GW zTv`g$4?5K8Q1l2a{TM9$1TNhLa=B)gL(x;P^fR#ZbGURkC}~aaawvKMmVODAeg&7l z1KLx>-|bNJ8Z7+=Ed3TP9Szdm-|bNJ4lMm1Ed2p4{TQTsc8^2RN3irKu=Hne(t&i* zz95p0Z0qYli&twLioSx?egmuh4mS!M`Cr#K6#W28{{&0_f=h$;xn^^$btw7`mi_~l z{tK6G0|kQWT8E;4VCny0X$E#!8xB-3NP4ezC}Ly>HSw6(K}|enxHPC%xKp{-p@;=6 z%?g%ggG;x(4%{(s(^`ijcCa)DSeg?qEe+~?ep%~K#08e-221n6rE@{uD2H_pMZ92X zKCm=DTpG0f@Z$7!4n+cBX+f~G5L|l7%$YMKf39;V5(Z0)fTcy@(mbHFk-FZYNDM43 z4wjaHOCJLz`aA0#iX_3(QebImxO4?*C{}-iLy-(vS{5uV2bcb=5!2sPzQLhL9xSZ@ zmR5vIZ" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtsAAAGHCAYAAABh1J65AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA8aElEQVR4nO3dfZxWdZ3/8ddnZrhRByUB70CEVFIUHIUgKxMjvCnTSk0tNzTNtLW13Pa3/OoXmvtzF9tdt5u1zLWSbH9mmSmQaYooiWaCgLerkWGChNwpDDIwN9/fH9c14zDMDDPMdea6Zng9H495cJ1zvud7Pueayd7X9/qecyKlhCRJkqTCKyt2AZIkSVJvZdiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYk7ZKIuCYiftqNx7s8IlZHRHVEDOqG4z0cEZdkfRxJvZthW1KPFRHLI2JLPnw1/vxnxsecFBErWqy7qUUNWyNiU7PtD0dETbPtLzbbdmBEzIqI1yIiRcSILtY3LCJ+GRFrI+LNiHg2Ii7sSp/5fnc47+4UEX2AG4CTU0qVwJhi1iNJHVVR7AIkqYs+mlJ6sJgFpJQuAy5rXI6IW4GGFs2uSCnd0sruDcB9wL8AjxWgnNuApcAhwFZgDHBAAfottv2B/sBzhegsIipSSnWF6EuS2uPItqReJyL6RcQbEXF0s3VD8qPg++WXT4+IJfl2j0XE2GZtl0fEVyLi6fzo8B0R0T8i9gJ+AxzUbJT6oBbH3gs4C5jZkVpTSqtTSt8DnizAqQO8G7g1pbQ5pVSXUlqcUvpNs/rOiIjn8uf9cEQc2WxbiojDmi3fGhH/dyfn3TcifhIRm/L9jt/VwiPiooh4Id/XyxHx+fz6UUDjtwFvRMS81uqJiLKImBYRf4qIdRHx84jYN9/HiPz5XRwRfwEeiojyiPj3/LcAf46IK/JtdhiIyvf9fyLilYh4PX/O++S3zYyIv8+/Hprv42/zy4dGxPqI8P9vpd2U/+OX1OuklLYCdwHnN1v9SeCRlNLrEXEs8CPg88Ag4AfArIjo16L9qcBIYCxwYUppM3Aa8FpKqTL/81qLw58FrAHmt1j/L/lQtyAiJhXiPNvwe+DGiDgvIoY335APrbcDXwKGAPcCsyOib3sd7uS8zwB+BgwEZgFdmcbzOnA6sDdwEfAfEXFcSukl4Kh8m4EppZPaqOeLwMeAE4GDgA3AjS2OcSJwJHAK8Ll8P1XAcfl923Jh/uck4J1AJW+f6yPApGb9vwx8oNny71JKLb/pkLSbMGxL6unuzo/SNv58Lr/+/wHnNWv3qfw6gEuBH6SUnkgp1aeUZpKbcvGeZu2/k1J6LaW0HphNLpB1xFTgJyml1GzdP5ILaEOBm8kF3EM7cY6dcQ7wO+DrwJ/zo/fvzm87F/h1SumBlFIt8G/AHsB7u3C8R1NK96aU6slNYTlmVztKKf06pfSnlPMI8FvghE50cRnwtZTSivwHrmuAs1uMVF+TH/XfQu4D1bfz7TcAM9rp+9PADSmll1NK1cD/Bs7L9/0I8P786PUHgG8C78vvd2J+u6TdlGFbUk/3sZTSwGY//5VfPw/YMyIm5i86rAJ+ld92CPD3zUM6cDC50dBGf232+i1yI5ntyo8kTwJ+0nx9PtRvSiltzQf7BcCHO3meRMSnm02b+E1rbVJKG1JK01JKR5Gb57yE3AeSIHd+rzRr2wC8Su5DwK5q+T71b2Maxleb1X5Tax1FxGkR8fv8tIs3yL1HgztRyyHAr5r9Tl8A6sm9D41ebfb6oBbLzV+3tN17l39dAeyfUvoTsJnc39gJwBzgtYh4F4Ztabdn2JbUK+VHWn9ObirJ+cCclFLjHUJeBa5rEdL3TCnd3pGu29n2N8CClNLLHegjOnCs7XdK6b+bTZs4rQPt15IbvT4I2Bd4jVwgBSAfwA8GVuZXvQXs2ayL5hdWtnfeHan9n5vVflnL7fkpPL/M17t/SmkguWkubb1PrdXzKnBai99r/5TSyjb2WwUMa7Z8cDunsN17BwwH6oDV+eVHgLOBvvnjPULuW453kPvAI2k3ZdiW1Jv9P3JTJz7N21NIAP4LuCw/6h0RsVdEfCQiBnSgz9XAoMaL41r4DHBr8xURMTAiTslfYFkREZ8mN9XgvmZt+gON88X75Zd3SURcHxFH5481ALgcWJZSWkfuw8dHImJy5G6l9/fkps803gVlCfCp/IWDp5Ible3IeRdCX3LvwRqgLiJOA05up31r9dwEXBcRh0DTRbFnttPHz4Er8xc1DiQ33acttwNfjoiREVEJ/DNwR7M7mjwCXMHbc/Ufzi8/mv/gJ2k3ZdiW1NPNju3vcd04VYSU0hPkvt4/iNzdKxrXLyR3cdx/kruIbhm5i992KqX0P+SC18v56QoHAUTE8eRGSX/RYpc+wP8lFyLXkr+IL3/RX6MtQHX+9f/kl3fVnuSmy7xB7kK9Q8hdxEhK6UXgAuC7+Vo+Su7Widvy+16ZX/cGuQ8od+/svAsl/63D35ELwBvIzbGf1U771ur5dn6f30buPue/Bya2c9j/Ijcv/GlgMbmR9DpyU09a+hG5OenzgT8DNeR+l40eAQbwdth+lNzvouWFspJ2M7H9NTySJO2e8qPpN6WUDtlpY0nqIEe2JUm7pYjYIyI+nJ9yMxS4mrcvopWkgnBkW5K0W4qIPclN/ziC3NSdXwNXppQ2FrUwSb2KYVuSJEnKiNNIJEmSpIwYtiVJkqSM7PCUr95i8ODBacSIEcUuQ5IkSb3cokWL1qaUhrS2rdeG7REjRrBw4cJilyFJkqReLiJeaWub00gkSZKkjBi2JUmSpIwYtiVJkqSM9No525IkSbuj2tpaVqxYQU1NTbFL6XX69+/PsGHD6NOnT4f3MWxLkiT1IitWrGDAgAGMGDGCiCh2Ob1GSol169axYsUKRo4c2eH9ij6NJCJ+FBGvR8SzbWz/dEQ8HRHPRMRjEXFMd9coSZLUU9TU1DBo0CCDdoFFBIMGDer0NwZFD9vArcCp7Wz/M3BiSmkM8E/Azd1RlCRJUk9l0M7GrryvRZ9GklKaHxEj2tn+WLPF3wPDMi9KkiRJKoBSGNnujIuB3xS7CEmSJLXvuuuu46ijjmLs2LFUVVXxxBNPdLqPhx9+mMcee3vc9cILL+TOO+/c5ZpeffVVTjrpJEaPHs1RRx3Ft7/97aZt69evZ8qUKRx++OFMmTKFDRs27PJxmiv6yHZHRcRJ5ML2+9tpcylwKcDw4cO7qTJJkqQSNHt2tv1/9KNtbnr88ceZM2cOTz31FP369WPt2rVs27at04d4+OGHqays5L3vfW9XKm1SUVHBv//7v3PcccexadMmxo0bx5QpUxg9ejQzZsxg8uTJTJs2jRkzZjBjxgyuv/76Lh+zR4xsR8RY4BbgzJTSurbapZRuTimNTymNHzKk1cfTS5IkKWOrVq1i8ODB9OvXD4DBgwdz0EEHMXfuXI499ljGjBnDZz/7WbZu3QrAiBEjWLt2LQALFy5k0qRJLF++nJtuuon/+I//oKqqit/97ncAzJ8/n/e+9728853vbHeUe/r06VRVVVFVVcXQoUO56KKLOPDAAznuuOMAGDBgAEceeSQrV64E4J577mHq1KkATJ06lbvvvrsg70XJh+2IGA7cBfxNSumlYtcjSZKk9p188sm8+uqrjBo1ii984Qs88sgj1NTUcOGFF3LHHXfwzDPPUFdXx/e///02+xgxYgSXXXYZX/7yl1myZAknnHACkAvyjz76KHPmzGHatGlt7n/ttdeyZMkSHn74Yfbdd1+uuOKK7bYvX76cxYsXM3HiRABWr17NgQceCMABBxzA6tWru/o2ACUQtiPiduBx4F0RsSIiLo6IyyLisnyT6cAg4HsRsSQiFhatWEmSJO1UZWUlixYt4uabb2bIkCGce+65/OAHP2DkyJGMGjUKyI0ez58/v9N9f+xjH6OsrIzRo0fvNBCnlLjgggu46qqrGDduXNP66upqzjrrLL71rW+x995777BfRBTsji5Fn7OdUjp/J9svAS7ppnK6bMYPf8GmrRv58EeOaFo3ZK8hrNm8pt1/ge1et1xub1try1msa6lfRT8OGXhIu20kSVKRtDOnujuUl5czadIkJk2axJgxY7jxxhvbbFtRUUFDQwMAGzZtoK6hjo01G9lat5U+dX2o3loNQG19LaksNS2nlHtdUVZBRNCvot92/V5zzTUMGzaMiy66qGldbW0tZ511Fp/+9Kf5xCc+0bR+//33Z9WqVRx44IGsWrWK/fbbryDvQ9FHtnujrfVbqd5W3fRT2a9yp/+2fN2Zba0tZ7Gu5c/Wuq3FfqslSVIJevHFF/njH//YtLxkyRIOPfRQli9fzrJlywC47bbbOPHEE4HclJFFixYBcNddd5FSorahlj322oONmzZSn+qpT/UkEg2poWkZoD7VU1ZWRkppuxpmz57Ngw8+yHe+852mdSklLr74Yo488kiuuuqq7dqfccYZzJw5E4CZM2dy5plnFuS9MGxLkiSpoKqrq5k6dSqjR49m7NixPP/888yYMYMf//jHnHPOOYwZM4aysjIuuyw3a/jqq6/myiuvZPz48ZSXlzf1c8qHT2HOrDm8f+L7eezRx9o6XKtuuOEGVq5cyYQJE6iqqmL69OksWLCA2267jYceeqjp4sl7770XgGnTpvHAAw9w+OGH8+CDD7Y7H7wzij6NRJIkSb3LuHHjtrs/dqPJkyezePHiHdafcMIJvPRS7j4YG7ZsoLa+FoDDDj+Mx558u5/3vn/7WwCuf3M9W2q3tFrDvHnzWl3fcgS80aBBg5g7d26r27rCkW1JkiQpI45sS5Ikqcd67tnnuPySyyHRdAeRfv367dITK7Ng2JYkSVKPddTRR/HkoidpaGigf5/+xS5nB04jkSRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSVLBXXfddRx11FGMHTuWqqqqXbo7yKPzH+WJx9/e7/LPXc49d93T5drq6+s59thjOf3005vW/fnPf2bixIkcdthhnHvuuWzbtq3LxwHvRiJJktRLzc64/4+2ueXxxx9nzpw5PPXUU/Tr14+1a9fuUnhd8LsF7F25NxOPn9iVQnfw7W9/myOPPJKNGzc2rfvHf/xHvvzlL3Peeedx2WWX8cMf/pDLL7+8y8dyZFuSJEkFtWrVKgYPHky/fv0AGDx4MAcddBBz587l2GOPZcyYMXz2s59l69atAIwYMYK1a9cCsHjRYj5+2sf5yyt/YeYtM/ned7+33ePaFzy6gJMnncwxRx7DXb+8q80apk+f3vRI9qFDh3LRRRcBsGLFCn79619zySWXNLVNKfHQQw9x9tlnAzB16lTuvvvugrwXhm1JkiQV1Mknn8yrr77KqFGj+MIXvsAjjzxCTU0NF154IXfccQfPPPMMdXV1fP/732+zj+GHDGfqJVP5whe/wKNPPNr0qPbVf13NfQ/dxx133cHXvvq1Nve/9tprWbJkCQ8//DD77rsvV1xxBQBf+tKX+OY3v0lZ2dsxeN26dQwcOJCKitykj2HDhrFy5cpCvBWGbUmSJBVWZWUlixYt4uabb2bIkCGce+65/OAHP2DkyJGMGjUKyI0ez58/v9N9f+SjH6GsrIwjjjyC11e/3m7blBIXXHABV111FePGjWPOnDnst99+jBs3bpfOa1c4Z1uSJKlXantOdXcoLy9n0qRJTJo0iTFjxnDjjTe22baiooKGhgaApqklbWmcmgK5MN2ea665hmHDhjVNIVmwYAGzZs3i3nvvpaamho0bN3LBBRdw22238cYbb1BXV0dFRQUrVqxg6NChHT3VdjmyLUmSpIJ68cUX+eMf/9i0vGTJEg499FCWL1/OsmXLALjttts48cQTgdyc7UWLFgEw6+5ZTftVVlayqXrTLtUwe/ZsHnzwQb7zne80rfuXf/kXVqxYwfLly/nZz37GBz/4QX76058SEZx00knceeedAMycOZMzzzxzl47bkmFbkiRJBVVdXc3UqVMZPXo0Y8eO5fnnn2fGjBn8+Mc/5pxzzmHMmDGUlZVx2WWXAXD11Vdz5ZVXMn78eMrLy5v6OeXDpzBn1pztLpDsqBtuuIGVK1cyYcIEqqqqmD59ervtr7/+em644QYOO+ww1q1bx8UXX9z5E2+F00gkSZJUUOPGjeOxx3YMx5MnT2bx4sU7rD/hhBN46aWXANiwZQO19bUAHHb4YTz25Nv9NF4k2Wj9m+vZUrul1RrmzZvXbo2NU1wavfOd7+QPf/hDu/vsCke2JUmSpIw4si1JkqQe67lnn+PySy6HBBEB5C6i3JUnVmbBsC1JkqQe66ijj+LJRU/S0NBA/z79i13ODpxGIkmSJGXEke0C+8uq16ltqC12GZIkSSoBjmxLkiRJGTFsS5IkSRkxbEuSJKngrrvuOo466ijGjh1LVVXVLt0d5NH5j/LE42/vd/nnLueeu+7pUl1vvPEGZ599NkcccQRHHnkkjz/+OADr169nypQpHH744UyZMoUNGzZ06TiNnLOdgbq6xNz7lzP5lBHFLkWSJO2mZr84O9P+P/quj7a57fHHH2fOnDk89dRT9OvXj7Vr17Jt27ZOH2PB7xawd+XeTDx+YldK3c6VV17Jqaeeyp133sm2bdt46623AJgxYwaTJ09m2rRpzJgxgxkzZnD99dd3+XiObEuSJKmgVq1axeDBg+nXrx8AgwcP5qCDDmLu3Lkce+yxjBkzhs9+9rNs3boVgBEjRrB27VoAFi9azMdP+zh/eeUvzLxlJt/77ve2e1z7gkcXcPKkkznmyGO465d3tVnD9OnTqaqqoqqqiqFDh3LRRRfx5ptvMn/+/KZHsfft25eBAwcCcM899zB16lQApk6dyt13312Q98KwLUmSpII6+eSTefXVVxk1ahRf+MIXeOSRR6ipqeHCCy/kjjvu4JlnnqGuro7vf//7bfYx/JDhTL1kKl/44hd49IlHmx7Vvvqvq7nvofu44647+NpXv9bm/tdeey1Llizh4YcfZt999+WKK67gz3/+M0OGDOGiiy7i2GOP5ZJLLmHz5s25flev5sADDwTggAMOYPXq1QV5LwzbkiRJKqjKykoWLVrEzTffzJAhQzj33HP5wQ9+wMiRIxk1ahSQGz2eP39+p/v+yEc/QllZGUcceQSvr3693bYpJS644AKuuuoqxo0bR11dHU899RSXX345ixcvZq+99mLGjBk77BcRTU+j7KqSmLMdET8CTgdeTykd3cr2AL4NfBh4C7gwpfRU91YpSZLUc7Q3p7o7lJeXM2nSJCZNmsSYMWO48cYb22xbUVFBQ0MDQNPUkrY0Tk2BXJhuzzXXXMOwYcO46KKLABg2bBjDhg1j4sTcHPCzzz67KWzvv//+rFq1igMPPJBVq1ax33777fwkO6BURrZvBU5tZ/tpwOH5n0uBtr9zkCRJUlG9+OKL/PGPf2xaXrJkCYceeijLly9n2bJlANx2222ceOKJQG7O9qJFiwCYdfespv0qKyvZVL1pl2qYPXs2Dz74IN/5znea1h1wwAEcfPDBvPjiiwDMnTuX0aNHA3DGGWcwc+ZMAGbOnMmZZ565S8dtqSTCdkppPrC+nSZnAj9JOb8HBkbEgd1TnSRJkjqjurqaqVOnMnr0aMaOHcvzzz/PjBkz+PGPf8w555zDmDFjKCsr47LLLgPg6quv5sorr2T8+PGUl5c39XPKh09hzqw5210g2VE33HADK1euZMKECVRVVTF9+nQAvvvd7/LpT3+asWPHsmTJEr761a8CMG3aNB544AEOP/xwHnzwQaZNm1aQ96IkppF0wFDg1WbLK/LrVjVvFBGXkhv5Zvjw4d1WnCRJkt42btw4Hntsx3A8efJkFi9evMP6E044gZdeegmADVs2UFtfC8Bhhx/GY0++3U/jRZKN1r+5ni21W1qtYd68ea2ur6qqYuHChTusHzRoEHPnzm3jjHZdSYxsF0pK6eaU0viU0vghQ4YUuxxJkiTt5nrKyPZK4OBmy8Py6yRJkrQbe+7Z57j8kssh0XQHkX79+u3SEyuz0FPC9izgioj4GTAReDOltGon+0iSJKmXO+roo3hy0ZM0NDTQv0//Ypezg5II2xFxOzAJGBwRK4CrgT4AKaWbgHvJ3fZvGblb/11UnEolSZKkjiuJsJ1SOn8n2xPwt91UjiRJklQQveoCSUmSJKmUGLYlSZKkjBi2JUmSpIwYtiVJklRQNTU1TJgwgWOOOYajjjqKq6++utglFY1hOyNvrK9h7v3Li12GJElS++rrYc4c+Kd/yv1bX9/lLvv168dDDz3E0qVLWbJkCffddx+///3vC1Bsz2PYliRJ2l3V18Mpp8D558PVV+f+PeWULgfuiKCyshKA2tpaamtrmx44s3TpUj7wgQ8wevRoysrKiAimT5/e5VMpVSVx6z9JkiRl4EtfgiVL2t6+bh08/zw0NOSWq6th3jyoqoJBg1rfp6oKvvWtnR66vr6ecePGsWzZMv72b/+WiRMnUlNTw7nnnstPfvITJkyYwNe//nVqamr4xje+0bnz6kEc2ZYkSdpdVVe/HbQbNTTk1ndReXk5S5YsYcWKFfzhD3/g2Wef5cEHH+S4445jwoQJAIwdO5b169c3jXr3Ro5sS5Ik9VY7G4GeMyc3daR5uK6shO9+F04/vSAlDBw4kJNOOon77ruPuro6xowZ07Ttqaee4rjjjivIcUqVI9uSJEm7q9NOg4kTcwE7IvfvxIm59V2wZs0a3njjDQC2bNnCAw88wBFHHMGgQYN4+umnAXjppZe46667OO+887p6FiXNkW1JkqTdVXk53H8//OY3ubndVVW5oF1e3qVuV61axdSpU6mvr6ehoYFPfvKTnH766VRXVzNr1iyOPvpoBg8ezO23386gtuaG9xKGbUmSpN1ZeXluykiBpo1Abi724sWLd1hfWVnJ7NmzC3acnsBpJJIkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRnyojSRJkgpuxIgRDBgwgPLycioqKli4cGGxSyoKw7YkSdJurL6hnt8s+w2LVy3m2AOP5bTDTqO8rGuPa280b948Bg8eXJC+eiqnkUiSJO2m6hvqOeWnp3D+L8/n6oev5vxfns8pPz2F+ob6zI65dOlSPvCBDzB69GjKysqICKZPn57Z8YrNkW1JkqRe6kv3fYklf13S5vZ1b63j+bXP05AaAKjeVs285fOouqmKQXsOanWfqgOq+Nap39rpsSOCk08+mYjg85//PJdeeik1NTWce+65/OQnP2HChAl8/etfp6amhm984xu7cno9gmFbkiRpN1W9rbopaDdqSA1Ub6tuM2x31KOPPsrQoUN5/fXXmTJlCkcccQQbN27kuOOOY8KECQCMHTuW++67j4jo0rFKmWFbkiSpl9rZCPScl+Zw/i/Pp3pbddO6yr6VfPfD3+X0Uad36dhDhw4FYL/99uPjH/84f/jDH6irq2PMmDFNbZ566imOO+64Lh2n1DlnuxvccvtDzL1/ebHLkCRJ2s5ph53GxKETqexbSRBU9q1k4tCJnHbYaV3qd/PmzWzatKnp9W9/+1uOPvpoBg0axNNPPw3ASy+9xF133cV5553X5fMoZY5sS5Ik7abKy8q5/4L7+c2y37Dkr0uoOqCqIHcjWb16NR//+McBqKur41Of+hSnnnoq1dXVzJo1i6OPPprBgwdz++23M2hQ16arlDrDtiRJ0m6svKyc00ed3uVpI829853vZOnSpTusr6ysZPbs2QU7Tk/gNBJJkiQpI4btDL2xvsa52pIkSbuxkgjbEXFqRLwYEcsiYlor24dHxLyIWBwRT0fEh4tRpyRJktQZRQ/bEVEO3AicBowGzo+I0S2a/R/g5ymlY4HzgO91b5WF4V1JJElSd0gpFbuEXmlX3teih21gArAspfRySmkb8DPgzBZtErB3/vU+wGvdWJ8kSVKP0b9/f9atW2fgLrCUEuvWraN///6d2q8U7kYyFHi12fIKYGKLNtcAv42ILwJ7AR9qraOIuBS4FGD48OEFL1SSJKnUDRs2jBUrVrBmzZpil7JL3qp9i/qGesoiNybc+G9r+pb3ZVv9NvqW96UhNdCnvE+mtfXv359hw4Z1ap9SCNsdcT5wa0rp3yPieOC2iDg6pe2fL5pSuhm4GWD8+PF+nJMkSbudPn36MHLkyGKXsct+9cKveG3ja7xjj3cAMGiPtu/DPeaAMTzz12cYc8AYqrdWM2rwqO4qs8NKYRrJSuDgZsvD8uuauxj4OUBK6XGgPzC4W6rLgHO3JUmSdg+lELafBA6PiJER0ZfcBZCzWrT5CzAZICKOJBe2e+Z3I5IkSdptFD1sp5TqgCuA+4EXyN115LmIuDYizsg3+3vgcxGxFLgduDD1gln/jnBLkiT1biUxZzuldC9wb4t105u9fh54X3fXJUmSJHVF0Ue2JUmSpN7KsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JkiRlxLAtSZIkZcSwLUmSJGXEsC1JkiRlxLBdInyapCRJUu9j2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJSUewCepPHl75AzdZtANTWNlDmRxlJkqTdmnGwQB5f+gK33vMgqdm6hgao2VK7S/3dcvtDzL1/eUFqkyRJUnEYtgvklw8sYFtt3Q7rN1fvuE6SJEm7B8N2gax7c1Or6xsaUqvre7pbbn+I/77z8WKXIUmSVNIM2wUyaJ8Bra4vK4turkSSJEmlwrBdIGdNeR99++x4velelV6DKkmStLsybBfI8cccyYVnfojm49hlZdB/jz5Fq0mSJEnFZdguoOOPOZL+/fpCQJ8+ZZSX+/ZKkiTtzkyDkiRJUkZKImxHxKkR8WJELIuIaW20+WREPB8Rz0XE/+vuGiVJkqTOKvrVexFRDtwITAFWAE9GxKyU0vPN2hwO/G/gfSmlDRGxX3GqlSRJkjquFEa2JwDLUkovp5S2AT8DzmzR5nPAjSmlDQAppde7uUZJkiSp00ohbA8FXm22vCK/rrlRwKiIWBARv4+IU7utOkmSJGkXlULY7ogK4HBgEnA+8F8RMbBlo4i4NCIWRsTCNWvWdG+FGbrl9oeYe//yYpchSZKkTiqFsL0SOLjZ8rD8uuZWALNSSrUppT8DL5EL39tJKd2cUhqfUho/ZMiQzAqWJEmSOqIUwvaTwOERMTIi+gLnAbNatLmb3Kg2ETGY3LSSl7uxRkmSJKnTih62U0p1wBXA/cALwM9TSs9FxLURcUa+2f3Auoh4HpgH/ENKaV1xKpYkSZI6pui3/gNIKd0L3Nti3fRmrxNwVf5HGbvl9oeo3raZMVPHFLsUSZKkHq3oI9uSJElSb2XY7oG8O4kkSVLPYNguoMeXvsCWrdsgQW1tA7W1DdRsqS12WZIkSSoSw3aBPL70BX541/07rN+0sZZNm2syPXZXRrodJZckScqOYbtAfvnAAuobUqvbNry5uZurkSRJUikwbBfIujc3tbmtvr6hGyvJaW3E2lFsSZKk7mXYLpBB+wxoc1tENxYiSZKkkmHYLpCzpryP8rLWU3VKeKGkJEnSbsiwXSDHH3MkF3/ilDa3b66u68ZqJEmSVAoM2wV0/DFHtrmtoY2LJyVJktR7GbYLrK3p2WVtTDGRJElS72XYLrCK8vId1kXAXpUVRahGkiRJxWQCLLCKinJq6+ublsvKgn0HVhJ96tvZS5IkSb2RI9tZCOjTp4w+fcoYNGQPBuzVv9gVSZIkqQgM25IkSVJGDNuSJElSRjo1Zzsivg68F1gJLE4p3ZhJVZIkSVIv0NmR7UHA74HrgHcVvhxJkiSp9+js3Ug2AOXA68D6wpfTe9VsqWX92nXU1zewvryGPfcqp7JvsauSJElSljoVtlNK34iIg4DvAM9mU1Lvs2lzDZs21jYt19c3sGljA/0qaog+RSxMkiRJmepw2I6Ih4CngEXA9SmllzKrqheor2+goQHWrH6rzTZr128C4F9vmk1FRbnBW5IkqZfpzJztB4GB+X0uiIjbM6moN0jQ0NDx5m9sfIt1GzZRs6V2540lSZLUY3Q4bKeU/hn4J+A9wAsppfMzq2o3lBJsrq4rdhmSJEkqoA6H7Yg4HfgU0AB8IiLKM6tqN9XQkIpdgiRJkgqoMxdI3ggsAGYDT6WU6rMpafdVVhbFLkGSJEkF1JlpJIcA/wuoAT4VEb/IrKrdUATsVdnZOzFKkiSplHXqoTYppRXAKGDPbMrpJQLKOvm4oIhg08Za/vWm2Sx5/pVs6pIkSVK36syc7U/mX94IHAz8JZOKeony8jL69CljyP57MvLgIQzZf08G7zugzbaN87Xf2PgWd9/3JJs213RnuZIkScpAZ8Zf3x8R/YF/B/4BWJ1NSb3XgL36M2DvPpSX59728vIyInL35G6utq6eDW9uLkaJkiRJKqDOhO0AbgWqyY1sj8qioN6u/x59GH7QIIbsvyfDDxpEauMGJC0DuCRJknqeDl+Rl1L6IkBEHA18CPh6VkXtTsrKotVb/jWOfkuSJKnn6syc7Wsj4p+B0cC9KaVVhSoiIk6NiBcjYllETGun3VkRkSJifKGOXWx7VVbQp2L7W5b3qSjnHfvsVaSKJEmSVCidufXfdODbwJvAxyPivwpRQP7hODcCp5EL8udHxOhW2g0ArgSeKMRxS0X/PfrwsVPf3TSSPXDvPfnYqe9mwF79i1yZJEmSuqqzt/5bnVK6P6V0PXBbgWqYACxLKb2cUtoG/Aw4s5V2/wRcT+4+371K1ehDmuZx/8NlH6Vq9CHFLkmSJEkF0JWJwWcXqIahwKvNllfk1zWJiOOAg1NKvy7QMSVJkqTMdfgCyYiYBfwZeApY1Jl9uyIiyoAbgAs70PZS4FKA4cOHZ1uYJEmSMjPv7tepq0sM2W9T07rJp4xote0ttz9EfUM937is9G6Wt9OR7Yi4BiCldAa50LsROA8o1FyHleRuJdhoWH5dowHA0cDDEbEceA8wq7WLJFNKN6eUxqeUxg8ZMqRA5XW/Jc+/wl9eW8ea1W/xrzfN9gE3kiRJPVRHRqenR8QewL7kRrV/llL6VQFreBI4PCJGkgvZ5wGfatyYUnoTGNy4HBEPA19JKS0sYA0lY8nzr3D3fU823Wf7jY1vEQGVA/oUuTJJkqTu98b6twcd596/vM3R7VLVkTnbidxFifeTG4F+LCKOKVQBKaU64Ip8/y8AP08pPZe/1eAZhTpOT/HA/Kepravfbl1KsLm6rkgV7ahx5P0vK9fzlX+7hceXvlDskiRJkkpSR0a2/yeldHX+9Z0RcStwE/DBQhWRUroXuLfFuulttJ1UqOOWojc2vtXq+tYefFMMLUfe1725iVvveRCA4485spilSZKkXu6N9TU9bnS7IyPbayNiXONCSukloOdOiC5xA/fes9X1ZWXRzZW0rrWR9221dfzygQVFqkiSJKl0dSRs/x3w04j4aUT8Y0T8N7m7kigDUz4wdocnSkbknjRZCtoaeV/35qZW10uSJHXWU/dto6629W/1G0e3e4qdhu2U0lKgCrg9v2oecH6GNe3WqkYfssMTJQe9YwD99yiNCyTbGnkftM+Abq5EkiSp9HXooTYppa0ppV+nlK5PKd2SUtqcdWG7s5ZPlCylR7e3NvLet08FZ015X5EqkiRJvcm9v3iFhtK5L0SXdeUJkuoGLe+5veT5V4p6vJYj74P2GcCFZ37IiyMlSZJaYdguYTVbane45/bd9z2Z2UNuWrvH9933Pdlq4B5+0CCGD92Xf/vKJQZtSZLUrXrSvG3DdgnbXF23w50/auvq2fBmNrN4WrvTSG1dPQ/MfzqT40mSJPV2pXGLC7WqrXtrN448F1pbdxppa70kSVKh1dV1/Nkit9z+ENXbNlPZd68MK+oaw3YJKyuLNgN345zqUe88kL+8to76+gb+9abZTPnAWIAd1lWNPmSHPhrnZze223OPvry1ZdsO7dq6A4kkSZLaZ9guYXtVVlDzVsMOUzsavbHxLf6w5E/bLf/y108QZbHDvOuWWpufXRZBeXnZdiPnfSrKmwK8JEmSOsc52yWs/x59trvzR8TOnyLZkNIO00xam3fd2vzshpTo26d8u3t8f+zUd7c6Ki5JkqSdc2S7xFWNPoSFS/9E9bbNrFm963On39j4Fu/YZ6/tlluzpaaWkQcPoXrbZr409aO7fDxJkqTOmvHDX0DHp2z3CI5s9yBdmTu9R/8+290/e889+hb8GJIkSQWVoLa2gdrahsxuEJE1w3YP0trTG1tqnHfdct222vrt5mfX1NTu0M752ZIkqVQ1NGR3R7YsGbZ7kJZPbxy4955MqDp0u+WzPjKRT5w2Ybt1/fv32eGP0/nZkiSp1LyyanW723ti4HbOdg/TfA5345zqNes27jDHunmbr33zjlb7cn62JEkqFd/88Z3UbK3dabuGBoCeE7gd2d4NtDUP2/nZkiSpVLzw8qsdbtvQc7K2YXt30Npcb+dnS5KkUvGT2XM7vU/Nlp2PgpcCw/ZuoLW53s7PliRJpeKRJ5/p9D6bq+syqKTwnLO9m2htrrckSVIpaEidv7l2Q0PPuCG3I9uSJEkqqrIOPCW7pzJsS5IkqahOfPeYNreVtZNWN22uyaCawjJsS5Ikqag+89HJnDShlRs3BDs8hK+5dRs2ZVhVYRi2JUmSVHSf+ehk3jViWKf2San070pi2JYkSVKPtWljLX95bR1/Wbmer/zbLTy+9IVil7Qdw7YkSZJKWnvztuHtR7ive3MTt97zYEkFbsO2JEmSSlp787Zb2lZbxy8fWJBhNZ1j2JYkSVJJmHbxOVT0af02gDsb3W5u3Zulc+GkYVuSJEklrzOj24P2GZBhJZ1j2JYkSVKv0bdPBWdNeV+xy2hi2JYkSVKvUBbBhWd+iOOPObLYpTQpibAdEadGxIsRsSwiprWy/aqIeD4ino6IuRFxSDHqlCRJUrY+fM4hlFW0vm1n87YbUiqpoA0lELYjohy4ETgNGA2cHxGjWzRbDIxPKY0F7gS+2b1VSpIkqdjKy8vaDdylNFe7UdHDNjABWJZSejmltA34GXBm8wYppXkppbfyi78HOvd4IUmSJPUYx53at827krQVuCMoqbnajUohbA8FXm22vCK/ri0XA7/JtCJJkiSVrPLyMgbs3afpDiXl5WW8Y5+9Sm4KCUAbM2JKU0RcAIwHTmxj+6XApQDDhw/vxsokSZJUSCd9bD8AlszfxBvra3bY3n+PPgzeZyDV2zZT2Xcv6hvqu7vEDimFke2VwMHNlofl120nIj4EfA04I6W0tbWOUko3p5TGp5TGDxkyJJNiJUmSpI4qhbD9JHB4RIyMiL7AecCs5g0i4ljgB+SC9utFqFGSJElFMPmUEQzct3+xy9hlRQ/bKaU64ArgfuAF4Ocppeci4tqIOCPf7F+BSuAXEbEkIma10Z0kSZJUMkpiznZK6V7g3hbrpjd7/aFuL0qSJEklYfIpI5h7//JW526XuqKPbEuSJEm9lWFbkiRJJa+nzt02bEuSJEkZMWxLkiRJGTFsS5IkSRkxbEuSJEkZMWxLkiRJGTFsF9El53+QyaeMKHYZkiRJPUJPvCOJYVuSJEnKiGG7G7U3ku0otyRJ0s71tNFtw7YkSZKUEcN2gQ0/cD8qKqLYZUiSJKkEGLYlSZKkjFQUu4Deqr25RJec/0Ge+esz3ViNJElS79F4ndsT89YUt5AOcGRbkiRJyogj2xnqyqeuYo5+O/IuSZJ6gsbMUsoj3I5sS5IkSRlxZLuXynp0+pLzP0j11urM+pckSeoNHNmWJEmSMuLIdjfo6ijzzvbvSv/Oz5YkST1dKX/j7si2JEmSlBFHtnsgR6MlSZJ6Bke2JUmSpIwYtjOwzzv6Nt1jW5IkSbsvw7YkSZKUEeds70ac6y1JktS9HNmWJEmSMmLYLrBpF5/D+z50YLHLkCRJUgkwbEuSJEkZMWxLkiRJGTFsS5IkSRkpibAdEadGxIsRsSwiprWyvV9E3JHf/kREjChCmZIkSVKnFD1sR0Q5cCNwGjAaOD8iRrdodjGwIaV0GPAfwPXdW6UkSZLUeUUP28AEYFlK6eWU0jbgZ8CZLdqcCczMv74TmBwR0Y01SpIkSZ1WCmF7KPBqs+UV+XWttkkp1QFvAoNadhQRl0bEwohYuGbNmozKlSRJkjqmFMJ2waSUbk4pjU8pjR8yZEixy5EkSdJurhTC9krg4GbLw/LrWm0TERXAPsC6bqlOkiRJ2kWlELafBA6PiJER0Rc4D5jVos0sYGr+9dnAQyml1I01SpIkSZ1WUewCUkp1EXEFcD9QDvwopfRcRFwLLEwpzQJ+CNwWEcuA9eQCuSRJklTSih62AVJK9wL3tlg3vdnrGuCc7q5LkiRJ6opSmEYiSZIk9UqGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSOGbUmSJCkjhm1JkiQpI4ZtSZIkKSNFDdsRsW9EPBARf8z/+45W2lRFxOMR8VxEPB0R5xajVkmSJKmzij2yPQ2Ym1I6HJibX27pLeAzKaWjgFOBb0XEwO4rUZIkSdo1xQ7bZwIz869nAh9r2SCl9FJK6Y/5168BrwNDuqtASZIkaVcVO2zvn1JalX/9V2D/9hpHxASgL/CnNrZfGhELI2LhmjVrClupJEmS1EkVWR8gIh4EDmhl09eaL6SUUkSkdvo5ELgNmJpSamitTUrpZuBmgPHjx7fZlyRJktQdMg/bKaUPtbUtIlZHxIEppVX5MP16G+32Bn4NfC2l9PuMSpUkSZIKqtjTSGYBU/OvpwL3tGwQEX2BXwE/SSnd2Y21SZIkSV1S7LA9A5gSEX8EPpRfJiLGR8Qt+TafBD4AXBgRS/I/VUWpVpIkSeqEzKeRtCeltA6Y3Mr6hcAl+dc/BX7azaVJkiRJXVbskW1JkiSp1zJsS5IkSRkxbEuSJEkZMWxLkiRJGTFsS5IkSRkxbEuSJEkZMWxLkiRJGTFsS5IkSRkxbEuSJEkZMWxLkiRJGTFsS5IkSRkxbEuSJEkZMWxLkiRJGTFsS5IkSRkxbEuSJEkZMWxLkiRJGakodgG90dDKodQ21DYtV2+tprJvZbv/Atu9brnc3rbWlrNY11K/in7tbpckSeqsoZVDKaOMPfvuCcCeFXu22bZ5lirVXBIppWLXkInx48enhQsXFrsMSZIk9XIRsSilNL61bU4jkSRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjISKaVi15CJiFgDvFLsOjKyD/BmsYtQ5vw9F4/v/a7xfds93oPeco498TwGA2uLXYRadUhKaUhrG3pt2O7NIuLmlNKlxa5D2fL3XDy+97vG9233eA96yzn2xPOIiIUppfHFrkOd4zSSnml2sQtQt/D3XDy+97vG9233eA96yzn2lvNQiXNkW5IkqQdwZLtncmRbkiSpZ7i52AWo8xzZliRJkjLiyLYkSZKUEcO2JEmSlBHD9m4kIvaKiIURcXqxa1F2/D2rp/FvVlJvZtjuRhHRPyL+EBFLI+K5iPhGF/r6UUS8HhHPtrLt1Ih4MSKWRcS0Zpv+Efj5rh5TnRMR5RGxOCLmdKEPf8/qFhExMCLujIj/iYgXIuL4XezHv1mpG0TEpIj4XUTcFBGTil2P2mbY7l5bgQ+mlI4BqoBTI+I9zRtExH4RMaDFusNa6etW4NSWKyOiHLgROA0YDZwfEaMjYgrwPPB6Ac5DHXMl8EJrG/w9qwR9G7gvpXQEcAwt/nb9m5Wy19aH1TY+qCagGugPrOjuWtVxhu1ulHKq84t98j8tbwdzInB3RPQDiIjPAd9tpa/5wPpWDjMBWJZSejmltA34GXAmMAl4D/Ap4HMR4e8+QxExDPgIcEsbTfw9q2RExD7AB4AfAqSUtqWU3mjRzL9ZKXu30uLDalsfVIHfpZROI/fN0C5/U67sVRS7gN1N/n80i4DDgBtTSk80355S+kVEjATuiIhfAJ8FpnTiEEOBV5strwAmppSuyB//QmBtSqlh189CHfAt4H8BA1rb6O9ZJWYksAb4cUQcQ+6/UVemlDY3NvBvVspeSml+RIxosbrpgypARPwMODOl9Hx++wagX/dVqc5yBKGbpZTqU0pVwDBgQkQc3UqbbwI1wPeBM5qNhhfi+LemlHZ5DrF2Ln+R1+sppUXttfP3rBJSARwHfD+ldCywGZjWspF/s1JRtPZBdWhEfCIifgDcBvxnUSpThxi2iyT/Fe08Wp/beAJwNPAr4OpOdr0SOLjZ8rD8OnWf9wFnRMRycl+VfzAiftqykb9nlZAVwIpm37TdSS58b8e/Wal0pJTuSil9PqV0bkrp4WLXo7YZtrtRRAyJiIH513uQ+wr2f1q0OZbc41jPBC4CBkXE/+3EYZ4EDo+IkRHRFzgPmFWA8tVBKaX/nVIallIaQe79fyildEHzNv6eVUpSSn8FXo2Id+VXTSZ30WIT/2alovGDag9n2O5eBwLzIuJpcv/H80ArX5vuCXwypfSn/NzFzwCvtOwoIm4HHgfeFRErIuJigJRSHXAFcD+5uwn8PKX0XGZnpF3l71ml5ovAf+f/+1QF/HOL7f7NSsXhB9UeLlJqeTMMSZIkdbf8h9VJwGBgNXB1SumHEfFhchfelwM/SildV7Qi1WmGbUmSJCkjTiORJEmSMmLYliRJkjJi2JYkSZIyYtiWJEmSMmLYliRJkjJi2JYkSZIyYtiWpBYiYlBELMn//DUiVuZfV0fE97qphvER8Z0M+58UEbdGxIURcU2B+74wIv5zF/br0jlHxPKIGBERDzdbd2xE/LCdfYZExH27ekxJ2pmKYhcgSaUmpbSO3FMUyQfR6pTSv3VzDQuBhd15zGLL6Jy/CrT5WPmU0pqIWBUR70spLSjwsSXJkW1J6qj8aPCc/OtrImJmRPwuIl6JiE9ExDcj4pmIuC8i+uTbjYuIRyJiUUTcHxEHttLvORHxbEQsjYj5bRzrRxHxcES8HBF/12zfz0TE0/l9b8uvGxIRv4yIJ/M/72vldLYBbwJbgOr29ouIeyLiM/nXn4+I/86/fjgivp0f9X82Iia0cm4jIuKhfI1zI2J4B89534i4O7/f7yNi7M7eC2ANUA+sz7cdAIxNKS3NL5/Y7BuLxfntAHcDn27nVy9Ju8yRbUnadYcCJwGjgceBs1JK/ysifgV8JCJ+DXwXODM/gnoucB3w2Rb9TAdOSSmtjIiBbRzriPyxBgAvRsT3gVHA/wHem1JaGxH75tt+G/iPlNKj+XB7P3Bk885SSo8Bj7U4Rlv7XQosiIg/A38PvKfZPnumlKoi4gPAj4CjW/T5XWBmSmlmRHwW+A7wsQ6c8zeAxSmlj0XEB4GfkP+2obX3IqVUm1J6d377J/L/jgeebdbnV4C/TSktiIhKoCa/fiHtjH5LUlcYtiVp1/0mpVQbEc8A5UDj3N9ngBHAu8iFzwcignybVa30swC4NSJ+DtzVxrF+nVLaCmyNiNeB/YEPAr9IKa0FSCmtz7f9EDA6f0yAvSOiMqVUvZPzaWu/1RExHZgHfLzZcQBuzx97fkTs3UpwPp63w+9twDc7eM7vB87K9/1Q5ObR793Oe7GilT4OJDfa3WgBcEN+ZP6ulFLjPq8DB7WyvyR1mWFbknbdVoCUUkNE1KaUUn59A7n/vgbwXErp+PY6SSldFhETgY8AiyJiXFvHyqun/f9+lwHvSSnVtNOms/uNAdaxYyhNO1luVQfPuS0dfS+2AP2bHXNG/tuGD5MbqT8lpfQ/+TZbOnF8Seow52xLUnZeBIZExPEAEdEnIo5q2SgiDk0pPZFSmk5uJPbgDvb/EHBORAzK99M4jeS3wBeb9V/Vwf5a3S8/F/s04FjgKxExstk+5+bbvB94M6X0Zos+HwPOy7/+NPC7fPudnfPv8u2JiEnA2pTSxg6eR6MXgMOanc+hKaVnUkrXA0+Sm44Cuek4z7ayvyR1mWFbkjKSUtoGnA1cHxFLgSXAe1tp+q+Ru7DyWXLhdGkH+3+O3BzwR/L935Df9HfA+PzFhc8Dl3Ww5B32i4h+wH8Bn00pvUZuzvaP4u25JjURsRi4Cbi4lT6/CFwUEU8DfwNc2cFzvgYYl99vBjC1g+fQJD9qvU+zCyG/lL8o82mgFvhNfv1JwK87278kdUS8/a2nJEkdF7n7WX8lf8u+khQRXwY2pZRuaafNfHIXsW7ovsok7S4c2ZYk9WbfZ/s53tuJiCHADQZtSVlxZFuSJEnKiCPbkiRJUkYM25IkSVJGDNuSJElSRgzbkiRJUkYM25IkSVJGDNuSJElSRv4/qx3EH5IGwgQAAAAASUVORK5CYII=\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": "iVBORw0KGgoAAAANSUhEUgAAAtIAAAGFCAYAAADQCRR4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAAA/FUlEQVR4nO3de5iWVb34//eH4aCIYo6oBIqZWrJlwBMIltuwSS0PFebZhI2RXWpZUmH7u1FrV1huTWx/t9vCUPOr2y0qYnnAEE0lBZWDh/SnCQqRwqCjCAgzrN8fzzPjzDgzzDwzz/PM4f26rrm873Wve63P/QxNn1mz7rUipYQkSZKk1ulR7AAkSZKkzshEWpIkScqBibQkSZKUAxNpSZIkKQcm0pIkSVIOTKQlSZKkHJhIS5JaJCLmR8S5BerrzIh4sIV1x0fEY/mOSZIaMpGW1KlExPKI2BgR6+t8/TrPfR4VESsblF3XIIYPIuK9OtfnR8SmOtdfqnNtYETcExF/j4gUEXu3Mb6Z2XZG1inbNyJy3iggIi6LiN+3Ja62SCndklL6Qnu01dgvABGxd0Q8HBEbIuKvEfH5OtcOjIgHImJtWz5DSV2fibSkzuiElFK/Ol8XFDqAlNJ5dWMAbgX+t0G1C+rU+VSd8q3A/cC4dgxpHfDv7dFQRPRsj3Y6uFuBZ4FS4F+BOyJiQPbaFuB2YGKRYpPUSZhIS+oSIqJPRLwTEQfWKRuQHb3eLXt+fEQsztZ7IiLK6tRdHhGTI2JpRFRGxP9ExHYRsQNwH/DxOqPLH2/Q9w5kkuIbWxJrSunNlNL/BRa2w6PXuBEoi4h/buxiRHw8Owq+LiJeiYhv1Ll2WUTcERG/j4h3gfOAHwGnZp93SZ2mhkTE4xHxXkQ8GBG75hpwdhT9vIj4/7Lfk/+MiMheqzddIyK+EBEvZb83/zciHmlklPnKiHg7Il6LiOOyZT8FPgv8uuavFxGxP3AwcGlKaWNKaRawjOwvNimll1JKM4Dnc302Sd2DibSkLiGl9AFwJ3B6neJTgEdSSm9FxEHADcA3yYxC/jdwT0T0aVD/WOATQBkwPqX0PnAc8Pc6o8t/b9D9OGAN8GiD8p9npwc8HhFHtcdzNmMD8DPgp01cvw1YCXwcOBn4WUSMrXP9JOAOYGdgRrat/8k+7/A69c4AJgC7Ab2ByW2M+3jgMDKf9ynAMQ0rZJP1O4BLyHzvXgLGNKg2Klu+K/ALYEZERErpX4E/8+FfBy4A/gn4W0rpvTr3L8mWS1KLmUhL6ozuzo5g1nzVjK7+P+C0OvXOyJYBTAL+O6X0ZEqpOqV0I/ABcHid+tNTSn9PKa0D5gAjWhjPOcBNKaW682l/COwDDAKuB+ZExCdb8Yy5+G9gr5rR2BoRsSdwBPDDlNKmlNJi4LfA1+tUW5BSujultDWltLGZPn6XUno5W+d2Wv4ZNWVaSumdlNLrwMNNtPdF4PmU0p0ppSpgOvCPBnVWpJR+k1KqJjM6PxDYvYk++wGVDcoqgR1zfAZJ3ZSJtKTO6MsppZ3rfP0mW/4w0DciRmVf4BsB3JW9NgS4uG4CDuxJZoS2Rt3kbAOZhKtZEbEXcBRwU93ybML+Xkrpg2zS/jiZhLBVIrN6Rc2Ukvuaq5sdlf9J9quujwPrGozAriCT5Nd4o4Uhtegzioj76sR9Zhvb+3jd+LK/sKxsUOcfda5vyB429f1bD+zUoGwn4L1G6kpSk7rDCyWSuomUUnVE3E5mesebwL11ksc3gJ+mlJqa+tBs081cOxt4PKX0txa0Ea3uOKVbgFtaccvvyIyGf7VO2d+BXSJixzqfx17Aqgbx0cx5q6SUjtt2rRZbDQyuOcnOox7cdPWPhtPg/Hlgnwafx3A+/OuFJLWII9KSupr/B5wKnEn9xOg3wHnZ0eqIiB0i4ksR0ZI/578JlEZE/0aufR2YWbcgInaOiGOyLyv2zI7IHklmpY6aOtsBNfOz+2TP2yw79eFSMsl0TdkbwBNk5mxvl33JciLQ3PJ2bwJ7R0RH+P+JPwDDIuLL2RVFzgf2aMX9b5KZZgNASullYDFwafbz+AqZOdqzIJOoZ78fvbPn2zWYSy9JgIm0pM5pTtRfw7lm+gYppSeB98lMB7ivTvki4BvAr4G3gVeA8S3pLKX0VzLLpf0tOy3k4wARMZrMyGjDZe96kVmKbg2wFriQzHSUl+vU2UhmigHAX7Pn7eVWMqO4dZ0O7E1mdPouMitWPNRMGzXPVBERz7RjbK2WUloLfI3MS4QVwFBgEZk57i1xDXBydkWP6dmy04BDyfxbmAacnFJak702hMz3o2bVjo1kXmSUpHqi/rsxkiR1bNlR8pXAmSmlh4sdj6TuyxFpSVKHl50qs3N2isWPyMw3/0uRw5LUzZlIS5I6g9HAq2SmypxAZqpMe06HkaRWK9jUjogoITOnbVVK6fgG18YDv+TDN8h/nVL6bUECkyRJknJQyOXvvgO8yEfX7qzxP9kdpyRJkqQOryCJdEQMBr5EZuva77VHm7vuumvae++926Op1tm8OfMF0LMn9KiC3jUfYxX1P9KG57mWt9f11tZrbd1c6ud6T1vua+u97XF/R2qjo7eVj/Y6U5v5bLdpm6ur2Jry02fV1ip69uh8bdu+fXTGPgrZT771iB70LuldlL6ffvrptSmlAQ3LC/Wp/gr4Ac1vvzouIo4EXga+m133tJ6ImERmm1/22msvFi1alIdQt+Hll+G11zLHw4bBpmWwz7DsxWXAsDqVG57nWt5e11tbr7V1c6mf6z1tua+t97bH/R2pjY7eVj7a60xt5rPdpr28dhn9+uSnz2X/WMawPTpf27ZvH52xj0L2k2/rP1jP/rvuX5S+I2JFY+V5f9kwIo4H3kopPd1MtTnA3imlMmAucGNjlVJK16eUDk0pHTpgwEd+KZAkSZIKphCrdhwBnBgRy4HbgLERUW83rZRSRUqpZmH93wKHFCAuSZIkKWd5T6RTSpeklAanlPYms5PUvJTSWXXrRMTAOqcnknkpUZIkSeqwijbzPCJ+DCxKKd0DfDsiTiTzRs06Wrhtb0Nbtmxh5cqVbNq0qf0C/WgnUDOtZN06SAPgxXXZiwPIhE8T57mWt9f11tbL1N1uu0oGD+5Hr14lLbxHkiSp6ytoIp1Smg/Mzx5PrVN+CXBJW9tfuXIlO+64I3vvvTcR0dbmGrdpE3yQnYWy/faQNkKf7bMXNwLb16nc8DzX8va63tp6kNIGKio2sXLl23ziE/1bdI8kSVJ30KV2Nty0aROlpaX5S6K7oYigtPRjbNpUXexQJEmSOpQulUgDJtF54GcqSZL0UV0ukZYkSZIKwURakiRJyoGJdDuL6MPFF/+g9vzKK6/isst+0qo25s9/hCeeWFB7Pn78udxxx505x/TGG2/wuc99gaFDh/NP/zSCa675r9pr69ato7z8OPbbbyjl5cfx9ttv59yPJElSd9L5N15vzJw5+Wt782b4/OebvNynTx/uvPNuLrnkB+y66w6tbr6qqor58x+lX78dGDNmdFsirdWzZ0/+4z+u4OCDD+K9997jkENGUV5+HEOHHsC0ab/k6KPHMmXK95k27ZdMm/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/notebooks/plotVisibility.ipynb b/rtavis/notebooks/plotVisibility.ipynb deleted file mode 100644 index df81102..0000000 --- a/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+3OVlZWpp07d7oO4Dmvprjrrrt07733KiUlRfHx8TXW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rVJTUz3aZvTo0Ro9erSPKgr89t1JTU31+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+8b1qNHD98UEyTtuzN58mSzSwAAWBCXbgIAAACAxRD0AAAN0rZtW6Wmpnq0zejRozV69GgfVRT47buTmprq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e7V69WpVVFRo0aJF6tChg9q1aydJysjI0NKlS1VSUqKSkhItXbqUU9EAAAAA0EA2h8Ph8NaL5eTkaOHChdWeGzFihLKysvT111/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/readVisTable.py b/rtavis/readVisTable.py deleted file mode 100644 index 7784fa4..0000000 --- a/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/runCatVisibility.py b/rtavis/runCatVisibility.py deleted file mode 100644 index 920d139..0000000 --- a/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/utils/__init__.py b/rtavis/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/rtavis/utils/functions.py b/rtavis/utils/functions.py deleted file mode 100644 index d1fd210..0000000 --- a/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/utils/visibility.py b/rtavis/utils/visibility.py deleted file mode 100644 index 3abae6b..0000000 --- a/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 -- GitLab From c28ab405281adcf64be4e7af580020c2c80fec4b Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 14:49:37 +0200 Subject: [PATCH 16/30] update setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8585013..d40ff4c 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ from setuptools import setup, find_packages setup( name='astrort', author='Ambra Di Piano ', - package_dir={'rtavis': 'rtavis', 'rtamock': 'rtamock', 'rtasci': 'rtasci', 'rtadeep': 'rtadeep'}, + package_dir={'astrort': 'astrort'}, packages=find_packages(), include_package_data=True, license='BSD-3-Clause', -- GitLab From 30aa134c5982ec501afc1e16d36bf887b7fd36bf Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 14:50:41 +0200 Subject: [PATCH 17/30] update readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60292ea..1f10de7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # astroRT - ## Environment To create a virtual environment with all required dependencies: @@ -23,6 +22,8 @@ To install editable version use instead: pip install -e . ``` +# Submodules + ## rtasci Simulator of CTA observations. @@ -35,6 +36,6 @@ Visibility tool for CTA observations. Scenario mocking software to reproduce CTA real-time observation and analysis conditions. -## rtadeep +## rtadeep [TODO] Deep learning solutions for CTA real-time analysis. \ No newline at end of file -- GitLab From 172955ea712eb4bd148ab915abf06b039d370c5a Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 14:51:46 +0200 Subject: [PATCH 18/30] rename --- .../test_cfg/test_check_configuration.py | 0 .../test_simulator/test_base_simulator.py | 0 .../{test_rtadeep => test_astrort}/test_utils/test_wrap.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename astrort/testing/{test_rtadeep => test_astrort}/test_cfg/test_check_configuration.py (100%) rename astrort/testing/{test_rtadeep => test_astrort}/test_simulator/test_base_simulator.py (100%) rename astrort/testing/{test_rtadeep => test_astrort}/test_utils/test_wrap.py (100%) diff --git a/astrort/testing/test_rtadeep/test_cfg/test_check_configuration.py b/astrort/testing/test_astrort/test_cfg/test_check_configuration.py similarity index 100% rename from astrort/testing/test_rtadeep/test_cfg/test_check_configuration.py rename to astrort/testing/test_astrort/test_cfg/test_check_configuration.py diff --git a/astrort/testing/test_rtadeep/test_simulator/test_base_simulator.py b/astrort/testing/test_astrort/test_simulator/test_base_simulator.py similarity index 100% rename from astrort/testing/test_rtadeep/test_simulator/test_base_simulator.py rename to astrort/testing/test_astrort/test_simulator/test_base_simulator.py diff --git a/astrort/testing/test_rtadeep/test_utils/test_wrap.py b/astrort/testing/test_astrort/test_utils/test_wrap.py similarity index 100% rename from astrort/testing/test_rtadeep/test_utils/test_wrap.py rename to astrort/testing/test_astrort/test_utils/test_wrap.py -- GitLab From 3c6f96344678517ecd1840f16c34300f6bf01082 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 14:56:11 +0200 Subject: [PATCH 19/30] add submodules --- .gitmodules | 9 +++++++++ rtamock | 1 + rtasci | 1 + rtavis | 1 + 4 files changed, 12 insertions(+) create mode 100644 .gitmodules create mode 160000 rtamock create mode 160000 rtasci create mode 160000 rtavis diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7b11121 --- /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/rtamock b/rtamock new file mode 160000 index 0000000..e0b346b --- /dev/null +++ b/rtamock @@ -0,0 +1 @@ +Subproject commit e0b346bca408515cbd94787374eadbf511d4708b diff --git a/rtasci b/rtasci new file mode 160000 index 0000000..20ce02e --- /dev/null +++ b/rtasci @@ -0,0 +1 @@ +Subproject commit 20ce02eb59e3598b72a14d85424a6e57ea3a3a55 diff --git a/rtavis b/rtavis new file mode 160000 index 0000000..1d895dc --- /dev/null +++ b/rtavis @@ -0,0 +1 @@ +Subproject commit 1d895dca2694f40c4b63ea2714c76d08cfce1d41 -- GitLab From a33bb2a900cdd4444c400eac616c772532d880d1 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 15:05:33 +0200 Subject: [PATCH 20/30] installation script --- .gitignore | 1 - install.sh | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100755 install.sh diff --git a/.gitignore b/.gitignore index b660af4..b3ff366 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Patterns .* -*.sh *.png # Folders diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..2324587 --- /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 -- GitLab From e6250ece55d8a7fb10d50ee23c7012c5715c91c8 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 15:05:41 +0200 Subject: [PATCH 21/30] fix conftest --- astrort/testing/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrort/testing/conftest.py b/astrort/testing/conftest.py index 6e519d8..2f6902e 100644 --- a/astrort/testing/conftest.py +++ b/astrort/testing/conftest.py @@ -7,7 +7,7 @@ # ***************************************************************************** import pytest -import rtadeep +import astrort from os.path import join, dirname, abspath @pytest.fixture(scope='function') -- GitLab From 348d6c2aef670ac50f4f4d178d5d87ada716770f Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 15:05:45 +0200 Subject: [PATCH 22/30] skip to do --- .../testing/test_astrort/test_simulator/test_base_simulator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrort/testing/test_astrort/test_simulator/test_base_simulator.py b/astrort/testing/test_astrort/test_simulator/test_base_simulator.py index 912c860..b209240 100644 --- a/astrort/testing/test_astrort/test_simulator/test_base_simulator.py +++ b/astrort/testing/test_astrort/test_simulator/test_base_simulator.py @@ -13,10 +13,10 @@ 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) - CheckConfiguration(configuration=configuration).check() -- GitLab From 85b00fcb41c6a3e095461ae0d4899081acd075e8 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:17:24 +0200 Subject: [PATCH 23/30] renamed --- astrort/cfg/check_configuration.py | 35 ------------------- astrort/cfg/test.yml | 12 ------- astrort/{cfg => configure}/__init__.py | 0 astrort/configure/test.yml | 15 ++++++++ .../test_check_configuration.py | 20 +++++------ 5 files changed, 25 insertions(+), 57 deletions(-) delete mode 100644 astrort/cfg/check_configuration.py delete mode 100644 astrort/cfg/test.yml rename astrort/{cfg => configure}/__init__.py (100%) create mode 100644 astrort/configure/test.yml rename astrort/testing/test_astrort/{test_cfg => test_configure}/test_check_configuration.py (64%) diff --git a/astrort/cfg/check_configuration.py b/astrort/cfg/check_configuration.py deleted file mode 100644 index 1722480..0000000 --- a/astrort/cfg/check_configuration.py +++ /dev/null @@ -1,35 +0,0 @@ -# ***************************************************************************** -# 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', 'irf', 'prod', 'coordinates', 'duration', 'samples', 'seed'] - assert self.conf['simulator'].keys() == keys - return self - - def check_visibility(self): - keys = ['start_time'] - assert self.conf['visibility'].keys() == keys - return self diff --git a/astrort/cfg/test.yml b/astrort/cfg/test.yml deleted file mode 100644 index a1c4afe..0000000 --- a/astrort/cfg/test.yml +++ /dev/null @@ -1,12 +0,0 @@ -simulator: - name: test - irf: North_z60_0.5h_LST - prod: prod5 - coordinates: random - duration: 300 - samples: 5 - seed: 1 - -visibility: - start_time: today - diff --git a/astrort/cfg/__init__.py b/astrort/configure/__init__.py similarity index 100% rename from astrort/cfg/__init__.py rename to astrort/configure/__init__.py diff --git a/astrort/configure/test.yml b/astrort/configure/test.yml new file mode 100644 index 0000000..5728707 --- /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/astrort/testing/test_astrort/test_cfg/test_check_configuration.py b/astrort/testing/test_astrort/test_configure/test_check_configuration.py similarity index 64% rename from astrort/testing/test_astrort/test_cfg/test_check_configuration.py rename to astrort/testing/test_astrort/test_configure/test_check_configuration.py index fdc053e..903a82d 100644 --- a/astrort/testing/test_astrort/test_cfg/test_check_configuration.py +++ b/astrort/testing/test_astrort/test_configure/test_check_configuration.py @@ -8,34 +8,34 @@ import pytest from astrort.utils.wrap import load_yaml_conf -from astrort.cfg.check_configuration import CheckConfiguration +from astrort.configure.check_configuration import CheckConfiguration -@pytest.mark.rtadeep_configuration +@pytest.mark.astrort_configuration class TestCheckConfiguration: - def test_check(self, rtadeep_configuration): - configuration = load_yaml_conf(rtadeep_configuration) + 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, rtadeep_configuration): - configuration = load_yaml_conf(rtadeep_configuration) + 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, rtadeep_configuration): - configuration = load_yaml_conf(rtadeep_configuration) + 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, rtadeep_configuration): - configuration = load_yaml_conf(rtadeep_configuration) + def test_check_visibility(self, astrort_configuration): + configuration = load_yaml_conf(astrort_configuration) try: CheckConfiguration(configuration=configuration).check_visibility() except AssertionError as e: -- GitLab From e7a7a057295f215368b131e4b971dce3146d8e64 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:17:42 +0200 Subject: [PATCH 24/30] add utils --- .../test_astrort/test_utils/test_utils.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 astrort/testing/test_astrort/test_utils/test_utils.py 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 0000000..a78255b --- /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 + + -- GitLab From 6464714e42d340e3edc050cf675a81b1041ccff8 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:17:55 +0200 Subject: [PATCH 25/30] add utils --- astrort/utils/utils.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 astrort/utils/utils.py diff --git a/astrort/utils/utils.py b/astrort/utils/utils.py new file mode 100644 index 0000000..c6723b0 --- /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 -- GitLab From ed5b0134bc4ac53f2134b8caf51553874e399822 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:18:07 +0200 Subject: [PATCH 26/30] add wrap and test --- astrort/testing/test_astrort/test_utils/test_wrap.py | 6 +++--- astrort/utils/wrap.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/astrort/testing/test_astrort/test_utils/test_wrap.py b/astrort/testing/test_astrort/test_utils/test_wrap.py index 5eefc31..04cb39b 100644 --- a/astrort/testing/test_astrort/test_utils/test_wrap.py +++ b/astrort/testing/test_astrort/test_utils/test_wrap.py @@ -9,7 +9,7 @@ import pytest from astrort.utils.wrap import load_yaml_conf -@pytest.mark.rtadeep_configuration -def test_load_yaml_conf(rtadeep_configuration): - configuration = load_yaml_conf(rtadeep_configuration) +@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/astrort/utils/wrap.py b/astrort/utils/wrap.py index 0d32fce..807fc6f 100644 --- a/astrort/utils/wrap.py +++ b/astrort/utils/wrap.py @@ -7,10 +7,18 @@ # ***************************************************************************** import yaml -from astrort.cfg.check_configuration import CheckConfiguration +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 \ No newline at end of file + 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']) -- GitLab From d7f70c43906ceae5e5d8a690fd2440b38b44013c Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:18:13 +0200 Subject: [PATCH 27/30] update --- astrort/testing/pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrort/testing/pytest.ini b/astrort/testing/pytest.ini index c780281..5c05197 100644 --- a/astrort/testing/pytest.ini +++ b/astrort/testing/pytest.ini @@ -1,3 +1,3 @@ [pytest] markers = - rtadeep_configuration: rtadeep test configurtion file \ No newline at end of file + astrort_configuration: rtadeep test configurtion file \ No newline at end of file -- GitLab From 40f6a624416991b433ebfff70aa5f900e729ab31 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:18:19 +0200 Subject: [PATCH 28/30] add templates --- astrort/templates/crab.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 astrort/templates/crab.xml diff --git a/astrort/templates/crab.xml b/astrort/templates/crab.xml new file mode 100644 index 0000000..47843b8 --- /dev/null +++ b/astrort/templates/crab.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- GitLab From f52c2d5435211f0722778b2b29155c0f4e9de795 Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:18:27 +0200 Subject: [PATCH 29/30] check configuration --- astrort/configure/check_configuration.py | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 astrort/configure/check_configuration.py diff --git a/astrort/configure/check_configuration.py b/astrort/configure/check_configuration.py new file mode 100644 index 0000000..cf4f957 --- /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 -- GitLab From 2e23fa3b9b25b43a9e0ddb807b99bd35bdc76a1c Mon Sep 17 00:00:00 2001 From: Ambra Di Piano Date: Tue, 12 Sep 2023 17:18:38 +0200 Subject: [PATCH 30/30] add tmp dir --- astrort/testing/conftest.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/astrort/testing/conftest.py b/astrort/testing/conftest.py index 2f6902e..4edcb34 100644 --- a/astrort/testing/conftest.py +++ b/astrort/testing/conftest.py @@ -9,7 +9,13 @@ import pytest import astrort from os.path import join, dirname, abspath +from os import makedirs @pytest.fixture(scope='function') -def rtadeep_configuration(): - return join(dirname(abspath(astrort.__file__)), 'cfg', 'test.yml') \ No newline at end of file +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') -- GitLab