SemMF - A Semantic Matching Framework

How to Create a Matching Description

Radoslaw Oldakowski
November 2006


A matching description contains all informatoin about the matching process (e.g. location of query and resource RDF Graphs, selection of nodes within these graphs, associated matchers, assinged weights, etc.) needed to set up the matching engine. It is represented in RDF using SemMF vocabulary.

This tutorial will demonstrate how to create a matching description for a simple use case scenario. Our example will be about job matching. Given a job position posting (query object) we want to find the best matching job applicant among three candidates (resource objects). A fragment of an RDF description of our simple job posing is shown below

 
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:jpp="http://example.org/JobPositionPosting.rdfs#"
    xmlns:skills="http://example.org/it-skills.rdfs#"
    xml:base="http://example.org/JobPositionPosting.rdfs#">

  <jpp:JobPositionPosting rdf:ID="JPP_1">

    <jpp:hasPostDetails>
      [...]
    </jpp:hasPostDetails>

    <jpp:hasHiringOrganisation>
      [...]   
    </jpp:hasHiringOrganisation>

    <jpp:hasJobPositionLocation>
      [...]
    </jpp:hasJobPositionLocation>

    <jpp:hasJobDetails>
      <jpp:JobDetails>

        <jpp:travelRequired>false</jpp:travelRequired>
        <jpp:hasSalaryBase>40000</jpp:hasSalaryBase>

        <jpp:requiredCompetence>
          <skills:Java>
            <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Advanced"/>
          </skills:Java>
        </jpp:requiredCompetence>

        <jpp:requiredCompetence>
          <skills:JavaScript>
            <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Expert"/>
          </skills:JavaScript>
        </jpp:requiredCompetence>

        <jpp:requiredCompetence>
          <skills:Relational_databases>
            <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Intermediate"/>
          </skills:Relational_databases>
        </jpp:requiredCompetence>

      </jpp:JobDetails>
    </jpp:hasJobDetails>

  </jpp:JobPositionPosting>
</rdf:RDF>

Note that, on the one hand this RDF graph contains a lot of information (post details, description of the hiring organization, and location information) which we don't need for similarity computation between this open position and an applicant's profile. On the other hand there is information which is crucial for our matching. Thus, in our matching description we will have to explicitly specify the latter. In this example there are three properties: salary information, requirement of travel, as well as required competences upon which we want to choose the best candidate.

Now we will have a look at a fragment of an RDF description of our job applicants

<rdf:RDF
    xmlns:skills="http://example.org/it-skills.rdfs#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:jps="http://example.org/JobPositionSeeker.rdfs#"
    xml:base="http://example.org/JobPositionSeeker.rdfs#">

  <jps:JobPositionSeeker rdf:ID="JPS_1">

    <jps:hasPostDetails>
      [...]
    </jps:hasPostDetails>

    <jps:hasPostingPerson>
      [...]
    </jps:hasPostingPerson>

    <jps:desiredSalaryBase>41000</jps:desiredSalaryBase>
    <jps:travelDesired>false</jps:travelDesired>

    <jps:hasSkill>
      <skills:PHP>
        <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Intermediate"/>
      </skills:PHP>
    </jps:hasSkill>

    <jps:hasSkill>
      <skills:Oracle>
        <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Advanced"/>
      </skills:Oracle>
    </jps:hasSkill>

    <jps:hasSkill>
      <skills:Java>
        <skills:competenceLevel rdf:resource="http://example.org/it-skills.rdfs#Expert"/>
      </skills:Java>
    </jps:hasSkill>

  </jps:JobPositionSeeker>

  <jps:JobPositionSeeker rdf:ID="JPS_2">
    [...]
  </jps:JobPositionSeeker>

  <jps:JobPositionSeeker rdf:ID="JPS_3">
    [...]
  </jps:JobPositionSeeker>

</rdf:RDF>

The resources representing job applicants also contain both information irrelevant for similarity computation as well as the properties (i.e. salary, travel desired, and skills) upon which we want to match.

Note that, SemMF does not require both graphs to use the same vocabulary, like in this example where we have terms from http://example.org/JobPositionPosting.rdfs# and http://example.org/JobPositionSeeker.rdfs#. However, the values of the coresponding properties to be matched must have the same range, like in this example where terms from a common skills taxonomy (http://example.org/it-skills.rdfs#) are used to describe both skills required and those of a candidate.

As meantioned before, a SemMF matching description is represented in RDF. In this tutorial we will be using Jena to construct an RDF graph of a matching description for our use case. So, first we include some Jena classes for manipulating RDF models as well as classes holding objects representing terms from RDF and SemMF vocabularies.

import com.hp.hpl.jena.rdf.model.Bag;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;

import com.hp.hpl.jena.vocabulary.RDF;
import de.fuberlin.wiwiss.semmf.vocabulary.MD;

We start with generating an empty Jena Model and putting inside a new Resource gmd representing our matching description. As next we use objects form the SemMF vocabulary class to describe it

Model m = ModelFactory.createDefaultModel();
		
Resource gmd = m.createResource();
gmd.addProperty(RDF.type, MD.GraphMatchingDescription);
gmd.addProperty(MD.queryModelURL, "file:./doc/examples/jpp.rdf");
gmd.addProperty(MD.resModelURL, "file:./doc/examples/jps.rdf");
gmd.addProperty(MD.queryGraphURI, "http://example.org/JobPositionPosting.rdfs#JPP_1");
gmd.addProperty(MD.resGraphURIpath, "(?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/JobPositionSeeker.rdfs#JobPositionSeeker>)");

A Resource representing a matching description MUST be of type semmf:GraphMatchingDescription and MUST have four properties:

In the next step we want to group the properteis to be matched into two 'clusters'. The first one (cmd_1) should hold salary and travel information the second one (cmd_2) description of skills. The rationale behind property grouping is that it will later allow us to sort the matching output not only by general object similary but also by each single cluster similarity, which in some scenarios might be usefull.

Bag cmds = m.createBag();
gmd.addProperty(MD.hasClusterMatchingDescriptions, cmds);	
Resource cmd_1 = m.createResource(); cmd_1.addProperty(RDF.type, MD.ClusterMatchingDescription); cmd_1.addProperty(MD.label, "job details"); cmd_1.addProperty(MD.weight, "0.25"); cmds.add(cmd_1); Resource cmd_2 = m.createResource(); cmd_2.addProperty(RDF.type, MD.ClusterMatchingDescription); cmd_2.addProperty(MD.label, "skills"); cmd_2.addProperty(MD.weight, "0.75"); cmds.add(cmd_2);

As you can see, the Resource representing a matching description (gmd) has now another property semmf:hasClusterMatchingDescriptions with its value being an rdf:Bag holding a collection of resources of type semmf:ClusterMatchingDescription. Each cluster Resource gets a label (smmf:label) and weight (semmf:weight) indicating its influence on the overall similarity score. The sum of all cluster weights MUST equal 1!

Since the values of our object properties (salary, travel, skills) are RDF Nodes, we now have to describe how these nodes are accessed and what matching techniques shall be used. We begin with travel information and put it inside the first cluster

Bag nmds_c1 = m.createBag();
cmd_1.addProperty(MD.hasNodeMatchingDescriptions, nmds_c1);		
		
Resource c1_nmd_1 = m.createResource();
nmds_c1.add(c1_nmd_1);
c1_nmd_1.addProperty(RDF.type, MD.NodeMatchingDescription);
c1_nmd_1.addProperty(MD.label, "travel required");
c1_nmd_1.addProperty(MD.weight, "0.3");
c1_nmd_1.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#travelRequired> ?x)");
c1_nmd_1.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#travelDesired> ?x)");
c1_nmd_1.addProperty(MD.reverseMatching, "false");

The resource representing our first cluster (cmd_1) gets a new property semmf:hasNodeMatchingDescriptions pointing to an rdf:Bag holding a collection of semmf:NodeMatchingDescriptions. For each NodeMatchingDescription we provide the following information:

Additionally, using the property semmf:useMatcher from the SemMF vocabulary we specify which matcher shall be used for similarity computation. Since the possible values of the travel property from our example are "true" or "false" we take the build-in simple StringMatcher

Resource sm = m.createResource();
sm.addProperty(RDF.type, MD.StringMatcher);
sm.addProperty(MD.caseSensitive, "false");
c1_nmd_1.addProperty(MD.useMatcher, sm);

The Resource describing our matcher is of type semmf:StringMatcher and contains one parameter semmf:caseSensitive indicating case insensitive matching, which is the default setting for this matcher and therefore can be left out.

Now, we are done with providing matching directives for the first object property (travel). As next, we do the same for salary information and also put it inside the firist cluster into an rdf:Bag (nmds_c1) with NodeMatchingDescriptions

Resource c1_nmd_2 = m.createResource();
nmds_c1.add(c1_nmd_2);
c1_nmd_2.addProperty(RDF.type, MD.NodeMatchingDescription);
c1_nmd_2.addProperty(MD.label, "salary");
c1_nmd_2.addProperty(MD.weight, "0.7");
c1_nmd_2.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#hasSalaryBase> ?x)");
c1_nmd_2.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#desiredSalaryBase> ?x)");
c1_nmd_2.addProperty(MD.reverseMatching, "false");
		
Resource nm = m.createResource();
nm.addProperty(RDF.type, MD.NumericMatcher);
nm.addProperty(MD.decreaseSim, "upwards");
nm.addProperty(MD.maxDevFraction, "0.25");
c1_nmd_2.addProperty(MD.useMatcher, nm);

This time, however, we take the integrated NumericMatcher to calculate the similarity of salay information. In RDF this matcher is represented by a resource of type semmf:NumericMatcher having two parameters semmf:decreaseSim and semmf:maxDevFraction. The former indicates that with increasing property values from a resource object (desired salary base) beeing greater than ("upwards") the property value of the query object (= 40.000 - see RDF fragment of our job posting) the similarity should desrease until it reaches 0 at the upper bound ( 40.000 + 40.000*0.25 (semmf:maxDevFraction) = 50.000). In our use case example this would mean, that the hiring organization offering 40.000 for the job, would consider every candidate wanting less than 40k as perfekt match (sim=1), and would be ready to pay maximum up to 50.000, beyond which sim=0. For all other candidates which desired salary base lays in the interval [40k,50k] the similarity score would be between 0 and 1.

As meantioned before we want to put the third object property (skills) into a separate cluster. Because there are no other properties inside this cluster the associated weight MUST equal 1.

Bag nmds_c2 = m.createBag();
cmd_2.addProperty(MD.hasNodeMatchingDescriptions, nmds_c2);
		
Resource c2_nmd_1 = m.createResource();
nmds_c2.add(c2_nmd_1);
c2_nmd_1.addProperty(RDF.type, MD.NodeMatchingDescription);
c2_nmd_1.addProperty(MD.label, "skill node");
c2_nmd_1.addProperty(MD.weight, "1");
c2_nmd_1.addProperty(MD.queryNodePath, "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#requiredCompetence> ?x)");
c2_nmd_1.addProperty(MD.resNodePath, "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#hasSkill> ?x)");
c2_nmd_1.addProperty(MD.reverseMatching, "false");

Note that, in the above specification the path expressions provided (semmf:queryNodePath and semmf:queryNodePath) both lead to an RDF BlankNode representing skill information. Each of them has two RDF properties describing type of skill (instance from our simple skill taxonomy) as well as competence level. Our similarity score for skills should therefore be computed upon both. Becasue the value of the object property 'skills' is not atomic, we do not assign it any matcher yet. Instead, we have to deliver further matching derectives regarding the RDF properties hanging from those blank nodes. We do it by first adding a new property semmf:hasPropertyMatchingDescriptions to our previously created NodeMatchingDescription (c2_nmd_1). The value of this property is an rdf:Bag for holding a collection of semmf:PropertyMatchingDescriptions. The first resource in this Bag describes a matching specificatuion for the porpoerty 'skill type' (see code snippet below), the second one for the property 'competence level'.

Bag pmds = m.createBag();
c2_nmd_1.addProperty(MD.hasPropertyMatchingDescriptions, pmds);
		
Resource pmd_1 = m.createResource();
pmds.add(pmd_1);
pmd_1.addProperty(RDF.type, MD.PropertyMatchingDescription);
pmd_1.addProperty(MD.label, "skill");
pmd_1.addProperty(MD.weight, "0.8");
pmd_1.addProperty(MD.queryPropURI, RDF.type);
pmd_1.addProperty(MD.resPropURI, RDF.type);
pmd_1.addProperty(MD.reverseMatching, "false");

For each resource representing a semmf:PropertyMatchingDescription we provide the following information:

Since the values of the RDF.type property hanging from our blank nodes are concepts from a skill taxonomy, we take the TaxonomicMatcher, which computes the similarity of two concepts based on their respective position in the unterlying taxonomy instead of merely relying on an exact match of keywords like, for instance, the StringMatcher does.

Resource tm_1 = m.createResource();
tm_1.addProperty(RDF.type, MD.TaxonomicMatcher);
tm_1.addProperty(MD.simInheritance, "true");
pmd_1.addProperty(MD.useMatcher, tm_1);
		
Resource taxon_skills = m.createResource();
taxon_skills.addProperty(RDF.type, MD.Taxonomy);
taxon_skills.addProperty(MD.taxonomyURL, "file:./doc/examples/it-skills.rdfs");
taxon_skills.addProperty(MD.rootConceptURI, "http://example.org/it-skills.rdfs#IT_Skills");
tm_1.addProperty(MD.taxonomy, taxon_skills);
		
Resource emc = m.createResource();
emc.addProperty(RDF.type, MD.ExpMilestCalc);
emc.addProperty(MD.k_factor, "2");
tm_1.addProperty(MD.useMilestoneCalc, emc);		

In RDF this kind of matcher is represented by a resource of type semmf:TaxonomicMatcher having three parameters:

In the same way as demonstrated above we provide matching information for the second property ('competence level') hanging from the blank node representing skill information

Resource pmd_2 = m.createResource();
pmds.add(pmd_2);
pmd_2.addProperty(RDF.type, MD.PropertyMatchingDescription);
pmd_2.addProperty(MD.label, "skill level");
pmd_2.addProperty(MD.weight, "0.2");
pmd_2.addProperty(MD.queryPropURI, "http://example.org/it-skills.rdfs#competenceLevel");
pmd_2.addProperty(MD.resPropURI, "http://example.org/it-skills.rdfs#competenceLevel");
pmd_2.addProperty(MD.reverseMatching, "false");

Since the range of this property are terms from a simple taxonomy of skill levels

<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xml:base="http://example.org/it-skills.rdfs#">
	
      <rdfs:Property rdf:ID="competenceLevel">
        <rdfs:range rdf:resource="#Skill_level"/>
      </rdfs:Property>

      <rdfs:Class rdf:about="#Beginner">
        <rdfs:subClassOf rdf:resource="#Skill_level"/>
      </rdfs:Class>
      <rdfs:Class rdf:about="#Intermediate">
        <rdfs:subClassOf rdf:resource="#Beginner"/>
      </rdfs:Class>
      <rdfs:Class rdf:about="#Advanced">
        <rdfs:subClassOf rdf:resource="#Intermediate"/>
      </rdfs:Class>
      <rdfs:Class rdf:about="#Expert">
        <rdfs:subClassOf rdf:resource="#Advanced"/>
      </rdfs:Class>
</rdf:RDF>

we specify another instance of TaxonomicMatcher by providing description of the taxonomy and milestone calculator to be used

Resource tm_2 = m.createResource();
tm_2.addProperty(RDF.type, MD.TaxonomicMatcher);
pmd_2.addProperty(MD.useMatcher, tm_2);
tm_2.addProperty(MD.simInheritance, "true");
		
Resource taxon_skillLevel = m.createResource();
taxon_skillLevel.addProperty(RDF.type, MD.Taxonomy);
taxon_skillLevel.addProperty(MD.taxonomyURL, "file:./doc/examples/it-skills.rdfs");
taxon_skillLevel.addProperty(MD.rootConceptURI, "http://example.org/it-skills.rdfs#Skill_level");
tm_2.addProperty(MD.taxonomy, taxon_skillLevel);
		
Resource lmc = m.createResource();
lmc.addProperty(RDF.type, MD.LinMilestCalc);
tm_2.addProperty(MD.useMilestoneCalc, lmc);

This time, hovever, we take the linear milestone calculator semmf:LinMilestCalc (See TaxonomicMatcher for further details).

Our matching description is finished now. Finally, we write the RDF model just created to file

m.setNsPrefix("semmf", MD.NS);
m.setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
m.setNsPrefix("ja", "http://jena.hpl.hp.com/2005/11/Assembler#");
		
  try {
    File outputFile = new File("file:doc/examples/jobMD.n3");
    if (!outputFile.exists()) {
      outputFile.createNewFile();        	 
    }
			
    FileOutputStream out = new FileOutputStream(outputFile);
    m.write(out, "N3");
    out.close();
  }
  catch (IOException e) { System.out.println(e.toString()); }

Here you can see the RDF/XML serialization of our matching description. The output in N3 should look something like this:

@prefix semmf:      <http://semmf.ag-nbi.de/vocabulary/1.1/semmf.rdfs#> .
@prefix ja:      <http://jena.hpl.hp.com/2005/11/Assembler#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix :        <#> .

[]    a       semmf:GraphMatchingDescription ;
      semmf:queryGraphURI "http://example.org/JobPositionPosting.rdfs#JPP_1" ;
      semmf:queryModelURL "file:./doc/examples/jpp.rdf" ;
      semmf:resGraphURIpath "(?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/JobPositionSeeker.rdfs#JobPositionSeeker>)" ;
      semmf:resModelURL "file:./doc/examples/jps.rdf" ;
      semmf:hasClusterMatchingDescriptions
              [ a       rdf:Bag ;
                rdf:_1  [ a       semmf:ClusterMatchingDescription ;
                          semmf:label "job details" ;
                          semmf:weight "0.25" ;
                          semmf:hasNodeMatchingDescriptions
                                  [ a       rdf:Bag ;
                                    rdf:_1  [ a       semmf:NodeMatchingDescription ;
                                              semmf:label "travel desired" ;
                                              semmf:weight "0.3" ;
                                              semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#travelDesired> ?x)" ;
                                              semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#travelDesired> ?x)" ;
                                              semmf:reverseMatching "false" ;
                                              semmf:useMatcher
                                                      [ a       semmf:StringMatcher ;
                                                        semmf:caseSensitive "false"
                                                      ] 
                                            ] ;
                                    rdf:_2  [ a       semmf:NodeMatchingDescription ;
                                              semmf:label "salary" ;
                                              semmf:weight "0.7" ;
                                              semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#hasSalaryBase> ?x)" ;
                                              semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#desiredSalaryBase> ?x)" ;
                                              semmf:reverseMatching "false" ;
                                              semmf:useMatcher
                                                      [ a       semmf:NumericMatcher ;
                                                        semmf:decreaseSim "upwards" ;
                                                        semmf:maxDevFraction "0.25"
                                                      ] 
                                            ]
                                  ] 
                        ] ;
                rdf:_2  [ a       semmf:ClusterMatchingDescription ;
                          semmf:label "skills" ;
                          semmf:weight "0.75" ;
                          semmf:hasNodeMatchingDescriptions
                                  [ a       rdf:Bag ;
                                    rdf:_1  [ a       semmf:NodeMatchingDescription ;
                                              semmf:label "skill node" ;
                                              semmf:queryNodePath "(<http://example.org/JobPositionPosting.rdfs#JPP_1> <http://example.org/JobPositionPosting.rdfs#hasJobDetails> ?jobDetails) (?jobDetails <http://example.org/JobPositionPosting.rdfs#requiredCompetence> ?x)" ;
                                              semmf:resNodePath "(<#graphEntryURI#> <http://example.org/JobPositionSeeker.rdfs#hasSkill> ?x)" ;
                                              semmf:reverseMatching "false" ;
                                              semmf:weight "1" ;
                                              semmf:hasPropertyMatchingDescriptions
                                                      [ a       rdf:Bag ;
                                                        rdf:_1  [ a       semmf:PropertyMatchingDescription ;
                                                                  semmf:label "skill" ;
                                                                  semmf:weight "0.8" ;
                                                                  semmf:queryPropURI rdf:type ;
                                                                  semmf:resPropURI rdf:type ;
                                                                  semmf:reverseMatching "false" ;
                                                                  semmf:useMatcher
                                                                          [ a       semmf:TaxonomicMatcher ;
                                                                            semmf:taxonomy
                                                                                    [ a       semmf:Taxonomy ;
                                                                                      semmf:rootConceptURI "http://example.org/it-skills.rdfs#IT_Skills" ;
                                                                                      semmf:taxonomyURL "file:./doc/examples/it-skills.rdfs"
                                                                                    ] ;
                                                                            semmf:milestoneCalc
                                                                                    [ a       semmf:ExpMilestCalc ;
                                                                                      semmf:k_factor "2"
                                                                                    ] ;
                                                                            semmf:simInheritance "true" 
                                                                          ] 
                                                                ] ;
                                                        rdf:_2  [ a       semmf:PropertyMatchingDescription ;
                                                                  semmf:label "skill level" ;
                                                                  semmf:weight "0.2" ;
                                                                  semmf:queryPropURI "http://example.org/it-skills.rdfs#competenceLevel" ;
                                                                  semmf:resPropURI "http://example.org/it-skills.rdfs#competenceLevel" ;
                                                                  semmf:reverseMatching "false" ;
                                                                  semmf:useMatcher
                                                                          [ a       semmf:TaxonomicMatcher ;
                                                                            semmf:taxonomy
                                                                                    [ a       semmf:Taxonomy ;
                                                                                      semmf:rootConceptURI "http://example.org/it-skills.rdfs#Skill_level" ;
                                                                                      semmf:taxonomyURL "file:./doc/examples/it-skills.rdfs"
                                                                                    ] ;
                                                                            semmf:milestoneCalc
                                                                                    [ a       semmf:LinMilestCalc
                                                                                    ] ;
                                                                            semmf:simInheritance "true" 
                                                                          ] 
                                                                ]
                                                      ] 
                                            ]
                                  ] 
                        ]
              ] .

All example files from this tutorial are included in SemMF distrubution package (SemMF\doc\examples). The full working java code for this tutorial is found in de.fuberlin.wiwiss.semmf.examples.JobMatching.java.



see next: Use Case Example: Traversing the Matching Result