torch-geopooling 1.0.0rc2__cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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.

Potentially problematic release.


This version of torch-geopooling might be problematic. Click here for more details.

@@ -0,0 +1,25 @@
1
+ #include <torch_geopooling/torch_geopooling.h>
2
+
3
+ #include <pybind11/pybind11.h>
4
+ #include <torch/extension.h>
5
+
6
+ #include "python_tuples.h"
7
+
8
+
9
+ namespace torch_geopooling {
10
+
11
+
12
+ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m)
13
+ {
14
+ m.def("quad_pool2d", &quad_pool2d);
15
+ m.def("quad_pool2d_backward", &quad_pool2d_backward);
16
+
17
+ m.def("max_quad_pool2d", &max_quad_pool2d);
18
+ m.def("max_quad_pool2d_backward", &max_quad_pool2d_backward);
19
+
20
+ m.def("avg_quad_pool2d", &avg_quad_pool2d);
21
+ m.def("avg_quad_pool2d_backward", &avg_quad_pool2d_backward);
22
+ }
23
+
24
+
25
+ } // namespace torch_geopooling
File without changes
@@ -0,0 +1,352 @@
1
+ # Copyright (C) 2024, Yakau Bubnou
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ from textwrap import dedent, indent
17
+ from functools import partial
18
+ from inspect import signature
19
+ from typing import Callable, NamedTuple, Optional, Tuple
20
+
21
+ import torch
22
+ from torch import Tensor, autograd
23
+ from torch.autograd.function import FunctionCtx
24
+
25
+ import torch_geopooling._C as _C
26
+ from torch_geopooling import return_types
27
+ from torch_geopooling.tiling import ExteriorTuple
28
+
29
+ __all__ = [
30
+ "adaptive_avg_quad_pool2d",
31
+ "adaptive_max_quad_pool2d",
32
+ "adaptive_quad_pool2d",
33
+ "avg_quad_pool2d",
34
+ "max_quad_pool2d",
35
+ "quad_pool2d",
36
+ ]
37
+
38
+
39
+ def __def__(fn: Callable, doc: str) -> Callable:
40
+ f = partial(fn)
41
+ f.__doc__ = doc + indent(dedent(fn.__doc__ or ""), " ")
42
+ f.__module__ = fn.__module__
43
+ f.__annotations__ = fn.__annotations__
44
+ f.__signature__ = signature(fn) # type: ignore
45
+ f.__defaults__ = fn.__defaults__ # type: ignore
46
+ f.__kwdefaults__ = fn.__kwdefaults__ # type: ignore
47
+ return f
48
+
49
+
50
+ class FunctionParams(NamedTuple):
51
+ max_terminal_nodes: Optional[int] = None
52
+ max_depth: Optional[int] = None
53
+ capacity: Optional[int] = None
54
+ precision: Optional[int] = None
55
+
56
+
57
+ ForwardType = Callable[
58
+ [
59
+ Tensor, # tiles
60
+ Tensor, # weight
61
+ Tensor, # input
62
+ ExteriorTuple, # exterior
63
+ bool, # training
64
+ Optional[int], # max_terminal_nodes
65
+ Optional[int], # max_depth
66
+ Optional[int], # capacity
67
+ Optional[int], # precision
68
+ ],
69
+ Tuple[Tensor, Tensor, Tensor], # (tiles, weight, values)
70
+ ]
71
+
72
+
73
+ BackwardType = Callable[
74
+ [
75
+ Tensor, # grad_output
76
+ Tensor, # tiles
77
+ Tensor, # weight
78
+ Tensor, # input
79
+ ExteriorTuple, # exterior
80
+ Optional[int], # max_terminal_nodes
81
+ Optional[int], # max_depth
82
+ Optional[int], # capacity
83
+ Optional[int], # precision
84
+ ],
85
+ Tensor, # (grad_weight)
86
+ ]
87
+
88
+
89
+ class Function(autograd.Function):
90
+ forward_impl: ForwardType
91
+ backward_impl: BackwardType
92
+
93
+ @classmethod
94
+ def forward(
95
+ cls,
96
+ tiles: Tensor,
97
+ weight: Tensor,
98
+ input: Tensor,
99
+ exterior: ExteriorTuple,
100
+ training: bool,
101
+ params: FunctionParams,
102
+ ) -> Tuple[Tensor, Tensor, Tensor]:
103
+ return cls.forward_impl(tiles, weight, input, exterior, training, *params)
104
+
105
+ @staticmethod
106
+ def setup_context(ctx: FunctionCtx, inputs: Tuple, outputs: Tuple) -> None:
107
+ _, _, input, exterior, _, params = inputs
108
+ tiles, weight, _ = outputs
109
+
110
+ ctx.save_for_backward(tiles.view_as(tiles), weight.view_as(weight), input.view_as(input))
111
+ ctx.exterior = exterior
112
+ ctx.params = params
113
+
114
+ @classmethod
115
+ def backward(
116
+ cls, ctx: FunctionCtx, grad_tiles: Tensor, grad_weight: Tensor, grad_values: Tensor
117
+ ) -> Tuple[Optional[Tensor], ...]:
118
+ grad_weight_out = cls.backward_impl(
119
+ grad_values, *ctx.saved_tensors, ctx.exterior, *ctx.params
120
+ ) # type: ignore
121
+ # Drop gradient for tiles, this should not be changed by an optimizer.
122
+ return None, grad_weight_out, None, None, None, None
123
+
124
+ @classmethod
125
+ def func(
126
+ cls,
127
+ tiles: Tensor,
128
+ weight: Tensor,
129
+ input: Tensor,
130
+ exterior: Tuple[float, ...],
131
+ *,
132
+ training: bool = True,
133
+ max_terminal_nodes: Optional[int] = None,
134
+ max_depth: Optional[int] = None,
135
+ capacity: Optional[int] = None,
136
+ precision: Optional[int] = None,
137
+ ) -> return_types.quad_pool2d:
138
+ """
139
+ Args:
140
+ tiles: Tiles tensor representing tiles of a quadtree (both, internal and terminal).
141
+ weight: Weights tensor associated with each tile of a quadtree.
142
+ input: Input 2D coordinates as pairs of x (longitude) and y (latitude).
143
+ exterior: Geometrical boundary of the learning space in (X, Y, W, H) format.
144
+ training: True, when executed during training, and False otherwise.
145
+ max_terminal_nodes: Optional maximum number of terminal nodes in a quadtree. Once a
146
+ maximum is reached, internal nodes are no longer sub-divided and tree stops
147
+ growing.
148
+ max_depth: Maximum depth of the quadtree. Default: 17.
149
+ capacity: Maximum number of inputs, after which a quadtree's node is subdivided and
150
+ depth of the tree grows. Default: 1.
151
+ precision: Optional rounding of the input coordinates. Default: 7.
152
+ """
153
+ params = FunctionParams(
154
+ max_terminal_nodes=max_terminal_nodes,
155
+ max_depth=max_depth,
156
+ capacity=capacity,
157
+ precision=precision,
158
+ )
159
+
160
+ result = cls.apply(tiles, weight, input, exterior, training, params)
161
+ return return_types.quad_pool2d(*result)
162
+
163
+
164
+ class QuadPool2d(Function):
165
+ forward_impl = _C.quad_pool2d
166
+ backward_impl = _C.quad_pool2d_backward
167
+
168
+
169
+ class MaxQuadPool2d(Function):
170
+ forward_impl = _C.max_quad_pool2d
171
+ backward_impl = _C.max_quad_pool2d_backward
172
+
173
+
174
+ class AvgQuadPool2d(Function):
175
+ forward_impl = _C.avg_quad_pool2d
176
+ backward_impl = _C.avg_quad_pool2d_backward
177
+
178
+
179
+ quad_pool2d = __def__(
180
+ QuadPool2d.func,
181
+ """Lookup index over quadtree decomposition of input 2D coordinates.
182
+
183
+ See :class:`torch_geopooling.nn.QuadPool2d` for more details.
184
+ """,
185
+ )
186
+ max_quad_pool2d = __def__(
187
+ MaxQuadPool2d.func,
188
+ """Maximum pooling over quadtree decomposition of input 2D coordinates.
189
+
190
+ See :class:`torch_geopooling.nn.MaxQuadPool2d` for more details.
191
+ """,
192
+ )
193
+ avg_quad_pool2d = __def__(
194
+ AvgQuadPool2d.func,
195
+ """Average pooling over quadtree decomposition of input 2D coordinates.
196
+
197
+ See :class:`torch_geopooling.nn.AvgQuadPool2d` for more details.
198
+ """,
199
+ )
200
+
201
+
202
+ class AdaptiveFunction(autograd.Function):
203
+ forward_impl: ForwardType
204
+ backward_impl: BackwardType
205
+
206
+ @staticmethod
207
+ def sparse_ravel(weight: Tensor) -> Tuple[Tensor, Tensor]:
208
+ """Transform weight as coordinate sparse tensor into a tuple of tiles and feature tensor.
209
+
210
+ The method transforms sparse encoding of quadtree (where 3 first dimensions are
211
+ coordinates of a tile and 4-th dimension is an index of a feature in the feature
212
+ vector), into tuple of coordinates (tiles) and dense weight tensor.
213
+
214
+ Effectively: (17,131072,131702,5) -> (nnz,3), (nnz,5); where nnz - is a number of
215
+ non-zero elements in the sparse tensor.
216
+ """
217
+ feature_dim = weight.size(-1)
218
+ weight = weight.coalesce()
219
+
220
+ # Transform sparse tensor into a tuple of (tiles, weight) that are directly usable
221
+ # by the C++ extension functions.
222
+ tiles = weight.indices().t()[::feature_dim, :-1]
223
+ w = weight.values().reshape((-1, feature_dim))
224
+ return tiles, w
225
+
226
+ @staticmethod
227
+ def sparse_unravel(tiles: Tensor, weight: Tensor, size: torch.Size) -> Tensor:
228
+ """Perform inverse operation of `ravel`.
229
+
230
+ Method packs tiles (coordinates) and weight (values) into a coordinate sparse tensor.
231
+ """
232
+ feature_dim = weight.size(-1)
233
+ feature_indices = torch.arange(0, feature_dim).repeat(tiles.size(0))
234
+
235
+ indices = tiles.repeat_interleave(feature_dim, dim=0)
236
+ indices = torch.column_stack((indices, feature_indices))
237
+
238
+ return torch.sparse_coo_tensor(indices.t(), weight.ravel(), size=size)
239
+
240
+ @classmethod
241
+ def forward(
242
+ cls,
243
+ weight: Tensor,
244
+ input: Tensor,
245
+ exterior: ExteriorTuple,
246
+ training: bool,
247
+ params: FunctionParams,
248
+ ) -> Tuple[Tensor, Tensor]:
249
+ tiles, w = cls.sparse_ravel(weight)
250
+
251
+ tiles_out, w_out, values_out = cls.forward_impl(
252
+ tiles, w, input, exterior, training, *params
253
+ )
254
+
255
+ weight_out = cls.sparse_unravel(tiles_out, w_out, size=weight.size())
256
+ return weight_out.coalesce(), values_out
257
+
258
+ @staticmethod
259
+ def setup_context(ctx: FunctionCtx, inputs: Tuple, outputs: Tuple) -> None:
260
+ _, input, exterior, _, params = inputs
261
+ weight, _ = outputs
262
+
263
+ ctx.save_for_backward(weight, input)
264
+ ctx.exterior = exterior
265
+ ctx.params = params
266
+
267
+ @classmethod
268
+ def backward(
269
+ cls, ctx: FunctionCtx, grad_weight: Tensor, grad_values: Tensor
270
+ ) -> Tuple[Optional[Tensor], ...]:
271
+ weight, input = ctx.saved_tensors
272
+ tiles, w = cls.sparse_ravel(weight)
273
+
274
+ grad_weight_out = cls.backward_impl(grad_values, tiles, w, input, ctx.exterior, *ctx.params) # type: ignore
275
+ grad_weight_out = cls.sparse_unravel(tiles, grad_weight_out, size=weight.size())
276
+
277
+ return grad_weight_out.coalesce(), None, None, None, None
278
+
279
+ @classmethod
280
+ def func(
281
+ cls,
282
+ weight: Tensor,
283
+ input: Tensor,
284
+ exterior: Tuple[float, ...],
285
+ *,
286
+ training: bool = True,
287
+ max_terminal_nodes: Optional[int] = None,
288
+ max_depth: Optional[int] = None,
289
+ capacity: Optional[int] = None,
290
+ precision: Optional[int] = None,
291
+ ) -> return_types.adaptive_quad_pool2d:
292
+ """
293
+ Args:
294
+ weight: Weights tensor associated with each tile of a quadtree.
295
+ input: Input 2D coordinates as pairs of x (longitude) and y (latitude).
296
+ exterior: Geometrical boundary of the learning space in (X, Y, W, H) format.
297
+ training: True, when executed during training, and False otherwise.
298
+ max_terminal_nodes: Optional maximum number of terminal nodes in a quadtree. Once a
299
+ maximum is reached, internal nodes are no longer sub-divided and tree stops
300
+ growing.
301
+ max_depth: Maximum depth of the quadtree. Default: 17.
302
+ capacity: Maximum number of inputs, after which a quadtree's node is subdivided and
303
+ depth of the tree grows. Default: 1.
304
+ precision: Optional rounding of the input coordinates. Default: 7.
305
+ """
306
+ params = FunctionParams(
307
+ max_terminal_nodes=max_terminal_nodes,
308
+ max_depth=max_depth,
309
+ capacity=capacity,
310
+ precision=precision,
311
+ )
312
+
313
+ result = cls.apply(weight, input, exterior, training, params)
314
+ return return_types.adaptive_quad_pool2d(*result)
315
+
316
+
317
+ class AdaptiveQuadPool2d(AdaptiveFunction):
318
+ forward_impl = _C.quad_pool2d
319
+ backward_impl = _C.quad_pool2d_backward
320
+
321
+
322
+ class AdaptiveMaxQuadPool2d(AdaptiveFunction):
323
+ forward_impl = _C.max_quad_pool2d
324
+ backward_impl = _C.max_quad_pool2d_backward
325
+
326
+
327
+ class AdaptiveAvgQuadPool2d(AdaptiveFunction):
328
+ forward_impl = _C.avg_quad_pool2d
329
+ backward_impl = _C.avg_quad_pool2d_backward
330
+
331
+
332
+ adaptive_quad_pool2d = __def__(
333
+ AdaptiveQuadPool2d.func,
334
+ """Adaptive lookup index over quadtree decomposition of input 2D coordinates.
335
+
336
+ See :class:`torch_geopooling.nn.AdaptiveQuadPool2d` for more details.
337
+ """,
338
+ )
339
+ adaptive_max_quad_pool2d = __def__(
340
+ AdaptiveMaxQuadPool2d.func,
341
+ """Adaptive maximum pooling over quadtree decomposition of input 2D coordinates.
342
+
343
+ See :class:`torch_geopooling.nn.AdaptiveMaxQuadPool2d` for more details.
344
+ """,
345
+ )
346
+ adaptive_avg_quad_pool2d = __def__(
347
+ AdaptiveAvgQuadPool2d.func,
348
+ """Adaptive average pooling over quadtree decomposition of input 2D coordinates.
349
+
350
+ See :class:`torch_geopooling.nn.AdaptiveAvgQuadPool2d` for more details.
351
+ """,
352
+ )
@@ -0,0 +1,85 @@
1
+ # Copyright (C) 2024, Yakau Bubnou
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ import pytest
17
+ import torch
18
+
19
+ from torch_geopooling.functional import (
20
+ adaptive_quad_pool2d,
21
+ adaptive_avg_quad_pool2d,
22
+ adaptive_max_quad_pool2d,
23
+ avg_quad_pool2d,
24
+ max_quad_pool2d,
25
+ quad_pool2d,
26
+ )
27
+
28
+
29
+ @pytest.mark.parametrize(
30
+ "function",
31
+ [
32
+ quad_pool2d,
33
+ max_quad_pool2d,
34
+ avg_quad_pool2d,
35
+ ],
36
+ ids=["id", "max", "avg"],
37
+ )
38
+ def test_quad_pool2d(function) -> None:
39
+ tiles = torch.empty((0, 3), dtype=torch.int64)
40
+ input = torch.rand((100, 2), dtype=torch.float64) * 10.0
41
+ weight = torch.randn([0, 5], dtype=torch.float64)
42
+
43
+ result = function(
44
+ tiles,
45
+ weight,
46
+ input,
47
+ (0.0, 0.0, 10.0, 10.0),
48
+ training=True,
49
+ max_depth=16,
50
+ capacity=1,
51
+ precision=6,
52
+ )
53
+ assert result.tiles.size(0) > 0
54
+ assert result.tiles.size(1) == 3
55
+
56
+ assert result.weight.size(0) == result.tiles.size(0)
57
+ assert result.values.size() == torch.Size([input.size(0), weight.size(1)])
58
+
59
+
60
+ @pytest.mark.parametrize(
61
+ "function",
62
+ [
63
+ adaptive_quad_pool2d,
64
+ adaptive_max_quad_pool2d,
65
+ adaptive_avg_quad_pool2d,
66
+ ],
67
+ ids=["id", "max", "avg"],
68
+ )
69
+ def test_adaptive_quad_pool2d(function) -> None:
70
+ input = torch.rand((100, 2), dtype=torch.float64) * 10.0
71
+ weight = torch.sparse_coo_tensor(size=(10, 1 << 10, 1 << 10, 4), dtype=torch.float64)
72
+
73
+ result = function(
74
+ weight,
75
+ input,
76
+ (0.0, 0.0, 10.0, 10.0),
77
+ training=True,
78
+ max_depth=16,
79
+ capacity=1,
80
+ precision=6,
81
+ )
82
+
83
+ assert result.weight.layout == torch.sparse_coo
84
+ assert result.weight.indices().size(0) > 0
85
+ assert result.values.size() == torch.Size([input.size(0), weight.size(-1)])