gammasimtools 0.19.0__py3-none-any.whl → 0.21.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 (59) hide show
  1. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/METADATA +1 -3
  2. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/RECORD +54 -51
  3. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/entry_points.txt +3 -3
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_incident_angles.py +182 -0
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +17 -14
  7. simtools/applications/db_add_value_from_json_to_db.py +6 -9
  8. simtools/applications/db_generate_compound_indexes.py +7 -3
  9. simtools/applications/db_get_file_from_db.py +11 -23
  10. simtools/applications/derive_psf_parameters.py +58 -39
  11. simtools/applications/derive_trigger_rates.py +91 -0
  12. simtools/applications/generate_corsika_histograms.py +7 -184
  13. simtools/applications/maintain_simulation_model_add_production.py +105 -0
  14. simtools/applications/plot_simtel_events.py +5 -189
  15. simtools/applications/print_version.py +8 -7
  16. simtools/applications/validate_file_using_schema.py +7 -4
  17. simtools/configuration/commandline_parser.py +17 -11
  18. simtools/corsika/corsika_histograms.py +81 -0
  19. simtools/data_model/validate_data.py +8 -3
  20. simtools/db/db_handler.py +122 -31
  21. simtools/db/db_model_upload.py +51 -30
  22. simtools/dependencies.py +10 -5
  23. simtools/layout/array_layout_utils.py +37 -5
  24. simtools/model/array_model.py +18 -1
  25. simtools/model/model_repository.py +118 -63
  26. simtools/model/site_model.py +25 -0
  27. simtools/production_configuration/derive_corsika_limits.py +9 -34
  28. simtools/ray_tracing/incident_angles.py +706 -0
  29. simtools/ray_tracing/psf_parameter_optimisation.py +999 -565
  30. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -2
  31. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
  32. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +22 -29
  33. simtools/schemas/model_parameters/stars.schema.yml +1 -1
  34. simtools/schemas/production_tables.schema.yml +5 -0
  35. simtools/simtel/simtel_config_writer.py +18 -20
  36. simtools/simtel/simtel_io_event_histograms.py +253 -516
  37. simtools/simtel/simtel_io_event_reader.py +51 -2
  38. simtools/simtel/simtel_io_event_writer.py +31 -11
  39. simtools/simtel/simtel_io_metadata.py +1 -1
  40. simtools/simtel/simtel_table_reader.py +3 -3
  41. simtools/simulator.py +1 -4
  42. simtools/telescope_trigger_rates.py +119 -0
  43. simtools/testing/log_inspector.py +13 -11
  44. simtools/utils/geometry.py +20 -0
  45. simtools/version.py +89 -0
  46. simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
  47. simtools/visualization/plot_incident_angles.py +431 -0
  48. simtools/visualization/plot_psf.py +673 -0
  49. simtools/visualization/plot_simtel_event_histograms.py +376 -0
  50. simtools/visualization/{simtel_event_plots.py → plot_simtel_events.py} +284 -87
  51. simtools/visualization/visualize.py +1 -3
  52. simtools/applications/calculate_trigger_rate.py +0 -187
  53. simtools/applications/generate_sim_telarray_histograms.py +0 -196
  54. simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
  55. simtools/simtel/simtel_io_histogram.py +0 -623
  56. simtools/simtel/simtel_io_histograms.py +0 -556
  57. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/WHEEL +0 -0
  58. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/licenses/LICENSE +0 -0
  59. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,7 @@ a gitlab repository ('SimulationModels'). This module provides service
5
5
  functions to interact with and verify the repository.
6
6
  """
7
7
 
8
- import json
9
8
  import logging
10
- import shutil
11
9
  from pathlib import Path
12
10
 
13
11
  from simtools.io import ascii_handler
@@ -136,9 +134,15 @@ def _get_model_parameter_file_path(
136
134
  )
137
135
 
138
136
 
139
- def copy_and_update_production_table(args_dict):
137
+ def generate_new_production(args_dict):
140
138
  """
141
- Copy and update simulation model production tables.
139
+ Generate a new production definition (production tables and model parameters).
140
+
141
+ The following steps are performed:
142
+
143
+ - copy of production tables from an existing base model version
144
+ - update production tables with changes defined in a YAML file
145
+ - generate new model parameter entries for changed parameters
142
146
 
143
147
  Parameters
144
148
  ----------
@@ -147,85 +151,134 @@ def copy_and_update_production_table(args_dict):
147
151
  """
148
152
  modifications = ascii_handler.collect_data_from_file(args_dict["modifications"])
149
153
  changes = modifications.get("changes", {})
154
+ base_model_version = args_dict["base_model_version"]
150
155
  model_version = modifications["model_version"]
151
156
 
152
157
  simulation_models_path = Path(args_dict["simulation_models_path"])
153
- source_prod_table_path = (
154
- simulation_models_path / "productions" / args_dict["source_prod_table_dir"]
155
- )
156
- target_prod_table_path = simulation_models_path / "productions" / model_version
158
+ source_path = simulation_models_path / "productions" / base_model_version
159
+ target_path = simulation_models_path / "productions" / model_version
157
160
  model_parameters_dir = simulation_models_path / "model_parameters"
161
+ patch_update = args_dict.get("patch_update", False)
158
162
 
159
- _logger.info(
160
- f"Copying production tables from {source_prod_table_path} to {target_prod_table_path}"
161
- )
163
+ _logger.info(f"Copying production tables from {source_path} to {target_path}")
162
164
 
163
- if Path(target_prod_table_path).exists():
164
- raise FileExistsError(
165
- f"The target production table directory '{target_prod_table_path}' already exists."
166
- )
167
- shutil.copytree(source_prod_table_path, target_prod_table_path)
165
+ _apply_changes_to_production_tables(
166
+ source_path,
167
+ target_path,
168
+ changes,
169
+ model_version,
170
+ patch_update,
171
+ )
168
172
 
169
- _apply_changes_to_production_tables(target_prod_table_path, changes, model_version)
173
+ _apply_changes_to_model_parameters(changes, model_parameters_dir)
170
174
 
171
- for telescope, parameters in changes.items():
172
- for param, param_data in parameters.items():
173
- if param_data.get("value"):
174
- _create_new_parameter_entry(telescope, param, param_data, model_parameters_dir)
175
175
 
176
+ def _apply_changes_to_production_tables(
177
+ source_path, target_path, changes, model_version, patch_update
178
+ ):
179
+ """
180
+ Apply changes to production tables and write them to target directory.
176
181
 
177
- def _apply_changes_to_production_tables(target_prod_table_path, changes, model_version):
178
- """Apply changes to the production tables in the target directory."""
179
- for file_path in Path(target_prod_table_path).rglob("*.json"):
180
- if file_path.name.startswith("configuration"):
181
- continue
182
+ Parameters
183
+ ----------
184
+ source_path: Path
185
+ Path to the source production tables.
186
+ target_path: Path
187
+ Path to the target production tables.
188
+ changes: dict
189
+ The changes to be applied.
190
+ model_version: str
191
+ The model version to be set in the JSON data.
192
+ patch_update: bool
193
+ Patch update, copy only tables for changed elements.
194
+ """
195
+ target_path.mkdir(parents=True, exist_ok=True)
196
+ for file_path in Path(source_path).rglob("*.json"):
182
197
  data = ascii_handler.collect_data_from_file(file_path)
183
- _apply_changes_to_production_table(data, changes, model_version)
184
- with file_path.open("w", encoding="utf-8") as f:
185
- json.dump(data, f, indent=4, sort_keys=True)
186
- f.write("\n")
198
+ write_to_disk = _apply_changes_to_production_table(
199
+ data, changes, model_version, patch_update
200
+ )
201
+ if write_to_disk:
202
+ ascii_handler.write_data_to_file(data, target_path / file_path.name, sort_keys=True)
187
203
 
188
204
 
189
- def _apply_changes_to_production_table(data, changes, model_version):
205
+ def _apply_changes_to_production_table(data, changes, model_version, patch_update):
190
206
  """
191
207
  Recursively apply changes to the new production tables.
192
208
 
193
209
  Parameters
194
210
  ----------
195
- data: dict or list
196
- The JSON data to be updated.
211
+ data: dict
212
+ The data to be updated.
197
213
  changes: dict
198
214
  The changes to be applied.
199
215
  model_version: str
200
216
  The model version to be set in the JSON data.
217
+ patch_update: bool
218
+ Patch update, copy only tables for changed elements.
219
+
220
+ Returns
221
+ -------
222
+ bool
223
+ True if data was modified and should be written to disk (patch updates) and always
224
+ for full updates.
201
225
  """
202
226
  if isinstance(data, dict):
203
- if "model_version" in data:
204
- data["model_version"] = model_version
205
- _update_parameters(data.get("parameters", {}), changes)
206
-
207
- elif isinstance(data, list):
208
- for item in data:
209
- _apply_changes_to_production_table(item, changes, model_version)
210
-
211
-
212
- def _update_parameters(params, changes):
213
- """Update parameters in the given dictionary based on changes."""
214
- for telescope, updates in changes.items():
215
- if telescope not in params:
216
- continue
217
- for param, param_data in updates.items():
218
- if param in params[telescope]:
219
- old = params[telescope][param]
220
- new = param_data["version"]
221
- _logger.info(f"Updating '{telescope} - {param}' from {old} to {new}")
222
- params[telescope][param] = new
223
- else:
224
- _logger.info(
225
- f"Adding new parameter '{telescope} - {param}' "
226
- f"with version {param_data['version']}"
227
- )
228
- params[telescope][param] = param_data["version"]
227
+ table_name = data["production_table_name"]
228
+ data["model_version"] = model_version
229
+ if table_name in changes:
230
+ data["parameters"] = _update_parameters(
231
+ {} if patch_update else data["parameters"].get(table_name, {}), changes, table_name
232
+ )
233
+ elif patch_update:
234
+ return False
235
+ else:
236
+ raise TypeError(f"Unsupported data type {type(data)} in production table update")
237
+
238
+ return True
239
+
240
+
241
+ def _update_parameters(table_parameters, changes, table_name):
242
+ """
243
+ Create a new parameters dictionary containing only the parameters for the specified table.
244
+
245
+ Parameters
246
+ ----------
247
+ table_parameters: dict
248
+ Parameters for the specific table.
249
+ changes: dict
250
+ The changes to be applied, containing table and parameter information.
251
+ table_name: str
252
+ The name of the production table to filter parameters for.
253
+
254
+ Returns
255
+ -------
256
+ dict
257
+ Dictionary containing only the new/changed parameters for the specified table.
258
+ """
259
+ updated_parameters_dict = {table_name: table_parameters}
260
+ for param, data in changes[table_name].items():
261
+ version = data["version"]
262
+ _logger.info(f"Setting '{table_name} - {param}' to version {version}")
263
+ updated_parameters_dict[table_name][param] = version
264
+ return updated_parameters_dict
265
+
266
+
267
+ def _apply_changes_to_model_parameters(changes, model_parameters_dir):
268
+ """
269
+ Apply changes to model parameters by creating new parameter entries.
270
+
271
+ Parameters
272
+ ----------
273
+ changes: dict
274
+ The changes to be applied.
275
+ model_parameters_dir: str
276
+ Path to the model parameters directory.
277
+ """
278
+ for telescope, parameters in changes.items():
279
+ for param, param_data in parameters.items():
280
+ if param_data.get("value"):
281
+ _create_new_parameter_entry(telescope, param, param_data, model_parameters_dir)
229
282
 
230
283
 
231
284
  def _create_new_parameter_entry(telescope, param, param_data, model_parameters_dir):
@@ -266,14 +319,16 @@ def _create_new_parameter_entry(telescope, param, param_data, model_parameters_d
266
319
  json_data["parameter_version"] = _update_model_parameter_version(
267
320
  json_data, param_data, param, telescope
268
321
  )
269
- json_data["value"] = param_data["value"]
322
+ # important for e.g. nsb_pixel_rate
323
+ if isinstance(json_data["value"], list) and not isinstance(param_data["value"], list):
324
+ json_data["value"] = [param_data["value"]] * len(json_data["value"])
325
+ else:
326
+ json_data["value"] = param_data["value"]
270
327
 
271
328
  new_file_name = f"{param}-{param_data['version']}.json"
272
329
  new_file_path = param_dir / new_file_name
273
330
 
274
- with new_file_path.open("w", encoding="utf-8") as f:
275
- json.dump(json_data, f, indent=4)
276
- f.write("\n")
331
+ ascii_handler.write_data_to_file(json_data, new_file_path, sort_keys=True)
277
332
  _logger.info(f"Created new model parameter JSON file: {new_file_path}")
278
333
 
279
334
 
@@ -4,6 +4,9 @@
4
4
  import logging
5
5
  from pathlib import Path
6
6
 
7
+ import numpy as np
8
+ from astropy import units as u
9
+
7
10
  from simtools.model.model_parameter import ModelParameter
8
11
 
9
12
  __all__ = ["SiteModel"]
@@ -160,3 +163,25 @@ class SiteModel(ModelParameter):
160
163
  },
161
164
  dest=model_directory,
162
165
  )
166
+
167
+ def get_nsb_integrated_flux(self, wavelength_min=300 * u.nm, wavelength_max=650 * u.nm):
168
+ """
169
+ Get the integrated flux for the NSB (Night Sky Background) model.
170
+
171
+ Returns
172
+ -------
173
+ float
174
+ Integrated flux value.
175
+ """
176
+ table = self.db.get_ecsv_file_as_astropy_table(
177
+ file_name=self.get_parameter_value("nsb_spectrum")
178
+ )
179
+ table.sort("wavelength")
180
+ wl = table["wavelength"].quantity.to(u.nm)
181
+ rate = table["differential photon rate"].quantity.to(1 / (u.nm * u.cm**2 * u.ns * u.sr))
182
+ mask = (wl >= wavelength_min) & (wl <= wavelength_max)
183
+ integral_cm2 = np.trapezoid(rate[mask], wl[mask])
184
+ self._logger.debug(
185
+ f"NSB integral between {wavelength_min} and {wavelength_max}: {integral_cm2}"
186
+ )
187
+ return integral_cm2.value
@@ -9,8 +9,9 @@ from astropy.table import Column, Table
9
9
 
10
10
  from simtools.data_model.metadata_collector import MetadataCollector
11
11
  from simtools.io import ascii_handler, io_handler
12
- from simtools.model.site_model import SiteModel
12
+ from simtools.layout.array_layout_utils import get_array_elements_from_db_for_layouts
13
13
  from simtools.simtel.simtel_io_event_histograms import SimtelIOEventHistograms
14
+ from simtools.visualization import plot_simtel_event_histograms
14
15
 
15
16
  _logger = logging.getLogger(__name__)
16
17
 
@@ -27,7 +28,7 @@ def generate_corsika_limits_grid(args_dict, db_config=None):
27
28
  Database configuration dictionary.
28
29
  """
29
30
  if args_dict.get("array_layout_name"):
30
- telescope_configs = _read_array_layouts_from_db(
31
+ telescope_configs = get_array_elements_from_db_for_layouts(
31
32
  args_dict["array_layout_name"],
32
33
  args_dict.get("site"),
33
34
  args_dict.get("model_version"),
@@ -92,9 +93,11 @@ def _process_file(file_path, array_name, telescope_ids, loss_fraction, plot_hist
92
93
  }
93
94
 
94
95
  if plot_histograms:
95
- histograms.plot_data(
96
+ plot_simtel_event_histograms.plot(
97
+ histograms.histograms,
96
98
  output_path=io_handler.IOHandler().get_output_directory(),
97
99
  limits=limits,
100
+ array_name=array_name,
98
101
  )
99
102
 
100
103
  return limits
@@ -213,34 +216,6 @@ def _create_table_columns(cols, columns, units):
213
216
  return table_cols
214
217
 
215
218
 
216
- def _read_array_layouts_from_db(layouts, site, model_version, db_config):
217
- """
218
- Read array layouts from the database.
219
-
220
- Parameters
221
- ----------
222
- layouts : list[str]
223
- List of layout names to read. If "all", read all available layouts.
224
- site : str
225
- Site name for the array layouts.
226
- model_version : str
227
- Model version for the array layouts.
228
- db_config : dict
229
- Database configuration dictionary.
230
-
231
- Returns
232
- -------
233
- dict
234
- Dictionary mapping layout names to telescope IDs.
235
- """
236
- site_model = SiteModel(site=site, model_version=model_version, mongo_db_config=db_config)
237
- layout_names = site_model.get_list_of_array_layouts() if layouts == ["all"] else layouts
238
- layout_dict = {}
239
- for layout_name in layout_names:
240
- layout_dict[layout_name] = site_model.get_array_elements_for_layout(layout_name)
241
- return layout_dict
242
-
243
-
244
219
  def _compute_limits(hist, bin_edges, loss_fraction, limit_type="lower"):
245
220
  """
246
221
  Compute the limits based on the loss fraction.
@@ -294,7 +269,7 @@ def compute_lower_energy_limit(histograms, loss_fraction):
294
269
  """
295
270
  energy_min = (
296
271
  _compute_limits(
297
- histograms.histograms.get("energy"),
272
+ histograms.histograms["energy"]["histogram"],
298
273
  histograms.energy_bins,
299
274
  loss_fraction,
300
275
  limit_type="lower",
@@ -336,7 +311,7 @@ def compute_upper_radius_limit(histograms, loss_fraction):
336
311
  """
337
312
  radius_limit = (
338
313
  _compute_limits(
339
- histograms.histograms.get("core_distance"),
314
+ histograms.histograms["core_distance"]["histogram"],
340
315
  histograms.core_distance_bins,
341
316
  loss_fraction,
342
317
  limit_type="upper",
@@ -374,7 +349,7 @@ def compute_viewcone(histograms, loss_fraction):
374
349
  """
375
350
  viewcone_limit = (
376
351
  _compute_limits(
377
- histograms.histograms.get("angular_distance"),
352
+ histograms.histograms["angular_distance"]["histogram"],
378
353
  histograms.view_cone_bins,
379
354
  loss_fraction,
380
355
  limit_type="upper",