From 57ee56930f6791e3c06eeddc2114f01d20cfa95e Mon Sep 17 00:00:00 2001 From: mathleur Date: Mon, 16 Sep 2024 14:04:39 +0100 Subject: [PATCH 01/21] different debug levels --- polytope/datacube/backends/datacube.py | 2 +- polytope/datacube/backends/fdb.py | 5 +---- polytope/options.py | 1 - polytope/polytope.py | 3 +++ 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/polytope/datacube/backends/datacube.py b/polytope/datacube/backends/datacube.py index a1dfbfb30..4cd34c8a4 100644 --- a/polytope/datacube/backends/datacube.py +++ b/polytope/datacube/backends/datacube.py @@ -128,7 +128,7 @@ def get_indices(self, path: DatacubePath, axis, lower, upper, method=None): indexes = axis.find_indexes(path, self) idx_between = axis.find_indices_between(indexes, lower, upper, self, method) - logging.info(f"For axis {axis.name} between {lower} and {upper}, found indices {idx_between}") + logging.debug(f"For axis {axis.name} between {lower} and {upper}, found indices {idx_between}") return idx_between diff --git a/polytope/datacube/backends/fdb.py b/polytope/datacube/backends/fdb.py index fd10a130b..76a508be9 100644 --- a/polytope/datacube/backends/fdb.py +++ b/polytope/datacube/backends/fdb.py @@ -78,7 +78,6 @@ def check_branching_axes(self, request): self._axes.pop(axis_name, None) def get(self, requests: TensorIndexTree): - requests.pprint() if len(requests.children) == 0: return requests fdb_requests = [] @@ -124,7 +123,7 @@ def get_fdb_requests( # First when request node is root, go to its children if requests.axis.name == "root": - logging.info("Looking for data for the tree: %s", [leaf.flatten() for leaf in requests.leaves]) + logging.debug("Looking for data for the tree: %s", [leaf.flatten() for leaf in requests.leaves]) for c in requests.children: self.get_fdb_requests(c, fdb_requests, fdb_requests_decoding_info) @@ -325,8 +324,6 @@ def sort_fdb_request_ranges(self, current_start_idx, lat_length, fdb_node_ranges request_ranges_with_idx = list(enumerate(interm_request_ranges)) sorted_list = sorted(request_ranges_with_idx, key=lambda x: x[1][0]) original_indices, sorted_request_ranges = zip(*sorted_list) - logging.debug("We sorted the request ranges into: %s", sorted_request_ranges) - logging.debug("The sorted and unique leaf node ranges are: %s", new_fdb_node_ranges) return (original_indices, sorted_request_ranges, new_fdb_node_ranges) def datacube_natural_indexes(self, axis, subarray): diff --git a/polytope/options.py b/polytope/options.py index 78c7a20da..6baf8bc73 100644 --- a/polytope/options.py +++ b/polytope/options.py @@ -66,7 +66,6 @@ class Config(ConfigModel): class PolytopeOptions(ABC): @staticmethod def get_polytope_options(options): - parser = argparse.ArgumentParser(allow_abbrev=False) conflator = Conflator(app_name="polytope", model=Config, cli=False, argparser=parser, **options) config_options = conflator.load() diff --git a/polytope/polytope.py b/polytope/polytope.py index 271725471..908bbab8e 100644 --- a/polytope/polytope.py +++ b/polytope/polytope.py @@ -1,3 +1,4 @@ +import logging from typing import List from .options import PolytopeOptions @@ -59,5 +60,7 @@ def retrieve(self, request: Request, method="standard"): """Higher-level API which takes a request and uses it to slice the datacube""" self.datacube.check_branching_axes(request) request_tree = self.engine.extract(self.datacube, request.polytopes()) + logging.info("Sliced polytopes") self.datacube.get(request_tree) + logging.info("Retrieved data") return request_tree From 206cb95815731b192e9da5a79ec18b5d2b9543d6 Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 21 Aug 2024 15:29:03 +0200 Subject: [PATCH 02/21] add 'infrastructure' for md5 hash in the grid mapper --- .../transformations/datacube_mappers/datacube_mappers.py | 1 + .../transformations/datacube_mappers/mapper_types/healpix.py | 5 +++++ .../datacube_mappers/mapper_types/healpix_nested.py | 5 +++++ .../datacube_mappers/mapper_types/local_regular.py | 5 +++++ .../datacube_mappers/mapper_types/octahedral.py | 5 +++++ .../datacube_mappers/mapper_types/reduced_ll.py | 5 +++++ .../transformations/datacube_mappers/mapper_types/regular.py | 5 +++++ 7 files changed, 31 insertions(+) diff --git a/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py b/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py index f79d43de3..0cb0f238b 100644 --- a/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py +++ b/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py @@ -20,6 +20,7 @@ def __init__(self, name, mapper_options): self._final_mapped_axes = self._final_transformation._mapped_axes self._axis_reversed = self._final_transformation._axis_reversed self.compressed_grid_axes = self._final_transformation.compressed_grid_axes + self.md5_hash = self._final_transformation.md5_hash def generate_final_transformation(self): map_type = _type_to_datacube_mapper_lookup[self.grid_type] diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py index b198a157a..13f0699ec 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py @@ -13,6 +13,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False} self._first_axis_vals = self.first_axis_vals() self.compressed_grid_axes = [self._mapped_axes[1]] + self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): rad2deg = 180 / math.pi @@ -133,3 +134,7 @@ def unmap(self, first_val, second_val): second_idx = self.second_axis_vals(first_val).index(second_val) healpix_index = self.axes_idx_to_healpix_idx(first_idx, second_idx) return healpix_index + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py index abbd01392..2d7052175 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py @@ -17,6 +17,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self.k = int(math.log2(self.Nside)) self.Npix = 12 * self.Nside * self.Nside self.Ncap = (self.Nside * (self.Nside - 1)) << 1 + self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): rad2deg = 180 / math.pi @@ -211,3 +212,7 @@ def ring_to_nested(self, idx): def int_sqrt(self, i): return int(math.sqrt(i + 0.5)) + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py index 40f86bfbd..2221f5dd0 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py @@ -23,6 +23,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False} self._first_axis_vals = self.first_axis_vals() self.compressed_grid_axes = [self._mapped_axes[1]] + self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): first_ax_vals = [self._first_axis_max - i * self._first_deg_increment for i in range(self.first_resolution + 1)] @@ -68,3 +69,7 @@ def unmap(self, first_val, second_val): second_idx = self.second_axis_vals(first_val).index(second_val) final_index = self.axes_idx_to_regular_idx(first_idx, second_idx) return final_index + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py index f48fca712..f97ed7633 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py @@ -15,6 +15,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self._second_axis_spacing = {} self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False} self.compressed_grid_axes = [self._mapped_axes[1]] + self.md5_hash = md5_hash.get(resolution, None) def gauss_first_guess(self): i = 0 @@ -2750,3 +2751,7 @@ def unmap(self, first_val, second_val): (first_idx, second_idx) = self.find_second_axis_idx(first_val, second_val) octahedral_index = self.axes_idx_to_octahedral_idx(first_idx, second_idx) return octahedral_index + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py index 5a76f5d10..52fc10541 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py @@ -12,6 +12,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self._axis_reversed = {mapped_axes[0]: False, mapped_axes[1]: False} self._first_axis_vals = self.first_axis_vals() self.compressed_grid_axes = [self._mapped_axes[1]] + self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): resolution = 180 / (self._resolution - 1) @@ -1504,3 +1505,7 @@ def unmap(self, first_val, second_val): second_idx = self.second_axis_vals(first_val).index(second_val) reduced_ll_index = self.axes_idx_to_reduced_ll_idx(first_idx, second_idx) return reduced_ll_index + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py index 3b40f77e4..bb0243646 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py @@ -13,6 +13,7 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False} self._first_axis_vals = self.first_axis_vals() self.compressed_grid_axes = [self._mapped_axes[1]] + self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): first_ax_vals = [90 - i * self.deg_increment for i in range(2 * self._resolution)] @@ -56,3 +57,7 @@ def unmap(self, first_val, second_val): second_idx = self.second_axis_vals(first_val).index(second_val) final_index = self.axes_idx_to_regular_idx(first_idx, second_idx) return final_index + + +# md5 grid hash in form {resolution : hash} +md5_hash = {} From 5d07861376acd5bd45d948ab3d57ef54ecbff33a Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 21 Aug 2024 16:07:27 +0200 Subject: [PATCH 03/21] add octahedral O1280 md5 hash --- .../transformations/datacube_mappers/mapper_types/octahedral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py index f97ed7633..3c037df5d 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py @@ -2754,4 +2754,4 @@ def unmap(self, first_val, second_val): # md5 grid hash in form {resolution : hash} -md5_hash = {} +md5_hash = {1280: "158db321ae8e773681eeb40e0a3d350f", } From 76291807c331e35e0ed65fbffcb5cf57de56c6c3 Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 21 Aug 2024 17:13:34 +0200 Subject: [PATCH 04/21] black --- .../datacube_mappers/mapper_types/octahedral.py | 4 +++- polytope/options.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py index 3c037df5d..afc54db2c 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py @@ -2754,4 +2754,6 @@ def unmap(self, first_val, second_val): # md5 grid hash in form {resolution : hash} -md5_hash = {1280: "158db321ae8e773681eeb40e0a3d350f", } +md5_hash = { + 1280: "158db321ae8e773681eeb40e0a3d350f", +} diff --git a/polytope/options.py b/polytope/options.py index 78c7a20da..6baf8bc73 100644 --- a/polytope/options.py +++ b/polytope/options.py @@ -66,7 +66,6 @@ class Config(ConfigModel): class PolytopeOptions(ABC): @staticmethod def get_polytope_options(options): - parser = argparse.ArgumentParser(allow_abbrev=False) conflator = Conflator(app_name="polytope", model=Config, cli=False, argparser=parser, **options) config_options = conflator.load() From 1d40665ff7aa1eda723f7406af39d0162c07a9ce Mon Sep 17 00:00:00 2001 From: mathleur Date: Tue, 17 Sep 2024 10:52:10 +0100 Subject: [PATCH 05/21] add context information all the way through gj --- polytope/datacube/backends/datacube.py | 4 ++-- polytope/datacube/backends/fdb.py | 4 +++- polytope/datacube/backends/mock.py | 4 +++- polytope/datacube/backends/xarray.py | 8 +++++--- polytope/polytope.py | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/polytope/datacube/backends/datacube.py b/polytope/datacube/backends/datacube.py index a1dfbfb30..eb13cebe9 100644 --- a/polytope/datacube/backends/datacube.py +++ b/polytope/datacube/backends/datacube.py @@ -1,6 +1,6 @@ import logging from abc import ABC, abstractmethod -from typing import Any +from typing import Any, Dict from ...utility.combinatorics import validate_axes from ..datacube_axis import DatacubeAxis @@ -33,7 +33,7 @@ def __init__(self, axis_options=None, compressed_axes_options=[]): self.compressed_axes = compressed_axes_options @abstractmethod - def get(self, requests: TensorIndexTree) -> Any: + def get(self, requests: TensorIndexTree, context: Dict) -> Any: """Return data given a set of request trees""" @property diff --git a/polytope/datacube/backends/fdb.py b/polytope/datacube/backends/fdb.py index fd10a130b..496249f1c 100644 --- a/polytope/datacube/backends/fdb.py +++ b/polytope/datacube/backends/fdb.py @@ -77,7 +77,9 @@ def check_branching_axes(self, request): for axis_name in axes_to_remove: self._axes.pop(axis_name, None) - def get(self, requests: TensorIndexTree): + def get(self, requests: TensorIndexTree, context=None): + if context is None: + context = {} requests.pprint() if len(requests.children) == 0: return requests diff --git a/polytope/datacube/backends/mock.py b/polytope/datacube/backends/mock.py index 1a48360eb..c23b80802 100644 --- a/polytope/datacube/backends/mock.py +++ b/polytope/datacube/backends/mock.py @@ -24,10 +24,12 @@ def __init__(self, dimensions, compressed_axes_options=[]): self.stride[k] = stride_cumulative stride_cumulative *= self.dimensions[k] - def get(self, requests: TensorIndexTree): + def get(self, requests: TensorIndexTree, context=None): # Takes in a datacube and verifies the leaves of the tree are complete # (ie it found values for all datacube axis) + if context is None: + context = {} for r in requests.leaves: path = r.flatten() if len(path.items()) == len(self.dimensions.items()): diff --git a/polytope/datacube/backends/xarray.py b/polytope/datacube/backends/xarray.py index c01ea52bc..2b7d579de 100644 --- a/polytope/datacube/backends/xarray.py +++ b/polytope/datacube/backends/xarray.py @@ -50,12 +50,14 @@ def __init__(self, dataarray: xr.DataArray, axis_options=None, compressed_axes_o val = self._axes[name].type self._check_and_add_axes(options, name, val) - def get(self, requests, leaf_path=None, axis_counter=0): + def get(self, requests, context=None, leaf_path=None, axis_counter=0): + if context is None: + context = {} if leaf_path is None: leaf_path = {} if requests.axis.name == "root": for c in requests.children: - self.get(c, leaf_path, axis_counter + 1) + self.get(c, context, leaf_path, axis_counter + 1) else: key_value_path = {requests.axis.name: requests.values} ax = requests.axis @@ -66,7 +68,7 @@ def get(self, requests, leaf_path=None, axis_counter=0): if len(requests.children) != 0: # We are not a leaf and we loop over for c in requests.children: - self.get(c, leaf_path, axis_counter + 1) + self.get(c, context, leaf_path, axis_counter + 1) else: if self.axis_counter != axis_counter: requests.remove_branch() diff --git a/polytope/polytope.py b/polytope/polytope.py index 271725471..3fd4568d7 100644 --- a/polytope/polytope.py +++ b/polytope/polytope.py @@ -55,9 +55,11 @@ def slice(self, polytopes: List[ConvexPolytope]): """Low-level API which takes a polytope geometry object and uses it to slice the datacube""" return self.engine.extract(self.datacube, polytopes) - def retrieve(self, request: Request, method="standard"): + def retrieve(self, request: Request, method="standard", context=None): """Higher-level API which takes a request and uses it to slice the datacube""" + if context is None: + context = {} self.datacube.check_branching_axes(request) request_tree = self.engine.extract(self.datacube, request.polytopes()) - self.datacube.get(request_tree) + self.datacube.get(request_tree, context) return request_tree From ae27f70dc0fe0964a45aae8a1b5903e4e85ebfa9 Mon Sep 17 00:00:00 2001 From: mathleur Date: Tue, 17 Sep 2024 15:34:58 +0100 Subject: [PATCH 06/21] send grid hash and context to gribjump --- polytope/datacube/backends/datacube.py | 2 ++ polytope/datacube/backends/fdb.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/polytope/datacube/backends/datacube.py b/polytope/datacube/backends/datacube.py index eb13cebe9..ce42a76ec 100644 --- a/polytope/datacube/backends/datacube.py +++ b/polytope/datacube/backends/datacube.py @@ -31,6 +31,7 @@ def __init__(self, axis_options=None, compressed_axes_options=[]): self.merged_axes = [] self.unwanted_path = {} self.compressed_axes = compressed_axes_options + self.grid_md5_hash = None @abstractmethod def get(self, requests: TensorIndexTree, context: Dict) -> Any: @@ -69,6 +70,7 @@ def _create_axes(self, name, values, transformation_type_key, transformation_opt # TODO: do we use this?? This shouldn't work for a disk in lat/lon on a octahedral or other grid?? for compressed_grid_axis in transformation.compressed_grid_axes: self.compressed_grid_axes.append(compressed_grid_axis) + self.grid_md5_hash = transformation.md5_hash if len(final_axis_names) > 1: self.coupled_axes.append(final_axis_names) for axis in final_axis_names: diff --git a/polytope/datacube/backends/fdb.py b/polytope/datacube/backends/fdb.py index 496249f1c..8a3dce436 100644 --- a/polytope/datacube/backends/fdb.py +++ b/polytope/datacube/backends/fdb.py @@ -106,13 +106,13 @@ def get(self, requests: TensorIndexTree, context=None): uncompressed_request = {} for i, key in enumerate(compressed_request[0].keys()): uncompressed_request[key] = combi[i] - complete_uncompressed_request = (uncompressed_request, compressed_request[1]) + complete_uncompressed_request = (uncompressed_request, compressed_request[1], self.grid_md5_hash) complete_list_complete_uncompressed_requests.append(complete_uncompressed_request) complete_fdb_decoding_info.append(fdb_requests_decoding_info[j]) logging.debug("The requests we give GribJump are: %s", complete_list_complete_uncompressed_requests) output_values = self.gj.extract(complete_list_complete_uncompressed_requests) logging.debug("GribJump outputs: %s", output_values) - self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info) + self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info, context) def get_fdb_requests( self, From 2c03e4c10a91ee171d5d895ea6f942137f8d884b Mon Sep 17 00:00:00 2001 From: mathleur Date: Tue, 17 Sep 2024 15:37:58 +0100 Subject: [PATCH 07/21] add context logging --- polytope/polytope.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/polytope/polytope.py b/polytope/polytope.py index 3fd4568d7..21513dd67 100644 --- a/polytope/polytope.py +++ b/polytope/polytope.py @@ -1,4 +1,5 @@ from typing import List +import logging from .options import PolytopeOptions from .shapes import ConvexPolytope @@ -59,7 +60,10 @@ def retrieve(self, request: Request, method="standard", context=None): """Higher-level API which takes a request and uses it to slice the datacube""" if context is None: context = {} + logging.info("Starting request for %s ", context) self.datacube.check_branching_axes(request) request_tree = self.engine.extract(self.datacube, request.polytopes()) + logging.info("Created request tree for %s ", context) self.datacube.get(request_tree, context) + logging.info("Retrieved data for %s ", context) return request_tree From dbc186fc84fa8b66f3e6743226e6b5d54502a917 Mon Sep 17 00:00:00 2001 From: mathleur Date: Tue, 17 Sep 2024 15:55:06 +0100 Subject: [PATCH 08/21] fix md5 hash of grid when the grid can have multiple resolutions in lat/lon --- .../datacube_mappers/mapper_types/local_regular.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py b/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py index 2221f5dd0..44d46998f 100644 --- a/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +++ b/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py @@ -15,15 +15,16 @@ def __init__(self, base_axis, mapped_axes, resolution, local_area=[]): if not isinstance(resolution, list): self.first_resolution = resolution self.second_resolution = resolution + self.md5_hash = md5_hash.get(resolution, None) else: self.first_resolution = resolution[0] self.second_resolution = resolution[1] + self.md5_hash = md5_hash.get(tuple(resolution), None) self._first_deg_increment = (local_area[1] - local_area[0]) / self.first_resolution self._second_deg_increment = (local_area[3] - local_area[2]) / self.second_resolution self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False} self._first_axis_vals = self.first_axis_vals() self.compressed_grid_axes = [self._mapped_axes[1]] - self.md5_hash = md5_hash.get(resolution, None) def first_axis_vals(self): first_ax_vals = [self._first_axis_max - i * self._first_deg_increment for i in range(self.first_resolution + 1)] From 05f1c4c9f19ff5f9328f3b8c1334f91b53c75b50 Mon Sep 17 00:00:00 2001 From: mathleur Date: Tue, 17 Sep 2024 16:03:32 +0100 Subject: [PATCH 09/21] isort --- polytope/polytope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polytope/polytope.py b/polytope/polytope.py index 21513dd67..29a03d631 100644 --- a/polytope/polytope.py +++ b/polytope/polytope.py @@ -1,5 +1,5 @@ -from typing import List import logging +from typing import List from .options import PolytopeOptions from .shapes import ConvexPolytope From cbd952449b26bfce4805ca5fc074e6ef8abe06dc Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 18 Sep 2024 14:01:07 +0100 Subject: [PATCH 10/21] fix to give context to gj.extract --- polytope/datacube/backends/fdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/polytope/datacube/backends/fdb.py b/polytope/datacube/backends/fdb.py index 8a3dce436..95249552a 100644 --- a/polytope/datacube/backends/fdb.py +++ b/polytope/datacube/backends/fdb.py @@ -110,9 +110,9 @@ def get(self, requests: TensorIndexTree, context=None): complete_list_complete_uncompressed_requests.append(complete_uncompressed_request) complete_fdb_decoding_info.append(fdb_requests_decoding_info[j]) logging.debug("The requests we give GribJump are: %s", complete_list_complete_uncompressed_requests) - output_values = self.gj.extract(complete_list_complete_uncompressed_requests) + output_values = self.gj.extract(complete_list_complete_uncompressed_requests, context) logging.debug("GribJump outputs: %s", output_values) - self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info, context) + self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info) def get_fdb_requests( self, From 34e550fe6da7d2a6dae0217c41527c84e3610434 Mon Sep 17 00:00:00 2001 From: mathleur Date: Mon, 16 Sep 2024 14:47:26 +0100 Subject: [PATCH 11/21] make sure all values in the tree even in compressed leaves are ordered --- polytope/datacube/tensor_index_tree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/polytope/datacube/tensor_index_tree.py b/polytope/datacube/tensor_index_tree.py index 5cb813b99..604aa7441 100644 --- a/polytope/datacube/tensor_index_tree.py +++ b/polytope/datacube/tensor_index_tree.py @@ -105,6 +105,7 @@ def add_child(self, node): def add_value(self, value): new_values = list(self.values) new_values.append(value) + new_values.sort() self.values = tuple(new_values) def create_child(self, axis, value, next_nodes): From 2466c6064888684191fb756aaf33e9c5ea4847dc Mon Sep 17 00:00:00 2001 From: mathleur Date: Fri, 9 Aug 2024 14:10:36 +0200 Subject: [PATCH 12/21] refactor --- polytope/engine/hullslicer.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/polytope/engine/hullslicer.py b/polytope/engine/hullslicer.py index 767f89161..2c722b805 100644 --- a/polytope/engine/hullslicer.py +++ b/polytope/engine/hullslicer.py @@ -110,7 +110,7 @@ def remap_values(self, ax, value): def _build_sliceable_child(self, polytope, ax, node, datacube, values, next_nodes, slice_axis_idx): for i, value in enumerate(values): - if i == 0: + if i == 0 or ax.name not in self.compressed_axes: fvalue = ax.to_float(value) new_polytope = slice(polytope, ax.name, fvalue, slice_axis_idx) remapped_val = self.remap_values(ax, value) @@ -121,19 +121,8 @@ def _build_sliceable_child(self, polytope, ax, node, datacube, values, next_node child["unsliced_polytopes"].add(new_polytope) next_nodes.append(child) else: - if ax.name not in self.compressed_axes: - fvalue = ax.to_float(value) - new_polytope = slice(polytope, ax.name, fvalue, slice_axis_idx) - remapped_val = self.remap_values(ax, value) - (child, next_nodes) = node.create_child(ax, remapped_val, next_nodes) - child["unsliced_polytopes"] = copy(node["unsliced_polytopes"]) - child["unsliced_polytopes"].remove(polytope) - if new_polytope is not None: - child["unsliced_polytopes"].add(new_polytope) - next_nodes.append(child) - else: - remapped_val = self.remap_values(ax, value) - child.add_value(remapped_val) + remapped_val = self.remap_values(ax, value) + child.add_value(remapped_val) def _build_branch(self, ax, node, datacube, next_nodes): if ax.name not in self.compressed_axes: From 41f6fd76830cc8519e001d8a04a7ffcb3748b970 Mon Sep 17 00:00:00 2001 From: mathleur Date: Fri, 9 Aug 2024 14:12:47 +0200 Subject: [PATCH 13/21] clean up --- polytope/engine/hullslicer.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/polytope/engine/hullslicer.py b/polytope/engine/hullslicer.py index 2c722b805..20257bba7 100644 --- a/polytope/engine/hullslicer.py +++ b/polytope/engine/hullslicer.py @@ -131,26 +131,23 @@ def _build_branch(self, ax, node, datacube, next_nodes): for polytope in node["unsliced_polytopes"]: if ax.name in polytope._axes: right_unsliced_polytopes.append(polytope) - # for polytope in node["unsliced_polytopes"]: for i, polytope in enumerate(right_unsliced_polytopes): node._parent = parent_node - # if ax.name in polytope._axes: - if True: - lower, upper, slice_axis_idx = polytope.extents(ax.name) - # here, first check if the axis is an unsliceable axis and directly build node if it is - # NOTE: we should have already created the ax_is_unsliceable cache before - if self.ax_is_unsliceable[ax.name]: - self._build_unsliceable_child(polytope, ax, node, datacube, [lower], next_nodes, slice_axis_idx) - else: - values = self.find_values_between(polytope, ax, node, datacube, lower, upper) - # NOTE: need to only remove the branches if the values are empty, - # but only if there are no other possible children left in the tree that - # we can append and if somehow this happens before and we need to remove, then what do we do?? - if i == len(right_unsliced_polytopes) - 1: - # we have iterated all polytopes and we can now remove the node if we need to - if len(values) == 0 and len(node.children) == 0: - node.remove_branch() - self._build_sliceable_child(polytope, ax, node, datacube, values, next_nodes, slice_axis_idx) + lower, upper, slice_axis_idx = polytope.extents(ax.name) + # here, first check if the axis is an unsliceable axis and directly build node if it is + # NOTE: we should have already created the ax_is_unsliceable cache before + if self.ax_is_unsliceable[ax.name]: + self._build_unsliceable_child(polytope, ax, node, datacube, [lower], next_nodes, slice_axis_idx) + else: + values = self.find_values_between(polytope, ax, node, datacube, lower, upper) + # NOTE: need to only remove the branches if the values are empty, + # but only if there are no other possible children left in the tree that + # we can append and if somehow this happens before and we need to remove, then what do we do?? + if i == len(right_unsliced_polytopes) - 1: + # we have iterated all polytopes and we can now remove the node if we need to + if len(values) == 0 and len(node.children) == 0: + node.remove_branch() + self._build_sliceable_child(polytope, ax, node, datacube, values, next_nodes, slice_axis_idx) else: all_values = [] all_lowers = [] From 32168a1ec2afe91882ac82fb456a48ed35ca0c55 Mon Sep 17 00:00:00 2001 From: mathleur Date: Fri, 9 Aug 2024 14:20:25 +0200 Subject: [PATCH 14/21] clean up fdb backend --- polytope/datacube/backends/fdb.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/polytope/datacube/backends/fdb.py b/polytope/datacube/backends/fdb.py index 23242ab1d..cc427f566 100644 --- a/polytope/datacube/backends/fdb.py +++ b/polytope/datacube/backends/fdb.py @@ -163,8 +163,8 @@ def remove_duplicates_in_request_ranges(self, fdb_node_ranges, current_start_idx new_current_start_idx = [] for j, idx in enumerate(sub_lat_idxs): if idx not in seen_indices: - # TODO: need to remove it from the values in the corresponding tree node - # TODO: need to read just the range we give to gj ... DONE? + # NOTE: need to remove it from the values in the corresponding tree node + # NOTE: need to read just the range we give to gj original_fdb_node_range_vals.append(actual_fdb_node[0].values[j]) seen_indices.add(idx) new_current_start_idx.append(idx) @@ -189,8 +189,6 @@ def nearest_lat_lon_search(self, requests): second_ax = requests.children[0].children[0].axis - # TODO: actually, here we should not remap the nearest_pts, we should instead unmap the - # found_latlon_pts and then remap them later once we have compared found_latlon_pts and nearest_pts nearest_pts = [ [lat_val, second_ax._remap_val_to_axis_range(lon_val)] for (lat_val, lon_val) in zip( From 69a985970ba2af31228cd9f724b85fac1ba83282 Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 21 Aug 2024 11:59:11 +0200 Subject: [PATCH 15/21] change merger --- .../datacube/transformations/datacube_merger/datacube_merger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/polytope/datacube/transformations/datacube_merger/datacube_merger.py b/polytope/datacube/transformations/datacube_merger/datacube_merger.py index 717237a33..5b0516638 100644 --- a/polytope/datacube/transformations/datacube_merger/datacube_merger.py +++ b/polytope/datacube/transformations/datacube_merger/datacube_merger.py @@ -93,6 +93,5 @@ def unmap_tree_node(self, node, unwanted_path): if node.axis.name == self._first_axis: (new_first_vals, new_second_vals) = self.unmerge(node.values) node.values = new_first_vals - # TODO: actually need to give the second axis of the transformation to get the interm axis interm_node = node.add_node_layer_after(self._second_axis, new_second_vals) return (interm_node, unwanted_path) From 4d46064c271c9959c5d455990b6a6227d0a94158 Mon Sep 17 00:00:00 2001 From: Mathilde Leuridan <90444327+mathleur@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:13:35 +0200 Subject: [PATCH 16/21] Delete serializedTree --- serializedTree | Bin 470272 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 serializedTree diff --git a/serializedTree b/serializedTree deleted file mode 100644 index 4bb27cb8d709e9cdd7d730d661484f06386cb52e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470272 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 Date: Wed, 18 Sep 2024 13:27:06 +0000 Subject: [PATCH 17/21] create base polytope error --- polytope/utility/exceptions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/polytope/utility/exceptions.py b/polytope/utility/exceptions.py index fb30f627d..deb19afc4 100644 --- a/polytope/utility/exceptions.py +++ b/polytope/utility/exceptions.py @@ -1,4 +1,7 @@ -class AxisOverdefinedError(KeyError): +class PolytopeError(Exception): + pass + +class AxisOverdefinedError(PolytopeError): def __init__(self, axis): self.axis = axis self.message = ( @@ -7,19 +10,19 @@ def __init__(self, axis): ) -class AxisUnderdefinedError(KeyError): +class AxisUnderdefinedError(PolytopeError): def __init__(self, axis): self.axis = axis self.message = f"Axis {axis} is underdefined. It does not appear in any input polytope." -class AxisNotFoundError(KeyError): +class AxisNotFoundError(PolytopeError): def __init__(self, axis): self.axis = axis self.message = f"Axis {axis} does not exist in the datacube." -class UnsliceableShapeError(KeyError): +class UnsliceableShapeError(PolytopeError): def __init__(self, axis): self.axis = axis self.message = f"Higher-dimensional shape does not support unsliceable axis {axis.name}." From 9ae97edcdf11e5ed8f42eaf86e8de677ce0a83ca Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 18 Sep 2024 14:33:13 +0100 Subject: [PATCH 18/21] fix tests --- tests/test_cyclic_axis_over_negative_vals.py | 40 ++++++------ tests/test_cyclic_axis_slicer_not_0.py | 40 ++++++------ tests/test_cyclic_axis_slicing.py | 66 ++++++++++---------- tests/test_cyclic_simple.py | 2 +- tests/test_cyclic_snapping.py | 6 +- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/tests/test_cyclic_axis_over_negative_vals.py b/tests/test_cyclic_axis_over_negative_vals.py index 24d30b636..b95a044ff 100644 --- a/tests/test_cyclic_axis_over_negative_vals.py +++ b/tests/test_cyclic_axis_over_negative_vals.py @@ -40,7 +40,6 @@ def test_cyclic_float_axis_across_seam(self): result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ - (-0.2,), (-1.1,), (-1.0,), (-0.9,), @@ -50,6 +49,7 @@ def test_cyclic_float_axis_across_seam(self): (-0.5,), (-0.4,), (-0.3,), + (-0.2,), ] def test_cyclic_float_axis_inside_cyclic_range(self): @@ -93,31 +93,31 @@ def test_cyclic_float_axis_two_range_loops(self): result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ - (-0.7,), - (-0.6,), - (-0.5,), - (-0.4,), - (-0.3,), - (-0.2,), (-1.1,), - (-1.0,), - (-0.9,), - (-0.8,), - (-0.7,), - (-0.6,), - (-0.5,), - (-0.4,), - (-0.3,), - (-0.2,), (-1.1,), (-1.0,), + (-1.0,), (-0.9,), + (-0.9,), + (-0.8,), (-0.8,), (-0.7,), + (-0.7,), + (-0.7,), + (-0.6,), (-0.6,), + (-0.6,), + (-0.5,), (-0.5,), + (-0.5,), + (-0.4,), + (-0.4,), (-0.4,), (-0.3,), + (-0.3,), + (-0.3,), + (-0.2,), + (-0.2,), ] def test_cyclic_float_axis_below_axis_range(self): @@ -142,6 +142,10 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): # result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ + (-1.0,), + (-0.9,), + (-0.8,), + (-0.7,), (-0.7,), (-0.6,), (-0.5,), @@ -149,8 +153,4 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): (-0.3,), (-0.2,), (-0.1,), - (-1.0,), - (-0.9,), - (-0.8,), - (-0.7,), ] diff --git a/tests/test_cyclic_axis_slicer_not_0.py b/tests/test_cyclic_axis_slicer_not_0.py index 5a117c8e3..ec041263d 100644 --- a/tests/test_cyclic_axis_slicer_not_0.py +++ b/tests/test_cyclic_axis_slicer_not_0.py @@ -44,7 +44,6 @@ def test_cyclic_float_axis_across_seam(self): result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ - (-0.2,), (-1.1,), (-1.0,), (-0.9,), @@ -54,6 +53,7 @@ def test_cyclic_float_axis_across_seam(self): (-0.5,), (-0.4,), (-0.3,), + (-0.2,), ] def test_cyclic_float_axis_inside_cyclic_range(self): @@ -94,31 +94,31 @@ def test_cyclic_float_axis_two_range_loops(self): result = self.API.retrieve(request) assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ - (-0.7,), - (-0.6,), - (-0.5,), - (-0.4,), - (-0.3,), - (-0.2,), (-1.1,), - (-1.0,), - (-0.9,), - (-0.8,), - (-0.7,), - (-0.6,), - (-0.5,), - (-0.4,), - (-0.3,), - (-0.2,), (-1.1,), (-1.0,), + (-1.0,), (-0.9,), + (-0.9,), + (-0.8,), (-0.8,), (-0.7,), + (-0.7,), + (-0.7,), + (-0.6,), (-0.6,), + (-0.6,), + (-0.5,), (-0.5,), + (-0.5,), + (-0.4,), + (-0.4,), (-0.4,), (-0.3,), + (-0.3,), + (-0.3,), + (-0.2,), + (-0.2,), ] def test_cyclic_float_axis_below_axis_range(self): @@ -142,6 +142,10 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): result = self.API.retrieve(request) assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ + (-1.0,), + (-0.9,), + (-0.8,), + (-0.7,), (-0.7,), (-0.6,), (-0.5,), @@ -149,8 +153,4 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): (-0.3,), (-0.2,), (-0.1,), - (-1.0,), - (-0.9,), - (-0.8,), - (-0.7,), ] diff --git a/tests/test_cyclic_axis_slicing.py b/tests/test_cyclic_axis_slicing.py index 2ad154315..3bccf2801 100644 --- a/tests/test_cyclic_axis_slicing.py +++ b/tests/test_cyclic_axis_slicing.py @@ -44,9 +44,6 @@ def test_cyclic_float_axis_across_seam(self): assert len(result.leaves) == 1 result.pprint() assert [(val,) for val in result.leaves[0].values] == [ - (0.8,), - (0.9,), - (1.0,), (0.1,), (0.2,), (0.3,), @@ -54,6 +51,9 @@ def test_cyclic_float_axis_across_seam(self): (0.5,), (0.6,), (0.7,), + (0.8,), + (0.9,), + (1.0,), ] def test_cyclic_float_axis_across_seam_repeated(self): @@ -85,27 +85,27 @@ def test_cyclic_float_axis_across_seam_repeated_twice(self): result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ + (0.0,), (0.0,), (0.1,), - (0.2,), - (0.3,), - (0.4,), - (0.5,), - (0.6,), - (0.7,), - (0.8,), - (0.9,), - (1.0,), (0.1,), (0.2,), + (0.2,), (0.3,), + (0.3,), + (0.4,), (0.4,), (0.5,), + (0.5,), + (0.6,), (0.6,), (0.7,), + (0.7,), + (0.8,), (0.8,), (0.9,), - (0.0,), + (0.9,), + (1.0,), ] def test_cyclic_float_axis_inside_cyclic_range(self): @@ -151,31 +151,31 @@ def test_cyclic_float_axis_two_range_loops(self): # result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ - (0.3,), - (0.4,), - (0.5,), - (0.6,), - (0.7,), - (0.8,), - (0.9,), - (1.0,), + (0.0,), + (0.1,), (0.1,), (0.2,), + (0.2,), + (0.3,), (0.3,), + (0.3,), + (0.4,), (0.4,), + (0.4,), + (0.5,), (0.5,), + (0.5,), + (0.6,), (0.6,), + (0.6,), + (0.7,), (0.7,), + (0.7,), + (0.8,), (0.8,), (0.9,), - (0.0,), - (0.1,), - (0.2,), - (0.3,), - (0.4,), - (0.5,), - (0.6,), - (0.7,), + (0.9,), + (1.0,), ] def test_cyclic_float_axis_below_axis_range(self): @@ -201,6 +201,10 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): # result.pprint() assert len(result.leaves) == 1 assert [(val,) for val in result.leaves[0].values] == [ + (0.0,), + (0.1,), + (0.2,), + (0.3,), (0.3,), (0.4,), (0.5,), @@ -208,10 +212,6 @@ def test_cyclic_float_axis_below_axis_range_crossing_seam(self): (0.7,), (0.8,), (0.9,), - (0.0,), - (0.1,), - (0.2,), - (0.3,), ] def test_cyclic_float_axis_reversed(self): diff --git a/tests/test_cyclic_simple.py b/tests/test_cyclic_simple.py index d12e45413..9913872cc 100644 --- a/tests/test_cyclic_simple.py +++ b/tests/test_cyclic_simple.py @@ -43,7 +43,7 @@ def test_cyclic_float_axis_across_seam(self): result = self.API.retrieve(request) result.pprint() assert len(result.leaves) == 1 - assert [leaf.values for leaf in result.leaves] == [(0.9, 1.0, 0.1, 0.2)] + assert [leaf.values for leaf in result.leaves] == [(0.1, 0.2, 0.9, 1.0)] def test_cyclic_float_surrounding(self): request = Request( diff --git a/tests/test_cyclic_snapping.py b/tests/test_cyclic_snapping.py index f81c65405..ac149db2d 100644 --- a/tests/test_cyclic_snapping.py +++ b/tests/test_cyclic_snapping.py @@ -30,7 +30,7 @@ def test_cyclic_float_axis_across_seam(self): result = self.API.retrieve(request) result.pprint() assert len(result.leaves) == 1 - assert result.leaves[0].flatten()["long"] == (0.5, 0.0) + assert result.leaves[0].flatten()["long"] == (0.0, 0.5) assert result.leaves[0].result[0] is None - assert result.leaves[0].result[1][0] == 1 - assert result.leaves[0].result[1][1] == 0 + assert result.leaves[0].result[1][0] == 0 + assert result.leaves[0].result[1][1] == 1 From 50a6359447002b94e6d0734cc59157f677e80dd3 Mon Sep 17 00:00:00 2001 From: Peter Tsrunchev Date: Wed, 18 Sep 2024 13:35:01 +0000 Subject: [PATCH 19/21] formatting --- polytope/utility/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/polytope/utility/exceptions.py b/polytope/utility/exceptions.py index deb19afc4..880b054eb 100644 --- a/polytope/utility/exceptions.py +++ b/polytope/utility/exceptions.py @@ -1,6 +1,7 @@ class PolytopeError(Exception): pass + class AxisOverdefinedError(PolytopeError): def __init__(self, axis): self.axis = axis From e0a33d0f994609e6183fa90d0781636a39e84d04 Mon Sep 17 00:00:00 2001 From: Peter Tsrunchev Date: Wed, 18 Sep 2024 13:43:09 +0000 Subject: [PATCH 20/21] still key errors --- polytope/utility/exceptions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/polytope/utility/exceptions.py b/polytope/utility/exceptions.py index 880b054eb..b9b25e0fb 100644 --- a/polytope/utility/exceptions.py +++ b/polytope/utility/exceptions.py @@ -2,7 +2,7 @@ class PolytopeError(Exception): pass -class AxisOverdefinedError(PolytopeError): +class AxisOverdefinedError(PolytopeError, KeyError): def __init__(self, axis): self.axis = axis self.message = ( @@ -11,19 +11,19 @@ def __init__(self, axis): ) -class AxisUnderdefinedError(PolytopeError): +class AxisUnderdefinedError(PolytopeError, KeyError): def __init__(self, axis): self.axis = axis self.message = f"Axis {axis} is underdefined. It does not appear in any input polytope." -class AxisNotFoundError(PolytopeError): +class AxisNotFoundError(PolytopeError, KeyError): def __init__(self, axis): self.axis = axis self.message = f"Axis {axis} does not exist in the datacube." -class UnsliceableShapeError(PolytopeError): +class UnsliceableShapeError(PolytopeError, KeyError): def __init__(self, axis): self.axis = axis self.message = f"Higher-dimensional shape does not support unsliceable axis {axis.name}." From e0f1e39be1c92f422569053474f1a03827cdbe7c Mon Sep 17 00:00:00 2001 From: mathleur Date: Wed, 18 Sep 2024 15:30:45 +0100 Subject: [PATCH 21/21] bump version --- polytope/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polytope/version.py b/polytope/version.py index 382021f30..9e604c040 100644 --- a/polytope/version.py +++ b/polytope/version.py @@ -1 +1 @@ -__version__ = "1.0.6" +__version__ = "1.0.7"