use crate::lock::synced;
use std::{
collections::HashMap,
ffi::CString,
fmt::Display,
ops::{BitAnd, BitOr, Index, Not, Shr},
time::Duration,
};
use vampire_sys::{self as sys, vampire_unit_t};
mod lock;
pub trait IntoTermArgs {
fn as_slice(&self) -> &[Term];
}
impl IntoTermArgs for Term {
fn as_slice(&self) -> &[Term] {
std::slice::from_ref(self)
}
}
impl<T> IntoTermArgs for T
where
T: AsRef<[Term]>,
{
fn as_slice(&self) -> &[Term] {
self.as_ref()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Function {
id: u32,
arity: u32,
}
impl Function {
pub fn new(name: &str, arity: u32) -> Self {
synced(|_| {
let name = CString::new(name).expect("valid c string");
let function = unsafe { sys::vampire_add_function(name.as_ptr(), arity) };
Self {
id: function,
arity,
}
})
}
pub fn arity(&self) -> u32 {
self.arity
}
pub fn constant(name: &str) -> Term {
Self::new(name, 0).with([])
}
pub fn with(&self, args: impl IntoTermArgs) -> Term {
Term::new_function(*self, args.as_slice())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Predicate {
id: u32,
arity: u32,
}
impl Predicate {
pub fn new(name: &str, arity: u32) -> Self {
synced(|_| {
let name = CString::new(name).expect("valid c string");
let predicate = unsafe { sys::vampire_add_predicate(name.as_ptr(), arity) };
Self {
id: predicate,
arity,
}
})
}
pub fn arity(&self) -> u32 {
self.arity
}
pub fn with(&self, args: impl IntoTermArgs) -> Formula {
Formula::new_predicate(*self, args.as_slice())
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Term {
id: *mut sys::vampire_term_t,
}
impl PartialEq for Term {
fn eq(&self, other: &Self) -> bool {
synced(|_| unsafe { sys::vampire_term_equal(self.id, other.id) })
}
}
impl Eq for Term {}
impl std::hash::Hash for Term {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
synced(|_| unsafe { sys::vampire_term_hash(self.id) }).hash(state);
}
}
impl std::fmt::Debug for Term {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Term({})", self.to_string())
}
}
impl std::fmt::Display for Term {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl Term {
pub fn to_string(&self) -> String {
synced(|_| unsafe {
let ptr = sys::vampire_term_to_string(self.id);
assert!(!ptr.is_null(), "vampire_term_to_string returned null");
let c_str = std::ffi::CStr::from_ptr(ptr);
let result = c_str
.to_str()
.expect("vampire returned invalid UTF-8")
.to_string();
sys::vampire_free_string(ptr);
result
})
}
pub fn new_function(func: Function, args: &[Term]) -> Self {
assert!(args.len() == func.arity() as usize);
synced(|_| unsafe {
let arg_count = args.len();
let args = std::mem::transmute(args.as_ptr());
let term = sys::vampire_term(func.id, args, arg_count);
Self { id: term }
})
}
pub fn new_var(idx: u32) -> Self {
synced(|info| unsafe {
info.free_var = info.free_var.max(idx + 1);
let term = sys::vampire_var(idx);
Self { id: term }
})
}
pub fn free_var() -> (Self, u32) {
synced(|info| unsafe {
let idx = info.free_var;
info.free_var += 1;
let term = sys::vampire_var(idx);
(Self { id: term }, idx)
})
}
pub fn eq(&self, rhs: Term) -> Formula {
Formula::new_eq(*self, rhs)
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Formula {
id: *mut sys::vampire_formula_t,
}
impl PartialEq for Formula {
fn eq(&self, other: &Self) -> bool {
synced(|_| unsafe { sys::vampire_formula_equal(self.id, other.id) })
}
}
impl Eq for Formula {}
impl std::hash::Hash for Formula {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
synced(|_| unsafe { sys::vampire_formula_hash(self.id) }).hash(state);
}
}
impl std::fmt::Debug for Formula {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Formula({})", self.to_string())
}
}
impl std::fmt::Display for Formula {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl Formula {
pub fn to_string(&self) -> String {
synced(|_| unsafe {
let ptr = sys::vampire_formula_to_string(self.id);
assert!(!ptr.is_null(), "vampire_formula_to_string returned null");
let c_str = std::ffi::CStr::from_ptr(ptr);
let result = c_str
.to_str()
.expect("vampire returned invalid UTF-8")
.to_string();
sys::vampire_free_string(ptr);
result
})
}
pub fn new_predicate(pred: Predicate, args: &[Term]) -> Self {
assert!(args.len() == pred.arity() as usize);
synced(|_| unsafe {
let arg_count = args.len();
let args = std::mem::transmute(args.as_ptr());
let lit = sys::vampire_lit(pred.id, true, args, arg_count);
let atom = sys::vampire_atom(lit);
Self { id: atom }
})
}
pub fn new_eq(lhs: Term, rhs: Term) -> Self {
synced(|_| unsafe {
let lit = sys::vampire_eq(true, lhs.id, rhs.id);
let atom = sys::vampire_atom(lit);
Self { id: atom }
})
}
pub fn new_and(formulas: &[Formula]) -> Self {
synced(|_| unsafe {
let formula_count = formulas.len();
let formulas = std::mem::transmute(formulas.as_ptr());
let id = sys::vampire_and(formulas, formula_count);
Self { id }
})
}
pub fn new_or(formulas: &[Formula]) -> Self {
synced(|_| unsafe {
let formula_count = formulas.len();
let formulas = std::mem::transmute(formulas.as_ptr());
let id = sys::vampire_or(formulas, formula_count);
Self { id }
})
}
pub fn new_not(formula: Formula) -> Self {
synced(|_| {
let id = unsafe { sys::vampire_not(formula.id) };
Self { id }
})
}
pub fn new_true() -> Self {
synced(|_| {
let id = unsafe { sys::vampire_true() };
Self { id }
})
}
pub fn new_false() -> Self {
synced(|_| {
let id = unsafe { sys::vampire_false() };
Self { id }
})
}
pub fn new_forall(var: u32, f: Formula) -> Self {
synced(|_| {
let id = unsafe { sys::vampire_forall(var, f.id) };
Self { id }
})
}
pub fn new_exists(var: u32, f: Formula) -> Self {
synced(|_| {
let id = unsafe { sys::vampire_exists(var, f.id) };
Self { id }
})
}
pub fn imp(&self, rhs: Formula) -> Self {
synced(|_| {
let id = unsafe { sys::vampire_imp(self.id, rhs.id) };
Self { id }
})
}
pub fn iff(&self, rhs: Formula) -> Self {
synced(|_| {
let id = unsafe { sys::vampire_iff(self.id, rhs.id) };
Self { id }
})
}
}
pub fn forall<F: FnOnce(Term) -> Formula>(f: F) -> Formula {
let (var, var_idx) = Term::free_var();
let f = f(var);
Formula::new_forall(var_idx, f)
}
pub fn exists<F: FnOnce(Term) -> Formula>(f: F) -> Formula {
let (var, var_idx) = Term::free_var();
let f = f(var);
Formula::new_exists(var_idx, f)
}
impl BitAnd for Formula {
type Output = Formula;
fn bitand(self, rhs: Self) -> Self::Output {
Formula::new_and(&[self, rhs])
}
}
impl BitOr for Formula {
type Output = Formula;
fn bitor(self, rhs: Self) -> Self::Output {
Formula::new_or(&[self, rhs])
}
}
impl Not for Formula {
type Output = Formula;
fn not(self) -> Self::Output {
Formula::new_not(self)
}
}
impl Shr for Formula {
type Output = Formula;
fn shr(self, rhs: Self) -> Self::Output {
self.imp(rhs)
}
}
#[derive(Debug, Clone)]
pub struct Options {
timeout: Option<Duration>,
}
impl Options {
pub fn new() -> Self {
Self { timeout: None }
}
pub fn timeout(&mut self, duration: Duration) -> &mut Self {
self.timeout = Some(duration);
self
}
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Problem {
options: Options,
axioms: Vec<Formula>,
conjecture: Option<Formula>,
}
impl Problem {
pub fn new(options: Options) -> Self {
Self {
options,
axioms: Vec::new(),
conjecture: None,
}
}
pub fn with_axiom(&mut self, f: Formula) -> &mut Self {
self.axioms.push(f);
self
}
pub fn conjecture(&mut self, f: Formula) -> &mut Self {
self.conjecture = Some(f);
self
}
unsafe fn unsynce_solve(&mut self) -> ProofRes {
unsafe {
sys::vampire_prepare_for_next_proof();
if let Some(timeout) = self.options.timeout {
let ms = timeout.as_millis().max(1);
sys::vampire_set_time_limit_milliseconds(ms as i32);
}
let mut units = Vec::new();
for axiom in &self.axioms {
let axiom_unit = sys::vampire_axiom_formula(axiom.id);
units.push(axiom_unit);
}
if let Some(conjecture) = self.conjecture {
let conjecture_unit = sys::vampire_conjecture_formula(conjecture.id);
units.push(conjecture_unit);
}
let problem = sys::vampire_problem_from_units(units.as_mut_ptr(), units.len());
let proof_res = sys::vampire_prove(problem);
ProofRes::new_from_raw(proof_res)
}
}
pub fn solve(&mut self) -> ProofRes {
synced(|_| unsafe { self.unsynce_solve() })
}
pub fn solve_and_prove(&mut self) -> (ProofRes, Option<Proof>) {
synced(|_| unsafe {
let res = self.unsynce_solve();
let ProofRes::Proved = res else {
return (res, None);
};
let refutation = sys::vampire_get_refutation();
let proof = Proof::from_refutation(refutation);
(res, Some(proof))
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProofRes {
Proved,
Unprovable,
Unknown(UnknownReason),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnknownReason {
Timeout,
MemoryLimit,
Incomplete,
Unknown,
}
impl ProofRes {
fn new_from_raw(idx: u32) -> ProofRes {
if idx == sys::vampire_proof_result_t_VAMPIRE_PROOF {
ProofRes::Proved
} else if idx == sys::vampire_proof_result_t_VAMPIRE_SATISFIABLE {
ProofRes::Unprovable
} else if idx == sys::vampire_proof_result_t_VAMPIRE_TIMEOUT {
ProofRes::Unknown(UnknownReason::Timeout)
} else if idx == sys::vampire_proof_result_t_VAMPIRE_MEMORY_LIMIT {
ProofRes::Unknown(UnknownReason::MemoryLimit)
} else if idx == sys::vampire_proof_result_t_VAMPIRE_INCOMPLETE {
ProofRes::Unknown(UnknownReason::Incomplete)
} else if idx == sys::vampire_proof_result_t_VAMPIRE_UNKNOWN {
ProofRes::Unknown(UnknownReason::Unknown)
} else {
panic!()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Proof {
steps: Vec<ProofStep>,
}
impl Proof {
unsafe fn from_refutation(refutation: *mut vampire_unit_t) -> Self {
unsafe {
let mut steps_ptr = std::ptr::null_mut();
let mut steps_len: usize = 0;
let success = sys::vampire_extract_proof(refutation, &mut steps_ptr, &mut steps_len);
assert!(success == 0);
let vsteps = std::slice::from_raw_parts(steps_ptr, steps_len);
let mut vsteps = vsteps.to_vec();
vsteps.sort_by_key(|s| s.id);
let mut steps = Vec::new();
let mut step_map = HashMap::new();
for vstep in vsteps {
let discovery_order = vstep.id;
let rule = ProofRule::from_raw(vstep.rule, vstep.input_type);
let premises = if vstep.premise_count == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(vstep.premise_ids, vstep.premise_count)
.iter()
.map(|p| step_map[p])
.collect()
};
let conclusion = sys::vampire_unit_as_formula(vstep.unit);
let conclusion = Formula { id: conclusion };
step_map.insert(discovery_order, steps.len());
let step = ProofStep {
discovery_order,
rule,
premises,
conclusion,
};
steps.push(step);
}
sys::vampire_free_proof_steps(steps_ptr, steps_len);
Self { steps }
}
}
pub fn steps(&self) -> &[ProofStep] {
&self.steps
}
pub fn topo_iter(&self) -> impl Iterator<Item = &ProofStep> {
self.steps.iter()
}
}
impl Display for Proof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, step) in self.steps().iter().enumerate() {
writeln!(
f,
"{}: {} [{:?}{}]",
i,
step.conclusion(),
step.rule(),
step.premises()
.iter()
.fold(String::new(), |s, p| s + " " + &p.to_string())
)?
}
Ok(())
}
}
impl Index<usize> for Proof {
type Output = ProofStep;
fn index(&self, index: usize) -> &Self::Output {
&self.steps[index]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ProofRule {
Axiom,
NegatedConjecture,
Rectify,
Flatten,
EENFTransformation,
CNFTransformation,
NNFTransformation,
SkolemSymbolIntroduction,
Skolemize,
Superposition,
ForwardDemodulation,
BackwardDemodulation,
ForwardSubsumptionResolution,
Resolution,
TrivialInequalityRemoval,
Avatar,
Other,
}
impl ProofRule {
fn from_raw(rule: u32, input_type: u32) -> Self {
if rule == sys::vampire_inference_rule_t_INPUT {
if input_type == sys::vampire_input_type_t_VAMPIRE_AXIOM {
Self::Axiom
} else if input_type == sys::vampire_input_type_t_VAMPIRE_NEGATED_CONJECTURE {
Self::NegatedConjecture
} else {
unreachable!()
}
} else if rule == sys::vampire_inference_rule_t_RECTIFY {
Self::Rectify
} else if rule == sys::vampire_inference_rule_t_FLATTEN {
Self::Flatten
} else if rule == sys::vampire_inference_rule_t_ENNF {
Self::EENFTransformation
} else if rule == sys::vampire_inference_rule_t_CLAUSIFY {
Self::CNFTransformation
} else if rule == sys::vampire_inference_rule_t_NNF {
Self::NNFTransformation
} else if rule == sys::vampire_inference_rule_t_SKOLEM_SYMBOL_INTRODUCTION {
Self::Skolemize
} else if rule == sys::vampire_inference_rule_t_SKOLEMIZE {
Self::SkolemSymbolIntroduction
} else if rule == sys::vampire_inference_rule_t_SUPERPOSITION {
Self::Superposition
} else if rule == sys::vampire_inference_rule_t_FORWARD_DEMODULATION {
Self::ForwardDemodulation
} else if rule == sys::vampire_inference_rule_t_BACKWARD_DEMODULATION {
Self::BackwardDemodulation
} else if rule == sys::vampire_inference_rule_t_FORWARD_SUBSUMPTION_RESOLUTION {
Self::ForwardSubsumptionResolution
} else if rule == sys::vampire_inference_rule_t_RESOLUTION {
Self::Resolution
} else if rule == sys::vampire_inference_rule_t_TRIVIAL_INEQUALITY_REMOVAL {
Self::TrivialInequalityRemoval
} else if rule == sys::vampire_inference_rule_t_AVATAR_DEFINITION
|| rule == sys::vampire_inference_rule_t_AVATAR_COMPONENT
|| rule == sys::vampire_inference_rule_t_AVATAR_SPLIT_CLAUSE
|| rule == sys::vampire_inference_rule_t_AVATAR_CONTRADICTION_CLAUSE
|| rule == sys::vampire_inference_rule_t_AVATAR_REFUTATION
{
Self::Avatar
} else {
Self::Other
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProofStep {
discovery_order: u32,
rule: ProofRule,
premises: Vec<usize>,
conclusion: Formula,
}
impl ProofStep {
pub fn conclusion(&self) -> Formula {
self.conclusion
}
pub fn rule(&self) -> ProofRule {
self.rule
}
pub fn premises(&self) -> &[usize] {
&self.premises
}
pub fn discovery_order(&self) -> u32 {
self.discovery_order
}
}
#[cfg(test)]
mod test {
use crate::{Function, Options, Predicate, Problem, ProofRes, Term, exists, forall};
#[test]
fn test_with_syntax() {
let f = Function::new("f", 2);
let p = Predicate::new("p", 1);
let x = Term::new_var(0);
let y = Term::new_var(1);
let _t1 = f.with([x, y]);
let _f1 = p.with([x]);
let _t2 = f.with(&[x, y]);
let _f2 = p.with(&[x]);
let _t3 = f.with(&vec![x, y]);
let _f3 = p.with(vec![x]);
let _f4 = p.with(x);
}
#[test]
fn socrates_proof() {
let is_mortal = Predicate::new("mortal", 1);
let is_man = Predicate::new("man", 1);
let men_are_mortal = forall(|x| is_man.with(x) >> is_mortal.with(x));
let socrates = Function::constant("socrates");
let socrates_is_man = is_man.with(socrates);
let socrates_is_mortal = is_mortal.with(socrates);
let solution = Problem::new(Options::new())
.with_axiom(socrates_is_man)
.with_axiom(men_are_mortal)
.conjecture(socrates_is_mortal)
.solve();
assert_eq!(solution, ProofRes::Proved);
}
#[test]
fn graph_reachability() {
let edge = Predicate::new("edge", 2);
let path = Predicate::new("path", 2);
let a = Function::constant("a");
let b = Function::constant("b");
let c = Function::constant("c");
let d = Function::constant("d");
let e = Function::constant("e");
let direct_edge_is_path = forall(|x| forall(|y| edge.with([x, y]) >> path.with([x, y])));
let path_transitivity = forall(|x| {
forall(|y| forall(|z| (path.with([x, y]) & path.with([y, z])) >> path.with([x, z])))
});
let edge_ab = edge.with([a, b]);
let edge_bc = edge.with([b, c]);
let edge_cd = edge.with([c, d]);
let edge_de = edge.with([d, e]);
let conjecture = path.with([a, e]);
let solution = Problem::new(Options::new())
.with_axiom(direct_edge_is_path)
.with_axiom(path_transitivity)
.with_axiom(edge_ab)
.with_axiom(edge_bc)
.with_axiom(edge_cd)
.with_axiom(edge_de)
.conjecture(conjecture)
.solve();
assert_eq!(solution, ProofRes::Proved);
}
#[test]
fn group_left_identity() {
let mult = Function::new("mult", 2);
let inv = Function::new("inv", 1);
let one = Function::constant("1");
let mul = |x: Term, y: Term| -> Term { mult.with([x, y]) };
let right_identity = forall(|x| mul(x, one).eq(x));
let right_inverse = forall(|x| {
let inv_x = inv.with(x);
mul(x, inv_x).eq(one)
});
let associativity =
forall(|x| forall(|y| forall(|z| mul(mul(x, y), z).eq(mul(x, mul(y, z))))));
let left_identity = forall(|x| mul(one, x).eq(x));
let solution = Problem::new(Options::new())
.with_axiom(right_identity)
.with_axiom(right_inverse)
.with_axiom(associativity)
.conjecture(left_identity)
.solve();
assert_eq!(solution, ProofRes::Proved);
}
#[test]
fn group_index2_subgroup_normal() {
let mult = Function::new("mult", 2);
let inv = Function::new("inv", 1);
let one = Function::constant("1");
let mul = |x: Term, y: Term| -> Term { mult.with([x, y]) };
let right_identity = forall(|x| mul(x, one).eq(x));
let right_inverse = forall(|x| {
let inv_x = inv.with(x);
mul(x, inv_x).eq(one)
});
let associativity =
forall(|x| forall(|y| forall(|z| mul(mul(x, y), z).eq(mul(x, mul(y, z))))));
let h = Predicate::new("h", 1);
let h_ident = h.with(one);
let h_mul_closed = forall(|x| forall(|y| (h.with(x) & h.with(y)) >> h.with(mul(x, y))));
let h_inv_closed = forall(|x| h.with(x) >> h.with(inv.with(x)));
let h_index_2 = exists(|x| {
let not_in_h = !h.with(x);
let class = forall(|y| h.with(y) | h.with(mul(inv.with(x), y)));
not_in_h & class
});
let h_normal = forall(|x| {
let h_x = h.with(x);
let conj_x = forall(|y| {
let y_inv = inv.with(y);
h.with(mul(mul(y, x), y_inv))
});
h_x.iff(conj_x)
});
let solution = Problem::new(Options::new())
.with_axiom(right_identity)
.with_axiom(right_inverse)
.with_axiom(associativity)
.with_axiom(h_ident)
.with_axiom(h_mul_closed)
.with_axiom(h_inv_closed)
.with_axiom(h_index_2)
.conjecture(h_normal)
.solve();
assert_eq!(solution, ProofRes::Proved);
}
#[test]
fn term_structural_equality() {
let f = Function::new("f_teq", 2);
let g = Function::new("g_teq", 1);
let a1 = Function::constant("a_teq");
let a2 = Function::constant("a_teq");
assert_eq!(a1, a2);
let b = Function::constant("b_teq");
assert_ne!(a1, b);
let t1 = f.with([g.with(a1), b]);
let t2 = f.with([g.with(a1), b]);
assert_eq!(t1, t2);
let t3 = f.with([g.with(b), b]);
assert_ne!(t1, t3);
let x0a = Term::new_var(0);
let x0b = Term::new_var(0);
assert_eq!(x0a, x0b);
let x1 = Term::new_var(1);
assert_ne!(x0a, x1);
assert_ne!(x0a, a1);
}
#[test]
fn term_structural_hash() {
use std::collections::HashSet;
let f = Function::new("f_thash", 2);
let a = Function::constant("a_thash");
let b = Function::constant("b_thash");
let t1 = f.with([a, b]);
let t2 = f.with([a, b]);
let t3 = f.with([b, a]);
let mut set = HashSet::new();
set.insert(t1);
assert!(set.contains(&t2));
set.insert(t3);
assert_eq!(set.len(), 2);
}
#[test]
fn formula_structural_equality() {
let p = Predicate::new("p_feq", 1);
let q = Predicate::new("q_feq", 1);
let a = Function::constant("a_feq");
let b = Function::constant("b_feq");
let pa1 = p.with(a);
let pa2 = p.with(a);
assert_eq!(pa1, pa2);
assert_ne!(p.with(a), q.with(a));
assert_ne!(p.with(a), p.with(b));
assert_eq!(!pa1, !pa2);
assert_ne!(!pa1, !p.with(b));
let f1 = p.with(a) & q.with(b);
let f2 = p.with(a) & q.with(b);
let f3 = p.with(a) & q.with(a);
assert_eq!(f1, f2);
assert_ne!(f1, f3);
let i1 = p.with(a) >> q.with(b);
let i2 = p.with(a) >> q.with(b);
assert_eq!(i1, i2);
assert_ne!(p.with(a) >> q.with(b), q.with(b) >> p.with(a));
let fa = forall(|x| p.with(x));
assert_eq!(fa, fa);
let fe = exists(|x| p.with(x));
assert_ne!(fa, fe);
let eq1 = a.eq(b);
let eq2 = a.eq(b);
assert_eq!(eq1, eq2);
assert_ne!(a.eq(b), !p.with(a));
}
#[test]
fn formula_structural_hash() {
use std::collections::HashSet;
let p = Predicate::new("p_fhash", 1);
let q = Predicate::new("q_fhash", 1);
let a = Function::constant("a_fhash");
let f1 = p.with(a) & q.with(a);
let f2 = p.with(a) & q.with(a);
let f3 = p.with(a) | q.with(a);
let mut set = HashSet::new();
set.insert(f1);
assert!(set.contains(&f2));
set.insert(f3);
assert_eq!(set.len(), 2);
}
}