Skip to content

Commit 514c1d6

Browse files
committed
Fixed and added example for SARE python bindings
1 parent c377e2d commit 514c1d6

6 files changed

Lines changed: 207 additions & 43 deletions

File tree

pyalp/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "alp-graphblas"
7-
version = "0.8.41"
7+
version = "0.8.42"
88
description = "Python bindings for ALP GraphBLAS (minimal package layout)"
99
authors = [ { name = "ALP" } ]
1010
readme = "README.md"

pyalp/src/matrix_wrappers.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void buildMatrix(
4141

4242
grb::RC io_rc;
4343
(void)io_rc;
44-
io_rc = grb::buildMatrixUnique( M, data_ptr_i, data_ptr_j , data_ptr_v, nnz, grb::SEQUENTIAL );
44+
io_rc = grb::buildMatrixUnique( M, data_ptr_i, data_ptr_i + nnz, data_ptr_j, data_ptr_j + nnz, data_ptr_v, data_ptr_v + nnz, grb::SEQUENTIAL );
4545
assert( io_rc == grb::SUCCESS );
4646
}
4747

@@ -57,7 +57,7 @@ grb::Matrix<ScalarType> matrix_factory(
5757

5858
// Helper for dispatch
5959
bool handled = false;
60-
auto try_type = [&](auto dummy) {
60+
auto try_type = [&](const auto dummy) {
6161
using IntType = decltype(dummy);
6262
if (py::dtype::of<IntType>().is(data1.dtype()) && py::dtype::of<IntType>().is(data2.dtype())) {
6363
buildMatrix<IntType, ScalarType>(

pyalp/src/pyalp/common_bindings.hpp

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <pybind11/pybind11.h>
55
#include <pybind11/numpy.h>
6+
#include <pybind11/stl.h>
67

78
#include <graphblas.hpp>
89

@@ -14,6 +15,7 @@
1415

1516
namespace py = pybind11;
1617

18+
1719
// Register all pyalp bindings. Module-local registration can be enabled by
1820
// instantiating with ModuleLocal = true. When ModuleLocal==true the
1921
// py::module_local() policy is applied to class bindings to avoid symbol
@@ -24,7 +26,7 @@ void register_pyalp(py::module_ &m) {
2426
// Common bindings for all backends
2527
m.def("backend_name", [](){ return "backend"; });
2628

27-
if constexpr (ModuleLocal) {
29+
if (ModuleLocal) {
2830
py::class_<grb::Matrix< ScalarType >>(m, "Matrix", py::module_local())
2931
.def(py::init([](size_t m_, size_t n_,
3032
py::array data1,
@@ -51,7 +53,20 @@ void register_pyalp(py::module_ &m) {
5153
py::arg("m"),
5254
py::arg("k_array")
5355
)
54-
.def("to_numpy", &to_numpy, "Convert to numpy array");
56+
.def("to_numpy", &to_numpy< ScalarType >, "Convert to numpy array");
57+
58+
py::class_<grb::Vector< StateType >>(m, "State", py::module_local())
59+
.def(py::init<size_t>())
60+
.def(py::init([](size_t m,
61+
py::array_t<StateType> data3) {
62+
grb::Vector< StateType > vec(m); // call the basic constructor
63+
buildVector(vec, data3); // initialize with data
64+
return vec;
65+
}),
66+
py::arg("m"),
67+
py::arg("k_array")
68+
)
69+
.def("to_numpy", &to_numpy< StateType >, "Convert to numpy array");
5570
} else {
5671
py::class_<grb::Matrix< ScalarType >>(m, "Matrix")
5772
.def(py::init([](size_t m_, size_t n_,
@@ -77,10 +92,54 @@ void register_pyalp(py::module_ &m) {
7792
py::arg("m"),
7893
py::arg("k_array")
7994
)
80-
.def("to_numpy", &to_numpy, "Convert to numpy array");
95+
.def("to_numpy", &to_numpy< ScalarType >, "Convert to numpy array");
96+
97+
py::class_<grb::Vector< StateType >>(m, "State")
98+
.def(py::init<size_t>())
99+
.def(py::init([](size_t m,
100+
py::array_t<StateType> data3) {
101+
grb::Vector< StateType > vec(m); // call the basic constructor
102+
buildVector(vec, data3); // initialize with data
103+
return vec;
104+
}),
105+
py::arg("m"),
106+
py::arg("k_array")
107+
)
108+
.def("to_numpy", &to_numpy< StateType >, "Convert to numpy array");
109+
81110
}
111+
112+
py::class_< std::vector<grb::Vector<StateType>> >(m, "stdVectorStates")
113+
.def(py::init<size_t>())
114+
.def(py::init([](
115+
size_t m,
116+
py::array_t< StateType > arr ) {
117+
(void) m;
82118

83-
m.def("buildVector", &buildVector, "Fill Vector from 1 NumPy array");
119+
const py::buffer_info buf = arr.request();
120+
121+
if (buf.ndim != 2) {
122+
throw std::runtime_error("Input array must be 2-dimensional");
123+
}
124+
const size_t sz = buf.shape[0];
125+
std::vector< grb::Vector<StateType> > vec (sz);
126+
assert( static_cast<size_t>( buf.shape[1] ) <= m );
127+
auto ptr = static_cast<StateType*>(buf.ptr);
128+
129+
grb::RC io_rc = grb::SUCCESS;
130+
for (size_t i = 0; i < sz; i++) {
131+
io_rc = io_rc ? io_rc :
132+
grb::buildVector( vec[i], ptr, ptr + buf.shape[1], grb::SEQUENTIAL );
133+
}
134+
return vec;
135+
}),
136+
py::arg("m"),
137+
py::arg("k_array")
138+
);
139+
// .def("get_vec", & );
140+
141+
m.def("buildVector", &buildVector<ScalarType>, "Fill Vector from 1 NumPy array");
142+
m.def("buildVectorInt8", &buildVector<StateType>, "Fill Vector from 1 NumPy array");
84143
m.def("print_my_numpy_array", &print_my_numpy_array, "Print a numpy array as a flattened std::vector");
85144
m.def("conjugate_gradient", &conjugate_gradient, "Pass alp data to alp CG solver",
86145
py::arg("L"),
@@ -114,4 +173,5 @@ void register_pyalp(py::module_ &m) {
114173
py::arg("seed") = 0,
115174
py::arg("verbose") = 0
116175
);
176+
117177
}

pyalp/src/simulated_annealing_replica_exchange.hpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ ScalarType SARE_QUBO(
3333
) {
3434
ScalarType best_energy = std::numeric_limits<ScalarType>::max();
3535

36-
if( !verbose )
36+
if( !verbose ){
3737
std::cout << "SARE_QUBO: start \n";
38+
}
3839
// get user process ID
3940
const size_t s = grb::spmd<>::pid();
4041
(void)s;
@@ -75,10 +76,10 @@ ScalarType SARE_QUBO(
7576

7677
if( !verbose ) {
7778
// output
78-
// std::cout << " solver_iterations = " << solver_iterations << "\n";
79-
// std::cout << " tol = " << tol << "\n";
80-
// std::cout << " iterations = " << iterations << "\n";
81-
// std::cout << " residual = " << residual << "\n";
79+
std::cout << " seed = " << seed << "\n";
80+
std::cout << " #iterations = " << solver_iterations << "\n";
81+
std::cout << " best_energy = " << best_energy << "\n";
82+
std::cout << " solver time = " << single_time << " s \n";
8283
}
8384

8485
if( !verbose ) {
@@ -99,7 +100,7 @@ ScalarType SARE_Ising(
99100
const size_t seed = 0,
100101
size_t verbose = 0
101102
) {
102-
ScalarType best_energy = std::numeric_limits<ScalarType>::max();
103+
ScalarType best_energy = 0;
103104

104105
if( !verbose )
105106
std::cout << "SARE_Ising: start \n";
@@ -144,10 +145,10 @@ ScalarType SARE_Ising(
144145

145146
if( !verbose ) {
146147
// output
147-
// std::cout << " solver_iterations = " << solver_iterations << "\n";
148-
// std::cout << " tol = " << tol << "\n";
149-
// std::cout << " iterations = " << iterations << "\n";
150-
// std::cout << " residual = " << residual << "\n";
148+
std::cout << " seed = " << seed << "\n";
149+
std::cout << " #iterations = " << solver_iterations << "\n";
150+
std::cout << " best_energy = " << best_energy << "\n";
151+
std::cout << " solver time = " << single_time << " s \n";
151152
}
152153

153154
if( !verbose ) {

pyalp/src/vector_wrappers.hpp

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,46 +20,39 @@
2020
namespace py = pybind11;
2121

2222

23-
using BaseScalarType = double;
24-
#ifdef _CG_COMPLEX
25-
using ScalarType = std::complex< BaseScalarType >;
26-
#else
27-
using ScalarType = BaseScalarType;
28-
#endif
29-
30-
void buildVector(grb::Vector< ScalarType >& V, py::array_t<ScalarType> arrv) {
23+
template< typename T >
24+
void buildVector(grb::Vector< T >& V, py::array_t<T> arrv) {
3125

3226
// Check array is 1D
3327
py::buffer_info info_v = arrv.request();
3428
if (info_v.ndim != 1) throw std::runtime_error("Array must be 1D");
35-
ScalarType* data_ptr_v = static_cast<ScalarType*>(info_v.ptr);
29+
T* data_ptr_v = static_cast<T*>(info_v.ptr);
3630

3731
grb::RC io_rc;
3832
(void)io_rc;
3933
io_rc = grb::buildVector( V, data_ptr_v, data_ptr_v + info_v.size, grb::SEQUENTIAL );
4034
assert( io_rc == grb::SUCCESS );
4135
}
4236

43-
py::array_t<ScalarType>
44-
to_numpy(grb::Vector< ScalarType >& x) {
45-
grb::PinnedVector< ScalarType > pinnedVector;
46-
pinnedVector = grb::PinnedVector< ScalarType >( x, grb::SEQUENTIAL );
37+
template< typename T >
38+
py::array_t< T > to_numpy( const grb::Vector< T >& x ) {
39+
grb::PinnedVector< T > pinnedVector;
40+
pinnedVector = grb::PinnedVector< T >( x, grb::SEQUENTIAL );
41+
const size_t sz = pinnedVector.size();
4742

4843
std::cout << "create numpy array from grb::vector\n";
4944

50-
ScalarType* data = new ScalarType[grb::size(x)];
51-
for( size_t k = 0; k < grb::size(x); ++k ) {
52-
const auto &value = pinnedVector.getNonzeroValue( k );
53-
data[k]=value;
54-
}
55-
56-
// Capsule to manage memory (will delete[] when array is destroyed in Python)
57-
py::capsule free_when_done(data, [](void *f) {
58-
delete[] reinterpret_cast<ScalarType*>(f);
59-
});
45+
auto result = py::array_t<T>(sz);
46+
py::buffer_info buf = result.request();
47+
T* ptr = static_cast< T* >( buf.ptr );
6048

61-
// Create NumPy array that shares memory with C++
62-
py::array_t<ScalarType> arr({grb::size(x)}, {sizeof(ScalarType)}, data, free_when_done);
63-
return arr;
49+
for( size_t k = 0; k < sz; ++k ) {
50+
ptr[ k ] = 0;
51+
}
52+
for( size_t k = 0; k < pinnedVector.nonzeroes(); ++k ) {
53+
const auto &value = pinnedVector.getNonzeroValue( k );
54+
ptr[pinnedVector.getNonzeroIndex( k )] = value;
55+
}
6456

57+
return result;
6558
}

tests/python/sare_test.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Test script for the pyalp_ref (GraphBLAS-like) Python module, specifically the simulated annealing Replica-Exchange (SARE) stuff.
3+
4+
This file should also work as a simple example of usage of such features.
5+
6+
Usage:
7+
python sare_test.py
8+
9+
Dependencies:
10+
- numpy
11+
- scipy.sparse
12+
- pyalp_ref (should be available in the Python path)
13+
"""
14+
15+
import numpy as np
16+
from scipy.sparse import dok_array
17+
from time import time
18+
19+
import pyalp
20+
# select backend
21+
backendname = "pyalp_omp" # or "pyalp_omp" "pyalp_nonblocking"
22+
pyalp = pyalp.get_backend(backendname)
23+
24+
# set here your parameters
25+
26+
n_replicas = 32
27+
seed = 0
28+
np.random.seed(seed)
29+
30+
# This is the data for the problem matrix
31+
# note that data types have been fixed at compile time!
32+
N = 5
33+
J = dok_array((N, N), dtype=np.float64)
34+
35+
J[0,1] = 2
36+
J[1,3] = 4
37+
J[2,3] = -1
38+
J[4,1] = 3
39+
J[4,1] = -2
40+
41+
# You can also import a matrix as follows:
42+
from scipy.io import mmread
43+
# J = mmread(matpath).astype(np.float64)
44+
45+
# read the local fields from a file
46+
# h = np.loadtxt(vecpath, dtype=np.float64)
47+
48+
# or get the vector from the diagonal of the matrix
49+
# if np.any(J.diagonal() != 0):
50+
# h = J.diagonal()
51+
# J.setdiag(0)
52+
N = J.shape[0]
53+
54+
# Make sure the matrix is symmetric with zero diagonal, as that is a precondition!
55+
J = (J + J.T)/2
56+
57+
# if diagonal is not zero we can call
58+
assert np.all(J.diagonal() == 0)
59+
60+
61+
# create coordinates and value arrays
62+
J = J.todok()
63+
idata,jdata = np.array([[x,y] for x,y in J.keys()], dtype=np.int32).T
64+
vdata = np.array( list(J.values()), dtype=np.float64 )
65+
idata, jdata, vdata = np.sort(idata), jdata[np.argsort(idata)], vdata[np.argsort(idata)]
66+
67+
# initial states
68+
states_numpy = np.random.randint(0,2, (n_replicas, N), dtype=np.int8 )
69+
70+
# Initialize temperatures
71+
betas_numpy = np.logspace( 1e-2, 1e+2, n_replicas, dtype=np.float64 )
72+
73+
# Initialize energies
74+
energies_numpy = np.diag(states_numpy@J@states_numpy.T)/2 + np.dot(states_numpy,h )
75+
76+
niterations = 100
77+
verbose = 0
78+
79+
#########################
80+
# Create the pyalp_ref Matrix and Vector objects
81+
J_alp = pyalp.Matrix( N, N, idata, jdata, vdata )
82+
h_alp = pyalp.Vector( N, h )
83+
states = [pyalp.State(N, state) for state in states_numpy ]
84+
betas = pyalp.Vector( n_replicas, betas_numpy )
85+
# energies will be initialized by the library
86+
energies = pyalp.Vector( n_replicas, energies_numpy )
87+
88+
# Preallocate best state
89+
best_state = pyalp.State( N, np.zeros(N, dtype=np.int8) )
90+
91+
t0 = time()
92+
93+
# For Ising solver
94+
best_energy = pyalp.SARE_Ising( J_alp, h_alp, states, energies, betas, best_state, niterations, seed, verbose )
95+
96+
# There is also a QUBO solver, with the only difference being that h is fixed 0
97+
# best_energy = pyalp.SARE_Ising( J_alp, states, energies, betas, best_state, niterations, seed, verbose )
98+
t1 = time()
99+
100+
best_state_numpy = best_state.to_numpy()
101+
102+
print( best_energy )
103+
print( best_state_numpy )
104+
print( f"Solver time is: {(t1-t0)*1000} ms" )
105+
106+
# check that energy is correct:
107+
assert best_state_numpy@(J@best_state_numpy/2 + h ) == best_energy
108+
109+
110+

0 commit comments

Comments
 (0)