odis is a Rust library for Formal Concept Analysis.
It works on formal contexts (objects × attributes incidence tables) and currently provides
concept enumeration, implication bases, lattice drawing, and attribute exploration.
[dependencies]
odis = "2026.9.1"use odis::{FormalContext, algorithms::NextClosure};
use odis::traits::ConceptEnumerator;
// Parse a Burmeister (.cxt) file
let bytes = std::fs::read("context.cxt").unwrap();
let ctx = FormalContext::<String>::from(&bytes).unwrap();
// Enumerate all formal concepts
for (extent, intent) in NextClosure.enumerate_concepts(&ctx) {
println!("extent: {:?} intent: {:?}", extent, intent);
}
// Or use the built-in shortcut (defaults to NextClosure)
let concepts: Vec<_> = ctx.concepts().collect();ConceptEnumerator is implemented by:
| Struct | Algorithm |
|---|---|
NextClosure |
Lectic-order enumeration |
Fcbo |
Fast Close-by-One |
use odis::algorithms::CanonicalBasis;
use odis::traits::ImplicationEngine;
let basis = CanonicalBasis.compute_named_basis(&ctx);
for (premise, conclusion) in &basis {
println!("{:?} → {:?}", premise, conclusion);
}DrawingAlgorithm is implemented by:
| Struct | Algorithm |
|---|---|
DimDraw { budget } |
Realizer-based drawing algorithm; budget defaults to one second, SearchBudget::Unbounded searches to a proven optimum |
Sugiyama |
Hierarchical layout via rust-sugiyama |
use odis::algorithms::{DimDraw, NextClosure};
use odis::traits::{ConceptEnumerator, DrawingAlgorithm};
let lattice = ctx.concept_lattice().unwrap();
let drawing = DimDraw::default().draw(&lattice).unwrap();ConceptDrawingAlgorithm
is for layouts that need to know which objects and attributes each node stands
for, which a node type generic in T cannot tell them:
| Struct | Algorithm |
|---|---|
DimFlux { budget, iterations } |
DimDraw layout projected into the space of additive diagrams, then refined by a force-directed model that pushes concept nodes away from the edges they are not part of |
use odis::{algorithms::DimFlux, traits::ConceptDrawingAlgorithm};
let drawing = DimFlux::default().draw_context(&ctx).unwrap();The same trait draws iceberg lattices, which need no bottom element of their own:
use odis::{algorithms::{DimFlux, Titanic}, traits::{ConceptDrawingAlgorithm, IcebergConceptEnumerator}};
let iceberg = Titanic.enumerate(&ctx, 3);
let drawing = DimFlux::default().draw_iceberg(&iceberg, ctx.attributes.len()).unwrap();use odis::algorithms::Titanic;
use odis::traits::IcebergConceptEnumerator;
// Concepts whose extent has at least 3 objects
let iceberg = Titanic.enumerate_named_concepts(&ctx, 3);ExplorationMachine drives interactive attribute exploration over a growing context.
The state machine separates exploration logic from I/O.
Contexts are parsed from the Burmeister .cxt format:
B
2
2
bird
fish
flies
lives_in_water
X.
.X
Contexts published in the FCA literature can be loaded straight from the FCA repository:
use odis::repository;
for entry in repository::fetch_catalog().await? {
println!("{} — {} ({:?} objects)", entry.filename, entry.title, entry.objects);
}
let ctx = repository::fetch_context("livingbeings_en.cxt").await?;The API is async because it also compiles for wasm32, where the only available
transport is the browser's fetch API, which has no blocking form. The same code runs
on all targets. repository::parse_catalog and repository::context_url are public
too, for a caller that already has the bytes.
cargo doc -p odis --no-deps --open