| 1 | | {{{ |
| 2 | | #!comment |
| 3 | | https://vsl.cis.udel.edu/civl/demo.html |
| 4 | | }}} |
| | 1 | |
| | 2 | == Sparse Matrix-Vector Multiplication == |
| | 3 | |
| | 4 | File `csr.h`: |
| | 5 | {{{ |
| | 6 | #ifndef _CSR_H |
| | 7 | #define _CSR_H |
| | 8 | /* Compressed Sparse Row (CSR) sparse matrix representation. */ |
| | 9 | |
| | 10 | struct csr_matrix { |
| | 11 | double *val; // length NZ (number of non-0s) |
| | 12 | int *col_ind; // length NZ |
| | 13 | int *row_ptr; // length N+1. row_ptr[N]=NZ. |
| | 14 | int rows, cols; // number of rows (N), number of columns |
| | 15 | }; |
| | 16 | |
| | 17 | /* Multiplies matrix m and (dense) vector v, storing result in already |
| | 18 | allocated vector p. If m has dimensions NxM then v has length M |
| | 19 | and p has length N. */ |
| | 20 | void csr_matrix_vector_multiply(struct csr_matrix *m, double *v, double *p); |
| | 21 | |
| | 22 | #endif |
| | 23 | }}} |
| | 24 | |
| | 25 | File `csr.c`: |
| | 26 | {{{ |
| | 27 | /* Implementation of CSR matrix-vector multiplication from "LAProof: A |
| | 28 | Library of Formal Proofs of Accuracy and Correctness for Linear |
| | 29 | Algebra Programs" by Kellison, Appel, Tekriwal, and Bindel, IEEE |
| | 30 | 30th Symposium on Computer Arithmetic, 2023. That paper cites |
| | 31 | Barrett et al., "Templates for the Solution of Linear Systems: |
| | 32 | Building Blocks for Iterative Methods", SIAM, 1994, as the source |
| | 33 | of the algorithm. */ |
| | 34 | #include "csr.h" |
| | 35 | |
| | 36 | #define fma(x,y,s) (x*y+s) |
| | 37 | |
| | 38 | void csr_matrix_vector_multiply(struct csr_matrix *m, double *v, double *p) { |
| | 39 | int i, rows=m->rows; |
| | 40 | double *val = m->val; |
| | 41 | int *col_ind = m->col_ind; |
| | 42 | int *row_ptr = m->row_ptr; |
| | 43 | int next=row_ptr[0]; |
| | 44 | for (i=0; i<rows; i++) { |
| | 45 | double s=0.0; |
| | 46 | int h=next; |
| | 47 | next=row_ptr[i+1]; |
| | 48 | for (; h<next; h++) { |
| | 49 | double x = val[h]; |
| | 50 | int j = col_ind[h]; |
| | 51 | double y = v[j]; |
| | 52 | s = fma(x,y,s); |
| | 53 | } |
| | 54 | p[i]=s; |
| | 55 | } |
| | 56 | } |
| | 57 | }}} |
| | 58 | |
| | 59 | File `csr_driver.cvl`: |
| | 60 | {{{ |
| | 61 | /* Filename : csr.cvl |
| | 62 | Author : Stephen F. Siegel |
| | 63 | Created : 2023-09-03 |
| | 64 | Modified : 2023-09-04 |
| | 65 | |
| | 66 | Verification of matrix-vector multiplication routine using |
| | 67 | Compressed Sparse Row representation for the matrix. Start with an |
| | 68 | arbitrary CSR matrix with N and M under specified bounds. Take an |
| | 69 | arbitrary vector v of length M. Call sparse matrix-vector |
| | 70 | multiplication routine to compute "actual" resulting vector. |
| | 71 | Convert the sparse matrix to a dense matrix and compute dense |
| | 72 | matrix-vector product to get "expected" result. Check the actual |
| | 73 | and expected agree. |
| | 74 | */ |
| | 75 | #include <stdio.h> |
| | 76 | #include <stdlib.h> |
| | 77 | #include <pointer.cvh> |
| | 78 | #include "csr.h" |
| | 79 | |
| | 80 | // N,M=number of rows,columns. N_B,M_B=upper bounds on N,M |
| | 81 | $input int N_B = 3, M_B = 3, N, M; |
| | 82 | $assume (1<=N && N<=N_B && 1<=M && M<=M_B); |
| | 83 | // the non-0 entries for the matrix and the (dense) vector |
| | 84 | $input double A[N*M], V[M]; |
| | 85 | |
| | 86 | /* Fills in p[0],...,p[len-1] with a strictly increasing sequence |
| | 87 | of integers in [0,max]. Precondition: 0 <= len <= max+1 */ |
| | 88 | void strict_inc(int * p, int len, int max) { |
| | 89 | $assert(0<=len && len <= max+1); |
| | 90 | for (int i=0; i<len; i++) { |
| | 91 | int a = (i == 0 ? 0 : p[i-1]+1), b = max - len + i + 1; |
| | 92 | p[i] = a + $choose_int(b-a+1); // choose in a..b |
| | 93 | } |
| | 94 | } |
| | 95 | |
| | 96 | /* Nondeterministically create a CSR matrix with n rows and |
| | 97 | m columns */ |
| | 98 | struct csr_matrix make_csr(int n, int m) { |
| | 99 | int * row_ptr = malloc((n+1)*sizeof(int)); |
| | 100 | row_ptr[0] = 0; |
| | 101 | for (int i=1; i<=n; i++) |
| | 102 | row_ptr[i] = row_ptr[i-1]+$choose_int(m+1); |
| | 103 | int NZ = row_ptr[n]; |
| | 104 | $assert(0<=NZ && NZ<=n*m); |
| | 105 | int * col_ind = malloc(NZ*sizeof(int)); |
| | 106 | for (int i=0; i<n; i++) |
| | 107 | strict_inc(col_ind+row_ptr[i], row_ptr[i+1]-row_ptr[i], m-1); |
| | 108 | double * val = malloc(NZ*sizeof(double)); |
| | 109 | for (int i=0; i<NZ; i++) val[i] = A[i]; |
| | 110 | return (struct csr_matrix){ val, col_ind, row_ptr, n, m }; |
| | 111 | } |
| | 112 | |
| | 113 | void print_int_array(int * p, int n) { |
| | 114 | printf("{ "); |
| | 115 | for (int i=0; i<n; i++) printf("%d ", p[i]); |
| | 116 | printf("}"); |
| | 117 | } |
| | 118 | |
| | 119 | void print_dbl_array(double * p, int n) { |
| | 120 | printf("{ "); |
| | 121 | for (int i=0; i<n; i++) printf("%lf ", p[i]); |
| | 122 | printf("}"); |
| | 123 | } |
| | 124 | |
| | 125 | void print_csr(struct csr_matrix mat) { |
| | 126 | int nz = mat.row_ptr[mat.rows]; |
| | 127 | printf("begin CSR[nrow=%d, ncol=%d, nz=%d]", |
| | 128 | mat.rows, mat.cols, nz); |
| | 129 | printf("\n row_ptr = "); |
| | 130 | print_int_array(mat.row_ptr, mat.rows+1); |
| | 131 | printf("\n col_ind = "); |
| | 132 | print_int_array(mat.col_ind, nz); |
| | 133 | printf("\n val = "); |
| | 134 | print_dbl_array(mat.val, nz); |
| | 135 | printf("\nend CSR\n"); |
| | 136 | } |
| | 137 | |
| | 138 | void destroy_csr(struct csr_matrix mat) { |
| | 139 | free(mat.row_ptr); |
| | 140 | free(mat.col_ind); |
| | 141 | free(mat.val); |
| | 142 | } |
| | 143 | |
| | 144 | /* Gets the (i,j)-th entry from the CSR matrix mat */ |
| | 145 | double get_i_j(struct csr_matrix mat, int i, int j) { |
| | 146 | int r = mat.row_ptr[i], rnxt = mat.row_ptr[i+1], col; |
| | 147 | for (int k=r; k<rnxt; k++) { |
| | 148 | col = mat.col_ind[k]; |
| | 149 | if (col == j) return mat.val[k]; |
| | 150 | if (col > j) break; |
| | 151 | } |
| | 152 | return 0.0; |
| | 153 | } |
| | 154 | |
| | 155 | /* Makes a dense matrix from the given CSR matrix */ |
| | 156 | double ** make_dense(struct csr_matrix mat) { |
| | 157 | int n = mat.rows, m = mat.cols; |
| | 158 | double ** dense = malloc(n*sizeof(double*)); |
| | 159 | for (int i=0; i<n; i++) { |
| | 160 | dense[i] = malloc(m*sizeof(double)); |
| | 161 | for (int j=0; j<m; j++) |
| | 162 | dense[i][j] = get_i_j(mat, i, j); |
| | 163 | } |
| | 164 | return dense; |
| | 165 | } |
| | 166 | |
| | 167 | void destroy_dense(double ** dense, int nrow) { |
| | 168 | for (int i=0; i<nrow; i++) free(dense[i]); |
| | 169 | free(dense); |
| | 170 | } |
| | 171 | |
| | 172 | void dense_matrix_vector_multiply(int nrow, int ncol, double ** dense, |
| | 173 | double * v, double *p) { |
| | 174 | for (int i=0; i<nrow; i++) { |
| | 175 | double s = 0.0; |
| | 176 | for (int j=0; j<ncol; j++) s += dense[i][j]*v[j]; |
| | 177 | p[i] = s; |
| | 178 | } |
| | 179 | } |
| | 180 | |
| | 181 | int main(void) { |
| | 182 | double v[M], actual[N], expected[N]; |
| | 183 | for (int i=0; i<M; i++) v[i] = V[i]; |
| | 184 | struct csr_matrix mat = make_csr(N, M); |
| | 185 | csr_matrix_vector_multiply(&mat, v, actual); |
| | 186 | double ** dense = make_dense(mat); |
| | 187 | dense_matrix_vector_multiply(N, M, dense, v, expected); |
| | 188 | #ifdef VERBOSE |
| | 189 | printf("\n"); |
| | 190 | print_csr(mat); |
| | 191 | printf("v = "); |
| | 192 | print_dbl_array(v, M); |
| | 193 | printf("\nactual = "); |
| | 194 | print_dbl_array(actual, N); |
| | 195 | printf("\nexpected = "); |
| | 196 | print_dbl_array(expected, N); |
| | 197 | printf("\n"); |
| | 198 | #endif |
| | 199 | destroy_csr(mat); |
| | 200 | destroy_dense(dense, N); |
| | 201 | $assert($equals(actual, expected)); |
| | 202 | } |
| | 203 | }}} |
| | 204 | |
| | 205 | File `Makefile`: |
| | 206 | {{{ |
| | 207 | |
| | 208 | small: csr.h csr.c csr_driver.cvl |
| | 209 | civl verify -DVERBOSE csr.c csr_driver.cvl |
| | 210 | |
| | 211 | medium: csr.h csr.c csr_driver.cvl |
| | 212 | civl verify -inputN_B=3 -inputM_B=4 csr.c csr_driver.cvl |
| | 213 | |
| | 214 | medium2: csr.h csr.c csr_driver.cvl |
| | 215 | civl verify -inputN_B=4 -inputM_B=3 csr.c csr_driver.cvl |
| | 216 | |
| | 217 | large: csr.h csr.c csr_driver.cvl |
| | 218 | civl verify -inputN_B=4 -inputM_B=4 csr.c csr_driver.cvl |
| | 219 | |
| | 220 | clean: |
| | 221 | rm -rf CIVLREP *~ *.out *.tmp |
| | 222 | }}} |
| | 223 | |
| | 224 | Output from `make` (excerpted): |
| | 225 | {{{ |
| | 226 | civl verify -DVERBOSE csr.c csr_driver.cvl |
| | 227 | CIVL v1.22+ of 2023-10-09 -- http://vsl.cis.udel.edu/civl |
| | 228 | |
| | 229 | begin CSR[nrow=3, ncol=3, nz=0] |
| | 230 | row_ptr = { 0 0 0 0 } |
| | 231 | col_ind = { } |
| | 232 | val = { } |
| | 233 | end CSR |
| | 234 | v = { X_V[0] X_V[1] X_V[2] } |
| | 235 | actual = { 0 0 0 } |
| | 236 | expected = { 0 0 0 } |
| | 237 | |
| | 238 | begin CSR[nrow=3, ncol=3, nz=1] |
| | 239 | row_ptr = { 0 0 0 1 } |
| | 240 | col_ind = { 0 } |
| | 241 | val = { X_A[0] } |
| | 242 | end CSR |
| | 243 | v = { X_V[0] X_V[1] X_V[2] } |
| | 244 | actual = { 0 0 X_V[0]*X_A[0] } |
| | 245 | expected = { 0 0 X_V[0]*X_A[0] } |
| | 246 | |
| | 247 | begin CSR[nrow=3, ncol=3, nz=1] |
| | 248 | row_ptr = { 0 0 0 1 } |
| | 249 | col_ind = { 1 } |
| | 250 | val = { X_A[0] } |
| | 251 | end CSR |
| | 252 | v = { X_V[0] X_V[1] X_V[2] } |
| | 253 | actual = { 0 0 X_V[1]*X_A[0] } |
| | 254 | expected = { 0 0 X_V[1]*X_A[0] } |
| | 255 | |
| | 256 | begin CSR[nrow=3, ncol=3, nz=1] |
| | 257 | row_ptr = { 0 0 0 1 } |
| | 258 | col_ind = { 2 } |
| | 259 | val = { X_A[0] } |
| | 260 | end CSR |
| | 261 | v = { X_V[0] X_V[1] X_V[2] } |
| | 262 | actual = { 0 0 X_V[2]*X_A[0] } |
| | 263 | expected = { 0 0 X_V[2]*X_A[0] } |
| | 264 | |
| | 265 | begin CSR[nrow=3, ncol=3, nz=2] |
| | 266 | row_ptr = { 0 0 0 2 } |
| | 267 | col_ind = { 0 1 } |
| | 268 | val = { X_A[0] X_A[1] } |
| | 269 | end CSR |
| | 270 | v = { X_V[0] X_V[1] X_V[2] } |
| | 271 | actual = { 0 0 (X_V[1]*X_A[1])+(X_V[0]*X_A[0]) } |
| | 272 | expected = { 0 0 (X_V[1]*X_A[1])+(X_V[0]*X_A[0]) } |
| | 273 | |
| | 274 | begin CSR[nrow=3, ncol=3, nz=2] |
| | 275 | row_ptr = { 0 0 0 2 } |
| | 276 | col_ind = { 0 2 } |
| | 277 | val = { X_A[0] X_A[1] } |
| | 278 | end CSR |
| | 279 | v = { X_V[0] X_V[1] X_V[2] } |
| | 280 | actual = { 0 0 (X_V[2]*X_A[1])+(X_V[0]*X_A[0]) } |
| | 281 | expected = { 0 0 (X_V[2]*X_A[1])+(X_V[0]*X_A[0]) } |
| | 282 | . |
| | 283 | . |
| | 284 | . |
| | 285 | begin CSR[nrow=3, ncol=2, nz=6] |
| | 286 | row_ptr = { 0 2 4 6 } |
| | 287 | col_ind = { 0 1 0 1 0 1 } |
| | 288 | val = { X_A[0] X_A[1] X_A[2] X_A[3] X_A[4] X_A[5] } |
| | 289 | end CSR |
| | 290 | v = { X_V[0] X_V[1] } |
| | 291 | actual = { (X_V[1]*X_A[1])+(X_V[0]*X_A[0]) (X_V[1]*X_A[3])+(X_V[0]*X_A[2]) (X_V[1]*X_A[5])+(X_V[0]*X_A[4]) } |
| | 292 | expected = { (X_V[1]*X_A[1])+(X_V[0]*X_A[0]) (X_V[1]*X_A[3])+(X_V[0]*X_A[2]) (X_V[1]*X_A[5])+(X_V[0]*X_A[4]) } |
| | 293 | . |
| | 294 | . |
| | 295 | . |
| | 296 | begin CSR[nrow=1, ncol=1, nz=1] |
| | 297 | row_ptr = { 0 1 } |
| | 298 | col_ind = { 0 } |
| | 299 | val = { X_A[0] } |
| | 300 | end CSR |
| | 301 | v = { X_V[0] } |
| | 302 | actual = { X_A[0]*X_V[0] } |
| | 303 | expected = { X_A[0]*X_V[0] } |
| | 304 | |
| | 305 | === Source files === |
| | 306 | csr.c (csr.c) |
| | 307 | csr.h (csr.h) |
| | 308 | csr_driver.cvl (csr_driver.cvl) |
| | 309 | |
| | 310 | === Command === |
| | 311 | civl verify -DVERBOSE csr.c csr_driver.cvl |
| | 312 | |
| | 313 | === Stats === |
| | 314 | time (s) : 9.22 transitions : 215091 |
| | 315 | memory (bytes) : 6.62700032E8 trace steps : 175869 |
| | 316 | max process count : 1 valid calls : 661988 |
| | 317 | states : 175197 provers : cvc4, z3 |
| | 318 | states saved : 246990 prover calls : 19 |
| | 319 | state matches : 673 |
| | 320 | |
| | 321 | === Result === |
| | 322 | All errors marked with '+' are absent on all executions. |
| | 323 | + Dereference errors + Functional equivalence violations |
| | 324 | + Internal errors + Library loading errors |
| | 325 | + Other errors + Assertion violations |
| | 326 | + Communication errors + Writes to constant variables |
| | 327 | + Absolute deadlocks + Division by zero |
| | 328 | + Writes to $input variables + Invalid casts |
| | 329 | + Malloc errors + Memory leaks |
| | 330 | + Memory management errors + MPI usage errors |
| | 331 | + Out of bounds errors + Reads from $output variables |
| | 332 | + Pointer errors + Process leaks |
| | 333 | + Sequence errors - Non-termination |
| | 334 | + Use of undefined values + Union errors |
| | 335 | }}} |
| | 336 | |