diff --git a/core/include/Common/SortUtils.h b/core/include/Common/SortUtils.h index 8afa3c8..0536439 100644 --- a/core/include/Common/SortUtils.h +++ b/core/include/Common/SortUtils.h @@ -37,7 +37,23 @@ struct SortUtils { } /** - * Sorts a container according to indices passed in parameter + * Sorts a container according to indices passed in parameter. The number of data in the + * container must be a multiple of the number of indices used to sort the container. + * + * Example 1: + * container: {1, 2, 3, 4, 5, 6} + * sortPermutation: {1, 0} + * + * Values will be sorted three by three, and the result will be: + * {4, 5, 6, 1, 2, 3} + * + * Example 2: + * container: {1, 2, 3, 4, 5, 6} + * sortPermutation: {2, 0, 1} + * + * Values will be sorted two by two, and the result will be: + * {5, 6, 1, 2, 3, 4} + * * @param container the container sorted * @param sortPermutation the indices used to sort the container * @return the container sorted @@ -45,18 +61,24 @@ struct SortUtils { * indices and its range is [0 ; vector.size()[ ) */ template - static Container sort(const Container &container, const std::vector &sortPermutation) + static Container sort(const Container &container, int nbValues, + const std::vector &sortPermutation) { - if (container.size() != sortPermutation.size()) { + auto containerSize = container.size(); + if (containerSize % nbValues != 0 + || ((containerSize / nbValues) != sortPermutation.size())) { return Container{}; } // Inits result auto sortedData = Container{}; - sortedData.resize(container.size()); + sortedData.reserve(containerSize); - std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(), - [&container](int i) { return container.at(i); }); + for (auto i = 0, componentIndex = 0, permutationIndex = 0; i < containerSize; + ++i, componentIndex = i % nbValues, permutationIndex = i / nbValues) { + auto insertIndex = sortPermutation.at(permutationIndex) * nbValues + componentIndex; + sortedData.append(container.at(insertIndex)); + } return sortedData; } diff --git a/core/include/Data/ArrayData.h b/core/include/Data/ArrayData.h index 8097f53..08bbcb4 100644 --- a/core/include/Data/ArrayData.h +++ b/core/include/Data/ArrayData.h @@ -1,6 +1,7 @@ #ifndef SCIQLOP_ARRAYDATA_H #define SCIQLOP_ARRAYDATA_H +#include "Data/ArrayDataIterator.h" #include #include @@ -12,35 +13,93 @@ template class ArrayData; -using DataContainer = QVector >; +using DataContainer = QVector; namespace arraydata_detail { /// Struct used to sort ArrayData template struct Sort { - static std::shared_ptr > sort(const DataContainer &data, + static std::shared_ptr > sort(const DataContainer &data, int nbComponents, const std::vector &sortPermutation) { - auto nbComponents = data.size(); - auto sortedData = DataContainer(nbComponents); - - for (auto i = 0; i < nbComponents; ++i) { - sortedData[i] = SortUtils::sort(data.at(i), sortPermutation); - } - - return std::make_shared >(std::move(sortedData)); + return std::make_shared >( + SortUtils::sort(data, nbComponents, sortPermutation), nbComponents); } }; /// Specialization for uni-dimensional ArrayData template <> struct Sort<1> { - static std::shared_ptr > sort(const DataContainer &data, + static std::shared_ptr > sort(const DataContainer &data, int nbComponents, const std::vector &sortPermutation) { - return std::make_shared >(SortUtils::sort(data.at(0), sortPermutation)); + Q_UNUSED(nbComponents) + return std::make_shared >(SortUtils::sort(data, 1, sortPermutation)); + } +}; + +template +class IteratorValue : public ArrayDataIteratorValue::Impl { +public: + explicit IteratorValue(const DataContainer &container, int nbComponents, bool begin) + : m_It{begin ? container.cbegin() : container.cend()}, m_NbComponents{nbComponents} + { + } + + IteratorValue(const IteratorValue &other) = default; + + std::unique_ptr clone() const override + { + return std::make_unique >(*this); + } + + bool equals(const ArrayDataIteratorValue::Impl &other) const override try { + const auto &otherImpl = dynamic_cast(other); + return std::tie(m_It, m_NbComponents) == std::tie(otherImpl.m_It, otherImpl.m_NbComponents); + } + catch (const std::bad_cast &) { + return false; + } + + void next() override { std::advance(m_It, m_NbComponents); } + void prev() override { std::advance(m_It, -m_NbComponents); } + + double at(int componentIndex) const override { return *(m_It + componentIndex); } + double first() const override { return *m_It; } + double min() const override + { + auto values = this->values(); + auto end = values.cend(); + auto it = std::min_element(values.cbegin(), end, [](const auto &v1, const auto &v2) { + return SortUtils::minCompareWithNaN(v1, v2); + }); + + return it != end ? *it : std::numeric_limits::quiet_NaN(); + } + double max() const override + { + auto values = this->values(); + auto end = values.cend(); + auto it = std::max_element(values.cbegin(), end, [](const auto &v1, const auto &v2) { + return SortUtils::maxCompareWithNaN(v1, v2); + }); + return it != end ? *it : std::numeric_limits::quiet_NaN(); + } + +private: + std::vector values() const + { + auto result = std::vector{}; + for (auto i = 0; i < m_NbComponents; ++i) { + result.push_back(*(m_It + i)); + } + + return result; } + + DataContainer::const_iterator m_It; + int m_NbComponents; }; } // namespace arraydata_detail @@ -58,100 +117,6 @@ struct Sort<1> { template class ArrayData { public: - class IteratorValue { - public: - explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{} - { - for (auto i = 0; i < container.size(); ++i) { - m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend()); - } - } - - double at(int index) const { return *m_Its.at(index); } - double first() const { return *m_Its.front(); } - - /// @return the min value among all components - double min() const - { - auto end = m_Its.cend(); - auto it = std::min_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) { - return SortUtils::minCompareWithNaN(*it1, *it2); - }); - return it != end ? **it : std::numeric_limits::quiet_NaN(); - } - - /// @return the max value among all components - double max() const - { - auto end = m_Its.cend(); - auto it = std::max_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) { - return SortUtils::maxCompareWithNaN(*it1, *it2); - }); - return it != end ? **it : std::numeric_limits::quiet_NaN(); - } - - void next() - { - for (auto &it : m_Its) { - ++it; - } - } - - void prev() - { - for (auto &it : m_Its) { - --it; - } - } - - bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; } - - private: - std::vector m_Its; - }; - - class Iterator { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = const IteratorValue; - using difference_type = std::ptrdiff_t; - using pointer = value_type *; - using reference = value_type &; - - Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {} - - virtual ~Iterator() noexcept = default; - Iterator(const Iterator &) = default; - Iterator(Iterator &&) = default; - Iterator &operator=(const Iterator &) = default; - Iterator &operator=(Iterator &&) = default; - - Iterator &operator++() - { - m_CurrentValue.next(); - return *this; - } - - Iterator &operator--() - { - m_CurrentValue.prev(); - return *this; - } - - pointer operator->() const { return &m_CurrentValue; } - reference operator*() const { return m_CurrentValue; } - - bool operator==(const Iterator &other) const - { - return m_CurrentValue == other.m_CurrentValue; - } - - bool operator!=(const Iterator &other) const { return !(*this == other); } - - private: - IteratorValue m_CurrentValue; - }; - // ///// // // Ctors // // ///// // @@ -161,37 +126,34 @@ public: * @param data the data the ArrayData will hold */ template > - explicit ArrayData(QVector data) : m_Data{1, QVector{}} + explicit ArrayData(DataContainer data) : m_Data{std::move(data)}, m_NbComponents{1} { - m_Data[0] = std::move(data); } /** - * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be - * greater than 2 and each component must have the same number of values + * Ctor for a two-dimensional ArrayData. The number of components (number of lines) must be + * greater than 2 and must be a divisor of the total number of data in the vector * @param data the data the ArrayData will hold - * @throws std::invalid_argument if the number of components is less than 2 - * @remarks if the number of values is not the same for each component, no value is set + * @param nbComponents the number of components + * @throws std::invalid_argument if the number of components is less than 2 or is not a divisor + * of the size of the data */ template > - explicit ArrayData(DataContainer data) + explicit ArrayData(DataContainer data, int nbComponents) + : m_Data{std::move(data)}, m_NbComponents{nbComponents} { - auto nbComponents = data.size(); if (nbComponents < 2) { throw std::invalid_argument{ - QString{"A multidimensional ArrayData must have at least 2 components (found: %1"} - .arg(data.size()) + QString{"A multidimensional ArrayData must have at least 2 components (found: %1)"} + .arg(nbComponents) .toStdString()}; } - auto nbValues = data.front().size(); - if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) { - return component.size() == nbValues; - })) { - m_Data = std::move(data); - } - else { - m_Data = DataContainer{nbComponents, QVector{}}; + if (m_Data.size() % m_NbComponents != 0) { + throw std::invalid_argument{QString{ + "The number of components (%1) is inconsistent with the total number of data (%2)"} + .arg(m_Data.size(), nbComponents) + .toStdString()}; } } @@ -200,6 +162,7 @@ public: { QReadLocker otherLocker{&other.m_Lock}; m_Data = other.m_Data; + m_NbComponents = other.m_NbComponents; } // /////////////// // @@ -218,91 +181,75 @@ public: QWriteLocker locker{&m_Lock}; QReadLocker otherLocker{&other.m_Lock}; - auto nbComponents = m_Data.size(); - if (nbComponents != other.m_Data.size()) { + if (m_NbComponents != other.componentCount()) { return; } - for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) { - if (prepend) { - const auto &otherData = other.data(componentIndex); - const auto otherDataSize = otherData.size(); - - auto &data = m_Data[componentIndex]; - data.insert(data.begin(), otherDataSize, 0.); - - for (auto i = 0; i < otherDataSize; ++i) { - data.replace(i, otherData.at(i)); - } - } - else { - m_Data[componentIndex] += other.data(componentIndex); + if (prepend) { + auto otherDataSize = other.m_Data.size(); + m_Data.insert(m_Data.begin(), otherDataSize, 0.); + for (auto i = 0; i < otherDataSize; ++i) { + m_Data.replace(i, other.m_Data.at(i)); } } + else { + m_Data.append(other.m_Data); + } } void clear() { QWriteLocker locker{&m_Lock}; - - auto nbComponents = m_Data.size(); - for (auto i = 0; i < nbComponents; ++i) { - m_Data[i].clear(); - } + m_Data.clear(); } - int componentCount() const noexcept { return m_Data.size(); } - - /** - * @return the data of a component - * @param componentIndex the index of the component to retrieve the data - * @return the component's data, empty vector if the index is invalid - */ - QVector data(int componentIndex) const noexcept - { - QReadLocker locker{&m_Lock}; - - return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex) - : QVector{}; - } + int componentCount() const noexcept { return m_NbComponents; } /// @return the size (i.e. number of values) of a single component /// @remarks in a case of a two-dimensional ArrayData, each component has the same size int size() const { QReadLocker locker{&m_Lock}; - return m_Data[0].size(); + return m_Data.size() / m_NbComponents; } std::shared_ptr > sort(const std::vector &sortPermutation) { QReadLocker locker{&m_Lock}; - return arraydata_detail::Sort::sort(m_Data, sortPermutation); + return arraydata_detail::Sort::sort(m_Data, m_NbComponents, sortPermutation); } // ///////// // // Iterators // // ///////// // - Iterator cbegin() const { return Iterator{m_Data, true}; } - Iterator cend() const { return Iterator{m_Data, false}; } + ArrayDataIterator cbegin() const + { + return ArrayDataIterator{ArrayDataIteratorValue{ + std::make_unique >(m_Data, m_NbComponents, true)}}; + } + ArrayDataIterator cend() const + { + return ArrayDataIterator{ + ArrayDataIteratorValue{std::make_unique >( + m_Data, m_NbComponents, false)}}; + } - // ///////////// // - // 1-dim methods // - // ///////////// // /** * @return the data at a specified index * @remarks index must be a valid position - * @remarks this method is only available for a unidimensional ArrayData */ - template > double at(int index) const noexcept { QReadLocker locker{&m_Lock}; - return m_Data[0].at(index); + return m_Data.at(index); } + // ///////////// // + // 1-dim methods // + // ///////////// // + /** * @return the data as a vector, as a const reference * @remarks this method is only available for a unidimensional ArrayData @@ -311,37 +258,13 @@ public: const QVector &cdata() const noexcept { QReadLocker locker{&m_Lock}; - return m_Data.at(0); - } - - /** - * @return the data as a vector - * @remarks this method is only available for a unidimensional ArrayData - */ - template > - QVector data() const noexcept - { - QReadLocker locker{&m_Lock}; - return m_Data[0]; - } - - // ///////////// // - // 2-dim methods // - // ///////////// // - - /** - * @return the data - * @remarks this method is only available for a two-dimensional ArrayData - */ - template > - DataContainer data() const noexcept - { - QReadLocker locker{&m_Lock}; return m_Data; } private: DataContainer m_Data; + /// Number of components (lines). Is always 1 in a 1-dim ArrayData + int m_NbComponents; mutable QReadWriteLock m_Lock; }; diff --git a/core/include/Data/ArrayDataIterator.h b/core/include/Data/ArrayDataIterator.h new file mode 100644 index 0000000..af46ede --- /dev/null +++ b/core/include/Data/ArrayDataIterator.h @@ -0,0 +1,56 @@ +#ifndef SCIQLOP_ARRAYDATAITERATOR_H +#define SCIQLOP_ARRAYDATAITERATOR_H + +#include "CoreGlobal.h" +#include "Data/SqpIterator.h" + +#include + +/** + * @brief The ArrayDataIteratorValue class represents the current value of an array data iterator. + * It offers standard access methods for the data in the series (at(), first()), but it is up to + * each array data to define its own implementation of how to retrieve this data (one-dim or two-dim + * array), by implementing the ArrayDataIteratorValue::Impl interface + * @sa ArrayDataIterator + */ +class SCIQLOP_CORE_EXPORT ArrayDataIteratorValue { +public: + struct Impl { + virtual ~Impl() noexcept = default; + virtual std::unique_ptr clone() const = 0; + virtual bool equals(const Impl &other) const = 0; + virtual void next() = 0; + virtual void prev() = 0; + virtual double at(int componentIndex) const = 0; + virtual double first() const = 0; + virtual double min() const = 0; + virtual double max() const = 0; + }; + + explicit ArrayDataIteratorValue(std::unique_ptr impl); + ArrayDataIteratorValue(const ArrayDataIteratorValue &other); + ArrayDataIteratorValue(ArrayDataIteratorValue &&other) = default; + ArrayDataIteratorValue &operator=(ArrayDataIteratorValue other); + + bool equals(const ArrayDataIteratorValue &other) const; + + /// Advances to the next value + void next(); + /// Moves back to the previous value + void prev(); + /// Gets value of a specified component + double at(int componentIndex) const; + /// Gets value of first component + double first() const; + /// Gets min value among all components + double min() const; + /// Gets max value among all components + double max() const; + +private: + std::unique_ptr m_Impl; +}; + +using ArrayDataIterator = SqpIterator; + +#endif // SCIQLOP_ARRAYDATAITERATOR_H diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index 1606810..c269c22 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -69,8 +69,8 @@ public: double maxValue() const override { return m_ValuesIt->max(); } private: - ArrayData<1>::Iterator m_XIt; - typename ArrayData::Iterator m_ValuesIt; + ArrayDataIterator m_XIt; + ArrayDataIterator m_ValuesIt; }; } // namespace dataseries_detail diff --git a/core/include/Data/DataSeriesIterator.h b/core/include/Data/DataSeriesIterator.h index 78c05af..3489d23 100644 --- a/core/include/Data/DataSeriesIterator.h +++ b/core/include/Data/DataSeriesIterator.h @@ -2,6 +2,7 @@ #define SCIQLOP_DATASERIESITERATOR_H #include "CoreGlobal.h" +#include "Data/SqpIterator.h" #include @@ -54,35 +55,6 @@ private: std::unique_ptr m_Impl; }; -/** - * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all - * operators needed for a standard forward iterator - * @sa http://www.cplusplus.com/reference/iterator/ - */ -class SCIQLOP_CORE_EXPORT DataSeriesIterator { -public: - using iterator_category = std::forward_iterator_tag; - using value_type = const DataSeriesIteratorValue; - using difference_type = std::ptrdiff_t; - using pointer = value_type *; - using reference = value_type &; - - explicit DataSeriesIterator(DataSeriesIteratorValue value); - virtual ~DataSeriesIterator() noexcept = default; - DataSeriesIterator(const DataSeriesIterator &) = default; - DataSeriesIterator(DataSeriesIterator &&) = default; - DataSeriesIterator &operator=(const DataSeriesIterator &) = default; - DataSeriesIterator &operator=(DataSeriesIterator &&) = default; - - DataSeriesIterator &operator++(); - DataSeriesIterator &operator--(); - pointer operator->() const { return &m_CurrentValue; } - reference operator*() const { return m_CurrentValue; } - bool operator==(const DataSeriesIterator &other) const; - bool operator!=(const DataSeriesIterator &other) const; - -private: - DataSeriesIteratorValue m_CurrentValue; -}; +using DataSeriesIterator = SqpIterator; #endif // SCIQLOP_DATASERIESITERATOR_H diff --git a/core/include/Data/SqpIterator.h b/core/include/Data/SqpIterator.h new file mode 100644 index 0000000..8b51389 --- /dev/null +++ b/core/include/Data/SqpIterator.h @@ -0,0 +1,54 @@ +#ifndef SCIQLOP_SQPITERATOR_H +#define SCIQLOP_SQPITERATOR_H + +#include "CoreGlobal.h" + +/** + * @brief The SqpIterator class represents an iterator used in SciQlop. It defines all operators + * needed for a standard forward iterator + * @tparam T the type of object handled in iterator + * @sa http://www.cplusplus.com/reference/iterator/ + */ +template +class SCIQLOP_CORE_EXPORT SqpIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = const T; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + explicit SqpIterator(T value) : m_CurrentValue{std::move(value)} {} + + virtual ~SqpIterator() noexcept = default; + SqpIterator(const SqpIterator &) = default; + SqpIterator(SqpIterator &&) = default; + SqpIterator &operator=(const SqpIterator &) = default; + SqpIterator &operator=(SqpIterator &&) = default; + + SqpIterator &operator++() + { + m_CurrentValue.next(); + return *this; + } + + SqpIterator &operator--() + { + m_CurrentValue.prev(); + return *this; + } + + pointer operator->() const { return &m_CurrentValue; } + reference operator*() const { return m_CurrentValue; } + + bool operator==(const SqpIterator &other) const + { + return m_CurrentValue.equals(other.m_CurrentValue); + } + bool operator!=(const SqpIterator &other) const { return !(*this == other); } + +private: + T m_CurrentValue; +}; + +#endif // SCIQLOP_SQPITERATOR_H diff --git a/core/include/Data/VectorSeries.h b/core/include/Data/VectorSeries.h index 634039c..99d49cd 100644 --- a/core/include/Data/VectorSeries.h +++ b/core/include/Data/VectorSeries.h @@ -11,8 +11,8 @@ class SCIQLOP_CORE_EXPORT VectorSeries : public DataSeries<2> { public: /** - * Ctor. The vectors must have the same size, otherwise a ScalarSeries with no values will be - * created. + * Ctor with three vectors (one per component). The vectors must have the same size, otherwise a + * ScalarSeries with no values will be created. * @param xAxisData x-axis data * @param xvaluesData x-values data * @param yvaluesData y-values data @@ -22,6 +22,10 @@ public: QVector yValuesData, QVector zValuesData, const Unit &xAxisUnit, const Unit &valuesUnit); + /// Default Ctor + explicit VectorSeries(QVector xAxisData, QVector valuesData, + const Unit &xAxisUnit, const Unit &valuesUnit); + std::unique_ptr clone() const; std::shared_ptr subDataSeries(const SqpRange &range) override; diff --git a/core/src/Data/ArrayDataIterator.cpp b/core/src/Data/ArrayDataIterator.cpp new file mode 100644 index 0000000..fb13181 --- /dev/null +++ b/core/src/Data/ArrayDataIterator.cpp @@ -0,0 +1,52 @@ +#include "Data/ArrayDataIterator.h" + +ArrayDataIteratorValue::ArrayDataIteratorValue(std::unique_ptr impl) + : m_Impl{std::move(impl)} +{ +} + +ArrayDataIteratorValue::ArrayDataIteratorValue(const ArrayDataIteratorValue &other) + : m_Impl{other.m_Impl->clone()} +{ +} + +ArrayDataIteratorValue &ArrayDataIteratorValue::operator=(ArrayDataIteratorValue other) +{ + std::swap(m_Impl, other.m_Impl); + return *this; +} + +bool ArrayDataIteratorValue::equals(const ArrayDataIteratorValue &other) const +{ + return m_Impl->equals(*other.m_Impl); +} + +void ArrayDataIteratorValue::next() +{ + m_Impl->next(); +} + +void ArrayDataIteratorValue::prev() +{ + m_Impl->prev(); +} + +double ArrayDataIteratorValue::at(int componentIndex) const +{ + return m_Impl->at(componentIndex); +} + +double ArrayDataIteratorValue::first() const +{ + return m_Impl->first(); +} + +double ArrayDataIteratorValue::min() const +{ + return m_Impl->min(); +} + +double ArrayDataIteratorValue::max() const +{ + return m_Impl->max(); +} diff --git a/core/src/Data/DataSeriesIterator.cpp b/core/src/Data/DataSeriesIterator.cpp index 87a2222..03daf1e 100644 --- a/core/src/Data/DataSeriesIterator.cpp +++ b/core/src/Data/DataSeriesIterator.cpp @@ -56,30 +56,3 @@ double DataSeriesIteratorValue::maxValue() const { return m_Impl->maxValue(); } - -DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value) - : m_CurrentValue{std::move(value)} -{ -} - -DataSeriesIterator &DataSeriesIterator::operator++() -{ - m_CurrentValue.next(); - return *this; -} - -DataSeriesIterator &DataSeriesIterator::operator--() -{ - m_CurrentValue.prev(); - return *this; -} - -bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const -{ - return m_CurrentValue.equals(other.m_CurrentValue); -} - -bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const -{ - return !(*this == other); -} diff --git a/core/src/Data/VectorSeries.cpp b/core/src/Data/VectorSeries.cpp index a8091b2..b7c9bf7 100644 --- a/core/src/Data/VectorSeries.cpp +++ b/core/src/Data/VectorSeries.cpp @@ -1,12 +1,56 @@ #include "Data/VectorSeries.h" +namespace { + +/** + * Flatten the three components of a vector to a single QVector that can be passed to an ArrayData + * + * Example: + * xValues = {1, 2, 3} + * yValues = {4, 5, 6} + * zValues = {7, 8, 9} + * + * result = {1, 4, 7, 2, 5, 8, 3, 6, 9} + * + * @param xValues the x-component values of the vector + * @param yValues the y-component values of the vector + * @param zValues the z-component values of the vector + * @return the single QVector + * @remarks the three components are consumed + * @sa ArrayData + */ +QVector flatten(QVector xValues, QVector yValues, QVector zValues) +{ + if (xValues.size() != yValues.size() || xValues.size() != zValues.size()) { + /// @todo ALX : log + return {}; + } + + auto result = QVector{}; + result.reserve(xValues.size() * 3); + + while (!xValues.isEmpty()) { + result.append({xValues.takeFirst(), yValues.takeFirst(), zValues.takeFirst()}); + } + + return result; +} + +} // namespace + VectorSeries::VectorSeries(QVector xAxisData, QVector xValuesData, QVector yValuesData, QVector zValuesData, const Unit &xAxisUnit, const Unit &valuesUnit) + : VectorSeries{std::move(xAxisData), flatten(std::move(xValuesData), std::move(yValuesData), + std::move(zValuesData)), + xAxisUnit, valuesUnit} +{ +} + +VectorSeries::VectorSeries(QVector xAxisData, QVector valuesData, + const Unit &xAxisUnit, const Unit &valuesUnit) : DataSeries{std::make_shared >(std::move(xAxisData)), xAxisUnit, - std::make_shared >(QVector >{ - std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}), - valuesUnit} + std::make_shared >(std::move(valuesData), 3), valuesUnit} { } diff --git a/core/tests/Data/TestDataSeries.cpp b/core/tests/Data/TestDataSeries.cpp index 410df20..adb30f6 100644 --- a/core/tests/Data/TestDataSeries.cpp +++ b/core/tests/Data/TestDataSeries.cpp @@ -10,6 +10,20 @@ Q_DECLARE_METATYPE(std::shared_ptr) Q_DECLARE_METATYPE(std::shared_ptr) +namespace { + +void validateRange(DataSeriesIterator first, DataSeriesIterator last, const QVector &xData, + const QVector &valuesData) +{ + QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(), + [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); + QVERIFY(std::equal( + first, last, valuesData.cbegin(), valuesData.cend(), + [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; })); +} + +} // namespace + class TestDataSeries : public QObject { Q_OBJECT private: @@ -164,13 +178,7 @@ void TestDataSeries::testCtor() QFETCH(QVector, expectedXAxisData); QFETCH(QVector, expectedValuesData); - auto seriesXAxisData = series->xAxisData()->data(); - auto seriesValuesData = series->valuesData()->data(); - - QVERIFY( - std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin())); - QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(), - seriesValuesData.cbegin())); + validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData); } namespace { @@ -250,13 +258,7 @@ void TestDataSeries::testMerge() QFETCH(QVector, expectedXAxisData); QFETCH(QVector, expectedValuesData); - auto seriesXAxisData = dataSeries->xAxisData()->data(); - auto seriesValuesData = dataSeries->valuesData()->data(); - - QVERIFY( - std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin())); - QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(), - seriesValuesData.cbegin())); + validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData); } void TestDataSeries::testMinXAxisData_data() @@ -438,12 +440,7 @@ void TestDataSeries::testXAxisRange() QFETCH(QVector, expectedValuesData); auto bounds = dataSeries->xAxisRange(min, max); - QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(), - expectedXAxisData.cend(), - [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); - QVERIFY(std::equal( - bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(), - [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; })); + validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData); } void TestDataSeries::testValuesBoundsScalar_data() diff --git a/core/tests/Data/TestOneDimArrayData.cpp b/core/tests/Data/TestOneDimArrayData.cpp index a7f0720..f491a2e 100644 --- a/core/tests/Data/TestOneDimArrayData.cpp +++ b/core/tests/Data/TestOneDimArrayData.cpp @@ -2,6 +2,17 @@ #include #include +namespace { + +void verifyArrayData(const ArrayData<1> &arrayData, const QVector &expectedData) +{ + QVERIFY(std::equal( + arrayData.cbegin(), arrayData.cend(), expectedData.cbegin(), expectedData.cend(), + [](const auto &it, const auto &expectedData) { return it.at(0) == expectedData; })); +} + +} // namespace + class TestOneDimArrayData : public QObject { Q_OBJECT private slots: @@ -9,10 +20,6 @@ private slots: void testData_data(); void testData(); - /// Tests @sa ArrayData::data(int componentIndex) - void testDataByComponentIndex_data(); - void testDataByComponentIndex(); - /// Tests @sa ArrayData::add() void testAdd_data(); void testAdd(); @@ -51,32 +58,7 @@ void TestOneDimArrayData::testData() QFETCH(QVector, expectedData); ArrayData<1> arrayData{inputData}; - QVERIFY(arrayData.data() == expectedData); -} - -void TestOneDimArrayData::testDataByComponentIndex_data() -{ - // Test structure - QTest::addColumn >("inputData"); // array data's input - QTest::addColumn("componentIndex"); // component index to test - QTest::addColumn >("expectedData"); // expected data - - // Test cases - QTest::newRow("validIndex") << QVector{1., 2., 3., 4., 5.} << 0 - << QVector{1., 2., 3., 4., 5.}; - QTest::newRow("invalidIndex1") << QVector{1., 2., 3., 4., 5.} << -1 - << QVector{}; - QTest::newRow("invalidIndex2") << QVector{1., 2., 3., 4., 5.} << 1 << QVector{}; -} - -void TestOneDimArrayData::testDataByComponentIndex() -{ - QFETCH(QVector, inputData); - QFETCH(int, componentIndex); - QFETCH(QVector, expectedData); - - ArrayData<1> arrayData{inputData}; - QVERIFY(arrayData.data(componentIndex) == expectedData); + verifyArrayData(arrayData, expectedData); } void TestOneDimArrayData::testAdd_data() @@ -107,7 +89,7 @@ void TestOneDimArrayData::testAdd() ArrayData<1> other{otherData}; arrayData.add(other, prepend); - QVERIFY(arrayData.data() == expectedData); + verifyArrayData(arrayData, expectedData); } void TestOneDimArrayData::testAt_data() @@ -147,7 +129,7 @@ void TestOneDimArrayData::testClear() ArrayData<1> arrayData{inputData}; arrayData.clear(); - QVERIFY(arrayData.data() == QVector{}); + verifyArrayData(arrayData, QVector{}); } void TestOneDimArrayData::testSize_data() @@ -192,7 +174,7 @@ void TestOneDimArrayData::testSort() ArrayData<1> arrayData{inputData}; auto sortedArrayData = arrayData.sort(sortPermutation); QVERIFY(sortedArrayData != nullptr); - QVERIFY(sortedArrayData->data() == expectedData); + verifyArrayData(*sortedArrayData, expectedData); } QTEST_MAIN(TestOneDimArrayData) diff --git a/core/tests/Data/TestTwoDimArrayData.cpp b/core/tests/Data/TestTwoDimArrayData.cpp index a8de502..f884148 100644 --- a/core/tests/Data/TestTwoDimArrayData.cpp +++ b/core/tests/Data/TestTwoDimArrayData.cpp @@ -2,15 +2,55 @@ #include #include -using DataContainer = QVector >; +using Container = QVector >; +using InputData = QPair, int>; + +namespace { + +InputData flatten(const Container &container) +{ + if (container.isEmpty()) { + return {}; + } + + // We assume here that each component of the container have the same size + auto containerSize = container.size(); + auto componentSize = container.first().size(); + + auto result = QVector{}; + result.reserve(componentSize * containerSize); + + for (auto i = 0; i < componentSize; ++i) { + for (auto j = 0; j < containerSize; ++j) { + result.append(container.at(j).at(i)); + } + } + + return {result, containerSize}; +} + +void verifyArrayData(const ArrayData<2> &arrayData, const Container &expectedData) +{ + auto verifyComponent = [&arrayData](const auto &componentData, const auto &equalFun) { + QVERIFY(std::equal(arrayData.cbegin(), arrayData.cend(), componentData.cbegin(), + componentData.cend(), + [&equalFun](const auto &dataSeriesIt, const auto &expectedValue) { + return equalFun(dataSeriesIt, expectedValue); + })); + }; + + for (auto i = 0; i < expectedData.size(); ++i) { + verifyComponent(expectedData.at(i), [i](const auto &seriesIt, const auto &value) { + return seriesIt.at(i) == value; + }); + } +} + +} // namespace class TestTwoDimArrayData : public QObject { Q_OBJECT private slots: - /// Tests @sa ArrayData::data(int componentIndex) - void testDataByComponentIndex_data(); - void testDataByComponentIndex(); - /// Tests @sa ArrayData ctor void testCtor_data(); void testCtor(); @@ -32,192 +72,167 @@ private slots: void testSort(); }; -void TestTwoDimArrayData::testDataByComponentIndex_data() -{ - // Test structure - QTest::addColumn("inputData"); // array data's input - QTest::addColumn("componentIndex"); // component index to test - QTest::addColumn >("expectedData"); // expected data - - // Test cases - auto inputData - = DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}; - - QTest::newRow("validIndex1") << inputData << 0 << QVector{1., 2., 3., 4., 5.}; - QTest::newRow("validIndex2") << inputData << 1 << QVector{6., 7., 8., 9., 10.}; - QTest::newRow("validIndex3") << inputData << 2 << QVector{11., 12., 13., 14., 15.}; - QTest::newRow("invalidIndex1") << inputData << -1 << QVector{}; - QTest::newRow("invalidIndex2") << inputData << 3 << QVector{}; -} - -void TestTwoDimArrayData::testDataByComponentIndex() -{ - QFETCH(DataContainer, inputData); - QFETCH(int, componentIndex); - QFETCH(QVector, expectedData); - - ArrayData<2> arrayData{inputData}; - QVERIFY(arrayData.data(componentIndex) == expectedData); -} - void TestTwoDimArrayData::testCtor_data() { // Test structure - QTest::addColumn("inputData"); // array data's input - QTest::addColumn("success"); // array data has been successfully constructed - QTest::addColumn("expectedData"); // expected array data (when success) + QTest::addColumn("inputData"); // array data's input + QTest::addColumn("success"); // array data has been successfully constructed + QTest::addColumn("expectedData"); // expected array data (when success) // Test cases - QTest::newRow("validInput") - << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}} - << true - << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}; - QTest::newRow("malformedInput (components of the array data haven't the same size") - << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8.}, {11., 12.}} << true - << DataContainer{{}, {}, {}}; - QTest::newRow("invalidInput (less than tow components") << DataContainer{{1., 2., 3., 4., 5.}} - << false << DataContainer{{}, {}, {}}; + QTest::newRow("validInput") << flatten(Container{{1., 2., 3., 4., 5.}, + {6., 7., 8., 9., 10.}, + {11., 12., 13., 14., 15.}}) + << true << Container{{1., 2., 3., 4., 5.}, + {6., 7., 8., 9., 10.}, + {11., 12., 13., 14., 15.}}; + QTest::newRow("invalidInput (invalid data size") + << InputData{{1., 2., 3., 4., 5., 6., 7.}, 3} << false << Container{{}, {}, {}}; + QTest::newRow("invalidInput (less than two components") + << flatten(Container{{1., 2., 3., 4., 5.}}) << false << Container{{}, {}, {}}; } void TestTwoDimArrayData::testCtor() { - QFETCH(DataContainer, inputData); + QFETCH(InputData, inputData); QFETCH(bool, success); if (success) { - QFETCH(DataContainer, expectedData); - - ArrayData<2> arrayData{inputData}; + QFETCH(Container, expectedData); - for (auto i = 0; i < expectedData.size(); ++i) { - QVERIFY(arrayData.data(i) == expectedData.at(i)); - } + ArrayData<2> arrayData{inputData.first, inputData.second}; + verifyArrayData(arrayData, expectedData); } else { - QVERIFY_EXCEPTION_THROWN(ArrayData<2> arrayData{inputData}, std::invalid_argument); + QVERIFY_EXCEPTION_THROWN(ArrayData<2>(inputData.first, inputData.second), + std::invalid_argument); } } void TestTwoDimArrayData::testAdd_data() { // Test structure - QTest::addColumn("inputData"); // array's data input - QTest::addColumn("otherData"); // array data's input to merge with - QTest::addColumn("prepend"); // prepend or append merge - QTest::addColumn("expectedData"); // expected data after merge + QTest::addColumn("inputData"); // array's data input + QTest::addColumn("otherData"); // array data's input to merge with + QTest::addColumn("prepend"); // prepend or append merge + QTest::addColumn("expectedData"); // expected data after merge // Test cases - auto inputData - = DataContainer{{1., 2., 3., 4., 5.}, {11., 12., 13., 14., 15.}, {21., 22., 23., 24., 25.}}; + auto inputData = flatten( + Container{{1., 2., 3., 4., 5.}, {11., 12., 13., 14., 15.}, {21., 22., 23., 24., 25.}}); - auto vectorContainer = DataContainer{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}}; - auto tensorContainer = DataContainer{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}, - {36., 37., 38.}, {46., 47., 48.}, {56., 57., 58}}; + auto vectorContainer = flatten(Container{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}}); + auto tensorContainer = flatten(Container{{6., 7., 8.}, + {16., 17., 18.}, + {26., 27., 28}, + {36., 37., 38.}, + {46., 47., 48.}, + {56., 57., 58}}); QTest::newRow("appendMerge") << inputData << vectorContainer << false - << DataContainer{{1., 2., 3., 4., 5., 6., 7., 8.}, - {11., 12., 13., 14., 15., 16., 17., 18.}, - {21., 22., 23., 24., 25., 26., 27., 28}}; + << Container{{1., 2., 3., 4., 5., 6., 7., 8.}, + {11., 12., 13., 14., 15., 16., 17., 18.}, + {21., 22., 23., 24., 25., 26., 27., 28}}; QTest::newRow("prependMerge") << inputData << vectorContainer << true - << DataContainer{{6., 7., 8., 1., 2., 3., 4., 5.}, - {16., 17., 18., 11., 12., 13., 14., 15.}, - {26., 27., 28, 21., 22., 23., 24., 25.}}; - QTest::newRow("invalidMerge") << inputData << tensorContainer << false << inputData; + << Container{{6., 7., 8., 1., 2., 3., 4., 5.}, + {16., 17., 18., 11., 12., 13., 14., 15.}, + {26., 27., 28, 21., 22., 23., 24., 25.}}; + QTest::newRow("invalidMerge") << inputData << tensorContainer << false + << Container{{1., 2., 3., 4., 5.}, + {11., 12., 13., 14., 15.}, + {21., 22., 23., 24., 25.}}; } void TestTwoDimArrayData::testAdd() { - QFETCH(DataContainer, inputData); - QFETCH(DataContainer, otherData); + QFETCH(InputData, inputData); + QFETCH(InputData, otherData); QFETCH(bool, prepend); - QFETCH(DataContainer, expectedData); + QFETCH(Container, expectedData); - ArrayData<2> arrayData{inputData}; - ArrayData<2> other{otherData}; + ArrayData<2> arrayData{inputData.first, inputData.second}; + ArrayData<2> other{otherData.first, otherData.second}; arrayData.add(other, prepend); - for (auto i = 0; i < expectedData.size(); ++i) { - QVERIFY(arrayData.data(i) == expectedData.at(i)); - } + verifyArrayData(arrayData, expectedData); } void TestTwoDimArrayData::testClear_data() { // Test structure - QTest::addColumn("inputData"); // array data's input + QTest::addColumn("inputData"); // array data's input // Test cases - QTest::newRow("data1") << DataContainer{ - {1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}; + QTest::newRow("data1") << flatten( + Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}); } void TestTwoDimArrayData::testClear() { - QFETCH(DataContainer, inputData); + QFETCH(InputData, inputData); - ArrayData<2> arrayData{inputData}; + ArrayData<2> arrayData{inputData.first, inputData.second}; arrayData.clear(); - for (auto i = 0; i < inputData.size(); ++i) { - QVERIFY(arrayData.data(i) == QVector{}); - } + auto emptyData = Container(inputData.second, QVector{}); + verifyArrayData(arrayData, emptyData); } void TestTwoDimArrayData::testSize_data() { // Test structure - QTest::addColumn > >("inputData"); // array data's input - QTest::addColumn("expectedSize"); // expected array data size + QTest::addColumn("inputData"); // array data's input + QTest::addColumn("expectedSize"); // expected array data size // Test cases - QTest::newRow("data1") << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}} << 5; - QTest::newRow("data2") << DataContainer{{1., 2., 3., 4., 5.}, - {6., 7., 8., 9., 10.}, - {11., 12., 13., 14., 15.}} + QTest::newRow("data1") << flatten(Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}}) << 5; + QTest::newRow("data2") << flatten(Container{{1., 2., 3., 4., 5.}, + {6., 7., 8., 9., 10.}, + {11., 12., 13., 14., 15.}}) << 5; } void TestTwoDimArrayData::testSize() { - QFETCH(DataContainer, inputData); + QFETCH(InputData, inputData); QFETCH(int, expectedSize); - ArrayData<2> arrayData{inputData}; + ArrayData<2> arrayData{inputData.first, inputData.second}; QVERIFY(arrayData.size() == expectedSize); } void TestTwoDimArrayData::testSort_data() { // Test structure - QTest::addColumn("inputData"); // array data's input + QTest::addColumn("inputData"); // array data's input QTest::addColumn >("sortPermutation"); // permutation used to sort data - QTest::addColumn("expectedData"); // expected data after sorting + QTest::addColumn("expectedData"); // expected data after sorting // Test cases QTest::newRow("data1") - << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}} + << flatten( + Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}) << std::vector{0, 2, 3, 1, 4} - << DataContainer{{1., 3., 4., 2., 5.}, {6., 8., 9., 7., 10.}, {11., 13., 14., 12., 15.}}; + << Container{{1., 3., 4., 2., 5.}, {6., 8., 9., 7., 10.}, {11., 13., 14., 12., 15.}}; QTest::newRow("data2") - << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}} + << flatten( + Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}) << std::vector{2, 4, 3, 0, 1} - << DataContainer{{3., 5., 4., 1., 2.}, {8., 10., 9., 6., 7.}, {13., 15., 14., 11., 12.}}; + << Container{{3., 5., 4., 1., 2.}, {8., 10., 9., 6., 7.}, {13., 15., 14., 11., 12.}}; } void TestTwoDimArrayData::testSort() { - QFETCH(DataContainer, inputData); + QFETCH(InputData, inputData); QFETCH(std::vector, sortPermutation); - QFETCH(DataContainer, expectedData); + QFETCH(Container, expectedData); - ArrayData<2> arrayData{inputData}; + ArrayData<2> arrayData{inputData.first, inputData.second}; auto sortedArrayData = arrayData.sort(sortPermutation); QVERIFY(sortedArrayData != nullptr); - for (auto i = 0; i < expectedData.size(); ++i) { - QVERIFY(sortedArrayData->data(i) == expectedData.at(i)); - } + verifyArrayData(*sortedArrayData, expectedData); } QTEST_MAIN(TestTwoDimArrayData) diff --git a/plugins/amda/tests/TestAmdaResultParser.cpp b/plugins/amda/tests/TestAmdaResultParser.cpp index 5bb2133..70a840f 100644 --- a/plugins/amda/tests/TestAmdaResultParser.cpp +++ b/plugins/amda/tests/TestAmdaResultParser.cpp @@ -17,51 +17,6 @@ QDateTime dateTime(int year, int month, int day, int hours, int minutes, int sec return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC}; } -/// Compares two vectors that can potentially contain NaN values -bool compareVectors(const QVector &v1, const QVector &v2) -{ - if (v1.size() != v2.size()) { - return false; - } - - auto result = true; - auto v2It = v2.cbegin(); - for (auto v1It = v1.cbegin(), v1End = v1.cend(); v1It != v1End && result; ++v1It, ++v2It) { - auto v1Value = *v1It; - auto v2Value = *v2It; - - // If v1 is NaN, v2 has to be NaN too - result = std::isnan(v1Value) ? std::isnan(v2Value) : (v1Value == v2Value); - } - - return result; -} - -bool compareVectors(const QVector > &v1, const QVector > &v2) -{ - if (v1.size() != v2.size()) { - return false; - } - - auto result = true; - for (auto i = 0; i < v1.size() && result; ++i) { - result &= compareVectors(v1.at(i), v2.at(i)); - } - - return result; -} - -QVector > valuesData(const ArrayData<1> &arrayData) -{ - return QVector >{arrayData.data()}; -} - -QVector > valuesData(const ArrayData<2> &arrayData) -{ - return arrayData.data(); -} - - QString inputFilePath(const QString &inputFileName) { return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath(); @@ -106,10 +61,26 @@ struct ExpectedResults { QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit); QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit); - // Checks values : as the vectors can potentially contain NaN values, we must use a - // custom vector comparison method - QVERIFY(compareVectors(dataSeries->xAxisData()->data(), m_XAxisData)); - QVERIFY(compareVectors(valuesData(*dataSeries->valuesData()), m_ValuesData)); + auto verifyRange = [dataSeries](const auto &expectedData, const auto &equalFun) { + QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), expectedData.cbegin(), + expectedData.cend(), + [&equalFun](const auto &dataSeriesIt, const auto &expectedX) { + return equalFun(dataSeriesIt, expectedX); + })); + }; + + // Checks x-axis data + verifyRange(m_XAxisData, [](const auto &seriesIt, const auto &value) { + return seriesIt.x() == value; + }); + + // Checks values data of each component + for (auto i = 0; i < m_ValuesData.size(); ++i) { + verifyRange(m_ValuesData.at(i), [i](const auto &seriesIt, const auto &value) { + auto itValue = seriesIt.value(i); + return (std::isnan(itValue) && std::isnan(value)) || seriesIt.value(i) == value; + }); + } } else { QVERIFY(results == nullptr);