Create a simple system

Before starting, make sure you understand the basic Drake concept at this link.

Drake Concepts

Goal

Given a Equation of Motion (EoM) of a particle mass system, create the exact system which moves with external force. Simulate the system and visualize the results.

Analysis

A non-linear system has EoM like this:

x˙=f(t,x,u)y=g(t,x,u)\dot{x} = f(t,x,u) \\ y = g(t,x,u)

Say in our example, the system has EoM like this:

x¨=f(t)/my(t)=x(t)where,x(t)=[x,x˙]\ddot{x} = f(t)/m \\ y(t) = x(t) \\ where, x(t) = [x, \dot{x}]'

The system get external force f(t)f(t) from input port, then we compute the system state and write the result observation y(t)y(t) to output port.

The f(t)f(t) comes from a input source, the output of the system goes to the visualization so we could observe the result.

Code Implementation

Create the system

class Particle1dPlant final : public systems::LeafSystem<T> {
 public:
  // Disables the built in copy and move constructor.
 DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(Particle1dPlant);
 
 /// Constructor defines a prototype for its continuous state and output port.
 Particle1dPlant();
 
 /// Return the OutputPort associated with this Particle1dPlant.
 /// This method is called when connecting the ports in the diagram builder.
 const systems::OutputPort<T>& get_output_port() const {
    return systems::System<T>::get_output_port(0);
 }
 /// Returns the current state of this Particle1dPlant as a BasicVector.
 /// The return value is mutable to allow the calling method to change the
 /// state (e.g., to set initial values). For example, this method is called
 /// when building a diagram so initial values can be set by the simulator.
 /// @param[in] context The Particle1dPlant sub-system context.
 static systems::BasicVector<T>& get_mutable_state(
      systems::Context<T>* context) {
    return get_mutable_state(&context->get_mutable_continuous_state());
 }
 
 private:
  // Casts the continuous state vector from a VectorBase to a BasicVector.
 static const systems::BasicVector<T>& get_state(
      const systems::ContinuousState<T>& cstate) {
    return dynamic_cast<const systems::BasicVector<T>&>(cstate.get_vector());
 }
 
  // This method is called in DoCalcTimeDerivative() as a way to update the
 // state before the time derivatives are calculated.
 static const systems::BasicVector<T>& get_state(
      const systems::Context<T>& context) {
    return get_state(context.get_continuous_state());
 }
 
  // Casts the mutable continuous state vector from a VectorBase to BasicVector.
 static systems::BasicVector<T>& get_mutable_state(
      systems::ContinuousState<T>* cstate) {
    return dynamic_cast<systems::BasicVector<T>&>(cstate->get_mutable_vector());
 }
 
 // This is the calculator method that assigns values to the state output port.
 void CopyStateOut(const systems::Context<T>& context,
 systems::BasicVector<T>* output) const;
 
 // Method that calculates the state time derivatives.
 void DoCalcTimeDerivatives(const systems::Context<T>& context,
 systems::ContinuousState<T>* derivatives) const override;
};

Build the diagram

// Parsing the URDF and constructing a RigidBodyTree from it
auto tree =
    std::make_unique<RigidBodyTree<double>>();
parsers::urdf::AddModelInstanceFromUrdfFileToWorld(
    FindResourceOrThrow("drake/examples/particle1d/particle1d.urdf"),
    multibody::joints::kFixed, tree.get());
 
// Construct and empty diagram, then add a particle plant.
systems::DiagramBuilder<double> builder;
 
Particle1dPlant<double>* particle_plant =
    builder.AddSystem<Particle1dPlant<double>>();
particle_plant->set_name("RigidBox");
 
// To set constant parameters (such as mass) in the particle plant, pass
// information from the tree (which read constant parameters in the .urdf).
particle_plant->SetConstantParameters(*tree);
 
// Add a visualizer block to the diagram, uses the tree and an lcm object.
lcm::DrakeLcm lcm;
systems::DrakeVisualizer* publisher =
    builder.AddSystem<systems::DrakeVisualizer>(*tree, &lcm);
publisher->set_name("publisher");
 
// Within the diagram, connect the particle plant's output port to the
// visualizer's input port.
builder.Connect(particle_plant->get_output_port(),
                publisher->get_input_port(0));
 
// Build the diagram described by previous call to connect input/export port.
auto diagram = builder.Build();
 
// Constructs a Simulator that advances this diagram through time.
systems::Simulator<double> simulator(*diagram);
 
// To set initial values for the simulation:
// * Get the Diagram's context.
// * Get the part of the Diagram's context associated with particle_plant.
// * Get the part of the particle_plant's context associated with state.
// * Fill the state with initial values.
systems::Context<double>& simulator_context = simulator.get_mutable_context();
systems::Context<double>& particle_plant_context =
    diagram->GetMutableSubsystemContext(*particle_plant, &simulator_context);
systems::BasicVector<double>& state =
    particle_plant->get_mutable_state(&particle_plant_context);
 
const double x_init = 0.0;
const double xDt_init = 0.0;
state.SetAtIndex(0, x_init);
state.SetAtIndex(1, xDt_init);
 
// Set upper-limit on simulation speed (mostly for visualization).
simulator.set_target_realtime_rate(1);
 
// Simulate for 10 seconds (default units are SI, with units of seconds).
std::cout << "Starting simulation." << std::endl;
simulator.StepTo(10.0);
std::cout << "Simulation done." << std::endl;

Credit

Thanks Manuel Ahumada for providing this example.

Last updated