-
Notifications
You must be signed in to change notification settings - Fork 797
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
Functorized Factor #329
Functorized Factor #329
Conversation
gtsam/linear/NoiseModel.cpp
Outdated
@@ -619,6 +619,7 @@ void Isotropic::WhitenInPlace(Eigen::Block<Matrix> H) const { | |||
// Unit | |||
/* ************************************************************************* */ | |||
void Unit::print(const std::string& name) const { | |||
//TODO(Varun): Do we need that space at the end? |
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.
remove from this PR
gtsam/nonlinear/FunctorizedFactor.h
Outdated
/** | ||
* Factor which evaluates functor and uses the result to compute | ||
* error on provided measurement. | ||
* The provided FUNCTOR should provide two definitions: `argument_type` which |
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.
type aliases
* corresponds to the type of input it accepts and `return_type` which indicates | ||
* the type of the return value. This factor uses those type values to construct | ||
* the functor. | ||
* |
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.
Add the example from the description after you fix it (constructor has no key, jacobian is ignored although it would be simple in your example)
gtsam/nonlinear/FunctorizedFactor.h
Outdated
/// @{ | ||
GTSAM_EXPORT friend std::ostream & | ||
operator<<(std::ostream &os, const FunctorizedFactor<FUNCTOR> &f) { | ||
os << " noise model sigmas: " << f.noiseModel_->sigmas().transpose(); |
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.
Stream operator is not part of Testable. Move it somewhere else? Or, in fact remove it. Adding streaming is a big design exercise, large refactor we might not want to tackle now
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.
Just as a note, there are factors that have the stream operator as a part of Testable.
gtsam/nonlinear/FunctorizedFactor.h
Outdated
const { | ||
const FunctorizedFactor<FUNCTOR> *e = | ||
dynamic_cast<const FunctorizedFactor<FUNCTOR>*>(&other); | ||
const bool base = Base::equals(*e, tol); |
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.
will fail if e == nullptr
gtsam/nonlinear/FunctorizedFactor.h
Outdated
} | ||
}; | ||
|
||
// TODO(Varun): Include or kill? |
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.
if factors have it, include. But do they?
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.
They do. Included.
Awesome. Some comments... |
gtsam/nonlinear/FunctorizedFactor.h
Outdated
*/ | ||
template <typename FUNCTOR> | ||
class GTSAM_EXPORT FunctorizedFactor | ||
: public NoiseModelFactor1<typename FUNCTOR::argument_type> { |
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.
Is it possible to allow this to accept more variables? It seems like it should be reasonably common to have
|h(x1, x2, ...) - z|
where we define a functor for h(x1, x2, ...).
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.
Yup it takes any number of arguments. That's why you have the template <typename... Args>
on the constructor.
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.
Sorry to clarify, I meant rather than NoiseModelFactor1, also be able to have NoiseModelFactor2, 3, etc
So right now from the constructor templating it's able to do something like
h(x, theta1, theta2, ...)
where theta are fixed parameters, but I was wondering about multiple variables
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.
Ah I see. Yeah that's phase 2. Now that we have this factor working, it should be straightforward to extend it to multiple keys.
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.
Sorry, this is just not ready yet - since this introduces a new core factor we need to think harder.
gtsam/nonlinear/FunctorizedFactor.h
Outdated
/** | ||
* Factor which evaluates functor and uses the result to compute | ||
* error on provided measurement. | ||
* The provided FUNCTOR should provide two type aliases: `argument_type` which |
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.
Now I’m wondering whether this is just any std::function
? And whether we could use it with a lambda? https://en.cppreference.com/w/cpp/utility/functional/function
I propose you add two unit tests that exercise those cases and we can see if and how it leads to a change in your requirements. It could also be much simpler to just say that the Functor has to satisfy the standard function concept
In fact, reading more, we probably have to:
https://stackoverflow.com/questions/35907454/why-stdfunctionargument-type-has-been-deprecated
“Now that we have decltype that can be used to deduce a callable's return type, variadic templates and perfect forwarding to forward arbitrary number of arguments to functions, etc. the pre-C++11 mechanisms are way too cumbersome to use.”
https://en.cppreference.com/w/cpp/language/decltype
The following post seems to hint that we might not want to know the argument types though: https://stackoverflow.com/questions/6512019/can-we-get-the-type-of-a-lambda-argument
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 actually thought about this at first and had some initial difficulty which I don't remember right now. Then again, my understanding of functionals in C++ has improved quite a bit since then so I'll tackle this today.
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.
So I've not had a lot of success with this without over-complicating the code. Something to consider would be just removing the need for argument_type
and return_type
from the FUNCTOR
and making it match the traits of std::function
so that they can be used interchangeably.
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.
Yes. And it should also do lambdas. Did you make the two unit tests I proposed?
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 did, but I've been having trouble getting the code to compile.
gtsam/nonlinear/FunctorizedFactor.h
Outdated
* double multiplier = 2.0; | ||
* FunctorizedFactor<MultiplyFunctor> factor(keyX, measurement, model, multiplier); | ||
*/ | ||
template <typename FUNCTOR> |
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.
To address my other comment, perhaps we should instead template on T. And then a Functor/lambda/std::function passed needs to take (T, jacobian). Not necessarily dynamic jacobian, btw...
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.
The problem here is that we won't know what the return type is since we don't have anything to call decltype
on. We need the return type to define the measurement type.
gtsam/nonlinear/FunctorizedFactor.h
Outdated
* @param model: Noise model | ||
* @param args: Variable number of arguments used to instantiate functor | ||
*/ | ||
template <typename... Args> |
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 would only be one constructor. The other takes a lambda or function object...
By adding the helper function MakeFunctorizedFactor, we now only need to provide the argument type in the template parameter list. This considerably simplifies the factor declaration, while removing the need for argument type and return type in the functor definition. Also added tests for std::function and lambda functions.
So lots of good stuff thanks to my discussion with @dellaert. Declaring the |
Merging since this PR doesn't introduce any breaking changes |
This PR adds a new type of factor which allows for more dynamic computation.
Consider the case that the input type to a factor's
evaluateError
method is different from themeasurement
, and the factor requires some intermediate processing of the input to match the type (both syntactically and semantically) of the measurement. TheFunctorizedFactor
accepts the functor argument type as a template argument and operates on the input, allowing for considerable reuse.E.g. if we wish to compute the error from a simple linear matrix operation
Ax = b
, we could define a factor like:Thus the factor is completely determined by the functor passed in, thus providing a great amount of simplicity.
The factor also supports
std::function
and C++ lambda types.This change is![Reviewable](https://camo.githubusercontent.com/1541c4039185914e83657d3683ec25920c672c6c5c7ab4240ee7bff601adec0b/68747470733a2f2f72657669657761626c652e696f2f7265766965775f627574746f6e2e737667)