Declaring your intent
At the heart of any Ontology is a set of business rules. Unlike traditional business intelligence and data processing, we don’t write Vadalog as imperative commands: first get data from this place, then, join it with data from this other place. Instead, we declare what we want the output to be, and let the system perform the necessary commands to arrive at our desired output. Let’s say you want to find out whether a user on your platform is “engaged”, and the way your company has decided this (i.e. your “business logic”), is that the user must have performed at least 10 actions on your system within the past 30 days. Imperatively, you might say: for a given user, find all events where the userId is user.id and event.date < 30 days. Then count the number of events and returntrue if that count is > 10:
Imperative pseudocode
showLineNumbers
Start by modelling business logic
When writing an ontology, it helps to start by thinking of the business rules you wish to model. What logic do you currently have that’s represented by some code, performed by some process or workflow, or trapped in the head of a subject matter expert? Let’s take the case of company control:A company X controls a company Y, if:The goal of this model is to determine which companies controls another, whether via direct or indirect ownership. It helps to think of a concrete example first before abstracting it, so let’s take the congolomerate Yum! Brands, with its many brands such as KFC, Pizza Hut and Taco Bell. We want to know all the companies that Yum! controls. Let’s start by writing out some facts — these are ground truths that are based in real data that will eventually come from our database.
- X directly owns more than 50% of Y; or,
- X controls a set of companies that jointly, and possibly together with X itself, own more than 50% of Y.
showLineNumbers
showLineNumbers
SomeBrand, and the number of shares as the variable
Shares.
showLineNumbers
FastFoodsGroup, and some ownership facts.
FastFoodsGroup owns 45% of McDonalds, and Yum owns 70% of FastFoodsGroup.
showLineNumbers
showLineNumbers
showLineNumbers
Shares > 0.5 for each of our current
rules, we should sum up the shares first before applying this “control”
condition. To do that, we consider all possible intermediaries, and keep track
of the running sum while we step through each intermediary. How do we do that?
Work back from the end goal
It always helps to think of working backwards from your end goal: Yum! controls some brand, if it controls directly or indirectly, more than 50% of its shares. Let’s use the verb phrasehas_shares to represent this.
showLineNumbers
has_shares, we should
rephrase our 2 existing rules by dropping the condition (since that now happens
later on line 19), and moving the Shares variable into the head predicate so
that we can keep track of its value.
showLineNumbers
When performing arithmetic across recursion, we need to use the recursion-specific
aggregators,
which keep track of the aggregation (in this case “sum”) across each recursive call.
showLineNumbers
has_total_shares rule, we’ve dropped Intermediary from the head
of the rule and replaced it with TotalShares. Since we only care about the
total sum of the shares, and we’ve already used the msum
aggregation to the joint shares for any given intermediary to
our running total, we no longer need the identity of
the intermediary.
But this program will fail to run! The eagle eyed will notice that we actually
have two definitions of has_shares, one with 3 arguments, and one with 4.
Because the arity of these two head atoms is different, Prometheux will treat
them as separate predicates, as if you had named them different things.
To resolve this, we simply force line 8 to have 4 arguments, by adding an anonymous
variable _ into the 2nd position. This argument is never processed anyway, so
you could call it anything you want and it would be ignored.
showLineNumbers
@output annotation
showLineNumbers
FastFoodsGroup owns 20% of McDonalds:
Thinking recursively
You might be wondering, why did we definehas_shares for Rule 2 to be based on control, which is our final output? Doesn’t that lead to an infinite loop?
This is a good intuition, and indeed we can write rules that don’t do this:
Non-recursive version of the program
controls, one for direct ownership, and one for
indirect ownership. Since we’re no longer using recursion, we don’t have to use
the msum operator, and can just use the regular + operator.
Note, however that we’ve replicated the >50% shares rule three times in this
ontology. While it may seem trivial, updating very large ontologies can be very
prone to human error. Where possible, you always want to have a single source
of truth for your business rules.
What’s more, our company control rules themselves are recursive, so sometimes it’s actually easier to think recursively!
What is recursion
Many systems are naturally recursive: folder directories, contact tracing for disease spread, family trees and so on. When we model a problem we’re creating simplified versions of the world. Models are simply abstractions of some more complex system. The Vadalog rules that you write in your program help decide what is within the scope of the model, and what you can handwave away for your current calculation. If we were to trace a family tree, we can keep going up the tree, finding an individual’s parents, grandparents, great-grandparents and so on. If we can’t abstract away this never-ending relationship, we’d have to model all of humanity just to conceptualise what a family tree is! For every predicate, the head atom (the thing before<-) declares what the rule’s
intention. We can read it as “ignore everything after <- for that is an implementation
detail”. We don’t care how the rule is implemented just what it represents.
The head atom is the abstraction of its definition in the rule’s body. Abstractions allow us to ignore how a rule is defined, and simply use it in the current context.
Consider our ontology again:
showLineNumbers
controls(Conglomerate, SomeBrand) is an abstraction over share ownership. All it’s saying is that some conglomerate may control some brand. How we
define control as a function of owning shares is an implementation detail.
Perhaps in another world, there’s a conglomerate who has control over a brand
by royal decree.
Structure of a recursive rule
A rule is recursive if its definition tree eventually leads back to itself. Recursive rules tend to follow a specific form: they have a recursive definition (the part that calls itself), and a base case (the termination condition). Recursive Case In our examplehas_shares, is defined with controls, which in turn is
defined by has_total_shares, which is then defined by has_shares. We’re able
to write rules like that because we’re relying on the abstraction of controls
to define our has_shares rule. has_shares can “use” controls without
having to care about how it’s defined.
But how do we resolve this infinite loop? At some point, we need to tell a
recursive programme to stop going in circles.
Base Case
This is why we have a base case, or a condition at which we tell the recursion to terminate. For us, this is the rule
- Recursive
- Non-recursive
showLineNumbers
showLineNumbers
@output,
is that we’ve essentially created a union. Instead of defining a rule that functions
as a base case for recursion, we’ve created two separate rules and joined
their outputs together.
A good rule of thumb is to try to keep your output to one atom, and perform
recursion one level deeper, which is exactly what our orignal ontology has done
with the has_shares rule. controls is recursively defined only at the level
of has_shares, and is not itself recursive.
Convert queries into Vadalog
If you’re familiar with relational or graph databases, you might wonder how Prometheux can help you connect the dots across different business entities that might require multiple joins or hops.From SQL to Vadalog
To compose data in relational database management systems (RDBMS), you often need to reach around the abstraction of business entities that tables provide. Answering complex then requires unweildy joins across many different tables. A simple way to think in Vadalog, is that multiple body predicates separated by commas are indeed a join between each predicate. Instead of thinking about which tables to join, you simply combine the rules that define the binding to the database.- Vadalog
- SQL
showLineNumbers
Modelling time
To model time, simply add a time variable to your predicates so that time can flow through your entire program. Assuming theevent predicate is backed by some data with timestamps:
showLineNumbers

