27[[noreturn]]
void typeError(
const std::string &message,
28 std::optional<std::string> field = std::nullopt) {
29 throw TypeError(ErrorKind::Type, message, {}, 0, std::move(field));
32[[noreturn]]
void stateError(
const std::string &message) {
33 throw StateError(ErrorKind::State, message);
36template <
class To,
class From>
37To checkedIntegralCast(From value) {
38 static_assert(std::is_integral_v<To> && std::is_integral_v<From>);
39 if constexpr (std::is_signed_v<From> == std::is_signed_v<To>) {
40 if constexpr (
sizeof(To) <
sizeof(From)) {
41 if (value <
static_cast<From
>(std::numeric_limits<To>::lowest()) ||
42 value >
static_cast<From
>(std::numeric_limits<To>::max()))
43 typeError(
"numeric conversion is out of range");
45 }
else if constexpr (std::is_signed_v<From>) {
47 typeError(
"negative value cannot be converted to an unsigned type");
48 using UnsignedFrom = std::make_unsigned_t<From>;
49 if constexpr (
sizeof(To) <
sizeof(UnsignedFrom))
50 if (
static_cast<UnsignedFrom
>(value) > std::numeric_limits<To>::max())
51 typeError(
"numeric conversion is out of range");
53 using UnsignedTo = std::make_unsigned_t<To>;
54 if constexpr (
sizeof(To) <=
sizeof(From))
55 if (value >
static_cast<From
>(
static_cast<UnsignedTo
>(std::numeric_limits<To>::max())))
56 typeError(
"numeric conversion is out of range");
58 return static_cast<To
>(value);
61long double rounded(
long double value, RoundingMode mode) {
62 if (!std::isfinite(value))
63 typeError(
"non-finite value cannot be converted to an integer");
65 case RoundingMode::Reject:
66 if (std::trunc(value) != value)
67 typeError(
"floating-to-integer conversion requires an explicit rounding mode");
69 case RoundingMode::TowardZero:
return std::trunc(value);
70 case RoundingMode::Nearest:
return std::nearbyint(value);
71 case RoundingMode::Down:
return std::floor(value);
72 case RoundingMode::Up:
return std::ceil(value);
74 typeError(
"unknown rounding mode");
77template <
class To,
class From>
78To checkedNumericCast(From value, RoundingMode rounding) {
79 static_assert(std::is_arithmetic_v<To> && std::is_arithmetic_v<From>);
80 if constexpr (std::is_integral_v<To> && std::is_integral_v<From>) {
81 return checkedIntegralCast<To>(value);
82 }
else if constexpr (std::is_integral_v<To>) {
83 const long double converted = rounded(
static_cast<long double>(value), rounding);
84 if (converted <
static_cast<long double>(std::numeric_limits<To>::lowest()) ||
85 converted >
static_cast<long double>(std::numeric_limits<To>::max()))
86 typeError(
"numeric conversion is out of range");
87 return static_cast<To
>(converted);
89 const long double converted =
static_cast<long double>(value);
90 if (std::isfinite(converted) &&
91 (converted < -
static_cast<long double>(std::numeric_limits<To>::max()) ||
92 converted >
static_cast<long double>(std::numeric_limits<To>::max())))
93 typeError(
"numeric conversion is out of range");
94 return static_cast<To
>(value);
99Scalar convertTo(
const Scalar &value, RoundingMode rounding) {
100 return std::visit([&](
const auto &source) -> Scalar {
101 using From = std::decay_t<
decltype(source)>;
102 if constexpr (std::is_same_v<From, std::string>) {
103 typeError(
"strings cannot be converted to numeric SDDS types");
105 return checkedNumericCast<To>(source, rounding);
110Scalar scalarAt(
const Values &values, std::size_t index) {
111 return std::visit([&](
const auto &items) -> Scalar {
return items.at(index); }, values);
114std::uint64_t valueCount(
const Values &values) {
115 return std::visit([](
const auto &items) {
116 return static_cast<std::uint64_t
>(items.size());
120void appendScalar(Values &values, Scalar value) {
121 std::visit([&](
auto &items) {
122 using Vector = std::decay_t<
decltype(items)>;
123 using Value =
typename Vector::value_type;
124 items.push_back(std::get<Value>(std::move(value)));
128Values emptyValues(Type type) {
130 case Type::LongDouble:
return std::vector<long double>{};
131 case Type::Double:
return std::vector<double>{};
132 case Type::Float:
return std::vector<float>{};
133 case Type::Int64:
return std::vector<std::int64_t>{};
134 case Type::UInt64:
return std::vector<std::uint64_t>{};
135 case Type::Int32:
return std::vector<std::int32_t>{};
136 case Type::UInt32:
return std::vector<std::uint32_t>{};
137 case Type::Int16:
return std::vector<std::int16_t>{};
138 case Type::UInt16:
return std::vector<std::uint16_t>{};
139 case Type::String:
return std::vector<std::string>{};
140 case Type::Character:
return std::vector<char>{};
142 typeError(
"unknown SDDS type");
145template <
class Definition>
146void renameDefinition(std::vector<Definition> &definitions, std::size_t index,
147 std::string newName) {
149 typeError(
"definition names cannot be empty");
150 for (std::size_t existing = 0; existing < definitions.size(); ++existing)
151 if (existing != index && definitions[existing].name == newName)
152 typeError(
"duplicate definition name: " + newName, newName);
153 definitions.at(index).name = std::move(newName);
156template <
class Definition>
157void replaceDefinition(std::vector<Definition> &definitions, std::size_t index,
158 Definition definition) {
159 if (definition.name.empty())
160 typeError(
"definition names cannot be empty");
161 for (std::size_t existing = 0; existing < definitions.size(); ++existing)
162 if (existing != index && definitions[existing].name == definition.name)
163 typeError(
"duplicate definition name: " + definition.name, definition.name);
164 definitions.at(index) = std::move(definition);
167template <
class Definition>
168void eraseDefinition(std::vector<Definition> &definitions, std::size_t index) {
169 definitions.erase(definitions.begin() +
static_cast<std::ptrdiff_t
>(index));
172bool selectedRow(
const RowSlice &slice, std::int64_t row, std::int64_t total) {
173 if (slice.first < 0 || slice.stride < 1 || (slice.count && *slice.count < 0) ||
174 (slice.last && *slice.last < 0) || (slice.last && (slice.first || slice.count)))
175 stateError(
"invalid row slice");
176 const std::int64_t begin = slice.last ? std::max<std::int64_t>(0, total - *slice.last)
177 : std::min(slice.first, total);
178 if (row < begin || (row - begin) % slice.stride)
182 return (row - begin) / slice.stride < *slice.count;
185std::vector<bool> selectedFields(
const FieldSelection &selection,
186 const std::vector<std::string> &names) {
187 std::vector<bool> result(names.size(), selection.all);
188 if (selection.all && selection.names.empty())
191 stateError(
"an all-fields selection cannot also list field names");
192 for (
const auto &name : selection.names) {
193 const auto found = std::find(names.begin(), names.end(), name);
194 if (found == names.end())
195 typeError(
"unknown projected field: " + name, name);
196 result[
static_cast<std::size_t
>(found - names.begin())] =
true;
201template <
class Definition>
202std::vector<std::string> namesOf(
const std::vector<Definition> &definitions) {
203 std::vector<std::string> names;
204 names.reserve(definitions.size());
205 for (
const auto &definition : definitions)
206 names.push_back(definition.name);
210Scalar scaledScalar(
const Scalar &value, Type type,
long double factor) {
211 if (type == Type::String)
212 typeError(
"unit conversion requires numeric data");
213 const long double source = std::visit([](
const auto &item) ->
long double {
214 using Value = std::decay_t<
decltype(item)>;
215 if constexpr (std::is_same_v<Value, std::string>)
216 typeError(
"unit conversion requires numeric data");
218 return static_cast<long double>(item);
220 return convertScalar(Scalar(source * factor), type, RoundingMode::Reject);
223Values scaledValues(
const Values &values, Type type,
long double factor) {
224 Values result = emptyValues(type);
225 const std::size_t count = std::visit([](
const auto &items) {
return items.size(); }, values);
226 for (std::size_t index = 0; index < count; ++index)
227 appendScalar(result, scaledScalar(scalarAt(values, index), type, factor));
233Scalar convertScalar(
const Scalar &value, Type target, RoundingMode rounding) {
234 if (target == Type::String) {
235 if (
const auto *text = std::get_if<std::string>(&value))
237 typeError(
"numeric values cannot be implicitly converted to strings");
240 case Type::LongDouble:
return convertTo<long double>(value, rounding);
241 case Type::Double:
return convertTo<double>(value, rounding);
242 case Type::Float:
return convertTo<float>(value, rounding);
243 case Type::Int64:
return convertTo<std::int64_t>(value, rounding);
244 case Type::UInt64:
return convertTo<std::uint64_t>(value, rounding);
245 case Type::Int32:
return convertTo<std::int32_t>(value, rounding);
246 case Type::UInt32:
return convertTo<std::uint32_t>(value, rounding);
247 case Type::Int16:
return convertTo<std::int16_t>(value, rounding);
248 case Type::UInt16:
return convertTo<std::uint16_t>(value, rounding);
249 case Type::Character:
return convertTo<char>(value, rounding);
250 case Type::String:
break;
252 typeError(
"unknown conversion target");
255Values convertValues(
const Values &values, Type target, RoundingMode rounding) {
256 if (typeOf(values) == target)
258 Values result = emptyValues(target);
259 const std::size_t count = std::visit([](
const auto &items) {
return items.size(); }, values);
260 for (std::size_t index = 0; index < count; ++index)
261 appendScalar(result, convertScalar(scalarAt(values, index), target, rounding));
265LayoutEditor::LayoutEditor(Layout layout) : layout_(std::move(layout)) {}
267LayoutEditor &LayoutEditor::renameParameter(std::string_view name, std::string newName) {
268 renameDefinition(layout_.parameters, layout_.parameterIndex(name), std::move(newName));
271LayoutEditor &LayoutEditor::renameArray(std::string_view name, std::string newName) {
272 renameDefinition(layout_.arrays, layout_.arrayIndex(name), std::move(newName));
275LayoutEditor &LayoutEditor::renameColumn(std::string_view name, std::string newName) {
276 renameDefinition(layout_.columns, layout_.columnIndex(name), std::move(newName));
279LayoutEditor &LayoutEditor::renameAssociate(std::string_view name, std::string newName) {
280 renameDefinition(layout_.associates, layout_.associateIndex(name), std::move(newName));
283LayoutEditor &LayoutEditor::dropParameter(std::string_view name) {
284 eraseDefinition(layout_.parameters, layout_.parameterIndex(name));
return *
this;
286LayoutEditor &LayoutEditor::dropArray(std::string_view name) {
287 eraseDefinition(layout_.arrays, layout_.arrayIndex(name));
return *
this;
289LayoutEditor &LayoutEditor::dropColumn(std::string_view name) {
290 eraseDefinition(layout_.columns, layout_.columnIndex(name));
return *
this;
292LayoutEditor &LayoutEditor::dropAssociate(std::string_view name) {
293 eraseDefinition(layout_.associates, layout_.associateIndex(name));
return *
this;
295LayoutEditor &LayoutEditor::replaceParameter(std::string_view name,
296 ParameterDefinition definition) {
297 replaceDefinition(layout_.parameters, layout_.parameterIndex(name), std::move(definition));
300LayoutEditor &LayoutEditor::replaceArray(std::string_view name, ArrayDefinition definition) {
301 replaceDefinition(layout_.arrays, layout_.arrayIndex(name), std::move(definition));
304LayoutEditor &LayoutEditor::replaceColumn(std::string_view name, ColumnDefinition definition) {
305 replaceDefinition(layout_.columns, layout_.columnIndex(name), std::move(definition));
308LayoutEditor &LayoutEditor::replaceAssociate(std::string_view name,
309 AssociateDefinition definition) {
310 replaceDefinition(layout_.associates, layout_.associateIndex(name), std::move(definition));
313LayoutEditor &LayoutEditor::addParameter(ParameterDefinition definition) {
314 layout_ = LayoutBuilder(layout_).addParameter(std::move(definition)).build();
return *
this;
316LayoutEditor &LayoutEditor::addArray(ArrayDefinition definition) {
317 layout_ = LayoutBuilder(layout_).addArray(std::move(definition)).build();
return *
this;
319LayoutEditor &LayoutEditor::addColumn(ColumnDefinition definition) {
320 layout_ = LayoutBuilder(layout_).addColumn(std::move(definition)).build();
return *
this;
322LayoutEditor &LayoutEditor::addAssociate(AssociateDefinition definition) {
323 layout_ = LayoutBuilder(layout_).addAssociate(std::move(definition)).build();
return *
this;
325Layout LayoutEditor::build()
const {
return LayoutBuilder(layout_).build(); }
327RowMask::RowMask(std::size_t rows,
bool selected) : selected_(rows, selected ? 1U : 0U) {}
328std::size_t RowMask::count() const noexcept {
329 return static_cast<std::size_t
>(std::count(selected_.begin(), selected_.end(), 1U));
331bool RowMask::test(std::size_t row)
const {
return selected_.at(row) != 0; }
332void RowMask::set(std::size_t row,
bool selected) { selected_.at(row) = selected ? 1U : 0U; }
333RowMask &RowMask::operator&=(
const RowMask &other) {
334 if (size() != other.size()) stateError(
"row masks have different sizes");
335 for (std::size_t row = 0; row < size(); ++row) selected_[row] &= other.selected_[row];
338RowMask &RowMask::operator|=(
const RowMask &other) {
339 if (size() != other.size()) stateError(
"row masks have different sizes");
340 for (std::size_t row = 0; row < size(); ++row) selected_[row] |= other.selected_[row];
343RowMask RowMask::operator~()
const {
344 RowMask result = *
this;
345 for (
auto &selected : result.selected_) selected = selected ? 0U : 1U;
349Scalar RowView::value(std::size_t column)
const {
350 if (!page_) stateError(
"row view has no page");
351 if (row_ < 0 || row_ >= page_->rowCount()) stateError(
"row index is out of range");
352 return scalarAt(page_->column(column),
static_cast<std::size_t
>(row_));
354Scalar RowView::value(std::string_view column)
const {
355 return value(page_->layout().columnIndex(column));
358RowView Page::row(std::int64_t index)
const {
359 if (index < 0 || index >= rowCount_) stateError(
"row index is out of range");
360 return RowView(
this, index);
363RowMask Page::matchRows(std::string_view name,
364 const std::function<
bool(
const Scalar &)> &predicate)
const {
365 if (!predicate) stateError(
"row predicate is empty");
366 const Values &values = column(name);
367 RowMask result(
static_cast<std::size_t
>(rowCount_));
368 for (std::size_t rowIndex = 0; rowIndex < static_cast<std::size_t>(rowCount_); ++rowIndex)
369 result.set(rowIndex, predicate(scalarAt(values, rowIndex)));
373Page Page::filtered(
const RowMask &mask)
const {
374 if (mask.size() !=
static_cast<std::size_t
>(rowCount_))
375 stateError(
"row mask size does not match the page");
376 Page result(layout_, LoadMode::None);
377 result.maxTransformationElements_ = maxTransformationElements_;
378 result.number_ = number_;
379 result.recovered_ = recovered_;
380 std::uint64_t elements = 0;
381 const auto addElements = [&](std::uint64_t amount) {
382 if (amount > maxTransformationElements_ - elements)
383 throw LimitError(ErrorKind::Limit,
384 "page filtering exceeds transformation output limit");
387 for (std::size_t index = 0; index < parameters_.size(); ++index)
388 if (parameterLoaded(index)) {
390 result.setParameter(index, parameters_[index]);
392 for (std::size_t index = 0; index < arrays_.size(); ++index)
393 if (arrayLoaded(index)) {
394 addElements(valueCount(arrays_[index].values));
395 result.setArray(index, arrays_[index]);
397 for (std::size_t columnIndex = 0; columnIndex < columns_.size(); ++columnIndex) {
398 if (!columnLoaded(columnIndex))
continue;
399 addElements(
static_cast<std::uint64_t
>(mask.count()));
400 Values selected = emptyValues(layout().columns[columnIndex].type);
401 for (std::size_t rowIndex = 0; rowIndex < mask.size(); ++rowIndex)
402 if (mask.test(rowIndex)) appendScalar(selected, scalarAt(columns_[columnIndex], rowIndex));
403 result.setColumn(columnIndex, std::move(selected));
405 result.rowCount_ =
static_cast<std::int64_t
>(mask.count());
409Page Page::projected(
const ReadRequest &request)
const {
410 const auto parameterSelection = selectedFields(request.parameters, namesOf(layout().parameters));
411 const auto arraySelection = selectedFields(request.arrays, namesOf(layout().arrays));
412 const auto columnSelection = selectedFields(request.columns, namesOf(layout().columns));
413 Page result(layout_, LoadMode::None);
414 result.maxTransformationElements_ = maxTransformationElements_;
415 result.number_ = number_;
416 result.recovered_ = recovered_;
417 for (std::size_t index = 0; index < parameters_.size(); ++index)
418 if (parameterSelection[index] && parameterLoaded(index)) result.setParameter(index, parameters_[index]);
419 for (std::size_t index = 0; index < arrays_.size(); ++index)
420 if (arraySelection[index] && arrayLoaded(index)) result.setArray(index, arrays_[index]);
421 for (std::size_t index = 0; index < columns_.size(); ++index)
422 if (columnSelection[index] && columnLoaded(index)) result.setColumn(index, columns_[index]);
423 RowMask rows(
static_cast<std::size_t
>(rowCount_));
424 for (std::int64_t index = 0; index < rowCount_; ++index)
425 rows.set(
static_cast<std::size_t
>(index), selectedRow(request.rows, index, rowCount_));
426 result.rowCount_ = rowCount_;
427 return result.filtered(rows);
430Page convertUnits(
const Page &page, FieldKind kind, std::string_view name,
431 std::optional<std::string> units,
long double factor) {
432 Layout changed = page.layout();
433 if (!std::isfinite(factor)) typeError(
"unit conversion factor must be finite");
435 case FieldKind::Parameter: changed.parameters.at(changed.parameterIndex(name)).units = units;
break;
436 case FieldKind::Array: changed.arrays.at(changed.arrayIndex(name)).units = units;
break;
437 case FieldKind::Column: changed.columns.at(changed.columnIndex(name)).units = units;
break;
439 auto layout = std::make_shared<const Layout>(LayoutBuilder(std::move(changed)).build());
440 Page result(layout, LoadMode::None);
441 result.maxTransformationElements_ = page.maxTransformationElements_;
442 result.number_ = page.number_;
443 result.recovered_ = page.recovered_;
444 result.rowCount_ = page.rowCount_;
445 for (std::size_t index = 0; index < page.parameters_.size(); ++index) {
446 if (!page.parameterLoaded(index))
continue;
447 Scalar value = page.parameters_[index];
448 if (kind == FieldKind::Parameter && page.layout().parameters[index].name == name)
449 value = scaledScalar(value, page.layout().parameters[index].type, factor);
450 result.setParameter(index, std::move(value));
452 for (std::size_t index = 0; index < page.arrays_.size(); ++index) {
453 if (!page.arrayLoaded(index))
continue;
454 ArrayData value = page.arrays_[index];
455 if (kind == FieldKind::Array && page.layout().arrays[index].name == name)
456 value.values = scaledValues(value.values, page.layout().arrays[index].type, factor);
457 result.setArray(index, std::move(value));
459 for (std::size_t index = 0; index < page.columns_.size(); ++index) {
460 if (!page.columnLoaded(index))
continue;
461 Values value = page.columns_[index];
462 if (kind == FieldKind::Column && page.layout().columns[index].name == name)
463 value = scaledValues(value, page.layout().columns[index].type, factor);
464 result.setColumn(index, std::move(value));
466 result.rowCount_ = page.rowCount_;
470void copyDataset(Reader &reader,
const std::filesystem::path &output, CopyOptions options) {
471 Layout outputLayout = options.transformLayout ? options.transformLayout(reader.layout())
473 auto shared = std::make_shared<const Layout>(outputLayout);
474 Writer writer = Writer::create(output, outputLayout, options.writer);
475 while (
auto page = reader.next(options.read)) {
476 if (options.transformPage) {
477 auto transformed = options.transformPage(std::move(*page), shared);
478 if (transformed) writer.write(std::move(*transformed));
480 writer.write(std::move(*page));