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}