bex/
nid.rs

1//! Node IDs (shared by various Base implementations)
2use std::fmt;
3use std::str::FromStr;
4use crate::vid;
5
6// -- core data types ---
7
8/// A NID represents a node in a Base. Essentially, this acts like a tuple
9/// containing a VID and index, but for performance reasons, it is packed into a u64.
10/// See below for helper functions that manipulate and analyze the packed bits.
11#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
12pub struct NID { n: u64 }
13
14/// A truth table stored directly in a nid for functions of up to 5 inputs.
15#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
16pub struct NidFun { nid:NID }
17
18
19// -- bits in the nid ---
20
21/// Single-bit mask representing that a NID is inverted.
22const INV:u64 = 1<<63;  // is inverted?
23
24/// Single-bit mask indicating that a NID represents a variable. (The corresponding
25/// "virtual" nodes have I as their hi branch and O as their lo branch. They're simple
26/// and numerous enough that we don't bother actually storing them.)
27const VAR:u64 = 1<<62;   // is variable?
28
29/// Single-bit mask indicating that the NID represents a constant. The corresponding
30/// virtual node branches on constant "true" value, hence the letter T. There is only
31/// one such node -- O (I is its inverse) but having this bit in the NID lets us
32/// easily detect and optimize the cases.
33const T:u64 = 1<<61;    // T: max VID (hack so O/I nodes show up at bottom)
34
35/// In addition, for solving, we want to distinguish between "virtual" variables which
36/// represent some intermediate, unsimplified calculation, and "real" variables, which
37/// represent actual input variables. That's what this bit does.
38const RVAR:u64 = 1<<60;  // is *real* variable?
39
40/// This bit indicates that the NID is meant to be used as a function.
41/// (All nids represent functions, but this bit indicates that rather
42/// than referring to an existing node, it is a function of <=5 inputs
43/// and the entire truth table is stored in the index field.
44// !TODO: Decide whether or not to merge F(unction) with T(able). If separate,
45// then F+T might mean treat this as a function with a table, and F+!T would
46// tell the interpreter to apply some previously defined expression as a function.
47const F:u64 = 1<<59;
48
49/// Constant used to extract the index part of a NID.
50const IDX_MASK:u64 = (1<<32)-1;
51
52/// NID of the virtual node represeting the constant function 0, or "always false."
53pub const O:NID = NID{n: T};
54/// NID of the virtual node represeting the constant function 1, or "always true."
55pub const I:NID = NID{ n:T|INV };
56
57// scaffolding for moving ASTBase over to use NIDS
58
59/// bit buffer used for extracting/inserting a VID
60type VidBits = usize;
61
62/// On which variable does this node branch? (I and O branch on TV)
63#[inline(always)] fn vid_bits(x:NID)->VidBits { ((x.n & !(INV|VAR)) >> 32) as VidBits}
64
65/// Construct the NID for the (virtual) node corresponding to an input variable.
66/// Private since moving to vid::VID, because this didn't set the "real" bit, and
67/// I want the real bit to eventually go away in favor of an unset "virtual" bit.
68#[inline(always)] const fn nv(v:VidBits)->NID { NID { n:((v as u64) << 32)|VAR }}
69
70/// Construct a NID with the given variable and index.
71#[inline(always)] fn nvi(v:VidBits,i:usize)->NID { NID{n: ((v as u64) << 32) + i as u64} }
72
73const NOVAR:VidBits = (1<<26) as VidBits; // 134_217_728
74const TOP:VidBits = (T>>32) as VidBits; // 536_870_912, // 1<<29, same as nid::T
75
76fn vid_to_bits(v:vid::VID)->VidBits {
77  if v.is_nov() { NOVAR }
78  else if v.is_top() { TOP }
79  else if v.is_var() { v.var_ix() | (RVAR>>32) as VidBits }
80  else if v.is_vir() { v.vir_ix() as VidBits }
81  else { panic!("unknown vid::VID {:?}?", v) }}
82
83fn bits_to_vid(o:VidBits)->vid::VID {
84  if o == TOP { vid::VID::top() }
85  else if o == NOVAR { vid::VID::nov() }
86  else if o & (RVAR>>32) as VidBits > 0 { vid::VID::var((o & !(RVAR>>32) as VidBits) as u32) }
87  else { vid::VID::vir(o as u32) }}
88
89
90impl std::ops::Not for NID {
91  type Output = NID;
92  fn not(self)-> NID {NID { n: self.n^INV }}}
93
94
95/// Pretty-printer for NIDS that reveal some of their internal data.
96impl fmt::Display for NID {
97  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98    if self.is_const() { if self.is_inv() { write!(f, "I") } else { write!(f, "O") } }
99    else if self.is_fun() {
100      let fnid = self.to_fun().unwrap();
101      let ar:u8 = fnid.arity(); // 2..5 inclusive
102      // !! arity of 1 would just be ID or NOT, which are redundant because of the INV bit
103      let ft:u32 = fnid.tbl();
104      if ar == 2 { write!(f, "t{:04b}", ft) }
105      else { write!(f, "f{}.{:X}", ar, ft) }}
106    else {
107      if self.is_inv() { write!(f, "!")?; }
108      if self.is_vid() { write!(f, "{}", self.vid()) }
109      else if self.is_ixn() { write!(f, "#{:X}", self.idx()) }
110      else { write!(f, "{}.{:X}", self.vid(), self.idx()) }}}}
111
112/// Same as fmt::Display. Mostly so it's easier to see the problem when an assertion fails.
113impl fmt::Debug for NID { // for test suite output
114  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) }}
115
116
117impl FromStr for NID {
118  type Err = String;
119
120  fn from_str(word: &str) -> Result<Self, Self::Err> {
121    match word {
122    "O" => Ok(O),
123    "I" => Ok(I),
124    _ => {
125      let (a, b) = if let Some(ix) = word.find('.') { word.split_at(ix) } else { (word, "") };
126      let mut ch = a.chars().peekable();
127      let mut inv: bool = false;
128      if ch.peek().unwrap() == &'!' { ch.next(); inv = true }
129      macro_rules! num_suffix {
130        ($radix:expr, $ch:expr) => { usize::from_str_radix(&$ch.collect::<String>(), $radix) }}
131      let c = ch.next().unwrap();
132      // literals or VHL NIDS:
133      if c == 'x' || 'c'=='v'  {
134        if let Ok(n) = num_suffix!(16, ch) {
135          let v = if c == 'x' { vid::VID::var(n as u32) } else { vid::VID::vir(n as u32) };
136          if b.is_empty() { Ok(NID::from_vid(v).inv_if(inv)) }
137          else if let Ok(ix) = usize::from_str_radix(&b[1..], 16) {
138            Ok(NID::from_vid_idx(v, ix).inv_if(inv) )}
139          else { Err(format!("bad index after '.': {}", word)) }}
140        else { Err(format!("malformed variable: {}", word)) }}
141      else { match c {
142        '#' => if let Ok(n) = num_suffix!(16, ch) { Ok(NID::ixn(n).inv_if(inv)) }
143              else { Err(format!("bad ixn: {}", word)) }
144        'f' =>
145          if let Some(i) = word.find('.') {
146            let (a, b) = word.split_at(i);
147            if let Ok(ar) = num_suffix!(16, a.chars().skip(1)) {
148              if let Ok(tb) = num_suffix!(16, b.chars().skip(1)) {
149                Ok(NID::fun(ar as u8, tb as u32).to_nid().inv_if(inv))}
150              else { Err(format!("bad fun arity: {}", word)) }}
151            else { Err(format!("bad fun code: {}", word)) }}
152          else if let Ok(n) = num_suffix!(16, ch) {
153            let ar: u8 = if n >= 2 << 16 { 5 }
154              else if n > 2 << 8 { 4 }
155              else if n > 2 << 4 { 3 }
156              else { 2 };
157            Ok(NID::fun(ar, n as u32).to_nid().inv_if(inv))}
158          else { Err(format!("bad fun: {}", word)) }
159        't' =>
160            if ch.clone().count() == 4 {
161              if let Ok(tb) = num_suffix!(2, ch) {
162                Ok(NID::fun(2, tb as u32).to_nid().inv_if(inv))}
163              else { Err(format!("bad table (expect 4 bits): {}", word)) }}
164            else { Err(format!("bad length for table (expect 4 bits): {}", word)) }
165        _ => Err(format!("{}?", word))}}}}}}
166
167
168#[test] fn test_nids() {
169  let new = |n| { NID{n} };
170  assert_eq!(O.n,   2305843009213693952); assert_eq!(O, new(0x2000000000000000));
171  assert_eq!(I.n,  11529215046068469760); assert_eq!(I, new(0xa000000000000000));
172  assert_eq!(NID::vir(0), new(0x4000000000000000u64));
173  assert_eq!(NID::var(0), new(0x5000000000000000u64));
174  assert_eq!(NID::vir(1),  new(0x4000000100000000u64));
175  assert!(vid_bits(NID::vir(0)) < vid_bits(NID::var(0)));
176  assert_eq!(nvi(0,0), new(0x0000000000000000u64));
177  assert_eq!(nvi(1,0), new(0x0000000100000000u64)); }
178
179#[test] fn test_var() {
180  assert_eq!(vid_bits(O), 536_870_912, "var(O)");
181  assert_eq!(vid_bits(I), vid_bits(O), "INV bit shouldn't be part of variable");
182  assert_eq!(vid_bits(NID::vir(0)), 0);
183  assert_eq!(vid_bits(NID::var(0)), 268_435_456);}
184
185#[test] fn test_cmp() {
186  let v = |x:usize|->NID { nv(x) };  let x=|x:u32|->NID { NID::var(x) };
187  let o=vid_bits;   let n=|x:NID|x.vid();
188  assert!(o(O) == o(I),      "old:no=no");  assert!(n(O) == n(I),       "new:no=no");
189  assert!(o(O)    > o(v(0)), "old:no>v0");  assert!(n(O).is_below(&n(v(0))), "new:no bel v0");
190  assert!(o(O)    > o(x(0)), "old:no>x0");  assert!(n(O).is_below(&n(x(0))), "new:no bel x0");
191  assert!(o(v(0)) < o(x(0)), "old:v0>x0");  assert!(n(v(0)).is_above(&n(x(0))),  "new:v0 abv x0");
192  assert!(o(v(1)) < o(x(0)), "old:v1<x0");  assert!(n(v(1)).is_above(&n(x(0))),  "new:v1 abv x0");}
193
194
195
196impl NID {
197  #[inline(always)] pub fn o()->Self { O }
198  #[inline(always)] pub fn i()->Self { I }
199  #[inline(always)] pub fn from_bit(b:bool)->Self { if b { I } else { O } }
200  #[inline(always)] pub fn to_bit(&self)->bool { self != &O }
201
202  #[inline(always)] pub const fn var(v:u32)->Self { nv((v | (RVAR>>32) as u32) as VidBits) }
203  #[inline(always)] pub const fn vir(v:u32)->Self { nv(v as VidBits) }
204  #[inline(always)] pub fn from_var(v:vid::VID)->Self { Self::var(v.var_ix() as u32)}
205  #[inline(always)] pub fn from_vir(v:vid::VID)->Self { Self::vir(v.vir_ix() as u32)}
206
207  #[inline(always)] pub fn from_vid(v:vid::VID)->Self { nv(vid_to_bits(v)) }
208  #[inline(always)] pub fn from_vid_idx(v:vid::VID, i:usize)->Self { nvi(vid_to_bits(v), i) }
209  #[inline(always)] pub fn vid(&self)->vid::VID { bits_to_vid(vid_bits(*self)) }
210  // return a nid that is not tied to a variable
211  #[inline(always)] pub fn ixn(ix:usize)->Self { nvi(NOVAR, ix) }
212
213  /// Does the NID refer to one of the two constant nodes (O or I)?
214  #[inline(always)] pub fn is_const(&self)->bool { (self.n & T) != 0 }
215
216  /// Does the NID represent a VID (either Var or Vir)?
217  #[inline(always)] pub fn is_vid(&self)->bool { (self.n & VAR) != 0 }
218
219  /// Does the NID represent an input variable?
220  #[inline(always)] pub fn is_var(&self)->bool { self.is_vid() && self.vid().is_var() }
221
222  /// Does the NID represent a virtual variable?
223  #[inline(always)] pub fn is_vir(&self)->bool { self.is_vid() && self.vid().is_vir() }
224
225  /// Is n a literal (variable or constant)?
226  #[inline(always)] pub fn is_lit(&self)->bool { self.is_vid() | self.is_const()}
227
228  /// Is the NID inverted? That is, does it represent `!(some other nid)`?
229  #[inline(always)] pub fn is_inv(&self)->bool { (self.n & INV) != 0 }
230
231  /// Invert if the condition is true
232  #[inline(always)] pub fn inv_if(&self, cond:bool)->NID {
233    if cond { NID { n: self.n ^ INV }} else { *self }}
234
235  /// is this NID just an indexed node with no variable?
236  #[inline(always)] pub fn is_ixn(self)->bool { (self.n & (F|T|VAR) == 0) && vid_bits(self)==NOVAR }
237
238  /// Map the NID to an index. (I.e., if n=idx(x), then x is the nth node branching on var(x))
239  #[inline(always)] pub fn idx(self)->usize { (self.n & IDX_MASK) as usize }
240
241  /// Return the NID with the 'INV' flag removed.
242  // !! pos()? abs()? I don't love any of these names.
243  #[inline(always)] pub fn raw(self)->NID { NID{ n: self.n & !INV }}
244
245  /// construct a NID holding a truth table for up to 5 input bits.
246  #[inline(always)] pub const fn fun(arity:u8, tbl:u32)->NidFun {
247    NidFun { nid: NID { n:F+(((1<<(1<<arity)) -1) & tbl as u64)+((arity as u64)<< 32)}} }
248
249  /// is this NID a function (truth table)?
250  #[inline(always)] pub fn is_fun(&self)->bool { self.n & F == F }
251  #[inline(always)] pub fn to_fun(&self)->Option<NidFun> {
252    if self.is_fun() { Some(NidFun { nid:*self }) } else { None }}
253
254  #[inline(always)] pub fn tbl(&self)->Option<u32> { if self.is_fun(){ Some(self.idx() as u32)} else {None} }
255
256  /// is it possible nid depends on var v?
257  /// the goal here is to avoid exploring a subgraph if we don't have to.
258  #[inline] pub fn might_depend_on(&self, v:vid::VID)->bool {
259    if self.is_const() { false }
260    else if self.is_vid() { self.vid() == v }
261    else { let sv = self.vid(); sv == v || sv.is_above(&v) }}
262
263  // -- int conversions used by the dd python package
264  #[inline] pub fn _from_u64(x:u64)->Self { NID{n:x} }
265  #[inline] pub fn _to_u64(&self)->u64 { self.n }
266}
267
268#[test] fn test_tbl_fmt() {
269  assert_eq!("t1110", format!("{}", NID::fun(2, 0b1110).to_nid()));
270  assert_eq!("f3.FC", format!("{}", NID::fun(3, 0xFC).to_nid()));}
271
272include!("nid-fun.rs");
273
274/// predefined consts for VIDS (mostly for tests)
275#[allow(non_upper_case_globals)]
276pub mod named {
277  use super::NID;
278  pub const x0:NID = NID::var(0);
279  pub const x1:NID = NID::var(1);
280  pub const x2:NID = NID::var(2);
281  pub const x3:NID = NID::var(3);
282  pub const x4:NID = NID::var(4);
283  pub const v0:NID = NID::vir(0);
284  pub const v1:NID = NID::vir(1);
285  pub const v2:NID = NID::vir(2);
286  pub const v3:NID = NID::vir(3);
287  pub const v4:NID = NID::vir(4);
288}