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}