Skip to contents

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 from X; "supplied" uses adj.list and weight.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() when graph = "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.

Value

A list of class "pttf_geometry".

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"