##// END OF EJS Templates
Merge pull request 261 from SCIQLOP-Initialisation develop...
leroux -
r700:64ba0d56cf7e merge
parent child
Show More
@@ -1,139 +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 40 * Sorts a container according to indices passed in parameter. The number of data in the
41 41 * container must be a multiple of the number of indices used to sort the container.
42 42 *
43 43 * Example 1:
44 44 * container: {1, 2, 3, 4, 5, 6}
45 45 * sortPermutation: {1, 0}
46 46 *
47 47 * Values will be sorted three by three, and the result will be:
48 48 * {4, 5, 6, 1, 2, 3}
49 49 *
50 50 * Example 2:
51 51 * container: {1, 2, 3, 4, 5, 6}
52 52 * sortPermutation: {2, 0, 1}
53 53 *
54 54 * Values will be sorted two by two, and the result will be:
55 55 * {5, 6, 1, 2, 3, 4}
56 56 *
57 57 * @param container the container sorted
58 58 * @param sortPermutation the indices used to sort the container
59 59 * @return the container sorted
60 60 * @warning no verification is made on validity of sortPermutation (i.e. the vector has unique
61 61 * indices and its range is [0 ; vector.size()[ )
62 62 */
63 63 template <typename Container>
64 64 static Container sort(const Container &container, int nbValues,
65 65 const std::vector<int> &sortPermutation)
66 66 {
67 67 auto containerSize = container.size();
68 68 if (containerSize % nbValues != 0
69 69 || ((containerSize / nbValues) != sortPermutation.size())) {
70 70 return Container{};
71 71 }
72 72
73 73 // Inits result
74 74 auto sortedData = Container{};
75 75 sortedData.reserve(containerSize);
76 76
77 77 for (auto i = 0, componentIndex = 0, permutationIndex = 0; i < containerSize;
78 78 ++i, componentIndex = i % nbValues, permutationIndex = i / nbValues) {
79 79 auto insertIndex = sortPermutation.at(permutationIndex) * nbValues + componentIndex;
80 sortedData.append(container.at(insertIndex));
80 sortedData.push_back(container.at(insertIndex));
81 81 }
82 82
83 83 return sortedData;
84 84 }
85 85
86 86 /**
87 87 * Compares two values that can be NaN. This method is intended to be used as a compare function
88 88 * for searching min value by excluding NaN values.
89 89 *
90 90 * Examples of use:
91 91 * - f({1, 3, 2, 4, 5}) will return 1
92 92 * - f({NaN, 3, 2, 4, 5}) will return 2 (NaN is excluded)
93 93 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
94 94 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
95 95 *
96 96 * @param v1 first value
97 97 * @param v2 second value
98 98 * @return true if v1 < v2, false otherwise
99 99 * @sa std::min_element
100 100 */
101 101 template <typename T>
102 102 static bool minCompareWithNaN(const T &v1, const T &v2)
103 103 {
104 104 // Table used with NaN values:
105 105 // NaN < v2 -> false
106 106 // v1 < NaN -> true
107 107 // NaN < NaN -> false
108 108 // v1 < v2 -> v1 < v2
109 109 return std::isnan(v1) ? false : std::isnan(v2) || (v1 < v2);
110 110 }
111 111
112 112 /**
113 113 * Compares two values that can be NaN. This method is intended to be used as a compare function
114 114 * for searching max value by excluding NaN values.
115 115 *
116 116 * Examples of use:
117 117 * - f({1, 3, 2, 4, 5}) will return 5
118 118 * - f({1, 3, 2, 4, NaN}) will return 4 (NaN is excluded)
119 119 * - f({NaN, NaN, 3, NaN, NaN}) will return 3 (NaN are excluded)
120 120 * - f({NaN, NaN, NaN, NaN, NaN}) will return NaN (no existing value)
121 121 *
122 122 * @param v1 first value
123 123 * @param v2 second value
124 124 * @return true if v1 < v2, false otherwise
125 125 * @sa std::max_element
126 126 */
127 127 template <typename T>
128 128 static bool maxCompareWithNaN(const T &v1, const T &v2)
129 129 {
130 130 // Table used with NaN values:
131 131 // NaN < v2 -> true
132 132 // v1 < NaN -> false
133 133 // NaN < NaN -> false
134 134 // v1 < v2 -> v1 < v2
135 135 return std::isnan(v1) ? true : !std::isnan(v2) && (v1 < v2);
136 136 }
137 137 };
138 138
139 139 #endif // SCIQLOP_SORTUTILS_H
@@ -1,379 +1,367
1 1 #ifndef SCIQLOP_ARRAYDATA_H
2 2 #define SCIQLOP_ARRAYDATA_H
3 3
4 4 #include "Data/ArrayDataIterator.h"
5 5 #include <Common/SortUtils.h>
6 6
7 7 #include <QReadLocker>
8 8 #include <QReadWriteLock>
9 9 #include <QVector>
10 10
11 11 #include <memory>
12 12
13 13 template <int Dim>
14 14 class ArrayData;
15 15
16 using DataContainer = QVector<double>;
16 using DataContainer = std::vector<double>;
17 17
18 18 namespace arraydata_detail {
19 19
20 20 /// Struct used to sort ArrayData
21 21 template <int Dim>
22 22 struct Sort {
23 23 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data, int nbComponents,
24 24 const std::vector<int> &sortPermutation)
25 25 {
26 26 return std::make_shared<ArrayData<Dim> >(
27 27 SortUtils::sort(data, nbComponents, sortPermutation), nbComponents);
28 28 }
29 29 };
30 30
31 31 /// Specialization for uni-dimensional ArrayData
32 32 template <>
33 33 struct Sort<1> {
34 34 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data, int nbComponents,
35 35 const std::vector<int> &sortPermutation)
36 36 {
37 37 Q_UNUSED(nbComponents)
38 38 return std::make_shared<ArrayData<1> >(SortUtils::sort(data, 1, sortPermutation));
39 39 }
40 40 };
41 41
42 42 template <int Dim, bool IsConst>
43 43 class IteratorValue;
44 44
45 45 template <int Dim, bool IsConst>
46 46 struct IteratorValueBuilder {
47 47 };
48 48
49 49 template <int Dim>
50 50 struct IteratorValueBuilder<Dim, true> {
51 51 using DataContainerIterator = DataContainer::const_iterator;
52 52
53 53 static void swap(IteratorValue<Dim, true> &o1, IteratorValue<Dim, true> &o2) {}
54 54 };
55 55
56 56 template <int Dim>
57 57 struct IteratorValueBuilder<Dim, false> {
58 58 using DataContainerIterator = DataContainer::iterator;
59 59
60 60 static void swap(IteratorValue<Dim, false> &o1, IteratorValue<Dim, false> &o2)
61 61 {
62 62 for (auto i = 0; i < o1.m_NbComponents; ++i) {
63 63 std::iter_swap(o1.m_It + i, o2.m_It + i);
64 64 }
65 65 }
66 66 };
67 67
68 68 template <int Dim, bool IsConst>
69 69 class IteratorValue : public ArrayDataIteratorValue::Impl {
70 70 public:
71 71 friend class ArrayData<Dim>;
72 72 friend class IteratorValueBuilder<Dim, IsConst>;
73 73
74 74 using DataContainerIterator =
75 75 typename IteratorValueBuilder<Dim, IsConst>::DataContainerIterator;
76 76
77 77 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
78 78 explicit IteratorValue(const DataContainer &container, int nbComponents, bool begin)
79 79 : m_It{begin ? container.cbegin() : container.cend()}, m_NbComponents{nbComponents}
80 80 {
81 81 }
82 82
83 83 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
84 84 explicit IteratorValue(DataContainer &container, int nbComponents, bool begin)
85 85 : m_It{begin ? container.begin() : container.end()}, m_NbComponents{nbComponents}
86 86 {
87 87 }
88 88
89 89 IteratorValue(const IteratorValue &other) = default;
90 90
91 91 std::unique_ptr<ArrayDataIteratorValue::Impl> clone() const override
92 92 {
93 93 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
94 94 }
95 95
96 96 int distance(const ArrayDataIteratorValue::Impl &other) const override try {
97 97 /// @todo ALX : validate
98 98 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
99 99 return std::distance(otherImpl.m_It, m_It) / m_NbComponents;
100 100 }
101 101 catch (const std::bad_cast &) {
102 102 return 0;
103 103 }
104 104
105 105 bool equals(const ArrayDataIteratorValue::Impl &other) const override try {
106 106 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
107 107 return std::tie(m_It, m_NbComponents) == std::tie(otherImpl.m_It, otherImpl.m_NbComponents);
108 108 }
109 109 catch (const std::bad_cast &) {
110 110 return false;
111 111 }
112 112
113 113 bool lowerThan(const ArrayDataIteratorValue::Impl &other) const override try {
114 114 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
115 115 return m_It < otherImpl.m_It;
116 116 }
117 117 catch (const std::bad_cast &) {
118 118 return false;
119 119 }
120 120
121 121 std::unique_ptr<ArrayDataIteratorValue::Impl> advance(int offset) const override
122 122 {
123 123 auto result = clone();
124 while (offset--) {
125 result->next();
126 }
124 result->next(offset);
127 125 return result;
128 126 }
129 127
130 void next() override { std::advance(m_It, m_NbComponents); }
128 void next(int offset) override { std::advance(m_It, offset * m_NbComponents); }
131 129 void prev() override { std::advance(m_It, -m_NbComponents); }
132 130
133 131 double at(int componentIndex) const override { return *(m_It + componentIndex); }
134 132 double first() const override { return *m_It; }
135 133 double min() const override
136 134 {
137 135 auto values = this->values();
138 136 auto end = values.cend();
139 137 auto it = std::min_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
140 138 return SortUtils::minCompareWithNaN(v1, v2);
141 139 });
142 140
143 141 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
144 142 }
145 143 double max() const override
146 144 {
147 145 auto values = this->values();
148 146 auto end = values.cend();
149 147 auto it = std::max_element(values.cbegin(), end, [](const auto &v1, const auto &v2) {
150 148 return SortUtils::maxCompareWithNaN(v1, v2);
151 149 });
152 150 return it != end ? *it : std::numeric_limits<double>::quiet_NaN();
153 151 }
154 152
155 153 QVector<double> values() const override
156 154 {
157 155 auto result = QVector<double>{};
158 156 for (auto i = 0; i < m_NbComponents; ++i) {
159 157 result.push_back(*(m_It + i));
160 158 }
161 159
162 160 return result;
163 161 }
164 162
165 163 void swap(ArrayDataIteratorValue::Impl &other) override
166 164 {
167 165 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
168 166 IteratorValueBuilder<Dim, IsConst>::swap(*this, otherImpl);
169 167 }
170 168
171 169 private:
172 170 DataContainerIterator m_It;
173 171 int m_NbComponents;
174 172 };
175 173
176 174 } // namespace arraydata_detail
177 175
178 176 /**
179 177 * @brief The ArrayData class represents a dataset for a data series.
180 178 *
181 179 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
182 180 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
183 181 * number of values
184 182 *
185 183 * @tparam Dim the dimension of the ArrayData (one or two)
186 184 * @sa IDataSeries
187 185 */
188 186 template <int Dim>
189 187 class ArrayData {
190 188 public:
191 189 // ///// //
192 190 // Ctors //
193 191 // ///// //
194 192
195 193 /**
196 194 * Ctor for a unidimensional ArrayData
197 195 * @param data the data the ArrayData will hold
198 196 */
199 197 template <int D = Dim, typename = std::enable_if_t<D == 1> >
200 198 explicit ArrayData(DataContainer data) : m_Data{std::move(data)}, m_NbComponents{1}
201 199 {
202 200 }
203 201
204 202 /**
205 203 * Ctor for a two-dimensional ArrayData. The number of components (number of lines) must be
206 204 * greater than 2 and must be a divisor of the total number of data in the vector
207 205 * @param data the data the ArrayData will hold
208 206 * @param nbComponents the number of components
209 207 * @throws std::invalid_argument if the number of components is less than 2 or is not a divisor
210 208 * of the size of the data
211 209 */
212 210 template <int D = Dim, typename = std::enable_if_t<D == 2> >
213 211 explicit ArrayData(DataContainer data, int nbComponents)
214 212 : m_Data{std::move(data)}, m_NbComponents{nbComponents}
215 213 {
216 214 if (nbComponents < 2) {
217 215 throw std::invalid_argument{
218 216 QString{"A multidimensional ArrayData must have at least 2 components (found: %1)"}
219 217 .arg(nbComponents)
220 218 .toStdString()};
221 219 }
222 220
223 221 if (m_Data.size() % m_NbComponents != 0) {
224 222 throw std::invalid_argument{QString{
225 223 "The number of components (%1) is inconsistent with the total number of data (%2)"}
226 224 .arg(m_Data.size(), nbComponents)
227 225 .toStdString()};
228 226 }
229 227 }
230 228
231 229 /// Copy ctor
232 230 explicit ArrayData(const ArrayData &other)
233 231 {
234 232 QReadLocker otherLocker{&other.m_Lock};
235 233 m_Data = other.m_Data;
236 234 m_NbComponents = other.m_NbComponents;
237 235 }
238 236
239 237 // /////////////// //
240 238 // General methods //
241 239 // /////////////// //
242 240
243 241 /**
244 242 * Merges into the array data an other array data. The two array datas must have the same number
245 243 * of components so the merge can be done
246 244 * @param other the array data to merge with
247 245 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
248 246 * inserted at the end
249 247 */
250 248 void add(const ArrayData<Dim> &other, bool prepend = false)
251 249 {
252 250 QWriteLocker locker{&m_Lock};
253 251 QReadLocker otherLocker{&other.m_Lock};
254 252
255 253 if (m_NbComponents != other.componentCount()) {
256 254 return;
257 255 }
258 256
259 if (prepend) {
260 auto otherDataSize = other.m_Data.size();
261 m_Data.insert(m_Data.begin(), otherDataSize, 0.);
262 for (auto i = 0; i < otherDataSize; ++i) {
263 m_Data.replace(i, other.m_Data.at(i));
264 }
265 }
266 else {
267 m_Data.append(other.m_Data);
268 }
257 insert(other.cbegin(), other.cend(), prepend);
269 258 }
270 259
271 260 void clear()
272 261 {
273 262 QWriteLocker locker{&m_Lock};
274 263 m_Data.clear();
275 264 }
276 265
277 266 int componentCount() const noexcept { return m_NbComponents; }
278 267
279 268 /// @return the size (i.e. number of values) of a single component
280 269 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
281 270 int size() const
282 271 {
283 272 QReadLocker locker{&m_Lock};
284 273 return m_Data.size() / m_NbComponents;
285 274 }
286 275
287 276 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
288 277 {
289 278 QReadLocker locker{&m_Lock};
290 279 return arraydata_detail::Sort<Dim>::sort(m_Data, m_NbComponents, sortPermutation);
291 280 }
292 281
293 282 // ///////// //
294 283 // Iterators //
295 284 // ///////// //
296 285
297 286 ArrayDataIterator begin()
298 287 {
299 288 return ArrayDataIterator{
300 289 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, false> >(
301 290 m_Data, m_NbComponents, true)}};
302 291 }
303 292
304 293 ArrayDataIterator end()
305 294 {
306 295 return ArrayDataIterator{
307 296 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, false> >(
308 297 m_Data, m_NbComponents, false)}};
309 298 }
310 299
311 300 ArrayDataIterator cbegin() const
312 301 {
313 302 return ArrayDataIterator{
314 303 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, true> >(
315 304 m_Data, m_NbComponents, true)}};
316 305 }
317 306
318 307 ArrayDataIterator cend() const
319 308 {
320 309 return ArrayDataIterator{
321 310 ArrayDataIteratorValue{std::make_unique<arraydata_detail::IteratorValue<Dim, true> >(
322 311 m_Data, m_NbComponents, false)}};
323 312 }
324 313
325 314 void erase(ArrayDataIterator first, ArrayDataIterator last)
326 315 {
327 316 auto firstImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, false> *>(first->impl());
328 317 auto lastImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, false> *>(last->impl());
329 318
330 319 if (firstImpl && lastImpl) {
331 320 m_Data.erase(firstImpl->m_It, lastImpl->m_It);
332 321 }
333 322 }
334 323
335 /// Inserts at the end of the array data the values passed as a parameter. This
336 /// method is intended to be used in the context of generating a back insert iterator, or only
337 /// if it's ensured that the total size of the vector is consistent with the number of
338 /// components of the array data
339 /// @param values the values to insert
340 /// @sa http://en.cppreference.com/w/cpp/iterator/back_inserter
341 void push_back(const QVector<double> &values)
324 void insert(ArrayDataIterator first, ArrayDataIterator last, bool prepend = false)
342 325 {
343 Q_ASSERT(values.size() % m_NbComponents == 0);
344 m_Data.append(values);
326 auto firstImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, true> *>(first->impl());
327 auto lastImpl = dynamic_cast<arraydata_detail::IteratorValue<Dim, true> *>(last->impl());
328
329 if (firstImpl && lastImpl) {
330 auto insertIt = prepend ? m_Data.begin() : m_Data.end();
331
332 m_Data.insert(insertIt, firstImpl->m_It, lastImpl->m_It);
333 }
345 334 }
346 335
347 336 /**
348 337 * @return the data at a specified index
349 338 * @remarks index must be a valid position
350 339 */
351 340 double at(int index) const noexcept
352 341 {
353 342 QReadLocker locker{&m_Lock};
354 343 return m_Data.at(index);
355 344 }
356 345
357 346 // ///////////// //
358 347 // 1-dim methods //
359 348 // ///////////// //
360 349
361 350 /**
362 351 * @return the data as a vector, as a const reference
363 352 * @remarks this method is only available for a unidimensional ArrayData
364 353 */
365 354 template <int D = Dim, typename = std::enable_if_t<D == 1> >
366 const QVector<double> &cdata() const noexcept
355 DataContainer cdata() const noexcept
367 356 {
368 QReadLocker locker{&m_Lock};
369 357 return m_Data;
370 358 }
371 359
372 360 private:
373 361 DataContainer m_Data;
374 362 /// Number of components (lines). Is always 1 in a 1-dim ArrayData
375 363 int m_NbComponents;
376 364 mutable QReadWriteLock m_Lock;
377 365 };
378 366
379 367 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,74 +1,74
1 1 #ifndef SCIQLOP_ARRAYDATAITERATOR_H
2 2 #define SCIQLOP_ARRAYDATAITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 5 #include "Data/SqpIterator.h"
6 6
7 7 #include <QVector>
8 8 #include <memory>
9 9
10 10 /**
11 11 * @brief The ArrayDataIteratorValue class represents the current value of an array data iterator.
12 12 * It offers standard access methods for the data in the series (at(), first()), but it is up to
13 13 * each array data to define its own implementation of how to retrieve this data (one-dim or two-dim
14 14 * array), by implementing the ArrayDataIteratorValue::Impl interface
15 15 * @sa ArrayDataIterator
16 16 */
17 17 class SCIQLOP_CORE_EXPORT ArrayDataIteratorValue {
18 18 public:
19 19 struct Impl {
20 20 virtual ~Impl() noexcept = default;
21 21 virtual std::unique_ptr<Impl> clone() const = 0;
22 22 virtual int distance(const Impl &other) const = 0;
23 23 virtual bool equals(const Impl &other) const = 0;
24 24 virtual bool lowerThan(const Impl &other) const = 0;
25 25 virtual std::unique_ptr<Impl> advance(int offset) const = 0;
26 virtual void next() = 0;
26 virtual void next(int offset) = 0;
27 27 virtual void prev() = 0;
28 28 virtual double at(int componentIndex) const = 0;
29 29 virtual double first() const = 0;
30 30 virtual double min() const = 0;
31 31 virtual double max() const = 0;
32 32 virtual QVector<double> values() const = 0;
33 33
34 34 virtual void swap(Impl &other) = 0;
35 35 };
36 36
37 37 explicit ArrayDataIteratorValue(std::unique_ptr<Impl> impl);
38 38 ArrayDataIteratorValue(const ArrayDataIteratorValue &other);
39 39 ArrayDataIteratorValue &operator=(ArrayDataIteratorValue other);
40 40
41 41 int distance(const ArrayDataIteratorValue &other) const;
42 42 bool equals(const ArrayDataIteratorValue &other) const;
43 43 bool lowerThan(const ArrayDataIteratorValue &other) const;
44 44
45 45 ArrayDataIteratorValue advance(int offset) const;
46 46 /// Advances to the next value
47 void next();
47 void next(int offset = 1);
48 48 /// Moves back to the previous value
49 49 void prev();
50 50 /// Gets value of a specified component
51 51 double at(int componentIndex) const;
52 52 /// Gets value of first component
53 53 double first() const;
54 54 /// Gets min value among all components
55 55 double min() const;
56 56 /// Gets max value among all components
57 57 double max() const;
58 58 /// Gets all values
59 59 QVector<double> values() const;
60 60
61 61 Impl *impl();
62 62
63 63 friend void swap(ArrayDataIteratorValue &lhs, ArrayDataIteratorValue &rhs)
64 64 {
65 65 std::swap(lhs.m_Impl, rhs.m_Impl);
66 66 }
67 67
68 68 private:
69 69 std::unique_ptr<Impl> m_Impl;
70 70 };
71 71
72 72 using ArrayDataIterator = SqpIterator<ArrayDataIteratorValue>;
73 73
74 74 #endif // SCIQLOP_ARRAYDATAITERATOR_H
@@ -1,410 +1,399
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/DataSeriesMergeHelper.h>
10 10 #include <Data/IDataSeries.h>
11 11
12 12 #include <QLoggingCategory>
13 13 #include <QReadLocker>
14 14 #include <QReadWriteLock>
15 15 #include <memory>
16 16
17 17 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
18 18 // definitions with inheritance. Inline method is used instead
19 19 inline const QLoggingCategory &LOG_DataSeries()
20 20 {
21 21 static const QLoggingCategory category{"DataSeries"};
22 22 return category;
23 23 }
24 24
25 25 template <int Dim>
26 26 class DataSeries;
27 27
28 28 namespace dataseries_detail {
29 29
30 30 template <int Dim, bool IsConst>
31 31 class IteratorValue : public DataSeriesIteratorValue::Impl {
32 32 public:
33 33 friend class DataSeries<Dim>;
34 34
35 35 template <bool IC = IsConst, typename = std::enable_if_t<IC == false> >
36 36 explicit IteratorValue(DataSeries<Dim> &dataSeries, bool begin)
37 37 : m_XIt(begin ? dataSeries.xAxisData()->begin() : dataSeries.xAxisData()->end()),
38 38 m_ValuesIt(begin ? dataSeries.valuesData()->begin() : dataSeries.valuesData()->end())
39 39 {
40 40 }
41 41
42 42 template <bool IC = IsConst, typename = std::enable_if_t<IC == true> >
43 43 explicit IteratorValue(const DataSeries<Dim> &dataSeries, bool begin)
44 44 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
45 45 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
46 46 : dataSeries.valuesData()->cend())
47 47 {
48 48 }
49 49
50 50 IteratorValue(const IteratorValue &other) = default;
51 51
52 52 std::unique_ptr<DataSeriesIteratorValue::Impl> clone() const override
53 53 {
54 54 return std::make_unique<IteratorValue<Dim, IsConst> >(*this);
55 55 }
56 56
57 57 int distance(const DataSeriesIteratorValue::Impl &other) const override try {
58 58 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
59 59 return m_XIt->distance(*otherImpl.m_XIt);
60 60 }
61 61 catch (const std::bad_cast &) {
62 62 return 0;
63 63 }
64 64
65 65 bool equals(const DataSeriesIteratorValue::Impl &other) const override try {
66 66 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
67 67 return std::tie(m_XIt, m_ValuesIt) == std::tie(otherImpl.m_XIt, otherImpl.m_ValuesIt);
68 68 }
69 69 catch (const std::bad_cast &) {
70 70 return false;
71 71 }
72 72
73 73 bool lowerThan(const DataSeriesIteratorValue::Impl &other) const override try {
74 74 const auto &otherImpl = dynamic_cast<const IteratorValue &>(other);
75 75 return m_XIt->lowerThan(*otherImpl.m_XIt);
76 76 }
77 77 catch (const std::bad_cast &) {
78 78 return false;
79 79 }
80 80
81 81 std::unique_ptr<DataSeriesIteratorValue::Impl> advance(int offset) const override
82 82 {
83 83 auto result = clone();
84 while (offset--) {
85 result->next();
86 }
84 result->next(offset);
87 85 return result;
88 86 }
89 87
90 void next() override
88 void next(int offset) override
91 89 {
92 ++m_XIt;
93 ++m_ValuesIt;
90 m_XIt->next(offset);
91 m_ValuesIt->next(offset);
94 92 }
95 93
96 94 void prev() override
97 95 {
98 96 --m_XIt;
99 97 --m_ValuesIt;
100 98 }
101 99
102 100 double x() const override { return m_XIt->at(0); }
103 101 double value() const override { return m_ValuesIt->at(0); }
104 102 double value(int componentIndex) const override { return m_ValuesIt->at(componentIndex); }
105 103 double minValue() const override { return m_ValuesIt->min(); }
106 104 double maxValue() const override { return m_ValuesIt->max(); }
107 105 QVector<double> values() const override { return m_ValuesIt->values(); }
108 106
109 107 void swap(DataSeriesIteratorValue::Impl &other) override
110 108 {
111 109 auto &otherImpl = dynamic_cast<IteratorValue &>(other);
112 110 m_XIt->impl()->swap(*otherImpl.m_XIt->impl());
113 111 m_ValuesIt->impl()->swap(*otherImpl.m_ValuesIt->impl());
114 112 }
115 113
116 114 private:
117 115 ArrayDataIterator m_XIt;
118 116 ArrayDataIterator m_ValuesIt;
119 117 };
120 118 } // namespace dataseries_detail
121 119
122 120 /**
123 121 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
124 122 *
125 123 * It proposes to set a dimension for the values ​​data.
126 124 *
127 125 * A DataSeries is always sorted on its x-axis data.
128 126 *
129 127 * @tparam Dim The dimension of the values data
130 128 *
131 129 */
132 130 template <int Dim>
133 131 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
134 132 friend class DataSeriesMergeHelper;
135 133
136 134 public:
137 /// Tag needed to define the push_back() method
138 /// @sa push_back()
139 using value_type = DataSeriesIteratorValue;
140
141 135 /// @sa IDataSeries::xAxisData()
142 136 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
143 137 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
144 138
145 139 /// @sa IDataSeries::xAxisUnit()
146 140 Unit xAxisUnit() const override { return m_XAxisUnit; }
147 141
148 142 /// @return the values dataset
149 143 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
150 144 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
151 145
152 146 /// @sa IDataSeries::valuesUnit()
153 147 Unit valuesUnit() const override { return m_ValuesUnit; }
154 148
155 149
156 150 SqpRange range() const override
157 151 {
158 if (!m_XAxisData->cdata().isEmpty()) {
159 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
152 if (!m_XAxisData->cdata().empty()) {
153 return SqpRange{m_XAxisData->cdata().front(), m_XAxisData->cdata().back()};
160 154 }
161 155
162 156 return SqpRange{};
163 157 }
164 158
165 159 void clear()
166 160 {
167 161 m_XAxisData->clear();
168 162 m_ValuesData->clear();
169 163 }
170 164
171 165 bool isEmpty() const noexcept { return m_XAxisData->size() == 0; }
172 166
173 167 /// Merges into the data series an other data series
174 168 /// @remarks the data series to merge with is cleared after the operation
175 169 void merge(IDataSeries *dataSeries) override
176 170 {
177 171 dataSeries->lockWrite();
178 172 lockWrite();
179 173
180 174 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
181 175 DataSeriesMergeHelper::merge(*other, *this);
182 176 }
183 177 else {
184 178 qCWarning(LOG_DataSeries())
185 179 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
186 180 }
187 181 unlock();
188 182 dataSeries->unlock();
189 183 }
190 184
191 185 void purge(double min, double max) override
192 186 {
193 187 // Nothing to purge if series is empty
194 188 if (isEmpty()) {
195 189 return;
196 190 }
197 191
198 192 if (min > max) {
199 193 std::swap(min, max);
200 194 }
201 195
202 196 // Nothing to purge if series min/max are inside purge range
203 197 auto xMin = cbegin()->x();
204 198 auto xMax = (--cend())->x();
205 199 if (xMin >= min && xMax <= max) {
206 200 return;
207 201 }
208 202
209 203 auto lowerIt = std::lower_bound(
210 204 begin(), end(), min, [](const auto &it, const auto &val) { return it.x() < val; });
211 205 erase(begin(), lowerIt);
212 206 auto upperIt = std::upper_bound(
213 207 begin(), end(), max, [](const auto &val, const auto &it) { return val < it.x(); });
214 208 erase(upperIt, end());
215 209 }
216 210
217 211 // ///////// //
218 212 // Iterators //
219 213 // ///////// //
220 214
221 215 DataSeriesIterator begin() override
222 216 {
223 217 return DataSeriesIterator{DataSeriesIteratorValue{
224 218 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, true)}};
225 219 }
226 220
227 221 DataSeriesIterator end() override
228 222 {
229 223 return DataSeriesIterator{DataSeriesIteratorValue{
230 224 std::make_unique<dataseries_detail::IteratorValue<Dim, false> >(*this, false)}};
231 225 }
232 226
233 227 DataSeriesIterator cbegin() const override
234 228 {
235 229 return DataSeriesIterator{DataSeriesIteratorValue{
236 230 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, true)}};
237 231 }
238 232
239 233 DataSeriesIterator cend() const override
240 234 {
241 235 return DataSeriesIterator{DataSeriesIteratorValue{
242 236 std::make_unique<dataseries_detail::IteratorValue<Dim, true> >(*this, false)}};
243 237 }
244 238
245 239 void erase(DataSeriesIterator first, DataSeriesIterator last)
246 240 {
247 241 auto firstImpl
248 242 = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(first->impl());
249 243 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, false> *>(last->impl());
250 244
251 245 if (firstImpl && lastImpl) {
252 246 m_XAxisData->erase(firstImpl->m_XIt, lastImpl->m_XIt);
253 247 m_ValuesData->erase(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt);
254 248 }
255 249 }
256 250
251 void insert(DataSeriesIterator first, DataSeriesIterator last, bool prepend = false)
252 {
253 auto firstImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(first->impl());
254 auto lastImpl = dynamic_cast<dataseries_detail::IteratorValue<Dim, true> *>(last->impl());
255
256 if (firstImpl && lastImpl) {
257 m_XAxisData->insert(firstImpl->m_XIt, lastImpl->m_XIt, prepend);
258 m_ValuesData->insert(firstImpl->m_ValuesIt, lastImpl->m_ValuesIt, prepend);
259 }
260 }
261
257 262 /// @sa IDataSeries::minXAxisData()
258 263 DataSeriesIterator minXAxisData(double minXAxisData) const override
259 264 {
260 265 return std::lower_bound(
261 266 cbegin(), cend(), minXAxisData,
262 267 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
263 268 }
264 269
265 270 /// @sa IDataSeries::maxXAxisData()
266 271 DataSeriesIterator maxXAxisData(double maxXAxisData) const override
267 272 {
268 273 // Gets the first element that greater than max value
269 274 auto it = std::upper_bound(
270 275 cbegin(), cend(), maxXAxisData,
271 276 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
272 277
273 278 return it == cbegin() ? cend() : --it;
274 279 }
275 280
276 281 std::pair<DataSeriesIterator, DataSeriesIterator> xAxisRange(double minXAxisData,
277 282 double maxXAxisData) const override
278 283 {
279 284 if (minXAxisData > maxXAxisData) {
280 285 std::swap(minXAxisData, maxXAxisData);
281 286 }
282 287
283 288 auto begin = cbegin();
284 289 auto end = cend();
285 290
286 291 auto lowerIt = std::lower_bound(
287 292 begin, end, minXAxisData,
288 293 [](const auto &itValue, const auto &value) { return itValue.x() < value; });
289 294 auto upperIt = std::upper_bound(
290 begin, end, maxXAxisData,
295 lowerIt, end, maxXAxisData,
291 296 [](const auto &value, const auto &itValue) { return value < itValue.x(); });
292 297
293 298 return std::make_pair(lowerIt, upperIt);
294 299 }
295 300
296 301 std::pair<DataSeriesIterator, DataSeriesIterator>
297 302 valuesBounds(double minXAxisData, double maxXAxisData) const override
298 303 {
299 304 // Places iterators to the correct x-axis range
300 305 auto xAxisRangeIts = xAxisRange(minXAxisData, maxXAxisData);
301 306
302 307 // Returns end iterators if the range is empty
303 308 if (xAxisRangeIts.first == xAxisRangeIts.second) {
304 309 return std::make_pair(cend(), cend());
305 310 }
306 311
307 312 // Gets the iterator on the min of all values data
308 313 auto minIt = std::min_element(
309 314 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
310 315 return SortUtils::minCompareWithNaN(it1.minValue(), it2.minValue());
311 316 });
312 317
313 318 // Gets the iterator on the max of all values data
314 319 auto maxIt = std::max_element(
315 320 xAxisRangeIts.first, xAxisRangeIts.second, [](const auto &it1, const auto &it2) {
316 321 return SortUtils::maxCompareWithNaN(it1.maxValue(), it2.maxValue());
317 322 });
318 323
319 324 return std::make_pair(minIt, maxIt);
320 325 }
321 326
322 327 // /////// //
323 328 // Mutexes //
324 329 // /////// //
325 330
326 331 virtual void lockRead() { m_Lock.lockForRead(); }
327 332 virtual void lockWrite() { m_Lock.lockForWrite(); }
328 333 virtual void unlock() { m_Lock.unlock(); }
329 334
330 // ///// //
331 // Other //
332 // ///// //
333
334 /// Inserts at the end of the data series the value of the iterator passed as a parameter. This
335 /// method is intended to be used in the context of generating a back insert iterator
336 /// @param iteratorValue the iterator value containing the values to insert
337 /// @sa http://en.cppreference.com/w/cpp/iterator/back_inserter
338 /// @sa merge()
339 /// @sa value_type
340 void push_back(const value_type &iteratorValue)
341 {
342 m_XAxisData->push_back(QVector<double>{iteratorValue.x()});
343 m_ValuesData->push_back(iteratorValue.values());
344 }
345
346 335 protected:
347 336 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
348 337 /// DataSeries with no values will be created.
349 338 /// @remarks data series is automatically sorted on its x-axis data
350 339 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
351 340 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
352 341 : m_XAxisData{xAxisData},
353 342 m_XAxisUnit{xAxisUnit},
354 343 m_ValuesData{valuesData},
355 344 m_ValuesUnit{valuesUnit}
356 345 {
357 346 if (m_XAxisData->size() != m_ValuesData->size()) {
358 347 clear();
359 348 }
360 349
361 350 // Sorts data if it's not the case
362 351 const auto &xAxisCData = m_XAxisData->cdata();
363 352 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
364 353 sort();
365 354 }
366 355 }
367 356
368 357 /// Copy ctor
369 358 explicit DataSeries(const DataSeries<Dim> &other)
370 359 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
371 360 m_XAxisUnit{other.m_XAxisUnit},
372 361 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
373 362 m_ValuesUnit{other.m_ValuesUnit}
374 363 {
375 364 // Since a series is ordered from its construction and is always ordered, it is not
376 365 // necessary to call the sort method here ('other' is sorted)
377 366 }
378 367
379 368 /// Assignment operator
380 369 template <int D>
381 370 DataSeries &operator=(DataSeries<D> other)
382 371 {
383 372 std::swap(m_XAxisData, other.m_XAxisData);
384 373 std::swap(m_XAxisUnit, other.m_XAxisUnit);
385 374 std::swap(m_ValuesData, other.m_ValuesData);
386 375 std::swap(m_ValuesUnit, other.m_ValuesUnit);
387 376
388 377 return *this;
389 378 }
390 379
391 380 private:
392 381 /**
393 382 * Sorts data series on its x-axis data
394 383 */
395 384 void sort() noexcept
396 385 {
397 386 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
398 387 m_XAxisData = m_XAxisData->sort(permutation);
399 388 m_ValuesData = m_ValuesData->sort(permutation);
400 389 }
401 390
402 391 std::shared_ptr<ArrayData<1> > m_XAxisData;
403 392 Unit m_XAxisUnit;
404 393 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
405 394 Unit m_ValuesUnit;
406 395
407 396 QReadWriteLock m_Lock;
408 397 };
409 398
410 399 #endif // SCIQLOP_DATASERIES_H
@@ -1,78 +1,78
1 1 #ifndef SCIQLOP_DATASERIESITERATOR_H
2 2 #define SCIQLOP_DATASERIESITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 5 #include "Data/SqpIterator.h"
6 6
7 7 #include <QVector>
8 8 #include <memory>
9 9
10 10 /**
11 11 * @brief The DataSeriesIteratorValue class represents the current value of a data series iterator.
12 12 * It offers standard access methods for the data in the series (x-axis, values), but it is up to
13 13 * each series to define its own implementation of how to retrieve this data, by implementing the
14 14 * DataSeriesIteratorValue::Impl interface
15 15 *
16 16 * @sa DataSeriesIterator
17 17 */
18 18 class SCIQLOP_CORE_EXPORT DataSeriesIteratorValue {
19 19 public:
20 20 struct Impl {
21 21 virtual ~Impl() noexcept = default;
22 22 virtual std::unique_ptr<Impl> clone() const = 0;
23 23 virtual int distance(const Impl &other) const = 0;
24 24 virtual bool equals(const Impl &other) const = 0;
25 25 virtual bool lowerThan(const Impl &other) const = 0;
26 26 virtual std::unique_ptr<Impl> advance(int offset) const = 0;
27 virtual void next() = 0;
27 virtual void next(int offset) = 0;
28 28 virtual void prev() = 0;
29 29 virtual double x() const = 0;
30 30 virtual double value() const = 0;
31 31 virtual double value(int componentIndex) const = 0;
32 32 virtual double minValue() const = 0;
33 33 virtual double maxValue() const = 0;
34 34 virtual QVector<double> values() const = 0;
35 35
36 36 virtual void swap(Impl &other) = 0;
37 37 };
38 38
39 39 explicit DataSeriesIteratorValue(std::unique_ptr<Impl> impl);
40 40 DataSeriesIteratorValue(const DataSeriesIteratorValue &other);
41 41 DataSeriesIteratorValue &operator=(DataSeriesIteratorValue other);
42 42
43 43 int distance(const DataSeriesIteratorValue &other) const;
44 44 bool equals(const DataSeriesIteratorValue &other) const;
45 45 bool lowerThan(const DataSeriesIteratorValue &other) const;
46 46
47 47 DataSeriesIteratorValue advance(int offset) const;
48 48 /// Advances to the next value
49 void next();
49 void next(int offset = 1);
50 50 /// Moves back to the previous value
51 51 void prev();
52 52 /// Gets x-axis data
53 53 double x() const;
54 54 /// Gets value data
55 55 double value() const;
56 56 /// Gets value data depending on an index
57 57 double value(int componentIndex) const;
58 58 /// Gets min of all values data
59 59 double minValue() const;
60 60 /// Gets max of all values data
61 61 double maxValue() const;
62 62 /// Gets all values data
63 63 QVector<double> values() const;
64 64
65 65 Impl *impl();
66 66
67 67 friend void swap(DataSeriesIteratorValue &lhs, DataSeriesIteratorValue &rhs)
68 68 {
69 69 std::swap(lhs.m_Impl, rhs.m_Impl);
70 70 }
71 71
72 72 private:
73 73 std::unique_ptr<Impl> m_Impl;
74 74 };
75 75
76 76 using DataSeriesIterator = SqpIterator<DataSeriesIteratorValue>;
77 77
78 78 #endif // SCIQLOP_DATASERIESITERATOR_H
@@ -1,132 +1,83
1 1 #ifndef SCIQLOP_DATASERIESMERGEHELPER_H
2 2 #define SCIQLOP_DATASERIESMERGEHELPER_H
3 3
4 4 template <int Dim>
5 5 class DataSeries;
6 6
7 7 namespace detail {
8 8
9 9 /**
10 10 * Scope that can be used for a merge operation
11 11 * @tparam FEnd the type of function that will be executed at the end of the scope
12 12 */
13 13 template <typename FEnd>
14 14 struct MergeScope {
15 15 explicit MergeScope(FEnd end) : m_End{end} {}
16 16 virtual ~MergeScope() noexcept { m_End(); }
17 17 FEnd m_End;
18 18 };
19 19
20 20 /**
21 21 * Creates a scope for merge operation
22 22 * @tparam end the function executed at the end of the scope
23 23 */
24 24 template <typename FEnd>
25 25 MergeScope<FEnd> scope(FEnd end)
26 26 {
27 27 return MergeScope<FEnd>{end};
28 28 }
29 29
30 /**
31 * Enum used to position a data series relative to another during a merge operation
32 */
33 enum class MergePosition { LOWER_THAN, GREATER_THAN, EQUAL, OVERLAP };
34
35 /**
36 * Computes the position of the first data series relative to the second data series
37 * @param lhs the first data series
38 * @param rhs the second data series
39 * @return the merge position computed
40 * @remarks the data series must not be empty
41 */
42 template <int Dim>
43 MergePosition mergePosition(DataSeries<Dim> &lhs, DataSeries<Dim> &rhs)
44 {
45 Q_ASSERT(!lhs.isEmpty() && !rhs.isEmpty());
46
47 // Case lhs < rhs
48 auto lhsLast = --lhs.cend();
49 auto rhsFirst = rhs.cbegin();
50 if (lhsLast->x() < rhsFirst->x()) {
51 return MergePosition::LOWER_THAN;
52 }
53
54 // Case lhs > rhs
55 auto lhsFirst = lhs.cbegin();
56 auto rhsLast = --rhs.cend();
57 if (lhsFirst->x() > rhsLast->x()) {
58 return MergePosition::GREATER_THAN;
59 }
60
61 // Other cases
62 auto equal = std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(),
63 [](const auto &it1, const auto &it2) {
64 return it1.x() == it2.x() && it1.values() == it2.values();
65 });
66 return equal ? MergePosition::EQUAL : MergePosition::OVERLAP;
67 }
68
69 30 } // namespace detail
70 31
71 32
72 33 /// Helper used to merge two DataSeries
73 34 /// @sa DataSeries
74 35 struct DataSeriesMergeHelper {
75 36 /// Merges the source data series into the dest data series. Data of the source data series are
76 37 /// consumed
77 38 template <int Dim>
78 39 static void merge(DataSeries<Dim> &source, DataSeries<Dim> &dest)
79 40 {
80 41 // Creates a scope to clear source data series at the end of the merge
81 42 auto _ = detail::scope([&source]() { source.clear(); });
82 43
83 44 // Case : source data series is empty -> no merge is made
84 45 if (source.isEmpty()) {
85 46 return;
86 47 }
87 48
88 49 // Case : dest data series is empty -> we simply swap the data
89 50 if (dest.isEmpty()) {
90 51 std::swap(dest.m_XAxisData, source.m_XAxisData);
91 52 std::swap(dest.m_ValuesData, source.m_ValuesData);
92 53 return;
93 54 }
94 55
95 // Gets the position of the source in relation to the destination
96 auto sourcePosition = detail::mergePosition(source, dest);
56 auto destMin = dest.cbegin()->x();
57 auto destMax = (--dest.cend())->x();
58
59 auto sourceBegin = source.cbegin();
60 auto sourceEnd = source.cend();
61 auto sourceMin = sourceBegin->x();
62 auto sourceMax = (--source.cend())->x();
97 63
98 switch (sourcePosition) {
99 case detail::MergePosition::LOWER_THAN:
100 case detail::MergePosition::GREATER_THAN: {
101 auto prepend = sourcePosition == detail::MergePosition::LOWER_THAN;
102 dest.m_XAxisData->add(*source.m_XAxisData, prepend);
103 dest.m_ValuesData->add(*source.m_ValuesData, prepend);
104 break;
105 }
106 case detail::MergePosition::EQUAL:
107 // the data series equal each other : no merge made
108 break;
109 case detail::MergePosition::OVERLAP: {
110 // the two data series overlap : merge is made
111 auto temp = dest.clone();
112 if (auto tempSeries = dynamic_cast<DataSeries<Dim> *>(temp.get())) {
113 // Makes the merge :
114 // - Data are sorted by x-axis values
115 // - If two entries are in the source range and the other range, only one entry
116 // is retained as result
117 // - The results are stored directly in the data series
118 dest.clear();
119 std::set_union(
120 tempSeries->cbegin(), tempSeries->cend(), source.cbegin(), source.cend(),
121 std::back_inserter(dest),
122 [](const auto &it1, const auto &it2) { return it1.x() < it2.x(); });
123 }
124 break;
125 }
126 default:
127 Q_ASSERT(false);
64 // Case : source bounds are inside dest bounds -> no merge is made
65 if (sourceMin >= destMin && sourceMax <= destMax) {
66 return;
128 67 }
68
69 // Default case :
70 // - prepend to dest the values of source that are lower than min value of dest
71 // - append to dest the values of source that are greater than max value of dest
72 auto lowerIt
73 = std::lower_bound(sourceBegin, sourceEnd, destMin,
74 [](const auto &it, const auto &val) { return it.x() < val; });
75 auto upperIt
76 = std::upper_bound(lowerIt, sourceEnd, destMax,
77 [](const auto &val, const auto &it) { return val < it.x(); });
78 dest.insert(sourceBegin, lowerIt, true);
79 dest.insert(upperIt, sourceEnd);
129 80 }
130 81 };
131 82
132 83 #endif // SCIQLOP_DATASERIESMERGEHELPER_H
@@ -1,27 +1,27
1 1 #ifndef SCIQLOP_SCALARSERIES_H
2 2 #define SCIQLOP_SCALARSERIES_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/DataSeries.h>
7 7
8 8 /**
9 9 * @brief The ScalarSeries class is the implementation for a data series representing a scalar.
10 10 */
11 11 class SCIQLOP_CORE_EXPORT ScalarSeries : public DataSeries<1> {
12 12 public:
13 13 /**
14 14 * Ctor with two vectors. The vectors must have the same size, otherwise a ScalarSeries with no
15 15 * values will be created.
16 16 * @param xAxisData x-axis data
17 17 * @param valuesData values data
18 18 */
19 explicit ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
19 explicit ScalarSeries(std::vector<double> xAxisData, std::vector<double> valuesData,
20 20 const Unit &xAxisUnit, const Unit &valuesUnit);
21 21
22 22 std::unique_ptr<IDataSeries> clone() const override;
23 23
24 24 std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) override;
25 25 };
26 26
27 27 #endif // SCIQLOP_SCALARSERIES_H
@@ -1,110 +1,108
1 1 #ifndef SCIQLOP_SQPITERATOR_H
2 2 #define SCIQLOP_SQPITERATOR_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 /**
7 7 * @brief The SqpIterator class represents an iterator used in SciQlop. It defines all operators
8 8 * needed for a standard forward iterator
9 9 * @tparam T the type of object handled in iterator
10 10 * @sa http://www.cplusplus.com/reference/iterator/
11 11 */
12 12 template <typename T>
13 13 class SCIQLOP_CORE_EXPORT SqpIterator {
14 14 public:
15 15 using iterator_category = std::random_access_iterator_tag;
16 16 using value_type = const T;
17 17 using difference_type = std::ptrdiff_t;
18 18 using pointer = value_type *;
19 19 using reference = value_type &;
20 20
21 21 explicit SqpIterator(T value) : m_CurrentValue{std::move(value)} {}
22 22
23 23 virtual ~SqpIterator() noexcept = default;
24 24 SqpIterator(const SqpIterator &) = default;
25 25 SqpIterator &operator=(SqpIterator other) { swap(m_CurrentValue, other.m_CurrentValue); }
26 26
27 27 SqpIterator &operator++()
28 28 {
29 29 m_CurrentValue.next();
30 30 return *this;
31 31 }
32 32
33 33 SqpIterator &operator--()
34 34 {
35 35 m_CurrentValue.prev();
36 36 return *this;
37 37 }
38 38
39 39 SqpIterator operator++(int)const
40 40 {
41 41 auto result = *this;
42 42 this->operator++();
43 43 return result;
44 44 }
45 45 SqpIterator operator--(int)const
46 46 {
47 47 auto result = *this;
48 48 this->operator--();
49 49 return result;
50 50 }
51 51
52 52 SqpIterator &operator+=(int offset)
53 53 {
54 54 if (offset >= 0) {
55 while (offset--) {
56 m_CurrentValue.next();
57 }
55 m_CurrentValue.next(offset);
58 56 }
59 57 else {
60 58 while (offset++) {
61 59 m_CurrentValue.prev();
62 60 }
63 61 }
64 62
65 63 return *this;
66 64 }
67 65 SqpIterator &operator-=(int offset) { return *this += -offset; }
68 66
69 67 SqpIterator operator+(int offset) const
70 68 {
71 69 auto result = *this;
72 70 result += offset;
73 71 return result;
74 72 }
75 73 SqpIterator operator-(int offset) const
76 74 {
77 75 auto result = *this;
78 76 result -= offset;
79 77 return result;
80 78 }
81 79
82 80 int operator-(const SqpIterator &other) const
83 81 {
84 82 return m_CurrentValue.distance(other.m_CurrentValue);
85 83 }
86 84
87 85 const T *operator->() const { return &m_CurrentValue; }
88 86 const T &operator*() const { return m_CurrentValue; }
89 87 T *operator->() { return &m_CurrentValue; }
90 88 T &operator*() { return m_CurrentValue; }
91 89 T &operator[](int offset) const { return m_CurrentValue.advance(offset); }
92 90
93 91 bool operator==(const SqpIterator &other) const
94 92 {
95 93 return m_CurrentValue.equals(other.m_CurrentValue);
96 94 }
97 95 bool operator!=(const SqpIterator &other) const { return !(*this == other); }
98 96 bool operator>(const SqpIterator &other) const { return other.m_CurrentValue.lowerThan(*this); }
99 97 bool operator<(const SqpIterator &other) const
100 98 {
101 99 return m_CurrentValue.lowerThan(other.m_CurrentValue);
102 100 }
103 101 bool operator>=(const SqpIterator &other) const { return !(*this < other); }
104 102 bool operator<=(const SqpIterator &other) const { return !(*this > other); }
105 103
106 104 private:
107 105 T m_CurrentValue;
108 106 };
109 107
110 108 #endif // SCIQLOP_SQPITERATOR_H
@@ -1,34 +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 14 * Ctor with three vectors (one per component). The vectors must have the same size, otherwise a
15 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 explicit VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
22 QVector<double> yValuesData, QVector<double> zValuesData,
21 explicit VectorSeries(std::vector<double> xAxisData, std::vector<double> xValuesData,
22 std::vector<double> yValuesData, std::vector<double> zValuesData,
23 23 const Unit &xAxisUnit, const Unit &valuesUnit);
24 24
25 25 /// Default Ctor
26 explicit VectorSeries(QVector<double> xAxisData, QVector<double> valuesData,
26 explicit VectorSeries(std::vector<double> xAxisData, std::vector<double> valuesData,
27 27 const Unit &xAxisUnit, const Unit &valuesUnit);
28 28
29 29 std::unique_ptr<IDataSeries> clone() const;
30 30
31 31 std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) override;
32 32 };
33 33
34 34 #endif // SCIQLOP_VECTORSERIES_H
@@ -1,77 +1,77
1 1 #include "Data/ArrayDataIterator.h"
2 2
3 3 ArrayDataIteratorValue::ArrayDataIteratorValue(std::unique_ptr<ArrayDataIteratorValue::Impl> impl)
4 4 : m_Impl{std::move(impl)}
5 5 {
6 6 }
7 7
8 8 ArrayDataIteratorValue::ArrayDataIteratorValue(const ArrayDataIteratorValue &other)
9 9 : m_Impl{other.m_Impl->clone()}
10 10 {
11 11 }
12 12
13 13 ArrayDataIteratorValue &ArrayDataIteratorValue::operator=(ArrayDataIteratorValue other)
14 14 {
15 15 m_Impl->swap(*other.m_Impl);
16 16 return *this;
17 17 }
18 18
19 19 int ArrayDataIteratorValue::distance(const ArrayDataIteratorValue &other) const
20 20 {
21 21 return m_Impl->distance(*other.m_Impl);
22 22 }
23 23
24 24 bool ArrayDataIteratorValue::equals(const ArrayDataIteratorValue &other) const
25 25 {
26 26 return m_Impl->equals(*other.m_Impl);
27 27 }
28 28
29 29 bool ArrayDataIteratorValue::lowerThan(const ArrayDataIteratorValue &other) const
30 30 {
31 31 return m_Impl->lowerThan(*other.m_Impl);
32 32 }
33 33
34 34 ArrayDataIteratorValue ArrayDataIteratorValue::advance(int offset) const
35 35 {
36 36 return ArrayDataIteratorValue{m_Impl->advance(offset)};
37 37 }
38 38
39 void ArrayDataIteratorValue::next()
39 void ArrayDataIteratorValue::next(int offset)
40 40 {
41 m_Impl->next();
41 m_Impl->next(offset);
42 42 }
43 43
44 44 void ArrayDataIteratorValue::prev()
45 45 {
46 46 m_Impl->prev();
47 47 }
48 48
49 49 double ArrayDataIteratorValue::at(int componentIndex) const
50 50 {
51 51 return m_Impl->at(componentIndex);
52 52 }
53 53
54 54 double ArrayDataIteratorValue::first() const
55 55 {
56 56 return m_Impl->first();
57 57 }
58 58
59 59 double ArrayDataIteratorValue::min() const
60 60 {
61 61 return m_Impl->min();
62 62 }
63 63
64 64 double ArrayDataIteratorValue::max() const
65 65 {
66 66 return m_Impl->max();
67 67 }
68 68
69 69 QVector<double> ArrayDataIteratorValue::values() const
70 70 {
71 71 return m_Impl->values();
72 72 }
73 73
74 74 ArrayDataIteratorValue::Impl *ArrayDataIteratorValue::impl()
75 75 {
76 76 return m_Impl.get();
77 77 }
@@ -1,84 +1,84
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 m_Impl->swap(*other.m_Impl);
17 17 return *this;
18 18 }
19 19
20 20 int DataSeriesIteratorValue::distance(const DataSeriesIteratorValue &other) const
21 21 {
22 22 auto dist = m_Impl->distance(*other.m_Impl);
23 23 return m_Impl->distance(*other.m_Impl);
24 24 }
25 25
26 26 bool DataSeriesIteratorValue::equals(const DataSeriesIteratorValue &other) const
27 27 {
28 28 return m_Impl->equals(*other.m_Impl);
29 29 }
30 30
31 31 bool DataSeriesIteratorValue::lowerThan(const DataSeriesIteratorValue &other) const
32 32 {
33 33 return m_Impl->lowerThan(*other.m_Impl);
34 34 }
35 35
36 36 DataSeriesIteratorValue DataSeriesIteratorValue::advance(int offset) const
37 37 {
38 38 return DataSeriesIteratorValue{m_Impl->advance(offset)};
39 39 }
40 40
41 void DataSeriesIteratorValue::next()
41 void DataSeriesIteratorValue::next(int offset)
42 42 {
43 m_Impl->next();
43 m_Impl->next(offset);
44 44 }
45 45
46 46 void DataSeriesIteratorValue::prev()
47 47 {
48 48 m_Impl->prev();
49 49 }
50 50
51 51 double DataSeriesIteratorValue::x() const
52 52 {
53 53 return m_Impl->x();
54 54 }
55 55
56 56 double DataSeriesIteratorValue::value() const
57 57 {
58 58 return m_Impl->value();
59 59 }
60 60
61 61 double DataSeriesIteratorValue::value(int componentIndex) const
62 62 {
63 63 return m_Impl->value(componentIndex);
64 64 }
65 65
66 66 double DataSeriesIteratorValue::minValue() const
67 67 {
68 68 return m_Impl->minValue();
69 69 }
70 70
71 71 double DataSeriesIteratorValue::maxValue() const
72 72 {
73 73 return m_Impl->maxValue();
74 74 }
75 75
76 76 QVector<double> DataSeriesIteratorValue::values() const
77 77 {
78 78 return m_Impl->values();
79 79 }
80 80
81 81 DataSeriesIteratorValue::Impl *DataSeriesIteratorValue::impl()
82 82 {
83 83 return m_Impl.get();
84 84 }
@@ -1,31 +1,31
1 1 #include <Data/ScalarSeries.h>
2 2
3 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
3 ScalarSeries::ScalarSeries(std::vector<double> xAxisData, std::vector<double> valuesData,
4 4 const Unit &xAxisUnit, const Unit &valuesUnit)
5 5 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
6 6 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
7 7 {
8 8 }
9 9
10 10 std::unique_ptr<IDataSeries> ScalarSeries::clone() const
11 11 {
12 12 return std::make_unique<ScalarSeries>(*this);
13 13 }
14 14
15 15 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
16 16 {
17 auto subXAxisData = QVector<double>();
18 auto subValuesData = QVector<double>();
17 auto subXAxisData = std::vector<double>();
18 auto subValuesData = std::vector<double>();
19 19 this->lockRead();
20 20 {
21 21 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
22 22 for (auto it = bounds.first; it != bounds.second; ++it) {
23 subXAxisData.append(it->x());
24 subValuesData.append(it->value());
23 subXAxisData.push_back(it->x());
24 subValuesData.push_back(it->value());
25 25 }
26 26 }
27 27 this->unlock();
28 28
29 return std::make_shared<ScalarSeries>(subXAxisData, subValuesData, this->xAxisUnit(),
30 this->valuesUnit());
29 return std::make_shared<ScalarSeries>(std::move(subXAxisData), std::move(subValuesData),
30 this->xAxisUnit(), this->valuesUnit());
31 31 }
@@ -1,83 +1,88
1 1 #include "Data/VectorSeries.h"
2 2
3 3 namespace {
4 4
5 5 /**
6 6 * Flatten the three components of a vector to a single QVector that can be passed to an ArrayData
7 7 *
8 8 * Example:
9 9 * xValues = {1, 2, 3}
10 10 * yValues = {4, 5, 6}
11 11 * zValues = {7, 8, 9}
12 12 *
13 13 * result = {1, 4, 7, 2, 5, 8, 3, 6, 9}
14 14 *
15 15 * @param xValues the x-component values of the vector
16 16 * @param yValues the y-component values of the vector
17 17 * @param zValues the z-component values of the vector
18 18 * @return the single QVector
19 19 * @remarks the three components are consumed
20 20 * @sa ArrayData
21 21 */
22 QVector<double> flatten(QVector<double> xValues, QVector<double> yValues, QVector<double> zValues)
22 std::vector<double> flatten(std::vector<double> xValues, std::vector<double> yValues,
23 std::vector<double> zValues)
23 24 {
24 25 if (xValues.size() != yValues.size() || xValues.size() != zValues.size()) {
25 26 /// @todo ALX : log
26 27 return {};
27 28 }
28 29
29 auto result = QVector<double>{};
30 auto result = std::vector<double>();
30 31 result.reserve(xValues.size() * 3);
31 32
32 while (!xValues.isEmpty()) {
33 result.append({xValues.takeFirst(), yValues.takeFirst(), zValues.takeFirst()});
33 while (!xValues.empty()) {
34 result.insert(result.cend(), {xValues.front(), yValues.front(), zValues.front()});
35 xValues.erase(xValues.begin());
36 yValues.erase(yValues.begin());
37 zValues.erase(zValues.begin());
34 38 }
35 39
36 40 return result;
37 41 }
38 42
39 43 } // namespace
40 44
41 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
42 QVector<double> yValuesData, QVector<double> zValuesData,
45 VectorSeries::VectorSeries(std::vector<double> xAxisData, std::vector<double> xValuesData,
46 std::vector<double> yValuesData, std::vector<double> zValuesData,
43 47 const Unit &xAxisUnit, const Unit &valuesUnit)
44 48 : VectorSeries{std::move(xAxisData), flatten(std::move(xValuesData), std::move(yValuesData),
45 49 std::move(zValuesData)),
46 50 xAxisUnit, valuesUnit}
47 51 {
48 52 }
49 53
50 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> valuesData,
54 VectorSeries::VectorSeries(std::vector<double> xAxisData, std::vector<double> valuesData,
51 55 const Unit &xAxisUnit, const Unit &valuesUnit)
52 56 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
53 57 std::make_shared<ArrayData<2> >(std::move(valuesData), 3), valuesUnit}
54 58 {
55 59 }
56 60
57 61 std::unique_ptr<IDataSeries> VectorSeries::clone() const
58 62 {
59 63 return std::make_unique<VectorSeries>(*this);
60 64 }
61 65
62 66 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
63 67 {
64 auto subXAxisData = QVector<double>();
65 auto subXValuesData = QVector<double>();
66 auto subYValuesData = QVector<double>();
67 auto subZValuesData = QVector<double>();
68 auto subXAxisData = std::vector<double>();
69 auto subXValuesData = std::vector<double>();
70 auto subYValuesData = std::vector<double>();
71 auto subZValuesData = std::vector<double>();
68 72
69 73 this->lockRead();
70 74 {
71 75 auto bounds = xAxisRange(range.m_TStart, range.m_TEnd);
72 76 for (auto it = bounds.first; it != bounds.second; ++it) {
73 subXAxisData.append(it->x());
74 subXValuesData.append(it->value(0));
75 subYValuesData.append(it->value(1));
76 subZValuesData.append(it->value(2));
77 subXAxisData.push_back(it->x());
78 subXValuesData.push_back(it->value(0));
79 subYValuesData.push_back(it->value(1));
80 subZValuesData.push_back(it->value(2));
77 81 }
78 82 }
79 83 this->unlock();
80 84
81 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
82 subZValuesData, this->xAxisUnit(), this->valuesUnit());
85 return std::make_shared<VectorSeries>(std::move(subXAxisData), std::move(subXValuesData),
86 std::move(subYValuesData), std::move(subZValuesData),
87 this->xAxisUnit(), this->valuesUnit());
83 88 }
@@ -1,638 +1,636
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 13 namespace {
14 14
15 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const QVector<double> &xData,
16 const QVector<double> &valuesData)
15 using DataContainer = std::vector<double>;
16
17 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData,
18 const DataContainer &valuesData)
17 19 {
18 20 QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(),
19 21 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
20 22 QVERIFY(std::equal(
21 23 first, last, valuesData.cbegin(), valuesData.cend(),
22 24 [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; }));
23 25 }
24 26
25 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const QVector<double> &xData,
26 const QVector<QVector<double> > &valuesData)
27 void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData,
28 const std::vector<DataContainer> &valuesData)
27 29 {
28 30 QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(),
29 31 [](const auto &it, const auto &expectedX) { return it.x() == expectedX; }));
30 32 for (auto i = 0; i < valuesData.size(); ++i) {
31 33 auto componentData = valuesData.at(i);
32 34
33 35 QVERIFY(std::equal(
34 36 first, last, componentData.cbegin(), componentData.cend(),
35 37 [i](const auto &it, const auto &expectedVal) { return it.value(i) == expectedVal; }));
36 38 }
37 39 }
38 40
39 41 } // namespace
40 42
41 43 class TestDataSeries : public QObject {
42 44 Q_OBJECT
43 45 private:
44 46 template <typename T>
45 47 void testValuesBoundsStructure()
46 48 {
47 49 // ////////////// //
48 50 // Test structure //
49 51 // ////////////// //
50 52
51 53 // Data series to get values bounds
52 54 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
53 55
54 56 // x-axis range
55 57 QTest::addColumn<double>("minXAxis");
56 58 QTest::addColumn<double>("maxXAxis");
57 59
58 60 // Expected results
59 61 QTest::addColumn<bool>(
60 62 "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators)
61 63 QTest::addColumn<double>("expectedMinValue");
62 64 QTest::addColumn<double>("expectedMaxValue");
63 65 }
64 66
65 67 template <typename T>
66 68 void testValuesBounds()
67 69 {
68 70 QFETCH(std::shared_ptr<T>, dataSeries);
69 71 QFETCH(double, minXAxis);
70 72 QFETCH(double, maxXAxis);
71 73
72 74 QFETCH(bool, expectedOK);
73 75 QFETCH(double, expectedMinValue);
74 76 QFETCH(double, expectedMaxValue);
75 77
76 78 auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis);
77 79 auto end = dataSeries->cend();
78 80
79 81 // Checks iterators with expected result
80 82 QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end);
81 83
82 84 if (expectedOK) {
83 85 auto compare = [](const auto &v1, const auto &v2) {
84 86 return (std::isnan(v1) && std::isnan(v2)) || v1 == v2;
85 87 };
86 88
87 89 QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue()));
88 90 QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue()));
89 91 }
90 92 }
91 93
92 94 template <typename T>
93 95 void testPurgeStructure()
94 96 {
95 97 // ////////////// //
96 98 // Test structure //
97 99 // ////////////// //
98 100
99 101 // Data series to purge
100 102 QTest::addColumn<std::shared_ptr<T> >("dataSeries");
101 103 QTest::addColumn<double>("min");
102 104 QTest::addColumn<double>("max");
103 105
104 106 // Expected values after purge
105 QTest::addColumn<QVector<double> >("expectedXAxisData");
106 QTest::addColumn<QVector<QVector<double> > >("expectedValuesData");
107 QTest::addColumn<DataContainer>("expectedXAxisData");
108 QTest::addColumn<std::vector<DataContainer> >("expectedValuesData");
107 109 }
108 110
109 111 template <typename T>
110 112 void testPurge()
111 113 {
112 114 QFETCH(std::shared_ptr<T>, dataSeries);
113 115 QFETCH(double, min);
114 116 QFETCH(double, max);
115 117
116 118 dataSeries->purge(min, max);
117 119
118 120 // Validates results
119 QFETCH(QVector<double>, expectedXAxisData);
120 QFETCH(QVector<QVector<double> >, expectedValuesData);
121 QFETCH(DataContainer, expectedXAxisData);
122 QFETCH(std::vector<DataContainer>, expectedValuesData);
121 123
122 124 validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData,
123 125 expectedValuesData);
124 126 }
125 127
126 128 private slots:
127 129
128 130 /// Input test data
129 131 /// @sa testCtor()
130 132 void testCtor_data();
131 133
132 134 /// Tests construction of a data series
133 135 void testCtor();
134 136
135 137 /// Input test data
136 138 /// @sa testMerge()
137 139 void testMerge_data();
138 140
139 141 /// Tests merge of two data series
140 142 void testMerge();
141 143
142 144 /// Input test data
143 145 /// @sa testPurgeScalar()
144 146 void testPurgeScalar_data();
145 147
146 148 /// Tests purge of a scalar series
147 149 void testPurgeScalar();
148 150
149 151 /// Input test data
150 152 /// @sa testPurgeVector()
151 153 void testPurgeVector_data();
152 154
153 155 /// Tests purge of a vector series
154 156 void testPurgeVector();
155 157
156 158 /// Input test data
157 159 /// @sa testMinXAxisData()
158 160 void testMinXAxisData_data();
159 161
160 162 /// Tests get min x-axis data of a data series
161 163 void testMinXAxisData();
162 164
163 165 /// Input test data
164 166 /// @sa testMaxXAxisData()
165 167 void testMaxXAxisData_data();
166 168
167 169 /// Tests get max x-axis data of a data series
168 170 void testMaxXAxisData();
169 171
170 172 /// Input test data
171 173 /// @sa testXAxisRange()
172 174 void testXAxisRange_data();
173 175
174 176 /// Tests get x-axis range of a data series
175 177 void testXAxisRange();
176 178
177 179 /// Input test data
178 180 /// @sa testValuesBoundsScalar()
179 181 void testValuesBoundsScalar_data();
180 182
181 183 /// Tests get values bounds of a scalar series
182 184 void testValuesBoundsScalar();
183 185
184 186 /// Input test data
185 187 /// @sa testValuesBoundsVector()
186 188 void testValuesBoundsVector_data();
187 189
188 190 /// Tests get values bounds of a vector series
189 191 void testValuesBoundsVector();
190 192 };
191 193
192 194 void TestDataSeries::testCtor_data()
193 195 {
194 196 // ////////////// //
195 197 // Test structure //
196 198 // ////////////// //
197 199
198 200 // x-axis data
199 QTest::addColumn<QVector<double> >("xAxisData");
201 QTest::addColumn<DataContainer>("xAxisData");
200 202 // values data
201 QTest::addColumn<QVector<double> >("valuesData");
203 QTest::addColumn<DataContainer>("valuesData");
202 204
203 205 // expected x-axis data
204 QTest::addColumn<QVector<double> >("expectedXAxisData");
206 QTest::addColumn<DataContainer>("expectedXAxisData");
205 207 // expected values data
206 QTest::addColumn<QVector<double> >("expectedValuesData");
208 QTest::addColumn<DataContainer>("expectedValuesData");
207 209
208 210 // ////////// //
209 211 // Test cases //
210 212 // ////////// //
211 213
212 214 QTest::newRow("invalidData (different sizes of vectors)")
213 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 200., 300.}
214 << QVector<double>{} << QVector<double>{};
215 << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300.} << DataContainer{}
216 << DataContainer{};
215 217
216 QTest::newRow("sortedData") << QVector<double>{1., 2., 3., 4., 5.}
217 << QVector<double>{100., 200., 300., 400., 500.}
218 << QVector<double>{1., 2., 3., 4., 5.}
219 << QVector<double>{100., 200., 300., 400., 500.};
218 QTest::newRow("sortedData") << DataContainer{1., 2., 3., 4., 5.}
219 << DataContainer{100., 200., 300., 400., 500.}
220 << DataContainer{1., 2., 3., 4., 5.}
221 << DataContainer{100., 200., 300., 400., 500.};
220 222
221 QTest::newRow("unsortedData") << QVector<double>{5., 4., 3., 2., 1.}
222 << QVector<double>{100., 200., 300., 400., 500.}
223 << QVector<double>{1., 2., 3., 4., 5.}
224 << QVector<double>{500., 400., 300., 200., 100.};
223 QTest::newRow("unsortedData") << DataContainer{5., 4., 3., 2., 1.}
224 << DataContainer{100., 200., 300., 400., 500.}
225 << DataContainer{1., 2., 3., 4., 5.}
226 << DataContainer{500., 400., 300., 200., 100.};
225 227
226 228 QTest::newRow("unsortedData2")
227 << QVector<double>{1., 4., 3., 5., 2.} << QVector<double>{100., 200., 300., 400., 500.}
228 << QVector<double>{1., 2., 3., 4., 5.} << QVector<double>{100., 500., 300., 200., 400.};
229 << DataContainer{1., 4., 3., 5., 2.} << DataContainer{100., 200., 300., 400., 500.}
230 << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 500., 300., 200., 400.};
229 231 }
230 232
231 233 void TestDataSeries::testCtor()
232 234 {
233 235 // Creates series
234 QFETCH(QVector<double>, xAxisData);
235 QFETCH(QVector<double>, valuesData);
236 QFETCH(DataContainer, xAxisData);
237 QFETCH(DataContainer, valuesData);
236 238
237 239 auto series = std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
238 240 Unit{}, Unit{});
239 241
240 242 // Validates results : we check that the data series is sorted on its x-axis data
241 QFETCH(QVector<double>, expectedXAxisData);
242 QFETCH(QVector<double>, expectedValuesData);
243 QFETCH(DataContainer, expectedXAxisData);
244 QFETCH(DataContainer, expectedValuesData);
243 245
244 246 validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData);
245 247 }
246 248
247 249 namespace {
248 250
249 std::shared_ptr<ScalarSeries> createScalarSeries(QVector<double> xAxisData,
250 QVector<double> valuesData)
251 std::shared_ptr<ScalarSeries> createScalarSeries(DataContainer xAxisData, DataContainer valuesData)
251 252 {
252 253 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData), Unit{},
253 254 Unit{});
254 255 }
255 256
256 std::shared_ptr<VectorSeries> createVectorSeries(QVector<double> xAxisData,
257 QVector<double> xValuesData,
258 QVector<double> yValuesData,
259 QVector<double> zValuesData)
257 std::shared_ptr<VectorSeries> createVectorSeries(DataContainer xAxisData, DataContainer xValuesData,
258 DataContainer yValuesData,
259 DataContainer zValuesData)
260 260 {
261 261 return std::make_shared<VectorSeries>(std::move(xAxisData), std::move(xValuesData),
262 262 std::move(yValuesData), std::move(zValuesData), Unit{},
263 263 Unit{});
264 264 }
265 265
266 266 } // namespace
267 267
268 268 void TestDataSeries::testMerge_data()
269 269 {
270 270 // ////////////// //
271 271 // Test structure //
272 272 // ////////////// //
273 273
274 274 // Data series to merge
275 275 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
276 276 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries2");
277 277
278 278 // Expected values in the first data series after merge
279 QTest::addColumn<QVector<double> >("expectedXAxisData");
280 QTest::addColumn<QVector<double> >("expectedValuesData");
279 QTest::addColumn<DataContainer>("expectedXAxisData");
280 QTest::addColumn<DataContainer>("expectedValuesData");
281 281
282 282 // ////////// //
283 283 // Test cases //
284 284 // ////////// //
285 285
286 286 QTest::newRow("sortedMerge")
287 287 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
288 288 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
289 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
290 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
289 << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
290 << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
291 291
292 292 QTest::newRow("unsortedMerge")
293 293 << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.})
294 294 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.})
295 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
296 << QVector<double>{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
295 << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
296 << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.};
297 297
298 QTest::newRow("unsortedMerge2")
299 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 300., 400., 500.})
300 << createScalarSeries({3., 4., 5., 6., 7.}, {600., 700., 800., 900., 1000.})
301 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
302 << QVector<double>{100., 200., 600., 700., 800., 900., 1000., 300., 400., 500.};
298 QTest::newRow("unsortedMerge2 (merge not made because source is in the bounds of dest)")
299 << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 800., 900., 1000.})
300 << createScalarSeries({3., 4., 5., 6., 7.}, {300., 400., 500., 600., 700.})
301 << DataContainer{1., 2., 8., 9., 10.} << DataContainer{100., 200., 800., 900., 1000.};
303 302
304 303 QTest::newRow("unsortedMerge3")
305 << createScalarSeries({3., 5., 8., 7., 2}, {100., 200., 300., 400., 500.})
306 << createScalarSeries({6., 4., 9., 10., 1.}, {600., 700., 800., 900., 1000.})
307 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.}
308 << QVector<double>{1000., 500., 100., 700., 200., 600., 400., 300., 800., 900.};
304 << createScalarSeries({3., 4., 5., 7., 8}, {300., 400., 500., 700., 800.})
305 << createScalarSeries({1., 2., 3., 7., 10.}, {100., 200., 333., 777., 1000.})
306 << DataContainer{1., 2., 3., 4., 5., 7., 8., 10.}
307 << DataContainer{100., 200., 300., 400., 500., 700., 800., 1000.};
309 308 }
310 309
311 310 void TestDataSeries::testMerge()
312 311 {
313 312 // Merges series
314 313 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
315 314 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries2);
316 315
317 316 dataSeries->merge(dataSeries2.get());
318 317
319 318 // Validates results : we check that the merge is valid and the data series is sorted on its
320 319 // x-axis data
321 QFETCH(QVector<double>, expectedXAxisData);
322 QFETCH(QVector<double>, expectedValuesData);
320 QFETCH(DataContainer, expectedXAxisData);
321 QFETCH(DataContainer, expectedValuesData);
323 322
324 323 validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData);
325 324 }
326 325
327 326 void TestDataSeries::testPurgeScalar_data()
328 327 {
329 328 testPurgeStructure<ScalarSeries>();
330 329
331 330 // ////////// //
332 331 // Test cases //
333 332 // ////////// //
334 333
335 334 QTest::newRow("purgeScalar") << createScalarSeries({1., 2., 3., 4., 5.},
336 335 {100., 200., 300., 400., 500.})
337 << 2. << 4. << QVector<double>{2., 3., 4.}
338 << QVector<QVector<double> >{{200., 300., 400.}};
336 << 2. << 4. << DataContainer{2., 3., 4.}
337 << std::vector<DataContainer>{{200., 300., 400.}};
339 338 QTest::newRow("purgeScalar2") << createScalarSeries({1., 2., 3., 4., 5.},
340 339 {100., 200., 300., 400., 500.})
341 << 0. << 2.5 << QVector<double>{1., 2.}
342 << QVector<QVector<double> >{{100., 200.}};
340 << 0. << 2.5 << DataContainer{1., 2.}
341 << std::vector<DataContainer>{{100., 200.}};
343 342 QTest::newRow("purgeScalar3") << createScalarSeries({1., 2., 3., 4., 5.},
344 343 {100., 200., 300., 400., 500.})
345 << 3.5 << 7. << QVector<double>{4., 5.}
346 << QVector<QVector<double> >{{400., 500.}};
344 << 3.5 << 7. << DataContainer{4., 5.}
345 << std::vector<DataContainer>{{400., 500.}};
347 346 QTest::newRow("purgeScalar4") << createScalarSeries({1., 2., 3., 4., 5.},
348 347 {100., 200., 300., 400., 500.})
349 << 0. << 7. << QVector<double>{1., 2., 3., 4., 5.}
350 << QVector<QVector<double> >{{100., 200., 300., 400., 500.}};
348 << 0. << 7. << DataContainer{1., 2., 3., 4., 5.}
349 << std::vector<DataContainer>{{100., 200., 300., 400., 500.}};
351 350 QTest::newRow("purgeScalar5") << createScalarSeries({1., 2., 3., 4., 5.},
352 351 {100., 200., 300., 400., 500.})
353 << 5.5 << 7. << QVector<double>{}
354 << QVector<QVector<double> >{{}};
352 << 5.5 << 7. << DataContainer{} << std::vector<DataContainer>{{}};
355 353 }
356 354
357 355 void TestDataSeries::testPurgeScalar()
358 356 {
359 357 testPurge<ScalarSeries>();
360 358 }
361 359
362 360 void TestDataSeries::testPurgeVector_data()
363 361 {
364 362 testPurgeStructure<VectorSeries>();
365 363
366 364 // ////////// //
367 365 // Test cases //
368 366 // ////////// //
369 367
370 368 QTest::newRow("purgeVector") << createVectorSeries({1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.},
371 369 {11., 12., 13., 14., 15.},
372 370 {16., 17., 18., 19., 20.})
373 << 2. << 4. << QVector<double>{2., 3., 4.}
374 << QVector<QVector<double> >{
371 << 2. << 4. << DataContainer{2., 3., 4.}
372 << std::vector<DataContainer>{
375 373 {7., 8., 9.}, {12., 13., 14.}, {17., 18., 19.}};
376 374 }
377 375
378 376 void TestDataSeries::testPurgeVector()
379 377 {
380 378 testPurge<VectorSeries>();
381 379 }
382 380
383 381 void TestDataSeries::testMinXAxisData_data()
384 382 {
385 383 // ////////////// //
386 384 // Test structure //
387 385 // ////////////// //
388 386
389 387 // Data series to get min data
390 388 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
391 389
392 390 // Min data
393 391 QTest::addColumn<double>("min");
394 392
395 393 // Expected results
396 394 QTest::addColumn<bool>(
397 395 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
398 396 QTest::addColumn<double>(
399 397 "expectedMin"); // Expected value when method doesn't return end iterator
400 398
401 399 // ////////// //
402 400 // Test cases //
403 401 // ////////// //
404 402
405 403 QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.},
406 404 {100., 200., 300., 400., 500.})
407 405 << 0. << true << 1.;
408 406 QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.},
409 407 {100., 200., 300., 400., 500.})
410 408 << 1. << true << 1.;
411 409 QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.},
412 410 {100., 200., 300., 400., 500.})
413 411 << 1.1 << true << 2.;
414 412 QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.},
415 413 {100., 200., 300., 400., 500.})
416 414 << 5. << true << 5.;
417 415 QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.},
418 416 {100., 200., 300., 400., 500.})
419 417 << 5.1 << false << std::numeric_limits<double>::quiet_NaN();
420 418 QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false
421 419 << std::numeric_limits<double>::quiet_NaN();
422 420 }
423 421
424 422 void TestDataSeries::testMinXAxisData()
425 423 {
426 424 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
427 425 QFETCH(double, min);
428 426
429 427 QFETCH(bool, expectedOK);
430 428 QFETCH(double, expectedMin);
431 429
432 430 auto it = dataSeries->minXAxisData(min);
433 431
434 432 QCOMPARE(expectedOK, it != dataSeries->cend());
435 433
436 434 // If the method doesn't return a end iterator, checks with expected value
437 435 if (expectedOK) {
438 436 QCOMPARE(expectedMin, it->x());
439 437 }
440 438 }
441 439
442 440 void TestDataSeries::testMaxXAxisData_data()
443 441 {
444 442 // ////////////// //
445 443 // Test structure //
446 444 // ////////////// //
447 445
448 446 // Data series to get max data
449 447 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
450 448
451 449 // Max data
452 450 QTest::addColumn<double>("max");
453 451
454 452 // Expected results
455 453 QTest::addColumn<bool>(
456 454 "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator)
457 455 QTest::addColumn<double>(
458 456 "expectedMax"); // Expected value when method doesn't return end iterator
459 457
460 458 // ////////// //
461 459 // Test cases //
462 460 // ////////// //
463 461
464 462 QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.},
465 463 {100., 200., 300., 400., 500.})
466 464 << 6. << true << 5.;
467 465 QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.},
468 466 {100., 200., 300., 400., 500.})
469 467 << 5. << true << 5.;
470 468 QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.},
471 469 {100., 200., 300., 400., 500.})
472 470 << 4.9 << true << 4.;
473 471 QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.},
474 472 {100., 200., 300., 400., 500.})
475 473 << 1.1 << true << 1.;
476 474 QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.},
477 475 {100., 200., 300., 400., 500.})
478 476 << 1. << true << 1.;
479 477 QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false
480 478 << std::numeric_limits<double>::quiet_NaN();
481 479 }
482 480
483 481 void TestDataSeries::testMaxXAxisData()
484 482 {
485 483 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
486 484 QFETCH(double, max);
487 485
488 486 QFETCH(bool, expectedOK);
489 487 QFETCH(double, expectedMax);
490 488
491 489 auto it = dataSeries->maxXAxisData(max);
492 490
493 491 QCOMPARE(expectedOK, it != dataSeries->cend());
494 492
495 493 // If the method doesn't return a end iterator, checks with expected value
496 494 if (expectedOK) {
497 495 QCOMPARE(expectedMax, it->x());
498 496 }
499 497 }
500 498
501 499 void TestDataSeries::testXAxisRange_data()
502 500 {
503 501 // ////////////// //
504 502 // Test structure //
505 503 // ////////////// //
506 504
507 505 // Data series to get x-axis range
508 506 QTest::addColumn<std::shared_ptr<ScalarSeries> >("dataSeries");
509 507
510 508 // Min/max values
511 509 QTest::addColumn<double>("min");
512 510 QTest::addColumn<double>("max");
513 511
514 512 // Expected values
515 QTest::addColumn<QVector<double> >("expectedXAxisData");
516 QTest::addColumn<QVector<double> >("expectedValuesData");
513 QTest::addColumn<DataContainer>("expectedXAxisData");
514 QTest::addColumn<DataContainer>("expectedValuesData");
517 515
518 516 // ////////// //
519 517 // Test cases //
520 518 // ////////// //
521 519
522 520 QTest::newRow("xAxisRange1") << createScalarSeries({1., 2., 3., 4., 5.},
523 521 {100., 200., 300., 400., 500.})
524 << -1. << 3.2 << QVector<double>{1., 2., 3.}
525 << QVector<double>{100., 200., 300.};
522 << -1. << 3.2 << DataContainer{1., 2., 3.}
523 << DataContainer{100., 200., 300.};
526 524 QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.},
527 525 {100., 200., 300., 400., 500.})
528 << 1. << 4. << QVector<double>{1., 2., 3., 4.}
529 << QVector<double>{100., 200., 300., 400.};
526 << 1. << 4. << DataContainer{1., 2., 3., 4.}
527 << DataContainer{100., 200., 300., 400.};
530 528 QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.},
531 529 {100., 200., 300., 400., 500.})
532 << 1. << 3.9 << QVector<double>{1., 2., 3.}
533 << QVector<double>{100., 200., 300.};
530 << 1. << 3.9 << DataContainer{1., 2., 3.}
531 << DataContainer{100., 200., 300.};
534 532 QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.},
535 533 {100., 200., 300., 400., 500.})
536 << 0. << 0.9 << QVector<double>{} << QVector<double>{};
534 << 0. << 0.9 << DataContainer{} << DataContainer{};
537 535 QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.},
538 536 {100., 200., 300., 400., 500.})
539 << 0. << 1. << QVector<double>{1.} << QVector<double>{100.};
537 << 0. << 1. << DataContainer{1.} << DataContainer{100.};
540 538 QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.},
541 539 {100., 200., 300., 400., 500.})
542 << 2.1 << 6. << QVector<double>{3., 4., 5.}
543 << QVector<double>{300., 400., 500.};
540 << 2.1 << 6. << DataContainer{3., 4., 5.}
541 << DataContainer{300., 400., 500.};
544 542 QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.},
545 543 {100., 200., 300., 400., 500.})
546 << 6. << 9. << QVector<double>{} << QVector<double>{};
544 << 6. << 9. << DataContainer{} << DataContainer{};
547 545 QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.},
548 546 {100., 200., 300., 400., 500.})
549 << 5. << 9. << QVector<double>{5.} << QVector<double>{500.};
547 << 5. << 9. << DataContainer{5.} << DataContainer{500.};
550 548 }
551 549
552 550 void TestDataSeries::testXAxisRange()
553 551 {
554 552 QFETCH(std::shared_ptr<ScalarSeries>, dataSeries);
555 553 QFETCH(double, min);
556 554 QFETCH(double, max);
557 555
558 QFETCH(QVector<double>, expectedXAxisData);
559 QFETCH(QVector<double>, expectedValuesData);
556 QFETCH(DataContainer, expectedXAxisData);
557 QFETCH(DataContainer, expectedValuesData);
560 558
561 559 auto bounds = dataSeries->xAxisRange(min, max);
562 560 validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData);
563 561 }
564 562
565 563 void TestDataSeries::testValuesBoundsScalar_data()
566 564 {
567 565 testValuesBoundsStructure<ScalarSeries>();
568 566
569 567 // ////////// //
570 568 // Test cases //
571 569 // ////////// //
572 570 auto nan = std::numeric_limits<double>::quiet_NaN();
573 571
574 572 QTest::newRow("scalarBounds1")
575 573 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6.
576 574 << true << 100. << 500.;
577 575 QTest::newRow("scalarBounds2")
578 576 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4.
579 577 << true << 200. << 400.;
580 578 QTest::newRow("scalarBounds3")
581 579 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5
582 580 << false << nan << nan;
583 581 QTest::newRow("scalarBounds4")
584 582 << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6.
585 583 << false << nan << nan;
586 584 QTest::newRow("scalarBounds5") << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100.
587 585 << 100.;
588 586 QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan;
589 587
590 588 // Tests with NaN values: NaN values are not included in min/max search
591 589 QTest::newRow("scalarBounds7")
592 590 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6.
593 591 << true << 200. << 400.;
594 592 QTest::newRow("scalarBounds8")
595 593 << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true
596 594 << std::numeric_limits<double>::quiet_NaN() << std::numeric_limits<double>::quiet_NaN();
597 595 }
598 596
599 597 void TestDataSeries::testValuesBoundsScalar()
600 598 {
601 599 testValuesBounds<ScalarSeries>();
602 600 }
603 601
604 602 void TestDataSeries::testValuesBoundsVector_data()
605 603 {
606 604 testValuesBoundsStructure<VectorSeries>();
607 605
608 606 // ////////// //
609 607 // Test cases //
610 608 // ////////// //
611 609 auto nan = std::numeric_limits<double>::quiet_NaN();
612 610
613 611 QTest::newRow("vectorBounds1")
614 612 << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.},
615 613 {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.})
616 614 << 0. << 6. << true << 0.3 << 35.; // min/max in same component
617 615 QTest::newRow("vectorBounds2")
618 616 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
619 617 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
620 618 << 0. << 6. << true << 2.3 << 35.; // min/max in same entry
621 619 QTest::newRow("vectorBounds3")
622 620 << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.},
623 621 {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.})
624 622 << 2. << 3. << true << 10. << 24.;
625 623
626 624 // Tests with NaN values: NaN values are not included in min/max search
627 625 QTest::newRow("vectorBounds4")
628 626 << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true
629 627 << nan << nan;
630 628 }
631 629
632 630 void TestDataSeries::testValuesBoundsVector()
633 631 {
634 632 testValuesBounds<VectorSeries>();
635 633 }
636 634
637 635 QTEST_MAIN(TestDataSeries)
638 636 #include "TestDataSeries.moc"
@@ -1,181 +1,181
1 1 #include "Data/ArrayData.h"
2 2 #include <QObject>
3 3 #include <QtTest>
4 4
5 5 namespace {
6 6
7 void verifyArrayData(const ArrayData<1> &arrayData, const QVector<double> &expectedData)
7 using DataContainer = std::vector<double>;
8
9 void verifyArrayData(const ArrayData<1> &arrayData, const DataContainer &expectedData)
8 10 {
9 11 QVERIFY(std::equal(
10 12 arrayData.cbegin(), arrayData.cend(), expectedData.cbegin(), expectedData.cend(),
11 13 [](const auto &it, const auto &expectedData) { return it.at(0) == expectedData; }));
12 14 }
13 15
14 16 } // namespace
15 17
16 18 class TestOneDimArrayData : public QObject {
17 19 Q_OBJECT
18 20 private slots:
19 21 /// Tests @sa ArrayData::data()
20 22 void testData_data();
21 23 void testData();
22 24
23 25 /// Tests @sa ArrayData::add()
24 26 void testAdd_data();
25 27 void testAdd();
26 28
27 29 /// Tests @sa ArrayData::at(int index)
28 30 void testAt_data();
29 31 void testAt();
30 32
31 33 /// Tests @sa ArrayData::clear()
32 34 void testClear_data();
33 35 void testClear();
34 36
35 37 /// Tests @sa ArrayData::size()
36 38 void testSize_data();
37 39 void testSize();
38 40
39 41 /// Tests @sa ArrayData::sort()
40 42 void testSort_data();
41 43 void testSort();
42 44 };
43 45
44 46 void TestOneDimArrayData::testData_data()
45 47 {
46 48 // Test structure
47 QTest::addColumn<QVector<double> >("inputData"); // array's data input
48 QTest::addColumn<QVector<double> >("expectedData"); // expected data
49 QTest::addColumn<DataContainer>("inputData"); // array's data input
50 QTest::addColumn<DataContainer>("expectedData"); // expected data
49 51
50 52 // Test cases
51 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.}
52 << QVector<double>{1., 2., 3., 4., 5.};
53 QTest::newRow("data1") << DataContainer{1., 2., 3., 4., 5.}
54 << DataContainer{1., 2., 3., 4., 5.};
53 55 }
54 56
55 57 void TestOneDimArrayData::testData()
56 58 {
57 QFETCH(QVector<double>, inputData);
58 QFETCH(QVector<double>, expectedData);
59 QFETCH(DataContainer, inputData);
60 QFETCH(DataContainer, expectedData);
59 61
60 62 ArrayData<1> arrayData{inputData};
61 63 verifyArrayData(arrayData, expectedData);
62 64 }
63 65
64 66 void TestOneDimArrayData::testAdd_data()
65 67 {
66 68 // Test structure
67 QTest::addColumn<QVector<double> >("inputData"); // array's data input
68 QTest::addColumn<QVector<double> >("otherData"); // array data's input to merge with
69 QTest::addColumn<bool>("prepend"); // prepend or append merge
70 QTest::addColumn<QVector<double> >("expectedData"); // expected data after merge
69 QTest::addColumn<DataContainer>("inputData"); // array's data input
70 QTest::addColumn<DataContainer>("otherData"); // array data's input to merge with
71 QTest::addColumn<bool>("prepend"); // prepend or append merge
72 QTest::addColumn<DataContainer>("expectedData"); // expected data after merge
71 73
72 74 // Test cases
73 QTest::newRow("appendMerge") << QVector<double>{1., 2., 3., 4., 5.}
74 << QVector<double>{6., 7., 8.} << false
75 << QVector<double>{1., 2., 3., 4., 5., 6., 7., 8.};
76 QTest::newRow("prependMerge") << QVector<double>{1., 2., 3., 4., 5.}
77 << QVector<double>{6., 7., 8.} << true
78 << QVector<double>{6., 7., 8., 1., 2., 3., 4., 5.};
75 QTest::newRow("appendMerge") << DataContainer{1., 2., 3., 4., 5.} << DataContainer{6., 7., 8.}
76 << false << DataContainer{1., 2., 3., 4., 5., 6., 7., 8.};
77 QTest::newRow("prependMerge") << DataContainer{1., 2., 3., 4., 5.} << DataContainer{6., 7., 8.}
78 << true << DataContainer{6., 7., 8., 1., 2., 3., 4., 5.};
79 79 }
80 80
81 81 void TestOneDimArrayData::testAdd()
82 82 {
83 QFETCH(QVector<double>, inputData);
84 QFETCH(QVector<double>, otherData);
83 QFETCH(DataContainer, inputData);
84 QFETCH(DataContainer, otherData);
85 85 QFETCH(bool, prepend);
86 QFETCH(QVector<double>, expectedData);
86 QFETCH(DataContainer, expectedData);
87 87
88 88 ArrayData<1> arrayData{inputData};
89 89 ArrayData<1> other{otherData};
90 90
91 91 arrayData.add(other, prepend);
92 92 verifyArrayData(arrayData, expectedData);
93 93 }
94 94
95 95 void TestOneDimArrayData::testAt_data()
96 96 {
97 97 // Test structure
98 QTest::addColumn<QVector<double> >("inputData"); // array data's input
99 QTest::addColumn<int>("index"); // index to retrieve data
100 QTest::addColumn<double>("expectedData"); // expected data at index
98 QTest::addColumn<DataContainer>("inputData"); // array data's input
99 QTest::addColumn<int>("index"); // index to retrieve data
100 QTest::addColumn<double>("expectedData"); // expected data at index
101 101
102 102 // Test cases
103 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << 0 << 1.;
104 QTest::newRow("data2") << QVector<double>{1., 2., 3., 4., 5.} << 3 << 4.;
103 QTest::newRow("data1") << DataContainer{1., 2., 3., 4., 5.} << 0 << 1.;
104 QTest::newRow("data2") << DataContainer{1., 2., 3., 4., 5.} << 3 << 4.;
105 105 }
106 106
107 107 void TestOneDimArrayData::testAt()
108 108 {
109 QFETCH(QVector<double>, inputData);
109 QFETCH(DataContainer, inputData);
110 110 QFETCH(int, index);
111 111 QFETCH(double, expectedData);
112 112
113 113 ArrayData<1> arrayData{inputData};
114 114 QVERIFY(arrayData.at(index) == expectedData);
115 115 }
116 116
117 117 void TestOneDimArrayData::testClear_data()
118 118 {
119 119 // Test structure
120 QTest::addColumn<QVector<double> >("inputData"); // array data's input
120 QTest::addColumn<DataContainer>("inputData"); // array data's input
121 121
122 122 // Test cases
123 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.};
123 QTest::newRow("data1") << DataContainer{1., 2., 3., 4., 5.};
124 124 }
125 125
126 126 void TestOneDimArrayData::testClear()
127 127 {
128 QFETCH(QVector<double>, inputData);
128 QFETCH(DataContainer, inputData);
129 129
130 130 ArrayData<1> arrayData{inputData};
131 131 arrayData.clear();
132 verifyArrayData(arrayData, QVector<double>{});
132 verifyArrayData(arrayData, DataContainer{});
133 133 }
134 134
135 135 void TestOneDimArrayData::testSize_data()
136 136 {
137 137 // Test structure
138 QTest::addColumn<QVector<double> >("inputData"); // array data's input
139 QTest::addColumn<int>("expectedSize"); // expected array data size
138 QTest::addColumn<DataContainer>("inputData"); // array data's input
139 QTest::addColumn<int>("expectedSize"); // expected array data size
140 140
141 141 // Test cases
142 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << 5;
142 QTest::newRow("data1") << DataContainer{1., 2., 3., 4., 5.} << 5;
143 143 }
144 144
145 145 void TestOneDimArrayData::testSize()
146 146 {
147 QFETCH(QVector<double>, inputData);
147 QFETCH(DataContainer, inputData);
148 148 QFETCH(int, expectedSize);
149 149
150 150 ArrayData<1> arrayData{inputData};
151 151 QVERIFY(arrayData.size() == expectedSize);
152 152 }
153 153
154 154 void TestOneDimArrayData::testSort_data()
155 155 {
156 156 // Test structure
157 QTest::addColumn<QVector<double> >("inputData"); // array data's input
157 QTest::addColumn<DataContainer>("inputData"); // array data's input
158 158 QTest::addColumn<std::vector<int> >("sortPermutation"); // permutation used to sort data
159 QTest::addColumn<QVector<double> >("expectedData"); // expected data after sorting
159 QTest::addColumn<DataContainer>("expectedData"); // expected data after sorting
160 160
161 161 // Test cases
162 QTest::newRow("data1") << QVector<double>{1., 2., 3., 4., 5.} << std::vector<int>{0, 2, 3, 1, 4}
163 << QVector<double>{1., 3., 4., 2., 5.};
164 QTest::newRow("data2") << QVector<double>{1., 2., 3., 4., 5.} << std::vector<int>{4, 1, 2, 3, 0}
165 << QVector<double>{5., 2., 3., 4., 1.};
162 QTest::newRow("data1") << DataContainer{1., 2., 3., 4., 5.} << std::vector<int>{0, 2, 3, 1, 4}
163 << DataContainer{1., 3., 4., 2., 5.};
164 QTest::newRow("data2") << DataContainer{1., 2., 3., 4., 5.} << std::vector<int>{4, 1, 2, 3, 0}
165 << DataContainer{5., 2., 3., 4., 1.};
166 166 }
167 167
168 168 void TestOneDimArrayData::testSort()
169 169 {
170 QFETCH(QVector<double>, inputData);
170 QFETCH(DataContainer, inputData);
171 171 QFETCH(std::vector<int>, sortPermutation);
172 QFETCH(QVector<double>, expectedData);
172 QFETCH(DataContainer, expectedData);
173 173
174 174 ArrayData<1> arrayData{inputData};
175 175 auto sortedArrayData = arrayData.sort(sortPermutation);
176 176 QVERIFY(sortedArrayData != nullptr);
177 177 verifyArrayData(*sortedArrayData, expectedData);
178 178 }
179 179
180 180 QTEST_MAIN(TestOneDimArrayData)
181 181 #include "TestOneDimArrayData.moc"
@@ -1,239 +1,240
1 1 #include "Data/ArrayData.h"
2 2 #include <QObject>
3 3 #include <QtTest>
4 4
5 using Container = QVector<QVector<double> >;
6 using InputData = QPair<QVector<double>, int>;
5 using DataContainer = std::vector<double>;
6 using Container = std::vector<DataContainer>;
7 using InputData = QPair<DataContainer, int>;
7 8
8 9 namespace {
9 10
10 11 InputData flatten(const Container &container)
11 12 {
12 if (container.isEmpty()) {
13 if (container.empty()) {
13 14 return {};
14 15 }
15 16
16 17 // We assume here that each component of the container have the same size
17 18 auto containerSize = container.size();
18 auto componentSize = container.first().size();
19 auto componentSize = container.front().size();
19 20
20 auto result = QVector<double>{};
21 auto result = DataContainer{};
21 22 result.reserve(componentSize * containerSize);
22 23
23 24 for (auto i = 0; i < componentSize; ++i) {
24 25 for (auto j = 0; j < containerSize; ++j) {
25 result.append(container.at(j).at(i));
26 result.push_back(container.at(j).at(i));
26 27 }
27 28 }
28 29
29 return {result, containerSize};
30 return {result, static_cast<int>(containerSize)};
30 31 }
31 32
32 33 void verifyArrayData(const ArrayData<2> &arrayData, const Container &expectedData)
33 34 {
34 35 auto verifyComponent = [&arrayData](const auto &componentData, const auto &equalFun) {
35 36 QVERIFY(std::equal(arrayData.cbegin(), arrayData.cend(), componentData.cbegin(),
36 37 componentData.cend(),
37 38 [&equalFun](const auto &dataSeriesIt, const auto &expectedValue) {
38 39 return equalFun(dataSeriesIt, expectedValue);
39 40 }));
40 41 };
41 42
42 43 for (auto i = 0; i < expectedData.size(); ++i) {
43 44 verifyComponent(expectedData.at(i), [i](const auto &seriesIt, const auto &value) {
44 45 return seriesIt.at(i) == value;
45 46 });
46 47 }
47 48 }
48 49
49 50 } // namespace
50 51
51 52 class TestTwoDimArrayData : public QObject {
52 53 Q_OBJECT
53 54 private slots:
54 55 /// Tests @sa ArrayData ctor
55 56 void testCtor_data();
56 57 void testCtor();
57 58
58 59 /// Tests @sa ArrayData::add()
59 60 void testAdd_data();
60 61 void testAdd();
61 62
62 63 /// Tests @sa ArrayData::clear()
63 64 void testClear_data();
64 65 void testClear();
65 66
66 67 /// Tests @sa ArrayData::size()
67 68 void testSize_data();
68 69 void testSize();
69 70
70 71 /// Tests @sa ArrayData::sort()
71 72 void testSort_data();
72 73 void testSort();
73 74 };
74 75
75 76 void TestTwoDimArrayData::testCtor_data()
76 77 {
77 78 // Test structure
78 79 QTest::addColumn<InputData>("inputData"); // array data's input
79 80 QTest::addColumn<bool>("success"); // array data has been successfully constructed
80 81 QTest::addColumn<Container>("expectedData"); // expected array data (when success)
81 82
82 83 // Test cases
83 84 QTest::newRow("validInput") << flatten(Container{{1., 2., 3., 4., 5.},
84 85 {6., 7., 8., 9., 10.},
85 86 {11., 12., 13., 14., 15.}})
86 87 << true << Container{{1., 2., 3., 4., 5.},
87 88 {6., 7., 8., 9., 10.},
88 89 {11., 12., 13., 14., 15.}};
89 90 QTest::newRow("invalidInput (invalid data size")
90 91 << InputData{{1., 2., 3., 4., 5., 6., 7.}, 3} << false << Container{{}, {}, {}};
91 92 QTest::newRow("invalidInput (less than two components")
92 93 << flatten(Container{{1., 2., 3., 4., 5.}}) << false << Container{{}, {}, {}};
93 94 }
94 95
95 96 void TestTwoDimArrayData::testCtor()
96 97 {
97 98 QFETCH(InputData, inputData);
98 99 QFETCH(bool, success);
99 100
100 101 if (success) {
101 102 QFETCH(Container, expectedData);
102 103
103 104 ArrayData<2> arrayData{inputData.first, inputData.second};
104 105 verifyArrayData(arrayData, expectedData);
105 106 }
106 107 else {
107 108 QVERIFY_EXCEPTION_THROWN(ArrayData<2>(inputData.first, inputData.second),
108 109 std::invalid_argument);
109 110 }
110 111 }
111 112
112 113 void TestTwoDimArrayData::testAdd_data()
113 114 {
114 115 // Test structure
115 116 QTest::addColumn<InputData>("inputData"); // array's data input
116 117 QTest::addColumn<InputData>("otherData"); // array data's input to merge with
117 118 QTest::addColumn<bool>("prepend"); // prepend or append merge
118 119 QTest::addColumn<Container>("expectedData"); // expected data after merge
119 120
120 121 // Test cases
121 122 auto inputData = flatten(
122 123 Container{{1., 2., 3., 4., 5.}, {11., 12., 13., 14., 15.}, {21., 22., 23., 24., 25.}});
123 124
124 125 auto vectorContainer = flatten(Container{{6., 7., 8.}, {16., 17., 18.}, {26., 27., 28}});
125 126 auto tensorContainer = flatten(Container{{6., 7., 8.},
126 127 {16., 17., 18.},
127 128 {26., 27., 28},
128 129 {36., 37., 38.},
129 130 {46., 47., 48.},
130 131 {56., 57., 58}});
131 132
132 133 QTest::newRow("appendMerge") << inputData << vectorContainer << false
133 134 << Container{{1., 2., 3., 4., 5., 6., 7., 8.},
134 135 {11., 12., 13., 14., 15., 16., 17., 18.},
135 136 {21., 22., 23., 24., 25., 26., 27., 28}};
136 137 QTest::newRow("prependMerge") << inputData << vectorContainer << true
137 138 << Container{{6., 7., 8., 1., 2., 3., 4., 5.},
138 139 {16., 17., 18., 11., 12., 13., 14., 15.},
139 140 {26., 27., 28, 21., 22., 23., 24., 25.}};
140 141 QTest::newRow("invalidMerge") << inputData << tensorContainer << false
141 142 << Container{{1., 2., 3., 4., 5.},
142 143 {11., 12., 13., 14., 15.},
143 144 {21., 22., 23., 24., 25.}};
144 145 }
145 146
146 147 void TestTwoDimArrayData::testAdd()
147 148 {
148 149 QFETCH(InputData, inputData);
149 150 QFETCH(InputData, otherData);
150 151 QFETCH(bool, prepend);
151 152 QFETCH(Container, expectedData);
152 153
153 154 ArrayData<2> arrayData{inputData.first, inputData.second};
154 155 ArrayData<2> other{otherData.first, otherData.second};
155 156
156 157 arrayData.add(other, prepend);
157 158
158 159 verifyArrayData(arrayData, expectedData);
159 160 }
160 161
161 162 void TestTwoDimArrayData::testClear_data()
162 163 {
163 164 // Test structure
164 165 QTest::addColumn<InputData>("inputData"); // array data's input
165 166
166 167 // Test cases
167 168 QTest::newRow("data1") << flatten(
168 169 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}});
169 170 }
170 171
171 172 void TestTwoDimArrayData::testClear()
172 173 {
173 174 QFETCH(InputData, inputData);
174 175
175 176 ArrayData<2> arrayData{inputData.first, inputData.second};
176 177 arrayData.clear();
177 178
178 auto emptyData = Container(inputData.second, QVector<double>{});
179 auto emptyData = Container(inputData.second, DataContainer{});
179 180 verifyArrayData(arrayData, emptyData);
180 181 }
181 182
182 183 void TestTwoDimArrayData::testSize_data()
183 184 {
184 185 // Test structure
185 186 QTest::addColumn<InputData>("inputData"); // array data's input
186 187 QTest::addColumn<int>("expectedSize"); // expected array data size
187 188
188 189 // Test cases
189 190 QTest::newRow("data1") << flatten(Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}}) << 5;
190 191 QTest::newRow("data2") << flatten(Container{{1., 2., 3., 4., 5.},
191 192 {6., 7., 8., 9., 10.},
192 193 {11., 12., 13., 14., 15.}})
193 194 << 5;
194 195 }
195 196
196 197 void TestTwoDimArrayData::testSize()
197 198 {
198 199 QFETCH(InputData, inputData);
199 200 QFETCH(int, expectedSize);
200 201
201 202 ArrayData<2> arrayData{inputData.first, inputData.second};
202 203 QVERIFY(arrayData.size() == expectedSize);
203 204 }
204 205
205 206 void TestTwoDimArrayData::testSort_data()
206 207 {
207 208 // Test structure
208 209 QTest::addColumn<InputData>("inputData"); // array data's input
209 210 QTest::addColumn<std::vector<int> >("sortPermutation"); // permutation used to sort data
210 211 QTest::addColumn<Container>("expectedData"); // expected data after sorting
211 212
212 213 // Test cases
213 214 QTest::newRow("data1")
214 215 << flatten(
215 216 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}})
216 217 << std::vector<int>{0, 2, 3, 1, 4}
217 218 << Container{{1., 3., 4., 2., 5.}, {6., 8., 9., 7., 10.}, {11., 13., 14., 12., 15.}};
218 219 QTest::newRow("data2")
219 220 << flatten(
220 221 Container{{1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, {11., 12., 13., 14., 15.}})
221 222 << std::vector<int>{2, 4, 3, 0, 1}
222 223 << Container{{3., 5., 4., 1., 2.}, {8., 10., 9., 6., 7.}, {13., 15., 14., 11., 12.}};
223 224 }
224 225
225 226 void TestTwoDimArrayData::testSort()
226 227 {
227 228 QFETCH(InputData, inputData);
228 229 QFETCH(std::vector<int>, sortPermutation);
229 230 QFETCH(Container, expectedData);
230 231
231 232 ArrayData<2> arrayData{inputData.first, inputData.second};
232 233 auto sortedArrayData = arrayData.sort(sortPermutation);
233 234 QVERIFY(sortedArrayData != nullptr);
234 235
235 236 verifyArrayData(*sortedArrayData, expectedData);
236 237 }
237 238
238 239 QTEST_MAIN(TestTwoDimArrayData)
239 240 #include "TestTwoDimArrayData.moc"
@@ -1,216 +1,217
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4 #include <Data/ScalarSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <QDateTime>
8 8 #include <QFile>
9 9 #include <QRegularExpression>
10 10
11 11 #include <cmath>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
14 14
15 15 namespace {
16 16
17 17 /// Message in result file when the file was not found on server
18 18 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
19 19
20 20 /// Format for dates in result files
21 21 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
22 22
23 23 /// Separator between values in a result line
24 24 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
25 25
26 26 /// Regex to find unit in a line. Examples of valid lines:
27 27 /// ... - Units : nT - ...
28 28 /// ... -Units:nT- ...
29 29 /// ... -Units: mΒ²- ...
30 30 /// ... - Units : m/s - ...
31 31 const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")};
32 32
33 33 /// Converts a string date to a double date
34 34 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
35 35 double doubleDate(const QString &stringDate) noexcept
36 36 {
37 37 auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT);
38 38 dateTime.setTimeSpec(Qt::UTC);
39 39 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
40 40 : std::numeric_limits<double>::quiet_NaN();
41 41 }
42 42
43 43 /// Checks if a line is a comment line
44 44 bool isCommentLine(const QString &line)
45 45 {
46 46 return line.startsWith("#");
47 47 }
48 48
49 49 /// @return the number of lines to be read depending on the type of value passed in parameter
50 50 int nbValues(AmdaResultParser::ValueType valueType) noexcept
51 51 {
52 52 switch (valueType) {
53 53 case AmdaResultParser::ValueType::SCALAR:
54 54 return 1;
55 55 case AmdaResultParser::ValueType::VECTOR:
56 56 return 3;
57 57 case AmdaResultParser::ValueType::UNKNOWN:
58 58 // Invalid case
59 59 break;
60 60 }
61 61
62 62 // Invalid cases
63 63 qCCritical(LOG_AmdaResultParser())
64 64 << QObject::tr("Can't get the number of values to read: unsupported type");
65 65 return 0;
66 66 }
67 67
68 68 /**
69 69 * Reads stream to retrieve x-axis unit
70 70 * @param stream the stream to read
71 71 * @return the unit that has been read in the stream, a default unit (time unit with no label) if an
72 72 * error occured during reading
73 73 */
74 74 Unit readXAxisUnit(QTextStream &stream)
75 75 {
76 76 QString line{};
77 77
78 78 // Searches unit in the comment lines
79 79 while (stream.readLineInto(&line) && isCommentLine(line)) {
80 80 auto match = UNIT_REGEX.match(line);
81 81 if (match.hasMatch()) {
82 82 return Unit{match.captured(1), true};
83 83 }
84 84 }
85 85
86 86 qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file");
87 87
88 88 // Error cases
89 89 return Unit{{}, true};
90 90 }
91 91
92 92 /**
93 93 * Reads stream to retrieve results
94 94 * @param stream the stream to read
95 95 * @return the pair of vectors x-axis data/values data that has been read in the stream
96 96 */
97 97 QPair<QVector<double>, QVector<QVector<double> > >
98 98 readResults(QTextStream &stream, AmdaResultParser::ValueType valueType)
99 99 {
100 100 auto expectedNbValues = nbValues(valueType);
101 101
102 102 auto xData = QVector<double>{};
103 103 auto valuesData = QVector<QVector<double> >(expectedNbValues);
104 104
105 105 QString line{};
106 106
107 107 while (stream.readLineInto(&line)) {
108 108 // Ignore comment lines
109 109 if (!isCommentLine(line)) {
110 110 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
111 111 if (lineData.size() == expectedNbValues + 1) {
112 112 // X : the data is converted from date to double (in secs)
113 113 auto x = doubleDate(lineData.at(0));
114 114
115 115 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
116 116 if (!std::isnan(x)) {
117 117 xData.push_back(x);
118 118
119 119 // Values
120 120 for (auto valueIndex = 0; valueIndex < expectedNbValues; ++valueIndex) {
121 121 auto column = valueIndex + 1;
122 122
123 123 bool valueOk;
124 124 auto value = lineData.at(column).toDouble(&valueOk);
125 125
126 126 if (!valueOk) {
127 127 qCWarning(LOG_AmdaResultParser())
128 128 << QObject::tr(
129 129 "Value from (line %1, column %2) is invalid and will be "
130 130 "converted to NaN")
131 131 .arg(line, column);
132 132 value = std::numeric_limits<double>::quiet_NaN();
133 133 }
134 134 valuesData[valueIndex].append(value);
135 135 }
136 136 }
137 137 else {
138 138 qCWarning(LOG_AmdaResultParser())
139 139 << QObject::tr("Can't retrieve results from line %1: x is invalid")
140 140 .arg(line);
141 141 }
142 142 }
143 143 else {
144 144 qCWarning(LOG_AmdaResultParser())
145 145 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
146 146 }
147 147 }
148 148 }
149 149
150 150 return qMakePair(std::move(xData), std::move(valuesData));
151 151 }
152 152
153 153 } // namespace
154 154
155 155 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath,
156 156 ValueType valueType) noexcept
157 157 {
158 158 if (valueType == ValueType::UNKNOWN) {
159 159 qCCritical(LOG_AmdaResultParser())
160 160 << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown");
161 161 return nullptr;
162 162 }
163 163
164 164 QFile file{filePath};
165 165
166 166 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
167 167 qCCritical(LOG_AmdaResultParser())
168 168 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
169 169 .arg(filePath, file.errorString());
170 170 return nullptr;
171 171 }
172 172
173 173 QTextStream stream{&file};
174 174
175 175 // Checks if the file was found on the server
176 176 auto firstLine = stream.readLine();
177 177 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
178 178 qCCritical(LOG_AmdaResultParser())
179 179 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
180 180 .arg(filePath);
181 181 return nullptr;
182 182 }
183 183
184 184 // Reads x-axis unit
185 185 stream.seek(0); // returns to the beginning of the file
186 186 auto xAxisUnit = readXAxisUnit(stream);
187 187
188 188 // Reads results
189 189 stream.seek(0); // returns to the beginning of the file
190 190 auto results = readResults(stream, valueType);
191 191
192 192 // Creates data series
193 193 switch (valueType) {
194 194 case ValueType::SCALAR:
195 195 Q_ASSERT(results.second.size() == 1);
196 196 return std::make_shared<ScalarSeries>(
197 std::move(results.first), std::move(results.second.takeFirst()), xAxisUnit, Unit{});
197 std::move(results.first.toStdVector()),
198 std::move(results.second.takeFirst().toStdVector()), xAxisUnit, Unit{});
198 199 case ValueType::VECTOR: {
199 200 Q_ASSERT(results.second.size() == 3);
200 auto xValues = results.second.takeFirst();
201 auto yValues = results.second.takeFirst();
202 auto zValues = results.second.takeFirst();
203 return std::make_shared<VectorSeries>(std::move(results.first), std::move(xValues),
204 std::move(yValues), std::move(zValues), xAxisUnit,
205 Unit{});
201 auto xValues = results.second.takeFirst().toStdVector();
202 auto yValues = results.second.takeFirst().toStdVector();
203 auto zValues = results.second.takeFirst().toStdVector();
204 return std::make_shared<VectorSeries>(std::move(results.first.toStdVector()),
205 std::move(xValues), std::move(yValues),
206 std::move(zValues), xAxisUnit, Unit{});
206 207 }
207 208 case ValueType::UNKNOWN:
208 209 // Invalid case
209 210 break;
210 211 }
211 212
212 213 // Invalid cases
213 214 qCCritical(LOG_AmdaResultParser())
214 215 << QObject::tr("Can't create data series: unsupported value type");
215 216 return nullptr;
216 217 }
@@ -1,103 +1,103
1 1 #include "CosinusProvider.h"
2 2
3 3 #include <Data/DataProviderParameters.h>
4 4 #include <Data/ScalarSeries.h>
5 5
6 6 #include <cmath>
7 7
8 8 #include <QFuture>
9 9 #include <QThread>
10 10 #include <QtConcurrent/QtConcurrent>
11 11
12 12 Q_LOGGING_CATEGORY(LOG_CosinusProvider, "CosinusProvider")
13 13
14 14 std::shared_ptr<IDataSeries> CosinusProvider::retrieveData(QUuid acqIdentifier,
15 15 const SqpRange &dataRangeRequested)
16 16 {
17 17 // TODO: Add Mutex
18 18 auto dataIndex = 0;
19 19
20 20 // Gets the timerange from the parameters
21 double freq = 1.0;
21 double freq = 100.0;
22 22 double start = std::ceil(dataRangeRequested.m_TStart * freq); // 100 htz
23 23 double end = std::floor(dataRangeRequested.m_TEnd * freq); // 100 htz
24 24
25 25 // We assure that timerange is valid
26 26 if (end < start) {
27 27 std::swap(start, end);
28 28 }
29 29
30 30 // Generates scalar series containing cosinus values (one value per second)
31 31 auto dataCount = end - start;
32 32
33 auto xAxisData = QVector<double>{};
33 auto xAxisData = std::vector<double>{};
34 34 xAxisData.resize(dataCount);
35 35
36 auto valuesData = QVector<double>{};
36 auto valuesData = std::vector<double>{};
37 37 valuesData.resize(dataCount);
38 38
39 39 int progress = 0;
40 40 auto progressEnd = dataCount;
41 41 for (auto time = start; time < end; ++time, ++dataIndex) {
42 42 auto it = m_VariableToEnableProvider.find(acqIdentifier);
43 43 if (it != m_VariableToEnableProvider.end() && it.value()) {
44 44 const auto timeOnFreq = time / freq;
45 45
46 xAxisData.replace(dataIndex, timeOnFreq);
47 valuesData.replace(dataIndex, std::cos(timeOnFreq));
46 xAxisData[dataIndex] = timeOnFreq;
47 valuesData[dataIndex] = std::cos(timeOnFreq);
48 48
49 49 // progression
50 50 int currentProgress = (time - start) * 100.0 / progressEnd;
51 51 if (currentProgress != progress) {
52 52 progress = currentProgress;
53 53
54 54 emit dataProvidedProgress(acqIdentifier, progress);
55 55 }
56 56 }
57 57 else {
58 58 if (!it.value()) {
59 59 qCDebug(LOG_CosinusProvider())
60 60 << "CosinusProvider::retrieveData: ARRET De l'acquisition detectΓ©"
61 61 << end - time;
62 62 }
63 63 }
64 64 }
65 65 emit dataProvidedProgress(acqIdentifier, 0.0);
66 66
67 67 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
68 68 Unit{QStringLiteral("t"), true}, Unit{});
69 69 }
70 70
71 71 void CosinusProvider::requestDataLoading(QUuid acqIdentifier,
72 72 const DataProviderParameters &parameters)
73 73 {
74 74 // TODO: Add Mutex
75 75 m_VariableToEnableProvider[acqIdentifier] = true;
76 76 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::requestDataLoading"
77 77 << QThread::currentThread()->objectName();
78 78 // NOTE: Try to use multithread if possible
79 79 const auto times = parameters.m_Times;
80 80
81 81 for (const auto &dateTime : qAsConst(times)) {
82 82 if (m_VariableToEnableProvider[acqIdentifier]) {
83 83 auto scalarSeries = this->retrieveData(acqIdentifier, dateTime);
84 84 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::dataProvided";
85 85 emit dataProvided(acqIdentifier, scalarSeries, dateTime);
86 86 }
87 87 }
88 88 }
89 89
90 90 void CosinusProvider::requestDataAborting(QUuid acqIdentifier)
91 91 {
92 92 // TODO: Add Mutex
93 93 qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataAborting" << acqIdentifier
94 94 << QThread::currentThread()->objectName();
95 95 auto it = m_VariableToEnableProvider.find(acqIdentifier);
96 96 if (it != m_VariableToEnableProvider.end()) {
97 97 it.value() = false;
98 98 }
99 99 else {
100 100 qCWarning(LOG_CosinusProvider())
101 101 << tr("Aborting progression of inexistant identifier detected !!!");
102 102 }
103 103 }
General Comments 0
You need to be logged in to leave comments. Login now