cudaDotProductD :: (Vector Double, Vector Double)
                -> CIO (Scalar Double)
cudaDotProductD (v1, v2) = do
    let n = arraySize (arrayShape v1)
    o <- allocScalar
    v1ptr  <- devVD v1
    v2ptr  <- devVD v2
    outPtr <- devSD o
    liftIO $ BL.withCublas $ \handle -> execute handle n v1ptr v2ptr outPtr
    return o

    where
      execute h n v1ptr v2ptr optr
        = BL.ddot h n v1ptr 1 v2ptr 1 optr    

-- | Execute the dot product of the two vectors using
--   CUBLAS in the CUDA backend if available, fallback to a "pure"
--   implementation otherwise:
--
--   >>> A.fold (+) 0 $ A.zipWith (*) v1 v2
cudaDotProductD :: (Vector Double, Vector Double)
                -> CIO (Scalar Double)
cudaDotProductD (v1, v2) = do
    let n = arraySize (arrayShape v1)
    o <- allocScalar
    v1ptr  <- devVD v1
    v2ptr  <- devVD v2
    outPtr <- devSD o
    liftIO $ BL.withCublas $ \handle -> execute handle n v1ptr v2ptr outPtr
    return o

    where
      execute h n v1ptr v2ptr optr
        = BL.ddot h n v1ptr 1 v2ptr 1 optr    

-- | Execute the dot product of the two vectors using
--   CUBLAS in the CUDA backend if available, fallback to a "pure"
--   implementation otherwise:
--
--   >>> A.fold (+) 0 $ A.zipWith (*) v1 v2
cudaDotProductD (v1, v2) = do
    let n = arraySize (arrayShape v1)
    o <- allocScalar
    v1ptr  <- devVD v1
    v2ptr  <- devVD v2
    outPtr <- devSD o
    liftIO $ BL.withCublas $ \handle -> execute handle n v1ptr v2ptr outPtr
    return o

    where
      execute h n v1ptr v2ptr optr
        = BL.ddot h n v1ptr 1 v2ptr 1 optr    

-- | Execute the dot product of the two vectors using
--   CUBLAS in the CUDA backend if available, fallback to a "pure"
--   implementation otherwise:
--
--   >>> A.fold (+) 0 $ A.zipWith (*) v1 v2