Commit 75ee5b12 by JasonPries

### Start of robustifying Sketch::solve()

parent 44d05ecd
 ... ... @@ -15,7 +15,19 @@ public: CircularArc(CircularArc const *c) : Curve(c->Start, c->End, c->ForConstruction), Center(c->Center), Radius(c->Radius) {}; CircularArc(std::shared_ptr v0, std::shared_ptr v1, std::shared_ptr c, bool fc = false) : Curve(v0, v1, fc), Center(c) {}; CircularArc(std::shared_ptr v0, std::shared_ptr v1, std::shared_ptr c, bool fc = false) : Curve(v0, v1, fc), Center(c) { double xc = c->x(); double yc = c->y(); double x0 = v0->x(); double y0 = v0->y(); double x1 = v1->x(); double y1 = v1->y(); double r0 = sqrt((xc - x0) * (xc - x0) + (yc - y0) * (yc - y0)); double r1 = sqrt((xc - x1) * (xc - x1) + (yc - y1) * (yc - y1)); Radius = std::make_shared((r0 + r1) / 2.0); }; CircularArc(std::shared_ptr v0, std::shared_ptr v1, std::shared_ptr c, double r, bool fc = false) : Curve(v0, v1, fc), Center(c), Radius(std::make_shared(r)) {}; ... ... @@ -34,7 +46,7 @@ public: double da(double s, bool orientation) const override; double length() const override { throw; }; double length() const override { return radius() * arc_angle(); }; double radius() const { return Radius->value(); }; ... ...
 #include #include "Sketch.h" #include "Vertex.h" #include "Curve.h" ... ... @@ -10,47 +12,159 @@ #include "Variable.h" #include "doublen.h" #include double Sketch::solve() { // #TODO: Tolerance based iteration convergence monitoring // #TODO: Use sparse matrix representation for Jacobian // TODO: Time to finish the bells and whistles here // TODO: Use sparse matrix representation for Jacobian, (this is lower priority since typical uses will not produce very large matrices) // TODO: Add residual instrumentation to tests if (NumVariables < NumEquations) { std::cerr << "Variables = " << NumVariables << ", Equations = " << NumEquations << ". Number of variables is less than the number of equations. The sketch may be over-constrained." << std::endl; } Eigen::VectorXd delta = Eigen::VectorXd::Zero(NumVariables); Eigen::VectorXd x = Eigen::VectorXd::Zero(NumVariables); Eigen::VectorXd r = Eigen::VectorXd::Zero(NumEquations); Eigen::VectorXd dr = Eigen::VectorXd::Zero(NumEquations); Eigen::MatrixXd J = Eigen::MatrixXd::Zero(NumEquations, NumVariables); Eigen::MatrixXd JJT = Eigen::MatrixXd::Zero(NumEquations, NumEquations); Eigen::FullPivLU LU; for (size_t j = 0; j < 32; ++j) { // TODO: Use QR or LDLT decomposition instead // TODO: Check for over/underdetermined system // TODO: Try extracting sub matrix and solving // TODO: Extract newton_update over Verticies, Curves, and Constraints into a private method J.setZero(); //Eigen::MatrixXd JJT = Eigen::MatrixXd::Zero(NumEquations, NumEquations); //Eigen::FullPivHouseholderQR QR; //Eigen::FullPivLU LU; // Calculate scale parameter for tolerances double scale = 0.0; if (Curves.size() > 0) { for (size_t i = 0;i!= Curves.size();++i){ scale = std::max(scale, Curves[i]->length()); } } else if (Verticies.size() > 0) { double vmax = DBL_MIN; double vmin = DBL_MAX; for (size_t i = 0; i != Verticies.size(); ++i) { Verticies[i]->update(J, r); vmax = std::max(vmax, Verticies[i]->x()); vmin = std::min(vmin, Verticies[i]->x()); vmax = std::max(vmax, Verticies[i]->y()); vmin = std::max(vmin, Verticies[i]->y()); } for (size_t i = 0; i != Curves.size(); ++i) { Curves[i]->update(J, r); if (vmax != vmin) { scale = vmax - vmin; } else { scale = 1.0; } } // Initial residual calculation for (size_t i = 0; i != Verticies.size(); ++i) { Verticies[i]->update(J, r); } for (size_t i = 0; i != Curves.size(); ++i) { Curves[i]->update(J, r); } for (size_t i = 0; i != Constraints.size(); ++i) { Constraints[i]->update(J, r); } for (size_t i = 0; i != Constraints.size(); ++i) { Constraints[i]->update(J, r); // Setup iteration double residual_norm{r.norm() / scale}; double residual_tol = std::sqrt(NumEquations + 1.0) * DBL_EPSILON; int armijo_int{0}; long max_rank(std::min(NumEquations, NumVariables)); for (size_t j = 0; residual_norm > residual_tol && j != (8 * sizeof(double)); ++j) { // TODO: Try extracting sub matrix and solving (Certain constraints are known to eliminate variables (Radius, Fixation), if the matrix can be permuted so that a submatrix on the block diagonal is lower-triangular, then a subsystem of equations can be solved independently of the remaining equations. Moreover, if the jacobian can be permuted into a block lower triangular form, subsets of equations can be solved blockwise) // TODO: Extract newton_update over Verticies, Curves, and Constraints into a private method // TODO: Extract armijo_update into a private method // TODO: Factor out rank deficiency perturbation to be independent of QR/LU/LDL^T choice Eigen::FullPivHouseholderQR QR = J.fullPivHouseholderQr(); delta = QR.solve(r) * std::pow(2.0, (double) armijo_int); long iter_rank = QR.rank(); if (iter_rank < max_rank) { max_rank = iter_rank; std::cerr << "Rank = " << iter_rank << ", Equations = " << NumEquations << ", Variables = " << NumVariables << ", Iteration = " << j << ". The jacobian rank is less than the number of equations. Some sketch elements may be over-constrained." << std::endl; // Randomly perturb the delta vector to prevent stagnation at inflection point std::random_device rdev; std::mt19937 rgen(rdev()); auto rdist = std::uniform_real_distribution<>(-FLT_EPSILON * scale, FLT_EPSILON * scale); for (size_t i = 0; i != Variables.size(); ++i) { delta(Variables[i]->get_index()) += rdist(rgen); } } JJT = J * J.transpose(); LU = JJT.fullPivLu(); delta = J.transpose() * LU.solve(r); // Update Jacobian, residual, and Armijo step-size double iter_norm{DBL_MAX}; int armijo_dir{0}; while (iter_norm > residual_norm && residual_norm > std::sqrt(NumEquations + 1) * DBL_EPSILON) { for (size_t i = 0; i < Variables.size(); ++i) { Variables[i]->update(delta); } J.setZero(); for (size_t i = 0; i != Verticies.size(); ++i) { Verticies[i]->update(J, r); } for (size_t i = 0; i != Curves.size(); ++i) { Curves[i]->update(J, r); } for (size_t i = 0; i != Constraints.size(); ++i) { Constraints[i]->update(J, r); } iter_norm = r.norm() / scale; if (armijo_dir == 0) { if (iter_norm < residual_norm) { armijo_dir = +1; } else { delta = -delta; armijo_dir = -1; } } if (armijo_dir == 1) { if (iter_norm < residual_norm) { if (armijo_int == 0) { residual_norm = iter_norm; break; } else { delta /= 2.0; armijo_int += 1; } } else { delta *= -2.0; armijo_dir = -1; } } for (size_t i = 0; i < Variables.size(); ++i) { Variables[i]->update(delta); if (armijo_dir == -1) { if (iter_norm > residual_norm) { if (armijo_int == -(8 * sizeof(double))) { std::cerr << "Residual norm was not reduced for Armijo step-sizes greater than 2^-(8*sizeof(double))" << std::endl; residual_norm = iter_norm; break; } else { delta /= 2.0; armijo_int -= 1; } } else { residual_norm = iter_norm; break; } } } } return r.norm(); return residual_norm; } bool Sketch::build() { ... ... @@ -88,7 +202,7 @@ void Sketch::save_as(std::string path, std::string file_n This is a stub for visualization */ if(!boost::filesystem::exists(path)) { if (!boost::filesystem::exists(path)) { boost::filesystem::create_directories(path); } ... ...
 ... ... @@ -38,9 +38,9 @@ public: size_t size_verticies() const { return Verticies.size(); }; bool build(); bool build(); // TODO: Detailed return enum double solve(); double solve(); // TODO: Detailed return enum void add_element(std::shared_ptr c); ... ...
 ... ... @@ -8,6 +8,7 @@ #include #include #include #include #include "Eigen" ... ...
 #include "test_Sketch.hpp" TEST(CircularArc, constructor) { { //ARGS::() EXPECT_NO_THROW(CircularArc c); } { //ARGS::(Vertex,Vertex,Vertex) std::shared_ptr v0, v1, center; EXPECT_NO_THROW(CircularArc c(v0, v1, center)); } EXPECT_NO_THROW(CircularArc c); } TEST(CircularArc, point) { ... ...
 ... ... @@ -234,7 +234,7 @@ TEST(Constraint, Angle) { ang->dim(a); s.solve(); double res_norm = s.solve(); s.save_as(SAVE_DIR, std::string("Constraint__Angle_LineSegment_LineSegment_")+std::to_string(i)); ... ... @@ -243,7 +243,7 @@ TEST(Constraint, Angle) { EXPECT_NEAR(v0->y(), v00->y(), TOL); EXPECT_NEAR(1.0, line1->length(), TOL); EXPECT_NEAR(tan(M_PI * i / 8.0), ((v01->y() - v0->y()) / (v01->x() - v0->x())), TOL); EXPECT_NEAR(sin(M_PI * i / 8.0) * (v01->x() - v0->x()), cos(M_PI * i / 8.0) * (v01->y() - v0->y()), TOL); // Tests line sloep. Avoid divide by zero error when calling tan(+/-M_PI / 2.0) by using tan = sin/cos EXPECT_NEAR(cos(M_PI * i / 8.0), v01->x(), TOL); EXPECT_NEAR(sin(M_PI * i / 8.0), v01->y(), TOL); } ... ... @@ -360,7 +360,8 @@ TEST(Constraint, Distance_LineSegment) { auto l0 = s.new_element(v00, v01); auto l1 = s.new_element(v10, v11); auto d = s.new_element>(l0, l1, 1.0); auto d01 = s.new_element>(l0, l1, 1.0); s.solve(); ... ...
 ... ... @@ -78,7 +78,7 @@ TEST(MirrorCopy, nonoverlapping) { s.save_as(SAVE_DIR, "Pattern__Mirror_nonoverlapping_square"); s.solve(); double residual = s.solve(); s.build(); // Run Tests ... ... @@ -91,7 +91,7 @@ TEST(MirrorCopy, nonoverlapping) { // Change elements { s.new_element(l0, 0.5); s.solve(); residual = s.solve(); s.build(); s.save_as(SAVE_DIR, "Pattern__Mirror_nonoverlapping_trapezoid"); ... ... @@ -123,13 +123,12 @@ TEST(MirrorCopy, overlapping) { s.new_element(v0); s.new_element(v3); // std::vector vec{&l0, &l1, &l2, &l3}; auto mvec = s.curves(); auto mc0 = s.new_element(mvec, l3, remove_internal); s.save_as(SAVE_DIR, std::string("Mirror__overlapping_parallelogram")+std::to_string(remove_internal)); s.solve(); double residual = s.solve(); s.build(); // Run Tests ... ... @@ -142,7 +141,7 @@ TEST(MirrorCopy, overlapping) { // Change elements { s.new_element(l1, 0.5); s.solve(); residual = s.solve(); s.build(); s.save_as(SAVE_DIR, std::string("Mirror__overlapping_trapezoid")+std::to_string(remove_internal)); ... ... @@ -179,11 +178,10 @@ TEST(MirrorCopy, multiple_overlapping) { //l3.ForConstruction = true; s.new_element>(v5, l3); //std::vector vec{&l0, &l1, &l2, &l3, &l4, &l5, &l6}; auto mvec = s.curves(); s.new_element(mvec, l3, remove_internal); s.solve(); double residual = s.solve(); s.build(); s.save_as(SAVE_DIR, std::string("Mirror__multiple_overlapping_0_")+std::to_string(remove_internal)); ... ... @@ -198,7 +196,7 @@ TEST(MirrorCopy, multiple_overlapping) { // Change elements { s.new_element(l6, 2.0); s.solve(); residual = s.solve(); s.build(); s.save_as(SAVE_DIR, std::string("Mirror__multiple_overlapping_1_")+std::to_string(remove_internal)); ... ...
 ... ... @@ -7,7 +7,7 @@ #include "Sketch.hpp" #include "gtest.h" #define TOL FLT_EPSILON //#TODO: Tolerance is limited by accuracy of tangency constraint #define TOL FLT_EPSILON // TODO: Tolerance is limited by accuracy of tangency constraint #define SAVE_DIR "./test/output/Sketch/" void test_sketch_size(Sketch &s, size_t nverts, size_t ncurves, size_t nconstraints, size_t ncontours); ... ...