ALP User Documentation  0.8.preview
Algebraic Programming User Documentation
conjugate_gradient.hpp
Go to the documentation of this file.
1 
2 /*
3  * Copyright 2021 Huawei Technologies Co., Ltd.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
26 #ifndef _H_GRB_ALGORITHMS_CONJUGATE_GRADIENT
27 #define _H_GRB_ALGORITHMS_CONJUGATE_GRADIENT
28 
29 #include <cstdio>
30 #include <cmath>
31 
32 #include <graphblas.hpp>
33 #include <graphblas/utils/iscomplex.hpp>
34 
35 
36 namespace grb {
37 
38  namespace algorithms {
39 
176  template<
178  bool preconditioned = true,
179  typename IOType,
180  typename ResidualType,
181  typename NonzeroType,
182  typename InputType,
183  class Ring = Semiring<
186  >,
187  class Minus = operators::subtract< IOType >,
188  class Divide = operators::divide< IOType >,
189  typename RSI, typename NZI, Backend backend
190  >
195  const std::function<
196  grb::RC(
199  )
200  > &Minv,
201  const size_t max_iterations,
202  ResidualType tol,
203  size_t &iterations,
204  ResidualType &residual,
208  grb::Vector< IOType, backend > &temp_precond,
209  const Ring &ring = Ring(),
210  const Minus &minus = Minus(),
211  const Divide &divide = Divide()
212  ) {
213  // static checks
214  static_assert( std::is_floating_point< ResidualType >::value,
215  "Can only use the CG algorithm with floating-point residual "
216  "types." ); // unless some different norm were used: issue #89
217  static_assert( !( descr & descriptors::no_casting ) || (
218  std::is_same< IOType, ResidualType >::value &&
219  std::is_same< IOType, NonzeroType >::value &&
220  std::is_same< IOType, InputType >::value
221  ), "One or more of the provided containers have differing element types "
222  "while the no-casting descriptor has been supplied"
223  );
224  static_assert( !( descr & descriptors::no_casting ) || (
225  std::is_same< NonzeroType, typename Ring::D1 >::value &&
226  std::is_same< IOType, typename Ring::D2 >::value &&
227  std::is_same< InputType, typename Ring::D3 >::value &&
228  std::is_same< InputType, typename Ring::D4 >::value
229  ), "no_casting descriptor was set, but semiring has incompatible domains "
230  "with the given containers."
231  );
232  static_assert( !( descr & descriptors::no_casting ) || (
233  std::is_same< InputType, typename Minus::D1 >::value &&
234  std::is_same< InputType, typename Minus::D2 >::value &&
235  std::is_same< InputType, typename Minus::D3 >::value
236  ), "no_casting descriptor was set, but given minus operator has "
237  "incompatible domains with the given containers."
238  );
239  static_assert( !( descr & descriptors::no_casting ) || (
240  std::is_same< ResidualType, typename Divide::D1 >::value &&
241  std::is_same< ResidualType, typename Divide::D2 >::value &&
242  std::is_same< ResidualType, typename Divide::D3 >::value
243  ), "no_casting descriptor was set, but given divide operator has "
244  "incompatible domains with the given tolerance type."
245  );
246  static_assert( std::is_floating_point< ResidualType >::value,
247  "Require floating-point residual type."
248  );
249 
250  constexpr const Descriptor descr_dense = descr | descriptors::dense;
251  const ResidualType zero_residual = ring.template getZero< ResidualType >();
252  const IOType zero = ring.template getZero< IOType >();
253  const size_t n = grb::ncols( A );
254 
255  // retrieve conditional buffers
256  grb::Vector< IOType, backend > &z = preconditioned ? temp_precond : r;
257 
258  // dynamic checks
259  {
260  const size_t m = grb::nrows( A );
261  if( grb::size( x ) != n ) {
262  std::cerr << "Error: initial solution guess and output vector length ("
263  << size( x ) << ") does not match matrix size (" << m << ").\n";
264  return grb::MISMATCH;
265  }
266  if( grb::size( b ) != m ) {
267  std::cerr << "Error: right-hand side size (" << grb::size( b ) << ") does "
268  << "not match matrix size (" << m << ").\n";
269  return grb::MISMATCH;
270  }
271  if( grb::size( r ) != n || grb::size( u ) != n || grb::size( temp ) != n ) {
272  std::cerr << "Error: provided workspace vectors are not of the correct "
273  << "length.\n";
274  return grb::MISMATCH;
275  }
276  if( preconditioned && grb::size( temp_precond ) != n ) {
277  std::cerr << "Error: (left) preconditioner workspace vector does not have "
278  << "the correct length.\n";
279  return grb::MISMATCH;
280  }
281  if( m != n ) {
282  std::cerr << "Warning: grb::algorithms::conjugate_gradient requires "
283  << "square input matrices, but a non-square input matrix was "
284  << "given instead.\n";
285  return grb::ILLEGAL;
286  }
287 
288  // capacities
289  if( grb::capacity( x ) != n ) {
290  return grb::ILLEGAL;
291  }
292  if( grb::capacity( r ) != n || grb::capacity( u ) != n ||
293  grb::capacity( temp ) != n
294  ) {
295  return grb::ILLEGAL;
296  }
297  if( preconditioned && grb::capacity( temp_precond ) != n ) {
298  return grb::ILLEGAL;
299  }
300 
301  // others
302  if( tol <= zero_residual ) {
303  std::cerr << "Error: tolerance input to CG must be strictly positive\n";
304  return grb::ILLEGAL;
305  }
306  if( max_iterations == 0 ) {
307  std::cerr << "Error: at least one CG iteration must be requested\n";
308  return grb::ILLEGAL;
309  }
310  }
311 
312  // set pure output fields to neutral defaults
313  iterations = 0;
314  residual = std::numeric_limits< double >::infinity();
315 
316  // make x and b structurally dense (if not already) so that the remainder
317  // algorithm can safely use the dense descriptor for faster operations
318  {
319  RC rc = grb::SUCCESS;
320  if( nnz( x ) != n ) {
321  rc = grb::set< descriptors::invert_mask | descriptors::structural >(
322  x, x, zero
323  );
324  }
325  if( rc != grb::SUCCESS ) { return rc; }
326  assert( nnz( x ) == n );
327  }
328 
329  IOType sigma, bnorm, alpha, beta;
330 
331  // r = b - temp;
332  grb::RC ret = grb::set( temp, 0 ); assert( ret == grb::SUCCESS );
333  ret = ret ? ret : grb::mxv< descr_dense >( temp, A, x, ring );
334  assert( ret == grb::SUCCESS );
335  ret = ret ? ret : grb::set( r, zero ); assert( ret == grb::SUCCESS );
336  // note: no dense descriptor since we actually allow sparse b
337  ret = ret ? ret : grb::foldl( r, b, ring.getAdditiveMonoid() );
338  // from here onwards, r, temp, x are dense and will remain so
339  assert( nnz( r ) == n );
340  assert( nnz( temp ) == n );
341  ret = ret ? ret : grb::foldl< descr_dense >( r, temp, minus );
342  assert( ret == grb::SUCCESS );
343 
344  // z = M^-1r
345  if( preconditioned ) {
346  ret = ret ? ret : grb::set( z, 0 ); // also ensures z is dense, henceforth
347  ret = ret ? ret : Minv( z, r );
348  } // else, z equals r (by reference)
349 
350  // u = z;
351  ret = ret ? ret : grb::set( u, z );
352  assert( ret == grb::SUCCESS );
353  // from here onwards, u is dense; i.e., all vectors are dense from now on,
354  // and we can freely use the dense descriptor in the subsequent
355 
356  // sigma = r' * z;
357  sigma = zero;
358  ret = ret ? ret : grb::dot< descr_dense >(
359  sigma,
360  r, z,
361  ring.getAdditiveMonoid(),
363  );
364 
365  assert( ret == grb::SUCCESS );
366 
367  // bnorm = b' * b;
368  bnorm = zero;
369  ret = ret ? ret : grb::dot< descr_dense >(
370  bnorm,
371  b, b,
372  ring.getAdditiveMonoid(),
374  assert( ret == grb::SUCCESS );
375 
376  // get effective tolerance and exit on any error during prelude
377  if( ret == grb::SUCCESS ) {
378  tol *= std::sqrt( grb::utils::is_complex< IOType >::modulus( bnorm ) );
379  } else {
380  std::cerr << "Warning: preconditioned CG caught error during prelude ("
381  << grb::toString( ret ) << ")\n";
382  return ret;
383  }
384 
385  // all OK; perform main iterations
386  size_t iter = 0;
387  do {
388  assert( iter < max_iterations );
389  (void) ++iter;
390 
391  // temp = A * u;
392  ret = ret ? ret : grb::set< descr_dense >( temp, 0 );
393  assert( ret == grb::SUCCESS );
394  ret = ret ? ret : grb::mxv< descr_dense >( temp, A, u, ring );
395  assert( ret == grb::SUCCESS );
396 
397  // beta = (A * u)' * u;
398  beta = zero;
399  ret = ret ? ret : grb::dot< descr_dense >(
400  beta,
401  temp, u,
402  ring.getAdditiveMonoid(),
404  );
405  assert( ret == grb::SUCCESS );
406 
407  // alpha = sigma / beta;
408  ret = ret ? ret : grb::apply( alpha, sigma, beta, divide );
409  assert( ret == grb::SUCCESS );
410 
411  // x = x + alpha * u;
412  ret = ret ? ret : grb::eWiseMul< descr_dense >( x, alpha, u, ring );
413  assert( ret == grb::SUCCESS );
414 
415  // r = r - alpha .* temp = r - alpha .* (A * u);
416  ret = ret ? ret : grb::foldr< descr_dense >( alpha, temp,
417  ring.getMultiplicativeMonoid() );
418  assert( ret == grb::SUCCESS );
419  ret = ret ? ret : grb::foldl< descr_dense >( r, temp, minus );
420  assert( ret == grb::SUCCESS );
421 
422  // get residual. In the preconditioned case, the resulting scalar is *not*
423  // used for subsequent operations. Therefore, we first compute the residual
424  // using alpha as a temporary scalar
425  alpha = zero;
426  ret = ret ? ret : grb::dot< descr_dense >(
427  alpha,
428  r, r,
429  ring.getAdditiveMonoid(),
431  );
432  assert( ret == grb::SUCCESS );
433  residual = grb::utils::is_complex< IOType >::modulus( alpha );
434 
435  // check residual
436  if( ret == grb::SUCCESS ) {
437  if( sqrt( residual ) < tol || iter >= max_iterations ) { break; }
438  }
439 
440  // apply preconditioner action (if required), and compute beta for the
441  // preconditioned case
442  // z = M^-1r
443  // beta = r' * z
444  if( preconditioned ) {
445  beta = zero;
446  ret = ret ? ret : Minv( z, r ); assert( ret == grb::SUCCESS );
447  ret = ret ? ret : grb::dot< descr_dense >(
448  beta,
449  r, z,
450  ring.getAdditiveMonoid(),
452  );
453  assert( ret == grb::SUCCESS );
454  } else {
455  beta = alpha;
456  }
457 
458  // alpha = beta / sigma;
459  ret = ret ? ret : grb::apply( alpha, beta, sigma, divide );
460  assert( ret == grb::SUCCESS );
461 
462  // u_next = z + beta * u_previous;
463  ret = ret ? ret : grb::foldr< descr_dense >( alpha, u,
464  ring.getMultiplicativeMonoid() );
465  assert( ret == grb::SUCCESS );
466  ret = ret ? ret : grb::foldr< descr_dense >( z, u,
467  ring.getAdditiveMonoid() );
468  assert( ret == grb::SUCCESS );
469 
470  sigma = beta;
471  } while( ret == grb::SUCCESS );
472 
473  // output that is independent of error code
474  iterations = iter;
475 
476  // return correct error code
477  if( ret == grb::SUCCESS ) {
478  if( std::sqrt( residual ) >= tol ) {
479  // did not converge within iterations
480  return grb::FAILED;
481  }
482  }
483  return ret;
484  }
485 
493  template<
495  typename IOType,
496  typename ResidualType,
497  typename NonzeroType,
498  typename InputType,
499  class Ring = Semiring<
502  >,
503  class Minus = operators::subtract< IOType >,
504  class Divide = operators::divide< IOType >,
505  typename RSI, typename NZI, Backend backend
506  >
511  const size_t max_iterations,
512  ResidualType tol,
513  size_t &iterations,
514  ResidualType &residual,
518  const Ring &ring = Ring(),
519  const Minus &minus = Minus(),
520  const Divide &divide = Divide()
521  ) {
522  // static checks
523  static_assert( std::is_floating_point< ResidualType >::value,
524  "Can only use the CG algorithm with floating-point residual "
525  "types." ); // unless some different norm were used: issue #89
526  static_assert( !( descr & descriptors::no_casting ) || (
527  std::is_same< IOType, ResidualType >::value &&
528  std::is_same< IOType, NonzeroType >::value &&
529  std::is_same< IOType, InputType >::value
530  ), "One or more of the provided containers have differing element types "
531  "while the no-casting descriptor has been supplied"
532  );
533  static_assert( !( descr & descriptors::no_casting ) || (
534  std::is_same< NonzeroType, typename Ring::D1 >::value &&
535  std::is_same< IOType, typename Ring::D2 >::value &&
536  std::is_same< InputType, typename Ring::D3 >::value &&
537  std::is_same< InputType, typename Ring::D4 >::value
538  ), "no_casting descriptor was set, but semiring has incompatible domains "
539  "with the given containers."
540  );
541  static_assert( !( descr & descriptors::no_casting ) || (
542  std::is_same< InputType, typename Minus::D1 >::value &&
543  std::is_same< InputType, typename Minus::D2 >::value &&
544  std::is_same< InputType, typename Minus::D3 >::value
545  ), "no_casting descriptor was set, but given minus operator has "
546  "incompatible domains with the given containers."
547  );
548  static_assert( !( descr & descriptors::no_casting ) || (
549  std::is_same< ResidualType, typename Divide::D1 >::value &&
550  std::is_same< ResidualType, typename Divide::D2 >::value &&
551  std::is_same< ResidualType, typename Divide::D3 >::value
552  ), "no_casting descriptor was set, but given divide operator has "
553  "incompatible domains with the given tolerance type."
554  );
555  static_assert( std::is_floating_point< ResidualType >::value,
556  "Require floating-point residual type."
557  );
558 
559  // create a dummy preconditioner and buffer that will never be used
560  std::function<
561  grb::RC(
564  )
565  > dummy_preconditioner =
566  [](
569  ) -> grb::RC {
570  return grb::FAILED;
571  };
572  grb::Vector< IOType, backend > dummy_buffer( 0 );
573 
574  // call PCG with preconditioning disabled
575  return preconditioned_conjugate_gradient< descr, false >(
576  x, A, b,
577  dummy_preconditioner,
578  max_iterations, tol,
579  iterations, residual,
580  r, u, temp, dummy_buffer,
581  ring, minus, divide
582  );
583  }
584 
585  } // namespace algorithms
586 
587 } // end namespace grb
588 
589 #endif // end _H_GRB_ALGORITHMS_CONJUGATE_GRADIENT
590 
RC set(Vector< DataType, backend, Coords > &x, const T val, const Phase &phase=EXECUTE, const typename std::enable_if< !grb::is_object< DataType >::value &&!grb::is_object< T >::value, void >::type *const =nullptr) noexcept
Sets all elements of a vector to the given value.
Definition: io.hpp:858
Standard identity for numerical addition.
Definition: identities.hpp:57
A call to a primitive has determined that one of its arguments was illegal as per the specification o...
Definition: rc.hpp:143
An ALP/GraphBLAS matrix.
Definition: matrix.hpp:72
RC
Return codes of ALP primitives.
Definition: rc.hpp:47
A GraphBLAS vector.
Definition: vector.hpp:64
Standard identity for numerical multiplication.
Definition: identities.hpp:79
Numerical substraction of two numbers.
Definition: ops.hpp:301
Conjugate-multiply operator that conjugates the left-hand operand before multiplication.
Definition: ops.hpp:969
grb::RC conjugate_gradient(grb::Vector< IOType, backend > &x, const grb::Matrix< NonzeroType, backend, RSI, RSI, NZI > &A, const grb::Vector< InputType, backend > &b, const size_t max_iterations, ResidualType tol, size_t &iterations, ResidualType &residual, grb::Vector< IOType, backend > &r, grb::Vector< IOType, backend > &u, grb::Vector< IOType, backend > &temp, const Ring &ring=Ring(), const Minus &minus=Minus(), const Divide &divide=Divide())
Solves a linear system with unknown by the Conjugate Gradients (CG) method on general fields.
Definition: conjugate_gradient.hpp:507
static constexpr Descriptor no_casting
Disallows the standard casting of input parameters to a compatible domain in case they did not match ...
Definition: descriptors.hpp:196
grb::RC preconditioned_conjugate_gradient(grb::Vector< IOType, backend > &x, const grb::Matrix< NonzeroType, backend, RSI, RSI, NZI > &A, const grb::Vector< InputType, backend > &b, const std::function< grb::RC(grb::Vector< IOType, backend > &, const grb::Vector< IOType, backend > &) > &Minv, const size_t max_iterations, ResidualType tol, size_t &iterations, ResidualType &residual, grb::Vector< IOType, backend > &r, grb::Vector< IOType, backend > &u, grb::Vector< IOType, backend > &temp, grb::Vector< IOType, backend > &temp_precond, const Ring &ring=Ring(), const Minus &minus=Minus(), const Divide &divide=Divide())
Solves a preconditioned linear system with unknown by the Conjugate Gradients (CG) method on genera...
Definition: conjugate_gradient.hpp:191
size_t nnz(const Vector< DataType, backend, Coords > &x) noexcept
Request the number of nonzeroes in a given vector.
Definition: io.hpp:479
static constexpr Descriptor no_operation
Indicates no additional pre- or post-processing on any of the GraphBLAS function arguments.
Definition: descriptors.hpp:63
This operator multiplies the two input parameters and writes the result to the output variable.
Definition: ops.hpp:208
unsigned int Descriptor
Descriptors indicate pre- or post-processing for some or all of the arguments to an ALP/GraphBLAS cal...
Definition: descriptors.hpp:54
size_t nrows(const Matrix< InputType, backend, RIT, CIT, NIT > &A) noexcept
Requests the row size of a given matrix.
Definition: io.hpp:286
static constexpr Descriptor dense
Indicates that all input and output vectors to an ALP/GraphBLAS primitive are structurally dense.
Definition: descriptors.hpp:151
static enum RC apply(OutputType &out, const InputType1 &x, const InputType2 &y, const OP &op=OP(), const typename std::enable_if< grb::is_operator< OP >::value &&!grb::is_object< InputType1 >::value &&!grb::is_object< InputType2 >::value &&!grb::is_object< OutputType >::value, void >::type *=nullptr)
Out-of-place application of the operator OP on two data elements.
Definition: blas0.hpp:179
size_t ncols(const Matrix< InputType, backend, RIT, CIT, NIT > &A) noexcept
Requests the column size of a given matrix.
Definition: io.hpp:339
Conjugate-multiply operator that conjugates the right-hand operand before multiplication.
Definition: ops.hpp:918
RC foldl(IOType &x, const Vector< InputType, backend, Coords > &y, const Vector< MaskType, backend, Coords > &mask, const Monoid &monoid=Monoid(), const typename std::enable_if< !grb::is_object< IOType >::value &&!grb::is_object< InputType >::value &&!grb::is_object< MaskType >::value &&grb::is_monoid< Monoid >::value, void >::type *const =nullptr)
Reduces, or folds, a vector into a scalar.
Definition: blas1.hpp:3840
Indicates when one of the grb::algorithms has failed to achieve its intended result,...
Definition: rc.hpp:154
Backend
A collection of all backends.
Definition: backends.hpp:49
This operator takes the sum of the two input parameters and writes it to the output variable.
Definition: ops.hpp:175
The ALP/GraphBLAS namespace.
Definition: graphblas.hpp:477
The main header to include in order to use the ALP/GraphBLAS API.
size_t size(const Vector< DataType, backend, Coords > &x) noexcept
Request the size of a given vector.
Definition: io.hpp:235
Numerical division of two numbers.
Definition: ops.hpp:328
Indicates the primitive has executed successfully.
Definition: rc.hpp:54
size_t capacity(const Vector< InputType, backend, Coords > &x) noexcept
Queries the capacity of the given ALP/GraphBLAS container.
Definition: io.hpp:388
A generalised semiring.
Definition: semiring.hpp:190
One or more of the ALP/GraphBLAS objects passed to the primitive that returned this error have mismat...
Definition: rc.hpp:90
std::string toString(const RC code)