Skip to content

Commit

Permalink
Optionally add Top and Bottom types to taxonomy.
Browse files Browse the repository at this point in the history
See issue #94. Untested.

This commit changes behaviour, because all sub- and supertypes in a
lineage must now be canonical --- it is no longer possible to have a
canonical root that has non-canonical supertypes.

Also fixed an issue in `.uri()` that goes along with it,
because it allowed unnamed operators to sometimes not throw an error
when put into the taxonomy.
  • Loading branch information
nsbgn committed Jun 21, 2022
1 parent ec3f15e commit 50eb863
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 27 deletions.
10 changes: 6 additions & 4 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,9 @@ def test_disabling_of_output_passthrough(self):
# Source (of the general type) that takes an expression output (of the
# specific type) as input. See issue #81.

A = TypeOperator()
B = TypeOperator(supertype=A)
TypeOperator(supertype=B)
A = TypeOperator('A')
B = TypeOperator('B', supertype=A)
TypeOperator('C', supertype=B)
f = Operator(type=lambda x: x ** x [x <= A])
lang = Language(locals(), namespace=TEST)

Expand Down Expand Up @@ -677,7 +677,9 @@ def test_taxonomy_with_parameterized_type_alias(self):
actual.add_taxonomy()
expected = make_taxonomy(lang, {
A: {B},
F: {F(A, B)},
F: {F(A, A)},
F(A, A): {F(A, B), F(B, A)},
F(A, B): {F(B, B)},
F(B, A): {F(B, B)}
})
self.assertIsomorphic(expected, actual)
19 changes: 11 additions & 8 deletions transformation_algebra/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from __future__ import annotations

from transformation_algebra.type import Type, TypeOperation, \
Function, TypeInstance
Function, TypeInstance, Top, Bottom
from transformation_algebra.expr import \
Expr, Operation, Application, Abstraction, Source
from transformation_algebra.lang import Language, TA
Expand Down Expand Up @@ -88,23 +88,26 @@ def add_taxonomy(self) -> None:
"""
Add a taxonomy of types.
"""
incl = self.language.include_top_and_bottom

# Add all canonical nodes and connect to their subtypes
for t in self.language.canon:
node = self.add_type(t)
for s in t.subtypes():
self.add((self.add_type(s), RDFS.subClassOf, node))
if t._operator is not Top:
for s in t.subtypes(incl):
self.add((self.add_type(s), RDFS.subClassOf, node))

# Connect top-level type nodes (i.e. compound type operators) to the
# roots of their respective trees
for t in self.language.canon:
# Connect roots of trees to the Top supertype & to their operator
if not any(s in self.language.canon for s in t.supertypes()):
op = t._operator
if op.arity > 0:
self.add((self.language.uri(t), RDFS.subClassOf,
self.language.uri(op)))
# self.add((self.language.uri(op), RDFS.subClassOf,
# self.language.uri(Top)))
if incl:
self.add((self.language.uri(op), RDFS.subClassOf,
self.language.uri(Top)))

# Transitive closure
if self.with_transitive_closure:
nodes = set(chain(
(self.type_nodes[t] for t in self.language.canon),
Expand Down
13 changes: 9 additions & 4 deletions transformation_algebra/lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, scope: dict[str, Any] = {},
self.operators: dict[str, Operator] = dict()
self.types: dict[str, TypeOperator] = dict()
self.synonyms: dict[str, TypeAlias] = dict()
self.include_top_and_bottom: bool = False

self._canon: set[TypeOperation] = set()
self._closed = False
Expand Down Expand Up @@ -56,6 +57,8 @@ def namespace(self) -> LanguageNamespace:
return self._namespace

def uri(self, value: Operator | TypeOperator | TypeOperation) -> URIRef:
if isinstance(value, TypeOperation) and value._operator.arity == 0:
value = value._operator
if isinstance(value, (Operator, TypeOperator)):
return (TA if value in builtins else self.namespace)[value.name]
elif value in self.canon:
Expand All @@ -65,17 +68,19 @@ def uri(self, value: Operator | TypeOperator | TypeOperation) -> URIRef:
raise ValueError("non-canonical type")

def generate_canon(self) -> set[TypeOperation]:
incl = self.include_top_and_bottom
canon: set[TypeOperation] = set()
# canon.update((Unit(), Top(), Bottom(), Product()))
canon.update((op() for op in self.types.values() if op.arity == 0))

# Canonicalize subtypes
# Start with base types and any compound type that has an alias
stack: list[TypeOperation] = [s.instance()
for s in self.synonyms.values() if s.canonical]
stack += [op() for op in self.types.values() if op.arity == 0]

# ... and make sure that sub+supertypes are included
while stack:
current = stack.pop()
canon.add(current)
for s in current.subtypes():
for s in chain(current.subtypes(incl), current.supertypes(incl)):
if s not in canon:
stack.append(s)
return canon
Expand Down
30 changes: 19 additions & 11 deletions transformation_algebra/type.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,41 +623,49 @@ def __hash__(self) -> int:
def basic(self) -> bool:
return self._operator.arity == 0

def subtypes(self, recursive: bool = False) -> Iterator[TypeOperation]:
def subtypes(self, inclusive: bool = False,
recursive: bool = False) -> Iterator[TypeOperation]:
if recursive:
raise NotImplementedError
op = self._operator
if op is Top:
raise NotImplementedError
raise RuntimeError("Cannot list all types.")
elif op is Bottom:
pass
elif op.arity == 0:
# We skip the Bottom type
yield from (c() for c in op.children)
if op.children:
yield from (c() for c in op.children)
elif inclusive:
yield Bottom()
else:
for i, v, p in zip(count(), op.variance, self.params):
if isinstance(p, TypeOperation):
for q in (p.subtypes() if Variance.CO else p.supertypes()):
for q in (p.subtypes(inclusive) if Variance.CO else
p.supertypes(inclusive)):
yield op(*(q if i == j else p
for j, p in enumerate(self.params)))
else:
raise RuntimeError

def supertypes(self, recursive: bool = False) -> Iterator[TypeOperation]:
def supertypes(self, inclusive: bool = False,
recursive: bool = False) -> Iterator[TypeOperation]:
if recursive:
raise NotImplementedError
op = self._operator
if op is Bottom:
raise NotImplementedError
raise RuntimeError("Cannot list all types.")
elif op is Top:
pass
elif op.arity == 0 and op.parent:
# We skip the Top type
yield op.parent()
elif op.arity == 0:
if op.parent:
yield op.parent()
elif inclusive:
yield Top()
else:
for i, v, p in zip(count(), op.variance, self.params):
if isinstance(p, TypeOperation):
for q in (p.supertypes() if Variance.CO else p.subtypes()):
for q in (p.supertypes(inclusive) if Variance.CO else
p.subtypes(inclusive)):
yield op(*(q if i == j else p
for j, p in enumerate(self.params)))
else:
Expand Down

0 comments on commit 50eb863

Please sign in to comment.