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