ACTS
Experiment-independent tracking
Loading...
Searching...
No Matches
dfe.hpp
1// This file is part of the ACTS project.
2//
3// Copyright (C) 2016 CERN for the benefit of the ACTS project
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9// This file is a modified version of dfelibs/dfe/dfe_namedtuple.hpp and
10// dfelibs/dfe/dfe_io_dsv.hpp
11// (https://github.com/msmk0/dfelibs/blob/master/dfe/dfe_namedtuple.hpp and
12// https://github.com/msmk0/dfelibs/blob/master/dfe/dfe_io_dsv.hpp). The
13// original files were licensed as follows:
14//
15// SPDX-License-Identifier: MIT
16// Copyright 2015,2018-2020 Moritz Kiehn
17//
18// Permission is hereby granted, free of charge, to any person obtaining a copy
19// of this software and associated documentation files (the "Software"), to deal
20// in the Software without restriction, including without limitation the rights
21// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22// copies of the Software, and to permit persons to whom the Software is
23// furnished to do so, subject to the following conditions:
24//
25// The above copyright notice and this permission notice shall be included in
26// all copies or substantial portions of the Software.
27//
28// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34// SOFTWARE.
35
36#pragma once
37
38#include <algorithm>
39#include <array>
40#include <cassert>
41#include <cstdint>
42#include <fstream>
43#include <iomanip>
44#include <limits>
45#include <ostream>
46#include <sstream>
47#include <stdexcept>
48#include <string>
49#include <tuple>
50#include <utility>
51#include <vector>
52
53// NOTE: This macro is deliberately namespaced. Traccc carries its own copy of
54// the dfe-derived namedtuple macro, and a translation unit that includes both
55// headers would otherwise end up with whichever definition came last.
56#define DETRAY_DFE_NAMEDTUPLE(name, ...) \
57 using Tuple = decltype(::std::make_tuple(__VA_ARGS__)); \
58 static ::std::array<::std::string, ::std::tuple_size<Tuple>::value> \
59 names() { \
60 return ::detray::io::csv::dfe::unstringify< \
61 ::std::tuple_size<Tuple>::value>((#__VA_ARGS__)); \
62 } \
63 template <typename... U> \
64 name& operator=(const ::std::tuple<U...>& other) { \
65 ::std::tie(__VA_ARGS__) = other; \
66 return *this; \
67 } \
68 template <typename... U> \
69 name& operator=(::std::tuple<U...>&& other) { \
70 ::std::tie(__VA_ARGS__) = ::std::forward<std::tuple<U...>>(other); \
71 return *this; \
72 } \
73 operator Tuple() const { \
74 return ::std::make_tuple(__VA_ARGS__); \
75 } \
76 Tuple tuple() const { \
77 return ::std::make_tuple(__VA_ARGS__); \
78 } \
79 template <std::size_t I> \
80 constexpr ::std::tuple_element_t<I, Tuple>& get() { \
81 return ::std::get<I>(std::tie(__VA_ARGS__)); \
82 } \
83 template <::std::size_t I> \
84 constexpr const ::std::tuple_element_t<I, Tuple>& get() const { \
85 return ::std::get<I>(std::tie(__VA_ARGS__)); \
86 } \
87 template <::std::size_t I> \
88 friend constexpr ::std::tuple_element_t<I, Tuple>& get(name& nt) { \
89 return nt.template get<I>(); \
90 } \
91 template <::std::size_t I> \
92 friend constexpr const ::std::tuple_element_t<I, Tuple>& get( \
93 const name& nt) { \
94 return nt.template get<I>(); \
95 } \
96 friend inline ::std::ostream& operator<<(::std::ostream& os, \
97 const name& nt) { \
98 return ::detray::io::csv::dfe::print_tuple( \
99 os, nt.names(), nt.tuple(), \
100 ::std::make_index_sequence<::std::tuple_size<Tuple>::value>{}); \
101 }
102
103namespace detray::io::csv::dfe {
104
105template <typename R, typename T>
106bool rangeContainsValue(const R& range, const T& value) {
107 return std::ranges::find(range, value) != std::ranges::end(range);
108}
109
110template <std::size_t N>
111constexpr std::array<std::string, N> unstringify(const char* str) {
112 assert(str && "Input string must be non-null");
113
114 std::array<std::string, N> out;
115
116 for (std::size_t idx = 0; idx < N; ++idx) {
117 while ((*str != '\0') && (*str == ' ')) {
118 ++str;
119 }
120 const char* sep = str;
121 while ((*sep != '\0') && (*sep != ',')) {
122 ++sep;
123 }
124 out[idx].assign(str, static_cast<std::size_t>(sep - str));
125 if (*sep == '\0') {
126 break;
127 }
128 str = ++sep;
129 }
130 return out;
131}
132
133template <typename Names, typename Values, std::size_t... I>
134inline std::ostream& print_tuple(std::ostream& os, const Names& n,
135 const Values& v,
136 std::index_sequence<I...> /*seq*/) {
137 using std::get;
138 using Vacuum = int[];
139 (void)Vacuum{
140 (os << ((0 < I) ? " " : "") << get<I>(n) << "=" << get<I>(v), 0)...};
141 return os;
142}
143
144template <char Delimiter>
145class DsvWriter {
146 public:
147 DsvWriter() = delete;
148 DsvWriter(const DsvWriter&) = delete;
149 DsvWriter(DsvWriter&&) noexcept = default;
150 ~DsvWriter() = default;
151 DsvWriter& operator=(const DsvWriter&) = delete;
152 DsvWriter& operator=(DsvWriter&&) noexcept = default;
153
154 DsvWriter(const std::vector<std::string>& columns, const std::string& path,
155 int precision = std::numeric_limits<double>::max_digits10);
156
157 template <typename Arg0, typename... Args>
158 void append(Arg0&& arg0, Args&&... args);
159
160 private:
161 std::ofstream m_file;
162 std::size_t m_num_columns;
163
164 template <typename T>
165 unsigned write(T&& x, std::ostream& os);
166
167 template <typename T, typename Allocator>
168 static unsigned write(const std::vector<T, Allocator>& xs, std::ostream& os);
169};
170
171template <char Delimiter>
172class DsvReader {
173 public:
174 DsvReader() = delete;
175 DsvReader(const DsvReader&) = delete;
176 DsvReader(DsvReader&&) noexcept = default;
177 ~DsvReader() = default;
178 DsvReader& operator=(const DsvReader&) = delete;
179 DsvReader& operator=(DsvReader&&) noexcept = default;
180
181 explicit DsvReader(const std::string& path);
182
183 bool read(std::vector<std::string>& columns);
184
185 std::size_t num_lines() const { return m_num_lines; }
186
187 private:
188 std::ifstream m_file;
189 std::string m_line;
190 std::size_t m_num_lines = 0;
191};
192
193template <char Delimiter, typename NamedTuple>
194class NamedTupleDsvWriter {
195 public:
196 NamedTupleDsvWriter() = delete;
197 NamedTupleDsvWriter(const NamedTupleDsvWriter&) = delete;
198 NamedTupleDsvWriter(NamedTupleDsvWriter&&) noexcept = default;
199 ~NamedTupleDsvWriter() = default;
200 NamedTupleDsvWriter& operator=(const NamedTupleDsvWriter&) = delete;
201 NamedTupleDsvWriter& operator=(NamedTupleDsvWriter&&) noexcept = default;
202
203 explicit NamedTupleDsvWriter(
204 const std::string& path,
205 int precision = std::numeric_limits<double>::max_digits10)
206 : m_writer(colum_names(), path, precision) {}
207
208 void append(const NamedTuple& record) {
209 append_impl(record, std::make_index_sequence<
210 std::tuple_size_v<typename NamedTuple::Tuple>>{});
211 }
212
213 private:
214 DsvWriter<Delimiter> m_writer;
215
216 static std::vector<std::string> colum_names() {
217 const auto& from_record = NamedTuple::names();
218 return {from_record.begin(), from_record.end()};
219 }
220 template <std::size_t... I>
221 void append_impl(const NamedTuple& values,
222 std::index_sequence<I...> /*seq*/) {
223 using std::get;
224 m_writer.append(get<I>(values)...);
225 }
226};
227
228template <typename T>
229static void parse(const std::string& str, T& value) {
230 std::istringstream is(str);
231 is >> value;
232}
233
234template <char Delimiter, typename NamedTuple>
235class NamedTupleDsvReader {
236 public:
237 NamedTupleDsvReader() = delete;
238 NamedTupleDsvReader(const NamedTupleDsvReader&) = delete;
239 NamedTupleDsvReader(NamedTupleDsvReader&&) noexcept = default;
240 ~NamedTupleDsvReader() = default;
241 NamedTupleDsvReader& operator=(const NamedTupleDsvReader&) = delete;
242 NamedTupleDsvReader& operator=(NamedTupleDsvReader&&) noexcept = default;
243
244 explicit NamedTupleDsvReader(
245 const std::string& path,
246 const std::vector<std::string>& optional_columns = {},
247 bool verify_header = true);
248 bool read(NamedTuple& record);
249
250 template <typename T>
251 bool read(NamedTuple& record, std::vector<T>& extra);
252
253 std::size_t num_extra_columns() const { return m_extra_columns.size(); }
254 std::size_t num_records() const { return m_reader.num_lines() - 1u; }
255
256 private:
257 using Tuple = typename NamedTuple::Tuple;
258
259 DsvReader<Delimiter> m_reader;
260 std::vector<std::string> m_columns;
261 std::size_t m_num_columns = SIZE_MAX;
262 std::array<std::size_t, std::tuple_size<Tuple>::value> m_tuple_column_map;
263 std::vector<std::size_t> m_extra_columns;
264
265 void use_default_columns();
266 void parse_header(const std::vector<std::string>& optional_columns);
267 template <std::size_t... I>
268 void parse_record(NamedTuple& record,
269 std::index_sequence<I...> /*seq*/) const {
270 using Vacuum = int[];
271 (void)Vacuum{(parse_element<I>(record), 0)...};
272 }
273 template <std::size_t I>
274 void parse_element(NamedTuple& record) const {
275 using std::get;
276 if (m_tuple_column_map[I] != SIZE_MAX) {
277 parse(m_columns[m_tuple_column_map[I]], get<I>(record));
278 }
279 }
280};
281
282template <char Delimiter>
283inline DsvWriter<Delimiter>::DsvWriter(const std::vector<std::string>& columns,
284 const std::string& path, int precision)
285 : m_file(path,
286 std::ios_base::binary | std::ios_base::out | std::ios_base::trunc),
287 m_num_columns(columns.size()) {
288 if (!m_file.is_open() || m_file.fail()) {
289 throw std::runtime_error("Could not open file '" + path + "'");
290 }
291 m_file.precision(precision);
292 if (m_num_columns == 0) {
293 throw std::invalid_argument("No columns were specified");
294 }
295 append(columns);
296}
297
298template <char Delimiter>
299template <typename Arg0, typename... Args>
300inline void DsvWriter<Delimiter>::append(Arg0&& arg0, Args&&... args) {
301 std::stringstream line;
302 unsigned written_columns[] = {
303 write(std::forward<Arg0>(arg0), line),
304 (line << std::setprecision(std::numeric_limits<double>::max_digits10)
305 << Delimiter,
306 write(std::forward<Args>(args), line))...,
307 };
308 line << '\n';
309 unsigned total_columns = 0;
310 for (auto nc : written_columns) {
311 total_columns += nc;
312 }
313 if (total_columns < m_num_columns) {
314 throw std::invalid_argument("Not enough columns");
315 }
316 if (m_num_columns < total_columns) {
317 throw std::invalid_argument("Too many columns");
318 }
319 m_file << line.rdbuf();
320 if (!m_file.good()) {
321 throw std::runtime_error("Could not write data to file");
322 }
323}
324
325template <char Delimiter>
326template <typename T>
327inline unsigned DsvWriter<Delimiter>::write(T&& x, std::ostream& os) {
328 os << std::forward<T>(x);
329 return 1u;
330}
331
332template <char Delimiter>
333template <typename T, typename Allocator>
334inline unsigned DsvWriter<Delimiter>::write(const std::vector<T, Allocator>& xs,
335 std::ostream& os) {
336 unsigned n = 0;
337 for (const auto& x : xs) {
338 if (0 < n) {
339 os << Delimiter;
340 }
341 os << x;
342 n += 1;
343 }
344 return n;
345}
346
347template <char Delimiter>
348inline DsvReader<Delimiter>::DsvReader(const std::string& path)
349 : m_file(path, std::ios_base::binary | std::ios_base::in) {
350 if (!m_file.is_open() || m_file.fail()) {
351 throw std::runtime_error("Could not open file '" + path + "'");
352 }
353}
354
355template <char Delimiter>
356inline bool DsvReader<Delimiter>::read(std::vector<std::string>& columns) {
357 std::getline(m_file, m_line);
358 if (m_file.eof()) {
359 return false;
360 }
361 if (m_file.fail()) {
362 throw std::runtime_error(std::string("Could not read line ") +
363 std::to_string(m_num_lines));
364 }
365 m_num_lines += 1;
366
367 columns.clear();
368 for (std::string::size_type pos = 0; pos < m_line.size();) {
369 auto del = m_line.find_first_of(Delimiter, pos);
370 if (del == std::string::npos) {
371 columns.emplace_back(m_line, pos);
372 break;
373 } else {
374 columns.emplace_back(m_line, pos, del - pos);
375 pos = del + 1;
376 }
377 }
378 return true;
379}
380
381template <char Delimiter, typename NamedTuple>
382inline NamedTupleDsvReader<Delimiter, NamedTuple>::NamedTupleDsvReader(
383 const std::string& path, const std::vector<std::string>& optional_columns,
384 bool verify_header)
385 : m_reader(path) {
386 if ((!optional_columns.empty()) && (!verify_header)) {
387 throw std::runtime_error(
388 "Optional columns can not be used without header verification");
389 }
390 if (!m_reader.read(m_columns)) {
391 throw std::runtime_error("Could not read header from '" + path + "'");
392 }
393 if (verify_header) {
394 parse_header(optional_columns);
395 } else {
396 use_default_columns();
397 }
398}
399
400template <char Delimiter, typename NamedTuple>
401inline bool NamedTupleDsvReader<Delimiter, NamedTuple>::read(
402 NamedTuple& record) {
403 if (!m_reader.read(m_columns)) {
404 return false;
405 }
406 if (m_columns.size() < m_num_columns) {
407 throw std::runtime_error("Too few columns in line " +
408 std::to_string(m_reader.num_lines()));
409 }
410 if (m_num_columns < m_columns.size()) {
411 throw std::runtime_error("Too many columns in line " +
412 std::to_string(m_reader.num_lines()));
413 }
414 parse_record(record, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
415 return true;
416}
417
418template <char Delimiter, typename NamedTuple>
419template <typename T>
420inline bool NamedTupleDsvReader<Delimiter, NamedTuple>::read(
421 NamedTuple& record, std::vector<T>& extra) {
422 if (!read(record)) {
423 return false;
424 }
425 extra.resize(m_extra_columns.size());
426 for (std::size_t i = 0; i < m_extra_columns.size(); ++i) {
427 parse(m_columns[m_extra_columns[i]], extra[i]);
428 }
429 return true;
430}
431
432template <char Delimiter, typename NamedTuple>
433inline void NamedTupleDsvReader<Delimiter, NamedTuple>::use_default_columns() {
434 m_num_columns = std::tuple_size_v<Tuple>;
435 for (std::size_t i = 0; i < m_tuple_column_map.size(); ++i) {
436 m_tuple_column_map[i] = i;
437 }
438 m_extra_columns.clear();
439}
440
441template <char Delimiter, typename NamedTuple>
442inline void NamedTupleDsvReader<Delimiter, NamedTuple>::parse_header(
443 const std::vector<std::string>& optional_columns) {
444 const auto& names = NamedTuple::names();
445
446 m_num_columns = m_columns.size();
447
448 for (const auto& name : names) {
449 if (rangeContainsValue(optional_columns, name)) {
450 continue;
451 }
452 if (!rangeContainsValue(m_columns, name)) {
453 throw std::runtime_error("Missing header column '" + name + "'");
454 }
455 }
456
457 m_tuple_column_map.fill(SIZE_MAX);
458
459 m_extra_columns.clear();
460 for (std::size_t i = 0; i < m_columns.size(); ++i) {
461 auto it = std::ranges::find(names, m_columns[i]);
462 if (it != names.end()) {
463 m_tuple_column_map[static_cast<std::size_t>(
464 std::distance(names.begin(), it))] = i;
465 } else {
466 m_extra_columns.push_back(i);
467 }
468 }
469}
470
471using CsvWriter = dfe::DsvWriter<','>;
472
473using TsvWriter = dfe::DsvWriter<'\t'>;
474
475template <typename T>
476using NamedTupleCsvWriter = dfe::NamedTupleDsvWriter<',', T>;
477
478template <typename T>
479using NamedTupleCsvReader = dfe::NamedTupleDsvReader<',', T>;
480
481template <typename T>
482using NamedTupleTsvWriter = dfe::NamedTupleDsvWriter<'\t', T>;
483
484template <typename T>
485using NamedTupleTsvReader = dfe::NamedTupleDsvReader<'\t', T>;
486
487} // namespace detray::io::csv::dfe
constexpr double T
Magnetic field, native unit (eV*s)/(e*m²).
Definition Units.hpp:168
bool rangeContainsValue(const R &range, const T &value)
This can be replaced with C++23 to use the std::ranges::contains method.
Definition Helpers.hpp:227
auto read(const std::string &intersection_file_name, const std::string &track_param_file_name, std::vector< std::vector< intersection_record< detector_t >, allocator_t > > &intersection_traces)
Read the intersection_record from file.
Definition detector_scanner.hpp:275
const auto & get(const detray::tuple< Ts... > &t) noexcept
Get a tuple element by index from a const tuple.
Definition tuple.hpp:152