Thank you! That was what I needed and I have learned quite a bit. Here is the rule I ended up using:
@prefix ro: <http://example.com/ont/roster/>. > > [r1: (?x rdf:type ro:RosterEntry), (?x ro:hasSignature ?sig), regex(?sig, > '(.*) (.*)', ?first, ?last), uriConcat(' > http://example.com/ont/roster/people/', ?first, ?last, ?y) -> (?y > ro:hasFirstName ?first), (?y ro:hasLastName ?last), (?y rdf:type > ro:Person)] With that rule, the inferred model contains: http://example.com/ont/roster/people/BobSmith type Person . > http://example.com/ont/roster/people/BobSmith hasLastName "Smith" . > http://example.com/ont/roster/people/BobSmith hasFirstName "Bob" . > http://example.com/ont/roster/people/SallyJones type Person . > http://example.com/ont/roster/people/SallyJones hasLastName "Jones" . > http://example.com/ont/roster/people/SallyJones hasFirstName "Sally" . > http://example.com/ont/roster/ versionIRI . > http://example.com/ont/roster/ type Ontology . > http://example.com/ont/roster/RosterEntry type Class . > http://example.com/ont/roster/bobEntry hasSignature "Bob Smith" . > http://example.com/ont/roster/bobEntry type RosterEntry . > http://example.com/ont/roster/bobEntry type NamedIndividual . > http://example.com/ont/roster/Person type Class . > http://example.com/ont/roster/hasLastName range string . > http://example.com/ont/roster/hasLastName domain Person . > http://example.com/ont/roster/hasLastName type DatatypeProperty . > http://example.com/ont/roster/sallyEntry hasSignature "Sally Jones" . > http://example.com/ont/roster/sallyEntry type RosterEntry . > http://example.com/ont/roster/sallyEntry type NamedIndividual . > http://example.com/ont/roster/hasSignature range string . > http://example.com/ont/roster/hasSignature domain RosterEntry . > http://example.com/ont/roster/hasSignature type DatatypeProperty . > http://example.com/ont/roster/hasFirstName range string . > http://example.com/ont/roster/hasFirstName domain Person . > http://example.com/ont/roster/hasFirstName type DatatypeProperty . On Wed, May 27, 2020 at 2:14 AM Dave Reynolds <dave.e.reyno...@gmail.com> wrote: > On 27/05/2020 04:54, Kenneth Keefe wrote: > > Thank you! Can you suggest any texts that teach how to effectively write > > inference rules for Jena? > > I don't know of any though no doubt there's the odd blog post example. > > > However, I'm not having success changing the node URIs. When I change the > > rule to this: > > > > [r1: (?x rdf:type ro:RosterEntry), (?x ro:hasSignature ?sig), > regex(?sig, > >> '(.*) (.*)', ?first, ?last), makeSkolem(?y, ?x), uriConcat(' > >> http://example.com/ont/roster/people/', ?first, ?last, ?y) -> (?y > >> ro:hasFirstName ?first), (?y ro:hasLastName ?last), (?y rdf:type > >> ro:Person)] > > > > > > Jena doesn't seem to infer any new nodes. > Why have both makeSkolem and uriConcat? You either want to create a URI > resource as your subject or create a bNode. That's trying to make it > both, which can never happen so the rule never fires. > > > I tried separating it into another rule: > > > > [r1: (?x rdf:type ro:RosterEntry), (?x ro:hasSignature ?sig), > regex(?sig, > >> '(.*) (.*)', ?first, ?last), makeSkolem(?y, ?x) -> (?y ro:hasFirstName > >> ?first), (?y ro:hasLastName ?last), (?y rdf:type ro:Person)] > >> [r2: (?x rdf:type ro:Person), (?x ro:hasFirstName ?first), (?x > >> ro:hasLastName ?last), uriConcat('http://example.com/ont/roster/people/ > ', > >> ?first, ?last, ?y) -> print('woot', ?y)] > > > > > > What is strange is that with the second rule, the inference engine prints > > out: > > > > 'woot' <http://example.com/ont/roster/people/BobSmith> > > 'woot' <http://example.com/ont/roster/people/SallyJones> > > > > But, when I listStatements and print out the InfModel, > > > > I get the same node URIs: > > > > 1PXx9jabIZ6w9mfO0GygJA== type Person . > >> 1PXx9jabIZ6w9mfO0GygJA== hasLastName "Jones" . > >> 1PXx9jabIZ6w9mfO0GygJA== hasFirstName "Sally" . > >> lOqL7Rx2Lwv5OK6BWD/jSA== type Person . > >> lOqL7Rx2Lwv5OK6BWD/jSA== hasLastName "Smith" . > >> lOqL7Rx2Lwv5OK6BWD/jSA== hasFirstName "Bob" . > > > > > > So, does uriConcat not set the node's uri permanently? > > Ah, I think you might be imaging that uriConcat somehow rewrites the > node in an RDF graph so it has a name. No. Like all the rule builtins > all it does is bind a value to a variable or, if the variable already > has a value, then test it matches. > > So just should just need something like (untested): > > [r1: (?x rdf:type ro:RosterEntry), > (?x ro:hasSignature ?sig), > regex(?sig, '(.*) (.*)', ?first, ?last), > uriConcat('http://example.com/ont/roster/people/', ?first, ?last, > ?y) > -> (?y ro:hasFirstName ?first), > (?y ro:hasLastName ?last), > (?y rdf:type ro:Person)] > > Dave > > > On Sun, May 24, 2020 at 6:03 AM Dave Reynolds <dave.e.reyno...@gmail.com > > > > wrote: > > > >> Sorry, only just noticed this question. Responses below: > >> > >> On 20/05/2020 10:41, Kenneth Keefe wrote: > >>> Thanks for all the help so far! I've made some good progress on this > >>> example I'm trying to forge. Here are the files for this example: > >>> > >>> Ontology: https://cioi.iti.illinois.edu/ont/examples/roster.owl > >>> Rules: https://cioi.iti.illinois.edu/ont/examples/roster.rules > >>> > >>> Using this code: > >>> > >>> OntModel model = > >> ModelFactory.*createOntologyModel*(OntModelSpec.*OWL_MEM*); > >>> > >>> model.read("file:roster.owl"); > >>> > >>> List<Rule> rules = Rule.*rulesFromURL*("file:roster.rules"); > >>> > >>> Reasoner reasoner = new GenericRuleReasoner(rules); > >>> > >>> InfModel inf = ModelFactory.*createInfModel*(reasoner, model); > >>> > >>> > >>> printAllStatements(inf); > >>> > >>> I get this output (I use the LocalName for the predicates and objects > for > >>> readability): > >>> > >>> b04230c7-55dc-4e08-92d7-26559c0c478f hasLastName "Smith" . > >>> c33de24f-82f1-4aa2-a78e-73201341b274 type Person . > >>> c812ff54-30bf-49e7-8e35-3544dad096b7 hasFirstName "Sally" . > >>> 16b7a887-2fb9-4bdb-a529-34ca8b3137e4 hasFirstName "Bob" . > >>> b31eb7d5-a8ad-469e-ae83-366bdf46fd72 hasLastName "Jones" . > >>> 8b46f0c7-4eb2-41e2-8789-8967e8f63eec type Person . > >>> http://example.com/ont/roster/ versionIRI . > >>> http://example.com/ont/roster/ type Ontology . > >>> http://example.com/ont/roster/RosterEntry type Class . > >>> http://example.com/ont/roster/bobEntry hasSignature "Bob Smith" . > >>> http://example.com/ont/roster/bobEntry type RosterEntry . > >>> http://example.com/ont/roster/bobEntry type NamedIndividual . > >>> http://example.com/ont/roster/Person type Class . > >>> http://example.com/ont/roster/hasLastName range string . > >>> http://example.com/ont/roster/hasLastName domain Person . > >>> http://example.com/ont/roster/hasLastName type DatatypeProperty . > >>> http://example.com/ont/roster/sallyEntry hasSignature "Sally Jones" . > >>> http://example.com/ont/roster/sallyEntry type RosterEntry . > >>> http://example.com/ont/roster/sallyEntry type NamedIndividual . > >>> http://example.com/ont/roster/hasSignature range string . > >>> http://example.com/ont/roster/hasSignature domain RosterEntry . > >>> http://example.com/ont/roster/hasSignature type DatatypeProperty . > >>> http://example.com/ont/roster/hasFirstName range string . > >>> http://example.com/ont/roster/hasFirstName domain Person . > >>> http://example.com/ont/roster/hasFirstName type DatatypeProperty . > >>> > >>> Here are my questions: > >>> > >>> Focusing on just the Bob Smith entry: > >>> > >>> b04230c7-55dc-4e08-92d7-26559c0c478f hasLastName "Smith" . > >>> c33de24f-82f1-4aa2-a78e-73201341b274 type Person . > >>> 16b7a887-2fb9-4bdb-a529-34ca8b3137e4 hasFirstName "Bob" . > >>> > >>> 1. Are these unique ids of anonymous nodes? > >> > >> Yes. > >> > >>> 2. Why are they not identical across these three lines? > >> > >> Because your ?y variable hasn't been bound in the rule body, each triple > >> pattern finds there's no value for ?y and separately treats it as a > bNode. > >> > >> If you want a shared bNode then use makeTemp(?y) in the body (or > >> makeSoklem). > >> > >>> 3. Is there a way to name these new nodes in the rule? For example, > make > >>> the new node http://example.com/ont/roster/people/BobSmith. > >> > >> Sure, use uriConcat, something like (untested): > >> > >> uriConcat('http://example.com/ont/roster/people/', ?first, ?last, > >> ?y) -> ... > >> > >> Dave > >> > >>> > >>> Thank you! > >>> > >>> Ken > >>> > >>> > >>> > >>> > >>> > >>> On Tue, May 12, 2020 at 1:44 AM Lorenz Buehmann < > >>> buehm...@informatik.uni-leipzig.de> wrote: > >>> > >>>> Hi, > >>>> > >>>> I think the rule would be basically > >>>> > >>>> [r1: (?x rdf:type ex:RosterEntry), (?x ex:hasSignature ?sig), > >>>> regex(?sig, '(.*) (.*)', ?first, ?last) -> (?x ex:hasFirstName > >>>> ?first), (?x ex:hasLastName ?last), (?x rdf:type ex:Person) ) ] > >>>> > >>>> Note, it's untested and you have to define your prefix ex: in the > rules > >>>> file. You might also have to adapt the regex pattern to cover > different > >>>> white space chars. > >>>> > >>>> On 12.05.20 00:56, Kenneth Keefe wrote: > >>>>> I am pretty new to using Jena and OWL. I have read many great > tutorials > >>>>> regarding RDF and OWL. The focus of those tutorials has largely been > >> how > >>>> to > >>>>> structure the ontology and define restrictions on properties and > such. > >>>>> However, I have not been able to find good tutorials that explain how > >>>>> inference is done and how I can define my own inference rules. I'm > >>>>> wondering if I am simply not searching for the right thing. > >>>>> > >>>>> Regardless, here is a significant example that I think will really > help > >>>> me > >>>>> get started with inference using Jena. I created a minimal example to > >>>>> enable discussion. Here is a pastebin: > https://pastebin.com/ScTGcbcZ > >>>>> > >>>>> The ontology has two classes, RosterEntry and Person and three data > >>>>> properties, Signature (associated with RosterEntry), and FirstName > and > >>>>> LastName (both associated with Person). The example also has two > >>>>> RosterEntry individuals with signatures of "Bob Smith" and "Sally > >> Jones." > >>>>> > >>>>> I would like to write a rule that causes Jena to infer the following > >> new > >>>>> facts: > >>>>> > >>>>> <owl:Individual> > >>>>>> <rdf:type > >>>>>> rdf:resource="http://example.com/ont/roster/Person" > /> > >>>>>> <hasFirstName>Bob</hasFirstName> > >>>>> <hasLastName>Smith</hasLastName> > >>>>> > >>>>> </owl:Individual> > >>>>> > >>>>> > >>>>> <owl:Individual> > >>>>>> <rdf:type > >>>>>> rdf:resource="http://example.com/ont/roster/Person" > /> > >>>>>> <hasFirstName>Sally</hasFirstName> > >>>>> <hasLastName>Jones</hasLastName> > >>>>> > >>>>> </owl:Individual> > >>>>> > >>>>> > >>>>> How do I do that? Full answers or nudges in the right direction are > >> both > >>>>> very welcome. Thank you! > >>>>> > >>>>> Ken > >>>>> > >>>> > >>>> > >>> > >> > > > > > -- ------- Ken Keefe Senior Software Engineer Information Trust Institute University of Illinois at Urbana-Champaign 1308 W. Main St. CSL 225 Urbana, Illinois 61801, USA Phone: 217-244-3203 Web: https://www.perform.illinois.edu/~kjkeefe Email: kjke...@illinois.edu