NeuroLang for Logic Programmers¶
NeuroLang is implemented over the basis of Datalog+/- with probabilistic extensions. In that there are two main frontend which might came useful: the python_embedded_ frontend and the datalog_ frontend
Using Datalog Embedded in Python¶
This requires a first step importing the NeuroLang frontend and initialising the class:
>>> from neurolang import frontend
>>> nl = frontend.NeurolangDL()
Then, we can add some facts (connected) and rules (reachable):
>>> with nl.environment as e:
... e.connected[0, 1] = True
... e.connected[1, 2] = True
... e.connected[2, 3] = True
... e.reachable[e.x, e.y] = e.connected[e.x, e.y]
... e.reachable[e.x, e.y] = e.reachable[e.x, e.z] & e.connected[e.z, e.y]
please note how the environment :python:`e` allows for the creation of logic programming symbols dynamically.
With this we now have program loaded in memory which can be explored as:
>>> print(nl.symbols['connected'])
connected: typing.AbstractSet[typing.Tuple[int, int]] = [(0, 1), (1, 2), (2, 3)]
>>> for rule in nl.current_program:
... print(rule)
reachable(x, y) ← connected(x, y)
reachable(x, y) ← ( reachable(x, z) ) ∧ ( reachable(z, y) )
Finally, we can solve the query:
>>> with nl.environment as e:
... res = nl.query((e.x, e.y), e.reachable(e.x, e.y))
>>> print(res)
0 1
0 0 1
1 1 2
2 2 3
3 0 2
4 1 3
5 0 3
Alternatively the connected table can be added more efficiently from a tuple iterable or a numpy.array as follows:
>>> nl.add_tuple_set([(0, 1), (1, 2), (2, 3)], name='connected')
Including Aggregations and Builtin Functions¶
Suppose that now we want to obtain the number of destinations reachable by each starting point. For this we need a new aggregation function that counts the number of distinct elements. Specifically:
>>> from typing import Iterable
>>> from neurolang import frontend
>>> nl = frontend.NeurolangDL()
>>> nl.add_tuple_set([(0, 1), (1, 2), (2, 3)], name='connected')
>>> @nl.add_symbol
>>> def agg_count(x: Iterable) -> int:
>>> return len(set(x))
>>> with nl.environment as e:
... e.reachable[e.x, e.y] = e.connected[e.x, e.y]
... e.reachable[e.x, e.y] = e.reachable[e.x, e.z] & e.connected[e.z, e.y]
This adds a new function agg_count to the NeuroLang interpreter, any built-in or aggregation function is added in the same manner. Then, we can then count all arrivals for starts 0 and 1:
>>> with nl.environment as e:
>>> e.count_arrivals[e.x, agg_count(e.y)] = e.reachable(e.x, e.y)
>>> counts = nl.query((e.x, e.c), e.count_arrivals(e.x, e.c) & (e.x < 2))
>>> print(counts)
0 1
0 0 3
1 1 2
Adding Constraints and Open Knowledge Rules¶
Neurolang also supports tuple-generating dependencies (TGDs). We can say that a person is a parent if they have a child:
>>> from neurolang.frontend import NeurolangPDL
>>> nl = NeurolangPDL()
>>> with nl.environment as e:
... e.parent['John', 'Carl'] = True
... e.parent['Mary', 'Carl'] = True
... e.parent['Pat', 'Anna'] = True
... e.parent['Anna', 'Pete'] = True
... e.person[e.x] = e.parent[e.x, ...]
... e.person[e.x] = e.parent[..., e.x]
... nl.add_constraint(e.person[e.x], e.person[e.y] & e.parent[e.y, e.x])
... e.has_parent[e.x] = e.person[e.x] & e.parent[e.x, e.y]
>>> res = nl.solve_all()
>>> print(res)
>>> from neurolang.frontend import NeurolangPDL
>>> nl = NeurolangPDL()
>>> with nl.environment as e:
... e.person['Pat'] = True
... e.person['Chris'] = True
... nl.add_constraint(e.person[e.x], e.parent[e.y, e.x])
... e.has_grand_parent[e.x] = e.person[e.x] & e.person[e.y] & e.parent[e.y, e.x] & e.parent[e.z, e.y]
>>> res = nl.solve_all()
>>> print(res)