Source code for logic1.theories.Sets.simplify

"""
This module implements *deep simplification* through the generation and
propagation of internal representations during recursion. It is an adaptation
of the *standard simplifier* from [DolzmannSturm-1997]_ tailored to the
specific requirements of Sets.
"""
from __future__ import annotations

from typing import Iterable, Iterator, Never, Optional, Self

from ... import abc
from dataclasses import dataclass, field

from ...firstorder import And, _F, Or, _T
from .atomic import AtomicFormula, C, C_, Eq, Index, Ne, oo, Variable
from .typing import Formula

from ...support.tracing import trace  # noqa


@dataclass(unsafe_hash=True)
class UnionFind:
    _parents: dict[Variable, Variable] = field(default_factory=dict)

    def copy(self) -> UnionFind:
        return UnionFind(self._parents.copy())

    def find(self, v: Variable) -> Variable:
        try:
            root = self.find(self._parents[v])
        except KeyError:
            return v
        self._parents[v] = root
        return root

    def union(self, v1: Variable, v2: Variable) -> None:
        root1 = self.find(v1)
        root2 = self.find(v2)
        if root1 == root2:
            return
        if Variable.sort_key(root1) <= Variable.sort_key(root2):
            self._parents[root2] = root1
        else:
            self._parents[root1] = root2

    def equations(self) -> Iterator[Eq]:
        for y in self._parents:
            yield Eq(self.find(y), y)


[docs] @dataclass class InternalRepresentation( abc.simplify.InternalRepresentation[AtomicFormula, Variable, Variable, Never]): """Implements the abstract methods :meth:`add() <.abc.simplify.InternalRepresentation.add>`, :meth:`extract() <.abc.simplify.InternalRepresentation.extract>`, and :meth:`next_() <.abc.simplify.InternalRepresentation.next_>` of it super class :class:`.abc.simplify.InternalRepresentation`. Required by :class:`.Sets.simplify.Simplify` for instantiating the type variable :data:`.abc.simplify.ρ` of :class:`.abc.simplify.Simplify`. """ _options: abc.simplify.Options _min_card: Index = 1 _max_card: Index = oo _equations: UnionFind = field(default_factory=UnionFind) _inequations: set[Ne] = field(default_factory=set) def add(self, gand: type[And | Or], atoms: Iterable[AtomicFormula]) -> abc.simplify.RESTART: """Implements the abstract method :meth:`.abc.simplify.InternalRepresentation.add`. """ for atom in atoms: if gand is Or: atom = atom.to_complement() # Collect information match atom: case C(index=n): if n > self._min_card: self._min_card = n if self._min_card > self._max_card: raise InternalRepresentation.Inconsistent() case C_(index=n): if n - 1 < self._max_card: self._max_card = n - 1 if self._min_card > self._max_card: raise InternalRepresentation.Inconsistent() case Eq(lhs=lhs, rhs=rhs): self._equations.union(lhs, rhs) case Ne(lhs=lhs, rhs=rhs): self._inequations.add(atom) case _: assert False for ne in self._inequations: if self._equations.find(ne.lhs) == self._equations.find(ne.rhs): raise InternalRepresentation.Inconsistent() for ne in self._inequations: if self._equations.find(ne.lhs) == self._equations.find(ne.rhs): raise InternalRepresentation.Inconsistent() return abc.simplify.RESTART.OTHERS def extract(self, gand: type[And | Or], ref: InternalRepresentation) -> list[AtomicFormula]: """Implements the abstract method :meth:`.abc.simplify.InternalRepresentation.extract`. """ def canonicalize(ne: Ne) -> Ne: ne_subs = ne.subs({ne.lhs: self._equations.find(ne.lhs), ne.rhs: self._equations.find(ne.rhs)}) if Variable.sort_key(ne_subs.lhs) <= Variable.sort_key(ne_subs.rhs): return ne_subs else: return Ne(ne_subs.rhs, ne_subs.lhs) L: list[AtomicFormula] = [] if self._min_card > ref._min_card: L.append(C(self._min_card)) if self._max_card < ref._max_card: L.append(C_(self._max_card + 1)) for eq in self._equations.equations(): if ref._equations.find(eq.lhs) != ref._equations.find(eq.rhs): L.append(eq) canonical_ref_inequations = {canonicalize(ne) for ne in ref._inequations} for ne in self._inequations: ne = canonicalize(ne) if ne not in canonical_ref_inequations: L.append(ne) if gand is Or: L = [atom.to_complement() for atom in L] return L def next_(self, remove: Optional[Variable] = None) -> Self: """Implements the abstract method :meth:`.abc.simplify.next_`. """ if remove is None: equations = self._equations.copy() inequations = self._inequations.copy() else: equations = UnionFind() for eq in self._equations.equations(): if remove not in eq.fvars(): equations.union(eq.lhs, eq.rhs) inequations = {ne for ne in self._inequations if remove not in ne.fvars()} return self.__class__(self._options, self._min_card, self._max_card, equations, inequations)
[docs] @dataclass(frozen=True) class Simplify(abc.simplify.Simplify[ AtomicFormula, Variable, Variable, Never, InternalRepresentation, abc.simplify.Options]): """Deep simplification in the style of [DolzmannSturm-1997]_. Implements the abstract methods :meth:`create_initial_representation <.abc.simplify.Simplify.create_initial_representation>` and :meth:`simpl_at <.abc.simplify.Simplify.simpl_at>` of its super class :class:`.abc.simplify.Simplify`. The simplifier should be called via :func:`.simplify`, as described below. In addition, this class inherits :meth:`.abc.simplify.Simplify.is_valid`, which should be called via :func:`.is_valid`, as described below. """ _options: abc.simplify.Options = field(default_factory=abc.simplify.Options) def create_initial_representation(self, assume=Iterable[AtomicFormula]) \ -> InternalRepresentation: """Implements the abstract method :meth:`.abc.simplify.Simplify.create_initial_representation`. """ ir = InternalRepresentation(self._options) for atom in assume: simplified_atom = self.simpl_at(atom, And) match simplified_atom: case AtomicFormula(): ir.add(And, [simplified_atom]) case _T(): continue case _F(): raise ir.Inconsistent() case _: assert False, simplified_atom return ir def simpl_at(self, atom: AtomicFormula, context: Optional[type[And] | type[Or]]) -> Formula: """Implements the abstract method :meth:`.abc.simplify.Simplify.simpl_at`. """ return atom.simplify()
[docs] def simplify(f: Formula, assume: Iterable[AtomicFormula] = []) -> Formula: return Simplify().simplify(f, assume)
[docs] def is_valid(f: Formula, assume: Iterable[AtomicFormula] = []) -> Optional[bool]: return Simplify().is_valid(f, assume)