qfall_math/rational/poly_over_q/
from.rs

1// Copyright © 2023 Marvin Beckmann
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 [`PolyOverQ`] value from other types.
10//!
11//! The explicit functions contain the documentation.
12
13use super::PolyOverQ;
14use crate::{
15    error::{MathError, StringConversionError},
16    integer::PolyOverZ,
17    macros::for_others::implement_for_owned,
18    rational::Q,
19};
20use flint_sys::fmpq_poly::{
21    fmpq_poly_canonicalise, fmpq_poly_set_fmpq, fmpq_poly_set_fmpz_poly, fmpq_poly_set_str,
22};
23use std::{ffi::CString, str::FromStr};
24
25impl FromStr for PolyOverQ {
26    type Err = MathError;
27
28    /// Creates a polynomial with arbitrarily many coefficients of type [`Q`].
29    ///
30    /// Parameters:
31    /// - `s`: the polynomial of form: "`[#number of coefficients]⌴⌴[0th coefficient]⌴[1st coefficient]⌴...`"
32    ///
33    /// Note that the `[#number of coefficients]` and `[0th coefficient]`
34    /// are divided by two spaces and the input string is trimmed, i.e. all whitespaces
35    /// before and after are ignored.
36    ///
37    /// Returns a [`PolyOverQ`] or an error if the provided string was not formatted
38    /// correctly, the number of coefficients was smaller than the number provided at the
39    /// start of the provided string, or the provided string contains a `Null` Byte.
40    ///
41    /// # Examples
42    /// ```
43    /// use qfall_math::rational::PolyOverQ;
44    /// use std::str::FromStr;
45    ///
46    /// let poly = PolyOverQ::from_str("5  0 1/3 2/10 -3/2 1").unwrap();
47    /// ```
48    /// # Errors and Failures
49    /// - Returns a [`MathError`] of type
50    ///   [`StringConversionError`](MathError::StringConversionError)
51    ///     - if the provided string was not formatted correctly,
52    ///     - if the number of coefficients was smaller than the number provided
53    ///       at the start of the provided string,
54    ///     - if the provided value did not contain two whitespaces, or
55    ///     - if the provided string contains a `Null` Byte.
56    fn from_str(s: &str) -> Result<Self, Self::Err> {
57        let mut res = Self::default();
58
59        let c_string = CString::new(s.trim())?;
60
61        // `0` is returned if the string is a valid input
62        // additionally if it was not successfully, test if the provided value 's' actually
63        // contains two whitespaces, since this might be a common error
64        match unsafe { fmpq_poly_set_str(&mut res.poly, c_string.as_ptr()) } {
65            0 => unsafe {
66                // set_str assumes that all coefficients are reduced as far as possible,
67                // hence we have to reduce manually
68                fmpq_poly_canonicalise(&mut res.poly);
69                Ok(res)
70            },
71            _ if !s.contains("  ") => Err(
72                StringConversionError::InvalidStringToPolyMissingWhitespace(s.to_owned()),
73            )?,
74            _ => Err(StringConversionError::InvalidStringToPolyInput(
75                s.to_owned(),
76            ))?,
77        }
78    }
79}
80
81impl From<&PolyOverZ> for PolyOverQ {
82    /// Creates a [`PolyOverQ`] from a [`PolyOverZ`].
83    ///
84    /// Parameters:
85    /// - `poly`: the polynomial from which the coefficients are copied
86    ///
87    /// # Examples
88    /// ```
89    /// use qfall_math::integer::PolyOverZ;
90    /// use qfall_math::rational::PolyOverQ;
91    /// use std::str::FromStr;
92    ///
93    /// let poly = PolyOverZ::from_str("4  0 1 102 3").unwrap();
94    ///
95    /// let poly_q = PolyOverQ::from(&poly);
96    ///
97    /// # let cmp_poly = PolyOverQ::from_str("4  0 1 102 3").unwrap();
98    /// # assert_eq!(cmp_poly, poly_q);
99    /// ```
100    fn from(poly: &PolyOverZ) -> Self {
101        let mut out = Self::default();
102        unsafe { fmpq_poly_set_fmpz_poly(&mut out.poly, &poly.poly) };
103        out
104    }
105}
106
107implement_for_owned!(PolyOverZ, PolyOverQ, From);
108
109impl<Rational: Into<Q>> From<Rational> for PolyOverQ {
110    /// Creates a constant [`PolyOverQ`] with a specified rational constant.
111    ///
112    /// Parameters:
113    /// - `value`: the constant value the polynomial will have. It has to be a rational
114    ///   number like [`Q`], an integer or a tuple of integers `(numerator, denominator)`.
115    ///
116    /// Returns a new constant polynomial with the specified value.
117    ///
118    /// # Examples
119    /// ```
120    /// use qfall_math::{rational::*, traits::GetCoefficient};
121    ///
122    /// let one = PolyOverQ::from(1);
123    /// let three_quarter = PolyOverQ::from(Q::from((3, 4)));
124    /// let one_half = PolyOverQ::from((1, 2));
125    ///
126    /// assert_eq!(one_half.get_coeff(0).unwrap(), Q::from((1, 2)));
127    /// assert_eq!(one_half.get_degree(), 0);
128    /// ```
129    ///
130    /// # Panics ...
131    /// - if the provided value can not be converted into a [`Q`].
132    ///   For example, because of a division by zero.
133    fn from(value: Rational) -> Self {
134        let mut out = PolyOverQ::default();
135        let value: Q = value.into();
136
137        unsafe {
138            fmpq_poly_set_fmpq(&mut out.poly, &value.value);
139        }
140        out
141    }
142}
143
144impl From<&PolyOverQ> for PolyOverQ {
145    /// Alias for [`PolyOverQ::clone`].
146    fn from(value: &PolyOverQ) -> Self {
147        value.clone()
148    }
149}
150
151#[cfg(test)]
152mod test_from_str {
153    use super::PolyOverQ;
154    use std::str::FromStr;
155
156    /// Ensure that zero-coefficients are reduced
157    #[test]
158    fn reduce_zero_coeff() {
159        let one_1 = PolyOverQ::from_str("2  24/42 1").unwrap();
160        let one_2 = PolyOverQ::from_str("3  24/42 1 0").unwrap();
161
162        assert_eq!(one_1, one_2);
163    }
164
165    /// Ensure that coefficients in the string are reduced
166    #[test]
167    fn reduce_coeff() {
168        assert_eq!(
169            PolyOverQ::from_str("3  4/77 4/14 -28/21").unwrap(),
170            PolyOverQ::from_str("3  4/77 2/7 -28/21").unwrap()
171        );
172    }
173
174    /// Tests whether the same string yields the same polynomial
175    #[test]
176    fn same_string() {
177        let str_1 = format!("3  1 2/3 {}/{}", u64::MAX, i64::MIN);
178
179        let poly_1 = PolyOverQ::from_str(&str_1).unwrap();
180        let poly_2 = PolyOverQ::from_str(&str_1).unwrap();
181
182        assert_eq!(poly_1, poly_2);
183    }
184
185    /// Tests whether a correctly formatted string outputs an instantiation of a
186    /// polynomial, i.e. does not return an error
187    #[test]
188    fn working_example() {
189        assert!(PolyOverQ::from_str("3  1 2/5 -3/2").is_ok());
190    }
191
192    /// Tests whether a falsely formatted string (missing double-space) returns
193    /// an error
194    #[test]
195    fn missing_whitespace() {
196        assert!(PolyOverQ::from_str("3 1 2/5 -3/2").is_err());
197        assert!(PolyOverQ::from_str("3 12/5 2 -3").is_err());
198        assert!(PolyOverQ::from_str("2 17 42/4").is_err());
199        assert!(PolyOverQ::from_str("2 17 42").is_err());
200        assert!(PolyOverQ::from_str("2 17/1 42").is_err());
201        assert!(PolyOverQ::from_str("2 17/13 42  ").is_err());
202        assert!(PolyOverQ::from_str("  2 17/5 42").is_err());
203    }
204
205    /// Tests whether a falsely formatted string (too many whitespaces) returns
206    /// an error
207    #[test]
208    fn too_many_whitespaces() {
209        assert!(PolyOverQ::from_str("3  1  2/5  -3/2").is_err());
210    }
211
212    /// Tests whether a falsely formatted string (wrong number of total
213    /// coefficients) returns an error
214    #[test]
215    fn false_number_of_coefficient() {
216        assert!(PolyOverQ::from_str("4  1 2/5 -3/2").is_err());
217    }
218
219    /// Tests whether a falsely formatted string (too many divisors) returns
220    /// an error
221    #[test]
222    fn too_many_divisors() {
223        assert!(PolyOverQ::from_str("3  1 2/5 -3/2/3").is_err());
224    }
225
226    /// Ensure that the input works with strings that have to be trimmed
227    #[test]
228    fn trim_input() {
229        let poly = PolyOverQ::from_str("                   4  1/2 2/3 3/4 -4                  ");
230        assert!(poly.is_ok());
231        assert_eq!(
232            PolyOverQ::from_str("4  1/2 2/3 3/4 -4").unwrap(),
233            poly.unwrap()
234        );
235    }
236}
237
238#[cfg(test)]
239mod test_from_poly_over_z {
240    use crate::{integer::PolyOverZ, rational::PolyOverQ};
241    use std::str::FromStr;
242
243    /// Ensure that the conversion works with negative entries
244    #[test]
245    fn small_negative() {
246        let poly = PolyOverZ::from_str("4  0 1 -102 -3").unwrap();
247
248        let poly_q = PolyOverQ::from(&poly);
249
250        let cmp_poly = PolyOverQ::from_str("4  0 1 -102 -3").unwrap();
251        assert_eq!(cmp_poly, poly_q);
252    }
253
254    /// Ensure that the conversion works with negative large entries
255    #[test]
256    fn large_negative() {
257        let poly = PolyOverZ::from_str(&format!("4  0 1 -102 -{}", u64::MAX)).unwrap();
258
259        let poly_q = PolyOverQ::from(&poly);
260
261        let cmp_poly = PolyOverQ::from_str(&format!("4  0 1 -102 -{}", u64::MAX)).unwrap();
262        assert_eq!(cmp_poly, poly_q);
263    }
264
265    /// Ensure that the conversion works with positive large entries
266    #[test]
267    fn large_positive() {
268        let poly = PolyOverZ::from_str(&format!("4  0 1 102 {}", u64::MAX)).unwrap();
269
270        let poly_q = PolyOverQ::from(&poly);
271
272        let cmp_poly = PolyOverQ::from_str(&format!("4  0 1 102 {}", u64::MAX)).unwrap();
273        assert_eq!(cmp_poly, poly_q);
274    }
275
276    /// Ensure that the conversion works for owned values
277    #[test]
278    fn availability() {
279        let poly = PolyOverZ::from_str("4  0 1 -102 -3").unwrap();
280
281        let _ = PolyOverQ::from(poly);
282    }
283}
284
285#[cfg(test)]
286mod test_from_rational {
287    use super::*;
288    use crate::{integer::Z, traits::GetCoefficient};
289
290    /// Ensure that the [`From`] trait works for large
291    /// borrowed and owned [`Q`],[`Z`] and [`u64`] instances.
292    #[test]
293    fn large() {
294        let value = Q::from(u64::MAX);
295
296        let poly = PolyOverQ::from(&value);
297        let poly_2 = PolyOverQ::from(value.clone());
298        let poly_3 = PolyOverQ::from(u64::MAX);
299        let poly_4 = PolyOverQ::from(&u64::MAX);
300        let poly_5 = PolyOverQ::from(Z::from(u64::MAX));
301        let poly_6 = PolyOverQ::from(&Z::from(u64::MAX));
302
303        assert_eq!(poly.get_coeff(0).unwrap(), value);
304        assert_eq!(poly.get_degree(), 0);
305        assert_eq!(poly, poly_2);
306        assert_eq!(poly, poly_3);
307        assert_eq!(poly, poly_4);
308        assert_eq!(poly, poly_5);
309        assert_eq!(poly, poly_6);
310    }
311
312    /// Ensure that the [`From`] trait works for small
313    /// borrowed and owned [`Q`] and integer tuples instances.
314    #[test]
315    fn small() {
316        let value = Q::from((1, 2));
317
318        let poly = PolyOverQ::from(&value);
319        let poly_2 = PolyOverQ::from(value.clone());
320        let poly_3 = PolyOverQ::from((1, 2));
321        let poly_4 = PolyOverQ::from((&1, &2));
322
323        assert_eq!(poly.get_coeff(0).unwrap(), value);
324        assert_eq!(poly.get_degree(), 0);
325        assert_eq!(poly, poly_2);
326        assert_eq!(poly, poly_3);
327        assert_eq!(poly, poly_4);
328    }
329
330    /// Ensure that a division by zero panics.
331    #[test]
332    #[should_panic]
333    fn divide_by_zero() {
334        let _ = PolyOverQ::from((1, 0));
335    }
336}