OWL
The owl generator writes an OWL 2 ontology for a LinkML schema. The OWL importer does the reverse, and the two should preserve most axioms of an OWL ontology.
OWL can express things LinkML cannot (property chains, defined classes, individuals), and LinkML can express things OWL cannot (tree_root, inlined, identifiers). Those are lost on the way.
The generator aims for OWL 2 DL. SAREF, SOSA, gist, PROV-O and D3FEND, after a trip through LinkML, pass ROBOT’s DL profile check (robot validate-profile --profile DL, run by hand).
ohi get w3c/prov-oohi linkml to owl --only-root-schema \ --to prov-o.owl.ttl prov-o.linkml.yamlimport ohi_linkml
with ohi_linkml.load_file("prov-o.linkml.yaml") as schema: output = schema.owl(only_root_schema=True)import { readFileSync } from "node:fs";import { LinkML } from "@neverblink/linkml";
const yaml = readFileSync("prov-o.linkml.yaml", "utf8");const { view, report } = LinkML.loadFromString(yaml, {});if (!view) throw new Error(JSON.stringify(report));const output = view.owl({ only_root_schema: true });import eu.neverblink.linkml.generator.options.OwlGeneratorOptionsimport eu.neverblink.linkml.generator.owl.OwlGeneratorimport eu.neverblink.linkml.schemaview.{SchemaIssues, SchemaView}
val loaded = SchemaView.loadSchemaViewFromUri("prov-o.linkml.yaml")given SchemaView = SchemaIssues.orThrow(loaded)val output = OwlGenerator().serialize( OwlGeneratorOptions(onlyRootSchema = true),)#include <stddef.h>#include "ohi_linkml.h"
linkml_init_threads();char *report = NULL, *error = NULL;long long schema = linkml_load_file("prov-o.linkml.yaml", NULL, &report, &error);char *output = linkml_owl( schema, "{\"only_root_schema\": true}", &error);linkml_free(output);linkml_free(report);linkml_free(error);linkml_close(schema);Options
Section titled “Options”formatRdfFormatdefault ttl--formatThe RDF serialization of the output.
ttlTurtle, with prefixes and pretty-printed.ntN-Triples, one statement per line.
metadata_profileOwlMetadataProfiledefault rdfs--metadata-profileScala metadataProfileThe annotation property for descriptions.
rdfsrdfs:comment, shown by most OWL tools.linkmlskos:definition, as in the LinkML metamodel andgen-owl.
only_root_schemabooleandefault false--only-root-schemaScala onlyRootSchemaWhether to describe only the root schema, with an owl:imports for each of its imports. Without it, the output includes the imported schemas, like gen-owl --mergeimports.
permissible_valuesOwlPermissibleValueKinddefault individual--permissible-valuesScala permissibleValuesThe kind of OWL entity for the permissible values of an enum.
individualNamed individuals of the enum's class.classSubclasses of the enum's class, as ingen-owl.
Mapping LinkML to OWL
Section titled “Mapping LinkML to OWL”| LinkML | OWL |
|---|---|
| schema | owl:Ontology with the schema id as its IRI |
prefixes, default_prefix |
sh:declare on the ontology, and vann:preferredNamespacePrefix / vann:preferredNamespaceUri |
| class | owl:Class |
is_a, mixins |
rdfs:subClassOf the parents |
a slot of the class (in slots, attributes, slot_usage) |
restrictions on the class: owl:allValuesFrom the range, cardinalities, owl:hasValue, owl:someValuesFrom |
required, minimum_cardinality, maximum_cardinality |
owl:minCardinality, owl:maxCardinality, or owl:cardinality when the two are equal. On a transitive slot, its parents and inverses, which OWL 2 DL allows no counts on, required is owl:someValuesFrom owl:Thing and the counts are skipped |
| a single-valued slot | owl:maxCardinality 1, and owl:FunctionalProperty when it is single-valued everywhere it is used. A langstring slot gets owl:maxQualifiedCardinality 1 on xsd:string instead, as a text in several languages is one literal per language |
equals_string, equals_number |
owl:hasValue |
has_member, and more has_member under all_of |
owl:someValuesFrom, one each |
abstract with two or more is_a children |
rdfs:subClassOf the union of the children |
disjoint_with, children_are_mutually_disjoint |
owl:disjointWith, owl:AllDisjointClasses |
unique_keys |
owl:hasKey |
exact_mappings to a class or slot of the same schema |
owl:equivalentClass, owl:equivalentProperty |
| slot | owl:ObjectProperty, or owl:DatatypeProperty when every use of it has a type as its range |
implements: [owl:DatatypeProperty] and friends |
the property kind asked for, as in gen-owl |
range, any_of |
rdfs:range, a union for an any_of with two or more members |
domain, domain_of |
rdfs:domain, a union for domain_of |
is_a, mixins on a slot |
rdfs:subPropertyOf |
inverse |
owl:inverseOf |
transitive, symmetric and the other characteristics |
owl:TransitiveProperty and so on |
| enum | owl:Class equivalent to owl:oneOf its permissible values |
| permissible value | owl:NamedIndividual with the meaning as its IRI, and skos:notation for its text when that is not the IRI’s local name |
| type | its uri; with constraints, a datatype restriction on it (xsd:integer[>= 0]) |
pattern, minimum_value, maximum_value, minimum_length, maximum_length |
xsd:pattern, xsd:minInclusive, xsd:maxInclusive, xsd:minLength, xsd:maxLength. An XSD pattern matches the whole value, so ^abc$ is written abc, and abc is written .*abc.* |
title |
rdfs:label, tagged with in_language when the schema has one |
description |
rdfs:comment (or skos:definition with --metadata-profile linkml) |
other documentation (aliases, comments, see_also, mappings, …) |
the property the LinkML metamodel gives it, such as skos:altLabel, skos:note, rdfs:seeAlso |
annotations |
annotations with the tag as the property. A value is an IRI if it is a full IRI, with :// or a scheme such as urn, mailto, did, info or tag, else text, even if it looks like a CURIE |
deprecated |
owl:deprecated true |
The LinkML-to-OWL generator declares every annotation property that is used, which OWL 2 DL requires. The same goes for every datatype that is not in OWL 2’s datatype map, such as xsd:date. RDF’s, RDFS’s, OWL’s and XSD’s own terms are never declared.
Differences from Python’s gen-owl
Section titled “Differences from Python’s gen-owl”These differences are in place mostly to make the OWL describe the same RDF data as the RDFS and SHACL generators, which we think is a more consistent approach.
- IRIs are the ones the data uses:
class_uri,slot_uri,enum_uriand the permissible valuemeaning, as in this project’s RDF, RDFS and SHACL.gen-owluses the schema’s own namespace by default (--use-native-uris). - Identifier slots are not properties. In RDF, an identifier is the IRI of the node.
- Slots with a
urioruriorcurierange, or with animplicit_prefix, are object properties without a range, since their values are IRIs in RDF, as in the SHACL generator. - Permissible values are individuals of a class defined by
owl:oneOf.gen-owlmakes them classes by default, which is still possible in ohi LinkML with the--permissible-values classoption. - Descriptions are
rdfs:commentby default, rather than the metamodel’sskos:definition. If you select the--metadata-profile linkmloption, descriptions will be written asskos:definition, asgen-owldoes by default. owl:allValuesFromrestrictions that repeat a property’s range are kept for the slots a class lists itself. They record which classes use which slots, which the importer needs. A class that only refines an inherited slot inslot_usagegets one only when it narrows the range.type_designatorrules,rulesandclassification_rulesare not written.
Improvements over Python gen-owl:
patternis translated to XSD’s form, which matches the whole value:gen-owlwrites it as it is, so^…$patterns match nothing.- An abstract class with only one subclass gets no covering axiom. Consider class B, which is a subclass of an abstract class A.
gen-owlin this case writes “every A is a B”, and since every B is already an A, a reasoner treats the two classes as the same. - A single-valued slot is also marked as an
owl:FunctionalPropertywhen all classes that use this property treat it as single-valued. - An attribute’s property gets an
rdfs:rangewhen every use of it has the same range. - The announced new defaults are used: no
owl:minCardinality 0, which says nothing, and oneowl:cardinalitywhere the lower and upper bounds are equal. - A
licensethat is a link is written as an IRI.