use bit_set::BitSet;
use std::collections::HashMap;
use crate::FormalContext;
enum OutputType {
FormalConcept(
(BitSet, BitSet),
usize,
),
DeadEndAttributes(
BitSet,
usize,
),
NodeCleared,
}
struct CallingContext {
input_attr: BitSet,
inner_index: usize,
dead_end_attr: Option<HashMap<usize, BitSet>>,
}
impl CallingContext {
fn new(input_attr: BitSet, inner_index: usize) -> Self {
CallingContext {
input_attr,
inner_index,
dead_end_attr: None,
}
}
}
fn canonicity_test_one(
smaller_subsets: &[BitSet],
inner_index: usize,
input_attributes: &BitSet,
dead_end_attr_set: &HashMap<usize, BitSet>,
) -> bool {
let intersection_dead = dead_end_attr_set
.get(&inner_index)
.unwrap()
.intersection(&smaller_subsets[inner_index])
.collect::<BitSet>();
let intersection_input = input_attributes
.intersection(&smaller_subsets[inner_index])
.collect();
intersection_dead.is_subset(&intersection_input)
}
fn canonicity_test_two(
smaller_subsets: &[BitSet],
inner_index: usize,
input_attributes: &BitSet,
next_attributes: &BitSet,
) -> bool {
let intersection_input: BitSet = input_attributes
.intersection(&smaller_subsets[inner_index])
.collect();
let intersection_next: BitSet = next_attributes
.intersection(&smaller_subsets[inner_index])
.collect();
intersection_input == intersection_next
}
fn fcbo_next_concept<T>(
context: &FormalContext<T>,
smaller_subsets: &[BitSet],
input_attributes: &BitSet,
inner_index: usize,
dead_end_attr_set: &HashMap<usize, BitSet>,
) -> OutputType {
for j in inner_index..context.attributes.len() {
if !input_attributes.contains(j)
&& canonicity_test_one(smaller_subsets, j, input_attributes, dead_end_attr_set)
{
let mut new_attr = BitSet::new();
new_attr.insert(j);
let next_objects: BitSet = context
.index_extent(input_attributes)
.intersection(&context.index_extent(&new_attr))
.collect();
let next_attributes = context.index_intent(&next_objects);
if canonicity_test_two(smaller_subsets, j, input_attributes, &next_attributes) {
return OutputType::FormalConcept((next_objects, next_attributes), j);
} else {
return OutputType::DeadEndAttributes(next_attributes, j);
}
}
}
OutputType::NodeCleared
}
pub fn index_fcbo_concepts<'a, T>(
context: &'a FormalContext<T>,
) -> impl Iterator<Item = (BitSet, BitSet)> + 'a {
let attr_length = context.attributes.len();
let mut smaller_subsets: Vec<BitSet> = Vec::with_capacity(attr_length);
for i in 0..attr_length {
smaller_subsets.push((0..i).collect());
}
let starting_objects = context.index_extent(&BitSet::new());
let mut input_attributes = context.index_intent(&starting_objects);
let mut inner_index = 0;
let mut dead_end_attr_set = HashMap::new();
for i in 0..attr_length {
dead_end_attr_set.insert(i, BitSet::new());
}
let mut queue: Vec<CallingContext> = Vec::new();
let mut branches: usize = 0;
let mut first_concept = true;
std::iter::from_fn(move || {
if first_concept {
first_concept = false;
return Some((starting_objects.clone(), input_attributes.clone()));
}
loop {
let output = fcbo_next_concept(
context,
&smaller_subsets,
&input_attributes,
inner_index,
&dead_end_attr_set,
);
match output {
OutputType::FormalConcept(formal_concept, previous_inner_index) => {
inner_index = previous_inner_index + 1;
if formal_concept.1 != (0..attr_length).collect()
&& previous_inner_index < attr_length - 1
{
branches += 1;
queue.push(CallingContext::new(formal_concept.1.clone(), inner_index));
}
return Some(formal_concept);
}
OutputType::DeadEndAttributes(dead_end_attributes, previous_inner_index) => {
dead_end_attr_set.insert(previous_inner_index, dead_end_attributes);
inner_index = previous_inner_index + 1;
}
OutputType::NodeCleared => {
if queue.is_empty() {
return None;
}
if branches != 0 {
for j in 0..queue[queue.len() - branches].inner_index {
dead_end_attr_set.remove(&j);
}
for i in (queue.len() - branches)..(queue.len()) {
queue[i].dead_end_attr = Some(dead_end_attr_set.clone());
}
branches = 0;
}
let state = queue.pop().unwrap();
input_attributes = state.input_attr;
inner_index = state.inner_index;
dead_end_attr_set = state.dead_end_attr.unwrap();
}
}
}
})
}
pub struct Fcbo;
impl<T> crate::traits::ConceptEnumerator<T> for Fcbo {
fn enumerate_concepts<'ctx>(
&self,
context: &'ctx crate::FormalContext<T>,
) -> impl Iterator<Item = (BitSet, BitSet)> + 'ctx {
index_fcbo_concepts(context)
}
}
#[cfg(test)]
mod tests {
use crate::{algorithms::fcbo::index_fcbo_concepts, FormalContext};
use bit_set::BitSet;
use itertools::Itertools;
use std::{collections::BTreeSet, fs};
#[test]
fn test_data_1() {
let context = FormalContext::<String>::from(
&fs::read("test_data/living_beings_and_water.cxt").unwrap(),
)
.unwrap();
let concepts: BTreeSet<_> = index_fcbo_concepts(&context).map(|(_, x)| x).collect();
let mut concepts_val = BTreeSet::new();
for ms in (0..context.attributes.len()).powerset() {
let sub: BitSet = ms.into_iter().collect();
let hull = context.index_attribute_hull(&sub);
if sub == hull {
concepts_val.insert(hull);
}
}
assert_eq!(concepts, concepts_val);
}
#[test]
fn test_data_2() {
let context =
FormalContext::<String>::from(&fs::read("test_data/eu.cxt").unwrap()).unwrap();
let concepts: BTreeSet<_> = index_fcbo_concepts(&context).map(|(_, x)| x).collect();
let mut concepts_val = BTreeSet::new();
for ms in (0..context.attributes.len()).powerset() {
let sub: BitSet = ms.into_iter().collect();
let hull = context.index_attribute_hull(&sub);
if sub == hull {
concepts_val.insert(hull);
}
}
assert_eq!(concepts, concepts_val);
}
#[test]
fn test_data_3() {
let context =
FormalContext::<String>::from(&fs::read("test_data/data_from_paper.cxt").unwrap())
.unwrap();
let concepts: BTreeSet<_> = index_fcbo_concepts(&context).map(|(_, x)| x).collect();
let mut concepts_val = BTreeSet::new();
for ms in (0..context.attributes.len()).powerset() {
let sub: BitSet = ms.into_iter().collect();
let hull = context.index_attribute_hull(&sub);
if sub == hull {
concepts_val.insert(hull);
}
}
assert_eq!(concepts, concepts_val);
}
fn next_closure_intents(ctx: &FormalContext<usize>) -> BTreeSet<BitSet> {
use crate::algorithms::next_closure::index_next_closure_concepts;
index_next_closure_concepts(ctx).map(|(_, ms)| ms).collect()
}
#[test]
fn test_fcbo_zero_objects() {
let mut ctx = FormalContext::<usize>::new();
for m in 0..5 {
ctx.add_attribute(m, &BitSet::new());
}
let fcbo: BTreeSet<_> = index_fcbo_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(fcbo, next_closure_intents(&ctx));
}
#[test]
fn test_fcbo_zero_attributes() {
let mut ctx = FormalContext::<usize>::new();
for g in 0..5 {
ctx.add_object(g, &BitSet::new());
}
let fcbo: BTreeSet<_> = index_fcbo_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(fcbo, next_closure_intents(&ctx));
}
#[test]
fn test_fcbo_single_object_full() {
let mut ctx = FormalContext::<usize>::new();
for m in 0..5 {
ctx.add_attribute(m, &BitSet::new());
}
ctx.add_object(0, &(0..5).collect::<BitSet>());
let fcbo: BTreeSet<_> = index_fcbo_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(fcbo, next_closure_intents(&ctx));
}
#[test]
fn test_fcbo_full_context() {
let mut ctx = FormalContext::<usize>::new();
for m in 0..5 {
ctx.add_attribute(m, &BitSet::new());
}
for g in 0..5 {
ctx.add_object(g, &(0..5).collect::<BitSet>());
}
let fcbo: BTreeSet<_> = index_fcbo_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(fcbo, next_closure_intents(&ctx));
}
#[test]
fn test_fcbo_random_20x20() {
use rand::{Rng, SeedableRng};
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let n = 20;
let mut ctx = FormalContext::<usize>::new();
for m in 0..n {
ctx.add_attribute(m, &BitSet::new());
}
for g in 0..n {
let intent: BitSet = (0..n).filter(|_| rng.gen::<bool>()).collect();
ctx.add_object(g, &intent);
}
let fcbo: BTreeSet<_> = index_fcbo_concepts(&ctx).map(|(_, ms)| ms).collect();
assert_eq!(fcbo, next_closure_intents(&ctx));
}
}