-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommon.h
454 lines (372 loc) · 12.7 KB
/
common.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#pragma once
#ifndef EL_COMMON_H_INCLUDED
# define EL_COMMON_H_INCLUDED
# include <QByteArray>
# include <QDateTime>
# include <QString>
# include <QVector>
# include <fmt/format.h>
# include <algorithm>
# include <optional>
# include <stdexcept>
# include <string>
# include <string_view>
QT_FORWARD_DECLARE_CLASS(QJsonObject)
template <>
struct fmt::formatter<QByteArray> : public fmt::formatter<std::string_view> {
template <typename ParseContext>
constexpr auto parse(ParseContext &ctx)
{
return fmt::formatter<std::string_view>::parse(ctx);
}
template <typename FormatContext>
auto format(QByteArray const &v, FormatContext &ctx) const
{
auto const sv = std::string_view{v.constData(), static_cast<size_t>(v.size())};
return fmt::formatter<std::string_view>::format(sv, ctx);
}
};
template <>
struct fmt::formatter<QString> : public fmt::formatter<QByteArray> {
template <typename ParseContext>
constexpr auto parse(ParseContext &ctx)
{
return fmt::formatter<QByteArray>::parse(ctx);
}
template <typename FormatContext>
auto format(QString const &v, FormatContext &ctx) const
{
return fmt::formatter<QByteArray>::format(v.toUtf8(), ctx);
}
};
template <>
struct fmt::formatter<QDateTime> : public fmt::formatter<QString> {
template <typename ParseContext>
constexpr auto parse(ParseContext &ctx)
{
return fmt::formatter<QString>::parse(ctx);
}
template <typename FormatContext>
auto format(QDateTime const &v, FormatContext &ctx) const
{
return fmt::formatter<QString>::format(v.toString("yyyy-MM-dd hh:mm"), ctx);
}
};
namespace El {
/// Number of seconds in an hour
constexpr int SECS_IN_HOUR = 3600;
/// Number of kWh in a MWh
constexpr double KWH_IN_MWH = 1000.0;
/// Exception
class Exception : public std::runtime_error {
public:
/// ctor
inline Exception(std::string const &msg)
: std::runtime_error(msg)
{}
template <typename... Args>
Exception(std::string const &fmt, Args const &...args)
: std::runtime_error(fmt::format(fmt, args...))
{}
};
/// Returns the number of hours since the EPOCH
/// @param[in] dt Date/time
/// @return Number of hours since the EPOCH
static inline auto to_hours(QDateTime const &dt) -> int
{
constexpr qint64 SECS_IN_MIN = 60;
constexpr qint64 MINS_IN_HOUR = 60;
return static_cast<int>(dt.toSecsSinceEpoch() / (SECS_IN_MIN * MINS_IN_HOUR));
}
/// Returns the date/time value from the number of hours since the EPOCH
/// @param[in] time_h Number of hours since the EPOCH
/// @return Date/time value
static inline auto to_datetime(int time_h) -> QDateTime
{
constexpr qint64 SECS_IN_MIN = 60;
constexpr qint64 MINS_IN_HOUR = 60;
return QDateTime::fromSecsSinceEpoch(static_cast<qint64>(time_h) * SECS_IN_MIN * MINS_IN_HOUR);
}
/// Nord Pool hourly price record
struct Price {
/// Constructs the Price record from a JSON object
/// @param[in] json JSON object with `timestamp` and `price` attributes
/// @return Price record
/// @throws Exception on errors
static auto from_json(QJsonObject const &json) -> Price;
/// Ctor
inline Price(int time_h_, double price_)
: time_h(time_h_)
, price(price_)
{}
/// Dtor
~Price() = default;
/// Time as hours since the EPOCH
int time_h = 0;
/// Price (EUR/MWh) without taxes
double price = 0.0;
};
/// Nord Pool hourly price block with start time and length in number of hours
struct PriceBlock {
/// Default ctr
PriceBlock() = default;
/// Dtir
~PriceBlock() = default;
/// Move and copy operations
PriceBlock(PriceBlock const &rhs) = default;
inline PriceBlock(PriceBlock &&rhs) noexcept
{
std::swap(start_time_h, rhs.start_time_h);
std::swap(prices, rhs.prices);
}
inline auto operator=(PriceBlock const &rhs) -> PriceBlock &
{
if (this != &rhs) {
start_time_h = rhs.start_time_h;
prices = rhs.prices;
}
return *this;
}
inline auto operator=(PriceBlock &&rhs) noexcept -> PriceBlock &
{
if (this != &rhs) {
start_time_h = rhs.start_time_h;
rhs.start_time_h = 0;
prices = std::move(rhs.prices);
}
return *this;
}
/// Size of the block in number of hours
auto size() const { return prices.size(); }
/// Returns true if the block is empty
auto empty() const { return prices.isEmpty(); }
/// Appends a price to the block
inline void append(Price const &price)
{
if (prices.isEmpty()) {
start_time_h = price.time_h;
}
prices.append(price.price);
}
/// Start time as hours since the EPOCH
int start_time_h = 0;
/// Hourly prices (EUR/MHh) without taxes
QVector<double> prices;
};
/// Start and end time pair
struct TimePair {
int start_h = 0; ///< Start time as number of hours since the EPOCH
int end_h = 0; ///< End time as number of hours since the EPOCH
};
/// Array of price blocks that is always sorted by the start time
class PriceBlocks {
public:
/// Ctor
PriceBlocks() = default;
/// Dtor
~PriceBlocks() = default;
/// Copy and move operations
PriceBlocks(PriceBlocks const &) = default;
PriceBlocks(PriceBlocks &&) noexcept = default;
auto operator=(PriceBlocks const &) -> PriceBlocks & = default;
auto operator=(PriceBlocks &&) noexcept -> PriceBlocks & = default;
/// Returns the number of price blocks
inline auto size() const { return _blocks.size(); }
/// Returns true if the array of price blocks is empty
inline auto empty() const { return _blocks.isEmpty(); }
/// Returns the array of price blocks
inline auto blocks() const noexcept -> auto const & { return _blocks; }
/// Returns the last price block in the array
inline auto last() -> auto & { return _blocks.back(); }
inline auto last() const -> auto const & { return _blocks.back(); }
/// Returns the start time (hours since the EPOCH)
inline auto start_time_h() const -> int
{
if (empty()) {
return 0;
}
return _blocks.first().start_time_h;
}
/// Returns the end time (hours since the EPOCH)
inline auto end_time_h() const -> int
{
if (empty()) {
return 0;
}
return _blocks.last().start_time_h + static_cast<int>(_blocks.last().size()) - 1;
}
/// Appends a price block
/// @param[in] block Price block being added
inline void append(PriceBlock const &block)
{
_blocks.append(block);
sort();
normalize();
}
/// Appends a price block
/// @param[in] block Price block being added
inline void append(PriceBlock &&block)
{
_blocks.append(std::forward<PriceBlock>(block));
sort();
normalize();
}
/// Appends prices from another prices array
/// @param[in] blocks Other prices array
inline void append(PriceBlocks const &blocks)
{
_blocks.append(blocks._blocks);
sort();
normalize();
}
/// Appends prices from another prices array
/// @param[in] blocks Other prices array
inline void append(PriceBlocks &&blocks)
{
_blocks.append(std::forward<QVector<PriceBlock>>(blocks._blocks));
sort();
normalize();
}
/// Checks for holes in price blocks
/// @return true if there are holes, otherwise false
inline auto has_holes() const -> bool
{
if (_blocks.isEmpty()) {
return false;
}
auto start = _blocks.first().start_time_h;
auto size = static_cast<int>(_blocks.first().size());
auto it = _blocks.cbegin();
for (++it; it != _blocks.cend(); ++it) {
if (it->start_time_h > (start + size)) {
// hole detected
return true;
}
start = it->start_time_h;
size = static_cast<int>(it->size());
}
// no holes detected
return false;
}
/// Returns missing price blocks information
/// @param[in] start_h Expected start time in hours since the EPOCH
/// @param[in] end_h Expected end time in hours since the EPOCH
/// @return Array of holes
inline auto get_missing_blocks(int start_h, int end_h) const -> QVector<TimePair>
{
if (empty()) {
return {
{start_h, end_h}
};
}
QVector<TimePair> result{};
// check for missing prices before the first block
if (start_h < start_time_h()) {
result.append({start_h, start_time_h() - 1});
}
// checks for holes between price blocks
auto start = _blocks.first().start_time_h;
auto size = static_cast<int>(_blocks.first().size());
auto it = _blocks.cbegin();
for (++it; it != _blocks.cend(); ++it) {
// check for the end time
if (end_h <= (start + size)) {
return result;
}
// check for missing data between price blocks
if (it->start_time_h > (start + size)) {
// hole detected
result.append({start + size, it->start_time_h - 1});
}
start = it->start_time_h;
size = static_cast<int>(it->size());
}
// check for missing prices after the last block
if (end_h > end_time_h()) {
result.append({end_time_h() + 1, end_h});
}
return result;
}
/// Checks for overlapping blocks
/// @param[in] block Block being tested
/// @return True if the block overlaps with existing blocks, false if not
inline auto is_overlapping(PriceBlock const &block) const -> bool
{
if (_blocks.isEmpty() || block.empty()) {
return false;
}
auto const start = block.start_time_h;
auto const end = start + static_cast<int>(block.size()) - 1;
if (std::any_of(_blocks.cbegin(), _blocks.cend(), [start, end](PriceBlock const &b) {
return (start >= b.start_time_h && start < (b.start_time_h + b.size())) ||
(end >= b.start_time_h && end < (b.start_time_h + b.size()));
})) {
return true;
};
// not overlapping
return false;
}
/// Returns price for the given time
/// @param[in] time_h Time value as hours since the EPOCH
/// @return Price as EUR/MWh when succeeded, otherwise an invalid optional
inline auto get_price(int time_h) const -> std::optional<double>
{
auto it = std::find_if(_blocks.cbegin(), _blocks.cend(), [time_h](PriceBlock const &b) {
return time_h >= b.start_time_h && time_h < (b.start_time_h + b.size());
});
if (it == _blocks.cend()) {
return {};
}
return it->prices[time_h - it->start_time_h];
}
private:
/// Array of price blocks
QVector<PriceBlock> _blocks;
/// Sorts the array of price blocks by the start time
inline void sort()
{
std::sort(_blocks.begin(), _blocks.end(), [](PriceBlock const &a, PriceBlock const &b) {
return a.start_time_h < b.start_time_h;
});
}
/// Normalizes the array by merging individual blocks without holes
/// The price blocks array MUST be sorted before calling this function
void normalize()
{
if (_blocks.isEmpty()) {
return;
}
QVector<PriceBlock> normalized;
for (auto const &b : _blocks) {
if (normalized.isEmpty()) {
normalized.append(b);
}
else {
if (b.start_time_h > (normalized.back().start_time_h + normalized.back().size())) {
// there is a hole
normalized.append(b);
}
else {
// block continues
normalized.back().prices.append(b.prices);
}
}
}
_blocks = std::move(normalized);
}
/// Finds a block that contains the given time (hours since the EPOCH)
/// @param[in] time_h Time to find
/// @return Pointer to the prices block or NULL if not found
inline auto find_block(int time_h) -> PriceBlock *
{
auto it = std::find_if(_blocks.begin(), _blocks.end(), [time_h](PriceBlock const &b) {
return time_h >= b.start_time_h && time_h < (b.start_time_h + b.size());
});
if (it != _blocks.end()) {
return &*it;
}
return nullptr;
}
};
} // namespace El
#endif