forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generic polynomial (RobotLocomotion#13940)
Add simple constructors for GenericPolynomial, and Differentiate function. The follow-on PRs will add more functionality to GenericPolynomial class. Eventually we will replace Polynomial class with GenericPolynomial<MonomialBasisElement> class.
- Loading branch information
1 parent
f789501
commit 1f51d66
Showing
7 changed files
with
597 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// NOLINTNEXTLINE(build/include): Its header file is included in symbolic.h. | ||
#include <map> | ||
#include <sstream> | ||
#include <stdexcept> | ||
#include <utility> | ||
|
||
#include "drake/common/symbolic.h" | ||
#define DRAKE_COMMON_SYMBOLIC_DETAIL_HEADER | ||
#include "drake/common/symbolic_expression_cell.h" | ||
#undef DRAKE_COMMON_SYMBOLIC_DETAIL_HEADER | ||
|
||
using std::map; | ||
using std::ostringstream; | ||
using std::pair; | ||
using std::runtime_error; | ||
|
||
namespace drake { | ||
namespace symbolic { | ||
namespace { | ||
// Helper function to add coeff * m to a map (BasisElement→ Expression). | ||
// Used to implement DecomposePolynomialVisitor::VisitAddition and | ||
// GenericPolynomial::Add. | ||
template <typename BasisElement> | ||
void DoAddProduct( | ||
const Expression& coeff, const BasisElement& basis_element, | ||
typename GenericPolynomial<BasisElement>::MapType* const map) { | ||
if (is_zero(coeff)) { | ||
return; | ||
} | ||
auto it = map->find(basis_element); | ||
if (it != map->end()) { | ||
// basis_element ∈ dom(map) | ||
Expression& existing_coeff = it->second; | ||
// Note that `.Expand()` is needed in the following line. For example, | ||
// consider the following case: | ||
// c1 := (a + b)² | ||
// c2 := - (a² + 2ab + b²) | ||
// Without expanding the terms, we have `is_zero(c1 + c2) = false` while | ||
// it's clear that c1 + c2 is a zero polynomial. Using `Expand()` help us | ||
// identify those cases. | ||
if (is_zero(existing_coeff.Expand() + coeff.Expand())) { | ||
map->erase(it); | ||
} else { | ||
existing_coeff += coeff; | ||
} | ||
} else { | ||
// basis_element ∉ dom(map) | ||
map->emplace_hint(it, basis_element, coeff); | ||
} | ||
} | ||
|
||
template <typename BasisElement> | ||
Variables GetIndeterminates( | ||
const typename GenericPolynomial<BasisElement>::MapType& m) { | ||
Variables vars; | ||
for (const pair<const BasisElement, Expression>& p : m) { | ||
const BasisElement& m_i{p.first}; | ||
vars += m_i.GetVariables(); | ||
} | ||
return vars; | ||
} | ||
|
||
template <typename BasisElement> | ||
Variables GetDecisionVariables( | ||
const typename GenericPolynomial<BasisElement>::MapType& m) { | ||
Variables vars; | ||
for (const pair<const BasisElement, Expression>& p : m) { | ||
const Expression& e_i{p.second}; | ||
vars += e_i.GetVariables(); | ||
} | ||
return vars; | ||
} | ||
} // namespace | ||
|
||
template <typename BasisElement> | ||
GenericPolynomial<BasisElement>::GenericPolynomial(MapType init) | ||
: basis_element_to_coefficient_map_{move(init)}, | ||
indeterminates_{ | ||
GetIndeterminates<BasisElement>(basis_element_to_coefficient_map_)}, | ||
decision_variables_{GetDecisionVariables<BasisElement>( | ||
basis_element_to_coefficient_map_)} { | ||
DRAKE_ASSERT_VOID(CheckInvariant()); | ||
} | ||
|
||
template <typename BasisElement> | ||
void GenericPolynomial<BasisElement>::CheckInvariant() const { | ||
// TODO(hongkai.dai and soonho.kong): improves the computation time of | ||
// CheckInvariant(). See github issue | ||
// https://github.com/RobotLocomotion/drake/issues/10229 | ||
Variables vars{intersect(decision_variables(), indeterminates())}; | ||
if (!vars.empty()) { | ||
ostringstream oss; | ||
oss << "Polynomial " << *this | ||
<< " does not satisfy the invariant because the following variable(s) " | ||
"are used as decision variables and indeterminates at the same " | ||
"time:\n" | ||
<< vars << "."; | ||
throw runtime_error(oss.str()); | ||
} | ||
} | ||
|
||
template <typename BasisElement> | ||
GenericPolynomial<BasisElement>::GenericPolynomial(const BasisElement& m) | ||
: basis_element_to_coefficient_map_{{m, 1}}, | ||
indeterminates_{m.GetVariables()}, | ||
decision_variables_{} { | ||
// No need to call CheckInvariant() because the following should hold. | ||
DRAKE_ASSERT(decision_variables().empty()); | ||
} | ||
|
||
template <typename BasisElement> | ||
int GenericPolynomial<BasisElement>::Degree(const Variable& v) const { | ||
int degree{0}; | ||
for (const pair<const BasisElement, Expression>& p : | ||
basis_element_to_coefficient_map_) { | ||
degree = std::max(degree, p.first.degree(v)); | ||
} | ||
return degree; | ||
} | ||
|
||
template <typename BasisElement> | ||
int GenericPolynomial<BasisElement>::TotalDegree() const { | ||
int degree{0}; | ||
for (const pair<const BasisElement, Expression>& p : | ||
basis_element_to_coefficient_map_) { | ||
degree = std::max(degree, p.first.total_degree()); | ||
} | ||
return degree; | ||
} | ||
|
||
template <typename BasisElement> | ||
GenericPolynomial<BasisElement> GenericPolynomial<BasisElement>::Differentiate( | ||
const Variable& x) const { | ||
if (this->indeterminates_.include(x)) { | ||
// x is an indeterminate. | ||
GenericPolynomial<BasisElement>::MapType map; | ||
for (const auto& [basis_element, coeff] : | ||
this->basis_element_to_coefficient_map_) { | ||
// Take the derivative of basis_element m as dm/dx = sum_{key} | ||
// basis_element_gradient[key] * key. | ||
const std::map<BasisElement, double> basis_element_gradient = | ||
basis_element.Differentiate(x); | ||
|
||
for (const auto& p : basis_element_gradient) { | ||
DoAddProduct(coeff * p.second, p.first, &map); | ||
} | ||
} | ||
return GenericPolynomial<BasisElement>(map); | ||
} else if (decision_variables_.include(x)) { | ||
// x is a decision variable. | ||
GenericPolynomial<BasisElement>::MapType map; | ||
for (const auto& [basis_element, coeff] : | ||
basis_element_to_coefficient_map_) { | ||
DoAddProduct(coeff.Differentiate(x), basis_element, &map); | ||
} | ||
return GenericPolynomial<BasisElement>(map); | ||
} else { | ||
// The variable `x` does not appear in this polynomial. Returns zero | ||
// polynomial. | ||
return GenericPolynomial<BasisElement>(); | ||
} | ||
} | ||
|
||
namespace { | ||
template <typename BasisElement> | ||
bool GenericPolynomialEqual(const GenericPolynomial<BasisElement>& p1, | ||
const GenericPolynomial<BasisElement>& p2, | ||
bool do_expansion) { | ||
const typename GenericPolynomial<BasisElement>::MapType& map1{ | ||
p1.basis_element_to_coefficient_map()}; | ||
const typename GenericPolynomial<BasisElement>::MapType& map2{ | ||
p2.basis_element_to_coefficient_map()}; | ||
if (map1.size() != map2.size()) { | ||
return false; | ||
} | ||
// Since both map1 and map2 are ordered map, we can compare them one by one in | ||
// an ordered manner. | ||
auto it1 = map1.begin(); | ||
auto it2 = map2.begin(); | ||
while (it1 != map1.end()) { | ||
if (it1->first != (it2->first)) { | ||
return false; | ||
} | ||
const Expression& e1{it1->second}; | ||
const Expression& e2{it2->second}; | ||
if (do_expansion) { | ||
if (!e1.Expand().EqualTo(e2.Expand())) { | ||
return false; | ||
} | ||
} else { | ||
if (!e1.EqualTo(e2)) { | ||
return false; | ||
} | ||
} | ||
it1++; | ||
it2++; | ||
} | ||
return true; | ||
} | ||
} // namespace | ||
|
||
template <typename BasisElement> | ||
bool GenericPolynomial<BasisElement>::EqualTo( | ||
const GenericPolynomial<BasisElement>& other) const { | ||
return GenericPolynomialEqual<BasisElement>(*this, other, false); | ||
} | ||
|
||
template class GenericPolynomial<MonomialBasisElement>; | ||
template class GenericPolynomial<ChebyshevBasisElement>; | ||
} // namespace symbolic | ||
} // namespace drake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
#pragma once | ||
|
||
#ifndef DRAKE_COMMON_SYMBOLIC_HEADER | ||
#error Do not directly include this file. Include "drake/common/symbolic.h". | ||
#endif | ||
|
||
#include <map> | ||
#include <ostream> | ||
|
||
#include <Eigen/Core> | ||
|
||
#include "drake/common/drake_copyable.h" | ||
#include "drake/common/symbolic.h" | ||
|
||
namespace drake { | ||
namespace symbolic { | ||
/** | ||
* Represents symbolic generic polynomials using a given basis (for example, | ||
* monomial basis, Chebyshev basis, etc). A generic symbolic polynomial keeps a | ||
* mapping from a basis element of indeterminates to its coefficient in a | ||
* symbolic expression. A generic polynomial `p` has to satisfy an invariant | ||
* such that `p.decision_variables() ∩ p.indeterminates() = ∅`. We have | ||
* CheckInvariant() method to check the invariant. | ||
* | ||
* Note that arithmetic operations (+,-,*) between a Polynomial and a Variable | ||
* are not provided. The problem is that Variable class has no intrinsic | ||
* information if a variable is a decision variable or an indeterminate while we | ||
* need this information to perform arithmetic operations over Polynomials. | ||
* We provide two instantiations of this template | ||
* - BasisElement = MonomialBasisElement | ||
* - BasisElement = ChebyshevBasisElement | ||
* @tparam BasisElement Must be a subclass of PolynomialBasisElement. | ||
*/ | ||
template <typename BasisElement> | ||
class GenericPolynomial { | ||
public: | ||
static_assert( | ||
std::is_base_of<PolynomialBasisElement, BasisElement>::value, | ||
"BasisElement should be a derived class of PolynomialBasisElement"); | ||
/** Type of mapping from basis element to coefficient */ | ||
using MapType = std::map<BasisElement, Expression>; | ||
|
||
/** Constructs a zero polynomial. */ | ||
GenericPolynomial() = default; | ||
|
||
DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(GenericPolynomial) | ||
|
||
/** Constructs a default value. This overload is used by Eigen when | ||
* EIGEN_INITIALIZE_MATRICES_BY_ZERO is enabled. | ||
*/ | ||
explicit GenericPolynomial(std::nullptr_t) | ||
: GenericPolynomial<BasisElement>() {} | ||
|
||
/** Constructs a generic polynomial from a map, basis_element → coefficient. | ||
* For example | ||
* GenericPolynomial<MonomialBasiElement>({{MonomialBasisElement(x, 2), a}, | ||
* {MonomialBasisElement(x, 3), a+b}}) constructs a polynomial ax²+(a+b)x³.*/ | ||
explicit GenericPolynomial(MapType init); | ||
|
||
/** Constructs a generic polynomial from a single basis element @p m. Note | ||
* that all variables in `m` are considered as indeterminates. Namely the | ||
* constructed generic polynomial contains the map with a single key `m`, with | ||
* the coefficient being 1. | ||
*/ | ||
// Note that this implicit conversion is desirable to have a dot product of | ||
// two Eigen::Vector<BasisElement>s return a GenericPolynomial<BasisElement>. | ||
// NOLINTNEXTLINE(runtime/explicit) | ||
GenericPolynomial(const BasisElement& m); | ||
|
||
/** Returns the indeterminates of this generic polynomial. */ | ||
const Variables& indeterminates() const { return indeterminates_; } | ||
|
||
/** Returns the decision variables of this generic polynomial. */ | ||
const Variables& decision_variables() const { return decision_variables_; } | ||
|
||
/** Returns the map from each basis element to its coefficient. */ | ||
const MapType& basis_element_to_coefficient_map() const { | ||
return basis_element_to_coefficient_map_; | ||
} | ||
|
||
/** Returns the highest degree of this generic polynomial in an indeterminate | ||
* @p v. */ | ||
int Degree(const Variable& v) const; | ||
|
||
/** Returns the total degree of this generic polynomial. */ | ||
int TotalDegree() const; | ||
|
||
/** | ||
* Differentiates this generic polynomial with respect to the variable @p x. | ||
* Note that a variable @p x can be either a decision variable or an | ||
* indeterminate. | ||
*/ | ||
GenericPolynomial<BasisElement> Differentiate(const Variable& x) const; | ||
|
||
/** Returns true if this generic polynomial and @p p are structurally equal. | ||
*/ | ||
bool EqualTo(const GenericPolynomial<BasisElement>& p) const; | ||
|
||
private: | ||
// Throws std::runtime_error if there is a variable appeared in both of | ||
// decision_variables() and indeterminates(). | ||
void CheckInvariant() const; | ||
|
||
MapType basis_element_to_coefficient_map_; | ||
Variables indeterminates_; | ||
Variables decision_variables_; | ||
}; | ||
|
||
template <typename BasisElement> | ||
std::ostream& operator<<(std::ostream& os, | ||
const GenericPolynomial<BasisElement>& p) { | ||
const typename GenericPolynomial<BasisElement>::MapType& map{ | ||
p.basis_element_to_coefficient_map()}; | ||
if (map.empty()) { | ||
return os << 0; | ||
} | ||
auto it = map.begin(); | ||
os << it->second << "*" << it->first; | ||
for (++it; it != map.end(); ++it) { | ||
os << " + " << it->second << "*" << it->first; | ||
} | ||
return os; | ||
} | ||
|
||
extern template class GenericPolynomial<MonomialBasisElement>; | ||
extern template class GenericPolynomial<ChebyshevBasisElement>; | ||
} // namespace symbolic | ||
} // namespace drake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.