polytope-python 1.0.5__tar.gz → 1.0.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {polytope-python-1.0.5/polytope_python.egg-info → polytope-python-1.0.7}/PKG-INFO +1 -1
  2. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/backends/datacube.py +5 -3
  3. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/backends/fdb.py +9 -10
  4. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/backends/mock.py +3 -1
  5. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/backends/xarray.py +5 -3
  6. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/tensor_index_tree.py +8 -3
  7. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py +1 -0
  8. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py +5 -0
  9. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +5 -0
  10. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +6 -0
  11. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +7 -0
  12. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +5 -0
  13. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py +5 -0
  14. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_merger/datacube_merger.py +0 -1
  15. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/engine/hullslicer.py +27 -25
  16. polytope-python-1.0.7/polytope/options.py +78 -0
  17. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/polytope.py +8 -2
  18. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/exceptions.py +8 -4
  19. polytope-python-1.0.7/polytope/version.py +1 -0
  20. {polytope-python-1.0.5 → polytope-python-1.0.7/polytope_python.egg-info}/PKG-INFO +1 -1
  21. polytope-python-1.0.5/polytope/options.py +0 -67
  22. polytope-python-1.0.5/polytope/version.py +0 -1
  23. {polytope-python-1.0.5 → polytope-python-1.0.7}/LICENSE +0 -0
  24. {polytope-python-1.0.5 → polytope-python-1.0.7}/MANIFEST.in +0 -0
  25. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/__init__.py +0 -0
  26. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/__init__.py +0 -0
  27. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/backends/__init__.py +0 -0
  28. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/datacube_axis.py +0 -0
  29. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/index_tree_pb2.py +0 -0
  30. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/__init__.py +0 -0
  31. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_cyclic/__init__.py +0 -0
  32. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_cyclic/datacube_cyclic.py +0 -0
  33. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/__init__.py +0 -0
  34. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_mappers/mapper_types/__init__.py +0 -0
  35. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_merger/__init__.py +0 -0
  36. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_reverse/__init__.py +0 -0
  37. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_reverse/datacube_reverse.py +0 -0
  38. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_transformations.py +0 -0
  39. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_type_change/__init__.py +0 -0
  40. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/transformations/datacube_type_change/datacube_type_change.py +0 -0
  41. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/datacube/tree_encoding.py +0 -0
  42. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/engine/__init__.py +0 -0
  43. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/engine/engine.py +0 -0
  44. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/shapes.py +0 -0
  45. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/__init__.py +0 -0
  46. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/combinatorics.py +0 -0
  47. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/geometry.py +0 -0
  48. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/list_tools.py +0 -0
  49. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope/utility/profiling.py +0 -0
  50. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope_python.egg-info/SOURCES.txt +0 -0
  51. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope_python.egg-info/dependency_links.txt +0 -0
  52. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope_python.egg-info/not-zip-safe +0 -0
  53. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope_python.egg-info/requires.txt +0 -0
  54. {polytope-python-1.0.5 → polytope-python-1.0.7}/polytope_python.egg-info/top_level.txt +0 -0
  55. {polytope-python-1.0.5 → polytope-python-1.0.7}/pyproject.toml +0 -0
  56. {polytope-python-1.0.5 → polytope-python-1.0.7}/requirements.txt +0 -0
  57. {polytope-python-1.0.5 → polytope-python-1.0.7}/setup.cfg +0 -0
  58. {polytope-python-1.0.5 → polytope-python-1.0.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: polytope-python
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Polytope datacube feature extraction library
5
5
  Home-page: https://github.com/ecmwf/polytope
6
6
  Author: ECMWF
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from abc import ABC, abstractmethod
3
- from typing import Any
3
+ from typing import Any, Dict
4
4
 
5
5
  from ...utility.combinatorics import validate_axes
6
6
  from ..datacube_axis import DatacubeAxis
@@ -31,9 +31,10 @@ class Datacube(ABC):
31
31
  self.merged_axes = []
32
32
  self.unwanted_path = {}
33
33
  self.compressed_axes = compressed_axes_options
34
+ self.grid_md5_hash = None
34
35
 
35
36
  @abstractmethod
36
- def get(self, requests: TensorIndexTree) -> Any:
37
+ def get(self, requests: TensorIndexTree, context: Dict) -> Any:
37
38
  """Return data given a set of request trees"""
38
39
 
39
40
  @property
@@ -69,6 +70,7 @@ class Datacube(ABC):
69
70
  # TODO: do we use this?? This shouldn't work for a disk in lat/lon on a octahedral or other grid??
70
71
  for compressed_grid_axis in transformation.compressed_grid_axes:
71
72
  self.compressed_grid_axes.append(compressed_grid_axis)
73
+ self.grid_md5_hash = transformation.md5_hash
72
74
  if len(final_axis_names) > 1:
73
75
  self.coupled_axes.append(final_axis_names)
74
76
  for axis in final_axis_names:
@@ -128,7 +130,7 @@ class Datacube(ABC):
128
130
  indexes = axis.find_indexes(path, self)
129
131
  idx_between = axis.find_indices_between(indexes, lower, upper, self, method)
130
132
 
131
- logging.info(f"For axis {axis.name} between {lower} and {upper}, found indices {idx_between}")
133
+ logging.debug(f"For axis {axis.name} between {lower} and {upper}, found indices {idx_between}")
132
134
 
133
135
  return idx_between
134
136
 
@@ -77,7 +77,10 @@ class FDBDatacube(Datacube):
77
77
  for axis_name in axes_to_remove:
78
78
  self._axes.pop(axis_name, None)
79
79
 
80
- def get(self, requests: TensorIndexTree):
80
+ def get(self, requests: TensorIndexTree, context=None):
81
+ if context is None:
82
+ context = {}
83
+ requests.pprint()
81
84
  if len(requests.children) == 0:
82
85
  return requests
83
86
  fdb_requests = []
@@ -103,11 +106,11 @@ class FDBDatacube(Datacube):
103
106
  uncompressed_request = {}
104
107
  for i, key in enumerate(compressed_request[0].keys()):
105
108
  uncompressed_request[key] = combi[i]
106
- complete_uncompressed_request = (uncompressed_request, compressed_request[1])
109
+ complete_uncompressed_request = (uncompressed_request, compressed_request[1], self.grid_md5_hash)
107
110
  complete_list_complete_uncompressed_requests.append(complete_uncompressed_request)
108
111
  complete_fdb_decoding_info.append(fdb_requests_decoding_info[j])
109
112
  logging.debug("The requests we give GribJump are: %s", complete_list_complete_uncompressed_requests)
110
- output_values = self.gj.extract(complete_list_complete_uncompressed_requests)
113
+ output_values = self.gj.extract(complete_list_complete_uncompressed_requests, context)
111
114
  logging.debug("GribJump outputs: %s", output_values)
112
115
  self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info)
113
116
 
@@ -123,7 +126,7 @@ class FDBDatacube(Datacube):
123
126
 
124
127
  # First when request node is root, go to its children
125
128
  if requests.axis.name == "root":
126
- logging.info("Looking for data for the tree: %s", [leaf.flatten() for leaf in requests.leaves])
129
+ logging.debug("Looking for data for the tree: %s", [leaf.flatten() for leaf in requests.leaves])
127
130
 
128
131
  for c in requests.children:
129
132
  self.get_fdb_requests(c, fdb_requests, fdb_requests_decoding_info)
@@ -160,8 +163,8 @@ class FDBDatacube(Datacube):
160
163
  new_current_start_idx = []
161
164
  for j, idx in enumerate(sub_lat_idxs):
162
165
  if idx not in seen_indices:
163
- # TODO: need to remove it from the values in the corresponding tree node
164
- # TODO: need to read just the range we give to gj ... DONE?
166
+ # NOTE: need to remove it from the values in the corresponding tree node
167
+ # NOTE: need to read just the range we give to gj
165
168
  original_fdb_node_range_vals.append(actual_fdb_node[0].values[j])
166
169
  seen_indices.add(idx)
167
170
  new_current_start_idx.append(idx)
@@ -186,8 +189,6 @@ class FDBDatacube(Datacube):
186
189
 
187
190
  second_ax = requests.children[0].children[0].axis
188
191
 
189
- # TODO: actually, here we should not remap the nearest_pts, we should instead unmap the
190
- # found_latlon_pts and then remap them later once we have compared found_latlon_pts and nearest_pts
191
192
  nearest_pts = [
192
193
  [lat_val, second_ax._remap_val_to_axis_range(lon_val)]
193
194
  for (lat_val, lon_val) in zip(
@@ -324,8 +325,6 @@ class FDBDatacube(Datacube):
324
325
  request_ranges_with_idx = list(enumerate(interm_request_ranges))
325
326
  sorted_list = sorted(request_ranges_with_idx, key=lambda x: x[1][0])
326
327
  original_indices, sorted_request_ranges = zip(*sorted_list)
327
- logging.debug("We sorted the request ranges into: %s", sorted_request_ranges)
328
- logging.debug("The sorted and unique leaf node ranges are: %s", new_fdb_node_ranges)
329
328
  return (original_indices, sorted_request_ranges, new_fdb_node_ranges)
330
329
 
331
330
  def datacube_natural_indexes(self, axis, subarray):
@@ -24,10 +24,12 @@ class MockDatacube(Datacube):
24
24
  self.stride[k] = stride_cumulative
25
25
  stride_cumulative *= self.dimensions[k]
26
26
 
27
- def get(self, requests: TensorIndexTree):
27
+ def get(self, requests: TensorIndexTree, context=None):
28
28
  # Takes in a datacube and verifies the leaves of the tree are complete
29
29
  # (ie it found values for all datacube axis)
30
30
 
31
+ if context is None:
32
+ context = {}
31
33
  for r in requests.leaves:
32
34
  path = r.flatten()
33
35
  if len(path.items()) == len(self.dimensions.items()):
@@ -50,12 +50,14 @@ class XArrayDatacube(Datacube):
50
50
  val = self._axes[name].type
51
51
  self._check_and_add_axes(options, name, val)
52
52
 
53
- def get(self, requests, leaf_path=None, axis_counter=0):
53
+ def get(self, requests, context=None, leaf_path=None, axis_counter=0):
54
+ if context is None:
55
+ context = {}
54
56
  if leaf_path is None:
55
57
  leaf_path = {}
56
58
  if requests.axis.name == "root":
57
59
  for c in requests.children:
58
- self.get(c, leaf_path, axis_counter + 1)
60
+ self.get(c, context, leaf_path, axis_counter + 1)
59
61
  else:
60
62
  key_value_path = {requests.axis.name: requests.values}
61
63
  ax = requests.axis
@@ -66,7 +68,7 @@ class XArrayDatacube(Datacube):
66
68
  if len(requests.children) != 0:
67
69
  # We are not a leaf and we loop over
68
70
  for c in requests.children:
69
- self.get(c, leaf_path, axis_counter + 1)
71
+ self.get(c, context, leaf_path, axis_counter + 1)
70
72
  else:
71
73
  if self.axis_counter != axis_counter:
72
74
  requests.remove_branch()
@@ -81,8 +81,12 @@ class TensorIndexTree(object):
81
81
  for i in range(len(other.values)):
82
82
  other_val = other.values[i]
83
83
  self_val = self.values[i]
84
- if abs(other_val - self_val) > 2 * max(other.axis.tol, self.axis.tol):
85
- return False
84
+ if self.axis.can_round:
85
+ if abs(other_val - self_val) > 2 * max(other.axis.tol, self.axis.tol):
86
+ return False
87
+ else:
88
+ if other_val != self_val:
89
+ return False
86
90
  return True
87
91
 
88
92
  def __lt__(self, other):
@@ -101,6 +105,7 @@ class TensorIndexTree(object):
101
105
  def add_value(self, value):
102
106
  new_values = list(self.values)
103
107
  new_values.append(value)
108
+ new_values.sort()
104
109
  self.values = tuple(new_values)
105
110
 
106
111
  def create_child(self, axis, value, next_nodes):
@@ -217,7 +222,7 @@ class TensorIndexTree(object):
217
222
  def get_ancestors(self):
218
223
  ancestors = []
219
224
  current_node = self
220
- while current_node.axis != TensorIndexTree.root:
225
+ while current_node.axis.name != "root":
221
226
  ancestors.append(current_node)
222
227
  current_node = current_node.parent
223
228
  return ancestors[::-1]
@@ -20,6 +20,7 @@ class DatacubeMapper(DatacubeAxisTransformation):
20
20
  self._final_mapped_axes = self._final_transformation._mapped_axes
21
21
  self._axis_reversed = self._final_transformation._axis_reversed
22
22
  self.compressed_grid_axes = self._final_transformation.compressed_grid_axes
23
+ self.md5_hash = self._final_transformation.md5_hash
23
24
 
24
25
  def generate_final_transformation(self):
25
26
  map_type = _type_to_datacube_mapper_lookup[self.grid_type]
@@ -13,6 +13,7 @@ class HealpixGridMapper(DatacubeMapper):
13
13
  self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False}
14
14
  self._first_axis_vals = self.first_axis_vals()
15
15
  self.compressed_grid_axes = [self._mapped_axes[1]]
16
+ self.md5_hash = md5_hash.get(resolution, None)
16
17
 
17
18
  def first_axis_vals(self):
18
19
  rad2deg = 180 / math.pi
@@ -133,3 +134,7 @@ class HealpixGridMapper(DatacubeMapper):
133
134
  second_idx = self.second_axis_vals(first_val).index(second_val)
134
135
  healpix_index = self.axes_idx_to_healpix_idx(first_idx, second_idx)
135
136
  return healpix_index
137
+
138
+
139
+ # md5 grid hash in form {resolution : hash}
140
+ md5_hash = {}
@@ -17,6 +17,7 @@ class NestedHealpixGridMapper(DatacubeMapper):
17
17
  self.k = int(math.log2(self.Nside))
18
18
  self.Npix = 12 * self.Nside * self.Nside
19
19
  self.Ncap = (self.Nside * (self.Nside - 1)) << 1
20
+ self.md5_hash = md5_hash.get(resolution, None)
20
21
 
21
22
  def first_axis_vals(self):
22
23
  rad2deg = 180 / math.pi
@@ -211,3 +212,7 @@ class NestedHealpixGridMapper(DatacubeMapper):
211
212
 
212
213
  def int_sqrt(self, i):
213
214
  return int(math.sqrt(i + 0.5))
215
+
216
+
217
+ # md5 grid hash in form {resolution : hash}
218
+ md5_hash = {}
@@ -15,9 +15,11 @@ class LocalRegularGridMapper(DatacubeMapper):
15
15
  if not isinstance(resolution, list):
16
16
  self.first_resolution = resolution
17
17
  self.second_resolution = resolution
18
+ self.md5_hash = md5_hash.get(resolution, None)
18
19
  else:
19
20
  self.first_resolution = resolution[0]
20
21
  self.second_resolution = resolution[1]
22
+ self.md5_hash = md5_hash.get(tuple(resolution), None)
21
23
  self._first_deg_increment = (local_area[1] - local_area[0]) / self.first_resolution
22
24
  self._second_deg_increment = (local_area[3] - local_area[2]) / self.second_resolution
23
25
  self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False}
@@ -68,3 +70,7 @@ class LocalRegularGridMapper(DatacubeMapper):
68
70
  second_idx = self.second_axis_vals(first_val).index(second_val)
69
71
  final_index = self.axes_idx_to_regular_idx(first_idx, second_idx)
70
72
  return final_index
73
+
74
+
75
+ # md5 grid hash in form {resolution : hash}
76
+ md5_hash = {}
@@ -15,6 +15,7 @@ class OctahedralGridMapper(DatacubeMapper):
15
15
  self._second_axis_spacing = {}
16
16
  self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False}
17
17
  self.compressed_grid_axes = [self._mapped_axes[1]]
18
+ self.md5_hash = md5_hash.get(resolution, None)
18
19
 
19
20
  def gauss_first_guess(self):
20
21
  i = 0
@@ -2750,3 +2751,9 @@ class OctahedralGridMapper(DatacubeMapper):
2750
2751
  (first_idx, second_idx) = self.find_second_axis_idx(first_val, second_val)
2751
2752
  octahedral_index = self.axes_idx_to_octahedral_idx(first_idx, second_idx)
2752
2753
  return octahedral_index
2754
+
2755
+
2756
+ # md5 grid hash in form {resolution : hash}
2757
+ md5_hash = {
2758
+ 1280: "158db321ae8e773681eeb40e0a3d350f",
2759
+ }
@@ -12,6 +12,7 @@ class ReducedLatLonMapper(DatacubeMapper):
12
12
  self._axis_reversed = {mapped_axes[0]: False, mapped_axes[1]: False}
13
13
  self._first_axis_vals = self.first_axis_vals()
14
14
  self.compressed_grid_axes = [self._mapped_axes[1]]
15
+ self.md5_hash = md5_hash.get(resolution, None)
15
16
 
16
17
  def first_axis_vals(self):
17
18
  resolution = 180 / (self._resolution - 1)
@@ -1504,3 +1505,7 @@ class ReducedLatLonMapper(DatacubeMapper):
1504
1505
  second_idx = self.second_axis_vals(first_val).index(second_val)
1505
1506
  reduced_ll_index = self.axes_idx_to_reduced_ll_idx(first_idx, second_idx)
1506
1507
  return reduced_ll_index
1508
+
1509
+
1510
+ # md5 grid hash in form {resolution : hash}
1511
+ md5_hash = {}
@@ -13,6 +13,7 @@ class RegularGridMapper(DatacubeMapper):
13
13
  self._axis_reversed = {mapped_axes[0]: True, mapped_axes[1]: False}
14
14
  self._first_axis_vals = self.first_axis_vals()
15
15
  self.compressed_grid_axes = [self._mapped_axes[1]]
16
+ self.md5_hash = md5_hash.get(resolution, None)
16
17
 
17
18
  def first_axis_vals(self):
18
19
  first_ax_vals = [90 - i * self.deg_increment for i in range(2 * self._resolution)]
@@ -56,3 +57,7 @@ class RegularGridMapper(DatacubeMapper):
56
57
  second_idx = self.second_axis_vals(first_val).index(second_val)
57
58
  final_index = self.axes_idx_to_regular_idx(first_idx, second_idx)
58
59
  return final_index
60
+
61
+
62
+ # md5 grid hash in form {resolution : hash}
63
+ md5_hash = {}
@@ -93,6 +93,5 @@ class DatacubeAxisMerger(DatacubeAxisTransformation):
93
93
  if node.axis.name == self._first_axis:
94
94
  (new_first_vals, new_second_vals) = self.unmerge(node.values)
95
95
  node.values = new_first_vals
96
- # TODO: actually need to give the second axis of the transformation to get the interm axis
97
96
  interm_node = node.add_node_layer_after(self._second_axis, new_second_vals)
98
97
  return (interm_node, unwanted_path)
@@ -109,11 +109,8 @@ class HullSlicer(Engine):
109
109
  return remapped_val
110
110
 
111
111
  def _build_sliceable_child(self, polytope, ax, node, datacube, values, next_nodes, slice_axis_idx):
112
- if len(values) == 0:
113
- node.remove_branch()
114
-
115
112
  for i, value in enumerate(values):
116
- if i == 0:
113
+ if i == 0 or ax.name not in self.compressed_axes:
117
114
  fvalue = ax.to_float(value)
118
115
  new_polytope = slice(polytope, ax.name, fvalue, slice_axis_idx)
119
116
  remapped_val = self.remap_values(ax, value)
@@ -124,38 +121,41 @@ class HullSlicer(Engine):
124
121
  child["unsliced_polytopes"].add(new_polytope)
125
122
  next_nodes.append(child)
126
123
  else:
127
- if ax.name not in self.compressed_axes:
128
- fvalue = ax.to_float(value)
129
- new_polytope = slice(polytope, ax.name, fvalue, slice_axis_idx)
130
- remapped_val = self.remap_values(ax, value)
131
- (child, next_nodes) = node.create_child(ax, remapped_val, next_nodes)
132
- child["unsliced_polytopes"] = copy(node["unsliced_polytopes"])
133
- child["unsliced_polytopes"].remove(polytope)
134
- if new_polytope is not None:
135
- child["unsliced_polytopes"].add(new_polytope)
136
- next_nodes.append(child)
137
- else:
138
- remapped_val = self.remap_values(ax, value)
139
- child.add_value(remapped_val)
124
+ remapped_val = self.remap_values(ax, value)
125
+ child.add_value(remapped_val)
140
126
 
141
127
  def _build_branch(self, ax, node, datacube, next_nodes):
142
128
  if ax.name not in self.compressed_axes:
129
+ parent_node = node.parent
130
+ right_unsliced_polytopes = []
143
131
  for polytope in node["unsliced_polytopes"]:
144
132
  if ax.name in polytope._axes:
145
- lower, upper, slice_axis_idx = polytope.extents(ax.name)
146
- # here, first check if the axis is an unsliceable axis and directly build node if it is
147
- # NOTE: we should have already created the ax_is_unsliceable cache before
148
- if self.ax_is_unsliceable[ax.name]:
149
- self._build_unsliceable_child(polytope, ax, node, datacube, [lower], next_nodes, slice_axis_idx)
150
- else:
151
- values = self.find_values_between(polytope, ax, node, datacube, lower, upper)
152
- self._build_sliceable_child(polytope, ax, node, datacube, values, next_nodes, slice_axis_idx)
133
+ right_unsliced_polytopes.append(polytope)
134
+ for i, polytope in enumerate(right_unsliced_polytopes):
135
+ node._parent = parent_node
136
+ lower, upper, slice_axis_idx = polytope.extents(ax.name)
137
+ # here, first check if the axis is an unsliceable axis and directly build node if it is
138
+ # NOTE: we should have already created the ax_is_unsliceable cache before
139
+ if self.ax_is_unsliceable[ax.name]:
140
+ self._build_unsliceable_child(polytope, ax, node, datacube, [lower], next_nodes, slice_axis_idx)
141
+ else:
142
+ values = self.find_values_between(polytope, ax, node, datacube, lower, upper)
143
+ # NOTE: need to only remove the branches if the values are empty,
144
+ # but only if there are no other possible children left in the tree that
145
+ # we can append and if somehow this happens before and we need to remove, then what do we do??
146
+ if i == len(right_unsliced_polytopes) - 1:
147
+ # we have iterated all polytopes and we can now remove the node if we need to
148
+ if len(values) == 0 and len(node.children) == 0:
149
+ node.remove_branch()
150
+ self._build_sliceable_child(polytope, ax, node, datacube, values, next_nodes, slice_axis_idx)
153
151
  else:
154
152
  all_values = []
155
153
  all_lowers = []
156
154
  first_polytope = False
157
155
  first_slice_axis_idx = False
156
+ parent_node = node.parent
158
157
  for polytope in node["unsliced_polytopes"]:
158
+ node._parent = parent_node
159
159
  if ax.name in polytope._axes:
160
160
  # keep track of the first polytope defined on the given axis
161
161
  if not first_polytope:
@@ -173,6 +173,8 @@ class HullSlicer(Engine):
173
173
  first_polytope, ax, node, datacube, all_lowers, next_nodes, first_slice_axis_idx
174
174
  )
175
175
  else:
176
+ if len(all_values) == 0:
177
+ node.remove_branch()
176
178
  self._build_sliceable_child(
177
179
  first_polytope, ax, node, datacube, all_values, next_nodes, first_slice_axis_idx
178
180
  )
@@ -0,0 +1,78 @@
1
+ import argparse
2
+ from abc import ABC
3
+ from typing import Dict, List, Literal, Optional, Union
4
+
5
+ from conflator import ConfigModel, Conflator
6
+ from pydantic import ConfigDict
7
+
8
+
9
+ class TransformationConfig(ConfigModel):
10
+ model_config = ConfigDict(extra="forbid")
11
+ name: str = ""
12
+
13
+
14
+ class CyclicConfig(TransformationConfig):
15
+ name: Literal["cyclic"]
16
+ range: List[float] = [0]
17
+
18
+
19
+ class MapperConfig(TransformationConfig):
20
+ name: Literal["mapper"]
21
+ type: str = ""
22
+ resolution: Union[int, List[int]] = 0
23
+ axes: List[str] = [""]
24
+ local: Optional[List[float]] = None
25
+
26
+
27
+ class ReverseConfig(TransformationConfig):
28
+ name: Literal["reverse"]
29
+ is_reverse: bool = False
30
+
31
+
32
+ class TypeChangeConfig(TransformationConfig):
33
+ name: Literal["type_change"]
34
+ type: str = "int"
35
+
36
+
37
+ class MergeConfig(TransformationConfig):
38
+ name: Literal["merge"]
39
+ other_axis: str = ""
40
+ linkers: List[str] = [""]
41
+
42
+
43
+ action_subclasses_union = Union[CyclicConfig, MapperConfig, ReverseConfig, TypeChangeConfig, MergeConfig]
44
+
45
+
46
+ class AxisConfig(ConfigModel):
47
+ axis_name: str = ""
48
+ transformations: list[action_subclasses_union]
49
+
50
+
51
+ path_subclasses_union = Union[str, int, float]
52
+
53
+
54
+ class GribJumpAxesConfig(ConfigModel):
55
+ axis_name: str = ""
56
+ values: List[str] = [""]
57
+
58
+
59
+ class Config(ConfigModel):
60
+ axis_config: List[AxisConfig] = []
61
+ compressed_axes_config: List[str] = [""]
62
+ pre_path: Optional[Dict[str, path_subclasses_union]] = {}
63
+ alternative_axes: List[GribJumpAxesConfig] = []
64
+
65
+
66
+ class PolytopeOptions(ABC):
67
+ @staticmethod
68
+ def get_polytope_options(options):
69
+ parser = argparse.ArgumentParser(allow_abbrev=False)
70
+ conflator = Conflator(app_name="polytope", model=Config, cli=False, argparser=parser, **options)
71
+ config_options = conflator.load()
72
+
73
+ axis_config = config_options.axis_config
74
+ compressed_axes_config = config_options.compressed_axes_config
75
+ pre_path = config_options.pre_path
76
+ alternative_axes = config_options.alternative_axes
77
+
78
+ return (axis_config, compressed_axes_config, pre_path, alternative_axes)
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from typing import List
2
3
 
3
4
  from .options import PolytopeOptions
@@ -55,9 +56,14 @@ class Polytope:
55
56
  """Low-level API which takes a polytope geometry object and uses it to slice the datacube"""
56
57
  return self.engine.extract(self.datacube, polytopes)
57
58
 
58
- def retrieve(self, request: Request, method="standard"):
59
+ def retrieve(self, request: Request, method="standard", context=None):
59
60
  """Higher-level API which takes a request and uses it to slice the datacube"""
61
+ if context is None:
62
+ context = {}
63
+ logging.info("Starting request for %s ", context)
60
64
  self.datacube.check_branching_axes(request)
61
65
  request_tree = self.engine.extract(self.datacube, request.polytopes())
62
- self.datacube.get(request_tree)
66
+ logging.info("Created request tree for %s ", context)
67
+ self.datacube.get(request_tree, context)
68
+ logging.info("Retrieved data for %s ", context)
63
69
  return request_tree
@@ -1,4 +1,8 @@
1
- class AxisOverdefinedError(KeyError):
1
+ class PolytopeError(Exception):
2
+ pass
3
+
4
+
5
+ class AxisOverdefinedError(PolytopeError, KeyError):
2
6
  def __init__(self, axis):
3
7
  self.axis = axis
4
8
  self.message = (
@@ -7,19 +11,19 @@ class AxisOverdefinedError(KeyError):
7
11
  )
8
12
 
9
13
 
10
- class AxisUnderdefinedError(KeyError):
14
+ class AxisUnderdefinedError(PolytopeError, KeyError):
11
15
  def __init__(self, axis):
12
16
  self.axis = axis
13
17
  self.message = f"Axis {axis} is underdefined. It does not appear in any input polytope."
14
18
 
15
19
 
16
- class AxisNotFoundError(KeyError):
20
+ class AxisNotFoundError(PolytopeError, KeyError):
17
21
  def __init__(self, axis):
18
22
  self.axis = axis
19
23
  self.message = f"Axis {axis} does not exist in the datacube."
20
24
 
21
25
 
22
- class UnsliceableShapeError(KeyError):
26
+ class UnsliceableShapeError(PolytopeError, KeyError):
23
27
  def __init__(self, axis):
24
28
  self.axis = axis
25
29
  self.message = f"Higher-dimensional shape does not support unsliceable axis {axis.name}."
@@ -0,0 +1 @@
1
+ __version__ = "1.0.7"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: polytope-python
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Polytope datacube feature extraction library
5
5
  Home-page: https://github.com/ecmwf/polytope
6
6
  Author: ECMWF
@@ -1,67 +0,0 @@
1
- import argparse
2
- from abc import ABC
3
- from typing import Dict, List, Literal, Optional, Union
4
-
5
- from conflator import ConfigModel, Conflator
6
- from pydantic import ConfigDict
7
-
8
-
9
- class PolytopeOptions(ABC):
10
- @staticmethod
11
- def get_polytope_options(options):
12
- class TransformationConfig(ConfigModel):
13
- model_config = ConfigDict(extra="forbid")
14
- name: str = ""
15
-
16
- class CyclicConfig(TransformationConfig):
17
- name: Literal["cyclic"]
18
- range: List[float] = [0]
19
-
20
- class MapperConfig(TransformationConfig):
21
- name: Literal["mapper"]
22
- type: str = ""
23
- resolution: Union[int, List[int]] = 0
24
- axes: List[str] = [""]
25
- local: Optional[List[float]] = None
26
-
27
- class ReverseConfig(TransformationConfig):
28
- name: Literal["reverse"]
29
- is_reverse: bool = False
30
-
31
- class TypeChangeConfig(TransformationConfig):
32
- name: Literal["type_change"]
33
- type: str = "int"
34
-
35
- class MergeConfig(TransformationConfig):
36
- name: Literal["merge"]
37
- other_axis: str = ""
38
- linkers: List[str] = [""]
39
-
40
- action_subclasses_union = Union[CyclicConfig, MapperConfig, ReverseConfig, TypeChangeConfig, MergeConfig]
41
-
42
- class AxisConfig(ConfigModel):
43
- axis_name: str = ""
44
- transformations: list[action_subclasses_union]
45
-
46
- path_subclasses_union = Union[str, int, float]
47
-
48
- class GribJumpAxesConfig(ConfigModel):
49
- axis_name: str = ""
50
- values: List[str] = [""]
51
-
52
- class Config(ConfigModel):
53
- axis_config: List[AxisConfig] = []
54
- compressed_axes_config: List[str] = [""]
55
- pre_path: Optional[Dict[str, path_subclasses_union]] = {}
56
- alternative_axes: List[GribJumpAxesConfig] = []
57
-
58
- parser = argparse.ArgumentParser(allow_abbrev=False)
59
- conflator = Conflator(app_name="polytope", model=Config, cli=False, argparser=parser, **options)
60
- config_options = conflator.load()
61
-
62
- axis_config = config_options.axis_config
63
- compressed_axes_config = config_options.compressed_axes_config
64
- pre_path = config_options.pre_path
65
- alternative_axes = config_options.alternative_axes
66
-
67
- return (axis_config, compressed_axes_config, pre_path, alternative_axes)
@@ -1 +0,0 @@
1
- __version__ = "1.0.5"
File without changes