diff --git a/src/oaklib/cli.py b/src/oaklib/cli.py index ff183193f..9beb6c495 100644 --- a/src/oaklib/cli.py +++ b/src/oaklib/cli.py @@ -3409,6 +3409,12 @@ def definitions( show_default=True, help="Include entailed indirect relationships", ) +@click.option( + "--non-redundant-entailed/--no-non-redundant-entailed", + default=False, + show_default=True, + help="Include entailed but exclude entailed redundant relationships", +) @click.option( "--include-tbox/--no-include-tbox", default=True, @@ -3439,6 +3445,7 @@ def relationships( include_entailed: bool, include_tbox: bool, include_abox: bool, + non_redundant_entailed: bool, include_metadata: bool, ): """ @@ -3509,6 +3516,15 @@ def relationships( include_tbox=include_tbox, include_entailed=include_entailed, ) + if non_redundant_entailed: + if not isinstance(impl, OboGraphInterface): + raise NotImplementedError(f"Cannot execute this using {impl} of type {type(impl)}") + up_it = impl.non_redundant_entailed_relationships( + subjects=curies, + predicates=actual_predicates, + include_abox=include_abox, + include_tbox=include_tbox, + ) if direction is None or direction == Direction.up.value: it = up_it elif direction == Direction.down.value: diff --git a/src/oaklib/interfaces/obograph_interface.py b/src/oaklib/interfaces/obograph_interface.py index 2165c3f51..52065da20 100644 --- a/src/oaklib/interfaces/obograph_interface.py +++ b/src/oaklib/interfaces/obograph_interface.py @@ -259,6 +259,36 @@ def descendant_graph( self.transitive_query_cache[key] = g return g + def non_redundant_entailed_relationships( + self, + predicates: List[PRED_CURIE] = None, + **kwargs, + ) -> Iterator[RELATIONSHIP]: + """ + Yields all relationships that are directly entailed. + + See https://github.com/INCATools/ontology-access-kit/issues/739 + + :param kwargs: same as relationships + :return: + """ + if "include_entailed" in kwargs: + kwargs.pop("include_entailed") + relationships = list( + self.relationships(predicates=predicates, include_entailed=True, **kwargs) + ) + rel_by_sp = defaultdict(list) + for s, p, o in relationships: + rel_by_sp[(s, p)].append(o) + for (s, p), objs in rel_by_sp.items(): + redundant_set = set() + for o in objs: + ancs = list(self.ancestors(o, predicates=predicates, reflexive=False)) + redundant_set.update(ancs) + for o in objs: + if o not in redundant_set: + yield s, p, o + def ancestors( self, start_curies: Union[CURIE, List[CURIE]],