diff --git a/scripts/grade-04.sh b/scripts/grade-04.sh index a5d1bfc..2f0fdc7 100755 --- a/scripts/grade-04.sh +++ b/scripts/grade-04.sh @@ -24,7 +24,7 @@ run_linters || exit 1 for RUNNER in "${RUNNERS[@]}"; do echo "Running with $RUNNER..." - TESTS=("--lib assignment04_grade") + TESTS=("--lib assignment04") if [ $(run_tests) -ne 0 ]; then exit 1 fi diff --git a/src/assignments/assignment09/matmul.rs b/src/assignments/assignment09/matmul.rs new file mode 100644 index 0000000..273742b --- /dev/null +++ b/src/assignments/assignment09/matmul.rs @@ -0,0 +1,66 @@ +//! Simple matrix multiplication + +use itertools::*; + +/// elementwise vector addition +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment09::vec_add; +/// +/// let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let res = vec_add(&vec1, &vec2); +/// assert_eq!(res, vec![2.0, 4.0, 6.0, 8.0, 10.0]); +/// ``` +pub fn vec_add(lhs: &[f64], rhs: &[f64]) -> Vec { + todo!() +} + +/// dot product of two arrays +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment09::dot_product; +/// +/// let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let res = dot_product(&vec1, &vec2); +/// +/// assert_eq!(res, 55.0); +/// ``` +pub fn dot_product(lhs: &[f64], rhs: &[f64]) -> f64 { + todo!() +} + +/// Matrix multiplication +/// +/// Assume rhs is transposed +/// - lhs: (m, n) +/// - rhs: (p, n) +/// - output: (m, p) +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment09::matmul; +/// +/// let mat1 = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]; +/// let mat2 = vec![ +/// vec![7.0, 8.0, 9.0], +/// vec![10.0, 11.0, 12.0], +/// vec![13.0, 14.0, 15.0], +/// vec![16.0, 17.0, 18.0], +/// ]; +/// let ans = vec![ +/// vec![50.0, 68.0, 86.0, 104.0], +/// vec![122.0, 167.0, 212.0, 257.0], +/// ]; +/// let res = matmul(&mat1, &mat2); +/// assert_eq!(ans, res); +/// ``` +pub fn matmul(lhs: &[Vec], rhs: &[Vec]) -> Vec> { + todo!() +} diff --git a/src/assignments/assignment09/matmul_grade.rs b/src/assignments/assignment09/matmul_grade.rs new file mode 100644 index 0000000..83efd14 --- /dev/null +++ b/src/assignments/assignment09/matmul_grade.rs @@ -0,0 +1,106 @@ +#[cfg(test)] +mod test { + use crate::assignments::assignment09::matmul::*; + + use approx::*; + use itertools::Itertools; + use ndarray::prelude::*; + use ndarray_rand::{rand_distr::Uniform, RandomExt}; + + #[test] + fn vec_add_test() { + let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let res = vec_add(&vec1, &vec2); + assert_eq!(res, vec![2.0, 4.0, 6.0, 8.0, 10.0]); + + for _ in 0..5 { + let vec1 = Array::random(500000, Uniform::new(0., 10.)); + let vec2 = Array::random(500000, Uniform::new(0., 10.)); + + let res = vec_add(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + + let ans = vec1 + vec2; + assert_eq!(Array::from_vec(res), ans); + } + } + + #[test] + fn dot_product_test() { + let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let res = dot_product(&vec1, &vec2); + assert_eq!(res, 55.0); + + for _ in 0..5 { + let vec1 = Array::random(1000000, Uniform::new(0., 10.)); + let vec2 = Array::random(1000000, Uniform::new(0., 10.)); + + let res = dot_product(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + let _res = relative_eq!(res, vec1.dot(&vec2), epsilon = f64::EPSILON); + } + } + + /// Reference: + /// Converts nested `Vec`s to a 2-D array by cloning the elements. + /// + /// **Panics** if the length of any axis overflows `isize`, if the + /// size in bytes of all the data overflows `isize`, or if not all the + /// rows have the same length. + fn vec_to_array(v: Vec>) -> Array2 { + if v.is_empty() { + return Array2::from_shape_vec((0, 0), Vec::new()).unwrap(); + } + let nrows = v.len(); + let ncols = v[0].len(); + let mut data = Vec::with_capacity(nrows * ncols); + for row in &v { + assert_eq!(row.len(), ncols); + data.extend_from_slice(row); + } + Array2::from_shape_vec((nrows, ncols), data).unwrap() + } + + #[test] + fn matmul_test() { + let mat1 = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]; + let mat2 = vec![ + vec![7.0, 8.0, 9.0], + vec![10.0, 11.0, 12.0], + vec![13.0, 14.0, 15.0], + vec![16.0, 17.0, 18.0], + ]; + let ans = vec![ + vec![50.0, 68.0, 86.0, 104.0], + vec![122.0, 167.0, 212.0, 257.0], + ]; + let res = matmul(&mat1, &mat2); + assert_eq!(ans, res); + + for _ in 0..5 { + let mat1 = Array::random((500, 500), Uniform::new(0., 10.)); + let mat2 = Array::random((500, 500), Uniform::new(0., 10.)); + let ans = mat1.dot(&mat2); + let mat2_transposed = mat2.t(); + + // Run sequential matrix multiplication + let res = matmul( + mat1.axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + mat2_transposed + .axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + ); + + // Check answer + for it in ans.iter().zip(vec_to_array(res).iter()) { + let (ans, res) = it; + let _res = relative_eq!(ans, res); + } + } + } +} diff --git a/src/assignments/assignment09/mod.rs b/src/assignments/assignment09/mod.rs index d82be95..9601827 100644 --- a/src/assignments/assignment09/mod.rs +++ b/src/assignments/assignment09/mod.rs @@ -6,7 +6,9 @@ //! See `assignment09_grade.rs` and `/scripts/grade-09.sh` for the test script. pub mod bigint; +pub mod matmul; pub mod small_exercises; mod bigint_grade; +mod matmul_grade; mod small_exercises_grade; diff --git a/src/assignments/assignment13.rs b/src/assignments/assignment13.rs index dfc7394..1ee00f0 100644 --- a/src/assignments/assignment13.rs +++ b/src/assignments/assignment13.rs @@ -16,10 +16,10 @@ use rayon::prelude::*; /// use cs220::assignments::assignment13::sigma; /// use rayon::iter::IntoParallelIterator; /// -/// assert_eq!(sigma([1, 2].into_par_iter(), |x| x + 2), 7); -/// assert_eq!(sigma([1, 2].into_par_iter(), |x| x * 4), 12); +/// assert_eq!(sigma_par([1, 2].into_par_iter(), |x| x + 2), 7); +/// assert_eq!(sigma_par([1, 2].into_par_iter(), |x| x * 4), 12); /// ``` -pub fn sigma i64 + Sync + Send>( +pub fn sigma_par i64 + Sync + Send>( inner: impl ParallelIterator, f: F, ) -> i64 { @@ -35,14 +35,77 @@ pub fn sigma i64 + Sync + Send>( /// use rayon::iter::IntoParallelIterator; /// /// assert_eq!( -/// interleave3([1, 2].into_par_iter(), [3, 4].into_par_iter(), [5, 6].into_par_iter()), +/// interleave3_par([1, 2].into_par_iter(), [3, 4].into_par_iter(), [5, 6].into_par_iter()), /// vec![1, 3, 5, 2, 4, 6] /// ); /// ``` -pub fn interleave3( +pub fn interleave3_par( list1: impl IndexedParallelIterator, list2: impl IndexedParallelIterator, list3: impl IndexedParallelIterator, ) -> Vec { todo!() } + +/// Parallel vector addition +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment13::vec_add_par; +/// +/// let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let res = vec_add_par(&vec1, &vec2); +/// assert_eq!(res, vec![2.0, 4.0, 6.0, 8.0, 10.0]); +/// ``` +pub fn vec_add_par(lhs: &[f64], rhs: &[f64]) -> Vec { + todo!() +} + +/// Parallel dot product of two arrays +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment13::dot_product_par; +/// +/// let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; +/// let res = dot_product_par(&vec1, &vec2); +/// +/// assert_eq!(res, 55.0); +/// ``` +pub fn dot_product_par(lhs: &[f64], rhs: &[f64]) -> f64 { + todo!() +} + +/// Parallel Matrix multiplication +/// +/// Assume rhs is transposed +/// - lhs: (m, n) +/// - rhs: (p, n) +/// - output: (m, p) +/// +/// # Exmaple +/// +/// ``` +/// use cs220::assignments::assignment13::matmul_par; +/// +/// let mat1 = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]; +/// let mat2 = vec![ +/// vec![7.0, 8.0, 9.0], +/// vec![10.0, 11.0, 12.0], +/// vec![13.0, 14.0, 15.0], +/// vec![16.0, 17.0, 18.0], +/// ]; +/// let ans = vec![ +/// vec![50.0, 68.0, 86.0, 104.0], +/// vec![122.0, 167.0, 212.0, 257.0], +/// ]; +/// let res = matmul_par(&mat1, &mat2); +/// assert_eq!(ans, res); +/// ``` +pub fn matmul_par(lhs: &[Vec], rhs: &[Vec]) -> Vec> { + todo!() +} diff --git a/src/assignments/assignment13_grade.rs b/src/assignments/assignment13_grade.rs index 6f560c6..68c75a8 100644 --- a/src/assignments/assignment13_grade.rs +++ b/src/assignments/assignment13_grade.rs @@ -1,42 +1,48 @@ #[cfg(test)] mod test { + use super::super::assignment09::matmul::*; use super::super::assignment13::*; + use approx::*; + use itertools::Itertools; + use ndarray::prelude::*; + use ndarray_rand::{rand_distr::Uniform, RandomExt}; use rayon::prelude::IntoParallelIterator; + use std::time::Instant; #[test] - fn test_sigma() { - assert_eq!(sigma([].into_par_iter(), |x: i64| x * 2), 0); - assert_eq!(sigma([1].into_par_iter(), |x| x * 3), 3); - assert_eq!(sigma([1, 2].into_par_iter(), |x| x + 2), 7); - assert_eq!(sigma([1, 2].into_par_iter(), |x| x * 4), 12); - assert_eq!(sigma([1, 2, 3].into_par_iter(), |x| x * 5), 30); + fn test_sigma_par() { + assert_eq!(sigma_par([].into_par_iter(), |x: i64| x * 2), 0); + assert_eq!(sigma_par([1].into_par_iter(), |x| x * 3), 3); + assert_eq!(sigma_par([1, 2].into_par_iter(), |x| x + 2), 7); + assert_eq!(sigma_par([1, 2].into_par_iter(), |x| x * 4), 12); + assert_eq!(sigma_par([1, 2, 3].into_par_iter(), |x| x * 5), 30); assert_eq!( - sigma([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.floor() + sigma_par([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.floor() as i64), 10 ); assert_eq!( - sigma([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.ceil() + sigma_par([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.ceil() as i64), 13 ); assert_eq!( - sigma([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.round() + sigma_par([-1.2, 3.0, 4.2, 5.8].into_par_iter(), |x: f64| x.round() as i64), 12 ); assert_eq!( - sigma(["Hello,", "World!"].into_par_iter(), |x| x.len() as i64), + sigma_par(["Hello,", "World!"].into_par_iter(), |x| x.len() as i64), 12 ); } #[test] - fn test_interleave3() { + fn test_interleave3_par() { assert_eq!( - interleave3( + interleave3_par( [1, 2].into_par_iter(), [3, 4].into_par_iter(), [5, 6].into_par_iter() @@ -45,7 +51,7 @@ mod test { ); assert_eq!( - interleave3( + interleave3_par( [1, 2, 3].into_par_iter(), [4, 5, 6].into_par_iter(), [7, 8, 9].into_par_iter() @@ -54,7 +60,7 @@ mod test { ); assert_eq!( - interleave3( + interleave3_par( ["a", "b", "c"].into_par_iter(), ["d", "e", "f"].into_par_iter(), ["g", "h", "i"].into_par_iter() @@ -64,4 +70,148 @@ mod test { "adgbehcfi" ); } + + #[test] + fn vec_add_test() { + let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let res = vec_add(&vec1, &vec2); + assert_eq!(res, vec![2.0, 4.0, 6.0, 8.0, 10.0]); + + for _ in 0..5 { + let vec1 = Array::random(500000, Uniform::new(0., 10.)); + let vec2 = Array::random(500000, Uniform::new(0., 10.)); + + let now_seq = Instant::now(); + let res_seq = vec_add(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + let elapsed_seq = now_seq.elapsed(); + + let now_par = Instant::now(); + let res_par = vec_add_par(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + let elapsed_par = now_par.elapsed(); + + let ans = vec1 + vec2; + assert_eq!(Array::from_vec(res_seq), ans); + assert_eq!(Array::from_vec(res_par), ans); + assert!(elapsed_par < elapsed_seq); + } + } + + #[test] + fn dot_product_test() { + let vec1 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let vec2 = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let res_seq = dot_product(&vec1, &vec2); + let res_par = dot_product_par(&vec1, &vec2); + assert_eq!(res_seq, 55.0); + assert_eq!(res_par, 55.0); + + for _ in 0..5 { + let vec1 = Array::random(1000000, Uniform::new(0., 10.)); + let vec2 = Array::random(1000000, Uniform::new(0., 10.)); + + let now_seq = Instant::now(); + let res_seq = dot_product(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + let elapsed_seq = now_seq.elapsed(); + + let now_par = Instant::now(); + let res_par = dot_product_par(vec1.as_slice().unwrap(), vec2.as_slice().unwrap()); + let elapsed_par = now_par.elapsed(); + + let _res = relative_eq!(res_seq, vec1.dot(&vec2), epsilon = f64::EPSILON); + let _res = relative_eq!(res_par, vec1.dot(&vec2), epsilon = f64::EPSILON); + assert!(elapsed_par < elapsed_seq); + } + } + + /// Reference: + /// Converts nested `Vec`s to a 2-D array by cloning the elements. + /// + /// **Panics** if the length of any axis overflows `isize`, if the + /// size in bytes of all the data overflows `isize`, or if not all the + /// rows have the same length. + fn vec_to_array(v: Vec>) -> Array2 { + if v.is_empty() { + return Array2::from_shape_vec((0, 0), Vec::new()).unwrap(); + } + let nrows = v.len(); + let ncols = v[0].len(); + let mut data = Vec::with_capacity(nrows * ncols); + for row in &v { + assert_eq!(row.len(), ncols); + data.extend_from_slice(row); + } + Array2::from_shape_vec((nrows, ncols), data).unwrap() + } + + #[test] + fn matmul_test() { + let mat1 = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]; + let mat2 = vec![ + vec![7.0, 8.0, 9.0], + vec![10.0, 11.0, 12.0], + vec![13.0, 14.0, 15.0], + vec![16.0, 17.0, 18.0], + ]; + let ans = vec![ + vec![50.0, 68.0, 86.0, 104.0], + vec![122.0, 167.0, 212.0, 257.0], + ]; + let res_seq = matmul(&mat1, &mat2); + let res_par = matmul_par(&mat1, &mat2); + assert_eq!(ans, res_seq); + assert_eq!(ans, res_par); + + for _ in 0..5 { + let mat1 = Array::random((500, 500), Uniform::new(0., 10.)); + let mat2 = Array::random((500, 500), Uniform::new(0., 10.)); + let ans = mat1.dot(&mat2); + let mat2_transposed = mat2.t(); + + // Run sequential matrix multiplication + let now_seq = Instant::now(); + let res_seq = matmul( + mat1.axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + mat2_transposed + .axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + ); + let elapsed_seq = now_seq.elapsed(); + + // Run parallel matrix multiplication + let now_par = Instant::now(); + let res_par = matmul_par( + mat1.axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + mat2_transposed + .axis_iter(Axis(0)) + .map(|row| row.to_vec()) + .collect::>() + .as_slice(), + ); + let elapsed_par = now_par.elapsed(); + + // Check answer + for it in ans.iter().zip(vec_to_array(res_seq).iter()) { + let (ans, seq) = it; + let _res = relative_eq!(ans, seq); + } + for it in ans.iter().zip(vec_to_array(res_par).iter()) { + let (ans, par) = it; + let _res = relative_eq!(ans, par); + } + + // Check time + // println!("Sequential: {:?}", elapsed_seq); + // println!("Parallel: {:?}", elapsed_par); + assert!(elapsed_par < elapsed_seq); + } + } }