Semantic Access¶
The SMOL interpreter implements semantic lifting, the process of generating
a knowledge graph from the current program state. The generated knowledge
graph can be investigated by external tools after creating a .ttl
file via
the REPL dump
command, queried during breakpoints using the REPL commands
listed in Commands for Querying SMOL, or accessed within the running program itself
via special statements for semantic reflection through OWL concepts, SHACL
shapes or SPARQL queries.
Semantic Lifting¶
Ontology and Virtualization¶
The generated knowledge graph is generated from the following sources:
The SMOL ontology defining the general vocabulary and basic axioms for states.
The class definitions of the SMOL program.
The knowledge generated from the current runtime state (object instances, heap and stack).
A user-defined domain ontology, if supplied.
The SMOL ontology and domain ontology are given as files, while the heap and class table are virtualized, i.e., the knowledge graph is built on demand. If parts of the knowledge graph are not needed by the current query, they are not generated.
The generated knowledge graph can be accessed using SPARQL (using the Apache Jena reasoner), SHACL (also using the Apache Jena model) or an OWL/DL concept (using HermiT). The general structure is pictured below:
What is generated, what is input, some example
Semantic Reflection¶
general introduction
Modeling Bridge¶
To connect with the domain model, SMOL implements the modeling bridge, a mechanism to manually add axioms over the lifted SMOL objects. A simple modeling bridge takes as a parameter a string containing a predicate object list in turtle syntax. The IRI of the lifted objects will be attached as the subject for the predicate object list.
A complex modeling bridge is a sequence of guarded modeling bridges, where the guard is an expression over the fields of an object. The guarded predicate object list is used if the corresponding guard evaluated to true. Guarded modeling bridges are evaluated from the top until a guard evaluates to true, the remaining part is skipped. A complex modeling bridge must end with a simple modeling bridge as the default.
A simple modeling bridge can be annotated either to an object creation or to a class. A complex modeling bridge can annotated only to classes. If a modeling bridge is given for a class and an object creation of this class, the bridge of the object overrides the one of the class.
MODELS ::= SIMPLE_MODELS | COMPLEX_MODELS ;
SIMPLE_MODELS ::= `models` STRING `;`;
COMPLEX_MODELS ::= `models` `(` Expression `)` STRING `;` MODELS;
Example
class C (Int i)
models (this.i > 0) "a :containsPositive .";
models "a :containsNonPositive .";
end
main
C c = new C(5);
C d = new C(0);
C e = new C(4) models "a :special .";
end
The lifting will contain the following axioms:
run:obj1 prog:C_i 5;
a prog:C.
run:obj2 prog:C_i 0;
a prog:C.
run:obj3 prog:C_i 4;
a prog:C.
run:obj1 a :containsPositive.
run:obj2 a :containsNonPositive.
run:obj3 a :special.
Crossing the Modeling Bridge¶
Additionally to adding axioms, the domain
modifier can be used to create a modelled object.
If a class contains a field or method modified by domain
, then each SMOL object is lifted to two objects in the knowledge graph,
the standard one using the run:
prefix (the modelling object), and a second one using the domain:
prefix (the modelled object). The second object is envisaged to be the corresponding
object in the domain, modelled by the created object. The two objects are connected using the smol:models
property.
If a field is annotated with domain
, then it is lifted for the modelled object. Its prefix is also changed to domain
.
The same holds for methods (see CSSA.). The modelled object is only created during lifting if at least one field or method is annotated with domain
.
class C(Int i, domain Int j) end
main
C c = new C(5, 4);
end
The lifting of the created object is as follows.
run:obj1 a prog:C.
run:obj1 prog:C_i 5.
run:obj1 domain:models domain:obj1.
domain:obj1 domain:C_j 4.
Ignoring Fields¶
To exclude certain fields in a class from being lifted, they can be annotated with the hidden
modifier.
The field will be completely ignored during lifting: neither general axioms nor instances are generated.
The hidden
modifier does not interact with the visibility modifiers
If the field is of object-type, the object it points to will still be lifted.
class C (Int i, hidden C j) end
main
C c = new C(5,null);
C d = new C(6, c);
end
The lifting will contain the following axioms. Note that prog:C_j
is not mentioned.
prog:C a smol:Class.
prog:C_i a smol:Field.
prog:C smol:hasField prog:C_i.
run:obj1 a prog:C;
C_i 5.
run:obj2 a prog:C;
C_i 6.
Computational Semantic State Access¶
Methods annotated with rule
generate additional triples during lifting.
To this end, for each created object with such a method, the method is executed in the current state the return value of the execution is then added to the knowledge graph.
The used property has the name prog:<class>_<method>_builtin_res
.
class C(Int i)
rule Int squared() return this.i*this.i;
end
main
C c = new C(5);
end
The lifting will generate the following axioms.
prog:C a smol:Class.
prog:squared a smol:Method.
prog:C smol:hasMethod prog:squared.
prog:C_squared_builtin_res a owl:ObjectProperty;
rdfs:domain prog:C;
rdfs:range xsd:integer.
run:obj1 a prog:C.
run:obj1 prog:C_i 5.
run:obj1 prog:C_squared_builtin_res 25.
A rule
method is not allowed to have side-effects (except exceptions), the following restrictions are statically checked:
It cannot have parameters.
It cannot create objects.
It cannot call non-
rule
methods.It cannot write into any fields.
Query Access¶
Query access retrieves data from the lifted knowledge graph using queries.
Retrieving a list of literals or lifted objects is done via the access
top-level expression.
It takes as its first parameter a String
-literal containing an extended SPARQL query,
which additionally may contain non-answer variables of the form %i
for some strictly positive number i
.
The set of numbers for the non-answer variables must form an interval [1,n] for some n.
Additionally, the top-level expression takes a list of expressions of the length n.
At runtime, these expressions are evaluated and the result is syntactically substituted for the corresponding non-answer variable.
The SPARQL query is then executed and the results of the ?obj
variable are then translated into a list.
For example, the following retrieves all objects o
of type C
with o.aCB.aB.sealing = x
.
List<C> l = access(
"SELECT ?obj WHERE {?obj prog:C_aCB ?b. ?b prog:B_aB ?a. ?a prog:A_sealing %1 }",
this.x); # %1 is substituted by this.x at runtime
The execution fails if any answer variable than ?obj
is used for retrieval, the elements are not literals or IRIs of lifted objects,
or mixes literals of lifted objects. The compiler outputs a warning if the SPARQL query cannot be shown to always return a list of elements of the type of the target variable.
Note
The query must be tree shaped for type-checking.
Constructing a list of new objects from a SPARQL query is done via the construct
top-level expression.
Its parameters are as the one of the access
top-level expression, but the variables are handled differently:
Each variable must have the name of a field of the type of the target location. For each field there must be one variable. All fields must be of primitive data type.
class C(Int j1, Int j2) end
...
List<C> v = construct("SELECT ?j1 ?j2 WHERE { ?y a prog:B. ?y prog:B_i2 ?j2.?y prog:B_a ?x.?x a prog:A. ?x prog:A_i1 ?j1 }");
Note
For a mechanism to load data into classes with structure, i.e., field of class types, see the advanced semantic access section below.
Shape Access¶
Shape access validates the correctness of the lifted knowledge graph with respect to a graph shape using the top-level expression validate(Literal)
.
The parameter must be a String
-literal containing a path to SHACL shapes in turtle syntax.
Boolean b = validate("examples/double.ttl");
The execution fails if the file does not accessable or the SHACL shapes are mal-formed.
Concept Access¶
Concept access retrieves the list of objects described by an OWL concept using the top-level expression member(Literal)
.
The parameter must be a String
-literal containing a concept in Manchester syntax.
For example, the following retrieves all members of class C
that model some domain concept domain:D
.
List<C> list := member("<domain:models> some <domain:D>");
The execution fails if the concept is either mal-formed or contains elements that are not IRIs of lifted objects.
Note
Currently, type checking of concept access is not supported.
Time Series Access¶
While not semantic, a syntactically similar mechanism is available to query data from InfluxDB databases.
Syntactically, one passes different parameters to the access
statement.
The first parameter is a path to a String
-literal containing a InfluxQL query, the second parameter is a mode of the form INFLUXDB(StringLiteral)
,
where the parameter of the mode is a String
-literal containing a path to a YAML configuration to connect to the InfluxDB endpoint.
In this case, the result is always a List
of Double
values.
main
List<Double> list := access(
"from(bucket: \"petwin\")
|> range(start: -1h, stop: -1m)
|> filter(fn: (r) => r[\"_measurement\"] == \"chili\")
|> filter(fn: (r) => r[\"_field\"] == \"temperature\")
|> filter(fn: (r) => r[\"name\"] == \"faarikaal1\")
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
|> yield(name: \"mean\")",
INFLUXDB("petwin.yml"));
print(list.content);
end
Note
Currently, only InfluxQL queries with a single return variable are supported. Influx-mode access
statements are not type-checked.
The access statement can additionally contain variables of the form %i
for some strictly positive number i
.
The values of these variables are assigned as parameters of the access
statement, starting from the third parameter.
E.g. In the following code snippet, the query is executed with the values of the variables name
and sensor tag
substituted for the corresponding parameters %1
and %2
.
The variable name
is passed as the third parameter of the access statement, the variable sensorTag
is passed as the fourth parameter.
main
String name = "faarikaal1";
Integer sensorTag = 1;
List<Double> list := access(
"from(bucket: \"petwin\")
|> range(start: -1h, stop: -1m)
|> filter(fn: (r) => r[\"_measurement\"] == \"chili\")
|> filter(fn: (r) => r[\"_field\"] == \"temperature\")
|> filter(fn: (r) => r[\"name\"] == %1)
|> filter(fn: (r) => r[\"sensorTag\"] == %2)
|> aggregateWindow(every: 5m, fn: mean, createEmpty: false)
|> yield(name: \"mean\")",
INFLUXDB("petwin.yml"),
name, # substitute %1 with the value of name (third parameter)
sensorTag); # substitute %2 with the value of sensorTag (fourth parameter)
print(list.content);
end
Advanced Semantic Access¶
Warning
The following section describes a feature that is on active development on a feature branch (lazy
) and is not available on the master branch.
Advanced query access in SMOL is a tight coupling between classes and the query that retrieves its contents from an external database.
To this end, a class can be annotated with a retrieval query, and a special statement loads all elements of this class through this query, possibly refined with a restriction.
Furthermore, we enable lazy loading for retrieval queries:
if a class C
refers to another class D
through a field f
, then the query of the second class D
is only executed if the field f
is accessed.
Retrieval Queries¶
retrieve, anchor
Lazy Loading¶
QFut etc.