Zoltan2
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Zoltan2_Sphynx.hpp
Go to the documentation of this file.
1 // @HEADER
2 //
3 // ***********************************************************************
4 //
5 // Sphynx
6 // Copyright 2020 National Technology & Engineering
7 // Solutions of Sandia, LLC (NTESS).
8 //
9 // Under the terms of Contract DE-NA0003525 with NTESS,
10 // the U.S. Government retains certain rights in this software.
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions are
14 // met:
15 //
16 // 1. Redistributions of source code must retain the above copyright
17 // notice, this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright
20 // notice, this list of conditions and the following disclaimer in the
21 // documentation and/or other materials provided with the distribution.
22 //
23 // 3. Neither the name of the Corporation nor the names of the
24 // contributors may be used to endorse or promote products derived from
25 // this software without specific prior written permission.
26 //
27 // THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
28 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
31 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 //
39 // Questions? Contact Seher Acer (sacer@sandia.gov)
40 // Erik Boman (egboman@sandia.gov)
41 // Siva Rajamanickam (srajama@sandia.gov)
42 // Karen Devine (kddevin@sandia.gov)
43 //
44 // ***********************************************************************
45 //
46 // @HEADER
47 
48 #ifndef _ZOLTAN2_SPHYNXALGORITHM_HPP_
49 #define _ZOLTAN2_SPHYNXALGORITHM_HPP_
50 
51 
53 // This file contains the Sphynx algorithm.
54 //
55 // Sphynx is a graph partitioning algorithm that is based on a spectral method.
56 // It has three major steps:
57 // 1) compute the Laplacian matrix of the input graph,
58 // 2) compute logK+1 eigenvectors on the Laplacian matrix,
59 // 3) use eigenvector entries as coordinates and compute a K-way partition on
60 // them using a geometric method.
61 //
62 // Step1: Sphynx provides three eigenvalue problems and hence Laplacian matrix:
63 // i) combinatorial (Lx = \lambdax, where L = A - D)
64 // ii) generalized (Lx = \lambdaDx, where L = A - D)
65 // iii) normalized (L_nx, \lambdax, where Ln = D^{-1/2}LD^{-1/2}
66 // and L = A - D)
67 //
68 // Step2: Sphynx calls the LOBPCG algorithm provided in Anasazi to obtain
69 // logK+1 eigenvectors.
70 // (Alternately, uses the experimental Randomized eigensolver.)
71 // Step3: Sphynx calls the MJ algorithm provided in Zoltan2Core to compute the
72 // partition.
74 
75 #include "Zoltan2Sphynx_config.h"
76 
79 
82 
83 #include "AnasaziLOBPCGSolMgr.hpp"
84 #include "AnasaziBlockDavidsonSolMgr.hpp"
85 #include "AnasaziGeneralizedDavidsonSolMgr.hpp"
86 #include "AnasaziBlockKrylovSchurSolMgr.hpp"
87 #include "AnasaziRandomizedSolMgr.hpp"
88 #include "AnasaziBasicEigenproblem.hpp"
89 #include "AnasaziTpetraAdapter.hpp"
90 
91 #include "BelosLinearProblem.hpp"
92 #include "BelosTpetraOperator.hpp"
93 
94 #include "Ifpack2_Factory.hpp"
95 
96 #include "Teuchos_TimeMonitor.hpp"
97 
98 #ifdef HAVE_ZOLTAN2SPHYNX_MUELU
99 #include "MueLu_CreateTpetraPreconditioner.hpp"
100 #endif
101 
102 namespace Zoltan2 {
103 
104  template <typename Adapter>
105  class Sphynx : public Algorithm<Adapter>
106  {
107 
108  public:
109 
110  using scalar_t = double; // Sphynx with scalar_t=double obtains better cutsize
111  using lno_t = typename Adapter::lno_t;
112  using gno_t = typename Adapter::gno_t;
113  using node_t = typename Adapter::node_t;
114  using offset_t = typename Adapter::offset_t;
115  using part_t = typename Adapter::part_t;
116  using weight_t = typename Adapter::scalar_t;
117 
118  using graph_t = Tpetra::CrsGraph<lno_t, gno_t, node_t>;
119  using matrix_t = Tpetra::CrsMatrix<scalar_t, lno_t, gno_t, node_t>;
120  using mvector_t = Tpetra::MultiVector<scalar_t, lno_t, gno_t, node_t>;
121  using op_t = Tpetra::Operator<scalar_t, lno_t, gno_t, node_t>;
122 
124  typedef Anasazi::MultiVecTraits<scalar_t,mvector_t> MVT;
125 
129 
130  // Takes the graph from the input adapter and computes the Laplacian matrix
131  Sphynx(const RCP<const Environment> &env,
132  const RCP<Teuchos::ParameterList> &params,
133  const RCP<Teuchos::ParameterList> &sphynxParams,
134  const RCP<const Comm<int> > &comm,
135  const RCP<const XpetraCrsGraphAdapter<graph_t> > &adapter) :
136  env_(env), params_(params), sphynxParams_(sphynxParams), comm_(comm), adapter_(adapter)
137  {
138 
139  // Don't compute the Laplacian if the number of requested parts is 1
140  const Teuchos::ParameterEntry *pe = params_->getEntryPtr("num_global_parts");
141  numGlobalParts_ = pe->getValue<int>(&numGlobalParts_);
142  if(numGlobalParts_ > 1){
143 
144  Teuchos::TimeMonitor t(*Teuchos::TimeMonitor::getNewTimer("Sphynx::Laplacian"));
145 
146  // The verbosity is common throughout the algorithm, better to check and set now
147  pe = sphynxParams_->getEntryPtr("sphynx_verbosity");
148  if (pe)
149  verbosity_ = pe->getValue<int>(&verbosity_);
150 
151  // Do we need to pre-process the input?
152  pe = sphynxParams_->getEntryPtr("sphynx_skip_preprocessing");
153  if (pe)
154  skipPrep_ = pe->getValue<bool>(&skipPrep_);
155 
156  // Get the graph from XpetraCrsGraphAdapter if skipPrep_ is true
157  // We assume the graph has all of the symmetric and diagonal entries
158  if(skipPrep_)
159  graph_ = adapter_->getUserGraph();
160  else {
161  throw std::runtime_error("\nSphynx Error: Preprocessing has not been implemented yet.\n");
162  }
163 
164  // Check if the graph is regular
166 
167  // Set Sphynx defaults: preconditioner, problem type, tolerance, initial vectors.
168  setDefaults();
169 
170  // Compute the Laplacian matrix
172 
173  if(problemType_ == GENERALIZED)
175 
176  }
177  }
178 
182 
183  void partition(const Teuchos::RCP<PartitioningSolution<Adapter> > &solution);
184 
185  int AnasaziWrapper(const int numEigenVectors);
186 
187  template<typename problem_t>
188  void setPreconditioner(Teuchos::RCP<problem_t> &problem);
189 
190  template<typename problem_t>
191  void setMueLuPreconditioner(Teuchos::RCP<problem_t> &problem);
192 
193  template<typename problem_t>
194  void setJacobiPreconditioner(Teuchos::RCP<problem_t> &problem);
195 
196  template<typename problem_t>
197  void setPolynomialPreconditioner(Teuchos::RCP<problem_t> &problem);
198 
199  void eigenvecsToCoords(Teuchos::RCP<mvector_t> &eigenVectors,
200  int computedNumEv,
201  Teuchos::RCP<mvector_t> &coordinates);
202 
203  void computeWeights(std::vector<const weight_t *> vecweights,
204  std::vector<int> strides);
205 
206  void MJwrapper(const Teuchos::RCP<const mvector_t> &coordinates,
207  std::vector<const weight_t *> weights,
208  std::vector<int> strides,
209  const Teuchos::RCP<PartitioningSolution<Adapter>> &solution);
210 
211  void setUserEigenvectors(const Teuchos::RCP<mvector_t> &userEvects);
215 
217  // Determine input graph's regularity = maxDegree/AvgDegree < 10.
218  // If Laplacian type is not specified, then use combinatorial for regular
219  // and generalized for irregular.
220  // MueLu settings will differ depending on the regularity, too.
222  {
223  // Get the row pointers in the host
224  auto rowOffsets = graph_->getLocalGraphDevice().row_map;
225  auto rowOffsets_h = Kokkos::create_mirror_view(rowOffsets);
226  Kokkos::deep_copy(rowOffsets_h, rowOffsets);
227 
228  // Get size information
229  const size_t numGlobalEntries = graph_->getGlobalNumEntries();
230  const size_t numLocalRows = graph_->getLocalNumRows();
231  const size_t numGlobalRows = graph_->getGlobalNumRows();
232 
233  // Compute local maximum degree
234  size_t localMax = 0;
235  for(size_t i = 0; i < numLocalRows; i++){
236  if(rowOffsets_h(i+1) - rowOffsets_h(i) - 1 > localMax)
237  localMax = rowOffsets_h(i+1) - rowOffsets_h(i) - 1;
238  }
239 
240  // Compute global maximum degree
241  size_t globalMax = 0;
242  Teuchos::reduceAll<int, size_t> (*comm_, Teuchos::REDUCE_MAX, 1, &localMax, &globalMax);
243 
244  double avg = static_cast<double>(numGlobalEntries-numGlobalRows)/numGlobalRows;
245  double maxOverAvg = static_cast<double>(globalMax)/avg;
246 
247  // Use generalized Laplacian on irregular graphs
248  irregular_ = false;
249  if(maxOverAvg > 10) {
250  irregular_ = true;
251  }
252 
253  // Let the user know about what we determined if verbose
254  if(verbosity_ > 0) {
255  if(comm_->getRank() == 0) {
256  std::cout << "Regularity of Graph ----------------" << std::endl;
257  std::cout << " Maximum degree: " << globalMax << std::endl;
258  std::cout << " Average degree: " << avg << std::endl;
259  std::cout << " Max/Avg: " << globalMax/avg << std::endl;
260  std::cout << " Regular graph?: " << !irregular_ << std::endl;
261  }
262  }
263  }
264 
266  // If preconditioner type is not specified:
267  // use muelu if it is enabled, and jacobi otherwise.
268  // If eigenvalue problem type is not specified:
269  // use combinatorial for regular and
270  // normalized for irregular with polynomial preconditioner,
271  // generalized for irregular with other preconditioners.
272  // If convergence tolerance is not specified:
273  // use 1e-3 for regular with jacobi and polynomial, and 1e-2 otherwise.
274  // If how to decide the initial vectors is not specified:
275  // use random for regular and constant for irregular
276  void setDefaults()
277  {
278 
279  // Set the default preconditioner to muelu if it is enabled, jacobi otherwise.
280  precType_ = "jacobi";
281 #ifdef HAVE_ZOLTAN2SPHYNX_MUELU
282  precType_ = "muelu";
283 #endif
284 
285  // Override the preconditioner type with the user's preference
286  const Teuchos::ParameterEntry *pe = sphynxParams_->getEntryPtr("sphynx_preconditioner_type");
287  if (pe) {
288  precType_ = pe->getValue<std::string>(&precType_);
289  if(precType_ != "muelu" && precType_ != "jacobi" && precType_ != "polynomial")
290  throw std::runtime_error("\nSphynx Error: " + precType_ + " is not recognized as a preconditioner.\n"
291  + " Possible values: muelu (if enabled), jacobi, and polynomial\n");
292  }
293 
294  solverType_ = sphynxParams_->get("sphynx_eigensolver","LOBPCG");
295  TEUCHOS_TEST_FOR_EXCEPTION(!(solverType_ == "LOBPCG" || solverType_ == "randomized" || solverType_ == "BlockDavidson" || solverType_ == "GeneralizedDavidson" || solverType_ == "BlockKrylovSchur" ),
296  std::invalid_argument, "Sphynx: sphynx_eigensolver must be set to LOBPCG, randomized, BlockDavidson, GeneralizedDavidson, or BlockKrylovSchur.");
297 
298  // Set the default problem type
299  problemType_ = COMBINATORIAL;
300  if(irregular_) {
301  if(precType_ == "polynomial")
302  problemType_ = NORMALIZED;
303  else
304  problemType_ = GENERALIZED;
305  }
306 
307  // Override the problem type with the user's preference
308  pe = sphynxParams_->getEntryPtr("sphynx_problem_type");
309  if (pe) {
310  std::string probType = "";
311  probType = pe->getValue<std::string>(&probType);
312 
313  if(probType == "combinatorial")
314  problemType_ = COMBINATORIAL;
315  else if(probType == "generalized")
316  problemType_ = GENERALIZED;
317  else if(probType == "normalized")
318  problemType_ = NORMALIZED;
319  else
320  throw std::runtime_error("\nSphynx Error: " + probType + " is not recognized as a problem type.\n"
321  + " Possible values: combinatorial, generalized, and normalized.\n");
322  }
323 
324 
325  // Set the default for tolerance
326  tolerance_ = 1.0e-2;
327  if(!irregular_ && (precType_ == "jacobi" || precType_ == "polynomial"))
328  tolerance_ = 1.0e-3;
329 
330 
331  // Override the tolerance with the user's preference
332  pe = sphynxParams_->getEntryPtr("sphynx_tolerance");
333  if (pe)
334  tolerance_ = pe->getValue<scalar_t>(&tolerance_);
335 
336 
337  // Set the default for initial vectors
338  randomInit_ = true;
339  if(irregular_)
340  randomInit_ = false;
341 
342  // Override the initialization method with the user's preference
343  pe = sphynxParams_->getEntryPtr("sphynx_initial_guess");
344  if (pe) {
345  std::string initialGuess = "";
346  initialGuess = pe->getValue<std::string>(&initialGuess);
347 
348  if(initialGuess == "random")
349  randomInit_ = true;
350  else if(initialGuess == "constants")
351  randomInit_ = false;
352  else
353  throw std::runtime_error("\nSphynx Error: " + initialGuess + " is not recognized as an option for initial guess.\n"
354  + " Possible values: random and constants.\n");
355  }
356 
357  }
358 
360  // Compute the Laplacian matrix depending on the eigenvalue problem type.
361  // There are 3 options for the type: combinatorial, generalized, and normalized.
362  // Combinatorial and generalized share the same Laplacian but generalized
363  // also needs a degree matrix.
365  {
366  if(solverType_ == "randomized")
367  laplacian_ = computeNormalizedLaplacian(true);
368  else if(problemType_ == NORMALIZED)
369  laplacian_ = computeNormalizedLaplacian();
370  else
371  laplacian_ = computeCombinatorialLaplacian();
372  }
373 
375  // Compute a diagonal matrix with the vertex degrees in the input graph
377  {
378 
379  // Get the row pointers in the host
380  auto rowOffsets = graph_->getLocalGraphHost().row_map;
381 
382  // Create the degree matrix with max row size set to 1
383  Teuchos::RCP<matrix_t> degMat(new matrix_t (graph_->getRowMap(),
384  graph_->getRowMap(),
385  1));
386 
387  scalar_t *val = new scalar_t[1];
388  lno_t *ind = new lno_t[1];
389  lno_t numRows = static_cast<lno_t>(graph_->getLocalNumRows());
390 
391  // Insert the diagonal entries as the degrees
392  for (lno_t i = 0; i < numRows; ++i) {
393  val[0] = rowOffsets(i+1) - rowOffsets(i) - 1;
394  ind[0] = i;
395  degMat->insertLocalValues(i, 1, val, ind);
396  }
397  delete [] val;
398  delete [] ind;
399 
400  degMat->fillComplete(graph_->getDomainMap(), graph_->getRangeMap());
401 
402  degMatrix_ = degMat;
403  }
404 
406  // Compute the combinatorial Laplacian: L = D - A.
407  // l_ij = degree of vertex i if i = j
408  // l_ij = -1 if i != j and a_ij != 0
409  // l_ij = 0 if i != j and a_ij = 0
410  Teuchos::RCP<matrix_t> computeCombinatorialLaplacian()
411  {
412  using range_policy = Kokkos::RangePolicy<
413  typename node_t::device_type::execution_space, Kokkos::IndexType<lno_t>>;
414  using values_view_t = Kokkos::View<scalar_t*, typename node_t::device_type>;
415  using offset_view_t = Kokkos::View<size_t*, typename node_t::device_type>;
416 
417  const size_t numEnt = graph_->getLocalNumEntries();
418  const size_t numRows = graph_->getLocalNumRows();
419 
420  // Create new values for the Laplacian, initialize to -1
421  values_view_t newVal (Kokkos::view_alloc("CombLapl::val", Kokkos::WithoutInitializing), numEnt);
422  Kokkos::deep_copy(newVal, -1);
423 
424  // Get the diagonal offsets
425  offset_view_t diagOffsets(Kokkos::view_alloc("Diag Offsets", Kokkos::WithoutInitializing), numRows);
426  graph_->getLocalDiagOffsets(diagOffsets);
427 
428  // Get the row pointers in the host
429  auto rowOffsets = graph_->getLocalGraphDevice().row_map;
430 
431  // Compute the diagonal entries as the vertex degrees
432  Kokkos::parallel_for("Combinatorial Laplacian", range_policy(0, numRows),
433  KOKKOS_LAMBDA(const lno_t i){
434  newVal(rowOffsets(i) + diagOffsets(i)) = rowOffsets(i+1) - rowOffsets(i) - 1;
435  }
436  );
437  Kokkos::fence ();
438 
439  // Create the Laplacian matrix using the input graph and with the new values
440  Teuchos::RCP<matrix_t> laplacian (new matrix_t(graph_, newVal));
441  laplacian->fillComplete (graph_->getDomainMap(), graph_->getRangeMap());
442 
443  // Create the Laplacian maatrix using the input graph and with the new values
444  return laplacian;
445  }
446 
448  // For AHat = false:
449  // Compute the normalized Laplacian: L_N = D^{-1/2} L D^{-1/2}, where L = D - A.
450  // l_ij = 1
451  // l_ij = -1/(sqrt(deg(v_i))sqrt(deg(v_j)) if i != j and a_ij != 0
452  // l_ij = 0 if i != j and a_ij = 0
453  //
454  // For AHat = true:
455  // AHat is turned to true if (and only if) using the randomized Eigensolver.
456  // For the randomized Eigensolver, we find eigenvalues of the matrix
457  // AHat = 2*I - L_N, and this is the matrix computed by this function.
458  Teuchos::RCP<matrix_t> computeNormalizedLaplacian(bool AHat = false)
459  {
460  using range_policy = Kokkos::RangePolicy<
461  typename node_t::device_type::execution_space, Kokkos::IndexType<lno_t>>;
462  using values_view_t = Kokkos::View<scalar_t*, typename node_t::device_type>;
463  using offset_view_t = Kokkos::View<size_t*, typename node_t::device_type>;
464  using vector_t = Tpetra::Vector<scalar_t, lno_t, gno_t, node_t>;
465  using dual_view_t = typename vector_t::dual_view_type;
466  using KAT = Kokkos::ArithTraits<scalar_t>;
467 
468  const size_t numEnt = graph_->getLocalNumEntries();
469  const size_t numRows = graph_->getLocalNumRows();
470 
471  // Create new values for the Laplacian, initialize to -1
472  values_view_t newVal (Kokkos::view_alloc("NormLapl::val", Kokkos::WithoutInitializing), numEnt);
473  if(AHat){
474  Kokkos::deep_copy(newVal, 1);
475  }
476  else{
477  Kokkos::deep_copy(newVal, -1);
478  }
479 
480  // D^{-1/2}
481  dual_view_t dv ("MV::DualView", numRows, 1);
482  auto deginvsqrt = dv.d_view;
483 
484  // Get the diagonal offsets
485  offset_view_t diagOffsets(Kokkos::view_alloc("Diag Offsets", Kokkos::WithoutInitializing), numRows);
486  graph_->getLocalDiagOffsets(diagOffsets);
487 
488  // Get the row pointers
489  auto rowOffsets = graph_->getLocalGraphDevice().row_map;
490 
491  // if(!AHat){
492  // Compute the diagonal entries as the vertex degrees
493  Kokkos::parallel_for("Combinatorial Laplacian", range_policy(0, numRows),
494  KOKKOS_LAMBDA(const lno_t i){
495  newVal(rowOffsets(i) + diagOffsets(i)) = rowOffsets(i+1) - rowOffsets(i) - 1;
496  deginvsqrt(i,0) = 1.0/KAT::sqrt(rowOffsets(i+1) - rowOffsets(i) - 1);
497  }
498  );
499  Kokkos::fence ();
500 
501  // Create the Laplacian graph using the same graph structure with the new values
502  Teuchos::RCP<matrix_t> laplacian (new matrix_t(graph_, newVal));
503  laplacian->fillComplete (graph_->getDomainMap(), graph_->getRangeMap());
504 
505  // Create the vector for D^{-1/2}
506  vector_t degInvSqrt(graph_->getRowMap(), dv);
507 
508  // Normalize the laplacian matrix as D^{-1/2} L D^{-1/2}
509  laplacian->leftScale(degInvSqrt);
510  laplacian->rightScale(degInvSqrt);
511 
512  return laplacian;
513  }
514 
518 
519  private:
520 
521  // User-provided members
522  const Teuchos::RCP<const Environment> env_;
523  Teuchos::RCP<Teuchos::ParameterList> params_;
524  Teuchos::RCP<Teuchos::ParameterList> sphynxParams_;
525  const Teuchos::RCP<const Teuchos::Comm<int> > comm_;
526  const Teuchos::RCP<const Adapter> adapter_;
527 
528  // Internal members
529  int numGlobalParts_;
530  Teuchos::RCP<const graph_t> graph_;
531  Teuchos::RCP<matrix_t> laplacian_;
532  Teuchos::RCP<const matrix_t> degMatrix_;
533  Teuchos::RCP<mvector_t> eigenVectors_;
534 
535  bool irregular_; // decided internally
536  std::string precType_; // obtained from user params or decided internally
537  std::string solverType_; // obtained from user params or decided internally
538  problemType problemType_; // obtained from user params or decided internally
539  double tolerance_; // obtained from user params or decided internally
540  bool randomInit_; // obtained from user params or decided internally
541  int verbosity_; // obtained from user params
542  bool skipPrep_; // obtained from user params
543  };
544 
548 
550  // Allows the user to manually set eigenvectors for the Sphynx partitioner
551  // to use rather than solving for them with Anasazi. Mainly intended
552  // for debugging purposes.
554  template <typename Adapter>
555  void Sphynx<Adapter>::setUserEigenvectors(const Teuchos::RCP<mvector_t> &userEvects)
556  {
557  eigenVectors_ = userEvects;
558  }
559 
561  // Compute a partition using the Laplacian matrix (and possibly the degree
562  // matrix as well). First call LOBPCG (or Randomized solver)
563  // to compute logK+1 eigenvectors, then
564  // transform the eigenvectors to coordinates, and finally call MJ to compute
565  // a partition on the coordinates.
566  template <typename Adapter>
567  void Sphynx<Adapter>::partition(const Teuchos::RCP<PartitioningSolution<Adapter>> &solution)
568  {
569  // Return a trivial solution if only one part is requested
570  if(numGlobalParts_ == 1) {
571 
572  size_t numRows =adapter_->getUserGraph()->getLocalNumRows();
573  Teuchos::ArrayRCP<part_t> parts(numRows,0);
574  solution->setParts(parts);
575 
576  return;
577 
578  }
579 
580  // The number of eigenvectors to be computed
581  int numEigenVectors = (int) log2(numGlobalParts_)+1;
582  int computedNumEv;
583 
584  if(eigenVectors_ == Teuchos::null){
585  // Compute the eigenvectors using LOBPCG
586  // or Randomized eigensolver
587  computedNumEv = Sphynx::AnasaziWrapper(numEigenVectors);
588 
589  if(computedNumEv <= 1 && (solverType_ == "LOBPCG" || solverType_ == "GeneralizedDavidson" || solverType_ == "BlockDavidson" || solverType_ == "BlockKrylovSchur")) {
590  throw
591  std::runtime_error("\nAnasazi Error: LOBPCGSolMgr::solve() returned unconverged.\n"
592  "Sphynx Error: LOBPCG could not compute any eigenvectors.\n"
593  " Increase either max iters or tolerance.\n");
594  }
595  }
596  else{
597  // Use eigenvectors provided by user.
598  computedNumEv = (int) eigenVectors_->getNumVectors();
599  if(computedNumEv <= numEigenVectors) {
600  throw
601  std::runtime_error("\nSphynx Error: Number of eigenvectors given by user\n"
602  " is less than number of Eigenvectors needed for partition." );
603  }
604  }
605 
606  // Transform the eigenvectors into coordinates
607  Teuchos::RCP<mvector_t> coordinates;
608 
609  Sphynx::eigenvecsToCoords(eigenVectors_, computedNumEv, coordinates);
610 
611  // Get the weights from the adapter
612  std::vector<const weight_t *> weights;
613  std::vector<int> wstrides;
614  Sphynx::computeWeights(weights, wstrides);
615 
616 
617  // Compute the partition using MJ on coordinates
618  Sphynx::MJwrapper(coordinates, weights, wstrides, solution);
619 
620  }
621 
623  // Call LOBPCG on the Laplacian matrix.
624  // Or use the randomized eigensolver.
625  template <typename Adapter>
626  int Sphynx<Adapter>::AnasaziWrapper(const int numEigenVectors)
627  {
628 
629  Teuchos::TimeMonitor t(*Teuchos::TimeMonitor::getNewTimer("Sphynx::Anasazi"));
630 
631  // Set defaults for the parameters
632  // and get user-set values.
633  std::string which = (solverType_ == "randomized" ? "LM" : "SR");
634  std::string ortho = "SVQB";
635  bool relConvTol = false;
636  int maxIterations = sphynxParams_->get("sphynx_max_iterations",1000);
637  int blockSize = sphynxParams_->get("sphynx_block_size",numEigenVectors);
638  int orthoFreq = sphynxParams_->get("sphynx_ortho_freq", 0);
639  int resFreq = sphynxParams_->get("sphynx_res_freq", 0);
640  bool isHermitian = true;
641  bool relLockTol = false;
642  bool lock = false;
643  bool useFullOrtho = sphynxParams_->get("sphynx_use_full_ortho",true);
644 
645  // Information to output in a verbose run
646  int numfailed = 0;
647  int iter = 0;
648 
649  // Set Anasazi verbosity level
650  int anasaziVerbosity = Anasazi::Errors + Anasazi::Warnings;
651  if (verbosity_ >= 1) // low
652  anasaziVerbosity += Anasazi::FinalSummary + Anasazi::TimingDetails;
653  if (verbosity_ >= 2) // medium
654  anasaziVerbosity += Anasazi::IterationDetails;
655  if (verbosity_ >= 3) // high
656  anasaziVerbosity += Anasazi::StatusTestDetails + Anasazi::OrthoDetails
657  + Anasazi::Debug;
658 
659  // Create the parameter list to pass into solver
660  Teuchos::ParameterList anasaziParams;
661  anasaziParams.set("Verbosity", anasaziVerbosity);
662  anasaziParams.set("Which", which);
663  anasaziParams.set("Convergence Tolerance", tolerance_);
664  anasaziParams.set("Maximum Iterations", maxIterations);
665  anasaziParams.set("Block Size", blockSize);
666  anasaziParams.set("Relative Convergence Tolerance", relConvTol);
667  anasaziParams.set("Orthogonalization", ortho);
668  anasaziParams.set("Use Locking", lock);
669  anasaziParams.set("Relative Locking Tolerance", relLockTol);
670  anasaziParams.set("Full Ortho", useFullOrtho);
671  anasaziParams.set("Orthogonalization Frequency", orthoFreq);
672  anasaziParams.set("Residual Frequency", resFreq);
673 
674  if(solverType_ == "GeneralizedDavidson" || solverType_ == "BlockKrylovSchur" || solverType_ == "BlockDavidson"){
675  anasaziParams.set( "Num Blocks", maxIterations+1 );
676  anasaziParams.set( "Maximum Restarts", 0 );
677  anasaziParams.set( "Maximum Subspace Dimension", (maxIterations+1)*blockSize );
678  }
679 
680  // Create and set initial vectors
681  auto map = laplacian_->getRangeMap();
682  Teuchos::RCP<mvector_t> ivec( new mvector_t(map, numEigenVectors));
683 
684  if (randomInit_) {
685  // 0-th vector constant 1, rest random
686  Anasazi::MultiVecTraits<scalar_t, mvector_t>::MvRandom(*ivec);
687  ivec->getVectorNonConst(0)->putScalar(1.);
688  }
689  else { // This implies we will use constant initial vectors.
690  // 0-th vector constant 1, other vectors constant per block
691  // Create numEigenVectors blocks, but only use numEigenVectors-1 of them.
692  // This assures orthogonality.
693  ivec->getVectorNonConst(0)->putScalar(1.);
694  for (int j = 1; j < numEigenVectors; j++)
695  ivec->getVectorNonConst(j)->putScalar(0.);
696 
697  gno_t blkSize = map->getGlobalNumElements() / numEigenVectors;
698  TEUCHOS_TEST_FOR_EXCEPTION(blkSize <= 0, std::runtime_error, "Blocksize too small for \"constants\" initial guess. Try \"random\".");
699 
700  for (size_t lid = 0; lid < ivec->getLocalLength(); lid++) {
701  gno_t gid = map->getGlobalElement(lid);
702  for (int j = 1; j < numEigenVectors; j++){
703  if (((j-1)*blkSize <= gid) && (j*blkSize > gid))
704  ivec->replaceLocalValue(lid,j,1.);
705  }
706  }
707  }
708 
709  // Create the eigenproblem to be solved
710  using problem_t = Anasazi::BasicEigenproblem<scalar_t, mvector_t, op_t>;
711  Teuchos::RCP<problem_t> problem (new problem_t(laplacian_, ivec));
712  problem->setHermitian(isHermitian);
713  problem->setNEV(numEigenVectors);
714 
715  if(solverType_ != "randomized"){
716  // Set preconditioner
717  Sphynx::setPreconditioner(problem);
718  if(problemType_ == Sphynx::GENERALIZED) problem->setM(degMatrix_);
719  }
720 
721  // Inform the eigenproblem that you are finished passing it information
722  bool boolret = problem->setProblem();
723  if (boolret != true) {
724  throw std::runtime_error("\nAnasazi::BasicEigenproblem::setProblem() returned with error.\n");
725  }
726  // Set Eigensolver
727  Teuchos::RCP<Anasazi::SolverManager<scalar_t, mvector_t, op_t>> solver;
728 
729  if(solverType_ == "LOBPCG"){
730  solver = Teuchos::rcp(new Anasazi::LOBPCGSolMgr<scalar_t, mvector_t, op_t>(problem, anasaziParams));
731  }
732  else if(solverType_ == "BlockDavidson"){
733  solver = Teuchos::rcp(new Anasazi::BlockDavidsonSolMgr<scalar_t, mvector_t, op_t>(problem, anasaziParams));
734  }
735  else if(solverType_ == "GeneralizedDavidson"){
736  solver = Teuchos::rcp(new Anasazi::GeneralizedDavidsonSolMgr<scalar_t, mvector_t, op_t>(problem, anasaziParams));
737  }
738  else if(solverType_ == "BlockKrylovSchur"){
739  solver = Teuchos::rcp(new Anasazi::BlockKrylovSchurSolMgr<scalar_t, mvector_t, op_t>(problem, anasaziParams));
740  }
741  else{
742  solver = Teuchos::rcp(new Anasazi::Experimental::RandomizedSolMgr<scalar_t, mvector_t, op_t>(problem, anasaziParams));
743  //numFailed = solver->getNumFailed();
744  }
745 
746  if (verbosity_ > 0 && comm_->getRank() == 0)
747  anasaziParams.print(std::cout);
748 
749  // Solve the problem
750  Anasazi::ReturnType returnCode = solver->solve();
751 
752  // Check convergence, niters, and solvetime
753  iter = solver->getNumIters();
754  //solvetime = (solver->getTimers()[0])->totalElapsedTime();
755 
756  // Retrieve the solution
757  using solution_t = Anasazi::Eigensolution<scalar_t, mvector_t>;
758  solution_t sol = problem->getSolution();
759  eigenVectors_ = sol.Evecs;
760  int numev = sol.numVecs;
761  std::vector<Anasazi::Value<double>> eigenValues_ = sol.Evals;
762 
763  // Summarize iteration counts and solve time
764  if (verbosity_ > 0 && comm_->getRank() == 0) {
765  std::cout << std::endl;
766  std::cout << "ANASAZI SUMMARY" << std::endl;
767  std::cout << "Failed to converge: " << numfailed << std::endl;
768  std::cout << "No of iterations : " << iter << std::endl;
769  std::cout << "Solve time: " << std::endl; // solvetime << std::endl;
770  std::cout << "No of comp. vecs. : " << numev << std::endl;
771  }
772 
773  std::cout << "Solver type: " << solverType_ << std::endl;
774  // Compute residuals (LOBPCG does this internally)
775  if(solverType_ == "randomized") {
776  std::vector<double> normR(numev);
777  mvector_t Aevec (map, numev);
778 
779  if (numev > 0) {
780  Teuchos::SerialDenseMatrix<int,double> T (numev, numev);
781  T.putScalar(0.0);
782  for (int i = 0; i < numev; ++i) T(i,i) = eigenValues_[i].realpart;
783  laplacian_->apply(*eigenVectors_, Aevec);
784  MVT::MvTimesMatAddMv(-1.0, *eigenVectors_, T, 1.0, Aevec);
785  MVT::MvNorm(Aevec, normR);
786  } // End residual computation
787 
788  if(comm_->getRank() == 0 && verbosity_ > 0) {
789  std::cout << std::endl << "Solver manager returned "
790  << (returnCode == Anasazi::Converged ? "converged." : "unconverged.")
791  << std::endl << std::endl
792  << std::setw(16) << "Eigenvalue"
793  << std::setw(18) << "Direct Residual"
794  << std::endl
795  << "------------------------------------------------------" << std::endl;
796 
797  for (int i = 0; i < numev; ++i) {
798  std::cout << std::setw(16) << 2.0-eigenValues_[i].realpart
799  << std::setw(18) << normR[i] / eigenValues_[i].realpart
800  << std::endl;
801  }
802  std::cout << "------------------------------------------------------" << std::endl;
803  }
804  }
805 
806  return numev;
807 
808  }
809 
811  template <typename Adapter>
812  template <typename problem_t>
813  void Sphynx<Adapter>::setPreconditioner(Teuchos::RCP<problem_t> &problem)
814  {
815  if(comm_->getRank() == 0) std::cout << "precType_ is: " << precType_ << std::endl;
816  // Set the preconditioner
817  if(precType_ == "muelu") {
819  }
820  else if(precType_ == "polynomial") {
822  }
823  else if(precType_ == "jacobi") {
825  }
826  // else: do we want a case where no preconditioning is applied?
827  }
828 
830  template <typename Adapter>
831  template <typename problem_t>
832  void Sphynx<Adapter>::setMueLuPreconditioner(Teuchos::RCP<problem_t> &problem)
833  {
834 #ifdef HAVE_ZOLTAN2SPHYNX_MUELU
835  Teuchos::ParameterList paramList;
836  if(verbosity_ == 0)
837  paramList.set("verbosity", "none");
838  else if(verbosity_ == 1)
839  paramList.set("verbosity", "low");
840  else if(verbosity_ == 2)
841  paramList.set("verbosity", "medium");
842  else if(verbosity_ == 3)
843  paramList.set("verbosity", "high");
844  else if(verbosity_ >= 4)
845  paramList.set("verbosity", "extreme");
846 
847  paramList.set("smoother: type", "CHEBYSHEV");
848  Teuchos::ParameterList smootherParamList;
849  smootherParamList.set("chebyshev: degree", 3);
850  smootherParamList.set("chebyshev: ratio eigenvalue", 7.0);
851  smootherParamList.set("chebyshev: eigenvalue max iterations", irregular_ ? 100 : 10);
852  paramList.set("smoother: params", smootherParamList);
853  paramList.set("use kokkos refactor", true);
854  paramList.set("transpose: use implicit", true);
855 
856  if(irregular_) {
857 
858  paramList.set("multigrid algorithm", "unsmoothed");
859 
860  paramList.set("coarse: type", "CHEBYSHEV");
861  Teuchos::ParameterList coarseParamList;
862  coarseParamList.set("chebyshev: degree", 3);
863  coarseParamList.set("chebyshev: ratio eigenvalue", 7.0);
864  paramList.set("coarse: params", coarseParamList);
865 
866  paramList.set("max levels", 5);
867  paramList.set("aggregation: drop tol", 0.40);
868 
869  }
870  using prec_t = MueLu::TpetraOperator<scalar_t, lno_t, gno_t, node_t>;
871  Teuchos::RCP<prec_t> prec = MueLu::CreateTpetraPreconditioner<
872  scalar_t, lno_t, gno_t, node_t>(laplacian_, paramList);
873 
874  problem->setPrec(prec);
875 
876 #else
877  throw std::runtime_error("\nSphynx Error: MueLu requested but not compiled into Trilinos.\n");
878 #endif
879  }
880 
882  template <typename Adapter>
883  template <typename problem_t>
884  void Sphynx<Adapter>::setPolynomialPreconditioner(Teuchos::RCP<problem_t> &problem)
885  {
886  int verbosity2 = Belos::Errors;
887  if(verbosity_ == 1)
888  verbosity2 += Belos::Warnings;
889  else if(verbosity_ == 2)
890  verbosity2 += Belos::Warnings + Belos::FinalSummary;
891  else if(verbosity_ == 3)
892  verbosity2 += Belos::Warnings + Belos::FinalSummary + Belos::TimingDetails;
893  else if(verbosity_ >= 4)
894  verbosity2 += Belos::Warnings + Belos::FinalSummary + Belos::TimingDetails
895  + Belos::StatusTestDetails;
896 
897  Teuchos::ParameterList paramList;
898  paramList.set("Polynomial Type", "Roots");
899  paramList.set("Orthogonalization","ICGS");
900  paramList.set("Maximum Degree", laplacian_->getGlobalNumRows() > 100 ? 25 : 5);
901  paramList.set("Polynomial Tolerance", 1.0e-6 );
902  paramList.set("Verbosity", verbosity2 );
903  paramList.set("Random RHS", false );
904  paramList.set("Outer Solver", "");
905  paramList.set("Timer Label", "Belos Polynomial Solve" );
906 
907  // Construct a linear problem for the polynomial solver manager
908  using lproblem_t = Belos::LinearProblem<scalar_t, mvector_t, op_t>;
909  Teuchos::RCP<lproblem_t> innerPolyProblem(new lproblem_t());
910  innerPolyProblem->setOperator(laplacian_);
911 
912  using btop_t = Belos::TpetraOperator<scalar_t, lno_t, gno_t, node_t>;
913  Teuchos::RCP<btop_t> polySolver(new btop_t(innerPolyProblem,
914  Teuchos::rcpFromRef(paramList),
915  "GmresPoly", true));
916  problem->setPrec(polySolver);
917  }
918 
920  template <typename Adapter>
921  template <typename problem_t>
922  void Sphynx<Adapter>::setJacobiPreconditioner(Teuchos::RCP<problem_t> &problem)
923  {
924 
925  Teuchos::RCP<Ifpack2::Preconditioner<scalar_t, lno_t, gno_t, node_t>> prec;
926  std::string precType = "RELAXATION";
927 
928  prec = Ifpack2::Factory::create<matrix_t> (precType, laplacian_);
929 
930  Teuchos::ParameterList precParams;
931  precParams.set("relaxation: type", "Jacobi");
932  precParams.set("relaxation: fix tiny diagonal entries", true);
933  precParams.set("relaxation: min diagonal value", 1.0e-8);
934 
935  prec->setParameters(precParams);
936  prec->initialize();
937  prec->compute();
938 
939  problem->setPrec(prec);
940 
941  }
942 
944  // Transform the computed eigenvectors into coordinates
945  template <typename Adapter>
946  void Sphynx<Adapter>::eigenvecsToCoords(Teuchos::RCP<mvector_t> &eigenVectors,
947  int computedNumEv,
948  Teuchos::RCP<mvector_t> &coordinates)
949  {
950  // Extract the meaningful eigenvectors by getting rid of the first one
951  Teuchos::Array<size_t> columns (computedNumEv-1);
952  for (int j = 0; j < computedNumEv-1; ++j) {
953  columns[j] = j+1;
954  }
955  coordinates = eigenVectors->subCopy (columns());
956  coordinates->setCopyOrView (Teuchos::View);
957  }
958 
959 
961  // If user provided some weights, use them by getting them from the adapter.
962  // If user didn't provide weights but told us to use degree as weight, do so.
963  // If user neither provided weights nor told us what to do, use degree as weight.
964  template <typename Adapter>
965  void Sphynx<Adapter>::computeWeights(std::vector<const weight_t *> vecweights,
966  std::vector<int> strides)
967  {
968 
969  int numWeights = adapter_->getNumWeightsPerVertex();
970  int numConstraints = numWeights > 0 ? numWeights : 1;
971 
972  size_t myNumVertices = adapter_->getLocalNumVertices();
973  weight_t ** weights = new weight_t*[numConstraints];
974  for(int j = 0; j < numConstraints; j++)
975  weights[j] = new weight_t[myNumVertices];
976 
977  // Will be needed if we use degree as weight
978  const offset_t *offset;
979  const gno_t *columnId;
980 
981  // If user hasn't set any weights, use vertex degrees as weights
982  if(numWeights == 0) {
983 
984  // Compute the weight of vertex i as the number of nonzeros in row i.
985  adapter_->getEdgesView(offset, columnId);
986  for (size_t i = 0; i < myNumVertices; i++)
987  weights[0][i] = offset[i+1] - offset[i] - 1;
988 
989  vecweights.push_back(weights[0]);
990  strides.push_back(1);
991  }
992  else {
993 
994  // Use the weights if there are any already set in the adapter
995  for(int j = 0; j < numConstraints; j++) {
996 
997  if(adapter_->useDegreeAsVertexWeight(j)) {
998  // Compute the weight of vertex i as the number of nonzeros in row i.
999  adapter_->getEdgesView(offset, columnId);
1000  for (size_t i = 0; i < myNumVertices; i++)
1001  weights[j][i] = offset[i+1] - offset[i];
1002  }
1003  else{
1004  int stride;
1005  const weight_t *wgt = NULL;
1006  adapter_->getVertexWeightsView(wgt, stride, j);
1007 
1008  for (size_t i = 0; i < myNumVertices; i++)
1009  weights[j][i] = wgt[i];
1010  }
1011 
1012  vecweights.push_back(weights[j]);
1013  strides.push_back(1);
1014 
1015  }
1016  }
1017 
1018  }
1019 
1020 
1022  // Compute a partition by calling MJ on given coordinates with given weights
1023  template <typename Adapter>
1024  void Sphynx<Adapter>::MJwrapper(const Teuchos::RCP<const mvector_t> &coordinates,
1025  std::vector<const weight_t *> weights,
1026  std::vector<int> strides,
1027  const Teuchos::RCP<PartitioningSolution<Adapter>> &solution)
1028  {
1029 
1030  Teuchos::TimeMonitor t(*Teuchos::TimeMonitor::getNewTimer("Sphynx::MJ"));
1031 
1032  using mvector_adapter_t = Zoltan2::XpetraMultiVectorAdapter<mvector_t>;
1036 
1037  size_t myNumVertices = coordinates->getLocalLength();
1038 
1039  // Create the base adapter for the multivector adapter
1040  Teuchos::RCP<mvector_adapter_t> adapcoordinates(new mvector_adapter_t(coordinates,
1041  weights,
1042  strides));
1043  Teuchos::RCP<const base_adapter_t> baseAdapter =
1044  Teuchos::rcp(dynamic_cast<const base_adapter_t *>(adapcoordinates.get()), false);
1045 
1046  // Create the coordinate model using the base multivector adapter
1047  Zoltan2::modelFlag_t flags;
1048 
1049  // Create the MJ object
1050  Teuchos::RCP<const Comm<int>> comm2 = comm_;
1051  Teuchos::RCP<mj_t> mj(new mj_t(env_, comm2, baseAdapter));
1052 
1053  // Partition with MJ
1054  Teuchos::RCP<solution_t> vectorsolution( new solution_t(env_, comm2, 1, mj));
1055  mj->partition(vectorsolution);
1056 
1057  // Transform the solution
1058  Teuchos::ArrayRCP<part_t> parts(myNumVertices);
1059  for(size_t i = 0; i < myNumVertices; i++) parts[i] = vectorsolution->getPartListView()[i];
1060  solution->setParts(parts);
1061  }
1062 
1063 } // namespace Zoltan2
1064 
1065 #endif
Zoltan2::BaseAdapter< userTypes_t > base_adapter_t
void partition(const Teuchos::RCP< PartitioningSolution< Adapter > > &solution)
Sphynx(const RCP< const Environment > &env, const RCP< Teuchos::ParameterList > &params, const RCP< Teuchos::ParameterList > &sphynxParams, const RCP< const Comm< int > > &comm, const RCP< const XpetraCrsGraphAdapter< graph_t > > &adapter)
Anasazi::MultiVecTraits< scalar_t, mvector_t > MVT
void computeWeights(std::vector< const weight_t * > vecweights, std::vector< int > strides)
void setMueLuPreconditioner(Teuchos::RCP< problem_t > &problem)
static ArrayRCP< ArrayRCP< zscalar_t > > weights
std::bitset< NUM_MODEL_FLAGS > modelFlag_t
void eigenvecsToCoords(Teuchos::RCP< mvector_t > &eigenVectors, int computedNumEv, Teuchos::RCP< mvector_t > &coordinates)
map_t::global_ordinal_type gno_t
Definition: mapRemotes.cpp:18
typename Adapter::node_t node_t
void setPolynomialPreconditioner(Teuchos::RCP< problem_t > &problem)
typename Zoltan2::InputTraits< ztcrsmatrix_t >::node_t node_t
Provides access for Zoltan2 to Xpetra::CrsGraph data.
Tpetra::MultiVector< scalar_t, lno_t, gno_t, node_t > mvector_t
void setJacobiPreconditioner(Teuchos::RCP< problem_t > &problem)
Teuchos::RCP< matrix_t > computeCombinatorialLaplacian()
void setUserEigenvectors(const Teuchos::RCP< mvector_t > &userEvects)
Defines the XpetraMultiVectorAdapter.
Defines XpetraCrsGraphAdapter class.
SparseMatrixAdapter_t::part_t part_t
Contains the Multi-jagged algorthm.
Adapter::scalar_t scalar_t
A PartitioningSolution is a solution to a partitioning problem.
static RCP< tMVector_t > coordinates
Tpetra::CrsGraph< lno_t, gno_t, node_t > graph_t
Adapter::part_t part_t
Tpetra::CrsMatrix< scalar_t, lno_t, gno_t, node_t > matrix_t
void MJwrapper(const Teuchos::RCP< const mvector_t > &coordinates, std::vector< const weight_t * > weights, std::vector< int > strides, const Teuchos::RCP< PartitioningSolution< Adapter >> &solution)
Algorithm defines the base class for all algorithms.
map_t::local_ordinal_type lno_t
Definition: mapRemotes.cpp:17
An adapter for Xpetra::MultiVector.
int AnasaziWrapper(const int numEigenVectors)
Tpetra::Operator< scalar_t, lno_t, gno_t, node_t > op_t
Defines the CoordinateModel classes.
typename Adapter::offset_t offset_t
void setPreconditioner(Teuchos::RCP< problem_t > &problem)
typename Adapter::scalar_t weight_t
Teuchos::RCP< matrix_t > computeNormalizedLaplacian(bool AHat=false)
Multi Jagged coordinate partitioning algorithm.