00001 /* 00002 Estimation and computation of a multivariate multidimensional polynomial. 00003 00004 Copyright (C) 2010 - 2014 Christoph Haenisch 00005 00006 Chair of Medical Engineering (mediTEC) 00007 RWTH Aachen University 00008 Pauwelsstr. 20 00009 52074 Aachen 00010 Germany 00011 00012 See license.txt for more information. 00013 00014 Version 0.1.0 (2013-06-06) 00015 */ 00016 00022 #ifndef GENERIC_POLYNOMIAL_HPP_7477314703 00023 #define GENERIC_POLYNOMIAL_HPP_7477314703 00024 00025 00026 #include <cassert> 00027 #include <cmath> 00028 #include <vector> 00029 00030 #include <Eigen/Core> 00031 #include <Eigen/Dense> 00032 00033 #include "Coordinate.hpp" 00034 #include "ErrorObj.hpp" 00035 #include "Polynomial.hpp" 00036 #include "Range.hpp" 00037 00038 00039 namespace TRTK 00040 { 00041 00042 00044 // Helper Functions // 00046 00047 template <class T> 00048 class GenericPolynomial; 00049 00050 00053 long long binomial(int n, int k) 00054 { 00055 if (k < 0 || k > n) return 0; 00056 00057 if (k > n - k) 00058 { 00059 k = n - k; // take advantage of symmetry 00060 } 00061 00062 long long result = 1; 00063 00064 for (int i = 1; i <= k; ++i) 00065 { 00066 result *= n - (k - i); 00067 result /= i; 00068 } 00069 00070 return result; 00071 } 00072 00073 00081 unsigned numberMonomials(unsigned n, unsigned r) 00082 { 00083 // Monomials of constant degree (i.e. r = 3) form a multiset, that is a set 00084 // where members are allowed to appear more than once (example: x^2 y = [x x y]). 00085 // In contrast to tuples, the order of elements is irrelevant. The number of 00086 // monomials of degree r in n variables is the number of multicombinations of r 00087 // elements chosen among the n variables: binomial(n + r - 1, r). The total 00088 // number of monomials of a polynomial of degree r in n variables is just the sum 00089 // of the numbers of monomials of constant degree. 00090 00091 unsigned result = 1; 00092 00093 for (unsigned i = 1; i <= r; ++i) 00094 { 00095 result += (int) binomial(n + i - 1, i); 00096 } 00097 00098 return result; 00099 } 00100 00101 00116 template <class T> 00117 void computeMonomials(unsigned n, 00118 unsigned r, 00119 unsigned number_points, 00120 Iterator<typename GenericPolynomial<T>::Point> it_source_points, 00121 std::vector<Eigen::Matrix<T, Eigen::Dynamic, 1> > & monomials) 00122 { 00123 /* 00124 How does the algorithm work? 00125 00126 The aim of the algorithm is to avoid unnecessary multiplications, i.e. if we want 00127 to compute x^3 and already computed x^2 we can reuse the former result. Consequently, 00128 computing the monomials we start with the lowest degree. 00129 00130 Since the monomials xyz and zyx are equivalent, we cannot just compute all permutations 00131 of products of x, y, and z (which was quite easy). However, a systematic approach for 00132 computing all monomials of a polynomial of degree r in n variables is as follows: 00133 00134 Example in n = 3 variables x, y, and z (note: the parentheses form exactly n groups): 00135 00136 r = 0 1 00137 00138 r = 1 (x) (y) (z) 00139 00140 r = 2 (xx xy xz) (yy yz) (zz) 00141 00142 r = 3 (xxx xxy xxz xyy xyz xzz) (yyy yyz yzz) (zzz) 00143 00144 r = 4 ... 00145 00146 Each single line holds all monomials of a certain degree (denoted by r). These are formed 00147 using the monomials of the line above (that is, from those with a degree of r - 1)· This 00148 is done by multiplying the first variable x with all n groups. Then by multiplying the 00149 second variable y by the last n - 1 groups, then by multiplying the third variable z by 00150 the last n - 2 groups and so on. This is done n times, each time forming a new group. 00151 This procedure is repeated until the desired degree is reached. Since building up the 00152 monomials is done recursively, we provide the monomials for the degrees r = 0 and r = 1. 00153 00154 The computation can be done iteratively using n + 1 pointers, where each pointer shows to 00155 the start of one of the groups and the last pointer shows behind the end of the last group; 00156 each pointer corresponds to one of the input variables. To make it more clear, if the 00157 monomials of degree r are computed the pointers show to the groups of monomials of degree 00158 r - 1. While computing each new group of monomials, the current pointer is set to the 00159 beginning of this new group, which can be safely done since computing new subsequent groups 00160 only involve using old subsequent groups. The beginning of a new group is the container's 00161 end at the moment of creating a new group. 00162 */ 00163 00164 assert(n > 0); 00165 00166 if (number_points == 0) return; 00167 00168 00169 // Initialization. 00170 00171 typedef Eigen::Matrix<T, Eigen::Dynamic, 1> VectorXT; 00172 00173 unsigned number_of_monomials = numberMonomials(n, r); 00174 00175 monomials = std::vector<VectorXT>(number_of_monomials); 00176 00177 std::vector<unsigned> begin_of_group(n); // these indeces denote the start of certain groups (or ranges) of monomials of constant degree 00178 unsigned end; // index denoting the common end of the above ranges 00179 00180 00181 // Construct the first n + 1 monomials from the user data. 00182 00183 if (monomials.size() == 0) return; // nothing to do 00184 00185 // r = 0 00186 00187 monomials[0] = VectorXT::Ones(number_points); 00188 00189 // r = 1 (e.g. monomials[1] = x, monomials[2] = y, monomials[3] = z, etc.) 00190 00191 if (monomials.size() == 1) return; // we are already done 00192 00193 for (unsigned i = 0; i < n; ++i) 00194 { 00195 monomials[i + 1].resize(number_points); // reserve memory 00196 } 00197 00198 for (unsigned i = 0; i < number_points; ++i, ++it_source_points) 00199 { 00200 typename GenericPolynomial<T>::Point point = *it_source_points; 00201 00202 for (unsigned j = 0; j < point.size(); ++j) 00203 { 00204 monomials[j + 1](i) = point[j]; 00205 } 00206 } 00207 00208 00209 // Set up the indeces of the particular ranges. 00210 00211 for (unsigned i = 0; i < n; ++i) 00212 { 00213 begin_of_group[i] = i + 1; 00214 } 00215 00216 end = n + 1; 00217 00218 00219 // Compute the remaining monomials. 00220 00221 for (unsigned degree = 2; degree <= r; ++degree) 00222 { 00223 unsigned end_of_groups = end; 00224 00225 for (unsigned variable = 0; variable < n; ++variable) // input variables x, y, z, ... 00226 { 00227 unsigned index = begin_of_group[variable]; 00228 00229 begin_of_group[variable] = end; // sets the begin of the currently newly generated group 00230 00231 while (index < end_of_groups) 00232 { 00233 // Multiply each element of the range [begin_of_group[variable]; end_of_groups) 00234 // with the current input variable and store it in the container of monomials. 00235 // Update the end index. 00236 // 00237 // Example: Multiply the range [xx xy xz yy yz zz] with x which 00238 // yields [xxx xxy xxz xyy xyz xzz]; in a second iteration 00239 // do this with [yy yz zz] and the factor y and so on. 00240 00241 monomials[end++] = monomials[index++].array() * monomials[variable + 1].array(); // second term is a monomial of first degree (i.e. x, y, or z, ...) 00242 } 00243 } 00244 } 00245 } 00246 00247 00249 // GenericPolynomial // 00251 00429 template <class T> 00430 class GenericPolynomial : public Polynomial<T> 00431 { 00432 private: 00433 00434 typedef Polynomial<T> super; 00435 00436 protected: 00437 00438 typedef typename super::VectorXT VectorXT; 00439 typedef typename super::Vector3T Vector3T; 00440 typedef typename super::coordinate_type coordinate_type; 00441 00442 public: 00443 00444 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 00445 00446 typedef T value_type; 00447 typedef typename super::MatrixXT MatrixXT; 00448 typedef typename super::Point Point; 00449 00450 using super::DIVISION_BY_ZERO; 00451 using super::INVALID_ARGUMENT; 00452 using super::NOT_ENOUGH_POINTS; 00453 using super::UNEQUAL_NUMBER_OF_POINTS; 00454 using super::UNKNOWN_ERROR; 00455 using super::WRONG_COORDINATE_SIZE; 00456 00457 GenericPolynomial(unsigned n, unsigned m, unsigned r); 00458 00459 GenericPolynomial(const MatrixXT &, unsigned n, unsigned m, unsigned r); 00460 00461 GenericPolynomial(const GenericPolynomial &); 00462 00463 template <class U> 00464 GenericPolynomial(const GenericPolynomial<U> &, unsigned n, unsigned m, unsigned r); 00465 00466 virtual ~GenericPolynomial(); 00467 00468 MatrixXT & getCoefficients(); 00469 const MatrixXT & getCoefficients() const; 00470 00471 T estimate(Range<Point> source_points, 00472 Range<Point> target_points, 00473 Range<T> weights = Range<T>()); 00474 00475 T estimate(Iterator<Point> source_points_first, 00476 Iterator<Point> source_points_last, 00477 Iterator<Point> target_points_first, 00478 Iterator<Point> target_points_last, 00479 Iterator<T> weights_first = Iterator<T>(), 00480 Iterator<T> weights_last = Iterator<T>()); 00481 00482 Point operator*(const Point &) const; 00483 Point transform(const Point &) const; 00484 00485 Polynomial<T> & reset(); 00486 00487 void setDegree(unsigned degree); 00488 00489 private: 00490 00491 MatrixXT m_matrix; 00492 00493 unsigned number_input_variables; 00494 unsigned dimension; // of the output vector 00495 unsigned degree; 00496 00497 friend void computeMonomials<T>(unsigned, unsigned, unsigned, Iterator<Point>, std::vector<VectorXT> &); 00498 00499 }; 00500 00501 00515 template <class T> 00516 GenericPolynomial<T>::GenericPolynomial(unsigned n, unsigned m, unsigned r) : 00517 m_matrix(MatrixXT(m, numberMonomials(n, r))), 00518 number_input_variables(n), 00519 dimension(m), 00520 degree(r) 00521 { 00522 assert(n > 0); 00523 assert(m > 0); 00524 reset(); 00525 } 00526 00527 00543 template <class T> 00544 GenericPolynomial<T>::GenericPolynomial(const MatrixXT & matrix, unsigned n, unsigned m, unsigned r) : 00545 number_input_variables(n), 00546 dimension(m), 00547 degree(r) 00548 { 00549 assert(n > 0); 00550 assert(m > 0); 00551 assert(matrix.rows() == m && matrix.cols() == numberMonomials(n, r)); 00552 m_matrix = matrix; 00553 } 00554 00555 00563 template <class T> 00564 GenericPolynomial<T>::GenericPolynomial(const GenericPolynomial & other) 00565 { 00566 m_matrix = other.m_matrix; 00567 number_input_variables = other.number_input_variables; 00568 dimension = other.dimension; 00569 degree = other.degree; 00570 } 00571 00572 00586 template <class T> 00587 template <class U> 00588 GenericPolynomial<T>::GenericPolynomial(const GenericPolynomial<U> & other, unsigned n, unsigned m, unsigned r) 00589 { 00590 m_matrix = other.getCoefficients().template cast<T>(); 00591 number_input_variables = n; 00592 dimension = m; 00593 degree = r; 00594 } 00595 00596 00602 template <class T> 00603 GenericPolynomial<T>::~GenericPolynomial() 00604 { 00605 } 00606 00607 00613 template <class T> 00614 typename GenericPolynomial<T>::MatrixXT & GenericPolynomial<T>::getCoefficients() 00615 { 00616 return m_matrix; 00617 } 00618 00619 00625 template <class T> 00626 const typename GenericPolynomial<T>::MatrixXT & GenericPolynomial<T>::getCoefficients() const 00627 { 00628 return m_matrix; 00629 } 00630 00631 00652 template <class T> 00653 T GenericPolynomial<T>::estimate(Range<Point> source_points, Range<Point> target_points, Range<T> weights) 00654 { 00655 return estimate(source_points.begin(), 00656 source_points.end(), 00657 target_points.begin(), 00658 target_points.end(), 00659 weights.begin(), 00660 weights.end()); 00661 } 00662 00663 00684 template <class T> 00685 T GenericPolynomial<T>::estimate(Iterator<Point> source_points_first, 00686 Iterator<Point> source_points_last, 00687 Iterator<Point> target_points_first, 00688 Iterator<Point> target_points_last, 00689 Iterator<T> weights_first, 00690 Iterator<T> weights_last) 00691 { 00692 const int number_points = distance(source_points_first, source_points_last); 00693 00694 if (distance(target_points_first, target_points_last) != number_points) 00695 { 00696 ErrorObj error; 00697 error.setClassName("GenericPolynomial<T>"); 00698 error.setFunctionName("estimate"); 00699 error.setErrorMessage("Point sets must have the same cardinality."); 00700 error.setErrorCode(UNEQUAL_NUMBER_OF_POINTS); 00701 throw error; 00702 } 00703 00704 if (weights_first && weights_last) 00705 { 00706 if (distance(weights_first, weights_last) != number_points) 00707 { 00708 ErrorObj error; 00709 error.setClassName("GenericPolynomial<T>"); 00710 error.setFunctionName("estimate"); 00711 error.setErrorMessage("Point sets must have the same cardinality."); 00712 error.setErrorCode(UNEQUAL_NUMBER_OF_POINTS); 00713 throw error; 00714 } 00715 } 00716 00717 if (number_points < 10) 00718 { 00719 ErrorObj error; 00720 error.setClassName("GenericPolynomial<T>"); 00721 error.setFunctionName("estimate"); 00722 error.setErrorMessage("Not enough points to estimate the transformation."); 00723 error.setErrorCode(NOT_ENOUGH_POINTS); 00724 throw error; 00725 } 00726 00727 /* 00728 00729 Explanation of the algorithm: 00730 00731 The polynomial can be written as follows (here n=3, m=3, r=2): 00732 00733 | x' | | m_1,1 m_1,2 ... m_1,10 | 00734 | y | = | m_2,1 m_2,2 ... m_2,10 | * | 1 x y ... y^2 yz z^2 |^T 00735 | z' | | m_3,1 m_3,2 ... m_3,10 | 00736 00737 The equation can be rewritten as: 00738 00739 | x' | | 1 ... z^2 0 ... 0 0 ... 0 | | m_1,1 | 00740 | y' | = | 0 ... 0 1 ... z^2 0 ... 0 | * | ... | 00741 | z' | | 0 ... 0 0 ... 0 1 ... z^2 | | m_3,10 | 00742 00743 Or in short: b = A * x (here, x is meant to be (m_1,1, ..., m_3,10)^T) 00744 00745 By using more and more points (source_1, target_1), ..., (source_n, target_n) 00746 00747 | A(1) | | b(1) | 00748 A := | ... | , b := | ... | 00749 | A(n) | | b(n) | 00750 00751 this leads to an over-determined system. However, this can be easily solved 00752 using the pseudo inverse: 00753 00754 <==> A * x = b 00755 <==> A^T * A * x = A^T * b 00756 <==> x = inverse(A^T * A) * A^T * b = pseudo_inverse * b 00757 00758 Thus, we have found the sought coefficients. 00759 00760 Update: We now use weighted least squares so that x = inverse(A^T * W * A) * A^T * W * b 00761 where W is a diagonal matrix 00762 00763 */ 00764 00765 const unsigned & n = number_input_variables; 00766 const unsigned & m = dimension; 00767 const unsigned & r = degree; 00768 00769 // Compute the monomials (terms like x^2yz) 00770 00771 unsigned number_of_monomials = numberMonomials(n, r); // number of all possible monomials (1, x, y, ..., x^2, y^2, ...) 00772 00773 std::vector<VectorXT> monomials; // e.g. monomials[3] might be something like x^2 := (x1^2, x2^2, ..., xn^2) 00774 00775 computeMonomials(number_input_variables, degree, number_points, source_points_first, monomials); 00776 00777 // Fill the matrix A with the above described entries (different order as above). 00778 00779 MatrixXT A(m * number_points, m * number_of_monomials); 00780 00781 A.setZero(); 00782 00783 for (unsigned j = 0; j < m; ++j) // rows 00784 { 00785 for (unsigned i = 0; i < number_of_monomials; ++i) // columns 00786 { 00787 int row = j * number_points; 00788 int col = j * number_of_monomials + i; 00789 A.block(row, col, number_points, 1) = monomials[i]; 00790 } 00791 } 00792 00793 // Fill the vector b. 00794 00795 VectorXT b(m * number_points); 00796 00797 int i = 0; 00798 for (Iterator<Point> it = target_points_first; it != target_points_last; ++it, ++i) 00799 { 00800 const Point & point = *it; 00801 00802 for (unsigned j = 0; j < m; ++j) 00803 { 00804 b(j * number_points + i) = point[j]; 00805 } 00806 } 00807 00808 // Compute the vector x whose components are the sought coefficients. 00809 00810 VectorXT x; 00811 VectorXT W = VectorXT::Ones(m * number_points); // diagonal weighting matrix (W.asDiagonal()) 00812 00813 if (weights_first && weights_last) 00814 { 00815 // Weighted Least Squares 00816 00817 int i = 0; 00818 for (Iterator<T> it = weights_first; it != weights_last; ++it, ++i) 00819 { 00820 T weight = *it; 00821 00822 for (unsigned j = 0; j < m; ++j) 00823 { 00824 W(j * number_points + i) = weight; 00825 } 00826 } 00827 00828 x = (A.transpose() * W.asDiagonal() * A).inverse() * (A.transpose() * (W.asDiagonal() * b)); 00829 } 00830 else 00831 { 00832 // Ordinary Least Squares 00833 00834 MatrixXT pseudo_inverse = (A.transpose() * A).inverse() * A.transpose(); 00835 x = pseudo_inverse * b; 00836 } 00837 00838 // Reinterprete the solution vector as a row-major matrix 00839 00840 // m_matrix = Eigen::Map<MatrixXT, Eigen::Unaligned, Eigen::Stride<1, 10> >(x.data(), m, 10); 00841 m_matrix = Eigen::Map<MatrixXT>(x.data(), number_of_monomials, m).transpose(); 00842 00843 // Compute the RMSE. 00844 00845 using std::sqrt; 00846 00847 if (weights_first && weights_last) 00848 { 00849 return (W.cwiseSqrt().asDiagonal() * (A * x - b)).norm() / sqrt(T(number_points)); 00850 } 00851 else 00852 { 00853 return (A * x - b).norm() / sqrt(T(number_points)); 00854 } 00855 } 00856 00857 00867 template <class T> 00868 typename GenericPolynomial<T>::Point GenericPolynomial<T>::operator* (const Point & point) const 00869 { 00870 return transform(point); 00871 } 00872 00873 00894 template <class T> 00895 Polynomial<T> & GenericPolynomial<T>::reset() 00896 { 00897 m_matrix.setZero(); 00898 00899 for (unsigned i = 0; i < std::min(number_input_variables, dimension); ++i) 00900 { 00901 m_matrix(i, i + 1) = 1; 00902 } 00903 00904 return *this; 00905 } 00906 00907 00917 template <class T> 00918 typename GenericPolynomial<T>::Point GenericPolynomial<T>::transform(const Point & point) const 00919 { 00920 // See computeMonomials for an explanation of the algorithm. 00921 00922 assert(point.size() > 0); 00923 assert(point.size() == number_input_variables); 00924 00925 // Initialization. 00926 00927 VectorXT monomials(numberMonomials(number_input_variables, degree)); 00928 std::vector<unsigned> begin_of_group(number_input_variables); 00929 unsigned end; 00930 00931 // Construct the first n + 1 monomials. 00932 00933 monomials(0) = 1; 00934 00935 for (unsigned i = 0; i < point.size(); ++i) 00936 { 00937 monomials(i + 1) = point[i]; 00938 } 00939 00940 // Set up the indeces of the particular ranges. 00941 00942 for (unsigned i = 0; i < number_input_variables; ++i) 00943 { 00944 begin_of_group[i] = i + 1; 00945 } 00946 00947 end = number_input_variables + 1; 00948 00949 // Compute the remaining monomials. 00950 00951 for (unsigned i = 2; i <= degree; ++i) 00952 { 00953 unsigned end_of_groups = end; 00954 for (unsigned variable = 0; variable < number_input_variables; ++variable) 00955 { 00956 unsigned index = begin_of_group[variable]; 00957 begin_of_group[variable] = end; 00958 while (index < end_of_groups) 00959 { 00960 monomials(end++) = monomials(index++) * monomials(variable + 1); 00961 } 00962 } 00963 } 00964 00965 // Compute the transformed point. 00966 00967 return Point(m_matrix * monomials); 00968 } 00969 00970 00971 } // namespace TRTK 00972 00973 00974 #endif // GENERIC_POLYNOMIAL_HPP_7477314703
Documentation generated by Doxygen