gammasimtools 0.24.0__py3-none-any.whl → 0.26.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.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +78 -0
- simtools/applications/calculate_incident_angles.py +0 -2
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -6
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/derive_mirror_rnda.py +1 -3
- simtools/applications/derive_psf_parameters.py +5 -1
- simtools/applications/derive_pulse_shape_parameters.py +194 -0
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +48 -235
- simtools/applications/generate_regular_arrays.py +5 -35
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +64 -108
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -4
- simtools/applications/simulate_illuminator.py +0 -1
- simtools/applications/simulate_pedestals.py +2 -6
- simtools/applications/simulate_prod.py +9 -28
- simtools/applications/simulate_prod_htcondor_generator.py +8 -1
- simtools/applications/submit_array_layouts.py +7 -7
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +0 -1
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +0 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +0 -13
- simtools/camera/camera_efficiency.py +1 -6
- simtools/camera/single_photon_electron_spectrum.py +2 -1
- simtools/configuration/commandline_parser.py +43 -8
- simtools/configuration/configurator.py +6 -11
- simtools/corsika/corsika_config.py +204 -99
- simtools/corsika/corsika_histograms.py +411 -1735
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +27 -17
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +80 -48
- simtools/db/db_handler.py +19 -8
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +83 -44
- simtools/io/ascii_handler.py +4 -2
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +4 -12
- simtools/layout/array_layout_utils.py +227 -58
- simtools/model/array_model.py +37 -18
- simtools/model/calibration_model.py +0 -4
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +24 -14
- simtools/model/model_repository.py +18 -5
- simtools/model/model_utils.py +1 -6
- simtools/model/site_model.py +0 -4
- simtools/model/telescope_model.py +6 -11
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +5 -11
- simtools/ray_tracing/mirror_panel_psf.py +3 -7
- simtools/ray_tracing/psf_analysis.py +29 -27
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
- simtools/ray_tracing/ray_tracing.py +6 -15
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +70 -16
- simtools/runners/corsika_runner.py +15 -10
- simtools/runners/corsika_simtel_runner.py +9 -8
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +11 -58
- simtools/runners/simtools_runner.py +2 -4
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/settings.py +154 -0
- simtools/sim_events/file_info.py +128 -0
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +273 -0
- simtools/simtel/simtel_config_writer.py +146 -22
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +62 -23
- simtools/simtel/simulator_camera_efficiency.py +4 -6
- simtools/simtel/simulator_light_emission.py +101 -19
- simtools/simtel/simulator_ray_tracing.py +4 -10
- simtools/simulator.py +360 -353
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +115 -8
- simtools/testing/configuration.py +2 -3
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +5 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +69 -23
- simtools/utils/general.py +37 -0
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +7 -9
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +21 -10
- simtools/visualization/plot_array_layout.py +312 -41
- simtools/visualization/plot_corsika_histograms.py +143 -605
- simtools/visualization/plot_mirrors.py +834 -0
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +0 -1
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Generate a reduced dataset from
|
|
1
|
+
"""Generate a reduced dataset from simulation files (CORSIKA/sim_telarray) using astropy tables."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from dataclasses import dataclass
|
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
import astropy.units as u
|
|
7
7
|
import numpy as np
|
|
8
8
|
from astropy.table import Table
|
|
9
|
-
from eventio import EventIOFile
|
|
9
|
+
from eventio import EventIOFile, iact
|
|
10
10
|
from eventio.simtel import (
|
|
11
11
|
ArrayEvent,
|
|
12
12
|
MCEvent,
|
|
@@ -17,7 +17,10 @@ from eventio.simtel import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from simtools.corsika.primary_particle import PrimaryParticle
|
|
20
|
-
from simtools.
|
|
20
|
+
from simtools.sim_events.file_info import (
|
|
21
|
+
get_combined_eventio_run_header,
|
|
22
|
+
get_corsika_run_and_event_headers,
|
|
23
|
+
)
|
|
21
24
|
from simtools.simtel.simtel_io_metadata import (
|
|
22
25
|
get_sim_telarray_telescope_id_to_telescope_name_mapping,
|
|
23
26
|
read_sim_telarray_metadata,
|
|
@@ -68,11 +71,11 @@ class TableSchemas:
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
|
|
71
|
-
class
|
|
74
|
+
class EventDataWriter:
|
|
72
75
|
"""
|
|
73
|
-
Process
|
|
76
|
+
Process simulation events (CORSIKA/sim_telarray) and write tables to file.
|
|
74
77
|
|
|
75
|
-
Extracts essential information from
|
|
78
|
+
Extracts essential information from simulation files, including:
|
|
76
79
|
|
|
77
80
|
- Shower parameters (energy, core location, direction)
|
|
78
81
|
- Trigger patterns
|
|
@@ -108,7 +111,7 @@ class SimtelIOEventDataWriter:
|
|
|
108
111
|
Returns
|
|
109
112
|
-------
|
|
110
113
|
list
|
|
111
|
-
List of
|
|
114
|
+
List of tables containing processed data.
|
|
112
115
|
"""
|
|
113
116
|
for i, file in enumerate(self.input_files[: self.max_files]):
|
|
114
117
|
self._logger.info(f"Processing file {i + 1}/{self.max_files}: {file}")
|
|
@@ -117,13 +120,15 @@ class SimtelIOEventDataWriter:
|
|
|
117
120
|
return self.create_tables()
|
|
118
121
|
|
|
119
122
|
def create_tables(self):
|
|
120
|
-
"""Create
|
|
123
|
+
"""Create tables from collected data."""
|
|
121
124
|
tables = []
|
|
122
125
|
for data, schema, name in [
|
|
123
126
|
(self.shower_data, TableSchemas.shower_schema, "SHOWERS"),
|
|
124
127
|
(self.trigger_data, TableSchemas.trigger_schema, "TRIGGERS"),
|
|
125
128
|
(self.file_info, TableSchemas.file_info_schema, "FILE_INFO"),
|
|
126
129
|
]:
|
|
130
|
+
if len(data) == 0:
|
|
131
|
+
continue
|
|
127
132
|
table = Table(rows=data, names=schema.keys())
|
|
128
133
|
table.meta["EXTNAME"] = name
|
|
129
134
|
self._add_units_to_table(table, schema)
|
|
@@ -149,40 +154,69 @@ class SimtelIOEventDataWriter:
|
|
|
149
154
|
self._process_mc_event(eventio_object)
|
|
150
155
|
elif isinstance(eventio_object, ArrayEvent):
|
|
151
156
|
self._process_array_event(eventio_object, file_id)
|
|
157
|
+
elif isinstance(eventio_object, iact.EventHeader):
|
|
158
|
+
self._process_mc_shower_from_iact(eventio_object, file_id)
|
|
152
159
|
|
|
153
160
|
def _process_mc_run_header(self, eventio_object):
|
|
154
|
-
"""Process MC run header
|
|
161
|
+
"""Process MC run header (sim_telarray file)."""
|
|
155
162
|
mc_head = eventio_object.parse()
|
|
156
163
|
self.n_use = mc_head["n_use"] # reuse factor n_use needed to extend the values below
|
|
157
164
|
self._logger.info(f"Shower reuse factor: {self.n_use} (viewcone: {mc_head['viewcone']})")
|
|
158
165
|
|
|
159
166
|
def _process_file_info(self, file_id, file):
|
|
160
167
|
"""Process file information and append to file info list."""
|
|
161
|
-
run_info =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
run_info = get_combined_eventio_run_header(file)
|
|
169
|
+
if run_info: # sim_telarray file
|
|
170
|
+
self.telescope_id_to_name = get_sim_telarray_telescope_id_to_telescope_name_mapping(
|
|
171
|
+
file
|
|
172
|
+
)
|
|
173
|
+
corsika7_id = PrimaryParticle(
|
|
174
|
+
particle_id_type="eventio_id",
|
|
175
|
+
particle_id=run_info.get("primary_id", 1),
|
|
176
|
+
).corsika7_id
|
|
177
|
+
nsb = self.get_nsb_level_from_sim_telarray_metadata(file)
|
|
178
|
+
|
|
179
|
+
e_min, e_max = run_info["E_range"]
|
|
180
|
+
view_cone_min, view_cone_max = run_info["viewcone"]
|
|
181
|
+
core_min, core_max = run_info["core_range"]
|
|
182
|
+
azimuth, el = np.degrees(run_info["direction"])
|
|
183
|
+
zenith = 90.0 - el
|
|
184
|
+
else: # CORSIKA IACT file
|
|
185
|
+
run_header, event_header = get_corsika_run_and_event_headers(file)
|
|
186
|
+
corsika7_id = int(event_header["particle_id"])
|
|
187
|
+
e_min = event_header["energy_min"]
|
|
188
|
+
e_max = event_header["energy_max"]
|
|
189
|
+
zenith = np.degrees(event_header["zenith"])
|
|
190
|
+
# Rotate to geographic north
|
|
191
|
+
azimuth = np.degrees(
|
|
192
|
+
event_header["azimuth"] - event_header["angle_array_x_magnetic_north"]
|
|
193
|
+
)
|
|
194
|
+
view_cone_min = event_header["viewcone_inner_angle"]
|
|
195
|
+
view_cone_max = event_header["viewcone_outer_angle"]
|
|
196
|
+
core_min = 0.0
|
|
197
|
+
core_max = run_header["x_scatter"] / 1.0e2 # cm to m
|
|
198
|
+
nsb = 0.0
|
|
199
|
+
|
|
166
200
|
self.file_info.append(
|
|
167
201
|
{
|
|
168
202
|
"file_name": str(file),
|
|
169
203
|
"file_id": file_id,
|
|
170
|
-
"particle_id":
|
|
171
|
-
"energy_min":
|
|
172
|
-
"energy_max":
|
|
173
|
-
"viewcone_min":
|
|
174
|
-
"viewcone_max":
|
|
175
|
-
"core_scatter_min":
|
|
176
|
-
"core_scatter_max":
|
|
177
|
-
"zenith":
|
|
178
|
-
"azimuth":
|
|
179
|
-
"nsb_level":
|
|
204
|
+
"particle_id": corsika7_id,
|
|
205
|
+
"energy_min": e_min,
|
|
206
|
+
"energy_max": e_max,
|
|
207
|
+
"viewcone_min": view_cone_min,
|
|
208
|
+
"viewcone_max": view_cone_max,
|
|
209
|
+
"core_scatter_min": core_min,
|
|
210
|
+
"core_scatter_max": core_max,
|
|
211
|
+
"zenith": zenith,
|
|
212
|
+
"azimuth": azimuth,
|
|
213
|
+
"nsb_level": nsb,
|
|
180
214
|
}
|
|
181
215
|
)
|
|
182
216
|
|
|
183
217
|
def _process_mc_shower(self, eventio_object, file_id):
|
|
184
218
|
"""
|
|
185
|
-
Process MC shower and update shower event list.
|
|
219
|
+
Process MC shower from sim_telarray file and update shower event list.
|
|
186
220
|
|
|
187
221
|
Duplicated entries 'self.n_use' times to match the number simulated events with
|
|
188
222
|
different core positions.
|
|
@@ -204,6 +238,31 @@ class SimtelIOEventDataWriter:
|
|
|
204
238
|
for _ in range(self.n_use)
|
|
205
239
|
)
|
|
206
240
|
|
|
241
|
+
def _process_mc_shower_from_iact(self, eventio_object, file_id):
|
|
242
|
+
"""
|
|
243
|
+
Process MC shower from IACT file and update shower event list.
|
|
244
|
+
|
|
245
|
+
Duplicated entries 'self.n_use' times to match the number simulated events with
|
|
246
|
+
different core positions.
|
|
247
|
+
"""
|
|
248
|
+
shower_header = eventio_object.parse()
|
|
249
|
+
self.n_use = int(shower_header["n_reuse"])
|
|
250
|
+
|
|
251
|
+
self.shower_data.extend(
|
|
252
|
+
{
|
|
253
|
+
"shower_id": shower_header["event_number"],
|
|
254
|
+
"event_id": shower_header["event_number"] * 100 + i,
|
|
255
|
+
"file_id": file_id,
|
|
256
|
+
"simulated_energy": shower_header["total_energy"],
|
|
257
|
+
"x_core": shower_header["reuse_x"][i] / 1.0e2,
|
|
258
|
+
"y_core": shower_header["reuse_y"][i] / 1.0e2,
|
|
259
|
+
"shower_azimuth": np.degrees(shower_header["azimuth"]),
|
|
260
|
+
"shower_altitude": 90.0 - np.degrees(shower_header["zenith"]),
|
|
261
|
+
"area_weight": 1.0,
|
|
262
|
+
}
|
|
263
|
+
for i in range(self.n_use)
|
|
264
|
+
)
|
|
265
|
+
|
|
207
266
|
def _process_mc_event(self, eventio_object):
|
|
208
267
|
"""
|
|
209
268
|
Process MC event and update shower event list.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Pulse shape computations for light emission simulations for flasher."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.optimize import least_squares
|
|
5
|
+
from scipy.signal import fftconvolve
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _rise_width(t, y, y_low=0.1, y_high=0.9):
|
|
9
|
+
"""Measure rise width between fractional amplitudes.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
t : array-like
|
|
14
|
+
Time samples in ns.
|
|
15
|
+
y : array-like
|
|
16
|
+
Pulse amplitude samples (normalized or arbitrary units).
|
|
17
|
+
y_low : float, optional
|
|
18
|
+
Lower fractional amplitude (0..1) on the rising edge. Default is 0.1.
|
|
19
|
+
y_high : float, optional
|
|
20
|
+
Upper fractional amplitude (0..1) on the rising edge. Default is 0.9.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
float
|
|
25
|
+
Width in ns between ``y_low`` and ``y_high`` on the rising edge.
|
|
26
|
+
"""
|
|
27
|
+
i_peak = int(np.argmax(y))
|
|
28
|
+
tr = t[: i_peak + 1]
|
|
29
|
+
yr = y[: i_peak + 1]
|
|
30
|
+
t_low = np.interp(y_low, yr, tr)
|
|
31
|
+
t_high = np.interp(y_high, yr, tr)
|
|
32
|
+
return t_high - t_low
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fall_width(t, y, y_high=0.9, y_low=0.1):
|
|
36
|
+
"""Measure fall width between fractional amplitudes.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
t : array-like
|
|
41
|
+
Time samples in ns.
|
|
42
|
+
y : array-like
|
|
43
|
+
Pulse amplitude samples (normalized or arbitrary units).
|
|
44
|
+
y_high : float, optional
|
|
45
|
+
Higher fractional amplitude (0..1) on the falling edge. Default is 0.9.
|
|
46
|
+
y_low : float, optional
|
|
47
|
+
Lower fractional amplitude (0..1) on the falling edge. Default is 0.1.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
float
|
|
52
|
+
Width in ns between ``y_high`` and ``y_low`` on the falling edge.
|
|
53
|
+
"""
|
|
54
|
+
i_peak = int(np.argmax(y))
|
|
55
|
+
tf = t[i_peak:]
|
|
56
|
+
yf = y[i_peak:]
|
|
57
|
+
t_rev = tf[::-1]
|
|
58
|
+
y_rev = yf[::-1]
|
|
59
|
+
t_hi = np.interp(y_high, y_rev, t_rev)
|
|
60
|
+
t_lo = np.interp(y_low, y_rev, t_rev)
|
|
61
|
+
return t_lo - t_hi
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _gaussian(t, sigma):
|
|
65
|
+
"""Gaussian pulse shape.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
t : array-like
|
|
70
|
+
Time samples in ns.
|
|
71
|
+
sigma : float
|
|
72
|
+
Gaussian standard deviation in ns.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
numpy.ndarray
|
|
77
|
+
Gaussian values at ``t`` (unitless), with a small safeguard for ``sigma``.
|
|
78
|
+
"""
|
|
79
|
+
return np.exp(-0.5 * (t / max(sigma, 1e-9)) ** 2)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _exp_decay(t, tau):
|
|
83
|
+
"""Causal exponential decay shape.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
t : array-like
|
|
88
|
+
Time samples in ns.
|
|
89
|
+
tau : float
|
|
90
|
+
Exponential decay constant in ns.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
numpy.ndarray
|
|
95
|
+
Exponential values at ``t`` (unitless), zero for ``t < 0``.
|
|
96
|
+
"""
|
|
97
|
+
tau = max(float(tau), 1e-9)
|
|
98
|
+
t_arr = np.asarray(t, dtype=float)
|
|
99
|
+
expo = -t_arr / tau
|
|
100
|
+
expo = np.minimum(expo, 0.0)
|
|
101
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
102
|
+
e = np.exp(expo)
|
|
103
|
+
return np.where(t_arr >= 0, e, 0.0)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def generate_gauss_expconv_pulse(
|
|
107
|
+
sigma_ns,
|
|
108
|
+
tau_ns,
|
|
109
|
+
dt_ns=0.1,
|
|
110
|
+
t_start_ns=-10,
|
|
111
|
+
t_stop_ns=25,
|
|
112
|
+
center_on_peak=False,
|
|
113
|
+
):
|
|
114
|
+
"""Generate a Gaussian convolved with a causal exponential.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
sigma_ns : float
|
|
119
|
+
Gaussian standard deviation (ns).
|
|
120
|
+
tau_ns : float
|
|
121
|
+
Exponential decay constant (ns).
|
|
122
|
+
dt_ns : float, optional
|
|
123
|
+
Time sampling step (ns). Default is 0.1 ns.
|
|
124
|
+
t_start_ns : float
|
|
125
|
+
Together with ``t_stop_ns``, defines the explicit start of the time grid
|
|
126
|
+
for pulse generation (ns).
|
|
127
|
+
t_stop_ns : float
|
|
128
|
+
Together with ``t_start_ns``, defines the explicit end of the time grid
|
|
129
|
+
for pulse generation (ns).
|
|
130
|
+
center_on_peak : bool, optional
|
|
131
|
+
If True, shift the returned time array so the pulse maximum is at t=0.
|
|
132
|
+
Default is False.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
tuple[numpy.ndarray, numpy.ndarray]
|
|
137
|
+
Tuple ``(t, y)`` with time samples in ns and normalized pulse amplitude (peak 1).
|
|
138
|
+
"""
|
|
139
|
+
left = float(t_start_ns)
|
|
140
|
+
right = float(t_stop_ns)
|
|
141
|
+
t = np.arange(left, right, dt_ns, dtype=float)
|
|
142
|
+
g = _gaussian(t, sigma_ns)
|
|
143
|
+
e = _exp_decay(t, tau_ns)
|
|
144
|
+
y = fftconvolve(g, e, mode="same")
|
|
145
|
+
if y.max() > 0:
|
|
146
|
+
y = y / y.max()
|
|
147
|
+
if center_on_peak:
|
|
148
|
+
i_max = int(np.argmax(y))
|
|
149
|
+
t = t - float(t[i_max])
|
|
150
|
+
return t, y
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def solve_sigma_tau_from_rise_fall(
|
|
154
|
+
rise_width_ns,
|
|
155
|
+
fall_width_ns,
|
|
156
|
+
dt_ns=0.1,
|
|
157
|
+
rise_range=(0.1, 0.9),
|
|
158
|
+
fall_range=(0.9, 0.1),
|
|
159
|
+
t_start_ns=-10,
|
|
160
|
+
t_stop_ns=25,
|
|
161
|
+
):
|
|
162
|
+
"""Solve (sigma, tau) so convolved pulse matches target rise/fall widths.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
rise_width_ns : float
|
|
167
|
+
Desired width on the rising edge in ns between rise_range=(low, high) fractions.
|
|
168
|
+
fall_width_ns : float
|
|
169
|
+
Desired width on the falling edge in ns between fall_range=(high, low) fractions.
|
|
170
|
+
dt_ns : float
|
|
171
|
+
Time step for internal pulse sampling in ns.
|
|
172
|
+
rise_range : tuple[float, float]
|
|
173
|
+
Fractional amplitudes (low, high) for the rising width, defaults to (0.1, 0.9).
|
|
174
|
+
fall_range : tuple[float, float]
|
|
175
|
+
Fractional amplitudes (high, low) for the falling width, defaults to (0.9, 0.1).
|
|
176
|
+
t_start_ns : float
|
|
177
|
+
Optional start time (ns) for the internal sampling window. If provided together with
|
|
178
|
+
``t_stop_ns``, overrides the default window.
|
|
179
|
+
t_stop_ns : float
|
|
180
|
+
Optional stop time (ns) for the internal sampling window. If provided together with
|
|
181
|
+
``t_start_ns``, overrides the default window.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
tuple[float, float]
|
|
186
|
+
Tuple ``(sigma_ns, tau_ns)`` giving the Gaussian sigma and exponential tau in ns.
|
|
187
|
+
"""
|
|
188
|
+
t = np.arange(float(t_start_ns), float(t_stop_ns) + dt_ns, dt_ns, dtype=float)
|
|
189
|
+
|
|
190
|
+
def pulse(sigma, tau):
|
|
191
|
+
g = _gaussian(t, sigma)
|
|
192
|
+
e = _exp_decay(t, tau)
|
|
193
|
+
y = fftconvolve(g, e, mode="same")
|
|
194
|
+
return y / y.max() if y.max() > 0 else y
|
|
195
|
+
|
|
196
|
+
rise_lo, rise_hi = rise_range
|
|
197
|
+
fall_hi, fall_lo = fall_range
|
|
198
|
+
|
|
199
|
+
def residuals(x):
|
|
200
|
+
sigma, tau = x
|
|
201
|
+
y = pulse(sigma, tau)
|
|
202
|
+
r = _rise_width(t, y, y_low=rise_lo, y_high=rise_hi) - rise_width_ns
|
|
203
|
+
f = _fall_width(t, y, y_high=fall_hi, y_low=fall_lo) - fall_width_ns
|
|
204
|
+
return [r, f]
|
|
205
|
+
|
|
206
|
+
res = least_squares(residuals, x0=[0.3, 10.0], bounds=(1e-6, 500))
|
|
207
|
+
sigma, tau = float(res.x[0]), float(res.x[1])
|
|
208
|
+
return sigma, tau
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def generate_pulse_from_rise_fall_times(
|
|
212
|
+
rise_width_ns,
|
|
213
|
+
fall_width_ns,
|
|
214
|
+
dt_ns=0.1,
|
|
215
|
+
rise_range=(0.1, 0.9),
|
|
216
|
+
fall_range=(0.9, 0.1),
|
|
217
|
+
t_start_ns=-10,
|
|
218
|
+
t_stop_ns=25,
|
|
219
|
+
center_on_peak=False,
|
|
220
|
+
):
|
|
221
|
+
"""Generate pulse from rise/fall time specifications.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
rise_width_ns : float
|
|
226
|
+
Target rise time (ns) between the fractional levels defined by ``rise_range``.
|
|
227
|
+
Defaults correspond to 10-90% rise time.
|
|
228
|
+
fall_width_ns : float
|
|
229
|
+
Target fall time (ns) between the fractional levels defined by ``fall_range``.
|
|
230
|
+
Defaults correspond to 90-10% fall time.
|
|
231
|
+
dt_ns : float, optional
|
|
232
|
+
Time sampling step (ns). Default is 0.1 ns.
|
|
233
|
+
rise_range : tuple[float, float], optional
|
|
234
|
+
Fractional amplitudes (low, high) for rise-time definition. Default (0.1, 0.9).
|
|
235
|
+
fall_range : tuple[float, float], optional
|
|
236
|
+
Fractional amplitudes (high, low) for fall-time definition. Default (0.9, 0.1).
|
|
237
|
+
t_start_ns : float, optional
|
|
238
|
+
Start time (ns) for the internal solver sampling window and output grid. Default -10.
|
|
239
|
+
t_stop_ns : float, optional
|
|
240
|
+
Stop time (ns) for the internal solver sampling window and output grid. Default 25.
|
|
241
|
+
center_on_peak : bool, optional
|
|
242
|
+
If True, shift the returned time array so the pulse maximum is at t=0.
|
|
243
|
+
Default is False.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
tuple[numpy.ndarray, numpy.ndarray]
|
|
248
|
+
Tuple ``(t, y)`` with time samples in ns and normalized pulse amplitude (peak 1).
|
|
249
|
+
|
|
250
|
+
Notes
|
|
251
|
+
-----
|
|
252
|
+
The model is a Gaussian convolved with an exponential. The parameters (sigma, tau)
|
|
253
|
+
are solved via least-squares such that the resulting pulse matches the requested rise and
|
|
254
|
+
fall times measured on monotonic segments relative to the peak.
|
|
255
|
+
"""
|
|
256
|
+
sigma, tau = solve_sigma_tau_from_rise_fall(
|
|
257
|
+
rise_width_ns,
|
|
258
|
+
fall_width_ns,
|
|
259
|
+
dt_ns=dt_ns,
|
|
260
|
+
rise_range=rise_range,
|
|
261
|
+
fall_range=fall_range,
|
|
262
|
+
t_start_ns=t_start_ns,
|
|
263
|
+
t_stop_ns=t_stop_ns,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
return generate_gauss_expconv_pulse(
|
|
267
|
+
sigma,
|
|
268
|
+
tau,
|
|
269
|
+
dt_ns=dt_ns,
|
|
270
|
+
t_start_ns=t_start_ns,
|
|
271
|
+
t_stop_ns=t_stop_ns,
|
|
272
|
+
center_on_peak=center_on_peak,
|
|
273
|
+
)
|