qfall_math/integer/mat_z/
from.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//! Implementations to create a [`MatZ`] matrix from other types.
10//!
11//! The explicit functions contain the documentation.
12
13use super::MatZ;
14use crate::{
15    error::MathError,
16    integer::Z,
17    traits::{MatrixDimensions, MatrixSetEntry},
18    utils::{
19        dimensions::find_matrix_dimensions,
20        index::evaluate_indices,
21        parse::{matrix_from_utf8_fill_bytes, parse_matrix_string},
22    },
23};
24use std::{fmt::Display, str::FromStr};
25
26impl FromStr for MatZ {
27    type Err = MathError;
28
29    /// Creates a [`MatZ`] matrix with entries in [`Z`] from a [`String`].
30    ///
31    /// Parameters:
32    /// - `string`: the matrix of form: `"[[1, 2, 3],[4, 5, 6]]"`
33    ///   for a 2x3 matrix with entries 1, 2, 3 in the first row and 4, 5, 6
34    ///   in the second row.
35    ///
36    /// Returns a [`MatZ`] or an error if the matrix is not formatted in a suitable way,
37    /// the number of rows or columns is too large (must fit into [`i64`]),
38    /// the number of entries in rows is unequal or if an entry is not formatted correctly.
39    ///
40    /// # Examples
41    /// ```
42    /// use qfall_math::integer::MatZ;
43    /// use std::str::FromStr;
44    ///
45    /// let string = String::from("[[1, 2, 3],[3, 4, 5]]");
46    /// let matrix = MatZ::from_str(&string).unwrap();
47    /// ```
48    ///
49    /// # Errors and Failures
50    /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
51    ///     - if the matrix is not formatted in a suitable way,
52    ///     - if the number of rows or columns is too large (must fit into i64),
53    ///     - if the number of entries in rows is unequal, or
54    ///     - if an entry is not formatted correctly.
55    ///       For further information see [`Z::from_str`].
56    ///
57    /// # Panics ...
58    /// - if the provided number of rows and columns are not suited to create a matrix.
59    ///   For further information see [`MatZ::new`].
60    fn from_str(string: &str) -> Result<Self, MathError> {
61        let string_matrix = parse_matrix_string(string)?;
62        let (num_rows, num_cols) = find_matrix_dimensions(&string_matrix)?;
63        let mut matrix = MatZ::new(num_rows, num_cols);
64
65        // fill entries of matrix according to entries in string_matrix
66        for (row_num, row) in string_matrix.iter().enumerate() {
67            for (col_num, entry) in row.iter().enumerate() {
68                let z_entry = Z::from_str(entry)?;
69                matrix.set_entry(row_num, col_num, z_entry)?;
70            }
71        }
72        Ok(matrix)
73    }
74}
75
76impl From<&MatZ> for MatZ {
77    /// Alias for [`MatZ::clone`].
78    fn from(value: &MatZ) -> Self {
79        value.clone()
80    }
81}
82
83impl MatZ {
84    /// Create a [`MatZ`] from a [`String`], i.e. its UTF8-Encoding.
85    /// This function can only construct positive or zero integers, but not negative ones.
86    /// If the number of bytes and number of entries does not line up, we pad the message
87    /// with `'0'`s.
88    /// The inverse of this function is [`MatZ::to_utf8`].
89    ///
90    /// Parameters:
91    /// - `message`: specifies the message that is transformed via its UTF8-Encoding
92    ///   to a new [`MatZ`] instance.
93    /// - `num_rows`: number of rows the new matrix should have
94    /// - `num_cols`: number of columns the new matrix should have
95    ///
96    /// Returns a [`MatZ`] with corresponding entries to the message's UTF8-Encoding.
97    ///
98    /// # Examples
99    /// ```
100    /// use qfall_math::integer::MatZ;
101    /// let message = "hello!";
102    ///  
103    /// let matrix = MatZ::from_utf8(&message, 2, 1);
104    /// ```
105    ///
106    /// # Panics ...
107    /// - if the provided number of rows and columns are not suited to create a matrix.
108    ///   For further information see [`MatZ::new`].
109    pub fn from_utf8(
110        message: &str,
111        num_rows: impl TryInto<i64> + Display,
112        num_cols: impl TryInto<i64> + Display,
113    ) -> Self {
114        let (num_rows_i64, num_cols_i64) = evaluate_indices(num_rows, num_cols).unwrap();
115        let mut mat = MatZ::new(num_rows_i64, num_cols_i64);
116        let num_columns = mat.get_num_columns() as usize;
117        let nr_entries = mat.get_num_rows() as usize * num_columns;
118
119        // This error can't be triggered as no modulus is provided.
120        let (byte_vector, nr_bytes_per_entry) = matrix_from_utf8_fill_bytes(message, nr_entries);
121
122        // Fill rows going from left to right, entry by entry
123        for row in 0..mat.get_num_rows() as usize {
124            let offset_row = row * num_columns * nr_bytes_per_entry;
125            for col in 0..num_columns {
126                let entry_value = Z::from_bytes(
127                    &byte_vector[offset_row + nr_bytes_per_entry * col
128                        ..offset_row + nr_bytes_per_entry * (col + 1)],
129                );
130                unsafe { mat.set_entry_unchecked(row as i64, col as i64, entry_value) };
131            }
132        }
133
134        mat
135    }
136}
137
138#[cfg(test)]
139mod test_from_str {
140    use crate::{
141        integer::{MatZ, Z},
142        traits::MatrixGetEntry,
143    };
144    use std::str::FromStr;
145
146    /// Ensure that initialization works.
147    #[test]
148    fn init_works() {
149        let matrix_str = "[[1, 2, 3],[3, 4, 5]]";
150
151        assert_eq!(
152            Z::ONE,
153            MatZ::from_str(matrix_str).unwrap().get_entry(0, 0).unwrap()
154        );
155    }
156
157    /// Ensure that initialization with positive numbers that are larger than [`i64`] works.
158    #[test]
159    fn init_works_large_numbers() {
160        let matrix_string = format!("[[{}, 2, 3],[3, 4, 5]]", u64::MAX);
161
162        assert_eq!(
163            Z::from(u64::MAX),
164            MatZ::from_str(&matrix_string)
165                .unwrap()
166                .get_entry(0, 0)
167                .unwrap()
168        );
169    }
170
171    /// Ensure that initialization with negative numbers that are larger than [`i64`] works.
172    #[test]
173    fn init_works_small_numbers() {
174        let matrix_string = format!("[[-{}, 2, 3],[3, 4, 5]]", u64::MAX);
175
176        let entry = format!("-{}", u64::MAX);
177
178        assert_eq!(
179            Z::from_str(&entry).unwrap(),
180            MatZ::from_str(&matrix_string)
181                .unwrap()
182                .get_entry(0, 0)
183                .unwrap()
184        );
185    }
186
187    /// Ensure that entries can have leading and trailing whitespaces.
188    #[test]
189    fn whitespaces_in_entries_works() {
190        let matrix_str = "[[  1, 2 ,  3  ],[3 , 4, 5 ]]";
191
192        assert_eq!(
193            Z::ONE,
194            MatZ::from_str(matrix_str).unwrap().get_entry(0, 0).unwrap()
195        );
196    }
197
198    /// Ensure that a wrong format causes an error.
199    #[test]
200    fn wrong_format_error() {
201        let matrix_str_1 = "[1, 2, 3],[3, 4, 5]]";
202        let matrix_str_2 = "[[1, 2, 3][3, 4, 5]]";
203        let matrix_str_3 = "[[1, 2, 3], 3, 4, 5]";
204        let matrix_str_4 = "[1, 2, 3, 4, 5]";
205        let matrix_str_5 = "[ [1, 2, 3],[3, 4, 5]]";
206        let matrix_str_6 = "[[1, 2, 3],[3, 4, 5]8]";
207        let matrix_str_7 = "";
208        let matrix_str_8 = "[]";
209        let matrix_str_9 = "[[]]";
210
211        assert!(MatZ::from_str(matrix_str_1).is_err());
212        assert!(MatZ::from_str(matrix_str_2).is_err());
213        assert!(MatZ::from_str(matrix_str_3).is_err());
214        assert!(MatZ::from_str(matrix_str_4).is_err());
215        assert!(MatZ::from_str(matrix_str_5).is_err());
216        assert!(MatZ::from_str(matrix_str_6).is_err());
217        assert!(MatZ::from_str(matrix_str_7).is_err());
218        assert!(MatZ::from_str(matrix_str_8).is_err());
219        assert!(MatZ::from_str(matrix_str_9).is_err());
220    }
221}
222
223#[cfg(test)]
224/// Test the implementation of [`MatZ::from_utf8`] briefly.
225/// This module omits tests that were already provided for [`Z::from_bytes`]
226/// and [`crate::utils::parse::matrix_from_utf8_fill_bytes`].
227mod test_from_utf8 {
228    use super::{MatZ, Z};
229    use crate::traits::MatrixGetEntry;
230    use std::str::FromStr;
231
232    /// Ensures that a wide range of (special) characters are transformed correctly.
233    #[test]
234    fn characters() {
235        let message = "flag{text#1234567890! a_zA-Z$€?/:;,.<>+*}";
236
237        let matrix = MatZ::from_utf8(message, 1, 1);
238        let value = matrix.get_entry(0, 0).unwrap();
239
240        // easy trick s.t. we don't have to initialize a huge [`Z`] value
241        // while this test should still fail if the value changes
242        let value_zq = value % 65537;
243
244        assert_eq!(Z::from(58285), value_zq);
245    }
246
247    /// Ensure that the empty string results in a zero value.
248    #[test]
249    fn empty_string() {
250        let message = "";
251
252        let matrix = MatZ::from_utf8(message, 1, 1);
253        let value = matrix.get_entry(0, 0).unwrap();
254
255        assert_eq!(Z::ZERO, value);
256    }
257
258    /// Ensures correct conversion of bytes and their order.
259    #[test]
260    fn conversion_and_order() {
261        let message = "{10_chars}";
262        let cmp_matrix = MatZ::from_str("[[12667, 24368],[26723, 29281],[32115, 12336]]").unwrap();
263
264        let matrix = MatZ::from_utf8(message, 3, 2);
265
266        assert_eq!(cmp_matrix, matrix);
267    }
268}