diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37c87e41..b806a40a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,6 +234,14 @@ jobs: source .venv/bin/activate echo PATH=$PATH >> $GITHUB_ENV python -m pip install -r iguana_src/bind/python/requirements.txt + ###### install doxygen + - name: install doxygen + if: ${{ matrix.id == 'documentation' }} + run: | + pacman -S --noconfirm doxygen graphviz + echo "INSTALLED VERSIONS:" + echo "doxygen: $(doxygen --version)" + echo "$(dot --version)" ###### hipo - name: get `hipo-withROOT` build if: ${{ matrix.id != 'noROOT' }} @@ -275,11 +283,6 @@ jobs: ### build - name: meson setup run: | - if [ "${{ matrix.id }}" = "cpp" ]; then - validate_all="true" # 'cpp' job will upload validator artifacts, so use all statistics - else - validate_all="false" - fi meson setup iguana_build iguana_src \ --prefix=$(pwd)/iguana \ --pkg-config-path=$(pwd)/hipo/lib/pkgconfig \ @@ -288,7 +291,6 @@ jobs: -Dtest_data_file=$(pwd)/test_data.hipo \ -Dtest_num_events=${{ env.num_events }} \ -Dtest_output_dir=$(pwd)/validation_results \ - -Dtest_validator_all_stats=${validate_all} \ ${{ matrix.opts }} - name: dump build options run: meson configure iguana_build --no-pager @@ -398,47 +400,34 @@ jobs: echo "========================================= TEST RUN =========================================" install_consumer_cmake/bin/iguana_ex_cpp_00_run_functions test_data.hipo 10 ### upload artifacts - - uses: actions/upload-artifact@v4 + - name: upload build log artifacts + uses: actions/upload-artifact@v4 if: always() with: name: logs_iguana_build_${{ matrix.id }} retention-days: 5 path: iguana_build/meson-logs - - uses: actions/upload-artifact@v4 + - name: upload coverage artifacts + uses: actions/upload-artifact@v4 if: ${{ matrix.id == 'coverage' }} with: name: coverage-report retention-days: 5 path: coverage-report - - uses: actions/upload-artifact@v4 + - name: upload validator artifacts + uses: actions/upload-artifact@v4 if: ${{ matrix.id == 'cpp' }} with: name: _validation_results retention-days: 5 path: validation_results - - # documentation - ######################################################### - - doc_generate: - if: ${{ inputs.id == 'linux-latest' }} - name: Generate documentation - runs-on: ${{ inputs.runner }} - container: - image: ${{ inputs.container }} - steps: - - uses: actions/checkout@v4 - - name: install dependencies - run: | - pacman -Syu --noconfirm - pacman -S --noconfirm doxygen graphviz - - name: doxygen - run: doxygen doc/gen/Doxyfile - - uses: actions/upload-artifact@v4 + - name: upload documentation artifacts + uses: actions/upload-artifact@v4 + if: ${{ matrix.id == 'documentation' }} with: name: doc_doxygen retention-days: 5 - path: doc/api/ + path: iguana/share/doc/iguana/html/ # deployment ######################################################### @@ -447,7 +436,6 @@ jobs: if: ${{ github.ref == 'refs/heads/main' && inputs.id == 'linux-latest' }} name: Collect webpages needs: - - doc_generate - iguana runs-on: ${{ inputs.runner }} steps: diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0e2d426b..2140651b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,6 +24,7 @@ jobs: "id": [ "cpp", "coverage", + "documentation", "address-sanitizer", "thread-sanitizer", "undefined-sanitizer", @@ -33,8 +34,9 @@ jobs: "fortran" ], "include": [ - { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true" }, + { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dtest_validator_all_stats=true" }, { "id": "coverage", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Db_coverage=true" }, + { "id": "documentation", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dinstall_documentation=true" }, { "id": "address-sanitizer", "CC": "clang", "CXX": "clang++", "opts": "-Dbuildtype=debug -Dz_require_root=true -Db_sanitize=address -Db_lundef=false -Db_pie=true" }, { "id": "thread-sanitizer", "CC": "clang", "CXX": "clang++", "opts": "-Dbuildtype=debug -Dz_require_root=true -Db_sanitize=thread -Db_lundef=false -Db_pie=true" }, { "id": "undefined-sanitizer", "CC": "clang", "CXX": "clang++", "opts": "-Dbuildtype=debug -Dz_require_root=true -Db_sanitize=undefined -Db_lundef=false -Db_pie=true" }, diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1dbd8136..ed7a56c1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: "fortran" ], "include": [ - { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true" }, + { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dtest_validator_all_stats=true" }, { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=false" }, { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dbind_python=true" }, { "id": "fortran", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dbind_fortran=true" } diff --git a/.github/workflows/minver.yml b/.github/workflows/minver.yml index e9243cea..07daf82a 100644 --- a/.github/workflows/minver.yml +++ b/.github/workflows/minver.yml @@ -27,7 +27,7 @@ jobs: "fortran" ], "include": [ - { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true" }, + { "id": "cpp", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dtest_validator_all_stats=true" }, { "id": "noROOT", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=false" }, { "id": "python", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dbind_python=true" }, { "id": "fortran", "CC": "gcc", "CXX": "g++", "opts": "-Dbuildtype=release -Dz_require_root=true -Dbind_fortran=true" } diff --git a/.gitignore b/.gitignore index 3b4989e6..350e856f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,8 @@ /share /.cache -# documentation artifacts -/doc/api +# chameleon artifacts +chameleon-tree # data files *.hipo diff --git a/bind/python/iguana_ex_python_01_action_functions.py b/bind/python/iguana_ex_python_01_action_functions.py index 39fa5f9d..34eebcda 100755 --- a/bind/python/iguana_ex_python_01_action_functions.py +++ b/bind/python/iguana_ex_python_01_action_functions.py @@ -54,7 +54,7 @@ # it requires reading full `hipo::bank` objects, whereas this example is meant to demonstrate # `iguana` usage operating _only_ on bank row elements - px, py, pz, = algo_momentum_correction.Transform( + p_corrected = algo_momentum_correction.Transform( particleBank.getFloat("px", row), particleBank.getFloat("py", row), particleBank.getFloat("pz", row), @@ -65,7 +65,7 @@ print(f'Accepted PID {pid}:') print(f' p_old = ({particleBank.getFloat("px", row)}, {particleBank.getFloat("py", row)}, {particleBank.getFloat("pz", row)})') - print(f' p_new = ({px}, {py}, {pz})') + print(f' p_new = ({p_corrected.px}, {p_corrected.py}, {p_corrected.pz})') algo_eventbuilder_filter.Stop() algo_momentum_correction.Stop() diff --git a/bind/python/iguana_ex_python_hipopy.py b/bind/python/iguana_ex_python_hipopy.py index 3622434c..a38e0fa5 100755 --- a/bind/python/iguana_ex_python_hipopy.py +++ b/bind/python/iguana_ex_python_hipopy.py @@ -55,7 +55,7 @@ # it requires reading full `hipo::bank` objects, whereas this example is meant to demonstrate # `iguana` usage operating _only_ on bank row elements - px, py, pz, = algo_momentum_correction.Transform( + p_corrected = algo_momentum_correction.Transform( batch['REC::Particle_px'][iEvent][row], batch['REC::Particle_py'][iEvent][row], batch['REC::Particle_pz'][iEvent][row], @@ -66,7 +66,7 @@ print(f'Accepted PID {pid}:') print(f' p_old = ({batch["REC::Particle_px"][iEvent][row]}, {batch["REC::Particle_py"][iEvent][row]}, {batch["REC::Particle_pz"][iEvent][row]})') - print(f' p_new = ({px}, {py}, {pz})') + print(f' p_new = ({p_corrected.px}, {p_corrected.py}, {p_corrected.pz})') # End iteration if maximum number of batches reached if (iBatch>=nbatches): break diff --git a/bind/python/meson.build b/bind/python/meson.build index ae6d8a2a..0c4fa619 100644 --- a/bind/python/meson.build +++ b/bind/python/meson.build @@ -1,3 +1,4 @@ +pythondir = 'python' project_pkg_vars += 'pythonpath=' + '${prefix}' / pythondir install_subdir('pyiguana', install_dir: pythondir) diff --git a/doc/gen/Doxyfile b/doc/gen/Doxyfile.in similarity index 99% rename from doc/gen/Doxyfile rename to doc/gen/Doxyfile.in index 098e9c92..e887557a 100644 --- a/doc/gen/Doxyfile +++ b/doc/gen/Doxyfile.in @@ -48,7 +48,7 @@ PROJECT_NAME = "Iguana" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = @version@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -68,7 +68,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = @top_builddir@/doc/gen # If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format @@ -832,7 +832,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = doc/gen/DoxygenLayout.xml +LAYOUT_FILE = @top_srcdir@/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 @@ -853,7 +853,7 @@ CITE_BIB_FILES = # messages are off. # The default value is: NO. -QUIET = NO +QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES @@ -960,11 +960,12 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = src/ \ - bind/ \ - doc/gen/ \ - doc/gen/mainpage.md \ - examples/ +INPUT = @top_srcdir@/src/ \ + @top_builddir@/src/iguana/algorithms \ + @top_srcdir@/bind/ \ + @top_srcdir@/doc/gen/ \ + @top_srcdir@/doc/gen/mainpage.md \ + @top_srcdir@/examples/ # 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 @@ -1006,9 +1007,9 @@ INPUT_FILE_ENCODING = FILE_PATTERNS = *.dox \ *.h \ + chameleon_*_bind.cc \ iguana_ex_*.cc \ iguana_ex_*.py \ - Bindings.cc \ *.f \ *.C @@ -1025,8 +1026,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = src/iguana/algorithms/Bindings.cc \ - src/iguana/algorithms/clas12/FiducialFilter/Pass1CutData.h +EXCLUDE = @top_srcdir@/src/iguana/algorithms/clas12/FiducialFilter/Pass1CutData.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1137,7 +1137,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/gen/mainpage.md +USE_MDFILE_AS_MAINPAGE = @top_srcdir@/doc/gen/mainpage.md # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common @@ -1269,7 +1269,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = doc/api +HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). @@ -2453,7 +2453,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = YES +HAVE_DOT = @have_dot@ # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of diff --git a/doc/gen/fortran.dox b/doc/gen/fortran.dox index acd498f2..83e778f0 100644 --- a/doc/gen/fortran.dox +++ b/doc/gen/fortran.dox @@ -20,8 +20,8 @@ Iguana provides a set of C functions which are callable from Fortran: - `[in,out]`: both an input and an output parameter, will be mutated @par -The available C functions and their documentation, organized by C++ namespace, are listed below -in the **Namespaces** section. +The available C functions and their documentation, organized by C++ namespace, are listed +at **the bottom of this page** in the **Namespaces** section. @par For information about each algorithm and their action functions, see: diff --git a/doc/gen/mainpage.md b/doc/gen/mainpage.md index 7b5a5dc3..2159bb17 100644 --- a/doc/gen/mainpage.md +++ b/doc/gen/mainpage.md @@ -1,9 +1,3 @@ - - # Iguana User's Guide This documentation shows how to use the Iguana algorithms. @@ -18,6 +12,11 @@ To see Iguana algorithms used in the context of analysis code, with **various la **NOTE:** If you're not familiar with Iguana, please read the sections below first. +## Language Bindings + +Iguana algorithms are in C++; to use Iguana with other languages, see: +- \ref fortran_usage_guide + ## Algorithms An Iguana algorithm is a function that maps input HIPO bank data to output data. The available algorithms are: diff --git a/doc/gen/meson.build b/doc/gen/meson.build new file mode 100644 index 00000000..0626ecff --- /dev/null +++ b/doc/gen/meson.build @@ -0,0 +1,25 @@ +prog_doxygen = find_program('doxygen') +prog_dot = find_program('dot', required: false) + +doxyfile = configure_file( + input: 'Doxyfile.in', + output: 'Doxyfile', + install: false, + configuration: { + 'version': meson.project_version(), + 'have_dot': prog_dot.found() ? 'YES' : 'NO', + 'top_srcdir': meson.project_source_root(), + 'top_builddir': meson.project_build_root(), + }, +) + +doc_tgt = custom_target( + 'documentation', + input: doxyfile, + output: 'html', + build_always_stale: true, + build_by_default: true, + command: [ prog_doxygen, doxyfile ], + install: true, + install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()), +) diff --git a/doc/troubleshooting.md b/doc/troubleshooting.md index 52b88796..469767dd 100644 --- a/doc/troubleshooting.md +++ b/doc/troubleshooting.md @@ -28,3 +28,13 @@ Then rebuild (`meson compile` and/or `meson install`). Remember to revert this change and rebuild/re-install, so that Iguana runs with full optimization when you are processing large data sets (`-Dbuildtype=release`). + +### 🔵 I got some error about "chameleon", or an error in some "chameleon" file that I can't find + +[Chameleon is a code generator](/src/chameleon) to automatically create +`iguana` bindings for programming languages other than C++. All generated code +is produced in your build directory. If you have issues with Chameleon, either: +- an `Action.yaml` file is not correct +- something is wrong with `chameleon` + +In either case, open an issue or contact the maintainers. diff --git a/examples/iguana_ex_fortran_01_action_functions.f b/examples/iguana_ex_fortran_01_action_functions.f index 03f05158..c019aaeb 100644 --- a/examples/iguana_ex_fortran_01_action_functions.f +++ b/examples/iguana_ex_fortran_01_action_functions.f @@ -219,7 +219,8 @@ program iguana_ex_fortran_01_action_functions call iguana_clas12_momentumcorrection_transform( & algo_mom_cor, & px(i), py(i), pz(i), - & sector(i), pid(i), torus(1)) + & sector(i), pid(i), torus(1), + & px(i), py(i), pz(i)) print *, ' after: p = (', px(i), py(i), pz(i), ')' endif enddo diff --git a/meson.build b/meson.build index f830d6e4..780cef9d 100644 --- a/meson.build +++ b/meson.build @@ -168,17 +168,35 @@ add_project_arguments( language: [ 'cpp' ], ) +# start chameleon +use_chameleon = get_option('bind_fortran') +if use_chameleon + subdir('src/chameleon') +endif + # build and install shared libraries subdir('src/iguana/services') subdir('src/iguana/algorithms') subdir('src/iguana/tests') -# build bindings -pythondir = 'python' +# build and install bindings +if use_chameleon + subdir('src/iguana/bindings') +endif if get_option('bind_python') subdir('bind/python') endif +# build examples +if get_option('install_examples') + subdir('examples') +endif + +# generate documentation +if get_option('install_documentation') + subdir('doc/gen') +endif + # generate pkg-config file project_pkg_vars_nonempty = [] foreach var : project_pkg_vars @@ -194,11 +212,6 @@ pkg.generate( variables: project_pkg_vars_nonempty, ) -# build examples -if get_option('install_examples') - subdir('examples') -endif - # install environment setup files if get_option('z_install_envfile') configure_file( diff --git a/meson.options b/meson.options index a419812e..de851cf0 100644 --- a/meson.options +++ b/meson.options @@ -3,7 +3,8 @@ option('bind_fortran', type: 'boolean', value: false, description: 'Install Fort option('bind_python', type: 'boolean', value: false, description: 'Install Python bindings') # optional installations: install additional objects, such as example executables -option('install_examples', type: 'boolean', value: false, description: 'Install examples') +option('install_examples', type: 'boolean', value: false, description: 'Install examples') +option('install_documentation', type: 'boolean', value: false, description: 'Install documentation; requires `doxygen`') # test options: control how tests are run option('test_data_file', type: 'string', value: '', description: 'Sample HIPO file for testing. Must be an absolute path.') diff --git a/src/chameleon/README.md b/src/chameleon/README.md new file mode 100644 index 00000000..bc995fc2 --- /dev/null +++ b/src/chameleon/README.md @@ -0,0 +1,26 @@ +# Chameleon + +Chameleon is a code generator. Given a `yaml` action function specification, +a file named `Action.yaml` in an algorithm's source code directory, Chameleon +generates: + +| Language | Bindings | Tests | +| --- | --- | --- | +| Fortran | :white_check_mark: | :x: | +| Java | :x: | :x: | +| Python | :x: | :x: | + +where +- :white_check_mark: means fully supported (as long as the algorithm has an `Action.yaml` file) +- :x: means this is not _yet_ supported + +The main script is [`chameleon`](chameleon). For usage, run +```bash +chameleon --help +``` +There is no need to run `chameleon` yourself unless you are developing or debugging it. +`chameleon` is run automatically by `meson` at build time and all generated +code is produced _within_ the _build directory_ as: +``` +src/iguana/algorithms/chameleon_* +``` diff --git a/src/chameleon/chameleon b/src/chameleon/chameleon new file mode 100755 index 00000000..8b04a913 --- /dev/null +++ b/src/chameleon/chameleon @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby + +require 'yaml' +require 'optparse' +require 'ostruct' +require 'fileutils' + +require_relative 'src/bind_c.rb' + +# default options +@args = OpenStruct.new +@args.action_yaml = '' +@args.out_dir = '' +@args.out_prefix = '' +@args.verbose = false +@args.quiet = false + +# parse arguments +OptionParser.new do |o| + o.banner = "USAGE: #{$0} [OPTIONS]..." + o.separator '' + o.separator 'OPTIONS:' + o.on('-i', '--input [ACTION_YAML]', 'action function specification YAML') {|a| @args.action_yaml = a} + o.separator '' + o.on('-o', '--output [OUTPUT_DIR]', 'output directory', 'default: a subdirectory of ./chameleon-tree') {|a| @args.out_dir = a} + o.on('-p', '--prefix [PREFIX]', 'if specified, each generated filename will start with [PREFIX]') {|a| @args.out_prefix = a} + o.separator '' + o.on('-v', '--verbose', 'print more output') {|a| @args.verbose = true} + o.on('-q', '--quiet', 'print no output (unless --verbose)') {|a| @args.quiet = true} + o.on_tail('-h', '--help', 'show this message') do + puts o + exit + end +end.parse! ARGV +VERBOSE = @args.verbose +ACTION_YAML = @args.action_yaml + +# start main generator +@main = Generator.new +@main.verbose "OPTIONS: #{@args}" + +# open action function specification +@main.error "option '--input' needs a value; use '--help' for guidance" if ACTION_YAML.empty? +@main.verbose "parsing #{ACTION_YAML}" +@main.error "input file '#{ACTION_YAML}' does not exist" unless File.exists? ACTION_YAML +main_spec = YAML.load_file ACTION_YAML + +# parse algorithm info +algo_name = @main.get_spec main_spec, 'algorithm', 'name' +@main.verbose "algorithm name: #{algo_name}" + +# make output files +def out_name(name) + [ @args.out_prefix, name ].reject(&:empty?).join '_' +end +algo_dir = File.dirname ACTION_YAML +@main.error "directory '#{algo_dir}' does not exist" unless Dir.exists? algo_dir +out_dir = @args.out_dir.empty? ? File.join('chameleon-tree', algo_dir) : @args.out_dir +FileUtils.mkdir_p out_dir, verbose: @args.verbose +out_files = { + :c_bindings => Bind_c.new(File.join(out_dir, out_name('bind.cc')), algo_name), +} + +# parse action functions +@main.get_spec(main_spec, 'actions').each do |action_spec| + out_files[:c_bindings].bind action_spec +end + +# print list of generated files +unless @args.quiet + puts "[chameleon] Generated files:" + out_files.each do |key,file| + puts "[chameleon] #{key} => #{file.out_name}" + end +end + +# close all generated files +out_files.values.each &:close diff --git a/src/chameleon/meson.build b/src/chameleon/meson.build new file mode 100644 index 00000000..44163b94 --- /dev/null +++ b/src/chameleon/meson.build @@ -0,0 +1,8 @@ +chameleon_sources = files( + 'chameleon', + 'src' / 'generator.rb', + 'src' / 'bind_c.rb', +) + +ruby = find_program('ruby', version: '>=3.0.0', required: use_chameleon) +chameleon_gen = find_program(chameleon_sources[0], required: use_chameleon) diff --git a/src/chameleon/src/bind_c.rb b/src/chameleon/src/bind_c.rb new file mode 100644 index 00000000..446459ce --- /dev/null +++ b/src/chameleon/src/bind_c.rb @@ -0,0 +1,126 @@ +require_relative 'generator' + +class Bind_c < Generator + + ################################################################################## + + def initialize(out_name='', algo_name='') + super(out_name, algo_name, description: 'C bindings', generator_name: __FILE__) + @out.puts <<~END_CODE + /// @file #{File.basename out_name} + /// @brief C functions to provide `iguana::#{@algo_name}` action function bindings for Fortran + + #{deterrence_banner 'c'} + + #include "iguana/algorithms/#{@algo_header}" + #include "iguana/bindings/Bindings.h" + + namespace iguana::bindings::#{@algo_name.split('::').first} { + extern "C" { + END_CODE + end + + ################################################################################## + + def bind(spec) + + # get function name and type + @action_spec = spec + @ftn_type = get_spec @action_spec, 'type' + ftn_name = get_spec @action_spec, 'name' + ftn_name_fortran = "iguana_#{@algo_name.downcase.gsub /::/, '_'}_#{ftn_name.downcase}" + ftn_name_c = "#{ftn_name_fortran}_" + verbose " - bind #{@ftn_type} function #{@algo_name}::#{ftn_name}" + check_function_type "#{@algo_name}::#{ftn_name}", @ftn_type + + # function to generate parameter docstrings + def gen_docstring_params(key, in_or_out) + map_spec_params(@action_spec, key) do |name, type, cast| + if name == RESULT_VAR + "[#{in_or_out}] #{name} the resulting value" + else + "[#{in_or_out}] #{name}" + end + end + end + + # function to generate C function parameters + def gen_ftn_params(key) + map_spec_params(@action_spec, key) do |name, type, cast| + "#{cast.empty? ? type : cast}* #{name}" + end + end + + # function to generate C++ function arguments + def gen_call_args(key) + map_spec_params(@action_spec, key) do |name, type, cast| + return nil if name == RESULT_VAR + cast.empty? ? "*#{name}" : "#{type}(*#{name})" + end + end + + # function to generate result assignments + def gen_assignments(key) + map_spec_params(@action_spec, key) do |name, type, cast| + out_var = name == RESULT_VAR ? + 'out' : + "out.#{name.sub /^#{RESULT_VAR}_/, ''}" + out_var = "#{cast}(#{out_var})" unless cast.empty? + "*#{name} = #{out_var};" + end + end + + # generate parts lists + docstring_param_list = ['[in] algo_idx the algorithm index'] + par_list = ['algo_idx_t* algo_idx'] + call_arg_list = [] + assignment_list = [] + case @ftn_type + when 'filter' + docstring_param_list += gen_docstring_params 'inputs', 'in' + docstring_param_list << "[in,out] #{RESULT_VAR} the filter return value; if this value is already set, the result will be the `AND` of the initial value and this filter" + par_list += gen_ftn_params 'inputs' + par_list << "bool* #{RESULT_VAR}" + call_arg_list += gen_call_args 'inputs' + assignment_list << "*#{RESULT_VAR} = *#{RESULT_VAR} && out;" + when 'creator', 'transformer' + docstring_param_list += gen_docstring_params 'inputs', 'in' + docstring_param_list += gen_docstring_params 'outputs', 'out' + par_list += gen_ftn_params 'inputs' + par_list += gen_ftn_params 'outputs' + call_arg_list += gen_call_args 'inputs' + assignment_list += gen_assignments 'outputs' + end + + # generate code + @out.puts <<~END_CODE.gsub(/^/,' ') + + /// This function is for Fortan usage, called as + /// ```fortran + /// call #{ftn_name_fortran}(algo_idx, ...params...) + /// ``` + /// It binds to the following C++ action function, wherein you can find its documentation: + /// - `iguana::#{@algo_name}::#{ftn_name}` + #{docstring_param_list.map{|s|"/// @param #{s}"}.join "\n"} + void #{ftn_name_c}( + #{par_list.join ', '}) + { + auto out = dynamic_cast(iguana_get_algo_(algo_idx))->#{ftn_name}( + #{call_arg_list.join ', '}); + #{assignment_list.join "\n "} + } + END_CODE + + end + + ################################################################################## + + def close + @out.puts <<~END_CODE + } + } + END_CODE + @out.close + end + +end diff --git a/src/chameleon/src/generator.rb b/src/chameleon/src/generator.rb new file mode 100644 index 00000000..4633394c --- /dev/null +++ b/src/chameleon/src/generator.rb @@ -0,0 +1,94 @@ +class Generator + + RESULT_VAR = 'result' # the name of the result of an action function call + + def initialize(out_name='', algo_name='', description: '', generator_name: '') + @out_name = out_name + @out_dir = File.dirname @out_name + @algo_name = algo_name + @algo_header = File.join *@algo_name.split('::'), 'Algorithm.h' + @log_tag = generator_name.empty? ? "[chameleon]" : "[chameleon::#{File.basename generator_name, '.rb'}]" + @ftn_type = nil + unless out_name.empty? + verbose "generating #{description} '#{@out_name}'" + @out = File.open @out_name, 'w' + end + end + + # accessors/modifiers + attr_accessor :out_name + + # print an error and exit + def error(msg, quit=true) + $stderr.puts "#{@log_tag} [ERROR]: #{msg}" + exit 1 if quit + end + + # print a log message + def verbose(msg) + puts "#{@log_tag} #{msg}" if VERBOSE + end + + # get a specification (a yaml node value) + def get_spec(node, *path, default: nil) + def get_node(node, path, full_path, default) + return node if path.empty? + key = path.shift + unless node.has_key? key + return default unless default.nil? + error "failed to find node '#{full_path.join ' : '}' in #{ACTION_YAML}", false unless node.has_key? key + error "this happened when searching the following sub-tree:", false + $stderr.puts "#{node.to_yaml}" + $stderr.puts '---' + error "please check this YAML file to see if something is missing or if you have a typo." + end + get_node node[key], path, full_path, default + end + get_node node, path.clone, path.clone, default + end + + # let `spec_params` be a specification tree of action function parameters, + # found at `node[key]`. This method "maps" its elements (i.e., + # "spec_params.map") by yielding a block with parameters |name, type, cast| + def map_spec_params(node, key) + spec_params = get_spec node, key + spec_params.map do |var| + name = '' + if key == 'outputs' and spec_params.size == 1 and !var.has_key?('name') # single, anonymous output + name = RESULT_VAR + else + name = get_spec var, 'name' + error "don't name your variable '#{name}', since this name is reserved" if name == RESULT_VAR + name = "#{RESULT_VAR}_#{name}" if @ftn_type == 'transformer' and key == 'outputs' + end + type = get_spec var, 'type' + cast = get_spec var, 'cast', default: '' + yield name, type, cast + end + .compact + end + + # check if the function type is known + def check_function_type(name, type) + error("action function '#{name}' has unknown type type '#{type}'") unless ['filter', 'transformer', 'creator'].include? type + end + + # deter developers from editting the generated files + def deterrence_banner(lang) + case lang + when 'c' + result = <<~EOS + // ===================================================== + // WARNING: DO NOT EDIT THIS FILE + // - it has been generated by chameleon + // - if you really want to edit this file, consider + // instead editting chameleon itself + // ===================================================== + EOS + else + error "deterrence_banner does not know the language '#{lang}'" + end + result + end + +end diff --git a/src/iguana/algorithms/TypeDefs.h b/src/iguana/algorithms/TypeDefs.h index b3e1ffb6..d97e4e08 100644 --- a/src/iguana/algorithms/TypeDefs.h +++ b/src/iguana/algorithms/TypeDefs.h @@ -3,20 +3,34 @@ #pragma once #include -#include #include namespace iguana { /// Vector element type using vector_element_t = double; - /// 2-vector container type - using vector2_t = std::tuple; - /// 3-vector container type - using vector3_t = std::tuple; - /// 4-vector container type - using vector4_t = std::tuple; + /// 3-momentum type + struct Momentum3 { + /// @f$x@f$-component + vector_element_t px; + /// @f$y@f$-component + vector_element_t py; + /// @f$z@f$-component + vector_element_t pz; + }; + + /// 4-momentum type + struct Momentum4 { + /// @f$x@f$-component + vector_element_t px; + /// @f$y@f$-component + vector_element_t py; + /// @f$z@f$-component + vector_element_t pz; + /// @f$E@f$-component + vector_element_t E; + }; /// Light-weight namespace for particle constants namespace particle { diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Action.yaml b/src/iguana/algorithms/clas12/EventBuilderFilter/Action.yaml new file mode 100644 index 00000000..b928a92b --- /dev/null +++ b/src/iguana/algorithms/clas12/EventBuilderFilter/Action.yaml @@ -0,0 +1,9 @@ +algorithm: + name: 'clas12::EventBuilderFilter' + +actions: + - name: Filter + type: filter + inputs: + - name: pid + type: int diff --git a/src/iguana/algorithms/clas12/EventBuilderFilter/Bindings.cc b/src/iguana/algorithms/clas12/EventBuilderFilter/Bindings.cc deleted file mode 100644 index f247b20f..00000000 --- a/src/iguana/algorithms/clas12/EventBuilderFilter/Bindings.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "Algorithm.h" -#include "iguana/algorithms/Bindings.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/FTEnergyCorrection/Action.yaml b/src/iguana/algorithms/clas12/FTEnergyCorrection/Action.yaml new file mode 100644 index 00000000..af58d195 --- /dev/null +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Action.yaml @@ -0,0 +1,43 @@ +algorithm: + name: 'clas12::FTEnergyCorrection' + +actions: + + - name: Transform + type: transformer + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: E + type: double + cast: float + outputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: E + type: double + cast: float + + - name: CorrectEnergy + type: transformer + inputs: + - name: E + type: double + cast: float + outputs: + - type: double + cast: float diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc index 574d8142..124c54b9 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.cc @@ -26,7 +26,7 @@ namespace iguana::clas12 { ShowBank(ftParticleBank, Logger::Header("OUTPUT FT PARTICLES")); } - vector4_t FTEnergyCorrection::Transform( + Momentum4 FTEnergyCorrection::Transform( vector_element_t const px, vector_element_t const py, vector_element_t const pz, diff --git a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h index 30f9ac25..acf82f98 100644 --- a/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/FTEnergyCorrection/Algorithm.h @@ -30,7 +30,7 @@ namespace iguana::clas12 { /// @param E @f$E@f$ /// @returns an electron 4-vector with the corrected energy for the Forward Tagger. /// @see `FTEnergyCorrection::CorrectEnergy` - vector4_t Transform( + Momentum4 Transform( vector_element_t const px, vector_element_t const py, vector_element_t const pz, diff --git a/src/iguana/algorithms/clas12/LorentzTransformer/Action.yaml b/src/iguana/algorithms/clas12/LorentzTransformer/Action.yaml new file mode 100644 index 00000000..d8e511a9 --- /dev/null +++ b/src/iguana/algorithms/clas12/LorentzTransformer/Action.yaml @@ -0,0 +1,39 @@ +algorithm: + name: 'clas12::LorentzTransformer' + +actions: + + - name: Boost + type: transformer + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: E + type: double + cast: float + - name: beta_x + type: double + - name: beta_y + type: double + - name: beta_z + type: double + outputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: E + type: double + cast: float diff --git a/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.cc b/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.cc index 492539a5..f4091ef3 100644 --- a/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.cc +++ b/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.cc @@ -41,7 +41,7 @@ namespace iguana::clas12 { // boosts if(m_transformation_type == e_boost) { // set the boost vector - vector3_t boost_vec; + std::tuple boost_vec; if(o_frame == "beam_rest_frame") { boost_vec = {0, 0, o_beam_energy / std::hypot(o_beam_energy, 0.000511)}; } @@ -64,17 +64,17 @@ namespace iguana::clas12 { } - vector4_t LorentzTransformer::Boost( - vector_element_t const p_x, - vector_element_t const p_y, - vector_element_t const p_z, + Momentum4 LorentzTransformer::Boost( + vector_element_t const px, + vector_element_t const py, + vector_element_t const pz, vector_element_t const E, vector_element_t const beta_x, vector_element_t const beta_y, vector_element_t const beta_z) const { m_log->Debug(fmt::format("{::<30}", "Boost ")); - m_log->Debug(fmt::format("{:>8} = ({:10f}, {:10f}, {:10f}, {:10f})", "p_in", p_x, p_y, p_z, E)); + m_log->Debug(fmt::format("{:>8} = ({:10f}, {:10f}, {:10f}, {:10f})", "p_in", px, py, pz, E)); // check if |beta| <= 1 auto beta_mag = std::hypot(beta_x, beta_y, beta_z); @@ -82,11 +82,11 @@ namespace iguana::clas12 { m_log->Error("attempt to boost with beta > 1 (faster than the speed of light); will NOT boost this momentum"); m_log->Debug("{:>8} = {}", "|beta|", beta_mag); m_log->Debug("{:>8} = ({:10f}, {:10f}, {:10f})", "beta", beta_x, beta_y, beta_z); - return {p_x, p_y, p_z, E}; + return {px, py, pz, E}; } // boost - ROOT::Math::PxPyPzEVector p_in(p_x, p_y, p_z, E); + ROOT::Math::PxPyPzEVector p_in(px, py, pz, E); ROOT::Math::Boost beta(beta_x, beta_y, beta_z); auto p_out = beta(p_in); diff --git a/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.h b/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.h index f8014692..656ba57d 100644 --- a/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.h +++ b/src/iguana/algorithms/clas12/LorentzTransformer/Algorithm.h @@ -32,18 +32,18 @@ namespace iguana::clas12 { void Stop() override; /// @action_function{scalar transformer} boost the 4-momentum @f$p=(p_x,p_y,p_z,E)@f$ along @f$\beta=(\beta_x, \beta_y, \beta_z)@f$ - /// @param p_x @f$p_x@f$ - /// @param p_y @f$p_y@f$ - /// @param p_z @f$p_z@f$ + /// @param px @f$p_x@f$ + /// @param py @f$p_y@f$ + /// @param pz @f$p_z@f$ /// @param E @f$E@f$ /// @param beta_x @f$\beta_x@f$ /// @param beta_y @f$\beta_y@f$ /// @param beta_z @f$\beta_z@f$ /// @returns the transformed momentum - vector4_t Boost( - vector_element_t const p_x, - vector_element_t const p_y, - vector_element_t const p_z, + Momentum4 Boost( + vector_element_t const px, + vector_element_t const py, + vector_element_t const pz, vector_element_t const E, vector_element_t const beta_x, vector_element_t const beta_y, diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Action.yaml b/src/iguana/algorithms/clas12/MomentumCorrection/Action.yaml new file mode 100644 index 00000000..8d68ba67 --- /dev/null +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Action.yaml @@ -0,0 +1,105 @@ +algorithm: + name: 'clas12::MomentumCorrection' + +actions: + + - name: Transform + type: transformer + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: sec + type: int + - name: pid + type: int + - name: torus + type: float + outputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + + - name: CorrectionInbending + type: creator + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: sec + type: int + - name: pid + type: int + outputs: + - type: double + + - name: CorrectionOutbending + type: creator + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: sec + type: int + - name: pid + type: int + outputs: + - type: double + + - name: EnergyLossInbending + type: creator + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: pid + type: int + outputs: + - type: double + + - name: EnergyLossOutbending + type: creator + inputs: + - name: px + type: double + cast: float + - name: py + type: double + cast: float + - name: pz + type: double + cast: float + - name: pid + type: int + outputs: + - type: double diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc index db5e5ff9..233eb6cf 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.cc @@ -40,7 +40,7 @@ namespace iguana::clas12 { } - vector3_t MomentumCorrection::Transform(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid, float const torus) const + Momentum3 MomentumCorrection::Transform(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid, float const torus) const { // energy loss correction auto e_cor = torus < 0 @@ -58,7 +58,7 @@ namespace iguana::clas12 { } - double MomentumCorrection::CorrectionInbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const sec, int const pid) const + double MomentumCorrection::CorrectionInbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid) const { // skip the correction if it's not defined @@ -66,13 +66,13 @@ namespace iguana::clas12 { return 1.0; // Momentum Magnitude - double pp = sqrt(Px * Px + Py * Py + Pz * Pz); + double pp = sqrt(px * px + py * py + pz * pz); // Initializing the correction factor double dp = 0; // Defining Phi Angle - double Phi = (180 / M_PI) * atan2(Py, Px); + double Phi = (180 / M_PI) * atan2(py, px); // (Initial) Shift of the Phi Angle (done to realign sectors whose data is separated when plotted from ±180˚) if(((sec == 4 || sec == 3) && Phi < 0) || (sec > 4 && Phi < 90)) { @@ -236,7 +236,7 @@ namespace iguana::clas12 { } - double MomentumCorrection::CorrectionOutbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const sec, int const pid) const + double MomentumCorrection::CorrectionOutbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid) const { // skip the correction if it's not defined @@ -244,13 +244,13 @@ namespace iguana::clas12 { return 1.0; // Momentum Magnitude - double pp = sqrt(Px * Px + Py * Py + Pz * Pz); + double pp = sqrt(px * px + py * py + pz * pz); // Initializing the correction factor double dp = 0; // Defining Phi Angle - double Phi = (180 / M_PI) * atan2(Py, Px); + double Phi = (180 / M_PI) * atan2(py, px); // (Initial) Shift of the Phi Angle (done to realign sectors whose data is separated when plotted from ±180˚) if(((sec == 4 || sec == 3) && Phi < 0) || (sec > 4 && Phi < 90)) { @@ -351,7 +351,7 @@ namespace iguana::clas12 { } - double MomentumCorrection::EnergyLossInbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const pid) const + double MomentumCorrection::EnergyLossInbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const pid) const { // The following code is for the Energy Loss Corrections for the proton @@ -359,8 +359,8 @@ namespace iguana::clas12 { return 1.0; double dE_loss = 0; - auto pro = sqrt(Px * Px + Py * Py + Pz * Pz); - auto proth = atan2(sqrt(Px * Px + Py * Py), Pz) * (180 / M_PI); + auto pro = sqrt(px * px + py * py + pz * pz); + auto proth = atan2(sqrt(px * px + py * py), pz) * (180 / M_PI); // Inbending Energy Loss Correction // if(proth < 27) { @@ -373,7 +373,7 @@ namespace iguana::clas12 { } - double MomentumCorrection::EnergyLossOutbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const pid) const + double MomentumCorrection::EnergyLossOutbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const pid) const { // The following code is for the Energy Loss Corrections for the proton @@ -381,8 +381,8 @@ namespace iguana::clas12 { return 1.0; double dE_loss = 0; - auto pro = sqrt(Px * Px + Py * Py + Pz * Pz); - auto proth = atan2(sqrt(Px * Px + Py * Py), Pz) * (180 / M_PI); + auto pro = sqrt(px * px + py * py + pz * pz); + auto proth = atan2(sqrt(px * px + py * py), pz) * (180 / M_PI); // Outbending Energy Loss Correction // if(proth > 27) { diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h index d59c67b7..38fa4f34 100644 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h +++ b/src/iguana/algorithms/clas12/MomentumCorrection/Algorithm.h @@ -32,41 +32,41 @@ namespace iguana::clas12 { /// @param pid the particle PDG /// @param torus torus setting /// @returns the transformed momentum - vector3_t Transform(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid, float const torus) const; + Momentum3 Transform(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid, float const torus) const; /// @action_function{scalar creator} Calculate the correction factor for inbending data - /// @param Px @f$p_x@f$ - /// @param Py @f$p_y@f$ - /// @param Pz @f$p_z@f$ + /// @param px @f$p_x@f$ + /// @param py @f$p_y@f$ + /// @param pz @f$p_z@f$ /// @param sec the sector /// @param pid the particle PDG /// @returns the correction factor - double CorrectionInbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const sec, int const pid) const; + double CorrectionInbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid) const; /// @action_function{scalar creator} Calculate the correction factor for outbending data - /// @param Px @f$p_x@f$ - /// @param Py @f$p_y@f$ - /// @param Pz @f$p_z@f$ + /// @param px @f$p_x@f$ + /// @param py @f$p_y@f$ + /// @param pz @f$p_z@f$ /// @param sec the sector /// @param pid the particle PDG /// @returns the correction factor - double CorrectionOutbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const sec, int const pid) const; + double CorrectionOutbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const sec, int const pid) const; /// @action_function{scalar creator} Energy loss correction for inbending data - /// @param Px @f$p_x@f$ - /// @param Py @f$p_y@f$ - /// @param Pz @f$p_z@f$ + /// @param px @f$p_x@f$ + /// @param py @f$p_y@f$ + /// @param pz @f$p_z@f$ /// @param pid the particle PDG /// @returns the correction factor - double EnergyLossInbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const pid) const; + double EnergyLossInbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const pid) const; /// @action_function{scalar creator} Energy loss correction for outbending data - /// @param Px @f$p_x@f$ - /// @param Py @f$p_y@f$ - /// @param Pz @f$p_z@f$ + /// @param px @f$p_x@f$ + /// @param py @f$p_y@f$ + /// @param pz @f$p_z@f$ /// @param pid the particle PDG /// @returns the correction factor - double EnergyLossOutbending(vector_element_t const Px, vector_element_t const Py, vector_element_t const Pz, int const pid) const; + double EnergyLossOutbending(vector_element_t const px, vector_element_t const py, vector_element_t const pz, int const pid) const; private: diff --git a/src/iguana/algorithms/clas12/MomentumCorrection/Bindings.cc b/src/iguana/algorithms/clas12/MomentumCorrection/Bindings.cc deleted file mode 100644 index 98cb796a..00000000 --- a/src/iguana/algorithms/clas12/MomentumCorrection/Bindings.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "Algorithm.h" -#include "iguana/algorithms/Bindings.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/ZVertexFilter/Action.yaml b/src/iguana/algorithms/clas12/ZVertexFilter/Action.yaml new file mode 100644 index 00000000..c06b0ca5 --- /dev/null +++ b/src/iguana/algorithms/clas12/ZVertexFilter/Action.yaml @@ -0,0 +1,13 @@ +algorithm: + name: 'clas12::ZVertexFilter' + +actions: + - name: Filter + type: filter + inputs: + - name: zvertex + type: double + - name: pid + type: int + - name: status + type: int diff --git a/src/iguana/algorithms/clas12/ZVertexFilter/Bindings.cc b/src/iguana/algorithms/clas12/ZVertexFilter/Bindings.cc deleted file mode 100644 index cbf1df25..00000000 --- a/src/iguana/algorithms/clas12/ZVertexFilter/Bindings.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "Algorithm.h" -#include "iguana/algorithms/Bindings.h" - -namespace iguana::bindings { - extern "C" { - - /// @see `iguana::clas12::ZVertexFilter::Filter` - /// @param [in] algo_idx the algorithm index - /// @param [in] vz - /// @param [in] pid - /// @param [in] status - /// @param [in,out] out the return value - void iguana_clas12_zvertexfilter_filter_(algo_idx_t* algo_idx, float* vz, int* pid, int* status, bool* out) - { - *out = *out && dynamic_cast(iguana_get_algo_(algo_idx))->Filter(*vz, *pid, *status); - } - } -} diff --git a/src/iguana/algorithms/example/ExampleAlgorithm/README.md b/src/iguana/algorithms/example/ExampleAlgorithm/README.md index 0bc31ca1..5a7fe5c8 100644 --- a/src/iguana/algorithms/example/ExampleAlgorithm/README.md +++ b/src/iguana/algorithms/example/ExampleAlgorithm/README.md @@ -30,5 +30,13 @@ Once you have generated your new algorithm: see existing algorithms for examples, and [documentation here](/doc/testing.md) - [ ] if you created a new namespace, update [`namespaces.dox`](/doc/gen/namespaces.dox) +> [!TIP] +> Enable the build option `install_documentation` (if you have `doxygen`), so +> that `meson` will generate the documentation locally. Documentation is +> automatically generated by the Continuous Integration (CI), but if you +> generate it locally, it can help you more quickly fix any documentation +> errors, and will allow you to _see_ the documentation before it's deployed +> online. + > [!TIP] > Enable debugging symbols when building by setting the Iguana build option `buildtype` to `debug`. diff --git a/src/iguana/algorithms/meson.build b/src/iguana/algorithms/meson.build index e2475324..37186cf4 100644 --- a/src/iguana/algorithms/meson.build +++ b/src/iguana/algorithms/meson.build @@ -1,18 +1,3 @@ -# start the algorithm dictionary list with the infrastructure sources -algo_dict = [ - { - 'name': 'main', - 'directory': '.', - 'has_config': false, - 'has_c_bindings': true, - 'has_validator': true, - 'algorithm_needs_ROOT': false, - 'validator_needs_ROOT': false, - 'add_algorithm_sources': [ 'AlgorithmFactory.cc', 'AlgorithmSequence.cc' ], - 'add_algorithm_headers': [ 'AlgorithmBoilerplate.h', 'TypeDefs.h', 'AlgorithmSequence.h' ], - }, -] - # list of dictionaries for info about each algorithm # # example: @@ -21,8 +6,8 @@ algo_dict = [ # 'name': str # algorithm name (REQUIRED) # 'directory': str # algorithm source code directory (default is based on 'name', replacing '::' with '/') # 'has_config': bool # assumes Config.yaml exists (default=true) -# 'has_c_bindings': bool # assumes Bindings.cc exists (default=true) # 'has_validator': bool # assumes Validator{cc,h} exist (default=true) +# 'has_action_yaml': bool # assumes Action.yaml exists (default=true) # 'algorithm_needs_ROOT': bool # whether this algorithm needs ROOT or not (default=false) # 'validator_needs_ROOT': bool # whether this validator needs ROOT or not (default=true) # 'add_algorithm_sources': list[str] # list of additional algorithm source files (default=[]) @@ -35,11 +20,11 @@ algo_dict = [ # }, # } # -algo_dict += [ +algo_dict = [ { 'name': 'example::ExampleAlgorithm', - 'has_c_bindings': false, 'has_validator': false, + 'has_action_yaml': false, 'test_args': {'banks': [ 'REC::Particle' ]}, }, { @@ -49,19 +34,17 @@ algo_dict += [ }, { 'name': 'clas12::ZVertexFilter', - 'has_validator': true, 'test_args': {'banks': [ 'REC::Particle' ]}, }, { 'name': 'clas12::FiducialFilter', - 'has_validator': true, - 'has_c_bindings': false, + 'has_action_yaml': false, 'test_args': {'banks': [ 'REC::Particle', 'REC::Traj', 'RUN::config' ]}, }, { 'name': 'clas12::SectorFinder', - 'has_c_bindings': false, 'has_validator': true, + 'has_action_yaml': false, 'test_args': { 'banks': [ 'REC::Particle', 'REC::Calorimeter', 'REC::Track', 'REC::Scintillator' ], }, @@ -69,14 +52,12 @@ algo_dict += [ { 'name': 'clas12::LorentzTransformer', 'algorithm_needs_ROOT': true, - 'has_c_bindings': false, 'has_validator': false, 'test_args': {'banks': [ 'REC::Particle' ]}, }, { 'name': 'clas12::FTEnergyCorrection', 'has_config': false, - 'has_c_bindings': false, 'has_validator': false, 'test_args': {'banks': [ 'RECFT::Particle' ]}, }, @@ -101,27 +82,27 @@ algo_dict += [ }, { 'name': 'clas12::PhotonGBTFilter', - 'has_c_bindings': false, 'algorithm_needs_ROOT': true, - 'has_validator': true, + 'has_action_yaml': false, 'test_args': {'banks': [ 'REC::Particle', 'REC::Calorimeter', 'RUN::config' ]}, }, ] # make lists of objects to build; inclusion depends on whether ROOT is needed or not, and if we have ROOT -algo_sources = [] -algo_headers = [] +algo_sources = [ 'Algorithm.cc', 'AlgorithmFactory.cc', 'AlgorithmSequence.cc' ] +algo_headers = [ 'Algorithm.h', 'AlgorithmBoilerplate.h', 'TypeDefs.h', 'AlgorithmSequence.h' ] +vdor_sources = [ 'Validator.cc' ] +vdor_headers = [ 'Validator.h' ] algo_configs = [] -vdor_sources = [] -vdor_headers = [] +algo_bind_c_sources = [] foreach algo : algo_dict algo_name = algo.get('name') algo_dir = algo.get('directory', '/'.join(algo_name.split('::'))) algo_has_config = algo.get('has_config', true) - algo_has_c_bindings = algo.get('has_c_bindings', true) algo_has_validator = algo.get('has_validator', true) + algo_has_action_yaml = algo.get('has_action_yaml', true) algo_needs_ROOT = algo.get('algorithm_needs_ROOT', false) vdor_needs_ROOT = algo.get('validator_needs_ROOT', true) @@ -138,20 +119,31 @@ foreach algo : algo_dict algo_headers += algo_dir / src_file endforeach - # C/Fortran bindings - if get_option('bind_fortran') - if algo_has_c_bindings - algo_sources += algo_dir / 'Bindings.cc' - else - warning('Algorithm "' + algo_name + '" has no Fortran bindings') - endif - endif - # config files if algo_has_config algo_configs += algo_dir / 'Config.yaml' endif + # run chameleon + if use_chameleon and algo_has_action_yaml + target_name = '_'.join([ 'chameleon', algo_name.split('::') ]) + tgt = custom_target( + target_name, + input: [ algo_dir / 'Action.yaml', chameleon_sources ], + output: [ target_name + '_bind.cc' ], + command: [ + chameleon_gen, + '--input', '@INPUT0@', + '--output', '@OUTDIR@', + '--prefix', target_name, + '--quiet' + ], + ) + algo_bind_c_sources += tgt[0] + else + warning('Algorithm "' + algo_name + '" has no bindings') + endif + else warning('Excluding algorithm "' + algo_name + '", which depends on ROOT') endif @@ -169,7 +161,7 @@ foreach algo : algo_dict endforeach endif else - warning('Algorithm "' + algo_name + '" has no Validator; use at your own risk!') + warning('Algorithm "' + algo_name + '" has no validator') endif endforeach diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Action.yaml b/src/iguana/algorithms/physics/InclusiveKinematics/Action.yaml new file mode 100644 index 00000000..6edc81f8 --- /dev/null +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Action.yaml @@ -0,0 +1,35 @@ +algorithm: + name: 'physics::InclusiveKinematics' + +actions: + - name: ComputeFromLepton + type: creator + inputs: + - name: lepton_px + type: double + cast: float + - name: lepton_py + type: double + cast: float + - name: lepton_pz + type: double + cast: float + outputs: + - name: qx + type: double + - name: qy + type: double + - name: qz + type: double + - name: qE + type: double + - name: Q2 + type: double + - name: x + type: double + - name: y + type: double + - name: W + type: double + - name: nu + type: double diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc index baa19827..87938fcf 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.cc @@ -135,10 +135,10 @@ namespace iguana::physics { result_bank.putDouble(i_y, 0, result_vars.y); result_bank.putDouble(i_W, 0, result_vars.W); result_bank.putDouble(i_nu, 0, result_vars.nu); - result_bank.putDouble(i_qx, 0, std::get<0>(result_vars.q)); - result_bank.putDouble(i_qy, 0, std::get<1>(result_vars.q)); - result_bank.putDouble(i_qz, 0, std::get<2>(result_vars.q)); - result_bank.putDouble(i_qE, 0, std::get<3>(result_vars.q)); + result_bank.putDouble(i_qx, 0, result_vars.qx); + result_bank.putDouble(i_qy, 0, result_vars.qy); + result_bank.putDouble(i_qz, 0, result_vars.qz); + result_bank.putDouble(i_qE, 0, result_vars.qE); ShowBank(result_bank, Logger::Header("CREATED BANK")); } @@ -197,7 +197,10 @@ namespace iguana::physics { ROOT::Math::PxPyPzMVector vec_lepton(lepton_px, lepton_py, lepton_pz, m_beam.mass); auto vec_q = vec_beam - vec_lepton; - result.q = {vec_q.Px(), vec_q.Py(), vec_q.Pz(), vec_q.E()}; + result.qx = vec_q.Px(); + result.qy = vec_q.Py(); + result.qz = vec_q.Pz(); + result.qE = vec_q.E(); result.Q2 = -1 * vec_q.M2(); result.x = result.Q2 / (2 * vec_q.Dot(vec_target)); result.y = vec_target.Dot(vec_q) / vec_target.Dot(vec_beam); diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h index f6b6c7c3..0f86c54c 100644 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h +++ b/src/iguana/algorithms/physics/InclusiveKinematics/Algorithm.h @@ -7,8 +7,14 @@ namespace iguana::physics { /// Set of inclusive kinematics variables struct InclusiveKinematicsVars { - /// @f$q=(q_x,q_y,q_z,q_E)@f$ - vector4_t q; + /// @f$x@f$-component of virtual photon momentum @f$q@f$ + vector_element_t qx; + /// @f$y@f$-component of virtual photon momentum @f$q@f$ + vector_element_t qy; + /// @f$z@f$-component of virtual photon momentum @f$q@f$ + vector_element_t qz; + /// @f$E@f$-component of virtual photon momentum @f$q@f$ + vector_element_t qE; /// @f$Q2@f$ (GeV@f$^2@f$) double Q2; /// @f$x_B@f$ diff --git a/src/iguana/algorithms/physics/InclusiveKinematics/Bindings.cc b/src/iguana/algorithms/physics/InclusiveKinematics/Bindings.cc deleted file mode 100644 index cecc4fe8..00000000 --- a/src/iguana/algorithms/physics/InclusiveKinematics/Bindings.cc +++ /dev/null @@ -1,38 +0,0 @@ -#include "Algorithm.h" -#include "iguana/algorithms/Bindings.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/algorithms/Bindings.cc b/src/iguana/bindings/Bindings.cc similarity index 100% rename from src/iguana/algorithms/Bindings.cc rename to src/iguana/bindings/Bindings.cc diff --git a/src/iguana/algorithms/Bindings.h b/src/iguana/bindings/Bindings.h similarity index 99% rename from src/iguana/algorithms/Bindings.h rename to src/iguana/bindings/Bindings.h index 79e41a2f..75ec14d3 100644 --- a/src/iguana/algorithms/Bindings.h +++ b/src/iguana/bindings/Bindings.h @@ -1,6 +1,6 @@ #pragma once -#include "Algorithm.h" +#include "iguana/algorithms/Algorithm.h" namespace iguana::bindings { extern "C" { diff --git a/src/iguana/bindings/meson.build b/src/iguana/bindings/meson.build new file mode 100644 index 00000000..909390b6 --- /dev/null +++ b/src/iguana/bindings/meson.build @@ -0,0 +1,22 @@ +algo_bind_c_sources += [ 'Bindings.cc' ] +algo_bind_c_headers = [ 'Bindings.h' ] + +# build C bindings +if get_option('bind_fortran') + algo_bind_c_lib = shared_library( + 'IguanaBindingsC', + algo_bind_c_sources, + include_directories: [ project_inc ] + ROOT_dep_inc_dirs, + dependencies: project_deps, + link_with: [ services_lib, algo_lib ], + link_args: ROOT_dep_link_args + ROOT_dep_link_args_for_validators, + install: true, + build_rpath: ROOT_dep_rpath, + ) + project_libs += algo_bind_c_lib +endif +install_headers( + algo_bind_c_headers, + subdir: meson.project_name() / 'bindings', + preserve_path: true, +)