Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Omp #324

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft

Omp #324

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
252f798
add sparse.csr and the structure for testing
lukeolson Nov 9, 2018
bc0a97a
add bindings for sparse
lukeolson Nov 9, 2018
7bc9392
fix complex test, change imports
lukeolson Oct 27, 2023
3042321
fix complex test, change imports
lukeolson Mar 22, 2022
6ea1fb1
add potential mac omp flag test
lukeolson Mar 22, 2022
9fec26a
update mac flags
lukeolson Mar 22, 2022
2db5579
hacking on omp
lukeolson Mar 22, 2022
e8475ea
update omp build
lukeolson Mar 22, 2022
53874ba
add test script
lukeolson Feb 4, 2019
e289f23
fix omp_str
lukeolson Mar 22, 2022
cade40c
add omp flag to solve
lukeolson Oct 27, 2023
779ba06
add omp flag to solve
lukeolson Feb 5, 2019
4a8254d
add test script
lukeolson Feb 5, 2019
18457c0
trying automatic paths
lukeolson Mar 22, 2022
26272e3
add omp info
lukeolson Oct 27, 2023
efa6612
add omp info
lukeolson Feb 9, 2019
194239a
update bind; add omp test
lukeolson Oct 27, 2023
e5365c4
start custom install
lukeolson Mar 22, 2022
a16954f
add sparse to setup
lukeolson Mar 22, 2022
7ee964b
build: update setup
lukeolson Mar 22, 2022
cc12a5e
update setup; fix test/trys in scripts
lukeolson Mar 23, 2022
a864f3f
update build script and instantiation
lukeolson Oct 27, 2023
55cce06
fix scipy >=1.8 import
lukeolson Mar 23, 2022
40c9166
lint: fix comma in imports
lukeolson Oct 27, 2023
c46d26e
lint: remove unneeded imports. fix docstrings
lukeolson Mar 23, 2022
60f1615
lint: add docstrings to tests
lukeolson Mar 23, 2022
56c048b
lint: fix/cleanup import levels
lukeolson Mar 23, 2022
728fc3b
ci: testing ci config
lukeolson Oct 27, 2023
2435c06
updating OMP discussion in two files
Mar 23, 2022
7771983
build: add exension-helpers
lukeolson Oct 27, 2023
54d560c
simplify omp setup
lukeolson Oct 27, 2023
d355c8a
add git pointer for dep
lukeolson Apr 13, 2022
b495766
fix conflict
lukeolson Oct 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

- name: Install
run: pip install -e .
run: pip install -v -e .

- name: Test
run: |
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,35 @@ It is possible to list all of the versions of `pyamg` available on your platform
```
conda search pyamg --channel conda-forge
```

# OpenMP

PyAMG handles OpenMP in the following way

- The `has_flag()` function of `pybind11` is called, with either `-fopenmp` (Linux) or `-Xpreprocessor -fopenmp` (MacOS). Then added to the build if present.

- Every instance of OpenMP is limited to `#pragma` or `#ifdef _OPENMP`. Each kernel in `amg_core` that has OpenMP should be buildable without.

- To test, try `export OMP_NUM_THREADS=4; python test_omp.py` in `scripts/`

- The AMG solve phase add threading by rewriting the sparse matrix-vector multiplications of `A`, `P`, and `R`, with `ml.solve(...., openmp=True)`.

#### MacOS
- To enable OpenMP on macOS, `brew install libomp`

- Then set environment variables, following https://scikit-learn.org/dev/developers/advanced_installation.html#macos-compilers-from-homebrew :
```
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
export CPPFLAGS="$CPPFLAGS -Xpreprocessor -fopenmp"
export CFLAGS="$CFLAGS -I$(brew --prefix libomp)/include"
export CXXFLAGS="$CXXFLAGS -I$(brew --prefix libomp)/include"
export LDFLAGS="$LDFLAGS -Wl,-rpath,$(brew --prefix libomp)/lib -L$(brew --prefix libomp)/lib -lomp"
```

- Then `setup.py` will attempt to add `-Xpreprocessor -fopenmp` to the compiler and `-lomp` to the linker.

#### Tips
- The build directory may need to be removed, in order force PyAMG to re-build an OMP-enabled version


4 changes: 4 additions & 0 deletions pyamg/amg_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
from .air import (one_point_interpolation, approx_ideal_restriction_pass1,
approx_ideal_restriction_pass2, block_approx_ideal_restriction_pass2)

from .sparse import csr_matvec, omp_info

__all__ = [
'apply_absolute_distance_filter',
'apply_distance_filter',
Expand Down Expand Up @@ -112,4 +114,6 @@
'approx_ideal_restriction_pass2',
'block_approx_ideal_restriction_pass2'
#
'csr_matvec',
'omp_info'
]
1 change: 1 addition & 0 deletions pyamg/amg_core/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
./bindthem.py relaxation.h
./bindthem.py ruge_stuben.h
./bindthem.py smoothed_aggregation.h
./bindthem.py sparse.h
1 change: 1 addition & 0 deletions pyamg/amg_core/instantiate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ instantiate:
- rs_classical_interpolation_pass1
- cluster_node_incidence
- print_it
- omp_info

- types:
- [int, float]
Expand Down
76 changes: 76 additions & 0 deletions pyamg/amg_core/sparse.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <complex>
#include <iostream>
#include <stdio.h>

#ifdef _OPENMP
# include <omp.h>
#endif

//
// Threaded SpMV
//
// y <- A * x
//
// Parameters
// ----------
// n_row, n_col : int
// dimensions of the n_row x n_col matrix A
// Ap, Aj, Ax : array
// CSR pointer, index, and data vectors for matrix A
// Xx : array
// input vector
// Yy : array
// output vector (modified in-place)
//
// See Also
// --------
// csr_matvec
//
// Notes
// -----
// Requires GCC 4.9 for ivdep
// Requires a compiler with OMP
//
template <class I, class T>
void csr_matvec(const I n_row,
const I n_col,
const I Ap[], const int Ap_size,
const I Aj[], const int Aj_size,
const T Ax[], const int Ax_size,
const T Xx[], const int Xx_size,
T Yx[], const int Yx_size)
{
I i, jj;
T sum;

#pragma omp parallel for default(shared) schedule(static) private(i, sum, jj)
for(i = 0; i < n_row; i++){
sum = Yx[i];
#pragma GCC ivdep
for(jj = Ap[i]; jj < Ap[i+1]; jj++){
sum += Ax[jj] * Xx[Aj[jj]];
}
Yx[i] = sum;
}
}


//
// OMP analytics
//
template <class I>
void omp_info(const I m)
{
I nthreads, tid;

#pragma omp parallel private(nthreads, tid)
{
#ifdef _OPENMP
tid = omp_get_thread_num();
nthreads = omp_get_num_threads();
printf("Thread %d of %d total threads\n", tid, nthreads);
#else
printf("OpenMP not available.\n");
#endif
}
}
106 changes: 106 additions & 0 deletions pyamg/amg_core/sparse_bind.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// DO NOT EDIT: this file is generated

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/complex.h>

#include "sparse.h"

namespace py = pybind11;

template <class I, class T>
void _csr_matvec(
const I n_row,
const I n_col,
py::array_t<I> & Ap,
py::array_t<I> & Aj,
py::array_t<T> & Ax,
py::array_t<T> & Xx,
py::array_t<T> & Yx
)
{
auto py_Ap = Ap.unchecked();
auto py_Aj = Aj.unchecked();
auto py_Ax = Ax.unchecked();
auto py_Xx = Xx.unchecked();
auto py_Yx = Yx.mutable_unchecked();
const I *_Ap = py_Ap.data();
const I *_Aj = py_Aj.data();
const T *_Ax = py_Ax.data();
const T *_Xx = py_Xx.data();
T *_Yx = py_Yx.mutable_data();

return csr_matvec <I, T>(
n_row,
n_col,
_Ap, Ap.shape(0),
_Aj, Aj.shape(0),
_Ax, Ax.shape(0),
_Xx, Xx.shape(0),
_Yx, Yx.shape(0)
);
}

template <class I>
void _omp_info(
const I m
)
{
return omp_info <I>(
m
);
}

PYBIND11_MODULE(sparse, m) {
m.doc() = R"pbdoc(
Pybind11 bindings for sparse.h

Methods
-------
csr_matvec
omp_info
)pbdoc";

py::options options;
options.disable_function_signatures();

m.def("csr_matvec", &_csr_matvec<int, float>,
py::arg("n_row"), py::arg("n_col"), py::arg("Ap").noconvert(), py::arg("Aj").noconvert(), py::arg("Ax").noconvert(), py::arg("Xx").noconvert(), py::arg("Yx").noconvert());
m.def("csr_matvec", &_csr_matvec<int, double>,
py::arg("n_row"), py::arg("n_col"), py::arg("Ap").noconvert(), py::arg("Aj").noconvert(), py::arg("Ax").noconvert(), py::arg("Xx").noconvert(), py::arg("Yx").noconvert());
m.def("csr_matvec", &_csr_matvec<int, std::complex<float>>,
py::arg("n_row"), py::arg("n_col"), py::arg("Ap").noconvert(), py::arg("Aj").noconvert(), py::arg("Ax").noconvert(), py::arg("Xx").noconvert(), py::arg("Yx").noconvert());
m.def("csr_matvec", &_csr_matvec<int, std::complex<double>>,
py::arg("n_row"), py::arg("n_col"), py::arg("Ap").noconvert(), py::arg("Aj").noconvert(), py::arg("Ax").noconvert(), py::arg("Xx").noconvert(), py::arg("Yx").noconvert(),
R"pbdoc(
Threaded SpMV

y <- A * x

Parameters
----------
n_row, n_col : int
dimensions of the n_row x n_col matrix A
Ap, Aj, Ax : array
CSR pointer, index, and data vectors for matrix A
Xx : array
input vector
Yy : array
output vector (modified in-place)

See Also
--------
csr_matvec

Notes
-----
Requires GCC 4.9 for ivdep
Requires a compiler with OMP)pbdoc");

m.def("omp_info", &_omp_info<int>,
py::arg("m"),
R"pbdoc(
OMP analytics)pbdoc");

}

22 changes: 20 additions & 2 deletions pyamg/multilevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .util.utils import to_type
from .util.params import set_tol
from .relaxation import smoothing
from .util import upcast
from .util import upcast, sparse


class MultilevelSolver:
Expand Down Expand Up @@ -354,8 +354,23 @@ def matvec(b):

return LinearOperator(shape, matvec, dtype=dtype)

def _enable_omp(self):
"""Enable OpenMP (if available) by calling pyamg.amg_core.sparse.csr_matvec.

See Also
--------
scipy.sparse.csr.csr_matvec, pyamg.amg_core.sparse.csr_matvec, pyamg.util.sparse.csr
"""
for l in self.levels:
l.A = sparse.csr(l.A)
if hasattr(l, 'P'):
l.P = sparse.csr(l.P)
if hasattr(l, 'R'):
l.R = sparse.csr(l.R)

def solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None,
callback=None, residuals=None, cycles_per_level=1, return_info=False):
callback=None, residuals=None, cycles_per_level=1, return_info=False,
openmp=False):
"""Execute multigrid cycling.

Parameters
Expand Down Expand Up @@ -421,6 +436,9 @@ def solve(self, b, x0=None, tol=1e-5, maxiter=100, cycle='V', accel=None,
>>> x = ml.solve(b, tol=1e-12, residuals=residuals) # standalone solver

"""
if openmp:
self._enable_omp()

if x0 is None:
x = np.zeros_like(b)
else:
Expand Down
3 changes: 2 additions & 1 deletion pyamg/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from . import linalg
from . import utils
from . import params
from . import sparse

from .utils import make_system, upcast

__all__ = ['linalg', 'utils', 'params', 'make_system', 'upcast']
__all__ = ['linalg', 'utils', 'params', 'make_system', 'upcast', 'sparse']

__doc__ += """
linalg.py provides some linear algebra functionality not yet found in scipy.
Expand Down
39 changes: 39 additions & 0 deletions pyamg/util/sparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Sparse matrix interface to internal sparse matrix operations."""
import numpy as np
from scipy.sparse import csr_matrix

try:
# scipy >=1.8
from scipy.sparse._sputils import upcast_char
except ImportError:
# scipy <1.8
from scipy.sparse.sputils import upcast_char

from .. import amg_core


class csr(csr_matrix): # noqa: N801
"""CSR class to redefine operations.

The purpose of this class is to redefine the matvec in scipy.sparse
"""

def _mul_vector(self, other):
"""Matrix-vector multiplication.

Identical to scipy.sparse with an in internal call to
pyamg.amg_core.sparse.csr_matvec
"""
M, N = self.shape

# output array
result = np.zeros(M, dtype=upcast_char(self.dtype.char,
other.dtype.char))

amg_core.csr_matvec(M, N, self.indptr, self.indices, self.data,
other, result)

return result


csr.__doc__ += csr_matrix.__doc__
41 changes: 41 additions & 0 deletions pyamg/util/tests/test_sparse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Test sparse matrix operations."""
import numpy as np
import pyamg.gallery
import pyamg.util
import scipy.sparse

from numpy.testing import TestCase, assert_array_almost_equal


class TestScipy(TestCase):
"""Test sparse matrix operations against scipy."""

def test_matvec(self):

# initialize a seed
np.random.seed(678)

# real
A = np.array([[100.0, 0, 0], [0, 101, 0], [0, 0, 99]])
A = scipy.sparse.csr_matrix(A)
A2 = pyamg.util.sparse.csr(A)
u = np.random.rand(A.shape[0])

assert_array_almost_equal(A * u, A2 * u)

# complex
A = np.array([[100+1.0j, 0, 0],
[0, 101-1.0j, 0],
[0, 0, 99+9.9j]])
A = scipy.sparse.csr_matrix(A)
A2 = pyamg.util.sparse.csr(A)
u = np.random.rand(A.shape[0]) + 1j * np.random.rand(A.shape[0])

assert_array_almost_equal(A * u, A2 * u)

# random
A = pyamg.gallery.sprand(20, 20, 6 / 20.0, format='csr')
A2 = pyamg.util.sparse.csr(A)
u = np.random.rand(A.shape[0])

assert_array_almost_equal(A * u, A2 * u)