-
Notifications
You must be signed in to change notification settings - Fork 5
Spatial tree interface #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
926b915
Move utils to `utils/utils.jl` and add edge list functionality
asinghvi17 838932a
Add LoopStateMachine and tests
asinghvi17 3419121
test all actions
asinghvi17 d6c1ffc
add SpatialTreeInterface implementation
asinghvi17 9210033
Define `isspatialtree` for FlatNoTree
asinghvi17 7b1b99b
Add basic tests for FlatNoTree
asinghvi17 7dd4014
ignore call notes since we don't want to commit them
asinghvi17 aa8ea43
test all of LoopStateMachine
asinghvi17 e9fb515
generic tests for STI with FlatNoTree and STR
asinghvi17 9efde48
actually include tests
asinghvi17 e5ad556
implement `isspatialtree` for STR types
asinghvi17 4ccf67b
fix tests
asinghvi17 0900db2
Apply suggestions from code review
asinghvi17 06c76a9
Implement and test a custom exception type for unknown actions in LSM
asinghvi17 728befc
Factor out SpatialTreeInterface into separate files
asinghvi17 bb76207
Fix tests
asinghvi17 8f4de31
no really
asinghvi17 d5a49de
add a test for multilevel dual tree query
asinghvi17 06727f1
Fix up the tests
asinghvi17 4024668
add tests for imbalanced dual tree
asinghvi17 3529ac2
add docs + animation to dfs
asinghvi17 b68fbba
test full_return properly
asinghvi17 86931a6
implement and use node_extent for FlatNoTree
asinghvi17 8456cb7
more robust implementation of `getchild` that may use `pairs`
asinghvi17 a6b0c52
rename LSM test
asinghvi17 ab1f5c8
add packages to docs Project
asinghvi17 73b6790
actually throw the error correctly
asinghvi17 8fad493
add options + docstrings to utils
asinghvi17 1b86b5e
add tests for new utils methods
asinghvi17 b9fbec8
don't run the gif I guess
asinghvi17 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,6 @@ node_modules/ | |
build/ | ||
package-lock.json | ||
src/source/ | ||
Manifest.toml | ||
Manifest.toml | ||
|
||
src/call_notes.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
""" | ||
LoopStateMachine | ||
|
||
Utilities for returning state from functions that run inside a loop. | ||
|
||
This is used in e.g clipping, where we may need to break or transition states. | ||
|
||
The main entry point is to return an [`Action`](@ref) from a function that | ||
is wrapped in a `@controlflow f(...)` macro in a loop. When a known `Action` | ||
(currently, `:continue`, `:break`, `:return`, or `:full_return` actions) is returned, | ||
it is processed by the `@controlflow` macro, which allows the function to break out of the loop | ||
early, continue to the next iteration, or return a value, basically a way to provoke syntactic | ||
behaviour from a function called from a inside a loop, where you do not have access to that loop. | ||
|
||
## Example | ||
|
||
```julia | ||
``` | ||
""" | ||
module LoopStateMachine | ||
|
||
export Action, @controlflow | ||
|
||
import ..GeometryOps as GO | ||
|
||
const ALL_ACTION_DESCRIPTIONS = """ | ||
- `:continue`: continue to the next iteration of the loop. | ||
This is the `continue` keyword in Julia. The contents of the action are not used. | ||
- `:break`: break out of the loop. | ||
This is the `break` keyword in Julia. The contents of the action are not used. | ||
- `:return`: cause the function executing the loop to return with the wrapped value. | ||
- `:full_return`: cause the function executing the loop to return `Action(:full_return, x)`. | ||
This is very useful to terminate recursive funtions, like tree queries terminating after you | ||
have found a single intersecting segment. | ||
""" | ||
|
||
""" | ||
Action(name::Symbol, [x]) | ||
|
||
Create an `Action` with the name `name` and optional contents `x`. | ||
|
||
`Action`s are returned from functions wrapped in a `@controlflow` macro, which | ||
does something based on the return value of that function if it is an `Action`. | ||
|
||
## Available actions | ||
|
||
$ALL_ACTION_DESCRIPTIONS | ||
""" | ||
struct Action{T} | ||
name::Symbol | ||
x::T | ||
end | ||
|
||
Action() = Action{Nothing}(:unnamed, nothing) | ||
Action(x::T) where T = Action{T}(:unnamed, x) | ||
Action(x::Symbol) = Action(x, nothing) | ||
|
||
function Base.show(io::IO, action::Action{T}) where T | ||
print(io, "Action") | ||
print(io, "(:$(action.name)") | ||
if isnothing(action.x) | ||
print(io, ")") | ||
else | ||
print(io, ", ",action.x, ")") | ||
end | ||
end | ||
|
||
struct UnrecognizedActionException <: Base.Exception | ||
name::Symbol | ||
end | ||
|
||
function Base.showerror(io::IO, e::UnrecognizedActionException) | ||
print(io, "Unrecognized action: ") | ||
printstyled(io, e.name; color = :red, bold = true) | ||
println(io, ".") | ||
println(io, "Valid actions are:") | ||
println(io, ALL_ACTION_DESCRIPTIONS) | ||
end | ||
|
||
# We exclude the macro definition from code coverage computations, | ||
# because I know it's tested but Codecov doesn't seem to think so. | ||
# COV_EXCL_START | ||
""" | ||
@controlflow f(...) | ||
|
||
Process the result of `f(...)` and return the result if it's not an `Action`(@ref LoopStateMachine.Action). | ||
|
||
If it is an `Action`, then process it according to the following rules, and throw an error if it's not recognized. | ||
`:continue`, `:break`, `:return`, or `:full_return` are valid actions. | ||
|
||
$ALL_ACTION_DESCRIPTIONS | ||
|
||
!!! warning | ||
Only use this inside a loop, otherwise you'll get a syntax error, especially if you use `:continue` or `:break`. | ||
|
||
## Examples | ||
""" | ||
macro controlflow(expr) | ||
varname = gensym("loop-state-machine-returned-value") | ||
return quote | ||
$varname = $(esc(expr)) | ||
if $varname isa Action | ||
if $varname.name == :continue | ||
continue | ||
elseif $varname.name == :break | ||
break | ||
elseif $varname.name == :return | ||
return $varname.x | ||
elseif $varname.name == :full_return | ||
return $varname | ||
else | ||
throw(UnrecognizedActionException($varname.name)) | ||
end | ||
else | ||
$varname | ||
end | ||
end | ||
end | ||
# COV_EXCL_STOP | ||
|
||
# You can define more actions as you desire. | ||
|
||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# SpatialTreeInterface.jl | ||
|
||
A simple interface for spatial tree types. | ||
|
||
## What is a spatial tree? | ||
|
||
- 2 dimensional extents | ||
- Parent nodes encompass all leaf nodes | ||
- Leaf nodes contain references to the geometries they represent as indices (or so we assume here) | ||
|
||
## Why is this useful? | ||
|
||
- It allows us to write algorithms that can work with any spatial tree type, without having to know the details of the tree type. | ||
- for example, dual tree traversal / queries | ||
- It allows us to flexibly and easily swap out and use different tree types, depending on the problem at hand. | ||
|
||
This is also a zero cost interface if implemented correctly! Verified implementations exist for "flat" trees like the "Natural Index" from `tg`, and "hierarchical" trees like the `STRtree` from `SortTileRecursiveTree.jl`. | ||
|
||
## Interface | ||
|
||
- `isspatialtree(tree)::Bool` | ||
- `isleaf(node)::Bool` - is the node a leaf node? In this context, a leaf node is a node that does not have other nodes as its children, but stores a list of indices and extents (even if implicit). | ||
- `getchild(node)` - get the children of a node. This may be materialized if necessary or available, but can also be lazy (like a generator). | ||
- `getchild(node, i)` - get the `i`-th child of a node. | ||
- `nchild(node)::Int` - the number of children of a node. | ||
- `child_indices_extents(node)` - an iterator over the indices and extents of the children of a **leaf** node. | ||
|
||
These are the only methods that are required to be implemented. | ||
|
||
Optionally, one may define: | ||
- `node_extent(node)` - get the extent of a node. This falls back to `GI.extent` but can potentially be overridden if you want to return a different but extent-like object. | ||
|
||
They enable the generic query functions described below: | ||
|
||
## Query functions | ||
|
||
- `do_query(f, predicate, node)` - call `f(i)` for each index `i` in `node` that satisfies `predicate(extent(i))`. | ||
- `do_dual_query(f, predicate, tree1, tree2)` - call `f(i1, i2)` for each index `i1` in `tree1` and `i2` in `tree2` that satisfies `predicate(extent(i1), extent(i2))`. | ||
|
||
These are both completely non-allocating, and will only call `f` for indices that satisfy the predicate. | ||
You can of course build a standard query interface on top of `do_query` if you want - that's simply: | ||
```julia | ||
a = Int[] | ||
do_query(Base.Fix1(push!, a), predicate, node) | ||
``` | ||
where `predicate` might be `Base.Fix1(Extents.intersects, extent_to_query)`. | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
module SpatialTreeInterface | ||
|
||
import ..LoopStateMachine: @controlflow | ||
|
||
import Extents | ||
import GeoInterface as GI | ||
import AbstractTrees | ||
|
||
# public isspatialtree, isleaf, getchild, nchild, child_indices_extents, node_extent | ||
export query, do_query | ||
export FlatNoTree | ||
|
||
# The spatial tree interface and its implementations are defined here. | ||
include("interface.jl") | ||
include("implementations.jl") | ||
|
||
# Here we have some algorithms that use the spatial tree interface. | ||
# The first file holds a single depth-first search, i.e., a single-tree query. | ||
include("depth_first_search.jl") | ||
|
||
# The second file holds a dual depth-first search, i.e., a dual-tree query. | ||
# This iterates over two trees simultaneously, and is substantially more efficient | ||
# than two separate single-tree queries since it can prune branches in tandem as it | ||
# descends into the trees. | ||
include("dual_depth_first_search.jl") | ||
|
||
|
||
""" | ||
query(tree, predicate) | ||
|
||
Return a sorted list of indices of the tree that satisfy the predicate. | ||
""" | ||
function query(tree, predicate) | ||
a = Int[] | ||
depth_first_search(Base.Fix1(push!, a), sanitize_predicate(predicate), tree) | ||
return sort!(a) | ||
end | ||
|
||
|
||
""" | ||
sanitize_predicate(pred) | ||
|
||
Convert a predicate to a function that returns a Boolean. | ||
|
||
If `pred` is an Extent, convert it to a function that returns a Boolean by intersecting with the extent. | ||
If `pred` is a geometry, convert it to an extent first, then wrap in Extents.intersects. | ||
|
||
Otherwise, return the predicate unchanged. | ||
|
||
|
||
Users and developers may overload this function to provide custom behaviour when something is passed in. | ||
""" | ||
sanitize_predicate(pred) = sanitize_predicate(GI.trait(pred), pred) | ||
sanitize_predicate(::Nothing, pred) = pred | ||
sanitize_predicate(::GI.AbstractTrait, pred) = sanitize_predicate(GI.extent(pred)) | ||
sanitize_predicate(pred::Extents.Extent) = Base.Fix1(Extents.intersects, pred) | ||
|
||
|
||
end # module SpatialTreeInterface |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separating :return and :full_return is 👨🍳 👌