Skip to content

feat(owlgen): give annotation properties their OWL role and no logical axioms - #59

Open
jdsika wants to merge 1 commit into
feat/declared-annotation-termsfrom
feat/owl-annotation-property-roles
Open

jdsika wants to merge 1 commit into
feat/declared-annotation-termsfrom
feat/owl-annotation-property-roles

Conversation

@jdsika

@jdsika jdsika commented Oct 21, 2026 •

Copy link
Copy Markdown

Mirror of linkml#4158, same head. Review there; this PR tracks the fork stack.

2026-10-23, third push: every predicate gen-owl writes outside the reserved vocabulary and without a role (skos:definition, skos:inScheme, dcterms:title, the mappings, linkml:permissible_values) is now declared as an annotation property. OWL 2 DL requires a declaration for each property (§5.8.1); without them OWLAPI 5.5.0 finds every generated ontology outside the profile, on main too. OWL snapshots gain only these declarations (checked triple by triple: nothing removed). Suites: 12,002 passed, slow 66 passed.

2026-10-23, second push (review): a super-property must now have the role of its sub-property; an annotation property under a data or object property, or the reverse, is an error, since a sub-property axiom relates two annotation properties (OWL 2 §10.2.1) or two logical properties (§9.2.1, §9.3.1). The PR's own test schema had such a pair in native-URI mode (note under ex:comment, a data property); comment now declares the role. Re-stacked on linkml#4157's second push. OWL, SHACL, annotation, compliance, issue and snapshot suites: 9772 passed.

2026-10-23: rebased onto the rebased linkml#4157 (6fe02bc65); no conflict, no code change. test_owl_annotation_properties.py + test_declared_annotations.py + test_owlgen.py: 153 passed.

gen-owl now gives annotation properties their OWL role and nothing else: a slot that implements: [owl:AnnotationProperty], or whose property IRI is one of the nine built-in annotation properties, is declared as an annotation property and produces no class restriction. Its domain, range and super-properties become annotation axioms; logical uses are errors.

Example

slots:
  note:
    slot_uri: dcterms:description
    implements: [owl:AnnotationProperty]
classes:
  Thing:
    slots: [note]
    attributes:
      name: {slot_uri: rdfs:label}
gen-owl --no-use-native-uris main this PR
dcterms:description a owl:AnnotationProperty and owl:allValuesFrom, owl:maxCardinality restrictions on Thing a owl:AnnotationProperty ; rdfs:range xsd:string, no restriction
rdfs:label a owl:DatatypeProperty and three restrictions nothing: a built-in annotation property

On main the declaration from linkml#2095 is emitted, but the restrictions make the ontology leave OWL 2 DL, and a slot mapped to rdfs:label redeclares a reserved IRI as a data property (linkml#3249).

The rule

  • A slot is an annotation property when it implements owl:AnnotationProperty or when its property IRI is rdfs:label, rdfs:comment, rdfs:seeAlso, rdfs:isDefinedBy, owl:deprecated, owl:versionInfo, owl:priorVersion, owl:backwardCompatibleWith or owl:incompatibleWith (OWL 2 §5.5, Table 5, the complete list).
  • No class restriction and no cardinality axiom on it (§10.1: annotations have no logical meaning; §8 and §9 admit only object and data property expressions in restrictions).
  • Domain, a datatype or class range, and super-properties as annotation axioms (§10.2); a node-type range such as nodeidentifier gives no range axiom. Built-in properties get no declaration and no axioms, since OWL 2 reserves them.
  • A slot that implements two roles is an error (§5.8.1), as is an inverse or a property characteristic, a unique key, a type designator, or a rule or class-expression condition on an annotation property.
  • A super-property (is_a or a mixin) must have the same role: a sub-property axiom relates two annotation properties (§10.2.1) or two logical properties (§9.2.1, §9.3.1).
  • Every other predicate the generator writes outside the reserved vocabulary, such as skos:definition and skos:inScheme for element metadata, is declared as an annotation property, since OWL 2 DL requires a declaration for each property (§5.8.1). A predicate a restriction uses keeps its logical role.
  • Other generators: gen-shacl and gen-json-schema keep constraining the slot's values in instance data. The role concerns OWL's logical meaning, not data validation.
  • The predicate of a declared annotation (the previous draft) whose metaclass slot has the role is declared too, so dcterms:creator in the documented example is an annotation property with native URIs as well.
  • Slots without the role keep their object or data property projection, also when a metaclass declares them: LinkML puns the annotated element as an individual, as it does for all metadata (cmungall in Allow customization of mapping to property types when converting schemas to OWL linkml/linkml#2091).

Behaviour changes

  • Slots with implements: [owl:AnnotationProperty] lose their restrictions and gain annotation axioms. Slots emitted with a built-in annotation property IRI lose their owl:DatatypeProperty declaration and restrictions.
  • New errors for contradictory roles and logical uses of annotation properties.
  • OwlSchemaGenerator.is_annotation_property(slot) is public. No option; the role is already expressed by implements.
  • Every predicate gen-owl writes outside the reserved vocabulary and without a role is declared as an annotation property, so OWL snapshots gain those declarations (and nothing else).

Not in this PR

  • Validation of annotation values against the metaclass (the annotations guide: "not yet supported in the current LinkML validator").
  • An OWLAPI consumer check. The fork can run one as a separate job; upstream CI has no Java.
  • Role collisions between different slots that map to the same IRI, which keep their current output.

Testing

  • test_owl_annotation_properties.py (12): declared and built-in annotation properties with native and mapped URIs, annotation axioms, the declared predicate of the documented example, contradictory roles, and five logical uses that fail.
  • test_declared_annotations.py (43) and the OWL, compliance, issue and snapshot suites pass.

Draft, stacked on linkml#4156 and linkml#4157: this PR's own change is its last commit. It will be rebased as those land. Fork review: #59. Refs linkml#3249, linkml#2091, linkml#2095.

…l axioms

A slot that implements owl:AnnotationProperty, or whose property IRI is one of
the built-in annotation properties of OWL 2 (Structural Specification §5.5), is
an annotation property: it is declared as such, produces no class restriction
or cardinality axiom (annotations have no logical meaning, §10.1), and its
domain, datatype or class range and super-properties are emitted as annotation
axioms (§10.2). Built-in properties get neither a declaration nor axioms. The
predicate of a declared annotation whose metaclass slot has the role is
declared too.

A slot that implements two roles is an error (§5.8.1), as is an inverse or a
property characteristic, a unique key, a type designator, or a rule or
class-expression condition on an annotation property.

Every other predicate gen-owl writes outside the reserved vocabulary,
such as skos:definition and skos:inScheme for element metadata, is
declared as an annotation property: OWL 2 DL requires a declaration for
each property (section 5.8.1), and without one OWLAPI found every
generated ontology outside the profile. A predicate a restriction uses
keeps its logical role. OWL snapshots gain only these declarations.

A super-property must have the role of its sub-property: a sub-property
axiom relates two annotation properties (§10.2.1) or two logical
properties (§9.2.1, §9.3.1), so an annotation property under a data or
object property, or the reverse, is an error.

Signed-off-by: jdsika <carlo.van-driesten@vdl.digital>
@jdsika
jdsika force-pushed the feat/owl-annotation-property-roles branch from 596bb1d to 63c711c Compare October 23, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant