FuXi is a Python-based, bi-directional logical reasoning system for the semantic web. FuXi aims to be the engine for contemporary expert systems based on the Semantic Web technologies. It is named after the first of the Three Sovereigns of ancient China. It originally formed from an idea to express the underlying symbols (and their relationships) in the Yi Jing or Zhou Yi (“The Book of Changes”) in OWL and RDF in order to reason over their structural relationships.
For an overview of the architecture, please read FuXi Overview and FuXi User Manual for more information.
Documentation of key classes and methods (referenced in above Wikis)
An implementation of most of the RETE-UL algorithms outlined in the PhD thesis (1995) of Robert Doorenbos: Production Matching for Large Learning Systems. See FuXi.Rete in manual for how to use SetupRuleStore to create an ReteNetwork.
Sets up a N3RuleStore, a Graph (that uses it as a store, and )
The Rete network. The constructor takes an N3 rule graph, an identifier (a BNode by default), an initial Set of Rete tokens that serve as the ‘working memory’, and an rdflib Graph to add inferred triples to - by forward-chaining via Rete evaluation algorithm),
end
The second ‘pass’ in the Rete network compilation algorithm: Attaches Beta nodes to the alpha nodes associated with all the patterns in a rule’s LHS recursively towards a ‘root’ Beta node - the terminal node for the rule. This root / terminal node is returned
Takes an iterator of triples in the LHS of an N3 rule and an iterator of the RHS and extends the Rete network, building / reusing Alpha and Beta nodes along the way (via a dictionary mapping of patterns to the built nodes)
Stratified Negation Semantics for DLP using SPARQL to handle the negation
Feeds the network an iterator of facts / tokens which are fed to the alpha nodes which propagate the matching process through the network
“In general, a p-node also contains a specifcation of what production it corresponds to | the name of the production, its right-hand-side actions, etc. A p-node may also contain information about the names of the variables that occur in the production. Note that variable names are not mentioned in any of the Rete node data structures we describe in this chapter. This is intentional |it enables nodes to be shared when two productions have conditions with the same basic form, but with different variable names.”
Takes a set of tokens and the terminal Beta node they came from and fires the inferred statements using the patterns associated with the terminal node. Statements that have been previously inferred or already exist in the working memory are not asserted
Register the given execute function for any rule with the given head using the override argument to determine whether or not the action completely handles the firing of the rule.
The signature of the execute action is as follows:
Reset the network by emptying the memory associated with all Beta Nodes nodes
FuXi includes an API that was originally implemented as a reference implementation of the W3C’s Rule Interchange Format Basic Logic Dialect but eventually evolved into a Pythonic API for managing an abstract Logic Programming syntax. It includes functions used for creating rulesets converted from OWL RDF expressions and creating a Horn ruleset from a parsed Notation 3 graph:
Takes the path or URL of a N3 document, and a mapping from predicates to functions that implement any builtins found in the N3 document
Takes an OWL RDF graph, an indication of what level of ruleset safety (see: http://code.google.com/p/fuxi/wiki/FuXiUserManual#Rule_Safety) to apply, and a list of derived predicates and returns a Ruleset instance comprised of the rules extracted from the OWL RDF graph (using a variation of the OWL 2 RL transformation)
Takes an N3 / RDF conjunctive graph and returns a ReteNetwork built from the rules in the N3 graph
Below are the various classes and functions that comprise this API
if-part) of a rule supported by the basic RIF logic. As explained in Section Overview, RIF’s Basic Logic Dialect corresponds to definite Horn rules, and the
bodies of such rules are conjunctions of atomic formulas without negation.
CONJUNCTION ::= ‘And’ ‘(‘ CONDITION* ‘)’
>>> And([Uniterm(RDF.type,[RDFS.comment,RDF.Property]),
... Uniterm(RDF.type,[OWL.Class,RDFS.Class])])
And( rdf:Property(rdfs:comment) rdfs:Class(owl:Class) )
A variable, v, is bound in a conjunction formula, f = And(c1...cn), n ≥ 1, if and only if, either
For now we don’t support equality predicates, so we only check the first condition
>>> x=Variable('X')
>>> y=Variable('Y')
>>> lit1 = Uniterm(RDF.type,[x,RDFS.Class])
>>> lit2 = Uniterm(RDF.Property,[y,RDFS.Class])
>>> conj = And([lit1,lit2])
>>> conj.binds(Variable('Y'))
True
>>> conj.binds(Variable('Z'))
False
A variable, v is safe in a condition formula if and only if ..
f is a conjunction, f = And(c1...cn), n ≥ 1, and v is safe in at least one conjunct in f
Since we currently don’t support equality predicates, we only check the first condition
>>> x=Variable('X')
>>> y=Variable('Y')
>>> lit1 = Uniterm(RDF.type,[x,RDFS.Class])
>>> lit2 = Uniterm(RDF.Property,[y,RDFS.Class])
>>> conj = And([lit1,lit2])
>>> conj.isSafeForVariable(y)
True
>>> And([Uniterm(RDF.type,[RDFS.comment,RDF.Property]),
... Uniterm(RDF.type,[OWL.Class,RDFS.Class])]).n3()
u'rdfs:comment a rdf:Property .\n owl:Class a rdfs:Class'
ATOMIC ::= Uniterm | Equal | Member | Subclass (| Frame)
A variable, v, is bound in an atomic formula, a, if and only if
Default is False
CONDITION ::= CONJUNCTION | DISJUNCTION | EXISTENTIAL | ATOMIC
A variable, v is safe in a condition formula if and only if ..
Equal ::= TERM ‘=’ TERM TERM ::= Const | Var | Uniterm | ‘External’ ‘(‘ Expr ‘)’
>>> Equal(RDFS.Resource,OWL.Thing)
rdfs:Resource = owl:Thing
EXISTENTIAL ::= ‘Exists’ Var+ ‘(‘ CONDITION ‘)’ >>> Exists(formula=Or([Uniterm(RDF.type,[RDFS.comment,RDF.Property]), ... Uniterm(RDF.type,[OWL.Class,RDFS.Class])]), ... declare=[Variable(‘X’),Variable(‘Y’)]) Exists ?X ?Y ( Or( rdf:Property(rdfs:comment) rdfs:Class(owl:Class) ) )
A variable, v, is bound in an existential formula, Exists v1,...,vn (f’), n ≥ 1, if and only if v is bound in f’
>>> ex=Exists(formula=And([Uniterm(RDF.type,[RDFS.comment,RDF.Property]),
... Uniterm(RDF.type,[Variable('X'),RDFS.Class])]),
... declare=[Variable('X')])
>>> ex.binds(Variable('X'))
True
A variable, v is safe in a condition formula if and only if ..
f is an existential formula, f = Exists v1,...,vn (f’), n ≥ 1, and v is safe in f’ .
An External(ATOMIC) is a call to an externally defined predicate, equality, membership, subclassing, or frame. Likewise, External(Expr) is a call to an externally defined function. >>> ExternalFunction(Uniterm(URIRef(‘http://www.w3.org/2000/10/swap/math#greaterThan’),[Variable(‘VAL’),Literal(2)])) math:greaterThan(?VAL “2”^^<http://www.w3.org/2001/XMLSchema#integer>)
DISJUNCTION ::= ‘Or’ ‘(‘ CONDITION* ‘)’
>>> Or([Uniterm(RDF.type,[RDFS.comment,RDF.Property]),
... Uniterm(RDF.type,[OWL.Class,RDFS.Class])])
Or( rdf:Property(rdfs:comment) rdfs:Class(owl:Class) )
A variable, v, is bound in a disjunction formula, if and only if v is bound in every disjunct where it occurs
>>> x=Variable('X')
>>> y=Variable('Y')
>>> lit1 = Uniterm(RDF.type,[x,RDFS.Class])
>>> lit2 = Uniterm(RDF.Property,[y,RDFS.Class])
>>> conj = And([lit1,lit2])
>>> disj = Or([conj,lit2])
>>> disj.binds(y)
True
>>> disj.binds(Variable('Z'))
False
>>> lit = Uniterm(RDF.type,[OWL.Class,RDFS.Class])
>>> disj= Or([lit,lit])
>>> disj.binds(x)
False
A variable, v is safe in a condition formula if and only if ..
f is a disjunction, and v is safe in every disjunct;
Creates an object which when indexed returns a Uniterm with the ‘registered’ symbol and two-tuple argument
>>> from rdflib import Namespace, URIRef
>>> EX_NS = Namespace('http://example.com/')
>>> ns = {'ex':EX_NS}
>>> somePredFactory = PredicateExtentFactory(EX_NS.somePredicate,newNss=ns)
>>> somePredFactory[(EX_NS.individual1,EX_NS.individual2)]
ex:somePredicate(ex:individual1 ex:individual2)
>>> somePred2Factory = PredicateExtentFactory(EX_NS.somePredicate,binary=False,newNss=ns)
>>> somePred2Factory[EX_NS.individual1]
ex:somePredicate(ex:individual1)
Uniterm ::= Const ‘(‘ TERM* ‘)’ TERM ::= Const | Var | Uniterm
We restrict to binary predicates (RDF triples)
>>> Uniterm(RDF.type,[RDFS.comment,RDF.Property])
rdf:Property(rdfs:comment)
Can the given mapping (presumably from variables to terms) be applied?
A variable, v, is bound in an atomic formula, a, if and only if
Default is False
>>> x = Variable('X')
>>> lit = Uniterm(RDF.type,[RDFS.comment,x])
>>> lit.binds(Variable('Z'))
False
>>> lit.binds(x)
False
>>> Uniterm(RDF.type,[x,RDFS.Class]).binds(x)
True
Takes another Uniterm and in every case where the corresponding term for both literals are different variables, we map from the variable for this uniterm to the corresponding variable of the other. The mapping will go in the other direction if the reverse keyword is True
>>> x = Variable('X')
>>> y = Variable('Y')
>>> lit1 = Uniterm(RDF.type,[RDFS.comment,x])
>>> lit2 = Uniterm(RDF.type,[RDFS.comment,y])
>>> lit1.getVarMapping(lit2)[x] == y
True
>>> lit1.getVarMapping(lit2,True)[y] == x
True
A variable, v is safe in a condition formula if and only if ..
f is an atomic formula and f is not an equality formula in which both terms are variables, and v occurs in f;
Serialize as N3 (using available namespace managers)
>>> Uniterm(RDF.type,[RDFS.comment,RDF.Property]).n3()
u'rdfs:comment a rdf:Property'
Class attribute that returns all the terms of the literal as a lists >>> x = Variable(‘X’) >>> lit = Uniterm(RDF.type,[RDFS.comment,x]) >>> lit.terms [rdflib.URIRef(‘http://www.w3.org/1999/02/22-rdf-syntax-ns#type‘), rdflib.URIRef(‘http://www.w3.org/2000/01/rdf-schema#comment‘), ?X]
This module is where the Sideways Information Passing reasoning capabilities are implemented.
Takes a goal and a ruleset and returns an iterator over the rulest that corresponds to the magic set transformation:
Backward chaining algorithms for SPARQL RIF-Core and OWL 2 RL entailment. The Backwards Fixpoint Procedure (BFP) implementation uses RETE-UL as the RIF PRD implementation of a meta-interpreter of a ruleset that evaluates conjunctive (BGPs) SPARQL queries using a SPARQL 1.1 RIF Entailment Regime.
See: Overview and User Manual
A Store which uses FuXi’s magic set “sip strategies” and the in-memory SPARQL Algebra implementation as a store-agnostic, top-down decision procedure for semanic web SPARQL (OWL2-RL/RIF/N3) entailment regimes. Exposed as a rdflib / layercake-python API for SPARQL datasets with entailment regimes Queries are mediated over the SPARQL protocol using global schemas captured as SW theories which describe and distinguish their predicate symbols
Perform RDF triple store-level unification of a list of triple patterns (4-item tuples which correspond to a SPARQL triple pattern with an additional constraint for the graph name).
Uses a SW sip-strategy implementation to solve the conjunctive goal and yield unified bindings
| Parameters : |
|---|
one of: Variable, URIRef, BNode, or Literal.
Returns a generator over dictionaries of solutions to the list of triple patterns that are entailed by the regime.
This closes the database connection. The commit_pending_transaction parameter specifies whether to commit all pending transactions before closing (if the store is transactional).
Given a conjunctive set of triples, invoke sip-strategy passing on intermediate solutions to facilitate ‘join’ behavior
Generator over all contexts in the graph. If triple is specified, a generator over all contexts the triple is in.
Given a triple, return its predicate (if derived) or None otherwise
This destroys the instance of the store identified by the configuration string.
If the given SPARQL query involves purely base predicates it returns it (as a parsed string), otherwise it returns a SPARQL algebra instance for top-down evaluation using this store
>>> graph=Graph()
>>> topDownStore = TopDownSPARQLEntailingStore(graph.store,[RDFS.seeAlso],nsBindings={u'rdfs':RDFS.RDFSNS})
>>> rt=topDownStore.isaBaseQuery("SELECT * { [] rdfs:seeAlso [] }")
>>> isinstance(rt,(BasicGraphPattern,AlgebraExpression))
True
>>> rt=topDownStore.isaBaseQuery("SELECT * { [] a [] }")
>>> isinstance(rt,(Query,basestring))
True
>>> rt=topDownStore.isaBaseQuery("SELECT * { [] a [] OPTIONAL { [] rdfs:seeAlso [] } }")
>>> isinstance(rt,(BasicGraphPattern,AlgebraExpression))
True
The default ‘native’ SPARQL implementation is based on sparql-p’s expansion trees layered on top of the read-only RDF APIs of the underlying store
A generator over all the triples matching the pattern. Pattern can include any objects for used for comparing against nodes in the store, for example, REGEXTerm, URIRef, Literal, BNode, Variable, Graph, QuotedGraph, Date? DateRange?
A conjunctive query can be indicated by either providing a value of None for the context or the identifier associated with the Conjunctive Graph (if it’s context aware).
A variant of triples that can take a list of terms instead of a single term in any slot. Stores can implement this to optimize the response time from the default ‘fallback’ implementation, which will iterate over each term in the list and dispatch to tripless
Includes the InfixOwl library (see the linked Wiki for more information). A Python binding for OWL Abstract Syntax that incorporates the Manchester OWL Syntax. See the Wiki and OWLED paper
RDFLib Python binding for OWL Abstract Syntax
3.2.3 Axioms for complete classes without using owl:equivalentClass
Named class description of type 2 (with owl:oneOf) or type 4-6 (with owl:intersectionOf, owl:unionOf or owl:complementOf
Uses Manchester Syntax for __repr__
>>> exNs = Namespace('http://example.com/')
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', exNs, override=False)
>>> namespace_manager.bind('owl', OWL_NS, override=False)
>>> g = Graph()
>>> g.namespace_manager = namespace_manager
Now we have an empty graph, we can construct OWL classes in it using the Python classes defined in this module
>>> a = Class(exNs.Opera,graph=g)
Now we can assert rdfs:subClassOf and owl:equivalentClass relationships (in the underlying graph) with other classes using the ‘subClassOf’ and ‘equivalentClass’ descriptors which can be set to a list of objects for the corresponding predicates.
>>> a.subClassOf = [exNs.MusicalWork]
We can then access the rdfs:subClassOf relationships
>>> print list(a.subClassOf)
[Class: ex:MusicalWork ]
This can also be used against already populated graphs:
#>>> owlGraph = Graph().parse(OWL_NS) #>>> namespace_manager.bind(‘owl’, OWL_NS, override=False) #>>> owlGraph.namespace_manager = namespace_manager #>>> list(Class(OWL_NS.Class,graph=owlGraph).subClassOf) #[Class: rdfs:Class ]
Operators are also available. For instance we can add ex:Opera to the extension of the ex:CreativeWork class via the ‘+=’ operator
>>> a
Class: ex:Opera SubClassOf: ex:MusicalWork
>>> b = Class(exNs.CreativeWork,graph=g)
>>> b += a
>>> print sorted(a.subClassOf,key=lambda c:c.identifier)
[Class: ex:CreativeWork , Class: ex:MusicalWork ]
And we can then remove it from the extension as well
>>> b -= a
>>> a
Class: ex:Opera SubClassOf: ex:MusicalWork
Boolean class constructions can also be created with Python operators For example, The | operator can be used to construct a class consisting of a owl:unionOf the operands:
>>> c = a | b | Class(exNs.Work,graph=g)
>>> c
( ex:Opera or ex:CreativeWork or ex:Work )
Boolean class expressions can also be operated as lists (using python list operators)
>>> del c[c.index(Class(exNs.Work,graph=g))]
>>> c
( ex:Opera or ex:CreativeWork )
The ‘&’ operator can be used to construct class intersection:
>>> woman = Class(exNs.Female,graph=g) & Class(exNs.Human,graph=g)
>>> woman.identifier = exNs.Woman
>>> woman
( ex:Female and ex:Human )
>>> len(woman)
2
Enumerated classes can also be manipulated
>>> contList = [Class(exNs.Africa,graph=g),Class(exNs.NorthAmerica,graph=g)]
>>> EnumeratedClass(members=contList,graph=g)
{ ex:Africa ex:NorthAmerica }
owl:Restrictions can also be instanciated:
>>> Restriction(exNs.hasParent,graph=g,allValuesFrom=exNs.Human)
( ex:hasParent only ex:Human )
Restrictions can also be created using Manchester OWL syntax in ‘colloquial’ Python >>> exNs.hasParent |some| Class(exNs.Physician,graph=g) ( ex:hasParent some ex:Physician )
>>> Property(exNs.hasParent,graph=g) |max| Literal(1)
( ex:hasParent max 1 )
#>>> print g.serialize(format=’pretty-xml’)
DisjointClasses(‘ description description { description } ‘)’
Terms in an OWL ontology with rdfs:label and rdfs:comment
See: http://www.w3.org/TR/owl-ref/#Boolean
owl:complementOf is an attribute of Class, however
Converts a unionOf / intersectionOf class expression into one that instead uses the given operator
>>> testGraph = Graph()
>>> Individual.factoryGraph = testGraph
>>> EX = Namespace("http://example.com/")
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', EX, override=False)
>>> testGraph.namespace_manager = namespace_manager
>>> fire = Class(EX.Fire)
>>> water = Class(EX.Water)
>>> testClass = BooleanClass(members=[fire,water])
>>> testClass
( ex:Fire and ex:Water )
>>> testClass.changeOperator(OWL_NS.unionOf)
>>> testClass
( ex:Fire or ex:Water )
>>> try: testClass.changeOperator(OWL_NS.unionOf)
... except Exception, e: print e
The new operator is already being used!
Create a copy of this class
>>> testGraph = Graph()
>>> Individual.factoryGraph = testGraph
>>> EX = Namespace("http://example.com/")
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', EX, override=False)
>>> testGraph.namespace_manager = namespace_manager
>>> fire = Class(EX.Fire)
>>> water = Class(EX.Water)
>>> testClass = BooleanClass(members=[fire,water])
>>> testClass2 = BooleanClass(operator=OWL_NS.unionOf,members=[fire,water])
>>> for c in BooleanClass.getIntersections():
... print c
( ex:Fire and ex:Water )
>>> for c in BooleanClass.getUnions():
... print c
( ex:Fire or ex:Water )
‘General form’ for classes:
The Manchester Syntax (supported in Protege) is used as the basis for the form of this class
See: http://owl-workshop.man.ac.uk/acceptedLong/submission_9.pdf:
- ‘Class:’ classID {Annotation
- ( (‘SubClassOf:’ ClassExpression) | (‘EquivalentTo’ ClassExpression) | (’DisjointWith’ ClassExpression)) }
Appropriate excerpts from OWL Reference:
- ”.. Subclass axioms provide us with partial definitions: they represent
- necessary but not sufficient conditions for establishing class membership of an individual.”
”.. A class axiom may contain (multiple) owl:equivalentClass statements”
”..A class axiom may also contain (multiple) owl:disjointWith statements..”
- ”..An owl:complementOf property links a class to precisely one class
- description.”
computed attributes that returns a generator over taxonomic ‘parents’ by disjunction, conjunction, and subsumption
>>> exNs = Namespace('http://example.com/')
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', exNs, override=False)
>>> namespace_manager.bind('owl', OWL_NS, override=False)
>>> g = Graph()
>>> g.namespace_manager = namespace_manager
>>> Individual.factoryGraph = g
>>> brother = Class(exNs.Brother)
>>> sister = Class(exNs.Sister)
>>> sibling = brother | sister
>>> sibling.identifier = exNs.Sibling
>>> sibling
( ex:Brother or ex:Sister )
>>> first(brother.parents)
Class: ex:Sibling EquivalentTo: ( ex:Brother or ex:Sister )
>>> parent = Class(exNs.Parent)
>>> male = Class(exNs.Male)
>>> father = parent & male
>>> father.identifier = exNs.Father
>>> list(father.parents)
[Class: ex:Parent , Class: ex:Male ]
Takes a graph and binds the common namespaces (rdf,rdfs, & owl)
Takes a Class instance and returns a generator over the classes that are involved in its definition, ignoring unamed classes
Recursively clear the given class, continuing where any related class is an anonymous class
>>> EX = Namespace('http://example.com/')
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', EX, override=False)
>>> namespace_manager.bind('owl', OWL_NS, override=False)
>>> g = Graph()
>>> g.namespace_manager = namespace_manager
>>> Individual.factoryGraph = g
>>> classB = Class(EX.B)
>>> classC = Class(EX.C)
>>> classD = Class(EX.D)
>>> classE = Class(EX.E)
>>> classF = Class(EX.F)
>>> anonClass = EX.someProp|some|classD
>>> classF += anonClass
>>> list(anonClass.subClassOf)
[Class: ex:F ]
>>> classA = classE | classF | anonClass
>>> classB += classA
>>> classA.equivalentClass = [Class()]
>>> classB.subClassOf = [EX.someProp|some|classC]
>>> classA
( ex:E or ex:F or ( ex:someProp some ex:D ) )
>>> DeepClassClear(classA)
>>> classA
( )
>>> list(anonClass.subClassOf)
[]
>>> classB
Class: ex:B SubClassOf: ( ex:someProp some ex:C )
>>> otherClass = classD | anonClass
>>> otherClass
( ex:D or ( ex:someProp some ex:D ) )
>>> DeepClassClear(otherClass)
>>> otherClass
( )
>>> otherClass.delete()
>>> list(g.triples((otherClass.identifier,None,None)))
[]
Class for owl:oneOf forms:
OWL Abstract Syntax is used
axiom ::= ‘EnumeratedClass(‘ classID [‘Deprecated’] { annotation } { individualID } ‘)’
>>> exNs = Namespace('http://example.com/')
>>> namespace_manager = NamespaceManager(Graph())
>>> namespace_manager.bind('ex', exNs, override=False)
>>> namespace_manager.bind('owl', OWL_NS, override=False)
>>> g = Graph()
>>> g.namespace_manager = namespace_manager
>>> Individual.factoryGraph = g
>>> ogbujiBros = EnumeratedClass(exNs.ogbujicBros,
... members=[exNs.chime,
... exNs.uche,
... exNs.ejike])
>>> ogbujiBros
{ ex:chime ex:uche ex:ejike }
>>> col = Collection(g,first(g.objects(predicate=OWL_NS.oneOf,subject=ogbujiBros.identifier)))
>>> [g.qname(item) for item in col]
[u'ex:chime', u'ex:uche', u'ex:ejike']
A typed individual
The owl ontology metadata
{ ‘super(‘ datavaluedPropertyID ‘)’} [‘Functional’] { ‘domain(‘ description ‘)’ } { ‘range(‘ dataRange ‘)’ } ‘)’
{ dataRestrictionComponent } ‘)’
>>> g1 = Graph()
>>> g2 = Graph()
>>> EX = Namespace("http://example.com/")
>>> namespace_manager = NamespaceManager(g1)
>>> namespace_manager.bind('ex', EX, override=False)
>>> namespace_manager = NamespaceManager(g2)
>>> namespace_manager.bind('ex', EX, override=False)
>>> Individual.factoryGraph = g1
>>> prop = Property(EX.someProp,baseType=OWL_NS.DatatypeProperty)
>>> restr1 = (Property(EX.someProp,baseType=OWL_NS.DatatypeProperty))|some|(Class(EX.Foo))
>>> restr1
( ex:someProp some ex:Foo )
>>> restr1.serialize(g2)
>>> Individual.factoryGraph = g2
>>> list(Property(EX.someProp,baseType=None).type)
[rdflib.URIRef('http://www.w3.org/2002/07/owl#DatatypeProperty')]
Core serialization