A car fleet -- FunctionalComplex, Collection, Quantity, and their part-whole properties
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).
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.
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.
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 .
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".
| gUFO class | What its parts have in common | Part-whole property | In this example |
|---|---|---|---|
gufo:FunctionalComplex | Each part plays a different role | gufo:isComponentOf | Engine, Wheel → Car |
gufo:Collection | Every part plays the same role | gufo:isCollectionMemberOf, gufo:isSubCollectionOf | Car → Fleet |
gufo:Quantity | Parts are just more of the same stuff | gufo:isSubQuantityOf | Delivery 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).
See ontology.ttl for the complete file.
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.
gufo.ttl + ontology.ttl is
consistent, and every class remains satisfiable (in particular, :ElectricSubfleet being both a
sub-collection of :CityWheelsFleet and itself a :Fleet does not collapse anything).cd examples/tests
.venv/bin/pytest -v -k objects-and-parts