Allow hashing clauses
This commit is contained in:
parent
65a8c0042f
commit
fe8d9b766c
52
ir.py
52
ir.py
|
@ -2,7 +2,7 @@ from emis_funky_funktions import *
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Sequence, TypeAlias
|
from typing import Collection, FrozenSet, Sequence, TypeAlias
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Subst:
|
class Subst:
|
||||||
|
@ -62,7 +62,7 @@ class IRProp:
|
||||||
The identifier of this thing, including its location in the source
|
The identifier of this thing, including its location in the source
|
||||||
"""
|
"""
|
||||||
|
|
||||||
arguments: 'Sequence[IRTerm]' = tuple()
|
arguments: 'Tuple[IRTerm, ...]' = tuple()
|
||||||
|
|
||||||
def subst(self, subst: Subst) -> 'IRTerm':
|
def subst(self, subst: Subst) -> 'IRTerm':
|
||||||
"""
|
"""
|
||||||
|
@ -78,7 +78,7 @@ class IRProp:
|
||||||
>>> original.subst(Subst('x1', IRProp('Alex')))
|
>>> original.subst(Subst('x1', IRProp('Alex')))
|
||||||
angry(Alex())
|
angry(Alex())
|
||||||
"""
|
"""
|
||||||
return IRProp(self.name, [arg.subst(subst) for arg in self.arguments])
|
return IRProp(self.name, tuple(arg.subst(subst) for arg in self.arguments))
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return repr(self)
|
return repr(self)
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
@ -166,7 +166,17 @@ class IRNeg:
|
||||||
return var in self.inner
|
return var in self.inner
|
||||||
|
|
||||||
IRTerm: TypeAlias = IRVar | IRProp | IRNeg
|
IRTerm: TypeAlias = IRVar | IRProp | IRNeg
|
||||||
Clause: TypeAlias = Sequence[IRTerm]
|
Clause: TypeAlias = FrozenSet[IRTerm]
|
||||||
|
Clause_: TypeAlias = Collection[IRTerm]
|
||||||
|
"""
|
||||||
|
A more general definition of `Clause` which uses a collection rather than a frozen set
|
||||||
|
|
||||||
|
Every `Clause` is a `Clause_`, but not vice versa. In other words, true `Clause` is a
|
||||||
|
subclass of `Clause_`.
|
||||||
|
|
||||||
|
Due to this generalization, `Clause_` does not necessarily benefit from hashability or
|
||||||
|
deduplication. It exists mostly to make inputing things easier, e.g. in doctests.
|
||||||
|
"""
|
||||||
|
|
||||||
sub_all: Callable[[Substitutions, IRTerm], IRTerm] = p(reduce, lambda t, s: t.subst(s)) #type:ignore
|
sub_all: Callable[[Substitutions, IRTerm], IRTerm] = p(reduce, lambda t, s: t.subst(s)) #type:ignore
|
||||||
"""
|
"""
|
||||||
|
@ -181,19 +191,6 @@ Applies every substitution to the term in order
|
||||||
kismesis(Karkat(), Karkat())
|
kismesis(Karkat(), Karkat())
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sub_all_clause: Callable[[Substitutions, Clause], Clause] = uncurry2(c(p_map, cur2(sub_all))) #type:ignore
|
|
||||||
"""
|
|
||||||
Perform a series of substitutions on every term in a list
|
|
||||||
|
|
||||||
Effectively calls `sub_all()` on every element of the list.
|
|
||||||
|
|
||||||
>>> sub_all_clause(
|
|
||||||
... [Subst('x1', IRVar('Dave')), Subst('x2', IRProp('Karkat'))],
|
|
||||||
... [IRProp('dating', [IRVar('x1'), IRVar('x2')]), IRVar('x1')],
|
|
||||||
... )
|
|
||||||
[dating(Dave(),Karkat()), Dave()]
|
|
||||||
"""
|
|
||||||
|
|
||||||
def unify(t1: IRTerm, t2: IRTerm) -> Result[Substitutions, UnificationError]:
|
def unify(t1: IRTerm, t2: IRTerm) -> Result[Substitutions, UnificationError]:
|
||||||
"""
|
"""
|
||||||
Attempt to find a substitution that unifies two terms
|
Attempt to find a substitution that unifies two terms
|
||||||
|
@ -228,28 +225,31 @@ def unify(t1: IRTerm, t2: IRTerm) -> Result[Substitutions, UnificationError]:
|
||||||
case (IRVar(v), t_other) | (t_other, IRVar(v)) if v not in t_other: #type: ignore
|
case (IRVar(v), t_other) | (t_other, IRVar(v)) if v not in t_other: #type: ignore
|
||||||
return Ok((Subst(v, t_other),))
|
return Ok((Subst(v, t_other),))
|
||||||
case (IRProp(n1, a1), IRProp(n2, a2)) if n1 == n2 and len(a1) == len(a2):
|
case (IRProp(n1, a1), IRProp(n2, a2)) if n1 == n2 and len(a1) == len(a2):
|
||||||
return unify_clauses(a1, a2)
|
return unify_lists(a1, a2)
|
||||||
case (IRNeg(i1), IRNeg(i2)):
|
case (IRNeg(i1), IRNeg(i2)):
|
||||||
return unify(i1, i2)
|
return unify(i1, i2)
|
||||||
return Err(UnificationMismatch(t1, t2))
|
return Err(UnificationMismatch(t1, t2))
|
||||||
|
|
||||||
def unify_clauses(c1: Clause, c2: Clause) -> Result[Substitutions, UnificationError]:
|
def unify_lists(c1: Sequence[IRTerm], c2: Sequence[IRTerm]) -> Result[Substitutions, UnificationError]:
|
||||||
"""
|
"""
|
||||||
Attempt to perform unification on two clauses or argument lists
|
Attempt to perform unification on two term/argument lists
|
||||||
|
|
||||||
See `unify()` for the details of how this works. When working with clauses, the same
|
See `unify()` for the details of how this works. When working with lists, the same
|
||||||
rules apply. The substitutions, when applied to every term of both clauses, will
|
rules apply. The substitutions, when applied to every term of both lists, will
|
||||||
cause the clauses to become exactly the same.
|
cause the lists to become exactly the same.
|
||||||
|
|
||||||
Lists which are not the same length cannot be unified, and will always fail.
|
Lists which are not the same length cannot be unified, and will always fail.
|
||||||
|
|
||||||
>>> unify_clauses(
|
Notice the difference between a list of `IRTerm`s and a `Clause`: Namely, that a
|
||||||
|
`Clause` is unordered, while a list of `IRTerm`s is ordered.
|
||||||
|
|
||||||
|
>>> unify_lists(
|
||||||
... [ IRProp('imaginary', [IRProp('Rufio')]), IRProp('friend', [IRVar('x1'), IRVar('x3')]) ],
|
... [ IRProp('imaginary', [IRProp('Rufio')]), IRProp('friend', [IRVar('x1'), IRVar('x3')]) ],
|
||||||
... [ IRProp('imaginary', [IRVar('x1')]), IRProp('friend', [IRVar('x2'), IRProp('Tavros')]) ]
|
... [ IRProp('imaginary', [IRVar('x1')]), IRProp('friend', [IRVar('x2'), IRProp('Tavros')]) ]
|
||||||
... )
|
... )
|
||||||
Ok((Rufio()/x1, Rufio()/x2, Tavros()/x3))
|
Ok((Rufio()/x1, Rufio()/x2, Tavros()/x3))
|
||||||
|
|
||||||
>>> unify_clauses(
|
>>> unify_lists(
|
||||||
... [ IRProp('imaginary', [IRProp('Rufio')]), IRProp('friend', [IRVar('x1'), IRVar('x3')]) ],
|
... [ IRProp('imaginary', [IRProp('Rufio')]), IRProp('friend', [IRVar('x1'), IRVar('x3')]) ],
|
||||||
... [ IRProp('imaginary', [IRVar('x1')]) ]
|
... [ IRProp('imaginary', [IRVar('x1')]) ]
|
||||||
... )
|
... )
|
||||||
|
@ -260,7 +260,7 @@ def unify_clauses(c1: Clause, c2: Clause) -> Result[Substitutions, UnificationEr
|
||||||
return Ok(tuple())
|
return Ok(tuple())
|
||||||
case ([h1, *t1], [h2, *t2]):
|
case ([h1, *t1], [h2, *t2]):
|
||||||
return unify(h1, h2) << (lambda subs:
|
return unify(h1, h2) << (lambda subs:
|
||||||
unify_clauses((*map(p(sub_all,subs),t1),), (*map(p(sub_all,subs),t2),)) <= (
|
unify_lists((*map(p(sub_all,subs),t1),), (*map(p(sub_all,subs),t2),)) <= (
|
||||||
lambda final_subs: (*subs, *final_subs)))
|
lambda final_subs: (*subs, *final_subs)))
|
||||||
case ([h, *t], []) | ([], [h, *t]):
|
case ([h, *t], []) | ([], [h, *t]):
|
||||||
return Err(LengthMismatch(h))
|
return Err(LengthMismatch(h))
|
||||||
|
|
|
@ -1,8 +1,19 @@
|
||||||
from emis_funky_funktions import *
|
from emis_funky_funktions import *
|
||||||
|
|
||||||
from itertools import combinations, product
|
from itertools import combinations, product
|
||||||
|
from typing import Collection, FrozenSet, TypeAlias
|
||||||
|
|
||||||
from ir import Clause, IRNeg, IRProp, IRTerm, IRVar, Substitutions, sub_all, unify, unify_clauses
|
from ir import Clause, Clause_, IRNeg, IRProp, IRTerm, IRVar, Substitutions, sub_all, unify
|
||||||
|
|
||||||
|
KnowledgeBase: TypeAlias = FrozenSet[Clause]
|
||||||
|
KnowledgeBase_: TypeAlias = Collection[Clause_]
|
||||||
|
"""
|
||||||
|
A more general version of `KnowledgeBase`
|
||||||
|
|
||||||
|
`KnowledgeBase_` : `KnowledgeBase` :: `Clause_` : `Clause`
|
||||||
|
|
||||||
|
A superclass of `KnowledgeBase`
|
||||||
|
"""
|
||||||
|
|
||||||
def terms_cancel(t1: IRTerm, t2: IRTerm) -> Option[Substitutions]:
|
def terms_cancel(t1: IRTerm, t2: IRTerm) -> Option[Substitutions]:
|
||||||
"""
|
"""
|
||||||
|
@ -29,53 +40,54 @@ def terms_cancel(t1: IRTerm, t2: IRTerm) -> Option[Substitutions]:
|
||||||
return hush(unify(x, IRNeg(a)))
|
return hush(unify(x, IRNeg(a)))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def merge_clauses(c1: Clause, c2: Clause) -> Sequence[Clause]:
|
def merge_clauses(c1: Clause_, c2: Clause_) -> KnowledgeBase:
|
||||||
"""
|
"""
|
||||||
Produce a list of all possible clauses which resolution could derive from c1 and c2
|
Produce a list of all possible clauses which resolution could derive from c1 and c2
|
||||||
|
|
||||||
For each term in c1 that could cancel with a term in c2 using a substitution, a
|
For each term in c1 that could cancel with a term in c2 using a substitution, a
|
||||||
possible clause is produced equal to the concatenation of c1 and c2 with the canceled
|
possible clause is produced equal to the union of c1 and c2 with the canceled
|
||||||
terms removed and the substitution applied.
|
terms removed and the substitution applied.
|
||||||
|
|
||||||
>>> merge_clauses(
|
>>> merge_clauses(
|
||||||
... [ IRProp('day'), IRNeg(IRProp('night')) ],
|
... [ IRProp('day'), IRNeg(IRProp('night')) ],
|
||||||
... [ IRProp('night') ]
|
... [ IRProp('night') ]
|
||||||
... )
|
... )
|
||||||
[[day()]]
|
{ { day() } }
|
||||||
|
|
||||||
>>> merge_clauses(
|
>>> merge_clauses(
|
||||||
... [ IRNeg(IRProp('transgender', [IRVar('x1')])), IRProp('powerful', [IRVar('x1')]) ],
|
... [ IRNeg(IRProp('transgender', [IRVar('x1')])), IRProp('powerful', [IRVar('x1')]) ],
|
||||||
... [ IRNeg(IRProp('powerful', [IRVar('x2')])), IRProp('god', [IRVar('x2')]) ]
|
... [ IRNeg(IRProp('powerful', [IRVar('x2')])), IRProp('god', [IRVar('x2')]) ]
|
||||||
... )
|
... )
|
||||||
[[¬transgender(*x1), god(*x1)]]
|
{ { god(*x1), ¬transgender(*x1) } }
|
||||||
|
|
||||||
>>> merge_clauses(
|
>>> merge_clauses(
|
||||||
... [ IRNeg(IRProp('day')), IRProp('night') ],
|
... [ IRNeg(IRProp('day')), IRProp('night') ],
|
||||||
... [ IRVar('x2') ]
|
... [ IRVar('x2') ]
|
||||||
... )
|
... )
|
||||||
[[night()], [¬day()]]
|
{ { night() }, { ¬day() } }
|
||||||
|
|
||||||
If two clauses cannot merge, an empty list is returned
|
If two clauses cannot merge, an empty set is returned
|
||||||
|
|
||||||
>>> merge_clauses(
|
>>> merge_clauses(
|
||||||
... [ IRProp('day') ],
|
... [ IRProp('day') ],
|
||||||
... [ IRProp('wet') ]
|
... [ IRProp('wet') ]
|
||||||
... )
|
... )
|
||||||
[]
|
{ }
|
||||||
"""
|
"""
|
||||||
|
terms1, terms2 = list(c1), list(c2)
|
||||||
valid_substitutions = drop_none(
|
valid_substitutions = drop_none(
|
||||||
map_opt(lambda subs: (subs, i1, i2), terms_cancel(t1, t2))
|
map_opt(lambda subs: (subs, i1, i2), terms_cancel(t1, t2))
|
||||||
for ((i1, t1), (i2, t2)) in product(enumerate(c1), enumerate(c2))
|
for ((i1, t1), (i2, t2)) in product(enumerate(terms1), enumerate(terms2))
|
||||||
)
|
)
|
||||||
return [
|
return FSet(
|
||||||
[
|
FSet(
|
||||||
sub_all(subs, term)
|
sub_all(subs, term)
|
||||||
for term in (*c1[:i1], *c1[i1 + 1:], *c2[:i2], *c2[i2 + 1:])
|
for term in (*terms1[:i1], *terms1[i1 + 1:], *terms2[:i2], *terms2[i2 + 1:])
|
||||||
]
|
)
|
||||||
for (subs, i1, i2) in valid_substitutions
|
for (subs, i1, i2) in valid_substitutions
|
||||||
]
|
)
|
||||||
|
|
||||||
def derive(clauses: Sequence[Clause]) -> Sequence[Clause]:
|
def derive(clauses: KnowledgeBase_) -> KnowledgeBase:
|
||||||
"""
|
"""
|
||||||
All possible clauses which derive in one step of resolution from a knowledge base
|
All possible clauses which derive in one step of resolution from a knowledge base
|
||||||
|
|
||||||
|
@ -86,16 +98,20 @@ def derive(clauses: Sequence[Clause]) -> Sequence[Clause]:
|
||||||
... [IRNeg(IRProp('dog', [IRVar('x0')])), IRProp('animal', [IRVar('x0')])],
|
... [IRNeg(IRProp('dog', [IRVar('x0')])), IRProp('animal', [IRVar('x0')])],
|
||||||
... [IRNeg(IRProp('cat', [IRVar('x1')])), IRProp('animal', [IRVar('x1')])],
|
... [IRNeg(IRProp('cat', [IRVar('x1')])), IRProp('animal', [IRVar('x1')])],
|
||||||
... [IRProp('dog', [IRProp('Kim')])],
|
... [IRProp('dog', [IRProp('Kim')])],
|
||||||
... ])
|
... ]) #doctest: +NORMALIZE_WHITESPACE
|
||||||
[[¬dog(Kim())], [¬cat(Kim())], [animal(Kim())]]
|
{
|
||||||
|
{ animal(Kim()) },
|
||||||
|
{ ¬cat(Kim()) },
|
||||||
|
{ ¬dog(Kim()) }
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
return [
|
return FSet(
|
||||||
clause
|
FSet(clause)
|
||||||
for (c1, c2) in combinations(clauses, 2)
|
for (c1, c2) in combinations(clauses, 2)
|
||||||
for clause in merge_clauses(c1, c2)
|
for clause in merge_clauses(c1, c2)
|
||||||
]
|
)
|
||||||
|
|
||||||
def derive2(kb1: Sequence[Clause], kb2: Sequence[Clause]) -> Sequence[Clause]:
|
def derive2(kb1: KnowledgeBase_, kb2: KnowledgeBase_) -> KnowledgeBase:
|
||||||
"""
|
"""
|
||||||
All clauses which derive in one step from the combination of two knowledge bases
|
All clauses which derive in one step from the combination of two knowledge bases
|
||||||
|
|
||||||
|
@ -110,13 +126,13 @@ def derive2(kb1: Sequence[Clause], kb2: Sequence[Clause]) -> Sequence[Clause]:
|
||||||
... [IRNeg(IRProp('dog', [IRVar('x0')])), IRProp('animal', [IRVar('x0')])],
|
... [IRNeg(IRProp('dog', [IRVar('x0')])), IRProp('animal', [IRVar('x0')])],
|
||||||
... [IRProp('dog', [IRProp('Kim')])],
|
... [IRProp('dog', [IRProp('Kim')])],
|
||||||
... ])
|
... ])
|
||||||
[[¬dog(Kim())]]
|
{ { ¬dog(Kim()) } }
|
||||||
"""
|
"""
|
||||||
return [
|
return FSet(
|
||||||
clause
|
FSet(clause)
|
||||||
for (c1, c2) in product(kb1, kb2)
|
for (c1, c2) in product(kb1, kb2)
|
||||||
for clause in merge_clauses(c1, c2)
|
for clause in merge_clauses(c1, c2)
|
||||||
]
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
|
|
Loading…
Reference in a new issue