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