Example 3: Qualities and quality values

Retail products -- basic, enumerated and multidimensional quality patterns

gufo:Quality gufo:QualityValue gufo:inheresIn gufo:hasQualityValue gufo:hasReifiedQualityValue gufo:hasValueComponent

What you'll learn

gUFO treats a quality (a gemstone's mass, a painting's color) as a first-class individual that inheres in the thing it characterizes, rather than as a bare literal attribute -- this is what lets you attach further information to the quality itself (when it changed, what its value means in different scales) and is called reification. This example walks through the three patterns the gUFO usage guide offers for giving a reified quality a value, from simplest to most expressive, on three different products.

The domain

A small retailer sells gemstones (characterized by mass), t-shirts (characterized by size) and paintings (characterized by color) -- three products, three qualities, three ways of recording a value.

Step by step

Pattern 1: a literal value (Gemstone1's mass)

The simplest pattern: the quality is a reified individual, related to its bearer via a sub-property of gufo:inheresIn, and given a value via a sub-property of the datatype property gufo:hasQualityValue:

:Mass rdfs:subClassOf gufo:Quality .

:massOf rdfs:subPropertyOf gufo:inheresIn ;
    rdfs:domain :Mass ; rdfs:range :Gemstone .

:massInGrams rdfs:subPropertyOf gufo:hasQualityValue ;
    rdfs:domain :Mass ; rdfs:range xsd:double .

:Gemstone1Mass a :Mass ;
    :massOf :Gemstone1 ;
    :massInGrams "4.5"^^xsd:double .

The ontology also restricts :Mass to inhere in exactly one gemstone (owl:qualifiedCardinality "1" on gufo:inheresIn) -- a good habit for any quality type once its bearer's class is known, since gUFO itself leaves gufo:inheresIn unconstrained.

Use this pattern whenever a plain number (or string, date, ...) is all a quality value needs to be.

Pattern 2: an enumerated reified value (TShirt1's size)

A shirt size is not naturally a number: "S", "M", "L", "XL" is a closed, ordered set with no obvious numeric encoding worth committing to. Instead of a literal, the quality is given an individual drawn from a class of gufo:QualityValue, restricted by owl:oneOf to exactly those four individuals:

:ShirtSize rdfs:subClassOf gufo:QualityValue ;
    owl:equivalentClass [ a owl:Class ; owl:oneOf ( :S :M :L :XL ) ] .

:Size rdfs:subClassOf gufo:Quality .

:sizeOf rdfs:subPropertyOf gufo:inheresIn ;
    rdfs:domain :Size ; rdfs:range :TShirt .

:hasSizeValue rdfs:subPropertyOf gufo:hasReifiedQualityValue ;
    rdfs:domain :Size ; rdfs:range :ShirtSize .

:TShirt1Size a :Size ;
    :sizeOf :TShirt1 ;
    :hasSizeValue :M .

The object property gufo:hasReifiedQualityValue (rather than the datatype property gufo:hasQualityValue from pattern 1) is gUFO's marker that the value itself is a reified individual, not a literal.

Pattern 3: a multidimensional reified value (Painting1's color)

Color needs more than one number at once -- a point in a 3-D space, not a single scalar or an item from a closed list. The value is again a gufo:QualityValue individual (this time not restricted by owl:oneOf, since the space of RGB triples is not enumerable), but now it carries its own datatype properties, each a sub-property of gufo:hasValueComponent, one per dimension:

:ColorValueInRGB rdfs:subClassOf gufo:QualityValue .

:hasRedValueComponent rdfs:subPropertyOf gufo:hasValueComponent ;
    rdfs:domain :ColorValueInRGB ; rdfs:range xsd:nonNegativeInteger .
:hasGreenValueComponent rdfs:subPropertyOf gufo:hasValueComponent ; ... .
:hasBlueValueComponent  rdfs:subPropertyOf gufo:hasValueComponent ; ... .

:Color rdfs:subClassOf gufo:Quality .

:hasColorValueInRGB rdfs:subPropertyOf gufo:hasReifiedQualityValue ;
    rdfs:domain :Color ; rdfs:range :ColorValueInRGB .

:Painting1Color a :Color ;
    :colorOf :Painting1 ;
    :hasColorValueInRGB [ a :ColorValueInRGB ;
        :hasRedValueComponent "0"^^xsd:nonNegativeInteger ;
        :hasGreenValueComponent "47"^^xsd:nonNegativeInteger ;
        :hasBlueValueComponent "167"^^xsd:nonNegativeInteger ] .

Because the value is reified, the same color could, without touching :Painting1Color's identity, also be given a value in a different space -- e.g. a CMYK quadruple via a second hasColorValueInCMYK property -- something a bare literal could never support cleanly.

Choosing a pattern

Value shapeUseExample here
A single number, string, date, ...gufo:hasQualityValue (datatype property) + a literalMass in grams
One of a fixed, enumerable setgufo:hasReifiedQualityValue + owl:oneOfShirt size
A point in a multidimensional spacegufo:hasReifiedQualityValue + gufo:hasValueComponent per dimensionRGB color

All three qualities remain, throughout, ordinary gufo:ConcreteIndividuals: nothing stops you from also tracking, e.g., when a quality's value last changed, using the situation-based pattern in example 6.

The full ontology

See ontology.ttl for the complete file.

How this is checked

cd examples/tests
.venv/bin/pytest -v -k qualities