IAP GITLAB

Skip to content
Snippets Groups Projects
Commit ffd830b4 authored by Alan Coleman's avatar Alan Coleman
Browse files

Resolve "CI fails python build step"

parent 27bd5bb2
No related branches found
No related tags found
1 merge request!584Resolve "CI fails python build step"
Showing
with 264 additions and 220 deletions
...@@ -65,6 +65,25 @@ check-clang-format: ...@@ -65,6 +65,25 @@ check-clang-format:
- if: $CI_COMMIT_BRANCH - if: $CI_COMMIT_BRANCH
allow_failure: true allow_failure: true
check-python-quality:
image: corsika/devel:u-22.04
stage: quality
tags:
- corsika
script:
- cd ${CI_PROJECT_DIR}/python # change into the Python directory
- pip3 install --user -e '.[test]' # install the package + test deps
- python3 --version
- python3 -m black -t py37 --check corsika tests
- python3 -m flake8 -v corsika tests
- python3 -m isort --atomic --check-only corsika tests
- python3 -m mypy corsika
rules:
- if: $CI_MERGE_REQUEST_ID
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH
allow_failure: true
### CodeQuality tool #### ### CodeQuality tool ####
#include: #include:
# - template: Code-Quality.gitlab-ci.yml # - template: Code-Quality.gitlab-ci.yml
...@@ -76,7 +95,8 @@ check-clang-format: ...@@ -76,7 +95,8 @@ check-clang-format:
# when: never # when: never
# - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # Run code quality job in merge request pipelines # - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # Run code quality job in merge request pipelines
# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # Run code quality job in pipelines on the master branch (but not in other branch pipelines) # - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # Run code quality job in pipelines on the master branch (but not in other branch pipelines)
# - if: '$CI_COMMIT_TAG' # Run code quality job in pipelines for tags # - if: '$CI_COMMIT_TAG'
####### CONFIG ############## ####### CONFIG ##############
...@@ -163,13 +183,11 @@ build_test-clang-14: ...@@ -163,13 +183,11 @@ build_test-clang-14:
artifacts: false artifacts: false
####### BUILD-TEST-EXAMPLE (only non-Draft/non-WIP) ############## ####### BUILD-TEST-EXAMPLE (only non-Draft/non-WIP) ##############
########################################################## ##########################################################
# generic example template job # generic example template job
# normal pipeline for each commit # normal pipeline for each commit
# artefacts are needed as input for python jobs
.build_test_example: .build_test_example:
stage: build_test_example stage: build_test_example
tags: tags:
...@@ -204,8 +222,10 @@ build_test-clang-14: ...@@ -204,8 +222,10 @@ build_test-clang-14:
junit: junit:
- ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
paths: paths:
- ${CI_PROJECT_DIR}/build/build_examples/example_outputs - ${CI_PROJECT_DIR}/build/build_examples/example_outputs #python tests need this
- ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml - ${CI_PROJECT_DIR}/build/test_outputs/junit*.xml
- ${CI_PROJECT_DIR}/build/CMakeCache.txt #python tests need this
- ${CI_PROJECT_DIR}/build/bin #python tests need this
# build_test_example for gcc # build_test_example for gcc
build_test_example-u-22_04: build_test_example-u-22_04:
...@@ -225,9 +245,6 @@ build_test_example-clang-14: ...@@ -225,9 +245,6 @@ build_test_example-clang-14:
artifacts: false artifacts: false
####### OPTIONAL ############## ####### OPTIONAL ##############
########################################################## ##########################################################
...@@ -288,7 +305,7 @@ release-full-clang-14: ...@@ -288,7 +305,7 @@ release-full-clang-14:
artifacts: false artifacts: false
###### COVERAGE ##########
########################################################## ##########################################################
# the coverage generation should either run when manually requested, OR always on the master # the coverage generation should either run when manually requested, OR always on the master
...@@ -330,7 +347,7 @@ coverage: ...@@ -330,7 +347,7 @@ coverage:
###### SANITY ##########
########################################################## ##########################################################
sanity: sanity:
...@@ -378,19 +395,17 @@ sanity: ...@@ -378,19 +395,17 @@ sanity:
- python3 -m black -t py37 corsika tests - python3 -m black -t py37 corsika tests
- python3 -m flake8 corsika tests - python3 -m flake8 corsika tests
- python3 -m pytest --cov=corsika tests - python3 -m pytest --cov=corsika tests
- cd ${CI_PROJECT_DIR} # reset the directory
coverage: '/^TOTAL\s*\d+\s*\d+\s*(.*\%)/' coverage: '/^TOTAL\s*\d+\s*\d+\s*(.*\%)/'
# the default Python version Ubuntu 22.04 is Python3.8 python-tests:
python-3.8:
extends: .python extends: .python
image: corsika/analysis:python-3.9.5 image: corsika/devel:u-22.04
needs: needs:
- job: build_test_example-u-22_04 - job: build_test_example-u-22_04
artifacts: true artifacts: true
artifacts: artifacts:
when: always when: always
expire_in: 3 month expire_in: 1 month
paths: paths:
- ${CI_PROJECT_DIR}/Python/python-test.log - ${CI_PROJECT_DIR}/python/python-test.log
allow_failure: true allow_failure: false
Subproject commit 331c0aad7c6c17fe456c4a80f8b2575ef341ce90 Subproject commit aa320e6a1203436766a374878e83e3685eae78e4
...@@ -8,8 +8,7 @@ ...@@ -8,8 +8,7 @@
the license. the license.
""" """
from .hist import read_hist
from .library import Library from .library import Library
# all exported objects # all exported objects
__all__ = ["read_hist", "Library"] __all__ = ["Library"]
"""
The 'io' module provides for reading CORSIKA8 output files.
(c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3). See file LICENSE for a full version of
the license.
"""
"""
Converter for the pyarrow data types to numpy ones
(c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3). See file LICENSE for a full version of
the license.
"""
import numpy as np
import pyarrow
def convert_to_numpy(pyarrow_table: pyarrow.lib.Table) -> np.ndarray:
"""
Converts a pyarrow Table to a numpy structured array
Parameters
----------
pyarrow_table: pyarrow.lib.Table
PyArrow table of any dimension to be sliced
Returns
-------
np.ndarray:
converted table with the same column labels and data types
"""
# Type conversions for pyarrow data types to numpy ones
# https://arrow.apache.org/docs/python/data.html
# https://numpy.org/doc/stable/reference/arrays.dtypes.html#arrays-dtypes-constructing
type_conversions = {
pyarrow.int8(): "int8",
pyarrow.int16(): "int16",
pyarrow.int32(): "int32",
pyarrow.int64(): "int64",
pyarrow.uint8(): "uint8",
pyarrow.uint16(): "uint16",
pyarrow.uint32(): "uint32",
pyarrow.uint64(): "uint64",
pyarrow.float16(): "float16",
pyarrow.float32(): "float32",
pyarrow.float64(): "float64",
}
# Perform type conversion of all fields
column_types = [
type_conversions[pyarrow_table[key].type] for key in pyarrow_table.column_names
]
dtypes = [(x, y) for (x, y) in zip(pyarrow_table.column_names, column_types)]
# Make an empty array and then fill the values
np_table = np.zeros(pyarrow_table.num_rows, dtype=dtypes)
for key in pyarrow_table.column_names:
np_table[key] = pyarrow_table[key]
return np_table
"""
This file supports reading boost_histograms from CORSIKA8.
(c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3). See file LICENSE for a full version of
the license.
"""
import boost_histogram as bh
import numpy as np
def read_hist(filename: str) -> bh.Histogram:
"""
Read a histogram produced with CORSIKA8's `SaveBoostHistogram()` function.
Parameters
----------
filename: str
The filename of the .npy file containing the histogram.
Returns
-------
hist: bh.Histogram
An initialized bh.Histogram instance.
Throws
------
ValueError:
If the histogram type is not supported.
"""
# load the filenames
d = np.load(filename)
# extract the axis and overflows information
axistypes = d["axistypes"].view("c")
overflow = d["overflow"]
underflow = d["underflow"]
# this is where we store the axes that we extract from the file.
axes = []
# we now loop over the axes
for i, (at, has_overflow, has_underflow) in enumerate(
zip(axistypes, overflow, underflow)
):
# continuous axis
if at == b"c":
axes.append(
bh.axis.Variable(
d[f"binedges_{i}"], overflow=has_overflow, underflow=has_underflow
)
)
# discrete axis
elif at == b"d":
axes.append(bh.axis.IntCategory(d[f"bins_{i}"], growth=(not has_overflow)))
# and unknown histogram type
else:
raise ValueError(f"'{at}' is not a valid C8 histogram axistype.")
# create the histogram and fill it in
data = d["data"]
if data.dtype.names: # structured array -> mean and variance
h = bh.Histogram(*axes, storage=bh.storage.Weight())
h.view(flow=True)[:]['value'] = data['mean']
h.view(flow=True)[:]['variance'] = data['variance']
else:
h = bh.Histogram(*axes)
h.view(flow=True)[:] = data
return h
...@@ -58,13 +58,6 @@ class Library(object): ...@@ -58,13 +58,6 @@ class Library(object):
""" """
return list(self.__outputs.keys()) return list(self.__outputs.keys())
@property
def modules(self) -> Dict[str, str]:
"""
Return the list of registered outputs.
"""
pass
def get(self, name: str) -> Optional[outputs.Output]: def get(self, name: str) -> Optional[outputs.Output]:
""" """
Return the output with a given name. Return the output with a given name.
...@@ -73,7 +66,7 @@ class Library(object): ...@@ -73,7 +66,7 @@ class Library(object):
return self.__outputs[name] return self.__outputs[name]
else: else:
msg = f"Output with name '{name}' not available in this library." msg = f"Output with name '{name}' not available in this library."
logging.getLogger("corsika").warn(msg) logging.getLogger("corsika").warning(msg)
return None return None
@staticmethod @staticmethod
...@@ -181,7 +174,6 @@ class Library(object): ...@@ -181,7 +174,6 @@ class Library(object):
# loop over the subdirectories # loop over the subdirectories
for subdir in dirs: for subdir in dirs:
# read the config file for this output # read the config file for this output
config = Library.load_config(op.join(path, subdir)) config = Library.load_config(op.join(path, subdir))
...@@ -197,13 +189,12 @@ class Library(object): ...@@ -197,13 +189,12 @@ class Library(object):
f"'{subdir}' does not contain a valid config." f"'{subdir}' does not contain a valid config."
"Missing 'type' or 'name' keyword." "Missing 'type' or 'name' keyword."
) )
logging.getLogger("corsika").warn(msg) logging.getLogger("corsika").warning(msg)
continue # skip to the next output, don't error continue # skip to the next output, don't error
# we now have a valid component type, get the corresponding # we now have a valid component type, get the corresponding
# type from the proccesses subdirectory # type from the proccesses subdirectory
try: try:
# instantiate the output and store it in our dict # instantiate the output and store it in our dict
component = getattr(outputs, out_type)(op.join(path, subdir)) component = getattr(outputs, out_type)(op.join(path, subdir))
...@@ -213,7 +204,7 @@ class Library(object): ...@@ -213,7 +204,7 @@ class Library(object):
f"'{name}' encountered an error while reading. " f"'{name}' encountered an error while reading. "
"This process will be not be loaded." "This process will be not be loaded."
) )
logging.getLogger("corsika").warn(msg) logging.getLogger("corsika").warning(msg)
else: else:
components[name] = component components[name] = component
...@@ -222,8 +213,8 @@ class Library(object): ...@@ -222,8 +213,8 @@ class Library(object):
f"Unable to instantiate an instance of '{out_type}' " f"Unable to instantiate an instance of '{out_type}' "
f"for a process called '{name}'" f"for a process called '{name}'"
) )
logging.getLogger("corsika").warn(msg) logging.getLogger("corsika").warning(msg)
logging.getLogger("corsika").warn(e) logging.getLogger("corsika").warning(e)
continue # skip to the next output, don't error continue # skip to the next output, don't error
# and we are done building - return the constructed outputs # and we are done building - return the constructed outputs
......
...@@ -7,16 +7,15 @@ ...@@ -7,16 +7,15 @@
the license. the license.
""" """
from .observation_plane import ObservationPlane
from .track_writer import TrackWriter
from .longitudinal_profile import LongitudinalProfile
from .bethe_bloch import BetheBlochPDG from .bethe_bloch import BetheBlochPDG
from .particle_cut import ParticleCut
from .energy_loss import EnergyLoss from .energy_loss import EnergyLoss
from .longitudinal_profile import LongitudinalProfile
from .observation_plane import ObservationPlane
from .output import Output from .output import Output
from .particle_cut import ParticleCut
from .primary import Particle, PrimaryParticle
from .radio_process import RadioProcess from .radio_process import RadioProcess
from .track_writer import TrackWriter from .track_writer import TrackWriter
from .primary import PrimaryParticle, Particle
__all__ = [ __all__ = [
"Output", "Output",
...@@ -25,7 +24,8 @@ __all__ = [ ...@@ -25,7 +24,8 @@ __all__ = [
"LongitudinalProfile", "LongitudinalProfile",
"BetheBlochPDG", "BetheBlochPDG",
"ParticleCut", "ParticleCut",
"EnergyLoss" "RadioProcess", "EnergyLoss",
"RadioProcess",
"PrimaryParticle", "PrimaryParticle",
"Particle", "Particle",
] ]
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class BetheBlochPDG(Output): ...@@ -72,11 +73,13 @@ class BetheBlochPDG(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for BetheBlochPDG. " f"Unknown format '{dtype}' for BetheBlochPDG. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
......
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class EnergyLoss(Output): ...@@ -72,11 +73,13 @@ class EnergyLoss(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for EnergyLoss. " f"Unknown format '{dtype}' for EnergyLoss. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
...@@ -84,4 +87,4 @@ class EnergyLoss(Output): ...@@ -84,4 +87,4 @@ class EnergyLoss(Output):
""" """
Return a string representation of this class. Return a string representation of this class.
""" """
return f"EnergyLess('{self.config['name']}')" return f"EnergyLoss('{self.config['name']}')"
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class LongitudinalProfile(Output): ...@@ -72,11 +73,13 @@ class LongitudinalProfile(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for LongitudinalProfile. " f"Unknown format '{dtype}' for LongitudinalProfile. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
......
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class ObservationPlane(Output): ...@@ -72,11 +73,13 @@ class ObservationPlane(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for ObservationPlane. " f"Unknown format '{dtype}' for ObservationPlane. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
......
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class ParticleCut(Output): ...@@ -72,11 +73,13 @@ class ParticleCut(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for ParticleCut. " f"Unknown format '{dtype}' for ParticleCut. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
......
...@@ -10,8 +10,9 @@ ...@@ -10,8 +10,9 @@
import logging import logging
import os.path as op import os.path as op
from typing import Any from typing import Any
import pyarrow.parquet as pq
import pandas as pd import pandas as pd
import pyarrow.parquet as pq
from .output import Output from .output import Output
...@@ -47,8 +48,7 @@ class RadioProcess(Output): ...@@ -47,8 +48,7 @@ class RadioProcess(Output):
f"An error occured loading a RadioProcess: {e}" f"An error occured loading a RadioProcess: {e}"
) )
def load_data(self, path: str) -> dict:
def load_data(self, path: str):
""" """
Load the data associated with this radio process. Load the data associated with this radio process.
...@@ -59,7 +59,7 @@ class RadioProcess(Output): ...@@ -59,7 +59,7 @@ class RadioProcess(Output):
""" """
data = pq.read_table(op.join(path, "antennas.parquet")) data = pq.read_table(op.join(path, "antennas.parquet"))
nshowers = data.to_pandas()['shower'].iloc[-1] + 1 nshowers = data.to_pandas()["shower"].iloc[-1] + 1
antennas = list(self.config["antennas"].keys()) antennas = list(self.config["antennas"].keys())
# check that we got some events # check that we got some events
...@@ -78,13 +78,17 @@ class RadioProcess(Output): ...@@ -78,13 +78,17 @@ class RadioProcess(Output):
# loop over each of the antennas # loop over each of the antennas
for name in antennas: for name in antennas:
sampling_period = self.config["antennas"][name]["number of bins"] sampling_period = self.config["antennas"][name]["number of bins"]
antenna_data = data[ant_nr*sampling_period:(ant_nr+1)*sampling_period].to_pandas() start = ant_nr * sampling_period
stop = (ant_nr + 1) * sampling_period
antenna_data = data[start:stop].to_pandas()
times = antenna_data["Time"] times = antenna_data["Time"]
Ex = antenna_data["Ex"] Ex = antenna_data["Ex"]
Ey = antenna_data["Ey"] Ey = antenna_data["Ey"]
Ez = antenna_data["Ez"] Ez = antenna_data["Ez"]
dictionary[name] = pd.DataFrame({'time': times, 'Ex': Ex, 'Ey': Ey, 'Ez': Ez}) dictionary[name] = pd.DataFrame(
ant_nr = ant_nr+ 1 {"time": times, "Ex": Ex, "Ey": Ey, "Ez": Ez}
)
ant_nr = ant_nr + 1
dataset[str(i)] = dictionary dataset[str(i)] = dictionary
...@@ -126,14 +130,14 @@ class RadioProcess(Output): ...@@ -126,14 +130,14 @@ class RadioProcess(Output):
) )
) )
def get_antenna_list(self): def get_antenna_list(self) -> list:
""" """
Return a list with the names of the antennas of this RadioProcess. Return a list with the names of the antennas of this RadioProcess.
""" """
antennas = list(self.config["antennas"].keys()) antennas = list(self.config["antennas"].keys())
return antennas return antennas
def get_antennas(self): def get_antennas(self) -> dict:
""" """
Return a pandas dataframe with all the information for the antennas Return a pandas dataframe with all the information for the antennas
of this RadioProcess. This information is shower number, antenna of this RadioProcess. This information is shower number, antenna
...@@ -150,13 +154,22 @@ class RadioProcess(Output): ...@@ -150,13 +154,22 @@ class RadioProcess(Output):
nr_bins = self.config["antennas"][name]["number of bins"] nr_bins = self.config["antennas"][name]["number of bins"]
sampling_frequency = self.config["antennas"][name]["sampling frequency"] sampling_frequency = self.config["antennas"][name]["sampling frequency"]
location = self.config["antennas"][name]["location"] location = self.config["antennas"][name]["location"]
dictionary[name] = pd.DataFrame({'type': type, 'start time': start_time, 'duration': duration, dictionary[name] = pd.DataFrame(
'number of bins': nr_bins, 'sampling frequency': sampling_frequency, {
'x': [location[0]], 'y': [location[1]], 'z': [location[2]]}) "type": type,
"start time": start_time,
"duration": duration,
"number of bins": nr_bins,
"sampling frequency": sampling_frequency,
"x": [location[0]],
"y": [location[1]],
"z": [location[2]],
}
)
return dictionary return dictionary
def get_units(self): def get_units(self) -> dict:
""" """
Return the units of the antennas of this RadioProcess. Return the units of the antennas of this RadioProcess.
""" """
......
...@@ -13,6 +13,7 @@ from typing import Any ...@@ -13,6 +13,7 @@ from typing import Any
import pyarrow.parquet as pq import pyarrow.parquet as pq
from ..converters import arrow_to_numpy
from .output import Output from .output import Output
...@@ -72,11 +73,13 @@ class TrackWriter(Output): ...@@ -72,11 +73,13 @@ class TrackWriter(Output):
return self.__data return self.__data
elif dtype == "pandas": elif dtype == "pandas":
return self.__data.to_pandas() return self.__data.to_pandas()
elif dtype == "numpy":
return arrow_to_numpy.convert_to_numpy(self.__data)
else: else:
raise ValueError( raise ValueError(
( (
f"Unknown format '{dtype}' for TrackWriter. " f"Unknown format '{dtype}' for TrackWriter. "
"We currently only support ['arrow', 'pandas']." "We currently only support ['arrow', 'pandas', 'numpy']."
) )
) )
......
...@@ -67,3 +67,7 @@ ignore_missing_imports = True ...@@ -67,3 +67,7 @@ ignore_missing_imports = True
# ignore missing types for pyarow # ignore missing types for pyarow
[mypy-pyarrow.*] [mypy-pyarrow.*]
ignore_missing_imports = True ignore_missing_imports = True
# ignore missing types for pandas
[mypy-pandas.*]
ignore_missing_imports = True
...@@ -32,7 +32,7 @@ setup( ...@@ -32,7 +32,7 @@ setup(
keywords=["cosmic ray", "physics", "air shower", "simulation"], keywords=["cosmic ray", "physics", "air shower", "simulation"],
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6, <4", python_requires=">=3.6, <4",
install_requires=["numpy", "pyyaml", "pyarrow", "boost_histogram", "xarray"], install_requires=["numpy", "pyyaml", "pyarrow", "pandas"],
extras_require={ extras_require={
"test": [ "test": [
"pytest", "pytest",
...@@ -42,8 +42,9 @@ setup( ...@@ -42,8 +42,9 @@ setup(
"coverage", "coverage",
"pytest-cov", "pytest-cov",
"flake8", "flake8",
"types-PyYAML",
"pandas-stubs",
], ],
"pandas": ["pandas"],
}, },
scripts=[], scripts=[],
project_urls={"code": "https://gitlab.iap.kit.edu/AirShowerPhysics/corsika"}, project_urls={"code": "https://gitlab.iap.kit.edu/AirShowerPhysics/corsika"},
......
...@@ -29,25 +29,34 @@ def find_build_directory() -> str: ...@@ -29,25 +29,34 @@ def find_build_directory() -> str:
If the build directory cannot be found or it is empty. If the build directory cannot be found or it is empty.
""" """
# check if we are running on Gitlab
gitlab_build_dir = os.getenv("CI_BUILDS_DIR")
# if we are running on Gitlab # if we are running on Gitlab
if gitlab_build_dir is not None: is_on_CI = os.getenv("CI_PROJECT_DIR") is not None
build_dir = op.abspath(gitlab_build_dir) if is_on_CI:
build_dir = op.abspath(op.join(os.getenv("CI_PROJECT_DIR"), "build"))
else: # otherwise, we are running locally else: # otherwise, we are running locally
build_dir = op.abspath( here = op.dirname(os.path.realpath(__file__))
op.join( for name in ["build", "corsika-build"]:
op.dirname(__file__), build_dir = op.realpath(
op.pardir, op.join(
op.pardir, here,
"build", op.pardir,
op.pardir,
op.pardir,
"corsika-build",
)
) )
) if op.isdir(build_dir):
break
# check that the build directory contains 'CMakeCache.txt' # check that the build directory contains 'CMakeCache.txt'
if not op.exists(op.join(build_dir, "CMakeCache.txt")): if not op.exists(op.join(build_dir, "CMakeCache.txt")):
raise RuntimeError("Python tests cannot find C8 build directory.") msg = "Python tests cannot find C8 build dir.\n"
msg += f"Is on CI: {is_on_CI}\n"
msg += f"Path for CI: {os.getenv('CI_PROJECT_DIR')}\n"
msg += f"Checked in {build_dir}\n"
msg += f"Contents: {os.listdir(build_dir)}"
raise RuntimeError(msg)
return build_dir return build_dir
......
"""
Tests for `corsika.io.hist`
(c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3). See file LICENSE for a full version of
the license.
"""
import os
import os.path as op
import subprocess
import corsika
from .. import build_directory
# the directory containing 'testSaveBoostHistogram'
bindir = op.join(build_directory, "bin")
def generate_hist() -> str:
"""
Generate a test with `testSaveBoostHistogram`.
Returns
-------
str
The path to the generated histogram.
bool
If True, this file was regenerated.
"""
# we construct the name of the bin
bin = op.join(bindir, "testFramework")
# check if a histogram already exists
if op.exists(op.join(bin, "hist.npz")):
return op.join(bin, "hist.npz"), False
else:
# run the program - this generates "hist.npz" in the CWD
subprocess.call([bin, "saveHistogram"])
return op.join(os.getcwd(), "hist.npz"), True
def test_corsika_io() -> None:
"""
Test I can corsika.io without a further import.
"""
corsika.io.read_hist
def test_corsika_read_hist() -> None:
"""
Check that I can read in the test histograms with `read_hist`.
"""
# generate a test histogram
filename, delete = generate_hist()
# and try and read in a histogram
h = corsika.io.read_hist(filename)
# and delete the generated histogram
if delete:
os.remove(filename)
# and check that it isn't empty
assert not h.empty()
"""
Tests for `corsika.io.outputs.energy_loss`
(c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
This software is distributed under the terms of the GNU General Public
Licence version 3 (GPL Version 3). See file LICENSE for a full version of
the license.
"""
import os
import os.path as op
import subprocess
import pytest
from corsika.io import Library
from .. import build_directory
bindir = op.join(build_directory, "bin")
def generate_data() -> str:
"""
Generate a test with `testOutput`.
Returns
-------
str
The path to the generated data.
"""
# Expected output directory that is made from the testOutput binary
output_dir = op.join(os.getcwd(), "out_test/check")
if not op.exists(output_dir): # only make if not already run
binary = op.join(bindir, "testOutput")
# ensure that the binary exists (not trivial on the CI)
if not op.exists(binary):
msg = f"Could not find testOutput binary at {binary}\n"
msg += f"Binary dir contains {os.listdir(bindir)}"
raise RuntimeError(msg)
subprocess.call([binary, "OutputManager"])
# Check if it still doesn't exist
if not op.exists(output_dir):
msg = "After running binary, could not find expected"
msg += f" output dir {output_dir}\n"
msg += "The binary did not execute successfully or the"
msg += " OutputManager tests have changed"
raise RuntimeError(msg)
return output_dir
def test_basic_Library() -> None:
dir_to_test = generate_data()
lib = Library(dir_to_test)
assert 0 == len(lib.names)
assert len(lib.summary)
assert len(lib.config)
# Check what happens for an unknown output subdir
assert lib.get("Does not exits") is None
def test_bad_Library() -> None:
with pytest.raises(ValueError):
Library("This does not exist")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment