Introduction
I want to model some kind of software ecosystem with the help of an ontology. I am planning to use OWL (perhaps it's not the right choice?). The ecosystem consists of many small functions each of which performs a specific task which requires specific inputs and produces specific outputs. For example there might be the following functions:
ImageToText: a function which takes anImageas input and produces aTextstring with all the contained words as output.TextToWordcount: a function which takes aTextas input and produces aWordcountinteger – representing the number of words in the text – as output.
While Text is simply a string and Wordcount is simply a (non-negative) integer, I would model these as separate Data entities in order to capture their semantic meaning (or, perhaps, create dedicated data types).
Then I want to use the ontology to answer questions such as "if I have an Image, is there a way to combine the functions of my ecosystem in such a way that I get a Wordcount of the text that is contained in the image?". In the example above that would be chaining the functions ImageToText --> TextToWordcount.
Similarly, if someone else claims "I can chain the functions ImageToText --> TextToWordcount in order to produce a Wordcount from an Image", I want to be able to check whether that claim makes sense, i.e., whether the mentioned functions can really work together and produce the claimed output.
I thought about modeling this via dedicated Input and Output classes, one per Function. Then I can add restrictions for the specific subclasses. For example, ImageToTextOutput would have the restriction hasOutputData exactly 1 Text.
A sample ontology can be found below.
The question
In the actual system the input types may not only be data, but also data wrapped in some container (e.g., instead of requiring a string as input, a function would require a JSON payload which contains the string under the key "text"). Suppose that TextToWordcount is such a function and it requires a Container as input which itself must contain the Text. In addition, there is a dedicated helper function DataToContainer which simply puts any kind of data inside a Container. That is, if the input to DataToContainer is an Image then the output is a Container which contains the Image. Similarly, if the input is a Text then the output is a Container which contains the Text.
The problem I have with that DataToContainer function is to model the correlation between its input and output. That is, the resulting container must contain an individual of the same type as was present in the input. Currently I have:
Class: DataToContainerInput
SubClassOf:
Input,
hasInputData exactly 1 Data
Class: DataToContainerOutput
SubClassOf:
Output,
hasOutputDataContainer exactly 1 (Container and (containsData exactly 1 Data))
However, the above definition expresses that both the input and output may refer to any Data. For example, the input could be a Text and the output could be a Container containing an Image. I'm looking for a way that let's me restrict the individual of Data in DataToContainerOutput to be of the same type as the one in DataToContainerInput. The input should remain general (i.e., accept Data) but the output must be of the same specific type.
In the end, I want to be able to answer similar questions as above. For example, "given an Image, is there a way to produce a Wordcount?". The answer would be yes, since ImageToText --> DataToContainer --> TextToWordcount is a working function chain. However, with my current definition it seems that this answer cannot be inferred since DataToContainer claims to produce a Container which contains any Data which is not what the next function TextToWordcount expects (it expects Text which is a subclass of Data).
P.S.: Python equivalent
I come from software where I work a lot with Python. In Python I would model the relationship with type hints in the following way. I hope that it helps in explaining my reasoning.
from dataclasses import dataclass
from typing import Generic, TypeVar
class Data:
pass
class Image(Data):
pass
class Text(Data):
pass
D = TypeVar('D', bound=Data)
@dataclass
class Container(Generic[D]):
data: D
def data_to_container(data: D) -> Container[D]:
return Container(data)
reveal_type(data_to_container(Image()).data) # Revealed type is "Image"
reveal_type(data_to_container(Text()).data) # Revealed type is "Text"
P.S.: Example ontology
In Manchester syntax (created with Protégé):
Prefix: : <http://example.org/functions/>
Prefix: owl: <http://www.w3.org/2002/07/owl#>
Prefix: rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Prefix: xml: <http://www.w3.org/XML/1998/namespace>
Prefix: xsd: <http://www.w3.org/2001/XMLSchema#>
Ontology: <http://example.org/functions>
ObjectProperty: <http://example.org/functions#containsData>
Domain:
<http://example.org/functions#Container>
Range:
<http://example.org/functions#Data>
ObjectProperty: <http://example.org/functions#hasInput>
Domain:
<http://example.org/functions#Function>
Range:
<http://example.org/functions#Input>
ObjectProperty: <http://example.org/functions#hasInputData>
Domain:
<http://example.org/functions#Input>
Range:
<http://example.org/functions#Data>
ObjectProperty: <http://example.org/functions#hasInputDataContainer>
Domain:
<http://example.org/functions#Input>
Range:
<http://example.org/functions#Container>
ObjectProperty: <http://example.org/functions#hasOutput>
Domain:
<http://example.org/functions#Function>
Range:
<http://example.org/functions#Output>
ObjectProperty: <http://example.org/functions#hasOutputData>
Domain:
<http://example.org/functions#Output>
Range:
<http://example.org/functions#Data>
ObjectProperty: <http://example.org/functions#hasOutputDataContainer>
Domain:
<http://example.org/functions#Output>
Range:
<http://example.org/functions#Container>
ObjectProperty: <http://example.org/functions#isFollowedBy>
Domain:
<http://example.org/functions#Function>
Range:
<http://example.org/functions#Function>
Class: <http://example.org/functions#Container>
Class: <http://example.org/functions#Data>
Class: <http://example.org/functions#DataToContainer>
SubClassOf:
<http://example.org/functions#Function>,
<http://example.org/functions#hasInput> exactly 1 <http://example.org/functions#DataToContainerInput>,
<http://example.org/functions#hasOutput> exactly 1 <http://example.org/functions#DataToContainerOutput>
Class: <http://example.org/functions#DataToContainerInput>
SubClassOf:
<http://example.org/functions#Input>,
<http://example.org/functions#hasInputData> exactly 1 <http://example.org/functions#Data>
Class: <http://example.org/functions#DataToContainerOutput>
SubClassOf:
<http://example.org/functions#Output>,
<http://example.org/functions#hasOutputDataContainer> exactly 1 (<http://example.org/functions#Container>
and (<http://example.org/functions#containsData> exactly 1 <http://example.org/functions#Data>))
Class: <http://example.org/functions#Function>
SubClassOf:
<http://example.org/functions#isFollowedBy> max 1 <http://example.org/functions#Function>
Class: <http://example.org/functions#GrayscaleImage>
SubClassOf:
<http://example.org/functions#Image>
Class: <http://example.org/functions#Image>
SubClassOf:
<http://example.org/functions#Data>
Class: <http://example.org/functions#ImageToText>
SubClassOf:
<http://example.org/functions#Function>,
<http://example.org/functions#hasInput> exactly 1 <http://example.org/functions#ImageToTextInput>,
<http://example.org/functions#hasOutput> exactly 1 <http://example.org/functions#ImageToTextOutput>
Class: <http://example.org/functions#ImageToTextInput>
SubClassOf:
<http://example.org/functions#Input>,
<http://example.org/functions#hasInputData> exactly 1 <http://example.org/functions#Image>
Class: <http://example.org/functions#ImageToTextOutput>
SubClassOf:
<http://example.org/functions#Output>,
<http://example.org/functions#hasOutputData> exactly 1 <http://example.org/functions#Text>
Class: <http://example.org/functions#Input>
Class: <http://example.org/functions#Integer>
SubClassOf:
<http://example.org/functions#Data>
Class: <http://example.org/functions#Output>
Class: <http://example.org/functions#String>
SubClassOf:
<http://example.org/functions#Data>
Class: <http://example.org/functions#Text>
SubClassOf:
<http://example.org/functions#String>
Class: <http://example.org/functions#TextToWordcount>
SubClassOf:
<http://example.org/functions#Function>,
<http://example.org/functions#hasInput> exactly 1 <http://example.org/functions#TextToWordcountInput>,
<http://example.org/functions#hasOutput> exactly 1 <http://example.org/functions#TextToWordcountOutput>
Class: <http://example.org/functions#TextToWordcountInput>
SubClassOf:
<http://example.org/functions#Input>,
<http://example.org/functions#hasInputDataContainer> exactly 1 (<http://example.org/functions#Container>
and (<http://example.org/functions#containsData> exactly 1 <http://example.org/functions#String>))
Class: <http://example.org/functions#TextToWordcountOutput>
SubClassOf:
<http://example.org/functions#Output>,
<http://example.org/functions#hasOutputData> exactly 1 <http://example.org/functions#Wordcount>
Class: <http://example.org/functions#Wordcount>
SubClassOf:
<http://example.org/functions#Integer>