LCOV - code coverage report
Current view: top level - tpglibs/test/apps - test_tpg_processor_app.cxx (source / functions) Coverage Total Hit
Test: code.result Lines: 0.0 % 184 0
Test Date: 2026-10-11 15:05:20 Functions: 0.0 % 9 0

            Line data    Source code
       1              : /**
       2              :  * @file test_tpg_processor_app.cxx
       3              :  *
       4              :  * @brief TPG Processor Test Application - Processes binary data through configured processors
       5              :  *
       6              :  * @copyright This is part of the DUNE DAQ Software Suite, copyright 2020.
       7              :  * Licensing/copyright details are in the COPYING file that you should have
       8              :  * received with this code.
       9              :  */
      10              : 
      11              : #include "tpglibs/AVXFactory.hpp"
      12              : #include "tpglibs/AbstractProcessor.hpp"
      13              : #include "tpglibs/NaiveFactory.hpp"
      14              : #include "tpglibs/testapp/reader/BinarySignalReader.hpp"
      15              : #include <algorithm>
      16              : #include <cstdint>
      17              : #include <cstring>
      18              : #include <fstream>
      19              : #include <iostream>
      20              : #include <nlohmann/json.hpp>
      21              : #include <vector>
      22              : 
      23              : struct BinaryFileHeader
      24              : {
      25              :   uint32_t magic_number;
      26              :   uint32_t version;
      27              :   uint32_t reserved;
      28              : };
      29              : 
      30              : bool
      31            0 : validate_binary_header(std::ifstream& file)
      32              : {
      33            0 :   BinaryFileHeader header;
      34            0 :   file.read(reinterpret_cast<char*>(&header), sizeof(BinaryFileHeader));
      35              : 
      36            0 :   if (header.magic_number != 0x54504754) { // "TPGT"
      37            0 :     std::cerr << "ERROR: Invalid input file magic number: 0x" << std::hex << header.magic_number << std::dec
      38            0 :               << std::endl;
      39            0 :     return false;
      40              :   }
      41            0 :   if (header.version != 0x010004) { // 1.0.4 in hex
      42            0 :     std::cerr << "ERROR: Unsupported input file version: 0x" << std::hex << header.version << std::dec << std::endl;
      43            0 :     return false;
      44              :   }
      45              :   return true;
      46              : }
      47              : 
      48              : std::shared_ptr<tpglibs::AbstractProcessor<std::array<int16_t, 16>>>
      49            0 : create_naive_processor(const std::string& processor_name)
      50              : {
      51            0 :   auto factory = tpglibs::NaiveFactory::get_instance();
      52            0 :   auto processor = factory->create_processor(processor_name);
      53            0 :   return processor;
      54            0 : }
      55              : 
      56              : std::shared_ptr<tpglibs::AVXProcessor>
      57            0 : create_avx_processor(const std::string& processor_name)
      58              : {
      59            0 :   auto factory = tpglibs::AVXFactory::get_instance();
      60            0 :   auto processor = factory->create_processor(processor_name);
      61            0 :   return processor;
      62            0 : }
      63              : 
      64              : std::array<int16_t, 16>
      65            0 : convert_vector_to_array(const std::vector<int16_t>& vec)
      66              : {
      67            0 :   std::array<int16_t, 16> result;
      68            0 :   std::fill(result.begin(), result.end(), 0);
      69            0 :   std::copy(vec.begin(), vec.begin() + std::min(vec.size(), size_t(16)), result.begin());
      70            0 :   return result;
      71              : }
      72              : 
      73              : std::vector<int16_t>
      74            0 : convert_array_to_vector(const std::array<int16_t, 16>& arr)
      75              : {
      76            0 :   return std::vector<int16_t>(arr.begin(), arr.end());
      77              : }
      78              : 
      79              : __m256i
      80            0 : convert_array_i16x16_to_m256(const std::array<int16_t, 16>& arr)
      81              : {
      82            0 :   return _mm256_lddqu_si256(reinterpret_cast<const __m256i*>(arr.data()));
      83              : }
      84              : 
      85              : std::array<int16_t, 16>
      86            0 : convert_m256_to_array_i16x16(const __m256i& avx_val)
      87              : {
      88            0 :   std::array<int16_t, 16> result;
      89            0 :   _mm256_storeu_si256(reinterpret_cast<__m256i*>(result.data()), avx_val);
      90            0 :   return result;
      91              : }
      92              : 
      93              : void
      94            0 : print_usage(const char* program_name)
      95              : {
      96            0 :   std::cerr << "Usage: " << program_name << " <input_file> <config_file> <validation_file>" << std::endl;
      97            0 :   std::cerr << "Example: " << program_name << " test_input.bin config.json validation.bin" << std::endl;
      98            0 : }
      99              : 
     100              : int
     101            0 : main(int argc, char* argv[])
     102              : {
     103            0 :   if (argc != 4) {
     104            0 :     print_usage(argv[0]);
     105            0 :     return 1;
     106              :   }
     107              : 
     108            0 :   std::string input_file = argv[1];
     109            0 :   std::string config_file = argv[2];
     110            0 :   std::string validation_file = argv[3];
     111              : 
     112              :   // Load configuration
     113            0 :   std::ifstream config_stream(config_file);
     114            0 :   if (!config_stream.is_open()) {
     115            0 :     std::cerr << "ERROR: Failed to open config file: " << config_file << std::endl;
     116              :     return 1;
     117              :   }
     118              : 
     119            0 :   nlohmann::json config;
     120            0 :   try {
     121            0 :     config_stream >> config;
     122            0 :   } catch (const std::exception& e) {
     123            0 :     std::cerr << "ERROR: Failed to parse config file: " << e.what() << std::endl;
     124            0 :     return 1;
     125            0 :   }
     126              : 
     127              :   // Validate input file
     128            0 :   std::ifstream input_stream(input_file, std::ios::binary);
     129            0 :   if (!input_stream.is_open()) {
     130            0 :     std::cerr << "ERROR: Failed to open input file: " << input_file << std::endl;
     131              :     return 1;
     132              :   }
     133              : 
     134            0 :   if (!validate_binary_header(input_stream)) {
     135              :     return 1;
     136              :   }
     137              : 
     138              :   // Validate validation file
     139            0 :   std::ifstream validation_stream(validation_file, std::ios::binary);
     140            0 :   if (!validation_stream.is_open()) {
     141            0 :     std::cerr << "ERROR: Failed to open validation file: " << validation_file << std::endl;
     142              :     return 1;
     143              :   }
     144              : 
     145            0 :   if (!validate_binary_header(validation_stream)) {
     146              :     return 1;
     147              :   }
     148              : 
     149              :   // Test config validation block
     150            0 :   if (!config.contains("test_config") || !config["test_config"].is_object()) {
     151            0 :     std::cerr << "ERROR: Missing test_config section in config" << std::endl;
     152              :     return 1;
     153              :   }
     154            0 :   const auto& test_config = config["test_config"];
     155              : 
     156            0 :   if (!test_config.contains("processor_name") || !test_config["processor_name"].is_string()) {
     157            0 :     std::cerr << "ERROR: test_config.processor_name must be a string" << std::endl;
     158              :     return 1;
     159              :   }
     160            0 :   std::string processor_name = test_config["processor_name"].get<std::string>();
     161              : 
     162            0 :   if (!test_config.contains("samples_per_time_step") || !test_config["samples_per_time_step"].is_number_integer()) {
     163            0 :     std::cerr << "ERROR: test_config.samples_per_time_step must be an integer" << std::endl;
     164              :     return 1;
     165              :   }
     166            0 :   int samples_per_time_step = test_config["samples_per_time_step"].get<int>();
     167              : 
     168            0 :   auto validation_steps = test_config.value("validation_steps", nlohmann::json::array());
     169            0 :   if (!validation_steps.is_array()) {
     170            0 :     std::cerr << "ERROR: test_config.validation_steps must be an array if provided" << std::endl;
     171              :     return 1;
     172              :   }
     173              : 
     174            0 :   int max_steps = 0;
     175            0 :   if (test_config.contains("max_steps") && test_config["max_steps"].is_number_integer()) {
     176            0 :     max_steps = test_config["max_steps"].get<int>();
     177            0 :     if (max_steps <= 0) {
     178            0 :       std::cerr << "ERROR: test_config.max_steps must be a positive integer" << std::endl;
     179              :       return 1;
     180              :     }
     181              :   } else {
     182            0 :     if (validation_steps.empty()) {
     183            0 :       std::cerr << "ERROR: Either test_config.max_steps or non-empty test_config.validation_steps must be provided"
     184            0 :                 << std::endl;
     185            0 :       return 1;
     186              :     }
     187            0 :     int derived_max = 0;
     188            0 :     for (const auto& v : validation_steps) {
     189            0 :       if (!v.is_number_integer()) {
     190            0 :         std::cerr << "ERROR: validation_steps must contain integers" << std::endl;
     191            0 :         return 1;
     192              :       }
     193            0 :       derived_max = std::max(derived_max, v.get<int>());
     194              :     }
     195            0 :     max_steps = derived_max + 1;
     196              :   }
     197              : 
     198              :   // Create processor based on name
     199            0 :   std::shared_ptr<tpglibs::AVXProcessor> avx_processor;
     200            0 :   std::shared_ptr<tpglibs::AbstractProcessor<std::array<int16_t, 16>>> naive_processor;
     201            0 :   bool is_avx = (processor_name.find("AVX") != std::string::npos);
     202              : 
     203            0 :   try {
     204            0 :     if (is_avx) {
     205            0 :       avx_processor = create_avx_processor(processor_name);
     206              :     } else {
     207            0 :       naive_processor = create_naive_processor(processor_name);
     208              :     }
     209            0 :   } catch (const std::exception& e) {
     210            0 :     std::cerr << "ERROR: Failed to create processor: " << e.what() << std::endl;
     211            0 :     return 1;
     212            0 :   }
     213              : 
     214              :   // Configure processor
     215            0 :   int16_t plane_numbers[16] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2 };
     216            0 :   nlohmann::json processor_config = config["processor_config"];
     217              : 
     218            0 :   if (is_avx) {
     219            0 :     avx_processor->configure(processor_config, plane_numbers);
     220              :   } else {
     221            0 :     naive_processor->configure(processor_config, plane_numbers);
     222              :   }
     223              : 
     224              :   // Read binary data
     225            0 :   tpglibs::testapp::BinarySignalReader<int16_t> reader(input_file);
     226              :   // Skip the header (12 bytes)
     227            0 :   reader.seekg(12);
     228              : 
     229              :   // Read validation data only if validation_steps is provided
     230            0 :   std::vector<std::vector<int16_t>> validation_data;
     231            0 :   bool has_validation = !validation_steps.empty();
     232              : 
     233            0 :   if (has_validation) {
     234              :     // Read expected outputs by seeking to the correct step offset in the validation file
     235            0 :     validation_data.resize(validation_steps.size());
     236            0 :     const std::streamsize step_bytes = static_cast<std::streamsize>(samples_per_time_step * sizeof(int16_t));
     237            0 :     const std::streamoff header_size = static_cast<std::streamoff>(sizeof(BinaryFileHeader));
     238              : 
     239            0 :     for (size_t i = 0; i < validation_steps.size(); ++i) {
     240            0 :       validation_data[i].resize(samples_per_time_step);
     241            0 :       int target_step = validation_steps.at(i).get<int>();
     242            0 :       std::streamoff offset = header_size + static_cast<std::streamoff>(target_step) * step_bytes;
     243              : 
     244            0 :       validation_stream.clear();
     245            0 :       validation_stream.seekg(offset, std::ios::beg);
     246            0 :       validation_stream.read(reinterpret_cast<char*>(validation_data[i].data()), step_bytes);
     247              : 
     248            0 :       if (validation_stream.gcount() < step_bytes) {
     249            0 :         std::cout << "WARNING: Validation file does not contain data for step " << target_step << ". Loaded " << i
     250            0 :                   << "/" << validation_steps.size() << " validation records." << std::endl;
     251            0 :         validation_data.resize(i);
     252              :         break;
     253              :       }
     254              :     }
     255              :   }
     256              : 
     257              :   // Process data step by step
     258            0 :   int step = 0;
     259            0 :   size_t validation_index = 0;
     260            0 :   bool all_passed = true;
     261            0 :   size_t num_steps_processed = 0;
     262              : 
     263            0 :   std::cout << "Starting processing..." << std::endl;
     264            0 :   std::cout << "Processor: " << processor_name << std::endl;
     265            0 :   std::cout << "Max steps: " << max_steps << std::endl;
     266            0 :   if (has_validation) {
     267            0 :     std::cout << "Validation steps: ";
     268            0 :     for (auto step_num : validation_steps) {
     269            0 :       std::cout << step_num << " ";
     270            0 :     }
     271            0 :     std::cout << std::endl;
     272              :   } else {
     273            0 :     std::cout << "Validation: Disabled (no validation_steps provided)" << std::endl;
     274              :   }
     275            0 :   std::cout << std::endl;
     276              : 
     277            0 :   while (step < max_steps && !reader.eof()) {
     278              :     // Read one time step of data
     279            0 :     auto time_step_data = reader.next(samples_per_time_step);
     280            0 :     if (time_step_data.empty())
     281              :       break;
     282              : 
     283              :     // Ensure we have exactly 16 samples
     284            0 :     time_step_data.resize(16, 0);
     285              : 
     286              :     // Convert to array
     287            0 :     std::array<int16_t, 16> input_array = convert_vector_to_array(time_step_data);
     288              : 
     289              :     // Process the time step
     290            0 :     std::array<int16_t, 16> result;
     291            0 :     if (is_avx) {
     292            0 :       __m256i input_avx = convert_array_i16x16_to_m256(input_array);
     293            0 :       __m256i result_avx = avx_processor->process(input_avx);
     294            0 :       result = convert_m256_to_array_i16x16(result_avx);
     295              :     } else {
     296            0 :       result = naive_processor->process(input_array);
     297              :     }
     298              : 
     299              :     // Check if this is a validation step
     300            0 :     if (validation_index < validation_data.size() && step == validation_steps.at(validation_index).get<int>()) {
     301            0 :       const std::vector<int16_t>& expected = validation_data[validation_index];
     302              : 
     303              :       // Find first mismatch using std::mismatch for clarity
     304            0 :       auto mm = std::mismatch(result.begin(), result.end(), expected.begin());
     305            0 :       bool passed = (mm.first == result.end());
     306              : 
     307            0 :       if (!passed) {
     308            0 :         if (all_passed) {
     309            0 :           int idx = static_cast<int>(std::distance(result.begin(), mm.first));
     310            0 :           std::cout << "Validation step " << step << " FAILED:" << std::endl;
     311            0 :           std::cout << "  Expected: ";
     312            0 :           for (int j = 0; j < 16; ++j) {
     313            0 :             std::cout << expected[j] << " ";
     314              :           }
     315            0 :           std::cout << std::endl;
     316            0 :           std::cout << "  Actual:   ";
     317            0 :           for (int j = 0; j < 16; ++j) {
     318            0 :             std::cout << result[j] << " ";
     319              :           }
     320            0 :           std::cout << std::endl;
     321            0 :           std::cout << "  First mismatch at index " << idx << ": expected " << expected[idx] << ", got " << result[idx]
     322            0 :                     << std::endl;
     323              :         }
     324              :         all_passed = false;
     325              :       } else {
     326            0 :         std::cout << "Validation step " << step << " PASSED" << std::endl;
     327              :       }
     328              : 
     329            0 :       validation_index++;
     330              :     }
     331              : 
     332            0 :     step++;
     333            0 :     num_steps_processed++;
     334            0 :   }
     335              : 
     336              :   // Warn if input ended before completing all loaded validation steps
     337            0 :   if (has_validation && validation_index < validation_data.size() && reader.eof()) {
     338            0 :     std::cout << "WARNING: Reached end of input before completing all validation steps. Completed " << validation_index
     339            0 :               << "/" << validation_data.size() << " validation steps." << std::endl;
     340              :   }
     341              : 
     342              :   // Report final results
     343            0 :   std::cout << std::endl << "Processing completed." << std::endl;
     344            0 :   std::cout << "Steps processed: " << num_steps_processed << std::endl;
     345            0 :   if (has_validation) {
     346            0 :     std::cout << "Validation results: " << (all_passed ? "PASS" : "FAIL") << std::endl;
     347            0 :     return all_passed ? 0 : 1;
     348              :   } else {
     349            0 :     std::cout << "Validation: Not performed" << std::endl;
     350              :     return 0;
     351              :   }
     352            0 : } // NOLINT
        

Generated by: LCOV version 2.0-1