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

Adds an annotation-based profiling API for measuring the performance of Hatchet and Thicket #142

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
13 changes: 11 additions & 2 deletions hatchet/external/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@
import numpy as np
import warnings
from ..util.colormaps import ColorMaps
from ..util.perf_measure import annotate


_console_class_annotate = annotate(fmt="HatchetConsoleRenderer.{}")


class ConsoleRenderer:
@_console_class_annotate
def __init__(self, unicode=False, color=False):
self.unicode = unicode
self.color = color
self.visited = []

@_console_class_annotate
def render(self, roots, dataframe, **kwargs):
self.render_header = kwargs["render_header"]

Expand Down Expand Up @@ -161,6 +167,7 @@ def render(self, roots, dataframe, **kwargs):
return result.encode("utf-8")

# pylint: disable=W1401
@_console_class_annotate
def render_preamble(self):
lines = [
r" __ __ __ __ ",
Expand All @@ -174,6 +181,7 @@ def render_preamble(self):

return "\n".join(lines)

@_console_class_annotate
def render_legend(self):
def render_label(index, low, high):
metric_range = self.max_metric - self.min_metric
Expand Down Expand Up @@ -247,6 +255,7 @@ def render_label(index, low, high):

return legend

@_console_class_annotate
def render_frame(self, node, dataframe, indent="", child_indent=""):
node_depth = node._depth
if node_depth < self.depth:
Expand Down Expand Up @@ -288,8 +297,8 @@ def render_frame(self, node, dataframe, indent="", child_indent=""):
"none": "",
"constant": "\U00002192",
"phased": "\U00002933",
"dynamic": "\U000021DD",
"sporadic": "\U0000219D",
"dynamic": "\U000021dd",
"sporadic": "\U0000219d",
}
pattern_metric = dataframe.loc[df_index, self.annotation_column]
annotation_content = self.temporal_symbols[pattern_metric]
Expand Down
17 changes: 17 additions & 0 deletions hatchet/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

from functools import total_ordering

from .util.perf_measure import annotate


_frame_annotate = annotate(fmt="Frame.{}")


@total_ordering
class Frame:
Expand All @@ -14,6 +19,7 @@ class Frame:
attrs (dict): dictionary of attributes and values
"""

@_frame_annotate
def __init__(self, attrs=None, **kwargs):
"""Construct a frame from a dictionary, or from immediate kwargs.

Expand Down Expand Up @@ -48,41 +54,52 @@ def __init__(self, attrs=None, **kwargs):

self._tuple_repr = None

@_frame_annotate
def __eq__(self, other):
return self.tuple_repr == other.tuple_repr

@_frame_annotate
def __lt__(self, other):
return self.tuple_repr < other.tuple_repr

@_frame_annotate
def __gt__(self, other):
return self.tuple_repr > other.tuple_repr

@_frame_annotate
def __hash__(self):
return hash(self.tuple_repr)

@_frame_annotate
def __str__(self):
"""str() with sorted attributes, so output is deterministic."""
return "{%s}" % ", ".join("'%s': '%s'" % (k, v) for k, v in self.tuple_repr)

@_frame_annotate
def __repr__(self):
return "Frame(%s)" % self

@property
@_frame_annotate
def tuple_repr(self):
"""Make a tuple of attributes and values based on reader."""
if not self._tuple_repr:
self._tuple_repr = tuple(sorted((k, v) for k, v in self.attrs.items()))
return self._tuple_repr

@_frame_annotate
def copy(self):
return Frame(self.attrs.copy())

@_frame_annotate
def __getitem__(self, name):
return self.attrs[name]

@_frame_annotate
def get(self, name, default=None):
return self.attrs.get(name, default)

@_frame_annotate
def values(self, names):
"""Return a tuple of attribute values from this Frame."""
if isinstance(names, (list, tuple)):
Expand Down
20 changes: 20 additions & 0 deletions hatchet/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
from collections import defaultdict

from .node import Node, traversal_order, node_traversal_order
from .util.perf_measure import annotate


_graph_annotate = annotate(fmt="Graph.{}")


@annotate()
def index_by(attr, objects):
"""Put objects into lists based on the value of an attribute.

Expand All @@ -23,11 +28,13 @@ def index_by(attr, objects):
class Graph:
"""A possibly multi-rooted tree or graph from one input dataset."""

@_graph_annotate
def __init__(self, roots):
assert roots is not None
self.roots = roots
self.node_ordering = False

@_graph_annotate
def traverse(self, order="pre", attrs=None, visited=None):
"""Preorder traversal of all roots of this Graph.

Expand All @@ -47,6 +54,7 @@ def traverse(self, order="pre", attrs=None, visited=None):
for value in root.traverse(order=order, attrs=attrs, visited=visited):
yield value

@_graph_annotate
def node_order_traverse(self, order="pre", attrs=None, visited=None):
"""Preorder traversal of all roots of this Graph, sorting by "node order" column.

Expand All @@ -69,6 +77,7 @@ def node_order_traverse(self, order="pre", attrs=None, visited=None):
):
yield value

@_graph_annotate
def is_tree(self):
"""True if this graph is a tree, false otherwise."""
if len(self.roots) > 1:
Expand All @@ -78,6 +87,7 @@ def is_tree(self):
list(self.traverse(visited=visited))
return all(v == 1 for v in visited.values())

@_graph_annotate
def find_merges(self):
"""Find nodes that have the same parent and frame.

Expand Down Expand Up @@ -135,6 +145,7 @@ def _find_child_merges(node_list):

return merges

@_graph_annotate
def merge_nodes(self, merges):
"""Merge some nodes in a graph into others.

Expand All @@ -159,11 +170,13 @@ def transform(node_list):
child.parents = transform(child.parents)
self.roots = transform(self.roots)

@_graph_annotate
def normalize(self):
merges = self.find_merges()
self.merge_nodes(merges)
return merges

@_graph_annotate
def copy(self, old_to_new=None):
"""Create and return a copy of this graph.

Expand Down Expand Up @@ -192,6 +205,7 @@ def copy(self, old_to_new=None):

return graph

@_graph_annotate
def union(self, other, old_to_new=None):
"""Create the union of self and other and return it as a new Graph.

Expand Down Expand Up @@ -342,6 +356,7 @@ def connect(parent, new_node):

return graph

@_graph_annotate
def enumerate_depth(self):
def _iter_depth(node, visited):
for child in node.children:
Expand All @@ -356,6 +371,7 @@ def _iter_depth(node, visited):
root._depth = 0 # depth of root node is 0
_iter_depth(root, visited)

@_graph_annotate
def enumerate_traverse(self):
if not self._check_enumerate_traverse():
# if "node order" column exists, we traverse sorting by _hatchet_nid
Expand All @@ -379,10 +395,12 @@ def _check_enumerate_traverse(self):
if i != node._hatchet_nid:
return False

@_graph_annotate
def __len__(self):
"""Size of the graph in terms of number of nodes."""
return sum(1 for _ in self.traverse())

@_graph_annotate
def __eq__(self, other):
"""Check if two graphs have the same structure by comparing frame at each
node.
Expand Down Expand Up @@ -415,10 +433,12 @@ def __eq__(self, other):

return True

@_graph_annotate
def __ne__(self, other):
return not (self == other)

@staticmethod
@_graph_annotate
def from_lists(*roots):
"""Convenience method to invoke Node.from_lists() on each root value."""
if not all(isinstance(r, (list, tuple)) for r in roots):
Expand Down
Loading
Loading