device_info() {
        getCudaDeviceInfo();
    }

    void cuda_matmul(double *a, double *b, double *c, int m, int n, int k) {
        double *d_a, *d_b, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
        cudaMallocManaged(&d_b, (n * k) * sizeof(double));
        cudaMallocManaged(&d_c, (m * k) * sizeof(double));

        cudaMemcpy(d_a, a, (m * n) * sizeof(double), cudaMemcpyHostToDevice);
        cudaMemcpy(d_b, b, (n * k) * sizeof(double), cudaMemcpyHostToDevice);

        dim3 dimBlock(NUM_THREADS, NUM_THREADS, 1);
        dim3 dimGrid((k / dimBlock.x) + 1, (m / dimBlock.y) + 1, 1);

        cudaError_t syncErr, asyncErr;
        matmul<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, m, n, k);

        syncErr = cudaGetLastError();
        asyncErr = cudaDeviceSynchronize();

        if (syncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(syncErr) << endl;
        if (asyncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(asyncErr) << endl;
    
        cudaMemcpy(c, d_c, (m * k) * sizeof(double), cudaMemcpyDeviceToHost);

        cudaFree(d_a);
        cudaFree(d_b);
        cudaFree(d_c);
    }

    void cuda_matsum(double *a, double *b, double *c, int m, int n) {
        double *d_a, *d_b, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
        cudaMallocManaged(&d_b, (m * n) * sizeof(double));
        cudaMallocManaged(&d_c, (m * n) * sizeof(double));

        cudaMemcpy(d_a, a, (m * n) * sizeof(double), cudaMemcpyHostToDevice);
        cudaMemcpy(d_b, b, (m * n) * sizeof(double), cudaMemcpyHostToDevice);

        dim3 dimBlock(NUM_THREADS, NUM_THREADS, 1);
        dim3 dimGrid((n / dimBlock.x) + 1, (m / dimBlock.y) + 1, 1);

        cudaError_t syncErr, asyncErr;
        matsum<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, m, n);
        
        syncErr = cudaGetLastError();
        asyncErr = cudaDeviceSynchronize();

        if (syncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(syncErr) << endl;
        if (asyncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(asyncErr) << endl;

        cudaMemcpy(c, d_c, (m * n) * sizeof(double), cudaMemcpyDeviceToHost);

        cudaFree(d_a);
        cudaFree(d_b);
        cudaFree(d_c);
    }

    void cuda_matprod(double *a, double *b, double *c, int m, int n) {
        double *d_a, *d_b, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
        cudaMallocManaged(&d_b, (m * n) * sizeof(double));
        cudaMallocManaged(&d_c, (m * n) * sizeof(double));

        cudaMemcpy(d_a, a, (m * n) * sizeof(double), cudaMemcpyHostToDevice);
        cudaMemcpy(d_b, b, (m * n) * sizeof(double), cudaMemcpyHostToDevice);

        dim3 dimBlock(NUM_THREADS, NUM_THREADS, 1);
        dim3 dimGrid((n / dimBlock.x) + 1, (m / dimBlock.y) + 1, 1);

        cudaError_t syncErr, asyncErr;
        matprod<<<dimGrid, dimBlock>>>(d_a, d_b, d_c, m, n);
        
        syncErr = cudaGetLastError();
        asyncErr = cudaDeviceSynchronize();

        if (syncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(syncErr) << endl;
        if (asyncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(asyncErr) << endl;

        cudaMemcpy(c, d_c, (m * n) * sizeof(double), cudaMemcpyDeviceToHost);

        cudaFree(d_a);
        cudaFree(d_b);
        cudaFree(d_c);
    }

    void cuda_sum(double *a, double b, double *c, int m, int n) {
        double *d_a, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
        cudaMallocManaged(&d_c, (m * n) * sizeof(double));

        cudaMemcpy(d_a, a, (m * n) * sizeof(double), cudaMemcpyHostToDevice);

        dim3 dimBlock(NUM_THREADS, NUM_THREADS, 1);
        dim3 dimGrid((n / dimBlock.x) + 1, (m / dimBlock.y) + 1, 1);

        cudaError_t syncErr, asyncErr;
        sum<<<dimGrid, dimBlock>>>(d_a, b, d_c, m, n);
        
        syncErr = cudaGetLastError();
        asyncErr = cudaDeviceSynchronize();

        if (syncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(syncErr) << endl;
        if (asyncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(asyncErr) << endl;

        cudaMemcpy(c, d_c, (m * n) * sizeof(double), cudaMemcpyDeviceToHost);

        cudaFree(d_a);
        cudaFree(d_c);
    }

    void cuda_prod(double *a, double b, double *c, int m, int n) {
        double *d_a, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
        cudaMallocManaged(&d_c, (m * n) * sizeof(double));

        cudaMemcpy(d_a, a, (m * n) * sizeof(double), cudaMemcpyHostToDevice);

        dim3 dimBlock(NUM_THREADS, NUM_THREADS, 1);
        dim3 dimGrid((n / dimBlock.x) + 1, (m / dimBlock.y) + 1, 1);

        cudaError_t syncErr, asyncErr;
        prod<<<dimGrid, dimBlock>>>(d_a, b, d_c, m, n);
        
        syncErr = cudaGetLastError();
        asyncErr = cudaDeviceSynchronize();

        if (syncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(syncErr) << endl;
        if (asyncErr!= cudaSuccess) 
            cout << "CUDA Error: " << cudaGetErrorString(asyncErr) << endl;

        cudaMemcpy(c, d_c, (m * n) * sizeof(double), cudaMemcpyDeviceToHost);

        cudaFree(d_a);
        cudaFree(d_c);
    }

    void cuda_maximum(double *a, double b, double *c, int m, int n) {
        double *d_a, *d_c;

        cudaMallocManaged(&d_a, (m * n) * sizeof(double));
