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}