diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 1076b3f..2a3772d 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,6 +1,17 @@ Changelog ========= +**1.2.2** +--------- + +*10-04-2023* + +Highlights: + ++ Fixed and refactored CLI application. ++ Switched to C++ for Cython files and now using native C++ queue class. + + **1.2.1** --------- diff --git a/setup.py b/setup.py index f78dee8..169ef2c 100644 --- a/setup.py +++ b/setup.py @@ -10,9 +10,6 @@ def main(): # fmt: off extensions = [ - Extension('ts2vg.utils.pairqueue', - [f'ts2vg/utils/pairqueue.pyx']), - Extension('ts2vg.graph._base', [f'ts2vg/graph/_base.pyx'], include_dirs=include_dirs, diff --git a/tests/naive_implementations.py b/tests/naive_implementations.py index 78bd579..3fde05b 100644 --- a/tests/naive_implementations.py +++ b/tests/naive_implementations.py @@ -6,7 +6,7 @@ """ -def natural_visibility_graph(ts, xs): +def natural_visibility_graph(ts, xs, penetrable_limit=0): n = len(ts) edges = [] @@ -18,19 +18,24 @@ def natural_visibility_graph(ts, xs): x_b = xs[i_b] y_b = ts[i_b] + penetrations = 0 + for i_c in range(i_a + 1, i_b): x_c = xs[i_c] y_c = ts[i_c] if y_c >= y_b + (y_a - y_b) * (x_b - x_c) / (x_b - x_a): - break + penetrations += 1 + + if penetrations > penetrable_limit: + break else: edges.append((i_a, i_b)) return edges -def horizontal_visibility_graph(ts, xs): +def horizontal_visibility_graph(ts, xs, penetrable_limit=0): n = len(ts) edges = [] @@ -42,12 +47,17 @@ def horizontal_visibility_graph(ts, xs): x_b = xs[i_b] y_b = ts[i_b] + penetrations = 0 + for i_c in range(i_a + 1, i_b): x_c = xs[i_c] y_c = ts[i_c] if y_c >= min(y_a, y_b): - break + penetrations += 1 + + if penetrations > penetrable_limit: + break else: edges.append((i_a, i_b)) diff --git a/tests/test_horizontal_penetrable.py b/tests/test_horizontal_penetrable.py index 9ac6228..7b02479 100644 --- a/tests/test_horizontal_penetrable.py +++ b/tests/test_horizontal_penetrable.py @@ -4,16 +4,9 @@ import pytest from pytest import approx +from fixtures import * import ts2vg -from fixtures import ( - empty_ts, - flat_ts, - sample_ts, - sample_ts_2, - linear_ts_small, - linear_ts_large, - linear_ts_large_negative, -) +from naive_implementations import horizontal_visibility_graph as naive_hvg def test_negative_parametric(sample_ts): @@ -117,6 +110,59 @@ def test_penetrable_3(sample_ts): assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + +def test_penetrable_1_white_noise(white_noise_ts): + ts = white_noise_ts + xs = list(range(len(ts))) + + vg = ts2vg.HorizontalVG(penetrable_limit=1) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_hvg(ts, xs, penetrable_limit=1) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_3_white_noise(white_noise_ts): + ts = white_noise_ts + xs = list(range(len(ts))) + + vg = ts2vg.HorizontalVG(penetrable_limit=3) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_hvg(ts, xs, penetrable_limit=3) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_1_brownian_motion(brownian_motion_ts): + ts = brownian_motion_ts + xs = list(range(len(ts))) + + vg = ts2vg.HorizontalVG(penetrable_limit=1) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_hvg(ts, xs, penetrable_limit=1) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_3_brownian_motion(brownian_motion_ts): + ts = brownian_motion_ts + xs = list(range(len(ts))) + + vg = ts2vg.HorizontalVG(penetrable_limit=3) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_hvg(ts, xs, penetrable_limit=3) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + def test_penetrable_1_ltr(sample_ts): vg = ts2vg.HorizontalVG(directed="left_to_right", penetrable_limit=1) out_got = vg.build(sample_ts).edges diff --git a/tests/test_natural_penetrable.py b/tests/test_natural_penetrable.py index f0c785c..b761efe 100644 --- a/tests/test_natural_penetrable.py +++ b/tests/test_natural_penetrable.py @@ -4,16 +4,9 @@ import pytest from pytest import approx +from fixtures import * import ts2vg -from fixtures import ( - empty_ts, - flat_ts, - sample_ts, - sample_ts_2, - linear_ts_small, - linear_ts_large, - linear_ts_large_negative, -) +from naive_implementations import natural_visibility_graph as naive_nvg def test_negative_parametric(sample_ts): @@ -128,6 +121,58 @@ def test_penetrable_3(sample_ts): assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) +def test_penetrable_1_white_noise(white_noise_ts): + ts = white_noise_ts + xs = list(range(len(ts))) + + vg = ts2vg.NaturalVG(penetrable_limit=1) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_nvg(ts, xs, penetrable_limit=1) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_3_white_noise(white_noise_ts): + ts = white_noise_ts + xs = list(range(len(ts))) + + vg = ts2vg.NaturalVG(penetrable_limit=3) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_nvg(ts, xs, penetrable_limit=3) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_1_brownian_motion(brownian_motion_ts): + ts = brownian_motion_ts + xs = list(range(len(ts))) + + vg = ts2vg.NaturalVG(penetrable_limit=1) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_nvg(ts, xs, penetrable_limit=1) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + +def test_penetrable_3_brownian_motion(brownian_motion_ts): + ts = brownian_motion_ts + xs = list(range(len(ts))) + + vg = ts2vg.NaturalVG(penetrable_limit=3) + + out_got = vg.build(ts, xs).edges + + out_truth = naive_nvg(ts, xs, penetrable_limit=3) + + assert sorted(sorted(e) for e in out_got) == sorted(sorted(e) for e in out_truth) + + def test_penetrable_1_ltr(sample_ts): vg = ts2vg.NaturalVG(directed="left_to_right", penetrable_limit=1) out_got = vg.build(sample_ts).edges diff --git a/ts2vg/_version.py b/ts2vg/_version.py index a955fda..bc86c94 100644 --- a/ts2vg/_version.py +++ b/ts2vg/_version.py @@ -1 +1 @@ -__version__ = "1.2.1" +__version__ = "1.2.2" diff --git a/ts2vg/cli.py b/ts2vg/cli.py index bb4af08..453af87 100644 --- a/ts2vg/cli.py +++ b/ts2vg/cli.py @@ -7,38 +7,112 @@ from ts2vg.graph.base import _DIRECTED_OPTIONS, _WEIGHTED_OPTIONS _OUTPUT_MODES = { - 'el': 'edge list', - 'ds': 'degree sequence', - 'dd': 'degree distribution', - 'dc': 'degree counts' + "el": "edge list", + "ds": "degree sequence", + "dd": "degree distribution", + "dc": "degree counts", } _GRAPH_TYPES = { - 'natural': NaturalVG, - 'horizontal': HorizontalVG, + "natural": NaturalVG, + "horizontal": HorizontalVG, } class SmartFormatter(argparse.HelpFormatter): def _split_lines(self, text, width): - if text.startswith('R|'): - return text[2:].splitlines() + if text.startswith("R|"): + return text[2:].splitlines() return argparse.HelpFormatter._split_lines(self, text, width) + def _format_action_invocation(self, action): + if not action.option_strings: + default = self._get_default_metavar_for_positional(action) + (metavar,) = self._metavar_formatter(action, default)(1) + return metavar + + else: + parts = [] + + if action.nargs == 0: + parts.extend(action.option_strings) + + else: + default = self._get_default_metavar_for_optional(action) + for option_string in action.option_strings: + parts.append(option_string) + parts.append(default) + + return " ".join(parts) + def main(): - parser = argparse.ArgumentParser(formatter_class=SmartFormatter, description="Compute the visibility graph from an input time series.") - parser.add_argument('input', help="Path to the file containing the input time series. Must be a text file with one value per line.") - parser.add_argument('-o', '--output', help="Path to the file where the output corresponding to the visibility graph will be saved. If not provided, output will go to stdout.") - parser.add_argument('-t', '--type', choices=_GRAPH_TYPES.keys(), default='natural', help="Type of graph.") - parser.add_argument('-d', '--directed', choices=[c for c in _DIRECTED_OPTIONS.keys() if c is not None], default=None) - parser.add_argument('-w', '--weighted', choices=[c for c in _WEIGHTED_OPTIONS.keys() if c is not None], default=None) - parser.add_argument('-m', '--outputmode', choices=_OUTPUT_MODES.keys(), default='el', # metavar="", - help="R|Graph properties and representation to use for the output. One of:" - "\n el : (default) Edge list. Nodes are labelled in the range [0, n-1] in the same order as the input time series." - "\n ds : Degree sequence. Degree values for the nodes in the range [0, n-1] in the same order as the input time series." - "\n dd : Degree distribution. 1st column is a degree value (k) and 2nd column is the empirical probability for that degree k." - "\n dc : egree counts. 1st column is a degree value (k) and 2nd column is the number of nodes with that degree k.") + parser = argparse.ArgumentParser( + formatter_class=SmartFormatter, + description="Compute the visibility graph from an input time series.", + ) + + parser.add_argument( + "input", + help="Path to the file containing the input time series. Must be a text file with one value per line.", + ) + + parser.add_argument( + "-o", + "--output", + help="Path to the file where the output corresponding to the visibility graph will be saved. If not provided, output will go to stdout.", + ) + + graph_type_options = _GRAPH_TYPES.keys() + parser.add_argument( + "-t", + "--type", + choices=graph_type_options, + default="natural", + help="General type of graph. {" + ",".join(graph_type_options) + "}", + ) + + directed_choices = [c for c in _DIRECTED_OPTIONS.keys() if c is not None] + parser.add_argument( + "-d", + "--directed", + choices=directed_choices, + default=None, + help="If provided, build a directed graph with one of the following values: {" + + ",".join(directed_choices) + + "}.", + ) + + weighted_choices = [c for c in _WEIGHTED_OPTIONS.keys() if c is not None] + parser.add_argument( + "-w", + "--weighted", + choices=weighted_choices, + default=None, + help="If provided, build a weighted graph with one of the following values: {" + + ",".join(weighted_choices) + + "}.", + ) + + parser.add_argument( + "-p", + "--penetrable_limit", + type=int, + default=0, + help="If larger than 0, build a limited penetrable visibility graph (LPVG) with this number of maximum allowed penetrations per edge.", + ) + + parser.add_argument( + "-m", + "--outputmode", + choices=_OUTPUT_MODES.keys(), + default="el", # metavar="", + help="R|Graph properties and representation to use for the output. One of:" + "\n el : (default) Edge list. Nodes are labelled in the range [0, n-1] in the same order as the input time series." + "\n ds : Degree sequence. Degree values for the nodes in the range [0, n-1] in the same order as the input time series." + "\n dd : Degree distribution. 1st column is a degree value (k) and 2nd column is the empirical probability for that degree k." + "\n dc : Degree counts. 1st column is a degree value (k) and 2nd column is the number of nodes with that degree k.", + ) args = parser.parse_args() input_path = args.input @@ -46,6 +120,7 @@ def main(): gtype = args.type directed = args.directed weighted = args.weighted + penetrable_limit = args.penetrable_limit output_mode = args.outputmode output_f = None @@ -54,32 +129,34 @@ def main(): if not output_path_.parent.exists(): print(f"ERROR: Output folder for '{output_path}' not found.") return - - output_f = open(output_path, 'w') + + output_f = open(output_path, "w") input_path_ = Path(input_path) if not input_path_.is_file(): print(f"ERROR: Input file '{input_path}' not found.") return - ts = np.loadtxt(input_path_, dtype='float64') + build_only_degrees = output_mode in ["ds", "dd", "dc"] + + ts = np.loadtxt(input_path_, dtype="float64") - g = _GRAPH_TYPES[gtype](directed=directed, weighted=weighted) - g.build(ts) + g = _GRAPH_TYPES[gtype](directed=directed, weighted=weighted, penetrable_limit=penetrable_limit) + g.build(ts, only_degrees=build_only_degrees) - if output_mode == 'el': + if output_mode == "el": es = g.edges for (a, b, *w) in es: print(a, b, *w, file=output_f) - elif output_mode == 'ds': - ds = g.degree_sequence + elif output_mode == "ds": + ds = g.degrees for d in ds: print(d, file=output_f) - elif output_mode == 'dd': + elif output_mode == "dd": ks, pks = g.degree_distribution for k, pk in zip(ks, pks): print(k, pk, file=output_f) - elif output_mode == 'dc': + elif output_mode == "dc": ks, nks = g.degree_counts for k, nk in zip(ks, nks): print(k, nk, file=output_f) @@ -88,3 +165,7 @@ def main(): output_f.close() print(g.summary()) print(f"Saved {_OUTPUT_MODES[output_mode]} to file: {output_path}") + + +if __name__ == "__main__": + main() diff --git a/ts2vg/graph/_horizontal.pyx b/ts2vg/graph/_horizontal.pyx index 14a8389..548752e 100644 --- a/ts2vg/graph/_horizontal.pyx +++ b/ts2vg/graph/_horizontal.pyx @@ -1,15 +1,18 @@ #cython: language_level=3 +#distutils: language=c++ cimport cython import numpy as np cimport numpy as np from libc.math cimport INFINITY, NAN +from libcpp.queue cimport queue as cqueue +from libcpp.pair cimport pair as cpair -from ts2vg.utils.pairqueue cimport PairQueue from ts2vg.graph.base import _DIRECTED_OPTIONS from ts2vg.graph._base cimport _argmax, _get_weight_func, weight_func_type ctypedef unsigned int uint +ctypedef cpair[uint, uint] uint_pair cdef uint _DIRECTED_LEFT_TO_RIGHT = _DIRECTED_OPTIONS['left_to_right'] cdef uint _DIRECTED_TOP_TO_BOTTOM = _DIRECTED_OPTIONS['top_to_bottom'] @@ -34,8 +37,8 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w cdef weight_func_type weight_func = _get_weight_func(weighted) - cdef PairQueue queue = PairQueue() - queue.push((0, n)) + cdef cqueue[uint_pair] queue + queue.push(uint_pair(0, n)) def add_edge(uint i1, uint i2, double x1, double x2, double y1, double y2): w = weight_func(x1, x2, y1, y2, NAN) @@ -53,8 +56,10 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w edges.append((i1, i2)) - while not queue.is_empty(): - (left, right) = queue.pop() + while not queue.empty(): + pair = queue.front() + left, right = pair.first, pair.second + queue.pop() if left+1 < right: i = _argmax(ts, left, right) @@ -87,7 +92,7 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w max_y = y_b - queue.push((left, i)) - queue.push((i+1, right)) + queue.push(uint_pair(left, i)) + queue.push(uint_pair(i+1, right)) return edges, np.asarray(degrees_in, dtype=np.uint32), np.asarray(degrees_out, dtype=np.uint32) diff --git a/ts2vg/graph/_horizontal_penetrable.pyx b/ts2vg/graph/_horizontal_penetrable.pyx index 077f98e..eecb4a5 100644 --- a/ts2vg/graph/_horizontal_penetrable.pyx +++ b/ts2vg/graph/_horizontal_penetrable.pyx @@ -33,7 +33,7 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w cdef np.uint32_t[:] degrees_in = np.zeros(n, dtype=np.uint32) cdef np.uint32_t[:] degrees_out = np.zeros(n, dtype=np.uint32) - cdef uint left, right, i_a, i_b + cdef uint i_a, i_b cdef double x_a, x_b, y_a, y_b cdef double w cdef np.float64_t[:] max_ys = np.full(penetrable_limit+1, -INFINITY, dtype=np.float64) diff --git a/ts2vg/graph/_natural.pyx b/ts2vg/graph/_natural.pyx index 91593ca..3702e54 100644 --- a/ts2vg/graph/_natural.pyx +++ b/ts2vg/graph/_natural.pyx @@ -1,15 +1,18 @@ #cython: language_level=3 +#distutils: language=c++ cimport cython import numpy as np cimport numpy as np from libc.math cimport fabs, INFINITY +from libcpp.queue cimport queue as cqueue +from libcpp.pair cimport pair as cpair -from ts2vg.utils.pairqueue cimport PairQueue from ts2vg.graph.base import _DIRECTED_OPTIONS from ts2vg.graph._base cimport _greater, _argmax, _get_weight_func, weight_func_type ctypedef unsigned int uint +ctypedef cpair[uint, uint] uint_pair cdef double ABS_TOL = 1e-14 cdef double REL_TOL = 1e-14 @@ -36,8 +39,8 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w cdef weight_func_type weight_func = _get_weight_func(weighted) - cdef PairQueue queue = PairQueue() - queue.push((0, n)) + cdef cqueue[uint_pair] queue + queue.push(uint_pair(0, n)) def add_edge(uint i1, uint i2, double x1, double x2, double y1, double y2, double slope): w = weight_func(x1, x2, y1, y2, slope) @@ -55,8 +58,10 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w edges.append((i1, i2)) - while not queue.is_empty(): - (left, right) = queue.pop() + while not queue.empty(): + pair = queue.front() + left, right = pair.first, pair.second + queue.pop() if left+1 < right: i = _argmax(ts, left, right) @@ -94,7 +99,7 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w max_slope = slope - queue.push((left, i)) - queue.push((i+1, right)) + queue.push(uint_pair(left, i)) + queue.push(uint_pair(i+1, right)) return edges, np.asarray(degrees_in, dtype=np.uint32), np.asarray(degrees_out, dtype=np.uint32) diff --git a/ts2vg/graph/_natural_penetrable.pyx b/ts2vg/graph/_natural_penetrable.pyx index 23f7dfa..022b42b 100644 --- a/ts2vg/graph/_natural_penetrable.pyx +++ b/ts2vg/graph/_natural_penetrable.pyx @@ -40,7 +40,7 @@ def _compute_graph(np.float64_t[:] ts, np.float64_t[:] xs, uint directed, uint w cdef np.uint32_t[:] degrees_in = np.zeros(n, dtype=np.uint32) cdef np.uint32_t[:] degrees_out = np.zeros(n, dtype=np.uint32) - cdef uint left, right, i_a, i_b + cdef uint i_a, i_b cdef double x_a, x_b, y_a, y_b cdef double slope, w cdef np.float64_t[:] max_slopes = np.full(penetrable_limit+1, -INFINITY, dtype=np.float64) diff --git a/ts2vg/graph/base.py b/ts2vg/graph/base.py index ef72fce..912ea8b 100644 --- a/ts2vg/graph/base.py +++ b/ts2vg/graph/base.py @@ -403,7 +403,7 @@ def as_snap(self): from snap import TUNGraph, TNGraph if self.is_weighted: - raise ValueError("SNAP weighted graphs not currently supported.") + raise NotImplementedError("SNAP weighted graphs not currently supported.") if self.is_directed: g = TNGraph.New(self.n_vertices, self.n_edges) diff --git a/ts2vg/graph/horizontal.py b/ts2vg/graph/horizontal.py index 45c31ab..cdf7566 100644 --- a/ts2vg/graph/horizontal.py +++ b/ts2vg/graph/horizontal.py @@ -9,20 +9,20 @@ class HorizontalVG(VG): r""" Horizontal Visibility Graph. - Transform a time series to a Horizontal Visibility Graph. + Used to transform a time series to a Horizontal Visibility Graph. Parameters ---------- directed : str, None If ``None`` make an undirected graph, otherwise, a directed graph by using one of the following values: - ``left_to_right``, ``top_to_bottom``. + ``'left_to_right'``, ``'top_to_bottom'``. See :ref:`Directed graphs` for more information. Default ``None``. weighted : str, None If ``None`` make an unweighted graph, otherwise, a weighted graph by using one of the following values: - ``distance``, ``sq_distance``, ``v_distance``, ``abs_v_distance``, ``h_distance``, ``abs_h_distance``, - ``slope``, ``abs_slope``, ``angle``, ``abs_angle``. + ``'distance'``, ``'sq_distance'``, ``'v_distance'``, ``'abs_v_distance'``, ``'h_distance'``, ``'abs_h_distance'``, + ``'slope'``, ``'abs_slope'``, ``'angle'``, ``'abs_angle'``, ``'num_penetrations'``. See :ref:`Weighted graphs` for more information. Default ``None``. diff --git a/ts2vg/graph/natural.py b/ts2vg/graph/natural.py index 7c8aeec..085ca0a 100644 --- a/ts2vg/graph/natural.py +++ b/ts2vg/graph/natural.py @@ -9,20 +9,20 @@ class NaturalVG(VG): r""" Natural Visibility Graph. - Transform a time series to a Natural Visibility Graph. + Used to transform a time series to a Natural Visibility Graph. Parameters ---------- directed : str, None If ``None`` make an undirected graph, otherwise, a directed graph by using one of the following values: - ``left_to_right``, ``top_to_bottom``. + ``'left_to_right'``, ``'top_to_bottom'``. See :ref:`Directed graphs` for more information. Default ``None``. weighted : str, None If ``None`` make an unweighted graph, otherwise, a weighted graph by using one of the following values: - ``distance``, ``sq_distance``, ``v_distance``, ``abs_v_distance``, ``h_distance``, ``abs_h_distance``, - ``slope``, ``abs_slope``, ``angle``, ``abs_angle``. + ``'distance'``, ``'sq_distance'``, ``'v_distance'``, ``'abs_v_distance'``, ``'h_distance'``, ``'abs_h_distance'``, + ``'slope'``, ``'abs_slope'``, ``'angle'``, ``'abs_angle'``, ``'num_penetrations'``. See :ref:`Weighted graphs` for more information. Default ``None``. diff --git a/ts2vg/utils/__init__.py b/ts2vg/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ts2vg/utils/pairqueue.pxd b/ts2vg/utils/pairqueue.pxd deleted file mode 100644 index 6e40ccd..0000000 --- a/ts2vg/utils/pairqueue.pxd +++ /dev/null @@ -1,21 +0,0 @@ -#cython: language_level=3 - -from libc.stdlib cimport malloc, free - -cdef struct QueueEntry: - unsigned int a; - unsigned int b; - # QueueEntry *prev; - QueueEntry *next; - -cdef struct Queue: - QueueEntry *head; - QueueEntry *tail; - -cdef class PairQueue: - cdef Queue *queue - - cdef void push(self, (unsigned int, unsigned int) values) - cdef (unsigned int, unsigned int) pop(self) - cdef bint is_empty(self) - cdef void free(self) diff --git a/ts2vg/utils/pairqueue.pyx b/ts2vg/utils/pairqueue.pyx deleted file mode 100644 index 3528bcf..0000000 --- a/ts2vg/utils/pairqueue.pyx +++ /dev/null @@ -1,77 +0,0 @@ -cdef class PairQueue: - def __cinit__(self): - self.queue = malloc(sizeof(Queue)) - self.queue[0].head = NULL - self.queue[0].tail = NULL - - def __dealloc__(self): - self.free() - - cdef void push(self, (unsigned int, unsigned int) values): - # Create the new entry and fill in the fields in the structure - cdef QueueEntry *new_entry = malloc(sizeof(QueueEntry)) - - if new_entry is NULL: - raise MemoryError() - - new_entry[0].a = values[0] - new_entry[0].b = values[1] - # new_entry[0].prev = self.queue[0].tail - new_entry[0].next = NULL - - # Insert into the queue tail - if self.queue[0].tail is NULL: - # If the queue was previously empty, both the head and - # tail must be pointed at the new entry - self.queue[0].head = new_entry - self.queue[0].tail = new_entry - - else: - # The current entry at the tail must have next pointed to this - # new entry - self.queue[0].tail[0].next = new_entry - - # Only the tail must be pointed at the new entry - self.queue[0].tail = new_entry - - cdef (unsigned int, unsigned int) pop(self): - cdef QueueEntry *entry - cdef unsigned int result # QueueValue result; - - # Check the queue is not empty - if self.is_empty(): - raise IndexError("Queue is empty") - - # Unlink the first entry from the head of the queue - entry = self.queue[0].head - self.queue[0].head = entry[0].next - a = entry[0].a - b = entry[0].b - - if self.queue[0].head is NULL: - # If doing this has unlinked the last entry in the queue, set - # tail to NULL as well. - - self.queue[0].tail = NULL - # else: - # The new first in the queue has no previous entry - # self.queue[0].head[0].prev = NULL - - # Free back the queue entry structure - free(entry) - - return a, b - - cdef bint is_empty(self): - return self.queue[0].head is NULL - - def __bool__(self): - return not self.is_empty() - - cdef void free(self): - # Empty the queue - while not self.is_empty(): - self.pop() - - # Free back the queue - free(self.queue)