Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Turtle & OWL Correspondence

Dolfin is a source language for OWL ontologies. Compiling a package produces a single Turtle document (plus a companion N3 file for rules, and a companion SPARQL file for queries). The reverse is also supported: an OWL/RDF graph can be recovered back into a Dolfin package. This chapter describes the mapping in both directions.

Two conversions exist:

  • Dolfin → Turtle: compile a .dlf package to an OWL graph.
  • Turtle → Dolfin: reconstruct a .dlf package from an OWL graph.

The mapping is defined at the level of triples, not text. A round trip Dolfin → Turtle → Dolfin preserves the ontology’s meaning; it does not guarantee a byte-identical source file (see Round trips).


How IRIs are named

Every concept, property, and individual gets an IRI. IRIs are built from three ingredients:

  1. a base IRI (the package identity),
  2. the file path of the declaration inside the package,
  3. the local name of the entity.

The base IRI

The base IRI comes from the package declaration in package.dlf:


package <http://example.com/animals>:
  dolfin_version "1"
  version "0.1.0"

If the package is declared with an absolute IRI (<http://…>), that IRI is the base directly. If it is declared with a bare name (package animals:), the base is <compiler-base-iri>/animals, where the compiler base IRI is supplied at compile time (default http://example.org/).

File path → namespace segment

Each ontology file contributes a namespace segment derived from its path relative to the package root. A file named mammals.dlf produces the namespace IRI:

http://example.com/animals/mammals#

The filename (without .dlf) becomes the last path segment, and a # fragment terminator is appended. Nested files nest on disk and in the IRI: a file at vertebrates/mammals.dlf yields …/animals/vertebrates/mammals#.

The base namespace (declarations that belong to the package root rather than a sub-file) uses the base IRI directly with no extra path segment. On the way back, base-namespace entities are written to main.dlf, with @iri_name <base/> when the base IRI ends in /.

Local names and prefix labels

An entity’s local name is its Dolfin name, appended to its namespace IRI. The namespace ends with a terminator, # unless an @iri_name says otherwise (see The namespace terminator):

DolfinIRI
concept Mammal in mammals.dlfhttp://example.com/animals/mammals#Mammal
concept Animal in the root filehttp://example.com/animals#Animal
concept Foo in a file with @iri_name <http://ex.org/ns/>http://ex.org/ns/Foo

In the emitted Turtle, entities are referenced with a prefix label taken from the filename. Mammal in mammals.dlf is written mammals:Mammal; entities in the base namespace use the empty prefix, :Animal. Each file’s namespace IRI is bound with an @prefix declaration at the top of the document.

A Dolfin fact reference keeps the Turtle meaning of the empty prefix: owner :JohnSmith is emitted as :JohnSmith, in the base namespace, whatever file it is written in. To reference the JohnSmith fact of data.dlf, write the bare owner JohnSmith, emitted as data:JohnSmith. No fact lives in the base namespace (package.dlf only holds the manifest), so the analyzer warns on every :Name fact reference.

Overriding the IRI with @iri_name

The @iri_name annotation overrides the derived IRI. It has three forms:


# Absolute: replaces the whole namespace IRI for this file
@iri_name <http://animals.kingdom/Mammalian>

# Local segment: replaces only the last path segment
@iri_name "Mammalian"      # → http://example.com/animals/Mammalian#

# On a single concept: overrides just that concept's IRI
concept Mammal:
  @iri_name <http://animals.kingdom/Mammals>

@iri_name affects only the IRI. The prefix label used to reference the entity is still derived from the filename, and sibling files and sibling concepts are unaffected.

The namespace terminator

Entity IRIs are namespace + terminator + local name. The terminator is decided by the file-level @iri_name:

@iri_name in the fileNamespace used for entitiesconcept Foo becomes
none (path-derived)…/animals/mammals#…/animals/mammals#Foo
"Mammalian" (segment)…/animals/Mammalian#…/animals/Mammalian#Foo
<http://ex.org/ns> (no terminator)http://ex.org/ns#http://ex.org/ns#Foo
<http://ex.org/ns#> (ends in #)http://ex.org/ns#http://ex.org/ns#Foo
<http://ex.org/ns/> (ends in /)http://ex.org/ns/http://ex.org/ns/Foo

A declared IRI that already ends in # or / keeps that terminator; anything else gets the default #. The @prefix declarations in the Turtle follow the same rule, so ns:Foo and <http://ex.org/ns/Foo> always agree.

Behaviour change. Older versions always appended #, so @iri_name <http://ex.org/ns#> produced http://ex.org/ns##Foo. It now produces http://ex.org/ns#Foo. If you worked around the doubled #, remove the workaround.

A concept-level @iri_name is never modified: its IRI is used verbatim.

File resources (rdfs:isDefinedBy)

To make files (and @iri_name overrides) recoverable, every emitted entity carries an rdfs:isDefinedBy triple pointing at its file resource — the file’s canonical, path-derived namespace IRI with no trailing terminator and no @iri_name override applied:

mammals:Mammal rdf:type rdfs:Class
  ; skos:prefLabel "Mammal"
  ; rdfs:isDefinedBy <http://example.com/animals/mammals> .

Each non-base file also declares its file resource and links it to the package ontology, so the package’s file set is explicit:

<http://example.com/animals/mammals> rdfs:isDefinedBy <http://example.com/animals> .

Base-namespace entities are defined by the package IRI itself (their file resource equals the owl:Ontology subject), so no self-loop is emitted.

Because the file resource is override-free, the reverse direction uses it to (1) assign each entity to its real file even when @iri_name rewrote the entity IRIs, and (2) detect that an override was used — by comparing the entity’s actual IRI against <file-resource>#<local>.


Declaration correspondence

The following table summarises the core mapping from Dolfin declarations to OWL triples.

DolfinOWL / RDF
concept CC rdf:type rdfs:Class
concept C: sub PC rdfs:subClassOf P
has p: T (T primitive)p rdf:type owl:DatatypeProperty ; rdfs:domain C ; rdfs:range xsd:…
has p: T (T a concept)p rdf:type owl:ObjectProperty ; rdfs:domain C ; rdfs:range T
enum / one of (a, b, …)owl:equivalentClass [ owl:oneOf ( … ) ] + one owl:NamedIndividual per variant
fact blockowl:NamedIndividual with rdf:type and property-value triples (the ABox)

A field name used by several concepts of one file is a single property: its rdfs:domain is [ a owl:Class ; owl:unionOf ( C1 C2 … ) ] (and its range the union of their ranges when they differ), so an instance of C1 is never inferred to be a C2.

Package metadata

Dolfin manifest fieldOWL triple on the owl:Ontology node
versionowl:versionInfo
descriptionrdfs:comment
authordc:creator (dc: = http://purl.org/dc/elements/1.1/)

Primitive types

DolfinXSD datatype
stringxsd:string
intxsd:integer
floatxsd:double
booleanxsd:boolean
datexsd:date
date_timexsd:dateTime
timexsd:time
durationxsd:duration

Quantities

A quantity(...) value is not a primitive type. It compiles to a typed literal whose datatype is a canonical unit IRI ("45"^^<https://dolfin.dev/unit/kg>), accompanied by a once-per-unit definition block that states the unit’s coefficient, dimension, and symbol with dq: predicates. See Units & Quantities for the full mapping.

Comments and labels

A declaration’s name, leading comment and #@ glossary: annotation become RDFS / SKOS annotations:

SourceRDFS / SKOS
declaration name (or #@ glossary: pref_label[@xx]=)skos:prefLabel
leading comment text (# … / #xx> …)rdfs:comment
#@ glossary: definition= / definition@xx=skos:definition
#@ glossary: alt_label[@xx]=skos:altLabel
#@ glossary: scope_note=skos:scopeNote

A #xx> prefix on a leading comment line gives the literal its language tag (#fr> … → rdfs:comment "…"@fr, one triple per language); a plain # … line yields an untagged literal. Likewise definition@xx= yields a tagged skos:definition and a plain definition= an untagged one.

Cardinality

A has field’s cardinality becomes an OWL restriction on the owning class:

Dolfin cardinalityOWL restriction on the class
oneowl:equivalentClass [ owl:cardinality 1 ], property is owl:FunctionalProperty
optionalowl:equivalentClass [ owl:maxCardinality 1 ], property is owl:FunctionalProperty
someowl:subClassOf [ owl:minCardinality 1 ]
exactly nowl:subClassOf [ owl:cardinality n ]
n to mowl:subClassOf [ owl:minCardinality n ; owl:maxCardinality m ]
any (default)no restriction

Property axioms

Axioms declared on a property map to OWL property characteristics:

DolfinOWL
symmetricrdf:type owl:SymmetricProperty
reflexiverdf:type owl:ReflexiveProperty
transitiverdf:type owl:TransitiveProperty
sub qrdfs:subPropertyOf q
inverse of qowl:inverseOf q
equivalent to qowl:equivalentProperty q
equivalent to ^qowl:equivalentProperty [ owl:inverseOf q ]
equivalent to a . b (chain)owl:propertyChainAxiom ( a b ) via an rdfs:subPropertyOf node
equivalent to q+transitive + propertyChainAxiom ( q self )
equivalent to q*transitive + reflexive + propertyChainAxiom ( q self )

Rules

Rules do not fit the OWL description-logic fragment. They compile to N3 (Notation3) { … } => { … } implications, written to a companion N3 document alongside the Turtle. The N3 file shares the same @prefix bindings as the Turtle so the two are consistent.

Comparisons

A comparison in a match: constraint block compiles to an N3 builtin. Which family is chosen depends on what is compared: the literal on the right, or, against a variable, the declared range of the property whose value is constrained.

Compared values=!=< <= > >=
Numbers, dates, times, durationsmath:equalTomath:notEqualTomath:lessThan, math:notGreaterThan, math:greaterThan, math:notLessThan
Quantities (quantity(...), or a dimension range such as unit.Mass)dq:equalTodq:notEqualTodq:lessThan, dq:lessThanOrEqual, dq:greaterThan, dq:greaterThanOrEqual
Strings, booleans, IRIs, individuals of a conceptlog:equalTolog:notEqualTomath: as for numbers

?p age [ = 18 ]        # ?_v1 math:equalTo "18"^^xsd:integer .
?p name [ = "Bob" ]    # ?_v2 log:equalTo "Bob" .
?q friend [ != ?f ]    # ?_v3 log:notEqualTo ?f .

log:equalTo is term identity: "18"^^xsd:integer and "18.0"^^xsd:decimal are different terms, and "Bob" differs from "Bob"@en. math:equalTo compares values, so it only holds between numbers, dates or times. A variable whose type cannot be inferred, or a float property holding quantities, is compared with math:.

Quantifiers

Quantifiers use the N3 log: and list: builtins, evaluated in the reasoning scope ?_scope:

DolfinN3
none ?v [C]: P?_scope log:notIncludes { ?v C . P } .
all ?v [C]: P( { ?v C } { P } ) log:forAllIn ?_scope .
at least N ?v [C]: P( ?v { ?v C . P } ?L ) log:collectAllIn ?_scope . ?L list:length ?n . ?n math:notLessThan N .
at most N, exactly N, between N, Msame, with math:notGreaterThan, math:equalTo, or both bounds
any ?v [C]: PC and P inline

A rule is never emitted with a weaker condition than its source. When a quantifier has no faithful encoding, the whole rule is written as a comment with the reason, and raft build prints a warning:

  • all ?v without a [ … ] constraint: there is no domain for ?v to range over.
  • A count whose sub-patterns bind variables other than ?v and the rule’s own (e.g. ?r distance ?d): log:collectAllIn would count one ?v once per ?d.

raft build --emit n3-rules (for retox) follows the same rules: quantified rules are emitted live, and retox’s ReasoningDataset evaluates them in strata. Loading fails when a quantifier depends on its own conclusion (a cycle through a quantifier), or when a quantified rule derives rdfs:subClassOf, rdfs:subPropertyOf or owl:sameAs.


Round trips

In Agrafe this direction is the Import from Turtle feature.

Reconstructing Dolfin from an OWL graph works at the triple level: it reads the owl:Ontology node, the classes, properties, restrictions, individuals, and SKOS/dc/rdfs metadata, and rebuilds the package layout from rdfs:isDefinedBy (falling back to the entity IRIs when it is absent).

A Dolfin → Turtle → Dolfin round trip preserves meaning but normalises form. Keep in mind:

  • Files are rebuilt from rdfs:isDefinedBy. Each entity’s file resource names its file (…/animals/mammals → mammals.dlf); base-namespace entities land in main.dlf. Turtle that carries no rdfs:isDefinedBy (e.g. graphs not produced by this compiler) falls back to deriving the file from the entity IRI structure.
  • @iri_name overrides are reconstructed. When an entity’s IRI deviates from its file resource, the override is recovered: a file-wide, uniform deviation becomes a file-level @iri_name <ns> (or <ns/> when every entity shares a /-terminated namespace; # wins if both occur); an isolated one becomes a concept-level @iri_name <iri>. The recovered directive uses the absolute form (a @iri_name "segment" is recovered as the equivalent absolute IRI).
  • Entities outside the package base are dropped. Only entities defined by the package (via rdfs:isDefinedBy, or IRI-under-base in the fallback) are reconstructed. Imported/external vocabulary is treated as external and skipped.
  • dolfin_version is not carried in the graph and defaults to "1" on the way back.
  • Comments and formatting are not preserved. Only the structured annotations that became triples (rdfs:comment, SKOS labels, dc:creator) are recovered.