Drake
System Scalar Conversion

System scalar conversion refers to cloning a System templatized by one scalar type into an identical System that is templatized by a different scalar type.

For example, a MySystem<double> could be cloned to create a MySystem<AutoDiffXd> in order to compute the partial numerical derivatives.

Common scalar types include:

Example use of system scalar conversion

This is a small example to illustrate the concept.

// Establish the plant and its initial conditions:
// tau = 0, theta = 0.1, thetadot = 0.2.
auto plant = std::make_unique<PendulumPlant<double>>();
auto context = plant->CreateDefaultContext();
context->FixInputPort(0, Vector1d::Zero()); // tau
auto* state = dynamic_cast<PendulumState<double>*>(
context->get_mutable_continuous_state_vector());
state->set_theta(0.1);
state->set_thetadot(0.2);
double energy = plant->CalcTotalEnergy(*context);
ASSERT_NEAR(energy, -4.875, 0.001);
// Convert the plant and its context to use AutoDiff.
auto autodiff_plant = System<double>::ToAutoDiffXd(*plant);
auto autodiff_context = autodiff_plant->CreateDefaultContext();
autodiff_context->SetTimeStateAndParametersFrom(*context);
autodiff_plant->FixInputPortsFrom(*plant, *context, autodiff_context.get());
// Differentiate with respect to theta by setting dtheta/dtheta = 1.0.
constexpr int kNumDerivatives = 1;
auto& xc = *autodiff_context->get_mutable_continuous_state_vector();
xc[PendulumStateIndices::kTheta].derivatives() =
MatrixXd::Identity(kNumDerivatives, kNumDerivatives).col(0);
// Compute denergy/dtheta around its initial conditions.
AutoDiffXd autodiff_energy =
autodiff_plant->CalcTotalEnergy(*autodiff_context);
ASSERT_NEAR(autodiff_energy.value(), -4.875, 0.001);
ASSERT_EQ(autodiff_energy.derivatives().size(), 1);
ASSERT_NEAR(autodiff_energy.derivatives()[0], 0.490, 0.001);

For a more thorough example, refer to the implementation of drake::systems::Linearize.

How to write a System that supports scalar conversion

In the typical case, Drake users' Systems should be marked with the C++ keyword final to prevent them from being subclassed. Whether or not the System is final determines the best way to support scalar conversion.

In the examples below, we use MySystem as the name of a System class being implemented by a Drake user.

Systems marked as final

For system marked with final, the two examples below show how to enable system scalar conversion.

Example using drake::systems::LeafSystem as the base class:

namespace sample {
template <typename T>
class MySystem final : public LeafSystem<T> {
public:
// Constructs a system with a gain of 1.0.
MySystem() : MySystem(1.0) {}
// Constructs a system with the given `gain`.
explicit MySystem(double gain)
: LeafSystem<T>(SystemTypeTag<sample::MySystem>{}), gain_{gain} {}
// Scalar-converting copy constructor. See @ref system_scalar_conversion.
template <typename U>
explicit MySystem(const MySystem<U>& other) : MySystem<T>(other.gain()) {}
// Returns the gain of this system.
double gain() const { return gain_; }
...

Example using drake::systems::VectorSystem as the base class:

namespace sample {
template <typename T>
class MySystem final : public VectorSystem<T> {
public:
// Default constructor.
MySystem() : VectorSystem<T>(SystemTypeTag<sample::MySystem>{}, 1, 1) {}
// Scalar-converting copy constructor. See @ref system_scalar_conversion.
template <typename U>
explicit MySystem(const MySystem<U>&) : MySystem<T>() {}
...

The relevant details of the examples are:

Systems not marked as final

For a class hierarchy of Systems that support scalar conversion, a slightly different pattern is required.

namespace sample {
template <typename T>
class MySystemBase : public LeafSystem<T> {
public:
// Constructs a system with the given `gain`.
// Subclasses must use the protected constructor, not this one.
explicit MySystemBase(double gain)
: LeafSystem<T>(SystemTypeTag<sample::MySystemBase>{}), gain_{gain} {}
// Scalar-converting copy constructor. See @ref system_scalar_conversion.
template <typename U>
explicit MySystemBase(const MySystemBase<U>& other)
: MySystemBase<T>(other.gain()) {}
// Returns the gain of this system.
double gain() const { return gain_; }
protected:
// Constructor that specifies scalar-type conversion support.
// @param converter scalar-type conversion support helper (i.e., AutoDiff,
// etc.); pass a default-constructed object if such support is not desired.
explicit MySystemBase(SystemScalarConverter converter, double gain)
: LeafSystem<T>(std::move(converter)), gain_{gain} {}
...
namespace sample {
template <typename T>
class MySystemDerived final : public MySystemBase<T> {
public:
// Constructs a system with a gain of 1.0.
MySystemDerived() : MySystemBase<T>(
SystemTypeTag<sample::MySystemDerived>{},
1.0) {}
// Scalar-converting copy constructor. See @ref system_scalar_conversion.
template <typename U>
explicit MySystemDerived(const MySystemDerived<U>&) : MySystemDerived<T>() {}
...

The relevant details of the examples are:

Limiting the supported scalar types

The framework's default implementation System scalar-type conversion only converts between a limited set of scalar types, as enumerated by the drake::systems::SystemScalarConverter::SystemScalarConverter(SystemTypeTag<S>) constructor.

Systems may specialize their drake::systems::scalar_conversion::Traits to govern the supported scalar types. The recommended mechanism is to use drake::systems::scalar_conversion::NonSymbolicTraits or drake::systems::scalar_conversion::FromDoubleTraits.

How to write a scalar-converting copy constructor

The scalar-converting copy constructor uses the following form:

template <typename T>
class Foo {
// Scalar-converting copy constructor.
template <typename U>
explicit Foo(const Foo<U>& other);
};

Here, U is the donor scalar type (to convert from), and T the resulting scalar type (to convert into). For example, in the second line of

Foo<double> foo;
Foo<AutoDiffXd> autodiff_foo{foo};

we are calling the constructor Foo<AutoDiffXd>::Foo(const Foo<double>&) so we have U = double and T = AutoDiffXd. In other words, we start with a double-valued object and use it to create an AutoDiffXd-valued object.

Delegation

In almost all cases, the implementation of the scalar-converting copy constructor should delegate to another regular constructor, rather than re-implementing its logic. For example, in the VectorSystem-based example above we have:

MySystem() : VectorSystem<T>(SystemTypeTag<sample::MySystem>{}, 1, 1) {}
template <typename U> explicit MySystem(const MySystem<U>&) : MySystem<T>() {}

The default constructor configures the System to have a input_size == 1 and output_size == 1. The scalar-converting copy constructor delegates to the default constructor to re-use that logic by stating : MySystem<T>().

Without delegation, we would have to duplicate those arguments:

MySystem() : VectorSystem<T>(SystemTypeTag<sample::MySystem>{}, 1, 1) {}
// NOT RECOMMENDED because it duplicates the details of calling VectorSystem.
template <typename U> explicit MySystem(const MySystem<U>&)
: VectorSystem<T>(SystemTypeTag<sample::MySystem>{}, 1, 1) {}

Instance data

If the object being copied from (usually named other) has any instance data or customizations, the scalar-converting copy constructor should propagate them from other to this. For example, in the LeafSystem-based example above, we have:

template <typename U>
explicit MySystem(const MySystem<U>& other) : MySystem<T>(other.gain()) {}