Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v0.4.2 #57

Merged
merged 8 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Benches
benches/history/

# IDE stuff
.idea/
*.iml
Expand Down
2 changes: 1 addition & 1 deletion HOW_TO_RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ tox run
python3 -m build

# Deploy
python3 -m twine upload --sign --identity <YOUR_GPG_KEY_HERE> dist/*
python3 -m twine upload dist/*

# Clear the deployed files
rm -rf dist
Expand Down
24 changes: 21 additions & 3 deletions benches/graph_traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def bench_base_graph(fpath_hpo: str,

results = defaultdict(list)

root = graph.root
for curie, label in CURIE2LABEL.items():
term_id = hpotk.TermId.from_curie(curie)
label = ontology.get_term_name(curie)
Expand All @@ -52,6 +53,11 @@ def bench_base_graph(fpath_hpo: str,
'get_ancestors': lambda: list(graph.get_ancestors(term_id)),
'get_children': lambda: list(graph.get_children(term_id)),
'get_descendants': lambda: list(graph.get_descendants(term_id)),

'is_parent_of': lambda: graph.is_parent_of(root, term_id),
'is_ancestor_of': lambda: graph.is_ancestor_of(root, term_id),
'is_child_of': lambda: graph.is_child_of(term_id, root),
'is_descendant_of': lambda: graph.is_descendant_of(term_id, root),
}

for method in benches:
Expand All @@ -66,12 +72,14 @@ def bench_base_graph(fpath_hpo: str,


def bench_indexed_graph(fpath_hpo: str,
number: int = 1000) -> typing.Mapping[str, typing.Mapping[str, float]]:
number: int = 1000) -> typing.Mapping[str, typing.Sequence]:
factory = hpotk.graph.CsrIndexedGraphFactory()
ontology = hpotk.load_minimal_ontology(fpath_hpo, graph_factory=factory)
graph: hpotk.graph.IndexedOntologyGraph = ontology.graph

results = defaultdict(list)
root = graph.root
root_idx = graph.root_idx
for curie, label in CURIE2LABEL.items():
term_id = hpotk.TermId.from_curie(curie)
idx = graph.node_to_idx(term_id)
Expand All @@ -87,6 +95,15 @@ def bench_indexed_graph(fpath_hpo: str,
'get_children': lambda: list(graph.get_children(term_id)),
'get_descendant_idx': lambda: list(graph.get_descendant_idx(idx)),
'get_descendants': lambda: list(graph.get_descendants(term_id)),

'is_parent_of_idx': lambda: graph.is_parent_of_idx(root_idx, idx),
'is_parent_of': lambda: graph.is_parent_of(root, term_id),
'is_ancestor_of_idx': lambda: graph.is_ancestor_of_idx(root_idx, idx),
'is_ancestor_of': lambda: graph.is_ancestor_of(root, term_id),
'is_child_of_idx': lambda: graph.is_child_of_idx(idx, root_idx),
'is_child_of': lambda: graph.is_child_of(term_id, root),
'is_descendant_of_idx': lambda: graph.is_descendant_of_idx(idx, root_idx),
'is_descendant_of': lambda: graph.is_descendant_of(term_id, root),
}

for method in benches:
Expand Down Expand Up @@ -121,9 +138,10 @@ def bench(fpath_hpo: str, number: int, revision: str):

df = pd.concat(results)
df['revision'] = revision
df = df.set_index(['revision', 'method', 'group', 'payload']).sort_index()
df = df.set_index(['group', 'method', 'payload', 'revision']).sort_index()

fpath_df = f'graph_traversal-{number}-{revision}.csv'
logger.info('Storing results at `%s`', fpath_df)
df.to_csv(fpath_df)


Expand All @@ -145,7 +163,7 @@ def main() -> int:

args = parser.parse_args(argv)
if args.revision is None:
revision = datetime.datetime.now().strftime('%Y-%m-%d')
revision = datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S')
else:
revision = args.revision
bench(args.hpo, args.number, revision)
Expand Down
2 changes: 1 addition & 1 deletion recipe/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% set name = "hpo-toolkit" %}
{% set version = "0.4.1" %}
{% set version = "0.4.2" %}

package:
name: {{ name|lower }}
Expand Down
2 changes: 1 addition & 1 deletion src/hpotk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
HPO toolkit is a library for working with Human Phenotype Ontology and the HPO annotation data.
"""

__version__ = "0.4.1"
__version__ = "0.4.2"

from . import algorithm
from . import annotations
Expand Down
136 changes: 126 additions & 10 deletions src/hpotk/graph/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def is_leaf(self, node: typing.Union[str, NODE, Identified]) -> bool:
:return: `True` if the `node` is a leaf node or `False` otherwise.
:raises ValueError: if `node` is not present in the graph.
"""
for _ in self.get_descendants(node):
for _ in self.get_children(node):
return False
return True

Expand Down Expand Up @@ -273,33 +273,149 @@ def get_ancestors(self, source: typing.Union[str, NODE, Identified],
include_source: bool = False) -> typing.Iterator[NODE]:
return self._map_with_iter_func(source, include_source, self.get_ancestor_idx)

def is_leaf(self, node: typing.Union[str, NODE, Identified]) -> bool:
node_idx = self._map_to_term_idx(node)
if node_idx is None:
raise ValueError(f'No graph node found for {node}')

for _ in self.get_children_idx(node_idx):
return True
return False

def is_parent_of_idx(self, sub: int, obj: int) -> bool:
"""
Return `True` if the subject `sub` is a parent of the object `obj`.

:param sub: index of a graph node.
:param obj: index of the other graph node.
:return: `True` if the `sub` is a parent of the `obj`.
:raises ValueError: if no such node exists for the `obj` index.
"""
return any(sub == idx for idx in self.get_parents_idx(obj))

def is_parent_of(self, sub: typing.Union[str, NODE, Identified],
obj: typing.Union[str, NODE, Identified]) -> bool:
obj_idx = self._map_to_term_idx(obj)
if obj_idx is None:
raise ValueError(f'No graph node found for {obj}')

sub_idx = self._map_to_term_idx(sub)
if sub_idx is None:
return False

return any(sub_idx == idx for idx in self.get_parents_idx(obj_idx))

def is_ancestor_of_idx(self, sub: int, obj: int) -> bool:
"""
Return `True` if the subject `sub` is an ancestor of the object `obj`.

:param sub: index of a graph node.
:param obj: index of the other graph node.
:return: `True` if the `sub` is an ancestor of the `obj`.
:raises ValueError: if no such node exists for the `obj` index.
"""
return any(sub == idx for idx in self.get_ancestor_idx(obj))

def is_ancestor_of(self, sub: typing.Union[str, NODE, Identified],
obj: typing.Union[str, NODE, Identified]) -> bool:
obj_idx = self._map_to_term_idx(obj)
if obj_idx is None:
raise ValueError(f'No graph node found for {obj}')

sub_idx = self._map_to_term_idx(sub)
if sub_idx is None:
return False

return any(sub_idx == idx for idx in self.get_ancestor_idx(obj_idx))

def is_child_of_idx(self, sub: int, obj: int) -> bool:
"""
Return `True` if the subject `sub` is a child of the object `obj`.

:param sub: index of a graph node.
:param obj: index of the other graph node.
:return: `True` if the `sub` is a child of the `obj`.
:raises ValueError: if no such node exists for the `sub` index.
"""
# TODO: ValueError for `sub` may break the pattern
return any(obj == idx for idx in self.get_parents_idx(sub))

def is_child_of(self, sub: typing.Union[str, NODE, Identified],
obj: typing.Union[str, NODE, Identified]) -> bool:
obj_idx = self._map_to_term_idx(obj)
if obj_idx is None:
raise ValueError(f'No graph node found for {obj}')

sub_idx = self._map_to_term_idx(sub)
if sub_idx is None:
return False

# Exploit the fact that a term has usually fewer parents than children.
return any(obj_idx == idx for idx in self.get_parents_idx(sub_idx))

def is_descendant_of_idx(self, sub: int, obj: int) -> bool:
"""
Return `True` if the subject `sub` is a descendant of the object `obj`.

:param sub: index of a graph node.
:param obj: index of the other graph node.
:return: `True` if the `sub` is a descendant of the `obj`.
:raises ValueError: if no such node exists for the `sub` index.
"""
# TODO: ValueError for `sub` may break the pattern
return any(obj == idx for idx in self.get_ancestor_idx(sub))

def is_descendant_of(self, sub: typing.Union[str, NODE, Identified],
obj: typing.Union[str, NODE, Identified]) -> bool:
obj_idx = self._map_to_term_idx(obj)
if obj_idx is None:
raise ValueError(f'No graph node found for {obj}')

sub_idx = self._map_to_term_idx(sub)
if sub_idx is None:
return False

# Exploit the fact that a term has usually fewer parents than children.
return any(obj_idx == idx for idx in self.get_ancestor_idx(sub_idx))

def _map_with_iter_func(self, node: typing.Union[str, NODE, Identified],
include_source: bool,
func: typing.Callable[[int], typing.Iterator[int]]) -> typing.Iterator[NODE]:
term_id = self._map_to_term_id(node)
idx = self.node_to_idx(term_id)
idx = self._map_to_term_idx(node)
if idx is not None:
if include_source:
return itertools.chain((term_id,), map(lambda i: self.idx_to_node(i), func(idx)))
return itertools.chain(
(self.idx_to_node(idx),),
map(self.idx_to_node, func(idx)))
else:
return map(lambda i: self.idx_to_node(i), func(idx))
return map(self.idx_to_node, func(idx))
else:
raise ValueError(f'{node} is not present in the graph!')

def _map_with_seq_func(self, node: typing.Union[str, NODE, Identified],
include_source: bool,
func: typing.Callable[[int], typing.Sequence[int]]) -> typing.Iterator[NODE]:
term_id = self._map_to_term_id(node)
idx = self.node_to_idx(term_id)
idx = self._map_to_term_idx(node)
if idx is not None:
if include_source:
return itertools.chain((term_id,), map(lambda i: self.idx_to_node(i), func(idx)))
return itertools.chain(
(self.idx_to_node(idx),),
map(self.idx_to_node, func(idx)))
else:
return map(lambda i: self.idx_to_node(i), func(idx))
return map(self.idx_to_node, func(idx))
else:
raise ValueError(f'{node} is not present in the graph!')

# TODO: possibly override is_parent, is_leaf, is_ancestor, etc.
def _map_to_term_idx(self, node: typing.Union[str, NODE, Identified]) -> typing.Optional[int]:
"""
A convenience method to convert a `node` into the node index.

:param node: one of the expected node types, including CURIE `str`, NODE, or an :class:`Identified` item.
:return: the node index or `None` if the node is not present in the graph.
:raises ValueError: if the node is not in one of the expected types.
"""
term_id = self._map_to_term_id(node)
return self.node_to_idx(term_id)

# The rest

Expand Down
Loading