26
26
#include < maths/CBasicStatisticsPersist.h>
27
27
#include < maths/CChecksum.h>
28
28
#include < maths/CPrior.h>
29
+ #include < maths/CPriorDetail.h>
29
30
#include < maths/CPriorStateSerialiser.h>
30
31
#include < maths/CRestoreParams.h>
31
32
#include < maths/CTimeSeriesModel.h>
@@ -50,7 +51,6 @@ using TDouble1Vec = core::CSmallVector<double, 1>;
50
51
using TDouble4Vec = core::CSmallVector<double , 4 >;
51
52
using TDouble4Vec1Vec = core::CSmallVector<TDouble4Vec, 1 >;
52
53
using TOptionalChangeDescription = CUnivariateTimeSeriesChangeDetector::TOptionalChangeDescription;
53
-
54
54
const std::string MINIMUM_TIME_TO_DETECT{" a" };
55
55
const std::string MAXIMUM_TIME_TO_DETECT{" b" };
56
56
const std::string MINIMUM_DELTA_BIC_TO_DETECT{" c" };
@@ -61,9 +61,12 @@ const std::string MIN_TIME_TAG{"g"};
61
61
const std::string MAX_TIME_TAG{" h" };
62
62
const std::string CHANGE_MODEL_TAG{" i" };
63
63
const std::string LOG_LIKELIHOOD_TAG{" j" };
64
- const std::string SHIFT_TAG{" k" };
65
- const std::string TREND_MODEL_TAG{" l" };
66
- const std::string RESIDUAL_MODEL_TAG{" m" };
64
+ const std::string EXPECTED_LOG_LIKELIHOOD_TAG{" k" };
65
+ const std::string SHIFT_TAG{" l" };
66
+ const std::string TREND_MODEL_TAG{" m" };
67
+ const std::string RESIDUAL_MODEL_TAG{" n" };
68
+ const std::size_t EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS{4u };
69
+ const double EXPECTED_EVIDENCE_THRESHOLD_MULTIPLIER{0.9 };
67
70
}
68
71
69
72
SChangeDescription::SChangeDescription (EDescription description,
@@ -169,12 +172,25 @@ TOptionalChangeDescription CUnivariateTimeSeriesChangeDetector::change()
169
172
170
173
double evidences[]{noChangeBic - candidates[0 ].first ,
171
174
noChangeBic - candidates[1 ].first };
172
- m_CurrentEvidenceOfChange = evidences[0 ];
173
- if ( evidences[0 ] > m_MinimumDeltaBicToDetect
174
- && evidences[0 ] > evidences[1 ] + m_MinimumDeltaBicToDetect / 2.0 )
175
+ double expectedEvidence{noChangeBic - (*candidates[0 ].second )->expectedBic ()};
176
+
177
+ double x[]{evidences[0 ] / m_MinimumDeltaBicToDetect,
178
+ 2.0 * (evidences[0 ] - evidences[1 ]) / m_MinimumDeltaBicToDetect,
179
+ evidences[0 ] / EXPECTED_EVIDENCE_THRESHOLD_MULTIPLIER / expectedEvidence,
180
+ static_cast <double >(m_TimeRange.range () - m_MinimumTimeToDetect)
181
+ / static_cast <double >(m_MaximumTimeToDetect - m_MinimumTimeToDetect)};
182
+ double p{ CTools::logisticFunction (x[0 ], 0.05 , 1.0 )
183
+ * CTools::logisticFunction (x[1 ], 0.1 , 1.0 )
184
+ * (x[2 ] < 0.0 ? 1.0 : CTools::logisticFunction (x[2 ], 0.2 , 1.0 ))
185
+ * (0.5 + CTools::logisticFunction (x[3 ], 0.2 , 0.5 ))};
186
+ LOG_TRACE (" p = " << p);
187
+
188
+ if (p > 0.0625 /* = std::pow(0.5, 4.0)*/ )
175
189
{
176
190
return (*candidates[0 ].second )->change ();
177
191
}
192
+
193
+ m_CurrentEvidenceOfChange = evidences[0 ];
178
194
}
179
195
return TOptionalChangeDescription ();
180
196
}
@@ -236,9 +252,34 @@ namespace time_series_change_detector_detail
236
252
237
253
CUnivariateChangeModel::CUnivariateChangeModel (const TDecompositionPtr &trendModel,
238
254
const TPriorPtr &residualModel) :
239
- m_LogLikelihood{0.0 }, m_TrendModel{trendModel}, m_ResidualModel{residualModel}
255
+ m_LogLikelihood{0.0 }, m_ExpectedLogLikelihood{0.0 },
256
+ m_TrendModel{trendModel}, m_ResidualModel{residualModel}
240
257
{}
241
258
259
+ bool CUnivariateChangeModel::acceptRestoreTraverser (const SModelRestoreParams &/* params*/ ,
260
+ core::CStateRestoreTraverser &traverser)
261
+ {
262
+ do
263
+ {
264
+ const std::string name{traverser.name ()};
265
+ RESTORE_BUILT_IN (LOG_LIKELIHOOD_TAG, m_LogLikelihood);
266
+ RESTORE_BUILT_IN (EXPECTED_LOG_LIKELIHOOD_TAG, m_ExpectedLogLikelihood);
267
+ return true ;
268
+ }
269
+ while (traverser.next ());
270
+ return true ;
271
+ }
272
+
273
+ void CUnivariateChangeModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
274
+ {
275
+ inserter.insertValue (LOG_LIKELIHOOD_TAG,
276
+ m_LogLikelihood,
277
+ core::CIEEE754::E_SinglePrecision);
278
+ inserter.insertValue (EXPECTED_LOG_LIKELIHOOD_TAG,
279
+ m_ExpectedLogLikelihood,
280
+ core::CIEEE754::E_SinglePrecision);
281
+ }
282
+
242
283
void CUnivariateChangeModel::debugMemoryUsage (core::CMemoryUsage::TMemoryUsagePtr mem) const
243
284
{
244
285
// Note if the trend and residual models are shallow copied their
@@ -258,6 +299,7 @@ std::size_t CUnivariateChangeModel::memoryUsage() const
258
299
uint64_t CUnivariateChangeModel::checksum (uint64_t seed) const
259
300
{
260
301
seed = CChecksum::calculate (seed, m_LogLikelihood);
302
+ seed = CChecksum::calculate (seed, m_ExpectedLogLikelihood);
261
303
seed = CChecksum::calculate (seed, m_TrendModel);
262
304
return CChecksum::calculate (seed, m_ResidualModel);
263
305
}
@@ -280,6 +322,16 @@ void CUnivariateChangeModel::addLogLikelihood(double logLikelihood)
280
322
m_LogLikelihood += logLikelihood;
281
323
}
282
324
325
+ double CUnivariateChangeModel::expectedLogLikelihood () const
326
+ {
327
+ return m_ExpectedLogLikelihood;
328
+ }
329
+
330
+ void CUnivariateChangeModel::addExpectedLogLikelihood (double logLikelihood)
331
+ {
332
+ m_ExpectedLogLikelihood += logLikelihood;
333
+ }
334
+
283
335
const CTimeSeriesDecompositionInterface &CUnivariateChangeModel::trendModel () const
284
336
{
285
337
return *m_TrendModel;
@@ -310,31 +362,29 @@ CUnivariateNoChangeModel::CUnivariateNoChangeModel(const TDecompositionPtr &tren
310
362
CUnivariateChangeModel{trendModel, residualModel}
311
363
{}
312
364
313
- bool CUnivariateNoChangeModel::acceptRestoreTraverser (const SModelRestoreParams &/* params*/ ,
365
+ bool CUnivariateNoChangeModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
314
366
core::CStateRestoreTraverser &traverser)
315
367
{
316
- do
317
- {
318
- const std::string name{traverser.name ()};
319
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
320
- double logLikelihood,
321
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
322
- this ->addLogLikelihood (logLikelihood))
323
- }
324
- while (traverser.next ());
325
- return true ;
368
+ return this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser);
326
369
}
327
370
328
371
void CUnivariateNoChangeModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
329
372
{
330
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
373
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
331
374
}
332
375
333
376
double CUnivariateNoChangeModel::bic () const
334
377
{
335
378
return -2.0 * this ->logLikelihood ();
336
379
}
337
380
381
+ double CUnivariateNoChangeModel::expectedBic () const
382
+ {
383
+ // This is irrelevant since this is only used for deciding
384
+ // whether to accept a change.
385
+ return this ->bic ();
386
+ }
387
+
338
388
TOptionalChangeDescription CUnivariateNoChangeModel::change () const
339
389
{
340
390
return TOptionalChangeDescription ();
@@ -357,7 +407,7 @@ void CUnivariateNoChangeModel::addSamples(std::size_t count,
357
407
samples.push_back (this ->trendModel ().detrend (sample.first , sample.second , 0.0 ));
358
408
}
359
409
360
- double logLikelihood;
410
+ double logLikelihood{ 0.0 } ;
361
411
if (this ->residualModel ().jointLogMarginalLikelihood (weightStyles, samples, weights,
362
412
logLikelihood) == maths_t ::E_FpNoErrors)
363
413
{
@@ -386,13 +436,13 @@ CUnivariateLevelShiftModel::CUnivariateLevelShiftModel(const TDecompositionPtr &
386
436
bool CUnivariateLevelShiftModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
387
437
core::CStateRestoreTraverser &traverser)
388
438
{
439
+ if (this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser) == false )
440
+ {
441
+ return false ;
442
+ }
389
443
do
390
444
{
391
445
const std::string name{traverser.name ()};
392
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
393
- double logLikelihood,
394
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
395
- this ->addLogLikelihood (logLikelihood))
396
446
RESTORE (SHIFT_TAG, m_Shift.fromDelimited (traverser.value ()))
397
447
RESTORE_BUILT_IN (RESIDUAL_MODEL_MODE_TAG, m_ResidualModelMode)
398
448
RESTORE_BUILT_IN (SAMPLE_COUNT_TAG, m_SampleCount)
@@ -404,7 +454,7 @@ bool CUnivariateLevelShiftModel::acceptRestoreTraverser(const SModelRestoreParam
404
454
405
455
void CUnivariateLevelShiftModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
406
456
{
407
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
457
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
408
458
inserter.insertValue (SHIFT_TAG, m_Shift.toDelimited ());
409
459
inserter.insertValue (SAMPLE_COUNT_TAG, m_SampleCount);
410
460
inserter.insertLevel (RESIDUAL_MODEL_TAG, boost::bind<void >(CPriorStateSerialiser (),
@@ -416,6 +466,11 @@ double CUnivariateLevelShiftModel::bic() const
416
466
return -2.0 * this ->logLikelihood () + std::log (m_SampleCount);
417
467
}
418
468
469
+ double CUnivariateLevelShiftModel::expectedBic () const
470
+ {
471
+ return -2.0 * this ->expectedLogLikelihood () + std::log (m_SampleCount);
472
+ }
473
+
419
474
TOptionalChangeDescription CUnivariateLevelShiftModel::change () const
420
475
{
421
476
// The "magic" 0.9 is due to the fact that the trend is updated
@@ -465,12 +520,24 @@ void CUnivariateLevelShiftModel::addSamples(std::size_t count,
465
520
residualModel.addSamples (weightStyles, samples, weights);
466
521
residualModel.propagateForwardsByTime (1.0 );
467
522
468
- double logLikelihood;
523
+ double logLikelihood{ 0.0 } ;
469
524
if (residualModel.jointLogMarginalLikelihood (weightStyles, samples, weights,
470
525
logLikelihood) == maths_t ::E_FpNoErrors)
471
526
{
472
527
this ->addLogLikelihood (logLikelihood);
473
528
}
529
+ for (const auto &weight : weights)
530
+ {
531
+ double expectedLogLikelihood{0.0 };
532
+ TDouble4Vec1Vec weight_{weight};
533
+ if (residualModel.expectation (maths::CPrior::CLogMarginalLikelihood{
534
+ residualModel, weightStyles, weight_},
535
+ EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS,
536
+ expectedLogLikelihood, weightStyles, weight))
537
+ {
538
+ this ->addExpectedLogLikelihood (expectedLogLikelihood);
539
+ }
540
+ }
474
541
}
475
542
}
476
543
@@ -496,13 +563,13 @@ CUnivariateTimeShiftModel::CUnivariateTimeShiftModel(const TDecompositionPtr &tr
496
563
bool CUnivariateTimeShiftModel::acceptRestoreTraverser (const SModelRestoreParams ¶ms,
497
564
core::CStateRestoreTraverser &traverser)
498
565
{
566
+ if (this ->CUnivariateChangeModel ::acceptRestoreTraverser (params, traverser) == false )
567
+ {
568
+ return false ;
569
+ }
499
570
do
500
571
{
501
572
const std::string name{traverser.name ()};
502
- RESTORE_SETUP_TEARDOWN (LOG_LIKELIHOOD_TAG,
503
- double logLikelihood,
504
- core::CStringUtils::stringToType (traverser.value (), logLikelihood),
505
- this ->addLogLikelihood (logLikelihood))
506
573
RESTORE (RESIDUAL_MODEL_TAG, this ->restoreResidualModel (params.s_DistributionParams , traverser))
507
574
}
508
575
while (traverser.next ());
@@ -511,7 +578,7 @@ bool CUnivariateTimeShiftModel::acceptRestoreTraverser(const SModelRestoreParams
511
578
512
579
void CUnivariateTimeShiftModel::acceptPersistInserter (core::CStatePersistInserter &inserter) const
513
580
{
514
- inserter. insertValue (LOG_LIKELIHOOD_TAG, this ->logLikelihood () );
581
+ this ->CUnivariateChangeModel ::acceptPersistInserter (inserter );
515
582
inserter.insertLevel (RESIDUAL_MODEL_TAG, boost::bind<void >(CPriorStateSerialiser (),
516
583
boost::cref (this ->residualModel ()), _1));
517
584
}
@@ -521,6 +588,11 @@ double CUnivariateTimeShiftModel::bic() const
521
588
return -2.0 * this ->logLikelihood ();
522
589
}
523
590
591
+ double CUnivariateTimeShiftModel::expectedBic () const
592
+ {
593
+ return -2.0 * this ->expectedLogLikelihood ();
594
+ }
595
+
524
596
TOptionalChangeDescription CUnivariateTimeShiftModel::change () const
525
597
{
526
598
return SChangeDescription{SChangeDescription::E_TimeShift,
@@ -549,12 +621,22 @@ void CUnivariateTimeShiftModel::addSamples(std::size_t count,
549
621
residualModel.addSamples (weightStyles, samples, weights);
550
622
residualModel.propagateForwardsByTime (1.0 );
551
623
552
- double logLikelihood;
624
+ double logLikelihood{ 0.0 } ;
553
625
if (residualModel.jointLogMarginalLikelihood (weightStyles, samples, weights,
554
626
logLikelihood) == maths_t ::E_FpNoErrors)
555
627
{
556
628
this ->addLogLikelihood (logLikelihood);
557
629
}
630
+ for (const auto &weight : weights)
631
+ {
632
+ double expectedLogLikelihood{0.0 };
633
+ TDouble4Vec1Vec weight_{weight};
634
+ residualModel.expectation (maths::CPrior::CLogMarginalLikelihood{
635
+ residualModel, weightStyles, weight_},
636
+ EXPECTED_LOG_LIKELIHOOD_NUMBER_INTERVALS,
637
+ expectedLogLikelihood, weightStyles, weight);
638
+ this ->addExpectedLogLikelihood (expectedLogLikelihood);
639
+ }
558
640
}
559
641
}
560
642
0 commit comments