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.
- {polytope-python-1.0.3/polytope_python.egg-info → polytope-python-1.0.5}/PKG-INFO +1 -1
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/datacube.py +64 -61
- polytope-python-1.0.5/polytope/datacube/backends/fdb.py +357 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/mock.py +5 -6
- polytope-python-1.0.5/polytope/datacube/backends/xarray.py +140 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/datacube_axis.py +100 -53
- polytope-python-1.0.5/polytope/datacube/index_tree_pb2.py +27 -0
- polytope-python-1.0.3/polytope/datacube/index_tree.py → polytope-python-1.0.5/polytope/datacube/tensor_index_tree.py +75 -95
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_cyclic/__init__.py +1 -0
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_cyclic/datacube_cyclic.py +171 -0
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_mappers/__init__.py +1 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/datacube_mappers.py +47 -5
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix.py +34 -23
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +213 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +3 -2
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +6 -7
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +5 -4
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/regular.py +3 -2
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_merger/__init__.py +1 -0
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_merger/datacube_merger.py +98 -0
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_reverse/__init__.py +1 -0
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_reverse/datacube_reverse.py +65 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_transformations.py +29 -4
- polytope-python-1.0.5/polytope/datacube/transformations/datacube_type_change/__init__.py +1 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_type_change/datacube_type_change.py +22 -2
- polytope-python-1.0.5/polytope/datacube/tree_encoding.py +132 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/engine/engine.py +2 -2
- polytope-python-1.0.5/polytope/engine/hullslicer.py +314 -0
- polytope-python-1.0.5/polytope/options.py +67 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/polytope.py +9 -6
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/shapes.py +23 -10
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/combinatorics.py +6 -1
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/geometry.py +9 -5
- polytope-python-1.0.5/polytope/version.py +1 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5/polytope_python.egg-info}/PKG-INFO +1 -1
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/SOURCES.txt +5 -9
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/requires.txt +3 -1
- {polytope-python-1.0.3 → polytope-python-1.0.5}/requirements.txt +3 -1
- polytope-python-1.0.3/polytope/datacube/backends/fdb.py +0 -252
- polytope-python-1.0.3/polytope/datacube/backends/xarray.py +0 -96
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/cyclic_axis_decorator.py +0 -189
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_cyclic/datacube_cyclic.py +0 -25
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_mappers/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_mappers/mapper_axis_decorator.py +0 -108
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/datacube_merger.py +0 -73
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_merger/merger_axis_decorator.py +0 -77
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/datacube_null_transformation.py +0 -22
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_null_transformation/null_axis_decorator.py +0 -22
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/datacube_reverse.py +0 -22
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_reverse/reverse_axis_decorator.py +0 -66
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_type_change/__init__.py +0 -2
- polytope-python-1.0.3/polytope/datacube/transformations/datacube_type_change/type_change_axis_decorator.py +0 -73
- polytope-python-1.0.3/polytope/engine/hullslicer.py +0 -254
- polytope-python-1.0.3/polytope/version.py +0 -1
- {polytope-python-1.0.3 → polytope-python-1.0.5}/LICENSE +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/MANIFEST.in +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/backends/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/datacube/transformations/datacube_mappers/mapper_types/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/engine/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/__init__.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/exceptions.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/list_tools.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope/utility/profiling.py +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/dependency_links.txt +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/not-zip-safe +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/polytope_python.egg-info/top_level.txt +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/pyproject.toml +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/setup.cfg +0 -0
- {polytope-python-1.0.3 → polytope-python-1.0.5}/setup.py +0 -0
|
@@ -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
|
-
|
|
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 ..
|
|
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:
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
153
|
-
|
|
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,
|
|
153
|
+
xadatacube = XArrayDatacube(datacube, axis_options, compressed_axes_options)
|
|
157
154
|
return xadatacube
|
|
158
|
-
|
|
159
|
-
|
|
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,
|
|
6
|
+
from .datacube import Datacube, DatacubePath, TensorIndexTree
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MockDatacube(Datacube):
|
|
10
|
-
def __init__(self, dimensions,
|
|
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:
|
|
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
|
|