oxidd_core/util/
mod.rs

1//! Various utilities
2
3use std::collections::{BTreeSet, HashMap, HashSet};
4use std::hash::BuildHasher;
5use std::iter::FusedIterator;
6use std::marker::PhantomData;
7use std::mem::ManuallyDrop;
8use std::ops::Deref;
9
10use crate::{Edge, LevelNo, Manager, NodeID};
11
12mod on_drop;
13mod substitution;
14
15pub mod edge_hash_map;
16pub use edge_hash_map::EdgeHashMap;
17pub mod num;
18pub use on_drop::*;
19pub use substitution::*;
20pub mod var_name_map;
21pub use var_name_map::VarNameMap;
22
23pub use nanorand::WyRand as Rng;
24
25/// Borrowed version of some handle
26///
27/// A handle is typically just a pointer, but we cannot copy it as we need to
28/// update the reference counters accordingly. However, if we want to have
29/// multiple instances of the same handle without changing the reference
30/// counters, we can use shared references. This works as long as one does not
31/// attempt to change the handle's tag. In this case, we need ownership of the
32/// handle with the different tag, but need to restrict its lifetime to the one
33/// of the original handle. Furthermore, we *must not* drop the new handle. This
34/// is exactly, what this data structure provides.
35///
36/// `Borrowed<'a, H>` always has the same representation as `H`.
37#[repr(transparent)]
38#[derive(PartialEq, Eq, PartialOrd, Ord)]
39pub struct Borrowed<'a, H>(ManuallyDrop<H>, PhantomData<&'a H>);
40
41impl<'a, H> Borrowed<'a, H> {
42    /// Create a new borrowed handle
43    ///
44    /// While the type of `handle` suggests that the handle is owned, it should
45    /// be just the owned representation of a borrowed handle, e.g. no reference
46    /// counters should be increased when creating `handle`.
47    #[must_use]
48    #[inline]
49    pub fn new(handle: H) -> Self {
50        Self(ManuallyDrop::new(handle), PhantomData)
51    }
52
53    /// Convert a borrowed handle into the underlying [`ManuallyDrop`] handle
54    ///
55    /// # Safety
56    ///
57    /// The caller must ensure that the resources referenced by the handle
58    /// remain valid during its usage. Furthermore the returned handle must not
59    /// be dropped.
60    #[inline]
61    pub unsafe fn into_inner(this: Self) -> ManuallyDrop<H> {
62        this.0
63    }
64}
65
66impl<'a, H> Deref for Borrowed<'a, H> {
67    type Target = H;
68
69    #[inline]
70    fn deref(&self) -> &H {
71        self.0.deref()
72    }
73}
74
75impl<'a, E: Edge> Borrowed<'a, E> {
76    /// Change the tag of a borrowed [`Edge`]
77    ///
78    /// This is equivalent to [`Edge::with_tag()`], but can be used in some
79    /// situations where [`Edge::with_tag()`] can't due to lifetime
80    /// restrictions.
81    #[inline]
82    pub fn edge_with_tag(self, tag: E::Tag) -> Self {
83        // We must not drop `edge`. This would happen if `Edge::with_tag_owned`
84        // panicked (which it definitely shouldn't, but …). Dropping the edge
85        // itself shouldn't immediately lead to undefined behavior, but we need
86        // to make sure that the owned edge is not dropped as well, so we abort
87        // in this case.
88        let guard = AbortOnDrop("`Edge::with_tag_owned` panicked.");
89        let edge = ManuallyDrop::into_inner(self.0);
90        let res = Self(ManuallyDrop::new(edge.with_tag_owned(tag)), PhantomData);
91        guard.defuse();
92        res
93    }
94}
95
96/// Drop functionality for containers of [`Edge`]s
97///
98/// An edge on its own cannot be dropped: It may well be just an integer index
99/// for an array. In this case we are lacking the base pointer, or more
100/// abstractly the [`Manager`]. Most edge containers cannot store a manager
101/// reference, be it for memory consumption or lifetime restrictions. This trait
102/// provides methods to drop such a container with an externally supplied
103/// function to drop edges.
104pub trait DropWith<E: Edge>: Sized {
105    /// Drop `self`
106    ///
107    /// Among dropping other parts, this calls `drop_edge` for all children.
108    ///
109    /// Having [`Self::drop_with_manager()`] only is not enough: To drop a
110    /// [`Function`][crate::function::Function], we should not require a manager
111    /// lock, otherwise we might end up in a dead-lock (if we are in a
112    /// [`.with_manager_exclusive()`][crate::function::Function::with_manager_exclusive]
113    /// block). So we cannot provide a `&Manager` reference. Furthermore, when
114    /// all [`Function`][crate::function::Function]s and
115    /// [`ManagerRef`][crate::ManagerRef]s referencing a manager are gone and
116    /// the manager needs to drop e.g. the apply cache, it may also provide a
117    /// function that only forgets edges rather than actually dropping them,
118    /// saving the (in this case) unnecessary work of changing reference
119    /// counters etc.
120    fn drop_with(self, drop_edge: impl Fn(E));
121
122    /// Drop `self`
123    ///
124    /// This is equivalent to `self.drop_with(|e| manager.drop_edge(e))`.
125    ///
126    /// Among dropping other parts, this calls
127    /// [`manager.drop_edge()`][Manager::drop_edge] for all children.
128    #[inline]
129    fn drop_with_manager<M: Manager<Edge = E>>(self, manager: &M) {
130        self.drop_with(|e| manager.drop_edge(e));
131    }
132}
133
134/// Iterator that yields borrowed edges ([`Borrowed<'a, E>`][Borrowed]) provided
135/// that `I` is an iterator that yields `&'a E`.
136pub struct BorrowedEdgeIter<'a, E, I>(I, PhantomData<Borrowed<'a, E>>);
137
138impl<'a, E: Edge, I: Iterator<Item = &'a E>> From<I> for BorrowedEdgeIter<'a, E, I> {
139    fn from(it: I) -> Self {
140        Self(it, PhantomData)
141    }
142}
143
144impl<'a, E: Edge, I: Iterator<Item = &'a E>> Iterator for BorrowedEdgeIter<'a, E, I> {
145    type Item = Borrowed<'a, E>;
146
147    #[inline]
148    fn next(&mut self) -> Option<Self::Item> {
149        Some(self.0.next()?.borrowed())
150    }
151
152    #[inline]
153    fn size_hint(&self) -> (usize, Option<usize>) {
154        self.0.size_hint()
155    }
156}
157
158impl<'a, E: Edge, I: FusedIterator<Item = &'a E>> FusedIterator for BorrowedEdgeIter<'a, E, I> {}
159
160impl<'a, E: Edge, I: ExactSizeIterator<Item = &'a E>> ExactSizeIterator
161    for BorrowedEdgeIter<'a, E, I>
162{
163    #[inline]
164    fn len(&self) -> usize {
165        self.0.len()
166    }
167}
168
169/// Set of nodes
170pub trait NodeSet<E>: Clone + Default + Eq {
171    /// Get the number of nodes in the set
172    #[must_use]
173    fn len(&self) -> usize;
174
175    /// Returns `true` iff there are no nodes in the set
176    #[must_use]
177    fn is_empty(&self) -> bool {
178        self.len() == 0
179    }
180
181    /// Add a node (the node to which edge points) to the set
182    ///
183    /// Returns `true` if the element was added (i.e. not previously present).
184    fn insert(&mut self, edge: &E) -> bool;
185
186    /// Return `true` if the set contains the given node
187    #[must_use]
188    fn contains(&self, edge: &E) -> bool;
189
190    /// Remove a node from the set
191    ///
192    /// Returns `true` if the node was present in the set.
193    fn remove(&mut self, edge: &E) -> bool;
194}
195impl<E: Edge, S: Clone + Default + BuildHasher> NodeSet<E> for HashSet<NodeID, S> {
196    #[inline]
197    fn len(&self) -> usize {
198        HashSet::len(self)
199    }
200    #[inline]
201    fn insert(&mut self, edge: &E) -> bool {
202        self.insert(edge.node_id())
203    }
204    #[inline]
205    fn contains(&self, edge: &E) -> bool {
206        self.contains(&edge.node_id())
207    }
208    #[inline]
209    fn remove(&mut self, edge: &E) -> bool {
210        self.remove(&edge.node_id())
211    }
212}
213impl<E: Edge> NodeSet<E> for BTreeSet<NodeID> {
214    #[inline]
215    fn len(&self) -> usize {
216        BTreeSet::len(self)
217    }
218    #[inline]
219    fn insert(&mut self, edge: &E) -> bool {
220        self.insert(edge.node_id())
221    }
222    #[inline]
223    fn contains(&self, edge: &E) -> bool {
224        self.contains(&edge.node_id())
225    }
226    #[inline]
227    fn remove(&mut self, edge: &E) -> bool {
228        self.remove(&edge.node_id())
229    }
230}
231
232/// Optional Boolean with `repr(i8)`
233#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
234#[repr(i8)]
235pub enum OptBool {
236    /// Don't care
237    None = -1,
238    #[allow(missing_docs)]
239    False = 0,
240    #[allow(missing_docs)]
241    True = 1,
242}
243
244impl From<bool> for OptBool {
245    fn from(value: bool) -> Self {
246        if value {
247            Self::True
248        } else {
249            Self::False
250        }
251    }
252}
253
254/// Deprecated alias for [`crate::error::OutOfMemory`]
255#[deprecated = "use oxidd_core::error::OutOfMemory instead"]
256pub type OutOfMemory = crate::error::OutOfMemory;
257
258/// Result type with [`OutOfMemory`][crate::error::OutOfMemory] error
259pub type AllocResult<T> = Result<T, crate::error::OutOfMemory>;
260
261/// Is the underlying type a floating point number?
262pub trait IsFloatingPoint {
263    /// `true` iff the underlying type is a floating point number
264    const FLOATING_POINT: bool;
265
266    /// One greater than the minimum possible normal power of 2 exponent, see
267    /// [`f64::MIN_EXP`] for instance. `0` for integers
268    const MIN_EXP: i32;
269}
270
271// dirty hack until we have specialization
272/// cbindgen:ignore
273impl<T: std::ops::ShlAssign<i32>> IsFloatingPoint for T {
274    const FLOATING_POINT: bool = false;
275    const MIN_EXP: i32 = 0;
276}
277
278/// A number type suitable for counting satisfying assignments
279pub trait SatCountNumber:
280    Clone
281    + From<u32>
282    + for<'a> std::ops::AddAssign<&'a Self>
283    + for<'a> std::ops::SubAssign<&'a Self>
284    + std::ops::ShlAssign<u32>
285    + std::ops::ShrAssign<u32>
286    + IsFloatingPoint
287{
288}
289
290impl<T> SatCountNumber for T where
291    T: Clone
292        + From<u32>
293        + for<'a> std::ops::AddAssign<&'a T>
294        + for<'a> std::ops::SubAssign<&'a T>
295        + std::ops::ShlAssign<u32>
296        + std::ops::ShrAssign<u32>
297        + IsFloatingPoint
298{
299}
300
301/// Cache for counting satisfying assignments
302pub struct SatCountCache<N: SatCountNumber, S: BuildHasher> {
303    /// Main map from [`NodeID`]s to their model count
304    pub map: HashMap<NodeID, N, S>,
305
306    /// Number of variables in the domain
307    vars: LevelNo,
308
309    /// Epoch to indicate if the cache is still valid.
310    ///
311    /// If we cached the number of satisfying assignments of a function that has
312    /// been dropped and garbage collected in the meantime, the [`NodeID`]s may
313    /// have been re-used for semantically different functions. The `map` should
314    /// only be considered valid if `epoch` is [`Manager::gc_count()`].
315    epoch: u64,
316}
317
318impl<N: SatCountNumber, S: BuildHasher + Default> Default for SatCountCache<N, S> {
319    fn default() -> Self {
320        Self {
321            map: HashMap::default(),
322            vars: 0,
323            epoch: 0,
324        }
325    }
326}
327
328impl<N: SatCountNumber, S: BuildHasher> SatCountCache<N, S> {
329    /// Create a new satisfiability counting cache
330    pub fn with_hasher(hash_builder: S) -> Self {
331        Self {
332            map: HashMap::with_hasher(hash_builder),
333            vars: 0,
334            epoch: 0,
335        }
336    }
337
338    /// Clear the cache if it has become invalid due to garbage collections or a
339    /// change in the number of variables
340    pub fn clear_if_invalid<M: Manager>(&mut self, manager: &M, vars: LevelNo) {
341        let epoch = manager.gc_count();
342        if epoch != self.epoch || vars != self.vars {
343            self.epoch = epoch;
344            self.vars = vars;
345            self.map.clear();
346        }
347    }
348}