Skip to content

Commit 7d31dad

Browse files
Merge pull request #16 from geopython/optimize
Optimization backend
2 parents ac6ed0f + eb865f0 commit 7d31dad

File tree

18 files changed

+792
-23
lines changed

18 files changed

+792
-23
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,37 @@ layer = data.ExecuteSQL(f"""
341341
Note that it is vital to specify the `SQLite` dialect as this is the one used internally.
342342

343343
:warning: Input values are *not* sanitized/separated from the generated SQL text. This is due to the compatibility with the OGR API not allowing to separate the SQL from the arguments.
344+
345+
346+
### Optimization
347+
348+
This is a special kind of backend, as the result of the AST evaluation is actually a new AST. The purpose of this backend is to eliminate static branches of the AST, potentially reducing the cost of an actual evaluation for filtering values.
349+
350+
What parts of an AST can be optimized:
351+
352+
- Arithmetic operations of purely static operands
353+
- All predicates (spatial, temporal, array, `like`, `between`, `in`) if all of the operands are already static
354+
- Functions, when passed in a special lookup table and all arguments are static
355+
- `And` and `Or` combinators can be eliminated if either branch can be predicted
356+
357+
What cannot be optimized are branches that contain references to attributes or functions not passed in the dictionary.
358+
359+
The following example shows how a static computation can be optimized to a static value, replacing the whole branch of the AST:
360+
361+
```python
362+
>>> import math
363+
>>> from pygeofilter import ast
364+
>>> from pygeofilter.parsers.ecql import parse
365+
>>> from pygeofilter.backends.optimize import optimize
366+
>>>
367+
>>> root = parse('attr < sin(3.7) - 5')
368+
>>> optimized_root = optimize(root, {'sin': math.sin})
369+
>>> print(ast.get_repr(root))
370+
ATTRIBUTE attr < (
371+
(
372+
sin (3.7)
373+
) - 5
374+
)
375+
>>> print(ast.get_repr(optimized_root))
376+
ATTRIBUTE attr < -5.529836140908493
377+
```

pygeofilter/ast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def get_sub_nodes(self):
157157
return [self.lhs, self.rhs]
158158

159159
def get_template(self):
160-
return f"{{}} {self.op} {{}}"
160+
return f"{{}} {self.op.value} {{}}"
161161

162162

163163
@dataclass

pygeofilter/backends/django/evaluate.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ def arithmetic(self, node, lhs, rhs):
170170
def literal(self, node):
171171
return filters.literal(node)
172172

173+
@handle(values.Interval)
174+
def interval(self, node):
175+
return filters.literal((node.start, node.end))
176+
173177
@handle(values.Geometry)
174178
def geometry(self, node):
175179
return GEOSGeometry(json.dumps(node.__geo_interface__))

pygeofilter/backends/evaluator.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
# ------------------------------------------------------------------------------
2+
#
3+
# Project: pygeofilter <https://github.com/geopython/pygeofilter>
4+
# Authors: Fabian Schindler <[email protected]>
5+
#
6+
# ------------------------------------------------------------------------------
7+
# Copyright (C) 2021 EOX IT Services GmbH
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in
17+
# all copies of this Software or works derived from this Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
# THE SOFTWARE.
26+
# ------------------------------------------------------------------------------
27+
128
from functools import wraps
229
from typing import Any, Callable, List, Type
330

pygeofilter/backends/geopandas/evaluate.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ def function(self, node, *arguments):
145145
def literal(self, node):
146146
return node
147147

148+
@handle(values.Interval)
149+
def interval(self, node):
150+
return (node.start, node.end)
151+
148152
@handle(values.Geometry)
149153
def geometry(self, node):
150154
return geometry.shape(node)

pygeofilter/backends/native/evaluate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ def function(self, node, *arguments):
184184
def literal(self, node):
185185
return node
186186

187+
@handle(values.Interval)
188+
def interval(self, node):
189+
return node
190+
187191
@handle(values.Geometry)
188192
def geometry(self, node):
189193
return shapely.geometry.shape(node)
@@ -198,8 +202,9 @@ def envelope(self, node):
198202
def to_interval(value):
199203
# TODO:
200204
zulu = None
201-
if isinstance(value, (list, tuple)):
202-
low, high = value
205+
if isinstance(value, values.Interval):
206+
low = value.start
207+
high = value.end
203208
if isinstance(low, date):
204209
low = datetime.combine(low, time.min, zulu)
205210
if isinstance(high, date):

pygeofilter/backends/native/filters.py

Whitespace-only changes.

0 commit comments

Comments
 (0)