use std::collections::{HashMap, HashSet};
use super::functions::PropVar;
use super::functions::*;
#[derive(Debug, Clone)]
pub struct PdlModel {
pub kripke: KripkeModel,
pub n_programs: usize,
}
impl PdlModel {
pub fn new(kripke: KripkeModel, n_programs: usize) -> Self {
PdlModel { kripke, n_programs }
}
pub fn reachable(&self, start: usize, prog: &PdlProgram) -> HashSet<usize> {
match prog {
PdlProgram::Atomic(rel) => self
.kripke
.frame
.successors(*rel, start)
.into_iter()
.collect(),
PdlProgram::Test(phi) => {
if self.kripke.satisfies(start, phi) {
let mut s = HashSet::new();
s.insert(start);
s
} else {
HashSet::new()
}
}
PdlProgram::Sequence(alpha, beta) => {
let after_alpha = self.reachable(start, alpha);
let mut result = HashSet::new();
for w in after_alpha {
result.extend(self.reachable(w, beta));
}
result
}
PdlProgram::Choice(alpha, beta) => {
let mut r = self.reachable(start, alpha);
r.extend(self.reachable(start, beta));
r
}
PdlProgram::Star(alpha) => {
let mut reachable = HashSet::new();
reachable.insert(start);
loop {
let current: Vec<usize> = reachable.iter().cloned().collect();
let mut added = false;
for w in current {
for v in self.reachable(w, alpha) {
if reachable.insert(v) {
added = true;
}
}
}
if !added {
break;
}
}
reachable
}
}
}
pub fn box_program(&self, w: usize, prog: &PdlProgram, phi: &ModalFormula) -> bool {
self.reachable(w, prog)
.iter()
.all(|&v| self.kripke.satisfies(v, phi))
}
pub fn diamond_program(&self, w: usize, prog: &PdlProgram, phi: &ModalFormula) -> bool {
self.reachable(w, prog)
.iter()
.any(|&v| self.kripke.satisfies(v, phi))
}
}
#[derive(Debug, Clone)]
pub struct MuCalculusEval {
pub model: KripkeModel,
pub env: HashMap<PropVar, HashSet<usize>>,
}
impl MuCalculusEval {
pub fn new(model: KripkeModel) -> Self {
MuCalculusEval {
model,
env: HashMap::new(),
}
}
pub fn least_fixed_point<F>(&self, step: F) -> HashSet<usize>
where
F: Fn(&HashSet<usize>) -> HashSet<usize>,
{
let mut current: HashSet<usize> = HashSet::new();
loop {
let next = step(¤t);
if next == current {
return current;
}
current = next;
}
}
pub fn greatest_fixed_point<F>(&self, step: F) -> HashSet<usize>
where
F: Fn(&HashSet<usize>) -> HashSet<usize>,
{
let all_worlds: HashSet<usize> = (0..self.model.frame.n_worlds).collect();
let mut current = all_worlds;
loop {
let next = step(¤t);
if next == current {
return current;
}
current = next;
}
}
pub fn reachability(&self, phi: &ModalFormula) -> HashSet<usize> {
let model = &self.model;
self.least_fixed_point(|x| {
(0..model.frame.n_worlds)
.filter(|&w| {
model.satisfies(w, phi)
|| model.frame.successors(0, w).iter().any(|&v| x.contains(&v))
})
.collect()
})
}
pub fn safety(&self, phi: &ModalFormula) -> HashSet<usize> {
let model = &self.model;
self.greatest_fixed_point(|x| {
(0..model.frame.n_worlds)
.filter(|&w| {
model.satisfies(w, phi)
&& model.frame.successors(0, w).iter().all(|&v| x.contains(&v))
})
.collect()
})
}
}
#[derive(Debug)]
pub struct TableauProver {
pub nodes: Vec<TableauNode>,
pub next_world: usize,
pub edges: Vec<(usize, usize)>,
}
impl TableauProver {
pub fn new() -> Self {
let root = TableauNode::new(0);
TableauProver {
nodes: vec![root],
next_world: 1,
edges: Vec::new(),
}
}
pub fn is_tautology(&mut self, phi: &ModalFormula) -> bool {
let neg = ModalFormula::not(phi.clone());
self.nodes[0].add_positive(neg);
self.nodes[0].detect_closure();
self.nodes[0].closed
}
}
#[derive(Debug)]
pub struct CanonicalModel {
pub worlds: Vec<MaximalConsistentSet>,
pub accessibility: HashSet<(usize, usize)>,
}
impl CanonicalModel {
pub fn new() -> Self {
CanonicalModel {
worlds: Vec::new(),
accessibility: HashSet::new(),
}
}
pub fn add_world(&mut self, mcs: MaximalConsistentSet) {
self.worlds.push(mcs);
}
pub fn build_accessibility(&mut self) {
let n = self.worlds.len();
for i in 0..n {
let box_formulas: Vec<ModalFormula> = self.worlds[i]
.formulas
.iter()
.filter_map(|f| {
if let ModalFormula::Box(0, phi) = f {
Some(*phi.clone())
} else {
None
}
})
.collect();
for j in 0..n {
let all_contained = box_formulas.iter().all(|phi| self.worlds[j].contains(phi));
if all_contained {
self.accessibility.insert((i, j));
}
}
}
}
pub fn size(&self) -> usize {
self.worlds.len()
}
}
#[derive(Debug, Clone)]
pub struct EpistemicModel {
pub n_worlds: usize,
pub n_agents: usize,
pub agent_relations: Vec<HashSet<(usize, usize)>>,
pub valuation: HashMap<PropVar, HashSet<usize>>,
}
impl EpistemicModel {
pub fn new(n_worlds: usize, n_agents: usize) -> Self {
EpistemicModel {
n_worlds,
n_agents,
agent_relations: vec![HashSet::new(); n_agents],
valuation: HashMap::new(),
}
}
pub fn add_edge(&mut self, agent: usize, from: usize, to: usize) {
if agent < self.n_agents {
self.agent_relations[agent].insert((from, to));
}
}
pub fn make_equivalence_relations(&mut self) {
for agent in 0..self.n_agents {
for w in 0..self.n_worlds {
self.agent_relations[agent].insert((w, w));
}
let edges: Vec<(usize, usize)> = self.agent_relations[agent].iter().cloned().collect();
for &(a, b) in &edges {
self.agent_relations[agent].insert((b, a));
}
loop {
let edges: Vec<(usize, usize)> =
self.agent_relations[agent].iter().cloned().collect();
let mut changed = false;
for &(a, b) in &edges {
for &(c, d) in &edges {
if b == c && !self.agent_relations[agent].contains(&(a, d)) {
self.agent_relations[agent].insert((a, d));
changed = true;
}
}
}
if !changed {
break;
}
}
}
}
pub fn knows(&self, agent: usize, w: usize, phi: &ModalFormula) -> bool {
if agent >= self.n_agents {
return false;
}
let successors: Vec<usize> = self.agent_relations[agent]
.iter()
.filter(|&&(f, _)| f == w)
.map(|&(_, t)| t)
.collect();
successors
.iter()
.all(|&v| self.satisfies_with_agent(v, phi))
}
fn satisfies_with_agent(&self, w: usize, phi: &ModalFormula) -> bool {
match phi {
ModalFormula::Top => true,
ModalFormula::Bot => false,
ModalFormula::Atom(p) => self.valuation.get(p).is_some_and(|s| s.contains(&w)),
ModalFormula::Not(psi) => !self.satisfies_with_agent(w, psi),
ModalFormula::And(a, b) => {
self.satisfies_with_agent(w, a) && self.satisfies_with_agent(w, b)
}
ModalFormula::Or(a, b) => {
self.satisfies_with_agent(w, a) || self.satisfies_with_agent(w, b)
}
ModalFormula::Implies(a, b) => {
!self.satisfies_with_agent(w, a) || self.satisfies_with_agent(w, b)
}
ModalFormula::Box(rel, psi) => {
let agent = *rel;
self.knows(agent, w, psi)
}
ModalFormula::Diamond(rel, psi) => {
let agent = *rel;
if agent >= self.n_agents {
return false;
}
self.agent_relations[agent]
.iter()
.filter(|&&(f, _)| f == w)
.any(|&(_, v)| self.satisfies_with_agent(v, psi))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SahlqvistClass {
Antecedent,
Consequent,
Full,
NotSahlqvist,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PdlProgram {
Atomic(usize),
Test(Box<ModalFormula>),
Sequence(Box<PdlProgram>, Box<PdlProgram>),
Choice(Box<PdlProgram>, Box<PdlProgram>),
Star(Box<PdlProgram>),
}
#[derive(Debug, Clone)]
pub struct FiniteTrace {
pub steps: Vec<HashMap<PropVar, bool>>,
}
impl FiniteTrace {
pub fn new() -> Self {
FiniteTrace { steps: Vec::new() }
}
pub fn push(&mut self, valuation: HashMap<PropVar, bool>) {
self.steps.push(valuation);
}
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn prop_at(&self, p: PropVar, i: usize) -> bool {
self.steps
.get(i)
.and_then(|s| s.get(&p))
.copied()
.unwrap_or(false)
}
pub fn satisfies(&self, i: usize, phi: &ModalFormula) -> bool {
if i >= self.steps.len() {
return false;
}
match phi {
ModalFormula::Top => true,
ModalFormula::Bot => false,
ModalFormula::Atom(p) => self.prop_at(*p, i),
ModalFormula::Not(psi) => !self.satisfies(i, psi),
ModalFormula::And(a, b) => self.satisfies(i, a) && self.satisfies(i, b),
ModalFormula::Or(a, b) => self.satisfies(i, a) || self.satisfies(i, b),
ModalFormula::Implies(a, b) => !self.satisfies(i, a) || self.satisfies(i, b),
ModalFormula::Box(0, psi) => (i..self.steps.len()).all(|j| self.satisfies(j, psi)),
ModalFormula::Diamond(0, psi) => (i..self.steps.len()).any(|j| self.satisfies(j, psi)),
ModalFormula::Box(1, psi) => i + 1 < self.steps.len() && self.satisfies(i + 1, psi),
ModalFormula::Diamond(1, psi) => {
i + 1 >= self.steps.len() || self.satisfies(i + 1, psi)
}
_ => false,
}
}
pub fn check(&self, phi: &ModalFormula) -> bool {
self.satisfies(0, phi)
}
}
#[derive(Debug, Clone)]
pub struct BeliefRevisionOp {
pub beliefs: Vec<ModalFormula>,
pub entrenchment: HashMap<ModalFormula, u32>,
}
impl BeliefRevisionOp {
pub fn new() -> Self {
BeliefRevisionOp {
beliefs: Vec::new(),
entrenchment: HashMap::new(),
}
}
pub fn add_belief(&mut self, phi: ModalFormula, level: u32) {
if !self.beliefs.contains(&phi) {
self.beliefs.push(phi.clone());
}
self.entrenchment.insert(phi, level);
}
pub fn believes(&self, phi: &ModalFormula) -> bool {
self.beliefs.contains(phi)
}
pub fn revise(&self, phi: &ModalFormula) -> BeliefRevisionOp {
let mut new_op = self.clone();
let neg_phi = ModalFormula::not(phi.clone());
let phi_level = self.entrenchment.get(phi).copied().unwrap_or(0);
new_op.beliefs.retain(|b| {
if b == &neg_phi {
let b_level = self.entrenchment.get(b).copied().unwrap_or(0);
b_level > phi_level
} else {
true
}
});
new_op.add_belief(phi.clone(), phi_level.max(1));
new_op
}
pub fn contract(&self, phi: &ModalFormula) -> BeliefRevisionOp {
let mut new_op = self.clone();
new_op.beliefs.retain(|b| b != phi);
new_op.entrenchment.remove(phi);
new_op
}
pub fn size(&self) -> usize {
self.beliefs.len()
}
}
#[derive(Debug, Clone)]
pub struct KripkeModel {
pub frame: KripkeFrame,
pub valuation: HashMap<PropVar, HashSet<usize>>,
}
impl KripkeModel {
pub fn new(frame: KripkeFrame) -> Self {
KripkeModel {
frame,
valuation: HashMap::new(),
}
}
pub fn set_true(&mut self, p: PropVar, w: usize) {
self.valuation.entry(p).or_default().insert(w);
}
pub fn prop_true(&self, p: PropVar, w: usize) -> bool {
self.valuation.get(&p).is_some_and(|s| s.contains(&w))
}
pub fn satisfies(&self, w: usize, phi: &ModalFormula) -> bool {
match phi {
ModalFormula::Top => true,
ModalFormula::Bot => false,
ModalFormula::Atom(p) => self.prop_true(*p, w),
ModalFormula::Not(psi) => !self.satisfies(w, psi),
ModalFormula::And(a, b) => self.satisfies(w, a) && self.satisfies(w, b),
ModalFormula::Or(a, b) => self.satisfies(w, a) || self.satisfies(w, b),
ModalFormula::Implies(a, b) => !self.satisfies(w, a) || self.satisfies(w, b),
ModalFormula::Box(rel, psi) => self
.frame
.successors(*rel, w)
.iter()
.all(|&v| self.satisfies(v, psi)),
ModalFormula::Diamond(rel, psi) => self
.frame
.successors(*rel, w)
.iter()
.any(|&v| self.satisfies(v, psi)),
}
}
pub fn model_valid(&self, phi: &ModalFormula) -> bool {
(0..self.frame.n_worlds).all(|w| self.satisfies(w, phi))
}
pub fn extension(&self, phi: &ModalFormula) -> HashSet<usize> {
(0..self.frame.n_worlds)
.filter(|&w| self.satisfies(w, phi))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MaximalConsistentSet {
pub id: usize,
pub formulas: Vec<ModalFormula>,
}
impl MaximalConsistentSet {
pub fn new(id: usize, formulas: Vec<ModalFormula>) -> Self {
MaximalConsistentSet { id, formulas }
}
pub fn contains(&self, phi: &ModalFormula) -> bool {
self.formulas.contains(phi)
}
pub fn add(&mut self, phi: ModalFormula) {
if !self.contains(&phi) {
self.formulas.push(phi);
}
}
}
#[derive(Debug, Clone)]
pub struct PublicAnnouncement {
pub announcement: ModalFormula,
}
impl PublicAnnouncement {
pub fn new(phi: ModalFormula) -> Self {
PublicAnnouncement { announcement: phi }
}
pub fn update(&self, model: &EpistemicModel) -> EpistemicModel {
let surviving: HashSet<usize> = (0..model.n_worlds)
.filter(|&w| model.satisfies_with_agent(w, &self.announcement))
.collect();
let old_to_new: HashMap<usize, usize> = surviving
.iter()
.enumerate()
.map(|(new, &old)| (old, new))
.collect();
let n_new = surviving.len();
let mut new_model = EpistemicModel::new(n_new, model.n_agents);
for agent in 0..model.n_agents {
for &(from, to) in &model.agent_relations[agent] {
if let (Some(&nf), Some(&nt)) = (old_to_new.get(&from), old_to_new.get(&to)) {
new_model.agent_relations[agent].insert((nf, nt));
}
}
}
for (&p, worlds) in &model.valuation {
for &w in worlds {
if let Some(&nw) = old_to_new.get(&w) {
new_model.valuation.entry(p).or_default().insert(nw);
}
}
}
new_model
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ModalSystem {
K,
T,
S4,
S5,
D,
KD45,
GL,
B,
}
impl ModalSystem {
pub fn name(&self) -> &'static str {
match self {
ModalSystem::K => "K",
ModalSystem::T => "T",
ModalSystem::S4 => "S4",
ModalSystem::S5 => "S5",
ModalSystem::D => "D",
ModalSystem::KD45 => "KD45",
ModalSystem::GL => "GL",
ModalSystem::B => "B",
}
}
pub fn frame_validates(&self, frame: &KripkeFrame, rel: usize) -> bool {
match self {
ModalSystem::K => true,
ModalSystem::T => frame.is_reflexive(rel),
ModalSystem::S4 => frame.is_reflexive(rel) && frame.is_transitive(rel),
ModalSystem::S5 => {
frame.is_reflexive(rel) && frame.is_transitive(rel) && frame.is_symmetric(rel)
}
ModalSystem::D => frame.is_serial(rel),
ModalSystem::KD45 => {
frame.is_serial(rel) && frame.is_transitive(rel) && frame.is_euclidean(rel)
}
ModalSystem::GL => frame.is_transitive(rel),
ModalSystem::B => frame.is_reflexive(rel) && frame.is_symmetric(rel),
}
}
}
#[derive(Debug, Clone)]
pub struct KripkeFrame {
pub n_worlds: usize,
pub relations: Vec<HashSet<(usize, usize)>>,
}
impl KripkeFrame {
pub fn new(n_worlds: usize, n_relations: usize) -> Self {
KripkeFrame {
n_worlds,
relations: vec![HashSet::new(); n_relations],
}
}
pub fn add_edge(&mut self, rel: usize, from: usize, to: usize) {
if rel < self.relations.len() {
self.relations[rel].insert((from, to));
}
}
pub fn accessible(&self, rel: usize, w: usize, v: usize) -> bool {
self.relations.get(rel).is_some_and(|r| r.contains(&(w, v)))
}
pub fn successors(&self, rel: usize, w: usize) -> Vec<usize> {
self.relations
.get(rel)
.map(|r| r.iter().filter(|(f, _)| *f == w).map(|(_, t)| *t).collect())
.unwrap_or_default()
}
pub fn is_reflexive(&self, rel: usize) -> bool {
(0..self.n_worlds).all(|w| self.accessible(rel, w, w))
}
pub fn is_transitive(&self, rel: usize) -> bool {
let r = match self.relations.get(rel) {
Some(r) => r,
None => return true,
};
let pairs: Vec<(usize, usize)> = r.iter().cloned().collect();
for &(a, b) in &pairs {
for &(c, d) in &pairs {
if b == c && !r.contains(&(a, d)) {
return false;
}
}
}
true
}
pub fn is_symmetric(&self, rel: usize) -> bool {
let r = match self.relations.get(rel) {
Some(r) => r,
None => return true,
};
r.iter().all(|&(a, b)| r.contains(&(b, a)))
}
pub fn is_serial(&self, rel: usize) -> bool {
(0..self.n_worlds).all(|w| !self.successors(rel, w).is_empty())
}
pub fn is_euclidean(&self, rel: usize) -> bool {
let r = match self.relations.get(rel) {
Some(r) => r,
None => return true,
};
let pairs: Vec<(usize, usize)> = r.iter().cloned().collect();
for &(w, v) in &pairs {
for &(w2, u) in &pairs {
if w == w2 && !r.contains(&(v, u)) {
return false;
}
}
}
true
}
pub fn make_reflexive(&mut self, rel: usize) {
for w in 0..self.n_worlds {
self.add_edge(rel, w, w);
}
}
pub fn make_transitive(&mut self, rel: usize) {
if rel >= self.relations.len() {
return;
}
loop {
let current: Vec<(usize, usize)> = self.relations[rel].iter().cloned().collect();
let mut added = false;
for &(a, b) in ¤t {
for &(c, d) in ¤t {
if b == c && !self.relations[rel].contains(&(a, d)) {
self.relations[rel].insert((a, d));
added = true;
}
}
}
if !added {
break;
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ModalFormula {
Atom(PropVar),
Top,
Bot,
Not(Box<ModalFormula>),
And(Box<ModalFormula>, Box<ModalFormula>),
Or(Box<ModalFormula>, Box<ModalFormula>),
Implies(Box<ModalFormula>, Box<ModalFormula>),
Box(usize, Box<ModalFormula>),
Diamond(usize, Box<ModalFormula>),
}
impl ModalFormula {
pub fn atom(p: PropVar) -> Self {
ModalFormula::Atom(p)
}
pub fn necessity(phi: ModalFormula) -> Self {
ModalFormula::Box(0, Box::new(phi))
}
pub fn possibility(phi: ModalFormula) -> Self {
ModalFormula::Diamond(0, Box::new(phi))
}
pub fn implies(a: ModalFormula, b: ModalFormula) -> Self {
ModalFormula::Implies(Box::new(a), Box::new(b))
}
pub fn and(a: ModalFormula, b: ModalFormula) -> Self {
ModalFormula::And(Box::new(a), Box::new(b))
}
pub fn or(a: ModalFormula, b: ModalFormula) -> Self {
ModalFormula::Or(Box::new(a), Box::new(b))
}
pub fn not(a: ModalFormula) -> Self {
ModalFormula::Not(Box::new(a))
}
pub fn subformulas(&self) -> Vec<ModalFormula> {
let mut result = vec![self.clone()];
match self {
ModalFormula::Not(phi) => result.extend(phi.subformulas()),
ModalFormula::And(a, b) | ModalFormula::Or(a, b) | ModalFormula::Implies(a, b) => {
result.extend(a.subformulas());
result.extend(b.subformulas());
}
ModalFormula::Box(_, phi) | ModalFormula::Diamond(_, phi) => {
result.extend(phi.subformulas())
}
_ => {}
}
result
}
pub fn prop_vars(&self) -> HashSet<PropVar> {
let mut vars = HashSet::new();
self.collect_vars(&mut vars);
vars
}
fn collect_vars(&self, vars: &mut HashSet<PropVar>) {
match self {
ModalFormula::Atom(p) => {
vars.insert(*p);
}
ModalFormula::Not(phi) => phi.collect_vars(vars),
ModalFormula::And(a, b) | ModalFormula::Or(a, b) | ModalFormula::Implies(a, b) => {
a.collect_vars(vars);
b.collect_vars(vars);
}
ModalFormula::Box(_, phi) | ModalFormula::Diamond(_, phi) => phi.collect_vars(vars),
_ => {}
}
}
pub fn modal_depth(&self) -> usize {
match self {
ModalFormula::Atom(_) | ModalFormula::Top | ModalFormula::Bot => 0,
ModalFormula::Not(phi) => phi.modal_depth(),
ModalFormula::And(a, b) | ModalFormula::Or(a, b) | ModalFormula::Implies(a, b) => {
a.modal_depth().max(b.modal_depth())
}
ModalFormula::Box(_, phi) | ModalFormula::Diamond(_, phi) => 1 + phi.modal_depth(),
}
}
}
#[derive(Debug, Clone)]
pub struct TableauNode {
pub world: usize,
pub positive: Vec<ModalFormula>,
pub negative: Vec<ModalFormula>,
pub closed: bool,
}
impl TableauNode {
pub fn new(world: usize) -> Self {
TableauNode {
world,
positive: Vec::new(),
negative: Vec::new(),
closed: false,
}
}
pub fn add_positive(&mut self, phi: ModalFormula) {
self.positive.push(phi);
}
pub fn add_negative(&mut self, phi: ModalFormula) {
self.negative.push(phi);
}
pub fn detect_closure(&mut self) {
for p in &self.positive {
if self.negative.contains(p) {
self.closed = true;
return;
}
}
if self.positive.contains(&ModalFormula::Bot) || self.negative.contains(&ModalFormula::Top)
{
self.closed = true;
}
}
}
#[derive(Debug, Clone)]
pub struct GradedModel {
pub model: KripkeModel,
}
impl GradedModel {
pub fn new(model: KripkeModel) -> Self {
GradedModel { model }
}
pub fn graded_diamond(&self, w: usize, n: usize, phi: &ModalFormula) -> bool {
let count = self
.model
.frame
.successors(0, w)
.iter()
.filter(|&&v| self.model.satisfies(v, phi))
.count();
count >= n
}
pub fn graded_box(&self, w: usize, n: usize, phi: &ModalFormula) -> bool {
let failures = self
.model
.frame
.successors(0, w)
.iter()
.filter(|&&v| !self.model.satisfies(v, phi))
.count();
failures <= n
}
pub fn count_satisfying(&self, w: usize, phi: &ModalFormula) -> usize {
self.model
.frame
.successors(0, w)
.iter()
.filter(|&&v| self.model.satisfies(v, phi))
.count()
}
}
#[derive(Debug, Clone)]
pub struct Bisimulation {
pub pairs: HashSet<(usize, usize)>,
}
impl Bisimulation {
pub fn new() -> Self {
Bisimulation {
pairs: HashSet::new(),
}
}
pub fn add_pair(&mut self, w: usize, v: usize) {
self.pairs.insert((w, v));
}
pub fn is_bisimulation_pair(
&self,
_m1: &KripkeModel,
_m2: &KripkeModel,
w1: usize,
w2: usize,
) -> bool {
self.pairs.contains(&(w1, w2))
}
pub fn size(&self) -> usize {
self.pairs.len()
}
}