1 #include "MUQ/Modeling/WorkGraphPiece.h"
2
3 #include "MUQ/Modeling/WorkGraph.h"
4
5 #include <boost/range/adaptor/reversed.hpp>
6
7 #include <boost/graph/topological_sort.hpp>
8
9 #include <Eigen/Core>
10
11 using namespace muq::Modeling;
12
DependentPredicate()13 DependentPredicate::DependentPredicate() {}
14
DependentPredicate(boost::graph_traits<Graph>::vertex_descriptor const & baseNode,Graph const & graph)15 DependentPredicate::DependentPredicate(boost::graph_traits<Graph>::vertex_descriptor const& baseNode, Graph const& graph) {
16 DownstreamNodes(baseNode, graph);
17 }
18
operator ()(const boost::graph_traits<Graph>::vertex_descriptor & node) const19 bool DependentPredicate::operator()(const boost::graph_traits<Graph>::vertex_descriptor& node) const {
20 // is the node in the vector of depends?
21 return std::find(doesDepend.begin(), doesDepend.end(), node)!=doesDepend.end();
22 }
23
DownstreamNodes(const boost::graph_traits<Graph>::vertex_descriptor & baseNode,Graph const & graph)24 void DependentPredicate::DownstreamNodes(const boost::graph_traits<Graph>::vertex_descriptor& baseNode, Graph const& graph) {
25
26 // add this node to the list of downstream nodes
27 doesDepend.push_back(baseNode);
28
29 // loop through its dependencies
30 boost::graph_traits<Graph>::out_edge_iterator e, e_end;
31 boost::tie(e, e_end) = boost::out_edges(baseNode, graph);
32 for( ; e!=e_end; ++e ) {
33 // recursivley add its dependendcies to the downstream node list
34 DownstreamNodes(boost::target(*e, graph), graph);
35 }
36 }
37
DependentEdgePredicate()38 DependentEdgePredicate::DependentEdgePredicate() {}
39
DependentEdgePredicate(DependentPredicate nodePred,Graph const & graph)40 DependentEdgePredicate::DependentEdgePredicate(DependentPredicate nodePred, Graph const& graph) : nodePred(nodePred), graph(&graph) {}
41
operator ()(const boost::graph_traits<Graph>::edge_descriptor & edge) const42 bool DependentEdgePredicate::operator()(const boost::graph_traits<Graph>::edge_descriptor& edge) const {
43
44 // check to see if the source is a downstream node
45 return nodePred(source(edge, *graph));
46 }
47
48
WorkGraphPiece(std::shared_ptr<WorkGraph> wgraph,std::vector<std::shared_ptr<ConstantPiece>> const & constantPieces,std::vector<std::string> const & inputNames,std::map<unsigned int,std::string> const & inTypes,std::shared_ptr<WorkPiece> outputPiece)49 WorkGraphPiece::WorkGraphPiece(std::shared_ptr<WorkGraph> wgraph,
50 std::vector<std::shared_ptr<ConstantPiece> > const& constantPieces,
51 std::vector<std::string> const& inputNames,
52 std::map<unsigned int, std::string> const& inTypes,
53 std::shared_ptr<WorkPiece> outputPiece) : WorkPiece(inTypes, constantPieces.size(), outputPiece->OutputTypes(), outputPiece->numOutputs), wgraph(wgraph), outputID(outputPiece->ID()), constantPieces(constantPieces) {
54
55 // build the run order
56 boost::topological_sort(wgraph->graph, std::front_inserter(runOrder));
57
58 // make sure we know the number of inputs
59 assert(numInputs>=0);
60
61 // each input only needs to loop over its downstream nodes when computing derivatives
62 derivRunOrders.resize(numInputs);
63 filtered_graphs.resize(numInputs);
64
65 // compute a run order for each of the inputs so we only have to loop over their downstream nodes
66 assert(numInputs==inputNames.size());
67 for( unsigned int i=0; i<numInputs; ++i ) { // loop through the inputs
68 // get iterators to the begining and end of the graph
69 boost::graph_traits<Graph>::vertex_iterator v, v_end;
70 boost::tie(v, v_end) = vertices(wgraph->graph);
71
72 // determine the downstream nodes of this input
73 DependentPredicate nFilt(*std::find_if(v, v_end, NodeNameFinder(inputNames[i], wgraph->graph)), wgraph->graph);
74 DependentEdgePredicate eFilt(nFilt, wgraph->graph);
75
76 // filter the graph, we only care about downstream nodes of this input
77 filtered_graphs[i] = std::make_shared<boost::filtered_graph<Graph, DependentEdgePredicate, DependentPredicate> >(wgraph->graph, eFilt, nFilt);
78
79 // build specialized run order for each input dimension
80 boost::topological_sort(*filtered_graphs[i], std::back_inserter(derivRunOrders[i]));
81 }
82 }
83
~WorkGraphPiece()84 WorkGraphPiece::~WorkGraphPiece() {}
85
EvaluateImpl(ref_vector<boost::any> const & inputs)86 void WorkGraphPiece::EvaluateImpl(ref_vector<boost::any> const& inputs) {
87
88 // set the inputs
89 SetInputs(inputs);
90
91 // fill the map from the WorkPiece ID to its outputs
92 OutputMap();
93
94 // store the result in the output vector
95 outputs.resize(valMap[outputID].size());
96 for(int i=0; i<outputs.size(); ++i) {
97 outputs.at(i) = valMap[outputID].at(i).get();
98 }
99 }
100
101 // void WorkGraphPiece::JacobianImpl(unsigned int const wrtIn, unsigned int const wrtOut, ref_vector<boost::any> const& inputs) {
102 // // set the inputs
103 // SetInputs(inputs);
104 //
105 // // fill the map from the WorkPiece ID to its outputs
106 // OutputMap();
107 //
108 // // a map from the WorkPiece ID a map from the output number to the jacobian of that output wrt the specified input
109 // std::map<unsigned int, std::map<unsigned int, boost::any> > jacMap;
110 //
111 // // loop through each downstream node
112 // for( auto node : boost::adaptors::reverse(derivRunOrders[wrtIn]) ) {
113 // // the ID of the current node
114 // const unsigned int nodeID = filtered_graphs[wrtIn]->operator[](node)->piece->ID();
115 //
116 // // get the outputs of this node that depend on the specified input
117 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> > & requiredOutNodes = RequiredOutputs(node, wrtIn, wrtOut);
118 // // remove duplicates
119 // std::vector<unsigned int> requiredOuts;
120 // requiredOuts.reserve(requiredOutNodes.size());
121 // for( auto out : requiredOutNodes ) {
122 // auto it = std::find(requiredOuts.begin(), requiredOuts.end(), std::get<1>(out));
123 // if( it==requiredOuts.end() ) {
124 // requiredOuts.push_back(std::get<1>(out));
125 // }
126 // }
127 //
128 // // get the inputs for this node --- the input WorkPiece ID, the output number, and the input number
129 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> >& requiredIns = RequiredInputs(node, wrtIn);
130 //
131 // // the inputs to this WorkPiece
132 // const ref_vector<boost::any>& ins = Inputs(node);
133 //
134 // // compute the jacobian of each required output wrt each input
135 // for( auto out : requiredOuts ) {
136 // if( requiredIns.size()==0 ) {
137 // // if there are no inputs, it is the input so set the Jacobian to the identity
138 // jacMap[nodeID][out] = algebra->Identity(valMap[nodeID][out].get().type(), algebra->Size(valMap[nodeID][out]), algebra->Size(valMap[nodeID][out]));
139 // } else {
140 // // initize the jacobian to nothing
141 // jacMap[nodeID][out] = boost::none;
142 //
143 // for( auto in : requiredIns ) {
144 // // compute the Jacobian with respect to each required input
145 // graph->operator[](node)->piece->Jacobian(std::get<2>(in), out, ins);
146 //
147 // // use chain rule to get the jacobian wrt to the required input
148 // const boost::any tempJac = algebra->Multiply(*(graph->operator[](node)->piece->jacobian), jacMap[std::get<0>(in)][std::get<1>(in)]);
149 // jacMap[nodeID][out] = algebra->Add(jacMap[nodeID][out], tempJac);
150 // }
151 // }
152 // }
153 // }
154 //
155 // // set the Jacobian for this WorkPiece
156 // jacobian = jacMap[outputID][wrtOut];
157 // }
158 //
159 // void WorkGraphPiece::JacobianActionImpl(unsigned int const wrtIn, unsigned int const wrtOut, boost::any const& vec, ref_vector<boost::any> const& inputs) {
160 // // set the inputs
161 // SetInputs(inputs);
162 //
163 // // fill the map from the WorkPiece ID to its outputs
164 // OutputMap();
165 //
166 // // a map from the WorkPiece ID a map from the output number to the action of the jacobian of that output wrt the specified input
167 // std::map<unsigned int, std::map<unsigned int, boost::any> > jacActionMap;
168 //
169 // // loop through each downstream node
170 // for( auto node : boost::adaptors::reverse(derivRunOrders[wrtIn]) ) {
171 // // the ID of the current node
172 // const unsigned int nodeID = filtered_graphs[wrtIn]->operator[](node)->piece->ID();
173 //
174 // // get the outputs of this node that depend on the specified input
175 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> >& requiredOutNodes = RequiredOutputs(node, wrtIn, wrtOut);
176 // // remove duplicates
177 // std::vector<unsigned int> requiredOuts;
178 // requiredOuts.reserve(requiredOutNodes.size());
179 // for( auto out : requiredOutNodes ) {
180 // auto it = std::find(requiredOuts.begin(), requiredOuts.end(), std::get<1>(out));
181 // if( it==requiredOuts.end() ) {
182 // requiredOuts.push_back(std::get<1>(out));
183 // }
184 // }
185 //
186 // // get the inputs for this node --- the input WorkPiece ID, the output number, and the input number
187 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> >& requiredIns = RequiredInputs(node, wrtIn);
188 //
189 // // the inputs to this WorkPiece
190 // const ref_vector<boost::any>& ins = Inputs(node);
191 //
192 // // compute the jacobian of each required output wrt each input
193 // for( auto out : requiredOuts ) {
194 // if( requiredIns.size()==0 ) {
195 // // if there are no inputs, it is the input so set the Jacobian to the identity
196 // jacActionMap[nodeID][out] = vec;
197 // } else {
198 // // initize the jacobian to nothing
199 // jacActionMap[nodeID][out] = boost::none;
200 //
201 // for( auto in : requiredIns ) {
202 // // compute the Jacobian with respect to each required input
203 // graph->operator[](node)->piece->JacobianAction(std::get<2>(in), out, jacActionMap[std::get<0>(in)][std::get<1>(in)], ins);
204 //
205 // // use chain rule to get the jacobian wrt to the required input
206 // jacActionMap[nodeID][out] = algebra->Add(jacActionMap[nodeID][out], *(graph->operator[](node)->piece->jacobianAction));
207 // }
208 // }
209 // }
210 // }
211 //
212 // // set the action of the Jacobian for this WorkPiece
213 // jacobianAction = jacActionMap[outputID][wrtOut];
214 // }
215 //
216 // void WorkGraphPiece::JacobianTransposeActionImpl(unsigned int const wrtIn, unsigned int const wrtOut, boost::any const& vec, ref_vector<boost::any> const& inputs) {
217 // // set the inputs
218 // SetInputs(inputs);
219 //
220 // // fill the map from the WorkPiece ID to its outputs
221 // OutputMap();
222 //
223 // // a map from the WorkPiece ID a map from the output number to the action of the jacobian of that output wrt the specified input
224 // std::map<unsigned int, std::map<unsigned int, boost::any> > jacTransActionMap;
225 //
226 // // loop through each downstream node
227 // for( auto node : derivRunOrders[wrtIn] ) {
228 // // the ID of the current node
229 // const unsigned int nodeID = filtered_graphs[wrtIn]->operator[](node)->piece->ID();
230 //
231 // // get the outputs of this node that depend on the specified input
232 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> >& requiredOuts = RequiredOutputs(node, wrtIn, wrtOut);
233 //
234 // // get the inputs for this node --- the input WorkPiece ID, the output number, and the input number
235 // const std::vector<std::tuple<unsigned int, unsigned int, unsigned int> >& requiredIns = RequiredInputs(node, wrtIn);
236 //
237 // // the inputs to this WorkPiece
238 // const ref_vector<boost::any>& ins = Inputs(node);
239 //
240 // for( auto in : requiredIns ) {
241 // if( nodeID==outputID ) {
242 // assert(requiredOuts.size()==1);
243 // assert(std::get<1>(requiredOuts[0])==wrtOut);
244 //
245 // // compute the Jacobian transpose action of the output node
246 // graph->operator[](node)->piece->JacobianTransposeAction(std::get<2>(in), wrtOut, vec, ins);
247 // jacTransActionMap[nodeID][std::get<2>(in)] = *(graph->operator[](node)->piece->jacobianTransposeAction);
248 // } else {
249 // // initialize the jacobian transpose action to nothing
250 // jacTransActionMap[nodeID][std::get<2>(in)] = boost::none;
251 //
252 // // loop through the outputs
253 // for( auto out : requiredOuts ) {
254 // // compute the jacobian transpose action for this output
255 // graph->operator[](node)->piece->JacobianTransposeAction(std::get<2>(in), std::get<1>(out), jacTransActionMap[std::get<0>(out)][std::get<2>(out)], ins);
256 // // add it (chain rule)
257 // jacTransActionMap[nodeID][std::get<2>(in)] = algebra->Add(jacTransActionMap[nodeID][std::get<2>(in)], *(graph->operator[](node)->piece->jacobianTransposeAction));
258 // }
259 // }
260 // }
261 //
262 // // if this is the input node
263 // if( requiredIns.size()==0 ) {
264 // // loop though the outputs
265 // for( auto out : requiredOuts ) {
266 // if( jacobianTransposeAction ) { // if the jacobian transpose action has not be initilized ...
267 // // it is equal to the action of the output
268 // *jacobianTransposeAction = algebra->Add(*jacobianTransposeAction, jacTransActionMap[std::get<0>(out)][std::get<1>(out)]);
269 // } else {
270 // // add it to the existing jacobian transpose action (chain rule)
271 // jacobianTransposeAction = jacTransActionMap[std::get<0>(out)][std::get<1>(out)];
272 // }
273 // }
274 // }
275 // }
276 // }
277
SetInputs(ref_vector<boost::any> const & inputs)278 void WorkGraphPiece::SetInputs(ref_vector<boost::any> const& inputs) {
279 // get the inputs and set them to the ConstantPiece nodes
280 assert(inputs.size()==constantPieces.size());
281 for( unsigned int i=0; i<inputs.size(); ++i ) {
282 constantPieces[i]->SetOutputs(inputs[i]);
283 }
284 }
285
InputNodes(boost::graph_traits<Graph>::vertex_descriptor const & node) const286 std::map<unsigned int, std::vector<std::pair<unsigned int, unsigned int> > > WorkGraphPiece::InputNodes(boost::graph_traits<Graph>::vertex_descriptor const& node) const {
287 // the map of input nodes
288 std::map<unsigned int, std::vector<std::pair<unsigned int, unsigned int> > > inMap;
289
290 // loop though the input nodes
291 boost::graph_traits<Graph>::in_edge_iterator e, e_end;
292 for( tie(e, e_end)=boost::in_edges(node, wgraph->graph); e!=e_end; ++e ) {
293 // get the WorkPiece id number, the output that it supplies, and the input that receives it
294 const unsigned int id = wgraph->GetPiece(boost::source(*e, wgraph->graph))->ID();
295 const unsigned int inNum = wgraph->graph[*e]->inputDim;
296 const unsigned int outNum = wgraph->graph[*e]->outputDim;
297
298 // try to find the WorkPiece in the other upstream nodes
299 auto it = inMap.find(id);
300
301 if( it==inMap.end() ) { // if we have not yet needed this WorkPiece ...
302 // ... add it to the list and store the input/output pair
303 inMap[id] = std::vector<std::pair<unsigned int, unsigned int> >(1, std::pair<unsigned int, unsigned int>(inNum, outNum));
304 } else { // we have needed this WorkPiece
305 // ... add the input/output pair
306 inMap[id].push_back(std::pair<unsigned int, unsigned int>(inNum, outNum));
307 }
308 }
309
310 return inMap;
311 }
312
OutputMap()313 void WorkGraphPiece::OutputMap() {
314 // clear the map from the WorkPiece ID to its outputs
315 valMap.clear();
316
317 // loop over the run order
318 for( auto it : runOrder ) {
319 // the inputs to this WorkPiece
320 const ref_vector<boost::any>& ins = Inputs(it);
321
322 // evaluate the current map and store the value
323 std::vector<boost::any> const& output = wgraph->GetPiece(it)->Evaluate(ins);
324 valMap[wgraph->GetPiece(it)->ID()] = ToRefVector(output);
325 }
326 }
327
Inputs(boost::graph_traits<Graph>::vertex_descriptor node) const328 ref_vector<boost::any> WorkGraphPiece::Inputs(boost::graph_traits<Graph>::vertex_descriptor node) const {
329 // how many inputs does this node require?
330 const int numIns = wgraph->GetPiece(node)->numInputs;
331
332 // get the inputs for this node
333 const std::map<unsigned int, std::vector<std::pair<unsigned int, unsigned int> > >& inMap = InputNodes(node);
334
335 boost::any empty = boost::none;
336 ref_vector<boost::any> ins(numIns, std::cref(empty));
337
338 // loop through the edges again, now we know which outputs supply which inputs
339 for( auto edge : inMap ) {
340 // loop over the input/output pairs supplied by this input
341 for( auto in_out : edge.second ) {
342 // use at instead of operator[] because this is a const function
343 ins[in_out.first] = valMap.at(edge.first)[in_out.second];
344 }
345 }
346
347 return ins;
348 }
349
RequiredOutputs(boost::graph_traits<FilteredGraph>::vertex_descriptor const & node,unsigned int const wrtIn,unsigned int const wrtOut) const350 std::vector<std::tuple<unsigned int, unsigned int, unsigned int> > WorkGraphPiece::RequiredOutputs(boost::graph_traits<FilteredGraph>::vertex_descriptor const& node, unsigned int const wrtIn, unsigned int const wrtOut) const {
351 // the ID of the current node
352 const unsigned int nodeID = filtered_graphs[wrtIn]->operator[](node)->piece->ID();
353
354 // get the outputs of this node that depend on the specified input
355 std::vector<std::tuple<unsigned int, unsigned int, unsigned int> > requiredOuts;
356
357 if( nodeID==outputID ) { // if it is the output node ...
358 // ... the user specifies the output derivative
359 requiredOuts.push_back(std::tuple<unsigned int, unsigned int, unsigned int>(nodeID, wrtOut, wrtIn));
360
361 return requiredOuts;
362 }
363
364 // loop though the output nodes
365 boost::graph_traits<FilteredGraph>::out_edge_iterator eout, eout_end;
366 for( tie(eout, eout_end)=boost::out_edges(node, *filtered_graphs[wrtIn]); eout!=eout_end; ++eout ) {
367 // get the output number
368 const unsigned int id = wgraph->GetPiece(boost::target(*eout, *filtered_graphs[wrtIn]))->ID();
369 const unsigned int outNum = filtered_graphs[wrtIn]->operator[](*eout)->outputDim;
370 const unsigned int inNum = filtered_graphs[wrtIn]->operator[](*eout)->inputDim;
371
372 // if we have not already required this output, save it
373 auto it = std::find(requiredOuts.begin(), requiredOuts.end(), std::tuple<unsigned int, unsigned int, unsigned int>(id, outNum, inNum));
374 if( it==requiredOuts.end() ) {
375 requiredOuts.push_back(std::tuple<unsigned int, unsigned int, unsigned int>(id, outNum, inNum));
376 }
377 }
378
379 return requiredOuts;
380 }
381
RequiredInputs(boost::graph_traits<FilteredGraph>::vertex_descriptor const & node,unsigned int const wrtIn) const382 std::vector<std::tuple<unsigned int, unsigned int, unsigned int> > WorkGraphPiece::RequiredInputs(boost::graph_traits<FilteredGraph>::vertex_descriptor const& node, unsigned int const wrtIn) const {
383 // how many inputs does this node require?
384 const int numIns = filtered_graphs[wrtIn]->operator[](node)->piece->numInputs;
385
386 std::vector<std::tuple<unsigned int, unsigned int, unsigned int> > requiredIns;
387 requiredIns.reserve(numIns);
388
389 // loop though the output nodes
390 boost::graph_traits<FilteredGraph>::in_edge_iterator ein, ein_end;
391 for( tie(ein, ein_end)=boost::in_edges(node, *filtered_graphs[wrtIn]); ein!=ein_end; ++ein ) {
392 // get the WorkPiece id number, the output that it supplies, and the input that receives it
393 const unsigned int id = wgraph->GetPiece(boost::source(*ein, *filtered_graphs[wrtIn]))->ID();
394 const unsigned int outNum = filtered_graphs[wrtIn]->operator[](*ein)->outputDim;
395 const unsigned int inNum = filtered_graphs[wrtIn]->operator[](*ein)->inputDim;
396
397 // store the requried input
398 requiredIns.push_back(std::tuple<unsigned int, unsigned int, unsigned int>(id, outNum, inNum));
399 }
400
401 return requiredIns;
402 }
403