-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Purge transactions older than ignition max stamp #183
Purge transactions older than ignition max stamp #183
Conversation
Consider an ignition sensor transaction `I` with minimum and maximum involved stamps `[I_min, I_max]`. And another transaction `T` with minimum and maximum involved stamps `[T_min, T_max]`. Now consider `I` represents an entire graph recorded, so we have `I_max = I_min + l` where `l` is approximately the `lag_duration`. We want to process `I` and the first transaction `T` after that graph was generated, so it adds constraints to existing variables or removes existing constraints or variables. For this to happen we must purge any transaction `T` that has: * `T_min < I_min`, or * `T_max <= I_max` Note that `T_max == I_max` for the `T` that generated the graph recorded, represented by `I`. This conditions should also be valid for other use cases. In the particular case of `I`, we also want `T_max` to be exactly the one after `I_max`. For this we need to disable the `reset` service in the ignition sensor, so we do not lose any transaction.
@@ -425,12 +425,12 @@ void FixedLagSmoother::transactionCallback( | |||
{ | |||
// If this transaction occurs before the start time, just ignore it | |||
auto start_time = getStartTime(); | |||
const auto& min_time = transaction->minStamp(); | |||
if (started_ && min_time < start_time) | |||
const auto max_time = transaction->maxStamp(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that we were doing const auto& min_time
, which is a const
reference to an attribute inside transaction
, which is std::move
d when it gets insert
ed into the pending_transactions_
, but still used later.
This wasn't an issue at the moment, but it could be a potential issue in the future if something changes the std::move
d transaction. For that reason I'm taking a copy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments to help with the review.
I think we could extend the existing FixedLagIgnition
test to exercise this case, if you're happy with this implementation.
{ | ||
pending_transactions_.pop_back(); | ||
// And purge out old transactions to limit the pending size while waiting for an ignition sensor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for using maxStamp()
instead of minStamp()
, this is the same logic as before for the case when started_
is still false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm rather indifferent on which stamp gets used here. The sole purpose of this block is to prevent the pending queue from getting too large while waiting for the ignition transaction.
// | ||
// TODO(efernandez) Do '&min_time = std::as_const(start_ime)' when C++17 is supported and we can use | ||
// std::as_const: https://en.cppreference.com/w/cpp/utility/as_const | ||
pending_transactions_.erase( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This performs the full interval check, so all other transactions [min, max]
intervals are after the ignition sensor transaction interval.
Equality is allowed for the min
, but not for the max
. This is because we don't want a transaction that happened at the same time as the ignition sensor transaction. See the motivation and use case in this PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this purge is only performed once, when the first ignition transaction is received. Because of the asynchronous nature of everything, it is possible that your initial graph transaction, G1
, is received before the accompanying transaction, T1
. In such a case, this purge code will check will fire and then T1 is received. Since T1.maxStamp() == start_time
, the check above if (started_ && max_time < start_time)
will also not purge out the T1
transaction. I feel like those two checks should both use <= start_time
@@ -448,7 +448,7 @@ void FixedLagSmoother::transactionCallback( | |||
pending_transactions_.end(), | |||
transaction->stamp(), | |||
comparator); | |||
pending_transactions_.insert(position, {sensor_name, std::move(transaction)}); // NOLINT | |||
position = pending_transactions_.insert(position, {sensor_name, std::move(transaction)}); // NOLINT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required because position
could be end()
before insert
ing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Apologies, but I am not quite following the argument in the PR description. Since we are talking about the ignition sequence, I am assuming the graph, We then receive an ignition transaction The processing sequence should go:
Next optimization iteration:
Case
|
Minor comment: I'm actually considering no motion model is used, since my intention is to recreated the intermediate graphs that where throttle and therefore not recorded, using the transactions.
I agree on all these cases, and this change does what you're saying.
Yes, this is the case I want to purged out in this change. The reason I don't want/think a transaction with Use CaseLet me explain you my use case again in more detail, so it's more clear. Recording Phase
I end up with a For example, let's say we recorded something like this: IMPORTANT The following properties are important to see why I want to purge out
Playback Phase
Now, let's assume we start playing the We'll have this sequence of events:
A unit test could be created to confirm that MotivationI think it's clear the motivation here is to play back the recorded (throttled) graph and transactions. This is useful to triage issues. That being said, I'm open to other approaches. Indeed, this allows to:
But I'm still not sure if it's convenient enough for other things:
I could also request permissions to contribute the |
I welcome all contributions you want to submit. But I also want to remind you that these sensors models can also be released in your own package, giving you full control over development and release. Which ever way works best for you. To be clear, I'm not against tightening up the filtering criteria. I was merely trying to reject as few transactions as possible. In practice, a "live" ignition sensor will likely involve only one timestamp, making the distinction between Possible issues:
|
I don't think this can happen with the current implementation. The reason is subtle though:
So I think that meaningful stamp is already in place. 😃
Yes, I disable marginalization because I can't allow new marginalization constraints to be generated. |
@svwilliams I'd like to hear your thoughts about this PR after my last comment. 😃 |
I agree the graph uses the transaction stamp, so the graph and transaction are guaranteed to be synchronized. However, the transaction stamp (of the <graph, transaction> pair) is derived from merging the transactions in the pending queue during the Imagine some sort of processor-intensive visual place recognition system or such that publishes measurements with a significant delay. The transaction gets stamped with the image frame capture time, but the measurement isn't published until 100ms after the frame was captured. This could lead to the first transaction, I'm wondering if we need a strictly monotonically increasing stamp on the published <graph, transaction> pairs? Or synchronize on the sequence number? I like the theory of the sequence number a lot, but ROS's strict control over the sequence number field always makes me hesitate using it. |
What's the status of this PR? |
@ayrton04 My understanding is that is waiting on me to do a few more tests after @svwilliams ' comments. And we likely a different approach. I've sent #195 and #196 so I can hopefully illustrate and motivate some changes here. But, obviously, only if that doesn't break the existing tests and functionality. I set the question label to highlight that because there's no label for changes required or similar. |
I guess what I'm wondering is: Do we stamp the composite transaction that gets merged into the graph with the current time? https://github.com/locusrobotics/fuse/blob/devel/fuse_optimizers/src/fixed_lag_smoother.cpp#L186 That would enforce the graph/transaction pair reported out of the smoother to have a strictly monotonically increasing timestamp. Since this transaction is a composite of multiple transactions as well as the motion models, I'm not sure there is an "expected" header stamp for this transaction. And I don't think any of the existing Publisher classes use the transaction header stamp for anything, so this change shouldn't break anything. |
@svwilliams I believe that happens when we merge the transactions in https://github.com/locusrobotics/fuse/blob/devel/fuse_optimizers/src/fixed_lag_smoother.cpp#L295 and wherever we call fuse/fuse_core/src/transaction.cpp Lines 215 to 217 in 1172343
|
Correct. Right now we choose the max stamp. I'm curious if we want to set it to |
Maybe. I don't know. I like that we can track down what sensor model input messages were used to generate the transaction using the timestamp. Actually, we can do that by looking at the constraint variables. The constraint has the I have a tool that filters the sensor model inputs based on that, so I get the same inputs even when I'm throttling things out with the |
@efernandez Are we ready for this to be merged in? Or are we still contemplating other changes? |
@svwilliams I'm not contemplating any other changes, so it should be good to merge it in. That being said, I've been thinking of some changes for the ignition/reset logic. It's a bit off topic, but I'd like to hear your thoughts. Below I try to explain my proposed changes in brief:
fuse_core::Transaction transaction;
transaction.removeVariable(fuse_core::uuid::NIL);
transaction.removeConstraint(fuse_core::uuid::NIL); The actual way we request all variables or constraints to be removed from the graphs could be done hiding the detail of the special UUID value used to indicate that. That means we could have: transaction.removeAllVaraibles();
transaction.removeAllConstraints(); or probably only allow to remove all variables if also all constraints are removed, in which case we could simply expose a method to do void Graph::update(const Transaction& transaction)
{
// Remove all constraints from the graph
for (const auto& constraint_uuid : transaction.removedConstraints())
{
if (constraint_uuid == fuse_core::uuid::NIL)
{
clearConstraints();
break;
}
}
// Remove all variables from the graph
for (const auto& variable_uuid : transaction.removedVariables())
{
if (variable_uuid == fuse_core::uuid::NIL)
{
clearVariables();
break;
}
}
// Insert the new variables into the graph
for (const auto& variable : transaction.addedVariables())
{
addVariable(variable.clone());
}
// Insert the new constraints into the graph
for (const auto& constraint : transaction.addedConstraints())
{
addConstraint(constraint.clone());
}
// Delete constraints from the graph
for (const auto& constraint_uuid : transaction.removedConstraints())
{
if (constraint_uuid != fuse_core::uuid::NIL)
{
removeConstraint(constraint_uuid);
}
}
// Delete variables from the graph
for (const auto& variable_uuid : transaction.removedVariables())
{
if (variable_uuid != fuse_core::uuid::NIL)
{
removeVariable(variable_uuid);
}
}
} where virtual void clearConstraints() = 0;
virtual void clearVariables() = 0; that could be efficiently implemented as follows for the void clearConstraints()
{
constraints_.clear();
constraints_by_variable_uuid_.clear();
}
void clearVariables()
{
variables_.clear();
variables_on_hold_.clear();
}
transaction->removeVariable(fuse_core::uuid::NIL);
transaction->removeConstraint(fuse_core::uuid::NIL); It shouldn't be a problem if this transactions get merged with others, that would add more variables and constraints, because the graph would be cleared before adding any variables or constraints. This will still rely on the optimizer to purge any transaction before the ignition one. If we want, I think it'd be possible to detect a transaction has I believe this would allow us to get rid of the reset logic in the ignition sensor models: fuse/fuse_models/src/unicycle_2d_ignition.cpp Lines 227 to 241 in da477ad
This would make them easier to implement. It'd also make things faster to update and we wouldn't lose any transactions that we currently lose when we call the I wonder if ignition sensor really have to support calling the |
I am definitely not opposed to streamlining the reset operation. Adding a reset option to the transaction is an interesting idea, but I'll need to spend some time to fully understand all of the ramifications. But a few immediate thoughts:
If you want to pursue this, we can work out some way of collaborating on the feature. A shared branch, or a PR I have push access to, or something. Since I want to merge this PR, I'm going to copy this conversation into an issue first. We can continue the discussion there. |
See issue #199 |
Consider an ignition sensor transaction
I
with minimum and maximum involved stamps[I_min, I_max]
.And another transaction
T
with minimum and maximum involved stamps[T_min, T_max]
.Now consider
I
represents an entire graph recorded, so we haveI_max = I_min + l
wherel
is approximately thelag_duration
.We want to process
I
and the first transactionT
after that graph was generated, so it addsconstraints to existing variables or removes existing constraints or variables.
For this to happen we must purge any transaction
T
that has:T_min < I_min
, orT_max <= I_max
Note that
T_max == I_max
for theT
that generated the graph recorded, represented byI
.This conditions should also be valid for other use cases.
In the particular case of
I
, we also wantT_max
to be exactly the one afterI_max
. For this we need to disable thereset
service in the ignition sensor, so we do not lose any transaction.