Skip to content
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

Minimum Feedback Arc Set (MFAS) for 1dsfm #387

Merged
merged 16 commits into from
Sep 18, 2020
Merged

Conversation

akshay-krishnan
Copy link
Contributor

@akshay-krishnan akshay-krishnan commented Jul 9, 2020

This PR implements a solution to the Minimum Feedback Arc Set (MFAS) problem. We use the solution from Kyle Wilson and Noah Snavely's ECCV14 paper.


This change is Reviewable

@akshay-krishnan akshay-krishnan requested review from dellaert and removed request for dellaert July 9, 2020 06:53
@varunagrawal varunagrawal added the feature New proposed feature label Jul 10, 2020
Copy link
Collaborator

@varunagrawal varunagrawal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run Google formatting on these files.

gtsam/sfm/mfas.h Outdated
@@ -0,0 +1,65 @@
/*
This file defines functions used to solve a Minimum feedback arc set (MFAS)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the file brief to the GTRC copyright. Do keep the comment about how this is taken from another source.

gtsam/sfm/mfas.h Outdated
* @param edges reference to vector of KeyPairs
* @param weights weights corresponding to edges
*/
void flipNegEdges(std::vector<KeyPair> &edges, std::vector<double> &weights);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flipNegativeEdges?

gtsam/sfm/mfas.h Outdated
* @param ordered_positions: map from node to position in the ordering (0
* indexed)
*/
void mfasRatio(const std::vector<KeyPair> &edges,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mfas in the function name necessary if we've defined a mfas namespace?

FastMap<Key, double> win_deg;
FastMap<Key, double> wout_deg;
FastMap<Key, vector<pair<int, double> > > inbrs;
FastMap<Key, vector<pair<int, double> > > onbrs;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use full names to make code more readable?

@akshay-krishnan
Copy link
Contributor Author

Thank you for reviewing @varunagrawal. I am currently working on changing this implementation, but I will make the changes you suggested too.

@akshay-krishnan akshay-krishnan changed the title Minimum Feedback Arc Set (MFAS) for 1dsfm Minimum Feedback Arc Set (MFAS) for 1dsfm [WIP] Jul 11, 2020
@varunagrawal varunagrawal modified the milestone: GTSAM 4.1 Jul 13, 2020
@dellaert dellaert added this to the GTSAM 4.1.1 milestone Jul 14, 2020
@akshay-krishnan akshay-krishnan changed the title Minimum Feedback Arc Set (MFAS) for 1dsfm [WIP] Minimum Feedback Arc Set (MFAS) for 1dsfm Jul 21, 2020
@akshay-krishnan akshay-krishnan requested review from varunagrawal and ayushbaid and removed request for binitshah July 21, 2020 14:57
Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Akshay, great first cut. A bunch of comments, but main one is this: I will need very strong arguments as to why any method would have to be non-const and you store intermediate results as instance variables. We try to be functional, even in C++ :-)

gtsam/sfm/MFAS.cpp Show resolved Hide resolved
gtsam/sfm/MFAS.h Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated Show resolved Hide resolved
gtsam/sfm/MFAS.h Outdated

// used to represent edges between two nodes in the graph
using KeyPair = std::pair<Key, Key>;
using TranslationEdges = std::map<KeyPair, Unit3>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe name it UnitTranslationEdges?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I do appreciate the name, I used the same that was used in TranslationRecovery.h for consistency. We might not use TranslationEdges now that we are changing the interface to vector of edges, so I'm not changing the name right now.

gtsam/sfm/MFAS.h Outdated
* @brief Construct from the nodes in a graph (points in 3D), edges
* that are translation directions in 3D and the direction in
* which edges are to be projected.
* @param nodes Nodes in the graph
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of nodes in a graph, we can add something more descriptive. Maybe something like pair of points in 3D?

Or are we making MFAS a generic class which works on any graph and not specific to SLAM?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the documentation to explain what these nodes represent when used in a translation averaging context.

gtsam/sfm/MFAS.h Show resolved Hide resolved
// in each iteration, one node is appended to the ordered list
while (orderedNodes_.size() < nodes_->size()) {

// finding the node with the max heuristic score
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate this in a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not used anywhere else, but yes, improves readability to factor that into a different function. I shall make that change in the next iteration if no other changes are suggested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a maxim that no function should have naked for loops, and no function should be bigger than 4 lines.

gtsam/sfm/MFAS.cpp Outdated Show resolved Hide resolved
@akshay-krishnan
Copy link
Contributor Author

akshay-krishnan commented Jul 22, 2020

Thank you for the comments @dellaert and @ayushbaid.

Prof. Frank, this class would be used on graphs with a large number of nodes (which represent cameras in our case). 1dsfm requires several instances of this class to compute the ordering for several sampled directions. These instances would run in parallel. I used the pointers for relativeTranslations_ and nodes_ to avoid storing/copying these large members for each instance. Yes, constant reference members would also serve the same purpose and I am happy to make that change, but for future reference please let me know why we prefer them.

@akshay-krishnan
Copy link
Contributor Author

I can also make most intermediate results internal to the methods, but its a little tricky for positiveEdgeWeights_. For some context, this is computed (but not returned) in computeOrdering, which is called by computeOutlierWeights. But then, computeOutlierWeights also needs positiveEdgeWeights_. Ultimately, 1dsfm only needs computeOutlierWeights, but I thought computeOrdering might be a nice method to have in the future. So the only way I could compute positiveEdgeWeights_ in computeOrdering and use it in computeOutlierWeights without making in an instance variable is through an optional pointer argument to computeOrdering. Is that okay? Otherwise, I could merge both of them into one large method, which I think is not very clean.

@dellaert
Copy link
Member

Thank you for the comments @dellaert and @ayushbaid.

Prof. Frank, this class would be used on graphs with a large number of nodes (which represent cameras in our case). 1dsfm requires several instances of this class to compute the ordering for several sampled directions. These instances would run in parallel. I used the pointers for relativeTranslations_ and nodes_ to avoid storing/copying these large members for each instance. Yes, constant reference members would also serve the same purpose and I am happy to make that change, but for future reference please let me know why we prefer them.

No, in that case shared pointer is fine, but then document it :-) although, I’m wondering about the design: why not have one class and run a method in parallel?

@dellaert
Copy link
Member

I can also make most intermediate results internal to the methods, but its a little tricky for positiveEdgeWeights_. For some context, this is computed (but not returned) in computeOrdering, which is called by computeOutlierWeights. But then, computeOutlierWeights also needs positiveEdgeWeights_. Ultimately, 1dsfm only needs computeOutlierWeights, but I thought computeOrdering might be a nice method to have in the future. So the only way I could compute positiveEdgeWeights_ in computeOrdering and use it in computeOutlierWeights without making in an instance variable is through an optional pointer argument to computeOrdering. Is that okay? Otherwise, I could merge both of them into one large method, which I think is not very clean.

Anything that is computed in the constructor is fine as an instance variable. The functional pattern we use is: only constructor is non-const.

@akshay-krishnan
Copy link
Contributor Author

Prof. @dellaert and @ayushbaid could you please re-review and let me know if more changes are necessary? I think it is better to make the change from the map to vector for representing edges in a separate PR.

Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to review as the code is hard to follow and rather c++03 :-/ maybe one more iteration?

// in each iteration, one node is appended to the ordered list
while (orderedNodes_.size() < nodes_->size()) {

// finding the node with the max heuristic score
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a maxim that no function should have naked for loops, and no function should be bigger than 4 lines.

}

// iterate over all edges
for (auto it = edgeWeights_.begin(); it != edgeWeights_.end(); it++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}
// find its in_neighbors, adjust their out_weights
for (auto it = in_neighbors[choice].begin();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use c++11 for loops, at least :-)

}
}
return outlier_weights;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add newline

}
}

std::vector<Key> MFAS::computeOrdering() const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe don’t go to four lines, but this function is indeed too long and complicated. Also ask yourself which STL algorithms you could be using.

@dellaert
Copy link
Member

@akshay-krishnan merge in develop before doing another push as we changed CI to GitHub actions...

@akshay-krishnan akshay-krishnan marked this pull request as draft September 12, 2020 20:30
@akshay-krishnan akshay-krishnan changed the title Minimum Feedback Arc Set (MFAS) for 1dsfm Minimum Feedback Arc Set (MFAS) for 1dsfm [WIP] Sep 12, 2020
@akshay-krishnan akshay-krishnan changed the title Minimum Feedback Arc Set (MFAS) for 1dsfm [WIP] Minimum Feedback Arc Set (MFAS) for 1dsfm Sep 12, 2020
@akshay-krishnan akshay-krishnan marked this pull request as ready for review September 12, 2020 22:33
@akshay-krishnan
Copy link
Contributor Author

@dellaert I have made some changes to improve the modularity and readability. Please re-review and let me know if there are more changes I can make.

@akshay-krishnan akshay-krishnan requested review from dellaert and removed request for varunagrawal September 18, 2020 02:04
Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@dellaert dellaert requested review from varunagrawal and removed request for ayushbaid and jingwuOUO September 18, 2020 12:33
@dellaert
Copy link
Member

@varunagrawal please approve and merge or comment on what is missing

Copy link
Collaborator

@varunagrawal varunagrawal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@varunagrawal varunagrawal merged commit 9350dd7 into develop Sep 18, 2020
@varunagrawal varunagrawal deleted the feature/1dsfm_mfas branch September 18, 2020 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New proposed feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants