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.
See the default scalars for list of supported types.
Example use of system scalar conversion
This is a small example to illustrate the concept.
auto plant = std::make_unique<PendulumPlant<double>>();
auto context = plant->CreateDefaultContext();
plant->get_input_port(0).FixValue(context.get(), 0.0);
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);
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());
constexpr int kNumDerivatives = 1;
auto& xc = *autodiff_context->get_mutable_continuous_state_vector();
xc[PendulumStateIndices::kTheta].derivatives() =
MatrixXd::Identity(kNumDerivatives, kNumDerivatives).col(0);
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.
- Warning
- The supported method to perform scalar conversion uses the member functions on System, e.g., System::ToAutoDiffXd. The scalar-converting copy constructor (described below) is intended for internal use only. For example:
PendulumPlant<double> plant;
PendulumPlant<AutoDiffXd> plant_autodiff(plant);
std::unique_ptr<PendulumPlant<AutoDiffXd>> autodiff_plant =
System<double>::ToAutoDiffXd(plant);
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:
MySystem() : MySystem(1.0) {}
explicit MySystem(double gain)
: LeafSystem<T>(SystemTypeTag<MySystem>{}), gain_{gain} {}
template <typename U>
explicit MySystem(const MySystem<U>& other) : MySystem<T>(other.gain()) {}
double gain() const { return gain_; }
...
#define DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(Classname)
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN deletes the special member functions for copy-construction,...
Definition drake_copyable.h:31
Example using drake::systems::VectorSystem as the base class:
namespace sample {
template <typename T>
class MySystem final : public VectorSystem<T> {
public:
MySystem() : VectorSystem<T>(SystemTypeTag<MySystem>{}, 1, 1) {}
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:
explicit MySystemBase(double gain)
: LeafSystem<T>(SystemTypeTag<MySystemBase>{}), gain_{gain} {}
template <typename U>
explicit MySystemBase(const MySystemBase<U>& other)
: MySystemBase<T>(other.gain()) {}
double gain() const { return gain_; }
protected:
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:
MySystemDerived() : MySystemBase<T>(
SystemTypeTag<MySystemDerived>{},
1.0) {}
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 documentation.
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 {
template <typename U>
explicit Foo(const Foo<U>& other);
};
Here, U is the source 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<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<MySystem>{}, 1, 1) {}
template <typename U> explicit MySystem(const MySystem<U>&)
: VectorSystem<T>(SystemTypeTag<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<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);
}
template <typename U>
explicit SpecialDiagram(const SpecialDiagram<U>& other)
: Diagram<T>(SystemTypeTag<SpecialDiagram>{}, other) {}
};