gammasimtools 0.20.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 (25) hide show
  1. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/METADATA +1 -1
  2. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/RECORD +24 -23
  3. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/entry_points.txt +1 -1
  4. simtools/_version.py +2 -2
  5. simtools/applications/db_generate_compound_indexes.py +1 -1
  6. simtools/applications/derive_psf_parameters.py +58 -39
  7. simtools/applications/generate_corsika_histograms.py +7 -184
  8. simtools/applications/maintain_simulation_model_add_production.py +105 -0
  9. simtools/applications/plot_simtel_events.py +2 -228
  10. simtools/applications/print_version.py +8 -7
  11. simtools/corsika/corsika_histograms.py +81 -0
  12. simtools/db/db_handler.py +45 -11
  13. simtools/db/db_model_upload.py +40 -14
  14. simtools/model/model_repository.py +118 -63
  15. simtools/ray_tracing/psf_parameter_optimisation.py +999 -565
  16. simtools/simtel/simtel_config_writer.py +1 -1
  17. simtools/simulator.py +1 -4
  18. simtools/version.py +89 -0
  19. simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
  20. simtools/visualization/plot_psf.py +673 -0
  21. simtools/visualization/plot_simtel_events.py +284 -87
  22. simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
  23. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/WHEEL +0 -0
  24. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/licenses/LICENSE +0 -0
  25. {gammasimtools-0.20.0.dist-info → gammasimtools-0.21.0.dist-info}/top_level.txt +0 -0
@@ -515,7 +515,7 @@ class SimtelConfigWriter:
515
515
  width = trigger_dict["width"]["value"] * u.Unit(trigger_dict["width"]["unit"]).to("ns")
516
516
  trigger_lines[tel_type] += f" width {width}"
517
517
  if trigger_dict.get("hard_stereo"):
518
- trigger_lines[tel_type] += " hard_stereo"
518
+ trigger_lines[tel_type] += " hardstereo"
519
519
  if all(trigger_dict["min_separation"][key] is not None for key in ["value", "unit"]):
520
520
  min_sep = trigger_dict["min_separation"]["value"] * u.Unit(
521
521
  trigger_dict["min_separation"]["unit"]
simtools/simulator.py CHANGED
@@ -19,6 +19,7 @@ from simtools.runners.corsika_simtel_runner import CorsikaSimtelRunner
19
19
  from simtools.simtel.simtel_io_event_writer import SimtelIOEventDataWriter
20
20
  from simtools.simtel.simulator_array import SimulatorArray
21
21
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
22
+ from simtools.version import semver_to_int
22
23
 
23
24
  __all__ = [
24
25
  "InvalidRunsToSimulateError",
@@ -163,10 +164,6 @@ class Simulator:
163
164
  if seed:
164
165
  return int(seed.split(",")[0].strip())
165
166
 
166
- def semver_to_int(version: str):
167
- major, minor, patch = map(int, version.split("."))
168
- return major * 10000 + minor * 100 + patch
169
-
170
167
  seed = semver_to_int(model_version) * 10000000
171
168
  seed = seed + 1000000 if self.args_dict.get("site") != "North" else seed + 2000000
172
169
  seed = seed + (int)(self.args_dict["zenith_angle"].value) * 1000
simtools/version.py CHANGED
@@ -4,6 +4,8 @@
4
4
  # which is adapted from https://github.com/astropy/astropy/blob/master/astropy/version.py
5
5
  # see https://github.com/astropy/astropy/pull/10774 for a discussion on why this needed.
6
6
 
7
+ from packaging.version import InvalidVersion, Version
8
+
7
9
  try:
8
10
  try:
9
11
  from ._dev_version import version
@@ -17,3 +19,90 @@ except Exception: # pylint: disable=broad-except
17
19
  version = "0.0.0" # pylint: disable=invalid-name
18
20
 
19
21
  __version__ = version
22
+
23
+
24
+ def resolve_version_to_latest_patch(partial_version, available_versions):
25
+ """
26
+ Resolve a partial version (major.minor) to the latest patch version.
27
+
28
+ Given a partial version string (e.g., "6.0") and a list of available versions,
29
+ finds the latest patch version that matches the major.minor pattern.
30
+
31
+ Parameters
32
+ ----------
33
+ partial_version : str
34
+ Partial version string in format "major.minor" (e.g., "6.0", "5.2")
35
+ available_versions : list of str
36
+ List of available semantic versions (e.g., ["5.0.0", "5.0.1", "6.0.0", "6.0.2"])
37
+
38
+ Returns
39
+ -------
40
+ str
41
+ Latest patch version matching the partial version pattern
42
+
43
+ Raises
44
+ ------
45
+ ValueError
46
+ If partial_version is not in major.minor format
47
+ ValueError
48
+ If no matching versions are found
49
+
50
+ Examples
51
+ --------
52
+ >>> versions = ["5.0.0", "5.0.1", "6.0.0", "6.0.2", "6.1.0"]
53
+ >>> resolve_version_to_latest_patch("6.0", versions)
54
+ '6.0.2'
55
+ >>> resolve_version_to_latest_patch("5.0", versions)
56
+ '5.0.1'
57
+ >>> resolve_version_to_latest_patch("5.0.1", versions)
58
+ '5.0.1'
59
+ """
60
+ try:
61
+ pv = Version(partial_version)
62
+ except InvalidVersion as exc:
63
+ raise ValueError(f"Invalid version string: {partial_version}") from exc
64
+
65
+ if pv.release and len(pv.release) >= 3:
66
+ return str(pv)
67
+
68
+ if len(pv.release) != 2:
69
+ raise ValueError(f"Partial version must be major.minor, got: {partial_version}")
70
+
71
+ major, minor = pv.release
72
+
73
+ candidates = [
74
+ v for v in available_versions if Version(v).major == major and Version(v).minor == minor
75
+ ]
76
+
77
+ if not candidates:
78
+ raise ValueError(
79
+ f"No versions found matching '{partial_version}.x' "
80
+ f"in available versions: {sorted(available_versions)}"
81
+ )
82
+
83
+ return str(max(map(Version, candidates)))
84
+
85
+
86
+ def semver_to_int(version_string):
87
+ """
88
+ Convert a semantic version string to an integer.
89
+
90
+ Parameters
91
+ ----------
92
+ version_string : str
93
+ Semantic version string (e.g., "6.0.2")
94
+
95
+ Returns
96
+ -------
97
+ int
98
+ Integer representation of the version (e.g., 60002 for "6.0.2")
99
+
100
+ """
101
+ try:
102
+ v = Version(version_string)
103
+ except InvalidVersion as exc:
104
+ raise ValueError(f"Invalid version: {version_string}") from exc
105
+
106
+ release = v.release + (0,) * (3 - len(v.release))
107
+ major, minor, patch = release[:3]
108
+ return major * 10000 + minor * 100 + patch
@@ -1,6 +1,7 @@
1
1
  """Visualize Cherenkov photon distributions from CORSIKA."""
2
2
 
3
3
  import logging
4
+ import re
4
5
  from pathlib import Path
5
6
 
6
7
  import matplotlib.pyplot as plt
@@ -569,3 +570,111 @@ def save_figs_to_pdf(figs, pdf_file_name):
569
570
  plt.tight_layout()
570
571
  pdf_pages.savefig(fig)
571
572
  pdf_pages.close()
573
+
574
+
575
+ def build_all_photon_figures(histograms_instance, test: bool = False):
576
+ """Return list of all photon histogram figures for the given instance.
577
+
578
+ When test is True, only generate the first two figure groups to reduce runtime.
579
+ """
580
+ plot_function_names = sorted(
581
+ [
582
+ name
583
+ for name, obj in globals().items()
584
+ if name.startswith("plot_")
585
+ and "event_header_distribution" not in name
586
+ and callable(obj)
587
+ ]
588
+ )
589
+ if test:
590
+ plot_function_names = plot_function_names[:2]
591
+
592
+ figure_list = []
593
+ module_obj = globals()
594
+ for fn_name in plot_function_names:
595
+ plot_fn = module_obj[fn_name]
596
+ figs = plot_fn(histograms_instance)
597
+ for fig in figs:
598
+ figure_list.append(fig)
599
+ return np.array(figure_list).flatten()
600
+
601
+
602
+ def export_all_photon_figures_pdf(histograms_instance, test: bool = False):
603
+ """Build and save all photon histogram figures into a single PDF.
604
+
605
+ The PDF name is derived from the HDF5 file name core and written under output_path.
606
+ """
607
+ figs = build_all_photon_figures(histograms_instance, test=test)
608
+ core_name = re.sub(r"\.hdf5$", "", Path(histograms_instance.hdf5_file_name).name)
609
+ output_file_name = Path(histograms_instance.output_path).joinpath(f"{core_name}.pdf")
610
+ save_figs_to_pdf(figs, output_file_name)
611
+ return output_file_name
612
+
613
+
614
+ def derive_event_1d_histograms(
615
+ histograms_instance,
616
+ event_1d_header_keys,
617
+ pdf: bool,
618
+ hdf5: bool,
619
+ overwrite: bool = False,
620
+ ):
621
+ """Create 1D event header histograms; optionally save to PDF and/or HDF5."""
622
+ figure_list = []
623
+ for key in event_1d_header_keys:
624
+ if pdf:
625
+ fig = plot_1d_event_header_distribution(histograms_instance, key)
626
+ figure_list.append(fig)
627
+ if hdf5:
628
+ histograms_instance.export_event_header_1d_histogram(
629
+ key, bins=50, hist_range=None, overwrite=overwrite
630
+ )
631
+ if pdf:
632
+ figs_array = np.array(figure_list).flatten()
633
+ pdf_name = Path(histograms_instance.output_path).joinpath(
634
+ f"{Path(histograms_instance.hdf5_file_name).name}_event_1d_histograms.pdf"
635
+ )
636
+ save_figs_to_pdf(figs_array, pdf_name)
637
+ return pdf_name
638
+ return None
639
+
640
+
641
+ def derive_event_2d_histograms(
642
+ histograms_instance,
643
+ event_2d_header_keys,
644
+ pdf: bool,
645
+ hdf5: bool,
646
+ overwrite: bool = False,
647
+ ):
648
+ """Create 2D event header histograms in pairs; optionally save PDF and/or HDF5.
649
+
650
+ If an odd number of keys is provided, the last one is ignored (with a warning).
651
+ """
652
+ if len(event_2d_header_keys) % 2 == 1:
653
+ _logger.warning(
654
+ "An odd number of keys was passed to generate 2D histograms.\n"
655
+ "The last key is being ignored."
656
+ )
657
+
658
+ figure_list = []
659
+ for i, _ in enumerate(event_2d_header_keys[::2]):
660
+ if pdf:
661
+ fig = plot_2d_event_header_distribution(
662
+ histograms_instance, event_2d_header_keys[i], event_2d_header_keys[i + 1]
663
+ )
664
+ figure_list.append(fig)
665
+ if hdf5:
666
+ histograms_instance.export_event_header_2d_histogram(
667
+ event_2d_header_keys[i],
668
+ event_2d_header_keys[i + 1],
669
+ bins=50,
670
+ hist_range=None,
671
+ overwrite=overwrite,
672
+ )
673
+ if pdf:
674
+ figs_array = np.array(figure_list).flatten()
675
+ pdf_name = Path(histograms_instance.output_path).joinpath(
676
+ f"{Path(histograms_instance.hdf5_file_name).name}_event_2d_histograms.pdf"
677
+ )
678
+ save_figs_to_pdf(figs_array, pdf_name)
679
+ return pdf_name
680
+ return None