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 { Self::True } else { Self::False }
247 }
248}
249
250/// Result type with [`OutOfMemory`][crate::error::OutOfMemory] error
251pub type AllocResult<T> = Result<T, crate::error::OutOfMemory>;
252
253/// Is the underlying type a floating point number?
254pub trait IsFloatingPoint {
255 /// `true` iff the underlying type is a floating point number
256 const FLOATING_POINT: bool;
257
258 /// One greater than the minimum possible normal power of 2 exponent, see
259 /// [`f64::MIN_EXP`] for instance. `0` for integers
260 const MIN_EXP: i32;
261}
262
263// dirty hack until we have specialization
264/// cbindgen:ignore
265impl<T: std::ops::ShlAssign<i32>> IsFloatingPoint for T {
266 const FLOATING_POINT: bool = false;
267 const MIN_EXP: i32 = 0;
268}
269
270/// A number type suitable for counting satisfying assignments
271pub trait SatCountNumber:
272 Clone
273 + From<u32>
274 + std::ops::Add<Self, Output = Self>
275 + std::ops::Shl<u32, Output = Self>
276 + std::ops::Shr<u32, Output = Self>
277 + IsFloatingPoint
278{
279}
280
281impl<T> SatCountNumber for T where
282 T: Clone
283 + From<u32>
284 + std::ops::Add<Self, Output = Self>
285 + std::ops::Shl<u32, Output = Self>
286 + std::ops::Shr<u32, Output = Self>
287 + IsFloatingPoint
288{
289}
290
291/// Cache for counting satisfying assignments
292pub struct SatCountCache<N: SatCountNumber, S: BuildHasher> {
293 /// Main map from [`NodeID`]s to their model count
294 pub map: HashMap<NodeID, N, S>,
295
296 /// Number of variables in the domain
297 vars: LevelNo,
298
299 /// Epoch to indicate if the cache is still valid.
300 ///
301 /// If we cached the number of satisfying assignments of a function that has
302 /// been dropped and garbage collected in the meantime, the [`NodeID`]s may
303 /// have been re-used for semantically different functions. The `map` should
304 /// only be considered valid if `epoch` is [`Manager::gc_count()`].
305 epoch: u64,
306
307 /// Whether to cache the SAT counts even for all nodes, including those that
308 /// have only a single incoming edge.
309 pub cache_all: bool,
310}
311
312impl<N: SatCountNumber, S: BuildHasher + Default> Default for SatCountCache<N, S> {
313 fn default() -> Self {
314 Self {
315 map: HashMap::default(),
316 vars: 0,
317 epoch: 0,
318 cache_all: false,
319 }
320 }
321}
322
323impl<N: SatCountNumber, S: BuildHasher> SatCountCache<N, S> {
324 /// Create a new satisfiability counting cache
325 pub fn with_hasher(hash_builder: S) -> Self {
326 Self {
327 map: HashMap::with_hasher(hash_builder),
328 vars: 0,
329 epoch: 0,
330 cache_all: false,
331 }
332 }
333
334 /// Clear the cache if it has become invalid due to garbage collections or a
335 /// change in the number of variables
336 pub fn clear_if_invalid<M: Manager>(&mut self, manager: &M, vars: LevelNo) {
337 let epoch = manager.gc_count();
338 if epoch != self.epoch || vars != self.vars {
339 self.epoch = epoch;
340 self.vars = vars;
341 self.map.clear();
342 }
343 }
344}