Drake
call_matlab.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include <memory>
4 #include <string>
5 #include <vector>
6 
7 #include "google/protobuf/io/coded_stream.h"
8 #include "google/protobuf/io/zero_copy_stream_impl.h"
9 
10 #include "drake/common/eigen_types.h"
11 #include "drake/common/proto/matlab_rpc.pb.h"
12 
13 /// @file
14 /// @brief Utilities for calling Matlab from C++
15 ///
16 /// Provides a simple interface for (one-directional) RPC to a simple matlab
17 /// remote client. Methods are provided to serialize our favorite data types
18 /// into protobuf and then published to a file. The interface is modeled
19 /// after mexCallMATLAB
20 /// https://www.mathworks.com/help/matlab/apiref/mexcallmatlab.html
21 /// but we use C++11 to provide a much nicer interface.
22 ///
23 /// To play the remote calls in matlab, simply run call_matlab_client from your
24 /// matlab terminal. For synchronous playback, use a named pipe by running
25 /// `mkfifo /tmp/matlab_rpc`
26 /// in a bash terminal.
27 ///
28 /// The primary use case that this was designed for was to make MATLAB plotting
29 /// available in C++ without requiring the C++ code to link against MATLAB in
30 /// any way... (if MATLAB is not present, the messages simply fall on deaf
31 /// ears).
32 ///
33 /// Support for multi-function commands is provided by allowing return values to
34 /// be stored on the remote client, and reused by a simple "remote variable
35 /// reference" that is kept by the publisher.
36 ///
37 /// See call_matlab_test.cc for some simple examples.
38 
39 namespace drake {
40 namespace common {
41 
42 /// Serialize our favorite data types into the matlab_array structure.
43 /// To support a calling call_matlab for a new data type, simply implement
44 /// another one of these methods.
45 
46 class MatlabRemoteVariable;
47 
48 namespace internal {
49 
51  const Eigen::Ref<const Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic>>&
52  mat,
53  MatlabArray* matlab_array, bool is_vector);
54 
55 void ToMatlabArrayMatrix(const Eigen::Ref<const Eigen::MatrixXd>& mat,
56  MatlabArray* matlab_array, bool is_vector);
57 
58 void ToMatlabArrayMatrix(const Eigen::Ref<const Eigen::MatrixXi>& mat,
59  MatlabArray* matlab_array, bool is_vector);
60 
61 } // namespace internal
62 
63 void ToMatlabArray(const MatlabRemoteVariable& var, MatlabArray* matlab_array);
64 
65 void ToMatlabArray(double scalar, MatlabArray* matlab_array);
66 
67 void ToMatlabArray(int scalar, MatlabArray* matlab_array);
68 
69 void ToMatlabArray(const std::string& str, MatlabArray* matlab_array);
70 
71 template <typename Derived>
72 void ToMatlabArray(const Eigen::MatrixBase<Derived>& mat,
73  MatlabArray* matlab_array) {
74  const bool is_vector = (Derived::ColsAtCompileTime == 1);
75  return internal::ToMatlabArrayMatrix(mat, matlab_array, is_vector);
76 }
77 
78 // Helper methods for variadic template call in CallMatlab.
79 namespace internal {
80 inline void AssembleCallMatlabMsg(MatlabRPC*) {
81  // Intentionally left blank. Base case for template recursion.
82 }
83 
84 template <typename T, typename... Types>
85 void AssembleCallMatlabMsg(MatlabRPC* msg, T first, Types... args) {
86  ToMatlabArray(first, msg->add_rhs());
87  AssembleCallMatlabMsg(msg, args...);
88 }
89 
90 std::unique_ptr<google::protobuf::io::FileOutputStream>
91 CreateOutputStream(const std::string& filename);
92 
93 void PublishCall(
94  google::protobuf::io::FileOutputStream* praw_output,
95  const MatlabRPC& message);
96 
97 // Simple wrapper to prevent the outside world from needing to worry about
98 // creating a the i/o stream object.
99 // TODO(russt): support setting the pipe name (via a
100 // CallMatlabChannelPush/Pop).
101 void PublishCallMatlab(const MatlabRPC& msg);
102 
103 } // namespace internal
104 
105 // forward declaration:
106 template <typename... Types>
107 MatlabRemoteVariable CallMatlabSingleOutput(const std::string& function_name,
108  Types... args);
109 
110 /// Holds a reference to a variable stored on the matlab client, which can be
111 /// passed back into a future lcm_call_matlab call.
113  public:
115  // ~MatlabRemoteVariable(); // TODO(russt): send a destroy message on
116  // deletion
117 
118  int64_t unique_id() const { return unique_id_; }
119 
120  /// Creates a new remote variable that contains the data at the prescribed
121  /// index. Supported calls are, for instance:
122  /// <pre>
123  /// var(1) // Access the first element.
124  /// var(1,2) // Access row 1, column 2.
125  /// var(3,":") // Access the entire third row.
126  /// var(Eigen::Vector2d(1,2),":") // Access the first and second rows.
127  /// </pre>
128  ///
129  /// Note that a tempting use case which is NOT supported (directly) is
130  /// <pre>
131  /// var("1:10") // ERROR!
132  /// </pre>
133  /// Matlab doesn't work that way. Instead use, e.g.
134  /// <pre>
135  /// var(Eigen::VectorXd::LinSpaced(10,1,10)) // Access elements 1:10.
136  /// <pre>
137  /// Note: yes, vector indices in Matlab are doubles.
138  template <typename... Types>
139  MatlabRemoteVariable operator()(Types... args) const {
140  MatlabRemoteVariable s = AssembleSubstruct(args...);
141 
142  return CallMatlabSingleOutput("subsref", *this, s);
143  }
144 
145  /// Creates a new remote variable that contains the data at the prescribed
146  /// index. Supported calls are, for instance:
147  /// <pre>
148  /// var.subsasgn(val,1) // Set the first element to val.
149  /// var.subsasgn(val,1,2) // Set row 1, column 2.
150  /// var.subsasgn(val,3,":") // Set the entire third row.
151  /// var(val,Eigen::Vector2d(1,2),":") // Set the first and second rows.
152  /// </pre>
153  ///
154  /// Note that a tempting use case which is NOT supported (directly) is
155  /// <pre>
156  /// var(val,"1:10") // ERROR!
157  /// </pre>
158  /// Matlab doesn't work that way. Instead use, e.g.
159  /// <pre>
160  /// var(val, Eigen::VectorXd::LinSpaced(10,1,10)) // Set elements 1:10.
161  /// <pre>
162  /// Note: yes, vector indices in Matlab are doubles.
163  template <typename T, typename... Types>
164  MatlabRemoteVariable subsasgn(T val, Types... args) const {
165  MatlabRemoteVariable s = AssembleSubstruct(args...);
166 
167  return CallMatlabSingleOutput("subsasgn", *this, s, val);
168  }
169 
170  private:
171  // Helper methods for variadic template call in CallMatlab.
172  inline void AssembleSubsPrepMsg(MatlabRPC*) const {
173  // Intentionally left blank. Base case for template recursion.
174  }
175 
176  template <typename T, typename... Types>
177  void AssembleSubsPrepMsg(MatlabRPC* msg, T first, Types... args) const {
178  const std::string dummy_field_name =
179  "f" + std::to_string(msg->rhs_size() / 2 + 1);
180  ToMatlabArray(dummy_field_name, msg->add_rhs());
181  ToMatlabArray(first, msg->add_rhs());
182  AssembleSubsPrepMsg(msg, args...);
183  }
184 
185  template <typename... Types>
186  MatlabRemoteVariable AssembleSubstruct(Types... args) const {
187  // construct a cell matrix (with one entry for each argument) using
188  // e.g., struct2cell(struct('f1',1:2,'f2','test'))
189  MatlabRemoteVariable temp_struct;
190  {
191  MatlabRPC msg;
192  msg.add_lhs(temp_struct.unique_id());
193 
194  AssembleSubsPrepMsg(&msg, args...);
195 
196  msg.set_function_name("struct");
198  }
199  MatlabRemoteVariable temp_cell =
200  CallMatlabSingleOutput("struct2cell", temp_struct);
201 
202  // create the substruct
203  return CallMatlabSingleOutput("substruct", "()", temp_cell);
204  }
205 
206  private:
207  const int64_t unique_id_{};
208 };
209 
210 /// Invokes a mexCallMATLAB call on the remote client.
211 ///
212 /// @param num_outputs Number of return variables (left-side arguments) to
213 /// request from matlab. As in matlab, you need not request all of the
214 /// outputs for any given method.
215 /// @param function_name Name of the matlab function to call. Any argument
216 /// that could have been passed to mexCallMATLAB is allowed.
217 /// https://www.mathworks.com/help/matlab/apiref/mexcallmatlab.html
218 /// @param argument1 Any data type which has a ToMatlabArray method
219 /// implemented.
220 /// @param argument2 Same as above.
221 /// ...
222 ///
223 /// See call_matlab_test.cc for some simple examples.
224 template <typename... Types>
225 std::vector<MatlabRemoteVariable> CallMatlab(int num_outputs,
226  const std::string& function_name,
227  Types... args) {
228  if (num_outputs < 0) num_outputs = 0;
229  std::vector<MatlabRemoteVariable> remote_vars(num_outputs);
230 
231  MatlabRPC msg;
232  for (int i = 0; i < num_outputs; i++) {
233  msg.add_lhs(remote_vars[i].unique_id());
234  }
235 
236  internal::AssembleCallMatlabMsg(&msg, args...);
237 
238  msg.set_function_name(function_name);
240  return remote_vars;
241 }
242 
243 /// Special cases the call with zero outputs, since it's so common.
244 template <typename... Types>
245 void CallMatlab(const std::string& function_name, Types... args) {
246  CallMatlab(0, function_name, args...);
247 }
248 
249 /// Special cases the call with one output.
250 template <typename... Types>
251 MatlabRemoteVariable CallMatlabSingleOutput(const std::string& function_name,
252  Types... args) {
253  std::vector<MatlabRemoteVariable> vars =
254  CallMatlab(1, function_name, args...);
255  return vars[0];
256 }
257 
258 /// Creates a new remote variable with the corresponding value set.
259 template <typename T>
261  // It took some time to figure out how to make a simple "assign" call via
262  // mexCallMatlab. The `deal` method is the trick.
263  return CallMatlabSingleOutput("deal", value);
264 }
265 
266 } // namespace common
267 } // namespace drake
int64_t unique_id() const
Definition: call_matlab.h:118
Definition: automotive_demo.cc:89
void PublishCallMatlab(const MatlabRPC &message)
Definition: call_matlab.cc:118
std::string to_string(const drake::geometry::Identifier< Tag > &id)
Enables use of identifiers with to_string.
Definition: identifier.h:203
std::unique_ptr< google::protobuf::io::FileOutputStream > CreateOutputStream(const std::string &filename)
Definition: call_matlab.cc:105
int value
Definition: copyable_unique_ptr_test.cc:61
std::vector< MatlabRemoteVariable > CallMatlab(int num_outputs, const std::string &function_name, Types...args)
Invokes a mexCallMATLAB call on the remote client.
Definition: call_matlab.h:225
void AssembleCallMatlabMsg(MatlabRPC *)
Definition: call_matlab.h:80
void ToMatlabArray(const MatlabRemoteVariable &var, MatlabArray *matlab_array)
Definition: call_matlab.cc:29
MatlabRemoteVariable CallMatlabSingleOutput(const std::string &function_name, Types...args)
Special cases the call with one output.
Definition: call_matlab.h:251
const VectorXDecisionVariable * vars
Definition: nlopt_solver.cc:103
void PublishCall(google::protobuf::io::FileOutputStream *praw_output, const MatlabRPC &message)
Definition: call_matlab.cc:124
::testing::Types< RungeKutta3Integrator< double > > Types
Definition: runge_kutta3_integrator_test.cc:23
void ToMatlabArrayMatrix(const Eigen::Ref< const Eigen::Matrix< bool, Eigen::Dynamic, Eigen::Dynamic >> &mat, MatlabArray *matlab_array, bool is_vector)
MatlabRemoteVariable NewRemoteVariable(T value)
Creates a new remote variable with the corresponding value set.
Definition: call_matlab.h:260
Holds a reference to a variable stored on the matlab client, which can be passed back into a future l...
Definition: call_matlab.h:112