Skip to content

Commit bcf1e31

Browse files
authored
Merge pull request #350 from UrbanAnalyst/astar
improve vectorized performance of Astar heuristic computation
2 parents 5d0ceac + 6a39ef5 commit bcf1e31

14 files changed

Lines changed: 842 additions & 836 deletions

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: dodgr
22
Title: Distances on Directed Graphs
3-
Version: 0.4.3.030
3+
Version: 0.4.3.033
44
Authors@R: c(
55
person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre")),
66
person("Andreas", "Petutschnig", role = "aut"),

R/flows-aggregate.R

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#' @title Aggregate flows throughout a network.
2+
#'
3+
#' @description Aggregate flows throughout a network based on an input matrix
4+
#' of flows between all pairs of `from` and `to` points. Flows are calculated
5+
#' by default on contracted graphs, via the `contract = TRUE` parameter. (These
6+
#' are derived by reducing the input graph down to junction vertices only, by
7+
#' joining all intermediate edges between each junction.) If changes to the
8+
#' input graph do not prompt changes to resultant flows, and the default
9+
#' `contract = TRUE` is used, it may be that calculations are using previously
10+
#' cached versions of the contracted graph. If so, please use either
11+
#' \link{clear_dodgr_cache} to remove the cached version, or
12+
#' \link{dodgr_cache_off} prior to initial graph construction to switch the
13+
#' cache off completely.
14+
#'
15+
#' @param graph `data.frame` or equivalent object representing the network
16+
#' graph (see Details)
17+
#' @param flows Matrix of flows with `nrow(flows)==length(from)` and
18+
#' `ncol(flows)==length(to)`.
19+
#' @param pairwise If `TRUE`, aggregate flows only only paths connecting the
20+
#' ordered pairs of `from` and `to`. In this case, both `from` and `to` must be
21+
#' of the same length, and `flows` must be either a vector of the same length,
22+
#' or a matrix with only one column and same number of rows. `flows` then
23+
#' quantifies the flows between each pair of `from` and `to` points.
24+
#' @param contract If `TRUE` (default), calculate flows on contracted graph
25+
#' before mapping them back on to the original full graph (recommended as this
26+
#' will generally be much faster). `FALSE` should only be used if the `graph`
27+
#' has already been contracted.
28+
#' @param heap Type of heap to use in priority queue. Options include
29+
#' Fibonacci Heap (default; `FHeap`), Binary Heap (`BHeap`),
30+
#' Trinomial Heap (`TriHeap`), Extended Trinomial Heap
31+
#' (`TriHeapExt`, and 2-3 Heap (`Heap23`).
32+
#' @param tol Relative tolerance below which flows towards `to` vertices are not
33+
#' considered. This will generally have no effect, but can provide speed gains
34+
#' when flow matrices represent spatial interaction models, in which case this
35+
#' parameter effectively reduces the radius from each `from` point over which
36+
#' flows are aggregated. To remove any such effect, set `tol = 0`.
37+
#' @param norm_sums Standardise sums from all origin points, so sum of flows
38+
#' throughout entire network equals sum of densities from all origins (see
39+
#' Note).
40+
#' @inheritParams dodgr_dists
41+
#' @return Modified version of graph with additional `flow` column added.
42+
#'
43+
#' @note The `norm_sums` parameter should be used whenever densities at origins
44+
#' and destinations are absolute values, and ensures that the sum of resultant
45+
#' flow values throughout the entire network equals the sum of densities at all
46+
#' origins. For example, with `norm_sums = TRUE` (the default), a flow from a
47+
#' single origin with density one to a single destination along two edges will
48+
#' allocate flows of one half to each of those edges, such that the sum of flows
49+
#' across the network will equal one, or the sum of densities from all origins.
50+
#' The `norm_sums = TRUE` option is appropriate where densities are relative
51+
#' values, and ensures that each edge maintains relative proportions. In the
52+
#' above example, flows along each of two edges would equal one, for a network
53+
#' sum of two, or greater than the sum of densities.
54+
#'
55+
#' Flows are calculated by default using parallel computation with the maximal
56+
#' number of available cores or threads. This number can be reduced by
57+
#' specifying a value via
58+
#' `RcppParallel::setThreadOptions (numThreads = <desired_number>)`.
59+
#'
60+
#' @family flows
61+
#' @export
62+
#' @examples
63+
#' graph <- weight_streetnet (hampi)
64+
#' from <- sample (graph$from_id, size = 10)
65+
#' to <- sample (graph$to_id, size = 5)
66+
#' to <- to [!to %in% from]
67+
#' flows <- matrix (10 * runif (length (from) * length (to)),
68+
#' nrow = length (from)
69+
#' )
70+
#' graph <- dodgr_flows_aggregate (graph, from = from, to = to, flows = flows)
71+
#' # graph then has an additonal 'flows' column of aggregate flows along all
72+
#' # edges. These flows are directed, and can be aggregated to equivalent
73+
#' # undirected flows on an equivalent undirected graph with:
74+
#' graph_undir <- merge_directed_graph (graph)
75+
#' # This graph will only include those edges having non-zero flows, and so:
76+
#' nrow (graph)
77+
#' nrow (graph_undir) # the latter is much smaller
78+
#'
79+
#' # The following code can be used to convert the resultant graph to an `sf`
80+
#' # object suitable for plotting
81+
#' \dontrun{
82+
#' gsf <- dodgr_to_sf (graph_undir)
83+
#'
84+
#' # example of plotting with the 'mapview' package
85+
#' library (mapview)
86+
#' flow <- gsf$flow / max (gsf$flow)
87+
#' ncols <- 30
88+
#' cols <- c ("lawngreen", "red")
89+
#' colranmp <- colorRampPalette (cols) (ncols) [ceiling (ncols * flow)]
90+
#' mapview (gsf, color = colranmp, lwd = 10 * flow)
91+
#' }
92+
#'
93+
#' # An example of flow aggregation across a generic (non-OSM) highway,
94+
#' # represented as the `routes_fast` object of the \pkg{stplanr} package,
95+
#' # which is a SpatialLinesDataFrame containing commuter densities along
96+
#' # components of a street network.
97+
#' \dontrun{
98+
#' library (stplanr)
99+
#' # merge all of the 'routes_fast' lines into a single network
100+
#' r <- overline (routes_fast, attrib = "length", buff_dist = 1)
101+
#' r <- sf::st_as_sf (r)
102+
#' # then extract the start and end points of each of the original 'routes_fast'
103+
#' # lines and use these for routing with `dodgr`
104+
#' l <- lapply (routes_fast@lines, function (i) {
105+
#' c (
106+
#' sp::coordinates (i) [[1]] [1, ],
107+
#' tail (sp::coordinates (i) [[1]], 1)
108+
#' )
109+
#' })
110+
#' l <- do.call (rbind, l)
111+
#' xy_start <- l [, 1:2]
112+
#' xy_end <- l [, 3:4]
113+
#' # Then just specify a generic OD matrix with uniform values of 1:
114+
#' flows <- matrix (1, nrow = nrow (l), ncol = nrow (l))
115+
#' # We need to specify both a `type` and `id` column for the
116+
#' # \link{weight_streetnet} function.
117+
#' r$type <- 1
118+
#' r$id <- seq (nrow (r))
119+
#' graph <- weight_streetnet (
120+
#' r,
121+
#' type_col = "type",
122+
#' id_col = "id",
123+
#' wt_profile = 1
124+
#' )
125+
#' f <- dodgr_flows_aggregate (
126+
#' graph,
127+
#' from = xy_start,
128+
#' to = xy_end,
129+
#' flows = flows
130+
#' )
131+
#' # Then merge directed flows and convert to \pkg{sf} for plotting as before:
132+
#' f <- merge_directed_graph (f)
133+
#' geoms <- dodgr_to_sfc (f)
134+
#' gc <- dodgr_contract_graph (f)
135+
#' gsf <- sf::st_sf (geoms)
136+
#' gsf$flow <- gc$flow
137+
#' # sf plot:
138+
#' plot (gsf ["flow"])
139+
#' }
140+
dodgr_flows_aggregate <- function (graph,
141+
from,
142+
to,
143+
flows,
144+
pairwise = FALSE,
145+
contract = TRUE,
146+
heap = "BHeap",
147+
tol = 1e-12,
148+
norm_sums = TRUE,
149+
quiet = TRUE) {
150+
151+
if (methods::is (graph, "dodgr_contracted")) {
152+
contract <- FALSE
153+
}
154+
155+
if (anyNA (flows)) {
156+
flows [is.na (flows)] <- 0
157+
}
158+
hps <- get_heap (heap, graph)
159+
heap <- hps$heap
160+
graph <- hps$graph
161+
162+
if (!identical (class (from), class (to))) {
163+
stop ("from and to must be the same class of object.")
164+
}
165+
check_for_flow_col (graph)
166+
167+
graph <- preprocess_spatial_cols (graph)
168+
gr_cols <- dodgr_graph_cols (graph)
169+
170+
to_from_indices <- to_from_index_with_tp (graph, from, to)
171+
if (to_from_indices$compound) {
172+
graph <- to_from_indices$graph_compound
173+
}
174+
175+
if (contract) {
176+
graph_full <- graph
177+
graph <- contract_graph_with_pts (
178+
graph,
179+
to_from_indices$from$id,
180+
to_from_indices$to$id
181+
)
182+
hashc <- get_hash (graph, contracted = TRUE)
183+
fname_c <- fs::path (
184+
fs::path_temp (),
185+
paste0 ("dodgr_edge_map_", hashc, ".Rds")
186+
)
187+
if (!fs::file_exists (fname_c)) {
188+
stop ("something went wrong extracting the edge_map ... ")
189+
} # nocov
190+
edge_map <- readRDS (fname_c)
191+
}
192+
193+
graph2 <- convert_graph (graph, gr_cols)
194+
195+
if (!is.matrix (flows)) {
196+
flows <- matrix (flows, nrow = length (to_from_indices$from$index))
197+
} else if (!(nrow (flows) == length (to_from_indices$from$index) &&
198+
ncol (flows) == length (to_from_indices$to$index))) {
199+
stop ("flows matrix is not compatible with 'from'/'to' arguments")
200+
}
201+
if (pairwise) {
202+
check_pairwise_from_to (from, to, flows)
203+
}
204+
205+
if (!quiet) {
206+
message ("\nAggregating flows ... ", appendLF = FALSE)
207+
}
208+
209+
if (pairwise) {
210+
graph$flow <- rcpp_flows_aggregate_pairwise (
211+
graph2, to_from_indices$vert_map,
212+
to_from_indices$from$index, to_from_indices$to$index,
213+
flows, norm_sums, tol, heap
214+
)
215+
} else {
216+
graph$flow <- rcpp_flows_aggregate_par (
217+
graph2, to_from_indices$vert_map,
218+
to_from_indices$from$index, to_from_indices$to$index,
219+
flows, norm_sums, tol, heap
220+
)
221+
}
222+
223+
if (contract) { # map contracted flows back onto full graph
224+
graph <- uncontract_graph (graph, edge_map, graph_full)
225+
graph$flow [is.na (graph$flow)] <- 0
226+
}
227+
if (to_from_indices$compound) {
228+
graph <- uncompound_junctions (
229+
graph,
230+
"flow",
231+
to_from_indices$compound_junction_map
232+
)
233+
graph$flow [is.na (graph$flow)] <- 0
234+
}
235+
236+
return (graph)
237+
}

0 commit comments

Comments
 (0)