diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 2da518cf5b6b9a8e0e3a278bf166fd246137554d..05f2e296d40db3554332511098a13b25751bc74b 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -6,7 +6,6 @@ discussions. General and infrastructure: - Ralf Ulrich <ralf.ulrich@kit.edu>, KIT - Maximilian Reininghaus <maximilian.reininghaus@kit.edu>, KIT -- Hans Dembinski <hdembins@mpi-hd.mpg.de>, Dortmund - Antonio Augusto Alves Junior <antonio.junior@kit.edu>, KIT High performance, GPU: diff --git a/SCIENTIFIC_AUTHORS b/SCIENTIFIC_AUTHORS deleted file mode 100644 index a00fe03acc791f21daa27da56ca89925febc7a9c..0000000000000000000000000000000000000000 --- a/SCIENTIFIC_AUTHORS +++ /dev/null @@ -1,28 +0,0 @@ -This is currently derived from -https://gitlab.ikp.kit.edu/AirShowerPhysics/corsika/wikis/ICRC2019 -and will be revised whenever needed: - -- Luisa Arrabito (1) -- Dominik Baack (2) -- Johan Bregeon (1) -- Hans Dembinski (3) -- Ralph Engel (4) -- Dieter Heck (4) -- Tim Huege (4,5) -- Lukas Nellen (6) -- Tanguy Pierog (4) -- Maximilian Reininghaus (4,7) -- Felix Riehn (8) -- Ralf Ulrich (4) -- Darko Veberic (4) - -Affiliations: - -- University of Montpellier, France -- TU Dortmund University, Germany -- Max Planck Institute for Nuclear Physics, Heidelberg, Germany -- Karlsruhe Institute of Technology (KIT), Germany -- Vrije Universiteit Brussel, Belgium -- National Autonomous University of Mexico (UNAM) -- Instituto de TecnologÃas en Detección y AstropartÃculas (CNEA, CONICET, UNSAM), Buenos Aires, Argentina -- Laboratory of Instrumentation and Experimental Particles (LIP), Portugal diff --git a/documentation/conf.py b/documentation/conf.py index b0a15a6f281ce7008fea14840657803c94f7b6ae..d281602e9bc083acc9046491a7bfcb952303b945 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -25,13 +25,12 @@ def configureDoxyLayout(template_file, output_file, page_url, page_tile): -read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' -build_version = os.environ.get('READTHEDOCS_VERSION', None) +read_the_docs_build = os.environ.get('READTHEDOCS', None) != None breathe_projects = {} -doc_url = 'https://corsika-8.readthedocs.io/en/'+build_version - if read_the_docs_build: + build_version = os.environ.get('READTHEDOCS_VERSION', None) + doc_url = 'https://corsika-8.readthedocs.io/en/'+build_version configureDoxyfile("Doxyfile.in", "Doxyfile", "../", "_build/workdir/doxygen") configureDoxyLayout("DoxyLayout.in", "DoxyLayout.xml", doc_url , "CORSIKA 8 Webpage") subprocess.call('mkdir -p _build/workdir/doxygen; doxygen Doxyfile', shell=True) diff --git a/documentation/howto_create_module.rst b/documentation/howto_create_module.rst new file mode 100644 index 0000000000000000000000000000000000000000..596bf723c545dc2b2af83418a99f0d1469c0c02b --- /dev/null +++ b/documentation/howto_create_module.rst @@ -0,0 +1,135 @@ +Howto create new physics modules +================================ + +There are different types of physics modules, which you can add to the CORSIKA 8 physics process +sequence. Modules can act on particles, secondaries or the entire stack. Modules can create new particles +or they can modify or delete particles. They can also produce output. + +Types of different modules are explained in ::modules + +When creating new modules, we suggest to stick as close as possible to the default CORSIKA 8 coding guidelines +and code structure. This makes code review and sharing with others not more complicated than needed. + +When your modules creates output, use the available CORSIKA 8 output machinery. This is not explained here. Also learn +how to use units, and use the loggers from the very beginning. Furthermore, get aquinted with C++17. + +Let's consider the case of an "InteractionProcess" which will remove the projectile particle and create +secondary particles on the stack instead. It also has a cross section in order to evaulate the probability +with respect to other InteractionProcesses. Create a header file `SimpleProcess.hpp`, which is conceptually +based to resemble (roughly) a Matthew-Heitler model: + +.. code-block:: c++ + + #pragma once + + #include <corsika/framework/core/ParticleProperties.hpp> + #include <corsika/framework/core/PhysicalConstants.hpp> + #include <corsika/framework/core/PhysicalUnits.hpp> + #include <corsika/framework/core/Logging.hpp> + #include <corsika/framework/process/InteractionProcess.hpp> + #include <corsika/framework/random/RNGManager.hpp> + + namespace corsika::simple_process { + + class SimpleProcess : public corsika::InteractionProcess<SimpleProcess> { + + public: + SimpleProcess(); + + template <typename TParticle> + GrammageType getInteractionLength(TParticle const&) const; + + template <typename TSecondaryView> + void doInteraction(TSecondaryView& view) const; + + private: + // the random number stream + corsika::default_prng_type& rng_ = + corsika::RNGManager<>::getInstance().getRandomStream("simple_process"); + + // the logger + std::shared_ptr<spdlog::logger> logger_ = get_logger("corsika_SimpleProcess"); + + }; + + } + + #include <SimpleProcess.inl> + +And the corresponding `SimpleProcess.inl` as: + +.. code-block:: c++ + + Namespace corsika::simple_process + { + + inline SimpleProcess::SimpleProcess() {} + + template <typename TParticle> + inline GrammageType SimpleProcess::getInteractionLength( + TParticle const &particle) const + { + // this process only applies to hadrons above Ekin=100GeV + if (is_hadron(particle.getPID()) && particle.getKineticEnergy()>100_GeV) { + return 2_g/square(1_cm); + } + // otherwise its cross section is 0 + return std::numeric_limits<double>::infinity() * 1_g/square(1_cm); + } + + template <typename TSecondaryView> + inline void SimpleProcess::doInteraction(TSecondaryView &view) const + { + // the actual projectile particle, which will disappear after the call to doInteraction finished. + auto projectile = view.getProjectile(); + int const nMult = 15; + auto pionEnergy = projectile.getEnergy() / nMult; + + auto const pOrig = projectile.getPosition(); + auto const tOrig = projectile.getTime(); + auto const projectileMomentum = projectile.getMomentum(); + auto const &cs = projectileMomentum.getCoordinateSystem(); + + for (int iMult=0; iMult<nMult; ++iMult) { + + projectile.addSecondary(std::make_tuple(Code::PiPlus, + projectileMomentum.normalized() * sqrt(square(pionEnergy) + square(PiPlus::mass)), + pOrig, + tOrig)); + } + CORSIKA_LOGGER_INFO(logger_, "Created {} new secondaries of energy {}.", nMult, pionEnergy); + } + + } + +In this example, `TParticle` as used here in the SimpleModule is one +particle on the CORSIKA8 particle stack. It has all methods you expect +as `getPosition()`, `getEnergy()`, `getKineticEnergy()`, etc. + +The `TSecondaryView` provides special access to the CORSIKA8 particle +stack as needed for a particle interaction with a projectile and +secondaries. For example, `getProjectil()` will return the actual +projectile particle, `addSecondary(...)` will produce a secondary of +the projectil. The advantage of addSecondary wrt. addParticle is that +there exists a direct reference to the projectile allowing to +automatically copy weights, keep the cascade history, +etc. Furthermore, you can define subsequent `SeoncariesProcesses` +which can further process all the newly created secondaries. This +could perform energy threshold cuts, or also calculate new weights, +etc. + +The SimpleProcess is not necessarily useful for anything and its sole purpuse is to illustrate the mechanism to +create your own processes. + +You can then include such a process in your programm (e.g. in vertical_EAS) with + - add: `#include <SimpleProcess.hpp>` + - register the new random stream: `RNGManager<>::getInstance().registerRandomStream("simple_process");` + - initialize it with `simple_process::SimpleProcess simple; ` + - add it to the physiscs sequence i.e. via `auto extended_sequence = make_sequence(old_sequence, simple);` + +Please follow the style and guidelines for programming CORSIKA 8 code +even for private project. Your code will be GPLv3, too, and thus +should be made public to the community. Any discussion or eventual +bug-fixing is much more complicated if there are deviations from the +guideline. + diff --git a/documentation/index.rst b/documentation/index.rst index 79440c503bd0b500d8235d5c1edd77c2bf459822..cbcb393b56f8cca704fd5379ba75ca93c342bd34 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -7,13 +7,16 @@ Welcome to the CORSIKA 8 air shower simulation framework. :maxdepth: 2 readme_link - modules + output particles + particle_stack media units environment stack utilities + modules + howto_create_module api diff --git a/documentation/output.rst b/documentation/output.rst new file mode 100644 index 0000000000000000000000000000000000000000..88edc4b3e934a4bbaa0c0c47f5c136d544db02ea --- /dev/null +++ b/documentation/output.rst @@ -0,0 +1,99 @@ +Output +====== + +The format of CORSIKA 8 is designed to allow simple and robust managmenet of large libraries, as well as high reading performance. There is a dedicated python library to help processing data. + +The basic structure of output is structured on the filesystem itself with a couple of subdirectories and files. Each run of CORSIKA~8 creates a library that can contain any number of showers. +The format is equally suited for single huge showers as well as for a very high number of very low-energy showers. Each module included in the run can +produce output inside this directory. The module output is separeted in individual user-named sub-directories, each containing files produced by the module. The file format is either yaml for basic configuration and summary data, or Apache parquet for any other (binary, compressed) +data. Parquet is optimal for columnar/tabular data as it is produced by CORSIKA 8. + +One advantage of this format is that with normal filesystem utilties users can manage the libraries. On all systems there are tools available to +directly read/process yaml as well as parquet files. If you, for example, don't need the particle data for space reasons, this is very simple to remove from a library. Individual +output stream (modules) can be easily separated with no extra effort. + +For example, the output of the "vertical_EAS" example program looks like this: + +:: + + vertical_EAS_outputs/ + config.yaml + summary.yaml + particles/ + config.yaml + summary.yaml + particles.parquet + +The "vertical_EAS_outputs" and the "particles" are user-defined names and can be arranged/changed. But the type +of data is well defined, e.g. in "particles" the data from an ObservationPlane object is stored. This is relevant, +since it allows python to access this data in a controlled way. + +The top level "config.yaml" contains top-level library information: + +.. code-block:: YAML + + name: vertical_EAS_outputs + creator: CORSIKA8 + version: 8.0.0-prealpha + +and the "summary.yaml" is written in the very end (thus, the presence of the summary also indicates that a run is finished): + +.. code-block:: YAML + + showers: 2 + start time: 06/02/2021 23:46:18 HST + end time: 06/02/2021 23:46:42 HST + runtime: 00:00:24.260 + +Each module has its own "config.yaml" and "summary.yaml" file, too. +To handle thus output for analysis any tool of your preference is feasible. We recommend python. There is a python library accompanied with +CORSIKA~8 to facilitate analysis and output handling (>>> is python prompt): + +.. code-block:: python + + >>> import corsika + >>> lib = corsika.Library("vertical_EAS_outputs") + >>> lib.config # this gets the library configuration as a Python dictionary + {'name': 'vertical_EAS_outputs', + 'creator': 'CORSIKA8', + 'version': '8.0.0-prealpha'} + >>> lib.names # get a list of all registered processes in the library + ['particles'] + >>> lib.summary # you can also load the summary information + {'showers': 1, + 'start time': '06/02/2021 23:46:18 HST', + 'end time': '06/02/2021 23:46:30 HST', + 'runtime': 11.13} + >>> lib.get("particles") # you can then get the process by its registered name. + ObservationPlane('particles') + >>> lib.get("particles").config # and you can also get its config as well + {'type': 'ObservationPlane', + 'plane': {'center': [0, 0, 6371000], + 'center.units': 'm', + 'normal': [0, 0, 1]}, + 'x-axis': [1, 0, 0], + 'y-axis': [0, 1, 0], + 'delete_on_hit': True, + 'name': 'particles'} + >>> lib.get("particles").data # this returns the data as a Pandas data frame + shower pdg energy x y radius + 0 0 211 9.066702e+10 2.449931 -5.913341 7.093710 + 1 0 22 2.403024e+11 -1.561504 -1.276160 2.024900 + 2 0 211 1.306354e+11 -4.626045 -3.237780 6.009696 + 3 0 211 1.773324e+11 -1.566567 4.172961 4.461556 + 4 0 211 7.835374e+10 3.152863 -1.049201 3.330416 + .. ... ... ... ... ... ... + >>> lib.get("particles").astype("arrow") # you can also request the data in a different format + pyarrow.Table + shower: int32 not null + pdg: int32 not null + energy: double not null + x: double not null + y: double not null + radius: double not null + >>>lib.get("particles").astype("pandas") # or astype("arrow"), or astype("pandas").to_numpy() + +You can locally install the corsika python analysis library from within your corsika +source code directory by `pip3 install --user -e python pyarrow==0.17.0`. Note, the pyarrow +version fix has shown to be needed on some older systems. You may not need this, or you may +need additional packages, too. diff --git a/documentation/particle_stack.rst b/documentation/particle_stack.rst new file mode 100644 index 0000000000000000000000000000000000000000..33f22f996ceebbd735665fc2d8d088e7e0b7dc7b --- /dev/null +++ b/documentation/particle_stack.rst @@ -0,0 +1,12 @@ +Particle Properties +=================== + +.. toctree:: + particle_classes + +.. doxygengroup:: Particles + :project: CORSIKA8 + :members: + + + diff --git a/documentation/particles.rst b/documentation/particles.rst index 33f22f996ceebbd735665fc2d8d088e7e0b7dc7b..ace733680c3af9b061288897f24b87febe70ba57 100644 --- a/documentation/particles.rst +++ b/documentation/particles.rst @@ -1,12 +1,33 @@ -Particle Properties -=================== +Particle Storage and Stack +========================== -.. toctree:: - particle_classes +Particles in memory are stored in a Stack. The Stack handles the memory management and access to particle data +properties. The stack can be extended with additional fields that then become part of the particle object. -.. doxygengroup:: Particles - :project: CORSIKA8 - :members: +The standard Stack object in CORSIKA 8 is the NuclearStackExtension, further extended with internal +fields like `geometry node` and `weight` or `history`. But the important part is the `NuclearStackExtension`. +There are several ways to create particles: +* novel particles on the stack: + * stack::addParticle(particle_data_type const&) + * stack::addParticle(nuclear_particle_data_type const&) + * stack::addParticle(particle_data_momentum_type const&) + * stack::addParticle(nuclear_particle_data_momentum_type const&) +* secondary particles: + * stack::addSecondary(parent const&, particle_data_type const&) + * stack::addSecondary(parent const&, nuclear_particle_data_type const&) + * stack::addSecondary(parent const&, particle_data_momentum_type const&) + * stack::addSecondary(parent const&, nuclear_particle_data_momentum_type const&) + or directly: + * particle::addSecondary(particle_data_type const&) + * particle::addSecondary(nuclear_particle_data_type const&) + * particle::addSecondary(particle_data_momentum_type const&) + * particle::addSecondary(nuclear_particle_data_momentum_type const&) + +The content of these method parameters are: + +* particle_data_type: {PID [corsika::Code], kinetic energy [HEPEnergyType], direction [DirectionVector], position [Point], time[TimeType]} +* particle_data_momentum_type: {PID [corsika::Code], momentum [MomentumVector], position [Point], time[TimeType]} +* nuclear_particle_data_type: {PID [corsika::Code], kinetic energy [HEPEnergyType], direction [DirectionVector], position [Point], time[TimeType], A [unsigned int], Z [unsigned int]} +* nuclear_particle_data_momentum_type: {PID [corsika::Code], momentum [MomentumVector], position [Point], time[TimeType], A [unsigned int], Z [unsigned int]} - diff --git a/tests/framework/testGeometry.cpp b/tests/framework/testGeometry.cpp index 7d39abccb0db3e54506b24b34525a1f8e599d42a..31e96c319741975b523bfb43482ce1de4d82b195 100644 --- a/tests/framework/testGeometry.cpp +++ b/tests/framework/testGeometry.cpp @@ -101,6 +101,10 @@ TEST_CASE("Geometry CoordinateSystems") { // vector norm invariant under rotation CHECK(v1.getComponents(rotatedCS).getNorm().magnitude() == Approx(v1.getComponents(rootCS).getNorm().magnitude())); + + // this is not possible + QuantityVector<length_d> const axis_invalid{0_m, 0_m, 0_km}; + CHECK_THROWS(make_rotation(rootCS, axis_invalid, angle)); } SECTION("multiple rotations") { @@ -201,6 +205,10 @@ TEST_CASE("Geometry CoordinateSystem-hirarchy") { CoordinateSystemPtr root = get_root_CoordinateSystem(); Point const p1(root, {0_m, 0_m, 0_m}); // the origin of the root CS + CHECK(p1.getX(root) == 0_m); + CHECK(p1.getY(root) == 0_m); + CHECK(p1.getZ(root) == 0_m); + // root -> cs2 CoordinateSystemPtr cs2 = make_translation(root, {0_m, 0_m, 1_m}); Point const p2(cs2, {0_m, 0_m, -1_m}); @@ -280,6 +288,8 @@ TEST_CASE("Geometry Trajectories") { .getNorm() .magnitude() == Approx(0).margin(absMargin)); + CHECK((line.getTimeFromArclength(10_m) / (10_m / v0.getNorm()) == Approx(1))); + auto const t = 1_s; StraightTrajectory base(line, t); CHECK(line.getPosition(t).getCoordinates() == base.getPosition(1.).getCoordinates()); @@ -348,8 +358,6 @@ TEST_CASE("Distance between points") { CHECK(distance(p5, p6) / 1_m == Approx(1)); } -TEST_CASE("Geometry Tree") {} - TEST_CASE("Path") { // define a known CS CoordinateSystemPtr root = get_root_CoordinateSystem(); diff --git a/tests/modules/testTracking.cpp b/tests/modules/testTracking.cpp index bbd94cb347e35adfec4b2eada8199258912b6d43..243cbffa7df9bc47b2486b048be356381587d6db 100644 --- a/tests/modules/testTracking.cpp +++ b/tests/modules/testTracking.cpp @@ -186,6 +186,7 @@ TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_curved::Tracking, particle.setNode(nextVol); particle.setPosition(traj.getPosition(1)); particle.setMomentum(traj.getDirection(1) * particle.getMomentum().getNorm()); + SpeedType const speed_0 = particle.getVelocity().getNorm(); if (outer) { // now we know we are in target volume, depending on "outer" CHECK(traj.getLength(1) / 1_m == Approx(0).margin(1e-3)); @@ -211,6 +212,7 @@ TEMPLATE_TEST_CASE("Tracking", "tracking", tracking_leapfrog_curved::Tracking, particle.getMomentum().getNorm(), particle.getVelocity().getNorm(), traj2.getLength(1), traj2.getLength(1) / particle.getVelocity().getNorm()); + CHECK(speed_0 / traj2.getVelocity(1).getNorm() == Approx(1)); } CHECK_FALSE(hit_2nd_behind); // this can never happen // the next line is maybe an actual BUG: this should be investigated and eventually