#include "trilateration.h" #include #include #include #include #include namespace Trilateration { // see: https://github.com/Wayne82/Trilateration/blob/master/source/Trilateration.cpp Point2 peusodInverse2d(const std::vector& positions, const std::vector& distances) { // To locate position on a 2d plan, have to get at least 3 becaons, // otherwise return false. if (positions.size() < 3) assert(false); if (positions.size() != distances.size()) assert(false); // Define the matrix that we are going to use size_t count = positions.size(); size_t rows = count * (count - 1) / 2; Eigen::MatrixXd m(rows, 2); Eigen::VectorXd b(rows); // Fill in matrix according to the equations size_t row = 0; double x1, x2, y1, y2, r1, r2; for (size_t i = 0; i < count; ++i) { for (size_t j = i + 1; j < count; ++j) { x1 = positions[i].x, y1 = positions[i].y; x2 = positions[j].x, y2 = positions[j].y; r1 = distances[i]; r2 = distances[j]; m(row, 0) = x1 - x2; m(row, 1) = y1 - y2; b(row) = ((pow(x1, 2) - pow(x2, 2)) + (pow(y1, 2) - pow(y2, 2)) - (pow(r1, 2) - pow(r2, 2))) / 2; row++; } } // Then calculate to solve the equations, using the least square solution //Eigen::Vector2d location = m.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); Eigen::Vector2d pseudoInv = (m.transpose()*m).inverse() * m.transpose() *b; return Point2(pseudoInv.x(), pseudoInv.y()); } Point3 pseudoInverse3d(const std::vector& positions, const std::vector& distances) { // To locate position in a 3D space, have to get at least 4 becaons if (positions.size() < 4) assert(false); if (positions.size() != distances.size()) assert(false); // Define the matrix that we are going to use size_t count = positions.size(); size_t rows = count * (count - 1) / 2; Eigen::MatrixXd m(rows, 3); Eigen::VectorXd b(rows); // Fill in matrix according to the equations size_t row = 0; double x1, x2, y1, y2, z1, z2, r1, r2; for (size_t i = 0; i < count; ++i) { for (size_t j = i + 1; j < count; ++j) { x1 = positions[i].x, y1 = positions[i].y, z1 = positions[i].z; x2 = positions[j].x, y2 = positions[j].y, z2 = positions[j].z; r1 = distances[i]; r2 = distances[j]; m(row, 0) = x1 - x2; m(row, 1) = y1 - y2; m(row, 2) = z1 - z2; b(row) = ((pow(x1, 2) - pow(x2, 2)) + (pow(y1, 2) - pow(y2, 2)) + (pow(z1, 2) - pow(z2, 2)) - (pow(r1, 2) - pow(r2, 2))) / 2; row++; } } // Then calculate to solve the equations, using the least square solution Eigen::Vector3d location = m.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b); return Point3(location.x(), location.y(), location.z()); } // Generic functor // See http://eigen.tuxfamily.org/index.php?title=Functors // C++ version of a function pointer that stores meta-data about the function template struct Functor { // Information that tells the caller the numeric type (eg. double) and size (input / output dim) typedef _Scalar Scalar; enum { // Required by numerical differentiation module InputsAtCompileTime = NX, ValuesAtCompileTime = NY }; // Tell the caller the matrix sizes associated with the input, output, and jacobian typedef Eigen::Matrix InputType; typedef Eigen::Matrix ValueType; typedef Eigen::Matrix JacobianType; // Local copy of the number of inputs int m_inputs, m_values; // Two constructors: Functor() : m_inputs(InputsAtCompileTime), m_values(ValuesAtCompileTime) {} Functor(int inputs, int values) : m_inputs(inputs), m_values(values) {} // Get methods for users to determine function input and output dimensions int inputs() const { return m_inputs; } int values() const { return m_values; } }; struct DistanceFunction : Functor { private: const std::vector& positions; const std::vector& distances; public: DistanceFunction(const std::vector& positions, const std::vector& distances) : Functor(positions.size(), positions.size()), positions(positions), distances(distances) {} int operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec) const { const Point2 p(x(0), x(1)); for (size_t i = 0; i < positions.size(); i++) { fvec(i) = p.getDistance(positions[i]) - distances[i]; } return 0; } }; struct DistanceFunctionDiff : public Eigen::NumericalDiff { DistanceFunctionDiff(const DistanceFunction& functor) : Eigen::NumericalDiff(functor, 1.0e-6) {} }; std::string lmStatusToString(const Eigen::LevenbergMarquardtSpace::Status stat) { switch (stat) { // Non-Erros case Eigen::LevenbergMarquardtSpace::NotStarted: return "Not started. No Error."; case Eigen::LevenbergMarquardtSpace::Running: return "Running. No Error."; // Error case Eigen::LevenbergMarquardtSpace::ImproperInputParameters: return "Error. Invalid input parameters"; case Eigen::LevenbergMarquardtSpace::UserAsked: return "Error in user-implemented evaluation or gradient computation."; // Warnings case Eigen::LevenbergMarquardtSpace::RelativeReductionTooSmall: return "WARN: The cosine of the angle between fvec and any column of the jacobian is at most gtol in absolute value."; case Eigen::LevenbergMarquardtSpace::RelativeErrorTooSmall: return "WARN: Relative error too small."; case Eigen::LevenbergMarquardtSpace::RelativeErrorAndReductionTooSmall: return "WARN: Relative error and reduction too small."; case Eigen::LevenbergMarquardtSpace::CosinusTooSmall: return "WARN: The cosine of the angle between fvec and any column of the jacobian is at most gtol in absolute value."; case Eigen::LevenbergMarquardtSpace::TooManyFunctionEvaluation: return "WARN: Too many function evaluations done."; case Eigen::LevenbergMarquardtSpace::FtolTooSmall: return "WARN: ftol is too small. No further reduction in the sum of squares is possible"; case Eigen::LevenbergMarquardtSpace::XtolTooSmall: return "WARN: xtol is too small. No further improvement in the approximate solution x is possible."; case Eigen::LevenbergMarquardtSpace::GtolTooSmall: return "WARN: gtol is too small.fvec is orthogonal to the columns of the jacobian to machine precision."; default: return "Unkown status. Status=" + std::to_string(static_cast(stat)); } } Point2 levenbergMarquardt(const std::vector& positions, const std::vector& distances) { const Point2 pseudoInvApprox = peusodInverse2d(positions, distances); Eigen::Vector2d startVal; startVal << pseudoInvApprox.x, pseudoInvApprox.y; //startVal << 0, 0; DistanceFunction functor(positions, distances); DistanceFunctionDiff numDiff(functor); Eigen::LevenbergMarquardt lm(numDiff); // Parameters // factor : Sets the step bound for the diagonal shift // maxfev : Sets the maximum number of function evaluation // ftol : Sets the tolerance for the norm of the vector function // xtol : Sets the tolerance for the norm of the solution vector // gtol : Sets the tolerance for the norm of the gradient of the error vector // epsilon: Sets the error precision lm.parameters.maxfev = 200; lm.parameters.xtol = 1.0e-10; Eigen::VectorXd z = startVal; auto status = lm.minimize(z); std::cout << "Levenberg Marquardt" << std::endl; std::cout << "status: " << lmStatusToString(status) << std::endl; std::cout << "iter count: " << lm.iter << std::endl; std::cout << "levenMarq: " << z.transpose() << std::endl; std::cout << "pseudoInv: " << startVal.transpose() << std::endl; const Point2 levenPos(z(0), z(1)); // Compute error double errPseudo = 0; double errLeven = 0; for (size_t i = 0; i < positions.size(); i++) { double d1 = pseudoInvApprox.getDistance(positions[i]) - distances[i]; errPseudo += d1 * d1; double d2 = levenPos.getDistance(positions[i]) - distances[i]; errLeven += d2 * d2; } //assert(errLeven <= errPseudo); std::cout << "err pseud: " << errPseudo << std::endl; std::cout << "err leven: " << errLeven << std::endl << std::endl; return levenPos; } }