Skip to content

Commit

Permalink
[ML] Fix bug upgrading state from 5.6 direct to 6.3 causing SIGSEGV o…
Browse files Browse the repository at this point in the history
…n open job (#143)

Closes #141.
  • Loading branch information
tveasey authored Jul 4, 2018
1 parent 06a0f99 commit d7d22c8
Show file tree
Hide file tree
Showing 8 changed files with 1,974 additions and 15 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Fix corner case failing to calculate lgamma values and the correspoinding log er
Function description for population lat_long results should be lat_long instead of mean ({pull}81[#81])
By-fields should respect model_plot_config.terms ({pull}86[#86])
The trend decomposition state wasn't being correctly upgraded potentially causing the autodetect process to abort ({pull}136[#136])
Fix a SIGSEGV in the autodetect process when jump upgrading from 5.6 to 6.3 ({pull}143[#143])

=== Regressions

Expand Down
1 change: 1 addition & 0 deletions include/maths/CTimeSeriesDecompositionDetail.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ class MATHS_EXPORT CTimeSeriesDecompositionDetail {

//! Initialize by reading state from \p traverser.
bool acceptRestoreTraverser(const SDistributionRestoreParams& params,
core_t::TTime lastValueTime,
core::CStateRestoreTraverser& traverser);

//! Persist state by passing information to \p inserter.
Expand Down
6 changes: 5 additions & 1 deletion lib/maths/CSeasonalComponentAdaptiveBucketing.cc
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,11 @@ bool CSeasonalComponentAdaptiveBucketing::acceptRestoreTraverser(core::CStateRes
RESTORE(LAST_UPDATES_OLD_TAG,
core::CPersistUtils::fromString(traverser.value(), lastUpdates))
} while (traverser.next());

// This wasn't present in version 5.6 so needs to be default
// initialised if it is missing from the state object.
if (lastUpdates.empty()) {
lastUpdates.resize(regressions.size(), UNSET_TIME);
}
m_Buckets.clear();
m_Buckets.reserve(regressions.size());
for (std::size_t i = 0u; i < regressions.size(); ++i) {
Expand Down
14 changes: 8 additions & 6 deletions lib/maths/CTimeSeriesDecomposition.cc
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ bool CTimeSeriesDecomposition::acceptRestoreTraverser(const SDistributionRestore
RESTORE(CALENDAR_CYCLIC_TEST_6_3_TAG,
traverser.traverseSubLevel(boost::bind(&CCalendarTest::acceptRestoreTraverser,
&m_CalendarCyclicTest, _1)))
RESTORE(COMPONENTS_6_3_TAG, traverser.traverseSubLevel(boost::bind(
&CComponents::acceptRestoreTraverser,
&m_Components, boost::cref(params), _1)))
RESTORE(COMPONENTS_6_3_TAG,
traverser.traverseSubLevel(
boost::bind(&CComponents::acceptRestoreTraverser, &m_Components,
boost::cref(params), m_LastValueTime, _1)))
}
} else {
// There is no version string this is historic state.
Expand All @@ -149,9 +150,10 @@ bool CTimeSeriesDecomposition::acceptRestoreTraverser(const SDistributionRestore
RESTORE(CALENDAR_CYCLIC_TEST_OLD_TAG,
traverser.traverseSubLevel(boost::bind(&CCalendarTest::acceptRestoreTraverser,
&m_CalendarCyclicTest, _1)))
RESTORE(COMPONENTS_OLD_TAG, traverser.traverseSubLevel(boost::bind(
&CComponents::acceptRestoreTraverser,
&m_Components, boost::cref(params), _1)))
RESTORE(COMPONENTS_OLD_TAG,
traverser.traverseSubLevel(
boost::bind(&CComponents::acceptRestoreTraverser, &m_Components,
boost::cref(params), m_LastValueTime, _1)))
} while (traverser.next());
this->decayRate(decayRate);
}
Expand Down
17 changes: 9 additions & 8 deletions lib/maths/CTimeSeriesDecompositionDetail.cc
Original file line number Diff line number Diff line change
Expand Up @@ -323,23 +323,22 @@ const std::string LAST_UPDATE_OLD_TAG{"j"};

const double MODEL_WEIGHT_UPGRADING_TO_VERSION_6_3{48.0};

bool upgradeTrendModelToVersion6p3(const core_t::TTime bucketLength,
CTrendComponent& trend,
core::CStateRestoreTraverser& traverser) {
bool upgradeTrendModelToVersion_6_3(const core_t::TTime bucketLength,
const core_t::TTime lastValueTime,
CTrendComponent& trend,
core::CStateRestoreTraverser& traverser) {
using TRegression = CRegression::CLeastSquaresOnline<3, double>;

TRegression regression;
double variance{0.0};
core_t::TTime origin{0};
core_t::TTime lastUpdate{0};
do {
const std::string& name{traverser.name()};
RESTORE(REGRESSION_OLD_TAG,
traverser.traverseSubLevel(boost::bind(
&TRegression::acceptRestoreTraverser, &regression, _1)))
RESTORE_BUILT_IN(VARIANCE_OLD_TAG, variance)
RESTORE_BUILT_IN(TIME_ORIGIN_OLD_TAG, origin)
RESTORE_BUILT_IN(LAST_UPDATE_OLD_TAG, lastUpdate)
} while (traverser.next());

// Generate some samples from the old trend model.
Expand All @@ -348,7 +347,8 @@ bool upgradeTrendModelToVersion6p3(const core_t::TTime bucketLength,
static_cast<double>(bucketLength) / static_cast<double>(4 * WEEK)};

CPRNG::CXorOShiro128Plus rng;
for (core_t::TTime time = lastUpdate - 4 * WEEK; time < lastUpdate; time += bucketLength) {
for (core_t::TTime time = lastValueTime - 4 * WEEK; time < lastValueTime;
time += bucketLength) {
double time_{static_cast<double>(time - origin) / static_cast<double>(WEEK)};
double sample{regression.predict(time_) + CSampling::normalSample(rng, 0.0, variance)};
trend.add(time, sample, weight);
Expand Down Expand Up @@ -1010,6 +1010,7 @@ CTimeSeriesDecompositionDetail::CComponents::CComponents(const CComponents& othe

bool CTimeSeriesDecompositionDetail::CComponents::acceptRestoreTraverser(
const SDistributionRestoreParams& params,
core_t::TTime lastValueTime,
core::CStateRestoreTraverser& traverser) {
if (traverser.name() == VERSION_6_3_TAG) {
while (traverser.next()) {
Expand Down Expand Up @@ -1059,8 +1060,8 @@ bool CTimeSeriesDecompositionDetail::CComponents::acceptRestoreTraverser(
RESTORE_SETUP_TEARDOWN(TREND_OLD_TAG,
/**/,
traverser.traverseSubLevel(boost::bind(
upgradeTrendModelToVersion6p3,
m_BucketLength, boost::ref(m_Trend), _1)),
upgradeTrendModelToVersion_6_3, m_BucketLength,
lastValueTime, boost::ref(m_Trend), _1)),
m_UsingTrendForPrediction = true)
RESTORE_SETUP_TEARDOWN(
SEASONAL_OLD_TAG, m_Seasonal = boost::make_unique<CSeasonal>(),
Expand Down
68 changes: 68 additions & 0 deletions lib/maths/unittest/CTimeSeriesDecompositionTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2074,6 +2074,7 @@ void CTimeSeriesDecompositionTest::testUpgrade() {
0.1, HALF_HOUR, maths::SDistributionRestoreParams{maths_t::E_ContinuousData, 0.1}};
std::string empty;

LOG_DEBUG(<< "**** From 6.2 ****");
LOG_DEBUG(<< "*** Seasonal and Calendar Components ***");
{
std::string xml;
Expand Down Expand Up @@ -2218,6 +2219,73 @@ void CTimeSeriesDecompositionTest::testUpgrade() {
decomposition.addPoint(time, 10.0);
}
}

LOG_DEBUG(<< "**** From 5.6 ****");
LOG_DEBUG(<< "*** Seasonal and Calendar Components ***");
{
std::string xml;
load("testfiles/CTimeSeriesDecomposition.5.6.seasonal.state.xml", xml);
LOG_DEBUG(<< "Saved state size = " << xml.size());

core::CRapidXmlParser parser;
CPPUNIT_ASSERT(parser.parseStringIgnoreCdata(xml));
core::CRapidXmlStateRestoreTraverser traverser(parser);

maths::CTimeSeriesDecomposition decomposition(params, traverser);

// Check that the decay rates match and the values and variances
// predictions match the values obtained from 6.2.

CPPUNIT_ASSERT_EQUAL(0.01, decomposition.decayRate());

double meanValue{decomposition.meanValue(18316800)};
double meanVariance{decomposition.meanVariance()};
LOG_DEBUG(<< "restored mean value = " << meanValue);
LOG_DEBUG(<< "restored mean variance = " << meanVariance);
CPPUNIT_ASSERT_DOUBLES_EQUAL(9.91269, meanValue, 0.005);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.99723, meanVariance, 0.5);

// Check some basic operations on the upgraded model.
decomposition.forecast(60480000, 60480000 + WEEK, HALF_HOUR, 90.0, 1.0,
[](core_t::TTime, const TDouble3Vec&) {});
for (core_t::TTime time = 60480000; time < 60480000 + WEEK; time += HALF_HOUR) {
decomposition.addPoint(time, 10.0);
}
}

LOG_DEBUG(<< "*** Trend and Seasonal Components ***");
{
std::string xml;
load("testfiles/CTimeSeriesDecomposition.5.6.trend_and_seasonal.state.xml", xml);
LOG_DEBUG(<< "Saved state size = " << xml.size());

core::CRapidXmlParser parser;
CPPUNIT_ASSERT(parser.parseStringIgnoreCdata(xml));
core::CRapidXmlStateRestoreTraverser traverser(parser);

maths::CTimeSeriesDecomposition decomposition(params, traverser);

// Check that the decay rates match and the values and variances
// predictions are close to the values obtained from 6.2. We can't
// update the state exactly in this case so the tolerances in this
// test are significantly larger.

CPPUNIT_ASSERT_EQUAL(0.024, decomposition.decayRate());

double meanValue{decomposition.meanValue(10366200)};
double meanVariance{decomposition.meanVariance()};
LOG_DEBUG(<< "restored mean value = " << meanValue);
LOG_DEBUG(<< "restored mean variance = " << meanVariance);
CPPUNIT_ASSERT_DOUBLES_EQUAL(96.5607, meanValue, 4.0);
CPPUNIT_ASSERT_DOUBLES_EQUAL(631.094, meanVariance, 7.0);

// Check some basic operations on the upgraded model.
decomposition.forecast(10366200, 10366200 + WEEK, HALF_HOUR, 90.0, 1.0,
[](core_t::TTime, const TDouble3Vec&) {});
for (core_t::TTime time = 60480000; time < 60480000 + WEEK; time += HALF_HOUR) {
decomposition.addPoint(time, 10.0);
}
}
}

CppUnit::Test* CTimeSeriesDecompositionTest::suite() {
Expand Down
Loading

0 comments on commit d7d22c8

Please sign in to comment.