Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 114 additions & 36 deletions src/minimalkb/services/simple_rdfs_reasoner.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,110 @@ def __repr__(self):
"\n\tParents: " + str(self.parents) + \
"\n\tChildren: " + str(self.children) + \
"\n\tInstances: " + str(self.instances)



# recursive functions for ontologic inheritances :
#------------------------------------------------

def addequivalent(equivalentclasses, cls, equivalent, memory=None):
'''
propagation of equivalence properties :
---------------------------------------
1) transitivity : (A = B) and (B = C) then (A = C)
2) symetry : (A = B) then (B = A)
3) the reflexive property results from symetry and transitivity.

we need to update the classes to make the additions available for the other calls of inheritance functions

the memory set is used to prevent the method to loop infinitly
for instance :

let B in the set of equivalents of A
then the method do with (A and B) :

1) equivalentclasses.add(A.name , B.name)
2) A is added to the set of equivalents of B
3) for every equivalents C of the set of B do the same with (A and C)
but inside this set we find A (we just added it !)
so :
1) equivalentclasses.add(A.name , A.name) --> ok, reflexivity is added
2) B is added to the set of equivalents of A --> (no effect because it was already here, but ok)
3) for every equivalents C of the set of A do the same with (A and C)
but inside we find B !!!
so :
without memory, it loops infinitly
but with memory, it finds that B have already been handled
'''
if not memory:
memory = set()

if equivalent not in memory:
memory.add(equivalent)

equivalentclasses.add((cls.name, equivalent.name))
cls.equivalents.add(equivalent) # update classes

# reflexive property :
equivalent.equivalents.add(cls) # update classes

# transitive property :
for e in frozenset(equivalent.equivalents):
addequivalent(equivalentclasses, cls, e, memory)


def addsubclassof(subclassof, scls, cls):
'''
propagation of inclusions :
---------------------------
the inclusions are just transitives : (A in B) and (B in C) then (A in C)

we need to update the classes to make the additions available for the other calls of inheritance functions
'''
subclassof.add((scls.name, cls.name))
# update classes
cls.children.add(scls)
scls.parents.add(cls)

# transitivity :
for p in frozenset(cls.parents):
addsubclassof(subclassof, scls, p)

def addoverclassof(subclassof, cls, ocls):
'''
back-track propagation of inclusion :
-------------------------------------
this backtracking seems to be unusful (it does the same thing than in addsubclassof)
but is used after adding equivalences :
indeed, the property " (A = B) and (C in A) then (C in B) " cannot be taken in account by the method addsubclassof

we need to update the classes to make the additions available for the other calls of inheritance functions
'''

subclassof.add((cls.name, ocls.name))
# update classes :
ocls.children.add(cls)
cls.parents.add(ocls)

# transitivity :
for c in frozenset(cls.children):
addoverclassof(subclassof, c, cls)

def addinstance(rdftype, instance, cls):
'''
propagation of instances :
---------------------------
the instances are just transitives : (A in B) and (B in C) then (A in C)

don't need to update the classes because they are not used by the other functions
'''

rdftype.add((instance, cls.name))

for p in cls.parents:
addinstance(rdftype, instance, p)

#----------------------------------------

class SQLiteSimpleRDFSReasoner:

Expand Down Expand Up @@ -121,6 +225,8 @@ def get_onto(self, db, model = DEFAULT_MODEL):


return onto, rdftype, subclassof, equivalentclasses



def get_missing_taxonomy_stmts(self, model = DEFAULT_MODEL):

Expand All @@ -129,52 +235,24 @@ def get_missing_taxonomy_stmts(self, model = DEFAULT_MODEL):
newrdftype = set()
newsubclassof = set()
newequivalentclasses=set()


def addinstance(instance, cls):
newrdftype.add((instance, cls.name))
for p in cls.parents:
addinstance(instance, p)

def addoverclassof(cls, ocls):
newsubclassof.add((cls.name, ocls.name))
ocls.children.add(cls)
cls.parents.add(ocls)
for c in frozenset(cls.children):
addoverclassof(c, cls)

def addsubclassof(scls, cls):
newsubclassof.add((scls.name, cls.name))
cls.children.add(scls)
scls.parents.add(cls)
for p in frozenset(cls.parents):
addsubclassof(scls, p)

def addequivalent(cls, equ, memory):
if equ not in memory:
memory.add(equ)
newequivalentclasses.add((cls.name, equ.name))
cls.equivalents.add(equ)
equ.equivalents.add(cls)
for e in frozenset(equ.equivalents):
addequivalent(cls, e, memory)


for name, cls in onto.items():
for p in frozenset(cls.parents):
addsubclassof(cls, p)
addsubclassof(newsubclassof, cls, p)
for i in cls.instances:
addinstance(i, cls)
addinstance(newrdftype, i, cls)

memory = set()
for equivalent in frozenset(cls.equivalents):
addequivalent(cls, equivalent, memory)
addequivalent(newequivalentclasses, cls, equivalent)

for equivalent in cls.equivalents:
for p in frozenset(cls.parents):
addsubclassof(equivalent, p)
addsubclassof(newsubclassof, equivalent, p)
for c in frozenset(cls.children):
addoverclassof(c, equivalent)
addoverclassof(newsubclassof, c, equivalent)
for i in cls.instances:
addinstance(i, equivalent)
addinstance(newrdftype, i, equivalent)



Expand Down
54 changes: 0 additions & 54 deletions testing/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,6 @@ def test_existence(self):
self.assertFalse('alfred likes icecream' in self.kb)
self.assertFalse('alfred' in self.kb)

def test_existence_with_inference(self):
""" Requires a RDFS reasoner to run.
"""
self.kb += ["alfred rdf:type Human", "Human rdfs:subClassOf Animal"]
time.sleep(REASONING_DELAY)
self.assertTrue('alfred rdf:type Animal' in self.kb)

self.kb += ["Animal rdfs:subClassOf Thing"]
time.sleep(REASONING_DELAY)
self.assertTrue('alfred rdf:type Thing' in self.kb)

def test_lookup(self):
self.assertItemsEqual(self.kb.lookup('alfred'), [])

Expand Down Expand Up @@ -380,36 +369,6 @@ def test_complex_events(self):
self.assertEqual(id, evtid)
self.assertItemsEqual(value, [u"alfred"])

def test_complex_events_rdfs(self):
""" Requires a RDFS reasoner to run.
"""
evtid = self.kb.subscribe(["?a desires ?act", "?act rdf:type Action"], var = "a")

# should not trigger an event
self.kb += ["alfred desires ragnagna"]
time.sleep(0.1)

with self.assertRaises(Empty):
self.kb.events.get_nowait()

# should not trigger an event
self.kb += ["ragnagna rdf:type Zorro"]
time.sleep(0.1)

with self.assertRaises(Empty):
self.kb.events.get_nowait()

# should trigger an event
self.kb += ["Zorro rdfs:subClassOf Action"]
time.sleep(REASONING_DELAY)
# required to ensure the event is triggered after classification!
self.kb += ["nop nop nop"]
time.sleep(0.1)

id, value = self.kb.events.get_nowait()
self.assertEqual(id, evtid)
self.assertItemsEqual(value, [u"alfred"])

def test_taxonomy_walking(self):

self.assertFalse(self.kb.classesof("john"))
Expand All @@ -420,19 +379,6 @@ def test_taxonomy_walking(self):
self.kb -= ["john rdf:type Human"]
self.assertItemsEqual(self.kb.classesof("john"), [u'Genius'])

def test_taxonomy_walking_inheritance(self):
""" Requires a RDFS reasoner to run.
"""
self.kb += ["john rdf:type Human"]
self.assertItemsEqual(self.kb.classesof("john"), [u'Human'])
self.kb += ["Human rdfs:subClassOf Animal"]
time.sleep(REASONING_DELAY)
self.assertItemsEqual(self.kb.classesof("john"), [u'Human', u'Animal'])
self.assertItemsEqual(self.kb.classesof("john", True), [u'Human'])
self.kb -= ["john rdf:type Human"]
time.sleep(REASONING_DELAY)
self.assertFalse(self.kb.classesof("john"))

def test_memory(self):

self.kb.add(["john rdf:type Human"])
Expand Down
99 changes: 73 additions & 26 deletions testing/test_reasoner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,93 @@

REASONING_DELAY = 0.2

class TestSequenceFunctions(unittest.TestCase):
class TestRDFSReasoner(unittest.TestCase):

def setUp(self):
self.kb = kb.KB()
self.kb.clear()
def setUp(self):
self.kb = kb.KB()
self.kb.clear()

def tearDown(self):
self.kb.close()
def tearDown(self):
self.kb.close()

def test_reason(self):
def test_complex_events_rdfs(self):

# basic adds :
evtid = self.kb.subscribe(["?a desires ?act", "?act rdf:type Action"], var = "a")

self.kb += ['self rdf:type robot']
self.kb.add(['robot rdfs:subClassOf agent', 'agent rdfs:subClassOf humanoide', 'human rdfs:subClassOf animals'])
# should not trigger an event
self.kb += ["alfred desires ragnagna"]
time.sleep(0.1)

self.kb.add(['robot owl:equivalentClass machine', 'machine owl:equivalentClass automate'])

self.kb += ['rp2 rdfs:subClassOf automate']

# basic tests :
with self.assertRaises(Empty):
self.kb.events.get_nowait()

self.assertTrue('self rdf:type robot' in self.kb)
# should not trigger an event
self.kb += ["ragnagna rdf:type Zorro"]
time.sleep(0.1)

time.sleep(0.1)
with self.assertRaises(Empty):
self.kb.events.get_nowait()

self.assertTrue('robot rdfs:subClassOf humanoide' in self.kb)
self.assertTrue('self rdf:type humanoide' in self.kb)
self.assertTrue('robot owl:equivalentClass automate' in self.kb)
self.assertTrue('self rdf:type machine' in self.kb)
self.assertTrue('self rdf:type automate' in self.kb)
# should trigger an event
self.kb += ["Zorro rdfs:subClassOf Action"]
time.sleep(REASONING_DELAY)
# required to ensure the event is triggered after classification!
self.kb += ["nop nop nop"]
time.sleep(0.1)

self.assertTrue('rp2 rdfs:subClassOf robot' in self.kb)
id, value = self.kb.events.get_nowait()
self.assertEqual(id, evtid)
self.assertItemsEqual(value, [u"alfred"])


def test_taxonomy_walking_inheritance(self):

self.kb += ["john rdf:type Human"]
self.assertItemsEqual(self.kb.classesof("john"), [u'Human'])
self.kb += ["Human rdfs:subClassOf Animal"]
time.sleep(REASONING_DELAY)
self.assertItemsEqual(self.kb.classesof("john"), [u'Human', u'Animal'])
self.assertItemsEqual(self.kb.classesof("john", True), [u'Human'])
self.kb -= ["john rdf:type Human"]
time.sleep(REASONING_DELAY)
self.assertFalse(self.kb.classesof("john"))


def test_second_level_inheritance(self):

self.kb += 'myself rdf:type Robot'
self.kb += ['Robot rdfs:subClassOf Agent', 'Agent rdfs:subClassOf PhysicalEntity']

time.sleep(REASONING_DELAY)

self.assertTrue('Robot rdfs:subClassOf PhysicalEntity' in self.kb)
self.assertTrue('myself rdf:type PhysicalEntity' in self.kb)


def test_equivalent_classes_transitivity(self):

self.kb += 'myself rdf:type Robot'
self.kb += ['Robot owl:equivalentClass Machine', 'Machine owl:equivalentClass Automaton']
self.kb += 'PR2 rdfs:subClassOf Automaton'

time.sleep(REASONING_DELAY)

self.assertTrue('Robot owl:equivalentClass Automaton' in self.kb)
self.assertItemsEqual(self.kb.classesof("myself"), [u'Robot', u'Machine', u'Automaton'])
self.assertTrue('PR2 rdfs:subClassOf Robot' in self.kb)

def test_existence_with_inference(self):

self.kb += ["alfred rdf:type Human", "Human rdfs:subClassOf Animal"]
time.sleep(REASONING_DELAY)
self.assertTrue('alfred rdf:type Animal' in self.kb)

self.kb += ["Animal rdfs:subClassOf Thing"]
time.sleep(REASONING_DELAY)
self.assertTrue('alfred rdf:type Thing' in self.kb)


def version():
print("minimalKB tests %s" % __version__)
print("minimalKB RDFS reasoner tests %s" % __version__)

if __name__ == '__main__':

Expand Down