lib/maths/time_series/unittest/CDecayRateControllerTest.cc (105 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the following additional limitation. Functionality enabled by the * files subject to the Elastic License 2.0 may only be used in production when * invoked by an Elasticsearch process with a license key installed that permits * use of machine learning features. You may not use this file except in * compliance with the Elastic License 2.0 and the foregoing additional * limitation. */ #include <core/CJsonStatePersistInserter.h> #include <core/CJsonStateRestoreTraverser.h> #include <core/CLogger.h> #include <maths/time_series/CDecayRateController.h> #include <test/CRandomNumbers.h> #include "TestUtils.h" #include <boost/test/unit_test.hpp> #include <vector> BOOST_AUTO_TEST_SUITE(CDecayRateControllerTest) using namespace ml; using namespace handy_typedefs; using TDoubleVec = std::vector<double>; BOOST_AUTO_TEST_CASE(testLowCov) { // Supply small but biased errors so we increase the decay rate to its maximum // then gradually reduce the error to less than the coefficient of variation // cutoff to control and make sure the decay rate reverts to typical. maths::time_series::CDecayRateController controller( maths::time_series::CDecayRateController::E_PredictionBias, 1); double decayRate{0.0005}; for (std::size_t i = 0; i < 1000; ++i) { double multiplier{controller.multiplier({10000.0}, {{1.0}}, 3600, 1.0, 0.0005)}; decayRate *= multiplier; } LOG_DEBUG(<< "Controlled decay = " << decayRate); BOOST_TEST_REQUIRE(decayRate > 0.0005); for (std::size_t i = 0; i < 1000; ++i) { double multiplier{controller.multiplier({10000.0}, {{0.0}}, 3600, 1.0, 0.0005)}; decayRate *= multiplier; } LOG_DEBUG(<< "Controlled decay = " << decayRate); BOOST_TEST_REQUIRE(decayRate < 0.0005); } BOOST_AUTO_TEST_CASE(testOrderedErrors) { // Test that if we add a number of ordered samples, such that overall they don't // have bias, the decay rate is not increased. using TDouble1VecVec = std::vector<TDouble1Vec>; maths::time_series::CDecayRateController controller( maths::time_series::CDecayRateController::E_PredictionBias, 1); double decayRate{0.0005}; TDouble1VecVec predictionErrors; for (std::size_t i = 0; i < 500; ++i) { for (int j = 0; j < 100; ++j) { predictionErrors.push_back({static_cast<double>(j - 50)}); } double multiplier{controller.multiplier({100.0}, predictionErrors, 3600, 1.0, 0.0005)}; decayRate *= multiplier; } LOG_DEBUG(<< "Controlled decay = " << decayRate); BOOST_TEST_REQUIRE(decayRate <= 0.0005); } BOOST_AUTO_TEST_CASE(testPersist) { // Test persist and restore preserves checksums. test::CRandomNumbers rng; TDoubleVec values; rng.generateUniformSamples(1000.0, 1010.0, 1000, values); TDoubleVec errors; rng.generateUniformSamples(-2.0, 6.0, 1000, errors); maths::time_series::CDecayRateController origController( maths::time_series::CDecayRateController::E_PredictionBias, 1); for (std::size_t i = 0; i < values.size(); ++i) { origController.multiplier({values[i]}, {{errors[i]}}, 3600, 1.0, 0.0005); } std::ostringstream origJson; core::CJsonStatePersistInserter::persist( origJson, std::bind_front(&maths::time_series::CDecayRateController::acceptPersistInserter, &origController)); LOG_TRACE(<< "Controller JSON = " << origJson.str()); LOG_DEBUG(<< "Controller JSON size = " << origJson.str().size()); // Restore the JSON into a new controller. { std::istringstream is{"{\"topLevel\":" + origJson.str() + "}"}; core::CJsonStateRestoreTraverser traverser(is); maths::time_series::CDecayRateController restoredController; BOOST_REQUIRE_EQUAL(true, traverser.traverseSubLevel(std::bind_front( &maths::time_series::CDecayRateController::acceptRestoreTraverser, &restoredController))); LOG_DEBUG(<< "orig checksum = " << origController.checksum() << ", new checksum = " << restoredController.checksum()); BOOST_REQUIRE_EQUAL(origController.checksum(), restoredController.checksum()); } } BOOST_AUTO_TEST_CASE(testBehaviourAfterPersistAndRestore) { // Test that we get the same decisions after persisting and restoring. test::CRandomNumbers rng; TDoubleVec values; rng.generateUniformSamples(1000.0, 1010.0, 1000, values); TDoubleVec errors; rng.generateUniformSamples(-2.0, 6.0, 1000, errors); maths::time_series::CDecayRateController origController{ maths::time_series::CDecayRateController::E_PredictionBias, 1}; maths::time_series::CDecayRateController restoredController; for (std::size_t i = 0; i < 500; ++i) { origController.multiplier({values[i]}, {{errors[i]}}, 3600, 1.0, 0.0005); } std::ostringstream origJson; core::CJsonStatePersistInserter::persist( origJson, std::bind_front(&maths::time_series::CDecayRateController::acceptPersistInserter, &origController)); // Restore the JSON into a new controller. { std::istringstream origJsonStrm{"{\"topLevel\" : " + origJson.str() + "}"}; core::CJsonStateRestoreTraverser traverser(origJsonStrm); BOOST_REQUIRE_EQUAL(true, traverser.traverseSubLevel(std::bind_front( &maths::time_series::CDecayRateController::acceptRestoreTraverser, &restoredController))); } for (std::size_t i = 500; i < values.size(); ++i) { BOOST_REQUIRE_CLOSE( origController.multiplier({values[i]}, {{errors[i]}}, 3600, 1.0, 0.0005), restoredController.multiplier({values[i]}, {{errors[i]}}, 3600, 1.0, 0.0005), 0.001); } } BOOST_AUTO_TEST_SUITE_END()