SPECIAL_CHARS = {
r"\sqsubseteq": "\u2291",
r"\exists": "\u2203",
r"\sqcap": "\u2293",
r"\equiv": "\u2261",
r"\top": "\u22A4",
}
[docs]
class FeatureNotSupported(Exception):
def __init__(self, *args):
super(FeatureNotSupported, self).__init__(*args)
[docs]
class Ontology(object):
def __init__(self):
self.__classes = {}
self.__rules = [] # class rules
self.__properties = []
self.__prop_sub = {} # property subclasses
self.__prop_range = {} # property ranges
self.__prop_domain = {} # property domains
[docs]
def add_rule(self, rule):
self.__rules.append(rule)
[docs]
def normalize(self):
result = Ontology()
for rule in self.rules:
for r in rule.normalize():
result.add_rule(r)
for prop in self.properties:
result.add_property(prop)
return result
[docs]
def add_property(self, prop):
self.__prop_sub[prop.identifier] = prop.parent
if prop.domain is not None:
self.__prop_domain[prop.identifier] = prop.domain
if prop.range is not None:
self.__prop_range[prop.identifier] = prop.range
elif prop.identifier not in self.__prop_range:
self.__prop_range[prop.identifier] = TopClass()
self.__properties.append(prop)
[docs]
def check_implicit_cycle(self):
for prop in self.__properties:
pass
@property
def properties(self):
return iter(self.__properties)
@property
def rules(self):
return iter(self.__rules)
@property
def subproperties(self):
for sub, sups in self.__prop_sub.items():
for sup in sups:
yield SubProperty(PropertyReference(sub), sup)
@property
def property_ranges(self):
return iter(self.__prop_range.items())
@property
def property_domains(self):
return iter(self.__prop_domain.items())
[docs]
def __iter__(self):
return iter(self.__rules)
[docs]
def __len__(self):
return len(self.__rules)
[docs]
class NameFactory(object):
__i = 0
created_names = set()
[docs]
@classmethod
def next_name(cls):
cls.__i += 1
name = "NC_%d" % cls.__i
cls.created_names.add(name)
return name
[docs]
class OntologyObject(object):
"""The base class for all entities in the ontology."""
[docs]
def to_latex(self):
raise NotImplementedError()
[docs]
def __repr__(self):
return self.__str__()
@property
def is_rule(self):
return False
[docs]
class SubProperty(object):
def __init__(self, subject, object_):
self.subject = subject
self.object = object_
[docs]
def __str__(self):
return "%s %s %s" % (self.subject, SPECIAL_CHARS["\sqsubseteq"], self.object)
[docs]
class Property(object):
pass
[docs]
class PropertyReference(Property):
def __init__(self, identifier):
self.identifier = identifier
[docs]
def __str__(self):
return self.identifier
[docs]
class ObjectProperty(Property):
def __init__(
self,
identifier,
parent=None,
is_functional=False,
is_reflexive=False,
is_inverse_functional=False,
is_transitive=False,
is_symmetric=False,
domain=None,
range=None,
inverse_of=None,
):
if parent is None:
parent = []
self.identifier = identifier
self.parent = parent
self.is_functional = is_functional
self.is_inverse_functional = is_inverse_functional
self.is_transitive = is_transitive
self.is_symmetric = is_symmetric
self.is_reflexive = is_reflexive
self.domain = domain
self.range = range
self.inverse_of = inverse_of
[docs]
def __str__(self):
if self.identifier is None and self.inverse_of is not None:
return "{}-".format(self.inverse_of)
return self.identifier
[docs]
class Expression(OntologyObject):
varcount = 0
[docs]
@classmethod
def get_safe_varname(cls):
cls.varcount += 1
return "Y%d" % cls.varcount
expression_types = {}
[docs]
@classmethod
def register_type(cls, tagname, factory):
cls.expression_types[tagname] = factory
[docs]
@classmethod
def get_factory(cls, tagname):
return cls.expression_types.get(tagname)
def __init__(self, identifier=None):
self.identifier = identifier
[docs]
def normalize_rhs(self):
raise NotImplementedError()
[docs]
def normalize_lhs(self):
return self, []
[docs]
def __and__(self, other):
return Intersection([self, other])
[docs]
def __lshift__(self, other):
return SubClassOf(self, other)
[docs]
class Rule(OntologyObject):
"""A ontology expression of the form A \sqsubseteq B."""
def __init__(self, subject, object_):
"""
:param subject:
:type subject: Expression
:param object_:
:type object_: Expression
"""
super(Rule, self).__init__()
self.__subject = subject
self.__object = object_
@property
def subject(self):
return self.__subject
@subject.setter
def subject(self, value):
self.__subject = value
@property
def object(self):
return self.__object
@property
def is_rule(self):
return True
[docs]
def normalize(self):
"""Return a normalization of this rule.
:return: list of new rules
"""
raise FeatureNotSupported()
[docs]
def issimple(expr):
return type(expr) == ClassIdentifier or type(expr) == Thing
[docs]
class SubClassOf(Rule):
[docs]
def normalize(self):
lhs, rules_lhs = self.subject.normalize_lhs()
rhss, rules_rhs = self.object.normalize_rhs()
rules = []
for rhs in rhss:
if issimple(lhs) or issimple(rhs):
rules.append(SubClassOf(lhs, rhs))
else:
name = NameFactory.next_name()
new_class = ClassIdentifier(name)
rules.append(SubClassOf(lhs, new_class))
rules.append(SubClassOf(new_class, rhs))
rules += rules_lhs
rules += rules_rhs
return rules
[docs]
def __str__(self):
return "%s %s %s" % (self.subject, SPECIAL_CHARS["\sqsubseteq"], self.object)
# return '%s %s %s' % (self.subject, '->', self.object)
[docs]
def to_latex(self):
return r"%s \sqsubseteq %s" % (self.subject.to_latex(), self.object.to_latex())
[docs]
class EquivalentClass(Rule):
[docs]
def normalize(self):
return (
SubClassOf(self.subject, self.object).normalize()
+ SubClassOf(self.object, self.subject).normalize()
)
[docs]
def __str__(self):
return "%s %s %s" % (self.subject, SPECIAL_CHARS["\equiv"], self.object)
[docs]
class DisjointWith(Rule):
[docs]
def normalize(self):
return [self]
# raise FeatureNotSupported('rule disjointWith')
[docs]
def __str__(self):
return "%s %s %s" % (self.subject, " disjoint ", self.object)
[docs]
class Thing(Expression):
def __init__(self, identifier):
super(Thing, self).__init__(identifier)
[docs]
def normalize_rhs(self):
return [self], []
[docs]
def __str__(self):
return self.identifier
[docs]
class ClassIdentifier(Expression):
"""A class in the ontology."""
def __init__(self, identifier):
super(ClassIdentifier, self).__init__(identifier)
[docs]
def normalize_rhs(self):
return [self], []
[docs]
def __str__(self):
return self.identifier
[docs]
def to_latex(self):
return r"%s" % self.identifier
[docs]
class TopClass(Expression):
"""A class in the ontology."""
def __init__(self):
super(TopClass, self).__init__()
[docs]
def normalize_rhs(self):
return [self], []
[docs]
def __str__(self):
return SPECIAL_CHARS[r"\top"]
[docs]
def to_latex(self):
return r"\top"
[docs]
class Restriction(Expression):
""""""
def __init__(self, quantifier, prop):
super(Restriction, self).__init__()
self.quantifier = quantifier
self.prop = prop
[docs]
def normalize_lhs(self):
quantifier, rules = self.quantifier.normalize_lhs()
return Restriction(quantifier, self.prop), rules
[docs]
def normalize_rhs(self):
quantifier, rules = self.quantifier.normalize_rhs()
return [Restriction(quantifier, self.prop)], rules
[docs]
def to_latex(self):
return self.quantifier.to_latex(self.prop)
[docs]
def __str__(self):
return self.quantifier.to_string(self.prop)
[docs]
class Intersection(Expression):
""""""
def __init__(self, children):
super(Intersection, self).__init__()
self.children = children
[docs]
def normalize_lhs(self):
# TODO extend to n-ary intersections, this here assumes binary
rules = []
children = []
for child in self.children:
nc, nr = child.normalize_lhs()
rules += nr
if issimple(nc):
children.append(nc)
else:
name = NameFactory.next_name()
new_class = ClassIdentifier(name)
rules.append(SubClassOf(nc, new_class))
children.append(new_class)
return Intersection(children), rules
[docs]
def normalize_rhs(self):
rules = []
children = []
for child in self.children:
child_rhs, child_rules = child.normalize_rhs()
assert len(child_rhs) == 1
children += child_rhs
rules += child_rules
return children, rules
[docs]
def __str__(self):
sep = " %s " % SPECIAL_CHARS["\sqcap"]
return "(%s)" % sep.join(map(str, self.children))
[docs]
def to_latex(self):
return r" \sqcap ".join(c.to_latex() for c in self.children)
[docs]
def __and__(self, other):
return Intersection(self.children + [other])
[docs]
class Union(Expression):
""""""
def __init__(self, children):
super(Union, self).__init__()
self.children = children
[docs]
def normalize_rhs(self):
return [self], []
# raise FeatureNotSupported("union in RHS")
[docs]
def __str__(self):
sep = " | "
return sep.join(map(str, self.children))
[docs]
class Complement(Expression):
def __init__(self, child):
super(Complement, self).__init__()
self.child = child
[docs]
def normalize_rhs(self):
return [self], []
# raise FeatureNotSupported('complement')
[docs]
def __str__(self):
return "not {}".format(str(self.child))
[docs]
class OneOf(Expression):
def __init__(self, children):
super(OneOf, self).__init__()
self.children = children
[docs]
def normalize_rhs(self):
raise FeatureNotSupported("oneOf in RHS")
[docs]
def __str__(self):
sep = ", "
return "one_of(%s)" % sep.join(map(str, self.children))
[docs]
class Quantifier(object):
[docs]
def to_string(self, prop):
raise NotImplementedError()
[docs]
def normalize_rhs(self):
raise NotImplementedError()
[docs]
class HasSelf(Quantifier):
[docs]
def to_string(self, prop):
return "hasSelf {}".format(prop)
[docs]
def normalize_rhs(self):
return self, []
[docs]
def normalize_lhs(self):
return self, []
[docs]
class SomeValues(Quantifier):
def __init__(self, from_class):
self.from_class = from_class
[docs]
def to_string(self, prop):
return "%s %s.%s" % (SPECIAL_CHARS["\exists"], prop, self.from_class)
[docs]
def to_latex(self, prop):
if self.from_class.identifier is not None:
return r"\exists %s.\!%s" % (prop, self.from_class.to_latex())
else:
return r"\exists %s.\!(%s)" % (prop, self.from_class.to_latex())
[docs]
def normalize_lhs(self):
rules = []
if self.from_class.identifier is None:
name = NameFactory.next_name()
from_class = ClassIdentifier(name)
fc_n, fc_r = self.from_class.normalize_lhs()
rules += fc_r
for fc in fc_n:
rules.append(SubClassOf(fc, from_class))
else:
from_class = self.from_class
return SomeValues(from_class), rules
[docs]
def normalize_rhs(self):
rules = []
if self.from_class.identifier is None:
name = NameFactory.next_name()
from_class = ClassIdentifier(name)
fc_n, fc_r = self.from_class.normalize_rhs()
rules += fc_r
for fc in fc_n:
rules.append(SubClassOf(from_class, fc))
else:
from_class = self.from_class
return SomeValues(from_class), rules
[docs]
class AllValues(Quantifier):
def __init__(self, from_class):
self.from_class = from_class
[docs]
def to_string(self, prop):
return "all %s %s" % (prop, self.from_class)
[docs]
def normalize_rhs(self):
raise FeatureNotSupported("quantifier allValues")
[docs]
class Cardinality(Quantifier):
def __init__(self, count):
self.count = count
[docs]
def to_string(self, prop):
return "count(%s) = %s" % (prop, self.count)
[docs]
def normalize_rhs(self):
raise FeatureNotSupported("quantifier cardinality")
[docs]
class MinCardinality(Quantifier):
def __init__(self, count):
self.count = count
[docs]
def to_string(self, prop):
return "count(%s) >= %s" % (prop, self.count)
[docs]
def normalize_rhs(self):
raise FeatureNotSupported("quantifier minCardinality")
[docs]
class MaxCardinality(Quantifier):
def __init__(self, count):
self.count = count
[docs]
def to_string(self, prop):
return "count(%s) <= %s" % (prop, self.count)
[docs]
def normalize_rhs(self):
raise FeatureNotSupported("quantifier maxCardinality")
[docs]
class HasValue(Quantifier):
def __init__(self, value):
self.value = value
[docs]
def to_string(self, prop):
return "value(%s) = %s" % (prop, self.value)
[docs]
def normalize_rhs(self):
return self, []
# raise FeatureNotSupported('hasValue')
[docs]
def normalize_lhs(self):
return self, []