From 15916a669bb7ec756f2993162e27b9856c613803 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 10:26:51 +0100 Subject: [PATCH 1/6] TripleStore based on rdflib.graph is implemented to load/retrieve information from a locally available RDF graph through SPARQL. RL states in do not store individuals --- ontolearn/base_concept_learner.py | 4 +- ontolearn/learners/drill.py | 34 +++-- ontolearn/triple_store.py | 220 +++++++++++++++++++++++++++++- ontolearn/utils/static_funcs.py | 15 +- 4 files changed, 257 insertions(+), 16 deletions(-) diff --git a/ontolearn/base_concept_learner.py b/ontolearn/base_concept_learner.py index 0c26810a..edb11f45 100644 --- a/ontolearn/base_concept_learner.py +++ b/ontolearn/base_concept_learner.py @@ -195,7 +195,6 @@ def fit(self, *args, **kwargs): Once finished, the results can be queried with the `best_hypotheses` function.""" pass - @abstractmethod def best_hypotheses(self, n=10) -> Iterable[_N]: """Get the current best found hypotheses according to the quality. @@ -205,6 +204,9 @@ def best_hypotheses(self, n=10) -> Iterable[_N]: Returns: Iterable with hypotheses in form of search tree nodes. + + @TODO: We need to write a a decorator for this function to convert each object into an instance of OWLclass epxression + """ pass diff --git a/ontolearn/learners/drill.py b/ontolearn/learners/drill.py index 1e787ac7..eb004c2a 100644 --- a/ontolearn/learners/drill.py +++ b/ontolearn/learners/drill.py @@ -2,7 +2,7 @@ from ontolearn.refinement_operators import LengthBasedRefinement from ontolearn.abstracts import AbstractScorer, AbstractNode from ontolearn.search import RL_State -from typing import Set, List, Tuple, Optional, Generator, SupportsFloat, Iterable, FrozenSet +from typing import Set, List, Tuple, Optional, Generator, SupportsFloat, Iterable, FrozenSet, Callable from owlapy.model import OWLNamedIndividual, OWLClassExpression from ontolearn.learning_problem import PosNegLPStandard, EncodedPosNegLPStandard import torch @@ -15,12 +15,15 @@ import dicee import os from owlapy.render import DLSyntaxObjectRenderer +# F1 class will be deprecated to become compute_f1_score function. from ontolearn.metrics import F1 +from ontolearn.utils.static_funcs import compute_f1_score import random from ontolearn.heuristics import CeloeBasedReward import torch from ontolearn.data_struct import PrepareBatchOfTraining, PrepareBatchOfPrediction + class Drill(RefinementBasedConceptLearner): """ Neuro-Symbolic Class Expression Learning (https://www.ijcai.org/proceedings/2023/0403.pdf)""" @@ -33,7 +36,7 @@ def __init__(self, knowledge_base, use_card_restrictions=True, card_limit=10, nominals=True, - quality_func: AbstractScorer = None, + quality_func: Callable = None, # Abstractscore will be deprecated. reward_func: object = None, batch_size=None, num_workers: int = 1, pretrained_model_name=None, iter_bound=None, max_num_of_concepts_tested=None, verbose: int = 0, terminate_on_goal=None, @@ -74,6 +77,7 @@ def __init__(self, knowledge_base, self.reward_func = CeloeBasedReward() else: self.reward_func = reward_func + # (4) Params. self.num_workers = num_workers self.learning_rate = learning_rate @@ -93,7 +97,7 @@ def __init__(self, knowledge_base, self.storage_path, _ = create_experiment_folder() self.search_tree = DRILLSearchTreePriorityQueue() self.renderer = DLSyntaxObjectRenderer() - self.stop_at_goal=stop_at_goal + self.stop_at_goal = stop_at_goal if self.pre_trained_kge: self.representation_mode = "averaging" @@ -117,7 +121,7 @@ def __init__(self, knowledge_base, else: self.heuristic_func = CeloeBasedReward() self.representation_mode = None - + # @CD: RefinementBasedConceptLearner redefines few attributes this should be avoided. RefinementBasedConceptLearner.__init__(self, knowledge_base=knowledge_base, refinement_operator=refinement_operator, quality_func=quality_func, @@ -126,6 +130,9 @@ def __init__(self, knowledge_base, iter_bound=iter_bound, max_num_of_concepts_tested=max_num_of_concepts_tested, max_runtime=max_runtime) + # CD: This setting the valiable will be removed later. + self.quality_func = compute_f1_score + def initialize_class_expression_learning_problem(self, pos: Set[OWLNamedIndividual], neg: Set[OWLNamedIndividual]): """ @@ -137,9 +144,9 @@ def initialize_class_expression_learning_problem(self, pos: Set[OWLNamedIndividu self.clean() assert 0 < len(pos) and 0 < len(neg) - # 1. + # 1. CD: PosNegLPStandard will be deprecated. # Generate a Learning Problem - self.learning_problem = PosNegLPStandard(pos=set(pos), neg=set(neg)).encode_kb(self.kb) + self.learning_problem = PosNegLPStandard(pos=set(pos), neg=set(neg)) # 2. Obtain embeddings of positive and negative examples. if self.pre_trained_kge is None: self.emb_pos = None @@ -175,7 +182,8 @@ def fit(self, learning_problem: PosNegLPStandard, max_runtime=None): [i for i in chain.from_iterable((self.kb.get_types(ind, direct=True) for ind in learning_problem.neg))]) type_bias = pos_type_counts - neg_type_counts # (1) Initialize learning problem - root_state = self.initialize_class_expression_learning_problem(pos=learning_problem.pos, neg=learning_problem.neg) + root_state = self.initialize_class_expression_learning_problem(pos=learning_problem.pos, + neg=learning_problem.neg) # (2) Add root state into search tree root_state.heuristic = root_state.quality self.search_tree.add(root_state) @@ -321,8 +329,16 @@ def create_rl_state(self, c: OWLClassExpression, parent_node: Optional[RL_State] return rl_state def compute_quality_of_class_expression(self, state: RL_State) -> None: - """ Compute Quality of owl class expression.""" - self.quality_func.apply(state, state.instances_bitset, self.learning_problem) + """ Compute Quality of owl class expression. + # (1) Perform concept retrieval + # (2) Compute the quality w.r.t. (1), positive and negative examples + # (3) Increment the number of tested concepts attribute. + + """ + individuals = frozenset({i for i in self.kb.individuals(state.concept)}) + quality = self.quality_func(individuals=individuals, pos=self.learning_problem.pos, + neg=self.learning_problem.neg) + state.quality=quality self._number_of_tested_concepts += 1 def apply_refinement(self, rl_state: RL_State) -> Generator: diff --git a/ontolearn/triple_store.py b/ontolearn/triple_store.py index ec4b2059..142fce8d 100644 --- a/ontolearn/triple_store.py +++ b/ontolearn/triple_store.py @@ -2,7 +2,7 @@ import logging import re from itertools import chain -from typing import Iterable, Set +from typing import Iterable, Set, Optional, Generator, Union, FrozenSet import requests from requests import Response from requests.exceptions import RequestException, JSONDecodeError @@ -15,15 +15,25 @@ OWLObjectInverseOf, OWLClass, \ IRI, OWLDataPropertyRangeAxiom, OWLDataPropertyDomainAxiom, OWLClassAxiom, \ OWLEquivalentClassesAxiom, OWLObjectProperty, OWLProperty, OWLDatatype - +import rdflib +from ontolearn.concept_generator import ConceptGenerator +from ontolearn.base.owl.utils import OWLClassExpressionLengthMetric +import traceback logger = logging.getLogger(__name__) rdfs_prefix = "PREFIX rdfs: \n " owl_prefix = "PREFIX owl: \n " rdf_prefix = "PREFIX rdf: \n " +xsd_prefix = "PREFIX xsd: \n" +# CD: For the sake of efficient software development. +limit_posix = "" +def rdflib_to_str(sparql_result: rdflib.plugins.sparql.processor.SPARQLResult) -> str: + for result_row in sparql_result: + str_iri: str + yield result_row.x.n3() def is_valid_url(url) -> bool: """ Check the validity of a URL. @@ -458,3 +468,209 @@ def __init__(self, triplestore_address: str): self.ontology = TripleStoreOntology(triplestore_address) self.reasoner = TripleStoreReasoner(self.ontology) super().__init__(ontology=self.ontology, reasoner=self.reasoner) + +class TripleStoreReasonerOntology: + + def __init__(self, graph: rdflib.graph.Graph): + self.g = graph + from owlapy.owl2sparql.converter import Owl2SparqlConverter + self.converter = Owl2SparqlConverter() + + def query(self, sparql_query: str) -> rdflib.plugins.sparql.processor.SPARQLResult: + return self.g.query(sparql_query) + + def classes_in_signature(self) -> Iterable[OWLClass]: + query = owl_prefix + """SELECT DISTINCT ?x WHERE { ?x a owl:Class }""" + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLClass(IRI.create(str_iri[1:-1])) + + def subconcepts(self, named_concept: OWLClass, direct=True): + assert isinstance(named_concept, OWLClass) + str_named_concept = f"<{named_concept.get_iri().as_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 rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLClass(IRI.create(str_iri[1:-1])) + + def get_type_individuals(self, individual: str): + query = f"""SELECT DISTINCT ?x WHERE {{ <{individual}> a ?x }}""" + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLClass(IRI.create(str_iri[1:-1])) + + def instances(self, expression: OWLClassExpression): + assert isinstance(expression, OWLClassExpression) + # convert to SPARQL query + # (1) + try: + query = self.converter.as_query("?x", expression) + except Exception as exc: + # @TODO creating a SPARQL query from OWLObjectMinCardinality causes a problem. + print(f"Error at converting {expression} into sparql") + traceback.print_exception(exc) + print(f"Error at converting {expression} into sparql") + query=None + if query: + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLNamedIndividual(IRI.create(str_iri[1:-1])) + else: + yield + + def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]: + # 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 str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLNamedIndividual(IRI.create(str_iri[1:-1])) + + def data_properties_in_signature(self) -> Iterable[OWLDataProperty]: + query = owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a owl:DatatypeProperty.}" + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLDataProperty(IRI.create(str_iri[1:-1])) + + def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]: + query = owl_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x a owl:ObjectProperty.}" + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + yield OWLObjectProperty(IRI.create(str_iri[1:-1])) + + def boolean_data_properties(self): + # @TODO: Double check the SPARQL query to return all boolean data properties + query = rdf_prefix + xsd_prefix + "SELECT DISTINCT ?x\n " + "WHERE {?x rdf:type rdf:Property; rdfs:range xsd:boolean}" + for str_iri in rdflib_to_str(sparql_result=self.query(query)): + assert str_iri[0] == "<" and str_iri[-1] == ">" + raise NotImplementedError("Unsure how to represent a boolean data proerty with owlapy") + # yield OWLObjectProperty(IRI.create(str_iri[1:-1])) + + yield + + +class TripleStore: + """ triple store """ + url: str + + def __init__(self, path: str, url: str = None): + if url is not None: + raise NotImplementedError("Will be implemented") + # Single object to replace the + self.g = TripleStoreReasonerOntology(rdflib.Graph().parse(path)) + + self.ontology = self.g + self.reasoner = self.g + # CD: We may want to remove it later. This is required at base_concept_learner.py + self.generator = ConceptGenerator() + self.length_metric = OWLClassExpressionLengthMetric.get_default() + + def get_object_properties(self): + yield from self.reasoner.object_properties_in_signature() + + def get_boolean_data_properties(self): + yield from self.reasoner.boolean_data_properties() + + def individuals(self, concept: Optional[OWLClassExpression] = None) -> Iterable[OWLNamedIndividual]: + """Given an OWL class expression, retrieve all individuals belonging to it. + + + Args: + concept: Class expression of which to list individuals. + Returns: + 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) + + 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) + + def get_all_sub_concepts(self, concept: OWLClass, direct=True): + yield from self.reasoner.subconcepts(concept, direct) + + def named_concepts(self): + yield from self.reasoner.classes_in_signature() + + def quality_retrieval(self, expression: OWLClass, pos: set[OWLNamedIndividual], neg: set[OWLNamedIndividual]): + assert isinstance(expression, + OWLClass), "Currently we can only compute the F1 score of a named concepts given pos and neg" + + sparql_str = f"{self.dbo_prefix}{self.rdf_prefix}" + num_pos = len(pos) + str_concept_reminder = expression.get_iri().get_remainder() + + str_concept = expression.get_iri().as_str() + str_pos = " ".join(("<" + i.str + ">" for i in pos)) + str_neg = " ".join(("<" + i.str + ">" for i in neg)) + + # TODO + sparql_str += f""" + SELECT ?tp ?fp ?fn + WHERE {{ + + {{SELECT DISTINCT (COUNT(?var) as ?tp) ( {num_pos}-COUNT(?var) as ?fn) + WHERE {{ VALUES ?var {{ {str_pos} }} ?var rdf:type dbo:{str_concept_reminder} .}} }} + + {{SELECT DISTINCT (COUNT(?var) as ?fp) + WHERE {{ VALUES ?var {{ {str_neg} }} ?var rdf:type dbo:{str_concept_reminder} .}} }} + + }} + """ + + response = requests.post('http://dice-dbpedia.cs.upb.de:9080/sparql', auth=("", ""), + data=sparql_str, + headers={"Content-Type": "application/sparql-query"}) + bindings = response.json()["results"]["bindings"] + assert len(bindings) == 1 + results = bindings.pop() + assert len(results) == 3 + tp = int(results["tp"]["value"]) + fp = int(results["fp"]["value"]) + fn = int(results["fn"]["value"]) + # Compute recall (Sensitivity): Relevant retrieved instances / all relevant instances. + recall = 0 if (tp + fn) == 0 else tp / (tp + fn) + # Compute recall (Sensitivity): Relevant retrieved instances / all retrieved instances. + precision = 0 if (tp + fp) == 0 else tp / (tp + fp) + f1 = 0 if precision == 0 or recall == 0 else 2 * ((precision * recall) / (precision + recall)) + + return f1 + + def concept_len(self, ce: OWLClassExpression) -> int: + """Calculates the length of a concept and is used by some concept learning algorithms to + find the best results considering also the length of the concepts. + + Args: + ce: The concept to be measured. + Returns: + Length of the concept. + """ + + return self.length_metric.length(ce) + + def individuals_set(self,arg: Union[Iterable[OWLNamedIndividual], OWLNamedIndividual, OWLClassExpression]) -> FrozenSet: + """Retrieve the individuals specified in the arg as a frozenset. If `arg` is an OWLClassExpression then this + method behaves as the method "individuals" but will return the final result as a frozenset. + + Args: + arg: more than one individual/ single individual/ class expression of which to list individuals. + Returns: + Frozenset of the individuals depending on the arg type. + + UPDATE: CD: This function should be deprecated it does not introduce any new functionality but coves a rewriting + ,e .g. if args needs to be a frozen set, doing frozenset(arg) solves this need without introducing this function + """ + + if isinstance(arg, OWLClassExpression): + return frozenset(self.individuals(arg)) + elif isinstance(arg, OWLNamedIndividual): + return frozenset({arg}) + else: + return frozenset(arg) diff --git a/ontolearn/utils/static_funcs.py b/ontolearn/utils/static_funcs.py index 4fb0308f..fb63a768 100644 --- a/ontolearn/utils/static_funcs.py +++ b/ontolearn/utils/static_funcs.py @@ -69,25 +69,32 @@ def compute_tp_fn_fp_tn(individuals, pos, neg): return tp, fn, fp, tn -def compute_f1_score(individuals, pos, neg): +def compute_f1_score(individuals, pos, neg)->float: + """ Compute F1-score of a concept + """ + assert type(individuals)==type(pos)==type(neg), f"Types must match:{type(individuals)},{type(pos)},{type(neg)}" + # true positive: |E^+ AND R(C) | tp = len(pos.intersection(individuals)) + # true negative : |E^- AND R(C)| tn = len(neg.difference(individuals)) + # false positive : |E^- AND R(C)| fp = len(neg.intersection(individuals)) + # false negative : |E^- \ R(C)| fn = len(pos.difference(individuals)) try: recall = tp / (tp + fn) except ZeroDivisionError: - return 0 + return 0.0 try: precision = tp / (tp + fp) except ZeroDivisionError: - return 0 + return 0.0 if precision == 0 or recall == 0: - return 0 + return 0.0 f_1 = 2 * ((precision * recall) / (precision + recall)) return f_1 \ No newline at end of file From af5f6f7582c5bf98b695c5618617804ce8f4f59c Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 10:31:52 +0100 Subject: [PATCH 2/6] Adding a small regression test --- tests/test_triplestore.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/test_triplestore.py diff --git a/tests/test_triplestore.py b/tests/test_triplestore.py new file mode 100644 index 00000000..8f34809a --- /dev/null +++ b/tests/test_triplestore.py @@ -0,0 +1,31 @@ +from ontolearn.learners import Drill, TDL +from ontolearn.knowledge_base import KnowledgeBase +from ontolearn.triple_store import TripleStore +from ontolearn.learning_problem import PosNegLPStandard +from ontolearn.verbalizer import LLMVerbalizer +from owlapy.model import OWLNamedIndividual, IRI +from owlapy.render import DLSyntaxObjectRenderer +from ontolearn.utils.static_funcs import compute_f1_score +import json + +# (1) Load a knowledge graph. +kb = TripleStore(path='KGs/Family/family-benchmark_rich_background.owl') +render = DLSyntaxObjectRenderer() +# (2) Get learning problems. +with open("LPs/Family/lps.json") as json_file: + settings = json.load(json_file) +# (3) Initialize learner +model = Drill(knowledge_base=kb) +# (4) +for str_target_concept, examples in settings['problems'].items(): + p = set(examples['positive_examples']) + n = set(examples['negative_examples']) + print('Target concept: ', str_target_concept) + typed_pos = set(map(OWLNamedIndividual, map(IRI.create, p))) + typed_neg = set(map(OWLNamedIndividual, map(IRI.create, n))) + lp = PosNegLPStandard(pos=typed_pos, neg=typed_neg) + h = model.fit(learning_problem=lp).best_hypotheses(1).concept + str_concept = render.render(h) + f1_score = compute_f1_score(individuals=frozenset({i for i in kb.individuals(h)}), pos=lp.pos, neg=lp.neg) + # CD: We need to specify ranges for the regression tests. + assert f1_score>=0.5 \ No newline at end of file From 8da03b5a4ca168bded60208b0b35039cc5027877 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 10:50:00 +0100 Subject: [PATCH 3/6] WIP:RL states do not store any indviduals anymore. Storing such data in each node leads to extensive memory usagea --- ontolearn/learners/drill.py | 32 ++++++++++--------------------- ontolearn/refinement_operators.py | 11 +++++++---- ontolearn/search.py | 18 +++-------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/ontolearn/learners/drill.py b/ontolearn/learners/drill.py index eb004c2a..7ce7cbbd 100644 --- a/ontolearn/learners/drill.py +++ b/ontolearn/learners/drill.py @@ -35,7 +35,7 @@ def __init__(self, knowledge_base, use_data_properties=True, use_card_restrictions=True, card_limit=10, - nominals=True, + use_nominals=True, quality_func: Callable = None, # Abstractscore will be deprecated. reward_func: object = None, batch_size=None, num_workers: int = 1, pretrained_model_name=None, @@ -68,7 +68,7 @@ def __init__(self, knowledge_base, use_card_restrictions=use_card_restrictions, card_limit=card_limit, use_inverse=use_inverse, - nominals=nominals) + use_nominals=use_nominals) else: refinement_operator = refinement_operator @@ -207,16 +207,13 @@ def fit(self, learning_problem: PosNegLPStandard, max_runtime=None): # (2.1) If the next possible RL-state is not a dead end # (2.1.) If the refinement of (1) is not equivalent to \bottom - if len(ref.instances): - # Compute quality - self.compute_quality_of_class_expression(ref) - if ref.quality == 0: - continue - next_possible_states.append(ref) - - if self.stop_at_goal: - if ref.quality == 1.0: - break + self.compute_quality_of_class_expression(ref) + if ref.quality == 0: + continue + next_possible_states.append(ref) + if self.stop_at_goal: + if ref.quality == 1.0: + break try: assert len(next_possible_states) > 0 except AssertionError: @@ -313,18 +310,9 @@ def init_training(self, pos_uri: Set[OWLNamedIndividual], neg_uri: Set[OWLNamedI def create_rl_state(self, c: OWLClassExpression, parent_node: Optional[RL_State] = None, is_root: bool = False) -> RL_State: """ Create an RL_State instance.""" - instances: Generator - instances = set(self.kb.individuals(c)) - instances_bitset: FrozenSet[OWLNamedIndividual] - instances_bitset = self.kb.individuals_set(c) - if self.pre_trained_kge is not None: raise NotImplementedError("No pre-trained knowledge") - - rl_state = RL_State(c, parent_node=parent_node, - is_root=is_root, - instances=instances, - instances_bitset=instances_bitset, embeddings=None) + rl_state = RL_State(c, parent_node=parent_node, is_root=is_root) rl_state.length = self.kb.concept_len(c) return rl_state diff --git a/ontolearn/refinement_operators.py b/ontolearn/refinement_operators.py index 82d17598..245c1661 100644 --- a/ontolearn/refinement_operators.py +++ b/ontolearn/refinement_operators.py @@ -28,15 +28,18 @@ class LengthBasedRefinement(BaseRefinement): """ A top-down refinement operator in ALC.""" - def __init__(self, knowledge_base: KnowledgeBase, use_inverse=False, - use_data_properties=False, use_card_restrictions=False, card_limit=11, nominals=True): + def __init__(self, knowledge_base: KnowledgeBase, + use_inverse: bool = False, + use_data_properties: bool = False, + use_card_restrictions: bool = False, + card_limit=11, use_nominals: bool = True): super().__init__(knowledge_base) self.use_inverse = use_inverse self.use_data_properties = use_data_properties self.use_card_restrictions = use_card_restrictions self.card_limit = card_limit - self.nominals = nominals + self.use_nominals = use_nominals # 1. Number of named classes and sanity checking num_of_named_classes = len(set(i for i in self.kb.ontology.classes_in_signature())) @@ -112,7 +115,7 @@ def refine_top(self) -> Iterable: self.kb.generator.max_cardinality_restriction(c, inverse_role, card), self.kb.generator.exact_cardinality_restriction(c, inverse_role, card)]) - if self.nominals: + if self.use_nominals: temp = [] for i in restrictions: for j in self.kb.individuals(i.get_filler()): diff --git a/ontolearn/search.py b/ontolearn/search.py index e8a5779e..09a51549 100644 --- a/ontolearn/search.py +++ b/ontolearn/search.py @@ -324,29 +324,17 @@ def __str__(self): class RL_State(_NodeConcept, _NodeQuality, _NodeHeuristic, AbstractNode, _NodeParentRef['RL_State']): renderer: ClassVar[OWLObjectRenderer] = DLSyntaxObjectRenderer() """RL_State node.""" - __slots__ = '_concept', '_quality', '_heuristic', \ - 'embeddings', 'individuals', \ - 'instances_bitset', 'length', 'instances', 'parent_node', 'is_root', '_parent_ref', '__weakref__' + __slots__ = '_concept', '_quality', '_heuristic', 'length', 'instances', 'parent_node', 'is_root', '_parent_ref', '__weakref__' - def __init__(self, concept: OWLClassExpression, parent_node: Optional['RL_State'] = None, is_root: bool = False, - embeddings=None, instances: Set = None, instances_bitset: FrozenSet = None, length=None): + def __init__(self, concept: OWLClassExpression, parent_node: Optional['RL_State'] = None, + is_root: bool = False, length=None): _NodeConcept.__init__(self, concept) _NodeQuality.__init__(self) _NodeHeuristic.__init__(self) _NodeParentRef.__init__(self, parent_node=parent_node, is_root=is_root) - - assert isinstance(instances, set), f"Instances must be a set {type(instances)}" - assert isinstance(instances_bitset, frozenset), "Instances must be a set" - # TODO: CD _NodeParentRef causes unintended results: - # Without using _NodeParentRef, one can reach the top class expression via recursive calling parent_node - # However, if one uses _NodeParentRef amd comments self.parent_node and self.is_root, we can reach T. AbstractNode.__init__(self) self.parent_node = parent_node self.is_root = is_root - - self.embeddings = embeddings # tensor - self.instances = instances # list - self.instances_bitset = instances_bitset # bitset self.length = length self.__sanity_checking() From ea771a90675908953a12c44ccf357a4dd324165b Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 12:15:12 +0100 Subject: [PATCH 4/6] LPs are downloaded and integrated into the tests --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34ce4ce8..da1b7bc1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,5 +23,6 @@ jobs: - name: Test with pytest run: | wget https://files.dice-research.org/projects/Ontolearn/KGs.zip - unzip KGs.zip + wget https://files.dice-research.org/projects/Ontolearn/LPs.zip + unzip KGs.zip && unzip LPs.zip pytest -p no:warnings -x \ No newline at end of file From a81157952c40dcb68dd01281295ccd18af2adfe6 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 12:36:04 +0100 Subject: [PATCH 5/6] Instances info is removed from RL_STATE class --- ontolearn/search.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ontolearn/search.py b/ontolearn/search.py index 09a51549..9304a8f1 100644 --- a/ontolearn/search.py +++ b/ontolearn/search.py @@ -324,7 +324,7 @@ def __str__(self): class RL_State(_NodeConcept, _NodeQuality, _NodeHeuristic, AbstractNode, _NodeParentRef['RL_State']): renderer: ClassVar[OWLObjectRenderer] = DLSyntaxObjectRenderer() """RL_State node.""" - __slots__ = '_concept', '_quality', '_heuristic', 'length', 'instances', 'parent_node', 'is_root', '_parent_ref', '__weakref__' + __slots__ = '_concept', '_quality', '_heuristic', 'length','parent_node', 'is_root', '_parent_ref', '__weakref__' def __init__(self, concept: OWLClassExpression, parent_node: Optional['RL_State'] = None, is_root: bool = False, length=None): @@ -344,18 +344,11 @@ def __sanity_checking(self): assert self.parent_node def __str__(self): - - if self.instances is None: - s = 'Not Init.' - else: - s = len(self.instances) - return "\t".join(( AbstractNode.__str__(self), _NodeConcept.__str__(self), _NodeQuality.__str__(self), _NodeHeuristic.__str__(self), - f'|Instance|:{s}', f'Length:{self.length}', )) From 7f01aa6da1bcb139f949b75a44795bbea3521c84 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Mon, 25 Mar 2024 13:08:28 +0100 Subject: [PATCH 6/6] nominals should not be used for SPARQL mapping at the moment --- tests/test_triplestore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_triplestore.py b/tests/test_triplestore.py index 8f34809a..84d16c27 100644 --- a/tests/test_triplestore.py +++ b/tests/test_triplestore.py @@ -15,7 +15,7 @@ with open("LPs/Family/lps.json") as json_file: settings = json.load(json_file) # (3) Initialize learner -model = Drill(knowledge_base=kb) +model = Drill(knowledge_base=kb,use_nominals=False) # (4) for str_target_concept, examples in settings['problems'].items(): p = set(examples['positive_examples'])