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