Home

RDF Reification Revised

Updated:
Created:

I think I misunderstood RDF all these years.

tl;dr: RDF can store everything neo4j has to say.

Problem

My main problem was expressing something like this in RDF:

flowchart LR Alice -- meets\ndate: 1 . 1 . 25 --> Bob Alice -- meets\ndate: 2 . 2 . 26 --> Bob
  1. How can I have properties on the relation(s)?
  2. How can I have two similar relations?

RDF has no direct notion of ids for triples, so I can not talk about relations / triples directly. Also, from what I understand, you can have the same relation / triple only once in a graph / triple store.

One way reification, but that had two problems:

  1. Very cumbersome to express
  2. You can talk about the triple :Alice :likes :Bob, but the triple does not get asserted
  3. Even if you additionally assert the triple :Alice :likes :Bob, you can only do it once.

Mistake and solution

Coming from a neo4j / memgraph / LPG background, I always thought you need to have separate relations going from Alice to Bob. But now I understand that in order to express the same thing in RDF, you would need to go another way.

flowchart TD subgraph rel [statement] direction LR Alice -- meets --> Bob end c1[rel1\n\ndate: 1.1.25] -- rdf:reifies --> rel c2[rel2\n\ndate: 1.1.25] -- rdf:reifies --> rel

The idea seems to be that logically the actual assertion is only needed once. With the assertion you can do whatever you want, e.g run inferences etc. The context / provenance is kept in the reified statements, e.g. containing date, observer.

Now, RDF 1.2 and RDF 1.2 turtle is going to make this so much easier. Kurt Cagle’s article explains it quite nicely.

:Alice :meets :Bob {|               # This is not named, could be rel1
    :date "2026-04-24"^^xsd:date ;
    |} .

:Alice :meets :Bob ~ :rel2 {|       # This is named
    :date "2026-04-24"^^xsd:date ;
    |} .

In both cases :Alice :meets :Bob is actually asserted. In the first expression the reification is not named, which means we can’t talk about it later on. The second one has a name, so we can make statements about it. Nice!

The LPG perspective

I think that RDF can store anything an LPG graph can and more. One only has to think differently about it. RDF is a bit more “low level”. Instead of having multiple relationships which carry different context / provenance information, you split it. You have the underlying logical expression (e.g. :Alice :meets :Bob), and then separate context statements.

I also think that RDF allows much more flexibility, but this comes at a cost: graph algorithms. This is where neo4j, memgraph and networkx really shine. But they assume that you have a clear separation between nodes and edges / relationships. So, you would need to project from your RDF bottom layer into a graph layer, where you turn everything into clearly defined nodes and edges, and then go from there.

I think you can transfer all neo4j graphs to RDF. You can also transfer a subset of RDF back to neo4j.

From neo4j to RDF

  1. Assign IRIs to all nodes. You don’t need to persist this in the neo4j database, but at least your transfer process needs to be aware of this
  2. Assign IRIs to relationships. Again, they don’t need to be persisted, but at least the transfer process needs to be aware of this
  3. Go through all nodes
    1. Write a node-IRI :is_node true"^^xsd:boolean
    2. The node has properties: write all properties and labels as statements to RDF
  4. Go through all relationships
    1. Create a “base relation” Start-IRI :type Target-IRI, if it not already exists
    2. Create a reification statement with the relation IRI as an identifier, even if it has no properties
    3. Write all properties of this relation as statements

From RDF to neo4j

This is only possible with an RDF graph that was created as described above

  1. Go through all node-IRIs for which :is_node is true
    1. Create a neo4j node per node-IRI
    2. Assign labels and properties
  2. Go through all reification statements
    1. Create a relationship per reification statement, using the type from the reificated “base relation”
    2. Assign all property statements from the reification statement to the relationship

Best of both worlds?

So I think you can store every LPG easily in RDF, but not the other way around. For RDF->LPG you need the above-mentioned node/edge transformation. If however you stick to the limitations of LPG, and sync your LPG and RDF, you could use the best of both worlds: