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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
pub mod multrix {
use rand::Rng;
use rayon::prelude::*;
use std::fs;
/// A matrix struct that stores the values in a one-dimensional vector, where each row is stored
/// contiguously.
pub struct Matrix {
data: Vec<f64>,
rows: usize,
cols: usize,
}
impl Matrix {
/// Creates a new (square) identity matrix with the given dimension.
pub fn new_identity(dimension: usize) -> Matrix {
let mut data: Vec<f64> = vec![0.0; dimension * dimension];
for i in 0..dimension {
for j in 0..dimension {
data[i * dimension + j] = {
if i == j {
1.0
} else {
0.0
}
};
}
}
Matrix { data, rows: dimension, cols: dimension }
}
/// Creates a new matrix with the given dimensions and random values.
pub fn new_rand(rows: usize, cols: usize) -> Matrix {
let mut data: Vec<f64> = vec![0.0; cols * rows];
let mut rng = rand::thread_rng();
for i in 0..rows {
for j in 0..cols {
data[i * cols + j] = rng.gen_range(0..10) as f64;
}
}
Matrix { data, rows, cols }
}
/// Creates a new matrix with the given dimensions reading the values from a file.
/// The file must contain a comma-separated list of numbers, with each row on a new line.
/// The last element on each row may or may not be followed by a comma.
///
/// # Panics
/// The function panics if the dimensions are incorrect, if it fails to read from the file,
/// or the file contains invalid data and numbers cannot be parsed.
pub fn new_from_file(filename: &str) -> Matrix {
let contents = match fs::read_to_string(filename) {
Ok(v) => v,
Err(e) => {
eprintln!(
"{} failed to read from file '{}': {:?}",
"Error:", filename, e
);
panic!();
}
};
let mut data = Vec::new();
let rows = contents.lines().count();
let mut cols = 0;
for line in contents.lines() {
for num_str in line.split(',') {
data.push(num_str.parse().unwrap());
}
if cols == 0 {
cols = line.split(',').count();
}
}
Matrix { data, rows, cols }
}
/// Creates a new matrix with the given dimensions from a one-dimensional vector containing
/// the values, where each row is stored contiguously.
///
/// # Panics
/// The function panics if the provided dimensions are different than the vector length.
pub fn new_from_vec(data: Vec<f64>, rows: usize, cols: usize) -> Matrix {
assert_eq!(data.len(), rows * cols, "Invalid matrix dimensions");
Matrix { data, rows, cols }
}
/// Creates a new matrix with the given dimensions from a two-dimensional vector containing
/// the values.
pub fn new_from_vec_vec(data: Vec<Vec<f64>>) -> Matrix {
let rows = data.len();
let cols = data[0].len();
let mut data_vec = Vec::with_capacity(rows * cols);
for row in data {
for element in row {
data_vec.push(element);
}
}
Matrix { data: data_vec, rows, cols }
}
/// Gets the value at the given row and column indices.
pub fn get(&self, row: usize, col: usize) -> f64 {
self.data[row * self.cols + col]
}
/// Sets the value at the given row and column indices.
pub fn set(&mut self, row: usize, col: usize, value: f64) {
self.data[row * self.cols + col] = value;
}
/// Returns the current matrix transposed (rows and columns swapped).
pub fn transpose(&self) -> Matrix {
let mut data = vec![0.0; self.rows * self.cols];
for i in 0..self.rows {
for j in 0..self.cols {
data[j * self.rows + i] = self.data[i * self.cols + j];
}
}
Matrix { data, rows: self.cols, cols: self.rows }
}
/// Returns whether the two matrices are conformable for multiplication.
pub fn is_conformable(&self, other: &Matrix) -> bool {
self.cols == other.rows
}
/// Adds the given matrix to the current one and returns the result.
///
/// # Panics
/// The function panics if the matrices cannot be added: they must have the same dimensions.
fn addition(self, other: Matrix) -> Matrix {
assert_eq!(self.rows, other.rows, "Matrices cannot be added");
assert_eq!(self.cols, other.cols, "Matrices cannot be added");
let mut data = vec![0.0; self.rows * self.cols];
for i in 0..self.rows * self.cols {
data[i] = self.data[i] + other.data[i];
}
Matrix { data, rows: self.rows, cols: self.cols }
}
/// Negates the sign of the current matrix and returns the result.
fn negation(self) -> Matrix {
let mut data = vec![0.0; self.rows * self.cols];
for i in 0..self.rows * self.cols {
data[i] = -self.data[i];
}
Matrix { data, rows: self.rows, cols: self.cols }
}
/// Returns the product between the current matrix and the given one, and uses only one thread.
///
/// # Panics
/// The function panics if the matrices cannot be multiplied: the number of columns of the
/// first matrix must be equal to the number of rows of the second matrix.
pub fn product(self, other: Matrix) -> Matrix {
if self.cols != other.rows {
panic!("Matrices cannot be multiplied");
}
let rows = self.rows;
let cols = other.cols;
let mut data = vec![0.0; cols * rows];
for i in 0..cols * rows {
let mut c = 0.0;
let row = i / cols;
let col = i % cols;
for k in 0..self.cols {
c += self.data[row * self.cols + k] * other.data[k * other.cols + col];
}
data[i] = c;
}
Matrix { data, rows, cols }
}
/// Returns the product between the current matrix and the given one, and uses multiple threads.
///
/// # Panics
/// The function panics if the matrices cannot be multiplied: the number of columns of the
/// first matrix must be equal to the number of rows of the second matrix.
pub fn parallel_product(self, other: Matrix) -> Matrix {
if self.cols != other.rows {
panic!("Matrices cannot be multiplied");
}
let rows = self.rows;
let cols = other.cols;
let mut data = vec![0.0; cols * rows];
data.par_iter_mut().enumerate().for_each(|(i, c)| {
let row = i / cols;
let col = i % cols;
for k in 0..self.cols {
*c += self.data[row * self.cols + k] * other.data[k * other.cols + col];
}
});
Matrix { data, rows, cols }
}
/// Writes the matrix to the given file in the same comma-separated format as the input.
///
/// # Panics
/// The function panics if it fails to write to the file.
pub fn write_to_file(&self, filename: &str) {
let mut matrix_str = String::with_capacity(self.rows * self.cols * 2);
for (i, element) in self.data.iter().enumerate() {
matrix_str.push_str(&format!("{},", element));
if (i + 1) % self.cols == 0 {
matrix_str.push('\n');
}
}
match fs::write(filename, matrix_str) {
Ok(_) => {}
Err(e) => {
eprintln!("Error: failed to write to file '{}': {:?}", filename, e);
panic!();
}
}
}
}
use std::ops::Add;
impl Add for Matrix {
type Output = Matrix;
fn add(self, other: Matrix) -> Matrix {
self.addition(other)
}
}
use std::ops::Neg;
impl Neg for Matrix {
type Output = Matrix;
fn neg(self) -> Matrix {
self.negation()
}
}
use std::ops::Mul;
impl Mul for Matrix {
type Output = Matrix;
fn mul(self, other: Matrix) -> Matrix {
self.parallel_product(other)
}
}
use std::ops::Sub;
impl Sub for Matrix {
type Output = Matrix;
fn sub(self, other: Matrix) -> Matrix {
self + (-other)
}
}
use std::fmt;
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, element) in self.data.iter().enumerate() {
write!(f, "{},", element)?;
if (i + 1) % self.cols == 0 {
writeln!(f)?;
}
}
Ok(())
}
}
}