Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes/2203.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add schema for CORSIKA limits tables.
2 changes: 0 additions & 2 deletions src/simtools/applications/production_derive_corsika_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@
+===========================+===========+========+==============================================+
| production_index | int64 | | Production index for multi-production runs. |
+---------------------------+-----------+--------+----------------------------------------------+
| event_data_file | string | | Input event-data pattern for this row. |
+---------------------------+-----------+--------+----------------------------------------------+
| primary_particle | string | | Particle type (e.g., gamma, proton). |
+---------------------------+-----------+--------+----------------------------------------------+
| array_name | string | | Array name (custom or as defined in |
Expand Down
66 changes: 34 additions & 32 deletions src/simtools/production_configuration/derive_corsika_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import numpy as np
from astropy.table import Column, Table

from simtools.constants import SCHEMA_PATH
from simtools.data_model.metadata_collector import MetadataCollector
from simtools.io import ascii_handler, io_handler
from simtools.job_execution.process_pool import process_pool_map_ordered
Expand All @@ -23,36 +24,38 @@

_logger = logging.getLogger(__name__)

FILE_INFO_KEYS = ("primary_particle", "zenith", "azimuth", "nsb_level")
BROAD_RANGE_FILE_INFO_KEYS = {
"br_energy_min": "energy_min",
"br_energy_max": "energy_max",
"br_core_scatter_max": "core_scatter_max",
"br_viewcone_max": "viewcone_max",
}
COLUMN_DESCRIPTIONS = {
"br_energy_min": "Energy min from broad-range simulations.",
"br_energy_max": "Energy max from broad-range simulations.",
"br_core_scatter_max": "Core scatter max from broad-range simulations.",
"br_viewcone_max": "Viewcone max from broad-range simulations.",
}
CORSIKA_LIMITS_TABLE_SCHEMA_FILE = SCHEMA_PATH / "corsika_limits_table.schema.yml"


def _load_output_table_configuration_from_schema(schema_file):
"""Load output table columns, descriptions, and file-info mappings from schema."""
schema_data = ascii_handler.collect_data_from_file(file_name=schema_file)
data_entries = schema_data.get("data", [])
if not data_entries:
raise KeyError(f"No 'data' entry found in schema {schema_file}")

table_columns = data_entries[0].get("table_columns", [])
if not table_columns:
raise KeyError(f"No 'table_columns' entry found in schema {schema_file}")

result_columns = [entry["name"] for entry in table_columns]
column_descriptions = {
entry["name"]: entry.get("description")
for entry in table_columns
if entry.get("description") is not None
}
file_info_columns = {
entry["name"]: entry["file_info_key"]
for entry in table_columns
if entry.get("file_info_key") is not None
}
return result_columns, column_descriptions, file_info_columns


RESULT_COLUMNS, COLUMN_DESCRIPTIONS, FILE_INFO_COLUMNS = (
_load_output_table_configuration_from_schema(CORSIKA_LIMITS_TABLE_SCHEMA_FILE)
)
LOSS_AXES = ("core_distance", "angular_distance")
RESULT_COLUMNS = [
"production_index",
"event_data_file",
"primary_particle",
"array_name",
"zenith",
"azimuth",
"nsb_level",
"lower_energy_limit",
"upper_radius_limit",
"viewcone_radius",
"br_energy_min",
"br_energy_max",
"br_core_scatter_max",
"br_viewcone_max",
]


def _normalize_event_data_file(event_data_file):
Expand Down Expand Up @@ -408,11 +411,10 @@ def _process_file(
differential_loss_bins_per_decade,
)
)
limits.update({key: histograms.file_info.get(key) for key in FILE_INFO_KEYS})
limits.update(
{
output_key: histograms.file_info.get(file_info_key)
for output_key, file_info_key in BROAD_RANGE_FILE_INFO_KEYS.items()
column_name: histograms.file_info.get(file_info_key)
for column_name, file_info_key in FILE_INFO_COLUMNS.items()
}
)

Expand Down
86 changes: 86 additions & 0 deletions src/simtools/schemas/corsika_limits_table.schema.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
%YAML 1.2
---
title: Schema for production_derive_corsika_limits output
schema_version: 0.1.0
meta_schema: simpipe-schema
meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
meta_schema_version: 0.1.0
name: production_derive_corsika_limits
description: |-
Derived CORSIKA limits for energy threshold, core distance, and viewcone radius.
data:
- type: data_table
table_columns:
- name: production_index
description: Production index for multi-production runs.
required: true
unit: dimensionless
type: int64
- name: primary_particle
description: Primary particle type.
required: true
unit: dimensionless
type: string
file_info_key: primary_particle
- name: array_name
description: Array layout or array identifier.
required: true
unit: dimensionless
type: string
- name: zenith
description: Direction of array pointing zenith.
required: true
unit: deg
type: float64
file_info_key: zenith
- name: azimuth
description: Direction of array pointing azimuth.
required: true
unit: deg
type: float64
file_info_key: azimuth
- name: nsb_level
description: Night sky background level.
required: true
unit: dimensionless
type: float64
file_info_key: nsb_level
- name: lower_energy_limit
description: Derived lower energy limit.
required: true
unit: TeV
type: float64
- name: upper_radius_limit
description: Derived upper core distance limit.
required: true
unit: m
type: float64
- name: viewcone_radius
description: Derived viewcone radius limit.
required: true
unit: deg
type: float64
- name: br_energy_min
description: Broad-range simulation minimum energy.
required: false
unit: TeV
type: float64
file_info_key: energy_min
- name: br_energy_max
description: Broad-range simulation maximum energy.
required: false
unit: TeV
type: float64
file_info_key: energy_max
- name: br_core_scatter_max
description: Broad-range simulation core scatter maximum.
required: false
unit: m
type: float64
file_info_key: core_scatter_max
- name: br_viewcone_max
description: Broad-range simulation viewcone maximum.
required: false
unit: deg
type: float64
file_info_key: viewcone_max
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ definitions:
type: array
items:
$ref: '#/definitions/TableColumn'
file_info_key:
type: string
description: "key in event-file metadata used to populate this table column"
required:
- description
- name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ def test_create_results_table(mock_results):
assert table["br_energy_max"].unit == u.TeV
assert table["br_core_scatter_max"].unit == u.m
assert table["br_viewcone_max"].unit == u.deg
assert table["br_viewcone_max"].description == "Viewcone max from broad-range simulations."
assert (
table["br_viewcone_max"].description
== derive_corsika_limits.COLUMN_DESCRIPTIONS["br_viewcone_max"]
)
assert table.meta["loss_fraction_core_distance"] == pytest.approx(0.2)
assert table.meta["loss_min_events_core_distance"] == 10
assert table.meta["loss_fraction_angular_distance"] == pytest.approx(0.2)
Expand All @@ -119,6 +122,20 @@ def test_create_results_table(mock_results):
assert "description" in table.meta


def test_file_info_columns_are_read_from_schema():
"""Test file-info column mappings are read from the CORSIKA limits schema."""
assert derive_corsika_limits.FILE_INFO_COLUMNS == {
"primary_particle": "primary_particle",
"zenith": "zenith",
"azimuth": "azimuth",
"nsb_level": "nsb_level",
"br_energy_min": "energy_min",
"br_energy_max": "energy_max",
"br_core_scatter_max": "core_scatter_max",
"br_viewcone_max": "viewcone_max",
}


def test_round_value():
"""Test _round_value function for different key types."""

Expand Down Expand Up @@ -1080,23 +1097,21 @@ def test_create_results_table_with_production_columns(mock_results):

# Should include production-origin columns
assert "production_index" in table.colnames
assert "event_data_file" in table.colnames
assert "event_data_file" not in table.colnames

# Check values
assert table["production_index"][0] == 0
assert table["event_data_file"][0] == "pattern_0_*.hdf5"


def test_create_results_table_without_production_columns(mock_results):
"""Test _create_results_table with missing production metadata values."""
# Results without production metadata (old format)
table = derive_corsika_limits._create_results_table(mock_results, DEFAULT_ALLOWED_LOSSES, 0.1)

# Production-origin columns are included and filled with None if missing
# Production-origin column is included and filled with None if missing
assert "production_index" in table.colnames
assert "event_data_file" in table.colnames
assert "event_data_file" not in table.colnames
assert table["production_index"][0] is None
assert table["event_data_file"][0] is None

# Standard columns should be present
assert "primary_particle" in table.colnames
Expand Down
Loading