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}