Skip to content

Commit 2d3505f

Browse files
committed
Add parallelism using OpenMP
Closes #10
1 parent da65c40 commit 2d3505f

File tree

6 files changed

+130
-85
lines changed

6 files changed

+130
-85
lines changed

.travis.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
notifications:
22
email: false
33

4+
branches:
5+
only:
6+
- trunk
7+
48
install:
59
- pip3 install .
610
script:
@@ -25,7 +29,7 @@ jobs:
2529
- ubuntu-toolchain-r-test
2630
packages:
2731
- g++-6
28-
before_install: pip3 install pytest-cov codecov
32+
before_install: pip3 install pytest-cov
2933
before_script: python3 setup.py clean --all build_ext --force --inplace
3034
after_script:
3135
- sudo chmod +x cppci.sh
@@ -56,6 +60,12 @@ jobs:
5660
os: osx
5761
osx_image: xcode11.2
5862
language: shell
63+
before_install:
64+
- brew pin gdal; brew pin postgis; brew pin cairo; brew pin glib; brew pin gnutls; brew pin p11-kit; brew pin gnupg; brew pin poppler;
65+
- brew install llvm
66+
env:
67+
- CC="/usr/local/opt/llvm/bin/clang -L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
68+
- CXX="/usr/local/opt/llvm/bin/clang++ -L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
5969
- stage: lint
6070
language: python
6171
install: pip install flake8

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Geodesic Library
44

55
.. image:: https://travis-ci.com/the-virtual-brain/tvb-gdist.svg?branch=trunk
66
:target: https://travis-ci.com/the-virtual-brain/tvb-gdist
7+
.. image:: https://codecov.io/gh/the-virtual-brain/tvb-gdist/branch/trunk/graph/badge.svg
8+
:target: https://codecov.io/gh/the-virtual-brain/tvb-gdist
79

810
The **tvb-gdist** module is a Cython interface to a C++ library
911
(https://code.google.com/archive/p/geodesic/) for computing
@@ -182,3 +184,13 @@ Notes
182184

183185
* In order for the algorithm to work the mesh must not be numbered incorrectly
184186
or disconnected or of somehow degenerate.
187+
188+
* The c++ compiler must have OpenMP installed. This is generally not an issue
189+
on g++ or Microsoft Visual Studio. However, in macOS one may need to install
190+
llvm from brew in order to use OpenMP: ``brew install llvm``. Then, change the
191+
``CC`` and ``CXX`` environment variables to the following:
192+
193+
.. code-block:: txt
194+
195+
CC="/usr/local/opt/llvm/bin/clang -L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
196+
CXX="/usr/local/opt/llvm/bin/clang++ -L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"

gdist.pyx

Lines changed: 23 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -61,61 +61,19 @@ from libcpp.vector cimport vector
6161
################################################################################
6262
############ Defininitions to access the C++ geodesic distance library #########
6363
################################################################################
64-
cdef extern from "geodesic_mesh_elements.h" namespace "geodesic":
65-
cdef cppclass Vertex:
66-
Vertex()
67-
68-
cdef extern from "geodesic_mesh_elements.h" namespace "geodesic":
69-
cdef cppclass SurfacePoint:
70-
SurfacePoint()
71-
SurfacePoint(Vertex*)
72-
double& x()
73-
double& y()
74-
double& z()
75-
76-
cdef extern from "geodesic_mesh.h" namespace "geodesic":
77-
cdef cppclass Mesh:
78-
Mesh()
79-
void initialize_mesh_data(vector[double]&, vector[unsigned]&, bool)
80-
vector[Vertex]& vertices()
81-
8264
cdef extern from "geodesic_utils.h":
65+
cdef cppclass SparseMatrix:
66+
vector[unsigned] rows
67+
vector[unsigned] columns
68+
vector[double] data
8369
vector[double] compute_gdist_impl(vector[double], vector[unsigned], vector[unsigned], vector[unsigned], double, bool, bool)
84-
85-
cdef extern from "geodesic_algorithm_exact.h" namespace "geodesic":
86-
cdef cppclass GeodesicAlgorithmExact:
87-
GeodesicAlgorithmExact(Mesh*)
88-
void propagate(vector[SurfacePoint]&, double, vector[SurfacePoint]*)
89-
unsigned best_source(SurfacePoint&, double&)
70+
SparseMatrix local_gdist_matrix_impl(vector[double], vector[unsigned], double, bool)
9071

9172
cdef extern from "geodesic_constants_and_simple_functions.h" namespace "geodesic":
9273
double GEODESIC_INF
9374
################################################################################
9475

9576

96-
cdef get_mesh(
97-
numpy.ndarray[numpy.float64_t, ndim=2] vertices,
98-
numpy.ndarray[numpy.int32_t, ndim=2] triangles,
99-
Mesh &amesh,
100-
bool is_one_indexed
101-
):
102-
# Define C++ vectors to contain the mesh surface components.
103-
cdef vector[double] points
104-
cdef vector[unsigned] faces
105-
106-
# Map numpy array of mesh "vertices" to C++ vector of mesh "points"
107-
cdef numpy.float64_t coord
108-
for coord in vertices.flatten():
109-
points.push_back(coord)
110-
111-
# Map numpy array of mesh "triangles" to C++ vector of mesh "faces"
112-
cdef numpy.int32_t indx
113-
for indx in triangles.flatten():
114-
faces.push_back(indx)
115-
116-
amesh.initialize_mesh_data(points, faces, is_one_indexed)
117-
118-
11977
def compute_gdist(numpy.ndarray[numpy.float64_t, ndim=2] vertices,
12078
numpy.ndarray[numpy.int32_t, ndim=2] triangles,
12179
numpy.ndarray[numpy.int32_t, ndim=1] source_indices=None,
@@ -245,43 +203,26 @@ def local_gdist_matrix(numpy.ndarray[numpy.float64_t, ndim=2] vertices,
245203
from the propgate step...
246204
"""
247205

248-
cdef Mesh amesh
249-
get_mesh(vertices, triangles, amesh, is_one_indexed)
206+
cdef Py_ssize_t N = vertices.shape[0]
250207

251-
# Define and create object for exact algorithm on that mesh:
252-
cdef GeodesicAlgorithmExact *algorithm = new GeodesicAlgorithmExact(&amesh)
208+
cdef vector[double] points
209+
cdef vector[unsigned] faces
253210

254-
cdef vector[SurfacePoint] source, targets
255-
cdef Py_ssize_t N = vertices.shape[0]
256-
cdef Py_ssize_t k
257-
cdef Py_ssize_t kk
258-
cdef numpy.float64_t distance = 0
259-
260-
# Add all vertices as targets
261-
for k in range(N):
262-
targets.push_back(SurfacePoint(&amesh.vertices()[k]))
263-
264-
rows = []
265-
columns = []
266-
data = []
267-
for k in range(N):
268-
source.push_back(SurfacePoint(&amesh.vertices()[k]))
269-
algorithm.propagate(source, max_distance, NULL)
270-
source.pop_back()
271-
272-
for kk in range(N): # TODO: Reduce to vertices reached during propagate.
273-
algorithm.best_source(targets[kk], distance)
274-
275-
if (
276-
distance is not GEODESIC_INF
277-
and distance is not 0
278-
and distance <= max_distance
279-
):
280-
rows.append(k)
281-
columns.append(kk)
282-
data.append(distance)
283-
284-
return scipy.sparse.csc_matrix((data, (rows, columns)), shape=(N, N))
211+
for k in vertices.flatten():
212+
points.push_back(k)
213+
for k in triangles.flatten():
214+
faces.push_back(k)
215+
216+
cdef SparseMatrix distances = local_gdist_matrix_impl(
217+
points,
218+
faces,
219+
max_distance,
220+
is_one_indexed,
221+
)
222+
223+
return scipy.sparse.csc_matrix(
224+
(distances.data, (distances.rows, distances.columns)), shape=(N, N)
225+
)
285226

286227

287228
def distance_matrix_of_selected_points(

geodesic_library/geodesic_utils.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
#include <vector>
2+
#include <omp.h>
23

34
#include "geodesic_algorithm_exact.h"
45

6+
class SparseMatrix {
7+
public:
8+
std::vector<unsigned> rows, columns;
9+
std::vector<double> data;
10+
};
11+
12+
513
std::vector<double> compute_gdist_impl(
614
std::vector<double> points,
715
std::vector<unsigned> faces,
@@ -35,3 +43,48 @@ std::vector<double> compute_gdist_impl(
3543
}
3644
return distances;
3745
}
46+
47+
SparseMatrix local_gdist_matrix_impl(
48+
std::vector<double> points,
49+
std::vector<unsigned> faces,
50+
double max_distance,
51+
bool is_one_indexed
52+
) {
53+
geodesic::Mesh mesh;
54+
mesh.initialize_mesh_data(points, faces, is_one_indexed=is_one_indexed); // create internal mesh data structure including edges
55+
56+
SparseMatrix distances;
57+
58+
std::vector<unsigned> rows, columns;
59+
std::vector<double> data;
60+
#pragma omp parallel
61+
{
62+
geodesic::GeodesicAlgorithmExact algorithm(&mesh);
63+
std::vector<unsigned> rows_private, columns_private;
64+
std::vector<double> data_private;
65+
#pragma omp for nowait
66+
for (int i = 0; i < (int)mesh.vertices().size(); ++i) {
67+
std::vector<geodesic::SurfacePoint> sources {&mesh.vertices()[i]};
68+
algorithm.propagate(sources, geodesic::GEODESIC_INF, NULL); // cover the whole mesh
69+
for(int j = 0; j < (int)mesh.vertices().size(); ++j) {
70+
geodesic::SurfacePoint p(&mesh.vertices()[j]);
71+
72+
double distance;
73+
unsigned best_source = algorithm.best_source(p, distance); // for a given surface point, find closest source and distance to this source
74+
75+
if (distance != 0 && distance <= geodesic::GEODESIC_INF && distance <= max_distance) {
76+
rows_private.push_back(i);
77+
columns_private.push_back(j);
78+
data_private.push_back(distance);
79+
}
80+
}
81+
}
82+
rows.insert(rows.end(), rows_private.begin(), rows_private.end());
83+
columns.insert(columns.end(), columns_private.begin(), columns_private.end());
84+
data.insert(data.end(), data_private.begin(), data_private.end());
85+
}
86+
distances.rows = rows;
87+
distances.columns = columns;
88+
distances.data = data;
89+
return distances;
90+
}

setup.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"""
4242

4343
import os
44+
import sys
4445
import setuptools
4546

4647
import numpy
@@ -66,9 +67,16 @@
6667
name=GEODESIC_NAME, # Name of extension
6768
sources=["gdist.pyx"], # Filename of Cython source
6869
language="c++", # Cython create C++ source
70+
# Disable assertions; one is failing geodesic_mesh.h:405
6971
define_macros=define_macros,
70-
extra_compile_args=["--std=c++14"],
71-
extra_link_args=["--std=c++14"],
72+
extra_compile_args=[
73+
'--std=c++14',
74+
'/openmp' if sys.platform == 'win32' else '-fopenmp',
75+
],
76+
extra_link_args=[
77+
'--std=c++14',
78+
'/openmp' if sys.platform == 'win32' else '-fopenmp',
79+
],
7280
include_dirs=[numpy.get_include(), "geodesic_library"],
7381
)
7482
]

tests/test_geodesic_utils.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55

66
#include "../geodesic_library/geodesic_utils.h"
77

8+
std::vector<std::vector<double>> sparse_to_matrix(SparseMatrix spareseMatrix, unsigned size) {
9+
std::vector<std::vector<double>> matrix(size, std::vector<double>(size));
10+
for (unsigned i = 0; i < spareseMatrix.rows.size(); ++i) {
11+
matrix[spareseMatrix.rows[i]][spareseMatrix.columns[i]] = spareseMatrix.data[i];
12+
}
13+
return matrix;
14+
}
815

916
TEST(compute_gdist_impl, flat_traingular_mesh_test) {
1017
std::vector<double> points;
@@ -27,6 +34,20 @@ TEST(compute_gdist_impl, flat_traingular_mesh_test) {
2734
}
2835
}
2936

37+
TEST(local_gdist_matrix_impl, flat_triangular_mesh_test) {
38+
std::vector<double> points;
39+
std::vector<unsigned> faces;
40+
geodesic::read_mesh_from_file("../data/flat_triangular_mesh.txt", points, faces);
41+
SparseMatrix gdist_matrix = local_gdist_matrix_impl(
42+
points,
43+
faces,
44+
geodesic::GEODESIC_INF,
45+
false
46+
);
47+
std::vector<std::vector<double>> matrix = sparse_to_matrix(gdist_matrix, points.size() / 3);
48+
EXPECT_NEAR(matrix[1][0], 0.2, 1e-6);
49+
}
50+
3051
int main(int argc, char **argv) {
3152
testing::InitGoogleTest(&argc, argv);
3253
return RUN_ALL_TESTS();

0 commit comments

Comments
 (0)