17
17
#include < maths/CBasicStatisticsPersist.h>
18
18
#include < maths/CChecksum.h>
19
19
#include < maths/CPrior.h>
20
+ #include < maths/CPriorDetail.h>
20
21
#include < maths/CPriorStateSerialiser.h>
21
22
#include < maths/CRestoreParams.h>
22
23
#include < maths/CTimeSeriesModel.h>
@@ -41,7 +42,6 @@ using TDouble1Vec = core::CSmallVector<double, 1>;
41
42
using TDouble4Vec = core::CSmallVector<double , 4 >;
42
43
using TDouble4Vec1Vec = core::CSmallVector<TDouble4Vec, 1 >;
43
44
using TOptionalChangeDescription = CUnivariateTimeSeriesChangeDetector::TOptionalChangeDescription;
44
-
45
45
const std::string MINIMUM_TIME_TO_DETECT{" a" };
46
46
const std::string MAXIMUM_TIME_TO_DETECT{" b" };
47
47
const std::string MINIMUM_DELTA_BIC_TO_DETECT{" c" };
@@ -52,9 +52,12 @@ const std::string MIN_TIME_TAG{"g"};
52
52
const std::string MAX_TIME_TAG{" h" };
53
53
const std::string CHANGE_MODEL_TAG{" i" };
54
54
const std::string LOG_LIKELIHOOD_TAG{" j" };
55
- const std::string SHIFT_TAG{" k" };
56
- const std::string TREND_MODEL_TAG{" l" };
57
- const std::string RESIDUAL_MODEL_TAG{" m" };
55
+ const std::string EXPECTED_LOG_LIKELIHOOD_TAG{" k" };
56
+ const std::string SHIFT_TAG{" l" };
57
+ const std::string TREND_MODEL_TAG{" m" };
58
+ const std::string RESIDUAL_MODEL_TAG{" n" };
59
+ const std::size_t EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS{4u };
60
+ const double EXPECTED_EVIDENCE_THRESHOLD_MULTIPLIER{0.9 };
58
61
}
59
62
60
63
SChangeDescription::SChangeDescription (EDescription description,
@@ -160,12 +163,25 @@ TOptionalChangeDescription CUnivariateTimeSeriesChangeDetector::change()
160
163
161
164
double evidences[]{noChangeBic - candidates[0 ].first ,
162
165
noChangeBic - candidates[1 ].first };
163
- m_CurrentEvidenceOfChange = evidences[0 ];
164
- if ( evidences[0 ] > m_MinimumDeltaBicToDetect
165
- && evidences[0 ] > evidences[1 ] + m_MinimumDeltaBicToDetect / 2.0 )
166
+ double expectedEvidence{noChangeBic - (*candidates[0 ].second )->expectedBic ()};
167
+
168
+ double x[]{evidences[0 ] / m_MinimumDeltaBicToDetect,
169
+ 2.0 * (evidences[0 ] - evidences[1 ]) / m_MinimumDeltaBicToDetect,
170
+ evidences[0 ] / EXPECTED_EVIDENCE_THRESHOLD_MULTIPLIER / expectedEvidence,
171
+ static_cast <double >(m_TimeRange.range () - m_MinimumTimeToDetect)
172
+ / static_cast <double >(m_MaximumTimeToDetect - m_MinimumTimeToDetect)};
173
+ double p{ CTools::logisticFunction (x[0 ], 0.05 , 1.0 )
174
+ * CTools::logisticFunction (x[1 ], 0.1 , 1.0 )
175
+ * (x[2 ] < 0.0 ? 1.0 : CTools::logisticFunction (x[2 ], 0.2 , 1.0 ))
176
+ * (0.5 + CTools::logisticFunction (x[3 ], 0.2 , 0.5 ))};
177
+ LOG_TRACE (" p = " << p);
178
+
179
+ if (p > 0.0625 /* = std::pow(0.5, 4.0)*/ )
166
180
{
167
181
return (*candidates[0 ].second )->change ();
168
182
}
183
+
184
+ m_CurrentEvidenceOfChange = evidences[0 ];
169
185
}
170
186
return TOptionalChangeDescription ();
171
187
}
@@ -227,9 +243,34 @@ namespace time_series_change_detector_detail
227
243
228
244
CUnivariateChangeModel::CUnivariateChangeModel (const TDecompositionPtr &trendModel,
229
245
const TPriorPtr &residualModel) :
230
- m_LogLikelihood{0.0 }, m_TrendModel{trendModel}, m_ResidualModel{residualModel}
246
+ m_LogLikelihood{0.0 }, m_ExpectedLogLikelihood{0.0 },
247
+ m_TrendModel{trendModel}, m_ResidualModel{residualModel}
231
248
{}
232
249
250
+ bool CUnivariateChangeModel::acceptRestoreTraverser (const SModelRestoreParams &/* params*/ ,
251
+ core::CStateRestoreTraverser &traverser)
252
+ {
253
+ do
254
+ {
255
+ const std::string name{traverser.name ()};
256
+ RESTORE_BUILT_IN (LOG_LIKELIHOOD_TAG, m_LogLikelihood);
257
+ RESTORE_BUILT_IN (EXPECTED_LOG_LIKELIHOOD_TAG, m_ExpectedLogLikelihood);
258
+ return true ;
259
+ }
260
+ while (traverser.next ());
261
+ return true ;
262
+ }
263
+
264
+ void CUnivariateChangeModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
265
+ {
266
+ inserter.insertValue (LOG_LIKELIHOOD_TAG,
267
+ m_LogLikelihood,
268
+ core::CIEEE754::E_SinglePrecision);
269
+ inserter.insertValue (EXPECTED_LOG_LIKELIHOOD_TAG,
270
+ m_ExpectedLogLikelihood,
271
+ core::CIEEE754::E_SinglePrecision);
272
+ }
273
+
233
274
void CUnivariateChangeModel::debugMemoryUsage (core::CMemoryUsage::TMemoryUsagePtr mem) const
234
275
{
235
276
// Note if the trend and residual models are shallow copied their
@@ -249,6 +290,7 @@ std::size_t CUnivariateChangeModel::memoryUsage() const
249
290
uint64_t CUnivariateChangeModel::checksum (uint64_t seed) const
250
291
{
251
292
seed = CChecksum::calculate (seed, m_LogLikelihood);
293
+ seed = CChecksum::calculate (seed, m_ExpectedLogLikelihood);
252
294
seed = CChecksum::calculate (seed, m_TrendModel);
253
295
return CChecksum::calculate (seed, m_ResidualModel);
254
296
}
@@ -271,6 +313,16 @@ void CUnivariateChangeModel::addLogLikelihood(double logLikelihood)
271
313
m_LogLikelihood += logLikelihood;
272
314
}
273
315
316
+ double CUnivariateChangeModel::expectedLogLikelihood () const
317
+ {
318
+ return m_ExpectedLogLikelihood;
319
+ }
320
+
321
+ void CUnivariateChangeModel::addExpectedLogLikelihood (double logLikelihood)
322
+ {
323
+ m_ExpectedLogLikelihood += logLikelihood;
324
+ }
325
+
274
326
const CTimeSeriesDecompositionInterface &CUnivariateChangeModel::trendModel () const
275
327
{
276
328
return *m_TrendModel;
@@ -301,31 +353,29 @@ CUnivariateNoChangeModel::CUnivariateNoChangeModel(const TDecompositionPtr &tren
301
353
CUnivariateChangeModel{trendModel, residualModel}
302
354
{}
303
355
304
- bool CUnivariateNoChangeModel::acceptRestoreTraverser (const SModelRestoreParams &/* params*/ ,
356
+ bool CUnivariateNoChangeModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
305
357
core::CStateRestoreTraverser &traverser)
306
358
{
307
- do
308
- {
309
- const std::string name{traverser.name ()};
310
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
311
- double logLikelihood,
312
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
313
- this ->addLogLikelihood (logLikelihood))
314
- }
315
- while (traverser.next ());
316
- return true ;
359
+ return this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser);
317
360
}
318
361
319
362
void CUnivariateNoChangeModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
320
363
{
321
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
364
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
322
365
}
323
366
324
367
double CUnivariateNoChangeModel::bic () const
325
368
{
326
369
return -2.0 * this ->logLikelihood ();
327
370
}
328
371
372
+ double CUnivariateNoChangeModel::expectedBic () const
373
+ {
374
+ // This is irrelevant since this is only used for deciding
375
+ // whether to accept a change.
376
+ return this ->bic ();
377
+ }
378
+
329
379
TOptionalChangeDescription CUnivariateNoChangeModel::change () const
330
380
{
331
381
return TOptionalChangeDescription ();
@@ -348,7 +398,7 @@ void CUnivariateNoChangeModel::addSamples(std::size_t count,
348
398
samples.push_back (this ->trendModel ().detrend (sample.first , sample.second , 0.0 ));
349
399
}
350
400
351
- double logLikelihood;
401
+ double logLikelihood{ 0.0 } ;
352
402
if (this ->residualModel ().jointLogMarginalLikelihood (weightStyles, samples, weights,
353
403
logLikelihood) == maths_t ::E_FpNoErrors)
354
404
{
@@ -377,13 +427,13 @@ CUnivariateLevelShiftModel::CUnivariateLevelShiftModel(const TDecompositionPtr &
377
427
bool CUnivariateLevelShiftModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
378
428
core::CStateRestoreTraverser &traverser)
379
429
{
430
+ if (this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser) == false )
431
+ {
432
+ return false ;
433
+ }
380
434
do
381
435
{
382
436
const std::string name{traverser.name ()};
383
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
384
- double logLikelihood,
385
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
386
- this ->addLogLikelihood (logLikelihood))
387
437
RESTORE (SHIFT_TAG, m_Shift.fromDelimited (traverser.value ()))
388
438
RESTORE_BUILT_IN (RESIDUAL_MODEL_MODE_TAG, m_ResidualModelMode)
389
439
RESTORE_BUILT_IN (SAMPLE_COUNT_TAG, m_SampleCount)
@@ -395,7 +445,7 @@ bool CUnivariateLevelShiftModel::acceptRestoreTraverser(const SModelRestoreParam
395
445
396
446
void CUnivariateLevelShiftModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
397
447
{
398
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
448
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
399
449
inserter.insertValue (SHIFT_TAG, m_Shift.toDelimited ());
400
450
inserter.insertValue (SAMPLE_COUNT_TAG, m_SampleCount);
401
451
inserter.insertLevel (RESIDUAL_MODEL_TAG, boost::bind<void >(CPriorStateSerialiser (),
@@ -407,6 +457,11 @@ double CUnivariateLevelShiftModel::bic() const
407
457
return -2.0 * this ->logLikelihood () + std::log (m_SampleCount);
408
458
}
409
459
460
+ double CUnivariateLevelShiftModel::expectedBic () const
461
+ {
462
+ return -2.0 * this ->expectedLogLikelihood () + std::log (m_SampleCount);
463
+ }
464
+
410
465
TOptionalChangeDescription CUnivariateLevelShiftModel::change () const
411
466
{
412
467
// The "magic" 0.9 is due to the fact that the trend is updated
@@ -456,12 +511,24 @@ void CUnivariateLevelShiftModel::addSamples(std::size_t count,
456
511
residualModel.addSamples (weightStyles, samples, weights);
457
512
residualModel.propagateForwardsByTime (1.0 );
458
513
459
- double logLikelihood;
514
+ double logLikelihood{ 0.0 } ;
460
515
if (residualModel.jointLogMarginalLikelihood (weightStyles, samples, weights,
461
516
logLikelihood) == maths_t ::E_FpNoErrors)
462
517
{
463
518
this ->addLogLikelihood (logLikelihood);
464
519
}
520
+ for (const auto &weight : weights)
521
+ {
522
+ double expectedLogLikelihood{0.0 };
523
+ TDouble4Vec1Vec weight_{weight};
524
+ if (residualModel.expectation (maths::CPrior::CLogMarginalLikelihood{
525
+ residualModel, weightStyles, weight_},
526
+ EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS,
527
+ expectedLogLikelihood, weightStyles, weight))
528
+ {
529
+ this ->addExpectedLogLikelihood (expectedLogLikelihood);
530
+ }
531
+ }
465
532
}
466
533
}
467
534
@@ -487,13 +554,13 @@ CUnivariateTimeShiftModel::CUnivariateTimeShiftModel(const TDecompositionPtr &tr
487
554
bool CUnivariateTimeShiftModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
488
555
core::CStateRestoreTraverser &traverser)
489
556
{
557
+ if (this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser) == false )
558
+ {
559
+ return false ;
560
+ }
490
561
do
491
562
{
492
563
const std::string name{traverser.name ()};
493
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
494
- double logLikelihood,
495
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
496
- this ->addLogLikelihood (logLikelihood))
497
564
RESTORE (RESIDUAL_MODEL_TAG, this ->restoreResidualModel (params.s_DistributionParams , traverser))
498
565
}
499
566
while (traverser.next ());
@@ -502,7 +569,7 @@ bool CUnivariateTimeShiftModel::acceptRestoreTraverser(const SModelRestoreParams
502
569
503
570
void CUnivariateTimeShiftModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
504
571
{
505
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
572
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
506
573
inserter.insertLevel (RESIDUAL_MODEL_TAG, boost::bind<void >(CPriorStateSerialiser (),
507
574
boost::cref (this ->residualModel ()), _1));
508
575
}
@@ -512,6 +579,11 @@ double CUnivariateTimeShiftModel::bic() const
512
579
return -2.0 * this ->logLikelihood ();
513
580
}
514
581
582
+ double CUnivariateTimeShiftModel::expectedBic () const
583
+ {
584
+ return -2.0 * this ->expectedLogLikelihood ();
585
+ }
586
+
515
587
TOptionalChangeDescription CUnivariateTimeShiftModel::change () const
516
588
{
517
589
return SChangeDescription{SChangeDescription::E_TimeShift,
@@ -540,12 +612,22 @@ void CUnivariateTimeShiftModel::addSamples(std::size_t count,
540
612
residualModel.addSamples (weightStyles, samples, weights);
541
613
residualModel.propagateForwardsByTime (1.0 );
542
614
543
- double logLikelihood;
615
+ double logLikelihood{ 0.0 } ;
544
616
if (residualModel.jointLogMarginalLikelihood (weightStyles, samples, weights,
545
617
logLikelihood) == maths_t ::E_FpNoErrors)
546
618
{
547
619
this ->addLogLikelihood (logLikelihood);
548
620
}
621
+ for (const auto &weight : weights)
622
+ {
623
+ double expectedLogLikelihood{0.0 };
624
+ TDouble4Vec1Vec weight_{weight};
625
+ residualModel.expectation (maths::CPrior::CLogMarginalLikelihood{
626
+ residualModel, weightStyles, weight_},
627
+ EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS,
628
+ expectedLogLikelihood, weightStyles, weight);
629
+ this ->addExpectedLogLikelihood (expectedLogLikelihood);
630
+ }
549
631
}
550
632
}
551
633
0 commit comments