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:

• double
• drake::AutoDiffXd, an automatic differentation scalar providing partial derivatives of any numerical result of the System with respect to any of the numerical values that can be contained in a Context (time, inputs, parameters, and state).
• drake::symbolic::Expression, a symbolic scalar providing the symbolic form of arithmetic expressions.

## 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, {0.0}); // 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.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:

• MySystem is templated on a scalar type T;
• MySystem is marked final;
• DRAKE_NO_COPY_... disables the built-in copy and move constructors;
• MySystem has whatever usual constructors make sense;
• sometimes it will have a default constructor;
• sometimes it will be given arguments in its constructor;
• as a C++ best practice, constructors should delegate into one primary constructor;
• we see that above in the first example where the default constructor delegates to the one-argument constructor;
• all MySystem constructors must pass a SystemTypeTag to their superclass constructor; if all MySystem constructors delegate to a single one, this is easy because only that one needs to provide the SystemTypeTag value;
• MySystem has a public scalar-converting copy constructor;
• the constructor takes a reference to MySystem<U> and delegates to another constructor;
• the section How to write a scalar-converting copy constructor below provides more details on how to implement this constructor.

### 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:

• Non-final classes like MySystemBase must offer a protected constructor that takes a SystemScalarConverter as the first argument.
• Constructors for derived classes such as MySystemDerived must delegate to a base class protected constructor that takes a SystemScalarConverter, never to a public constructor without one.
• MySystemBase and MySystemDerived both have a public scalar-converting copy constructor;
• if the base system is abstract (cannot be constructed), then it may omit this constructor.

### 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()) {}

## How to create a Diagram that supports scalar conversion

In the typical case, no special effort is needed to create a Diagram that support scalar-type conversion. The Diagram does not even need to be templated on a scalar type T.

Example using DiagramBuilder::BuildInto:

namespace sample {
class MyDiagram : public Diagram<double> {
public:
MyDiagram() {
DiagramBuilder<double> builder;
const auto* integrator = builder.AddSystem<Integrator<double>>(1);
builder.ExportInput(integrator->get_input_port());
builder.ExportOutput(integrator->get_output_port());
builder.BuildInto(this);
}
};

In this example, MyDiagram will support the same scalar types as the Integrator. If any sub-system had been added that did not support, e.g., symbolic form, then the Diagram would also not support symbolic form.

By default, even subclasses of a Diagram<U> will convert to a Diagram<T>, discarding the diagram subclass details. For example, in the above sample code, MyDiagram::ToAutoDiffXd() will return an object of runtime type Diagram<AutoDiffXd>, not type MyDiagram<AutoDiffXd>. (There is no such class as MyDiagram<AutoDiffXd> anyway, because MyDiagram is not templated.)

In the unusual case that the Diagram's subclass must be preserved during conversion, a drake::systems::SystemTypeTag should be used:

Example using DiagramBuilder::BuildInto along with a SystemTypeTag:

namespace sample {
template <typename T>
class SpecialDiagram<T> final : public Diagram<T> {
public:
SpecialDiagram() : Diagram<T>(SystemTypeTag<sample::SpecialDiagram>{}) {
DiagramBuilder<T> builder;
const auto* integrator = builder.template AddSystem<Integrator<T>>(1);
builder.ExportInput(integrator->get_input_port());
builder.ExportOutput(integrator->get_output_port());
builder.BuildInto(this);
}
};