forked from sparsemat/sprs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
heat.rs
172 lines (155 loc) · 5.62 KB
/
heat.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
///! This file demonstrates basic usage of the sprs library,
///! where a heat diffusion problem with Dirichlet boundary condition
///! is solved for equilibrium. For simplicity we omit any relevant
///! physical constant.
///!
///! This problem can be modelled as solving a linear system
///! L * x = rhs
///! where L is a laplacian matrix on a 2 dimensional grid, and rhs is
///! zero everywhere except for values corresponding to borders, where
///! a constant heat value is imposed.
///! Since the L matrix is diagonally dominant, we can use a Gauss-Seidel
///! iterative scheme to solve the system.
///!
///! This shows how a laplacian matrix can be constructed by directly
///! constructing the compressed structure, and how the resulting linear
///! system can be solved using an iterative method.
extern crate sprs;
extern crate ndarray;
type VecView<'a, T> = ndarray::ArrayView<'a, T, ndarray::Ix1>;
type VecViewMut<'a, T> = ndarray::ArrayViewMut<'a, T, ndarray::Ix1>;
type OwnedVec<T> = ndarray::Array<T, ndarray::Ix1>;
/// Determine whether the grid location at `(row, col)` is a border
/// of the grid defined by `shape`.
fn is_border(row: usize, col: usize, shape: (usize, usize)) -> bool {
let (rows, cols) = shape;
let top_row = row == 0;
let bottom_row = row + 1 == rows;
let border_row = top_row || bottom_row;
let left_col = col == 0;
let right_col = col + 1 == cols;
let border_col = left_col || right_col;
border_row || border_col
}
/// Compute the discrete laplacian operator on a grid, assuming the
/// step size is 1.
/// We assume this operator operates on the C-order flattened version of
/// the grid.
///
/// This example shows how a relatively straightforward sparse matrix
/// can be constructed with a minimal number of allocations by directly
/// building up its sparse structure.
fn grid_laplacian(shape: (usize, usize)) -> sprs::CsMat<f64> {
let (rows, cols) = shape;
let nb_vert = rows * cols;
let mut indptr = Vec::with_capacity(nb_vert + 1);
let nnz = 5*nb_vert + 5;
let mut indices = Vec::with_capacity(nnz);
let mut data = Vec::with_capacity(nnz);
let mut cumsum = 0;
for i in 0..rows {
for j in 0..cols {
indptr.push(cumsum);
let mut add_elt = |i, j, x| {
indices.push(i * rows + j);
data.push(x);
cumsum += 1;
};
if is_border(i, j, shape) {
// establish Dirichlet boundary conditions
add_elt(i, j, 1.);
}
else {
add_elt(i - 1, j, 1.);
add_elt(i, j - 1, 1.);
add_elt(i, j, -4.);
add_elt(i, j + 1, 1.);
add_elt(i + 1, j, 1.);
}
}
}
indptr.push(cumsum);
sprs::CsMat::new((nb_vert, nb_vert), indptr, indices, data)
}
/// Set a dirichlet boundary condition
fn set_boundary_condition<F>(mut rhs: VecViewMut<f64>,
grid_shape: (usize, usize),
f: F
)
where F: Fn(usize, usize) -> f64
{
let (rows, cols) = grid_shape;
for i in 0..rows {
for j in 0..cols {
if is_border(i, j, grid_shape) {
let index = i*rows + j;
rhs[[index]] = f(i, j);
}
}
}
}
/// Gauss-Seidel method to solve the system
/// see https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method#Algorithm
fn gauss_seidel(mat: sprs::CsMatView<f64>,
mut x: VecViewMut<f64>,
rhs: VecView<f64>,
max_iter: usize,
eps: f64
) -> Result<(usize, f64), f64> {
assert!(mat.rows() == mat.cols());
assert!(mat.rows() == x.shape()[0]);
let mut error = (&mat * &x - rhs).scalar_sum().sqrt();
for it in 0..max_iter {
for (row_ind, vec) in mat.outer_iterator().enumerate() {
let mut sigma = 0.;
let mut diag = None;
for (col_ind, &val) in vec.iter() {
if row_ind != col_ind {
sigma += val * x[[col_ind]];
}
else {
diag = Some(val);
}
}
// Gauss-Seidel requires a non-zero diagonal, which
// is satisfied for a laplacian matrix
let diag = diag.unwrap();
let cur_rhs = rhs[[row_ind]];
x[[row_ind]] = ( cur_rhs - sigma) / diag;
}
error = (&mat * &x - rhs).scalar_sum().sqrt();
// error corresponds to the state before iteration, but
// that shouldn't be a problem
if error < eps {
return Ok((it, error));
}
}
Err(error)
}
fn main() {
let (rows, cols) = (10, 10);
let lap = grid_laplacian((rows, cols));
let mut rhs = OwnedVec::zeros(rows * cols);
set_boundary_condition(rhs.view_mut(), (rows, cols), |row, col| {
(row + col) as f64
});
let mut x = OwnedVec::zeros(rows * cols);
match gauss_seidel(lap.view(), x.view_mut(), rhs.view(), 300, 1e-8) {
Ok((iters, error)) => {
println!("Solved system in {} iterations with residual error {}",
iters,
error);
let grid = x.view().into_shape((rows, cols)).unwrap();
for i in 0..rows {
for j in 0..cols {
print!("{} ", grid[[i, j]]);
}
println!("");
}
}
Err(error) => {
println!("Solving the system failed to converge fast enough");
println!("Residual error was {}", error);
}
}
}