qfall_math/integer_mod_q/mat_zq/
to_string.rs

1// Copyright © 2023 Marcel Luca Schmidt
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//! This module contains all options to convert a matrix of type
10//! [`MatZq`] into a [`String`].
11//!
12//! This includes the [`Display`](std::fmt::Display) trait.
13
14use super::MatZq;
15use crate::{
16    integer::Z,
17    macros::for_others::implement_for_owned,
18    traits::{MatrixDimensions, MatrixGetEntry},
19    utils::parse::matrix_to_string,
20};
21use core::fmt;
22use std::string::FromUtf8Error;
23
24impl From<&MatZq> for String {
25    /// Converts a [`MatZq`] into its [`String`] representation.
26    ///
27    /// Parameters:
28    /// - `value`: specifies the matrix that will be represented as a [`String`]
29    ///
30    /// Returns a [`String`] of the form `"[[row_0],[row_1],...[row_n]] mod q"`.
31    ///
32    /// # Examples
33    /// ```
34    /// use qfall_math::integer_mod_q::MatZq;
35    /// use std::str::FromStr;
36    /// let matrix = MatZq::from_str("[[6, 1],[5, 2]] mod 4").unwrap();
37    ///
38    /// let string: String = matrix.into();
39    /// ```
40    fn from(value: &MatZq) -> Self {
41        value.to_string()
42    }
43}
44
45implement_for_owned!(MatZq, String, From);
46
47impl fmt::Display for MatZq {
48    /// Allows to convert a matrix of type [`MatZq`] into a [`String`].
49    ///
50    /// Returns the Matrix in form of a [`String`]. For matrix `[[1, 2, 3],[4, 5, 6]] mod 4`
51    /// the String looks like this `[[1, 2, 3],[0, 1, 2]] mod 4`.
52    ///
53    /// # Examples
54    /// ```
55    /// use qfall_math::integer_mod_q::MatZq;
56    /// use core::fmt;
57    /// use std::str::FromStr;
58    ///
59    /// let matrix = MatZq::from_str("[[1, 2, 3],[4, 5, 6]] mod 4").unwrap();
60    /// println!("{matrix}");
61    /// ```
62    ///
63    /// ```
64    /// use qfall_math::integer_mod_q::MatZq;
65    /// use core::fmt;
66    /// use std::str::FromStr;
67    ///
68    /// let matrix = MatZq::from_str("[[1, 2, 3],[4, 5, 6]] mod 4").unwrap();
69    /// let matrix_string = matrix.to_string();
70    /// ```
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        let matrix = matrix_to_string::<Z, MatZq>(self);
73        write!(f, "{matrix} mod {}", self.get_mod())
74    }
75}
76
77impl MatZq {
78    /// Enables conversion to a UTF8-Encoded [`String`] for [`MatZq`] values.
79    /// Every entry is padded with `00`s s.t. all entries contain the same number of bytes.
80    /// Afterwards, they are appended row-by-row and converted.
81    /// The inverse to this function is [`MatZq::from_utf8`] for valid UTF8-Encodings.
82    ///
83    /// **Warning**: Not every byte-sequence forms a valid UTF8-Encoding.
84    /// In these cases, an error is returned. Please check the format of your message again.
85    /// The matrix entries are evaluated row by row, i.e. in the order of the output of `mat_zq.to_string()`.
86    ///
87    /// Returns the corresponding UTF8-encoded [`String`] or a
88    /// [`FromUtf8Error`] if the byte sequence contains an invalid UTF8-character.
89    ///
90    /// # Examples
91    /// ```
92    /// use qfall_math::integer::MatZ;
93    /// use std::str::FromStr;
94    /// let matrix = MatZ::from_str("[[104, 101, 108],[108, 111, 33]]").unwrap();
95    ///
96    /// let message = matrix.to_utf8().unwrap();
97    ///
98    /// assert_eq!("hello!", message);
99    /// ```
100    ///
101    /// # Errors and Failures
102    /// - Returns a [`FromUtf8Error`] if the integer's byte sequence contains
103    ///   invalid UTF8-characters.
104    pub fn to_utf8(&self) -> Result<String, FromUtf8Error> {
105        let mut byte_vectors: Vec<Vec<u8>> =
106            Vec::with_capacity((self.get_num_rows() * self.get_num_columns()) as usize);
107        let mut max_length = 0;
108
109        // Fill byte vector
110        for row in 0..self.get_num_rows() as usize {
111            for col in 0..self.get_num_columns() as usize {
112                let entry_value: Z = unsafe { self.get_entry_unchecked(row as i64, col as i64) };
113                let entry_bytes = entry_value.to_bytes();
114
115                // Find maximum length of bytes in one entry of the matrix
116                if max_length < entry_bytes.len() {
117                    max_length = entry_bytes.len();
118                }
119
120                byte_vectors.push(entry_bytes);
121            }
122        }
123
124        // Pad every entry to the same length with `0`s
125        // to ensure any matrix given a string provides the same matrix
126        // and append them in the same iteration
127        let mut bytes = Vec::with_capacity(byte_vectors.len() * max_length);
128        for mut byte_vector in byte_vectors {
129            // 0 encodes a control character �, which can be followed by anything
130            // Hence, this might change the encoding of any trailing sequences
131            byte_vector.resize(max_length, 0u8);
132
133            bytes.append(&mut byte_vector);
134        }
135
136        String::from_utf8(bytes)
137    }
138}
139
140impl MatZq {
141    /// Outputs the matrix as a [`String`], where the upper leftmost `nr_printed_rows x nr_printed_columns`
142    /// submatrix is output entirely as well as the corresponding entries in the last column and row of the matrix.
143    ///
144    /// Parameters:
145    /// - `nr_printed_rows`: defines the number of rows of the upper leftmost matrix that are printed entirely
146    /// - `nr_printed_columns`: defines the number of columns of the upper leftmost matrix that are printed entirely
147    ///
148    /// Returns a [`String`] representing the abbreviated matrix.
149    ///
150    /// # Example
151    /// ```
152    /// use qfall_math::integer::MatZ;
153    /// let matrix = MatZ::identity(10, 10);
154    ///
155    /// println!("Matrix: {}", matrix.pretty_string(2, 2));
156    /// // outputs the following:
157    /// // Matrix: [
158    /// //   [1, 0, , ..., 0],
159    /// //   [0, 1, , ..., 0],
160    /// //   [...],
161    /// //   [0, 0, , ..., 1]
162    /// // ]
163    /// ```
164    pub fn pretty_string(&self, nr_printed_rows: u64, nr_printed_columns: u64) -> String {
165        let mut result = crate::utils::parse::partial_string(
166            &self.get_representative_least_nonnegative_residue(),
167            nr_printed_rows,
168            nr_printed_columns,
169        );
170        result.push_str(&format!(" mod {}", self.modulus));
171        result
172    }
173}
174
175#[cfg(test)]
176mod test_to_string {
177    use crate::integer_mod_q::MatZq;
178    use std::str::FromStr;
179
180    /// Tests whether a matrix with a large entry works in a roundtrip
181    #[test]
182    fn working_large_positive() {
183        let cmp = MatZq::from_str(&format!(
184            "[[{}, 1, 3],[5, 6, 7]] mod {}",
185            u64::MAX - 1,
186            u64::MAX
187        ))
188        .unwrap();
189
190        assert_eq!(
191            format!("[[{}, 1, 3],[5, 6, 7]] mod {}", u64::MAX - 1, u64::MAX),
192            cmp.to_string()
193        )
194    }
195
196    /// Tests whether a matrix with a large negative entry works in a roundtrip
197    #[test]
198    fn working_large_negative() {
199        let cmp = MatZq::from_str(&format!(
200            "[[-{}, 1, 3],[5, 6, 7]] mod {}",
201            u64::MAX - 1,
202            u64::MAX
203        ))
204        .unwrap();
205
206        assert_eq!(
207            format!("[[1, 1, 3],[5, 6, 7]] mod {}", u64::MAX),
208            cmp.to_string()
209        )
210    }
211
212    /// Tests whether a matrix with positive entries works in a roundtrip
213    #[test]
214    fn working_positive() {
215        let cmp = MatZq::from_str("[[2, 1, 3],[5, 6, 7]] mod 4").unwrap();
216
217        assert_eq!("[[2, 1, 3],[1, 2, 3]] mod 4", cmp.to_string());
218    }
219
220    /// Tests whether a matrix with negative entries works in a roundtrip
221    #[test]
222    fn working_negative() {
223        let cmp = MatZq::from_str("[[-2, 1, 3],[5, -6, 7]] mod 4").unwrap();
224
225        assert_eq!("[[2, 1, 3],[1, 2, 3]] mod 4", cmp.to_string());
226    }
227
228    /// Tests whether a matrix with a large modulus works in a roundtrip
229    #[test]
230    fn working_large_modulus() {
231        let cmp = MatZq::from_str(&format!("[[1, 1, 3],[5, 6, 7]] mod {}", u64::MAX)).unwrap();
232
233        assert_eq!(
234            format!("[[1, 1, 3],[5, 6, 7]] mod {}", u64::MAX),
235            cmp.to_string()
236        )
237    }
238
239    /// Tests whether a large matrix works in a roundtrip
240    #[test]
241    fn working_large_dimensions() {
242        let cmp_1 =
243            MatZq::from_str(&format!("[{}[5, 6, 7]] mod 4", "[1, 2, 3],".repeat(99))).unwrap();
244        let cmp_2 = MatZq::from_str(&format!("[[{}1]] mod 4", "1, ".repeat(99))).unwrap();
245
246        assert_eq!(
247            format!("[{}[1, 2, 3]] mod 4", "[1, 2, 3],".repeat(99)),
248            cmp_1.to_string()
249        );
250        assert_eq!(
251            format!("[[{}1]] mod 4", "1, ".repeat(99)),
252            cmp_2.to_string()
253        );
254    }
255
256    /// Tests whether a matrix that is created using a string, returns a
257    /// string that can be used to create a [`MatZq`]
258    #[test]
259    fn working_use_result_of_to_string_as_input() {
260        let cmp = MatZq::from_str("[[-2, 1, 3],[5, -6, 7]] mod 4").unwrap();
261
262        let cmp_str_2 = cmp.to_string();
263
264        assert!(MatZq::from_str(&cmp_str_2).is_ok());
265    }
266
267    /// Ensures that the `Into<String>` trait works properly
268    #[test]
269    fn into_works_properly() {
270        let cmp = "[[6, 1, 3],[5, 2, 7]] mod 8";
271        let matrix = MatZq::from_str(cmp).unwrap();
272
273        let string: String = matrix.clone().into();
274        let borrowed_string: String = (&matrix).into();
275
276        assert_eq!(cmp, string);
277        assert_eq!(cmp, borrowed_string);
278    }
279}
280
281#[cfg(test)]
282mod test_to_utf8 {
283    use crate::integer_mod_q::MatZq;
284    use std::str::FromStr;
285
286    /// Ensures that [`MatZq::to_utf8`] is inverse to [`MatZq::from_utf8`].
287    #[test]
288    fn inverse_of_from_utf8() {
289        let message = "some_random_string_1-9A-Z!?-_;:#";
290
291        let matrix = MatZq::from_utf8(message, 8, 4, 256).unwrap();
292
293        let string = matrix.to_utf8().unwrap();
294
295        assert_eq!(message, string);
296    }
297
298    /// Ensures that [`MatZq::from_utf8`] is inverse to [`MatZq::to_utf8`].
299    #[test]
300    fn inverse_to_from_utf8() {
301        let matrix_cmp_w_padding =
302            MatZq::from_str("[[104, 101, 108],[28524, 48, 48]] mod 256").unwrap();
303        let matrix_cmp_wo_padding =
304            MatZq::from_str("[[104, 101],[108, 108],[111, 33]] mod 256").unwrap();
305
306        let string_w_padding = matrix_cmp_w_padding.to_utf8().unwrap();
307        let string_wo_padding = matrix_cmp_wo_padding.to_utf8().unwrap();
308
309        let matrix_w_padding = MatZq::from_utf8(&string_w_padding, 2, 3, 256).unwrap();
310        let matrix_wo_padding = MatZq::from_utf8(&string_wo_padding, 3, 2, 256).unwrap();
311
312        assert_eq!(matrix_cmp_w_padding, matrix_w_padding);
313        assert_eq!(matrix_cmp_wo_padding, matrix_wo_padding);
314    }
315
316    /// Ensures that [`MatZq::to_utf8`] is inverse to [`MatZq::from_utf8`]
317    /// and padding is applied if necessary.
318    #[test]
319    fn inverse_incl_padding() {
320        let message = "some_random_string_1-9A-Z!?-_;";
321        let cmp_text = "some_random_string_1-9A-Z!?-_;00";
322
323        let matrix = MatZq::from_utf8(message, 4, 8, 256).unwrap();
324
325        let string = matrix.to_utf8().unwrap();
326
327        assert_eq!(cmp_text, string);
328    }
329
330    /// Ensures that [`MatZq::to_utf8`] outputs an error
331    /// if the integer contains an invalid UTF8-Encoding.
332    #[test]
333    fn invalid_encoding() {
334        // 128 is an invalid UTF8-character (at least at the end and on its own)
335        let matrix = MatZq::from_str("[[1,2],[3,128]] mod 256").unwrap();
336        let string = matrix.to_utf8();
337
338        assert!(string.is_err());
339    }
340}