polytope-python 1.0.3__tar.gz → 1.0.5__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 (76) hide show
  1. {polytope-python-1.0.3/polytope_python.egg-info → polytope-python-1.0.5}/PKG-INFO +1 -1
  2. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/datacube.py +64 -61
  3. polytope-python-1.0.5/polytope/datacube/backends/fdb.py +357 -0
  4. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/mock.py +5 -6
  5. polytope-python-1.0.5/polytope/datacube/backends/xarray.py +140 -0
  6. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/datacube_axis.py +100 -53
  7. polytope-python-1.0.5/polytope/datacube/index_tree_pb2.py +27 -0
  8. polytope-python-1.0.3/polytope/datacube/index_tree.py → polytope-python-1.0.5/polytope/datacube/tensor_index_tree.py +75 -95
  9. polytope-python-1.0.5/polytope/datacube/transformations/datacube_cyclic/__init__.py +1 -0
  10. polytope-python-1.0.5/polytope/datacube/transformations/datacube_cyclic/datacube_cyclic.py +171 -0
  11. polytope-python-1.0.5/polytope/datacube/transformations/datacube_mappers/__init__.py +1 -0
  12. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py +47 -5
  13. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py +34 -23
  14. polytope-python-1.0.5/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +213 -0
  15. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +3 -2
  16. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +6 -7
  17. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +5 -4
  18. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py +3 -2
  19. polytope-python-1.0.5/polytope/datacube/transformations/datacube_merger/__init__.py +1 -0
  20. polytope-python-1.0.5/polytope/datacube/transformations/datacube_merger/datacube_merger.py +98 -0
  21. polytope-python-1.0.5/polytope/datacube/transformations/datacube_reverse/__init__.py +1 -0
  22. polytope-python-1.0.5/polytope/datacube/transformations/datacube_reverse/datacube_reverse.py +65 -0
  23. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_transformations.py +29 -4
  24. polytope-python-1.0.5/polytope/datacube/transformations/datacube_type_change/__init__.py +1 -0
  25. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_type_change/datacube_type_change.py +22 -2
  26. polytope-python-1.0.5/polytope/datacube/tree_encoding.py +132 -0
  27. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/engine/engine.py +2 -2
  28. polytope-python-1.0.5/polytope/engine/hullslicer.py +314 -0
  29. polytope-python-1.0.5/polytope/options.py +67 -0
  30. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/polytope.py +9 -6
  31. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/shapes.py +23 -10
  32. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/combinatorics.py +6 -1
  33. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/geometry.py +9 -5
  34. polytope-python-1.0.5/polytope/version.py +1 -0
  35. {polytope-python-1.0.3 → polytope-python-1.0.5/polytope_python.egg-info}/PKG-INFO +1 -1
  36. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/SOURCES.txt +5 -9
  37. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/requires.txt +3 -1
  38. {polytope-python-1.0.3 → polytope-python-1.0.5}/requirements.txt +3 -1
  39. polytope-python-1.0.3/polytope/datacube/backends/fdb.py +0 -252
  40. polytope-python-1.0.3/polytope/datacube/backends/xarray.py +0 -96
  41. polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/__init__.py +0 -2
  42. polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/cyclic_axis_decorator.py +0 -189
  43. polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/datacube_cyclic.py +0 -25
  44. polytope-python-1.0.3/polytope/datacube/transformations/datacube_mappers/__init__.py +0 -2
  45. polytope-python-1.0.3/polytope/datacube/transformations/datacube_mappers/mapper_axis_decorator.py +0 -108
  46. polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/__init__.py +0 -2
  47. polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/datacube_merger.py +0 -73
  48. polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/merger_axis_decorator.py +0 -77
  49. polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/__init__.py +0 -2
  50. polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/datacube_null_transformation.py +0 -22
  51. polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/null_axis_decorator.py +0 -22
  52. polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/__init__.py +0 -2
  53. polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/datacube_reverse.py +0 -22
  54. polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/reverse_axis_decorator.py +0 -66
  55. polytope-python-1.0.3/polytope/datacube/transformations/datacube_type_change/__init__.py +0 -2
  56. polytope-python-1.0.3/polytope/datacube/transformations/datacube_type_change/type_change_axis_decorator.py +0 -73
  57. polytope-python-1.0.3/polytope/engine/hullslicer.py +0 -254
  58. polytope-python-1.0.3/polytope/version.py +0 -1
  59. {polytope-python-1.0.3 → polytope-python-1.0.5}/LICENSE +0 -0
  60. {polytope-python-1.0.3 → polytope-python-1.0.5}/MANIFEST.in +0 -0
  61. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/__init__.py +0 -0
  62. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/__init__.py +0 -0
  63. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/__init__.py +0 -0
  64. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/__init__.py +0 -0
  65. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/__init__.py +0 -0
  66. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/engine/__init__.py +0 -0
  67. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/__init__.py +0 -0
  68. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/exceptions.py +0 -0
  69. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/list_tools.py +0 -0
  70. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/profiling.py +0 -0
  71. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/dependency_links.txt +0 -0
  72. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/not-zip-safe +0 -0
  73. {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/top_level.txt +0 -0
  74. {polytope-python-1.0.3 → polytope-python-1.0.5}/pyproject.toml +0 -0
  75. {polytope-python-1.0.3 → polytope-python-1.0.5}/setup.cfg +0 -0
  76. {polytope-python-1.0.3 → polytope-python-1.0.5}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: polytope-python
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Polytope datacube feature extraction library
5
5
  Home-page: https://github.com/ecmwf/polytope
6
6
  Author: ECMWF
@@ -1,14 +1,11 @@
1
- import importlib
2
1
  import logging
3
- import math
4
2
  from abc import ABC, abstractmethod
5
3
  from typing import Any
6
4
 
7
- import xarray as xr
8
-
9
- from ...utility.combinatorics import unique, validate_axes
5
+ from ...utility.combinatorics import validate_axes
10
6
  from ..datacube_axis import DatacubeAxis
11
- from ..index_tree import DatacubePath, IndexTree
7
+ from ..tensor_index_tree import DatacubePath, TensorIndexTree
8
+ from ..transformations.datacube_mappers.datacube_mappers import DatacubeMapper
12
9
  from ..transformations.datacube_transformations import (
13
10
  DatacubeAxisTransformation,
14
11
  has_transform,
@@ -16,8 +13,27 @@ from ..transformations.datacube_transformations import (
16
13
 
17
14
 
18
15
  class Datacube(ABC):
16
+ def __init__(self, axis_options=None, compressed_axes_options=[]):
17
+ if axis_options is None:
18
+ self.axis_options = {}
19
+ else:
20
+ self.axis_options = axis_options
21
+ self.coupled_axes = []
22
+ self.axis_counter = 0
23
+ self.complete_axes = []
24
+ self.blocked_axes = []
25
+ self.fake_axes = []
26
+ self.treated_axes = []
27
+ self.nearest_search = {}
28
+ self._axes = None
29
+ self.transformed_axes = []
30
+ self.compressed_grid_axes = []
31
+ self.merged_axes = []
32
+ self.unwanted_path = {}
33
+ self.compressed_axes = compressed_axes_options
34
+
19
35
  @abstractmethod
20
- def get(self, requests: IndexTree) -> Any:
36
+ def get(self, requests: TensorIndexTree) -> Any:
21
37
  """Return data given a set of request trees"""
22
38
 
23
39
  @property
@@ -30,39 +46,55 @@ class Datacube(ABC):
30
46
 
31
47
  def _create_axes(self, name, values, transformation_type_key, transformation_options):
32
48
  # first check what the final axes are for this axis name given transformations
49
+ transformation_options = transformation_type_key
33
50
  final_axis_names = DatacubeAxisTransformation.get_final_axes(
34
- name, transformation_type_key, transformation_options
51
+ name, transformation_type_key.name, transformation_options
35
52
  )
36
53
  transformation = DatacubeAxisTransformation.create_transform(
37
- name, transformation_type_key, transformation_options
54
+ name, transformation_type_key.name, transformation_options
38
55
  )
56
+
57
+ # do not compress merged axes
58
+ if transformation_type_key.name == "merge":
59
+ self.merged_axes.append(name)
60
+ self.merged_axes.append(final_axis_names)
61
+ for axis in final_axis_names:
62
+ # remove the merged_axes from the possible compressed axes
63
+ if axis in self.compressed_axes:
64
+ self.compressed_axes.remove(axis)
65
+
39
66
  for blocked_axis in transformation.blocked_axes():
40
67
  self.blocked_axes.append(blocked_axis)
68
+ if isinstance(transformation, DatacubeMapper):
69
+ # TODO: do we use this?? This shouldn't work for a disk in lat/lon on a octahedral or other grid??
70
+ for compressed_grid_axis in transformation.compressed_grid_axes:
71
+ self.compressed_grid_axes.append(compressed_grid_axis)
41
72
  if len(final_axis_names) > 1:
42
73
  self.coupled_axes.append(final_axis_names)
74
+ for axis in final_axis_names:
75
+ if axis in self.compressed_axes and axis != final_axis_names[-1]:
76
+ self.compressed_axes.remove(axis)
43
77
  for axis_name in final_axis_names:
44
78
  self.fake_axes.append(axis_name)
45
79
  # if axis does not yet exist, create it
46
80
 
47
81
  # first need to change the values so that we have right type
48
82
  values = transformation.change_val_type(axis_name, values)
49
- if self._axes is None:
50
- DatacubeAxis.create_standard(axis_name, values, self)
51
- elif axis_name not in self._axes.keys():
83
+ if self._axes is None or axis_name not in self._axes.keys():
52
84
  DatacubeAxis.create_standard(axis_name, values, self)
53
85
  # add transformation tag to axis, as well as transformation options for later
54
- setattr(self._axes[axis_name], has_transform[transformation_type_key], True) # where has_transform is a
55
- # factory inside datacube_transformations to set the has_transform, is_cyclic etc axis properties
86
+ setattr(self._axes[axis_name], has_transform[transformation_type_key.name], True) # where has_transform is
87
+ # a factory inside datacube_transformations to set the has_transform, is_cyclic etc axis properties
56
88
  # add the specific transformation handled here to the relevant axes
57
89
  # Modify the axis to update with the tag
58
- decorator_module = importlib.import_module("polytope.datacube.datacube_axis")
59
- decorator = getattr(decorator_module, transformation_type_key)
60
- decorator(self._axes[axis_name])
90
+
61
91
  if transformation not in self._axes[axis_name].transformations: # Avoids duplicates being stored
62
92
  self._axes[axis_name].transformations.append(transformation)
63
93
 
64
94
  def _add_all_transformation_axes(self, options, name, values):
65
- for transformation_type_key in options.keys():
95
+ for transformation_type_key in options.transformations:
96
+ if transformation_type_key != "cyclic":
97
+ self.transformed_axes.append(name)
66
98
  self._create_axes(name, values, transformation_type_key, options)
67
99
 
68
100
  def _check_and_add_axes(self, options, name, values):
@@ -70,9 +102,7 @@ class Datacube(ABC):
70
102
  self._add_all_transformation_axes(options, name, values)
71
103
  else:
72
104
  if name not in self.blocked_axes:
73
- if self._axes is None:
74
- DatacubeAxis.create_standard(name, values, self)
75
- elif name not in self._axes.keys():
105
+ if self._axes is None or name not in self._axes.keys():
76
106
  DatacubeAxis.create_standard(name, values, self)
77
107
 
78
108
  def has_index(self, path: DatacubePath, axis, index):
@@ -96,46 +126,12 @@ class Datacube(ABC):
96
126
  """
97
127
  path = self.fit_path(path)
98
128
  indexes = axis.find_indexes(path, self)
99
- search_ranges = axis.remap([lower, upper])
100
- original_search_ranges = axis.to_intervals([lower, upper])
101
- # Find the offsets for each interval in the requested range, which we will need later
102
- search_ranges_offset = []
103
- for r in original_search_ranges:
104
- offset = axis.offset(r)
105
- search_ranges_offset.append(offset)
106
- idx_between = self._look_up_datacube(search_ranges, search_ranges_offset, indexes, axis, method)
107
- # Remove duplicates even if difference of the order of the axis tolerance
108
- if offset is not None:
109
- # Note that we can only do unique if not dealing with time values
110
- idx_between = unique(idx_between)
129
+ idx_between = axis.find_indices_between(indexes, lower, upper, self, method)
111
130
 
112
131
  logging.info(f"For axis {axis.name} between {lower} and {upper}, found indices {idx_between}")
113
132
 
114
133
  return idx_between
115
134
 
116
- def _look_up_datacube(self, search_ranges, search_ranges_offset, indexes, axis, method):
117
- idx_between = []
118
- for i in range(len(search_ranges)):
119
- r = search_ranges[i]
120
- offset = search_ranges_offset[i]
121
- low = r[0]
122
- up = r[1]
123
- indexes_between = axis.find_indices_between([indexes], low, up, self, method)
124
- # Now the indexes_between are values on the cyclic range so need to remap them to their original
125
- # values before returning them
126
- for j in range(len(indexes_between)):
127
- # if we have a special indexes between range that needs additional offset, treat it here
128
- if len(indexes_between[j]) == 0:
129
- idx_between = idx_between
130
- else:
131
- for k in range(len(indexes_between[j])):
132
- if offset is None:
133
- indexes_between[j][k] = indexes_between[j][k]
134
- else:
135
- indexes_between[j][k] = round(indexes_between[j][k] + offset, int(-math.log10(axis.tol)))
136
- idx_between.append(indexes_between[j][k])
137
- return idx_between
138
-
139
135
  def get_mapper(self, axis):
140
136
  """
141
137
  Get the type mapper for a subaxis of the datacube given by label
@@ -149,11 +145,18 @@ class Datacube(ABC):
149
145
  return path
150
146
 
151
147
  @staticmethod
152
- def create(datacube, axis_options: dict, datacube_options={}):
153
- if isinstance(datacube, (xr.core.dataarray.DataArray, xr.core.dataset.Dataset)):
148
+ def create(datacube, config={}, axis_options={}, compressed_axes_options=[], alternative_axes=[]):
149
+ # TODO: get the configs as None for pre-determined value and change them to empty dictionary inside the function
150
+ if type(datacube).__name__ == "DataArray":
154
151
  from .xarray import XArrayDatacube
155
152
 
156
- xadatacube = XArrayDatacube(datacube, axis_options, datacube_options)
153
+ xadatacube = XArrayDatacube(datacube, axis_options, compressed_axes_options)
157
154
  return xadatacube
158
- else:
159
- return datacube
155
+ if type(datacube).__name__ == "GribJump":
156
+ from .fdb import FDBDatacube
157
+
158
+ fdbdatacube = FDBDatacube(datacube, config, axis_options, compressed_axes_options, alternative_axes)
159
+ return fdbdatacube
160
+
161
+ def check_branching_axes(self, request):
162
+ pass
@@ -0,0 +1,357 @@
1
+ import logging
2
+ import operator
3
+ from copy import deepcopy
4
+ from itertools import product
5
+
6
+ from ...utility.geometry import nearest_pt
7
+ from .datacube import Datacube, TensorIndexTree
8
+
9
+
10
+ class FDBDatacube(Datacube):
11
+ def __init__(self, gj, config=None, axis_options=None, compressed_axes_options=[], alternative_axes=[]):
12
+ if config is None:
13
+ config = {}
14
+
15
+ super().__init__(axis_options, compressed_axes_options)
16
+
17
+ logging.info("Created an FDB datacube with options: " + str(axis_options))
18
+
19
+ self.unwanted_path = {}
20
+ self.axis_options = axis_options
21
+
22
+ partial_request = config
23
+ # Find values in the level 3 FDB datacube
24
+
25
+ self.gj = gj
26
+ if len(alternative_axes) == 0:
27
+ self.fdb_coordinates = self.gj.axes(partial_request)
28
+ else:
29
+ self.fdb_coordinates = {}
30
+ for axis_config in alternative_axes:
31
+ self.fdb_coordinates[axis_config.axis_name] = axis_config.values
32
+
33
+ logging.info("Axes returned from GribJump are: " + str(self.fdb_coordinates))
34
+
35
+ self.fdb_coordinates["values"] = []
36
+ for name, values in self.fdb_coordinates.items():
37
+ values.sort()
38
+ options = None
39
+ for opt in self.axis_options:
40
+ if opt.axis_name == name:
41
+ options = opt
42
+
43
+ self._check_and_add_axes(options, name, values)
44
+ self.treated_axes.append(name)
45
+ self.complete_axes.append(name)
46
+
47
+ # add other options to axis which were just created above like "lat" for the mapper transformations for eg
48
+ for name in self._axes:
49
+ if name not in self.treated_axes:
50
+ options = None
51
+ for opt in self.axis_options:
52
+ if opt.axis_name == name:
53
+ options = opt
54
+
55
+ val = self._axes[name].type
56
+ self._check_and_add_axes(options, name, val)
57
+
58
+ logging.info("Polytope created axes for %s", self._axes.keys())
59
+
60
+ def check_branching_axes(self, request):
61
+ polytopes = request.polytopes()
62
+ for polytope in polytopes:
63
+ for ax in polytope._axes:
64
+ if ax == "levtype":
65
+ (upper, lower, idx) = polytope.extents(ax)
66
+ if "sfc" in polytope.points[idx]:
67
+ self.fdb_coordinates.pop("levelist", None)
68
+ self.fdb_coordinates.pop("quantile", None)
69
+ # TODO: When do these not appear??
70
+ self.fdb_coordinates.pop("direction", None)
71
+ self.fdb_coordinates.pop("frequency", None)
72
+
73
+ # NOTE: verify that we also remove the axis object for axes we've removed here
74
+ axes_to_remove = set(self.complete_axes) - set(self.fdb_coordinates.keys())
75
+
76
+ # Remove the keys from self._axes
77
+ for axis_name in axes_to_remove:
78
+ self._axes.pop(axis_name, None)
79
+
80
+ def get(self, requests: TensorIndexTree):
81
+ if len(requests.children) == 0:
82
+ return requests
83
+ fdb_requests = []
84
+ fdb_requests_decoding_info = []
85
+ self.get_fdb_requests(requests, fdb_requests, fdb_requests_decoding_info)
86
+
87
+ # here, loop through the fdb requests and request from gj and directly add to the nodes
88
+ complete_list_complete_uncompressed_requests = []
89
+ complete_fdb_decoding_info = []
90
+ for j, compressed_request in enumerate(fdb_requests):
91
+ uncompressed_request = {}
92
+
93
+ # Need to determine the possible decompressed requests
94
+
95
+ # find the possible combinations of compressed indices
96
+ interm_branch_tuple_values = []
97
+ for key in compressed_request[0].keys():
98
+ interm_branch_tuple_values.append(compressed_request[0][key])
99
+ request_combis = product(*interm_branch_tuple_values)
100
+
101
+ # Need to extract the possible requests and add them to the right nodes
102
+ for combi in request_combis:
103
+ uncompressed_request = {}
104
+ for i, key in enumerate(compressed_request[0].keys()):
105
+ uncompressed_request[key] = combi[i]
106
+ complete_uncompressed_request = (uncompressed_request, compressed_request[1])
107
+ complete_list_complete_uncompressed_requests.append(complete_uncompressed_request)
108
+ complete_fdb_decoding_info.append(fdb_requests_decoding_info[j])
109
+ 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)
111
+ logging.debug("GribJump outputs: %s", output_values)
112
+ self.assign_fdb_output_to_nodes(output_values, complete_fdb_decoding_info)
113
+
114
+ def get_fdb_requests(
115
+ self,
116
+ requests: TensorIndexTree,
117
+ fdb_requests=[],
118
+ fdb_requests_decoding_info=[],
119
+ leaf_path=None,
120
+ ):
121
+ if leaf_path is None:
122
+ leaf_path = {}
123
+
124
+ # First when request node is root, go to its children
125
+ if requests.axis.name == "root":
126
+ logging.info("Looking for data for the tree: %s", [leaf.flatten() for leaf in requests.leaves])
127
+
128
+ for c in requests.children:
129
+ self.get_fdb_requests(c, fdb_requests, fdb_requests_decoding_info)
130
+ # If request node has no children, we have a leaf so need to assign fdb values to it
131
+ else:
132
+ key_value_path = {requests.axis.name: requests.values}
133
+ ax = requests.axis
134
+ (key_value_path, leaf_path, self.unwanted_path) = ax.unmap_path_key(
135
+ key_value_path, leaf_path, self.unwanted_path
136
+ )
137
+ leaf_path.update(key_value_path)
138
+ if len(requests.children[0].children[0].children) == 0:
139
+ # find the fdb_requests and associated nodes to which to add results
140
+ (path, current_start_idxs, fdb_node_ranges, lat_length) = self.get_2nd_last_values(requests, leaf_path)
141
+ (
142
+ original_indices,
143
+ sorted_request_ranges,
144
+ fdb_node_ranges,
145
+ ) = self.sort_fdb_request_ranges(current_start_idxs, lat_length, fdb_node_ranges)
146
+ fdb_requests.append((path, sorted_request_ranges))
147
+ fdb_requests_decoding_info.append((original_indices, fdb_node_ranges))
148
+
149
+ # Otherwise remap the path for this key and iterate again over children
150
+ else:
151
+ for c in requests.children:
152
+ self.get_fdb_requests(c, fdb_requests, fdb_requests_decoding_info, leaf_path)
153
+
154
+ def remove_duplicates_in_request_ranges(self, fdb_node_ranges, current_start_idxs):
155
+ seen_indices = set()
156
+ for i, idxs_list in enumerate(current_start_idxs):
157
+ for k, sub_lat_idxs in enumerate(idxs_list):
158
+ actual_fdb_node = fdb_node_ranges[i][k]
159
+ original_fdb_node_range_vals = []
160
+ new_current_start_idx = []
161
+ for j, idx in enumerate(sub_lat_idxs):
162
+ 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?
165
+ original_fdb_node_range_vals.append(actual_fdb_node[0].values[j])
166
+ seen_indices.add(idx)
167
+ new_current_start_idx.append(idx)
168
+ if original_fdb_node_range_vals != []:
169
+ actual_fdb_node[0].values = tuple(original_fdb_node_range_vals)
170
+ else:
171
+ # there are no values on this node anymore so can remove it
172
+ actual_fdb_node[0].remove_branch()
173
+ if len(new_current_start_idx) == 0:
174
+ current_start_idxs[i].pop(k)
175
+ else:
176
+ current_start_idxs[i][k] = new_current_start_idx
177
+ return (fdb_node_ranges, current_start_idxs)
178
+
179
+ def nearest_lat_lon_search(self, requests):
180
+ if len(self.nearest_search) != 0:
181
+ first_ax_name = requests.children[0].axis.name
182
+ second_ax_name = requests.children[0].children[0].axis.name
183
+
184
+ if first_ax_name not in self.nearest_search.keys() or second_ax_name not in self.nearest_search.keys():
185
+ raise Exception("nearest point search axes are wrong")
186
+
187
+ second_ax = requests.children[0].children[0].axis
188
+
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
+ nearest_pts = [
192
+ [lat_val, second_ax._remap_val_to_axis_range(lon_val)]
193
+ for (lat_val, lon_val) in zip(
194
+ self.nearest_search[first_ax_name][0], self.nearest_search[second_ax_name][0]
195
+ )
196
+ ]
197
+
198
+ found_latlon_pts = []
199
+ for lat_child in requests.children:
200
+ for lon_child in lat_child.children:
201
+ found_latlon_pts.append([lat_child.values, lon_child.values])
202
+
203
+ # now find the nearest lat lon to the points requested
204
+ nearest_latlons = []
205
+ for pt in nearest_pts:
206
+ nearest_latlon = nearest_pt(found_latlon_pts, pt)
207
+ nearest_latlons.append(nearest_latlon)
208
+
209
+ # need to remove the branches that do not fit
210
+ lat_children_values = [child.values for child in requests.children]
211
+ for i in range(len(lat_children_values)):
212
+ lat_child_val = lat_children_values[i]
213
+ lat_child = [child for child in requests.children if child.values == lat_child_val][0]
214
+ if lat_child.values not in [(latlon[0],) for latlon in nearest_latlons]:
215
+ lat_child.remove_branch()
216
+ else:
217
+ possible_lons = [latlon[1] for latlon in nearest_latlons if (latlon[0],) == lat_child.values]
218
+ lon_children_values = [child.values for child in lat_child.children]
219
+ for j in range(len(lon_children_values)):
220
+ lon_child_val = lon_children_values[j]
221
+ lon_child = [child for child in lat_child.children if child.values == lon_child_val][0]
222
+ for value in lon_child.values:
223
+ if value not in possible_lons:
224
+ lon_child.remove_compressed_branch(value)
225
+
226
+ def get_2nd_last_values(self, requests, leaf_path=None):
227
+ if leaf_path is None:
228
+ leaf_path = {}
229
+ # In this function, we recursively loop over the last two layers of the tree and store the indices of the
230
+ # request ranges in those layers
231
+ self.nearest_lat_lon_search(requests)
232
+
233
+ lat_length = len(requests.children)
234
+ current_start_idxs = [False] * lat_length
235
+ fdb_node_ranges = [False] * lat_length
236
+ for i in range(len(requests.children)):
237
+ lat_child = requests.children[i]
238
+ lon_length = len(lat_child.children)
239
+ current_start_idxs[i] = [None] * lon_length
240
+ fdb_node_ranges[i] = [[TensorIndexTree.root for y in range(lon_length)] for x in range(lon_length)]
241
+ current_start_idx = deepcopy(current_start_idxs[i])
242
+ fdb_range_nodes = deepcopy(fdb_node_ranges[i])
243
+ key_value_path = {lat_child.axis.name: lat_child.values}
244
+ ax = lat_child.axis
245
+ (key_value_path, leaf_path, self.unwanted_path) = ax.unmap_path_key(
246
+ key_value_path, leaf_path, self.unwanted_path
247
+ )
248
+ leaf_path.update(key_value_path)
249
+ (current_start_idxs[i], fdb_node_ranges[i]) = self.get_last_layer_before_leaf(
250
+ lat_child, leaf_path, current_start_idx, fdb_range_nodes
251
+ )
252
+
253
+ leaf_path_copy = deepcopy(leaf_path)
254
+ leaf_path_copy.pop("values", None)
255
+ return (leaf_path_copy, current_start_idxs, fdb_node_ranges, lat_length)
256
+
257
+ def get_last_layer_before_leaf(self, requests, leaf_path, current_idx, fdb_range_n):
258
+ current_idx = [[] for i in range(len(requests.children))]
259
+ fdb_range_n = [[] for i in range(len(requests.children))]
260
+ for i, c in enumerate(requests.children):
261
+ # now c are the leaves of the initial tree
262
+ key_value_path = {c.axis.name: c.values}
263
+ ax = c.axis
264
+ (key_value_path, leaf_path, self.unwanted_path) = ax.unmap_path_key(
265
+ key_value_path, leaf_path, self.unwanted_path
266
+ )
267
+ # TODO: change this to accommodate non consecutive indexes being compressed too
268
+ current_idx[i].extend(key_value_path["values"])
269
+ fdb_range_n[i].append(c)
270
+ return (current_idx, fdb_range_n)
271
+
272
+ def assign_fdb_output_to_nodes(self, output_values, fdb_requests_decoding_info):
273
+ for k in range(len(output_values)):
274
+ request_output_values = output_values[k]
275
+ (
276
+ original_indices,
277
+ fdb_node_ranges,
278
+ ) = fdb_requests_decoding_info[k]
279
+ sorted_fdb_range_nodes = [fdb_node_ranges[i] for i in original_indices]
280
+ for i in range(len(sorted_fdb_range_nodes)):
281
+ n = sorted_fdb_range_nodes[i][0]
282
+ if len(request_output_values[0]) == 0:
283
+ # If we are here, no data was found for this path in the fdb
284
+ none_array = [None] * len(n.values)
285
+ n.result.extend(none_array)
286
+ else:
287
+ interm_request_output_values = request_output_values[0][i][0]
288
+ n.result.extend(interm_request_output_values)
289
+
290
+ def sort_fdb_request_ranges(self, current_start_idx, lat_length, fdb_node_ranges):
291
+ (new_fdb_node_ranges, new_current_start_idx) = self.remove_duplicates_in_request_ranges(
292
+ fdb_node_ranges, current_start_idx
293
+ )
294
+ interm_request_ranges = []
295
+ # TODO: modify the start indexes to have as many arrays as the request ranges
296
+ new_fdb_node_ranges = []
297
+ for i in range(lat_length):
298
+ interm_fdb_nodes = fdb_node_ranges[i]
299
+ old_interm_start_idx = current_start_idx[i]
300
+ for j in range(len(old_interm_start_idx)):
301
+ # TODO: if we sorted the cyclic values in increasing order on the tree too,
302
+ # then we wouldn't have to sort here?
303
+ sorted_list = sorted(enumerate(old_interm_start_idx[j]), key=lambda x: x[1])
304
+ original_indices_idx, interm_start_idx = zip(*sorted_list)
305
+ for interm_fdb_nodes_obj in interm_fdb_nodes[j]:
306
+ interm_fdb_nodes_obj.values = tuple([interm_fdb_nodes_obj.values[k] for k in original_indices_idx])
307
+ if abs(interm_start_idx[-1] + 1 - interm_start_idx[0]) <= len(interm_start_idx):
308
+ current_request_ranges = (interm_start_idx[0], interm_start_idx[-1] + 1)
309
+ interm_request_ranges.append(current_request_ranges)
310
+ new_fdb_node_ranges.append(interm_fdb_nodes[j])
311
+ else:
312
+ jumps = list(map(operator.sub, interm_start_idx[1:], interm_start_idx[:-1]))
313
+ last_idx = 0
314
+ for k, jump in enumerate(jumps):
315
+ if jump > 1:
316
+ current_request_ranges = (interm_start_idx[last_idx], interm_start_idx[k] + 1)
317
+ new_fdb_node_ranges.append(interm_fdb_nodes[j])
318
+ last_idx = k + 1
319
+ interm_request_ranges.append(current_request_ranges)
320
+ if k == len(interm_start_idx) - 2:
321
+ current_request_ranges = (interm_start_idx[last_idx], interm_start_idx[-1] + 1)
322
+ interm_request_ranges.append(current_request_ranges)
323
+ new_fdb_node_ranges.append(interm_fdb_nodes[j])
324
+ request_ranges_with_idx = list(enumerate(interm_request_ranges))
325
+ sorted_list = sorted(request_ranges_with_idx, key=lambda x: x[1][0])
326
+ 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
+ return (original_indices, sorted_request_ranges, new_fdb_node_ranges)
330
+
331
+ def datacube_natural_indexes(self, axis, subarray):
332
+ indexes = subarray.get(axis.name, None)
333
+ return indexes
334
+
335
+ def select(self, path, unmapped_path):
336
+ return self.fdb_coordinates
337
+
338
+ def ax_vals(self, name):
339
+ return self.fdb_coordinates.get(name, None)
340
+
341
+ def prep_tree_encoding(self, node, unwanted_path=None):
342
+ # TODO: prepare the tree for protobuf encoding
343
+ # ie transform all axes for gribjump and adding the index property on the leaves
344
+ if unwanted_path is None:
345
+ unwanted_path = {}
346
+
347
+ ax = node.axis
348
+ (new_node, unwanted_path) = ax.unmap_tree_node(node, unwanted_path)
349
+
350
+ if len(node.children) != 0:
351
+ for c in new_node.children:
352
+ self.prep_tree_encoding(c, unwanted_path)
353
+
354
+ def prep_tree_decoding(self, tree):
355
+ # TODO: transform the tree after decoding from protobuf
356
+ # ie unstransform all axes from gribjump and put the indexes back as a leaf/extra node
357
+ pass
@@ -3,11 +3,12 @@ from copy import deepcopy
3
3
 
4
4
  from ...utility.combinatorics import validate_axes
5
5
  from ..datacube_axis import IntDatacubeAxis
6
- from .datacube import Datacube, DatacubePath, IndexTree
6
+ from .datacube import Datacube, DatacubePath, TensorIndexTree
7
7
 
8
8
 
9
9
  class MockDatacube(Datacube):
10
- def __init__(self, dimensions, datacube_options={}):
10
+ def __init__(self, dimensions, compressed_axes_options=[]):
11
+ super().__init__({}, compressed_axes_options)
11
12
  assert isinstance(dimensions, dict)
12
13
 
13
14
  self.dimensions = dimensions
@@ -22,17 +23,15 @@ class MockDatacube(Datacube):
22
23
  for k, v in reversed(dimensions.items()):
23
24
  self.stride[k] = stride_cumulative
24
25
  stride_cumulative *= self.dimensions[k]
25
- self.coupled_axes = []
26
- self.axis_with_identical_structure_after = ""
27
26
 
28
- def get(self, requests: IndexTree):
27
+ def get(self, requests: TensorIndexTree):
29
28
  # Takes in a datacube and verifies the leaves of the tree are complete
30
29
  # (ie it found values for all datacube axis)
31
30
 
32
31
  for r in requests.leaves:
33
32
  path = r.flatten()
34
33
  if len(path.items()) == len(self.dimensions.items()):
35
- result = 0
34
+ result = (0,)
36
35
  for k, v in path.items():
37
36
  result += v * self.stride[k]
38
37