forked from pz4kybsvg/Conception
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
501 lines
16 KiB
501 lines
16 KiB
#include "drake/systems/primitives/affine_system.h"
|
|
|
|
#include <set>
|
|
#include <utility>
|
|
|
|
#include <Eigen/Eigenvalues>
|
|
|
|
#include "drake/common/default_scalars.h"
|
|
#include "drake/common/drake_assert.h"
|
|
#include "drake/common/drake_throw.h"
|
|
#include "drake/common/eigen_types.h"
|
|
#include "drake/common/symbolic/decompose.h"
|
|
#include "drake/systems/framework/basic_vector.h"
|
|
|
|
namespace drake {
|
|
namespace systems {
|
|
|
|
using std::make_unique;
|
|
using std::unique_ptr;
|
|
|
|
template <typename T>
|
|
TimeVaryingAffineSystem<T>::TimeVaryingAffineSystem(
|
|
SystemScalarConverter converter, int num_states, int num_inputs,
|
|
int num_outputs, double time_period)
|
|
: LeafSystem<T>(std::move(converter)),
|
|
num_states_(num_states),
|
|
num_inputs_(num_inputs),
|
|
num_outputs_(num_outputs),
|
|
time_period_(time_period),
|
|
x0_(VectorX<T>::Zero(num_states)),
|
|
Sqrt_Sigma_x0_(Eigen::MatrixXd::Zero(num_states, num_states)) {
|
|
DRAKE_DEMAND(num_states_ >= 0);
|
|
DRAKE_DEMAND(num_inputs_ >= 0);
|
|
DRAKE_DEMAND(num_outputs_ >= 0);
|
|
DRAKE_DEMAND(time_period_ >= 0.0);
|
|
|
|
// Declare state and input/output ports.
|
|
// Declares the state variables and (potentially) the discrete-time update.
|
|
if (num_states > 0) {
|
|
if (time_period_ == 0.0) {
|
|
this->DeclareContinuousState(num_states_);
|
|
} else {
|
|
this->DeclareDiscreteState(num_states_);
|
|
this->DeclarePeriodicDiscreteUpdateEvent(
|
|
time_period_, 0.0, &TimeVaryingAffineSystem<T>::CalcDiscreteUpdate);
|
|
|
|
// Allow a forced update to trigger the same handler.
|
|
this->DeclareForcedDiscreteUpdateEvent(
|
|
&TimeVaryingAffineSystem<T>::CalcDiscreteUpdate);
|
|
}
|
|
}
|
|
|
|
if (num_inputs_ > 0)
|
|
this->DeclareInputPort(kUseDefaultName, kVectorValued, num_inputs_);
|
|
if (num_outputs_ > 0) {
|
|
// N.B. Subclasses that override CalcOutputY may want to fine-tune the
|
|
// output port's prerequisites; see AffineSystem's ctor for an example.
|
|
// By default, the output port will depend on everything (time, input,
|
|
// state, accuracy, etc.).
|
|
this->DeclareVectorOutputPort(kUseDefaultName, num_outputs_,
|
|
&TimeVaryingAffineSystem::CalcOutputY);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
const InputPort<T>& TimeVaryingAffineSystem<T>::get_input_port()
|
|
const {
|
|
DRAKE_DEMAND(num_inputs_ > 0);
|
|
return System<T>::get_input_port(0);
|
|
}
|
|
|
|
template <typename T>
|
|
const OutputPort<T>& TimeVaryingAffineSystem<T>::get_output_port()
|
|
const {
|
|
DRAKE_DEMAND(num_outputs_ > 0);
|
|
return System<T>::get_output_port(0);
|
|
}
|
|
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::configure_default_state(
|
|
const Eigen::Ref<const VectorX<T>>& x0) {
|
|
DRAKE_DEMAND(x0.rows() == num_states_);
|
|
if (num_states_ == 0) return;
|
|
x0_ = x0;
|
|
}
|
|
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::configure_random_state(
|
|
const Eigen::Ref<const Eigen::MatrixXd>& covariance) {
|
|
DRAKE_DEMAND(covariance.rows() == num_states_);
|
|
DRAKE_DEMAND(covariance.cols() == num_states_);
|
|
if (num_states_ == 0) return;
|
|
Sqrt_Sigma_x0_ = Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd>(covariance)
|
|
.operatorSqrt();
|
|
}
|
|
|
|
template <typename T>
|
|
template <typename U>
|
|
void TimeVaryingAffineSystem<T>::ConfigureDefaultAndRandomStateFrom(
|
|
const TimeVaryingAffineSystem<U>& other) {
|
|
// Convert default state from U -> double -> T.
|
|
VectorX<T> x0(other.num_states());
|
|
const VectorX<U>& other_x0 = other.get_default_state();
|
|
for (int i = 0; i < other.num_states(); i++) {
|
|
x0[i] = ExtractDoubleOrThrow(other_x0[i]);
|
|
}
|
|
this->configure_default_state(x0);
|
|
this->configure_random_state(other.get_random_state_covariance());
|
|
}
|
|
|
|
// This is the default implementation for this virtual method.
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::CalcOutputY(
|
|
const Context<T>& context, BasicVector<T>* output_vector) const {
|
|
const T t = context.get_time();
|
|
|
|
VectorX<T> y = y0(t);
|
|
DRAKE_DEMAND(y.rows() == num_outputs_);
|
|
|
|
if (num_states_ > 0) {
|
|
const MatrixX<T> Ct = C(t);
|
|
DRAKE_DEMAND(Ct.rows() == num_outputs_ && Ct.cols() == num_states_);
|
|
const VectorX<T>& x = (this->time_period() == 0.)
|
|
? dynamic_cast<const BasicVector<T>&>(
|
|
context.get_continuous_state_vector()).get_value()
|
|
: context.get_discrete_state().get_vector().get_value();
|
|
y += Ct * x;
|
|
}
|
|
|
|
if (num_inputs_ > 0) {
|
|
const auto& u = get_input_port().Eval(context);
|
|
const MatrixX<T> Dt = D(t);
|
|
DRAKE_DEMAND(Dt.rows() == num_outputs_ && Dt.cols() == num_inputs_);
|
|
y += Dt * u;
|
|
}
|
|
|
|
output_vector->SetFromVector(y);
|
|
}
|
|
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::DoCalcTimeDerivatives(
|
|
const Context<T>& context, ContinuousState<T>* derivatives) const {
|
|
if (num_states_ == 0 || time_period_ > 0) return;
|
|
|
|
const T t = context.get_time();
|
|
|
|
VectorX<T> xdot = f0(t);
|
|
DRAKE_THROW_UNLESS(xdot.rows() == num_states_);
|
|
|
|
const auto& x =
|
|
dynamic_cast<const BasicVector<T>&>(context.get_continuous_state_vector())
|
|
.get_value();
|
|
const MatrixX<T> At = A(t);
|
|
DRAKE_THROW_UNLESS(At.rows() == num_states_ && At.cols() == num_states_);
|
|
xdot += At * x;
|
|
|
|
if (num_inputs_ > 0) {
|
|
const auto& u = get_input_port().Eval(context);
|
|
|
|
const MatrixX<T> Bt = B(t);
|
|
DRAKE_THROW_UNLESS(Bt.rows() == num_states_ && Bt.cols() == num_inputs_);
|
|
xdot += Bt * u;
|
|
}
|
|
derivatives->SetFromVector(xdot);
|
|
}
|
|
|
|
// This is the default implementation; may be overridden in derived classes.
|
|
template <typename T>
|
|
EventStatus TimeVaryingAffineSystem<T>::CalcDiscreteUpdate(
|
|
const Context<T>& context, DiscreteValues<T>* updates) const {
|
|
if (num_states_ == 0 || time_period_ == 0.0)
|
|
return EventStatus::DidNothing();
|
|
|
|
const T t = context.get_time();
|
|
|
|
// TODO(russt): consider demanding that t is a multiple of time_period_.
|
|
// But this could be non-trivial for non-double T.
|
|
|
|
VectorX<T> xn = f0(t);
|
|
DRAKE_DEMAND(xn.rows() == num_states_);
|
|
|
|
const auto& x = context.get_discrete_state(0).get_value();
|
|
|
|
const MatrixX<T> At = A(t);
|
|
DRAKE_DEMAND(At.rows() == num_states_ && At.cols() == num_states_);
|
|
xn += At * x;
|
|
|
|
if (num_inputs_ > 0) {
|
|
const auto& u = get_input_port().Eval(context);
|
|
|
|
const MatrixX<T> Bt = B(t);
|
|
DRAKE_DEMAND(Bt.rows() == num_states_ && Bt.cols() == num_inputs_);
|
|
xn += Bt * u;
|
|
}
|
|
updates->set_value(xn);
|
|
return EventStatus::Succeeded();
|
|
}
|
|
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::SetDefaultState(const Context<T>& context,
|
|
State<T>* state) const {
|
|
unused(context);
|
|
if (num_states_ == 0) return;
|
|
|
|
if (time_period_ == 0.0) {
|
|
state->get_mutable_continuous_state().SetFromVector(x0_);
|
|
} else {
|
|
state->get_mutable_discrete_state(0).SetFromVector(x0_);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void TimeVaryingAffineSystem<T>::SetRandomState(
|
|
const Context<T>& context, State<T>* state,
|
|
RandomGenerator* generator) const {
|
|
unused(context);
|
|
if (num_states_ == 0) return;
|
|
|
|
Eigen::VectorXd w(num_states_);
|
|
std::normal_distribution<double> normal;
|
|
for (int i = 0; i < num_states_; i++) {
|
|
w[i] = normal(*generator);
|
|
}
|
|
const auto x0 = x0_ + Sqrt_Sigma_x0_ * w;
|
|
if (time_period_ == 0.0) {
|
|
state->get_mutable_continuous_state().SetFromVector(x0);
|
|
} else {
|
|
state->get_mutable_discrete_state(0).SetFromVector(x0);
|
|
}
|
|
}
|
|
|
|
// Our public constructor declares that our most specific subclass is
|
|
// AffineSystem, and then delegates to our protected constructor.
|
|
template <typename T>
|
|
AffineSystem<T>::AffineSystem(
|
|
const Eigen::Ref<const Eigen::MatrixXd>& A,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& B,
|
|
const Eigen::Ref<const Eigen::VectorXd>& f0,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& C,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& D,
|
|
const Eigen::Ref<const Eigen::VectorXd>& y0,
|
|
|
|
double time_period)
|
|
: AffineSystem<T>(SystemTypeTag<AffineSystem>{}, A, B, f0, C, D, y0,
|
|
time_period) {}
|
|
|
|
namespace {
|
|
|
|
// Returns the number of states, where any empty matrix is assumed to have the
|
|
// correct size.
|
|
int CalcNumStates(const Eigen::Ref<const Eigen::MatrixXd>& A,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& B,
|
|
const Eigen::Ref<const Eigen::VectorXd>& f0,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& C) {
|
|
int num_states = 0;
|
|
if (A.size() > 0) {
|
|
num_states = A.rows();
|
|
DRAKE_DEMAND(A.rows() == A.cols());
|
|
}
|
|
if (B.size() > 0) {
|
|
if (num_states) {
|
|
// This error message is more informative than comparing with num_states.
|
|
DRAKE_DEMAND(B.rows() == A.rows());
|
|
} else {
|
|
num_states = B.rows();
|
|
}
|
|
}
|
|
if (f0.size() > 0) {
|
|
if (num_states) {
|
|
DRAKE_DEMAND(f0.size() == num_states);
|
|
} else {
|
|
num_states = f0.size();
|
|
}
|
|
}
|
|
if (C.size() > 0) {
|
|
if (num_states) {
|
|
DRAKE_DEMAND(C.cols() == num_states);
|
|
} else {
|
|
num_states = C.cols();
|
|
}
|
|
}
|
|
return num_states;
|
|
}
|
|
|
|
// Returns the number of inputs, where any empty matrix is assumed to have the
|
|
// correct size.
|
|
int CalcNumInputs(const Eigen::Ref<const Eigen::MatrixXd>& B,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& D) {
|
|
int num_inputs = 0;
|
|
if (B.size() > 0) {
|
|
num_inputs = B.cols();
|
|
}
|
|
if (D.size() > 0) {
|
|
if (num_inputs) {
|
|
DRAKE_DEMAND(D.cols() == B.cols());
|
|
} else {
|
|
num_inputs = D.cols();
|
|
}
|
|
}
|
|
return num_inputs;
|
|
}
|
|
|
|
// Returns the number of outputs, where any empty matrix is assumed to have the
|
|
// correct size.
|
|
int CalcNumOutputs(const Eigen::Ref<const Eigen::MatrixXd>& C,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& D,
|
|
const Eigen::Ref<const Eigen::VectorXd>& y0) {
|
|
int num_outputs = 0;
|
|
if (C.size() > 0) {
|
|
num_outputs = C.rows();
|
|
}
|
|
if (D.size() > 0) {
|
|
if (num_outputs) {
|
|
DRAKE_DEMAND(D.rows() == C.rows());
|
|
} else {
|
|
num_outputs = D.rows();
|
|
}
|
|
}
|
|
if (y0.size() > 0) {
|
|
if (num_outputs) {
|
|
DRAKE_DEMAND(y0.size() == num_outputs);
|
|
} else {
|
|
num_outputs = y0.size();
|
|
}
|
|
}
|
|
return num_outputs;
|
|
}
|
|
|
|
// Returns whether a matrix is "meaningful" when pre-multiplying a vector.
|
|
bool IsMeaningful(const Eigen::Ref<const Eigen::MatrixXd>& m) {
|
|
return m.size() > 0 && (m.array() != 0).any();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Our protected constructor does all of the real work -- everything else
|
|
// delegates to here.
|
|
template <typename T>
|
|
AffineSystem<T>::AffineSystem(SystemScalarConverter converter,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& A,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& B,
|
|
const Eigen::Ref<const Eigen::VectorXd>& f0,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& C,
|
|
const Eigen::Ref<const Eigen::MatrixXd>& D,
|
|
const Eigen::Ref<const Eigen::VectorXd>& y0,
|
|
double time_period)
|
|
: TimeVaryingAffineSystem<T>(
|
|
std::move(converter), CalcNumStates(A, B, f0, C), CalcNumInputs(B, D),
|
|
CalcNumOutputs(C, D, y0), time_period),
|
|
A_(A.size()
|
|
? A
|
|
: Eigen::MatrixXd::Zero(this->num_states(), this->num_states())),
|
|
B_(B.size()
|
|
? B
|
|
: Eigen::MatrixXd::Zero(this->num_states(), this->num_inputs())),
|
|
f0_(f0.size() ? f0 : Eigen::VectorXd::Zero(this->num_states())),
|
|
C_(C.size()
|
|
? C
|
|
: Eigen::MatrixXd::Zero(this->num_outputs(), this->num_states())),
|
|
D_(D.size()
|
|
? D
|
|
: Eigen::MatrixXd::Zero(this->num_outputs(), this->num_inputs())),
|
|
y0_(y0.size() ? y0 : Eigen::VectorXd::Zero(this->num_outputs())),
|
|
has_meaningful_C_(IsMeaningful(C)),
|
|
has_meaningful_D_(IsMeaningful(D)) {
|
|
// Specify our output port's dependencies more precisely than our base class
|
|
// is able to. We know that output never depends on time nor parameters,
|
|
// only on state (iff C if non-zero) and input (iff D is non-zero).
|
|
if (this->num_outputs() > 0) {
|
|
const OutputPort<T>& output_port = this->get_output_port();
|
|
const auto& leaf_port = dynamic_cast<const LeafOutputPort<T>&>(output_port);
|
|
const CacheIndex cache_index = leaf_port.cache_entry().cache_index();
|
|
CacheEntry& cache_entry = this->get_mutable_cache_entry(cache_index);
|
|
std::set<DependencyTicket>& prereqs = cache_entry.mutable_prerequisites();
|
|
prereqs.clear();
|
|
if (has_meaningful_C_) {
|
|
prereqs.insert(this->all_state_ticket());
|
|
}
|
|
if (has_meaningful_D_) {
|
|
prereqs.insert(this->all_input_ports_ticket());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Our copy constructor delegates to the public constructor; this used only by
|
|
// SystemScalarConverter as known to our public constructor, not by subclasses.
|
|
template <typename T>
|
|
template <typename U>
|
|
AffineSystem<T>::AffineSystem(const AffineSystem<U>& other)
|
|
: AffineSystem(other.A(), other.B(), other.f0(), other.C(), other.D(),
|
|
other.y0(), other.time_period()) {
|
|
this->ConfigureDefaultAndRandomStateFrom(other);
|
|
}
|
|
|
|
template <typename T>
|
|
unique_ptr<AffineSystem<T>> AffineSystem<T>::MakeAffineSystem(
|
|
const Eigen::Ref<const VectorX<symbolic::Expression>>& dynamics,
|
|
const Eigen::Ref<const VectorX<symbolic::Expression>>& output,
|
|
const Eigen::Ref<const VectorX<symbolic::Variable>>& state_vars,
|
|
const Eigen::Ref<const VectorX<symbolic::Variable>>& input_vars,
|
|
const double time_period) {
|
|
// Need to extract, A, B, f₀, C, D, y₀ such that,
|
|
//
|
|
// dynamics = Ax + Bu + f₀
|
|
// output = Cx + Du + y₀
|
|
//
|
|
// where x = state_vars and u = input_vars.
|
|
const int num_states = state_vars.size();
|
|
DRAKE_DEMAND(num_states == dynamics.size());
|
|
const int num_inputs = input_vars.size();
|
|
const int num_outputs = output.size();
|
|
|
|
Eigen::MatrixXd AB(num_states, num_states + num_inputs);
|
|
Eigen::VectorXd f0(num_states);
|
|
VectorX<symbolic::Variable> vars(num_states + num_inputs);
|
|
vars << state_vars, input_vars;
|
|
DecomposeAffineExpressions(dynamics, vars, &AB, &f0);
|
|
const auto A = AB.leftCols(num_states);
|
|
const auto B = AB.rightCols(num_inputs);
|
|
|
|
Eigen::MatrixXd CD(num_outputs, num_states + num_inputs);
|
|
Eigen::VectorXd y0(num_outputs);
|
|
DecomposeAffineExpressions(output, vars, &CD, &y0);
|
|
const auto C = CD.leftCols(num_states);
|
|
const auto D = CD.rightCols(num_inputs);
|
|
|
|
return make_unique<AffineSystem<T>>(A, B, f0, C, D, y0, time_period);
|
|
}
|
|
|
|
template <typename T>
|
|
void AffineSystem<T>::CalcOutputY(const Context<T>& context,
|
|
BasicVector<T>* output_vector) const {
|
|
auto y = output_vector->get_mutable_value();
|
|
y = y0_;
|
|
|
|
if (has_meaningful_C_) {
|
|
const VectorX<T>& x = (this->time_period() == 0.)
|
|
? dynamic_cast<const BasicVector<T>&>(
|
|
context.get_continuous_state_vector()).get_value()
|
|
: context.get_discrete_state().get_vector().get_value();
|
|
y += C_ * x;
|
|
}
|
|
|
|
if (has_meaningful_D_) {
|
|
const auto& u = this->get_input_port().Eval(context);
|
|
y += D_ * u;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void AffineSystem<T>::DoCalcTimeDerivatives(
|
|
const Context<T>& context, ContinuousState<T>* derivatives) const {
|
|
if (this->num_states() == 0 || this->time_period() > 0.0) return;
|
|
|
|
const auto& x =
|
|
dynamic_cast<const BasicVector<T>&>(context.get_continuous_state_vector())
|
|
.get_value();
|
|
|
|
VectorX<T> xdot = A_ * x + f0_;
|
|
|
|
if (this->num_inputs() > 0) {
|
|
const auto& u = this->get_input_port().Eval(context);
|
|
|
|
xdot += B_ * u;
|
|
}
|
|
derivatives->SetFromVector(xdot);
|
|
}
|
|
|
|
// Overrides the base class default event handler with a simpler one.
|
|
template <typename T>
|
|
EventStatus AffineSystem<T>::CalcDiscreteUpdate(
|
|
const Context<T>& context, DiscreteValues<T>* updates) const {
|
|
if (this->num_states() == 0 || this->time_period() == 0.0)
|
|
return EventStatus::DidNothing();
|
|
|
|
const auto& x = context.get_discrete_state(0).get_value();
|
|
|
|
VectorX<T> xnext = A_ * x + f0_;
|
|
|
|
if (this->num_inputs() > 0) {
|
|
const auto& u = this->get_input_port().Eval(context);
|
|
|
|
xnext += B_ * u;
|
|
}
|
|
updates->set_value(xnext);
|
|
return EventStatus::Succeeded();
|
|
}
|
|
|
|
DRAKE_DEFINE_FUNCTION_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_SCALARS((
|
|
&TimeVaryingAffineSystem<T>::template ConfigureDefaultAndRandomStateFrom<U>
|
|
))
|
|
|
|
} // namespace systems
|
|
} // namespace drake
|
|
|
|
DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_SCALARS(
|
|
class ::drake::systems::TimeVaryingAffineSystem)
|
|
|
|
DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_SCALARS(
|
|
class ::drake::systems::AffineSystem)
|