diff --git a/.github/install-dependency-packages.sh b/.github/install-dependency-packages.sh index 74d44c81..fddab130 100755 --- a/.github/install-dependency-packages.sh +++ b/.github/install-dependency-packages.sh @@ -8,6 +8,7 @@ set -e GENERAL_PACKAGE_LIST_LINUX=( python gcc + gcc-fortran clang make cmake @@ -42,6 +43,7 @@ GENERAL_PACKAGE_LIST_MACOS=( tree ninja meson + gcc # for gfortran ### ROOT dependencies binutils libx11 @@ -128,6 +130,9 @@ case $runner in brew install $pkg info_homebrew $pkg done + ### link homebrew's gcc, for gfortran + brew unlink gcc + brew link gcc ;; *) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f52fbe8..9ccf150f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -273,7 +273,7 @@ jobs: -Dtest_output_dir=$(pwd)/validation_results \ ${{ matrix.opts }} - name: dump build options - run: meson configure iguana_build | cat + run: meson configure iguana_build --no-pager - run: meson compile working-directory: iguana_build - run: meson install @@ -331,6 +331,9 @@ jobs: if: ${{ matrix.id == 'python' }} - run: iguana-example-01-bank-rows.py test_data.hipo ${{ env.num_events }} if: ${{ matrix.id == 'python' }} + ###### fortran + - run: iguana-example-fortran test_data.hipo ${{ env.num_events }} + if: ${{ matrix.id == 'fortran' }} ### test consumers - name: consumer test make if: ${{ matrix.id == 'cpp' }} @@ -389,10 +392,10 @@ jobs: pacman -Syu --noconfirm pacman -S --noconfirm doxygen graphviz - name: doxygen - run: doxygen doc/Doxyfile + run: doxygen doc/gen/Doxyfile - uses: actions/upload-artifact@v4 with: - name: doxygen + name: doc_doxygen retention-days: 5 path: doc/api/ @@ -413,7 +416,7 @@ jobs: - name: download doxygen documentation uses: actions/download-artifact@v4 with: - name: doxygen + name: doc_doxygen path: doxygen - name: download coverage report uses: actions/download-artifact@v4 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6211dda6..98a1010c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -39,7 +39,8 @@ jobs: { "id": "undefined-sanitizer", "CC": "clang", "CXX": "clang++", "opts": "-Dbuildtype=debug -Drequire_ROOT=true -Db_sanitize=undefined -Db_lundef=false -Db_pie=true" }, { "id": "leak-sanitizer", "CC": "clang", "CXX": "clang++", "opts": "-Dbuildtype=debug -Drequire_ROOT=true -Db_sanitize=leak -Db_lundef=false -Db_pie=true" }, { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=false" }, - { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" } + { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" }, + { "id": "fortran", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_fortran=true" } ] } diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 58f6925f..69ac2d28 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,9 +24,10 @@ jobs: "python" ], "include": [ - { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true" }, - { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=false" }, - { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" } + { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true" }, + { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=false" }, + { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" }, + { "id": "fortran", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_fortran=true" } ] } diff --git a/.github/workflows/minver.yml b/.github/workflows/minver.yml index 090d6df7..ac533f44 100644 --- a/.github/workflows/minver.yml +++ b/.github/workflows/minver.yml @@ -1,6 +1,10 @@ name: MinVer # minimum required-version of dependencies on Linux on: + pull_request: + push: + branches: [ main ] + tags: [ '*' ] schedule: - cron: '15 7 * * 0' # Sundays at 0715Z @@ -22,9 +26,10 @@ jobs: "python" ], "include": [ - { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true" }, - { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=false" }, - { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" } + { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true" }, + { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=false" }, + { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_python=true" }, + { "id": "fortran", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Drequire_ROOT=true -Dbind_fortran=true" } ] } diff --git a/.gitignore b/.gitignore index f101d611..a54e5b53 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ /iguana*/ /.cache -# doxygen artifacts +# documentation artifacts /doc/api # data files diff --git a/README.md b/README.md index abe05577..f3335226 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ Iguana is not a framework for _reading_ data, rather it is a set of algorithms t 1. [Troubleshooting](doc/troubleshooting.md) 1. [API documentation](https://jeffersonlab.github.io/iguana/doxygen) +#### Language Bindings +1. [Python](/bind/python/README.md) +1. [Fortran](https://jeffersonlab.github.io/iguana/doxygen/group__binding__namespaces.html) + ### For Developers 1. [Design Notes](doc/design.md) 1. [Developing a new Algorithm](src/iguana/algorithms/example/README.md) diff --git a/doc/Doxyfile b/doc/gen/Doxyfile similarity index 99% rename from doc/Doxyfile rename to doc/gen/Doxyfile index 531348d1..c79a64f6 100644 --- a/doc/Doxyfile +++ b/doc/gen/Doxyfile @@ -829,7 +829,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = doc/DoxygenLayout.xml +LAYOUT_FILE = doc/gen/DoxygenLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -957,7 +957,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = src/ examples/ doc/DoxygenMainpage.md +INPUT = src/ examples/ doc/gen/DoxygenMainpage.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -997,7 +997,7 @@ INPUT_FILE_ENCODING = # provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.h iguana-example-*.cc +FILE_PATTERNS = *.h iguana-example-*.cc *Bindings.cc *.f # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -1028,7 +1028,7 @@ EXCLUDE_SYMLINKS = YES # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = AlgorithmBindings.cc # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1123,7 +1123,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = doc/DoxygenMainpage.md +USE_MDFILE_AS_MAINPAGE = doc/gen/DoxygenMainpage.md # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common diff --git a/doc/DoxygenLayout.xml b/doc/gen/DoxygenLayout.xml similarity index 100% rename from doc/DoxygenLayout.xml rename to doc/gen/DoxygenLayout.xml diff --git a/doc/DoxygenMainpage.md b/doc/gen/DoxygenMainpage.md similarity index 95% rename from doc/DoxygenMainpage.md rename to doc/gen/DoxygenMainpage.md index aba59a13..360e3651 100644 --- a/doc/DoxygenMainpage.md +++ b/doc/gen/DoxygenMainpage.md @@ -1,7 +1,7 @@ # Iguana API Documentation @@ -51,6 +51,7 @@ These functions are unique to each algorithm, so view the algorithm documentation for details, or browse the full list: - \ref action "List of all Action Functions" +- \ref binding_namespaces "Fortran Action Functions (C-bindings)" The action functions have types that correspond to the algorithm types. Furthermore, some action functions can only be useful if all of the rows of a diff --git a/examples/config/my_config_directory/algorithms/clas12/ZVertexFilter.yaml b/examples/config/my_config_directory/algorithms/clas12/ZVertexFilter.yaml index 67b0ccaa..1ef2b639 100644 --- a/examples/config/my_config_directory/algorithms/clas12/ZVertexFilter.yaml +++ b/examples/config/my_config_directory/algorithms/clas12/ZVertexFilter.yaml @@ -3,12 +3,12 @@ clas12::ZVertexFilter: # default cuts - default: - cuts: [ -1.5, 1.3 ] + cuts: [ -15.0, 15.0 ] # RG-A fall2018 inbending - runs: [ 4760, 5419 ] - cuts: [ -0.5, 0.5 ] + cuts: [ -5.0, 3.0 ] # RG-A fall2018 outbending - runs: [ 5420, 5674 ] - cuts: [ -0.8, 0.7 ] + cuts: [ -8.0, 7.0 ] diff --git a/examples/config/my_z_vertex_cuts.yaml b/examples/config/my_z_vertex_cuts.yaml index 1ef2b639..67b0ccaa 100644 --- a/examples/config/my_z_vertex_cuts.yaml +++ b/examples/config/my_z_vertex_cuts.yaml @@ -3,12 +3,12 @@ clas12::ZVertexFilter: # default cuts - default: - cuts: [ -15.0, 15.0 ] + cuts: [ -1.5, 1.3 ] # RG-A fall2018 inbending - runs: [ 4760, 5419 ] - cuts: [ -5.0, 3.0 ] + cuts: [ -0.5, 0.5 ] # RG-A fall2018 outbending - runs: [ 5420, 5674 ] - cuts: [ -8.0, 7.0 ] + cuts: [ -0.8, 0.7 ] diff --git a/examples/iguana-example-03-config-files.cc b/examples/iguana-example-03-config-files.cc index 1fb9b4b5..60675d69 100644 --- a/examples/iguana-example-03-config-files.cc +++ b/examples/iguana-example-03-config-files.cc @@ -28,7 +28,7 @@ int main(int argc, char** argv) if(argc > 1) configDir = std::string(argv[1]); else - configDir = iguana::ConfigFileReader::DirName(argv[0]) + "/../etc/iguana/examples"; + configDir = iguana::ConfigFileReader::GetConfigInstallationPrefix() + "/examples"; fmt::print("Using top-level configuration directory {}\n", configDir); // loop over multiple examples how to use configuration files and set options @@ -66,8 +66,8 @@ int main(int argc, char** argv) algo->SetConfigFile(configDir + "/my_z_vertex_cuts.yaml"); algo->SetOption("runnum", 5500); algo->Start(); - assert((algo->GetZcutLower() == -8.0)); - assert((algo->GetZcutUpper() == 7.0)); + assert((algo->GetZcutLower() == -0.8)); + assert((algo->GetZcutUpper() == 0.7)); break; case 4: @@ -76,8 +76,8 @@ int main(int argc, char** argv) algo->SetConfigDirectory(configDir); algo->SetConfigFile("my_z_vertex_cuts.yaml"); algo->Start(); - assert((algo->GetZcutLower() == -15.0)); - assert((algo->GetZcutUpper() == 15.0)); + assert((algo->GetZcutLower() == -1.5)); + assert((algo->GetZcutUpper() == 1.3)); break; case 5: @@ -87,8 +87,8 @@ int main(int argc, char** argv) // may use that directory instead of the default, and modify any configuration file within. algo->SetConfigDirectory(configDir + "/my_config_directory"); algo->Start(); - assert((algo->GetZcutLower() == -1.5)); - assert((algo->GetZcutUpper() == 1.3)); + assert((algo->GetZcutLower() == -15.0)); + assert((algo->GetZcutUpper() == 15.0)); break; case 6: diff --git a/examples/iguana-example-fortran.f b/examples/iguana-example-fortran.f new file mode 100644 index 00000000..8256d7c2 --- /dev/null +++ b/examples/iguana-example-fortran.f @@ -0,0 +1,275 @@ + !> @begin_doc_example + !> @file iguana-example-fortran.f + !> @brief Fortran example demonstrating how to read a HIPO file and use + !> its data with Iguana algorithms. + !> Run with no arguments to see the usage guide. + !> @see @ref binding_namespaces for the Fortran API guide + !> @end_doc_example + !> + !> Fortran example program + program iguana_example_fortran + + use iso_c_binding + implicit none + +c ------------------------------------------------------------------ +c data declarations +c ------------------------------------------------------------------ +c NOTE: using `iso_c_binding` types for input and output of C-bound +c functions and subroutines, i.e., for HIPO and Iguana usage; +c using standard F77 types may still work, but might be +c compiler dependent in some cases + +c program parameters + integer*4 argc + character*1024 in_file ! HIPO file + integer num_events ! number of events to read + character*1024 config_file ! YAML configuration file + character*16 num_events_arg + logical config_file_set + character(kind=c_char, len=1024) + & in_file_cstr, config_file_cstr, etc_dir + +c HIPO and bank variables + integer counter ! event counter + integer(c_int) reader_status ! hipo event loop vars + integer(c_int) nrows, nrows_c ! number of rows + integer(c_int) nr ! unused + integer N_MAX ! max number of rows we can read + parameter (N_MAX=50) + +c REC::Particle columns + integer(c_int) pid(N_MAX) + real(c_float) px(N_MAX), py(N_MAX), pz(N_MAX) + real(c_float) vz(N_MAX) + integer(c_int) stat(N_MAX) + integer(c_int) sector(N_MAX) + real(c_float) torus(N_MAX) + +c iguana algorithm outputs + logical(c_bool) accept(N_MAX) ! filter + real(c_double) qx, qy, qz, qE ! q vector + real(c_double) Q2, x, y, W, nu ! inclusive kinematics + +c iguana algorithm indices + integer(c_int) algo_eb_filter, algo_vz_filter, + & algo_inc_kin, algo_mom_cor + +c misc. + integer i + real p, p_ele + integer i_ele + logical found_ele + +c ------------------------------------------------------------------ +c open the input file +c ------------------------------------------------------------------ + +c parse arguments + num_events = 10 + config_file_set = .false. + argc = iargc() + if(argc.lt.1) then + etc_dir = '' + call iguana_getconfiginstallationprefix(etc_dir) + print *, 'ERROR: please at least specify a HIPO_FILE' + print *, '' + print *, 'ARGS: ', 'HIPO_FILE', ' ', 'NUM_EVENTS' + print *, '' + print *, ' HIPO_FILE: ', 'the input HIPO file' + print *, '' + print *, ' NUM_EVENTS (optional): ', + & 'the number of events (0 for all)' + print *, ' default: ', num_events + print *, '' + print *, ' CONFIG_FILE (optional): ', + & 'algorithm configuration file' + print *, ' default: ', 'use the internal defaults' + print *, ' example config file: ', + & trim(etc_dir)//'/examples/my_z_vertex_cuts.yaml' + stop + else + call getarg(1, in_file) + in_file_cstr = trim(in_file)//c_null_char + end if + if(argc.ge.2) then + call getarg(2, num_events_arg) + read(num_events_arg,*) num_events + end if + if(argc.ge.3) then + call getarg(3, config_file) + config_file_cstr = trim(config_file)//c_null_char + config_file_set = .true. + endif + +c open the input HIPO file + call hipo_file_open(in_file_cstr) + reader_status = 0 + counter = 0 + +c ------------------------------------------------------------------ +c create iguana algorithms +c ------------------------------------------------------------------ + +c before anything for Iguana, call `iguana_create()`; when done, you +c must also call `iguana_destroy()` to deallocate the memory + call iguana_create() +! call iguana_bindings_set_verbose() ! enable additional log print + +c then create the algorithm instances +c - the 1st argument is an integer, the algorithm index, which +c references the created instance of the algorithm; it will be +c set after calling this subroutine and you will need it to call +c other iguana subroutines (namely, "action functions") +c - the 2nd argument is the algorithm name + call iguana_algo_create( + & algo_eb_filter, + & 'clas12::EventBuilderFilter') + call iguana_algo_create( + & algo_vz_filter, + & 'clas12::ZVertexFilter') + call iguana_algo_create( + & algo_inc_kin, + & 'physics::InclusiveKinematics') + call iguana_algo_create( + & algo_mom_cor, + & 'clas12::MomentumCorrection') + +c ------------------------------------------------------------------ +c configure and start iguana algorithms +c ------------------------------------------------------------------ + +c set log levels + call iguana_algo_set_log_level(algo_eb_filter,'debug') + call iguana_algo_set_log_level(algo_vz_filter,'debug') + call iguana_algo_set_log_level(algo_inc_kin,'debug') + call iguana_algo_set_log_level(algo_mom_cor,'debug') + +c configure algorithms with a configuration file + if(config_file_set) then + call iguana_set_config_file(config_file_cstr) + endif + +c start all algorithms, which "locks" their configuration + call iguana_start() + +c ------------------------------------------------------------------ +c event loop +c ------------------------------------------------------------------ + + 10 if(reader_status.eq.0 .and. + & (num_events.eq.0 .or. counter.lt.num_events)) then + + print *, '' + print *, '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> event ', counter + +c read banks + call hipo_file_next(reader_status) + call hipo_read_bank('REC::Particle', nrows) + call hipo_read_bank('RUN::config', nrows_c) + call hipo_read_int('REC::Particle', 'pid', nr, pid, N_MAX) + call hipo_read_float('REC::Particle', 'px', nr, px, N_MAX) + call hipo_read_float('REC::Particle', 'py', nr, py, N_MAX) + call hipo_read_float('REC::Particle', 'pz', nr, pz, N_MAX) + call hipo_read_float('REC::Particle', 'vz', nr, vz, N_MAX) + call hipo_read_int('REC::Particle', 'status', nr, stat, N_MAX) + call hipo_read_float('RUN::config', 'torus', nr, torus, N_MAX) + +c call iguana filters +c - the `logical` variable `accept` must be initialized to +c `.true.`, since we will use it to "chain" the filters +c - the event builder filter trivial: by default it accepts only +c `REC::Particle::pid == 11 or -211` (simple example algorithm) +c - the AND with the z-vertex filter is the final filter, `accept` + print *, '===> filter particles with iguana:' + do i=1, nrows + accept(i) = .true. + call iguana_clas12_eventbuilderfilter_filter( + & algo_eb_filter, pid(i), accept(i)) + call iguana_clas12_zvertexfilter_filter( + & algo_vz_filter, vz(i), accept(i)) + print *, ' i = ', i, ' pid = ', pid(i), ' vz = ', vz(i), + & ' => accept = ', accept(i) + enddo + +c get sector number +c FIXME: we have the algorithm `iguana::clas12::SectorFinder`, +c but it is not yet compatible with Fortran bindings; until +c then, assume the sector number is 1 + do i=1, nrows + sector(i) = 1 ! FIXME + enddo + +c momentum corrections + if(nrows_c.lt.1) then + print *, '===> no RUN::config bank; cannot ', + & 'apply momentum corrections' + else + print *, '===> momentum corrections:' + do i=1, nrows + if(accept(i)) then + print *, ' i = ', i + print *, ' before: p = (', px(i), py(i), pz(i), ')' + call iguana_clas12_momentumcorrection_transform( + & algo_mom_cor, + & px(i), py(i), pz(i), + & sector(i), pid(i), torus(1)) + print *, ' after: p = (', px(i), py(i), pz(i), ')' + endif + enddo + endif + +c simple electron finder: trigger and highest |p| + p_ele = 0 + found_ele = .false. + print *, '===> finding electron...' + do i=1, nrows + if(accept(i)) then + print *, ' i = ', i, ' status = ', stat(i) + if(pid(i).eq.11 .and. stat(i).lt.0) then + p = sqrt(px(i)**2 + py(i)**2 + pz(i)**2) + if(p.gt.p_ele) then + i_ele = i + p_ele = p + found_ele = .true. + endif + endif + endif + enddo + if(found_ele) then + print *, '===> found DIS electron:' + print *, ' i = ', i_ele + print *, ' p = ', p_ele + else + print *, '===> no DIS electron' + endif + +c compute DIS kinematics with iguana, if electron is found + if(found_ele) then + call iguana_physics_inclusivekinematics_computefromlepton( + & algo_inc_kin, + & px(i_ele), py(i_ele), pz(i_ele), + & qx, qy, qz, qE, + & Q2, x, y, W, nu) + print *, '===> inclusive kinematics:' + print *, ' q = (', qx, qy, qz, qE, ')' + print *, ' Q2 = ', Q2 + print *, ' x = ', x + print *, ' y = ', y + print *, ' W = ', W + print *, ' nu = ', nu + endif + + counter = counter + 1 + goto 10 + endif + +c ------------------------------------------------------------------ +c cleanup +c ------------------------------------------------------------------ + +c don't forget to call `iguana_stop()` to stop the algorithms +c and free the allocated memory + call iguana_stop() + + end program diff --git a/examples/meson.build b/examples/meson.build index 58523b7a..6a955bb5 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -18,6 +18,10 @@ example_sources = { 'sources': [ 'iguana-example-03-config-files.cc' ], 'test_args': [ get_option('prefix') / example_config_files_prefix ], }, + 'iguana-example-fortran': { + 'sources': [ 'iguana-example-fortran.f' ], + 'build_this': get_option('bind_fortran') + }, } # build executables and test them diff --git a/meson.build b/meson.build index 6580d231..f9d87aa1 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'iguana', - 'cpp', + 'cpp', 'fortran', license: 'LGPLv3', meson_version: '>=1.2', default_options: { diff --git a/meson.options b/meson.options index 1b2b26f6..520759e1 100644 --- a/meson.options +++ b/meson.options @@ -4,3 +4,4 @@ option('test_num_events', type: 'string', value: '10', description: 'Number of option('test_output_dir', type: 'string', value: '', description: 'Output directory for tests. Must be an absolute path. If unspecified, tests will still run, but will not produce output files.') option('require_ROOT', type: 'boolean', value: false, description: 'If false, ROOT is only used if it is found; if true, ROOT is REQUIRED and the build will fail if ROOT is not found') option('bind_python', type: 'boolean', value: false, description: 'Generate Python bindings and their examples') +option('bind_fortran', type: 'boolean', value: false, description: 'Generate Fortran bindings') diff --git a/src/iguana/algorithms/Algorithm.h b/src/iguana/algorithms/Algorithm.h index 998484d6..01c51d70 100644 --- a/src/iguana/algorithms/Algorithm.h +++ b/src/iguana/algorithms/Algorithm.h @@ -130,11 +130,13 @@ namespace iguana { /// @param yaml_config the custom `YAMLReader` instance void SetConfig(std::unique_ptr&& yaml_config); - /// Set a custom configuration file for this algorithm; see also `Algorithm::SetConfigDirectory` + /// Set a custom configuration file for this algorithm + /// @see `Algorithm::SetConfigDirectory` /// @param name the configuration file name void SetConfigFile(std::string const& name); - /// Set a custom configuration file directory for this algorithm; see also `Algorithm::SetConfigFile` + /// Set a custom configuration file directory for this algorithm + /// @see `Algorithm::SetConfigFile` /// @param name the directory name void SetConfigDirectory(std::string const& name); diff --git a/src/iguana/algorithms/AlgorithmBindings.cc b/src/iguana/algorithms/AlgorithmBindings.cc new file mode 100644 index 00000000..96b3b8d0 --- /dev/null +++ b/src/iguana/algorithms/AlgorithmBindings.cc @@ -0,0 +1,153 @@ +#include "AlgorithmBindings.h" + +#include + +namespace iguana::bindings { + extern "C" { + + algo_owner_t __boss; // the boss owns the algorithm instances + + void iguana_print_debug_(char const* format, ...) + { + if(__boss.verbose) { + va_list args; + va_start(args, format); + printf("[IGUANA C-BINDINGS] [DEBUG] "); + vprintf(format, args); + printf("\n"); + va_end(args); + } + } + + void iguana_print_error_(char const* format, ...) + { + va_list args; + va_start(args, format); + fprintf(stderr, "[IGUANA C-BINDINGS] [ERROR] "); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + va_end(args); + } + + Algorithm* iguana_get_algo_(algo_idx_t* algo_idx, bool verbose) + { + if(*algo_idx >= 0 && *algo_idx < algo_idx_t(__boss.algos.size())) { + auto& algo = __boss.algos[*algo_idx]; + if(verbose) + iguana_print_debug_(" algo %d is at %p", *algo_idx, algo); + if(algo == nullptr) + iguana_print_error_("algorithm number %d is NULL", *algo_idx); + return algo; + } + iguana_print_error_("algorithm number %d is not defined", *algo_idx); + return nullptr; + } + + void iguana_create_() + { + __boss.verbose = false; + } + + void iguana_set_config_file_(char const* name) + { + for(algo_idx_t i = 0; i < algo_idx_t(__boss.algos.size()); i++) + iguana_algo_set_config_file_(&i, name); + } + + void iguana_set_config_dir_(char const* name) + { + for(algo_idx_t i = 0; i < algo_idx_t(__boss.algos.size()); i++) + iguana_algo_set_config_dir_(&i, name); + } + + void iguana_start_() + { + for(algo_idx_t i = 0; i < algo_idx_t(__boss.algos.size()); i++) + iguana_algo_start_(&i); + } + + void iguana_stop_() + { + iguana_stop_and_keep_(); + iguana_destroy_(); + } + + void iguana_stop_and_keep_() + { + for(algo_idx_t i = 0; i < algo_idx_t(__boss.algos.size()); i++) + iguana_algo_stop_(&i); + } + + void iguana_destroy_() + { + iguana_print_debug_("destroying all algorithm instances..."); + for(auto& algo : __boss.algos) { + iguana_print_debug_(" - destroy %p", algo); + delete algo; + } + __boss.algos.clear(); + } + + void iguana_bindings_set_verbose_() + { + __boss.verbose = true; + iguana_print_debug_("enable verbose mode"); + } + + void iguana_bindings_set_quiet_() + { + iguana_print_debug_("disable verbose mode"); + __boss.verbose = false; + } + + void iguana_algo_create_(algo_idx_t* algo_idx, char const* algo_name) + { + iguana_print_debug_("creating algorithm '%s' ...", algo_name); + *algo_idx = __boss.algos.size(); + __boss.algos.push_back(AlgorithmFactory::Create(algo_name).release()); + iguana_print_debug_("... created '%s' algo %d at %p", algo_name, *algo_idx, __boss.algos.back()); + } + + void iguana_algo_set_name_(algo_idx_t* algo_idx, char const* name) + { + iguana_print_debug_("set algo %d name", *algo_idx); + iguana_get_algo_(algo_idx, true)->SetName(name); + } + + void iguana_algo_set_log_level_(algo_idx_t* algo_idx, char const* level) + { + iguana_print_debug_("set algo %d log level", *algo_idx); + iguana_get_algo_(algo_idx, true)->SetLogLevel(level); + } + + void iguana_algo_set_config_file_(algo_idx_t* algo_idx, char const* name) + { + iguana_print_debug_("set algo %d config file to '%s'", *algo_idx, name); + iguana_get_algo_(algo_idx, true)->SetConfigFile(name); + } + + void iguana_algo_set_config_dir_(algo_idx_t* algo_idx, char const* name) + { + iguana_print_debug_("set algo %d config dir to '%s'", *algo_idx, name); + iguana_get_algo_(algo_idx, true)->SetConfigDirectory(name); + } + + void iguana_algo_start_(algo_idx_t* algo_idx) + { + iguana_print_debug_("start algo %d", *algo_idx); + iguana_get_algo_(algo_idx, true)->Start(); + } + + void iguana_algo_stop_(algo_idx_t* algo_idx) + { + iguana_print_debug_("stop algo %d", *algo_idx); + iguana_get_algo_(algo_idx, true)->Stop(); + } + + void iguana_getconfiginstallationprefix_(char* out) + { + sprintf(out, "%s", ConfigFileReader::GetConfigInstallationPrefix().c_str()); + } + + } +} diff --git a/src/iguana/algorithms/AlgorithmBindings.h b/src/iguana/algorithms/AlgorithmBindings.h new file mode 100644 index 00000000..54f690fe --- /dev/null +++ b/src/iguana/algorithms/AlgorithmBindings.h @@ -0,0 +1,115 @@ +#pragma once + +#include "Algorithm.h" + +namespace iguana::bindings { + extern "C" { + + /// `Algorithm` instance index type + typedef int algo_idx_t; + + /// `Algorithm` instance owner type + typedef struct + { + /// A list of `Algorithm` instance pointers + std::vector algos; + /// Control printout verbosity + bool verbose; + } algo_owner_t; + + /// Print a log message, only if `iguana_bindings_set_verbose_` was called. + /// @warning This function is not for Fortran + /// @param format `printf` arguments + void iguana_print_log_(char const* format, ...); + + /// Print an error message. + /// @warning This function is not for Fortran + /// @param format `printf` arguments + void iguana_print_error_(char const* format, ...); + + /// Get a pointer to an algorithm. + /// @warning This function is not for Fortran + /// @param [in] algo_idx the algorithm index + /// @param [in] verbose enable verbose printout + /// @returns a pointer to the algorithm, if it exists; if not, `nullptr` + Algorithm* iguana_get_algo_(algo_idx_t* algo_idx, bool verbose = false); + + /// Create the Iguana instance. You may only create one, and you must destroy + /// it with `iguana_stop` or `iguana_destroy` when you are done. This instance is the _owner_ + /// of algorithm objects. + void iguana_create_(); + + /// Set a custom configuration file for _all_ algorithms + /// @param [in] name the configuration file name + void iguana_set_config_file_(char const* name); + + /// Set a custom configuration file directory for _all_ algorithms + /// @param [in] name the directory name + void iguana_set_config_dir_(char const* name); + + /// Start all created algorithm instances, + /// calling `Algorithm::Start` on each. + void iguana_start_(); + + /// Stop all created algorithm instances, calling `Algorithm::Stop` on each, + /// and free the allocated memory. + /// @see iguana_stop_and_keep_() + void iguana_stop_(); + + /// Stop all created algorithm instances, + /// but do not destroy them + void iguana_stop_and_keep_(); + + /// Destroy the Iguana instance, along with its algorithms. This must be called + /// when you are done using Iguana, to free the allocated memory. + void iguana_destroy_(); + + /// Enable additional runtime printouts for these binding functions. This setting + /// is _not_ related to algorithm log levels. + /// @see `iguana_bindings_set_quiet_` + void iguana_bindings_set_verbose_(); + + /// Disable additional runtime printouts for these binding functions. This setting + /// is _not_ related to algorithm log levels. + /// @see `iguana_bindings_set_verbose_` + void iguana_bindings_set_quiet_(); + + /// Create an algorithm. Be sure to run `iguana_create_()` before creating any algorithm. + /// @param [out] algo_idx the algorithm index + /// @param [in] algo_name the name of the algorithm + void iguana_algo_create_(algo_idx_t* algo_idx, char const* algo_name); + + /// Set the name of an algorithm. + /// @param [in] algo_idx the algorithm index + /// @param [in] name the name + void iguana_algo_set_name_(algo_idx_t* algo_idx, char const* name); + + /// Set the log level of an algorithm. + /// @param [in] algo_idx the algorithm index + /// @param [in] level the log level + void iguana_algo_set_log_level_(algo_idx_t* algo_idx, char const* level); + + /// Set a custom configuration file for this algorithm + /// @param [in] algo_idx the algorithm index + /// @param [in] name the configuration file name + void iguana_algo_set_config_file_(algo_idx_t* algo_idx, char const* name); + + /// Set a custom configuration file directory for this algorithm + /// @param [in] algo_idx the algorithm index + /// @param [in] name the directory name + void iguana_algo_set_config_dir_(algo_idx_t* algo_idx, char const* name); + + /// Start an algorithm by calling `Algorithm::Start`. + /// @param [in] algo_idx the algorithm index + void iguana_algo_start_(algo_idx_t* algo_idx); + + /// Stop an algorithm by calling `Algorithm::Stop`. + /// @param [in] algo_idx the algorithm index + void iguana_algo_stop_(algo_idx_t* algo_idx); + + /// Get the configuration file installation prefix + /// @param [in,out] out will be set to the prefix + void iguana_getconfiginstallationprefix_(char* out); + + } +} diff --git a/src/iguana/algorithms/Docstrings.h b/src/iguana/algorithms/Docstrings.h index de9322cd..b0155041 100644 --- a/src/iguana/algorithms/Docstrings.h +++ b/src/iguana/algorithms/Docstrings.h @@ -7,16 +7,59 @@ /// /// @{ -/// General `iguana` namespace for algorithms and infrastructure. +/// General, top-level namespace for algorithms and infrastructure. For algorithms and bindings, see its sub-namespaces. namespace iguana {} -/// Namespace for example algorithms +/// Example algorithms namespace iguana::example {} -/// Namespace for CLAS12 algorithms +/// CLAS12 algorithms namespace iguana::clas12 {} -/// Namespace for physics algorithms +/// Physics algorithms namespace iguana::physics {} /// @} + +///////////////////////////////////////////////////////////////////////////////// + +/// @defgroup binding_namespaces Fortran Bindings Namespaces +/// +/// @brief C bindings, for Fortran usage +/// +/// The functions in these namespaces are designed to provide bindings for Fortran (and for C). +/// The function names are all lowercase, and end in an underscore, to permit automatic binding +/// to Fortran 77. Visit each namespace page to view the available functions. +/// +/// To use a function in Fortran, call it as a subroutine, but without the final +/// underscore; use `iso_c_binding` data types for the arguments, otherwise you +/// may have subtle runtime problems. +/// +/// For example, consider the following C function: +/// ```cpp +/// void iguana_example_function_(int* a, float* b); +/// ``` +/// To use this in Fortran: +/// ```fortran +/// use iso_c_binding +/// integer(c_int) a +/// real(c_float) b +/// call iguana_example_function(a, b) +/// ``` +/// +/// To use these bindings with your Fortran code, link against the installed `iguana` libraries. +/// +/// @see A Fortran example: `iguana-example-fortran.f` +/// +/// @{ + +/// General `iguana` bindings +namespace iguana::bindings {} + +/// CLAS12 algorithm action function bindings +namespace iguana::bindings::clas12 {} + +/// Physics algorithm action function bindings +namespace iguana::bindings::physics {} + +/// @} diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter.h b/src/iguana/algorithms/clas12/EventBuilderFilter.h index 7d9e506a..ec08a213 100644 --- a/src/iguana/algorithms/clas12/EventBuilderFilter.h +++ b/src/iguana/algorithms/clas12/EventBuilderFilter.h @@ -43,4 +43,5 @@ namespace iguana::clas12 { /// Configuration options std::set o_pids; }; + } diff --git a/src/iguana/algorithms/clas12/EventBuilderFilterBindings.cc b/src/iguana/algorithms/clas12/EventBuilderFilterBindings.cc new file mode 100644 index 00000000..7346d508 --- /dev/null +++ b/src/iguana/algorithms/clas12/EventBuilderFilterBindings.cc @@ -0,0 +1,27 @@ +#include "iguana/algorithms/AlgorithmBindings.h" +#include "EventBuilderFilter.h" + +namespace iguana::bindings::clas12 { + extern "C" { + + //-------------------------- FIXME: move this to a better place ------------------------- + // Action function binding. Rules for Fortran compatibility: + // - name must be all lowercase and end with an underscore (`_`) + // - must be `void` + // - parameters must be pointers + // - to return a value (or values), mutate the approprate pointers' values + // - filter action functions must AND with `out`, to allow function chaining; say + // `*out = *out && _call_action_function_` to avoid the action function call when `! *out` + //--------------------------------------------------------------------------------------- + + /// @see `iguana::clas12::EventBuilderFilter::Filter` + /// @param [in] algo_idx the algorithm index + /// @param [in] pid + /// @param [in,out] out the return value + void iguana_clas12_eventbuilderfilter_filter_(algo_idx_t* algo_idx, int* pid, bool* out) + { + *out = *out && dynamic_cast(iguana_get_algo_(algo_idx))->Filter(*pid); + } + + } +} diff --git a/src/iguana/algorithms/clas12/MomentumCorrectionBindings.cc b/src/iguana/algorithms/clas12/MomentumCorrectionBindings.cc new file mode 100644 index 00000000..47f2e7b1 --- /dev/null +++ b/src/iguana/algorithms/clas12/MomentumCorrectionBindings.cc @@ -0,0 +1,33 @@ +#include "iguana/algorithms/AlgorithmBindings.h" +#include "MomentumCorrection.h" + +namespace iguana::bindings::clas12 { + extern "C" { + + /// @see `iguana::clas12::MomentumCorrection::Transform` + /// @param [in] algo_idx the algorithm index + /// @param [in,out] px, py, pz the momentum; it will be corrected + /// @param [in] sec, pid, torus + void iguana_clas12_momentumcorrection_transform_( + algo_idx_t* algo_idx, + float* px, + float* py, + float* pz, + int* sec, + int* pid, + float* torus) + { + auto out = dynamic_cast(iguana_get_algo_(algo_idx))->Transform( + vector_element_t(*px), + vector_element_t(*py), + vector_element_t(*pz), + *sec, + *pid, + *torus); + *px = float(std::get<0>(out)); + *py = float(std::get<1>(out)); + *pz = float(std::get<2>(out)); + } + + } +} diff --git a/src/iguana/algorithms/clas12/ZVertexFilterBindings.cc b/src/iguana/algorithms/clas12/ZVertexFilterBindings.cc new file mode 100644 index 00000000..f1d18e89 --- /dev/null +++ b/src/iguana/algorithms/clas12/ZVertexFilterBindings.cc @@ -0,0 +1,17 @@ +#include "iguana/algorithms/AlgorithmBindings.h" +#include "ZVertexFilter.h" + +namespace iguana::bindings { + extern "C" { + + /// @see `iguana::clas12::ZVertexFilter::Filter` + /// @param [in] algo_idx the algorithm index + /// @param [in] vz + /// @param [in,out] out the return value + void iguana_clas12_zvertexfilter_filter_(algo_idx_t* algo_idx, float* vz, bool* out) + { + *out = *out && dynamic_cast(iguana_get_algo_(algo_idx))->Filter(*vz); + } + + } +} diff --git a/src/iguana/algorithms/meson.build b/src/iguana/algorithms/meson.build index c7554392..3236a569 100644 --- a/src/iguana/algorithms/meson.build +++ b/src/iguana/algorithms/meson.build @@ -4,9 +4,11 @@ # ======== # ALGORITHM_FULL_NAME: { # 'algorithm': { -# 'sources': LIST_OF_ALGORITHM_SOURCE FILES, -# 'headers': LIST_OF_ALGORITHM_HEADER_FILES, -# 'needs_ROOT': BOOLEAN, # whether this algorithm needs ROOT or not (default=false) +# 'sources': LIST_OF_ALGORITHM_SOURCE FILES, +# 'headers': LIST_OF_ALGORITHM_HEADER_FILES, +# 'binding_headers': LIST_OF_BINDING_HEADERS, # for Fortran +# 'binding_sources': LIST_OF_BINDING_SOURCES, # for Fortran +# 'needs_ROOT': BOOLEAN, # whether this algorithm needs ROOT or not (default=false), # }, # 'validator': { # if excluded, a validator test will not be built for this algorithm # 'sources': LIST_OF_VALIDATOR_SOURCE FILES, @@ -22,6 +24,8 @@ algo_dict = { 'algorithm': { 'sources': [ 'Algorithm.cc', 'AlgorithmFactory.cc', 'AlgorithmSequence.cc' ], 'headers': [ 'Algorithm.h', 'AlgorithmBoilerplate.h', 'TypeDefs.h', 'AlgorithmSequence.h' ], + 'binding_sources': [ 'AlgorithmBindings.cc' ], + 'binding_headers': [ 'AlgorithmBindings.h' ], }, 'validator': { 'sources': [ 'Validator.cc' ], @@ -40,6 +44,7 @@ algo_dict = { 'algorithm': { 'sources': [ 'clas12/EventBuilderFilter.cc' ], 'headers': [ 'clas12/EventBuilderFilter.h' ], + 'binding_sources': [ 'clas12/EventBuilderFilterBindings.cc' ], }, 'configs': [ 'clas12/EventBuilderFilter.yaml' ], 'test_args': { 'banks': ['REC::Particle'] }, @@ -48,6 +53,7 @@ algo_dict = { 'algorithm': { 'sources': [ 'clas12/ZVertexFilter.cc' ], 'headers': [ 'clas12/ZVertexFilter.h' ], + 'binding_sources': [ 'clas12/ZVertexFilterBindings.cc' ], }, 'configs': [ 'clas12/ZVertexFilter.yaml' ], 'test_args': { 'banks': ['REC::Particle'] }, @@ -73,6 +79,7 @@ algo_dict = { 'algorithm': { 'sources': [ 'clas12/MomentumCorrection.cc' ], 'headers': [ 'clas12/MomentumCorrection.h' ], + 'binding_sources': [ 'clas12/MomentumCorrectionBindings.cc' ], }, 'validator': { 'sources': [ 'clas12/MomentumCorrectionValidator.cc' ], @@ -85,6 +92,7 @@ algo_dict = { 'algorithm': { 'sources': [ 'physics/InclusiveKinematics.cc' ], 'headers': [ 'physics/InclusiveKinematics.h' ], + 'binding_sources': [ 'physics/InclusiveKinematicsBindings.cc' ], 'needs_ROOT': true, }, 'validator': { @@ -110,6 +118,10 @@ foreach name, info : algo_dict if (algo_needs_ROOT and ROOT_dep.found()) or not algo_needs_ROOT algo_sources += info_algo.get('sources', []) algo_headers += info_algo.get('headers', []) + if(get_option('bind_fortran')) + algo_sources += info_algo.get('binding_sources', []) + algo_headers += info_algo.get('binding_headers', []) + endif algo_configs += info.get('configs', []) else warning('Excluding algorithm "' + name + '", which depends on ROOT') diff --git a/src/iguana/algorithms/physics/InclusiveKinematics.cc b/src/iguana/algorithms/physics/InclusiveKinematics.cc index 0ec6c9dd..bfb8155c 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics.cc @@ -190,7 +190,7 @@ namespace iguana::physics { { InclusiveKinematicsVars result; - m_log->Debug("Reconstruct inclusive kinematics from lepton with p=({}, {}, {})", lepton_px, lepton_py, lepton_pz); + m_log->Trace("Reconstruct inclusive kinematics from lepton with p=({}, {}, {})", lepton_px, lepton_py, lepton_pz); ROOT::Math::PxPyPzMVector vec_beam(m_beam.px, m_beam.py, m_beam.pz, m_beam.mass); ROOT::Math::PxPyPzMVector vec_target(m_target.px, m_target.py, m_target.pz, m_target.mass); diff --git a/src/iguana/algorithms/physics/InclusiveKinematicsBindings.cc b/src/iguana/algorithms/physics/InclusiveKinematicsBindings.cc new file mode 100644 index 00000000..fa25bb94 --- /dev/null +++ b/src/iguana/algorithms/physics/InclusiveKinematicsBindings.cc @@ -0,0 +1,42 @@ +#include "iguana/algorithms/AlgorithmBindings.h" +#include "InclusiveKinematics.h" + +namespace iguana::bindings::physics { + extern "C" { + + /// @see `iguana::physics::InclusiveKinematics::ComputeFromLepton` + /// @param [in] algo_idx the algorithm index + /// @param [in] lepton_px, lepton_py, lepton_pz scattered lepton momentum + /// @param [out] qx, qy, qz, qE, Q2, x, y, W, nu inclusive kinematics + void iguana_physics_inclusivekinematics_computefromlepton_( + algo_idx_t* algo_idx, + float* lepton_px, + float* lepton_py, + float* lepton_pz, + double* qx, + double* qy, + double* qz, + double* qE, + double* Q2, + double* x, + double* y, + double* W, + double* nu) + { + auto out = dynamic_cast(iguana_get_algo_(algo_idx))->ComputeFromLepton( + vector_element_t(*lepton_px), + vector_element_t(*lepton_py), + vector_element_t(*lepton_pz)); + *qx = std::get<0>(out.q); + *qy = std::get<1>(out.q); + *qz = std::get<2>(out.q); + *qE = std::get<3>(out.q); + *Q2 = out.Q2; + *x = out.x; + *y = out.y; + *W = out.W; + *nu = out.nu; + } + + } +} diff --git a/src/iguana/services/ConfigFileReader.cc b/src/iguana/services/ConfigFileReader.cc index 42ffba50..18cc9bdc 100644 --- a/src/iguana/services/ConfigFileReader.cc +++ b/src/iguana/services/ConfigFileReader.cc @@ -67,14 +67,6 @@ namespace iguana { throw std::runtime_error("configuration file not found"); } - std::string ConfigFileReader::DirName(std::string_view name) - { - auto result = std::filesystem::path{name}.parent_path().string(); - if(result == "") - result = "."; - return result; - } - std::string ConfigFileReader::ConvertAlgoNameToConfigName(std::string_view algo_name, std::string_view ext) { std::string result = std::string(algo_name); diff --git a/src/iguana/services/ConfigFileReader.h b/src/iguana/services/ConfigFileReader.h index dad9b154..e8349a81 100644 --- a/src/iguana/services/ConfigFileReader.h +++ b/src/iguana/services/ConfigFileReader.h @@ -39,12 +39,6 @@ namespace iguana { /// @return the found configuration file (with the directory) std::string FindFile(std::string const& name); - /// Return the directory containing a file, by stripping the last - /// component of a file name, similarly to the `dirname` command. - /// @param name the file name - /// @return the parent directory name - static std::string DirName(std::string_view name); - /// Convert a full algorithm name to its corresponding default config file name /// @param algo_name the algorithm name /// @param ext the file extension