qfall_math/rational/poly_over_q/
ownership.rs

1// Copyright © 2023 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 flint_sys::fmpq_poly::{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](flint_sys::fmpz::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!(
89            unsafe { *a.poly.coeffs.offset(0) }.0,
90            unsafe { *b.poly.coeffs.offset(0) }.0
91        ); // stack
92
93        assert_eq!(
94            unsafe { *a.poly.coeffs.offset(1) }.0,
95            unsafe { *b.poly.coeffs.offset(1) }.0
96        ); // stack
97        assert_eq!(
98            unsafe { *a.poly.coeffs.offset(2) }.0,
99            unsafe { *b.poly.coeffs.offset(2) }.0
100        ); // stack
101        assert_eq!(
102            unsafe { *a.poly.coeffs.offset(3) }.0,
103            unsafe { *b.poly.coeffs.offset(3) }.0
104        ); // stack
105
106        // denominator should be kept on stack (since common denominator is 8)
107        assert_eq!(a.poly.den[0].0, b.poly.den[0].0); // stack
108
109        // check that length is equal
110        assert_eq!(a.poly.length, b.poly.length);
111    }
112
113    /// Check if clone points to same point in memory for large nominators and denominators
114    #[test]
115    fn same_reference_large() {
116        let a =
117            PolyOverQ::from_str(&format!("4  {}/1 1/{} -2/2 3/-4", i64::MAX, i64::MIN)).unwrap();
118
119        let b = a.clone();
120
121        // check that nominators on heap are stored separately
122        assert_ne!(
123            unsafe { *a.poly.coeffs.offset(0) }.0,
124            unsafe { *b.poly.coeffs.offset(0) }.0
125        ); // heap
126        assert_eq!(
127            unsafe { *a.poly.coeffs.offset(1) }.0,
128            unsafe { *b.poly.coeffs.offset(1) }.0
129        ); // stack
130        assert_ne!(
131            unsafe { *a.poly.coeffs.offset(2) }.0,
132            unsafe { *b.poly.coeffs.offset(2) }.0
133        ); // heap
134        assert_ne!(
135            unsafe { *a.poly.coeffs.offset(3) }.0,
136            unsafe { *b.poly.coeffs.offset(3) }.0
137        ); // heap
138
139        // denominator should be kept on heap (as common denominator is at least i64::MIN)
140        // hence stored separately
141        assert_ne!(a.poly.den[0].0, b.poly.den[0].0); // heap
142
143        // check that length is equal
144        assert_eq!(a.poly.length, b.poly.length);
145    }
146}
147
148#[cfg(test)]
149mod test_drop {
150    use super::PolyOverQ;
151    use std::{collections::HashSet, str::FromStr};
152
153    /// Creates and drops a [`PolyOverQ`] object, and outputs
154    /// the storage point in memory of that [`fmpq_poly`](flint_sys::fmpz_mod_poly::fmpz_mod_poly_t) struct
155    fn create_and_drop_modulus() -> (i64, i64, i64) {
156        let a = PolyOverQ::from_str(&format!("2  {}/1 -2/-3", i64::MAX)).unwrap();
157
158        (
159            unsafe { *a.poly.coeffs.offset(0) }.0,
160            unsafe { *a.poly.coeffs.offset(1) }.0,
161            a.poly.den[0].0,
162        )
163    }
164
165    /// Check whether freed memory is reused afterwards
166    #[test]
167    fn free_memory() {
168        let mut storage_addresses = HashSet::new();
169
170        for _i in 0..5 {
171            storage_addresses.insert(create_and_drop_modulus());
172        }
173
174        assert!(storage_addresses.len() < 5);
175    }
176}