Zoltan2
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Zoltan2_GraphMetricsUtility.hpp
Go to the documentation of this file.
1 // @HEADER
2 //
3 // ***********************************************************************
4 //
5 // Zoltan2: A package of combinatorial algorithms for scientific computing
6 // Copyright 2012 Sandia Corporation
7 //
8 // Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
9 // the U.S. Government retains certain rights in this software.
10 //
11 // Redistribution and use in source and binary forms, with or without
12 // modification, are permitted provided that the following conditions are
13 // met:
14 //
15 // 1. Redistributions of source code must retain the above copyright
16 // notice, this list of conditions and the following disclaimer.
17 //
18 // 2. Redistributions in binary form must reproduce the above copyright
19 // notice, this list of conditions and the following disclaimer in the
20 // documentation and/or other materials provided with the distribution.
21 //
22 // 3. Neither the name of the Corporation nor the names of the
23 // contributors may be used to endorse or promote products derived from
24 // this software without specific prior written permission.
25 //
26 // THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
27 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
30 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 //
38 // Questions? Contact Karen Devine (kddevin@sandia.gov)
39 // Erik Boman (egboman@sandia.gov)
40 // Siva Rajamanickam (srajama@sandia.gov)
41 //
42 // ***********************************************************************
43 //
44 // @HEADER
45 
49 #ifndef ZOLTAN2_GRAPHICMETRICVALUESUTILITY_HPP
50 #define ZOLTAN2_GRAPHICMETRICVALUESUTILITY_HPP
51 
55 #include <zoltan_dd.h>
56 #include <Zoltan2_TPLTraits.hpp>
57 #include <map>
58 #include <vector>
59 
60 namespace Zoltan2 {
61 
90 template <typename Adapter,
91  typename MachineRep = // Default MachineRep type
92  MachineRepresentation<typename Adapter::scalar_t,
93  typename Adapter::part_t> >
95  const RCP<const Environment> &env,
96  const RCP<const Comm<int> > &comm,
97  const RCP<const GraphModel<typename Adapter::base_adapter_t> > &graph,
98  const ArrayView<const typename Adapter::part_t> &parts,
99  typename Adapter::part_t &numParts,
100  ArrayRCP<RCP<BaseClassMetrics<typename Adapter::scalar_t> > > &metrics,
101  ArrayRCP<typename Adapter::scalar_t> &globalSums,
102  bool bMessages = true,
103  const RCP <const MachineRep> machine = Teuchos::null)
104 {
105  env->timerStart(MACRO_TIMERS, "globalWeightedByPart");
106 
107  // Note we used to have with hops as a separate method but decided to combine
108  // both into this single method. machine is an optional parameter to choose
109  // between the two methods.
110  bool bHops = (machine != Teuchos::null);
111 
112  env->debug(DETAILED_STATUS, "Entering globalWeightedByPart");
113 
115  // Initialize return values
116 
117  typedef typename Adapter::lno_t lno_t;
118  typedef typename Adapter::gno_t gno_t;
119  typedef typename Adapter::offset_t offset_t;
120  typedef typename Adapter::scalar_t scalar_t;
121  typedef typename Adapter::part_t part_t;
122 
124 
125  lno_t localNumVertices = graph->getLocalNumVertices();
126  lno_t localNumEdges = graph->getLocalNumEdges();
127 
128  ArrayView<const gno_t> Ids;
129  ArrayView<t_input_t> v_wghts;
130  graph->getVertexList(Ids, v_wghts);
131 
132  typedef GraphMetrics<scalar_t> gm_t;
133 
134  // get the edge ids, and weights
135  ArrayView<const gno_t> edgeIds;
136  ArrayView<const offset_t> offsets;
137  ArrayView<t_input_t> e_wgts;
138  graph->getEdgeList(edgeIds, offsets, e_wgts);
139 
140 
141  std::vector <scalar_t> edge_weights;
142  int numWeightPerEdge = graph->getNumWeightsPerEdge();
143 
144  int numMetrics = bHops ?
145  4 : // "edge cuts", messages, hops, weighted hops
146  2; // "edge cuts", messages
147 
148  if (numWeightPerEdge) numMetrics += bHops ?
149  numWeightPerEdge * 2 : // "weight n", weighted hops per weight n
150  numWeightPerEdge; // "weight n"
151 
152  // add some more metrics to the array
153  auto next = metrics.size(); // where we begin filling
154  for (auto n = 0; n < numMetrics; ++n) {
155  addNewMetric<gm_t, scalar_t>(env, metrics);
156  }
157 
158  std::vector <part_t> e_parts (localNumEdges);
159 
160  std::vector<part_t> local_parts;
161 
162 #ifdef HAVE_ZOLTAN2_MPI
163  if (comm->getSize() > 1) {
164  const bool bUseLocalIDs = false; // Local IDs not needed
166  int debug_level = 0;
167  directory_t directory(comm, bUseLocalIDs, debug_level);
168  if (localNumVertices)
169  directory.update(localNumVertices, &Ids[0], NULL, &parts[0],
170  NULL, directory_t::Update_Mode::Replace);
171  else
172  directory.update(localNumVertices, NULL, NULL, NULL,
173  NULL, directory_t::Update_Mode::Replace);
174 
175  if (localNumEdges)
176  directory.find(localNumEdges, &edgeIds[0], NULL, &e_parts[0],
177  NULL, NULL, false);
178  else
179  directory.find(localNumEdges, NULL, NULL, NULL,
180  NULL, NULL, false);
181  } else
182 #endif
183  {
184  std::map<gno_t,lno_t> global_id_to_local_index;
185 
186  // else everything is local.
187  // we need a globalid to local index conversion.
188  // this does not exists till this point, so we need to create one.
189  for (lno_t i = 0; i < localNumVertices; ++i){
190  //at the local index i, we have the global index Ids[i].
191  //so write i, to Ids[i] index of the vector.
192  global_id_to_local_index[Ids[i]] = i;
193  }
194 
195  for (lno_t i = 0; i < localNumEdges; ++i){
196  gno_t ei = edgeIds[i];
197  //ei is the global index of the neighbor one.
198  part_t p = parts[global_id_to_local_index[ei]];
199  e_parts[i] = p;
200  }
201  }
202 
203  RCP<const Teuchos::Comm<int> > tcomm = comm;
204 
205  {
206  const bool bUseLocalIDs = false; // Local IDs not needed
207  int debug_level = 0;
208  // Create a directory indexed by part_t with values t_scalar_t for weight sums
209 
210  // this struct is the user data type for a part
211  // each part will have a std::vector<part_info> for its user data
212  // representing the list of all neighbors and a weight for each.
213  struct part_info {
214  part_info() : sum_weights(0) {
215  }
216 
217  // operator +=
218  // this allows the directory to know how to assemble two structs
219  // which return true for ==.
220  // TODO: Decide if we want directory to work like this for AggregateAdd
221  const part_info & operator+=(const part_info & src) {
222  sum_weights += src.sum_weights;
223  return *this; // return old value
224  }
225 
226  // operator>
227  // TODO: Decide if we want directory to work like this for AggregateAdd
228  bool operator>(const part_info & src) {
229  // Note: Currently this doesn't actually do anything except allow this
230  // struct to compile. Aggregate mode used operator> to preserve ordering
231  // and therefore a custom struct must currently define it. However in
232  // this test we will be using AggregateAdd mode which doesn't actually
233  // use this operator> . However if we change the test so we require the
234  // input data to already be ordered by target_part, then we could change
235  // the directory implementation so AggregateAdd and Aggregate are almost
236  // identical. The only difference would be that Aggregate mode would
237  // check operator== and if true, throw away the duplicate, while
238  // AggregateAdd mode would check operator== and if true, call operator+=
239  // to combine the values, in this case summing sum_weights.
240  return (target_part > src.target_part);
241  }
242 
243  // operator==
244  // this allows the directory to know that two structs should be
245  // aggregated into one using the operator +=.
246  // TODO: Decide if we want directory to work like this for AggregateAdd
247  // This works but seems fussy/complicated - to discuss. I'm not yet sure
248  // how to best integrate this so we can aggregate both simple types where
249  // we just keep unique elements and more complex structs with a 'rule'
250  // for combining them.
251  bool operator==(const part_info & src) {
252  // if target_part is the same then the values for sum_weights will
253  // be summed.
254  return (target_part == src.target_part);
255  }
256 
257  part_t target_part; // the part this part_info refers to
258  scalar_t sum_weights; // the sum of weights
259  };
260 
261  //get the vertices in each part in my part.
262  std::vector <lno_t> part_begins(numParts, -1);
263  std::vector <lno_t> part_nexts(localNumVertices, -1);
264 
265  //cluster vertices according to their parts.
266  //create local part graph.
267  for (lno_t i = 0; i < localNumVertices; ++i){
268  part_t ap = parts[i];
269  part_nexts[i] = part_begins[ap];
270  part_begins[ap] = i;
271  }
272 
273  for (int weight_index = -1; weight_index < numWeightPerEdge ; ++weight_index) {
274  std::vector<part_t> part_data(numParts); // will resize to lower as needed
275  std::vector<std::vector<part_info>> user_data(numParts); // also to resize
276  int count_total_entries = 0;
277 
278  std::vector <part_t> part_neighbors(numParts);
279  std::vector <scalar_t> part_neighbor_weights(numParts, 0);
280  std::vector <scalar_t> part_neighbor_weights_ordered(numParts);
281 
282  // coarsen for all vertices in my part in order with parts.
283  for (part_t i = 0; i < numParts; ++i) {
284  part_t num_neighbor_parts = 0;
285  lno_t v = part_begins[i];
286  // get part i, and first vertex in this part v.
287  while (v != -1){
288  // now get the neightbors of v.
289  for (offset_t j = offsets[v]; j < offsets[v+1]; ++j){
290 
291  //get the part of the second vertex.
292  part_t ep = e_parts[j];
293 
294  // TODO: Can we skip condition (i==ep)
295  // The self reference set is going to be excluded later anyways
296  // so we could make this more efficient.
297  scalar_t ew = 1;
298  if (weight_index > -1){
299  ew = e_wgts[weight_index][j];
300  }
301  // add it to my local part neighbors for part i.
302  if (part_neighbor_weights[ep] < 0.00001){
303  part_neighbors[num_neighbor_parts++] = ep;
304  }
305  part_neighbor_weights[ep] += ew;
306  }
307  v = part_nexts[v];
308  }
309 
310  // now get the part list.
311  for (lno_t j = 0; j < num_neighbor_parts; ++j) {
312  part_t neighbor_part = part_neighbors[j];
313  part_neighbor_weights_ordered[j] = part_neighbor_weights[neighbor_part];
314  part_neighbor_weights[neighbor_part] = 0;
315  }
316 
317  if (num_neighbor_parts > 0) {
318  // for the new directory note a difference in the logic flow
319  // originally we have CrsMatrix which could collect these values
320  // as we built each row. For the directory it's probably better to
321  // have update called just once so we collect the values and then
322  // do all of the update at the end.
323  part_data[count_total_entries] = i; // set up for directory
324  std::vector<part_info> & add_user_data = user_data[count_total_entries];
325  ++count_total_entries;
326  add_user_data.resize(num_neighbor_parts);
327  for(int n = 0; n < num_neighbor_parts; ++n) {
328  part_info & add_data = add_user_data[n];
329  add_data.target_part = part_neighbors[n];
330  add_data.sum_weights = part_neighbor_weights_ordered[n];
331  }
332  }
333  }
334 
335  scalar_t max_edge_cut = 0;
336  scalar_t total_edge_cut = 0;
337  part_t max_message = 0;
338  part_t total_message = 0;
339 
340  part_t total_hop_count = 0;
341  scalar_t total_weighted_hop_count = 0;
342  part_t max_hop_count = 0;
343  scalar_t max_weighted_hop_count = 0;
344 
345  // for serial or comm size 1 we need to fill this from local data
346  // TODO: Maybe remove all special casing for serial and make this pipeline
347  // uniform always
348  if(local_parts.size() == 0) {
349  local_parts.resize(numParts);
350  for(size_t n = 0; n < local_parts.size(); ++n) {
351  local_parts[n] = n;
352  }
353  }
354 
355  std::vector<std::vector<part_info>> find_user_data;
356 
357  // directory does not yet support SerialComm because it still has older
358  // MPI calls which need to be refactored to Teuchos::comm format. To
359  // work around this issue skip the directory calls and just set the
360  // find data equal to the input update data. This works because above
361  // logic has already summed the weights per process so in the SerialComm
362  // case, there won't be duplicates.
363  bool bSerialComm =
364  (dynamic_cast<const Teuchos::SerialComm<int>*>(&(*comm)) != NULL);
365 
366  if(!bSerialComm) {
368  directory_t;
369  directory_t directory(comm, bUseLocalIDs, debug_level);
370 
371  // update
372  directory.update(count_total_entries, &part_data[0], NULL, &user_data[0],
373  NULL, directory_t::Update_Mode::AggregateAdd);
374 
375  // get my local_parts (parts managed on this directory)
376  directory.get_locally_managed_gids(local_parts);
377 
378  // set up find_user_data to have the right size
379  find_user_data.resize(local_parts.size());
380 
381  // find
382  directory.find(local_parts.size(), &local_parts[0], NULL,
383  &find_user_data[0], NULL, NULL, false);
384  }
385  else {
386  find_user_data = user_data;
387  }
388 
389  for(size_t n = 0; n < local_parts.size(); ++n) {
390  scalar_t part_edge_cut = 0;
391  part_t part_messages = 0;
392  const std::vector<part_info> & data = find_user_data[n];
393  for(size_t q = 0; q < data.size(); ++q) {
394  const part_t local_part = local_parts[n];
395  const part_info & info = data[q];
396  if (info.target_part != local_part) {
397  part_edge_cut += info.sum_weights;
398  part_messages += 1;
399 
400  if(bHops) {
401  typename MachineRep::machine_pcoord_t hop_count = 0;
402  machine->getHopCount(local_part, info.target_part, hop_count);
403 
404  part_t hop_counts = hop_count;
405  scalar_t weighted_hop_counts = hop_count * info.sum_weights;
406 
407  total_hop_count += hop_counts;
408  total_weighted_hop_count += weighted_hop_counts;
409 
410  if (hop_counts > max_hop_count ){
411  max_hop_count = hop_counts;
412  }
413  if (weighted_hop_counts > max_weighted_hop_count ){
414  max_weighted_hop_count = weighted_hop_counts;
415  }
416  }
417  }
418  }
419 
420  if(part_edge_cut > max_edge_cut) {
421  max_edge_cut = part_edge_cut;
422  }
423  total_edge_cut += part_edge_cut;
424 
425  if (part_messages > max_message){
426  max_message = part_messages;
427  }
428  total_message += part_messages;
429  }
430 
431 
432  scalar_t g_max_edge_cut = 0;
433  scalar_t g_total_edge_cut = 0;
434  part_t g_max_message = 0;
435  part_t g_total_message = 0;
436 
437  part_t g_total_hop_count = 0;
438  scalar_t g_total_weighted_hop_count = 0;
439  part_t g_max_hop_count = 0;
440  scalar_t g_max_weighted_hop_count = 0;
441 
442  try{
443  Teuchos::reduceAll<int, scalar_t>(*comm,Teuchos::REDUCE_MAX,1,&max_edge_cut,&g_max_edge_cut);
444  Teuchos::reduceAll<int, part_t>(*comm,Teuchos::REDUCE_MAX,1,&max_message,&g_max_message);
445 
446  Teuchos::reduceAll<int, scalar_t>(*comm,Teuchos::REDUCE_SUM,1,&total_edge_cut,&g_total_edge_cut);
447  Teuchos::reduceAll<int, part_t>(*comm,Teuchos::REDUCE_SUM,1,&total_message,&g_total_message);
448 
449  if(bHops) {
450  Teuchos::reduceAll<int, part_t>(*comm,Teuchos::REDUCE_MAX,1,&max_hop_count,&g_max_hop_count);
451  Teuchos::reduceAll<int, scalar_t>(*comm,Teuchos::REDUCE_MAX,1,&max_weighted_hop_count,&g_max_weighted_hop_count);
452 
453  Teuchos::reduceAll<int, part_t>(*comm,Teuchos::REDUCE_SUM,1,&total_hop_count,&g_total_hop_count);
454  Teuchos::reduceAll<int, scalar_t>(*comm,Teuchos::REDUCE_SUM,1,&total_weighted_hop_count,&g_total_weighted_hop_count);
455  }
456  }
458 
459  if (weight_index == -1){
460  metrics[next]->setName("edge cuts");
461  }
462  else {
463  std::ostringstream oss;
464  oss << "weight " << weight_index;
465  metrics[next]->setName( oss.str());
466  }
467  metrics[next]->setMetricValue("global maximum", g_max_edge_cut);
468  metrics[next]->setMetricValue("global sum", g_total_edge_cut);
469  next++;
470  if (weight_index == -1){
471  metrics[next]->setName("message");
472  metrics[next]->setMetricValue("global maximum", g_max_message);
473  metrics[next]->setMetricValue("global sum", g_total_message);
474  next++;
475  }
476 
477  if(bHops) {
478  if (weight_index == -1){
479  metrics[next]->setName("hops (No Weight)");
480  metrics[next]->setMetricValue("global maximum", g_max_hop_count);
481  metrics[next]->setMetricValue("global sum", g_total_hop_count);
482  next++;
483  }
484 
485  std::ostringstream oss;
486  oss << "weighted hops" << weight_index;
487  metrics[next]->setName( oss.str());
488  metrics[next]->setMetricValue("global maximum", g_max_weighted_hop_count);
489  metrics[next]->setMetricValue("global sum", g_total_weighted_hop_count);
490  next++;
491  }
492  }
493  }
494 
495  env->timerStop(MACRO_TIMERS, "globalWeightedByPart");
496 
497  env->debug(DETAILED_STATUS, "Exiting globalWeightedByPart");
498 }
501 template <typename scalar_t, typename part_t>
502 void printGraphMetricsHeader(std::ostream &os, part_t targetNumParts, part_t numParts )
503 {
504  os << "Graph Metrics: (" << numParts << " parts)";
505  os << std::endl;
506  if (targetNumParts != numParts) {
507  os << "Target number of parts is: " << targetNumParts << std::endl;
508  }
510 }
511 
514 template <typename scalar_t, typename part_t>
515 void printGraphMetrics(std::ostream &os, part_t targetNumParts, part_t numParts, const ArrayView<RCP<BaseClassMetrics<scalar_t> > > &infoList)
516 {
517  printGraphMetricsHeader<scalar_t, part_t>(os, targetNumParts, numParts);
518  for (int i=0; i < infoList.size(); i++) {
519  if (infoList[i]->getName() != METRICS_UNSET_STRING) {
520  infoList[i]->printLine(os);
521  }
522  }
523  os << std::endl;
524 }
525 
528 template <typename scalar_t, typename part_t>
529 void printGraphMetrics(std::ostream &os, part_t targetNumParts, part_t numParts, RCP<BaseClassMetrics<scalar_t>> metricValue)
530 {
531  printGraphMetricsHeader<scalar_t, part_t>(os, targetNumParts, numParts);
532  metricValue->printLine(os);
533 }
534 
535 } //namespace Zoltan2
536 
537 #endif
Time an algorithm (or other entity) as a whole.
static void printHeader(std::ostream &os)
Print a standard header.
void printGraphMetricsHeader(std::ostream &os, part_t targetNumParts, part_t numParts)
Print out header info for graph metrics.
sub-steps, each method&#39;s entry and exit
SparseMatrixAdapter_t::part_t part_t
void globalWeightedByPart(const RCP< const Environment > &env, const RCP< const Comm< int > > &comm, const RCP< const GraphModel< typename Adapter::base_adapter_t > > &graph, const ArrayView< const typename Adapter::part_t > &parts, typename Adapter::part_t &numParts, ArrayRCP< RCP< BaseClassMetrics< typename Adapter::scalar_t > > > &metrics, ArrayRCP< typename Adapter::scalar_t > &globalSums, bool bMessages=true, const RCP< const MachineRep > machine=Teuchos::null)
Given the local partitioning, compute the global weighted cuts in each part.
void printGraphMetrics(std::ostream &os, part_t targetNumParts, part_t numParts, const ArrayView< RCP< BaseClassMetrics< scalar_t > > > &infoList)
Print out list of graph metrics.
size_t getLocalNumVertices() const
Returns the number vertices on this process.
Traits class to handle conversions between gno_t/lno_t and TPL data types (e.g., ParMETIS&#39;s idx_t...
GraphModel defines the interface required for graph models.
#define Z2_THROW_OUTSIDE_ERROR(env)
Throw an error returned from outside the Zoltan2 library.
#define METRICS_UNSET_STRING