Source code for knowledgespaces.structures.reports

"""Path validation and multi-item differences, distinct from canonical fringes."""

from __future__ import annotations

from collections.abc import Collection, Iterable
from dataclasses import dataclass
from itertools import pairwise

from knowledgespaces.structures.knowledge_structure import KnowledgeStructure
from knowledgespaces.structures.set_family import SetFamily


[docs] def is_gradation( structure: KnowledgeStructure, path: Iterable[Collection[str]], *, start: Collection[str] | None = None, target: Collection[str] | None = None, ) -> bool: """Whether a supplied path adds one item per step between its endpoints. Defaults are ∅ and Q, giving a full gradation. With explicit endpoints this validates a single-item chain in that interval. All members must belong to ``structure``; repetition, deletion, or exchanging items is invalid even if successive cardinalities differ by one. Invalid expected endpoints raise ``ValueError``; an empty or invalid path returns false. Equal expected endpoints admit only their singleton path. """ first = frozenset() if start is None else frozenset(start) last = structure.domain if target is None else frozenset(target) if first not in structure or last not in structure or not first <= last: raise ValueError("Endpoints must be states with start <= target.") states = tuple(frozenset(s) for s in path) return bool( states and states[0] == first and states[-1] == last and all(s in structure for s in states) and all(a < b and len(b - a) == 1 for a, b in pairwise(states)) )
[docs] @dataclass(frozen=True) class StateChanges: """Families of changed-item sets within a symmetric-difference radius. ``all_changes`` corresponds to the generalized ``kstpy.fringe`` output. ``removals`` reach proper subsets, ``additions`` proper supersets, and ``mixed`` incomparable states. These contain sets of items, whereas a canonical fringe contains individual items. The centre is excluded. """ centre: frozenset[str] distance: int all_changes: frozenset[frozenset[str]] removals: frozenset[frozenset[str]] additions: frozenset[frozenset[str]] @property def mixed(self) -> frozenset[frozenset[str]]: """Changes that remove some items and add others.""" return self.all_changes - self.removals - self.additions
[docs] def state_changes( structure: KnowledgeStructure | SetFamily, state: Collection[str], distance: int = 1, ) -> StateChanges: """Report multi-item changes to states at distance 1 through ``distance``. The radius uses set distance, not graph distance; intermediate states need not exist. ``distance=1`` recovers singleton sets for the canonical inner/outer fringes. No states, including endpoints, are inserted when the input is a ``SetFamily``. This enumerates only the supplied family. """ centre = frozenset(state) if centre not in structure: raise ValueError("The centre must belong to the supplied family.") if isinstance(distance, bool) or not isinstance(distance, int) or distance < 0: raise ValueError("distance must be a nonnegative integer.") neighbours = [s for s in structure if 0 < len(s ^ centre) <= distance] return StateChanges( centre, distance, frozenset(s ^ centre for s in neighbours), frozenset(centre - s for s in neighbours if s < centre), frozenset(s - centre for s in neighbours if centre < s), )