forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpolynomial_basis.h
160 lines (148 loc) · 6.38 KB
/
polynomial_basis.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#pragma once
#include <functional>
#include <map>
#include <set>
#include <Eigen/Core>
#include "drake/common/drake_assert.h"
#include "drake/common/symbolic/expression.h"
#include "drake/common/symbolic/monomial_util.h"
#include "drake/common/symbolic/polynomial.h"
#include "drake/common/symbolic/polynomial_basis_element.h"
namespace drake {
namespace symbolic {
namespace internal {
/* Adds the set {RaisePower(b, m) for m in Basis(vars, degree)} to `bin`.
`Basis` is the polynomial basis associated with `BasisElement` and
`Basis(vars, degree)` returns all BasisElement objects with variables `vars`
and total degree = `degree`. RaisePower(m, b) is the operation that "merges"
two `BasisElement`s such that the merged `BasisElement`:
- Is a product of all variables present in either m or b.
- The power of variable v is the sum of the degree of that variable in m and
b. If a variable is missing in a BasisElement, it has degree 0.
As a concrete example, assume that BasisElement is actually
MonomialBasisElement with
vars = {x, y}, degree = 2, and b = x²yz³. MonomialBasis({x, y}, 2) would
produce the set of MonomialBasisElements: x², xy, y². And applying
RaisePower(b, m) to each m would produce:
- RaisePower(x²yz³, x²) --> x⁴yz³
- RaisePower(x²yz³, xy) --> x³y²z³
- RaisePower(x²yz³, y²) --> x²y³z³
@tparam BasisElementOrder a total order of PolynomialBasisElement. An example
is BasisElementGradedReverseLexOrder.
@tparam BasisElement A derived class of PolynomialBasisElement.
@note A non-zero degree has no effect if vars is empty. If vars is empty
and degree is zero, then {RaisePower(b, m) for m in Basis(vars, degree)} = {b}.
*/
template <typename BasisElementOrder, typename BasisElement>
void AddPolynomialBasisElementsOfDegreeN(
const Variables& vars, int degree, const BasisElement& b,
std::set<BasisElement, BasisElementOrder>* const bin) {
/* Iterating through Basis(vars, degree) is tricky due to the fact that the
number of variables is determined at runtime. For example, assume the
MonomialBasisElement with vars = {x, y, z} and degree = 3, then
Basis({x, y, z}, 3) would produce the basis elements: x³, x²y, xy², xyz, y³,
y²z, yz², z³. Conceptually, this matches well to nested for loops. Each
for loop "consumes" a portion of the target degree, leaving the remaining
part to the nested loops.
for (int p_x = degree; p_x >= 0; --p_x) {
for (int p_y = degree - p_x; p_y >= 0; --p_y) {
for (int p_z = degree - p_x - p_y; p_z >= 0; --p_z) {
Monomial m = x ** p_x * y ** p_y * z ** p_z; // Ok, not real code.
bin->insert(RaisePower(m, b));
}
}
}
However, if we add another variable, we have to add another nested for loop.
We can't do that dynamically. So, we use recursion to create the *effect*
of a dynamically-determined number of nested for loops. Each level of the
recursion selects the "first" variable in the set and iteratively consumes
some portion of the target degree. The recursive call is made on the
remaining variables with the remaining available degree. The consumed portion
is preserved by pre-multiplying it with the `b` value and passing on this
accumulated b to the recursive call. RaisePower(m, b) is simply m * b. We
can refactor the product: RaisePower(m, b) = RaisePower(m/xⁱ, b * xⁱ) and
this relationship is the recursive call. */
static_assert(
std::is_base_of_v<PolynomialBasisElement, BasisElement> ||
std::is_same_v<BasisElement, Monomial>,
"BasisElement should be a derived class of PolynomialBasisElement");
if (degree == 0) {
bin->insert(b);
return;
}
if (vars.empty()) {
return;
}
const Variable& var{*vars.cbegin()};
for (int var_degree = degree; var_degree >= 0; --var_degree) {
std::map<Variable, int> new_var_to_degree_map = b.get_powers();
auto it = new_var_to_degree_map.find(var);
if (it != new_var_to_degree_map.end()) {
it->second += var_degree;
} else {
new_var_to_degree_map.emplace_hint(it, var, var_degree);
}
const BasisElement new_b(new_var_to_degree_map);
AddPolynomialBasisElementsOfDegreeN(vars - var, degree - var_degree, new_b,
bin);
}
}
} // namespace internal
/**
* Returns all polynomial basis elements up to a given degree under the graded
* reverse lexicographic order.
* @tparam rows Number of rows or Eigen::Dynamic.
* @tparam BasisElement A derived class of PolynomialBasisElement.
* @param vars The variables appearing in the polynomial basis.
* @param degree The highest total degree of the polynomial basis elements.
* @param degree_type If degree_type is kAny, then the polynomial basis
* elements' degrees are no larger than @p degree. If degree_type is kEven, then
* the elements' degrees are even numbers no larger than @p degree. If
* degree_type is kOdd, then the elements' degrees are odd numbers no larger
* than @p degree.
* TODO(hongkai.dai): this will replace ComputeMonomialBasis in monomial_util.h.
*/
template <int rows, typename BasisElement>
Eigen::Matrix<BasisElement, rows, 1> ComputePolynomialBasisUpToDegree(
const Variables& vars, int degree, internal::DegreeType degree_type) {
DRAKE_DEMAND(!vars.empty());
DRAKE_DEMAND(degree >= 0);
// 1. Collect elements.
std::set<BasisElement,
BasisElementGradedReverseLexOrder<std::less<Variable>, BasisElement>>
basis_elements_set;
int start_degree = 0;
int degree_stride = 1;
switch (degree_type) {
case internal::DegreeType::kAny: {
start_degree = 0;
degree_stride = 1;
break;
}
case internal::DegreeType::kEven: {
start_degree = 0;
degree_stride = 2;
break;
}
case internal::DegreeType::kOdd: {
start_degree = 1;
degree_stride = 2;
}
}
for (int i = start_degree; i <= degree; i += degree_stride) {
internal::AddPolynomialBasisElementsOfDegreeN(vars, i, BasisElement{},
&basis_elements_set);
}
// 2. Prepare the return value, basis.
DRAKE_DEMAND((rows == Eigen::Dynamic) ||
(static_cast<size_t>(rows) == basis_elements_set.size()));
Eigen::Matrix<BasisElement, rows, 1> basis(basis_elements_set.size());
int i{0};
for (const auto& m : basis_elements_set) {
basis[i] = m;
i++;
}
return basis;
}
} // namespace symbolic
} // namespace drake