From 3bc656bc39598b4dfa47084cca51a5e8bd98b1e7 Mon Sep 17 00:00:00 2001 From: Nikolaos Karalis Date: Wed, 7 Feb 2024 16:33:15 +0100 Subject: [PATCH 1/3] introduced alternative for forAll base on demorgan laws --- owlapy/owl2sparql/converter.py | 65 +++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/owlapy/owl2sparql/converter.py b/owlapy/owl2sparql/converter.py index c7074570..4b010eff 100644 --- a/owlapy/owl2sparql/converter.py +++ b/owlapy/owl2sparql/converter.py @@ -80,7 +80,7 @@ def __getitem__(self, item: OWLEntity) -> str: class Owl2SparqlConverter: """Convert owl (owlapy model class expressions) to SPARQL.""" __slots__ = 'ce', 'sparql', 'variables', 'parent', 'parent_var', 'properties', 'variable_entities', 'cnt', \ - 'mapping', 'grouping_vars', 'having_conditions', '_intersection' + 'mapping', 'grouping_vars', 'having_conditions', 'for_all_de_morgan', 'named_individuals', '_intersection' ce: OWLClassExpression sparql: List[str] @@ -94,8 +94,13 @@ class Owl2SparqlConverter: grouping_vars: Dict[OWLClassExpression, Set[str]] having_conditions: Dict[OWLClassExpression, Set[str]] cnt: int + for_all_de_morgan: bool + named_individuals: bool - def convert(self, root_variable: str, ce: OWLClassExpression, named_individuals: bool = False): + def convert(self, root_variable: str, + ce: OWLClassExpression, + for_all_de_morgan: bool = True, + named_individuals: bool = False): """Used to convert owl class expression to SPARQL syntax. Args: @@ -118,9 +123,11 @@ def convert(self, root_variable: str, ce: OWLClassExpression, named_individuals: self.mapping = VariablesMapping() self.grouping_vars = defaultdict(set) self.having_conditions = defaultdict(set) - # if named_individuals is True, we return only entities that are instances of owl:NamedIndividual - if named_individuals: - self.append_triple(root_variable, 'a', f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>") + self.for_all_de_morgan = for_all_de_morgan + self.named_individuals = named_individuals + # # if named_individuals is True, we return only entities that are instances of owl:NamedIndividual + # if named_individuals: + # self.append_triple(root_variable, 'a', f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>") with self.stack_variable(root_variable): with self.stack_parent(ce): self.process(ce) @@ -283,7 +290,11 @@ def _(self, ce: OWLObjectComplementOf): # the exclusion of "?x ?p ?o" results in the group graph pattern to just return true or false (not bindings) # as a result, we need to comment out the if-clause of the following line # if not self.in_intersection and self.modal_depth == 1: - self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable()) + # if namedIndividual is set to True, do not use variables --> restrict the subject to instances of NamedIndividual + if self.named_individuals: + self.append_triple(subject, "a", f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>") + else: + self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable()) self.append("FILTER NOT EXISTS { ") # process the concept after the ¬ @@ -308,11 +319,17 @@ def _(self, ce: OWLObjectSomeValuesFrom): with self.stack_variable(object_variable): self.process(filler) + @process.register + def _(self, ce: OWLObjectAllValuesFrom): + if self.for_all_de_morgan is True: + self.forAllDeMorgan(ce) + else: + self.forAll(ce) + # an overload of process function # this overload is responsible for handling the forAll operator (e.g., ∀hasChild.Male) # general case: ∀r.C - @process.register - def _(self, ce: OWLObjectAllValuesFrom): + def forAll(self, ce: OWLObjectAllValuesFrom): subject = self.current_variable object_variable = self.mapping.new_individual_variable() # property expression holds the role of the class expression (hasChild in our example) @@ -372,6 +389,34 @@ def _(self, ce: OWLObjectAllValuesFrom): self.append_triple(self.current_variable, predicate, self.mapping.new_individual_variable()) self.append(" } }") + # an overload of process function + # this overload is responsible for handling the forAll operator but as if the DeMorgan law was applied + # (e.g., ∀hasChild.Male == ¬(∃hasChild.¬Male)) + # general case: ∀r.C == ¬(∃r.¬C) + def forAllDeMorgan(self, ce: OWLObjectAllValuesFrom): + subject = self.current_variable + # here, we need to apply the complement rule twice + # the first filter not exists covers the outer ¬ + self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable()) + self.append("FILTER NOT EXISTS { ") + object_variable = self.mapping.new_individual_variable() + # property expression holds the role of the class expression (hasChild in our example) + property_expression = ce.get_property() + if property_expression.is_anonymous(): + # property expression is inverse of a property + self.append_triple(object_variable, property_expression.get_named_property(), self.current_variable) + else: + self.append_triple(self.current_variable, property_expression.get_named_property(), object_variable) + + # the second filter not exists covers the inner ¬ + # filler holds the concept of the expression (Male in our example) and is processed recursively + self.append("FILTER NOT EXISTS { ") + filler = ce.get_filler() + with self.stack_variable(object_variable): + self.process(filler) + self.append(" }") + self.append(" }") + # an overload of process function # this overload is responsible for handling the exists operator combined with an individual (e.g., ∃hasChild.{john}) # general case: ∃r.{a} @@ -590,17 +635,19 @@ def triple(self, subject, predicate, object_): def as_query(self, root_variable: str, ce: OWLClassExpression, + for_all_de_morgan: bool = True, count: bool = False, values: Optional[Iterable[OWLNamedIndividual]] = None, named_individuals: bool = False): # root variable: the variable that will be projected # ce: the class expression to be transformed to a SPARQL query + # for_all_de_morgan: true -> ¬(∃r.¬C), false -> (∀r.C) # count: True, counts the results ; False, projects the individuals # values: positive or negative examples from a class expression problem # named_individuals: if set to True, the generated SPARQL query will return only entities that are instances # of owl:NamedIndividual qs = ["SELECT"] - tp = self.convert(root_variable, ce, named_individuals) + tp = self.convert(root_variable, ce, for_all_de_morgan=for_all_de_morgan, named_individuals=named_individuals) if count: qs.append(f" ( COUNT ( DISTINCT {root_variable} ) AS ?cnt ) WHERE {{ ") else: From 8d3267259b2154b5e2f107dc7c3f3e12721bf758 Mon Sep 17 00:00:00 2001 From: Nikolaos Karalis Date: Wed, 8 May 2024 11:31:22 +0200 Subject: [PATCH 2/3] fix test case --- owlapy/converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlapy/converter.py b/owlapy/converter.py index 5352739d..c80be928 100644 --- a/owlapy/converter.py +++ b/owlapy/converter.py @@ -651,4 +651,4 @@ def owl_expression_to_sparql(expression: OWLClassExpression = None, that are instances of owl:NamedIndividual """ assert expression is not None, "expression cannot be None" - return converter.as_query(root_variable, expression, False, values, named_individuals) + return converter.as_query(root_variable, expression, count=False, values=values, named_individuals=named_individuals) From 19288aeb6124ae256d3146eff77650870996f7fd Mon Sep 17 00:00:00 2001 From: Nikolaos Karalis Date: Fri, 10 May 2024 15:56:59 +0200 Subject: [PATCH 3/3] added a for_all_de_morgan option in owl_expression_to_sparql --- owlapy/converter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/owlapy/converter.py b/owlapy/converter.py index c80be928..fb7ee3f4 100644 --- a/owlapy/converter.py +++ b/owlapy/converter.py @@ -641,14 +641,18 @@ def as_query(self, def owl_expression_to_sparql(expression: OWLClassExpression = None, root_variable: str = "?x", values: Optional[Iterable[OWLNamedIndividual]] = None, + for_all_de_morgan: bool = True, named_individuals: bool = False) -> str: """Convert an OWL Class Expression (https://www.w3.org/TR/owl2-syntax/#Class_Expressions) into a SPARQL query root variable: the variable that will be projected expression: the class expression to be transformed to a SPARQL query values: positive or negative examples from a class expression problem. Unclear + for_all_de_morgan: if set to True, the SPARQL mapping will use the mapping containing the nested FILTER NOT EXISTS + patterns for the universal quantifier (¬(∃r.¬C)), instead of the counting query named_individuals: if set to True, the generated SPARQL query will return only entities that are instances of owl:NamedIndividual """ assert expression is not None, "expression cannot be None" - return converter.as_query(root_variable, expression, count=False, values=values, named_individuals=named_individuals) + return converter.as_query(root_variable, expression, count=False, values=values, + named_individuals=named_individuals, for_all_de_morgan=for_all_de_morgan)