Giter Club home page Giter Club logo

simplextree's Introduction

simplextree

Codecov test coverage CRAN badge

simplextree is an R package aimed at simplifying computation for general simplicial complexes of any dimension. This package facilitates this aim by providing R bindings to a Simplex Tree data structure, implemented using modern C++11 and exported as a Rcpp module. The underlying library implementation also exports a C++ header, which can be specified as a dependency and used in other packages via Rcpp attributes.

The Simplex Tree was originally introduced in the following paper:

Boissonnat, Jean-Daniel, and Clément Maria. "The simplex tree: An efficient data structure for general simplicial complexes." Algorithmica 70.3 (2014): 406-427.

A Simplex Tree is an ordered, trie-like structure whose nodes are in bijection with the faces of the complex. Here's a picture (taken from the paper) of a simplicial 3-complex (left) and its corresponding Simplex Tree (right):

simplex tree picture

Installation

The most recent release is available on CRAN and can be installed via the usual method

install.packages("simplextree")

Alternatively, the current development version can be installed with the devtools package:

require("devtools")
devtools::install_github("peekxc/simplextree")

Quickstart

library(simplextree)
st <- simplex_tree()              ## creates a simplex tree
st %>% insert(list(1:3, 4:5, 6))  ## Inserts simplices { 1, 2, 3 }, { 4, 5 }, and { 6 }

## Summary of complex
print(st) 
# Simplex Tree with (6, 4, 1) (0, 1, 2)-simplices

## More detailed look at structure
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0): 
# 4 (h = 1): .( 5 )
# 5 (h = 0): 
# 6 (h = 0): 

## Print the set of simplices making up the star of the simplex '2'
print_simplices(st %>% cofaces(2))
# 2, 2 3, 2 3 4, 2 3 4 5, 2 3 5, 2 4, 2 4 5, 2 5, 1 2, 1 2 3

## You can also compactly print traversals of the complex  
dfs <- preorder(st)
print_simplices(dfs, "column")
# 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 4 4 5 6
#   2 2 3 3 3 3 3 4 4 5 6   3 3 3 3 4 4 5   4 4 5 6   5    
#     3   4 4 5 6   5         4 4 5   5       5            
#           5                   5

## Or save them as a list 
dfs_list <- as.list(dfs)
# list(1, c(1,2), ...)

## Get connected components 
print(st$connected_components)
# [1] 1 1 1 4 4 5

## Serialization/persistent storage options available
saveRDS(st %>% serialize(), file = tempfile())

## As are various export options
list_of_simplices <- st$as_list()
adj_matrix <- st$as_adjacency_matrix()
# ... see also as_adjacency_list(), as_edge_list

API Reference

Below is the API reference for the R-bindings of the SimplexTree class. A SimplexTree object can also be passed, manipulated, and modified via C++ in another R package as well. See Usage with Rcpp.

In what follows, individual simplex's are assumed to be integer vectors. Numeric vectors are implicitly cast to integer vectors. Some methods accept multiple simplices as well, which can be specified as a list of integer vectors.

SimplexTree

# simplex_tree()

Wrapper for constructing a SimplexTree instance. See below.

# SimplexTree (C++ Class)

Exposed binding for an internal SimplexTree C++ class. The binding is exposed as an Rcpp Module. Module instances are of the class type Rcpp_SimplexTree.

SimplexTree instances can be created with either simplex_tree() or new(SimplexTree).

Fields

Fields are data attributes associated with the SimplexTree instance containing useful information about the simplex tree. Writeable fields can be set by the user to change the behaviour of certain methods, whereas read-only fields are automatically updated as the tree is modified.

# SimplexTree $ n_simplices

An integer vector, where each index k denotes the number of (k-1)-simplices. ( Read-only )

# SimplexTree $ dimension

The dimension of a simplicial complex K is the highest dimension of any of K's simplices. ( Read-only )

# SimplexTree $ id_policy

The id_policy of the SimplexTree instance determines how new vertex ids are generated by the generate_ids method. Can be either "compressed" or "unique". Defaults to "compressed".

Properties

Properties are aliases to methods that act as data fields, but on access dynamically generate and return their values, much like active bindings in R. Unlike fields, properties do not store their results with the instance, and do not contribute to the memory footprint of the simplicial complex.

# SimplexTree $ connected_components

Returns the connected components of the simplicial complex.

Each connected component is associated with an integer; the result of this function is an integer vector mapping the (ordered) set vertices to their corresponding connected component.

# SimplexTree $ vertices

Returns the 0-simplices in the simplicial complex as an n-length vector, where n is the number of 0-simplices.

# SimplexTree $ edges

Returns the 1-simplices in the simplicial complex as an ( n x 2 ) matrix, where n is the number of 1-simplices.

# SimplexTree $ triangles

Returns the 2-simplices in the simplicial complex as an ( n x 3 ) matrix, where n is the number of 2-simplices.

# SimplexTree $ quads

Returns the 3-simplices in the simplicial complex as an ( n x 4 ) matrix, where n is the number of 3-simplices.

Modifying the tree

# insert(SimplexTree, [simplices])

Inserts simplices into the simplex tree. Each simplex is ordered prior to insertion. If a simplex already exists, the tree is not modified. To keep the property of being a simplex tree, the proper faces of simplex are also inserted.

Insertion examples
st <- simplex_tree()
st %>% insert(c(1, 2, 3))       ## insert the simplex { 1, 2, 3 }
st %>% insert(list(c(4, 5), 6)) ## insert the simplices { 4, 5 } and { 6 }
print(st)
# Simplex Tree with (6, 4, 1) (0, 1, 2)-simplices

# remove(SimplexTree, [simplices])

Removes simplices from the simplex tree. Each simplex is ordered prior to removal. If a simplex doesn't exist, the tree is not modified. To keep the property of being a simplex tree, the cofaces of simplex are also removed.

Removal examples
st <- simplex_tree()
st %>% insert(list(c(1, 2, 3), c(4, 5), 6))
st %>% remove(c(2, 3)) ## { 2, 3 } and { 1, 2, 3 } both removed
print(st)
# Simplex Tree with (6, 3) (0, 1)-simplices

# contract(SimplexTree, [a, b])

Performs and edge contraction, contracting vertex b to vertex a. This is equivalent to removing vertex b from the simplex tree and augmenting the link of vertex a with the link of vertex b. If the edge does not exist in the tree, the tree is not modified.

Contraction example
st <- simplex_tree()
st %>% insert(1:3)
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0): 
st %>% contract(c(1, 3))
st %>% print_simplices("tree")
# 1 (h = 1): .( 2 )
# 2 (h = 0): 

# collapse(SimplexTree, ...)

  1. ([tau], [sigma])
  2. ((u, v), w)

Performs an elementary collapse. There are multiple simplifying operations that have been referred to as elementary collapses; this method provides two such operations.

(1) elementary collapse ( from 1 )

Collapses tau through its coface sigma if sigma is the only coface of tau, where tau and sigma are both simplexes.

(2) vertex collapse ( from 2 )

Collapses a free pair (u, v) -> (w), where u, v, and w are all vertices.

Note that an elementary collapse in this sense has an injectivity requirement that either u = w, such that (u, v) -> (u), or v = w, such that (u, v) -> (v). If (u, v) -> (w) is specified, where u != w and v != w , the collapse is decomposed into two elementary collapses, (u, w) -> (w) and (v, w) -> (w), and both are performed.

Collapse example
st <- simplex_tree()
st %>% insert(1:3)
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0): 
st %>% collapse(list(1:2, 1:3)) ## collapse in the sense of (1)
st %>% print_simplices("tree")
# 1 (h = 1): .( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0):
  
st %>% insert(list(1:3, 2:5))
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 3): .( 3 4 5 )..( 4 5 5 )...( 5 )
# 3 (h = 2): .( 4 5 )..( 5 )
# 4 (h = 1): .( 5 )
# 5 (h = 0): 
st %>% collapse(list(3, 4), 5) ## collapse in the sense of (2)
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 5 )..( 5 )
# 2 (h = 2): .( 5 )..( 5 )
# 5 (h = 1): .( 5 )

# expand(SimplexTree, k)

Performs a k-expansion, constructing the k-skeleton as a flag complex. The expansion is performed by successively inserting all the simplices of k-skeleton into the tree.

Expansion example
st <- simplex_tree()
st %>% insert(list(c(1, 2), c(2, 3), c(1, 3)))
st %>% print_simplices("tree")
# 1 (h = 1): .( 2 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0):
  
st %>% expand(k=2) ## expand to simplicial 2-complex
st %>% print_simplices("tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 0): 

# SimplexTree $ as_XPtr()

Converts the simplex tree into an XPtr, sometimes called an external pointer. XPtr's can be passed as an SEXP to other C/C++ functions via R's C API or Rcpp. For usage, see Usage with Rcpp.

This method does not register a delete finalizer.

Querying the tree

# print_simplices(SimplexTree, format = "short")

Prints a formatted summary of the simplicial complex to R's buffered output. By default, this shows up on the R console. Available outputs include "summary", "tree", "cousins", "short", "column", or "row". See the internal R documentation for more details.

# find(SimplexTree, [simplices])

Traverses the simplex tree downard starting at the root by successively using each of the ordered labels in simplex as keys. Returns a logical indicating whether simplex was found, for each simplex in simplices. Each simplex is sorted prior to traversing.

# degree(SimplexTree, [vertices])

Returns the degree of a given vertex or set of vertices.

# adjacent(SimplexTree, vertex)

Returns the set of vertices adjacent to a given vertex.

# is_face(SimplexTree, [tau], [sigma])

Returns a logical indicating whether tau is a face of sigma.

# is_tree(SimplexTree)

Returns a logical indicating whether the simplex tree is fully connected and acyclic.

# generate_ids(SimplexTree, n)

Generates n new vertex ids which do not exist in the tree according to the current id_policy.

Traversals

The SimplexTree data structure supports various types of traversals. A traversal is a (possibly optimized) path that allows iteration through a subset of the SimplexTree. Once a traversal is parameterized, they can be saved as variables and used as arguments into the free function traverse, straverse, and ltraverse. They can also be converted explicitly to list form via the as.list S3 specialization.

# traverse(traversal, f)

Executes a given traversal, passing each simplex in the traversal as the only argument to f. Output returned by f, if any, is discarded. This function does not return a value.

# ltraverse(traversal, f)

Executes a given traversal, passing each simplex in the traversal as the only argument to f, returning a list of the same length as the traversal path whose elements contain the result of f. ltraverse is meant to used in a similar way as lapply.

# straverse(traversal, f)

Executes a given traversal, passing each simplex in the traversal as the only argument to f, returning a vector of the same length as the traversal path whose elements contain the result of f. straverse is meant to used in a similar way as sapply.

The currently supported traversals are the following:

# preorder(SimplexTree, simplex)

Performs a preorder traversal of the SimplexTree starting at simplex. If simplex is not supplied, the traversal starts at the first vertex in the complex.

# level_order(SimplexTree, simplex)

Performs a level order traversal of the SimplexTree starting at simplex. If simplex is not supplied, the traversal starts at the first vertex in the complex.

# faces(SimplexTree, simplex)

Performs a traversal over the faces of simplex in the SimplexTree. Supplying simplex is required.

# cofaces(SimplexTree, simplex)

Traverse all of the cofaces (the star) of simplex in the SimplexTree. Supplying simplex is required.

# coface_roots(SimplexTree, simplex)

Traverse all of the roots whose subtrees comprise the cofaces of simplex in the SimplexTree. Supplying simplex is required.

# link(SimplexTree, simplex)

Traverse all of the simplices of the link of simplex in the SimplexTree. Supplying simplex is required.

# k_skeleton(SimplexTree, k, simplex)

Traverses all of simplices of dimension k or less in the SimplexTree. If simplex is not supplied, the traversal starts at the first vertex in the complex.

# k_simplices(SimplexTree, k, simplex)

Traverses all of simplices of dimension k in the SimplexTree. If simplex is not supplied, the traversal starts at the first vertex in the complex.

# maximal(SimplexTree)

Performs a traversal over the maximal faces in the SimplexTree. A simplex is considered maximal in this case if it has itself as its only coface.

Import / Export options

# serialize(SimplexTree)

Serializes the simplex tree K into some of smaller representation needed to recover the simplex tree.

# SimplexTree $ deserialize(complex, SimplexTree)

Deserializes complex, the result of serialize, into the given SimplexTree if supplied, or an empty SimplexTree otherwise.

Conversions

The full simplicial complex can always be converted to a list of matrices, and the 1-skeleton can also be converted to any of the standard graph representations.

# SimplexTree $ as_list()

Converts the simplex tree to list of (n x k) matrices, where each matrix represents the set of k-1 simplices.

# SimplexTree $ as_edge_list()

Converts the 1-skeleton to an edge list.

# SimplexTree $ as_adjacency_list()

Converts the 1-skeleton to an adjacency list.

# SimplexTree $ as_adjacency_matrix()

Converts the 1-skeleton to an adjacency matrix.

Usage with Rcpp

There are two ways to use a SimplexTree object with Rcpp.

1. Pure Rcpp

If you're developing purely in Rcpp, you can just use the SimplexTree class directly. The SimplexTree is header-only, and can be imported via the Rcpp::depends attribute (see Rcpp Attributes)

Example Usage in Rcpp
#include "Rcpp.h"

// [[Rcpp::depends(simplextree)]]
#include "simplextree.h"

void my_function(){
  SimplexTree st = SimplexTree();
  ...
}

If you're developing using a package, make sure to add simplextree to the LinkingTo list to ensure the header is properly included (e.g. -I"<...>/simplextree/include" should appear in the build steps).

2. Moving between R and Rcpp

A SimplexTree Rcpp module can be passed directly from R to any Rcpp method. To do so, export the object as an external pointer (XPtr) in R, pass the SEXP directly to the method, then wrap as as smart external pointer on the C++ side.

Example Usage with R and Rcpp

For example, on the C++ side, one might do:

// my_source.cpp
#include "Rcpp.h"

// [[Rcpp::depends(simplextree)]]
#include "simplextree.h"

[[Rcpp::export]]
void modify_tree(SEXP stree){
  Rcpp::XPtr<SimplexTree> stree_ptr(stree);
  stree_ptr->print_tree();
  ....
}

Then on the R-side, use as_XPtr method to get an XPtr.

# my_source.R
st <- simplex_tree()
modify_tree(st$as_XPtr())

Note that the C++ class contains a superset of the functionality exported to R, however they do not necessarily have the same bindings. See the header file for a complete list.

Visualizing the complex

Summarizing the complex can be achieved via either the overridden S3 print generic or the more detailed print_tree method.

library(simplextree)
st <- simplex_tree()
st %>% insert(list(1:3, 3:6))

print(st)
# Simplex Tree with (6, 9, 5, 1) (0, 1, 2, 3)-simplices

print_simplices(st, "tree")
# 1 (h = 2): .( 2 3 )..( 3 )
# 2 (h = 1): .( 3 )
# 3 (h = 3): .( 4 5 6 )..( 5 6 6 )...( 6 )
# 4 (h = 2): .( 5 6 )..( 6 )
# 5 (h = 1): .( 6 )
# 6 (h = 0): 

Alternatively, the plot generic is also overridden for SimplexTree objects (of class type 'Rcpp_SimplexTree') to display the complex with base graphics.

st %>% insert(list(6:7, 7:8, 8:10, 11:12))
plot(st)

simplex tree vis picture

There are many other options for controlling how the complex is displayed (e.g. the positions of the simplices, the sizes/linewidths of the vertices/edges, the vertex labels, whether to color only maximal faces or individual simplices, etc.). For the full documentation, see ?plot.simplextree.

More information

The full documentation for both the plot package is available in the man pages.

## see ?simplextree

References

1. Boissonnat, Jean-Daniel, and Clément Maria. "The simplex tree: An efficient data structure for general simplicial complexes." Algorithmica 70.3 (2014): 406-427.

2. Dey, Tamal K., Fengtao Fan, and Yusu Wang. "Computing topological persistence for simplicial maps." Proceedings of the thirtieth annual symposium on Computational geometry. ACM, 2014.

simplextree's People

Contributors

corybrunson avatar peekxc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

simplextree's Issues

analogs of graph-theoretic / network-analytic measures

There's some mileage to be got from adapting concepts from graph theory and network analysis to the analysis of simplicial complexes, whether simulated or constructed from data. This paper provides a good survey, including

  • degree, distance, and other tools to measure centrality
  • clustering coefficients and other measures of neighborhood closure
  • community detection and evaluation

These are not priorities for me, but they seem worth adding in the medium term, and they might make good student projects or first issues since (i expect) many could be done in R using the (amazing) traversals provided in simplextree.

handling, shifting, & permuting vertex indices

One occasional use need is to introduce new vertices "before" or "between" existing vertices, or to reorder existing vertices, relative to some height function that otherwise does not change. It would be convenient to simply use print as the height function, but this would require methods to shift and reorder vertex indices in a simplex tree. Would this be feasible to implement, given the C++ infrastructure?

Relatedly, the insert_simplex method interprets both 0 and 18446744073709551616 (264) as 0 but interprets negative numbers modulo 264 and ignores (without error) numbers greater than 264. My own preferred behavior would be to throw errors for cases outside [0,264–1]; there may be better options or good reasons to keep the behavior as-is, but i didn't see it documented anywhere.

Add templated and/or runtime level support simplex properties

Many graph and simplicial-complex-related algorithms rely on an association between the simplices in the structure and some abstract data type. For example, each node may have a 'color', or each edge may have real-valued 'weight', etc.

One method of achieving this is to leave it to the user to an make some external mapping themselves using the simplices or node pointers as keys, e.g. with a hashmap or some sorted map built using the lexicographical order). However, accessing the
properties then associated with each simplex can only be done in constant time under restrictive settings---in more general problems, access-time for certain simplicial operations may be logarithmic or even linear in the worst case.

Since every node in the simplex tree has a one to one map to a simplex, associating any data with the simplex tree amounts to adding an extra field member to the node class. One way of adding this functionality is to append a type-erasured property to each simplex, such as std::any, or to use indirection via something like void*. However, both approaches add unnecessary overhead in the case where properties are not needed, suggesting a statically-typed strategy would be better. Moreover, the type information can be useful for certain optimizations.

This issue documents the need to add properties to simplices that are available at compile-time and more generally at runtime. The current thought process is to add a POD-type to each node as a template argument and then specialize common situations with void. The more general runtime case could be handle possible with a std::function closure

as_list() omits highest-dimensional simplices

I'm building a workflow using the current development version and came across a bug with $as_list(): Only the d - 1L-dimensional simplices of a d-dimensional simplicial complex are returned (see the reprex below). If helpful, i'd be glad to submit a PR with a fix and a new test.

library(simplextree)
#> 
#> Attaching package: 'simplextree'
#> The following object is masked from 'package:utils':
#> 
#>     find
#> The following objects are masked from 'package:base':
#> 
#>     remove, serialize
st <- simplex_tree(list(1:3, 2:5, c(6, 7, 9), 7:8, 10))
st$n_simplices
#> [1] 10 12  6  1
st$as_list()
#> [[1]]
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,]    1    2    3    4    5    6    7    8    9    10
#> 
#> [[2]]
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
#> [1,]    1    1    2    2    2    3    3    4    6     6     7     7
#> [2,]    2    3    3    4    5    4    5    5    7     9     8     9
#> 
#> [[3]]
#>      [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,]    1    2    2    2    3    6
#> [2,]    2    3    3    4    4    7
#> [3,]    3    4    5    5    5    9

Created on 2020-11-06 by the reprex package (v0.3.0)

collapse interprets vertices starting at 0

After some experimenting, it seems that the collapse() method interprets vector IDs as originating at 0 rather than at 1. The following code, using the example from the documentation, illustrates this. Though i haven't examined the source code so something subtler may be at play.

Edit: Added some verification at the end. Perhaps the free pair are not removed after all? The error is no longer thrown, but the simplex tree remains unchanged.

library(simplextree)

# example from documentation
stree <- simplex_tree()
stree$insert_simplex(c(1,2,3))
stree$insert_simplex(c(2,3,4,5))
stree$insert_simplex(c(5,6,9))
stree$insert_simplex(c(7,8))
stree$insert_simplex(10)
stree$print_tree()
#> 1 (h = 2): .( 2 3 )..( 3 )
#> 2 (h = 3): .( 3 4 5 )..( 4 5 5 )...( 5 )
#> 3 (h = 2): .( 4 5 )..( 5 )
#> 4 (h = 1): .( 5 )
#> 5 (h = 2): .( 6 9 )..( 9 )
#> 6 (h = 1): .( 9 )
#> 7 (h = 1): .( 8 )
#> 8 (h = 0): 
#> 9 (h = 0): 
#> 10 (h = 0):

# a free face and its unique coface
tau <- c(3, 4, 5)
sigma <- c(2, 3, 4, 5)
# both simplices are in the ST
stree$find_simplex(tau)
#> [1] TRUE
stree$find_simplex(sigma)
#> [1] TRUE
# the free pair cannot be collapsed as encoded
stree$collapse(tau, sigma)
#> Warning in evalq((function (..., call. = TRUE, immediate. = FALSE,
#> noBreaks. = FALSE, : Invalid collapse: tau does not have sigma as its only
#> coface.
# no error is thrown when the simplices are encoded starting at 0
stree$collapse(tau - 1, sigma - 1)
# however, the ST remains unchanged, with the simplices included
stree$print_tree()
#> 1 (h = 2): .( 2 3 )..( 3 )
#> 2 (h = 3): .( 3 4 5 )..( 4 5 5 )...( 5 )
#> 3 (h = 2): .( 4 5 )..( 5 )
#> 4 (h = 1): .( 5 )
#> 5 (h = 2): .( 6 9 )..( 9 )
#> 6 (h = 1): .( 9 )
#> 7 (h = 1): .( 8 )
#> 8 (h = 0): 
#> 9 (h = 0): 
#> 10 (h = 0):
stree$find_simplex(tau)
#> [1] TRUE
stree$find_simplex(sigma)
#> [1] TRUE

Created on 2019-03-20 by the reprex package (v0.2.1)

vertex-level operations

Hi again! I wanted to flag some behavior of the vertex-level operations degree() and adjacent() i think is counterintuitive and would suggest changing slightly—though i haven't carefully checked their possible downstream effects. The reprex below illustrates, and i re-installed simplextree from GitHub. I use igraph as a comparison in both cases because the package is widely-used and it's UI has had a while to mature.

The degree() operation works the same way as an R6 method and as a wrapper, and in practice it is similar to the function of the same name in igraph. Two key behaviors are different, though:

  1. When no vertices argument is supplied, simplextree::degree(s) throws an error; i think the preferred behavior in this case is igraph's, in which igraph::degree(g) returns the degree sequence of the entire graph.
  2. When some vertices are not valid vertex IDs, the simplextree function returns a vector with 0s in those positions, possibly misleading the user. Again i think igraph's behavior is preferred: an error is thrown when any vertex IDs are invalid. (Though i'm most persuadable on this point.)

The adjacent() operation actually works differently as an R6 method and as a wrapper.

  1. It makes sense to me for st$adjacent(vertices) and adjacent(st, vertices) to have the same behavior, in particular to ease code translation between R6 method-chaining and base R function-nesting (or pipe-based method-chaining). Between the two, i prefer the more flexible wrapper behavior, which can handle one or multiple vertices.
  2. Similarly to degree(), i think the operation should fail if any vertex IDs are invalid rather than return numeric(0) or a list with numeric(0) entries.
  3. In case length(vertices) == 1, i suggest that the operation still return a (length-1) list rather than a vector. This would make scripting much easier, in that the structure of the returned value would not depend on the length of the input vector.

If we can agree to an appropriate set of behaviors, then i'll be glad to make the minor changes and submit a PR!

library(igraph)
#> Warning: package 'igraph' was built under R version 4.0.2
#> 
#> Attaching package: 'igraph'
#> The following objects are masked from 'package:stats':
#> 
#>     decompose, spectrum
#> The following object is masked from 'package:base':
#> 
#>     union
library(simplextree)
#> 
#> Attaching package: 'simplextree'
#> The following object is masked from 'package:igraph':
#> 
#>     contract
#> The following object is masked from 'package:utils':
#> 
#>     find
#> The following objects are masked from 'package:base':
#> 
#>     remove, serialize
# igraph degree behavior
g <- graph(c(1,2, 2,3, 1,3, 3,4))
degree(g)
#> [1] 2 2 3 1
degree(g, 0:5)
#> Error in degree(g, 0:5): At iterators.c:759 : Cannot create iterator, invalid vertex id, Invalid vertex id
# simplextree degree behavior
s <- simplex_tree(list(1:3, 3:4))
s$degree()
#> Error in s$degree(): could not find valid method
s$degree(0:5)
#> [1] 0 2 2 3 1 0
# igraph adjacent behavior
g <- graph(c(1,2, 2,3, 1,3, 3,4))
adjacent_vertices(g, 1)
#> [[1]]
#> + 2/4 vertices, from 12101ee:
#> [1] 2 3
adjacent_vertices(g, 0:5)
#> Error in adjacent_vertices(g, 0:5): At iterators.c:759 : Cannot create iterator, invalid vertex id, Invalid vertex id
# simplextree adjacent behavior
s$adjacent(1)
#> [1] 2 3
adjacent(s, 1)
#> [1] 2 3
s$adjacent(0:5)
#> Error in s$adjacent(0:5): Expecting a single value: [extent=6].
adjacent(s, 0:5)
#> [[1]]
#> numeric(0)
#> 
#> [[2]]
#> [1] 2 3
#> 
#> [[3]]
#> [1] 1 3
#> 
#> [[4]]
#> [1] 1 2 4
#> 
#> [[5]]
#> [1] 3
#> 
#> [[6]]
#> numeric(0)

Created on 2020-10-24 by the reprex package (v0.3.0)

Several reported installation issues

Installing the package

A few users have reported running into issues when installing the package, including the binary packages from CRAN. This issue is here to document potential solutions and workarounds, and things that people have tried to replicate the errors.

Known Issues

Apples Clang 6 and older

The file st_iterators.cpp in the inst/include folder uses a bit of C++ template meta programming, which includes some techniques that seem to not be supported by all compilers. @corybrunson and I have determined that at the very least, apples clang 6 (clang-600.0.56) and possibly versions older than that have trouble compiling that file. The open source version of clang 6 installed via homebrew doesn't seem to have this issue.

R 4.0 linking issues

After I updated to R 4.0.0 (2020-04-24) on my system (x86_64-apple-darwin17.0), I had issues getting the binary build of Rcpp from CRAN linking external ptrs correctly. Steps to reproduce:

  1. Install packrat, create a new project via packrat::init(...)
  2. Load the newly created packrat project in Rstudio. A newly created packrat project uses an isolated library, and should now contain the bare minimum set of base R packages to get Rstudio and R up and running.
  3. Install Rcpp with install.packages("Rcpp")
  4. Restart R and load Rcpp with library("Rcpp")

The error I receive after doing the above is:

> library(Rcpp)
Error: package or namespace load failed forRcppin dyn.load(file, DLLpath = DLLpath, ...):
 unable to load shared object '/Users/mpiekenbrock/R4/packrat/lib/x86_64-apple-darwin17.0/4.0.0/Rcpp/libs/Rcpp.so':
  dlopen(/Users/mpiekenbrock/R4/packrat/lib/x86_64-apple-darwin17.0/4.0.0/Rcpp/libs/Rcpp.so, 6): Symbol not found: _EXTPTR_PTR
  Referenced from: /Users/mpiekenbrock/R4/packrat/lib/x86_64-apple-darwin17.0/4.0.0/Rcpp/libs/Rcpp.so
  Expected in: /Library/Frameworks/R.framework/Resources/lib/libR.dylib
 in /Users/mpiekenbrock/R4/packrat/lib/x86_64-apple-darwin17.0/4.0.0/Rcpp/libs/Rcpp.so

I receive the same error when installing simplextree from CRAN, but simplextree depends heavily on Rcpp, so this makes sense. This may be related this issue.

Workaround

  1. Install devtools w/ install.packages("devtools")
  2. Install Rcpp from source via devtools::install_github("RcppCore/Rcpp")
  3. If installation was successful, reload the R session and try library(Rcpp) to check it loads correctly
  4. Install simplextree from source via devtools::install_github("peekxc/simplextree")
  5. If installation was successful, reload the R session and try library(simplextree) to check it loads correctly

I was able to get both Rcpp and simplextree loading correctly using these steps.

If (2) or (4) fail, then the point of failure can be determine by looking at where your compiler is failing to compile the source. If your compiler is relatively new, these errors should be fixable by isolating a VM--make an issue on the authors github.

If (3) fails, there's no point to try steps (4) or (5), and the path forward is unclear; possibly try updating the version or R/Rstudio.

If (4) or (5) fails but (1-3) work, please document it here.

Supported compilers

I have verified locally that the package compiles fine on the following compilers:

  • clang version 10.0.1
  • clang version 6.0.1
  • x86_64-w64-mingw32-g++ (GCC) 9.1.0
  • g++-10 (Homebrew GCC 10.2.0) 10.2.0

CI Servers

To help ensure cross-platform compatibility, windows, apple, and linux-based VMs have been created, which compile the package and run unit tests on every commit. They can be found at these two links:

Travis CI Checks the package on OS X (10.13.6) and Linux Xenial (16.04.06)
AppVeyor Checks the package on Windows (Visual Studio 2015)

If anyone has an image on e.g. AppVeyor they would like me to add to the testing coverage, please comment on this issue.

random generators / samplers

So far as i can tell, the theory of random simplicial complexes is fairly young: This basic definition, on which some limit theorems have been based, is from 2016. Still, the ability to generate arbitrary simplicial complexes quickly for testing purposes would be useful, and would set the stage for more specialized generators.

What would be the (or a) right way to do this in simplextree? Perhaps the tools exist for an efficient implemetation in some combination of existing functions, but i just wrote a clunky function to illustrate. I don't know that i'd prioritize it, but it seems like a good feature to eventually include.

library(simplextree)

sample_costa_farber <- function(n, prob) {
  
  if (length(prob) > 3L) warning("Only 2-dimensional complexes supported.")
  res <- simplex_tree()
  
  if (length(prob) < 1L) return(res)
  # select 0-simplices
  simp0 <- as.logical(rbinom(n, 1L, prob[[1L]]))
  simp0 <- seq(n)[simp0]
  res$insert(as.list(simp0))
  
  if (length(prob) < 2L || length(res$vertices) <= 1L) return(res)
  # select 1-simplices from potential boundaries
  simp1 <- as.logical(rbinom(choose(length(res$vertices), 2L), 1L, prob[[2L]]))
  simp1 <- combn(res$vertices, 2L)[, simp1, drop = FALSE]
  simp1 <- if (ncol(simp1) == 0L) list() else
    unlist(apply(simp1, 2L, list), recursive = FALSE)
  res$insert(simp1)
  
  if (length(prob) < 3L || length(res$edges) <= 2L) return(res)
  # select 2-simplices from potential boundaries
  simp2 <- merge(`colnames<-`(res$edges, c("v1", "v2")),
                 `colnames<-`(res$edges, c("v2", "v3")),
                 by = "v2")
  simp2 <- merge(simp2,
                 `colnames<-`(res$edges, c("v1", "v3")),
                 by = c("v1", "v3"))
  simp2 <- `colnames<-`(as.matrix(simp2), NULL)
  simp2 <- simp2[as.logical(rbinom(nrow(simp2), 1L, prob[[3L]])), , drop = FALSE]
  simp2 <- if (nrow(simp2) == 0L) list() else
    unlist(apply(simp2, 1L, list), recursive = FALSE)
  res$insert(simp2)
  
  return(res)
}

set.seed(34)
plot(sample_costa_farber(n = 6L, prob = c(.5, .75)))

plot(sample_costa_farber(n = 6L, prob = c(.5, .75, .875)))

plot(sample_costa_farber(n = 12L, prob = c(.5, .5, .5)))

Created on 2020-09-26 by the reprex package (v0.3.0)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.