Skip to main content

karpal_proof/
refinement.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6
7use karpal_core::Semigroup;
8
9/// A `Vec<T>` guaranteed to contain at least one element.
10///
11/// Unlike `NonEmptyVec<T>` in karpal-core (which has a structurally
12/// different representation with separate `head` and `tail` fields),
13/// `NonEmpty<Vec<T>>` wraps a standard `Vec<T>` with a refinement
14/// invariant. Construction is only possible via `try_new` (which checks)
15/// or `from_parts` (which requires at least one element).
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct NonEmpty<C> {
18    inner: C,
19}
20
21impl<T> NonEmpty<Vec<T>> {
22    /// Attempt to construct a `NonEmpty<Vec<T>>` from a `Vec<T>`.
23    /// Returns `None` if the vector is empty.
24    pub fn try_new(v: Vec<T>) -> Option<Self> {
25        if v.is_empty() {
26            None
27        } else {
28            Some(NonEmpty { inner: v })
29        }
30    }
31
32    /// Construct from a head element and remaining tail.
33    pub fn from_parts(head: T, tail: Vec<T>) -> Self {
34        let mut v = tail;
35        v.insert(0, head);
36        NonEmpty { inner: v }
37    }
38
39    /// Construct a single-element vector.
40    pub fn singleton(value: T) -> Self {
41        NonEmpty {
42            inner: [value].into(),
43        }
44    }
45
46    /// The first element (always exists).
47    pub fn head(&self) -> &T {
48        // Safety: invariant guarantees non-empty
49        &self.inner[0]
50    }
51
52    /// Number of elements (always >= 1).
53    pub fn len(&self) -> usize {
54        self.inner.len()
55    }
56
57    /// Always returns `false` — a `NonEmpty` is never empty by construction.
58    pub fn is_empty(&self) -> bool {
59        false
60    }
61
62    /// Push a new element.
63    pub fn push(&mut self, value: T) {
64        self.inner.push(value);
65    }
66
67    /// Access the underlying slice.
68    pub fn as_slice(&self) -> &[T] {
69        &self.inner
70    }
71
72    /// Convert into the underlying `Vec<T>`, discarding the proof.
73    pub fn into_vec(self) -> Vec<T> {
74        self.inner
75    }
76
77    /// Iterate over references.
78    pub fn iter(&self) -> core::slice::Iter<'_, T> {
79        self.inner.iter()
80    }
81
82    /// Map a function over all elements, preserving non-emptiness.
83    pub fn map<U>(self, f: impl FnMut(T) -> U) -> NonEmpty<Vec<U>> {
84        NonEmpty {
85            inner: self.inner.into_iter().map(f).collect(),
86        }
87    }
88}
89
90impl<T: Semigroup + Clone> Semigroup for NonEmpty<Vec<T>> {
91    fn combine(mut self, other: Self) -> Self {
92        self.inner.extend(other.inner);
93        self
94    }
95}
96
97/// A numeric value guaranteed to be strictly positive (> 0).
98///
99/// Useful for operations that require nonzero values, such as
100/// `Field::reciprocal`.
101#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
102pub struct Positive<T> {
103    value: T,
104}
105
106macro_rules! impl_positive_float {
107    ($($t:ty),*) => {
108        $(
109            impl Positive<$t> {
110                /// Attempt to construct a `Positive` from a value.
111                /// Returns `None` if the value is not strictly positive.
112                pub fn try_new(v: $t) -> Option<Self> {
113                    if v > 0.0 && v.is_finite() {
114                        Some(Positive { value: v })
115                    } else {
116                        None
117                    }
118                }
119
120                /// Get the inner value.
121                pub fn get(self) -> $t {
122                    self.value
123                }
124
125                /// Safe reciprocal: always valid for positive values.
126                pub fn reciprocal(self) -> Self {
127                    Positive { value: 1.0 / self.value }
128                }
129            }
130        )*
131    };
132}
133
134impl_positive_float!(f32, f64);
135
136macro_rules! impl_positive_int {
137    ($($t:ty),*) => {
138        $(
139            impl Positive<$t> {
140                /// Attempt to construct a `Positive` from a value.
141                /// Returns `None` if the value is zero or negative.
142                pub fn try_new(v: $t) -> Option<Self> {
143                    if v > 0 {
144                        Some(Positive { value: v })
145                    } else {
146                        None
147                    }
148                }
149
150                /// Get the inner value.
151                pub fn get(self) -> $t {
152                    self.value
153                }
154            }
155        )*
156    };
157}
158
159impl_positive_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
160
161macro_rules! impl_positive_unsigned {
162    ($($t:ty),*) => {
163        $(
164            impl Positive<$t> {
165                /// Construct from a nonzero unsigned value.
166                /// Returns `None` if zero.
167                pub fn from_nonzero(v: $t) -> Option<Self> {
168                    if v > 0 {
169                        Some(Positive { value: v })
170                    } else {
171                        None
172                    }
173                }
174            }
175        )*
176    };
177}
178
179impl_positive_unsigned!(u8, u16, u32, u64, u128);
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn non_empty_try_new() {
187        assert!(NonEmpty::try_new(Vec::<i32>::new()).is_none());
188        let ne = NonEmpty::try_new(vec![1, 2, 3]).unwrap();
189        assert_eq!(*ne.head(), 1);
190        assert_eq!(ne.len(), 3);
191    }
192
193    #[test]
194    fn non_empty_from_parts() {
195        let ne = NonEmpty::from_parts(10, vec![20, 30]);
196        assert_eq!(*ne.head(), 10);
197        assert_eq!(ne.as_slice(), &[10, 20, 30]);
198    }
199
200    #[test]
201    fn non_empty_singleton() {
202        let ne = NonEmpty::singleton(42);
203        assert_eq!(*ne.head(), 42);
204        assert_eq!(ne.len(), 1);
205    }
206
207    #[test]
208    fn non_empty_push() {
209        let mut ne = NonEmpty::singleton(1);
210        ne.push(2);
211        assert_eq!(ne.len(), 2);
212        assert_eq!(ne.as_slice(), &[1, 2]);
213    }
214
215    #[test]
216    fn non_empty_map() {
217        let ne = NonEmpty::from_parts(1, vec![2, 3]);
218        let doubled = ne.map(|x| x * 2);
219        assert_eq!(doubled.as_slice(), &[2, 4, 6]);
220    }
221
222    #[test]
223    fn non_empty_into_vec() {
224        let ne = NonEmpty::from_parts(1, vec![2]);
225        assert_eq!(ne.into_vec(), vec![1, 2]);
226    }
227
228    #[test]
229    fn non_empty_iter() {
230        let ne = NonEmpty::from_parts(1, vec![2, 3]);
231        let sum: i32 = ne.iter().sum();
232        assert_eq!(sum, 6);
233    }
234
235    #[test]
236    fn non_empty_semigroup() {
237        let a = NonEmpty::from_parts(1, vec![2]);
238        let b = NonEmpty::from_parts(3, vec![4]);
239        let c = a.combine(b);
240        assert_eq!(c.as_slice(), &[1, 2, 3, 4]);
241    }
242
243    #[test]
244    fn positive_f64() {
245        assert!(Positive::<f64>::try_new(0.0).is_none());
246        assert!(Positive::<f64>::try_new(-1.0).is_none());
247        assert!(Positive::<f64>::try_new(f64::NAN).is_none());
248        assert!(Positive::<f64>::try_new(f64::INFINITY).is_none());
249
250        let p = Positive::<f64>::try_new(4.0).unwrap();
251        assert_eq!(p.get(), 4.0);
252
253        let r = p.reciprocal();
254        assert!((r.get() - 0.25).abs() < 1e-10);
255    }
256
257    #[test]
258    fn positive_f32() {
259        let p = Positive::<f32>::try_new(2.0).unwrap();
260        assert!((p.reciprocal().get() - 0.5).abs() < 1e-6);
261    }
262
263    #[test]
264    fn positive_i32() {
265        assert!(Positive::<i32>::try_new(0).is_none());
266        assert!(Positive::<i32>::try_new(-5).is_none());
267        let p = Positive::<i32>::try_new(7).unwrap();
268        assert_eq!(p.get(), 7);
269    }
270
271    #[test]
272    fn positive_u32() {
273        assert!(Positive::<u32>::try_new(0).is_none());
274        let p = Positive::<u32>::try_new(10).unwrap();
275        assert_eq!(p.get(), 10);
276    }
277
278    #[test]
279    fn positive_u32_from_nonzero() {
280        assert!(Positive::<u32>::from_nonzero(0).is_none());
281        let p = Positive::<u32>::from_nonzero(5).unwrap();
282        assert_eq!(p.get(), 5);
283    }
284}