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