Merge branch 'develop' into city10000
commit
69424c6b29
|
@ -68,12 +68,15 @@ class Experiment {
|
|||
|
||||
size_t maxNrHypotheses = 10;
|
||||
|
||||
size_t reLinearizationFrequency = 10;
|
||||
|
||||
double marginalThreshold = 0.9999;
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
HybridSmoother smoother_;
|
||||
HybridNonlinearFactorGraph graph_;
|
||||
HybridNonlinearFactorGraph newFactors_, allFactors_;
|
||||
Values initial_;
|
||||
Values result_;
|
||||
|
||||
/**
|
||||
* @brief Write the result of optimization to file.
|
||||
|
@ -83,7 +86,7 @@ class Experiment {
|
|||
* @param filename The file name to save the result to.
|
||||
*/
|
||||
void writeResult(const Values& result, size_t numPoses,
|
||||
const std::string& filename = "Hybrid_city10000.txt") {
|
||||
const std::string& filename = "Hybrid_city10000.txt") const {
|
||||
std::ofstream outfile;
|
||||
outfile.open(filename);
|
||||
|
||||
|
@ -100,9 +103,9 @@ class Experiment {
|
|||
* @brief Create a hybrid loop closure factor where
|
||||
* 0 - loose noise model and 1 - loop noise model.
|
||||
*/
|
||||
HybridNonlinearFactor hybridLoopClosureFactor(size_t loopCounter, size_t keyS,
|
||||
size_t keyT,
|
||||
const Pose2& measurement) {
|
||||
HybridNonlinearFactor hybridLoopClosureFactor(
|
||||
size_t loopCounter, size_t keyS, size_t keyT,
|
||||
const Pose2& measurement) const {
|
||||
DiscreteKey l(L(loopCounter), 2);
|
||||
|
||||
auto f0 = std::make_shared<BetweenFactor<Pose2>>(
|
||||
|
@ -119,7 +122,7 @@ class Experiment {
|
|||
/// @brief Create hybrid odometry factor with discrete measurement choices.
|
||||
HybridNonlinearFactor hybridOdometryFactor(
|
||||
size_t numMeasurements, size_t keyS, size_t keyT, const DiscreteKey& m,
|
||||
const std::vector<Pose2>& poseArray) {
|
||||
const std::vector<Pose2>& poseArray) const {
|
||||
auto f0 = std::make_shared<BetweenFactor<Pose2>>(
|
||||
X(keyS), X(keyT), poseArray[0], kPoseNoiseModel);
|
||||
auto f1 = std::make_shared<BetweenFactor<Pose2>>(
|
||||
|
@ -132,25 +135,59 @@ class Experiment {
|
|||
}
|
||||
|
||||
/// @brief Perform smoother update and optimize the graph.
|
||||
void smootherUpdate(HybridSmoother& smoother,
|
||||
HybridNonlinearFactorGraph& graph, const Values& initial,
|
||||
size_t maxNrHypotheses, Values* result) {
|
||||
HybridGaussianFactorGraph linearized = *graph.linearize(initial);
|
||||
smoother.update(linearized, maxNrHypotheses);
|
||||
// throw if x0 not in hybridBayesNet_:
|
||||
const KeySet& keys = smoother.hybridBayesNet().keys();
|
||||
if (keys.find(X(0)) == keys.end()) {
|
||||
throw std::runtime_error("x0 not in hybridBayesNet_");
|
||||
auto smootherUpdate(size_t maxNrHypotheses) {
|
||||
std::cout << "Smoother update: " << newFactors_.size() << std::endl;
|
||||
gttic_(SmootherUpdate);
|
||||
clock_t beforeUpdate = clock();
|
||||
auto linearized = newFactors_.linearize(initial_);
|
||||
smoother_.update(*linearized, maxNrHypotheses);
|
||||
allFactors_.push_back(newFactors_);
|
||||
newFactors_.resize(0);
|
||||
clock_t afterUpdate = clock();
|
||||
return afterUpdate - beforeUpdate;
|
||||
}
|
||||
|
||||
/// @brief Re-linearize, solve ALL, and re-initialize smoother.
|
||||
auto reInitialize() {
|
||||
std::cout << "================= Re-Initialize: " << allFactors_.size()
|
||||
<< std::endl;
|
||||
clock_t beforeUpdate = clock();
|
||||
allFactors_ = allFactors_.restrict(smoother_.fixedValues());
|
||||
auto linearized = allFactors_.linearize(initial_);
|
||||
auto bayesNet = linearized->eliminateSequential();
|
||||
HybridValues delta = bayesNet->optimize();
|
||||
initial_ = initial_.retract(delta.continuous());
|
||||
smoother_.reInitialize(std::move(*bayesNet));
|
||||
clock_t afterUpdate = clock();
|
||||
std::cout << "Took " << (afterUpdate - beforeUpdate) / CLOCKS_PER_SEC
|
||||
<< " seconds." << std::endl;
|
||||
return afterUpdate - beforeUpdate;
|
||||
}
|
||||
|
||||
// Parse line from file
|
||||
std::pair<std::vector<Pose2>, std::pair<size_t, size_t>> parseLine(
|
||||
const std::string& line) const {
|
||||
std::vector<std::string> parts;
|
||||
split(parts, line, is_any_of(" "));
|
||||
|
||||
size_t keyS = stoi(parts[1]);
|
||||
size_t keyT = stoi(parts[3]);
|
||||
|
||||
int numMeasurements = stoi(parts[5]);
|
||||
std::vector<Pose2> poseArray(numMeasurements);
|
||||
for (int i = 0; i < numMeasurements; ++i) {
|
||||
double x = stod(parts[6 + 3 * i]);
|
||||
double y = stod(parts[7 + 3 * i]);
|
||||
double rad = stod(parts[8 + 3 * i]);
|
||||
poseArray[i] = Pose2(x, y, rad);
|
||||
}
|
||||
graph.resize(0);
|
||||
// HybridValues delta = smoother.hybridBayesNet().optimize();
|
||||
// result->insert_or_assign(initial.retract(delta.continuous()));
|
||||
return {poseArray, {keyS, keyT}};
|
||||
}
|
||||
|
||||
public:
|
||||
/// Construct with filename of experiment to run
|
||||
explicit Experiment(const std::string& filename)
|
||||
: filename_(filename), smoother_(0.99) {}
|
||||
: filename_(filename), smoother_(marginalThreshold) {}
|
||||
|
||||
/// @brief Run the main experiment with a given maxLoopCount.
|
||||
void run() {
|
||||
|
@ -162,49 +199,34 @@ class Experiment {
|
|||
}
|
||||
|
||||
// Initialize local variables
|
||||
size_t discreteCount = 0, index = 0;
|
||||
size_t loopCount = 0;
|
||||
size_t discreteCount = 0, index = 0, loopCount = 0, updateCount = 0;
|
||||
|
||||
std::list<double> timeList;
|
||||
|
||||
// Set up initial prior
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
double rad = 0.0;
|
||||
|
||||
Pose2 priorPose(x, y, rad);
|
||||
Pose2 priorPose(0, 0, 0);
|
||||
initial_.insert(X(0), priorPose);
|
||||
graph_.push_back(PriorFactor<Pose2>(X(0), priorPose, kPriorNoiseModel));
|
||||
newFactors_.push_back(
|
||||
PriorFactor<Pose2>(X(0), priorPose, kPriorNoiseModel));
|
||||
|
||||
// Initial update
|
||||
clock_t beforeUpdate = clock();
|
||||
smootherUpdate(smoother_, graph_, initial_, maxNrHypotheses, &result_);
|
||||
clock_t afterUpdate = clock();
|
||||
auto time = smootherUpdate(maxNrHypotheses);
|
||||
std::vector<std::pair<size_t, double>> smootherUpdateTimes;
|
||||
smootherUpdateTimes.push_back({index, afterUpdate - beforeUpdate});
|
||||
smootherUpdateTimes.push_back({index, time});
|
||||
|
||||
// Flag to decide whether to run smoother update
|
||||
size_t numberOfHybridFactors = 0;
|
||||
|
||||
// Start main loop
|
||||
Values result;
|
||||
size_t keyS = 0, keyT = 0;
|
||||
clock_t startTime = clock();
|
||||
std::string line;
|
||||
while (getline(in, line) && index < maxLoopCount) {
|
||||
std::vector<std::string> parts;
|
||||
split(parts, line, is_any_of(" "));
|
||||
|
||||
keyS = stoi(parts[1]);
|
||||
keyT = stoi(parts[3]);
|
||||
|
||||
int numMeasurements = stoi(parts[5]);
|
||||
std::vector<Pose2> poseArray(numMeasurements);
|
||||
for (int i = 0; i < numMeasurements; ++i) {
|
||||
x = stod(parts[6 + 3 * i]);
|
||||
y = stod(parts[7 + 3 * i]);
|
||||
rad = stod(parts[8 + 3 * i]);
|
||||
poseArray[i] = Pose2(x, y, rad);
|
||||
}
|
||||
auto [poseArray, keys] = parseLine(line);
|
||||
keyS = keys.first;
|
||||
keyT = keys.second;
|
||||
size_t numMeasurements = poseArray.size();
|
||||
|
||||
// Take the first one as the initial estimate
|
||||
Pose2 odomPose = poseArray[0];
|
||||
|
@ -215,13 +237,13 @@ class Experiment {
|
|||
DiscreteKey m(M(discreteCount), numMeasurements);
|
||||
HybridNonlinearFactor mixtureFactor =
|
||||
hybridOdometryFactor(numMeasurements, keyS, keyT, m, poseArray);
|
||||
graph_.push_back(mixtureFactor);
|
||||
newFactors_.push_back(mixtureFactor);
|
||||
discreteCount++;
|
||||
numberOfHybridFactors += 1;
|
||||
std::cout << "mixtureFactor: " << keyS << " " << keyT << std::endl;
|
||||
} else {
|
||||
graph_.add(BetweenFactor<Pose2>(X(keyS), X(keyT), odomPose,
|
||||
kPoseNoiseModel));
|
||||
newFactors_.add(BetweenFactor<Pose2>(X(keyS), X(keyT), odomPose,
|
||||
kPoseNoiseModel));
|
||||
}
|
||||
// Insert next pose initial guess
|
||||
initial_.insert(X(keyT), initial_.at<Pose2>(X(keyS)) * odomPose);
|
||||
|
@ -231,21 +253,20 @@ class Experiment {
|
|||
hybridLoopClosureFactor(loopCount, keyS, keyT, odomPose);
|
||||
// print loop closure event keys:
|
||||
std::cout << "Loop closure: " << keyS << " " << keyT << std::endl;
|
||||
graph_.add(loopFactor);
|
||||
newFactors_.add(loopFactor);
|
||||
numberOfHybridFactors += 1;
|
||||
loopCount++;
|
||||
}
|
||||
|
||||
if (numberOfHybridFactors >= updateFrequency) {
|
||||
// print the keys involved in the smoother update
|
||||
std::cout << "Smoother update: " << graph_.size() << std::endl;
|
||||
gttic_(SmootherUpdate);
|
||||
beforeUpdate = clock();
|
||||
smootherUpdate(smoother_, graph_, initial_, maxNrHypotheses, &result_);
|
||||
afterUpdate = clock();
|
||||
smootherUpdateTimes.push_back({index, afterUpdate - beforeUpdate});
|
||||
gttoc_(SmootherUpdate);
|
||||
auto time = smootherUpdate(maxNrHypotheses);
|
||||
smootherUpdateTimes.push_back({index, time});
|
||||
numberOfHybridFactors = 0;
|
||||
updateCount++;
|
||||
|
||||
if (updateCount % reLinearizationFrequency == 0) {
|
||||
reInitialize();
|
||||
}
|
||||
}
|
||||
|
||||
// Record timing for odometry edges only
|
||||
|
@ -270,17 +291,15 @@ class Experiment {
|
|||
}
|
||||
|
||||
// Final update
|
||||
beforeUpdate = clock();
|
||||
smootherUpdate(smoother_, graph_, initial_, maxNrHypotheses, &result_);
|
||||
afterUpdate = clock();
|
||||
smootherUpdateTimes.push_back({index, afterUpdate - beforeUpdate});
|
||||
time = smootherUpdate(maxNrHypotheses);
|
||||
smootherUpdateTimes.push_back({index, time});
|
||||
|
||||
// Final optimize
|
||||
gttic_(HybridSmootherOptimize);
|
||||
HybridValues delta = smoother_.optimize();
|
||||
gttoc_(HybridSmootherOptimize);
|
||||
|
||||
result_.insert_or_assign(initial_.retract(delta.continuous()));
|
||||
result.insert_or_assign(initial_.retract(delta.continuous()));
|
||||
|
||||
std::cout << "Final error: " << smoother_.hybridBayesNet().error(delta)
|
||||
<< std::endl;
|
||||
|
@ -291,7 +310,7 @@ class Experiment {
|
|||
<< std::endl;
|
||||
|
||||
// Write results to file
|
||||
writeResult(result_, keyT + 1, "Hybrid_City10000.txt");
|
||||
writeResult(result, keyT + 1, "Hybrid_City10000.txt");
|
||||
|
||||
// TODO Write to file
|
||||
// for (size_t i = 0; i < smoother_update_times.size(); i++) {
|
||||
|
|
|
@ -393,6 +393,13 @@ namespace gtsam {
|
|||
return DecisionTree(newRoot);
|
||||
}
|
||||
|
||||
/** Choose multiple values. */
|
||||
DecisionTree restrict(const Assignment<L>& assignment) const {
|
||||
NodePtr newRoot = root_;
|
||||
for (const auto& [l, v] : assignment) newRoot = newRoot->choose(l, v);
|
||||
return DecisionTree(newRoot);
|
||||
}
|
||||
|
||||
/** combine subtrees on key with binary operation "op" */
|
||||
DecisionTree combine(const L& label, size_t cardinality,
|
||||
const Binary& op) const;
|
||||
|
|
|
@ -540,5 +540,11 @@ namespace gtsam {
|
|||
return DecisionTreeFactor(this->discreteKeys(), thresholded);
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
DiscreteFactor::shared_ptr DecisionTreeFactor::restrict(
|
||||
const DiscreteValues& assignment) const {
|
||||
throw std::runtime_error("DecisionTreeFactor::restrict not implemented");
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -220,6 +220,10 @@ namespace gtsam {
|
|||
return combine(keys, Ring::max);
|
||||
}
|
||||
|
||||
/// Restrict the factor to the given assignment.
|
||||
DiscreteFactor::shared_ptr restrict(
|
||||
const DiscreteValues& assignment) const override;
|
||||
|
||||
/// @}
|
||||
/// @name Advanced Interface
|
||||
/// @{
|
||||
|
|
|
@ -167,8 +167,8 @@ class GTSAM_EXPORT DiscreteFactor : public Factor {
|
|||
/**
|
||||
* @brief Scale the factor values by the maximum
|
||||
* to prevent underflow/overflow.
|
||||
*
|
||||
* @return DiscreteFactor::shared_ptr
|
||||
*
|
||||
* @return DiscreteFactor::shared_ptr
|
||||
*/
|
||||
DiscreteFactor::shared_ptr scale() const;
|
||||
|
||||
|
@ -178,6 +178,10 @@ class GTSAM_EXPORT DiscreteFactor : public Factor {
|
|||
*/
|
||||
virtual uint64_t nrValues() const = 0;
|
||||
|
||||
/// Restrict the factor to the given assignment.
|
||||
virtual DiscreteFactor::shared_ptr restrict(
|
||||
const DiscreteValues& assignment) const = 0;
|
||||
|
||||
/// @}
|
||||
/// @name Wrapper support
|
||||
/// @{
|
||||
|
|
|
@ -391,12 +391,12 @@ void TableFactor::print(const string& s, const KeyFormatter& formatter) const {
|
|||
|
||||
/* ************************************************************************ */
|
||||
DiscreteFactor::shared_ptr TableFactor::sum(size_t nrFrontals) const {
|
||||
return combine(nrFrontals, Ring::add);
|
||||
return combine(nrFrontals, Ring::add);
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
DiscreteFactor::shared_ptr TableFactor::sum(const Ordering& keys) const {
|
||||
return combine(keys, Ring::add);
|
||||
return combine(keys, Ring::add);
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
|
@ -418,7 +418,6 @@ DiscreteFactor::shared_ptr TableFactor::max(const Ordering& keys) const {
|
|||
return combine(keys, Ring::max);
|
||||
}
|
||||
|
||||
|
||||
/* ************************************************************************ */
|
||||
TableFactor TableFactor::apply(Unary op) const {
|
||||
// Initialize new factor.
|
||||
|
@ -781,5 +780,11 @@ TableFactor TableFactor::prune(size_t maxNrAssignments) const {
|
|||
return TableFactor(this->discreteKeys(), pruned_vec);
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
DiscreteFactor::shared_ptr TableFactor::restrict(
|
||||
const DiscreteValues& assignment) const {
|
||||
throw std::runtime_error("TableFactor::restrict not implemented");
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -342,6 +342,10 @@ class GTSAM_EXPORT TableFactor : public DiscreteFactor {
|
|||
*/
|
||||
uint64_t nrValues() const override { return sparse_table_.nonZeros(); }
|
||||
|
||||
/// Restrict the factor to the given assignment.
|
||||
DiscreteFactor::shared_ptr restrict(
|
||||
const DiscreteValues& assignment) const override;
|
||||
|
||||
/// @}
|
||||
/// @name Wrapper support
|
||||
/// @{
|
||||
|
|
|
@ -59,21 +59,30 @@ TEST(ADT, arithmetic) {
|
|||
|
||||
// Negate and subtraction
|
||||
CHECK(assert_equal(-a, zero - a));
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
CHECK(assert_equal({zero}, a - a));
|
||||
#else
|
||||
CHECK(assert_equal({A, 0, 0}, a - a));
|
||||
#endif
|
||||
CHECK(assert_equal(a + b, b + a));
|
||||
CHECK(assert_equal({A, 3, 4}, a + 2));
|
||||
CHECK(assert_equal({B, 1, 2}, b - 2));
|
||||
|
||||
// Multiplication
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
CHECK(assert_equal(zero, zero * a));
|
||||
CHECK(assert_equal(zero, a * zero));
|
||||
#else
|
||||
CHECK(assert_equal({A, 0, 0}, zero * a));
|
||||
#endif
|
||||
CHECK(assert_equal(a, one * a));
|
||||
CHECK(assert_equal(a, a * one));
|
||||
CHECK(assert_equal(a * b, b * a));
|
||||
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
// division
|
||||
// CHECK(assert_equal(a, (a * b) / b)); // not true because no pruning
|
||||
CHECK(assert_equal(b, (a * b) / a));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
|
|
@ -228,9 +228,9 @@ TEST(DecisionTree, Example) {
|
|||
// Test choose 0
|
||||
DT actual0 = notba.choose(A, 0);
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
EXPECT(assert_equal(DT(0.0), actual0));
|
||||
EXPECT(assert_equal(DT(0), actual0));
|
||||
#else
|
||||
EXPECT(assert_equal(DT({0.0, 0.0}), actual0));
|
||||
EXPECT(assert_equal(DT(B, 0, 0), actual0));
|
||||
#endif
|
||||
DOT(actual0);
|
||||
|
||||
|
@ -618,6 +618,21 @@ TEST(DecisionTree, ApplyWithAssignment) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
// Test apply with assignment.
|
||||
TEST(DecisionTree, Restrict) {
|
||||
// Create three level tree
|
||||
const vector<DT::LabelC> keys{DT::LabelC("C", 2), DT::LabelC("B", 2),
|
||||
DT::LabelC("A", 2)};
|
||||
DT tree(keys, "1 2 3 4 5 6 7 8");
|
||||
|
||||
DT restrictedTree = tree.restrict({{"A", 0}, {"B", 1}});
|
||||
EXPECT(assert_equal(DT({DT::LabelC("C", 2)}, "3 7"), restrictedTree));
|
||||
|
||||
DT restrictMore = tree.restrict({{"A", 1}, {"B", 1}, {"C", 1}});
|
||||
EXPECT(assert_equal(DT(8), restrictMore));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
int main() {
|
||||
TestResult tr;
|
||||
|
|
|
@ -124,8 +124,10 @@ TEST(DecisionTreeFactor, Divide) {
|
|||
EXPECT(assert_inequal(pS, s));
|
||||
|
||||
// The underlying data should be the same
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
using ADT = AlgebraicDecisionTree<Key>;
|
||||
EXPECT(assert_equal(ADT(pS), ADT(s)));
|
||||
#endif
|
||||
|
||||
KeySet keys(joint.keys());
|
||||
keys.insert(pA.keys().begin(), pA.keys().end());
|
||||
|
|
|
@ -69,11 +69,13 @@ HybridBayesNet HybridBayesNet::prune(
|
|||
|
||||
// Go through all the Gaussian conditionals, restrict them according to
|
||||
// fixed values, and then prune further.
|
||||
for (std::shared_ptr<gtsam::HybridConditional> conditional : *this) {
|
||||
for (std::shared_ptr<HybridConditional> conditional : *this) {
|
||||
if (conditional->isDiscrete()) continue;
|
||||
|
||||
// No-op if not a HybridGaussianConditional.
|
||||
if (marginalThreshold) conditional = conditional->restrict(fixed);
|
||||
if (marginalThreshold)
|
||||
conditional = std::static_pointer_cast<HybridConditional>(
|
||||
conditional->restrict(fixed));
|
||||
|
||||
// Now decide on type what to do:
|
||||
if (auto hgc = conditional->asHybrid()) {
|
||||
|
|
|
@ -170,8 +170,8 @@ double HybridConditional::evaluate(const HybridValues &values) const {
|
|||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
HybridConditional::shared_ptr HybridConditional::restrict(
|
||||
const DiscreteValues &discreteValues) const {
|
||||
std::shared_ptr<Factor> HybridConditional::restrict(
|
||||
const DiscreteValues &assignment) const {
|
||||
if (auto gc = asGaussian()) {
|
||||
return std::make_shared<HybridConditional>(gc);
|
||||
} else if (auto dc = asDiscrete()) {
|
||||
|
@ -184,21 +184,20 @@ HybridConditional::shared_ptr HybridConditional::restrict(
|
|||
"HybridConditional::restrict: conditional type not handled");
|
||||
|
||||
// Case 1: Fully determined, return corresponding Gaussian conditional
|
||||
auto parentValues = discreteValues.filter(discreteKeys_);
|
||||
auto parentValues = assignment.filter(discreteKeys_);
|
||||
if (parentValues.size() == discreteKeys_.size()) {
|
||||
return std::make_shared<HybridConditional>(hgc->choose(parentValues));
|
||||
}
|
||||
|
||||
// Case 2: Some live parents remain, build a new tree
|
||||
auto unspecifiedParentKeys = discreteValues.missingKeys(discreteKeys_);
|
||||
if (!unspecifiedParentKeys.empty()) {
|
||||
auto remainingKeys = assignment.missingKeys(discreteKeys_);
|
||||
if (!remainingKeys.empty()) {
|
||||
auto newTree = hgc->factors();
|
||||
for (const auto &[key, value] : parentValues) {
|
||||
newTree = newTree.choose(key, value);
|
||||
}
|
||||
return std::make_shared<HybridConditional>(
|
||||
std::make_shared<HybridGaussianConditional>(unspecifiedParentKeys,
|
||||
newTree));
|
||||
std::make_shared<HybridGaussianConditional>(remainingKeys, newTree));
|
||||
}
|
||||
|
||||
// Case 3: No changes needed, return original
|
||||
|
|
|
@ -153,7 +153,8 @@ class GTSAM_EXPORT HybridConditional
|
|||
* @return HybridGaussianConditional::shared_ptr otherwise
|
||||
*/
|
||||
HybridGaussianConditional::shared_ptr asHybrid() const {
|
||||
return std::dynamic_pointer_cast<HybridGaussianConditional>(inner_);
|
||||
if (!isHybrid()) return nullptr;
|
||||
return std::static_pointer_cast<HybridGaussianConditional>(inner_);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +163,8 @@ class GTSAM_EXPORT HybridConditional
|
|||
* @return GaussianConditional::shared_ptr otherwise
|
||||
*/
|
||||
GaussianConditional::shared_ptr asGaussian() const {
|
||||
return std::dynamic_pointer_cast<GaussianConditional>(inner_);
|
||||
if (!isContinuous()) return nullptr;
|
||||
return std::static_pointer_cast<GaussianConditional>(inner_);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,7 +174,8 @@ class GTSAM_EXPORT HybridConditional
|
|||
*/
|
||||
template <typename T = DiscreteConditional>
|
||||
typename T::shared_ptr asDiscrete() const {
|
||||
return std::dynamic_pointer_cast<T>(inner_);
|
||||
if (!isDiscrete()) return nullptr;
|
||||
return std::static_pointer_cast<T>(inner_);
|
||||
}
|
||||
|
||||
/// Get the type-erased pointer to the inner type
|
||||
|
@ -221,7 +224,8 @@ class GTSAM_EXPORT HybridConditional
|
|||
* which is just a GaussianConditional. If this conditional is *not* a hybrid
|
||||
* conditional, just return that.
|
||||
*/
|
||||
shared_ptr restrict(const DiscreteValues& discreteValues) const;
|
||||
std::shared_ptr<Factor> restrict(
|
||||
const DiscreteValues& assignment) const override;
|
||||
|
||||
/// @}
|
||||
|
||||
|
|
|
@ -133,10 +133,14 @@ class GTSAM_EXPORT HybridFactor : public Factor {
|
|||
/// Return only the continuous keys for this factor.
|
||||
const KeyVector &continuousKeys() const { return continuousKeys_; }
|
||||
|
||||
/// Virtual class to compute tree of linear errors.
|
||||
/// Compute tree of linear errors.
|
||||
virtual AlgebraicDecisionTree<Key> errorTree(
|
||||
const VectorValues &values) const = 0;
|
||||
|
||||
/// Restrict the factor to the given discrete values.
|
||||
virtual std::shared_ptr<Factor> restrict(
|
||||
const DiscreteValues &discreteValues) const = 0;
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
|
|
|
@ -363,4 +363,12 @@ double HybridGaussianConditional::evaluate(const HybridValues &values) const {
|
|||
return conditional->evaluate(values.continuous());
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
std::shared_ptr<Factor> HybridGaussianConditional::restrict(
|
||||
const DiscreteValues &assignment) const {
|
||||
throw std::runtime_error(
|
||||
"HybridGaussianConditional::restrict not implemented");
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -241,6 +241,10 @@ class GTSAM_EXPORT HybridGaussianConditional
|
|||
/// Return true if the conditional has already been pruned.
|
||||
bool pruned() const { return pruned_; }
|
||||
|
||||
/// Restrict to the given discrete values.
|
||||
std::shared_ptr<Factor> restrict(
|
||||
const DiscreteValues &discreteValues) const override;
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
|
|
|
@ -199,4 +199,12 @@ double HybridGaussianFactor::error(const HybridValues& values) const {
|
|||
return PotentiallyPrunedComponentError(pair, values.continuous());
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
std::shared_ptr<Factor> HybridGaussianFactor::restrict(
|
||||
const DiscreteValues& assignment) const {
|
||||
throw std::runtime_error("HybridGaussianFactor::restrict not implemented");
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -157,6 +157,10 @@ class GTSAM_EXPORT HybridGaussianFactor : public HybridFactor {
|
|||
*/
|
||||
virtual HybridGaussianProductFactor asProductFactor() const;
|
||||
|
||||
/// Restrict the factor to the given discrete values.
|
||||
std::shared_ptr<Factor> restrict(
|
||||
const DiscreteValues &discreteValues) const override;
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
|
|
|
@ -239,4 +239,21 @@ HybridNonlinearFactor::shared_ptr HybridNonlinearFactor::prune(
|
|||
return std::make_shared<HybridNonlinearFactor>(discreteKeys(), prunedFactors);
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
std::shared_ptr<Factor> HybridNonlinearFactor::restrict(
|
||||
const DiscreteValues& assignment) const {
|
||||
auto restrictedFactors = factors_.restrict(assignment);
|
||||
auto filtered = assignment.filter(discreteKeys_);
|
||||
if (filtered.size() == discreteKeys_.size()) {
|
||||
auto [nonlinearFactor, val] = factors_(filtered);
|
||||
return nonlinearFactor;
|
||||
} else {
|
||||
auto remainingKeys = assignment.missingKeys(discreteKeys());
|
||||
return std::make_shared<HybridNonlinearFactor>(remainingKeys,
|
||||
factors_.restrict(filtered));
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -80,6 +80,9 @@ class GTSAM_EXPORT HybridNonlinearFactor : public HybridFactor {
|
|||
}
|
||||
|
||||
public:
|
||||
/// @name Constructors
|
||||
/// @{
|
||||
|
||||
/// Default constructor, mainly for serialization.
|
||||
HybridNonlinearFactor() = default;
|
||||
|
||||
|
@ -137,7 +140,7 @@ class GTSAM_EXPORT HybridNonlinearFactor : public HybridFactor {
|
|||
* @return double The error of this factor.
|
||||
*/
|
||||
double error(const Values& continuousValues,
|
||||
const DiscreteValues& discreteValues) const;
|
||||
const DiscreteValues& assignment) const;
|
||||
|
||||
/**
|
||||
* @brief Compute error of factor given hybrid values.
|
||||
|
@ -154,7 +157,8 @@ class GTSAM_EXPORT HybridNonlinearFactor : public HybridFactor {
|
|||
*/
|
||||
size_t dim() const;
|
||||
|
||||
/// Testable
|
||||
/// @}
|
||||
/// @name Testable
|
||||
/// @{
|
||||
|
||||
/// print to stdout
|
||||
|
@ -165,15 +169,16 @@ class GTSAM_EXPORT HybridNonlinearFactor : public HybridFactor {
|
|||
bool equals(const HybridFactor& other, double tol = 1e-9) const override;
|
||||
|
||||
/// @}
|
||||
/// @name Standard API
|
||||
/// @{
|
||||
|
||||
/// Getter for NonlinearFactor decision tree
|
||||
const FactorValuePairs& factors() const { return factors_; }
|
||||
|
||||
/// Linearize specific nonlinear factors based on the assignment in
|
||||
/// discreteValues.
|
||||
GaussianFactor::shared_ptr linearize(
|
||||
const Values& continuousValues,
|
||||
const DiscreteValues& discreteValues) const;
|
||||
GaussianFactor::shared_ptr linearize(const Values& continuousValues,
|
||||
const DiscreteValues& assignment) const;
|
||||
|
||||
/// Linearize all the continuous factors to get a HybridGaussianFactor.
|
||||
std::shared_ptr<HybridGaussianFactor> linearize(
|
||||
|
@ -183,6 +188,12 @@ class GTSAM_EXPORT HybridNonlinearFactor : public HybridFactor {
|
|||
HybridNonlinearFactor::shared_ptr prune(
|
||||
const DecisionTreeFactor& discreteProbs) const;
|
||||
|
||||
/// Restrict the factor to the given discrete values.
|
||||
std::shared_ptr<Factor> restrict(
|
||||
const DiscreteValues& assignment) const override;
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
/// Helper struct to assist private constructor below.
|
||||
struct ConstructorHelper;
|
||||
|
|
|
@ -221,5 +221,30 @@ AlgebraicDecisionTree<Key> HybridNonlinearFactorGraph::discretePosterior(
|
|||
return p / p.sum();
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
HybridNonlinearFactorGraph HybridNonlinearFactorGraph::restrict(
|
||||
const DiscreteValues& discreteValues) const {
|
||||
using std::dynamic_pointer_cast;
|
||||
|
||||
HybridNonlinearFactorGraph result;
|
||||
result.reserve(size());
|
||||
for (auto& f : factors_) {
|
||||
// First check if it is a valid factor
|
||||
if (!f) {
|
||||
continue;
|
||||
}
|
||||
// Check if it is a hybrid factor
|
||||
if (auto hf = dynamic_pointer_cast<HybridFactor>(f)) {
|
||||
result.push_back(hf->restrict(discreteValues));
|
||||
} else if (auto df = dynamic_pointer_cast<DiscreteFactor>(f)) {
|
||||
result.push_back(df->restrict(discreteValues));
|
||||
} else {
|
||||
result.push_back(f); // Everything else is just added as is
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ************************************************************************ */
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -116,6 +116,10 @@ class GTSAM_EXPORT HybridNonlinearFactorGraph : public HybridFactorGraph {
|
|||
AlgebraicDecisionTree<Key> discretePosterior(
|
||||
const Values& continuousValues) const;
|
||||
|
||||
/// Restrict all factors in the graph to the given discrete values.
|
||||
HybridNonlinearFactorGraph restrict(
|
||||
const DiscreteValues& assignment) const;
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ namespace gtsam {
|
|||
class GTSAM_EXPORT HybridSmoother {
|
||||
private:
|
||||
HybridBayesNet hybridBayesNet_;
|
||||
HybridGaussianFactorGraph remainingFactorGraph_;
|
||||
|
||||
/// The threshold above which we make a decision about a mode.
|
||||
std::optional<double> marginalThreshold_;
|
||||
|
@ -44,6 +43,16 @@ class GTSAM_EXPORT HybridSmoother {
|
|||
HybridSmoother(const std::optional<double> marginalThreshold = {})
|
||||
: marginalThreshold_(marginalThreshold) {}
|
||||
|
||||
/// Return fixed values:
|
||||
const DiscreteValues& fixedValues() const { return fixedValues_; }
|
||||
|
||||
/**
|
||||
* Re-initialize the smoother from a new hybrid Bayes Net.
|
||||
*/
|
||||
void reInitialize(HybridBayesNet&& hybridBayesNet) {
|
||||
hybridBayesNet_ = std::move(hybridBayesNet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given new factors, perform an incremental update.
|
||||
* The relevant densities in the `hybridBayesNet` will be added to the input
|
||||
|
|
|
@ -318,21 +318,27 @@ TEST(HybridGaussianConditional, Restrict) {
|
|||
const auto hc =
|
||||
std::make_shared<HybridConditional>(two_mode_measurement::hgc);
|
||||
|
||||
const HybridConditional::shared_ptr same = hc->restrict({});
|
||||
const auto same =
|
||||
std::dynamic_pointer_cast<HybridConditional>(hc->restrict({}));
|
||||
CHECK(same);
|
||||
EXPECT(same->isHybrid());
|
||||
EXPECT(same->asHybrid()->nrComponents() == 4);
|
||||
|
||||
const HybridConditional::shared_ptr oneParent = hc->restrict({{M(1), 0}});
|
||||
const auto oneParent =
|
||||
std::dynamic_pointer_cast<HybridConditional>(hc->restrict({{M(1), 0}}));
|
||||
CHECK(oneParent);
|
||||
EXPECT(oneParent->isHybrid());
|
||||
EXPECT(oneParent->asHybrid()->nrComponents() == 2);
|
||||
|
||||
const HybridConditional::shared_ptr oneParent2 =
|
||||
hc->restrict({{M(7), 0}, {M(1), 0}});
|
||||
const auto oneParent2 = std::dynamic_pointer_cast<HybridConditional>(
|
||||
hc->restrict({{M(7), 0}, {M(1), 0}}));
|
||||
CHECK(oneParent2);
|
||||
EXPECT(oneParent2->isHybrid());
|
||||
EXPECT(oneParent2->asHybrid()->nrComponents() == 2);
|
||||
|
||||
const HybridConditional::shared_ptr gaussian =
|
||||
hc->restrict({{M(1), 0}, {M(2), 1}});
|
||||
const auto gaussian = std::dynamic_pointer_cast<HybridConditional>(
|
||||
hc->restrict({{M(1), 0}, {M(2), 1}}));
|
||||
CHECK(gaussian);
|
||||
EXPECT(gaussian->asGaussian());
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,18 @@ TEST(HybridNonlinearFactor, Dim) {
|
|||
EXPECT_LONGS_EQUAL(1, hybridFactor.dim());
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
// Test restrict method
|
||||
TEST(HybridNonlinearFactor, Restrict) {
|
||||
using namespace test_constructor;
|
||||
HybridNonlinearFactor factor(m1, {f0, f1});
|
||||
DiscreteValues assignment = {{m1.first, 0}};
|
||||
auto restricted = factor.restrict(assignment);
|
||||
auto betweenFactor = dynamic_pointer_cast<BetweenFactor<double>>(restricted);
|
||||
CHECK(betweenFactor);
|
||||
EXPECT(assert_equal(*f0, *betweenFactor));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
int main() {
|
||||
TestResult tr;
|
||||
|
|
|
@ -102,12 +102,16 @@ TEST(HybridSmoother, IncrementalSmoother) {
|
|||
graph.resize(0);
|
||||
}
|
||||
|
||||
EXPECT_LONGS_EQUAL(11,
|
||||
smoother.hybridBayesNet().at(5)->asDiscrete()->nrValues());
|
||||
auto& hybridBayesNet = smoother.hybridBayesNet();
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
EXPECT_LONGS_EQUAL(11, hybridBayesNet.at(5)->asDiscrete()->nrValues());
|
||||
#else
|
||||
EXPECT_LONGS_EQUAL(16, hybridBayesNet.at(5)->asDiscrete()->nrValues());
|
||||
#endif
|
||||
|
||||
// Get the continuous delta update as well as
|
||||
// the optimal discrete assignment.
|
||||
HybridValues delta = smoother.hybridBayesNet().optimize();
|
||||
HybridValues delta = hybridBayesNet.optimize();
|
||||
|
||||
// Check discrete assignment
|
||||
DiscreteValues expected_discrete;
|
||||
|
@ -156,8 +160,12 @@ TEST(HybridSmoother, ValidPruningError) {
|
|||
graph.resize(0);
|
||||
}
|
||||
|
||||
EXPECT_LONGS_EQUAL(14,
|
||||
smoother.hybridBayesNet().at(8)->asDiscrete()->nrValues());
|
||||
auto& hybridBayesNet = smoother.hybridBayesNet();
|
||||
#ifdef GTSAM_DT_MERGING
|
||||
EXPECT_LONGS_EQUAL(14, hybridBayesNet.at(8)->asDiscrete()->nrValues());
|
||||
#else
|
||||
EXPECT_LONGS_EQUAL(128, hybridBayesNet.at(8)->asDiscrete()->nrValues());
|
||||
#endif
|
||||
|
||||
// Get the continuous delta update as well as
|
||||
// the optimal discrete assignment.
|
||||
|
|
|
@ -53,11 +53,6 @@ class GTSAM_UNSTABLE_EXPORT AllDiff : public Constraint {
|
|||
/// Multiply into a decisiontree
|
||||
DecisionTreeFactor operator*(const DecisionTreeFactor& f) const override;
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
AlgebraicDecisionTree<Key> errorTree() const override {
|
||||
throw std::runtime_error("AllDiff::error not implemented");
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure Arc-consistency by checking every possible value of domain j.
|
||||
* @param j domain to be checked
|
||||
|
|
|
@ -91,11 +91,6 @@ class BinaryAllDiff : public Constraint {
|
|||
const Domains&) const override {
|
||||
throw std::runtime_error("BinaryAllDiff::partiallyApply not implemented");
|
||||
}
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
AlgebraicDecisionTree<Key> errorTree() const override {
|
||||
throw std::runtime_error("BinaryAllDiff::error not implemented");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gtsam
|
||||
|
|
|
@ -125,6 +125,17 @@ class GTSAM_UNSTABLE_EXPORT Constraint : public DiscreteFactor {
|
|||
return toDecisionTreeFactor().max(keys);
|
||||
}
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
AlgebraicDecisionTree<Key> errorTree() const override {
|
||||
throw std::runtime_error("Constraint::error not implemented");
|
||||
}
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
DiscreteFactor::shared_ptr restrict(
|
||||
const DiscreteValues& assignment) const override {
|
||||
throw std::runtime_error("Constraint::restrict not implemented");
|
||||
}
|
||||
|
||||
/// @}
|
||||
/// @name Wrapper support
|
||||
/// @{
|
||||
|
|
|
@ -69,11 +69,6 @@ class GTSAM_UNSTABLE_EXPORT Domain : public Constraint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
AlgebraicDecisionTree<Key> errorTree() const override {
|
||||
throw std::runtime_error("Domain::error not implemented");
|
||||
}
|
||||
|
||||
// Return concise string representation, mostly to debug arc consistency.
|
||||
// Converts from base 0 to base1.
|
||||
std::string base1Str() const;
|
||||
|
|
|
@ -49,11 +49,6 @@ class GTSAM_UNSTABLE_EXPORT SingleValue : public Constraint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compute error for each assignment and return as a tree
|
||||
AlgebraicDecisionTree<Key> errorTree() const override {
|
||||
throw std::runtime_error("SingleValue::error not implemented");
|
||||
}
|
||||
|
||||
/// Calculate value
|
||||
double evaluate(const Assignment<Key>& values) const override;
|
||||
|
||||
|
|
Loading…
Reference in New Issue