Builds the geometry layer for the experimental parallel-transport trend filtering (PTTF) route. The returned object contains graph supports, local PCA tangent frames, edge directions, orthogonal edge transports, sampling density metadata, and diagnostics. It does not assemble transported difference operators and it does not fit a response.
Usage
pttf.geometry(
X,
adj.list = NULL,
weight.list = NULL,
graph = c("rknn", "supplied"),
tangent.dim,
local.support = c("graph.disk", "neighbors"),
min.support = NULL,
max.hops = 3L,
support.k = NULL,
graph.k.scale = 1L,
graph.radius.factor = 1,
graph.radius.rule = "geomean",
transport.rule = c("procrustes"),
synchronize.orientation = TRUE,
density.method = c("support.radius"),
alpha = 1,
diagnostics = TRUE
)Arguments
- X
Numeric data matrix with one row per vertex.
- adj.list
Optional supplied graph adjacency list using 1-based vertex indices.
- weight.list
Optional positive edge-length list parallel to
adj.list.- graph
Graph source.
"rknn"builds an adaptive-radius graph fromX;"supplied"usesadj.listandweight.list.- tangent.dim
Fixed global tangent dimension for local geometry.
- local.support
Local support construction rule.
"graph.disk"uses graph-hop disks;"neighbors"starts from the closed one-hop neighborhood and tops up by graph-hop expansion.- min.support
Minimum support size. Defaults to \(\max(2m+2,m+3)\), where \(m=\code{tangent.dim}\).
- max.hops
Maximum hop radius used when topping up local supports.
- support.k
Optional cap on local support size after top-up. The center vertex is always retained and remaining vertices are selected by graph metric distance, with vertex-index tie breaking.
- graph.k.scale, graph.radius.factor, graph.radius.rule
Adaptive-radius graph controls passed to
dgraphs::create.rknn.graph()whengraph = "rknn".- transport.rule
Edge transport rule. The current implementation uses
"procrustes".- synchronize.orientation
Logical. If
TRUE, return deterministic spanning-tree orientation-synchronization metadata. This metadata does not mutate the returned frames.- density.method
Sampling-density metadata rule. The current implementation uses
"support.radius".- alpha
Numeric exponent used only to report proposed future density-normalization weights.
- diagnostics
Logical. If
TRUE, compute cycle diagnostics.
Details
The stored transport convention is column-vector based. For an oriented edge
\(i \leftarrow j\), Oij maps tangent coordinates from frame
\(j\) into frame \(i\):
$$v_j \mapsto O_{ij} v_j.$$
For shared-support Procrustes, row-coordinate matrices \(Z_i\) and \(Z_j\) are formed on the same vertex set. The row problem \(\min_Q \|Z_j Q - Z_i\|_F^2\) gives \(Q = U V^\top\) when \(Z_j^\top Z_i = U\Sigma V^\top\). The stored column map is \(O_{ij}=Q^\top=VU^\top\); the reported residual is \(\|Z_j O_{ij}^\top - Z_i\|_F / \max(\|Z_i\|_F,\epsilon)\).
Examples
n <- 10L
X <- matrix(seq(0, 1, length.out = n), ncol = 1)
adj <- lapply(seq_len(n), function(i) intersect(c(i - 1L, i + 1L), 1:n))
lengths <- Map(function(i, j) abs(X[j, 1] - X[i, 1]), seq_len(n), adj)
pttf.geometry(X, adj, lengths, graph = "supplied", tangent.dim = 1L)
#> $graph
#> $graph$adj.list
#> $graph$adj.list[[1]]
#> [1] 2
#>
#> $graph$adj.list[[2]]
#> [1] 1 3
#>
#> $graph$adj.list[[3]]
#> [1] 2 4
#>
#> $graph$adj.list[[4]]
#> [1] 3 5
#>
#> $graph$adj.list[[5]]
#> [1] 4 6
#>
#> $graph$adj.list[[6]]
#> [1] 5 7
#>
#> $graph$adj.list[[7]]
#> [1] 6 8
#>
#> $graph$adj.list[[8]]
#> [1] 7 9
#>
#> $graph$adj.list[[9]]
#> [1] 8 10
#>
#> $graph$adj.list[[10]]
#> [1] 9
#>
#>
#> $graph$weight.list
#> $graph$weight.list[[1]]
#> [1] 0.1111111
#>
#> $graph$weight.list[[2]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[3]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[4]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[5]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[6]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[7]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[8]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[9]]
#> [1] 0.1111111 0.1111111
#>
#> $graph$weight.list[[10]]
#> [1] 0.1111111
#>
#>
#> $graph$edge.table
#> from to length
#> 1 1 2 0.1111111
#> 2 2 3 0.1111111
#> 3 3 4 0.1111111
#> 4 4 5 0.1111111
#> 5 5 6 0.1111111
#> 6 6 7 0.1111111
#> 7 7 8 0.1111111
#> 8 8 9 0.1111111
#> 9 9 10 0.1111111
#>
#> $graph$constructor
#> [1] "supplied"
#>
#> $graph$parameters
#> list()
#>
#> $graph$n.vertices
#> [1] 10
#>
#> $graph$n.edges
#> [1] 9
#>
#>
#> $frames
#> $frames$tangent.dim
#> [1] 1
#>
#> $frames$centers
#> [,1]
#> [1,] 0.0000000
#> [2,] 0.1111111
#> [3,] 0.2222222
#> [4,] 0.3333333
#> [5,] 0.4444444
#> [6,] 0.5555556
#> [7,] 0.6666667
#> [8,] 0.7777778
#> [9,] 0.8888889
#> [10,] 1.0000000
#>
#> $frames$frame.list
#> $frames$frame.list[[1]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[2]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[3]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[4]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[5]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[6]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[7]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[8]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[9]]
#> [,1]
#> [1,] 1
#>
#> $frames$frame.list[[10]]
#> [,1]
#> [1,] 1
#>
#>
#> $frames$pca.spectra
#> sv1
#> 1 0.4157397
#> 2 0.2721655
#> 3 0.3513642
#> 4 0.3513642
#> 5 0.3513642
#> 6 0.3513642
#> 7 0.3513642
#> 8 0.3513642
#> 9 0.2721655
#> 10 0.4157397
#>
#> $frames$support.index.list
#> $frames$support.index.list[[1]]
#> [1] 1 2 3 4
#>
#> $frames$support.index.list[[2]]
#> [1] 1 2 3 4
#>
#> $frames$support.index.list[[3]]
#> [1] 1 2 3 4 5
#>
#> $frames$support.index.list[[4]]
#> [1] 2 3 4 5 6
#>
#> $frames$support.index.list[[5]]
#> [1] 3 4 5 6 7
#>
#> $frames$support.index.list[[6]]
#> [1] 4 5 6 7 8
#>
#> $frames$support.index.list[[7]]
#> [1] 5 6 7 8 9
#>
#> $frames$support.index.list[[8]]
#> [1] 6 7 8 9 10
#>
#> $frames$support.index.list[[9]]
#> [1] 7 8 9 10
#>
#> $frames$support.index.list[[10]]
#> [1] 7 8 9 10
#>
#>
#> $frames$support.diagnostics
#> vertex support.size initial.size hop.radius metric.radius rank condition
#> 1 1 4 2 3 0.3333333 1 1
#> 2 2 4 3 2 0.2222222 1 1
#> 3 3 5 3 2 0.2222222 1 1
#> 4 4 5 3 2 0.2222222 1 1
#> 5 5 5 3 2 0.2222222 1 1
#> 6 6 5 3 2 0.2222222 1 1
#> 7 7 5 3 2 0.2222222 1 1
#> 8 8 5 3 2 0.2222222 1 1
#> 9 9 4 3 2 0.2222222 1 1
#> 10 10 4 2 3 0.3333333 1 1
#> status
#> 1 ok
#> 2 ok
#> 3 ok
#> 4 ok
#> 5 ok
#> 6 ok
#> 7 ok
#> 8 ok
#> 9 ok
#> 10 ok
#>
#>
#> $edges
#> from to length support.shared procrustes.residual frame.transport.residual
#> 1 1 2 0.1111111 4 0.5345225 0
#> 2 2 3 0.1111111 4 0.8164966 0
#> 3 3 4 0.1111111 4 0.8164966 0
#> 4 4 5 0.1111111 4 0.8164966 0
#> 5 5 6 0.1111111 4 0.8164966 0
#> 6 6 7 0.1111111 4 0.8164966 0
#> 7 7 8 0.1111111 4 0.8164966 0
#> 8 8 9 0.1111111 4 0.8164966 0
#> 9 9 10 0.1111111 4 0.8164966 0
#> frame.inverse.residual inverse.residual det.transport status
#> 1 0 0 1 ok
#> 2 0 0 1 ok
#> 3 0 0 1 ok
#> 4 0 0 1 ok
#> 5 0 0 1 ok
#> 6 0 0 1 ok
#> 7 0 0 1 ok
#> 8 0 0 1 ok
#> 9 0 0 1 ok
#>
#> $transport
#> $transport$convention
#> [1] "Oij maps frame j coordinates to frame i coordinates"
#>
#> $transport$oriented.edge.table
#> from to key edge.index
#> 1 1 2 1->2 1
#> 2 2 1 2->1 1
#> 3 2 3 2->3 2
#> 4 3 2 3->2 2
#> 5 3 4 3->4 3
#> 6 4 3 4->3 3
#> 7 4 5 4->5 4
#> 8 5 4 5->4 4
#> 9 5 6 5->6 5
#> 10 6 5 6->5 5
#> 11 6 7 6->7 6
#> 12 7 6 7->6 6
#> 13 7 8 7->8 7
#> 14 8 7 8->7 7
#> 15 8 9 8->9 8
#> 16 9 8 9->8 8
#> 17 9 10 9->10 9
#> 18 10 9 10->9 9
#>
#> $transport$Oij
#> $transport$Oij$`1->2`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`2->1`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`2->3`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`3->2`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`3->4`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`4->3`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`4->5`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`5->4`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`5->6`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`6->5`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`6->7`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`7->6`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`7->8`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`8->7`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`8->9`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`9->8`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`9->10`
#> [,1]
#> [1,] 1
#>
#> $transport$Oij$`10->9`
#> [,1]
#> [1,] 1
#>
#>
#> $transport$uij
#> $transport$uij$`1->2`
#> [1] 0.1111111
#>
#> $transport$uij$`2->1`
#> [1] -0.1111111
#>
#> $transport$uij$`2->3`
#> [1] 0.1111111
#>
#> $transport$uij$`3->2`
#> [1] -0.1111111
#>
#> $transport$uij$`3->4`
#> [1] 0.1111111
#>
#> $transport$uij$`4->3`
#> [1] -0.1111111
#>
#> $transport$uij$`4->5`
#> [1] 0.1111111
#>
#> $transport$uij$`5->4`
#> [1] -0.1111111
#>
#> $transport$uij$`5->6`
#> [1] 0.1111111
#>
#> $transport$uij$`6->5`
#> [1] -0.1111111
#>
#> $transport$uij$`6->7`
#> [1] 0.1111111
#>
#> $transport$uij$`7->6`
#> [1] -0.1111111
#>
#> $transport$uij$`7->8`
#> [1] 0.1111111
#>
#> $transport$uij$`8->7`
#> [1] -0.1111111
#>
#> $transport$uij$`8->9`
#> [1] 0.1111111
#>
#> $transport$uij$`9->8`
#> [1] -0.1111111
#>
#> $transport$uij$`9->10`
#> [1] 0.1111111
#>
#> $transport$uij$`10->9`
#> [1] -0.1111111
#>
#>
#> $transport$normalized.uij
#> $transport$normalized.uij$`1->2`
#> [1] 1
#>
#> $transport$normalized.uij$`2->1`
#> [1] -1
#>
#> $transport$normalized.uij$`2->3`
#> [1] 1
#>
#> $transport$normalized.uij$`3->2`
#> [1] -1
#>
#> $transport$normalized.uij$`3->4`
#> [1] 1
#>
#> $transport$normalized.uij$`4->3`
#> [1] -1
#>
#> $transport$normalized.uij$`4->5`
#> [1] 1
#>
#> $transport$normalized.uij$`5->4`
#> [1] -1
#>
#> $transport$normalized.uij$`5->6`
#> [1] 1
#>
#> $transport$normalized.uij$`6->5`
#> [1] -1
#>
#> $transport$normalized.uij$`6->7`
#> [1] 1
#>
#> $transport$normalized.uij$`7->6`
#> [1] -1
#>
#> $transport$normalized.uij$`7->8`
#> [1] 1
#>
#> $transport$normalized.uij$`8->7`
#> [1] -1
#>
#> $transport$normalized.uij$`8->9`
#> [1] 1
#>
#> $transport$normalized.uij$`9->8`
#> [1] -1
#>
#> $transport$normalized.uij$`9->10`
#> [1] 1
#>
#> $transport$normalized.uij$`10->9`
#> [1] -1
#>
#>
#>
#> $density
#> $density$method
#> [1] "support.radius"
#>
#> $density$alpha
#> [1] 1
#>
#> $density$used.in.operator
#> [1] FALSE
#>
#> $density$rho.hat
#> [1] 12.0 18.0 22.5 22.5 22.5 22.5 22.5 22.5 18.0 12.0
#>
#> $density$node.mass
#> [1] 0.08333333 0.05555556 0.04444444 0.04444444 0.04444444 0.04444444
#> [7] 0.04444444 0.04444444 0.05555556 0.08333333
#>
#> $density$proposed.alpha.weight
#> [1] 0.08333333 0.05555556 0.04444444 0.04444444 0.04444444 0.04444444
#> [7] 0.04444444 0.04444444 0.05555556 0.08333333
#>
#>
#> $diagnostics
#> $diagnostics$orientation.synchronization
#> $diagnostics$orientation.synchronization$enabled
#> [1] TRUE
#>
#> $diagnostics$orientation.synchronization$method
#> [1] "bfs.sign"
#>
#> $diagnostics$orientation.synchronization$root
#> [1] 1
#>
#> $diagnostics$orientation.synchronization$metadata.only
#> [1] TRUE
#>
#> $diagnostics$orientation.synchronization$n.reached
#> [1] 10
#>
#> $diagnostics$orientation.synchronization$n.flips
#> [1] 0
#>
#> $diagnostics$orientation.synchronization$parent
#> [1] 0 1 2 3 4 5 6 7 8 9
#>
#> $diagnostics$orientation.synchronization$sign
#> [1] 1 1 1 1 1 1 1 1 1 1
#>
#> $diagnostics$orientation.synchronization$non.tree.edge.residuals
#> [1] from to residual
#> <0 rows> (or 0-length row.names)
#>
#> $diagnostics$orientation.synchronization$non.tree.edge.summary
#> $diagnostics$orientation.synchronization$non.tree.edge.summary$n.edges
#> [1] 0
#>
#> $diagnostics$orientation.synchronization$non.tree.edge.summary$median.residual
#> [1] NA
#>
#> $diagnostics$orientation.synchronization$non.tree.edge.summary$max.residual
#> [1] NA
#>
#>
#>
#> $diagnostics$cycles
#> $diagnostics$cycles$triangles
#> data frame with 0 columns and 0 rows
#>
#> $diagnostics$cycles$fundamental.cycles
#> data frame with 0 columns and 0 rows
#>
#>
#> $diagnostics$status.summary
#> $diagnostics$status.summary$support
#> ok
#> 10
#>
#> $diagnostics$status.summary$edge
#> ok
#> 9
#>
#>
#>
#> $parameters
#> $parameters$tangent.dim
#> [1] 1
#>
#> $parameters$local.support
#> [1] "graph.disk"
#>
#> $parameters$min.support
#> [1] 4
#>
#> $parameters$max.hops
#> [1] 3
#>
#> $parameters$support.k
#> NULL
#>
#> $parameters$transport.rule
#> [1] "procrustes"
#>
#> $parameters$synchronize.orientation
#> [1] TRUE
#>
#> $parameters$density.method
#> [1] "support.radius"
#>
#>
#> attr(,"class")
#> [1] "pttf_geometry" "list"