gammasimtools 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/RECORD +64 -77
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/entry_points.txt +2 -1
- simtools/_version.py +2 -2
- simtools/applications/convert_all_model_parameters_from_simtel.py +77 -88
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +52 -22
- simtools/applications/derive_photon_electron_spectrum.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +1 -10
- simtools/applications/docs_produce_model_parameter_reports.py +4 -17
- simtools/applications/plot_tabular_data.py +14 -2
- simtools/applications/{production_derive_limits.py → production_derive_corsika_limits.py} +20 -8
- simtools/applications/production_extract_mc_event_data.py +125 -0
- simtools/applications/run_application.py +9 -10
- simtools/applications/submit_data_from_external.py +1 -1
- simtools/applications/submit_model_parameter_from_external.py +2 -1
- simtools/camera/single_photon_electron_spectrum.py +6 -2
- simtools/constants.py +7 -0
- simtools/data_model/metadata_collector.py +159 -61
- simtools/data_model/model_data_writer.py +11 -55
- simtools/data_model/schema.py +2 -1
- simtools/data_model/validate_data.py +5 -3
- simtools/db/db_handler.py +115 -31
- simtools/model/model_parameter.py +0 -31
- simtools/production_configuration/derive_corsika_limits.py +260 -0
- simtools/production_configuration/extract_mc_event_data.py +253 -0
- simtools/ray_tracing/mirror_panel_psf.py +1 -1
- simtools/reporting/docs_read_parameters.py +164 -91
- simtools/schemas/metadata.metaschema.yml +7 -6
- simtools/schemas/model_parameter.metaschema.yml +0 -4
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +13 -5
- simtools/schemas/model_parameters/array_coordinates.schema.yml +1 -1
- simtools/schemas/model_parameters/array_layouts.schema.yml +3 -0
- simtools/schemas/model_parameters/asum_shaping.schema.yml +1 -1
- simtools/schemas/model_parameters/atmospheric_profile.schema.yml +1 -1
- simtools/schemas/model_parameters/camera_config_file.schema.yml +1 -1
- simtools/schemas/model_parameters/camera_degraded_map.schema.yml +1 -1
- simtools/schemas/model_parameters/camera_filter.schema.yml +1 -1
- simtools/schemas/model_parameters/dsum_shaping.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +1 -1
- simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +3 -3
- simtools/schemas/model_parameters/fadc_max_sum.schema.yml +3 -3
- simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
- simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
- simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +1 -1
- simtools/schemas/model_parameters/mirror_list.schema.yml +1 -1
- simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
- simtools/schemas/model_parameters/nsb_skymap.schema.yml +1 -1
- simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +1 -1
- simtools/schemas/model_parameters/primary_mirror_segmentation.schema.yml +1 -1
- simtools/schemas/model_parameters/secondary_mirror_degraded_map.schema.yml +1 -1
- simtools/schemas/model_parameters/secondary_mirror_segmentation.schema.yml +1 -1
- simtools/schemas/plot_configuration.metaschema.yml +162 -0
- simtools/schemas/production_tables.schema.yml +1 -1
- simtools/simtel/simtel_config_reader.py +85 -34
- simtools/simtel/simtel_table_reader.py +4 -0
- simtools/utils/general.py +50 -9
- simtools/utils/names.py +7 -2
- simtools/visualization/plot_tables.py +25 -20
- simtools/visualization/visualize.py +71 -23
- simtools/_dev_version/__init__.py +0 -9
- simtools/applications/__init__.py +0 -0
- simtools/configuration/__init__.py +0 -0
- simtools/corsika/__init__.py +0 -0
- simtools/data_model/__init__.py +0 -0
- simtools/db/__init__.py +0 -0
- simtools/io_operations/__init__.py +0 -0
- simtools/job_execution/__init__.py +0 -0
- simtools/layout/__init__.py +0 -0
- simtools/model/__init__.py +0 -0
- simtools/production_configuration/limits_calculation.py +0 -202
- simtools/ray_tracing/__init__.py +0 -0
- simtools/runners/__init__.py +0 -0
- simtools/simtel/__init__.py +0 -0
- simtools/testing/__init__.py +0 -0
- simtools/utils/__init__.py +0 -0
- simtools/visualization/__init__.py +0 -0
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/LICENSE +0 -0
- {gammasimtools-0.12.0.dist-info → gammasimtools-0.13.0.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import astropy.units as u
|
|
8
8
|
import numpy as np
|
|
9
|
-
import yaml
|
|
10
9
|
from astropy.io.registry.base import IORegistryError
|
|
11
10
|
|
|
12
11
|
import simtools.utils.general as gen
|
|
@@ -95,7 +94,7 @@ class ModelDataWriter:
|
|
|
95
94
|
Dictionary with configuration parameters (including output file name and path).
|
|
96
95
|
output_file: string or Path
|
|
97
96
|
Name of output file (args["output_file"] is used if this parameter is not set).
|
|
98
|
-
metadata:
|
|
97
|
+
metadata: MetadataCollector object
|
|
99
98
|
Metadata to be written.
|
|
100
99
|
product_data: astropy Table
|
|
101
100
|
Model data to be written
|
|
@@ -174,11 +173,11 @@ class ModelDataWriter:
|
|
|
174
173
|
if metadata_input_dict is not None:
|
|
175
174
|
metadata_input_dict["output_file"] = output_file
|
|
176
175
|
metadata_input_dict["output_file_format"] = Path(output_file).suffix.lstrip(".")
|
|
177
|
-
metadata = MetadataCollector(args_dict=metadata_input_dict)
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
metadata = MetadataCollector(args_dict=metadata_input_dict)
|
|
177
|
+
metadata.write(output_path / Path(output_file))
|
|
178
|
+
unique_id = (
|
|
179
|
+
metadata.get_top_level_metadata().get("cta", {}).get("product", {}).get("id")
|
|
180
180
|
)
|
|
181
|
-
unique_id = metadata.get("cta", {}).get("product", {}).get("id")
|
|
182
181
|
|
|
183
182
|
_json_dict = writer.get_validated_parameter_dict(
|
|
184
183
|
parameter_name, value, instrument, parameter_version, unique_id
|
|
@@ -364,7 +363,7 @@ class ModelDataWriter:
|
|
|
364
363
|
----------
|
|
365
364
|
product_data: astropy Table
|
|
366
365
|
Model data to be written
|
|
367
|
-
metadata:
|
|
366
|
+
metadata: MetadataCollector object
|
|
368
367
|
Metadata to be written.
|
|
369
368
|
|
|
370
369
|
Raises
|
|
@@ -377,7 +376,9 @@ class ModelDataWriter:
|
|
|
377
376
|
return
|
|
378
377
|
|
|
379
378
|
if metadata is not None:
|
|
380
|
-
product_data.meta.update(
|
|
379
|
+
product_data.meta.update(
|
|
380
|
+
gen.change_dict_keys_case(metadata.get_top_level_metadata(), False)
|
|
381
|
+
)
|
|
381
382
|
|
|
382
383
|
self._logger.info(f"Writing data to {self.product_data_file}")
|
|
383
384
|
if isinstance(product_data, dict) and Path(self.product_data_file).suffix == ".json":
|
|
@@ -390,6 +391,8 @@ class ModelDataWriter:
|
|
|
390
391
|
except IORegistryError:
|
|
391
392
|
self._logger.error(f"Error writing model data to {self.product_data_file}.")
|
|
392
393
|
raise
|
|
394
|
+
if metadata is not None:
|
|
395
|
+
metadata.write(self.product_data_file, add_activity_name=True)
|
|
393
396
|
|
|
394
397
|
def write_dict_to_model_parameter_json(self, file_name, data_dict):
|
|
395
398
|
"""
|
|
@@ -449,53 +452,6 @@ class ModelDataWriter:
|
|
|
449
452
|
pass
|
|
450
453
|
return data_dict
|
|
451
454
|
|
|
452
|
-
def write_metadata_to_yml(self, metadata, yml_file=None, keys_lower_case=False):
|
|
453
|
-
"""
|
|
454
|
-
Write model metadata file (yaml file format).
|
|
455
|
-
|
|
456
|
-
Parameters
|
|
457
|
-
----------
|
|
458
|
-
metadata: dict
|
|
459
|
-
Metadata to be stored
|
|
460
|
-
yml_file: str
|
|
461
|
-
Name of output file.
|
|
462
|
-
keys_lower_case: bool
|
|
463
|
-
Write yaml keys in lower case.
|
|
464
|
-
|
|
465
|
-
Returns
|
|
466
|
-
-------
|
|
467
|
-
str
|
|
468
|
-
Name of output file
|
|
469
|
-
|
|
470
|
-
Raises
|
|
471
|
-
------
|
|
472
|
-
FileNotFoundError
|
|
473
|
-
If yml_file not found.
|
|
474
|
-
TypeError
|
|
475
|
-
If yml_file is not defined.
|
|
476
|
-
"""
|
|
477
|
-
try:
|
|
478
|
-
yml_file = names.file_name_with_version(
|
|
479
|
-
yml_file or self.product_data_file, ".metadata.yml"
|
|
480
|
-
)
|
|
481
|
-
with open(yml_file, "w", encoding="UTF-8") as file:
|
|
482
|
-
yaml.safe_dump(
|
|
483
|
-
gen.change_dict_keys_case(metadata, keys_lower_case),
|
|
484
|
-
file,
|
|
485
|
-
sort_keys=False,
|
|
486
|
-
)
|
|
487
|
-
self._logger.info(f"Writing metadata to {yml_file}")
|
|
488
|
-
return yml_file
|
|
489
|
-
except FileNotFoundError:
|
|
490
|
-
self._logger.error(f"Error writing model data to {yml_file}")
|
|
491
|
-
raise
|
|
492
|
-
except AttributeError:
|
|
493
|
-
self._logger.error("No metadata defined for writing")
|
|
494
|
-
raise
|
|
495
|
-
except TypeError:
|
|
496
|
-
self._logger.error("No output file for metadata defined")
|
|
497
|
-
raise
|
|
498
|
-
|
|
499
455
|
@staticmethod
|
|
500
456
|
def _astropy_data_format(product_data_format):
|
|
501
457
|
"""
|
simtools/data_model/schema.py
CHANGED
|
@@ -108,7 +108,7 @@ def validate_dict_using_schema(data, schema_file=None, json_schema=None):
|
|
|
108
108
|
"""
|
|
109
109
|
if json_schema is None and schema_file is None:
|
|
110
110
|
_logger.warning(f"No schema provided for validation of {data}")
|
|
111
|
-
return
|
|
111
|
+
return None
|
|
112
112
|
if json_schema is None:
|
|
113
113
|
json_schema = load_schema(
|
|
114
114
|
schema_file,
|
|
@@ -131,6 +131,7 @@ def validate_dict_using_schema(data, schema_file=None, json_schema=None):
|
|
|
131
131
|
raise FileNotFoundError(f"Meta schema URL does not exist: {data['meta_schema_url']}")
|
|
132
132
|
|
|
133
133
|
_logger.debug(f"Successful validation of data using schema ({json_schema.get('name')})")
|
|
134
|
+
return data
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def load_schema(schema_file=None, schema_version=None):
|
|
@@ -220,7 +220,7 @@ class DataValidator:
|
|
|
220
220
|
json_schema=self._get_data_description(index).get("json_schema"),
|
|
221
221
|
)
|
|
222
222
|
else:
|
|
223
|
-
self._check_data_type(np.array(value).dtype, index)
|
|
223
|
+
self._check_data_type(np.array(value).dtype, index, value)
|
|
224
224
|
|
|
225
225
|
if self.data_dict.get("type") not in ("string", "dict", "file"):
|
|
226
226
|
self._check_for_not_a_number(value, index)
|
|
@@ -436,7 +436,7 @@ class DataValidator:
|
|
|
436
436
|
|
|
437
437
|
return u.Unit(reference_unit)
|
|
438
438
|
|
|
439
|
-
def _check_data_type(self, dtype, column_name):
|
|
439
|
+
def _check_data_type(self, dtype, column_name, value=None):
|
|
440
440
|
"""
|
|
441
441
|
Check column data type.
|
|
442
442
|
|
|
@@ -446,6 +446,8 @@ class DataValidator:
|
|
|
446
446
|
data type
|
|
447
447
|
column_name: str
|
|
448
448
|
column name
|
|
449
|
+
value: value
|
|
450
|
+
value to be tested (optional)
|
|
449
451
|
|
|
450
452
|
Raises
|
|
451
453
|
------
|
|
@@ -456,7 +458,7 @@ class DataValidator:
|
|
|
456
458
|
reference_dtype = self._get_data_description(column_name).get("type", None)
|
|
457
459
|
if not gen.validate_data_type(
|
|
458
460
|
reference_dtype=reference_dtype,
|
|
459
|
-
value=
|
|
461
|
+
value=value,
|
|
460
462
|
dtype=dtype,
|
|
461
463
|
allow_subtypes=(not self.check_exact_data_type),
|
|
462
464
|
):
|
simtools/db/db_handler.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
+
from collections import defaultdict
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from threading import Lock
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ from pymongo import MongoClient
|
|
|
13
14
|
|
|
14
15
|
from simtools.data_model import validate_data
|
|
15
16
|
from simtools.io_operations import io_handler
|
|
17
|
+
from simtools.simtel import simtel_table_reader
|
|
16
18
|
from simtools.utils import names, value_conversion
|
|
17
19
|
|
|
18
20
|
__all__ = ["DatabaseHandler"]
|
|
@@ -113,6 +115,7 @@ class DatabaseHandler:
|
|
|
113
115
|
direct_connection = self.mongo_db_config["db_server"] in (
|
|
114
116
|
"localhost",
|
|
115
117
|
"simtools-mongodb",
|
|
118
|
+
"mongodb",
|
|
116
119
|
)
|
|
117
120
|
return MongoClient(
|
|
118
121
|
self.mongo_db_config["db_server"],
|
|
@@ -177,48 +180,62 @@ class DatabaseHandler:
|
|
|
177
180
|
def get_model_parameter(
|
|
178
181
|
self,
|
|
179
182
|
parameter,
|
|
180
|
-
parameter_version,
|
|
181
183
|
site,
|
|
182
184
|
array_element_name,
|
|
185
|
+
parameter_version=None,
|
|
186
|
+
model_version=None,
|
|
183
187
|
):
|
|
184
188
|
"""
|
|
185
|
-
Get a model parameter using
|
|
189
|
+
Get a single model parameter using model or parameter version.
|
|
190
|
+
|
|
191
|
+
Note that this function should not be called in a loop for many parameters,
|
|
192
|
+
as it each call queries the database.
|
|
186
193
|
|
|
187
194
|
Parameters
|
|
188
195
|
----------
|
|
189
196
|
parameter: str
|
|
190
197
|
Name of the parameter.
|
|
191
|
-
parameter_version: str
|
|
192
|
-
Version of the parameter.
|
|
193
198
|
site: str
|
|
194
199
|
Site name.
|
|
195
200
|
array_element_name: str
|
|
196
|
-
Name of the array element model
|
|
201
|
+
Name of the array element model.
|
|
202
|
+
parameter_version: str
|
|
203
|
+
Version of the parameter.
|
|
204
|
+
model_version: str
|
|
205
|
+
Version of the model.
|
|
197
206
|
|
|
198
207
|
Returns
|
|
199
208
|
-------
|
|
200
209
|
dict containing the parameter
|
|
201
210
|
|
|
202
211
|
"""
|
|
212
|
+
collection_name = names.get_collection_name_from_parameter_name(parameter)
|
|
213
|
+
if model_version:
|
|
214
|
+
production_table = self._read_production_table_from_mongo_db(
|
|
215
|
+
collection_name, model_version
|
|
216
|
+
)
|
|
217
|
+
array_element_list = self._get_array_element_list(
|
|
218
|
+
array_element_name, site, production_table, collection_name
|
|
219
|
+
)
|
|
220
|
+
for array_element in reversed(array_element_list):
|
|
221
|
+
parameter_version = (
|
|
222
|
+
production_table["parameters"].get(array_element, {}).get(parameter)
|
|
223
|
+
)
|
|
224
|
+
if parameter_version:
|
|
225
|
+
array_element_name = array_element
|
|
226
|
+
break
|
|
227
|
+
|
|
203
228
|
query = {
|
|
204
229
|
"parameter_version": parameter_version,
|
|
205
230
|
"parameter": parameter,
|
|
206
231
|
}
|
|
207
|
-
if array_element_name
|
|
232
|
+
if array_element_name:
|
|
208
233
|
query["instrument"] = array_element_name
|
|
209
|
-
if site
|
|
234
|
+
if site:
|
|
210
235
|
query["site"] = site
|
|
211
|
-
return self._read_mongo_db(
|
|
212
|
-
query=query, collection_name=names.get_collection_name_from_parameter_name(parameter)
|
|
213
|
-
)
|
|
236
|
+
return self._read_mongo_db(query=query, collection_name=collection_name)
|
|
214
237
|
|
|
215
|
-
def get_model_parameters(
|
|
216
|
-
self,
|
|
217
|
-
site,
|
|
218
|
-
array_element_name,
|
|
219
|
-
collection,
|
|
220
|
-
model_version=None,
|
|
221
|
-
):
|
|
238
|
+
def get_model_parameters(self, site, array_element_name, collection, model_version):
|
|
222
239
|
"""
|
|
223
240
|
Get model parameters using the model version.
|
|
224
241
|
|
|
@@ -239,22 +256,44 @@ class DatabaseHandler:
|
|
|
239
256
|
-------
|
|
240
257
|
dict containing the parameters
|
|
241
258
|
"""
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
pars = {}
|
|
260
|
+
production_table = self._read_production_table_from_mongo_db(collection, model_version)
|
|
261
|
+
array_element_list = self._get_array_element_list(
|
|
262
|
+
array_element_name, site, production_table, collection
|
|
244
263
|
)
|
|
264
|
+
for array_element in array_element_list:
|
|
265
|
+
pars.update(
|
|
266
|
+
self._get_parameter_for_model_version(
|
|
267
|
+
array_element, model_version, site, collection, production_table
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
return pars
|
|
245
271
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
272
|
+
def get_model_parameters_for_all_model_versions(self, site, array_element_name, collection):
|
|
273
|
+
"""
|
|
274
|
+
Get model parameters for all model versions.
|
|
275
|
+
|
|
276
|
+
Queries parameters for design and for the specified array element (if necessary).
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
site: str
|
|
281
|
+
Site name.
|
|
282
|
+
array_element_name: str
|
|
283
|
+
Name of the array element model (e.g. LSTN-01, MSTx-FlashCam, ILLN-01).
|
|
284
|
+
collection: str
|
|
285
|
+
Collection of array element (e.g. telescopes, calibration_devices).
|
|
286
|
+
|
|
287
|
+
Returns
|
|
288
|
+
-------
|
|
289
|
+
dict containing the parameters with model version as first key
|
|
290
|
+
"""
|
|
291
|
+
pars = defaultdict(dict)
|
|
292
|
+
for _model_version in self.get_model_versions(collection):
|
|
293
|
+
parameter_data = self.get_model_parameters(
|
|
294
|
+
site, array_element_name, collection, _model_version
|
|
251
295
|
)
|
|
252
|
-
|
|
253
|
-
pars.update(
|
|
254
|
-
self._get_parameter_for_model_version(
|
|
255
|
-
array_element, _model_version, site, collection, production_table
|
|
256
|
-
)
|
|
257
|
-
)
|
|
296
|
+
pars[_model_version].update(parameter_data)
|
|
258
297
|
return pars
|
|
259
298
|
|
|
260
299
|
def _get_parameter_for_model_version(
|
|
@@ -331,9 +370,54 @@ class DatabaseHandler:
|
|
|
331
370
|
return [collection for collection in collections if not collection.startswith("fs.")]
|
|
332
371
|
return collections
|
|
333
372
|
|
|
373
|
+
def export_model_file(
|
|
374
|
+
self,
|
|
375
|
+
parameter,
|
|
376
|
+
site,
|
|
377
|
+
array_element_name,
|
|
378
|
+
model_version=None,
|
|
379
|
+
parameter_version=None,
|
|
380
|
+
export_file_as_table=False,
|
|
381
|
+
):
|
|
382
|
+
"""
|
|
383
|
+
Export single model file from the DB identified by the parameter name.
|
|
384
|
+
|
|
385
|
+
The parameter can be identified by model or parameter version.
|
|
386
|
+
Files can be exported as astropy tables (ecsv format).
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
parameter: str
|
|
391
|
+
Name of the parameter.
|
|
392
|
+
site: str
|
|
393
|
+
Site name.
|
|
394
|
+
array_element_name: str
|
|
395
|
+
Name of the array element model (e.g. MSTN, SSTS).
|
|
396
|
+
parameter_version: str
|
|
397
|
+
Version of the parameter.
|
|
398
|
+
model_version: str
|
|
399
|
+
Version of the model.
|
|
400
|
+
export_file_as_table: bool
|
|
401
|
+
If True, export the file as an astropy table (ecsv format).
|
|
402
|
+
"""
|
|
403
|
+
parameters = self.get_model_parameter(
|
|
404
|
+
parameter,
|
|
405
|
+
site,
|
|
406
|
+
array_element_name,
|
|
407
|
+
parameter_version=parameter_version,
|
|
408
|
+
model_version=model_version,
|
|
409
|
+
)
|
|
410
|
+
self.export_model_files(parameters=parameters, dest=self.io_handler.get_output_directory())
|
|
411
|
+
if export_file_as_table:
|
|
412
|
+
return simtel_table_reader.read_simtel_table(
|
|
413
|
+
parameter,
|
|
414
|
+
self.io_handler.get_output_directory().joinpath(parameters[parameter]["value"]),
|
|
415
|
+
)
|
|
416
|
+
return None
|
|
417
|
+
|
|
334
418
|
def export_model_files(self, parameters=None, file_names=None, dest=None, db_name=None):
|
|
335
419
|
"""
|
|
336
|
-
Export files from the DB to
|
|
420
|
+
Export models files from the DB to given directory.
|
|
337
421
|
|
|
338
422
|
The files to be exported can be specified by file_name or retrieved from the database
|
|
339
423
|
using the parameters dictionary.
|
|
@@ -6,12 +6,10 @@ import shutil
|
|
|
6
6
|
from copy import copy
|
|
7
7
|
|
|
8
8
|
import astropy.units as u
|
|
9
|
-
from astropy.table import Table
|
|
10
9
|
|
|
11
10
|
import simtools.utils.general as gen
|
|
12
11
|
from simtools.db import db_handler
|
|
13
12
|
from simtools.io_operations import io_handler
|
|
14
|
-
from simtools.simtel import simtel_table_reader
|
|
15
13
|
from simtools.simtel.simtel_config_writer import SimtelConfigWriter
|
|
16
14
|
from simtools.utils import names
|
|
17
15
|
|
|
@@ -517,35 +515,6 @@ class ModelParameter:
|
|
|
517
515
|
self.db.export_model_files(parameters=pars_from_db, dest=self.config_file_directory)
|
|
518
516
|
self._is_exported_model_files_up_to_date = True
|
|
519
517
|
|
|
520
|
-
def get_model_file_as_table(self, par_name):
|
|
521
|
-
"""
|
|
522
|
-
Return tabular data from file as astropy table.
|
|
523
|
-
|
|
524
|
-
Parameters
|
|
525
|
-
----------
|
|
526
|
-
par_name: str
|
|
527
|
-
Name of the parameter.
|
|
528
|
-
|
|
529
|
-
Returns
|
|
530
|
-
-------
|
|
531
|
-
Table
|
|
532
|
-
Astropy table.
|
|
533
|
-
"""
|
|
534
|
-
_par_entry = {}
|
|
535
|
-
try:
|
|
536
|
-
_par_entry[par_name] = self._parameters[par_name]
|
|
537
|
-
except KeyError as exc:
|
|
538
|
-
raise ValueError(f"Parameter {par_name} not found in the model.") from exc
|
|
539
|
-
self.db.export_model_files(parameters=_par_entry, dest=self.config_file_directory)
|
|
540
|
-
if _par_entry[par_name]["value"].endswith("ecsv"):
|
|
541
|
-
return Table.read(
|
|
542
|
-
self.config_file_directory.joinpath(_par_entry[par_name]["value"]),
|
|
543
|
-
format="ascii.ecsv",
|
|
544
|
-
)
|
|
545
|
-
return simtel_table_reader.read_simtel_table(
|
|
546
|
-
par_name, self.config_file_directory.joinpath(_par_entry[par_name]["value"])
|
|
547
|
-
)
|
|
548
|
-
|
|
549
518
|
def export_config_file(self):
|
|
550
519
|
"""Export the config file used by sim_telarray."""
|
|
551
520
|
# Exporting model file
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Calculate the thresholds for energy, radial distance, and viewcone."""
|
|
2
|
+
|
|
3
|
+
import astropy.units as u
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import numpy as np
|
|
6
|
+
import tables
|
|
7
|
+
from astropy.coordinates import AltAz
|
|
8
|
+
from ctapipe.coordinates import GroundFrame, TiltedGroundFrame
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LimitCalculator:
|
|
12
|
+
"""
|
|
13
|
+
Compute thresholds/limits for energy, radial distance, and viewcone.
|
|
14
|
+
|
|
15
|
+
Event data is read from the reduced MC event data file.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
event_data_file : str
|
|
20
|
+
Path to the HDF5 file containing the event data.
|
|
21
|
+
telescope_list : list, optional
|
|
22
|
+
List of telescope IDs to filter the events (default is None).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, event_data_file, telescope_list=None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the LimitCalculator with the given event data file.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
event_data_file : str
|
|
32
|
+
Path to the reduced MC event data file.
|
|
33
|
+
telescope_list : list, optional
|
|
34
|
+
List of telescope IDs to filter the events (default is None).
|
|
35
|
+
"""
|
|
36
|
+
self.event_data_file = event_data_file
|
|
37
|
+
self.telescope_list = telescope_list
|
|
38
|
+
self.event_x_core = None
|
|
39
|
+
self.event_y_core = None
|
|
40
|
+
self.simulated = None
|
|
41
|
+
self.shower_id_triggered = None
|
|
42
|
+
self.list_of_files = None
|
|
43
|
+
self.shower_sim_azimuth = None
|
|
44
|
+
self.shower_sim_altitude = None
|
|
45
|
+
self.array_azimuth = None
|
|
46
|
+
self.array_altitude = None
|
|
47
|
+
self.trigger_telescope_list_list = None
|
|
48
|
+
self.units = {}
|
|
49
|
+
self._read_event_data()
|
|
50
|
+
|
|
51
|
+
def _read_event_data(self):
|
|
52
|
+
"""Read the event data from the reduced MC event data file."""
|
|
53
|
+
with tables.open_file(self.event_data_file, mode="r") as f:
|
|
54
|
+
reduced_data = f.root.data.reduced_data
|
|
55
|
+
triggered_data = f.root.data.triggered_data
|
|
56
|
+
file_names = f.root.data.file_names
|
|
57
|
+
trigger_telescope_list_list = f.root.data.trigger_telescope_list_list
|
|
58
|
+
|
|
59
|
+
self.event_x_core = reduced_data.col("core_x")
|
|
60
|
+
self.event_y_core = reduced_data.col("core_y")
|
|
61
|
+
self.simulated = reduced_data.col("simulated")
|
|
62
|
+
self.shower_id_triggered = triggered_data.col("shower_id_triggered")
|
|
63
|
+
self.list_of_files = file_names.col("file_names")
|
|
64
|
+
self.shower_sim_azimuth = reduced_data.col("shower_sim_azimuth")
|
|
65
|
+
self.shower_sim_altitude = reduced_data.col("shower_sim_altitude")
|
|
66
|
+
self.array_altitude = reduced_data.col("array_altitudes")
|
|
67
|
+
self.array_azimuth = reduced_data.col("array_azimuths")
|
|
68
|
+
|
|
69
|
+
self.trigger_telescope_list_list = [
|
|
70
|
+
[np.int16(tel) for tel in event] for event in trigger_telescope_list_list
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def _compute_limits(self, hist, bin_edges, loss_fraction, limit_type="lower"):
|
|
74
|
+
"""
|
|
75
|
+
Compute the limits based on the loss fraction.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
hist : np.ndarray
|
|
80
|
+
1D histogram array.
|
|
81
|
+
bin_edges : np.ndarray
|
|
82
|
+
Array of bin edges.
|
|
83
|
+
loss_fraction : float
|
|
84
|
+
Fraction of events to be lost.
|
|
85
|
+
limit_type : str, optional
|
|
86
|
+
Type of limit ('lower' or 'upper'). Default is 'lower'.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
float
|
|
91
|
+
Bin edge value corresponding to the threshold.
|
|
92
|
+
"""
|
|
93
|
+
cumulative_sum = np.cumsum(hist) if limit_type == "upper" else np.cumsum(hist[::-1])
|
|
94
|
+
total_events = np.sum(hist)
|
|
95
|
+
threshold = (1 - loss_fraction) * total_events
|
|
96
|
+
bin_index = np.searchsorted(cumulative_sum, threshold)
|
|
97
|
+
return bin_edges[bin_index] if limit_type == "upper" else bin_edges[-bin_index]
|
|
98
|
+
|
|
99
|
+
def _prepare_data_for_limits(self):
|
|
100
|
+
"""
|
|
101
|
+
Prepare the data required for computing limits.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
tuple
|
|
106
|
+
Tuple containing core distances, triggered energies, core bins, and energy bins.
|
|
107
|
+
"""
|
|
108
|
+
shower_id_triggered_masked = self.shower_id_triggered
|
|
109
|
+
if self.telescope_list is not None:
|
|
110
|
+
mask = np.array(
|
|
111
|
+
[
|
|
112
|
+
all(tel in event for tel in self.telescope_list)
|
|
113
|
+
for event in self.trigger_telescope_list_list
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
shower_id_triggered_masked = self.shower_id_triggered[mask]
|
|
117
|
+
|
|
118
|
+
triggered_energies = self.simulated[shower_id_triggered_masked]
|
|
119
|
+
energy_bins = np.logspace(
|
|
120
|
+
np.log10(triggered_energies.min()), np.log10(triggered_energies.max()), 1000
|
|
121
|
+
)
|
|
122
|
+
event_x_core_shower, event_y_core_shower = self._transform_to_shower_coordinates()
|
|
123
|
+
core_distances_all = np.sqrt(event_x_core_shower**2 + event_y_core_shower**2)
|
|
124
|
+
core_distances_triggered = core_distances_all[shower_id_triggered_masked]
|
|
125
|
+
core_bins = np.linspace(
|
|
126
|
+
core_distances_triggered.min(), core_distances_triggered.max(), 1000
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return core_distances_triggered, triggered_energies, core_bins, energy_bins
|
|
130
|
+
|
|
131
|
+
def compute_lower_energy_limit(self, loss_fraction):
|
|
132
|
+
"""
|
|
133
|
+
Compute the lower energy limit in TeV based on the event loss fraction.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
loss_fraction : float
|
|
138
|
+
Fraction of events to be lost.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
astropy.units.Quantity
|
|
143
|
+
Lower energy limit.
|
|
144
|
+
"""
|
|
145
|
+
_, triggered_energies, _, energy_bins = self._prepare_data_for_limits()
|
|
146
|
+
|
|
147
|
+
hist, _ = np.histogram(triggered_energies, bins=energy_bins)
|
|
148
|
+
lower_bin_edge_value = self._compute_limits(
|
|
149
|
+
hist, energy_bins, loss_fraction, limit_type="lower"
|
|
150
|
+
)
|
|
151
|
+
return lower_bin_edge_value * u.TeV
|
|
152
|
+
|
|
153
|
+
def compute_upper_radial_distance(self, loss_fraction):
|
|
154
|
+
"""
|
|
155
|
+
Compute the upper radial distance based on the event loss fraction.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
loss_fraction : float
|
|
160
|
+
Fraction of events to be lost.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
astropy.units.Quantity
|
|
165
|
+
Upper radial distance in m.
|
|
166
|
+
"""
|
|
167
|
+
core_distances_triggered, _, core_bins, _ = self._prepare_data_for_limits()
|
|
168
|
+
|
|
169
|
+
hist, _ = np.histogram(core_distances_triggered, bins=core_bins)
|
|
170
|
+
upper_bin_edge_value = self._compute_limits(
|
|
171
|
+
hist, core_bins, loss_fraction, limit_type="upper"
|
|
172
|
+
)
|
|
173
|
+
return upper_bin_edge_value * u.m
|
|
174
|
+
|
|
175
|
+
def compute_viewcone(self, loss_fraction):
|
|
176
|
+
"""
|
|
177
|
+
Compute the viewcone based on the event loss fraction.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
loss_fraction : float
|
|
182
|
+
Fraction of events to be lost.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
astropy.units.Quantity
|
|
187
|
+
Viewcone radius in degrees.
|
|
188
|
+
"""
|
|
189
|
+
# already in radians
|
|
190
|
+
azimuth_diff = self.array_azimuth - self.shower_sim_azimuth # * (np.pi / 180.0)
|
|
191
|
+
sim_altitude_rad = self.shower_sim_altitude # * (np.pi / 180.0)
|
|
192
|
+
array_altitude_rad = self.array_altitude # * (np.pi / 180.0)
|
|
193
|
+
x_1 = np.cos(azimuth_diff) * np.cos(sim_altitude_rad)
|
|
194
|
+
y_1 = np.sin(azimuth_diff) * np.cos(sim_altitude_rad)
|
|
195
|
+
z_1 = np.sin(sim_altitude_rad)
|
|
196
|
+
x_2 = x_1 * np.sin(array_altitude_rad) - z_1 * np.cos(array_altitude_rad)
|
|
197
|
+
y_2 = y_1
|
|
198
|
+
z_2 = x_1 * np.cos(array_altitude_rad) + z_1 * np.sin(array_altitude_rad)
|
|
199
|
+
off_angles = np.arctan2(np.sqrt(x_2**2 + y_2**2), z_2) * (180.0 / np.pi)
|
|
200
|
+
angle_bins = np.linspace(off_angles.min(), off_angles.max(), 400)
|
|
201
|
+
hist, _ = np.histogram(off_angles, bins=angle_bins)
|
|
202
|
+
|
|
203
|
+
upper_bin_edge_value = self._compute_limits(
|
|
204
|
+
hist, angle_bins, loss_fraction, limit_type="upper"
|
|
205
|
+
)
|
|
206
|
+
return upper_bin_edge_value * u.deg
|
|
207
|
+
|
|
208
|
+
def _transform_to_shower_coordinates(self):
|
|
209
|
+
"""
|
|
210
|
+
Transform core positions from ground coordinates to shower coordinates.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
tuple
|
|
215
|
+
Core positions in shower coordinates (x, y).
|
|
216
|
+
"""
|
|
217
|
+
pointing_az = self.shower_sim_azimuth * u.rad
|
|
218
|
+
pointing_alt = self.shower_sim_altitude * u.rad
|
|
219
|
+
|
|
220
|
+
pointing = AltAz(az=pointing_az, alt=pointing_alt)
|
|
221
|
+
ground = GroundFrame(x=self.event_x_core * u.m, y=self.event_y_core * u.m, z=0 * u.m)
|
|
222
|
+
shower_frame = ground.transform_to(TiltedGroundFrame(pointing_direction=pointing))
|
|
223
|
+
|
|
224
|
+
return shower_frame.x.value, shower_frame.y.value
|
|
225
|
+
|
|
226
|
+
def plot_data(self):
|
|
227
|
+
"""Plot the core distances and energies of triggered events."""
|
|
228
|
+
shower_id_triggered_masked = self.shower_id_triggered
|
|
229
|
+
if self.telescope_list is not None:
|
|
230
|
+
mask = np.array(
|
|
231
|
+
[
|
|
232
|
+
all(tel in event for tel in self.telescope_list)
|
|
233
|
+
for event in self.trigger_telescope_list_list
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
shower_id_triggered_masked = self.shower_id_triggered[mask]
|
|
237
|
+
|
|
238
|
+
core_distances_all = np.sqrt(self.event_x_core**2 + self.event_y_core**2)
|
|
239
|
+
core_distances_triggered = core_distances_all[shower_id_triggered_masked]
|
|
240
|
+
triggered_energies = self.simulated[shower_id_triggered_masked]
|
|
241
|
+
|
|
242
|
+
core_bins = np.linspace(core_distances_triggered.min(), core_distances_triggered.max(), 400)
|
|
243
|
+
energy_bins = np.logspace(
|
|
244
|
+
np.log10(triggered_energies.min()), np.log10(triggered_energies.max()), 400
|
|
245
|
+
)
|
|
246
|
+
plt.figure(figsize=(8, 6))
|
|
247
|
+
plt.hist2d(
|
|
248
|
+
core_distances_triggered,
|
|
249
|
+
triggered_energies,
|
|
250
|
+
bins=[core_bins, energy_bins],
|
|
251
|
+
norm="log",
|
|
252
|
+
cmap="viridis",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
plt.colorbar(label="Event Count")
|
|
256
|
+
plt.xlabel("Core Distance [m]")
|
|
257
|
+
plt.ylabel("Energy [TeV]")
|
|
258
|
+
plt.yscale("log")
|
|
259
|
+
plt.title("2D Histogram of Triggered Core Distance vs Energy")
|
|
260
|
+
plt.show()
|