Skip to content

Commit

Permalink
analysis: Add simulator_flags helper (#13013)
Browse files Browse the repository at this point in the history
This extends pydrake.systems.Simulator.reset_integrator's deprecation
window because up to this point there was no viable replacement.
  • Loading branch information
jwnimmer-tri authored Apr 11, 2020
1 parent 712424d commit e99ea34
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 84 deletions.
56 changes: 38 additions & 18 deletions bindings/pydrake/systems/analysis_py.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "drake/systems/analysis/runge_kutta2_integrator.h"
#include "drake/systems/analysis/runge_kutta3_integrator.h"
#include "drake/systems/analysis/simulator.h"
#include "drake/systems/analysis/simulator_flags.h"

using std::unique_ptr;

Expand Down Expand Up @@ -113,24 +114,9 @@ PYBIND11_MODULE(analysis, m) {
doc.Simulator.has_context.doc)
.def("reset_context", &Simulator<T>::reset_context, py::arg("context"),
// Keep alive, ownership: `context` keeps `self` alive.
py::keep_alive<2, 1>(), doc.Simulator.reset_context.doc);
// TODO(eric.cousineau): Bind `release_context` once some form of the
// PR RobotLocomotion/pybind11#33 lands. Presently, it fails.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
cls // BR
.def("reset_integrator",
WrapDeprecated(doc.Simulator.reset_integrator.doc_deprecated_1args,
[](Simulator<T>* self,
std::unique_ptr<IntegratorBase<T>> integrator) {
return self->reset_integrator(std::move(integrator));
}),
py::arg("integrator"),
// Keep alive, ownership: `integrator` keeps `self` alive.
py::keep_alive<2, 1>(),
doc.Simulator.reset_integrator.doc_deprecated_1args);
#pragma GCC diagnostic pop
cls // BR
py::keep_alive<2, 1>(), doc.Simulator.reset_context.doc)
// TODO(eric.cousineau): Bind `release_context` once some form of the
// PR RobotLocomotion/pybind11#33 lands. Presently, it fails.
.def("set_publish_every_time_step",
&Simulator<T>::set_publish_every_time_step,
doc.Simulator.set_publish_every_time_step.doc)
Expand All @@ -143,9 +129,43 @@ PYBIND11_MODULE(analysis, m) {
.def("get_actual_realtime_rate",
&Simulator<T>::get_actual_realtime_rate,
doc.Simulator.get_actual_realtime_rate.doc);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
const char* const reset_integrator_doc =
"(Deprecated.) pydrake.systems.Simulator.reset_integrator is "
"deprecated and will be removed from Drake on or after 2020-08-01. "
"Use pydrake.systems.ResetIntegratorFromFlags instead.";
cls // BR
.def("reset_integrator",
WrapDeprecated(reset_integrator_doc,
[](Simulator<T>* self,
std::unique_ptr<IntegratorBase<T>> integrator) {
return self->reset_integrator(std::move(integrator));
}),
py::arg("integrator"),
// Keep alive, ownership: `integrator` keeps `self` alive.
py::keep_alive<2, 1>(), reset_integrator_doc);
#pragma GCC diagnostic pop
};
type_visit(bind_nonsymbolic_scalar_types, NonSymbolicScalarPack{});

// Simulator Flags
m // BR
.def("ResetIntegratorFromFlags",
[](Simulator<double>* simulator, const std::string& scheme,
const double& max_step_size) {
IntegratorBase<double>& result =
ResetIntegratorFromFlags(simulator, scheme, max_step_size);
return &result;
},
py::arg("simulator"), py::arg("scheme"), py::arg("max_step_size"),
py_reference,
// Keep alive, reference: `return` keeps `simulator` alive.
py::keep_alive<0, 1>(),
pydrake_doc.drake.systems.ResetIntegratorFromFlags.doc)
.def("GetIntegrationSchemes", &GetIntegrationSchemes,
pydrake_doc.drake.systems.GetIntegrationSchemes.doc);

// Monte Carlo Testing
{
// NOLINTNEXTLINE(build/namespaces): Emulate placement in namespace.
Expand Down
13 changes: 13 additions & 0 deletions bindings/pydrake/systems/test/general_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
from pydrake.examples.rimless_wheel import RimlessWheel
from pydrake.symbolic import Expression
from pydrake.systems.analysis import (
GetIntegrationSchemes,
IntegratorBase, IntegratorBase_,
ResetIntegratorFromFlags,
RungeKutta2Integrator, RungeKutta3Integrator,
SimulatorStatus, Simulator, Simulator_,
)
Expand Down Expand Up @@ -526,6 +528,17 @@ def test_simulator_integrator_manipulation(self):
# TODO(12873) We need an API for this that isn't deprecated.
simulator.reset_integrator(rk3)

def test_simulator_flags(self):
system = ConstantVectorSource([1])
simulator = Simulator(system)

ResetIntegratorFromFlags(simulator, "runge_kutta2", 0.00123)
integrator = simulator.get_integrator()
self.assertEqual(type(integrator), RungeKutta2Integrator)
self.assertEqual(integrator.get_maximum_step_size(), 0.00123)

self.assertGreater(len(GetIntegrationSchemes()), 5)

def test_abstract_output_port_eval(self):
model_value = AbstractValue.Make("Hello World")
source = ConstantValueSource(copy.copy(model_value))
Expand Down
43 changes: 32 additions & 11 deletions systems/analysis/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,34 @@ drake_cc_package_library(
":scalar_view_dense_output",
":semi_explicit_euler_integrator",
":simulator",
":simulator_flags",
":simulator_print_stats",
":simulator_status",
":stepwise_dense_output",
":velocity_implicit_euler_integrator",
],
)

drake_cc_library(
name = "simulator_flags",
srcs = ["simulator_flags.cc"],
hdrs = ["simulator_flags.h"],
deps = [
":bogacki_shampine3_integrator",
":explicit_euler_integrator",
":implicit_euler_integrator",
":radau_integrator",
":runge_kutta2_integrator",
":runge_kutta3_integrator",
":runge_kutta5_integrator",
":semi_explicit_euler_integrator",
":simulator",
":velocity_implicit_euler_integrator",
"//common:essential",
"//common:nice_type_name",
],
)

drake_cc_library(
name = "simulator_gflags",
srcs = ["simulator_gflags.cc"],
Expand All @@ -55,17 +76,7 @@ drake_cc_library(
],
visibility = ["//:__subpackages__"],
deps = [
":bogacki_shampine3_integrator",
":explicit_euler_integrator",
":implicit_euler_integrator",
":radau_integrator",
":runge_kutta2_integrator",
":runge_kutta3_integrator",
":runge_kutta5_integrator",
":semi_explicit_euler_integrator",
":simulator",
":velocity_implicit_euler_integrator",
"//common:essential",
":simulator_flags",
"@gflags",
],
)
Expand Down Expand Up @@ -382,6 +393,16 @@ drake_cc_library(

# === test/ ===

drake_cc_googletest(
name = "simulator_flags_test",
deps = [
":simulator",
":simulator_flags",
"//common:nice_type_name",
"//systems/primitives:constant_vector_source",
],
)

drake_cc_googletest(
name = "simulator_status_test",
deps = [
Expand Down
3 changes: 3 additions & 0 deletions systems/analysis/integrator_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ namespace systems {
(t₀, x₀). Thus, integrators advance the continuous state of a dynamical
system forward in time.
Drake's subclasses of IntegratorBase<T> should follow the naming pattern
`FooIntegrator<T>` by convention.
@tparam_default_scalar
@ingroup integrators
*/
Expand Down
14 changes: 14 additions & 0 deletions systems/analysis/radau_integrator.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ namespace systems {
*
* @see ImplicitIntegrator class documentation for information about implicit
* integration methods in general.
* @see Radau3Integrator and Radau1Integrator alises for third- and first-order
* templates with num_stages already specified.
* @note This integrator uses the integrator accuracy setting, even when run
* in fixed-step mode, to limit the error in the underlying Newton-Raphson
* process. See IntegratorBase::set_target_accuracy() for more info.
Expand Down Expand Up @@ -216,6 +218,18 @@ class RadauIntegrator final : public ImplicitIntegrator<T> {
int64_t num_err_est_nr_iterations_{0};
};

/** A third-order fully implicit integrator with error estimation.
See RadauIntegrator with `num_stages == 2` for details.
@tparam_nonsymbolic_scalar */
template <typename T>
using Radau3Integrator = RadauIntegrator<T, 2>;

/** A first-order fully implicit integrator with error estimation.
See RadauIntegrator with `num_stages == 1` for details.
@tparam_nonsymbolic_scalar */
template <typename T>
using Radau1Integrator = RadauIntegrator<T, 1>;

} // namespace systems
} // namespace drake

Expand Down
2 changes: 1 addition & 1 deletion systems/analysis/simulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ class Simulator {

template <class U>
DRAKE_DEPRECATED(
"2020-05-01",
"2020-08-01",
"Use void or max-step-size version of reset_integrator() instead.")
U* reset_integrator(std::unique_ptr<U> integrator) {
if (!integrator)
Expand Down
164 changes: 164 additions & 0 deletions systems/analysis/simulator_flags.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#include "drake/systems/analysis/simulator_flags.h"

#include <cctype>
#include <initializer_list>
#include <stdexcept>
#include <utility>

#include "drake/common/drake_throw.h"
#include "drake/common/never_destroyed.h"
#include "drake/common/nice_type_name.h"
#include "drake/common/unused.h"
#include "drake/systems/analysis/bogacki_shampine3_integrator.h"
#include "drake/systems/analysis/explicit_euler_integrator.h"
#include "drake/systems/analysis/implicit_euler_integrator.h"
#include "drake/systems/analysis/radau_integrator.h"
#include "drake/systems/analysis/runge_kutta2_integrator.h"
#include "drake/systems/analysis/runge_kutta3_integrator.h"
#include "drake/systems/analysis/runge_kutta5_integrator.h"
#include "drake/systems/analysis/semi_explicit_euler_integrator.h"
#include "drake/systems/analysis/velocity_implicit_euler_integrator.h"

namespace drake {
namespace systems {
namespace {

using std::function;
using std::pair;
using std::string;
using std::vector;
using symbolic::Expression;

// A functor that implements ResetIntegrator.
template <typename T>
using ResetIntegratorFunc =
function<IntegratorBase<T>*(Simulator<T>*, const T& /* max_step_size */)>;

// Returns (scheme, functor) pair that implements ResetIntegrator.
template <typename T>
using NamedResetIntegratorFunc =
pair<string, ResetIntegratorFunc<T>>;

// Converts the class name of the `Integrator` template argument into a string
// name for the scheme, e.g., FooBarIntegrator<double> becomes "foo_bar".
template <template <typename> class Integrator>
string GetIntegratorName() {
// Get the class name, e.g., FooBarIntegrator<double>.
string full_name = NiceTypeName::Get<Integrator<double>>();
string class_name = NiceTypeName::RemoveNamespaces(full_name);
if (class_name == "RadauIntegrator<double,1>") {
class_name = "Radau1Integrator<double>";
} else if (class_name == "RadauIntegrator<double,2>") {
class_name = "Radau3Integrator<double>";
}

// Strip off "Integrator<double>" suffix to leave just "FooBar".
const string suffix = "Integrator<double>";
DRAKE_DEMAND(class_name.size() > suffix.size());
const size_t suffix_begin = class_name.size() - suffix.size();
DRAKE_DEMAND(class_name.substr(suffix_begin) == suffix);
const string camel_name = class_name.substr(0, suffix_begin);

// Convert "FooBar to "foo_bar".
string result;
for (char ch : camel_name) {
if (std::isupper(ch)) {
if (!result.empty()) { result.push_back('_'); }
result.push_back(std::tolower(ch));
} else {
result.push_back(ch);
}
}
return result;
}

// Returns (scheme, functor) pair to implement reset for this `Integrator`.
// This would be much simpler if all integrators accepted a max_step_size.
template <typename T, template <typename> class Integrator>
NamedResetIntegratorFunc<T> MakeResetter() {
constexpr bool is_fixed_step = std::is_constructible_v<
Integrator<T>,
const System<T>&, T, Context<T>*>;
constexpr bool is_error_controlled = std::is_constructible_v<
Integrator<T>,
const System<T>&, Context<T>*>;
static_assert(is_fixed_step ^ is_error_controlled);
return NamedResetIntegratorFunc<T>(
GetIntegratorName<Integrator>(),
[](Simulator<T>* simulator, const T& max_step_size) {
if constexpr (is_fixed_step) {
IntegratorBase<T>& result =
simulator->template reset_integrator<Integrator<T>>(
max_step_size);
return &result;
} else {
IntegratorBase<T>& result =
simulator->template reset_integrator<Integrator<T>>();
result.set_maximum_step_size(max_step_size);
return &result;
}
});
}

// Returns the full list of supported (scheme, functor) pairs. N.B. The list
// here must be kept in sync with the help string in simulator_gflags.cc.
template <typename T>
const vector<NamedResetIntegratorFunc<T>>& GetAllNamedResetIntegratorFuncs() {
static const never_destroyed<vector<NamedResetIntegratorFunc<T>>> result{
std::initializer_list<NamedResetIntegratorFunc<T>>{
// Keep this list sorted alphabetically.
MakeResetter<T, BogackiShampine3Integrator>(),
MakeResetter<T, ExplicitEulerIntegrator>(),
MakeResetter<T, ImplicitEulerIntegrator>(),
MakeResetter<T, Radau1Integrator>(),
MakeResetter<T, Radau3Integrator>(),
MakeResetter<T, RungeKutta2Integrator>(),
MakeResetter<T, RungeKutta3Integrator>(),
MakeResetter<T, RungeKutta5Integrator>(),
MakeResetter<T, SemiExplicitEulerIntegrator>(),
MakeResetter<T, VelocityImplicitEulerIntegrator>(),
}};
return result.access();
}

} // namespace

template <typename T>
IntegratorBase<T>& ResetIntegratorFromFlags(
Simulator<T>* simulator,
const string& scheme,
const T& max_step_size) {
DRAKE_THROW_UNLESS(simulator != nullptr);

const auto& name_func_pairs = GetAllNamedResetIntegratorFuncs<T>();
for (const auto& [one_name, one_func] : name_func_pairs) {
if (scheme == one_name) {
return *one_func(simulator, max_step_size);
}
}
throw std::runtime_error(fmt::format(
"Unknown integration scheme: {}", scheme));
}

const vector<string>& GetIntegrationSchemes() {
static const never_destroyed<vector<string>> result{[]() {
vector<string> names;
const auto& name_func_pairs = GetAllNamedResetIntegratorFuncs<double>();
for (const auto& [one_name, one_func] : name_func_pairs) {
names.push_back(one_name);
unused(one_func);
}
return names;
}()};
return result.access();
}

// Explicit instantiations.
// We can't support T=Expression because Simulator doesn't support it.
template IntegratorBase<double>& ResetIntegratorFromFlags(
Simulator<double>*, const string&, const double&);
template IntegratorBase<AutoDiffXd>& ResetIntegratorFromFlags(
Simulator<AutoDiffXd>*, const string&, const AutoDiffXd&);

} // namespace systems
} // namespace drake
Loading

0 comments on commit e99ea34

Please sign in to comment.