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
|