qfall_math/integer_mod_q/mat_zq/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 [`MatZq`].
10
11use super::MatZq;
12use crate::{
13 error::MathError,
14 traits::{MatrixDimensions, MatrixGetSubmatrix, MatrixSetSubmatrix},
15};
16
17impl MatZq {
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_mod_q::MatZq;
33 /// use std::str::FromStr;
34 /// let mat = MatZq::from_str("[[3, 2, 1]] mod 7").unwrap();
35 /// let cmp = MatZq::from_str("[[1, 2, 3]] mod 7").unwrap();
36 ///
37 /// let sorted = mat.sort_by_column(MatZq::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_mod_q::MatZq, integer::Z, error::MathError, traits::{MatrixDimensions, MatrixGetEntry}};
45 /// use std::str::FromStr;
46 /// let mat = MatZq::from_str("[[3, 2, 1]] mod 7").unwrap();
47 /// let cmp = MatZq::from_str("[[1, 2, 3]] mod 7").unwrap();
48 ///
49 /// fn custom_cond_func(matrix: &MatZq) -> Result<Z, MathError> {
50 /// let mut sum = Z::ZERO;
51 /// for entry in MatrixGetEntry::<Z>::get_entries_rowwise(matrix){
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(), self.get_mod());
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_mod_q::MatZq;
100 /// use std::str::FromStr;
101 /// let mat = MatZq::from_str("[[3],[2],[1]] mod 7").unwrap();
102 /// let cmp = MatZq::from_str("[[1],[2],[3]] mod 7").unwrap();
103 ///
104 /// let sorted = mat.sort_by_row(MatZq::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_mod_q::MatZq, integer::Z, error::MathError, traits::{MatrixDimensions, MatrixGetEntry}};
112 /// use std::str::FromStr;
113 /// let mat = MatZq::from_str("[[3],[2],[1]] mod 7").unwrap();
114 /// let cmp = MatZq::from_str("[[1],[2],[3]] mod 7").unwrap();
115 ///
116 /// fn custom_cond_func(matrix: &MatZq) -> Result<Z, MathError> {
117 /// let mut sum = Z::ZERO;
118 /// for entry in MatrixGetEntry::<Z>::get_entries_rowwise(matrix){
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(), self.get_mod());
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::MatZq;
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: &MatZq) -> 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 = MatZq::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]] mod 7").unwrap();
175 let cmp = MatZq::from_str("[[0, -1, 2, 3],[2, 2, 2, 2]] mod 7").unwrap();
176
177 let res = mat.sort_by_column(MatZq::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 = MatZq::from_str(&format!(
186 "[[{}, {}, 5],[1, 2, 5],[0, 0, 0]] mod {}",
187 i64::MIN,
188 i64::MAX,
189 u64::MAX
190 ))
191 .unwrap();
192 let cmp = MatZq::from_str(&format!(
193 "[[5, {}, {}],[5, 1, 2],[0, 0, 0]] mod {}",
194 i64::MAX,
195 i64::MIN,
196 u64::MAX
197 ))
198 .unwrap();
199
200 let res = mat.sort_by_column(MatZq::norm_eucl_sqrd).unwrap();
201
202 assert_eq!(cmp, res);
203 }
204
205 /// Checks whether sorting by column length acc. to infty norm works correct for large entries
206 #[test]
207 fn column_norm_infty_large_entries() {
208 let mat = MatZq::from_str(&format!(
209 "[[{}, {}, 5],[1, 2, 5],[0, 0, 0]] mod {}",
210 i64::MIN,
211 i64::MAX,
212 u64::MAX
213 ))
214 .unwrap();
215 let cmp = MatZq::from_str(&format!(
216 "[[5, {}, {}],[5, 1, 2],[0, 0, 0]] mod {}",
217 i64::MAX,
218 i64::MIN,
219 u64::MAX
220 ))
221 .unwrap();
222
223 let res = mat.sort_by_column(MatZq::norm_infty).unwrap();
224
225 assert_eq!(cmp, res);
226 }
227
228 /// Checks whether sorting by columns length acc. to eucl. norm works properly
229 /// for matrices with a few more entries
230 #[test]
231 fn many_columns() {
232 let mat = MatZq::from_str("[[3, 4, 1, 7, 2, 0, 9, -8, 6, 5]] mod 19").unwrap();
233 let cmp = MatZq::from_str("[[0, 1, 2, 3, 4, 5, 6, 7, -8, 9]] mod 19").unwrap();
234
235 let res = mat.sort_by_column(MatZq::norm_eucl_sqrd).unwrap();
236
237 assert_eq!(cmp, res);
238 }
239
240 /// Checks whether an error is returned for sorting by columns if the `cond_func` returns an error
241 #[test]
242 fn column_error_cond_func() {
243 let mat = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
244
245 let res = mat.sort_by_column(failing_func);
246
247 assert!(res.is_err());
248 }
249
250 /// Checks whether sorting by row length acc. to eucl. norm works correct for small entries
251 #[test]
252 fn row_norm_eucl_sqrd_small_entries() {
253 let mat = MatZq::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]] mod 7").unwrap();
254 let cmp = MatZq::from_str("[[3, 0, 2, -1],[2, 2, 2, 2]] mod 7").unwrap();
255
256 let res = mat.sort_by_row(MatZq::norm_eucl_sqrd).unwrap();
257
258 assert_eq!(cmp, res);
259 }
260
261 /// Checks whether sorting by row length acc. to eucl. norm works correct for large entries
262 #[test]
263 fn row_norm_eucl_sqrd_large_entries() {
264 let mat = MatZq::from_str(&format!(
265 "[[{}, 0, 5],[{}, 2, 5],[0, 0, 0]] mod {}",
266 i64::MIN,
267 i64::MAX,
268 u64::MAX
269 ))
270 .unwrap();
271 let cmp = MatZq::from_str(&format!(
272 "[[0, 0, 0],[{}, 0, 5],[{}, 2, 5]] mod {}",
273 i64::MAX,
274 i64::MIN,
275 u64::MAX
276 ))
277 .unwrap();
278
279 let res = mat.sort_by_row(MatZq::norm_eucl_sqrd).unwrap();
280
281 assert_eq!(cmp, res);
282 }
283
284 /// Checks whether sorting by row length acc. to infty norm works correct for large entries
285 #[test]
286 fn row_norm_infty_large_entries() {
287 let mat = MatZq::from_str(&format!(
288 "[[{}, 0, 5],[{}, 2, 5],[0, 0, 0]] mod {}",
289 i64::MIN,
290 i64::MAX,
291 u64::MAX
292 ))
293 .unwrap();
294 let cmp = MatZq::from_str(&format!(
295 "[[0, 0, 0],[{}, 0, 5],[{}, 2, 5]] mod {}",
296 i64::MAX,
297 i64::MIN,
298 u64::MAX
299 ))
300 .unwrap();
301
302 let res = mat.sort_by_row(MatZq::norm_infty).unwrap();
303
304 assert_eq!(cmp, res);
305 }
306
307 /// Checks whether sorting by rows length acc. to eucl. norm works properly
308 /// for matrices with a few more entries
309 #[test]
310 fn many_rows() {
311 let mat = MatZq::from_str("[[3],[0],[-1],[-7],[2],[9],[4],[8],[6],[5]] mod 19").unwrap();
312 let cmp = MatZq::from_str("[[0],[-1],[2],[3],[4],[5],[6],[-7],[8],[9]] mod 19").unwrap();
313
314 let res = mat.sort_by_row(MatZq::norm_eucl_sqrd).unwrap();
315
316 assert_eq!(cmp, res);
317 }
318
319 /// Checks whether an error is returned for sorting by rows if the `cond_func` returns an error
320 #[test]
321 fn row_error_cond_func() {
322 let mat = MatZq::from_str("[[1, 2],[3, 4]] mod 7").unwrap();
323
324 let res = mat.sort_by_row(failing_func);
325
326 assert!(res.is_err());
327 }
328}