Add a frozen boolean to NoncausalOffsetEstimator.
This gives us a place to track if we have changed a line after it has
been used or not. A following commit should improve tracking.
Change-Id: I9e9cd0b28ae035dd247cf045ccce5b65d4e69c23
diff --git a/aos/network/multinode_timestamp_filter.cc b/aos/network/multinode_timestamp_filter.cc
index 2e02eec..7faf637 100644
--- a/aos/network/multinode_timestamp_filter.cc
+++ b/aos/network/multinode_timestamp_filter.cc
@@ -120,8 +120,14 @@
filters_) {
message_bridge::NoncausalOffsetEstimator *estimator = &filter.second;
- if (estimator->a_timestamps().size() == 0 &&
- estimator->b_timestamps().size() == 0) {
+ const std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ a_timestamps = estimator->ATimestamps();
+ const std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ b_timestamps = estimator->BTimestamps();
+
+ if (a_timestamps.size() == 0 && b_timestamps.size() == 0) {
continue;
}
@@ -157,34 +163,29 @@
const aos::distributed_clock::time_point a0 =
node_a_factory->ToDistributedClock(
- std::get<0>(estimator->a_timestamps()[0]));
+ std::get<0>(a_timestamps[0]));
const aos::distributed_clock::time_point a1 =
- node_a_factory->ToDistributedClock(
- std::get<0>(estimator->a_timestamps()[1]));
+ node_a_factory->ToDistributedClock(std::get<0>(a_timestamps[1]));
- VLOG(2) << node_a->name()->string_view() << " timestamps()[0] = "
- << std::get<0>(estimator->a_timestamps()[0]) << " -> " << a0
- << " distributed -> " << node_b->name()->string_view() << " "
+ VLOG(2) << node_a->name()->string_view()
+ << " timestamps()[0] = " << std::get<0>(a_timestamps[0]) << " -> "
+ << a0 << " distributed -> " << node_b->name()->string_view() << " "
<< node_b_factory->FromDistributedClock(a0) << " should be "
<< aos::monotonic_clock::time_point(
std::chrono::nanoseconds(static_cast<int64_t>(
- std::get<0>(estimator->a_timestamps()[0])
- .time_since_epoch()
- .count() *
+ std::get<0>(a_timestamps[0]).time_since_epoch().count() *
(1.0 + estimator->fit().slope()))) +
estimator->fit().offset())
<< ((a0 <= event_loop_factory_->distributed_now())
? ""
: " After now, investigate");
- VLOG(2) << node_a->name()->string_view() << " timestamps()[1] = "
- << std::get<0>(estimator->a_timestamps()[1]) << " -> " << a1
- << " distributed -> " << node_b->name()->string_view() << " "
+ VLOG(2) << node_a->name()->string_view()
+ << " timestamps()[1] = " << std::get<0>(a_timestamps[1]) << " -> "
+ << a1 << " distributed -> " << node_b->name()->string_view() << " "
<< node_b_factory->FromDistributedClock(a1) << " should be "
<< aos::monotonic_clock::time_point(
std::chrono::nanoseconds(static_cast<int64_t>(
- std::get<0>(estimator->a_timestamps()[1])
- .time_since_epoch()
- .count() *
+ std::get<0>(a_timestamps[1]).time_since_epoch().count() *
(1.0 + estimator->fit().slope()))) +
estimator->fit().offset())
<< ((event_loop_factory_->distributed_now() <= a1)
@@ -192,21 +193,19 @@
: " Before now, investigate");
const aos::distributed_clock::time_point b0 =
- node_b_factory->ToDistributedClock(
- std::get<0>(estimator->b_timestamps()[0]));
+ node_b_factory->ToDistributedClock(std::get<0>(b_timestamps[0]));
const aos::distributed_clock::time_point b1 =
- node_b_factory->ToDistributedClock(
- std::get<0>(estimator->b_timestamps()[1]));
+ node_b_factory->ToDistributedClock(std::get<0>(b_timestamps[1]));
VLOG(2) << node_b->name()->string_view() << " timestamps()[0] = "
- << std::get<0>(estimator->b_timestamps()[0]) << " -> " << b0
+ << std::get<0>(b_timestamps[0]) << " -> " << b0
<< " distributed -> " << node_a->name()->string_view() << " "
<< node_a_factory->FromDistributedClock(b0)
<< ((b0 <= event_loop_factory_->distributed_now())
? ""
: " After now, investigate");
VLOG(2) << node_b->name()->string_view() << " timestamps()[1] = "
- << std::get<0>(estimator->b_timestamps()[1]) << " -> " << b1
+ << std::get<0>(b_timestamps[1]) << " -> " << b1
<< " distributed -> " << node_a->name()->string_view() << " "
<< node_a_factory->FromDistributedClock(b1)
<< ((event_loop_factory_->distributed_now() <= b1)
diff --git a/aos/network/timestamp_filter.cc b/aos/network/timestamp_filter.cc
index bb18095..5e18185 100644
--- a/aos/network/timestamp_filter.cc
+++ b/aos/network/timestamp_filter.cc
@@ -507,13 +507,32 @@
}
}
+std::tuple<monotonic_clock::time_point, chrono::nanoseconds> TrimTuple(
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds, bool>
+ t) {
+ return std::make_tuple(std::get<0>(t), std::get<1>(t));
+}
+
+std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+NoncausalTimestampFilter::Timestamps() {
+ std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ result;
+
+ for (const auto x : timestamps_) {
+ result.emplace_back(TrimTuple(x));
+ }
+ return result;
+}
+
Line NoncausalTimestampFilter::FitLine() {
DCHECK_GE(timestamps_.size(), 1u);
if (timestamps_.size() == 1) {
Line fit(std::get<1>(timestamps_[0]), 0.0);
return fit;
} else {
- return Line::Fit(timestamps_[0], timestamps_[1]);
+ return Line::Fit(TrimTuple(timestamps_[0]), TrimTuple(timestamps_[1]));
}
}
@@ -530,13 +549,13 @@
// The first sample is easy. Just do it!
if (timestamps_.size() == 0) {
- timestamps_.emplace_back(std::make_pair(monotonic_now, sample_ns));
+ timestamps_.emplace_back(std::make_tuple(monotonic_now, sample_ns, false));
return true;
} else {
// Future samples get quite a bit harder. We want the line to track the
// highest point without volating the slope constraint.
- std::tuple<aos::monotonic_clock::time_point, chrono::nanoseconds> back =
- timestamps_.back();
+ std::tuple<aos::monotonic_clock::time_point, chrono::nanoseconds, bool>
+ back = timestamps_.back();
aos::monotonic_clock::duration dt = monotonic_now - std::get<0>(back);
aos::monotonic_clock::duration doffset = sample_ns - std::get<1>(back);
@@ -558,7 +577,7 @@
}
// TODO(austin): Refuse to modify the 0th element after we have used it.
- timestamps_.emplace_back(std::make_pair(monotonic_now, sample_ns));
+ timestamps_.emplace_back(std::make_tuple(monotonic_now, sample_ns, true));
// If we are early in the log file, the filter hasn't had time to get
// started. We might only have 2 samples, and the first sample was
@@ -574,19 +593,19 @@
VLOG(1) << csv_file_name_ << " slope " << std::setprecision(20)
<< FitLine().slope() << " offset " << FitLine().offset().count()
- << " a [(" << std::get<0>(timestamps()[0]) << " -> "
- << std::get<1>(timestamps()[0]).count() << "ns), ("
- << std::get<0>(timestamps()[1]) << " -> "
- << std::get<1>(timestamps()[1]).count()
+ << " a [(" << std::get<0>(timestamps_[0]) << " -> "
+ << std::get<1>(timestamps_[0]).count() << "ns), ("
+ << std::get<0>(timestamps_[1]) << " -> "
+ << std::get<1>(timestamps_[1]).count()
<< "ns) => {dt: " << std::fixed << std::setprecision(6)
<< chrono::duration<double, std::milli>(
- std::get<0>(timestamps()[1]) -
- std::get<0>(timestamps()[0]))
+ std::get<0>(timestamps_[1]) -
+ std::get<0>(timestamps_[0]))
.count()
<< "ms, do: " << std::fixed << std::setprecision(6)
<< chrono::duration<double, std::milli>(
- std::get<1>(timestamps()[1]) -
- std::get<1>(timestamps()[0]))
+ std::get<1>(timestamps_[1]) -
+ std::get<1>(timestamps_[0]))
.count()
<< "ms}]";
VLOG(1) << "Back is out of range, clipping from "
@@ -638,7 +657,7 @@
}
void NoncausalTimestampFilter::MaybeWriteTimestamp(
- std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds, bool>
timestamp) {
if (fp_ && first_time_ != aos::monotonic_clock::min_time) {
fprintf(fp_, "%.9f, %.9f, %.9f\n",
@@ -699,66 +718,72 @@
}
void NoncausalOffsetEstimator::LogFit(std::string_view prefix) {
- if (a_.timestamps().size() >= 2u) {
+ const std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ a_timestamps = ATimestamps();
+ const std::deque<
+ std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ b_timestamps = BTimestamps();
+ if (a_timestamps.size() >= 2u) {
LOG(INFO) << prefix << " " << node_a_->name()->string_view() << " from "
<< node_b_->name()->string_view() << " slope "
<< std::setprecision(20) << fit_.slope() << " offset "
<< fit_.offset().count() << " a [("
- << std::get<0>(a_.timestamps()[0]) << " -> "
- << std::get<1>(a_.timestamps()[0]).count() << "ns), ("
- << std::get<0>(a_.timestamps()[1]) << " -> "
- << std::get<1>(a_.timestamps()[1]).count()
+ << std::get<0>(a_timestamps[0]) << " -> "
+ << std::get<1>(a_timestamps[0]).count() << "ns), ("
+ << std::get<0>(a_timestamps[1]) << " -> "
+ << std::get<1>(a_timestamps[1]).count()
<< "ns) => {dt: " << std::fixed << std::setprecision(6)
<< std::chrono::duration<double, std::milli>(
- std::get<0>(a_.timestamps()[1]) -
- std::get<0>(a_.timestamps()[0]))
+ std::get<0>(a_timestamps[1]) -
+ std::get<0>(a_timestamps[0]))
.count()
<< "ms, do: " << std::fixed << std::setprecision(6)
<< std::chrono::duration<double, std::milli>(
- std::get<1>(a_.timestamps()[1]) -
- std::get<1>(a_.timestamps()[0]))
+ std::get<1>(a_timestamps[1]) -
+ std::get<1>(a_timestamps[0]))
.count()
<< "ms}]";
- } else if (a_.timestamps().size() == 1u) {
+ } else if (a_timestamps.size() == 1u) {
LOG(INFO) << prefix << " " << node_a_->name()->string_view() << " from "
<< node_b_->name()->string_view() << " slope "
<< std::setprecision(20) << fit_.slope() << " offset "
<< fit_.offset().count() << " a [("
- << std::get<0>(a_.timestamps()[0]) << " -> "
- << std::get<1>(a_.timestamps()[0]).count() << "ns)";
+ << std::get<0>(a_timestamps[0]) << " -> "
+ << std::get<1>(a_timestamps[0]).count() << "ns)";
} else {
LOG(INFO) << prefix << " " << node_a_->name()->string_view() << " from "
<< node_b_->name()->string_view() << " slope "
<< std::setprecision(20) << fit_.slope() << " offset "
<< fit_.offset().count() << " no samples.";
}
- if (b_.timestamps().size() >= 2u) {
+ if (b_timestamps.size() >= 2u) {
LOG(INFO) << prefix << " " << node_a_->name()->string_view() << " from "
<< node_b_->name()->string_view() << " slope "
<< std::setprecision(20) << fit_.slope() << " offset "
<< fit_.offset().count() << " b [("
- << std::get<0>(b_.timestamps()[0]) << " -> "
- << std::get<1>(b_.timestamps()[0]).count() << "ns), ("
- << std::get<0>(b_.timestamps()[1]) << " -> "
- << std::get<1>(b_.timestamps()[1]).count()
+ << std::get<0>(b_timestamps[0]) << " -> "
+ << std::get<1>(b_timestamps[0]).count() << "ns), ("
+ << std::get<0>(b_timestamps[1]) << " -> "
+ << std::get<1>(b_timestamps[1]).count()
<< "ns) => {dt: " << std::fixed << std::setprecision(6)
<< std::chrono::duration<double, std::milli>(
- std::get<0>(b_.timestamps()[1]) -
- std::get<0>(b_.timestamps()[0]))
+ std::get<0>(b_timestamps[1]) -
+ std::get<0>(b_timestamps[0]))
.count()
<< "ms, do: " << std::fixed << std::setprecision(6)
<< std::chrono::duration<double, std::milli>(
- std::get<1>(b_.timestamps()[1]) -
- std::get<1>(b_.timestamps()[0]))
+ std::get<1>(b_timestamps[1]) -
+ std::get<1>(b_timestamps[0]))
.count()
<< "ms}]";
- } else if (b_.timestamps().size() == 1u) {
+ } else if (b_timestamps.size() == 1u) {
LOG(INFO) << prefix << " " << node_b_->name()->string_view() << " from "
<< node_a_->name()->string_view() << " slope "
<< std::setprecision(20) << fit_.slope() << " offset "
<< fit_.offset().count() << " b [("
- << std::get<0>(b_.timestamps()[0]) << " -> "
- << std::get<1>(b_.timestamps()[0]).count() << "ns)";
+ << std::get<0>(b_timestamps[0]) << " -> "
+ << std::get<1>(b_timestamps[0]).count() << "ns)";
} else {
LOG(INFO) << prefix << " " << node_b_->name()->string_view() << " from "
<< node_a_->name()->string_view() << " slope "
@@ -768,7 +793,7 @@
}
void NoncausalOffsetEstimator::Refit() {
- if (a_.timestamps().size() == 0 || b_.timestamps().size() == 0) {
+ if (a_timestamps_size() == 0 || b_timestamps_size() == 0) {
VLOG(1) << "Not fitting because there is no data";
return;
}
diff --git a/aos/network/timestamp_filter.h b/aos/network/timestamp_filter.h
index 10f436e..82cc9b8 100644
--- a/aos/network/timestamp_filter.h
+++ b/aos/network/timestamp_filter.h
@@ -338,17 +338,19 @@
bool Pop(aos::monotonic_clock::time_point time);
// Returns the current list of timestamps in our list.
- const std::deque<
+ std::deque<
std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
- ×tamps() {
- return timestamps_;
- }
+ Timestamps();
+
+ size_t timestamps_size() const { return timestamps_.size(); }
void Debug() {
- for (std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>
+ for (std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds,
+ bool>
timestamp : timestamps_) {
LOG(INFO) << std::get<0>(timestamp) << " offset "
- << std::get<1>(timestamp).count();
+ << std::get<1>(timestamp).count() << " frozen? "
+ << std::get<2>(timestamp);
}
}
@@ -365,12 +367,15 @@
}
// Writes a timestamp to the file if it is reasonable.
- void MaybeWriteTimestamp(
- std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>
- timestamp);
+ void MaybeWriteTimestamp(std::tuple<aos::monotonic_clock::time_point,
+ std::chrono::nanoseconds, bool>
+ timestamp);
- std::deque<
- std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
+ // Timestamp, offest, and then a boolean representing if this sample is frozen
+ // and can't be modified or not.
+ // TODO(austin): Actually use and update the bool.
+ std::deque<std::tuple<aos::monotonic_clock::time_point,
+ std::chrono::nanoseconds, bool>>
timestamps_;
std::string csv_file_name_;
@@ -413,17 +418,20 @@
}
// Returns the data points from each filter.
- const std::deque<
+ std::deque<
std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
- &a_timestamps() {
- return a_.timestamps();
+ ATimestamps() {
+ return a_.Timestamps();
}
- const std::deque<
+ std::deque<
std::tuple<aos::monotonic_clock::time_point, std::chrono::nanoseconds>>
- &b_timestamps() {
- return b_.timestamps();
+ BTimestamps() {
+ return b_.Timestamps();
}
+ size_t a_timestamps_size() const { return a_.timestamps_size(); }
+ size_t b_timestamps_size() const { return b_.timestamps_size(); }
+
void SetFirstFwdTime(monotonic_clock::time_point time) {
a_.SetFirstTime(time);
}
diff --git a/aos/network/timestamp_filter_test.cc b/aos/network/timestamp_filter_test.cc
index b495e2d..9cff160 100644
--- a/aos/network/timestamp_filter_test.cc
+++ b/aos/network/timestamp_filter_test.cc
@@ -171,7 +171,7 @@
NoncausalTimestampFilter filter;
filter.Sample(ta, chrono::nanoseconds(1000));
- EXPECT_EQ(filter.timestamps().size(), 1u);
+ EXPECT_EQ(filter.Timestamps().size(), 1u);
{
Line l1 = filter.FitLine();
@@ -181,7 +181,7 @@
}
filter.Sample(tb, chrono::nanoseconds(1100));
- EXPECT_EQ(filter.timestamps().size(), 2u);
+ EXPECT_EQ(filter.Timestamps().size(), 2u);
{
Line l2 = filter.FitLine();
@@ -204,7 +204,7 @@
filter.Debug();
filter.Sample(tb, chrono::microseconds(2));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
{
Line l2 = filter.FitLine();
@@ -221,7 +221,7 @@
filter.Debug();
filter.Sample(tb, chrono::microseconds(0));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
{
Line l2 = filter.FitLine();
@@ -238,7 +238,7 @@
filter.Debug();
filter.Sample(tb, -chrono::microseconds(1));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 1u);
+ ASSERT_EQ(filter.Timestamps().size(), 1u);
}
{
@@ -249,10 +249,10 @@
filter.Debug();
filter.Sample(tb, chrono::microseconds(3));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
- EXPECT_EQ(std::get<1>(filter.timestamps()[0]), chrono::microseconds(2));
- EXPECT_EQ(std::get<1>(filter.timestamps()[1]), chrono::microseconds(3));
+ EXPECT_EQ(std::get<1>(filter.Timestamps()[0]), chrono::microseconds(2));
+ EXPECT_EQ(std::get<1>(filter.Timestamps()[1]), chrono::microseconds(3));
}
{
@@ -263,16 +263,16 @@
filter.Debug();
filter.Sample(tb, chrono::microseconds(1));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
// Now add a sample with a slope of 0.002. This should back propagate and
// remove the middle point since it violates our constraints.
filter.Sample(tc, chrono::microseconds(3));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
- EXPECT_EQ(std::get<1>(filter.timestamps()[0]), chrono::microseconds(1));
- EXPECT_EQ(std::get<1>(filter.timestamps()[1]), chrono::microseconds(3));
+ EXPECT_EQ(std::get<1>(filter.Timestamps()[0]), chrono::microseconds(1));
+ EXPECT_EQ(std::get<1>(filter.Timestamps()[1]), chrono::microseconds(3));
}
}
@@ -292,24 +292,24 @@
filter.Debug();
filter.Sample(tc, chrono::microseconds(1));
filter.Debug();
- ASSERT_EQ(filter.timestamps().size(), 3u);
+ ASSERT_EQ(filter.Timestamps().size(), 3u);
// Before or in the middle of the first line segment shouldn't change the
// number of points.
EXPECT_FALSE(filter.Pop(t_before));
- ASSERT_EQ(filter.timestamps().size(), 3u);
+ ASSERT_EQ(filter.Timestamps().size(), 3u);
EXPECT_FALSE(filter.Pop(ta));
- ASSERT_EQ(filter.timestamps().size(), 3u);
+ ASSERT_EQ(filter.Timestamps().size(), 3u);
EXPECT_FALSE(filter.Pop(ta + chrono::microseconds(100)));
- ASSERT_EQ(filter.timestamps().size(), 3u);
+ ASSERT_EQ(filter.Timestamps().size(), 3u);
// The second point should trigger a pop, since the offset computed using the
// points won't change when it is used, and any times after (even 1-2 ns
// later) would be wrong.
EXPECT_TRUE(filter.Pop(tb));
- ASSERT_EQ(filter.timestamps().size(), 2u);
+ ASSERT_EQ(filter.Timestamps().size(), 2u);
}
// Run a couple of points through the estimator and confirm it works.
@@ -336,32 +336,32 @@
// Add 3 timestamps in and confirm that the slopes come out reasonably.
estimator.Sample(node_a, ta1, tb1);
estimator.Sample(node_b, tb1, ta1);
- EXPECT_EQ(estimator.a_timestamps().size(), 1u);
- EXPECT_EQ(estimator.b_timestamps().size(), 1u);
+ EXPECT_EQ(estimator.ATimestamps().size(), 1u);
+ EXPECT_EQ(estimator.BTimestamps().size(), 1u);
// 1 point -> a line.
EXPECT_EQ(estimator.fit().mpq_slope(), mpq_class(0));
estimator.Sample(node_a, ta2, tb2);
estimator.Sample(node_b, tb2, ta2);
- EXPECT_EQ(estimator.a_timestamps().size(), 2u);
- EXPECT_EQ(estimator.b_timestamps().size(), 2u);
+ EXPECT_EQ(estimator.ATimestamps().size(), 2u);
+ EXPECT_EQ(estimator.BTimestamps().size(), 2u);
// Adding the second point should slope up.
EXPECT_EQ(estimator.fit().mpq_slope(), mpq_class(1, 100000));
estimator.Sample(node_a, ta3, tb3);
estimator.Sample(node_b, tb3, ta3);
- EXPECT_EQ(estimator.a_timestamps().size(), 3u);
- EXPECT_EQ(estimator.b_timestamps().size(), 3u);
+ EXPECT_EQ(estimator.ATimestamps().size(), 3u);
+ EXPECT_EQ(estimator.BTimestamps().size(), 3u);
// And the third point shouldn't change anything.
EXPECT_EQ(estimator.fit().mpq_slope(), mpq_class(1, 100000));
estimator.Pop(node_a, ta2);
estimator.Pop(node_b, tb2);
- EXPECT_EQ(estimator.a_timestamps().size(), 2u);
- EXPECT_EQ(estimator.b_timestamps().size(), 2u);
+ EXPECT_EQ(estimator.ATimestamps().size(), 2u);
+ EXPECT_EQ(estimator.BTimestamps().size(), 2u);
// Dropping the first point should have the slope point back down.
EXPECT_EQ(estimator.fit().mpq_slope(), mpq_class(-1, 100000));
@@ -369,8 +369,8 @@
// And dropping down to 1 point means 0 slope.
estimator.Pop(node_a, ta3);
estimator.Pop(node_b, tb3);
- EXPECT_EQ(estimator.a_timestamps().size(), 1u);
- EXPECT_EQ(estimator.b_timestamps().size(), 1u);
+ EXPECT_EQ(estimator.ATimestamps().size(), 1u);
+ EXPECT_EQ(estimator.BTimestamps().size(), 1u);
EXPECT_EQ(estimator.fit().mpq_slope(), mpq_class(0));
}