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
@@ -8,24 +8,16 @@ from collections import OrderedDict
8
8
  from pathlib import Path
9
9
 
10
10
  import astropy.units as u
11
- import matplotlib.patches as mpatches
12
11
  import matplotlib.pyplot as plt
13
- from astropy.table import Column, QTable
12
+ from astropy.table import QTable
14
13
  from cycler import cycler
15
14
  from matplotlib import gridspec
16
- from matplotlib.collections import PatchCollection
17
-
18
- from simtools.utils import geometry as transf
19
- from simtools.utils import names
20
- from simtools.visualization import legend_handlers as leg_h
21
15
 
22
16
  __all__ = [
23
17
  "get_colors",
24
18
  "get_lines",
25
19
  "get_markers",
26
- "get_telescope_patch",
27
20
  "plot_1d",
28
- "plot_array",
29
21
  "plot_hist_2d",
30
22
  "plot_table",
31
23
  "save_figure",
@@ -635,214 +627,6 @@ def plot_hist_2d(data, **kwargs):
635
627
  return fig
636
628
 
637
629
 
638
- @u.quantity_input(x=u.m, y=u.m, radius=u.m)
639
- def get_telescope_patch(name, x, y, radius):
640
- """
641
- Collect the patch of one telescope to be plotted by plot_array.
642
-
643
- Parameters
644
- ----------
645
- name: str
646
- Name of the telescope (type).
647
- x: astropy.units.Quantity
648
- x position of the telescope usually in meters.
649
- y: astropy.units.Quantity
650
- y position of the telescope usually in meters.
651
- radius: astropy.units.Quantity
652
- Radius of the telescope sphere usually in meters.
653
-
654
- Returns
655
- -------
656
- patch
657
- Instance of mpatches.Circle.
658
- """
659
- tel_obj = leg_h.TelescopeHandler()
660
- valid_name = names.get_array_element_type_from_name(name)
661
- fill_flag = False
662
-
663
- x = x.to(u.m)
664
- y = y.to(u.m)
665
- radius = radius.to(u.m)
666
-
667
- if valid_name.startswith("MST"):
668
- fill_flag = True
669
- if valid_name == "SCTS":
670
- patch = mpatches.Rectangle(
671
- ((x - radius / 2).value, (y - radius / 2).value),
672
- width=radius.value,
673
- height=radius.value,
674
- fill=False,
675
- color=tel_obj.colors_dict["SCTS"],
676
- )
677
- else:
678
- patch = mpatches.Circle(
679
- (x.value, y.value),
680
- radius=radius.value,
681
- fill=fill_flag,
682
- color=tel_obj.colors_dict[valid_name],
683
- )
684
- return patch
685
-
686
-
687
- @u.quantity_input(rotate_angle=u.deg)
688
- def plot_array(
689
- telescopes, rotate_angle=0, show_tel_label=False, axes_range=None, marker_scaling=1.0
690
- ):
691
- """
692
- Plot the array of telescopes.
693
-
694
- The x axis gives the easting direction and y axis gives the northing direction.
695
- Note that in order to convert from the CORSIKA coordinate system to the 'conventional' system
696
- of North/East, a 90 degree rotation is always applied.
697
- Rotation of the array elements is possible through the 'rotate_angle' given either in degrees,
698
- or in radians.
699
- The direction of rotation of the array elements is counterclockwise.
700
- The rotation does not change Telescope instance attributes.
701
-
702
- Parameters
703
- ----------
704
- telescopes: astropy.table
705
- Table with the telescope position and names. Note the orientation of the axes.
706
- rotate_angle:
707
- Angle to rotate the plot. For rotate_angle = 0 the resulting plot will have
708
- the x-axis pointing towards the east, and the y-axis pointing towards the North.
709
- show_tel_label: bool
710
- If True it will print the label of the individual telescopes in the plot.
711
- While it works well for the smaller arrays, it gets crowded for larger arrays.
712
- axes_range : float
713
- Axis range for both axes. Range is from -plot_range to plot_range.
714
- maker_scaling : float
715
- Scaling factor for marker size to be plotted.
716
-
717
- Returns
718
- -------
719
- plt.figure
720
- Instance of plt.figure with the array of telescopes plotted.
721
- """
722
- fig, ax = plt.subplots(1)
723
- legend_objects = []
724
- legend_labels = []
725
- tel_counters = initialize_tel_counters()
726
-
727
- pos_x_rotated, pos_y_rotated = get_rotated_positions(telescopes, rotate_angle)
728
- telescopes.add_column(Column(pos_x_rotated, name="pos_x_rotated"))
729
- telescopes.add_column(Column(pos_y_rotated, name="pos_y_rotated"))
730
-
731
- fontsize, scale = get_plot_params(len(pos_x_rotated))
732
- patches = create_patches(
733
- telescopes, scale, marker_scaling, show_tel_label, ax, fontsize, tel_counters
734
- )
735
-
736
- update_legend(ax, tel_counters, legend_objects, legend_labels)
737
- finalize_plot(ax, patches, x_title="Easting [m]", y_title="Northing [m]", axes_range=axes_range)
738
-
739
- return fig
740
-
741
-
742
- def initialize_tel_counters():
743
- return dict.fromkeys(names.get_list_of_array_element_types(), 0)
744
-
745
-
746
- def get_rotated_positions(telescopes, rotate_angle):
747
- pos_x_rotated = pos_y_rotated = None
748
- if "position_x" in telescopes.colnames and "position_y" in telescopes.colnames:
749
- pos_x_rotated, pos_y_rotated = telescopes["position_x"], telescopes["position_y"]
750
- rotate_angle = rotate_angle + 90.0 * u.deg
751
- elif "utm_east" in telescopes.colnames and "utm_north" in telescopes.colnames:
752
- pos_x_rotated, pos_y_rotated = telescopes["utm_east"], telescopes["utm_north"]
753
- else:
754
- raise ValueError(
755
- "Telescopes table must contain either 'position_x'/'position_y'"
756
- "or 'utm_east'/'utm_north' columns"
757
- )
758
- if rotate_angle != 0:
759
- pos_x_rotated, pos_y_rotated = transf.rotate(pos_x_rotated, pos_y_rotated, rotate_angle)
760
- return pos_x_rotated, pos_y_rotated
761
-
762
-
763
- def get_plot_params(position_length):
764
- if position_length > 30:
765
- return 4, 2
766
- return 8, 1
767
-
768
-
769
- def create_patches(telescopes, scale, marker_scaling, show_tel_label, ax, fontsize, tel_counters):
770
- patches = []
771
- for tel_now in telescopes:
772
- telescope_name = get_telescope_name(tel_now)
773
- update_tel_counters(tel_counters, telescope_name)
774
- sphere_radius = get_sphere_radius(tel_now)
775
- i_tel_name = names.get_array_element_type_from_name(telescope_name)
776
- patches.append(
777
- get_telescope_patch(
778
- i_tel_name,
779
- tel_now["pos_x_rotated"],
780
- tel_now["pos_y_rotated"],
781
- scale * sphere_radius * marker_scaling,
782
- )
783
- )
784
- if show_tel_label:
785
- ax.text(
786
- tel_now["pos_x_rotated"].value,
787
- tel_now["pos_y_rotated"].value + scale * sphere_radius.value,
788
- telescope_name,
789
- horizontalalignment="center",
790
- verticalalignment="bottom",
791
- fontsize=fontsize,
792
- )
793
- return patches
794
-
795
-
796
- def get_telescope_name(tel_now):
797
- """Get the telescope name from the table row."""
798
- try:
799
- return tel_now["telescope_name"]
800
- except KeyError:
801
- return tel_now["asset_code"] + "-" + tel_now["sequence_number"]
802
-
803
-
804
- def update_tel_counters(tel_counters, telescope_name):
805
- """Update the counter for the given telescope type."""
806
- for tel_type in tel_counters:
807
- if tel_type in telescope_name:
808
- tel_counters[tel_type] += 1
809
-
810
-
811
- def get_sphere_radius(tel_now):
812
- """Get the sphere radius of the telescope."""
813
- return 1.0 * u.m if "sphere_radius" not in tel_now.colnames else tel_now["sphere_radius"]
814
-
815
-
816
- def update_legend(ax, tel_counters, legend_objects, legend_labels):
817
- """Update the legend with the telescope counts."""
818
- for one_telescope in names.get_list_of_array_element_types():
819
- if tel_counters[one_telescope] > 0:
820
- legend_objects.append(leg_h.all_telescope_objects[one_telescope]())
821
- legend_labels.append(f"{one_telescope} ({tel_counters[one_telescope]})")
822
- legend_handler_map = {k: v() for k, v in leg_h.legend_handler_map.items()}
823
- ax.legend(
824
- legend_objects,
825
- legend_labels,
826
- handler_map=legend_handler_map,
827
- prop={"size": 11},
828
- loc="best",
829
- )
830
-
831
-
832
- def finalize_plot(ax, patches, x_title, y_title, axes_range):
833
- """Finalize the plot by adding titles, setting limits, and adding patches."""
834
- ax.add_collection(PatchCollection(patches, match_original=True))
835
- ax.set_xlabel(x_title, fontsize=12, labelpad=0)
836
- ax.set_ylabel(y_title, fontsize=12, labelpad=0)
837
- ax.tick_params(axis="both", which="major", labelsize=8)
838
- ax.set_axisbelow(True)
839
- ax.axis("square")
840
- if axes_range is not None:
841
- ax.set_xlim(-axes_range, axes_range)
842
- ax.set_ylim(-axes_range, axes_range)
843
- plt.tight_layout()
844
-
845
-
846
630
  def plot_simtel_ctapipe(filename, cleaning_args, distance, return_cleaned=False):
847
631
  """
848
632
  Read in a sim_telarray file and plots reconstructed photoelectrons via ctapipe.
@@ -942,7 +726,7 @@ def plot_simtel_ctapipe(filename, cleaning_args, distance, return_cleaned=False)
942
726
  return fig
943
727
 
944
728
 
945
- def save_figure(fig, output_file, figure_format=None, log_title=""):
729
+ def save_figure(fig, output_file, figure_format=None, log_title="", dpi="figure"):
946
730
  """
947
731
  Save figure to output file(s).
948
732
 
@@ -960,7 +744,7 @@ def save_figure(fig, output_file, figure_format=None, log_title=""):
960
744
  figure_format = figure_format or ["pdf", "png"]
961
745
  for fmt in figure_format:
962
746
  _file = Path(output_file).with_suffix(f".{fmt}")
963
- fig.savefig(_file, format=fmt, bbox_inches="tight")
747
+ fig.savefig(_file, format=fmt, bbox_inches="tight", dpi=dpi)
964
748
  logging.info(f"Saved plot {log_title} to {_file}")
965
749
 
966
750
  fig.clf()
@@ -1,152 +0,0 @@
1
- #!/usr/bin/python3
2
-
3
- r"""
4
- Derive simulation configuration parameters for a simulation production.
5
-
6
- Derived simulation configuration parameters include:
7
-
8
- * energy range
9
- * shower core scatter radius
10
- * view cone radius
11
- * total number of events to be simulated
12
-
13
- Configuration parameters depend on characteristics of the observations, especially elevation,
14
- azimuth, and night sky background.
15
-
16
- The configuration parameters are derived according to the required precision. The metrics are:
17
-
18
- * statistical uncertainty on the determination of the effective area as function of primary energy
19
- * fraction of lost events to the selected core scatter and view cone radius (to be implemented)
20
- * statistical uncertainty of the energy migration matrix as function of primary energy
21
- (to be implemented)
22
-
23
- Command line arguments
24
- ----------------------
25
- azimuth (float, required)
26
- Azimuth angle in degrees.
27
- elevation (float, required)
28
- Elevation angle in degrees.
29
- nsb (float, required)
30
- Night sky background value.
31
- file_path (str, required)
32
- Path to file with MC events at CTAO DL2 data level.
33
- Used for statistical uncertainty evaluation.
34
- metrics (str, required)
35
- Path to a YAML file containing metrics for evaluation.
36
- site (str, required)
37
- The observatory site (North or South).
38
-
39
- Example
40
- -------
41
- To run the simulation configuration, execute the script as follows:
42
-
43
- .. code-block:: console
44
-
45
- simtools-production-generate-simulation-config --azimuth 60.0 --elevation 45.0 \
46
- --nsb 0.3 --file_path tests/resources/production_dl2_fits/dl2_mc_events_file.fits \
47
- --metrics_file tests/resources/production_simulation_config_metrics.yml --site North
48
-
49
- The output will show the derived simulation parameters.
50
- """
51
-
52
- import json
53
- import logging
54
- from pathlib import Path
55
-
56
- import astropy.units as u
57
-
58
- import simtools.utils.general as gen
59
- from simtools.configuration import configurator
60
- from simtools.data_model import schema
61
- from simtools.io_operations import io_handler
62
- from simtools.production_configuration.generate_simulation_config import (
63
- SimulationConfig,
64
- )
65
-
66
-
67
- def _parse(label):
68
- """Parse command-line arguments."""
69
- config = configurator.Configurator(
70
- label=label,
71
- description="Derive simulation configuration parameters for a simulation production.",
72
- )
73
- config.parser.add_argument(
74
- "--azimuth", type=float, required=True, help="Azimuth angle in degrees."
75
- )
76
- config.parser.add_argument(
77
- "--elevation", type=float, required=True, help="Elevation angle in degrees."
78
- )
79
- config.parser.add_argument(
80
- "--nsb", type=float, required=True, help="Night sky background in units of 1/(sr*ns*cm**2)."
81
- )
82
- config.parser.add_argument(
83
- "--file_path", type=str, required=True, help="Path to MC event file in DL2 format."
84
- )
85
- config.parser.add_argument(
86
- "--metrics_file",
87
- required=True,
88
- type=str,
89
- help="Path to YAML file containing metrics and required precision as values.",
90
- )
91
- config.parser.add_argument(
92
- "--site",
93
- type=str,
94
- required=True,
95
- help="Site location for the simulation (e.g., 'North', 'South').",
96
- )
97
- config.parser.add_argument(
98
- "--output_file",
99
- help="Name of the file to save the configured simulation parameters.",
100
- type=str,
101
- required=False,
102
- default="configured_simulation_params.json",
103
- )
104
-
105
- return config.initialize(db_config=False)
106
-
107
-
108
- def main():
109
- """Run the Simulation Config application."""
110
- label = Path(__file__).stem
111
- args_dict, _ = _parse(label)
112
-
113
- logger = logging.getLogger()
114
- logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))
115
- output_path = io_handler.IOHandler().get_output_directory(label)
116
- output_filepath = Path(output_path).joinpath(f"{args_dict['output_file']}")
117
-
118
- grid_point_config = {
119
- "azimuth": args_dict["azimuth"],
120
- "elevation": args_dict["elevation"],
121
- "night_sky_background": args_dict["nsb"],
122
- }
123
-
124
- metrics = gen.collect_data_from_file(args_dict["metrics_file"])
125
- schema.validate_dict_using_schema(
126
- data=metrics, schema_file="production_configuration_metrics.schema.yml"
127
- )
128
-
129
- simulation_config = SimulationConfig(
130
- grid_point=grid_point_config,
131
- file_path=args_dict["file_path"],
132
- metrics=metrics,
133
- )
134
-
135
- simulation_params = simulation_config.configure_simulation()
136
-
137
- serializable_config = {}
138
-
139
- for key, value in simulation_params.items():
140
- if isinstance(value, u.Quantity):
141
- serializable_config[key] = f"{value.value} {value.unit}"
142
- else:
143
- serializable_config[key] = value
144
-
145
- logger.info(f"Simulation configuration: {serializable_config}")
146
- with open(output_filepath, "w", encoding="utf-8") as f:
147
- json.dump(serializable_config, f, indent=4)
148
- logger.info(f"Simulation configuration saved to: {output_filepath}")
149
-
150
-
151
- if __name__ == "__main__":
152
- main()
@@ -1,172 +0,0 @@
1
- """Retrieve, merge, and write layouts from CTAO common identifiers repository."""
2
-
3
- import logging
4
- from pathlib import Path
5
-
6
- import simtools.utils.general as gen
7
- from simtools.data_model.metadata_collector import MetadataCollector
8
- from simtools.data_model.model_data_writer import ModelDataWriter
9
- from simtools.io_operations import io_handler
10
- from simtools.utils import names
11
-
12
- _logger = logging.getLogger(__name__)
13
-
14
-
15
- def retrieve_array_layouts(site, repository_url, branch_name="main"):
16
- """
17
- Retrieve array layouts from CTAO common identifiers repository.
18
-
19
- Parameters
20
- ----------
21
- site : str
22
- Site identifier.
23
- repository_url : str
24
- URL or path to CTAO common identifiers
25
- branch_name : str
26
- Repository branch to use for CTAO common identifiers.
27
-
28
- Returns
29
- -------
30
- dict
31
- Array layouts for all CTAO sites.
32
- """
33
- _logger.info(f"Retrieving array layouts from {repository_url} on branch {branch_name}.")
34
-
35
- if gen.is_url(repository_url):
36
- array_element_ids = gen.collect_data_from_http(
37
- url=f"{repository_url}/{branch_name}/array-element-ids.json"
38
- )
39
- sub_arrays = gen.collect_data_from_http(
40
- url=f"{repository_url}/{branch_name}/subarray-ids.json"
41
- )
42
- else:
43
- array_element_ids = gen.collect_data_from_file(
44
- Path(repository_url) / "array-element-ids.json"
45
- )
46
- sub_arrays = gen.collect_data_from_file(Path(repository_url) / "subarray-ids.json")
47
-
48
- return _get_layouts_per_site(site, sub_arrays, array_element_ids)
49
-
50
-
51
- def _get_layouts_per_site(site, sub_arrays, array_element_ids):
52
- """
53
- Get array layouts for CTAO sites.
54
-
55
- Parameters
56
- ----------
57
- site : str
58
- Site identifier.
59
- sub_arrays : dict
60
- Sub-array definitions.
61
- array_element_ids : dict
62
- Array element definitions.
63
-
64
- Returns
65
- -------
66
- dict
67
- Array layouts for CTAO sites.
68
- """
69
- layouts_per_site = []
70
-
71
- for array in sub_arrays.get("subarrays", []):
72
- elements = []
73
- for ids in array.get("array_element_ids", []):
74
- element_name = _get_array_element_name(ids, array_element_ids)
75
- if names.get_site_from_array_element_name(element_name) != site:
76
- break
77
- elements.append(element_name)
78
- if len(elements) > 0:
79
- array_layout = {
80
- "name": array.get("name"),
81
- "elements": elements,
82
- }
83
- layouts_per_site.append(array_layout)
84
-
85
- _logger.info(f"CTAO array layout definition: {layouts_per_site}")
86
- return layouts_per_site
87
-
88
-
89
- def _get_array_element_name(ids, array_element_ids):
90
- """Return array element name for common identifier."""
91
- for element in array_element_ids.get("array_elements", []):
92
- if element.get("id") == ids:
93
- return element.get("name")
94
- return None
95
-
96
-
97
- def merge_array_layouts(layouts_1, layouts_2):
98
- """
99
- Compare array layout dictionaries and merge them.
100
-
101
- Parameters
102
- ----------
103
- layouts_1 : dict
104
- Array layout dictionary 1.
105
- layouts_2 : dict
106
- Array layout dictionary 2.
107
-
108
- Returns
109
- -------
110
- dict
111
- Merged array layout dictionary based on layout_1.
112
- """
113
- merged_layout = layouts_1
114
- for layout_2 in layouts_2:
115
- layout_found = False
116
- for layout_1 in layouts_1.get("value", {}):
117
- if sorted(layout_1["elements"]) == sorted(layout_2["elements"]):
118
- print(
119
- f"Equal telescope list: simtools '{layout_1['name']}' "
120
- f"and CTAO '{layout_2['name']}'"
121
- )
122
- layout_1["name"] = layout_2["name"]
123
- layout_found = True
124
- if not layout_found:
125
- merged_layout["value"].append(
126
- {
127
- "name": layout_2["name"],
128
- "elements": layout_2["elements"],
129
- }
130
- )
131
- _logger.info(f"Adding {layout_2['name']} with {layout_2['elements']}")
132
- return merged_layout
133
-
134
-
135
- def write_array_layouts(array_layouts, args_dict, db_config):
136
- """
137
- Write array layouts as model parameter.
138
-
139
- Parameters
140
- ----------
141
- args_dict : dict
142
- Command line arguments.
143
- array_layouts : dict
144
- Array layouts to be written.
145
- db_config : dict
146
- Database configuration.
147
- """
148
- _logger.info(f"Writing updated array layouts to the database for site {args_dict['site']}.")
149
-
150
- io_handler_instance = io_handler.IOHandler()
151
- io_handler_instance.set_paths(
152
- output_path=args_dict["output_path"],
153
- use_plain_output_path=args_dict["use_plain_output_path"],
154
- )
155
- output_file = io_handler_instance.get_output_file(
156
- f"array-layouts-{args_dict['updated_parameter_version']}.json"
157
- )
158
-
159
- ModelDataWriter.dump_model_parameter(
160
- parameter_name="array_layouts",
161
- value=array_layouts["value"],
162
- instrument=args_dict["site"],
163
- parameter_version=args_dict.get("updated_parameter_version"),
164
- output_file=output_file,
165
- use_plain_output_path=args_dict["use_plain_output_path"],
166
- db_config=db_config,
167
- )
168
- MetadataCollector.dump(
169
- args_dict,
170
- output_file,
171
- add_activity_name=True,
172
- )