Skip to content

LazyLattice and LazyConcept #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions concepts/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import formats
from . import junctors
from . import lattices
from . import lazy_lattices
from . import matrices
from . import tools

Expand Down Expand Up @@ -513,6 +514,15 @@ def lattice(self) -> 'lattices.Lattice':
"""
return lattices.Lattice(self)

@tools.lazyproperty
def lazy_lattice(self) -> 'lazy_lattices.LazyLattice':
"""The lazy concept lattice of the formal context.

Returns:
lazy_lattices.LazyLattice: Cached or new :class:`lazy_lattices.LazyLattice` instance.
"""
return lazy_lattices.LazyLattice(self)


class ExportableMixin:

Expand Down
101 changes: 101 additions & 0 deletions concepts/lazy_lattice_members.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import typing

from .algorithms import lindig
from .lattice_members import OrderableMixin


class LazyPair:
def __init__(self,
lattice,
extent,
intent,
upper=None,
lower=None) -> None:
self.lattice = lattice
self._extent = extent
self._intent = intent
self._upper_neighbors = upper
self._lower_neighbors = lower

@property
def upper_neighbors(self):
if self._upper_neighbors is None:
neighbors = lindig.neighbors(self._extent, Objects=self.lattice._context._Objects)

shortlex = self.lattice._shortlex

upper = (self.lattice[extent.members()] for extent, _ in neighbors)
self._upper_neighbors = tuple(sorted(upper, key=shortlex))

return self._upper_neighbors

@property
def lower_neighbors(self):
if self._lower_neighbors is None:
neighbors = lindig.neighbors(self._intent, Objects=self.lattice._context._Properties)

longlex = self.lattice._longlex

lower = (self.lattice[intent.members()] for intent, _ in neighbors)
self._lower_neighbors = tuple(sorted(lower, key=longlex))

return self._lower_neighbors

@property
def extent(self) -> typing.Tuple[str, ...]:
return self._extent.members()

@property
def intent(self) -> typing.Tuple[str, ...]:
return self._intent.members()

def _eq(self, other):
if not isinstance(other, LazyConcept):
return NotImplemented

if (other._extent.members() != self._extent.members()
or other._intent.members() != self._intent.members()):
return False

return True


class LazyRelationsMixin:
def incompatible_with(self, other: 'LazyConcept') -> bool:
return not self._extent & other._extent


class LazyTransformableMixin:
def join(self, other: 'LazyConcept') -> 'LazyConcept':
common = self._extent | other._extent
extent = self.lattice._context._extents.double(common)
return self.lattice[extent.members()]

__or__ = join

def meet(self, other: 'LazyConcept') -> 'LazyConcept':
common = self._extent & other._extent
extent = self.lattice._context._extents.double(common)
return self.lattice[extent.members()]

__and__ = meet


class LazyFormattingMixin:
def __str__(self) -> str:
extent = ', '.join(self._extent.members())
intent = ' '.join(self._intent.members())
return f'{{{extent}}} <-> [{intent}]'

def __repr__(self) -> str:
return f'<{self.__class__.__name__} {self}>'


class LazyConcept(LazyRelationsMixin, OrderableMixin, LazyFormattingMixin, LazyPair):
def minimal(self) -> typing.Tuple[str, ...]:
return self.lattice._context._minimal(self._extent,
self._intent).members()

def attributes(self) -> typing.Iterator[typing.Tuple[str]]:
minimize = self.lattice._context._minimize(self._extent, self._intent)
return (i.members() for i in minimize)
63 changes: 63 additions & 0 deletions concepts/lazy_lattices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import typing

from . import contexts
from .lazy_lattice_members import LazyConcept


class LazyFormattingMixin:
def __str__(self) -> str:
concepts = '\n'.join(f' {c}' for c in self._concepts)
return f'{self!r}\n{concepts}'

def __repr__(self) -> str:
return (f'<{self.__class__.__name__} object'
f' {len(self)} concepts'
f' at {id(self):#x}>')


class LazyLattice(LazyFormattingMixin):
@staticmethod
def _longlex(concept):
return concept._extent.longlex()

@staticmethod
def _shortlex(concept):
return concept._extent.shortlex()

def __init__(self, context: 'contexts.Context'):
self._context = context
self._concepts = []
self._mapping = {}

def __repr__(self) -> str:
return (f'<{self.__class__.__name__} object'
f' {len(self)} concepts'
f' at {id(self):#x}>')

def __getitem__(self, key: typing.Tuple[str, ...]) -> LazyConcept:
if not key:
key = self._context._Objects.supremum.members()

extent, intent = self._context.__getitem__(key, raw=True)

if extent not in self._mapping:
new_concept = LazyConcept(self, extent, intent)
self._mapping[extent] = new_concept
self._concepts.append(new_concept)
self._concepts.sort(key=self._shortlex)

return self._mapping[extent]

def __iter__(self) -> typing.Iterator[LazyConcept]:
return iter(self._concepts)

def __len__(self) -> int:
return len(self._concepts)