Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions bindings/generated_docstrings/systems_primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -1847,8 +1847,11 @@ However this system incurs no computational cost. The input to this
system directly feeds through to its output.

The system can also be used to provide default values for a port in
any diagram. If the input port does not have a value, then the default
value passed in the constructor is passed to the output.
any diagram. If the input port does not have a value (and the input is
not required), then the default value passed in the constructor is
passed to the output. Alternatively, the input can be declared as
required, in which case evaluating the output with an unconnected
input will throw.

This system is used, for instance, in PidController which is a Diagram
composed of simple framework primitives. In this case a PassThrough is
Expand Down Expand Up @@ -1876,23 +1879,41 @@ R"""(Constructs a pass-through system.

Parameter ``vector_size``:
number of elements in the signal to be processed. When no input is
connected, the output will be a vector of all zeros.)""";
connected and ``input_required`` is false, the output will be a
vector of all zeros.

Parameter ``input_required``:
If true, then evaluating the output with no input connected will
throw. If false (the default), the zero default value is used
instead.)""";
// Source: drake/systems/primitives/pass_through.h
const char* doc_1args_value =
R"""(Constructs a pass-through system with vector-valued input/output
ports.

Parameter ``value``:
The model value, which defines the size of the ports and serves as
the default when no input is connected.)""";
the default when no input is connected (unless ``input_required``
is true).

Parameter ``input_required``:
If true, then evaluating the output with no input connected will
throw. If false (the default), ``value`` is used as the output
instead.)""";
// Source: drake/systems/primitives/pass_through.h
const char* doc_1args_abstract_model_value =
R"""(Constructs a pass-through system with abstract-valued input/output
ports.

Parameter ``abstract_model_value``:
A model value, which defines the type of the ports and serves as
the default when no input is connected.)""";
the default when no input is connected (unless ``input_required``
is true).

Parameter ``input_required``:
If true, then evaluating the output with no input connected will
throw. If false (the default), ``abstract_model_value`` is used as
the output instead.)""";
// Source: drake/systems/primitives/pass_through.h
const char* doc_copyconvert =
R"""(Scalar-type converting copy constructor. See system_scalar_conversion.)""";
Expand All @@ -1902,6 +1923,13 @@ R"""(Scalar-type converting copy constructor. See system_scalar_conversion.)""";
// Source: drake/systems/primitives/pass_through.h
const char* doc = R"""(Returns the sole input port.)""";
} get_input_port;
// Symbol: drake::systems::PassThrough::input_required
struct /* input_required */ {
// Source: drake/systems/primitives/pass_through.h
const char* doc =
R"""(Returns true iff the input port must be connected before evaluating
the output.)""";
} input_required;
} PassThrough;
// Symbol: drake::systems::PerceptronActivationType
struct /* PerceptronActivationType */ {
Expand Down
14 changes: 10 additions & 4 deletions bindings/pydrake/systems/primitives_py.cc
Original file line number Diff line number Diff line change
Expand Up @@ -469,12 +469,18 @@ PYDRAKE_MODULE(primitives, m) {

DefineTemplateClassWithDefault<PassThrough<T>, LeafSystem<T>>(
m, "PassThrough", GetPyParam<T>(), doc.PassThrough.doc)
.def(py::init<int>(), py::arg("vector_size"),
.def(py::init<int, bool>(), py::arg("vector_size"),
py::arg("input_required") = false,
doc.PassThrough.ctor.doc_1args_vector_size)
.def(py::init<const Eigen::Ref<const VectorXd>&>(), py::arg("value"),
.def(py::init<const Eigen::Ref<const VectorXd>&, bool>(),
py::arg("value"), py::arg("input_required") = false,
doc.PassThrough.ctor.doc_1args_value)
.def(py::init<const AbstractValue&>(), py::arg("abstract_model_value"),
doc.PassThrough.ctor.doc_1args_abstract_model_value);
.def(py::init<const AbstractValue&, bool>(),
py::arg("abstract_model_value"),
py::arg("input_required") = false,
doc.PassThrough.ctor.doc_1args_abstract_model_value)
.def("input_required", &PassThrough<T>::input_required,
doc.PassThrough.input_required.doc);

DefineTemplateClassWithDefault<PortSwitch<T>, LeafSystem<T>>(
m, "PortSwitch", GetPyParam<T>(), doc.PortSwitch.doc)
Expand Down
31 changes: 31 additions & 0 deletions bindings/pydrake/systems/test/primitives_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ def test_bus_selector(self, T):
def test_vector_pass_through(self):
model_value = BasicVector([1.0, 2, 3])
system = PassThrough(vector_size=model_value.size())
self.assertFalse(system.input_required())
context = system.CreateDefaultContext()
system.get_input_port(0).FixValue(context, model_value)
output = system.AllocateOutput()
Expand All @@ -445,6 +446,7 @@ def test_vector_pass_through(self):
def test_default_vector_pass_through(self):
model_value = [1.0, 2, 3]
system = PassThrough(value=model_value)
self.assertFalse(system.input_required())
context = system.CreateDefaultContext()
np.testing.assert_array_equal(
model_value, system.get_output_port().Eval(context)
Expand All @@ -453,6 +455,7 @@ def test_default_vector_pass_through(self):
def test_abstract_pass_through(self):
model_value = Value("Hello world")
system = PassThrough(abstract_model_value=model_value)
self.assertFalse(system.input_required())
context = system.CreateDefaultContext()
system.get_input_port(0).FixValue(context, model_value)
output = system.AllocateOutput()
Expand All @@ -462,6 +465,34 @@ def test_abstract_pass_through(self):
output_value = output.get_data(0)
compare_value(self, output_value, model_value)

def test_pass_through_required_input(self):
model_value = [1.0, 2, 3]
input_value = [4.0, 5, 6]
system = PassThrough(value=model_value, input_required=True)
self.assertTrue(system.input_required())
context = system.CreateDefaultContext()
with self.assertRaisesRegex(RuntimeError, "not connected"):
system.get_output_port().Eval(context)
system.get_input_port().FixValue(context, input_value)
np.testing.assert_array_equal(
input_value, system.get_output_port().Eval(context)
)

abstract_system = PassThrough(
abstract_model_value=Value("default"), input_required=True
)
self.assertTrue(abstract_system.input_required())
abstract_context = abstract_system.CreateDefaultContext()
with self.assertRaisesRegex(RuntimeError, "not connected"):
abstract_system.get_output_port().Eval(abstract_context)
abstract_system.get_input_port().FixValue(
abstract_context, Value("connected")
)
self.assertEqual(
abstract_system.get_output_port().Eval(abstract_context),
"connected",
)

def test_port_switch(self):
system = PortSwitch(vector_size=2)
a = system.DeclareInputPort(name="a")
Expand Down
15 changes: 10 additions & 5 deletions systems/primitives/pass_through.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ namespace systems {
template <typename T>
PassThrough<T>::PassThrough(
const Eigen::Ref<const Eigen::VectorXd>& model_vector,
std::unique_ptr<const AbstractValue> abstract_model_value)
std::unique_ptr<const AbstractValue> abstract_model_value,
bool input_required)
: LeafSystem<T>(SystemTypeTag<PassThrough>()),
model_vector_(model_vector),
abstract_model_value_(std::move(abstract_model_value)) {
abstract_model_value_(std::move(abstract_model_value)),
input_required_(input_required) {
if (!is_abstract()) {
input_port_ =
&this->DeclareVectorInputPort("u", BasicVector<T>(model_vector));
Expand All @@ -38,7 +40,8 @@ template <typename U>
PassThrough<T>::PassThrough(const PassThrough<U>& other)
: PassThrough(other.model_vector_,
other.is_abstract() ? other.abstract_model_value_->Clone()
: nullptr) {}
: nullptr,
other.input_required_) {}

template <typename T>
PassThrough<T>::~PassThrough() = default;
Expand All @@ -47,7 +50,8 @@ template <typename T>
void PassThrough<T>::DoCalcVectorOutput(const Context<T>& context,
BasicVector<T>* output) const {
DRAKE_ASSERT(!is_abstract());
if (this->get_input_port().HasValue(context)) {
if (input_required_ || this->get_input_port().HasValue(context)) {
// When input_required_ is true and the port is unconnected, Eval throws.
const auto& input = this->get_input_port().Eval(context);
DRAKE_ASSERT(input.size() == output->size());
output->get_mutable_value() = input;
Expand All @@ -60,7 +64,8 @@ template <typename T>
void PassThrough<T>::DoCalcAbstractOutput(const Context<T>& context,
AbstractValue* output) const {
DRAKE_ASSERT(is_abstract());
if (this->get_input_port().HasValue(context)) {
if (input_required_ || this->get_input_port().HasValue(context)) {
// When input_required_ is true and the port is unconnected, Eval throws.
output->SetFrom(
this->get_input_port().template Eval<AbstractValue>(context));
} else {
Expand Down
51 changes: 39 additions & 12 deletions systems/primitives/pass_through.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ namespace systems {
/// directly feeds through to its output.
///
/// The system can also be used to provide default values for a port in any
/// diagram. If the input port does not have a value, then the default value
/// passed in the constructor is passed to the output.
/// diagram. If the input port does not have a value (and the input is not
/// required), then the default value passed in the constructor is passed to
/// the output. Alternatively, the input can be declared as required, in which
/// case evaluating the output with an unconnected input will throw.
///
/// This system is used, for instance, in PidController which is a Diagram
/// composed of simple framework primitives. In this case a PassThrough is used
Expand Down Expand Up @@ -46,21 +48,40 @@ class PassThrough final : public LeafSystem<T> {

/// Constructs a pass-through system.
/// @param vector_size number of elements in the signal to be processed.
/// When no input is connected, the output will be a vector of all zeros.
explicit PassThrough(int vector_size)
: PassThrough(Eigen::VectorXd::Zero(vector_size), nullptr) {}
/// When no input is connected and `input_required` is false, the output will
/// be a vector of all zeros.
/// @param input_required If true, then evaluating the output with no input
/// connected will throw. If false (the default), the zero default value is
/// used instead.
/// @pydrake_mkdoc_identifier{1args_vector_size}
explicit PassThrough(int vector_size, bool input_required = false)
: PassThrough(Eigen::VectorXd::Zero(vector_size), nullptr,
input_required) {}

/// Constructs a pass-through system with vector-valued input/output ports.
/// @param value The model value, which defines the size of the ports and
/// serves as the default when no input is connected.
explicit PassThrough(const Eigen::Ref<const Eigen::VectorXd>& value)
: PassThrough(value, nullptr) {}
/// serves as the default when no input is connected (unless `input_required`
/// is true).
/// @param input_required If true, then evaluating the output with no input
/// connected will throw. If false (the default), `value` is used as the
/// output instead.
/// @pydrake_mkdoc_identifier{1args_value}
explicit PassThrough(const Eigen::Ref<const Eigen::VectorXd>& value,
bool input_required = false)
: PassThrough(value, nullptr, input_required) {}

/// Constructs a pass-through system with abstract-valued input/output ports.
/// @param abstract_model_value A model value, which defines the type of the
/// ports and serves as the default when no input is connected.
explicit PassThrough(const AbstractValue& abstract_model_value)
: PassThrough(Vector0<double>(), abstract_model_value.Clone()) {}
/// ports and serves as the default when no input is connected (unless
/// `input_required` is true).
/// @param input_required If true, then evaluating the output with no input
/// connected will throw. If false (the default), `abstract_model_value` is
/// used as the output instead.
/// @pydrake_mkdoc_identifier{1args_abstract_model_value}
explicit PassThrough(const AbstractValue& abstract_model_value,
bool input_required = false)
: PassThrough(Vector0<double>(), abstract_model_value.Clone(),
input_required) {}

/// Scalar-type converting copy constructor.
/// See @ref system_scalar_conversion.
Expand All @@ -78,14 +99,19 @@ class PassThrough final : public LeafSystem<T> {
return *input_port_;
}

/// Returns true iff the input port must be connected before evaluating the
/// output.
bool input_required() const { return input_required_; }

private:
// Allow different specializations to access each other's private data.
template <typename U>
friend class PassThrough;

// All of the other constructors delegate here.
PassThrough(const Eigen::Ref<const Eigen::VectorXd>& model_vector,
std::unique_ptr<const AbstractValue> abstract_model_value);
std::unique_ptr<const AbstractValue> abstract_model_value,
bool input_required);

/// Sets the output port to equal the input port.
void DoCalcVectorOutput(const Context<T>& context,
Expand All @@ -99,6 +125,7 @@ class PassThrough final : public LeafSystem<T> {

const Eigen::VectorXd model_vector_;
const std::unique_ptr<const AbstractValue> abstract_model_value_;
const bool input_required_;

// We store our port pointer so that DoCalcVectorOutput's access to the
// input_port_->Eval is inlined (without any port-count bounds checking).
Expand Down
51 changes: 51 additions & 0 deletions systems/primitives/test/pass_through_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <gtest/gtest.h>

#include "drake/common/autodiff.h"
#include "drake/common/test_utilities/expect_throws_message.h"
#include "drake/math/autodiff_gradient.h"
#include "drake/systems/framework/basic_vector.h"
#include "drake/systems/framework/fixed_input_port_value.h"
Expand Down Expand Up @@ -60,6 +61,9 @@ class PassThroughTest
make_unique<PassThrough<double>>(Value<SimpleAbstractType>(size));
}
}
EXPECT_FALSE(
dynamic_cast<const PassThrough<double>&>(*pass_through_)
.input_required());
context_ = pass_through_->CreateDefaultContext();
}

Expand Down Expand Up @@ -152,6 +156,53 @@ GTEST_TEST(PassThroughTest, AutoDiffFromDouble) {
math::DiscardZeroGradient(pass.get_output_port().Eval(*context)));
}

GTEST_TEST(PassThroughRequiredInputTest, VectorRequiredConnected) {
const Eigen::Vector3d value(1.0, 2.0, 3.0);
const Eigen::Vector3d input(4.0, 5.0, 6.0);
PassThrough<double> dut(value, true);
EXPECT_TRUE(dut.input_required());
auto context = dut.CreateDefaultContext();
dut.get_input_port().FixValue(context.get(), input);
EXPECT_EQ(dut.get_output_port().Eval(*context), input);
}

GTEST_TEST(PassThroughRequiredInputTest, VectorRequiredUnconnectedThrows) {
PassThrough<double> dut(3, true);
EXPECT_TRUE(dut.input_required());
auto context = dut.CreateDefaultContext();
DRAKE_EXPECT_THROWS_MESSAGE(dut.get_output_port().Eval(*context),
".*required InputPort.*\\(u\\).*is not connected.*");
}

GTEST_TEST(PassThroughRequiredInputTest, AbstractRequiredConnected) {
const Eigen::Vector3d value(1.0, 2.0, 3.0);
const Eigen::Vector3d input(4.0, 5.0, 6.0);
PassThrough<double> dut(Value<SimpleAbstractType>(value), true);
EXPECT_TRUE(dut.input_required());
auto context = dut.CreateDefaultContext();
dut.get_input_port().FixValue(context.get(), SimpleAbstractType(input));
EXPECT_EQ(dut.get_output_port().Eval<SimpleAbstractType>(*context).value(),
input);
}

GTEST_TEST(PassThroughRequiredInputTest, AbstractRequiredUnconnectedThrows) {
PassThrough<double> dut(Value<SimpleAbstractType>(3), true);
EXPECT_TRUE(dut.input_required());
auto context = dut.CreateDefaultContext();
DRAKE_EXPECT_THROWS_MESSAGE(dut.get_output_port().Eval(*context),
".*required InputPort.*\\(u\\).*is not connected.*");
}

GTEST_TEST(PassThroughRequiredInputTest, ScalarConversionPreservesFlag) {
PassThrough<double> dut(2, true);
EXPECT_TRUE(is_autodiffxd_convertible(dut, [](const auto& converted) {
EXPECT_TRUE(converted.input_required());
}));
EXPECT_TRUE(is_symbolic_convertible(dut, [](const auto& converted) {
EXPECT_TRUE(converted.input_required());
}));
}

} // namespace
} // namespace systems
} // namespace drake