Skip to main content

qfall_math/rational/poly_over_q/
ownership.rs

1// Copyright 2023 Jan 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//! This module contains implementations of functions
10//! important for ownership such as the [`Clone`] and [`Drop`] trait.
11//!
12//! The explicit functions contain the documentation.
13
14use super::PolyOverQ;
15use flint3_sys::{fmpq_poly_clear, fmpq_poly_init, fmpq_poly_set};
16use std::mem::MaybeUninit;
17
18// The use of [`PolyOverQ`] should be thread safe because
19// a) just mutable objects/references can manipulate the data in memory.
20// b) [`PolyOverQ`] just wraps a FLINT data type. These are probably
21//    thread safe, because FLINT supports multithreading. Additionally, FLINT
22//    relays on GMP which is thread safe in most conditions <https://gmplib.org/manual/Reentrancy>.
23unsafe impl Send for PolyOverQ {}
24unsafe impl Sync for PolyOverQ {}
25
26impl Clone for PolyOverQ {
27    /// Clones the given [`PolyOverQ`] element by returning a deep clone,
28    /// storing two separately stored [fmpz](flint3_sys::fmpz) values
29    /// for `nominator` and `denominator` in memory.
30    ///
31    /// # Examples
32    /// ```
33    /// use qfall_math::rational::PolyOverQ;
34    ///
35    /// let a = PolyOverQ::default();
36    /// let b = a.clone();
37    /// ```
38    fn clone(&self) -> Self {
39        let mut poly = MaybeUninit::uninit();
40        unsafe {
41            fmpq_poly_init(poly.as_mut_ptr());
42            let mut poly = poly.assume_init();
43            fmpq_poly_set(&mut poly, &self.poly);
44            Self { poly }
45        }
46    }
47}
48
49impl Drop for PolyOverQ {
50    /// Drops the given memory allocated for the underlying value.
51    ///
52    /// # Examples
53    /// ```
54    /// use qfall_math::rational::PolyOverQ;
55    /// {
56    ///     let a = PolyOverQ::default();
57    /// } // as a's scope ends here, it get's dropped
58    /// ```
59    ///
60    /// ```
61    /// use qfall_math::rational::PolyOverQ;
62    ///
63    /// let a = PolyOverQ::default();
64    /// drop(a); // explicitly drops a's value
65    /// ```
66    fn drop(&mut self) {
67        unsafe {
68            fmpq_poly_clear(&mut self.poly);
69        }
70    }
71}
72
73/// Test that the [`Clone`] trait is correctly implemented.
74#[cfg(test)]
75mod test_clone {
76    use super::PolyOverQ;
77    use std::str::FromStr;
78
79    /// Check if nominators and denominators are equal for small integers
80    #[test]
81    fn same_reference_small() {
82        let a = PolyOverQ::from_str("4  1/1 -1/-2 -2/2 3/-4").unwrap();
83
84        let b = a.clone();
85
86        // check that nominators on stack should are equal,
87        // as they directly store the value in the `i64`
88        assert_eq!(unsafe { *a.poly.coeffs.offset(0) }, unsafe {
89            *b.poly.coeffs.offset(0)
90        }); // stack
91
92        assert_eq!(unsafe { *a.poly.coeffs.offset(1) }, unsafe {
93            *b.poly.coeffs.offset(1)
94        }); // stack
95        assert_eq!(unsafe { *a.poly.coeffs.offset(2) }, unsafe {
96            *b.poly.coeffs.offset(2)
97        }); // stack
98        assert_eq!(unsafe { *a.poly.coeffs.offset(3) }, unsafe {
99            *b.poly.coeffs.offset(3)
100        }); // stack
101
102        // denominator should be kept on stack (since common denominator is 8)
103        assert_eq!(a.poly.den[0], b.poly.den[0]); // stack
104
105        // check that length is equal
106        assert_eq!(a.poly.length, b.poly.length);
107    }
108
109    /// Check if clone points to same point in memory for large nominators and denominators
110    #[test]
111    fn same_reference_large() {
112        let a =
113            PolyOverQ::from_str(&format!("4  {}/1 1/{} -2/2 3/-4", i64::MAX, i64::MIN)).unwrap();
114
115        let b = a.clone();
116
117        // check that nominators on heap are stored separately
118        assert_ne!(unsafe { *a.poly.coeffs.offset(0) }, unsafe {
119            *b.poly.coeffs.offset(0)
120        }); // heap
121        assert_eq!(unsafe { *a.poly.coeffs.offset(1) }, unsafe {
122            *b.poly.coeffs.offset(1)
123        }); // stack
124        assert_ne!(unsafe { *a.poly.coeffs.offset(2) }, unsafe {
125            *b.poly.coeffs.offset(2)
126        }); // heap
127        assert_ne!(unsafe { *a.poly.coeffs.offset(3) }, unsafe {
128            *b.poly.coeffs.offset(3)
129        }); // heap
130
131        // denominator should be kept on heap (as common denominator is at least i64::MIN)
132        // hence stored separately
133        assert_ne!(a.poly.den[0], b.poly.den[0]); // heap
134
135        // check that length is equal
136        assert_eq!(a.poly.length, b.poly.length);
137    }
138}
139
140#[cfg(test)]
141mod test_drop {
142    use super::PolyOverQ;
143    use std::{collections::HashSet, str::FromStr};
144
145    /// Creates and drops a [`PolyOverQ`] object, and outputs
146    /// the storage point in memory of that [`fmpq_poly`](flint3_sys::fmpz_mod_poly::fmpz_mod_poly_t) struct
147    fn create_and_drop_modulus() -> (i64, i64, i64) {
148        let a = PolyOverQ::from_str(&format!("2  {}/1 -2/-3", i64::MAX)).unwrap();
149
150        (
151            unsafe { *a.poly.coeffs.offset(0) },
152            unsafe { *a.poly.coeffs.offset(1) },
153            a.poly.den[0],
154        )
155    }
156
157    /// Check whether freed memory is reused afterwards
158    #[test]
159    fn free_memory() {
160        let mut storage_addresses = HashSet::new();
161
162        for _i in 0..5 {
163            storage_addresses.insert(create_and_drop_modulus());
164        }
165
166        assert!(storage_addresses.len() < 5);
167    }
168}