implies/
formula.rs

1use super::symbol::{Symbol, Symbolic};
2use crate::parser::ParseError;
3use cascade::cascade;
4use enum_iterator::*;
5use std::collections::{HashMap, HashSet};
6use std::fmt::Display;
7use std::hash::Hash;
8
9/// Classic tree implementation, but the enum variants
10/// for Binary, Unary and Atom ensure that ill-formed
11/// formulas can never be constructed. Uses [`Box`]
12/// as internal pointer because we modify formulae through
13/// a zipper. Within the formula struct, represents the
14/// untraversed/'unzipped' parts of the formula.
15#[derive(PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Debug)]
16pub enum Tree<B, U, A>
17where
18    B: Symbolic,
19    U: Symbolic,
20    A: Symbolic,
21{
22    Binary {
23        conn: B,
24        left: Box<Tree<B, U, A>>,
25        right: Box<Tree<B, U, A>>,
26    },
27    Unary {
28        conn: U,
29        next: Box<Tree<B, U, A>>,
30    },
31    Atom(A),
32}
33
34impl<B, U, A> Display for Tree<B, U, A>
35where
36    B: Symbolic,
37    U: Symbolic,
38    A: Symbolic,
39{
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        let mut repr = String::new();
42        self.read_inorder(&mut repr);
43        write!(f, "{}", repr)
44    }
45}
46
47impl<B, U, A> std::default::Default for Tree<B, U, A>
48where
49    B: Symbolic,
50    U: Symbolic,
51    A: Symbolic,
52{
53    /// Assuming `A::default()` returns a 0 value, effectively amounts
54    /// to a null value without allowing invalid trees to be constructed.
55    fn default() -> Self {
56        Tree::Atom(A::default())
57    }
58}
59
60/// Only the most basic manipulations (adding unary operators and combining
61/// formulas) are provided; more complex manipulations are provided by the [`Formula`]
62/// which is much more ergonomic for expressing in-place mutation using its
63/// internal [`Zipper`]
64/// [`Zipper`]: Zipper
65/// [`Formula`]: Formula
66impl<B, U, A> Tree<B, U, A>
67where
68    B: Symbolic,
69    U: Symbolic,
70    A: Symbolic,
71{
72    pub fn new(atom: A) -> Self {
73        Tree::Atom(atom)
74    }
75
76    pub fn is_atomic(&self) -> bool {
77        if let Tree::Atom(_) = self {
78            true
79        } else {
80            false
81        }
82    }
83
84    pub fn is_unary(&self) -> bool {
85        if let Tree::Unary { .. } = self {
86            true
87        } else {
88            false
89        }
90    }
91
92    pub fn is_binary(&self) -> bool {
93        if let Tree::Binary { .. } = self {
94            true
95        } else {
96            false
97        }
98    }
99
100    /// Inorder traversal starting at the current zipper.
101    /// Does not mutate the formula at all but the closure passed
102    /// in is still [`std::ops::FnMut`] so that other state
103    /// can be mutated. As usual returns [`Option<()>`] so that
104    /// traversal can be stopped early by returning `None`.
105    pub fn inorder_traverse<F: FnMut(&Self) -> Option<()>>(&self, func: &mut F) -> Option<()> {
106        Some(match &self {
107            Tree::Binary { left, right, .. } => {
108                left.inorder_traverse(func);
109                func(self)?;
110                right.inorder_traverse(func);
111            }
112            Tree::Unary { next, .. } => {
113                func(self)?;
114                next.inorder_traverse(func);
115            }
116            Tree::Atom(_) => func(self)?,
117        })
118    }
119
120    /// Preorder traversal starting at the current context.
121    /// Also takes in a closure that can read the formula.
122    pub fn preorder_traverse<F: FnMut(&Self) -> Option<()>>(&self, func: &mut F) -> Option<()> {
123        func(self)?;
124        Some(match &self {
125            Tree::Binary { left, right, .. } => {
126                left.preorder_traverse(func);
127                right.preorder_traverse(func);
128            }
129            Tree::Unary { next, .. } => {
130                next.preorder_traverse(func);
131            }
132            Tree::Atom(_) => {}
133        })
134    }
135
136    /// Combine two trees with a binary operator, inserting the new tree
137    /// on the right side.
138    pub fn combine(&mut self, bin: B, formula: Self) {
139        let old = std::mem::take(self);
140        *self = Tree::Binary {
141            conn: bin,
142            left: Box::new(old),
143            right: Box::new(formula),
144        }
145    }
146
147    /// Combine with new tree on the left!
148    pub fn left_combine(&mut self, bin: B, formula: Self) {
149        let old = std::mem::take(self);
150        *self = Tree::Binary {
151            conn: bin,
152            left: Box::new(formula),
153            right: Box::new(old),
154        }
155    }
156
157    /// Add a unary operator to the existing formula.
158    pub fn unify(&mut self, un: U) {
159        let old = std::mem::take(self);
160        *self = Tree::Unary {
161            conn: un,
162            next: Box::new(old),
163        }
164    }
165
166    /// Print the customary inorder traversal of a tree formula into an outparameter.
167    pub fn read_inorder(&self, repr: &mut String) {
168        match self {
169            Tree::Binary { conn, left, right } => {
170                if Symbol::from_tree(left.as_ref()) <= Symbol::Binary(*conn) {
171                    repr.push_str("(");
172                    left.read_inorder(repr);
173                    repr.push_str(")");
174                } else {
175                    left.read_inorder(repr)
176                };
177                repr.push_str(&conn.to_string());
178                if Symbol::from_tree(right.as_ref()) < Symbol::Binary(*conn) {
179                    repr.push_str("(");
180                    right.read_inorder(repr);
181                    repr.push_str(")");
182                } else {
183                    right.read_inorder(repr)
184                }
185            }
186            Tree::Unary { conn, next } => {
187                repr.push_str(&conn.to_string());
188                if Symbol::from_tree(next.as_ref()) < Symbol::Unary(*conn) {
189                    repr.push_str("(");
190                    next.read_inorder(repr);
191                    repr.push_str(")");
192                } else {
193                    next.read_inorder(repr)
194                }
195            }
196            Tree::Atom(a) => repr.push_str(&a.to_string()),
197        }
198    }
199}
200
201/// The thread or 'zipper' that actually tracks where you currently
202/// are in a given tree formula. The recursively nested zippers themselves
203/// contain the node values that trace out a partial walk from the head
204/// of the tree toward a leaf node, i.e. an atom. Zippers contain trees themselves
205/// if and only if they make a 'choice' during the walk, e.g. they traverse
206/// one of two binary subtrees, to retain the choice not made.
207#[derive(PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Debug, Default)]
208pub enum Zipper<B, U, A>
209where
210    B: Symbolic,
211    U: Symbolic,
212    A: Symbolic,
213{
214    #[default]
215    Top,
216    Right {
217        bin: B,
218        sub: Tree<B, U, A>,
219        zip: Box<Zipper<B, U, A>>,
220    },
221    Left {
222        bin: B,
223        sub: Tree<B, U, A>,
224        zip: Box<Zipper<B, U, A>>,
225    },
226    Up {
227        un: U,
228        zip: Box<Zipper<B, U, A>>,
229    },
230}
231
232impl<B: Symbolic, U: Symbolic, A: Symbolic> Zipper<B, U, A> {
233    pub fn is_left(&self) -> bool {
234        if let Zipper::Left { .. } = self {
235            true
236        } else {
237            false
238        }
239    }
240
241    pub fn is_right(&self) -> bool {
242        if let Zipper::Right { .. } = self {
243            true
244        } else {
245            false
246        }
247    }
248
249    pub fn is_up(&self) -> bool {
250        if let Zipper::Up { .. } = self {
251            true
252        } else {
253            false
254        }
255    }
256
257    pub fn is_top(&self) -> bool {
258        if let Zipper::Top = self {
259            true
260        } else {
261            false
262        }
263    }
264
265    /// For formula traversal through the zipper when
266    /// the actual zipper state doesn't need to be changed.
267    pub fn peek_up(&self) -> &Self {
268        match self {
269            Zipper::Top => self,
270            Zipper::Right { zip, .. } | Zipper::Left { zip, .. } | Zipper::Up { zip, .. } => {
271                zip.as_ref()
272            }
273        }
274    }
275
276    /// Flip a right zipper to left or vice versa while retaining
277    /// all the same data.
278    pub fn flip(&mut self) {
279        if let Zipper::Right { bin, sub, zip } = self {
280            *self = Zipper::Left {
281                bin: *bin,
282                sub: std::mem::take(sub),
283                zip: std::mem::take(zip),
284            }
285        } else if let Zipper::Left { bin, sub, zip } = self {
286            *self = Zipper::Right {
287                bin: *bin,
288                sub: std::mem::take(sub),
289                zip: std::mem::take(zip),
290            }
291        }
292    }
293}
294
295/// If you're really lazy I guess!
296impl<B, U, A> From<&Formula<B, U, A>> for Tree<B, U, A>
297where
298    B: Symbolic,
299    U: Symbolic,
300    A: Symbolic,
301{
302    fn from(value: &Formula<B, U, A>) -> Self {
303        value.tree.clone()
304    }
305}
306
307/// The primary generic struct for logical formulas that implement both unary and binary operators.
308/// The struct is generic over the type of binary operators `B`, unary operators `U`, and the atoms `A`,
309/// and assumes all three are very cheap (e.g. fieldless enums, integers) and therefore implement Copy.
310/// It's possible this requirement will be relaxed in future versions for the atoms 'A', in case there's
311/// a need for very complex atoms (i.e. arbitrarily large relations).
312///
313/// This is a [zipper](https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf)
314/// implementation of binary/unary trees which represent logical formulae. This means that a `Formula` is not
315/// just a tree, but a tree *and a particular location in that tree*, represented by the `zipper`; please read
316/// the source material for more if you want to understand how this works. At the basic level you may assume that
317/// `.*_unzip`, methods go 'down' the tree while `.*_zip` methods go up. If the formula is fully zipped (i.e. by calling
318/// [`top_zip`]) the tree is in fact the whole formula, with `Zipper::Top` stored as a sentinel zipper.
319///
320/// The implementation therefore always operates/mutates the formula at its current location in the tree, so if you want
321/// to operate on the very 'top' of the formula you must call [`top_zip`] first. The only exception to this is methods that
322/// start with `.top_`, which you may assume call [`top_zip`] before performing mutations.
323///
324/// The mutating methods therefore change different properties about the formula but leave the 'location' of the formula,
325/// i.e. the current `.tree`, untouched or as untouched as possible, so that these mutations can be done repeatedly as part
326/// of, for example, an [`inorder_traverse_mut`]. It can be hard to understand this abstractly so it's best to read the
327/// documentation for concrete transforming methods like [`combine`] or [`distribute_right`] for a clearer view.
328///
329/// This implementation does *not* use the builder pattern, both out of dislike but also because this would be impossible
330/// to PyBind if methods took self or require a duplicate implementation.
331///
332/// For builder-style method chaining use the wonderful [`cascade!`](https://docs.rs/cascade/latest/cascade/) macro.
333/// The Python wrappers do use a chainable pattern (taking and receiving `&mut self`) and can of course be used
334/// from the Rust API, but the Python API is really designed for Pythonic use and this API for Rust.
335///
336/// Finally, the API is designed for safety in the sense that transformations that are applied are only applied
337/// if they're valid at the current location in the formula and don't do anything otherwise! This is an opinionated
338/// design decision because this crate is fundamentally designed for situations that demand lots of rapid transformation
339/// the circumstance when you want to apply a transformation if possible or not do anything, e.g. when converting a formula
340/// to conjunctive normal form. Returning [`Result<T, E>`] in this situation would inappropriately stop such fast transformation
341/// chains and require a lot of branching in code. Nonetheless a trait may be added and implemented for this type which
342/// reimplements the methods to allow one to immediately discern if a formula has changed after a method call because there's
343/// no signal something has either happened or failed to (e.g. as would be signaled by returning a `Result<T, E>` type).
344///
345/// [`Result<T, E>`]: std::result::Result
346/// [`top_zip`]: Self::top_zip
347/// [`combine`]: Self::combine
348/// [`distribute_right`]: Self::distribute_right
349/// [`inorder_traverse_mut`]: Self::inorder_traverse_mut
350#[derive(PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Debug, Default)]
351pub struct Formula<B, U, A>
352where
353    B: Symbolic,
354    U: Symbolic,
355    A: Symbolic,
356{
357    pub tree: Tree<B, U, A>,
358    pub zipper: Zipper<B, U, A>,
359}
360
361/// This first impl block is for initialization and traversal.
362impl<B: Symbolic, U: Symbolic, A: Symbolic> Formula<B, U, A> {
363    /// A new formula that's just an atom.
364    pub fn new(atom: A) -> Self {
365        Formula {
366            tree: Tree::Atom(atom),
367            zipper: Zipper::Top,
368        }
369    }
370
371    /// Traverse the current zipper one step, reconstructing the tree,
372    /// whether you're in a binary tree (i.e. a left or right zipper)
373    /// or a unary one (up zipper); doesn't do anything if you're at `Zipper::Top`.
374    pub fn zip(&mut self) {
375        match &self.zipper {
376            Zipper::Top => {}
377            Zipper::Right { .. } => self.zip_right(),
378            Zipper::Left { .. } => self.zip_left(),
379            Zipper::Up { .. } => self.zip_up(),
380        }
381    }
382
383    /// In the subtree of a unary tree, go UP and eat the unary operator back into the tree. That is,
384    /// ```text
385    ///   zipper: (U, Zipper)       zipper: (*Zipper)
386    ///                                
387    ///            ^        =>         ^
388    ///            |                   |
389    ///                                
390    ///           tree              tree: U
391    ///                                   |
392    ///                                old_tree
393    /// ```
394    pub fn zip_up(&mut self) {
395        if let Zipper::Up { un, zip } = &mut self.zipper {
396            self.tree.unify(*un);
397            self.zipper = std::mem::take((*zip).as_mut());
398        }
399    }
400
401    /// In the left subtree of a binary tree, go "right" and eat the binary operator and right subtree!
402    /// You're basically recombining them, like
403    /// ```text
404    ///         zipper: (B, RTree, Zipper)        zipper: (*Zipper)
405    ///          /                                      |
406    ///         /                            =>         |
407    ///       tree: LTree                          tree: B
408    ///                                                /   \
409    ///                                             LTree  RTree
410    /// ```
411    pub fn zip_right(&mut self) {
412        if let Zipper::Right { sub, bin, zip } = &mut self.zipper {
413            self.tree.combine(*bin, std::mem::take(sub));
414            self.zipper = std::mem::take((*zip).as_mut());
415        }
416    }
417
418    /// Just like [`zip_right`] except you're in the right subtree of a
419    /// binary tree. The destination state is the exact same.
420    ///
421    /// [`zip_right`]: Self::zip_right
422    pub fn zip_left(&mut self) {
423        if let Zipper::Left { sub, bin, zip } = &mut self.zipper {
424            self.tree.left_combine(*bin, std::mem::take(sub));
425            self.zipper = std::mem::take((*zip).as_mut());
426        }
427    }
428
429    /// The inverse of [`zip_left`]. Decompose a binary tree and
430    /// traverse to the right subtree of a binary formula.
431    ///
432    /// [`zip_left`]: Self::zip_left
433    pub fn unzip_right(&mut self) {
434        if let Tree::Binary { conn, left, right } = &mut self.tree {
435            self.zipper = Zipper::Left {
436                bin: *conn,
437                sub: std::mem::take((*left).as_mut()),
438                zip: Box::new(std::mem::take(&mut self.zipper)),
439            };
440            self.tree = std::mem::take((*right).as_mut());
441        }
442    }
443
444    /// The inverse of [`zip_right`]: decompose a binary tree and travel
445    /// to the left subtree of a binary formula.
446    ///
447    /// [`zip_right`]: Self::zip_right
448    pub fn unzip_left(&mut self) {
449        if let Tree::Binary { conn, left, right } = &mut self.tree {
450            self.zipper = Zipper::Right {
451                bin: *conn,
452                sub: std::mem::take((*right).as_mut()),
453                zip: Box::new(std::mem::take(&mut self.zipper)),
454            };
455            self.tree = std::mem::take((*left).as_mut());
456        }
457    }
458
459    /// Traverse to the formula contained in a unary tree.
460    /// The inverse of [`zip_up`].
461    ///
462    /// [`zip_up`]: Self::zip_up
463    pub fn unzip_down(&mut self) {
464        if let Tree::Unary { conn, next } = &mut self.tree {
465            self.zipper = Zipper::Up {
466                un: *conn,
467                zip: Box::new(std::mem::take(&mut self.zipper)),
468            };
469            self.tree = std::mem::take((*next).as_mut());
470        }
471    }
472
473    /// Unzip the formula, i.e. return to the top node.
474    /// After this, `self.tree` contains the whole formula.
475    pub fn top_zip(&mut self) {
476        while !self.zipper.is_top() {
477            self.zip();
478        }
479    }
480
481    /// Inorder traversal starting at the current context.
482    /// If you want the whole formula simply [`top_zip`] first.
483    /// Takes in a closure which can mutate the formula in
484    /// place somehow. Returns Option<()> so traversal can be
485    /// stopped early if needed; in most cases can be ignored.
486    ///
487    /// [`top_zip`]: Self::top_zip
488    pub fn inorder_traverse_mut<F: FnMut(&mut Self) -> Option<()>>(
489        &mut self,
490        func: &mut F,
491    ) -> Option<()> {
492        match &self.tree {
493            Tree::Binary { .. } => cascade! {
494                self;
495                ..unzip_left();
496                ..inorder_traverse_mut(func);
497                ..zip();    // a general zip to account for any potential transformations
498                ..apply_mut(func)?;
499                ..unzip_right();
500                ..inorder_traverse_mut(func);
501                ..zip()    // a general zip to account for any potential transformations
502            },
503            Tree::Unary { .. } => cascade! {
504                self;
505                ..apply_mut(func)?;
506                ..unzip_down();
507                ..inorder_traverse_mut(func);
508                ..zip()    // a general zip to account for any potential transformations
509            },
510            Tree::Atom(_) => self.apply_mut(func)?,
511        }
512        Some(())
513    }
514
515    /// Preorder traversal starting at the current context.
516    /// Also takes in a closure that can mutate the formula.
517    /// This is a much more dangerous traversal strategy to call
518    /// with a transformation method like [`rotate_right`] compared
519    /// to [`inorder_traverse_mut`]; the latter will travel to the leaves
520    /// of a formula and then perform transforms going back *up* the zipper,
521    /// whereas this method will apply transformations immediately as it
522    /// visits each node, having potentially weird consequences if there
523    /// are in place mutations going on with formulae.
524    ///
525    /// [`rotate_right`]: Self::rotate_right
526    /// [`inorder_traverse_mut`]: Self::inorder_traverse_mut
527    pub fn preorder_traverse_mut<F: FnMut(&mut Self) -> Option<()>>(
528        &mut self,
529        func: &mut F,
530    ) -> Option<()> {
531        self.apply_mut(func)?;
532        match &self.tree {
533            Tree::Binary { .. } => cascade! {
534                self;
535                ..apply_mut(func)?;
536                ..unzip_left();
537                ..preorder_traverse_mut(func);
538                ..zip();    // a general zip to account for any potential transformations
539                ..unzip_right();
540                ..preorder_traverse_mut(func);
541                ..zip()    // a general zip to account for any potential transformations
542            },
543            Tree::Unary { .. } => cascade! {
544                self;
545                ..apply_mut(func)?;
546                ..unzip_down();
547                ..preorder_traverse_mut(func);
548                ..zip()    // a general zip to account for any potential transformations
549            },
550            Tree::Atom(_) => self.apply_mut(func)?,
551        }
552        Some(())
553    }
554
555    pub fn inorder_traverse<F: FnMut(&Tree<B, U, A>) -> Option<()>>(
556        &self,
557        func: &mut F,
558    ) -> Option<()> {
559        Some(self.tree.inorder_traverse(func)?)
560    }
561
562    pub fn preorder_traverse<F: FnMut(&Tree<B, U, A>) -> Option<()>>(
563        &self,
564        func: &mut F,
565    ) -> Option<()> {
566        Some(self.tree.preorder_traverse(func)?)
567    }
568
569    /// Purely for the sake of nicer syntax, allows closures to be called method-style
570    /// as part of method chaining.
571    fn apply_mut<F: FnMut(&mut Self) -> Option<()>>(&mut self, func: &mut F) -> Option<()> {
572        Some(func(self)?)
573    }
574
575    /// Purely for the sake of nicer syntax, allows closures to be called method-style
576    /// as part of method chaining.
577    fn apply<F: FnMut(&Self) -> Option<()>>(&self, func: &mut F) -> Option<()> {
578        Some(func(self)?)
579    }
580}
581
582/// This impl is about manipulating and combining formulas.
583impl<B: Symbolic, U: Symbolic, A: Symbolic> Formula<B, U, A> {
584    /// Connects to a new formula WITHOUT
585    /// unzipping, which is why this takes in a tree.
586    /// Whatever the current `.tree` is will become
587    /// the left subtree of a binary tree, where `new_tree`
588    /// is the right subtree and they're connected by `bin`.
589    /// This is a very zipper-style impl which might be counter
590    /// intuitive, and perhaps better illustrated with some
591    /// poor ASCII art:
592    ///
593    /// ```text
594    ///          zipper                 zipper: Zipper::Right(bin, new_tree, old_zipper)
595    ///                                
596    ///            ^        =>            /
597    ///            |                    /
598    ///                                
599    ///           tree               tree
600    /// ```
601    pub fn combine(&mut self, bin: B, new_tree: Tree<B, U, A>) {
602        self.zipper = Zipper::Right {
603            bin,
604            sub: new_tree,
605            zip: Box::new(std::mem::take(&mut self.zipper)),
606        };
607    }
608
609    /// Exactly the same as [`combine`] but the new subtree is inserted as
610    /// a left subtree, so you're now in the right subtree of a binary tree.
611    /// And therefore you end up with a [`Zipper::Left`].
612    ///
613    /// [`combine`]: Self::combine
614    /// [`Zipper::Left`]: Zipper::Left
615    pub fn left_combine(&mut self, bin: B, new_tree: Tree<B, U, A>) {
616        self.zipper = Zipper::Left {
617            bin,
618            sub: new_tree,
619            zip: Box::new(std::mem::take(&mut self.zipper)),
620        };
621    }
622
623    /// Combine two formulas with a binary connective.
624    /// But unzip first.
625    pub fn top_combine(&mut self, bin: B, mut formula: Self) {
626        formula.top_zip();
627        self.top_zip();
628        self.combine(bin, formula.tree);
629    }
630
631    /// [`top_combine`] but on the left side.
632    ///
633    /// [`top_combine`]: Self::top_combine
634    pub fn top_left_combine(&mut self, bin: B, mut formula: Self) {
635        formula.top_zip();
636        self.top_zip();
637        self.left_combine(bin, formula.tree)
638    }
639
640    /// Insert a unary operator in the formula's current position.
641    pub fn unify(&mut self, un: U) {
642        self.zipper = Zipper::Up {
643            un,
644            zip: Box::new(std::mem::take(&mut self.zipper)),
645        };
646    }
647
648    /// Insert a unary operator for the whole formula.
649    pub fn top_unify(&mut self, un: U) {
650        cascade! {
651            self;
652            ..top_zip();
653            ..unify(un)
654        }
655    }
656
657    /// A function which demonstrates some zipper-y fun, if you're currently at the
658    /// right or left subtree of a binary formula, i.e. the current zipper is
659    /// `Zipper::Right{..}` or `Zipper::Left{..}`, swap your position with the other
660    /// subtree (without moving memory). Otherwise, the formula remains the same.
661    pub fn flip(&mut self) {
662        self.zipper.flip()
663    }
664
665    /// If it applies in the current context, 'rotate' a tree formula,
666    /// i.e. change precedence between two binary operators,
667    /// to the left. As an example,
668    /// ```text
669    ///     →                                       
670    ///   /   \\
671    /// A       ∧          
672    ///       /   \
673    ///     B       C
674    ///
675    ///         =>
676    ///
677    ///             ∧                               
678    ///          //   \
679    ///         →       C   
680    ///       /   \
681    ///     A       B
682    /// ```
683    /// is an example of a left rotation.
684    /// Rotations are always performed assuming the current zipper holds the
685    /// lower-precedence `B`, i.e. the one higher up in the tree. In the example
686    /// above, the rotation would be performed on a `Formula` where the `zipper`
687    /// is the \\ pictured, holding the → operator. `self` is left in the same
688    /// position after rotation, with // denoting the new active zipper.
689    pub fn rotate_left(&mut self) {
690        if let Formula {
691            tree:
692                Tree::Binary {
693                    conn,
694                    left: b,
695                    right: c,
696                },
697            zipper: Zipper::Left { bin, sub: a, .. },
698        } = self
699        {
700            std::mem::swap(conn, bin);
701            std::mem::swap(b.as_mut(), c.as_mut());
702            std::mem::swap(a, b.as_mut()); // b now holds the c tree, so really swapping A, C
703        }
704        // You now need a right zipper, so flip it!
705        self.flip()
706    }
707
708    /// If it applies in the current context, 'rotate' a tree formula,
709    /// i.e. change precedence between two binary operators,
710    /// to the right. As an example,
711    /// ```text
712    ///          ∧                               
713    ///       //   \
714    ///      →      C   
715    ///    /   \
716    ///  A       B
717    ///
718    ///               =>
719    ///
720    ///                     →                                       
721    ///                   /   \\
722    ///                 A       ∧          
723    ///                       /   \
724    ///                     B       C
725    /// ```
726    /// is an example of a right rotation. More detail available in the
727    /// documentation of [`.rotate_left()`].
728    ///
729    /// [`.rotate_left()`]: Self::rotate_left
730    pub fn rotate_right(&mut self) {
731        if let Formula {
732            tree:
733                Tree::Binary {
734                    conn,
735                    left: a,
736                    right: b,
737                },
738            zipper: Zipper::Right { bin, sub: c, .. },
739        } = self
740        {
741            std::mem::swap(conn, bin);
742            std::mem::swap(a.as_mut(), b.as_mut());
743            std::mem::swap(c, b.as_mut()); // b now holds the a tree, so really swapping A, C
744        }
745        // You now need a left zipper, so flip it!
746        self.flip()
747    }
748
749    /// 'Distribute' a binary operator over the right (binary) subtree.
750    /// Often used in, for example, creating the conjunctive normal forms
751    /// of formulae. Easiest to see by example:
752    /// ```text
753    ///         ∧
754    ///      /     \\
755    ///    p        ∨
756    ///          /      \
757    ///        q         r
758    ///
759    ///                        =>
760    ///                                    ∨
761    ///                               /        \\
762    ///                            ∧               ∧
763    ///                        /       \       /       \
764    ///                       p         q     p         r
765    /// ```
766    /// The dummy formula corresponding to `p` above gets cloned.
767    /// Just like with the rotation methods, the above method occurs
768    /// starting from the higher-precedence operator (lower in the subtree)
769    /// corresponding to \\ being the active zipper, and \\ in the second
770    /// formula describes the active zipper after distribution.
771    pub fn distribute_right(&mut self) {
772        if !self.zipper.is_left() || !self.tree.is_binary() {
773            return;
774        }
775        cascade! {
776            let curr = self;
777            ..rotate_left();
778            // while you're here, steal another copy of the formula to distribute
779            let (clone, bin) = if let &Tree::Binary {ref left, conn,.. } = &curr.tree {(left.as_ref().clone(), conn)}
780                               else {unreachable!()};
781            ..zip_right();
782            ..unzip_right();
783            ..left_combine(bin, clone)
784        }
785    }
786
787    /// Distribute a binary operator over the left subtree (corresponding to a right
788    /// rotation). See [`distribute_right`] for more.
789    ///
790    /// [`distribute_right`]: Self::distribute_right
791    pub fn distribute_left(&mut self) {
792        if !self.zipper.is_right() || !self.tree.is_binary() {
793            return;
794        }
795        cascade! {
796            let curr = self;
797            ..rotate_right();
798            // while you're here, steal another copy of the formula to distribute
799            let (clone, bin) = if let &Tree::Binary {ref right, conn,.. } = &curr.tree {(right.as_ref().clone(), conn)}
800                               else {unreachable!()};
801            ..zip_left();
802            ..unzip_left();
803            ..combine(bin, clone)
804        }
805    }
806
807    /// Very similar to the [`.rotate_*`] methods but with unary operators
808    /// swapping precedence with binary ones instead. Because of this the
809    /// `.lower_*` methods don't reverse each other unlike the [`.rotate_*`] methods.
810    /// This method unifies the right subformula.
811    ///
812    /// [`.rotate_*`]: Self::rotate_right
813    pub fn lower_right(&mut self) {
814        if let Formula {
815            tree: Tree::Binary { right, .. },
816            zipper: Zipper::Up { un, zip },
817        } = self
818        {
819            right.as_mut().unify(*un);
820            self.zipper = *std::mem::take(zip);
821        }
822    }
823
824    /// Same as [`.lower_right()`] but unifies the left subformula.
825    ///
826    /// [`.lower_right()`]: Self::lower_right
827    pub fn lower_left(&mut self) {
828        if let Formula {
829            tree: Tree::Binary { left, .. },
830            zipper: Zipper::Up { un, zip },
831        } = self
832        {
833            left.as_mut().unify(*un);
834            self.zipper = *std::mem::take(zip);
835        }
836    }
837
838    /// Distribute a unary operator over a binary operator. Optionally
839    /// pass in a new binary operator to root the formula after distribution.
840    /// Basically like executing [`lower_left`] and [`lower_right`]
841    /// at the same time, and optionally swapping the root of the binary tree.
842    /// If you're unfamiliar with logic, this method exists because in numerous
843    /// languages a binary operator will have a 'dual' operator that it swaps
844    /// with when a unary operator is distributed over the two operands.
845    ///
846    /// [`lower_left`]: Self::lower_left
847    /// [`lower_right`]: Self::lower_right
848    pub fn distribute_down(&mut self, new_bin: Option<B>) {
849        if let Formula {
850            tree: Tree::Binary { conn, left, right },
851            zipper: Zipper::Up { un, zip },
852        } = self
853        {
854            left.unify(*un);
855            right.unify(*un);
856            if let Some(mut b) = new_bin {
857                std::mem::swap(&mut b, conn)
858            }
859            self.zipper = *std::mem::take(zip);
860        }
861    }
862
863    /// Swap two unary operators, optionally changing the one that's been
864    /// swapped up. This exists because oftentimes unary operators have
865    /// a 'dual' operator which they change to when another unary operator
866    /// changes precedence with them.
867    pub fn push_down(&mut self, new_un: Option<U>) {
868        if let Formula {
869            tree: Tree::Unary { conn, .. },
870            zipper: Zipper::Up { un, .. },
871        } = self
872        {
873            if let Some(mut u) = new_un {
874                std::mem::swap(&mut u, conn)
875            }
876            std::mem::swap(conn, un)
877        }
878    }
879
880    /// Instantiate an *atom* in the formula (as usual, starting where you currently are)
881    /// with another tree subformula. If you want to do this over a whole formula,
882    /// just call this inside [`inorder_traverse_mut`].
883    ///
884    /// [`inorder_traverse_mut`]: Self::inorder_traverse_mut
885    pub fn instantiate(&mut self, formulas: &HashMap<A, Tree<B, U, A>>) {
886        if let Tree::Atom(a) = self.tree {
887            if formulas.contains_key(&a) {
888                self.tree = formulas[&a].clone()
889            }
890        }
891    }
892}
893
894/// This block is about interfacing with tensors.
895impl<B: Symbolic, U: Symbolic, A: Symbolic> Formula<B, U, A> {
896    /// Given some arbitrary mapping from symbols to nonnegative integers, encode
897    /// a formula as a list of integers corresponding to an inorder traversal of
898    /// the nodes, and another list of the parent-child relationships between
899    /// the nodes. If the mapping given is not total, return None.
900    /// The edges are returned in COO format (two lists of pairs,
901    /// corresponding by index).
902    pub fn tensorize(
903        &self,
904        mapping: &HashMap<Symbol<B, U, A>, usize>,
905    ) -> Result<(Vec<usize>, Vec<Vec<usize>>), ParseError> {
906        let mut nodes: Vec<usize> = vec![];
907        let mut edges: Vec<Vec<usize>> = vec![vec![], vec![]];
908
909        let mut counter: usize = 0;
910        let mut stack: Vec<usize> = vec![];
911
912        self.preorder_traverse(&mut |node| {
913            nodes.push(mapping[&Symbol::from_tree(&node)]);
914            if let Some(idx) = stack.pop() {
915                edges[0].push(idx);
916                edges[1].push(counter);
917            }
918            if let Tree::Binary { .. } | Tree::Unary { .. } = node {
919                stack.push(counter)
920            }
921            if let Tree::Binary { .. } = node {
922                stack.push(counter)
923            }
924            Some(counter += 1)
925        })
926        .ok_or(ParseError::IncompleteEnum)?;
927
928        Ok((nodes, edges))
929    }
930
931    /// Store all the atoms that appear in the formula in a HashSet.
932    pub fn get_atoms(&self) -> HashSet<A> {
933        let mut atoms: HashSet<A> = HashSet::new();
934        self.tree.inorder_traverse(&mut |t: &Tree<B, U, A>| {
935            Some(if let &Tree::Atom(a) = t {
936                atoms.insert(a);
937            })
938        });
939        atoms
940    }
941
942    /// Normalize all the atoms in a formula by replacing its atoms with atoms
943    /// that index their first appearance in an inorder traversal.
944    /// The indices given are taken from a passed in iterator (over A) on which
945    /// [`std::iter::Iterator::next`] is called precisely once every time a
946    /// new atom is observed. In case the iterator runs out, returns None.
947    pub fn normalize<I: Iterator<Item = A>>(&self, mut indices: I) -> Option<Self> {
948        let mut seen: HashMap<A, A> = HashMap::new();
949        let mut formula = self.clone();
950        formula.inorder_traverse_mut(&mut |node| {
951            Some(if let Tree::Atom(a) = node.tree {
952                let replacement: &mut A = seen.entry(a).or_insert(indices.next()?);
953                node.tree = Tree::Atom(*replacement);
954            })
955        })?;
956        Some(formula)
957    }
958}
959
960// Special functions for enumerable operator types. Utilities for a rainy day.
961impl<B, U, A> Formula<B, U, A>
962where
963    B: Symbolic + Sequence,
964    U: Symbolic + Sequence,
965    A: Symbolic,
966{
967    /// Give the number of binary operators in this formula type.
968    fn num_binary() -> usize {
969        enum_iterator::cardinality::<B>()
970    }
971    /// Give the number of unary operators in this formula type.
972    fn num_unary() -> usize {
973        enum_iterator::cardinality::<U>()
974    }
975    /// Offer an indexing map for all unary operators.
976    fn unary_counting(offset: usize) -> HashMap<U, usize> {
977        let mut counter = offset..;
978        all::<U>()
979            .map(|s| (s, counter.next().unwrap()))
980            .collect::<HashMap<U, usize>>()
981    }
982
983    /// Offer an indexing map for the string repr of the unary operators.
984    fn unary_str_counting(offset: usize) -> HashMap<String, usize> {
985        let mut counter = offset..;
986        all::<U>()
987            .map(|s| (s.to_string(), counter.next().unwrap()))
988            .collect::<HashMap<String, usize>>()
989    }
990
991    /// Offer an indexing map for all binary operators.
992    fn binary_counting(offset: usize) -> HashMap<B, usize> {
993        let mut counter = offset..;
994        all::<B>()
995            .map(|s| (s, counter.next().unwrap()))
996            .collect::<HashMap<B, usize>>()
997    }
998
999    /// Offer an indexing map for the string repr of the binary operators.
1000    fn binary_str_counting(offset: usize) -> HashMap<String, usize> {
1001        let mut counter = offset..;
1002        all::<B>()
1003            .map(|s| (s.to_string(), counter.next().unwrap()))
1004            .collect::<HashMap<String, usize>>()
1005    }
1006
1007    /// Index all operators for the type, unary first, wrapped in the Symbol type.
1008    fn operator_counting(offset: usize) -> HashMap<Symbol<B, U, A>, usize> {
1009        Self::unary_counting(offset)
1010            .into_iter()
1011            .map(|(u, i)| (Symbol::Unary(u), i))
1012            .chain(
1013                Self::binary_counting(offset + cardinality::<U>())
1014                    .into_iter()
1015                    .map(|(b, i)| (Symbol::Binary(b), i)),
1016            )
1017            .collect::<HashMap<_, _>>()
1018    }
1019
1020    /// Index all operators for the type, unary first, wrapped in the Symbol type.
1021    fn operator_str_counting(offset: usize) -> HashMap<String, usize> {
1022        Self::unary_str_counting(offset)
1023            .into_iter()
1024            .chain(Self::binary_str_counting(offset + cardinality::<U>()).into_iter())
1025            .collect::<HashMap<_, _>>()
1026    }
1027}
1028
1029impl<B, U, A> Display for Formula<B, U, A>
1030where
1031    B: Symbolic,
1032    U: Symbolic,
1033    A: Symbolic,
1034{
1035    /// Read string representation starting from current position in formula.
1036    /// THIS WILL NOT PRINT ANY UNZIPPED PARTS OF THE FORMULA. Make sure to
1037    /// [`Formula::top_zip`] first if you want the whole formula.
1038    /// Printing is an easy way to see "where" you are in the formula.
1039    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040        let mut outparam = String::new();
1041        self.tree.read_inorder(&mut outparam);
1042        write!(f, "{}", outparam)
1043    }
1044}
1045
1046impl<B, U, A> From<Tree<B, U, A>> for Formula<B, U, A>
1047where
1048    B: Symbolic,
1049    U: Symbolic,
1050    A: Symbolic,
1051{
1052    fn from(value: Tree<B, U, A>) -> Self {
1053        Formula {
1054            tree: value,
1055            zipper: Zipper::Top,
1056        }
1057    }
1058}