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, 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 more or less stuff

Gasoline has neither distinct components nor uniform "members" -- it is not made of parts at all in the way a car or a fleet is. gufo:Quantity models exactly this: a maximally self-connected portion of stuff whose sub-quantities are, again, just more (or less) of the same stuff. Unlike a car's components, a quantity's sub-quantities do not each play a distinct functional role. 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:

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

:isPortionOf a owl:ObjectProperty ;
    rdfs:subPropertyOf gufo:isSubQuantityOf ;
    rdfs:domain :QuantityOfGasoline ;
    rdfs:range :QuantityOfGasoline .

:CityWheelsDepotSupply a :QuantityOfGasoline .

:TuesdayDeliveryLot a :QuantityOfGasoline ;
    :isPortionOf :CityWheelsDepotSupply .

gufo:isSubQuantityOf, like gufo:isComponentOf, is a genuine parthood relation: it holds only for as long as the part actually, currently is part of the whole. Tuesday's delivery lot is a sub-quantity of the depot supply right now, because it is still sitting in the same tank as the rest of it -- identifiable (for quality tracing, say) without having gone anywhere.

That is what makes a quantity's parthood different from a component's, and it is easy to get backwards: once some of that gasoline is pumped into a car, it is no longer part of the depot supply at all -- it has been physically separated into a new, independent quantity. That does not mean the connection to where it came from has to go unrecorded, only that gufo:isSubQuantityOf is the wrong property for it. gUFO has a property for exactly this weaker, historical link: gufo:historicallyDependsOn (the same property used for causal and historical dependence between events in example 5, since its domain and range are the fully general gufo:ConcreteIndividual, not just gufo:Event):

:Car002FuelTankContents a :QuantityOfGasoline ;
    gufo:historicallyDependsOn :TuesdayDeliveryLot .
    # no :isPortionOf triple: this fuel is no longer part of :CityWheelsDepotSupply
    # or :TuesdayDeliveryLot -- asserting one would misstate the current facts, not
    # just their history. gufo:historicallyDependsOn records the history instead,
    # without claiming the two are still, in any sense, one connected portion of stuff.

Contrast this with gufo:isComponentOf: unbolt a wheel and put it back on and the car has regained the very same component. Siphon gasoline out of a tank and there is no "putting it back" that restores anything -- a quantity's identity is exhausted by what it currently, physically consists of. gUFO's gufo:Quantity comment calls this "fixed constitution".

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 just more of the same stuffgufo:isSubQuantityOfDelivery lot → Depot supply

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