qfall_math/integer/mat_z/sort.rs
1// Copyright © 2023 Niklas Siemer
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! Contains functions to sort [`MatZ`].
10
11use super::MatZ;
12use crate::{
13 error::MathError,
14 traits::{MatrixDimensions, MatrixGetSubmatrix, MatrixSetSubmatrix},
15};
16
17impl MatZ {
18 /// Sorts the columns of the matrix based on some condition defined by `cond_func` in an ascending order.
19 ///
20 /// This condition is usually a norm with the described input-output behaviour.
21 ///
22 /// Parameters:
23 /// - `cond_func`: computes values implementing [`Ord`] over the columns of the specified matrix.
24 /// These values are then used to re-order / sort the rows of the matrix.
25 ///
26 /// Returns an empty `Ok` if the action could be performed successfully.
27 /// A [`MathError`] is returned if the execution of `cond_func` returned an error.
28 ///
29 /// # Examples
30 /// ## Use a build-in function as condition
31 /// ```
32 /// use qfall_math::integer::MatZ;
33 /// use std::str::FromStr;
34 /// let mat = MatZ::from_str("[[3, 2, 1]]").unwrap();
35 /// let cmp = MatZ::from_str("[[1, 2, 3]]").unwrap();
36 ///
37 /// let sorted = mat.sort_by_column(MatZ::norm_eucl_sqrd).unwrap();
38 ///
39 /// assert_eq!(cmp, sorted);
40 /// ```
41 /// ## Use a custom function as condition
42 /// This function needs to take a column vector as input and output a type implementing [`PartialOrd`]
43 /// ```
44 /// use qfall_math::{integer::{MatZ, Z}, error::MathError, traits::{MatrixDimensions, MatrixGetEntry}};
45 /// use std::str::FromStr;
46 /// let mat = MatZ::from_str("[[3, 2, 1]]").unwrap();
47 /// let cmp = MatZ::from_str("[[1, 2, 3]]").unwrap();
48 ///
49 /// fn custom_cond_func(matrix: &MatZ) -> Result<Z, MathError> {
50 /// let mut sum = Z::ZERO;
51 /// for entry in matrix.get_entries_rowwise() {
52 /// sum += entry;
53 /// }
54 /// Ok(sum)
55 /// }
56 ///
57 /// let sorted = mat.sort_by_column(custom_cond_func).unwrap();
58 ///
59 /// assert_eq!(cmp, sorted);
60 /// ```
61 ///
62 /// # Errors and Failures
63 /// - Returns a [`MathError`] of the same type as `cond_func` if the execution of `cond_func` fails.
64 pub fn sort_by_column<T: Ord>(
65 &self,
66 cond_func: fn(&Self) -> Result<T, MathError>,
67 ) -> Result<Self, MathError> {
68 let mut condition_values = Vec::with_capacity(self.get_num_columns() as usize);
69 for col in 0..self.get_num_columns() {
70 condition_values.push(cond_func(&unsafe { self.get_column_unchecked(col) })?);
71 }
72
73 let mut id_vec: Vec<usize> = (0..self.get_num_columns() as usize).collect();
74 id_vec.sort_by_key(|x| &condition_values[*x]);
75
76 let mut out = Self::new(self.get_num_rows(), self.get_num_columns());
77 for (col, item) in id_vec.iter().enumerate() {
78 let (col_0, col_1) = (col as i64, *item as i64);
79 unsafe { out.set_column_unchecked(col_0, self, col_1) };
80 }
81
82 Ok(out)
83 }
84
85 /// Sorts the rows of the matrix based on some condition defined by `cond_func` in an ascending order.
86 ///
87 /// This condition is usually a norm with the described input-output behaviour.
88 ///
89 /// Parameters:
90 /// - `cond_func`: computes values implementing [`Ord`] over the columns of the specified matrix.
91 /// These values are then used to re-order / sort the columns of the matrix.
92 ///
93 /// Returns an empty `Ok` if the action could be performed successfully.
94 /// A [`MathError`] is returned if the execution of `cond_func` returned an error.
95 ///
96 /// # Examples
97 /// ## Use a build-in function as condition
98 /// ```
99 /// use qfall_math::integer::MatZ;
100 /// use std::str::FromStr;
101 /// let mat = MatZ::from_str("[[3],[2],[1]]").unwrap();
102 /// let cmp = MatZ::from_str("[[1],[2],[3]]").unwrap();
103 ///
104 /// let sorted = mat.sort_by_row(MatZ::norm_infty).unwrap();
105 ///
106 /// assert_eq!(cmp, sorted);
107 /// ```
108 /// ## Use a custom function as condition
109 /// This function needs to take a row vector as input and output a type implementing [`PartialOrd`]
110 /// ```
111 /// use qfall_math::{integer::{MatZ, Z}, error::MathError, traits::{MatrixDimensions, MatrixGetEntry}};
112 /// use std::str::FromStr;
113 /// let mat = MatZ::from_str("[[3],[2],[1]]").unwrap();
114 /// let cmp = MatZ::from_str("[[1],[2],[3]]").unwrap();
115 ///
116 /// fn custom_cond_func(matrix: &MatZ) -> Result<Z, MathError> {
117 /// let mut sum = Z::ZERO;
118 /// for entry in matrix.get_entries_columnwise() {
119 /// sum += entry;
120 /// }
121 /// Ok(sum)
122 /// }
123 ///
124 /// let sorted = mat.sort_by_row(custom_cond_func).unwrap();
125 ///
126 /// assert_eq!(cmp, sorted);
127 /// ```
128 ///
129 /// # Errors and Failures
130 /// - Returns a [`MathError`] of the same type as `cond_func` if the execution of `cond_func` fails.
131 pub fn sort_by_row<T: Ord>(
132 &self,
133 cond_func: fn(&Self) -> Result<T, MathError>,
134 ) -> Result<Self, MathError> {
135 let mut condition_values = Vec::with_capacity(self.get_num_rows() as usize);
136 for row in 0..self.get_num_rows() {
137 condition_values.push(cond_func(&unsafe { self.get_row_unchecked(row) })?);
138 }
139 let mut id_vec: Vec<usize> = (0..self.get_num_rows() as usize).collect();
140 id_vec.sort_by_key(|x| &condition_values[*x]);
141
142 let mut out = Self::new(self.get_num_rows(), self.get_num_columns());
143 for (row, item) in id_vec.iter().enumerate() {
144 let (row_0, row_1) = (row as i64, *item as i64);
145 unsafe { out.set_row_unchecked(row_0, self, row_1) };
146 }
147
148 Ok(out)
149 }
150}
151
152#[cfg(test)]
153mod test_sort_by_length {
154 use super::MatZ;
155 use crate::error::{MathError, StringConversionError};
156 use std::str::FromStr;
157
158 /// This function should fail in any case a vector is provided to it.
159 /// As `sort_by_column` and `sort_by_row` execute this function on the columns resp. rows of its matrix,
160 /// it should always return an error if used as `cond_func` for these two functions
161 fn failing_func(matrix: &MatZ) -> Result<(), MathError> {
162 if matrix.is_vector() {
163 Err(StringConversionError::InvalidMatrix(String::from(
164 "Some silly mistake was made - on purpose",
165 )))?
166 } else {
167 Ok(())
168 }
169 }
170
171 /// Checks whether sorting by column length acc. to eucl. norm works correct for small entries
172 #[test]
173 fn column_norm_eucl_sqrd_small_entries() {
174 let mat = MatZ::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]]").unwrap();
175 let cmp = MatZ::from_str("[[0, -1, 2, 3],[2, 2, 2, 2]]").unwrap();
176
177 let res = mat.sort_by_column(MatZ::norm_eucl_sqrd).unwrap();
178
179 assert_eq!(cmp, res);
180 }
181
182 /// Checks whether sorting by column length acc. to eucl. norm works correct for large entries
183 #[test]
184 fn column_norm_eucl_sqrd_large_entries() {
185 let mat = MatZ::from_str(&format!(
186 "[[{}, {}, 5],[1, 2, 5],[0, 0, 0]]",
187 i64::MIN,
188 i64::MAX
189 ))
190 .unwrap();
191 let cmp = MatZ::from_str(&format!(
192 "[[5, {}, {}],[5, 2, 1],[0, 0, 0]]",
193 i64::MAX,
194 i64::MIN
195 ))
196 .unwrap();
197
198 let res = mat.sort_by_column(MatZ::norm_eucl_sqrd).unwrap();
199
200 assert_eq!(cmp, res);
201 }
202
203 /// Checks whether sorting by column length acc. to infty norm works correct for large entries
204 #[test]
205 fn column_norm_infty_large_entries() {
206 let mat = MatZ::from_str(&format!(
207 "[[{}, {}, 5],[1, 2, 5],[0, 0, 0]]",
208 i64::MIN,
209 i64::MAX
210 ))
211 .unwrap();
212 let cmp = MatZ::from_str(&format!(
213 "[[5, {}, {}],[5, 2, 1],[0, 0, 0]]",
214 i64::MAX,
215 i64::MIN
216 ))
217 .unwrap();
218
219 let res = mat.sort_by_column(MatZ::norm_infty).unwrap();
220
221 assert_eq!(cmp, res);
222 }
223
224 /// Checks whether sorting by columns length acc. to eucl. norm works properly
225 /// for matrices with a few more entries
226 #[test]
227 fn many_columns() {
228 let mat = MatZ::from_str("[[3, 4, 1, 7, 2, 0, 9, -8, 6, 5]]").unwrap();
229 let cmp = MatZ::from_str("[[0, 1, 2, 3, 4, 5, 6, 7, -8, 9]]").unwrap();
230
231 let res = mat.sort_by_column(MatZ::norm_eucl_sqrd).unwrap();
232
233 assert_eq!(cmp, res);
234 }
235
236 /// Checks whether an error is returned for sorting by columns if the `cond_func` returns an error
237 #[test]
238 fn column_error_cond_func() {
239 let mat = MatZ::from_str("[[1, 2],[3, 4]]").unwrap();
240
241 let res = mat.sort_by_column(failing_func);
242
243 assert!(res.is_err());
244 }
245
246 /// Checks whether sorting by row length acc. to eucl. norm works correct for small entries
247 #[test]
248 fn row_norm_eucl_sqrd_small_entries() {
249 let mat = MatZ::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]]").unwrap();
250 let cmp = MatZ::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]]").unwrap();
251
252 let res = mat.sort_by_row(MatZ::norm_eucl_sqrd).unwrap();
253
254 assert_eq!(cmp, res);
255 }
256
257 /// Checks whether sorting by row length acc. to eucl. norm works correct for large entries
258 #[test]
259 fn row_norm_eucl_sqrd_large_entries() {
260 let mat = MatZ::from_str(&format!(
261 "[[{}, 0, 5],[{}, 2, 5],[0, 0, 0]]",
262 i64::MIN,
263 i64::MAX
264 ))
265 .unwrap();
266 let cmp = MatZ::from_str(&format!(
267 "[[0, 0, 0],[{}, 2, 5],[{}, 0, 5]]",
268 i64::MAX,
269 i64::MIN
270 ))
271 .unwrap();
272
273 let res = mat.sort_by_row(MatZ::norm_eucl_sqrd).unwrap();
274
275 assert_eq!(cmp, res);
276 }
277
278 /// Checks whether sorting by row length acc. to infty norm works correct for large entries
279 #[test]
280 fn row_norm_infty_large_entries() {
281 let mat = MatZ::from_str(&format!(
282 "[[{}, 0, 5],[{}, 2, 5],[0, 0, 0]]",
283 i64::MIN,
284 i64::MAX
285 ))
286 .unwrap();
287 let cmp = MatZ::from_str(&format!(
288 "[[0, 0, 0],[{}, 2, 5],[{}, 0, 5]]",
289 i64::MAX,
290 i64::MIN
291 ))
292 .unwrap();
293
294 let res = mat.sort_by_row(MatZ::norm_infty).unwrap();
295
296 assert_eq!(cmp, res);
297 }
298
299 /// Checks whether sorting by rows length acc. to eucl. norm works properly
300 /// for matrices with a few more entries
301 #[test]
302 fn many_rows() {
303 let mat = MatZ::from_str("[[3],[0],[-1],[-7],[2],[9],[4],[8],[6],[5]]").unwrap();
304 let cmp = MatZ::from_str("[[0],[-1],[2],[3],[4],[5],[6],[-7],[8],[9]]").unwrap();
305
306 let res = mat.sort_by_row(MatZ::norm_eucl_sqrd).unwrap();
307
308 assert_eq!(cmp, res);
309 }
310
311 /// Checks whether an error is returned for sorting by rows if the `cond_func` returns an error
312 #[test]
313 fn row_error_cond_func() {
314 let mat = MatZ::from_str("[[1, 2],[3, 4]]").unwrap();
315
316 let res = mat.sort_by_row(failing_func);
317
318 assert!(res.is_err());
319 }
320}