text: #pragma once
  
  #include <memory>
  #include <numeric>
  
  extern "C" {
  #define LAGRAPH_EXPERIMENTAL_ASK_BEFORE_BENCHMARKING 1
  #include <GraphBLAS.h>
  #include <LAGraph.h>
  }
  
  #include "utils.h"
  
  //------------------------------------------------------------------------------
  // ok: call a GraphBLAS method and check the result
  //------------------------------------------------------------------------------
  
  // ok(GrB_Info) is a function that processes result of a GraphBLAS method and checks the status;
  // if a failure occurs, it returns the error status to the caller.
  
  inline __attribute__((always_inline))
  GrB_Info ok(GrB_Info info, bool no_value_is_error = true) {
      using namespace std::string_literals;
  
      if (info == GrB_SUCCESS || (!no_value_is_error && info == GrB_NO_VALUE)) {
          return info;
      } else {
          throw std::runtime_error{"GraphBLAS error."};
      }
  }
  
  inline __attribute__((always_inline))
  std::unique_ptr<bool[]> array_of_true(size_t n) {
      std::unique_ptr<bool[]> array{new bool[n]};
  
      int nthreads = GlobalNThreads;
      nthreads = std::min<size_t>(n / 4096, nthreads);
      nthreads = std::max(nthreads, 1);
  #pragma omp parallel for num_threads(nthreads) schedule(static)
      for (size_t i = 0; i < n; ++i) {
          array[i] = true;
      }
  
      return array;
  }
  
  inline __attribute__((always_inline))
  std::unique_ptr<GrB_Index[]> array_of_indices(size_t n) {
      std::unique_ptr<GrB_Index[]> array{new GrB_Index[n]};
  
      int nthreads = GlobalNThreads;
      nthreads = std::min<size_t>(n / 4096, nthreads);
      nthreads = std::max(nthreads, 1);
  #pragma omp parallel for num_threads(nthreads) schedule(static)
      for (size_t i = 0; i < n; ++i) {
          array[i] = i;
      }
  
      return array;
  }
  
  /*
   * DEBUG FUNCTIONS
   */
  
  inline void WriteOutDebugMatrix(GrB_Matrix result, const char *title = nullptr) {
      if (title)
          printf("%s:\n", title);
  
      GrB_Index rows, cols;
      ok(GrB_Matrix_nrows(&rows, result));
      ok(GrB_Matrix_ncols(&cols, result));
      double element;
  
      for (GrB_Index i = 0; i < rows; i++) {
          for (GrB_Index j = 0; j < cols; j++) {
              GrB_Info info = ok(GrB_Matrix_extractElement_FP64(&element, result, i, j), false);
  
              if (info == GrB_SUCCESS) {
                  printf("%g ", element);
              } else if (info == GrB_NO_VALUE) {
                  // It is up to the user to determine what 'no value'
                  // means.  It depends on the semiring used.
                  printf("- ");
              } else {
                  printf("GraphBLAS error");
              }
  
          }
          printf("\n");
      }
  }
  
  inline void WriteOutDebugVector(GrB_Vector result, const char *title = nullptr) {
      if (title)
          printf("%s:\n", title);
  
      GrB_Index size;
      ok(GrB_Vector_size(&size, result));
      double element;
  
      for (unsigned int i = 0; i < size; i++) {
          GrB_Info info = ok(GrB_Vector_extractElement_FP64(&element, result, i), false);
  
          if (info == GrB_SUCCESS) {
              if (element == UINT64_MAX) {
                  printf("inf ");
              } else {
                  printf("%g ", element);
              }
  
          } else if (info == GrB_NO_VALUE) {
              // It is up to the user to determine what 'no value'
              // means.  It depends on the semiring used.
              printf("- ");
          } else {
              throw std::runtime_error{"GraphBLAS error."};
          }
  
      }
      printf("\n");
  }
  
  namespace gbxx {
      inline void GBxx_free_cpp(GrB_Type *pointer) { ok(GrB_Type_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_UnaryOp *pointer) { ok(GrB_UnaryOp_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_BinaryOp *pointer) { ok(GrB_BinaryOp_free(pointer)); }
  
      inline void GBxx_free_cpp(GxB_SelectOp *pointer) { ok(GxB_SelectOp_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_Monoid *pointer) { ok(GrB_Monoid_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_Semiring *pointer) { ok(GrB_Semiring_free(pointer)); }
  
      inline void GBxx_free_cpp(GxB_Scalar *pointer) { ok(GxB_Scalar_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_Vector *pointer) { ok(GrB_Vector_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_Matrix *pointer) { ok(GrB_Matrix_free(pointer)); }
  
      inline void GBxx_free_cpp(GrB_Descriptor *pointer) { ok(GrB_Descriptor_free(pointer)); }
  
      template<typename Type>
      struct GBxx_deleter {
          void operator()(Type b) {
              GBxx_free_cpp(&b);
          }
      };
  }
  
  /**
   * Owning smart pointer that disposes GrB object when it goes out of scope.
   *
   * Usage:
   * Initialize using first (out) parameter of function
   *   GBxx_Object<GrB_Matrix> mx = GB(GrB_Matrix_new, GrB_BOOL, nrows, ncols);
   *
   * Initialize manually
   *   GrB_Matrix mx_ptr = nullptr;
   *   // init
   *   GBxx_Object<GrB_Matrix> mx{mx_ptr};
   *
   * When a GrB_Matrix* pointer is needed
   *   // binwrite might change the pointer since it uses export and import
   *   GrB_Matrix mx_ptr = mx.release();
   *   LAGraph_binwrite(&mx_ptr, "outfile.grb", nullptr);
   *   mx.reset(mx_ptr);
   */
  template<typename Type>
  using GBxx_Object = std::unique_ptr<typename std::remove_pointer<Type>::type, gbxx::GBxx_deleter<Type>>;
  
  template<typename Type>
  using GBxx_Object_shared = std::shared_ptr<typename std::remove_pointer<Type>::type>;
  
  
  /**
   * Initialize a GBxx_Object<Type> smart pointer using func.
   *
   * Usage:
   *   GBxx_Object<GrB_Matrix> mx = GB(GrB_Matrix_new, GrB_BOOL, nrows, ncols);
   *
   * @tparam Type GrB object type
   * @param func A function: func(Type*, Args...)
   * @param args 2nd and more parameters of func.
   * @return The smart pointer initialized with func(&out, args...)
   */
  template<typename Type, typename ...ArgsIn, typename ...Args>
  GBxx_Object<Type> GB(GrB_Info (&func)(Type *, Args...), ArgsIn &&... args) {
      Type gb_instance = nullptr;
      ok(func(&gb_instance, std::forward<ArgsIn>(args)...));
  
      return {gb_instance, {}};
  }
  
  template<typename Z, typename X>
  using GBxx_unary_function = void (*)(Z *, const X *);
  