##// END OF EJS Templates
Merge branch 'feature/UpdateArrayDataStruct' into develop
Alexandre Leroux -
r648:340220774853 merge
parent child
Show More
@@ -0,0 +1,56
1 #ifndef SCIQLOP_ARRAYDATAITERATOR_H
2 #define SCIQLOP_ARRAYDATAITERATOR_H
3
4 #include "CoreGlobal.h"
5 #include "Data/SqpIterator.h"
6
7 #include <memory>
8
9 /**
10 * @brief The ArrayDataIteratorValue class represents the current value of an array data iterator.
11 * It offers standard access methods for the data in the series (at(), first()), but it is up to
12 * each array data to define its own implementation of how to retrieve this data (one-dim or two-dim
13 * array), by implementing the ArrayDataIteratorValue::Impl interface
14 * @sa ArrayDataIterator
15 */
16 class SCIQLOP_CORE_EXPORT ArrayDataIteratorValue {
17 public:
18 struct Impl {
19 virtual ~Impl() noexcept = default;
20 virtual std::unique_ptr<Impl> clone() const = 0;
21 virtual bool equals(const Impl &other) const = 0;
22 virtual void next() = 0;
23 virtual void prev() = 0;
24 virtual double at(int componentIndex) const = 0;
25 virtual double first() const = 0;
26 virtual double min() const = 0;
27 virtual double max() const = 0;
28 };
29
30 explicit ArrayDataIteratorValue(std::unique_ptr<Impl> impl);
31 ArrayDataIteratorValue(const ArrayDataIteratorValue &other);
32 ArrayDataIteratorValue(ArrayDataIteratorValue &&other) = default;
33 ArrayDataIteratorValue &operator=(ArrayDataIteratorValue other);
34
35 bool equals(const ArrayDataIteratorValue &other) const;
36
37 /// Advances to the next value
38 void next();
39 /// Moves back to the previous value
40 void prev();
41 /// Gets value of a specified component
42 double at(int componentIndex) const;
43 /// Gets value of first component
44 double first() const;
45 /// Gets min value among all components
46 double min() const;
47 /// Gets max value among all components
48 double max() const;
49
50 private:
51 std::unique_ptr<Impl> m_Impl;
52 };
53
54 using ArrayDataIterator = SqpIterator<ArrayDataIteratorValue>;
55
56 #endif // SCIQLOP_ARRAYDATAITERATOR_H
@@ -0,0 +1,54
1 #ifndef SCIQLOP_SQPITERATOR_H
2 #define SCIQLOP_SQPITERATOR_H
3
4 #include "CoreGlobal.h"
5
6 /**
7 * @brief The SqpIterator class represents an iterator used in SciQlop. It defines all operators
8 * needed for a standard forward iterator
9 * @tparam T the type of object handled in iterator
10 * @sa http://www.cplusplus.com/reference/iterator/
11 */
12 template <typename T>
13 class SCIQLOP_CORE_EXPORT SqpIterator {
14 public:
15 using iterator_category = std::forward_iterator_tag;
16 using value_type = const T;
17 using difference_type = std::ptrdiff_t;
18 using pointer = value_type *;
19 using reference = value_type &;
20
21 explicit SqpIterator(T value) : m_CurrentValue{std::move(value)} {}
22
23 virtual ~SqpIterator() noexcept = default;
24 SqpIterator(const SqpIterator &) = default;
25 SqpIterator(SqpIterator &&) = default;
26 SqpIterator &operator=(const SqpIterator &) = default;
27 SqpIterator &operator=(SqpIterator &&) = default;
28
29 SqpIterator &operator++()
30 {
31 m_CurrentValue.next();
32 return *this;
33 }
34
35 SqpIterator &operator--()
36 {
37 m_CurrentValue.prev();
38 return *this;
39 }
40
41 pointer operator->() const { return &m_CurrentValue; }
42 reference operator*() const { return m_CurrentValue; }
43
44 bool operator==(const SqpIterator &other) const
45 {
46 return m_CurrentValue.equals(other.m_CurrentValue);
47 }
48 bool operator!=(const SqpIterator &other) const { return !(*this == other); }
49
50 private:
51 T m_CurrentValue;
52 };
53
54 #endif // SCIQLOP_SQPITERATOR_H
@@ -0,0 +1,52
1 #include "Data/ArrayDataIterator.h"
2
3 ArrayDataIteratorValue::ArrayDataIteratorValue(std::unique_ptr<ArrayDataIteratorValue::Impl> impl)
4 : m_Impl{std::move(impl)}
5 {
6 }
7
8 ArrayDataIteratorValue::ArrayDataIteratorValue(const ArrayDataIteratorValue &other)
9 : m_Impl{other.m_Impl->clone()}
10 {
11 }
12
13 ArrayDataIteratorValue &ArrayDataIteratorValue::operator=(ArrayDataIteratorValue other)
14 {
15 std::swap(m_Impl, other.m_Impl);
16 return *this;
17 }
18
19 bool ArrayDataIteratorValue::equals(const ArrayDataIteratorValue &other) const
20 {
21 return m_Impl->equals(*other.m_Impl);
22 }
23
24 void ArrayDataIteratorValue::next()
25 {
26 m_Impl->next();
27 }
28
29 void ArrayDataIteratorValue::prev()
30 {
31 m_Impl->prev();
32 }
33
34 double ArrayDataIteratorValue::at(int componentIndex) const
35 {
36 return m_Impl->at(componentIndex);
37 }
38
39 double ArrayDataIteratorValue::first() const
40 {
41 return m_Impl->first();
42 }
43
44 double ArrayDataIteratorValue::min() const
45 {
46 return m_Impl->min();
47 }
48
49 double ArrayDataIteratorValue::max() const
50 {
51 return m_Impl->max();
52 }
@@ -1,117 +1,139
1 1 #ifndef SCIQLOP_SORTUTILS_H
2 2 #define SCIQLOP_SORTUTILS_H
3 3
4 4 #include <algorithm>
5 5 #include <cmath>
6 6 #include <numeric>
7 7 #include <vector>
8 8
9 9 /**
10 10 * Utility class with methods for sorting data
11 11 */
12 12 struct SortUtils {
13 13 /**
14 14 * Generates a vector representing the index of insertion of each data of a container if this
15 15 * one had to be sorted according to a comparison function.
16 16 *
17 17 * For example:
18 18 * If the container is a vector {1; 4; 2; 5; 3} and the comparison function is std::less, the
19 19 * result would be : {0; 3; 1; 4; 2}
20 20 *
21 21 * @tparam Container the type of the container.
22 22 * @tparam Compare the type of the comparison function
23 23 * @param container the container from which to generate the result. The container must have a
24 24 * at() method that returns a value associated to an index
25 25 * @param compare the comparison function
26 26 */
27 27 template <typename Container, typename Compare>
28 28 static std::vector<int> sortPermutation(const Container &container, const Compare &compare)
29 29 {
30 30 auto permutation = std::vector<int>{};
31 31 permutation.resize(container.size());
32 32
33 33 std::iota(permutation.begin(), permutation.end(), 0);
34 34 std::sort(permutation.begin(), permutation.end(),
35 35 [&](int i, int j) { return compare(container.at(i), container.at(j)); });
36 36 return permutation;
37 37 }
38 38
39 39 /**
40 * Sorts a container according to indices passed in parameter
40 * Sorts a container according to indices passed in parameter. The number of data in the
41 * container must be a multiple of the number of indices used to sort the container.
42 *
43 * Example 1:
44 * container: {1, 2, 3, 4, 5, 6}
45 * sortPermutation: {1, 0}
46 *
47 * Values will be sorted three by three, and the result will be:
48 * {4, 5, 6, 1, 2, 3}
49 *
50 * Example 2:
51 * container: {1, 2, 3, 4, 5, 6}
52 * sortPermutation: {2, 0, 1}
53 *
54 * Values will be sorted two by two, and the result will be:
55 * {5, 6, 1, 2, 3, 4}
56 *
41 57 * @param container the container sorted
42 58 * @param sortPermutation the indices used to sort the container
43 59 * @return the container sorted
44 60 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
45 61 * indices and its range is [0 ; vector.size()[ )
46 62 */
47 63 template <typename Container>
48 static Container sort(const Container &container, const std::vector<int> &sortPermutation)
64 static Container sort(const Container &container, int nbValues,
65 const std::vector<int> &sortPermutation)
49 66 {
50 if (container.size() != sortPermutation.size()) {
67 auto containerSize = container.size();
68 if (containerSize % nbValues != 0
69 || ((containerSize / nbValues) != sortPermutation.size())) {
51 70 return Container{};
52 71 }
53 72
54 73 // Inits result
55 74 auto sortedData = Container{};
56 sortedData.resize(container.size());
75 sortedData.reserve(containerSize);
57 76
58 std::transform(sortPermutation.cbegin(), sortPermutation.cend(), sortedData.begin(),
59 [&container](int i) { return container.at(i); });
77 for (auto i = 0, componentIndex = 0, permutationIndex = 0; i < containerSize;
78 ++i, componentIndex = i % nbValues, permutationIndex = i / nbValues) {
79 auto insertIndex = sortPermutation.at(permutationIndex) * nbValues + componentIndex;
80 sortedData.append(container.at(insertIndex));
81 }
60 82
61 83 return sortedData;
62 84 }
63 85
64 86 /**
65 87 * Compares two values that can be NaN. This method is intended to be used as a compare function
66 88 * for searching min value by excluding NaN values.
67 89 *
68 90 * Examples of use:
69 91 * - f({1, 3, 2, 4, 5}) will return 1
70 92 * - f({NaN, 3, 2, 4, 5}) will return 2 (NaN is excluded)
71 93 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
72 94 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
73 95 *
74 96 * @param v1 first value
75 97 * @param v2 second value
76 98 * @return true if v1 < v2, false otherwise
77 99 * @sa std::min_element
78 100 */
79 101 template <typename T>
80 102 static bool minCompareWithNaN(const T &v1, const T &v2)
81 103 {
82 104 // Table used with NaN values:
83 105 // NaN < v2 -> false
84 106 // v1 < NaN -> true
85 107 // NaN < NaN -> false
86 108 // v1 < v2 -> v1 < v2
87 109 return std::isnan(v1) ? false : std::isnan(v2) || (v1 < v2);
88 110 }
89 111
90 112 /**
91 113 * Compares two values that can be NaN. This method is intended to be used as a compare function
92 114 * for searching max value by excluding NaN values.
93 115 *
94 116 * Examples of use:
95 117 * - f({1, 3, 2, 4, 5}) will return 5
96 118 * - f({1, 3, 2, 4, NaN}) will return 4 (NaN is excluded)
97 119 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
98 120 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
99 121 *
100 122 * @param v1 first value
101 123 * @param v2 second value
102 124 * @return true if v1 < v2, false otherwise
103 125 * @sa std::max_element
104 126 */
105 127 template <typename T>
106 128 static bool maxCompareWithNaN(const T &v1, const T &v2)
107 129 {
108 130 // Table used with NaN values:
109 131 // NaN < v2 -> true
110 132 // v1 < NaN -> false
111 133 // NaN < NaN -> false
112 134 // v1 < v2 -> v1 < v2
113 135 return std::isnan(v1) ? true : !std::isnan(v2) && (v1 < v2);
114 136 }
115 137 };
116 138
117 139 #endif // SCIQLOP_SORTUTILS_H
@@ -1,348 +1,271
1 1 #ifndef SCIQLOP_ARRAYDATA_H
2 2 #define SCIQLOP_ARRAYDATA_H
3 3
4 #include "Data/ArrayDataIterator.h"
4 5 #include <Common/SortUtils.h>
5 6
6 7 #include <QReadLocker>
7 8 #include <QReadWriteLock>
8 9 #include <QVector>
9 10
10 11 #include <memory>
11 12
12 13 template <int Dim>
13 14 class ArrayData;
14 15
15 using DataContainer = QVector<QVector<double> >;
16 using DataContainer = QVector<double>;
16 17
17 18 namespace arraydata_detail {
18 19
19 20 /// Struct used to sort ArrayData
20 21 template <int Dim>
21 22 struct Sort {
22 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data,
23 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data, int nbComponents,
23 24 const std::vector<int> &sortPermutation)
24 25 {
25 auto nbComponents = data.size();
26 auto sortedData = DataContainer(nbComponents);
27
28 for (auto i = 0; i < nbComponents; ++i) {
29 sortedData[i] = SortUtils::sort(data.at(i), sortPermutation);
30 }
31
32 return std::make_shared<ArrayData<Dim> >(std::move(sortedData));
26 return std::make_shared<ArrayData<Dim> >(
27 SortUtils::sort(data, nbComponents, sortPermutation), nbComponents);
33 28 }
34 29 };
35 30
36 31 /// Specialization for uni-dimensional ArrayData
37 32 template <>
38 33 struct Sort<1> {
39 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data,
34 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data, int nbComponents,
40 35 const std::vector<int> &sortPermutation)
41 36 {
42 return std::make_shared<ArrayData<1> >(SortUtils::sort(data.at(0), sortPermutation));
37 Q_UNUSED(nbComponents)
38 return std::make_shared<ArrayData<1> >(SortUtils::sort(data, 1, sortPermutation));
39 }
40 };
41
42 template <int Dim>
43 class IteratorValue : public ArrayDataIteratorValue::Impl {
44 public:
45 explicit IteratorValue(const DataContainer &container, int nbComponents, bool begin)
46 : m_It{begin ? container.cbegin() : container.cend()}, m_NbComponents{nbComponents}
47 {
48 }
49
50 IteratorValue(const IteratorValue &other) = default;
51
52 std::unique_ptr<ArrayDataIteratorValue::Impl> clone() const override
53 {
54 return std::make_unique<IteratorValue<Dim> >(*this);
55 }
56
57 bool equals(const ArrayDataIteratorValue::Impl &other) const override try {
58 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
59 return std::tie(m_It, m_NbComponents) == std::tie(otherImpl.m_It, otherImpl.m_NbComponents);
60 }
61 catch (const std::bad_cast &) {
62 return false;
63 }
64
65 void next() override { std::advance(m_It, m_NbComponents); }
66 void prev() override { std::advance(m_It, -m_NbComponents); }
67
68 double at(int componentIndex) const override { return *(m_It + componentIndex); }
69 double first() const override { return *m_It; }
70 double min() const override
71 {
72 auto values = this->values();
73 auto end = values.cend();
74 auto it = std::min_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
75 return SortUtils::minCompareWithNaN(v1, v2);
76 });
77
78 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
79 }
80 double max() const override
81 {
82 auto values = this->values();
83 auto end = values.cend();
84 auto it = std::max_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
85 return SortUtils::maxCompareWithNaN(v1, v2);
86 });
87 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
88 }
89
90 private:
91 std::vector<double> values() const
92 {
93 auto result = std::vector<double>{};
94 for (auto i = 0; i < m_NbComponents; ++i) {
95 result.push_back(*(m_It + i));
96 }
97
98 return result;
43 99 }
100
101 DataContainer::const_iterator m_It;
102 int m_NbComponents;
44 103 };
45 104
46 105 } // namespace arraydata_detail
47 106
48 107 /**
49 108 * @brief The ArrayData class represents a dataset for a data series.
50 109 *
51 110 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
52 111 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
53 112 * number of values
54 113 *
55 114 * @tparam Dim the dimension of the ArrayData (one or two)
56 115 * @sa IDataSeries
57 116 */
58 117 template <int Dim>
59 118 class ArrayData {
60 119 public:
61 class IteratorValue {
62 public:
63 explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{}
64 {
65 for (auto i = 0; i < container.size(); ++i) {
66 m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend());
67 }
68 }
69
70 double at(int index) const { return *m_Its.at(index); }
71 double first() const { return *m_Its.front(); }
72
73 /// @return the min value among all components
74 double min() const
75 {
76 auto end = m_Its.cend();
77 auto it = std::min_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) {
78 return SortUtils::minCompareWithNaN(*it1, *it2);
79 });
80 return it != end ? **it : std::numeric_limits<double>::quiet_NaN();
81 }
82
83 /// @return the max value among all components
84 double max() const
85 {
86 auto end = m_Its.cend();
87 auto it = std::max_element(m_Its.cbegin(), end, [](const auto &it1, const auto &it2) {
88 return SortUtils::maxCompareWithNaN(*it1, *it2);
89 });
90 return it != end ? **it : std::numeric_limits<double>::quiet_NaN();
91 }
92
93 void next()
94 {
95 for (auto &it : m_Its) {
96 ++it;
97 }
98 }
99
100 void prev()
101 {
102 for (auto &it : m_Its) {
103 --it;
104 }
105 }
106
107 bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; }
108
109 private:
110 std::vector<DataContainer::value_type::const_iterator> m_Its;
111 };
112
113 class Iterator {
114 public:
115 using iterator_category = std::forward_iterator_tag;
116 using value_type = const IteratorValue;
117 using difference_type = std::ptrdiff_t;
118 using pointer = value_type *;
119 using reference = value_type &;
120
121 Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {}
122
123 virtual ~Iterator() noexcept = default;
124 Iterator(const Iterator &) = default;
125 Iterator(Iterator &&) = default;
126 Iterator &operator=(const Iterator &) = default;
127 Iterator &operator=(Iterator &&) = default;
128
129 Iterator &operator++()
130 {
131 m_CurrentValue.next();
132 return *this;
133 }
134
135 Iterator &operator--()
136 {
137 m_CurrentValue.prev();
138 return *this;
139 }
140
141 pointer operator->() const { return &m_CurrentValue; }
142 reference operator*() const { return m_CurrentValue; }
143
144 bool operator==(const Iterator &other) const
145 {
146 return m_CurrentValue == other.m_CurrentValue;
147 }
148
149 bool operator!=(const Iterator &other) const { return !(*this == other); }
150
151 private:
152 IteratorValue m_CurrentValue;
153 };
154
155 120 // ///// //
156 121 // Ctors //
157 122 // ///// //
158 123
159 124 /**
160 125 * Ctor for a unidimensional ArrayData
161 126 * @param data the data the ArrayData will hold
162 127 */
163 128 template <int D = Dim, typename = std::enable_if_t<D == 1> >
164 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
129 explicit ArrayData(DataContainer data) : m_Data{std::move(data)}, m_NbComponents{1}
165 130 {
166 m_Data[0] = std::move(data);
167 131 }
168 132
169 133 /**
170 * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be
171 * greater than 2 and each component must have the same number of values
134 * Ctor for a two-dimensional ArrayData. The number of components (number of lines) must be
135 * greater than 2 and must be a divisor of the total number of data in the vector
172 136 * @param data the data the ArrayData will hold
173 * @throws std::invalid_argument if the number of components is less than 2
174 * @remarks if the number of values is not the same for each component, no value is set
137 * @param nbComponents the number of components
138 * @throws std::invalid_argument if the number of components is less than 2 or is not a divisor
139 * of the size of the data
175 140 */
176 141 template <int D = Dim, typename = std::enable_if_t<D == 2> >
177 explicit ArrayData(DataContainer data)
142 explicit ArrayData(DataContainer data, int nbComponents)
143 : m_Data{std::move(data)}, m_NbComponents{nbComponents}
178 144 {
179 auto nbComponents = data.size();
180 145 if (nbComponents < 2) {
181 146 throw std::invalid_argument{
182 QString{"A multidimensional ArrayData must have at least 2 components (found: %1"}
183 .arg(data.size())
147 QString{"A multidimensional ArrayData must have at least 2 components (found: %1)"}
148 .arg(nbComponents)
184 149 .toStdString()};
185 150 }
186 151
187 auto nbValues = data.front().size();
188 if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) {
189 return component.size() == nbValues;
190 })) {
191 m_Data = std::move(data);
192 }
193 else {
194 m_Data = DataContainer{nbComponents, QVector<double>{}};
152 if (m_Data.size() % m_NbComponents != 0) {
153 throw std::invalid_argument{QString{
154 "The number of components (%1) is inconsistent with the total number of data (%2)"}
155 .arg(m_Data.size(), nbComponents)
156 .toStdString()};
195 157 }
196 158 }
197 159
198 160 /// Copy ctor
199 161 explicit ArrayData(const ArrayData &other)
200 162 {
201 163 QReadLocker otherLocker{&other.m_Lock};
202 164 m_Data = other.m_Data;
165 m_NbComponents = other.m_NbComponents;
203 166 }
204 167
205 168 // /////////////// //
206 169 // General methods //
207 170 // /////////////// //
208 171
209 172 /**
210 173 * Merges into the array data an other array data. The two array datas must have the same number
211 174 * of components so the merge can be done
212 175 * @param other the array data to merge with
213 176 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
214 177 * inserted at the end
215 178 */
216 179 void add(const ArrayData<Dim> &other, bool prepend = false)
217 180 {
218 181 QWriteLocker locker{&m_Lock};
219 182 QReadLocker otherLocker{&other.m_Lock};
220 183
221 auto nbComponents = m_Data.size();
222 if (nbComponents != other.m_Data.size()) {
184 if (m_NbComponents != other.componentCount()) {
223 185 return;
224 186 }
225 187
226 for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) {
227 if (prepend) {
228 const auto &otherData = other.data(componentIndex);
229 const auto otherDataSize = otherData.size();
230
231 auto &data = m_Data[componentIndex];
232 data.insert(data.begin(), otherDataSize, 0.);
233
234 for (auto i = 0; i < otherDataSize; ++i) {
235 data.replace(i, otherData.at(i));
236 }
237 }
238 else {
239 m_Data[componentIndex] += other.data(componentIndex);
188 if (prepend) {
189 auto otherDataSize = other.m_Data.size();
190 m_Data.insert(m_Data.begin(), otherDataSize, 0.);
191 for (auto i = 0; i < otherDataSize; ++i) {
192 m_Data.replace(i, other.m_Data.at(i));
240 193 }
241 194 }
195 else {
196 m_Data.append(other.m_Data);
197 }
242 198 }
243 199
244 200 void clear()
245 201 {
246 202 QWriteLocker locker{&m_Lock};
247
248 auto nbComponents = m_Data.size();
249 for (auto i = 0; i < nbComponents; ++i) {
250 m_Data[i].clear();
251 }
203 m_Data.clear();
252 204 }
253 205
254 int componentCount() const noexcept { return m_Data.size(); }
255
256 /**
257 * @return the data of a component
258 * @param componentIndex the index of the component to retrieve the data
259 * @return the component's data, empty vector if the index is invalid
260 */
261 QVector<double> data(int componentIndex) const noexcept
262 {
263 QReadLocker locker{&m_Lock};
264
265 return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex)
266 : QVector<double>{};
267 }
206 int componentCount() const noexcept { return m_NbComponents; }
268 207
269 208 /// @return the size (i.e. number of values) of a single component
270 209 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
271 210 int size() const
272 211 {
273 212 QReadLocker locker{&m_Lock};
274 return m_Data[0].size();
213 return m_Data.size() / m_NbComponents;
275 214 }
276 215
277 216 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
278 217 {
279 218 QReadLocker locker{&m_Lock};
280 return arraydata_detail::Sort<Dim>::sort(m_Data, sortPermutation);
219 return arraydata_detail::Sort<Dim>::sort(m_Data, m_NbComponents, sortPermutation);
281 220 }
282 221
283 222 // ///////// //
284 223 // Iterators //
285 224 // ///////// //
286 225
287 Iterator cbegin() const { return Iterator{m_Data, true}; }
288 Iterator cend() const { return Iterator{m_Data, false}; }
226 ArrayDataIterator cbegin() const
227 {
228 return ArrayDataIterator{ArrayDataIteratorValue{
229 std::make_unique<arraydata_detail::IteratorValue<Dim> >(m_Data, m_NbComponents, true)}};
230 }
231 ArrayDataIterator cend() const
232 {
233 return ArrayDataIterator{
234 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim> >(
235 m_Data, m_NbComponents, false)}};
236 }
289 237
290 // ///////////// //
291 // 1-dim methods //
292 // ///////////// //
293 238
294 239 /**
295 240 * @return the data at a specified index
296 241 * @remarks index must be a valid position
297 * @remarks this method is only available for a unidimensional ArrayData
298 242 */
299 template <int D = Dim, typename = std::enable_if_t<D == 1> >
300 243 double at(int index) const noexcept
301 244 {
302 245 QReadLocker locker{&m_Lock};
303 return m_Data[0].at(index);
246 return m_Data.at(index);
304 247 }
305 248
249 // ///////////// //
250 // 1-dim methods //
251 // ///////////// //
252
306 253 /**
307 254 * @return the data as a vector, as a const reference
308 255 * @remarks this method is only available for a unidimensional ArrayData
309 256 */
310 257 template <int D = Dim, typename = std::enable_if_t<D == 1> >
311 258 const QVector<double> &cdata() const noexcept
312 259 {
313 260 QReadLocker locker{&m_Lock};
314 return m_Data.at(0);
315 }
316
317 /**
318 * @return the data as a vector
319 * @remarks this method is only available for a unidimensional ArrayData
320 */
321 template <int D = Dim, typename = std::enable_if_t<D == 1> >
322 QVector<double> data() const noexcept
323 {
324 QReadLocker locker{&m_Lock};
325 return m_Data[0];
326 }
327
328 // ///////////// //
329 // 2-dim methods //
330 // ///////////// //
331
332 /**
333 * @return the data
334 * @remarks this method is only available for a two-dimensional ArrayData
335 */
336 template <int D = Dim, typename = std::enable_if_t<D == 2> >
337 DataContainer data() const noexcept
338 {
339 QReadLocker locker{&m_Lock};
340 261 return m_Data;
341 262 }
342 263
343 264 private:
344 265 DataContainer m_Data;
266 /// Number of components (lines). Is always 1 in a 1-dim ArrayData
267 int m_NbComponents;
345 268 mutable QReadWriteLock m_Lock;
346 269 };
347 270
348 271 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,333 +1,333
1 1 #ifndef SCIQLOP_DATASERIES_H
2 2 #define SCIQLOP_DATASERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/SortUtils.h>
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/IDataSeries.h>
10 10
11 11 #include <QLoggingCategory>
12 12 #include <QReadLocker>
13 13 #include <QReadWriteLock>
14 14 #include <memory>
15 15
16 16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
17 17 // definitions with inheritance. Inline method is used instead
18 18 inline const QLoggingCategory &LOG_DataSeries()
19 19 {
20 20 static const QLoggingCategory category{"DataSeries"};
21 21 return category;
22 22 }
23 23
24 24 template <int Dim>
25 25 class DataSeries;
26 26
27 27 namespace dataseries_detail {
28 28
29 29 template <int Dim>
30 30 class IteratorValue : public DataSeriesIteratorValue::Impl {
31 31 public:
32 32 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
33 33 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
34 34 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
35 35 : dataSeries.valuesData()->cend())
36 36 {
37 37 }
38 38 IteratorValue(const IteratorValue &other) = default;
39 39
40 40 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
41 41 {
42 42 return std::make_unique<IteratorValue<Dim> >(*this);
43 43 }
44 44
45 45 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
46 46 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
47 47 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
48 48 }
49 49 catch (const std::bad_cast &) {
50 50 return false;
51 51 }
52 52
53 53 void next() override
54 54 {
55 55 ++m_XIt;
56 56 ++m_ValuesIt;
57 57 }
58 58
59 59 void prev() override
60 60 {
61 61 --m_XIt;
62 62 --m_ValuesIt;
63 63 }
64 64
65 65 double x() const override { return m_XIt->at(0); }
66 66 double value() const override { return m_ValuesIt->at(0); }
67 67 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
68 68 double minValue() const override { return m_ValuesIt->min(); }
69 69 double maxValue() const override { return m_ValuesIt->max(); }
70 70
71 71 private:
72 ArrayData<1>::Iterator m_XIt;
73 typename ArrayData<Dim>::Iterator m_ValuesIt;
72 ArrayDataIterator m_XIt;
73 ArrayDataIterator m_ValuesIt;
74 74 };
75 75 } // namespace dataseries_detail
76 76
77 77 /**
78 78 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
79 79 *
80 80 * It proposes to set a dimension for the values ​​data.
81 81 *
82 82 * A DataSeries is always sorted on its x-axis data.
83 83 *
84 84 * @tparam Dim The dimension of the values data
85 85 *
86 86 */
87 87 template <int Dim>
88 88 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
89 89 public:
90 90 /// @sa IDataSeries::xAxisData()
91 91 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
92 92 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
93 93
94 94 /// @sa IDataSeries::xAxisUnit()
95 95 Unit xAxisUnit() const override { return m_XAxisUnit; }
96 96
97 97 /// @return the values dataset
98 98 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
99 99 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
100 100
101 101 /// @sa IDataSeries::valuesUnit()
102 102 Unit valuesUnit() const override { return m_ValuesUnit; }
103 103
104 104
105 105 SqpRange range() const override
106 106 {
107 107 if (!m_XAxisData->cdata().isEmpty()) {
108 108 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
109 109 }
110 110
111 111 return SqpRange{};
112 112 }
113 113
114 114 void clear()
115 115 {
116 116 m_XAxisData->clear();
117 117 m_ValuesData->clear();
118 118 }
119 119
120 120 /// Merges into the data series an other data series
121 121 /// @remarks the data series to merge with is cleared after the operation
122 122 void merge(IDataSeries *dataSeries) override
123 123 {
124 124 dataSeries->lockWrite();
125 125 lockWrite();
126 126
127 127 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
128 128 const auto &otherXAxisData = other->xAxisData()->cdata();
129 129 const auto &xAxisData = m_XAxisData->cdata();
130 130
131 131 // As data series are sorted, we can improve performances of merge, by call the sort
132 132 // method only if the two data series overlap.
133 133 if (!otherXAxisData.empty()) {
134 134 auto firstValue = otherXAxisData.front();
135 135 auto lastValue = otherXAxisData.back();
136 136
137 137 auto xAxisDataBegin = xAxisData.cbegin();
138 138 auto xAxisDataEnd = xAxisData.cend();
139 139
140 140 bool prepend;
141 141 bool sortNeeded;
142 142
143 143 if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) {
144 144 // Other data series if after data series
145 145 prepend = false;
146 146 sortNeeded = false;
147 147 }
148 148 else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue)
149 149 == xAxisDataBegin) {
150 150 // Other data series if before data series
151 151 prepend = true;
152 152 sortNeeded = false;
153 153 }
154 154 else {
155 155 // The two data series overlap
156 156 prepend = false;
157 157 sortNeeded = true;
158 158 }
159 159
160 160 // Makes the merge
161 161 m_XAxisData->add(*other->xAxisData(), prepend);
162 162 m_ValuesData->add(*other->valuesData(), prepend);
163 163
164 164 if (sortNeeded) {
165 165 sort();
166 166 }
167 167 }
168 168
169 169 // Clears the other data series
170 170 other->clear();
171 171 }
172 172 else {
173 173 qCWarning(LOG_DataSeries())
174 174 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
175 175 }
176 176 unlock();
177 177 dataSeries->unlock();
178 178 }
179 179
180 180 // ///////// //
181 181 // Iterators //
182 182 // ///////// //
183 183
184 184 DataSeriesIterator cbegin() const override
185 185 {
186 186 return DataSeriesIterator{DataSeriesIteratorValue{
187 187 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, true)}};
188 188 }
189 189
190 190 DataSeriesIterator cend() const override
191 191 {
192 192 return DataSeriesIterator{DataSeriesIteratorValue{
193 193 std::make_unique<dataseries_detail::IteratorValue<Dim> >(*this, false)}};
194 194 }
195 195
196 196 /// @sa IDataSeries::minXAxisData()
197 197 DataSeriesIterator minXAxisData(double minXAxisData) const override
198 198 {
199 199 return std::lower_bound(
200 200 cbegin(), cend(), minXAxisData,
201 201 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
202 202 }
203 203
204 204 /// @sa IDataSeries::maxXAxisData()
205 205 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
206 206 {
207 207 // Gets the first element that greater than max value
208 208 auto it = std::upper_bound(
209 209 cbegin(), cend(), maxXAxisData,
210 210 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
211 211
212 212 return it == cbegin() ? cend() : --it;
213 213 }
214 214
215 215 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
216 216 double maxXAxisData) const override
217 217 {
218 218 if (minXAxisData > maxXAxisData) {
219 219 std::swap(minXAxisData, maxXAxisData);
220 220 }
221 221
222 222 auto begin = cbegin();
223 223 auto end = cend();
224 224
225 225 auto lowerIt = std::lower_bound(
226 226 begin, end, minXAxisData,
227 227 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
228 228 auto upperIt = std::upper_bound(
229 229 begin, end, maxXAxisData,
230 230 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
231 231
232 232 return std::make_pair(lowerIt, upperIt);
233 233 }
234 234
235 235 std::pair<DataSeriesIterator, DataSeriesIterator>
236 236 valuesBounds(double minXAxisData, double maxXAxisData) const override
237 237 {
238 238 // Places iterators to the correct x-axis range
239 239 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
240 240
241 241 // Returns end iterators if the range is empty
242 242 if (xAxisRangeIts.first == xAxisRangeIts.second) {
243 243 return std::make_pair(cend(), cend());
244 244 }
245 245
246 246 // Gets the iterator on the min of all values data
247 247 auto minIt = std::min_element(
248 248 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
249 249 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
250 250 });
251 251
252 252 // Gets the iterator on the max of all values data
253 253 auto maxIt = std::max_element(
254 254 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
255 255 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
256 256 });
257 257
258 258 return std::make_pair(minIt, maxIt);
259 259 }
260 260
261 261 // /////// //
262 262 // Mutexes //
263 263 // /////// //
264 264
265 265 virtual void lockRead() { m_Lock.lockForRead(); }
266 266 virtual void lockWrite() { m_Lock.lockForWrite(); }
267 267 virtual void unlock() { m_Lock.unlock(); }
268 268
269 269 protected:
270 270 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
271 271 /// DataSeries with no values will be created.
272 272 /// @remarks data series is automatically sorted on its x-axis data
273 273 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
274 274 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
275 275 : m_XAxisData{xAxisData},
276 276 m_XAxisUnit{xAxisUnit},
277 277 m_ValuesData{valuesData},
278 278 m_ValuesUnit{valuesUnit}
279 279 {
280 280 if (m_XAxisData->size() != m_ValuesData->size()) {
281 281 clear();
282 282 }
283 283
284 284 // Sorts data if it's not the case
285 285 const auto &xAxisCData = m_XAxisData->cdata();
286 286 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
287 287 sort();
288 288 }
289 289 }
290 290
291 291 /// Copy ctor
292 292 explicit DataSeries(const DataSeries<Dim> &other)
293 293 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
294 294 m_XAxisUnit{other.m_XAxisUnit},
295 295 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
296 296 m_ValuesUnit{other.m_ValuesUnit}
297 297 {
298 298 // Since a series is ordered from its construction and is always ordered, it is not
299 299 // necessary to call the sort method here ('other' is sorted)
300 300 }
301 301
302 302 /// Assignment operator
303 303 template <int D>
304 304 DataSeries &operator=(DataSeries<D> other)
305 305 {
306 306 std::swap(m_XAxisData, other.m_XAxisData);
307 307 std::swap(m_XAxisUnit, other.m_XAxisUnit);
308 308 std::swap(m_ValuesData, other.m_ValuesData);
309 309 std::swap(m_ValuesUnit, other.m_ValuesUnit);
310 310
311 311 return *this;
312 312 }
313 313
314 314 private:
315 315 /**
316 316 * Sorts data series on its x-axis data
317 317 */
318 318 void sort() noexcept
319 319 {
320 320 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
321 321 m_XAxisData = m_XAxisData->sort(permutation);
322 322 m_ValuesData = m_ValuesData->sort(permutation);
323 323 }
324 324
325 325 std::shared_ptr<ArrayData<1> > m_XAxisData;
326 326 Unit m_XAxisUnit;
327 327 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
328 328 Unit m_ValuesUnit;
329 329
330 330 QReadWriteLock m_Lock;
331 331 };
332 332
333 333 #endif // SCIQLOP_DATASERIES_H
@@ -1,88 +1,60
1 1 #ifndef SCIQLOP_DATASERIESITERATOR_H
2 2 #define SCIQLOP_DATASERIESITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 #include "Data/SqpIterator.h"
5 6
6 7 #include <memory>
7 8
8 9 /**
9 10 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
10 11 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
11 12 * each series to define its own implementation of how to retrieve this data, by implementing the
12 13 * DataSeriesIteratorValue::Impl interface
13 14 *
14 15 * @sa DataSeriesIterator
15 16 */
16 17 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
17 18 public:
18 19 struct Impl {
19 20 virtual ~Impl() noexcept = default;
20 21 virtual std::unique_ptr<Impl> clone() const = 0;
21 22 virtual bool equals(const Impl &other) const = 0;
22 23 virtual void next() = 0;
23 24 virtual void prev() = 0;
24 25 virtual double x() const = 0;
25 26 virtual double value() const = 0;
26 27 virtual double value(int componentIndex) const = 0;
27 28 virtual double minValue() const = 0;
28 29 virtual double maxValue() const = 0;
29 30 };
30 31
31 32 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
32 33 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
33 34 DataSeriesIteratorValue(DataSeriesIteratorValue &&other) = default;
34 35 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
35 36
36 37 bool equals(const DataSeriesIteratorValue &other) const;
37 38
38 39 /// Advances to the next value
39 40 void next();
40 41 /// Moves back to the previous value
41 42 void prev();
42 43 /// Gets x-axis data
43 44 double x() const;
44 45 /// Gets value data
45 46 double value() const;
46 47 /// Gets value data depending on an index
47 48 double value(int componentIndex) const;
48 49 /// Gets min of all values data
49 50 double minValue() const;
50 51 /// Gets max of all values data
51 52 double maxValue() const;
52 53
53 54 private:
54 55 std::unique_ptr<Impl> m_Impl;
55 56 };
56 57
57 /**
58 * @brief The DataSeriesIterator class represents an iterator used for data series. It defines all
59 * operators needed for a standard forward iterator
60 * @sa http://www.cplusplus.com/reference/iterator/
61 */
62 class SCIQLOP_CORE_EXPORT DataSeriesIterator {
63 public:
64 using iterator_category = std::forward_iterator_tag;
65 using value_type = const DataSeriesIteratorValue;
66 using difference_type = std::ptrdiff_t;
67 using pointer = value_type *;
68 using reference = value_type &;
69
70 explicit DataSeriesIterator(DataSeriesIteratorValue value);
71 virtual ~DataSeriesIterator() noexcept = default;
72 DataSeriesIterator(const DataSeriesIterator &) = default;
73 DataSeriesIterator(DataSeriesIterator &&) = default;
74 DataSeriesIterator &operator=(const DataSeriesIterator &) = default;
75 DataSeriesIterator &operator=(DataSeriesIterator &&) = default;
76
77 DataSeriesIterator &operator++();
78 DataSeriesIterator &operator--();
79 pointer operator->() const { return &m_CurrentValue; }
80 reference operator*() const { return m_CurrentValue; }
81 bool operator==(const DataSeriesIterator &other) const;
82 bool operator!=(const DataSeriesIterator &other) const;
83
84 private:
85 DataSeriesIteratorValue m_CurrentValue;
86 };
58 using DataSeriesIterator = SqpIterator<DataSeriesIteratorValue>;
87 59
88 60 #endif // SCIQLOP_DATASERIESITERATOR_H
@@ -1,30 +1,34
1 1 #ifndef SCIQLOP_VECTORSERIES_H
2 2 #define SCIQLOP_VECTORSERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/DataSeries.h>
7 7
8 8 /**
9 9 * @brief The VectorSeries class is the implementation for a data series representing a vector.
10 10 */
11 11 class SCIQLOP_CORE_EXPORT VectorSeries : public DataSeries<2> {
12 12 public:
13 13 /**
14 * Ctor. The vectors must have the same size, otherwise a ScalarSeries with no values will be
15 * created.
14 * Ctor with three vectors (one per component). The vectors must have the same size, otherwise a
15 * ScalarSeries with no values will be created.
16 16 * @param xAxisData x-axis data
17 17 * @param xvaluesData x-values data
18 18 * @param yvaluesData y-values data
19 19 * @param zvaluesData z-values data
20 20 */
21 21 explicit VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
22 22 QVector<double> yValuesData, QVector<double> zValuesData,
23 23 const Unit &xAxisUnit, const Unit &valuesUnit);
24 24
25 /// Default Ctor
26 explicit VectorSeries(QVector<double> xAxisData, QVector<double> valuesData,
27 const Unit &xAxisUnit, const Unit &valuesUnit);
28
25 29 std::unique_ptr<IDataSeries> clone() const;
26 30
27 31 std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) override;
28 32 };
29 33
30 34 #endif // SCIQLOP_VECTORSERIES_H
@@ -1,85 +1,58
1 1 #include "Data/DataSeriesIterator.h"
2 2
3 3 DataSeriesIteratorValue::DataSeriesIteratorValue(
4 4 std::unique_ptr<DataSeriesIteratorValue::Impl> impl)
5 5 : m_Impl{std::move(impl)}
6 6 {
7 7 }
8 8
9 9 DataSeriesIteratorValue::DataSeriesIteratorValue(const DataSeriesIteratorValue &other)
10 10 : m_Impl{other.m_Impl->clone()}
11 11 {
12 12 }
13 13
14 14 DataSeriesIteratorValue &DataSeriesIteratorValue::operator=(DataSeriesIteratorValue other)
15 15 {
16 16 std::swap(m_Impl, other.m_Impl);
17 17 return *this;
18 18 }
19 19
20 20 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
21 21 {
22 22 return m_Impl->equals(*other.m_Impl);
23 23 }
24 24
25 25 void DataSeriesIteratorValue::next()
26 26 {
27 27 m_Impl->next();
28 28 }
29 29
30 30 void DataSeriesIteratorValue::prev()
31 31 {
32 32 m_Impl->prev();
33 33 }
34 34
35 35 double DataSeriesIteratorValue::x() const
36 36 {
37 37 return m_Impl->x();
38 38 }
39 39
40 40 double DataSeriesIteratorValue::value() const
41 41 {
42 42 return m_Impl->value();
43 43 }
44 44
45 45 double DataSeriesIteratorValue::value(int componentIndex) const
46 46 {
47 47 return m_Impl->value(componentIndex);
48 48 }
49 49
50 50 double DataSeriesIteratorValue::minValue() const
51 51 {
52 52 return m_Impl->minValue();
53 53 }
54 54
55 55 double DataSeriesIteratorValue::maxValue() const
56 56 {
57 57 return m_Impl->maxValue();
58 58 }
59
60 DataSeriesIterator::DataSeriesIterator(DataSeriesIteratorValue value)
61 : m_CurrentValue{std::move(value)}
62 {
63 }
64
65 DataSeriesIterator &DataSeriesIterator::operator++()
66 {
67 m_CurrentValue.next();
68 return *this;
69 }
70
71 DataSeriesIterator &DataSeriesIterator::operator--()
72 {
73 m_CurrentValue.prev();
74 return *this;
75 }
76
77 bool DataSeriesIterator::operator==(const DataSeriesIterator &other) const
78 {
79 return m_CurrentValue.equals(other.m_CurrentValue);
80 }
81
82 bool DataSeriesIterator::operator!=(const DataSeriesIterator &other) const
83 {
84 return !(*this == other);
85 }
@@ -1,39 +1,83
1 1 #include "Data/VectorSeries.h"
2 2
3 namespace {
4
5 /**
6 * Flatten the three components of a vector to a single QVector that can be passed to an ArrayData
7 *
8 * Example:
9 * xValues = {1, 2, 3}
10 * yValues = {4, 5, 6}
11 * zValues = {7, 8, 9}
12 *
13 * result = {1, 4, 7, 2, 5, 8, 3, 6, 9}
14 *
15 * @param xValues the x-component values of the vector
16 * @param yValues the y-component values of the vector
17 * @param zValues the z-component values of the vector
18 * @return the single QVector
19 * @remarks the three components are consumed
20 * @sa ArrayData
21 */
22 QVector<double> flatten(QVector<double> xValues, QVector<double> yValues, QVector<double> zValues)
23 {
24 if (xValues.size() != yValues.size() || xValues.size() != zValues.size()) {
25 /// @todo ALX : log
26 return {};
27 }
28
29 auto result = QVector<double>{};
30 result.reserve(xValues.size() * 3);
31
32 while (!xValues.isEmpty()) {
33 result.append({xValues.takeFirst(), yValues.takeFirst(), zValues.takeFirst()});
34 }
35
36 return result;
37 }
38
39 } // namespace
40
3 41 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
4 42 QVector<double> yValuesData, QVector<double> zValuesData,
5 43 const Unit &xAxisUnit, const Unit &valuesUnit)
44 : VectorSeries{std::move(xAxisData), flatten(std::move(xValuesData), std::move(yValuesData),
45 std::move(zValuesData)),
46 xAxisUnit, valuesUnit}
47 {
48 }
49
50 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> valuesData,
51 const Unit &xAxisUnit, const Unit &valuesUnit)
6 52 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
7 std::make_shared<ArrayData<2> >(QVector<QVector<double> >{
8 std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}),
9 valuesUnit}
53 std::make_shared<ArrayData<2> >(std::move(valuesData), 3), valuesUnit}
10 54 {
11 55 }
12 56
13 57 std::unique_ptr<IDataSeries> VectorSeries::clone() const
14 58 {
15 59 return std::make_unique<VectorSeries>(*this);
16 60 }
17 61
18 62 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
19 63 {
20 64 auto subXAxisData = QVector<double>();
21 65 auto subXValuesData = QVector<double>();
22 66 auto subYValuesData = QVector<double>();
23 67 auto subZValuesData = QVector<double>();
24 68
25 69 this->lockRead();
26 70 {
27 71 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
28 72 for (auto it = bounds.first; it != bounds.second; ++it) {
29 73 subXAxisData.append(it->x());
30 74 subXValuesData.append(it->value(0));
31 75 subYValuesData.append(it->value(1));
32 76 subZValuesData.append(it->value(2));
33 77 }
34 78 }
35 79 this->unlock();
36 80
37 81 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
38 82 subZValuesData, this->xAxisUnit(), this->valuesUnit());
39 83 }
@@ -1,522 +1,519
1 1 #include "Data/DataSeries.h"
2 2 #include "Data/ScalarSeries.h"
3 3 #include "Data/VectorSeries.h"
4 4
5 5 #include <cmath>
6 6
7 7 #include <QObject>
8 8 #include <QtTest>
9 9
10 10 Q_DECLARE_METATYPE(std::shared_ptr<ScalarSeries>)
11 11 Q_DECLARE_METATYPE(std::shared_ptr<VectorSeries>)
12 12
13 namespace {
14
15 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const QVector<double> &xData,
16 const QVector<double> &valuesData)
17 {
18 QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(),
19 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
20 QVERIFY(std::equal(
21 first, last, valuesData.cbegin(), valuesData.cend(),
22 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
23 }
24
25 } // namespace
26
13 27 class TestDataSeries : public QObject {
14 28 Q_OBJECT
15 29 private:
16 30 template <typename T>
17 31 void testValuesBoundsStructure()
18 32 {
19 33 // ////////////// //
20 34 // Test structure //
21 35 // ////////////// //
22 36
23 37 // Data series to get values bounds
24 38 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
25 39
26 40 // x-axis range
27 41 QTest::addColumn<double>("minXAxis");
28 42 QTest::addColumn<double>("maxXAxis");
29 43
30 44 // Expected results
31 45 QTest::addColumn<bool>(
32 46 "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators)
33 47 QTest::addColumn<double>("expectedMinValue");
34 48 QTest::addColumn<double>("expectedMaxValue");
35 49 }
36 50
37 51 template <typename T>
38 52 void testValuesBounds()
39 53 {
40 54 QFETCH(std::shared_ptr<T>, dataSeries);
41 55 QFETCH(double, minXAxis);
42 56 QFETCH(double, maxXAxis);
43 57
44 58 QFETCH(bool, expectedOK);
45 59 QFETCH(double, expectedMinValue);
46 60 QFETCH(double, expectedMaxValue);
47 61
48 62 auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis);
49 63 auto end = dataSeries->cend();
50 64
51 65 // Checks iterators with expected result
52 66 QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end);
53 67
54 68 if (expectedOK) {
55 69 auto compare = [](const auto &v1, const auto &v2) {
56 70 return (std::isnan(v1) && std::isnan(v2)) || v1 == v2;
57 71 };
58 72
59 73 QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue()));
60 74 QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue()));
61 75 }
62 76 }
63 77
64 78 private slots:
65 79 /// Input test data
66 80 /// @sa testCtor()
67 81 void testCtor_data();
68 82
69 83 /// Tests construction of a data series
70 84 void testCtor();
71 85
72 86 /// Input test data
73 87 /// @sa testMerge()
74 88 void testMerge_data();
75 89
76 90 /// Tests merge of two data series
77 91 void testMerge();
78 92
79 93 /// Input test data
80 94 /// @sa testMinXAxisData()
81 95 void testMinXAxisData_data();
82 96
83 97 /// Tests get min x-axis data of a data series
84 98 void testMinXAxisData();
85 99
86 100 /// Input test data
87 101 /// @sa testMaxXAxisData()
88 102 void testMaxXAxisData_data();
89 103
90 104 /// Tests get max x-axis data of a data series
91 105 void testMaxXAxisData();
92 106
93 107 /// Input test data
94 108 /// @sa testXAxisRange()
95 109 void testXAxisRange_data();
96 110
97 111 /// Tests get x-axis range of a data series
98 112 void testXAxisRange();
99 113
100 114 /// Input test data
101 115 /// @sa testValuesBoundsScalar()
102 116 void testValuesBoundsScalar_data();
103 117
104 118 /// Tests get values bounds of a scalar series
105 119 void testValuesBoundsScalar();
106 120
107 121 /// Input test data
108 122 /// @sa testValuesBoundsVector()
109 123 void testValuesBoundsVector_data();
110 124
111 125 /// Tests get values bounds of a vector series
112 126 void testValuesBoundsVector();
113 127 };
114 128
115 129 void TestDataSeries::testCtor_data()
116 130 {
117 131 // ////////////// //
118 132 // Test structure //
119 133 // ////////////// //
120 134
121 135 // x-axis data
122 136 QTest::addColumn<QVector<double> >("xAxisData");
123 137 // values data
124 138 QTest::addColumn<QVector<double> >("valuesData");
125 139
126 140 // expected x-axis data
127 141 QTest::addColumn<QVector<double> >("expectedXAxisData");
128 142 // expected values data
129 143 QTest::addColumn<QVector<double> >("expectedValuesData");
130 144
131 145 // ////////// //
132 146 // Test cases //
133 147 // ////////// //
134 148
135 149 QTest::newRow("invalidData (different sizes of vectors)")
136 150 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
137 151 << QVector<double>{} << QVector<double>{};
138 152
139 153 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
140 154 << QVector<double>{100., 200., 300., 400., 500.}
141 155 << QVector<double>{1., 2., 3., 4., 5.}
142 156 << QVector<double>{100., 200., 300., 400., 500.};
143 157
144 158 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
145 159 << QVector<double>{100., 200., 300., 400., 500.}
146 160 << QVector<double>{1., 2., 3., 4., 5.}
147 161 << QVector<double>{500., 400., 300., 200., 100.};
148 162
149 163 QTest::newRow("unsortedData2")
150 164 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
151 165 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
152 166 }
153 167
154 168 void TestDataSeries::testCtor()
155 169 {
156 170 // Creates series
157 171 QFETCH(QVector<double>, xAxisData);
158 172 QFETCH(QVector<double>, valuesData);
159 173
160 174 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
161 175 Unit{}, Unit{});
162 176
163 177 // Validates results : we check that the data series is sorted on its x-axis data
164 178 QFETCH(QVector<double>, expectedXAxisData);
165 179 QFETCH(QVector<double>, expectedValuesData);
166 180
167 auto seriesXAxisData = series->xAxisData()->data();
168 auto seriesValuesData = series->valuesData()->data();
169
170 QVERIFY(
171 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
172 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
173 seriesValuesData.cbegin()));
181 validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData);
174 182 }
175 183
176 184 namespace {
177 185
178 186 std::shared_ptr<ScalarSeries> createScalarSeries(QVector<double> xAxisData,
179 187 QVector<double> valuesData)
180 188 {
181 189 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
182 190 Unit{});
183 191 }
184 192
185 193 std::shared_ptr<VectorSeries> createVectorSeries(QVector<double> xAxisData,
186 194 QVector<double> xValuesData,
187 195 QVector<double> yValuesData,
188 196 QVector<double> zValuesData)
189 197 {
190 198 return std::make_shared<VectorSeries>(std::move(xAxisData), std::move(xValuesData),
191 199 std::move(yValuesData), std::move(zValuesData), Unit{},
192 200 Unit{});
193 201 }
194 202
195 203 } // namespace
196 204
197 205 void TestDataSeries::testMerge_data()
198 206 {
199 207 // ////////////// //
200 208 // Test structure //
201 209 // ////////////// //
202 210
203 211 // Data series to merge
204 212 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
205 213 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
206 214
207 215 // Expected values in the first data series after merge
208 216 QTest::addColumn<QVector<double> >("expectedXAxisData");
209 217 QTest::addColumn<QVector<double> >("expectedValuesData");
210 218
211 219 // ////////// //
212 220 // Test cases //
213 221 // ////////// //
214 222
215 223 QTest::newRow("sortedMerge")
216 224 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
217 225 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
218 226 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
219 227 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
220 228
221 229 QTest::newRow("unsortedMerge")
222 230 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
223 231 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
224 232 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
225 233 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
226 234
227 235 QTest::newRow("unsortedMerge2")
228 236 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
229 237 << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
230 238 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
231 239 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
232 240
233 241 QTest::newRow("unsortedMerge3")
234 242 << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
235 243 << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
236 244 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
237 245 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
238 246 }
239 247
240 248 void TestDataSeries::testMerge()
241 249 {
242 250 // Merges series
243 251 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
244 252 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
245 253
246 254 dataSeries->merge(dataSeries2.get());
247 255
248 256 // Validates results : we check that the merge is valid and the data series is sorted on its
249 257 // x-axis data
250 258 QFETCH(QVector<double>, expectedXAxisData);
251 259 QFETCH(QVector<double>, expectedValuesData);
252 260
253 auto seriesXAxisData = dataSeries->xAxisData()->data();
254 auto seriesValuesData = dataSeries->valuesData()->data();
255
256 QVERIFY(
257 std::equal(expectedXAxisData.cbegin(), expectedXAxisData.cend(), seriesXAxisData.cbegin()));
258 QVERIFY(std::equal(expectedValuesData.cbegin(), expectedValuesData.cend(),
259 seriesValuesData.cbegin()));
261 validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData);
260 262 }
261 263
262 264 void TestDataSeries::testMinXAxisData_data()
263 265 {
264 266 // ////////////// //
265 267 // Test structure //
266 268 // ////////////// //
267 269
268 270 // Data series to get min data
269 271 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
270 272
271 273 // Min data
272 274 QTest::addColumn<double>("min");
273 275
274 276 // Expected results
275 277 QTest::addColumn<bool>(
276 278 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
277 279 QTest::addColumn<double>(
278 280 "expectedMin"); // Expected value when method doesn't return end iterator
279 281
280 282 // ////////// //
281 283 // Test cases //
282 284 // ////////// //
283 285
284 286 QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.},
285 287 {100., 200., 300., 400., 500.})
286 288 << 0. << true << 1.;
287 289 QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.},
288 290 {100., 200., 300., 400., 500.})
289 291 << 1. << true << 1.;
290 292 QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.},
291 293 {100., 200., 300., 400., 500.})
292 294 << 1.1 << true << 2.;
293 295 QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.},
294 296 {100., 200., 300., 400., 500.})
295 297 << 5. << true << 5.;
296 298 QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.},
297 299 {100., 200., 300., 400., 500.})
298 300 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
299 301 QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false
300 302 << std::numeric_limits<double>::quiet_NaN();
301 303 }
302 304
303 305 void TestDataSeries::testMinXAxisData()
304 306 {
305 307 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
306 308 QFETCH(double, min);
307 309
308 310 QFETCH(bool, expectedOK);
309 311 QFETCH(double, expectedMin);
310 312
311 313 auto it = dataSeries->minXAxisData(min);
312 314
313 315 QCOMPARE(expectedOK, it != dataSeries->cend());
314 316
315 317 // If the method doesn't return a end iterator, checks with expected value
316 318 if (expectedOK) {
317 319 QCOMPARE(expectedMin, it->x());
318 320 }
319 321 }
320 322
321 323 void TestDataSeries::testMaxXAxisData_data()
322 324 {
323 325 // ////////////// //
324 326 // Test structure //
325 327 // ////////////// //
326 328
327 329 // Data series to get max data
328 330 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
329 331
330 332 // Max data
331 333 QTest::addColumn<double>("max");
332 334
333 335 // Expected results
334 336 QTest::addColumn<bool>(
335 337 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
336 338 QTest::addColumn<double>(
337 339 "expectedMax"); // Expected value when method doesn't return end iterator
338 340
339 341 // ////////// //
340 342 // Test cases //
341 343 // ////////// //
342 344
343 345 QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.},
344 346 {100., 200., 300., 400., 500.})
345 347 << 6. << true << 5.;
346 348 QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.},
347 349 {100., 200., 300., 400., 500.})
348 350 << 5. << true << 5.;
349 351 QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.},
350 352 {100., 200., 300., 400., 500.})
351 353 << 4.9 << true << 4.;
352 354 QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.},
353 355 {100., 200., 300., 400., 500.})
354 356 << 1.1 << true << 1.;
355 357 QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.},
356 358 {100., 200., 300., 400., 500.})
357 359 << 1. << true << 1.;
358 360 QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false
359 361 << std::numeric_limits<double>::quiet_NaN();
360 362 }
361 363
362 364 void TestDataSeries::testMaxXAxisData()
363 365 {
364 366 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
365 367 QFETCH(double, max);
366 368
367 369 QFETCH(bool, expectedOK);
368 370 QFETCH(double, expectedMax);
369 371
370 372 auto it = dataSeries->maxXAxisData(max);
371 373
372 374 QCOMPARE(expectedOK, it != dataSeries->cend());
373 375
374 376 // If the method doesn't return a end iterator, checks with expected value
375 377 if (expectedOK) {
376 378 QCOMPARE(expectedMax, it->x());
377 379 }
378 380 }
379 381
380 382 void TestDataSeries::testXAxisRange_data()
381 383 {
382 384 // ////////////// //
383 385 // Test structure //
384 386 // ////////////// //
385 387
386 388 // Data series to get x-axis range
387 389 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
388 390
389 391 // Min/max values
390 392 QTest::addColumn<double>("min");
391 393 QTest::addColumn<double>("max");
392 394
393 395 // Expected values
394 396 QTest::addColumn<QVector<double> >("expectedXAxisData");
395 397 QTest::addColumn<QVector<double> >("expectedValuesData");
396 398
397 399 // ////////// //
398 400 // Test cases //
399 401 // ////////// //
400 402
401 403 QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.},
402 404 {100., 200., 300., 400., 500.})
403 405 << -1. << 3.2 << QVector<double>{1., 2., 3.}
404 406 << QVector<double>{100., 200., 300.};
405 407 QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.},
406 408 {100., 200., 300., 400., 500.})
407 409 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
408 410 << QVector<double>{100., 200., 300., 400.};
409 411 QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.},
410 412 {100., 200., 300., 400., 500.})
411 413 << 1. << 3.9 << QVector<double>{1., 2., 3.}
412 414 << QVector<double>{100., 200., 300.};
413 415 QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.},
414 416 {100., 200., 300., 400., 500.})
415 417 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
416 418 QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.},
417 419 {100., 200., 300., 400., 500.})
418 420 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
419 421 QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.},
420 422 {100., 200., 300., 400., 500.})
421 423 << 2.1 << 6. << QVector<double>{3., 4., 5.}
422 424 << QVector<double>{300., 400., 500.};
423 425 QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.},
424 426 {100., 200., 300., 400., 500.})
425 427 << 6. << 9. << QVector<double>{} << QVector<double>{};
426 428 QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.},
427 429 {100., 200., 300., 400., 500.})
428 430 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
429 431 }
430 432
431 433 void TestDataSeries::testXAxisRange()
432 434 {
433 435 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
434 436 QFETCH(double, min);
435 437 QFETCH(double, max);
436 438
437 439 QFETCH(QVector<double>, expectedXAxisData);
438 440 QFETCH(QVector<double>, expectedValuesData);
439 441
440 442 auto bounds = dataSeries->xAxisRange(min, max);
441 QVERIFY(std::equal(bounds.first, bounds.second, expectedXAxisData.cbegin(),
442 expectedXAxisData.cend(),
443 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
444 QVERIFY(std::equal(
445 bounds.first, bounds.second, expectedValuesData.cbegin(), expectedValuesData.cend(),
446 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
443 validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData);
447 444 }
448 445
449 446 void TestDataSeries::testValuesBoundsScalar_data()
450 447 {
451 448 testValuesBoundsStructure<ScalarSeries>();
452 449
453 450 // ////////// //
454 451 // Test cases //
455 452 // ////////// //
456 453 auto nan = std::numeric_limits<double>::quiet_NaN();
457 454
458 455 QTest::newRow("scalarBounds1")
459 456 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6.
460 457 << true << 100. << 500.;
461 458 QTest::newRow("scalarBounds2")
462 459 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4.
463 460 << true << 200. << 400.;
464 461 QTest::newRow("scalarBounds3")
465 462 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5
466 463 << false << nan << nan;
467 464 QTest::newRow("scalarBounds4")
468 465 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6.
469 466 << false << nan << nan;
470 467 QTest::newRow("scalarBounds5") << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100.
471 468 << 100.;
472 469 QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan;
473 470
474 471 // Tests with NaN values: NaN values are not included in min/max search
475 472 QTest::newRow("scalarBounds7")
476 473 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6.
477 474 << true << 200. << 400.;
478 475 QTest::newRow("scalarBounds8")
479 476 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true
480 477 << std::numeric_limits<double>::quiet_NaN() << std::numeric_limits<double>::quiet_NaN();
481 478 }
482 479
483 480 void TestDataSeries::testValuesBoundsScalar()
484 481 {
485 482 testValuesBounds<ScalarSeries>();
486 483 }
487 484
488 485 void TestDataSeries::testValuesBoundsVector_data()
489 486 {
490 487 testValuesBoundsStructure<VectorSeries>();
491 488
492 489 // ////////// //
493 490 // Test cases //
494 491 // ////////// //
495 492 auto nan = std::numeric_limits<double>::quiet_NaN();
496 493
497 494 QTest::newRow("vectorBounds1")
498 495 << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.},
499 496 {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.})
500 497 << 0. << 6. << true << 0.3 << 35.; // min/max in same component
501 498 QTest::newRow("vectorBounds2")
502 499 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
503 500 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
504 501 << 0. << 6. << true << 2.3 << 35.; // min/max in same entry
505 502 QTest::newRow("vectorBounds3")
506 503 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
507 504 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
508 505 << 2. << 3. << true << 10. << 24.;
509 506
510 507 // Tests with NaN values: NaN values are not included in min/max search
511 508 QTest::newRow("vectorBounds4")
512 509 << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true
513 510 << nan << nan;
514 511 }
515 512
516 513 void TestDataSeries::testValuesBoundsVector()
517 514 {
518 515 testValuesBounds<VectorSeries>();
519 516 }
520 517
521 518 QTEST_MAIN(TestDataSeries)
522 519 #include "TestDataSeries.moc"
@@ -1,199 +1,181
1 1 #include "Data/ArrayData.h"
2 2 #include <QObject>
3 3 #include <QtTest>
4 4
5 namespace {
6
7 void verifyArrayData(const ArrayData<1> &arrayData, const QVector<double> &expectedData)
8 {
9 QVERIFY(std::equal(
10 arrayData.cbegin(), arrayData.cend(), expectedData.cbegin(), expectedData.cend(),
11 [](const auto &it, const auto &expectedData) { return it.at(0) == expectedData; }));
12 }
13
14 } // namespace
15
5 16 class TestOneDimArrayData : public QObject {
6 17 Q_OBJECT
7 18 private slots:
8 19 /// Tests @sa ArrayData::data()
9 20 void testData_data();
10 21 void testData();
11 22
12 /// Tests @sa ArrayData::data(int componentIndex)
13 void testDataByComponentIndex_data();
14 void testDataByComponentIndex();
15
16 23 /// Tests @sa ArrayData::add()
17 24 void testAdd_data();
18 25 void testAdd();
19 26
20 27 /// Tests @sa ArrayData::at(int index)
21 28 void testAt_data();
22 29 void testAt();
23 30
24 31 /// Tests @sa ArrayData::clear()
25 32 void testClear_data();
26 33 void testClear();
27 34
28 35 /// Tests @sa ArrayData::size()
29 36 void testSize_data();
30 37 void testSize();
31 38
32 39 /// Tests @sa ArrayData::sort()
33 40 void testSort_data();
34 41 void testSort();
35 42 };
36 43
37 44 void TestOneDimArrayData::testData_data()
38 45 {
39 46 // Test structure
40 47 QTest::addColumn<QVector<double> >("inputData"); // array's data input
41 48 QTest::addColumn<QVector<double> >("expectedData"); // expected data
42 49
43 50 // Test cases
44 51 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.}
45 52 << QVector<double>{1., 2., 3., 4., 5.};
46 53 }
47 54
48 55 void TestOneDimArrayData::testData()
49 56 {
50 57 QFETCH(QVector<double>, inputData);
51 58 QFETCH(QVector<double>, expectedData);
52 59
53 60 ArrayData<1> arrayData{inputData};
54 QVERIFY(arrayData.data() == expectedData);
55 }
56
57 void TestOneDimArrayData::testDataByComponentIndex_data()
58 {
59 // Test structure
60 QTest::addColumn<QVector<double> >("inputData"); // array data's input
61 QTest::addColumn<int>("componentIndex"); // component index to test
62 QTest::addColumn<QVector<double> >("expectedData"); // expected data
63
64 // Test cases
65 QTest::newRow("validIndex") << QVector<double>{1., 2., 3., 4., 5.} << 0
66 << QVector<double>{1., 2., 3., 4., 5.};
67 QTest::newRow("invalidIndex1") << QVector<double>{1., 2., 3., 4., 5.} << -1
68 << QVector<double>{};
69 QTest::newRow("invalidIndex2") << QVector<double>{1., 2., 3., 4., 5.} << 1 << QVector<double>{};
70 }
71
72 void TestOneDimArrayData::testDataByComponentIndex()
73 {
74 QFETCH(QVector<double>, inputData);
75 QFETCH(int, componentIndex);
76 QFETCH(QVector<double>, expectedData);
77
78 ArrayData<1> arrayData{inputData};
79 QVERIFY(arrayData.data(componentIndex) == expectedData);
61 verifyArrayData(arrayData, expectedData);
80 62 }
81 63
82 64 void TestOneDimArrayData::testAdd_data()
83 65 {
84 66 // Test structure
85 67 QTest::addColumn<QVector<double> >("inputData"); // array's data input
86 68 QTest::addColumn<QVector<double> >("otherData"); // array data's input to merge with
87 69 QTest::addColumn<bool>("prepend"); // prepend or append merge
88 70 QTest::addColumn<QVector<double> >("expectedData"); // expected data after merge
89 71
90 72 // Test cases
91 73 QTest::newRow("appendMerge") << QVector<double>{1., 2., 3., 4., 5.}
92 74 << QVector<double>{6., 7., 8.} << false
93 75 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8.};
94 76 QTest::newRow("prependMerge") << QVector<double>{1., 2., 3., 4., 5.}
95 77 << QVector<double>{6., 7., 8.} << true
96 78 << QVector<double>{6., 7., 8., 1., 2., 3., 4., 5.};
97 79 }
98 80
99 81 void TestOneDimArrayData::testAdd()
100 82 {
101 83 QFETCH(QVector<double>, inputData);
102 84 QFETCH(QVector<double>, otherData);
103 85 QFETCH(bool, prepend);
104 86 QFETCH(QVector<double>, expectedData);
105 87
106 88 ArrayData<1> arrayData{inputData};
107 89 ArrayData<1> other{otherData};
108 90
109 91 arrayData.add(other, prepend);
110 QVERIFY(arrayData.data() == expectedData);
92 verifyArrayData(arrayData, expectedData);
111 93 }
112 94
113 95 void TestOneDimArrayData::testAt_data()
114 96 {
115 97 // Test structure
116 98 QTest::addColumn<QVector<double> >("inputData"); // array data's input
117 99 QTest::addColumn<int>("index"); // index to retrieve data
118 100 QTest::addColumn<double>("expectedData"); // expected data at index
119 101
120 102 // Test cases
121 103 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << 0 << 1.;
122 104 QTest::newRow("data2") << QVector<double>{1., 2., 3., 4., 5.} << 3 << 4.;
123 105 }
124 106
125 107 void TestOneDimArrayData::testAt()
126 108 {
127 109 QFETCH(QVector<double>, inputData);
128 110 QFETCH(int, index);
129 111 QFETCH(double, expectedData);
130 112
131 113 ArrayData<1> arrayData{inputData};
132 114 QVERIFY(arrayData.at(index) == expectedData);
133 115 }
134 116
135 117 void TestOneDimArrayData::testClear_data()
136 118 {
137 119 // Test structure
138 120 QTest::addColumn<QVector<double> >("inputData"); // array data's input
139 121
140 122 // Test cases
141 123 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.};
142 124 }
143 125
144 126 void TestOneDimArrayData::testClear()
145 127 {
146 128 QFETCH(QVector<double>, inputData);
147 129
148 130 ArrayData<1> arrayData{inputData};
149 131 arrayData.clear();
150 QVERIFY(arrayData.data() == QVector<double>{});
132 verifyArrayData(arrayData, QVector<double>{});
151 133 }
152 134
153 135 void TestOneDimArrayData::testSize_data()
154 136 {
155 137 // Test structure
156 138 QTest::addColumn<QVector<double> >("inputData"); // array data's input
157 139 QTest::addColumn<int>("expectedSize"); // expected array data size
158 140
159 141 // Test cases
160 142 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << 5;
161 143 }
162 144
163 145 void TestOneDimArrayData::testSize()
164 146 {
165 147 QFETCH(QVector<double>, inputData);
166 148 QFETCH(int, expectedSize);
167 149
168 150 ArrayData<1> arrayData{inputData};
169 151 QVERIFY(arrayData.size() == expectedSize);
170 152 }
171 153
172 154 void TestOneDimArrayData::testSort_data()
173 155 {
174 156 // Test structure
175 157 QTest::addColumn<QVector<double> >("inputData"); // array data's input
176 158 QTest::addColumn<std::vector<int> >("sortPermutation"); // permutation used to sort data
177 159 QTest::addColumn<QVector<double> >("expectedData"); // expected data after sorting
178 160
179 161 // Test cases
180 162 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << std::vector<int>{0, 2, 3, 1, 4}
181 163 << QVector<double>{1., 3., 4., 2., 5.};
182 164 QTest::newRow("data2") << QVector<double>{1., 2., 3., 4., 5.} << std::vector<int>{4, 1, 2, 3, 0}
183 165 << QVector<double>{5., 2., 3., 4., 1.};
184 166 }
185 167
186 168 void TestOneDimArrayData::testSort()
187 169 {
188 170 QFETCH(QVector<double>, inputData);
189 171 QFETCH(std::vector<int>, sortPermutation);
190 172 QFETCH(QVector<double>, expectedData);
191 173
192 174 ArrayData<1> arrayData{inputData};
193 175 auto sortedArrayData = arrayData.sort(sortPermutation);
194 176 QVERIFY(sortedArrayData != nullptr);
195 QVERIFY(sortedArrayData->data() == expectedData);
177 verifyArrayData(*sortedArrayData, expectedData);
196 178 }
197 179
198 180 QTEST_MAIN(TestOneDimArrayData)
199 181 #include "TestOneDimArrayData.moc"
@@ -1,224 +1,239
1 1 #include "Data/ArrayData.h"
2 2 #include <QObject>
3 3 #include <QtTest>
4 4
5 using DataContainer = QVector<QVector<double> >;
5 using Container = QVector<QVector<double> >;
6 using InputData = QPair<QVector<double>, int>;
7
8 namespace {
9
10 InputData flatten(const Container &container)
11 {
12 if (container.isEmpty()) {
13 return {};
14 }
15
16 // We assume here that each component of the container have the same size
17 auto containerSize = container.size();
18 auto componentSize = container.first().size();
19
20 auto result = QVector<double>{};
21 result.reserve(componentSize * containerSize);
22
23 for (auto i = 0; i < componentSize; ++i) {
24 for (auto j = 0; j < containerSize; ++j) {
25 result.append(container.at(j).at(i));
26 }
27 }
28
29 return {result, containerSize};
30 }
31
32 void verifyArrayData(const ArrayData<2> &arrayData, const Container &expectedData)
33 {
34 auto verifyComponent = [&arrayData](const auto &componentData, const auto &equalFun) {
35 QVERIFY(std::equal(arrayData.cbegin(), arrayData.cend(), componentData.cbegin(),
36 componentData.cend(),
37 [&equalFun](const auto &dataSeriesIt, const auto &expectedValue) {
38 return equalFun(dataSeriesIt, expectedValue);
39 }));
40 };
41
42 for (auto i = 0; i < expectedData.size(); ++i) {
43 verifyComponent(expectedData.at(i), [i](const auto &seriesIt, const auto &value) {
44 return seriesIt.at(i) == value;
45 });
46 }
47 }
48
49 } // namespace
6 50
7 51 class TestTwoDimArrayData : public QObject {
8 52 Q_OBJECT
9 53 private slots:
10 /// Tests @sa ArrayData::data(int componentIndex)
11 void testDataByComponentIndex_data();
12 void testDataByComponentIndex();
13
14 54 /// Tests @sa ArrayData ctor
15 55 void testCtor_data();
16 56 void testCtor();
17 57
18 58 /// Tests @sa ArrayData::add()
19 59 void testAdd_data();
20 60 void testAdd();
21 61
22 62 /// Tests @sa ArrayData::clear()
23 63 void testClear_data();
24 64 void testClear();
25 65
26 66 /// Tests @sa ArrayData::size()
27 67 void testSize_data();
28 68 void testSize();
29 69
30 70 /// Tests @sa ArrayData::sort()
31 71 void testSort_data();
32 72 void testSort();
33 73 };
34 74
35 void TestTwoDimArrayData::testDataByComponentIndex_data()
36 {
37 // Test structure
38 QTest::addColumn<DataContainer>("inputData"); // array data's input
39 QTest::addColumn<int>("componentIndex"); // component index to test
40 QTest::addColumn<QVector<double> >("expectedData"); // expected data
41
42 // Test cases
43 auto inputData
44 = DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}};
45
46 QTest::newRow("validIndex1") << inputData << 0 << QVector<double>{1., 2., 3., 4., 5.};
47 QTest::newRow("validIndex2") << inputData << 1 << QVector<double>{6., 7., 8., 9., 10.};
48 QTest::newRow("validIndex3") << inputData << 2 << QVector<double>{11., 12., 13., 14., 15.};
49 QTest::newRow("invalidIndex1") << inputData << -1 << QVector<double>{};
50 QTest::newRow("invalidIndex2") << inputData << 3 << QVector<double>{};
51 }
52
53 void TestTwoDimArrayData::testDataByComponentIndex()
54 {
55 QFETCH(DataContainer, inputData);
56 QFETCH(int, componentIndex);
57 QFETCH(QVector<double>, expectedData);
58
59 ArrayData<2> arrayData{inputData};
60 QVERIFY(arrayData.data(componentIndex) == expectedData);
61 }
62
63 75 void TestTwoDimArrayData::testCtor_data()
64 76 {
65 77 // Test structure
66 QTest::addColumn<DataContainer>("inputData"); // array data's input
67 QTest::addColumn<bool>("success"); // array data has been successfully constructed
68 QTest::addColumn<DataContainer>("expectedData"); // expected array data (when success)
78 QTest::addColumn<InputData>("inputData"); // array data's input
79 QTest::addColumn<bool>("success"); // array data has been successfully constructed
80 QTest::addColumn<Container>("expectedData"); // expected array data (when success)
69 81
70 82 // Test cases
71 QTest::newRow("validInput")
72 << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}
73 << true
74 << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}};
75 QTest::newRow("malformedInput (components of the array data haven't the same size")
76 << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8.}, {11., 12.}} << true
77 << DataContainer{{}, {}, {}};
78 QTest::newRow("invalidInput (less than tow components") << DataContainer{{1., 2., 3., 4., 5.}}
79 << false << DataContainer{{}, {}, {}};
83 QTest::newRow("validInput") << flatten(Container{{1., 2., 3., 4., 5.},
84 {6., 7., 8., 9., 10.},
85 {11., 12., 13., 14., 15.}})
86 << true << Container{{1., 2., 3., 4., 5.},
87 {6., 7., 8., 9., 10.},
88 {11., 12., 13., 14., 15.}};
89 QTest::newRow("invalidInput (invalid data size")
90 << InputData{{1., 2., 3., 4., 5., 6., 7.}, 3} << false << Container{{}, {}, {}};
91 QTest::newRow("invalidInput (less than two components")
92 << flatten(Container{{1., 2., 3., 4., 5.}}) << false << Container{{}, {}, {}};
80 93 }
81 94
82 95 void TestTwoDimArrayData::testCtor()
83 96 {
84 QFETCH(DataContainer, inputData);
97 QFETCH(InputData, inputData);
85 98 QFETCH(bool, success);
86 99
87 100 if (success) {
88 QFETCH(DataContainer, expectedData);
89
90 ArrayData<2> arrayData{inputData};
101 QFETCH(Container, expectedData);
91 102
92 for (auto i = 0; i < expectedData.size(); ++i) {
93 QVERIFY(arrayData.data(i) == expectedData.at(i));
94 }
103 ArrayData<2> arrayData{inputData.first, inputData.second};
104 verifyArrayData(arrayData, expectedData);
95 105 }
96 106 else {
97 QVERIFY_EXCEPTION_THROWN(ArrayData<2> arrayData{inputData}, std::invalid_argument);
107 QVERIFY_EXCEPTION_THROWN(ArrayData<2>(inputData.first, inputData.second),
108 std::invalid_argument);
98 109 }
99 110 }
100 111
101 112 void TestTwoDimArrayData::testAdd_data()
102 113 {
103 114 // Test structure
104 QTest::addColumn<DataContainer>("inputData"); // array's data input
105 QTest::addColumn<DataContainer>("otherData"); // array data's input to merge with
106 QTest::addColumn<bool>("prepend"); // prepend or append merge
107 QTest::addColumn<DataContainer>("expectedData"); // expected data after merge
115 QTest::addColumn<InputData>("inputData"); // array's data input
116 QTest::addColumn<InputData>("otherData"); // array data's input to merge with
117 QTest::addColumn<bool>("prepend"); // prepend or append merge
118 QTest::addColumn<Container>("expectedData"); // expected data after merge
108 119
109 120 // Test cases
110 auto inputData
111 = DataContainer{{1., 2., 3., 4., 5.}, {11., 12., 13., 14., 15.}, {21., 22., 23., 24., 25.}};
121 auto inputData = flatten(
122 Container{{1., 2., 3., 4., 5.}, {11., 12., 13., 14., 15.}, {21., 22., 23., 24., 25.}});
112 123
113 auto vectorContainer = DataContainer{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}};
114 auto tensorContainer = DataContainer{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28},
115 {36., 37., 38.}, {46., 47., 48.}, {56., 57., 58}};
124 auto vectorContainer = flatten(Container{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}});
125 auto tensorContainer = flatten(Container{{6., 7., 8.},
126 {16., 17., 18.},
127 {26., 27., 28},
128 {36., 37., 38.},
129 {46., 47., 48.},
130 {56., 57., 58}});
116 131
117 132 QTest::newRow("appendMerge") << inputData << vectorContainer << false
118 << DataContainer{{1., 2., 3., 4., 5., 6., 7., 8.},
119 {11., 12., 13., 14., 15., 16., 17., 18.},
120 {21., 22., 23., 24., 25., 26., 27., 28}};
133 << Container{{1., 2., 3., 4., 5., 6., 7., 8.},
134 {11., 12., 13., 14., 15., 16., 17., 18.},
135 {21., 22., 23., 24., 25., 26., 27., 28}};
121 136 QTest::newRow("prependMerge") << inputData << vectorContainer << true
122 << DataContainer{{6., 7., 8., 1., 2., 3., 4., 5.},
123 {16., 17., 18., 11., 12., 13., 14., 15.},
124 {26., 27., 28, 21., 22., 23., 24., 25.}};
125 QTest::newRow("invalidMerge") << inputData << tensorContainer << false << inputData;
137 << Container{{6., 7., 8., 1., 2., 3., 4., 5.},
138 {16., 17., 18., 11., 12., 13., 14., 15.},
139 {26., 27., 28, 21., 22., 23., 24., 25.}};
140 QTest::newRow("invalidMerge") << inputData << tensorContainer << false
141 << Container{{1., 2., 3., 4., 5.},
142 {11., 12., 13., 14., 15.},
143 {21., 22., 23., 24., 25.}};
126 144 }
127 145
128 146 void TestTwoDimArrayData::testAdd()
129 147 {
130 QFETCH(DataContainer, inputData);
131 QFETCH(DataContainer, otherData);
148 QFETCH(InputData, inputData);
149 QFETCH(InputData, otherData);
132 150 QFETCH(bool, prepend);
133 QFETCH(DataContainer, expectedData);
151 QFETCH(Container, expectedData);
134 152
135 ArrayData<2> arrayData{inputData};
136 ArrayData<2> other{otherData};
153 ArrayData<2> arrayData{inputData.first, inputData.second};
154 ArrayData<2> other{otherData.first, otherData.second};
137 155
138 156 arrayData.add(other, prepend);
139 157
140 for (auto i = 0; i < expectedData.size(); ++i) {
141 QVERIFY(arrayData.data(i) == expectedData.at(i));
142 }
158 verifyArrayData(arrayData, expectedData);
143 159 }
144 160
145 161 void TestTwoDimArrayData::testClear_data()
146 162 {
147 163 // Test structure
148 QTest::addColumn<DataContainer>("inputData"); // array data's input
164 QTest::addColumn<InputData>("inputData"); // array data's input
149 165
150 166 // Test cases
151 QTest::newRow("data1") << DataContainer{
152 {1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}};
167 QTest::newRow("data1") << flatten(
168 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}});
153 169 }
154 170
155 171 void TestTwoDimArrayData::testClear()
156 172 {
157 QFETCH(DataContainer, inputData);
173 QFETCH(InputData, inputData);
158 174
159 ArrayData<2> arrayData{inputData};
175 ArrayData<2> arrayData{inputData.first, inputData.second};
160 176 arrayData.clear();
161 177
162 for (auto i = 0; i < inputData.size(); ++i) {
163 QVERIFY(arrayData.data(i) == QVector<double>{});
164 }
178 auto emptyData = Container(inputData.second, QVector<double>{});
179 verifyArrayData(arrayData, emptyData);
165 180 }
166 181
167 182 void TestTwoDimArrayData::testSize_data()
168 183 {
169 184 // Test structure
170 QTest::addColumn<QVector<QVector<double> > >("inputData"); // array data's input
171 QTest::addColumn<int>("expectedSize"); // expected array data size
185 QTest::addColumn<InputData>("inputData"); // array data's input
186 QTest::addColumn<int>("expectedSize"); // expected array data size
172 187
173 188 // Test cases
174 QTest::newRow("data1") << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}} << 5;
175 QTest::newRow("data2") << DataContainer{{1., 2., 3., 4., 5.},
176 {6., 7., 8., 9., 10.},
177 {11., 12., 13., 14., 15.}}
189 QTest::newRow("data1") << flatten(Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}}) << 5;
190 QTest::newRow("data2") << flatten(Container{{1., 2., 3., 4., 5.},
191 {6., 7., 8., 9., 10.},
192 {11., 12., 13., 14., 15.}})
178 193 << 5;
179 194 }
180 195
181 196 void TestTwoDimArrayData::testSize()
182 197 {
183 QFETCH(DataContainer, inputData);
198 QFETCH(InputData, inputData);
184 199 QFETCH(int, expectedSize);
185 200
186 ArrayData<2> arrayData{inputData};
201 ArrayData<2> arrayData{inputData.first, inputData.second};
187 202 QVERIFY(arrayData.size() == expectedSize);
188 203 }
189 204
190 205 void TestTwoDimArrayData::testSort_data()
191 206 {
192 207 // Test structure
193 QTest::addColumn<DataContainer>("inputData"); // array data's input
208 QTest::addColumn<InputData>("inputData"); // array data's input
194 209 QTest::addColumn<std::vector<int> >("sortPermutation"); // permutation used to sort data
195 QTest::addColumn<DataContainer>("expectedData"); // expected data after sorting
210 QTest::addColumn<Container>("expectedData"); // expected data after sorting
196 211
197 212 // Test cases
198 213 QTest::newRow("data1")
199 << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}
214 << flatten(
215 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}})
200 216 << std::vector<int>{0, 2, 3, 1, 4}
201 << DataContainer{{1., 3., 4., 2., 5.}, {6., 8., 9., 7., 10.}, {11., 13., 14., 12., 15.}};
217 << Container{{1., 3., 4., 2., 5.}, {6., 8., 9., 7., 10.}, {11., 13., 14., 12., 15.}};
202 218 QTest::newRow("data2")
203 << DataContainer{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}}
219 << flatten(
220 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}})
204 221 << std::vector<int>{2, 4, 3, 0, 1}
205 << DataContainer{{3., 5., 4., 1., 2.}, {8., 10., 9., 6., 7.}, {13., 15., 14., 11., 12.}};
222 << Container{{3., 5., 4., 1., 2.}, {8., 10., 9., 6., 7.}, {13., 15., 14., 11., 12.}};
206 223 }
207 224
208 225 void TestTwoDimArrayData::testSort()
209 226 {
210 QFETCH(DataContainer, inputData);
227 QFETCH(InputData, inputData);
211 228 QFETCH(std::vector<int>, sortPermutation);
212 QFETCH(DataContainer, expectedData);
229 QFETCH(Container, expectedData);
213 230
214 ArrayData<2> arrayData{inputData};
231 ArrayData<2> arrayData{inputData.first, inputData.second};
215 232 auto sortedArrayData = arrayData.sort(sortPermutation);
216 233 QVERIFY(sortedArrayData != nullptr);
217 234
218 for (auto i = 0; i < expectedData.size(); ++i) {
219 QVERIFY(sortedArrayData->data(i) == expectedData.at(i));
220 }
235 verifyArrayData(*sortedArrayData, expectedData);
221 236 }
222 237
223 238 QTEST_MAIN(TestTwoDimArrayData)
224 239 #include "TestTwoDimArrayData.moc"
@@ -1,310 +1,281
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/VectorSeries.h>
5 5
6 6 #include <QObject>
7 7 #include <QtTest>
8 8
9 9 namespace {
10 10
11 11 /// Path for the tests
12 12 const auto TESTS_RESOURCES_PATH
13 13 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
14 14
15 15 QDateTime dateTime(int year, int month, int day, int hours, int minutes, int seconds)
16 16 {
17 17 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
18 18 }
19 19
20 /// Compares two vectors that can potentially contain NaN values
21 bool compareVectors(const QVector<double> &v1, const QVector<double> &v2)
22 {
23 if (v1.size() != v2.size()) {
24 return false;
25 }
26
27 auto result = true;
28 auto v2It = v2.cbegin();
29 for (auto v1It = v1.cbegin(), v1End = v1.cend(); v1It != v1End && result; ++v1It, ++v2It) {
30 auto v1Value = *v1It;
31 auto v2Value = *v2It;
32
33 // If v1 is NaN, v2 has to be NaN too
34 result = std::isnan(v1Value) ? std::isnan(v2Value) : (v1Value == v2Value);
35 }
36
37 return result;
38 }
39
40 bool compareVectors(const QVector<QVector<double> > &v1, const QVector<QVector<double> > &v2)
41 {
42 if (v1.size() != v2.size()) {
43 return false;
44 }
45
46 auto result = true;
47 for (auto i = 0; i < v1.size() && result; ++i) {
48 result &= compareVectors(v1.at(i), v2.at(i));
49 }
50
51 return result;
52 }
53
54 QVector<QVector<double> > valuesData(const ArrayData<1> &arrayData)
55 {
56 return QVector<QVector<double> >{arrayData.data()};
57 }
58
59 QVector<QVector<double> > valuesData(const ArrayData<2> &arrayData)
60 {
61 return arrayData.data();
62 }
63
64
65 20 QString inputFilePath(const QString &inputFileName)
66 21 {
67 22 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
68 23 }
69 24
70 25 template <typename T>
71 26 struct ExpectedResults {
72 27 explicit ExpectedResults() = default;
73 28
74 29 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
75 30 QVector<double> valuesData)
76 31 : ExpectedResults(xAxisUnit, valuesUnit, xAxisData,
77 32 QVector<QVector<double> >{std::move(valuesData)})
78 33 {
79 34 }
80 35
81 36 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
82 37 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
83 38 QVector<QVector<double> > valuesData)
84 39 : m_ParsingOK{true},
85 40 m_XAxisUnit{xAxisUnit},
86 41 m_ValuesUnit{valuesUnit},
87 42 m_XAxisData{},
88 43 m_ValuesData{std::move(valuesData)}
89 44 {
90 45 // Converts QVector<QDateTime> to QVector<double>
91 46 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
92 47 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
93 48 }
94 49
95 50 /**
96 51 * Validates a DataSeries compared to the expected results
97 52 * @param results the DataSeries to validate
98 53 */
99 54 void validate(std::shared_ptr<IDataSeries> results)
100 55 {
101 56 if (m_ParsingOK) {
102 57 auto dataSeries = dynamic_cast<T *>(results.get());
103 58 QVERIFY(dataSeries != nullptr);
104 59
105 60 // Checks units
106 61 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
107 62 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
108 63
109 // Checks values : as the vectors can potentially contain NaN values, we must use a
110 // custom vector comparison method
111 QVERIFY(compareVectors(dataSeries->xAxisData()->data(), m_XAxisData));
112 QVERIFY(compareVectors(valuesData(*dataSeries->valuesData()), m_ValuesData));
64 auto verifyRange = [dataSeries](const auto &expectedData, const auto &equalFun) {
65 QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), expectedData.cbegin(),
66 expectedData.cend(),
67 [&equalFun](const auto &dataSeriesIt, const auto &expectedX) {
68 return equalFun(dataSeriesIt, expectedX);
69 }));
70 };
71
72 // Checks x-axis data
73 verifyRange(m_XAxisData, [](const auto &seriesIt, const auto &value) {
74 return seriesIt.x() == value;
75 });
76
77 // Checks values data of each component
78 for (auto i = 0; i < m_ValuesData.size(); ++i) {
79 verifyRange(m_ValuesData.at(i), [i](const auto &seriesIt, const auto &value) {
80 auto itValue = seriesIt.value(i);
81 return (std::isnan(itValue) && std::isnan(value)) || seriesIt.value(i) == value;
82 });
83 }
113 84 }
114 85 else {
115 86 QVERIFY(results == nullptr);
116 87 }
117 88 }
118 89
119 90 // Parsing was successfully completed
120 91 bool m_ParsingOK{false};
121 92 // Expected x-axis unit
122 93 Unit m_XAxisUnit{};
123 94 // Expected values unit
124 95 Unit m_ValuesUnit{};
125 96 // Expected x-axis data
126 97 QVector<double> m_XAxisData{};
127 98 // Expected values data
128 99 QVector<QVector<double> > m_ValuesData{};
129 100 };
130 101
131 102 } // namespace
132 103
133 104 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
134 105 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
135 106
136 107 class TestAmdaResultParser : public QObject {
137 108 Q_OBJECT
138 109 private:
139 110 template <typename T>
140 111 void testReadDataStructure()
141 112 {
142 113 // ////////////// //
143 114 // Test structure //
144 115 // ////////////// //
145 116
146 117 // Name of TXT file to read
147 118 QTest::addColumn<QString>("inputFileName");
148 119 // Expected results
149 120 QTest::addColumn<ExpectedResults<T> >("expectedResults");
150 121 }
151 122
152 123 template <typename T>
153 124 void testRead(AmdaResultParser::ValueType valueType)
154 125 {
155 126 QFETCH(QString, inputFileName);
156 127 QFETCH(ExpectedResults<T>, expectedResults);
157 128
158 129 // Parses file
159 130 auto filePath = inputFilePath(inputFileName);
160 131 auto results = AmdaResultParser::readTxt(filePath, valueType);
161 132
162 133 // ///////////////// //
163 134 // Validates results //
164 135 // ///////////////// //
165 136 expectedResults.validate(results);
166 137 }
167 138
168 139 private slots:
169 140 /// Input test data
170 141 /// @sa testReadScalarTxt()
171 142 void testReadScalarTxt_data();
172 143
173 144 /// Tests parsing scalar series of a TXT file
174 145 void testReadScalarTxt();
175 146
176 147 /// Input test data
177 148 /// @sa testReadVectorTxt()
178 149 void testReadVectorTxt_data();
179 150
180 151 /// Tests parsing vector series of a TXT file
181 152 void testReadVectorTxt();
182 153 };
183 154
184 155 void TestAmdaResultParser::testReadScalarTxt_data()
185 156 {
186 157 testReadDataStructure<ScalarSeries>();
187 158
188 159 // ////////// //
189 160 // Test cases //
190 161 // ////////// //
191 162
192 163 // Valid files
193 164 QTest::newRow("Valid file")
194 165 << QStringLiteral("ValidScalar1.txt")
195 166 << ExpectedResults<ScalarSeries>{
196 167 Unit{QStringLiteral("nT"), true}, Unit{},
197 168 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
198 169 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
199 170 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
200 171 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
201 172 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)},
202 173 QVector<double>{-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
203 174 -2.55800, -2.43250, -2.42200}};
204 175
205 176 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
206 177 << QStringLiteral("WrongValue.txt")
207 178 << ExpectedResults<ScalarSeries>{
208 179 Unit{QStringLiteral("nT"), true}, Unit{},
209 180 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
210 181 dateTime(2013, 9, 23, 9, 2, 30)},
211 182 QVector<double>{std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150}};
212 183
213 184 QTest::newRow("Valid file that contains NaN values")
214 185 << QStringLiteral("NaNValue.txt")
215 186 << ExpectedResults<ScalarSeries>{
216 187 Unit{QStringLiteral("nT"), true}, Unit{},
217 188 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
218 189 dateTime(2013, 9, 23, 9, 2, 30)},
219 190 QVector<double>{std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150}};
220 191
221 192 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
222 193 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
223 194 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true},
224 195 Unit{}, QVector<QDateTime>{},
225 196 QVector<double>{}};
226 197 QTest::newRow("Wrong unit file")
227 198 << QStringLiteral("WrongUnit.txt")
228 199 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true}, Unit{},
229 200 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
230 201 dateTime(2013, 9, 23, 9, 1, 30),
231 202 dateTime(2013, 9, 23, 9, 2, 30)},
232 203 QVector<double>{-2.83950, -2.71850, -2.52150}};
233 204
234 205 QTest::newRow("Wrong results file (date of first line is invalid")
235 206 << QStringLiteral("WrongDate.txt")
236 207 << ExpectedResults<ScalarSeries>{
237 208 Unit{QStringLiteral("nT"), true}, Unit{},
238 209 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
239 210 QVector<double>{-2.71850, -2.52150}};
240 211
241 212 QTest::newRow("Wrong results file (too many values for first line")
242 213 << QStringLiteral("TooManyValues.txt")
243 214 << ExpectedResults<ScalarSeries>{
244 215 Unit{QStringLiteral("nT"), true}, Unit{},
245 216 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
246 217 QVector<double>{-2.71850, -2.52150}};
247 218
248 219 QTest::newRow("Wrong results file (x of first line is NaN")
249 220 << QStringLiteral("NaNX.txt")
250 221 << ExpectedResults<ScalarSeries>{
251 222 Unit{QStringLiteral("nT"), true}, Unit{},
252 223 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
253 224 QVector<double>{-2.71850, -2.52150}};
254 225
255 226 QTest::newRow("Invalid file type (vector)")
256 227 << QStringLiteral("ValidVector1.txt")
257 228 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral("nT"), true}, Unit{},
258 229 QVector<QDateTime>{}, QVector<double>{}};
259 230
260 231 // Invalid files
261 232 QTest::newRow("Invalid file (unexisting file)") << QStringLiteral("UnexistingFile.txt")
262 233 << ExpectedResults<ScalarSeries>{};
263 234
264 235 QTest::newRow("Invalid file (file not found on server)") << QStringLiteral("FileNotFound.txt")
265 236 << ExpectedResults<ScalarSeries>{};
266 237 }
267 238
268 239 void TestAmdaResultParser::testReadScalarTxt()
269 240 {
270 241 testRead<ScalarSeries>(AmdaResultParser::ValueType::SCALAR);
271 242 }
272 243
273 244 void TestAmdaResultParser::testReadVectorTxt_data()
274 245 {
275 246 testReadDataStructure<VectorSeries>();
276 247
277 248 // ////////// //
278 249 // Test cases //
279 250 // ////////// //
280 251
281 252 // Valid files
282 253 QTest::newRow("Valid file")
283 254 << QStringLiteral("ValidVector1.txt")
284 255 << ExpectedResults<VectorSeries>{
285 256 Unit{QStringLiteral("nT"), true}, Unit{},
286 257 QVector<QDateTime>{dateTime(2013, 7, 2, 9, 13, 50), dateTime(2013, 7, 2, 9, 14, 6),
287 258 dateTime(2013, 7, 2, 9, 14, 22), dateTime(2013, 7, 2, 9, 14, 38),
288 259 dateTime(2013, 7, 2, 9, 14, 54), dateTime(2013, 7, 2, 9, 15, 10),
289 260 dateTime(2013, 7, 2, 9, 15, 26), dateTime(2013, 7, 2, 9, 15, 42),
290 261 dateTime(2013, 7, 2, 9, 15, 58), dateTime(2013, 7, 2, 9, 16, 14)},
291 262 QVector<QVector<double> >{
292 263 {-0.332, -1.011, -1.457, -1.293, -1.217, -1.443, -1.278, -1.202, -1.22, -1.259},
293 264 {3.206, 2.999, 2.785, 2.736, 2.612, 2.564, 2.892, 2.862, 2.859, 2.764},
294 265 {0.058, 0.496, 1.018, 1.485, 1.662, 1.505, 1.168, 1.244, 1.15, 1.358}}};
295 266
296 267 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
297 268 QTest::newRow("Invalid file type (scalar)")
298 269 << QStringLiteral("ValidScalar1.txt")
299 270 << ExpectedResults<VectorSeries>{Unit{QStringLiteral("nT"), true}, Unit{},
300 271 QVector<QDateTime>{},
301 272 QVector<QVector<double> >{{}, {}, {}}};
302 273 }
303 274
304 275 void TestAmdaResultParser::testReadVectorTxt()
305 276 {
306 277 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
307 278 }
308 279
309 280 QTEST_MAIN(TestAmdaResultParser)
310 281 #include "TestAmdaResultParser.moc"
General Comments 0
You need to be logged in to leave comments. Login now