Example 2: Objects and their parts

A car fleet -- FunctionalComplex, Collection, Quantity, and their part-whole properties

gufo:FunctionalComplex gufo:Collection gufo:VariableCollection gufo:Quantity gufo:isComponentOf gufo:isCollectionMemberOf gufo:isSubCollectionOf gufo:isSubQuantityOf gufo:historicallyDependsOn

What you'll learn

gUFO's gufo:Object is not a single undifferentiated bucket. It has three subclasses that differ in how their parts relate to the whole, and each comes with its own part-whole property. This example shows all three at once, in a domain where they naturally coexist: a car (a complex made of parts playing different roles), a fleet (a collection of uniform members), and fuel (a quantity of stuff).

The domain

A car-sharing operator, CityWheels, has a fleet of cars. Each car is built from components (an engine, wheels). The fleet itself gains and loses cars as they are bought and retired. Cars run on gasoline -- a blend of a petroleum distillate and ethanol -- stored in bulk in a tank at the depot before being pumped into individual cars.

Step by step

Functional complex: parts with different roles

A gufo:FunctionalComplex is what most ordinary objects are: a whole whose parts (components) each contribute something different. A car's engine and its wheels do not play the same role in the car, so :Car, :Engine and :Wheel are all functional complexes, and the part-whole link between them is (a sub-property of) gufo:isComponentOf:

:Car a owl:Class , gufo:Kind ;
    rdfs:subClassOf gufo:FunctionalComplex .

:Engine a owl:Class , gufo:Kind ;
    rdfs:subClassOf gufo:FunctionalComplex .

:isEngineOf a owl:ObjectProperty ;
    rdfs:subPropertyOf gufo:isComponentOf ;
    rdfs:domain :Engine ;
    rdfs:range :Car .

:Car001Engine a :Engine ;
    :isEngineOf :Car001 .

Declaring a domain-specific sub-property (:isEngineOf) rather than using gufo:isComponentOf directly is optional but recommended: it lets you constrain domain and range per part type (an engine is a component of a car; a wheel, of a car too, but through its own property) while every asserted fact remains, by subsumption, also an instance of the general gufo:isComponentOf relation.

Collection: parts with a uniform role

The fleet is different: every member -- every car in it -- plays the very same role ("being a member of the fleet"). That uniform structure is what gufo:Collection is for. Because CityWheels buys and retires cars over time, the fleet's membership is variable, so it is specifically a gufo:VariableCollection (the alternative, gufo:FixedCollection, is for collections like a hand of cards dealt in a game, where swapping a member yields a strictly different collection):

:Fleet a owl:Class , gufo:Kind ;
    rdfs:subClassOf gufo:VariableCollection .

:isFleetMemberOf a owl:ObjectProperty ;
    rdfs:subPropertyOf gufo:isCollectionMemberOf ;
    rdfs:domain :Car ;
    rdfs:range :Fleet .

:CityWheelsFleet a :Fleet .
:Car001 :isFleetMemberOf :CityWheelsFleet .

FunctionalComplex or Collection? gUFO's own documentation flags this exact case: if the fleet's cars are genuinely undifferentiated -- each just "a member of the fleet" -- then Fleet is a collection. If the cars actually play distinct functional roles in it -- "cargo car", "passenger car" -- then the fleet is really a functional complex, whose parts play those distinct roles. This is not a free choice for the ontology to make: it is a fact about how the fleet is structured, which the ontology is responsible for getting right. What genuinely is a modeling choice is scope -- whether the ontology needs to represent that role differentiation at all. Choosing to model the fleet as a plain collection because the roles do not matter for the task at hand is a legitimate (if coarser-grained) conceptualization.

Collections can also have sub-collections, when a subset of the members forms a collection in its own right -- gufo:isSubCollectionOf (transitive) is for exactly this, distinct from membership:

:ElectricSubfleet a :Fleet ;
    gufo:isSubCollectionOf :CityWheelsFleet .

:Car001 :isFleetMemberOf :ElectricSubfleet .

Quantity: no roles at all, just stuff

Gasoline has neither distinct components nor uniform "members" -- it is not made of parts in the way a car or a fleet is. gufo:Quantity models exactly this: a maximally self-connected portion of stuff. Note the class name, :QuantityOfGasoline, not just :Gasoline: its instances are individual portions of the stuff (this tankful, that delivery), not the material itself -- the same naming convention the gUFO usage guide uses for AmountOfMarble.

"Maximally self-connected" is not decoration; it is what constrains everything below. Any two portions of gasoline in contact are, together, one quantity rather than two, because neither of them alone is maximal. So a quantity of gasoline can never have a smaller quantity of gasoline as a part. What it does have as parts are portions of the different stuffs it is a blend of: pump gasoline is a blend of a petroleum distillate and ethanol, and each of those is a maximally self-connected portion of its own stuff even while mixed into the whole. This is the standard UFO illustration -- a portion of wine has a portion of water and a portion of alcohol as sub-quantities -- and gUFO's own definition of gufo:isSubQuantityOf uses it verbatim: "the quantity of water in a wine glass is a sub-quantity of the wine in that glass".

:QuantityOfGasoline a owl:Class , gufo:Kind ;
    rdfs:subClassOf gufo:Quantity .

:QuantityOfEthanol a owl:Class , gufo:Kind ;
    rdfs:subClassOf gufo:Quantity ;
    owl:disjointWith :QuantityOfGasoline .

:isEthanolPortionOf a owl:ObjectProperty , owl:FunctionalProperty ;
    rdfs:subPropertyOf gufo:isSubQuantityOf ;
    rdfs:domain :QuantityOfEthanol ;
    rdfs:range :QuantityOfGasoline .      # part and whole are of *different* kinds

:Car002FuelTankContents a :QuantityOfGasoline .

:Car002EthanolPortion a :QuantityOfEthanol ;
    :isEthanolPortionOf :Car002FuelTankContents .

The mistake to avoid

It is tempting to declare :isPortionOf with :QuantityOfGasoline as both domain and range, and then assert that a delivery lot poured into the depot tank is a sub-quantity of the tank's supply. That is exactly what quantities rule out. Once the lot is in the tank it is in contact with the rest of the gasoline, so it is not a maximal portion any more -- it is not a quantity at all, and there is nothing there for gufo:isSubQuantityOf to relate. Quantities are maximally self-connected and therefore do not have parts of the same kind. In this ontology the quantity kinds are declared pairwise disjoint, which is enough for a DL reasoner to reject the mistake outright: asserting :SomeGasoline :isEthanolPortionOf :OtherGasoline makes the ontology inconsistent, because the property's domain would force that portion of gasoline to also be a portion of ethanol.

Four further constraints come with subQuantityOf, each stated in the ontology as far as OWL allows. The first three all fall out of the same two facts -- quantities are topologically connected, and they are maximal:

Transitivity is the one characteristic not declared on the typed properties. It belongs to gufo:isSubQuantityOf, which gUFO declares transitive (Guizzardi, 2005, p. 184) -- unlike gufo:isComponentOf and gufo:isCollectionMemberOf, which are not. Each typed property here relates one specific stuff to another, so it is not transitive on its own; its assertions feed the transitive super-property instead:

:isBenzenePortionOf a owl:ObjectProperty ;
    rdfs:subPropertyOf gufo:isSubQuantityOf ;
    rdfs:domain :QuantityOfBenzene ;
    rdfs:range :QuantityOfPetroleumDistillate .

:Car002BenzenePortion a :QuantityOfBenzene ;
    :isBenzenePortionOf :Car002DistillatePortion .
    # inferred: :Car002BenzenePortion gufo:isSubQuantityOf :Car002FuelTankContents
    # -- which is what one means by saying the fuel in the tank contains benzene.

All of this is where quantities part company with functional complexes, and it is easy to get backwards. Unbolt a wheel and put it back on and the car has regained the very same component -- components can come and go while the whole keeps its identity. A quantity cannot: gUFO's gufo:Quantity comment states that it "has a fixed constitution, and thus, removing or adding a sub-quantity would result in a different quantity".

Mixing and splitting: new quantities, not new parts

So what does one say about Tuesday's delivery being pumped into the depot tank, and about fuel later being pumped from the tank into a car? Because constitution is fixed, none of these events gives an existing quantity a new part or takes one away. Each yields a different quantity, and what survives is a historical link, not a parthood link. gufo:historicallyDependsOn is the property for it (the same one used for causal and historical dependence between events in example 5 -- its domain and range are the fully general gufo:ConcreteIndividual, not just gufo:Event):

:DepotSupplyBeforeTuesday a :QuantityOfGasoline .
:TuesdayDeliveryLot a :QuantityOfGasoline .

:DepotSupplyAfterTuesday a :QuantityOfGasoline ;
    gufo:historicallyDependsOn :DepotSupplyBeforeTuesday , :TuesdayDeliveryLot .

:Car002FuelTankContents gufo:historicallyDependsOn :DepotSupplyAfterTuesday .

Note what is not asserted: neither input is a sub-quantity of the mixture. They are of the same kind as it, and after mixing neither is a maximally self-connected portion any more. Nor did :DepotSupplyBeforeTuesday simply grow into :DepotSupplyAfterTuesday -- a quantity does not survive a change of constitution.

If the ontology needs to track "the tank's supply" as one thing that persists across deliveries, the answer is not to stretch gufo:isSubQuantityOf to cover it. The persisting thing is the tank (a gufo:FunctionalComplex), successively constituted by different quantities of gasoline -- see gufo:isConstitutedBy and the gufo:TemporaryConstitutionSituation pattern in the gUFO usage guide, which uses exactly this arrangement for the Venus de Milo and its quantities of marble.

All three, side by side

gUFO classWhat its parts have in commonPart-whole propertyIn this example
gufo:FunctionalComplexEach part plays a different rolegufo:isComponentOfEngine, Wheel → Car
gufo:CollectionEvery part plays the same rolegufo:isCollectionMemberOf, gufo:isSubCollectionOfCar → Fleet
gufo:QuantityParts are portions of the other stuffs the whole is a blend of -- never of the same stuff, which maximality rules outgufo:isSubQuantityOf (essential, non-shareable)Ethanol portion, distillate portion → Tank contents

All of these are sub-properties of the top-level gufo:isObjectProperPartOf, which is itself a sub-property of gufo:isProperPartOf (the part-whole relation shared with events -- see example 5).

The full ontology

See ontology.ttl for the complete file.

How this is checked

Unlike example 1, this example is not primarily about the taxonomy of types that gufoshapes targets, so its main value here is a straightforward pass: both checks confirm the ontology is well-formed.

cd examples/tests
.venv/bin/pytest -v -k objects-and-parts