Source code for ontolearn.triple_store

# -----------------------------------------------------------------------------
# MIT License
#
# Copyright (c) 2024 Ontolearn Team
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------

"""Triple store representations."""

import logging
import re
from itertools import chain
from typing import Iterable, Set, Optional, Generator, Union, Tuple, Callable
import requests

from owlapy import owl_expression_to_sparql
from owlapy.class_expression import *
from owlapy.class_expression import OWLThing
from owlapy.iri import IRI
from owlapy.owl_axiom import (
    OWLObjectPropertyRangeAxiom,
    OWLObjectPropertyDomainAxiom,
    OWLDataPropertyRangeAxiom,
    OWLDataPropertyDomainAxiom,
    OWLClassAxiom,
    OWLEquivalentClassesAxiom, OWLAxiom,
)
from owlapy.owl_datatype import OWLDatatype
from owlapy.owl_individual import OWLNamedIndividual
from owlapy.owl_literal import OWLLiteral
from owlapy.owl_ontology import OWLOntologyID
from owlapy.abstracts import AbstractOWLOntology, AbstractOWLReasoner
from owlapy.owl_property import (
    OWLDataProperty,
    OWLObjectPropertyExpression,
    OWLObjectInverseOf,
    OWLObjectProperty,
    OWLProperty,
)
from requests import Response
from requests.exceptions import RequestException, JSONDecodeError
from owlapy.converter import Owl2SparqlConverter
from ontolearn.knowledge_base import KnowledgeBase
import traceback
from collections import Counter

logger = logging.getLogger(__name__)

rdfs_prefix = "PREFIX  rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n "
owl_prefix = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n "
rdf_prefix = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n "
xsd_prefix = "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>\n"


[docs] def is_valid_url(url) -> bool: """ Check the validity of a URL. Args: url (str): The url to validate. Returns: True if url is not None, and it passes the regex check. """ regex = re.compile( r"^https?://" # http:// or https:// r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain... r"localhost|" # localhost... r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip r"(?::\d+)?" # optional port r"(?:/?|[/?]\S+)$", re.IGNORECASE, ) return url is not None and regex.search(url)
[docs] def send_http_request_to_ts_and_fetch_results(triplestore_address: str, query: str, return_type: Callable): """ Execute the SPARQL query in the given triplestore_address and return the result as the given return_type. Args: triplestore_address (str): The triplestore address where the query will be executed. query (str): SPARQL query where the root variable should be '?x'. return_type (Callable): OWLAPY class as type. e.g. OWLClass, OWLNamedIndividual, etc. Returns: Generator containing the results of the query as the given type. """ try: response = requests.post(triplestore_address, data={"query": query}) except RequestException as e: raise RequestException( f"Make sure the server is running on the `triplestore_address` = '{triplestore_address}'" f". Check the error below:" f"\n -->Error: {e}" ) try: if return_type == OWLLiteral: yield from unwrap(response) else: yield from [return_type(i) for i in unwrap(response) if i is not None] # return [return_type(IRI.create(i['x']['value'])) for i in # response.json()['results']['bindings']] except JSONDecodeError as e: raise JSONDecodeError( f"Something went wrong with decoding JSON from the response. Check for typos in " f"the `triplestore_address` = '{triplestore_address}' otherwise the error is likely " f"caused by an internal issue. \n -->Error: {e}" )
[docs] def unwrap(result: Response): json = result.json() vars_ = list(json["head"]["vars"]) for b in json["results"]["bindings"]: val = [] for v in vars_: if b[v]["type"] == "uri": val.append(IRI.create(b[v]["value"])) elif b[v]["type"] == "bnode": continue elif b[v]["type"] == "literal" and "datatype" in b[v]: val.append( OWLLiteral(b[v]["value"], OWLDatatype(IRI.create(b[v]["datatype"]))) ) elif b[v]["type"] == "literal" and "datatype" not in b[v]: continue elif b[v]["type"] == "literal" and "datatype" in b[v]: val.append( OWLLiteral(b[v]["value"], OWLDatatype(IRI.create(b[v]["datatype"]))) ) elif b[v]["type"] == "literal" and "datatype" not in b[v]: continue else: raise NotImplementedError( f"Seems like this kind of data is not handled: {b[v]}" ) if len(val) == 1: yield val.pop() else: yield None
[docs] def suf(direct: bool): """Put the star for rdfs properties depending on direct param""" return " " if direct else "* "
[docs] class TripleStoreOntology(AbstractOWLOntology): def __init__(self, triplestore_address: str): assert is_valid_url(triplestore_address), ( "You should specify a valid URL in the following argument: " "'triplestore_address' of class `TripleStore`") self.url = triplestore_address
[docs] def classes_in_signature(self) -> Iterable[OWLClass]: query = owl_prefix + "SELECT DISTINCT ?x WHERE {?x a owl:Class.}" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass)
[docs] def data_properties_in_signature(self) -> Iterable[OWLDataProperty]: query = owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a owl:DatatypeProperty.}" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLDataProperty)
[docs] def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]: query = owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a owl:ObjectProperty.}" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty)
[docs] def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]: query = owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a owl:NamedIndividual.}" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLNamedIndividual)
[docs] def equivalent_classes_axioms(self, c: OWLClass) -> Iterable[OWLEquivalentClassesAxiom]: # TODO:CD: Please fit the query into a single line query = ( owl_prefix + "SELECT DISTINCT ?x" + "WHERE { ?x owl:equivalentClass " + f"<{c.str}>." + "FILTER(?x != " + f"<{c.str}>)}}" ) for cls in send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass): yield OWLEquivalentClassesAxiom([c, cls])
[docs] def general_class_axioms(self) -> Iterable[OWLClassAxiom]: # TODO:CD: What does general class axiom mean ? Please document this function. # / RE:AB: Doc strings in the base class raise NotImplementedError("Currently, ")
[docs] def data_property_domain_axioms(self, pe: OWLDataProperty) -> Iterable[OWLDataPropertyDomainAxiom]: domains = self.get_property_domains(pe) if len(domains) == 0: yield OWLDataPropertyDomainAxiom(pe, OWLThing) else: for dom in domains: yield OWLDataPropertyDomainAxiom(pe, dom)
[docs] def data_property_range_axioms( self, pe: OWLDataProperty )-> Iterable[OWLDataPropertyRangeAxiom]: query = f"{rdfs_prefix}SELECT DISTINCT ?x WHERE {{ <{pe.str}> rdfs:range ?x. }}" for rng in send_http_request_to_ts_and_fetch_results(self.url, query, OWLDatatype): yield OWLDataPropertyRangeAxiom(pe, rng)
[docs] def object_property_domain_axioms( self, pe: OWLObjectProperty ) -> Iterable[OWLObjectPropertyDomainAxiom]: domains = self.get_property_domains(pe) if len(domains) == 0: yield OWLObjectPropertyDomainAxiom(pe, OWLThing) else: for dom in domains: yield OWLObjectPropertyDomainAxiom(pe, dom)
[docs] def object_property_range_axioms(self, pe: OWLObjectProperty) -> Iterable[OWLObjectPropertyRangeAxiom]: query = rdfs_prefix + "SELECT ?x WHERE { " + f"<{pe.str}>" + " rdfs:range ?x. }" # TODO: CD: Why do we need to use set operation ?! \ RE:AB: In order to calculate its length im converting to set ranges = set(send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass)) if len(ranges) == 0: yield OWLObjectPropertyRangeAxiom(pe, OWLThing) else: for rng in ranges: yield OWLObjectPropertyRangeAxiom(pe, rng)
[docs] def get_property_domains(self, pe: OWLProperty)->Set: if isinstance(pe, OWLObjectProperty) or isinstance(pe, OWLDataProperty): query = ( rdfs_prefix + "SELECT ?x WHERE { " + f"<{pe.str}>" + " rdfs:domain ?x. }" ) # TODO: CD: Why do we need to use set operation ?! domains = set(send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass)) return domains else: raise NotImplementedError
[docs] def get_owl_ontology_manager(self): # no manager for this kind of Ontology # @TODO:CD: Please document this class method / RE:AB: Doc strings in the base class pass
[docs] def get_ontology_id(self) -> OWLOntologyID: # @TODO:CD: Please document this class method / RE:AB: Doc strings in the base class # query = (rdf_prefix + owl_prefix + # "SELECT ?ontologyIRI WHERE { ?ontology rdf:type owl:Ontology . ?ontology rdf:about ?ontologyIRI .}") # return list(get_results_from_ts(self.url, query, OWLOntologyID)).pop() raise NotImplementedError
[docs] def add_axiom(self, axiom: Union[OWLAxiom, Iterable[OWLAxiom]]): """Cant modify a triplestore ontology. Implemented because of the base class.""" pass
[docs] def remove_axiom(self, axiom: Union[OWLAxiom, Iterable[OWLAxiom]]): """Cant modify a triplestore ontology. Implemented because of the base class.""" pass
[docs] def __eq__(self, other): if isinstance(other, type(self)): return self.url == other.url return NotImplemented
[docs] def __hash__(self): return hash(self.url)
[docs] def __repr__(self): return f"TripleStoreOntology({self.url})"
[docs] class TripleStoreReasoner(AbstractOWLReasoner): #__slots__ = "ontology" def __init__(self, ontology: TripleStoreOntology): self.ontology = ontology self.url = self.ontology.url self._owl2sparql_converter = Owl2SparqlConverter()
[docs] def data_property_domains( self, pe: OWLDataProperty, direct: bool = False ) -> Iterable[OWLClassExpression]: domains = { d.get_domain() for d in self.ontology.data_property_domain_axioms(pe) } sub_domains = set(chain.from_iterable([self.sub_classes(d) for d in domains])) yield from domains - sub_domains if not direct: yield from sub_domains
[docs] def object_property_domains( self, pe: OWLObjectProperty, direct: bool = False ) -> Iterable[OWLClassExpression]: domains = { d.get_domain() for d in self.ontology.object_property_domain_axioms(pe) } sub_domains = set(chain.from_iterable([self.sub_classes(d) for d in domains])) yield from domains - sub_domains if not direct: yield from sub_domains
[docs] def object_property_ranges( self, pe: OWLObjectProperty, direct: bool = False ) -> Iterable[OWLClassExpression]: ranges = {r.get_range() for r in self.ontology.object_property_range_axioms(pe)} sub_ranges = set(chain.from_iterable([self.sub_classes(d) for d in ranges])) yield from ranges - sub_ranges if not direct: yield from sub_ranges
[docs] def equivalent_classes( self, ce: OWLClassExpression, only_named: bool = True ) -> Iterable[OWLClassExpression]: if only_named: if isinstance(ce, OWLClass): query = ( owl_prefix + "SELECT DISTINCT ?x " + "WHERE { {?x owl:equivalentClass " + f"<{ce.str}>.}}" + "UNION {" + f"<{ce.str}>" + " owl:equivalentClass ?x.}" + "FILTER(?x != " + f"<{ce.str}>)}}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass) else: print(f"Equivalent classes for complex class expressions is not implemented\t{ce}") # raise NotImplementedError(f"Equivalent classes for complex class expressions is not implemented\t{ce}") yield from {} else: raise NotImplementedError("Finding equivalent complex classes is not implemented")
[docs] def disjoint_classes( self, ce: OWLClassExpression, only_named: bool = True ) -> Iterable[OWLClassExpression]: if only_named: if isinstance(ce, OWLClass): query = ( owl_prefix + " SELECT DISTINCT ?x " + "WHERE { " + f"<{ce.str}>" + " owl:disjointWith ?x .}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass) else: raise NotImplementedError( "Disjoint classes for complex class expressions is not implemented" ) else: raise NotImplementedError( "Finding disjoint complex classes is not implemented" )
[docs] def different_individuals( self, ind: OWLNamedIndividual ) -> Iterable[OWLNamedIndividual]: query = ( owl_prefix + rdf_prefix + "SELECT DISTINCT ?x \n" + "WHERE{ ?allDifferent owl:distinctMembers/rdf:rest*/rdf:first ?x.\n" + "?allDifferent owl:distinctMembers/rdf:rest*/rdf:first" + f"<{ind.str}>" + ".\n" + "FILTER(?x != " + f"<{ind.str}>" + ")}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLNamedIndividual)
[docs] def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]: query = ( owl_prefix + "SELECT DISTINCT ?x " + "WHERE {{ ?x owl:sameAs " + f"<{ind.str}>" + " .}" + "UNION { " + f"<{ind.str}>" + " owl:sameAs ?x.}}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLNamedIndividual)
[docs] def equivalent_object_properties( self, op: OWLObjectPropertyExpression ) -> Iterable[OWLObjectPropertyExpression]: if isinstance(op, OWLObjectProperty): query = ( owl_prefix + "SELECT DISTINCT ?x " + "WHERE { {?x owl:equivalentProperty " + f"<{op.str}>.}}" + "UNION {" + f"<{op.str}>" + " owl:equivalentProperty ?x.}" + "FILTER(?x != " + f"<{op.str}>)}}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty) elif isinstance(op, OWLObjectInverseOf): query = ( owl_prefix + "SELECT DISTINCT ?x " + "WHERE { ?inverseProperty owl:inverseOf " + f"<{op.get_inverse().str}> ." + " {?x owl:equivalentProperty ?inverseProperty .}" + "UNION { ?inverseProperty owl:equivalentClass ?x.}" + "FILTER(?x != ?inverseProperty }>)}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty)
[docs] def equivalent_data_properties( self, dp: OWLDataProperty ) -> Iterable[OWLDataProperty]: query = ( owl_prefix + "SELECT DISTINCT ?x" + "WHERE { {?x owl:equivalentProperty " + f"<{dp.str}>.}}" + "UNION {" + f"<{dp.str}>" + " owl:equivalentProperty ?x.}" + "FILTER(?x != " + f"<{dp.str}>)}}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLDataProperty)
[docs] def data_property_values( self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True ) -> Iterable[OWLLiteral]: query = "SELECT ?x WHERE { " + f"<{ind.str}>" + f"<{pe.str}>" + " ?x . }" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLLiteral) if not direct: for prop in self.sub_data_properties(pe): yield from self.data_property_values(ind, prop, True)
[docs] def object_property_values( self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = True, ) -> Iterable[OWLNamedIndividual]: if isinstance(pe, OWLObjectProperty): query = "SELECT ?x WHERE { " + f"<{ind.str}> " + f"<{pe.str}>" + " ?x . }" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLNamedIndividual) elif isinstance(pe, OWLObjectInverseOf): query = ( owl_prefix + "SELECT ?x WHERE { ?inverseProperty owl:inverseOf " + f"<{pe.get_inverse().str}>." + f"<{ind.str}> ?inverseProperty ?x . }}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLNamedIndividual) if not direct: for prop in self.sub_object_properties(pe): yield from self.object_property_values(ind, prop, True)
[docs] def flush(self) -> None: pass
[docs] def instances( self, ce: OWLClassExpression, direct: bool = False, seen_set: Set = None ) -> Iterable[OWLNamedIndividual]: if not seen_set: seen_set = set() seen_set.add(ce) ce_to_sparql = self._owl2sparql_converter.as_query("?x", ce) if not direct: ce_to_sparql = ce_to_sparql.replace( "?x a ", "?x a ?some_cls. \n ?some_cls " "<http://www.w3.org/2000/01/rdf-schema#subClassOf>* ", ) yield from send_http_request_to_ts_and_fetch_results(self.url, ce_to_sparql, OWLNamedIndividual) if not direct: for cls in self.equivalent_classes(ce): if cls not in seen_set: seen_set.add(cls) yield from self.instances(cls, direct, seen_set)
[docs] def sub_classes( self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True ) -> Iterable[OWLClassExpression]: if not only_named: raise NotImplementedError("Finding anonymous subclasses not implemented") if isinstance(ce, OWLClass): query = ( rdfs_prefix + "SELECT ?x WHERE { ?x rdfs:subClassOf" + suf(direct) + f"<{ce.str}>" + ". }" ) results = list(send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass)) if ce in results: results.remove(ce) yield from results else: raise NotImplementedError( "Subclasses of complex classes retrieved via triple store is not implemented" )
# query = "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> " \ # "SELECT DISTINCT ?x WHERE { ?x rdfs:subClassOf" + suf(direct) + " ?c. \n" \ # "?s a ?c . \n" # ce_to_sparql_statements = self._owl2sparql_converter.convert("?s", ce) # for s in ce_to_sparql_statements: # query = query + s + "\n" # query = query + "}" # yield from get_results_from_ts(self._triplestore_address, query, OWLClass)
[docs] def super_classes( self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True ) -> Iterable[OWLClassExpression]: if not only_named: raise NotImplementedError("Finding anonymous superclasses not implemented") if isinstance(ce, OWLClass): if ce == OWLThing: return [] query = ( rdfs_prefix + "SELECT ?x WHERE { " + f"<{ce.str}>" + " rdfs:subClassOf" + suf(direct) + "?x. }" ) results = list(send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass)) if ce in results: results.remove(ce) if (not direct and OWLThing not in results) or len(results) == 0: results.append(OWLThing) yield from results else: raise NotImplementedError( "Superclasses of complex classes retrieved via triple store is not " "implemented" )
[docs] def disjoint_object_properties( self, op: OWLObjectPropertyExpression ) -> Iterable[OWLObjectPropertyExpression]: if isinstance(op, OWLObjectProperty): query = ( owl_prefix + rdf_prefix + "SELECT DISTINCT ?x \n" + "WHERE{ ?AllDisjointProperties owl:members/rdf:rest*/rdf:first ?x.\n" + "?AllDisjointProperties owl:members/rdf:rest*/rdf:first" + f"<{op.str}>" + ".\n" + "FILTER(?x != " + f"<{op.str}>" + ")}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty) elif isinstance(op, OWLObjectInverseOf): query = ( owl_prefix + " SELECT DISTINCT ?x " + "WHERE { ?inverseProperty owl:inverseOf " + f"<{op.get_inverse().str}> ." + " ?AllDisjointProperties owl:members/rdf:rest*/rdf:first ?x.\n" + " ?AllDisjointProperties owl:members/rdf:rest*/rdf:first ?inverseProperty.\n" + " FILTER(?x != ?inverseProperty)}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty)
[docs] def disjoint_data_properties( self, dp: OWLDataProperty ) -> Iterable[OWLDataProperty]: query = ( owl_prefix + rdf_prefix + "SELECT DISTINCT ?x \n" + "WHERE{ ?AllDisjointProperties owl:members/rdf:rest*/rdf:first ?x.\n" + "?AllDisjointProperties owl:members/rdf:rest*/rdf:first" + f"<{dp.str}>" + ".\n" + "FILTER(?x != " + f"<{dp.str}>" + ")}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLDataProperty)
[docs] def all_data_property_values( self, pe: OWLDataProperty, direct: bool = True ) -> Iterable[OWLLiteral]: query = "SELECT DISTINCT ?x WHERE { ?y" + f"<{pe.str}>" + " ?x . }" yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLLiteral) if not direct: for prop in self.sub_data_properties(pe): yield from self.all_data_property_values(prop, True)
[docs] def sub_data_properties( self, dp: OWLDataProperty, direct: bool = False ) -> Iterable[OWLDataProperty]: query = ( rdfs_prefix + "SELECT ?x WHERE { ?x rdfs:subPropertyOf" + suf(direct) + f"<{dp.str}>" + ". }" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLDataProperty)
[docs] def super_data_properties( self, dp: OWLDataProperty, direct: bool = False ) -> Iterable[OWLDataProperty]: query = ( rdfs_prefix + "SELECT ?x WHERE {" + f"<{dp.str}>" + " rdfs:subPropertyOf" + suf(direct) + " ?x. }" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLDataProperty)
[docs] def sub_object_properties( self, op: OWLObjectPropertyExpression, direct: bool = False ) -> Iterable[OWLObjectPropertyExpression]: if isinstance(op, OWLObjectProperty): query = ( rdfs_prefix + "SELECT ?x WHERE { ?x rdfs:subPropertyOf" + suf(direct) + f"<{op.str}> . FILTER(?x != " + f"<{op.str}>) }}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty) elif isinstance(op, OWLObjectInverseOf): query = ( rdfs_prefix + "SELECT ?x " + "WHERE { ?inverseProperty owl:inverseOf " + f"<{op.get_inverse().str}> ." + " ?x rdfs:subPropertyOf" + suf(direct) + " ?inverseProperty . }" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty)
[docs] def super_object_properties( self, op: OWLObjectPropertyExpression, direct: bool = False ) -> Iterable[OWLObjectPropertyExpression]: if isinstance(op, OWLObjectProperty): query = ( rdfs_prefix + "SELECT ?x WHERE {" + f"<{op.str}>" + " rdfs:subPropertyOf" + suf(direct) + " ?x. FILTER(?x != " + f"<{op.str}>) }}" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty) elif isinstance(op, OWLObjectInverseOf): query = ( rdfs_prefix + "SELECT ?x " + "WHERE { ?inverseProperty owl:inverseOf " + f"<{op.get_inverse().str}> ." + " ?inverseProperty rdfs:subPropertyOf" + suf(direct) + "?x . }" ) yield from send_http_request_to_ts_and_fetch_results(self.url, query, OWLObjectProperty)
[docs] def types( self, ind: OWLNamedIndividual, direct: bool = False ) -> Iterable[OWLClass]: if direct: query = "SELECT ?x WHERE {" + f"<{ind.str}> a" + " ?x. }" else: query = ( rdfs_prefix + "SELECT DISTINCT ?x WHERE {" + f"<{ind.str}> a ?cls. " " ?cls rdfs:subClassOf* ?x}" ) yield from [ i for i in send_http_request_to_ts_and_fetch_results(self.url, query, OWLClass) if i != OWLClass(IRI("http://www.w3.org/2002/07/owl#", "NamedIndividual")) ]
[docs] def get_root_ontology(self) -> AbstractOWLOntology: return self.ontology
[docs] def is_isolated(self): # not needed here pass
[docs] class TripleStoreKnowledgeBase(KnowledgeBase): url: str ontology: TripleStoreOntology reasoner: TripleStoreReasoner def __init__(self, url: str=None): assert url is not None, "url must be string" self.url = url self.ontology = TripleStoreOntology(url) self.reasoner = TripleStoreReasoner(self.ontology) super().__init__( ontology=self.ontology, reasoner=self.reasoner, load_class_hierarchy=False)
[docs] def get_direct_sub_concepts(self, concept: OWLClass) -> Iterable[OWLClass]: assert isinstance(concept, OWLClass) yield from self.reasoner.sub_classes(concept, direct=True)
[docs] def get_direct_parents(self, concept: OWLClassExpression) -> Iterable[OWLClass]: assert isinstance(concept, OWLClass) yield from self.reasoner.super_classes(concept, direct=True)
[docs] def get_all_direct_sub_concepts(self, concept: OWLClassExpression) -> Iterable[OWLClassExpression]: assert isinstance(concept, OWLClass) yield from self.reasoner.sub_classes(concept, direct=True)
[docs] def get_all_sub_concepts(self, concept: OWLClassExpression) -> Iterable[OWLClassExpression]: assert isinstance(concept, OWLClass) yield from self.reasoner.sub_classes(concept, direct=False)
[docs] def get_concepts(self) -> Iterable[OWLClass]: yield from self.ontology.classes_in_signature()
@property def concepts(self) -> Iterable[OWLClass]: yield from self.ontology.classes_in_signature()
[docs] def contains_class(self, concept: OWLClassExpression) -> bool: assert isinstance(concept, OWLClass) return concept in self.ontology.classes_in_signature()
[docs] def most_general_object_properties( self, *, domain: OWLClassExpression, inverse: bool = False) -> Iterable[OWLObjectProperty]: assert isinstance(domain, OWLClassExpression) func: Callable func = ( self.get_object_property_ranges if inverse else self.get_object_property_domains ) inds_domain = self.individuals_set(domain) for prop in self.ontology.object_properties_in_signature(): if domain.is_owl_thing() or inds_domain <= self.individuals_set(func(prop)): yield prop
@property def object_properties(self) -> Iterable[OWLObjectProperty]: yield from self.ontology.object_properties_in_signature()
[docs] def get_object_properties(self) -> Iterable[OWLObjectProperty]: yield from self.ontology.object_properties_in_signature()
@property def data_properties(self) -> Iterable[OWLDataProperty]: yield from self.ontology.data_properties_in_signature()
[docs] def get_data_properties( self, ranges: Set[OWLDatatype] = None ) -> Iterable[OWLDataProperty]: if ranges is not None: for dp in self.ontology.data_properties_in_signature(): if self.get_data_property_ranges(dp) & ranges: yield dp else: yield from self.ontology.data_properties_in_signature()
####################################################################################################################### # See https://github.com/dice-group/Ontolearn/issues/451 for the decision behind this seperation
[docs] class TripleStoreReasonerOntology: def __init__(self, url: str = None): assert url is not None, "URL cannot be None" self.url = url
[docs] def __str__(self): return f"TripleStoreReasonerOntology:{self.url}"
[docs] def query(self, sparql_query: str): return requests.Session().post(self.url, data={"query": sparql_query})
[docs] def are_owl_concept_disjoint(self, c: OWLClass, cc: OWLClass) -> bool: query = f"""{owl_prefix}ASK WHERE {{<{c.str}> owl:disjointWith <{cc.str}> .}}""" # Workaround self.query doesn't work for ASK at the moment return ( requests.Session().post(self.url, data={"query": query}).json()["boolean"] )
[docs] def abox(self, str_iri: str) -> Generator[ Tuple[ Tuple[OWLNamedIndividual, OWLProperty, OWLClass], Tuple[OWLObjectProperty, OWLObjectProperty, OWLNamedIndividual], Tuple[OWLObjectProperty, OWLDataProperty, OWLLiteral], ], None, None, ]: """@TODO:""" sparql_query = f"SELECT DISTINCT ?p ?o WHERE {{ <{str_iri}> ?p ?o }}" subject_ = OWLNamedIndividual(str_iri) for binding in self.query(sparql_query).json()["results"]["bindings"]: p, o = binding["p"], binding["o"] # ORDER MATTERS if p["value"] == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": yield subject_, OWLProperty( "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" ), OWLClass(o["value"]) elif o["type"] == "uri": ################################################################# # IMPORTANT # Can we assume that if o has URI and is not owl class, then o can be considered as an individual ? ################################################################# yield subject_, OWLObjectProperty(p["value"]), OWLNamedIndividual( o["value"] ) elif o["type"] == "literal": if data_type := o.get("datatype", None): if data_type == "http://www.w3.org/2001/XMLSchema#boolean": yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=bool(o["value"])) elif data_type == "http://www.w3.org/2001/XMLSchema#integer": yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=float(o["value"])) elif data_type == "http://www.w3.org/2001/XMLSchema#nonNegativeInteger": # TODO: We do not have http://www.w3.org/2001/XMLSchema#nonNegativeInteger implemented yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=float(o["value"])) elif data_type == "http://www.w3.org/2001/XMLSchema#double": yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=float(o["value"])) else: # TODO: Unclear for the time being. # print(f"Currently this type of literal is not supported:{o} but can done easily let us know :)") continue """ # TODO: Converting a SPARQL query becomes an issue with strings. elif data_type == "http://www.w3.org/2001/XMLSchema#string": yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=repr(o["value"])) elif data_type == "http://www.w3.org/2001/XMLSchema#date": yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=repr(o["value"])) """ else: # print(f"Currently this type of literal is not supported:{o} but can done easily let us know :)") continue # yield subject_, OWLDataProperty(p["value"]), OWLLiteral(value=repr(o["value"])) else: raise RuntimeError(f"Unrecognized type {subject_} ({p}) ({o})")
[docs] def classes_in_signature(self) -> Iterable[OWLClass]: query = owl_prefix + """SELECT DISTINCT ?x WHERE { ?x a owl:Class }""" for binding in self.query(query).json()["results"]["bindings"]: yield OWLClass(binding["x"]["value"])
[docs] def most_general_classes(self) -> Iterable[OWLClass]: """At least it has single subclass and there is no superclass""" query = f"""{rdf_prefix}{rdfs_prefix}{owl_prefix} SELECT ?x WHERE {{ ?concept rdf:type owl:Class . FILTER EXISTS {{ ?x rdfs:subClassOf ?z . }} FILTER NOT EXISTS {{ ?y rdfs:subClassOf ?x . }} }} """ for binding in self.query(query).json()["results"]["bindings"]: yield OWLClass(binding["x"]["value"])
[docs] def least_general_named_concepts(self) -> Generator[OWLClass, None, None]: """At least it has single superclass and there is no subclass""" query = f"""{rdf_prefix}{rdfs_prefix}{owl_prefix} SELECT ?concept WHERE {{ ?concept rdf:type owl:Class . FILTER EXISTS {{ ?concept rdfs:subClassOf ?x . }} FILTER NOT EXISTS {{ ?y rdfs:subClassOf ?concept . }} }}""" for binding in self.query(query).json()["results"]["bindings"]: yield OWLClass(binding["concept"]["value"])
[docs] def get_direct_parents(self, named_concept: OWLClass): """Father rdf:subClassOf Person""" assert isinstance(named_concept, OWLClass) str_named_concept = f"<{named_concept.str}>" query = f"""{rdfs_prefix} SELECT ?x WHERE {{ {str_named_concept} rdfs:subClassOf ?x . }} """ for binding in self.query(query).json()["results"]["bindings"]: yield OWLClass(binding["x"]["value"])
[docs] def subconcepts(self, named_concept: OWLClass, direct=True): assert isinstance(named_concept, OWLClass) str_named_concept = f"<{named_concept.str}>" if direct: query = f"""{rdfs_prefix} SELECT ?x WHERE {{ ?x rdfs:subClassOf* {str_named_concept}. }} """ else: query = f"""{rdf_prefix} SELECT ?x WHERE {{ ?x rdf:subClassOf {str_named_concept}. }} """ for str_iri in self.query(query): yield OWLClass(str_iri)
[docs] def get_type_individuals(self, individual: str): query = f"""SELECT DISTINCT ?x WHERE {{ <{individual}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?x }}""" for binding in self.query(query).json()["results"]["bindings"]: yield OWLClass(binding["x"]["value"])
[docs] def instances( self, expression: OWLClassExpression, named_individuals: bool = False ) -> Generator[OWLNamedIndividual, None, None]: assert isinstance(expression, OWLClassExpression) try: sparql_query = owl_expression_to_sparql(expression=expression, named_individuals=named_individuals) except Exception as exc: print(f"Error at converting {expression} into sparql") traceback.print_exception(exc) print(f"Error at converting {expression} into sparql") raise RuntimeError("Couldn't convert") try: # TODO:Be aware of the implicit inference of x being OWLNamedIndividual! for binding in self.query(sparql_query).json()["results"]["bindings"]: yield OWLNamedIndividual(binding["x"]["value"]) except: print(self.query(sparql_query).text) raise RuntimeError
[docs] def individuals_in_signature(self) -> Generator[OWLNamedIndividual, None, None]: # owl:OWLNamedIndividual is often missing: Perhaps we should add union as well query = ( owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a ?y. ?y a owl:Class.}" ) for binding in self.query(query).json()["results"]["bindings"]: yield OWLNamedIndividual(binding["x"]["value"])
[docs] def data_properties_in_signature(self) -> Iterable[OWLDataProperty]: query = ( owl_prefix + "SELECT DISTINCT ?x " + "WHERE {?x a owl:DatatypeProperty.}" ) for binding in self.query(query).json()["results"]["bindings"]: yield OWLDataProperty(binding["x"]["value"])
[docs] def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]: query = owl_prefix + "SELECT DISTINCT ?x " + "WHERE {?x a owl:ObjectProperty.}" for binding in self.query(query).json()["results"]["bindings"]: yield OWLObjectProperty(binding["x"]["value"])
[docs] def boolean_data_properties(self): query = f"{rdf_prefix}\n{rdfs_prefix}\n{xsd_prefix}SELECT DISTINCT ?x WHERE {{?x rdfs:range xsd:boolean}}" for binding in self.query(query).json()["results"]["bindings"]: yield OWLDataProperty(binding["x"]["value"])
[docs] def double_data_properties(self): query = f"{rdf_prefix}\n{rdfs_prefix}\n{xsd_prefix}SELECT DISTINCT ?x WHERE {{?x rdfs:range xsd:double}}" for binding in self.query(query).json()["results"]["bindings"]: yield OWLDataProperty(binding["x"]["value"])
[docs] def range_of_double_data_properties(self, prop: OWLDataProperty): query = f"{rdf_prefix}\n{rdfs_prefix}\n{xsd_prefix}SELECT DISTINCT ?x WHERE {{?z <{prop.str}> ?x}}" for binding in self.query(query).json()["results"]["bindings"]: yield OWLLiteral(value=float(binding["x"]["value"]))
[docs] def domain_of_double_data_properties(self, prop: OWLDataProperty): query = f"{rdf_prefix}\n{rdfs_prefix}\n{xsd_prefix}SELECT DISTINCT ?x WHERE {{?x <{prop.str}> ?z}}" for binding in self.query(query).json()["results"]["bindings"]: yield OWLNamedIndividual(binding["x"]["value"])
[docs] class TripleStore: url: str def __init__(self, reasoner=None, url: str = None): if reasoner is None: assert url is not None, f"Reasoner:{reasoner} and url of a triplestore {url} cannot be both None." self.g = TripleStoreReasonerOntology(url=url) else: self.g = reasoner self.ontology = self.g self.reasoner = self.g
[docs] def __str__(self): return f"TripleStore:{self.g}"
def __abox_expression(self, individual: OWLNamedIndividual) -> Generator[ Union[ OWLClass, OWLObjectSomeValuesFrom, OWLObjectMinCardinality, OWLDataSomeValuesFrom, ], None, None, ]: """ Return OWL Class Expressions obtained from all set of triples where an input OWLNamedIndividual is subject. Retrieve all triples (i,p,o) where p \in Resources, and o \in [Resources, Literals] and return the followings 1- Owl Named Classes: C(i)=1. 2- ObjectSomeValuesFrom Nominals: \exists r. {a, b, ..., d}, e.g. (i r, a) exists. 3- OWLObjectSomeValuesFrom over named classes: \exists r. C s.t. x \in {a, b, ..., d} C(x)=1. 4- OWLObjectMinCardinality over named classes: ≥ c r. C 5- OWLDataSomeValuesFrom over literals: \exists r. {literal_a, ..., literal_b} """ object_property_to_individuals = dict() data_property_to_individuals = dict() # To no return duplicate objects. quantifier_gate = set() # (1) Iterate over triples where individual is in the subject position. for s, p, o in self.g.abox(str_iri=individual.str): if isinstance(p, OWLProperty) and isinstance(o, OWLClass): ############################################################## # RETURN OWLClass ############################################################## yield o elif isinstance(p, OWLObjectProperty) and isinstance(o, OWLNamedIndividual): ############################################################## # Store for \exist r. {i, ..., j} and OWLObjectMinCardinality over type counts ############################################################## object_property_to_individuals.setdefault(p, []).append(o) elif isinstance(p, OWLDataProperty) and isinstance(o, OWLLiteral): ############################################################## # Store for \exist r. {literal, ..., another literal} ############################################################## data_property_to_individuals.setdefault(p, []).append(o) else: raise RuntimeError( f"Unrecognized triples to expression mappings {p}{o}" ) # Iterating over the mappings of object properties to individuals. for ( object_property, list_owl_individuals, ) in object_property_to_individuals.items(): # RETURN: \exists r. {x1,x33, .., x8} => Existential restriction over nominals yield OWLObjectSomeValuesFrom( property=object_property, filler=OWLObjectOneOf(list_owl_individuals) ) owl_class: OWLClass count: int for owl_class, count in Counter( [ type_i for i in list_owl_individuals for type_i in self.get_types(ind=i, direct=True) ] ).items(): existential_quantifier = OWLObjectSomeValuesFrom( property=object_property, filler=owl_class ) if existential_quantifier in quantifier_gate: "Do nothing" else: ############################################################## # RETURN: \exists r. C => Existential quantifiers over Named OWL Class ############################################################## quantifier_gate.add(existential_quantifier) yield existential_quantifier object_min_cardinality = OWLObjectMinCardinality( cardinality=count, property=object_property, filler=owl_class ) if object_min_cardinality in quantifier_gate: "Do nothing" else: ############################################################## # RETURN: ≥ c r. C => OWLObjectMinCardinality over Named OWL Class ############################################################## quantifier_gate.add(object_min_cardinality) yield object_min_cardinality # Iterating over the mappings of data properties to individuals. for data_property, list_owl_literal in data_property_to_individuals.items(): ############################################################## # RETURN: \exists r. {literal, ..., another literal} => Existential quantifiers over Named OWL Class ############################################################## # if list_owl_literal is {True, False) doesn't really make sense OWLDataSomeValuesFrom # Perhaps, if yield OWLDataSomeValuesFrom( property=data_property, filler=OWLDataOneOf(list_owl_literal) )
[docs] def abox(self, individual: OWLNamedIndividual, mode: str = "native"): """ Get all axioms of a given individual being a subject entity Args: individual (OWLNamedIndividual): An individual mode (str): The return format. 1) 'native' -> returns triples as tuples of owlapy objects, 2) 'iri' -> returns triples as tuples of IRIs as string, 3) 'axiom' -> triples are represented by owlapy axioms. 4) 'expression' -> unique owl class expressions based on (1). Returns: Iterable of tuples or owlapy axiom, depending on the mode. """ assert mode in [ "native", "iri", "axiom", "expression", ], "Valid modes are: 'native', 'iri' or 'axiom', 'expression'" if mode == "native": yield from self.g.abox(str_iri=individual.str) elif mode == "expression": yield from self.__abox_expression(individual) elif mode == "axiom": raise NotImplementedError("Axioms should be checked.")
[docs] def are_owl_concept_disjoint(self, c: OWLClass, cc: OWLClass) -> bool: assert isinstance(c, OWLClass) and isinstance(cc, OWLClass) return self.reasoner.are_owl_concept_disjoint(c, cc)
[docs] def get_object_properties(self): yield from self.reasoner.object_properties_in_signature()
[docs] def get_data_properties(self): yield from self.reasoner.data_properties_in_signature()
[docs] def get_concepts(self) -> OWLClass: yield from self.reasoner.classes_in_signature()
[docs] def get_classes_in_signature(self) -> OWLClass: yield from self.reasoner.classes_in_signature()
[docs] def get_most_general_classes(self): yield from self.reasoner.most_general_classes()
[docs] def get_boolean_data_properties(self): yield from self.reasoner.boolean_data_properties()
[docs] def get_double_data_properties(self): yield from self.reasoner.double_data_properties()
[docs] def get_range_of_double_data_properties(self, prop: OWLDataProperty): yield from self.reasoner.range_of_double_data_properties(prop)
[docs] def individuals( self, concept: Optional[OWLClassExpression] = None, named_individuals: bool = False, ) -> Generator[OWLNamedIndividual, None, None]: """Given an OWL class expression, retrieve all individuals belonging to it. Args: concept: Class expression of which to list individuals. named_individuals: flag for returning only owl named individuals in the SPARQL mapping Returns: Generator of individuals belonging to the given class. """ if concept is None or concept.is_owl_thing(): yield from self.reasoner.individuals_in_signature() else: yield from self.reasoner.instances(concept, named_individuals=named_individuals)
[docs] def get_types(self, ind: OWLNamedIndividual, direct: True) -> Generator[OWLClass, None, None]: if not direct: raise NotImplementedError("Inferring indirect types not available") return self.reasoner.get_type_individuals(ind.str)
[docs] def get_all_sub_concepts(self, concept: OWLClass, direct=True): yield from self.reasoner.subconcepts(concept, direct)
[docs] def classes_in_signature(self): yield from self.reasoner.classes_in_signature()
[docs] def get_direct_parents(self, c: OWLClass): yield from self.reasoner.get_direct_parents(c)
[docs] def most_general_named_concepts(self): yield from self.reasoner.most_general_named_concepts()
[docs] def least_general_named_concepts(self): yield from self.reasoner.least_general_named_concepts()
[docs] def query(self, sparql: str): yield from self.g.query(sparql_query=sparql)