gammasimtools 0.16.0__py3-none-any.whl → 0.17.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.
Files changed (63) hide show
  1. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/METADATA +4 -2
  2. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/RECORD +60 -54
  3. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/entry_points.txt +3 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/derive_ctao_array_layouts.py +5 -5
  7. simtools/applications/generate_simtel_event_data.py +36 -46
  8. simtools/applications/merge_tables.py +104 -0
  9. simtools/applications/plot_array_layout.py +145 -258
  10. simtools/applications/production_derive_corsika_limits.py +35 -220
  11. simtools/applications/production_derive_statistics.py +77 -43
  12. simtools/applications/simulate_light_emission.py +1 -0
  13. simtools/applications/simulate_prod.py +30 -18
  14. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  15. simtools/applications/submit_array_layouts.py +93 -0
  16. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  17. simtools/camera/camera_efficiency.py +3 -3
  18. simtools/configuration/commandline_parser.py +28 -34
  19. simtools/configuration/configurator.py +0 -4
  20. simtools/corsika/corsika_config.py +17 -12
  21. simtools/corsika/primary_particle.py +46 -13
  22. simtools/data_model/metadata_collector.py +7 -3
  23. simtools/db/db_handler.py +11 -11
  24. simtools/db/db_model_upload.py +2 -2
  25. simtools/io_operations/io_handler.py +2 -2
  26. simtools/io_operations/io_table_handler.py +345 -0
  27. simtools/job_execution/htcondor_script_generator.py +2 -2
  28. simtools/job_execution/job_manager.py +7 -121
  29. simtools/layout/array_layout_utils.py +385 -0
  30. simtools/model/array_model.py +5 -0
  31. simtools/model/model_repository.py +134 -0
  32. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
  33. simtools/production_configuration/derive_corsika_limits.py +239 -111
  34. simtools/production_configuration/derive_corsika_limits_grid.py +189 -0
  35. simtools/production_configuration/derive_production_statistics.py +57 -26
  36. simtools/production_configuration/derive_production_statistics_handler.py +70 -37
  37. simtools/production_configuration/interpolation_handler.py +296 -94
  38. simtools/ray_tracing/ray_tracing.py +7 -6
  39. simtools/reporting/docs_read_parameters.py +104 -62
  40. simtools/runners/corsika_simtel_runner.py +4 -1
  41. simtools/runners/runner_services.py +5 -4
  42. simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
  43. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  44. simtools/simtel/simtel_config_writer.py +34 -14
  45. simtools/simtel/simtel_io_event_reader.py +301 -194
  46. simtools/simtel/simtel_io_event_writer.py +207 -227
  47. simtools/simtel/simtel_io_file_info.py +9 -4
  48. simtools/simtel/simtel_io_metadata.py +20 -5
  49. simtools/simtel/simulator_array.py +2 -2
  50. simtools/simtel/simulator_light_emission.py +79 -34
  51. simtools/simtel/simulator_ray_tracing.py +2 -2
  52. simtools/simulator.py +101 -68
  53. simtools/testing/validate_output.py +4 -1
  54. simtools/utils/general.py +1 -1
  55. simtools/utils/names.py +5 -5
  56. simtools/visualization/plot_array_layout.py +242 -0
  57. simtools/visualization/plot_pixels.py +681 -0
  58. simtools/visualization/visualize.py +3 -219
  59. simtools/applications/production_generate_simulation_config.py +0 -152
  60. simtools/layout/ctao_array_layouts.py +0 -172
  61. simtools/production_configuration/generate_simulation_config.py +0 -158
  62. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/licenses/LICENSE +0 -0
  63. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,385 @@
1
+ """Retrieve, merge, and write layout dictionaries."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import simtools.utils.general as gen
7
+ from simtools.data_model import data_reader
8
+ from simtools.data_model.metadata_collector import MetadataCollector
9
+ from simtools.data_model.model_data_writer import ModelDataWriter
10
+ from simtools.io_operations import io_handler
11
+ from simtools.model.array_model import ArrayModel
12
+ from simtools.model.site_model import SiteModel
13
+ from simtools.utils import names
14
+
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ def retrieve_ctao_array_layouts(site, repository_url, branch_name="main"):
19
+ """
20
+ Retrieve array layouts from CTAO common identifiers repository.
21
+
22
+ Parameters
23
+ ----------
24
+ site : str
25
+ Site identifier.
26
+ repository_url : str
27
+ URL or path to CTAO common identifiers
28
+ branch_name : str
29
+ Repository branch to use for CTAO common identifiers.
30
+
31
+ Returns
32
+ -------
33
+ dict
34
+ Array layouts for all CTAO sites.
35
+ """
36
+ _logger.info(f"Retrieving array layouts from {repository_url} on branch {branch_name}.")
37
+
38
+ if gen.is_url(repository_url):
39
+ array_element_ids = gen.collect_data_from_http(
40
+ url=f"{repository_url}/{branch_name}/array-element-ids.json"
41
+ )
42
+ sub_arrays = gen.collect_data_from_http(
43
+ url=f"{repository_url}/{branch_name}/subarray-ids.json"
44
+ )
45
+ else:
46
+ array_element_ids = gen.collect_data_from_file(
47
+ Path(repository_url) / "array-element-ids.json"
48
+ )
49
+ sub_arrays = gen.collect_data_from_file(Path(repository_url) / "subarray-ids.json")
50
+
51
+ return _get_ctao_layouts_per_site(site, sub_arrays, array_element_ids)
52
+
53
+
54
+ def _get_ctao_layouts_per_site(site, sub_arrays, array_element_ids):
55
+ """
56
+ Get array layouts for CTAO sites.
57
+
58
+ Parameters
59
+ ----------
60
+ site : str
61
+ Site identifier.
62
+ sub_arrays : dict
63
+ Sub-array definitions.
64
+ array_element_ids : dict
65
+ Array element definitions.
66
+
67
+ Returns
68
+ -------
69
+ dict
70
+ Array layouts for CTAO sites.
71
+ """
72
+ layouts_per_site = []
73
+
74
+ for array in sub_arrays.get("subarrays", []):
75
+ elements = []
76
+ for ids in array.get("array_element_ids", []):
77
+ element_name = _get_ctao_array_element_name(ids, array_element_ids)
78
+ if names.get_site_from_array_element_name(element_name) != site:
79
+ break
80
+ elements.append(element_name)
81
+ if len(elements) > 0:
82
+ array_layout = {
83
+ "name": array.get("name"),
84
+ "elements": elements,
85
+ }
86
+ layouts_per_site.append(array_layout)
87
+
88
+ _logger.info(f"CTAO array layout definition: {layouts_per_site}")
89
+ return layouts_per_site
90
+
91
+
92
+ def _get_ctao_array_element_name(ids, array_element_ids):
93
+ """Return array element name for common identifier."""
94
+ for element in array_element_ids.get("array_elements", []):
95
+ if element.get("id") == ids:
96
+ return element.get("name")
97
+ return None
98
+
99
+
100
+ def merge_array_layouts(layouts_1, layouts_2):
101
+ """
102
+ Compare two array layout dictionaries and merge them.
103
+
104
+ Parameters
105
+ ----------
106
+ layouts_1 : dict
107
+ Array layout dictionary 1.
108
+ layouts_2 : dict
109
+ Array layout dictionary 2.
110
+
111
+ Returns
112
+ -------
113
+ dict
114
+ Merged array layout dictionary based on layout_1.
115
+ """
116
+ merged_layout = layouts_1
117
+ for layout_2 in layouts_2:
118
+ layout_found = False
119
+ for layout_1 in layouts_1.get("value", {}):
120
+ if sorted(layout_1["elements"]) == sorted(layout_2["elements"]):
121
+ print(
122
+ f"Equal telescope list: simtools '{layout_1['name']}' "
123
+ f"and CTAO '{layout_2['name']}'"
124
+ )
125
+ layout_1["name"] = layout_2["name"]
126
+ layout_found = True
127
+ if not layout_found:
128
+ merged_layout["value"].append(
129
+ {
130
+ "name": layout_2["name"],
131
+ "elements": layout_2["elements"],
132
+ }
133
+ )
134
+ _logger.info(f"Adding {layout_2['name']} with {layout_2['elements']}")
135
+ return merged_layout
136
+
137
+
138
+ def write_array_layouts(array_layouts, args_dict, db_config):
139
+ """
140
+ Write array layouts as model parameter.
141
+
142
+ Parameters
143
+ ----------
144
+ args_dict : dict
145
+ Command line arguments.
146
+ array_layouts : dict
147
+ Array layouts to be written.
148
+ db_config : dict
149
+ Database configuration.
150
+ """
151
+ site = args_dict.get("site") or array_layouts.get("site")
152
+ _logger.info(f"Writing updated array layouts to the database for site {site}.")
153
+
154
+ io_handler_instance = io_handler.IOHandler()
155
+ io_handler_instance.set_paths(
156
+ output_path=args_dict["output_path"],
157
+ use_plain_output_path=args_dict["use_plain_output_path"],
158
+ )
159
+ output_file = io_handler_instance.get_output_file(
160
+ f"array-layouts-{args_dict['updated_parameter_version']}.json"
161
+ )
162
+
163
+ ModelDataWriter.dump_model_parameter(
164
+ parameter_name="array_layouts",
165
+ value=array_layouts["value"],
166
+ instrument=site,
167
+ parameter_version=args_dict.get("updated_parameter_version"),
168
+ output_file=output_file,
169
+ use_plain_output_path=args_dict["use_plain_output_path"],
170
+ db_config=db_config,
171
+ )
172
+ MetadataCollector.dump(
173
+ args_dict,
174
+ output_file,
175
+ add_activity_name=True,
176
+ )
177
+
178
+
179
+ def validate_array_layouts_with_db(production_table, array_layouts):
180
+ """
181
+ Validate array layouts against the production table in the database.
182
+
183
+ Confirm that every telescope defined in the array layouts exist in the
184
+ production table.
185
+
186
+ Parameters
187
+ ----------
188
+ production_table : dict
189
+ Production table from the database.
190
+ array_layouts : dict
191
+ Array layouts to be validated.
192
+
193
+ Returns
194
+ -------
195
+ dict
196
+ Validated array layouts.
197
+ """
198
+ db_elements = set(production_table.get("parameters", {}).keys())
199
+
200
+ invalid_array_elements = [
201
+ e
202
+ for layout in array_layouts.get("value", [])
203
+ for e in layout.get("elements", [])
204
+ if e not in db_elements
205
+ ]
206
+
207
+ if invalid_array_elements:
208
+ raise ValueError(f"Invalid array elements found: {invalid_array_elements}. ")
209
+
210
+ return array_layouts
211
+
212
+
213
+ def get_array_layouts_from_parameter_file(
214
+ file_path, model_version, db_config, coordinate_system="ground"
215
+ ):
216
+ """
217
+ Retrieve array layouts from parameter file.
218
+
219
+ Parameters
220
+ ----------
221
+ file_path : str or Path
222
+ Path to the array layout parameter file.
223
+ model_version : str
224
+ Model version to retrieve.
225
+ db_config : dict
226
+ Database configuration.
227
+ coordinate_system : str
228
+ Coordinate system to use for the array elements (default is "ground").
229
+
230
+ Returns
231
+ -------
232
+ list
233
+ List of dictionaries containing array layout names and their elements.
234
+ """
235
+ array_layouts = gen.collect_data_from_file(file_path)
236
+ try:
237
+ value = array_layouts["value"]
238
+ except KeyError as exc:
239
+ raise ValueError("Missing 'value' key in layout file.") from exc
240
+ site = array_layouts.get("site")
241
+
242
+ layouts = []
243
+ for layout in value:
244
+ layouts.append(
245
+ _get_array_layout_dict(
246
+ db_config, model_version, site, None, layout["name"], coordinate_system
247
+ )
248
+ )
249
+ return layouts
250
+
251
+
252
+ def get_array_layouts_from_db(
253
+ layout_name, site, model_version, db_config, coordinate_system="ground"
254
+ ):
255
+ """
256
+ Retrieve all array layouts from the database and return as list of astropy tables.
257
+
258
+ Parameters
259
+ ----------
260
+ layout_name : str
261
+ Name of the array layout to retrieve (for None, all layouts are retrieved).
262
+ site : str
263
+ Site identifier.
264
+ model_version : str
265
+ Model version to retrieve.
266
+ db_config : dict
267
+ Database configuration.
268
+ coordinate_system : str
269
+ Coordinate system to use for the array elements (default is "ground").
270
+
271
+ Returns
272
+ -------
273
+ list
274
+ List of dictionaries containing array layout names and their elements.
275
+ """
276
+ layout_names = []
277
+ if layout_name:
278
+ layout_names.append(layout_name)
279
+ else:
280
+ site_model = SiteModel(site=site, model_version=model_version, mongo_db_config=db_config)
281
+ layout_names = site_model.get_list_of_array_layouts()
282
+
283
+ layouts = []
284
+ for _layout_name in layout_names:
285
+ layouts.append(
286
+ _get_array_layout_dict(
287
+ db_config, model_version, site, None, _layout_name, coordinate_system
288
+ )
289
+ )
290
+ if len(layouts) == 1:
291
+ return layouts[0]
292
+ return layouts
293
+
294
+
295
+ def get_array_layouts_using_telescope_lists_from_db(
296
+ telescope_lists, site, model_version, db_config, coordinate_system="ground"
297
+ ):
298
+ """
299
+ Retrieve array layouts from the database using telescope lists.
300
+
301
+ Parameters
302
+ ----------
303
+ telescope_lists : list
304
+ List of telescope lists to retrieve array layouts for.
305
+ site : str
306
+ Site identifier.
307
+ model_version : str
308
+ Model version to retrieve.
309
+ db_config : dict
310
+ Database configuration.
311
+ coordinate_system : str
312
+ Coordinate system to use for the array elements (default is "ground").
313
+
314
+ Returns
315
+ -------
316
+ list
317
+ List of dictionaries containing array layout names and their elements.
318
+
319
+ """
320
+ layouts = []
321
+ for telescope_list in telescope_lists:
322
+ _site = site
323
+ if _site is None:
324
+ sites = {names.get_site_from_array_element_name(t) for t in telescope_list}
325
+ if len(sites) != 1:
326
+ raise ValueError(
327
+ f"Telescope list contains elements from multiple sites: {sites}."
328
+ "Please specify a site."
329
+ )
330
+ _site = sites.pop()
331
+
332
+ layouts.append(
333
+ _get_array_layout_dict(
334
+ db_config, model_version, _site, telescope_list, None, coordinate_system
335
+ )
336
+ )
337
+ return layouts
338
+
339
+
340
+ def get_array_layouts_from_file(file_path):
341
+ """
342
+ Retrieve array layout(s) from astropy table file(s).
343
+
344
+ Parameters
345
+ ----------
346
+ file_path : str or Path or list of str or list of Path
347
+ Path(s) to array layout files(s).
348
+
349
+ Returns
350
+ -------
351
+ list
352
+ List of dictionaries containing array layout names and their elements.
353
+ """
354
+ if isinstance(file_path, str | Path):
355
+ file_path = [file_path]
356
+
357
+ layouts = []
358
+ for _file in file_path:
359
+ layouts.append(
360
+ {
361
+ "name": (Path(_file).name).split(".")[0],
362
+ "array_elements": data_reader.read_table_from_file(file_name=_file),
363
+ }
364
+ )
365
+ return layouts
366
+
367
+
368
+ def _get_array_layout_dict(
369
+ db_config, model_version, site, telescope_list, layout_name, coordinate_system
370
+ ):
371
+ """Return array layout dictionary for a given telescope list."""
372
+ array_model = ArrayModel(
373
+ mongo_db_config=db_config,
374
+ model_version=model_version,
375
+ site=site,
376
+ array_elements=telescope_list,
377
+ layout_name=layout_name,
378
+ )
379
+ return {
380
+ "name": layout_name if layout_name else "list",
381
+ "site": site,
382
+ "array_elements": array_model.export_array_elements_as_table(
383
+ coordinate_system=coordinate_system
384
+ ),
385
+ }
@@ -38,6 +38,8 @@ class ArrayModel:
38
38
  the array element positions).
39
39
  sim_telarray_seeds : dict, optional
40
40
  Dictionary with configuration for sim_telarray random instrument setup.
41
+ simtel_path: str, Path, optional
42
+ Path to the sim_telarray installation directory.
41
43
  """
42
44
 
43
45
  def __init__(
@@ -49,6 +51,7 @@ class ArrayModel:
49
51
  layout_name: str | None = None,
50
52
  array_elements: str | Path | list[str] | None = None,
51
53
  sim_telarray_seeds: dict | None = None,
54
+ simtel_path: str | Path | None = None,
52
55
  ):
53
56
  """Initialize ArrayModel."""
54
57
  self._logger = logging.getLogger(__name__)
@@ -69,6 +72,7 @@ class ArrayModel:
69
72
  self._telescope_model_files_exported = False
70
73
  self._array_model_file_exported = False
71
74
  self.sim_telarray_seeds = sim_telarray_seeds
75
+ self.simtel_path = simtel_path
72
76
 
73
77
  def _initialize(self, site: str, array_elements_config: str | Path | list[str]):
74
78
  """
@@ -258,6 +262,7 @@ class ArrayModel:
258
262
  layout_name=self.layout_name,
259
263
  model_version=self.model_version,
260
264
  label=self.label,
265
+ simtel_path=self.simtel_path,
261
266
  )
262
267
  simtel_writer.write_array_config_file(
263
268
  config_file_path=self.config_file_path,
@@ -0,0 +1,134 @@
1
+ """Utilities for managing the simulation models repository.
2
+
3
+ Simulation model parameters and production tables are managed through
4
+ a gitlab repository ('SimulationModels'). This module provides service
5
+ functions to interact with and verify the repository.
6
+ """
7
+
8
+ import logging
9
+ from pathlib import Path
10
+
11
+ from simtools.utils import general as gen
12
+ from simtools.utils import names
13
+
14
+ _logger = logging.getLogger(__name__)
15
+
16
+
17
+ def verify_simulation_model_production_tables(simulation_models_path):
18
+ """
19
+ Verify the simulation model production tables in the specified path.
20
+
21
+ Checks that all model parameters defined in the production tables are
22
+ present in the simulation models repository.
23
+
24
+ Parameters
25
+ ----------
26
+ simulation_models_path : str
27
+ Path to the simulation models repository.
28
+
29
+ Returns
30
+ -------
31
+ bool
32
+ True if all parameters found, False if any missing.
33
+ """
34
+ productions_path = Path(simulation_models_path) / "simulation-models" / "productions"
35
+ production_files = list(productions_path.rglob("*.json"))
36
+
37
+ _logger.info(
38
+ f"Verifying {len(production_files)} simulation model production "
39
+ f"tables in {simulation_models_path}"
40
+ )
41
+
42
+ missing_files = []
43
+ total_checked = 0
44
+
45
+ for production_file in production_files:
46
+ file_missing, file_checked = _verify_model_parameters_for_production(
47
+ simulation_models_path, production_file
48
+ )
49
+ missing_files.extend(file_missing)
50
+ total_checked += file_checked
51
+
52
+ _logger.info(f"Checked {total_checked} parameters, {len(missing_files)} missing")
53
+
54
+ if missing_files:
55
+ for missing_file in missing_files:
56
+ _logger.error(f"Missing: {missing_file}")
57
+ return False
58
+
59
+ _logger.info("Verification passed: All parameters found")
60
+ return True
61
+
62
+
63
+ def _verify_model_parameters_for_production(simulation_models_path, production_file):
64
+ """
65
+ Verify that model parameters defined in the production tables exist.
66
+
67
+ Parameters
68
+ ----------
69
+ simulation_models_path : str
70
+ Path to the simulation models repository.
71
+ production_file : Path
72
+ Path to the production file.
73
+
74
+ Returns
75
+ -------
76
+ tuple
77
+ (missing_files_list, total_checked_count)
78
+ """
79
+ production_table = gen.collect_data_from_file(production_file)
80
+ missing_files = []
81
+ total_checked = 0
82
+
83
+ parameters = production_table.get("parameters", {})
84
+ for array_element, par_dict in parameters.items():
85
+ if isinstance(par_dict, dict):
86
+ for param_name, param_version in par_dict.items():
87
+ total_checked += 1
88
+ parameter_file = _get_model_parameter_file_path(
89
+ simulation_models_path, array_element, param_name, param_version
90
+ )
91
+ if parameter_file and not parameter_file.exists():
92
+ missing_files.append(str(parameter_file))
93
+
94
+ return missing_files, total_checked
95
+
96
+
97
+ def _get_model_parameter_file_path(
98
+ simulation_models_path, array_element, parameter_name, parameter_version
99
+ ):
100
+ """
101
+ Get the file path for a model parameter.
102
+
103
+ Take into account path structure based on collections and array elements.
104
+
105
+ Parameters
106
+ ----------
107
+ simulation_models_path : str
108
+ Path to the simulation models repository.
109
+ array_element : str
110
+ Name of the array element (e.g., 'telescope').
111
+ parameter_name : str
112
+ Name of the parameter.
113
+ parameter_version : str
114
+ Version of the parameter.
115
+
116
+ Returns
117
+ -------
118
+ Path
119
+ The file path to the model parameter JSON file.
120
+ """
121
+ collection = names.get_collection_name_from_parameter_name(parameter_name)
122
+ return (
123
+ Path(simulation_models_path)
124
+ / "simulation-models"
125
+ / "model_parameters"
126
+ / (
127
+ collection
128
+ if collection in ("configuration_sim_telarray", "configuration_corsika")
129
+ else ""
130
+ )
131
+ / (array_element if collection != "configuration_corsika" else "")
132
+ / parameter_name
133
+ / f"{parameter_name}-{parameter_version}.json"
134
+ )