The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Paths in the Space of Arcs

Glenn Davis

2025-06-10



Introduction

library(polarzonoid)

In the User Guide vignette it is shown that there is are homeomorphisms \[\begin{equation} A_n ~~ \longleftrightarrow ~~ \partial Z_n ~~ \longleftrightarrow ~~ \mathbb{S}^{2n} \end{equation}\] where \(A_n\) is the space of \(n\) or fewer pairwise disjoint arcs in the circle, and \(Z_n\) is the polar zonoid in \(\mathbb{R}^{2n+1}\).

In this vignette, we take some easily-defined paths in the sphere \(\mathbb{S}^{2n}\), compute the corresponding paths in the space of arcs \(A_n\), and display those paths as animated GIF plots. We make these plots with help of the package gifski and this function:

GIFfromarclist <- function( arclist, arcmat, index=1L, fps=5, vpsize=c(480,512) )
    {
    require( 'gifski' )

    # make temp folder
    pathtemp = tempdir()   # "./figs"     ;   if( ! file.exists(pathtemp) ) dir.create(pathtemp)
    count   = length( arclist )
    namevec = names( arclist )

    for( k in 1:count )
        {
        filename    = sprintf( "%s/plot%03d.png", pathtemp, k )
        png( filename=filename, width=vpsize[1], height=vpsize[2], units = "px" )
        u   = spherefromarcs( arclist[[k]] )
        plotarcs( arclist[[k]], labels=FALSE, margintext=namevec[k] )
        plotarcs( arcmat, labels=FALSE, rad=0.95, col='blue', lwd=1, add=TRUE )
        dev.off()
        }

    pathvec = dir( pathtemp, pattern="png$", full=T )
    gif_file = sprintf( "%s/animation%g.gif", pathtemp, index )
    out = gifski( pathvec, gif_file=gif_file, delay=1/fps, progress=F, width=vpsize[1], height=vpsize[2] )
    res = file.remove( pathvec )  # cleanup the .PNG files, leaving just the .GIF

    return(out)
    }



The Tubular Neighborhood of \(A_n \subseteq A_{n+1}\)

\(A_n\) is a stratum in \(A_{n+1}\) and there is a tubular neighborhood of \(A_n\) in \(A_{n+1}\). Since the codimension is 2, the fiber of a point \(a \in A_n\) in the neighborhood is an open 2-disk \(\text{int}(D^2)\). The boundary of the 2-disk is a circle, which we think of this circle as a closed path of points at a small and equal distance from \(a\).

We can compute this circle by mapping \(a\) to \(\mathbb{S}^{2n+2}\) using the homeomorphism \(A_{n+1} ~ \rightleftarrows ~ \mathbb{S}^{2n+2}\), computing the circle in \(\mathbb{S}^{2n+2}\), and then mapping back to \(A_{n+1}\) using the inverse homeomorphism. The function we will use is:

circleofarcs <- function( arcmat, rad=0.1, count=180 )
    {
    res = spherefromarcs_plus( arcmat, n=nrow(arcmat)+1L )
    
    out     = vector( count, mode='list' )
    namevec = character( count )
    
    for( i in 1:count )
        {
        theta   = 2*pi * (i-1)/count     # theta is in radians, starting at 0
        u   = res$u  +  rad * ( cos(theta)*res$normal[ ,1]  +  sin(theta)*res$normal[ ,2] )
        out[[i]]      = arcsfromsphere( u )     # u is automatically unitized
        namevec[i]    = sprintf( "i = %d", i )
        }

    names(out)  = namevec

    return( out )
    }

The case of \(n{=}0\) is easy to visualize. The space \(A_0\) is 2 points, which map to the “poles” of the sphere \(\mathbb{S}^2\). The empty arc maps to the “south” and the full circle maps to the “north” Around each pole is a small circle. For the “south pole” it is a circle of tiny arcs, almost empty. For the “north pole” it is a circle of very large arcs, almost the full circle. In both cases, the length of the arcs is constant, while the center loops around \(\mathbb{S}^1\).



A Circle of Arcs in \(A_2\) around a Single Arc in \(A_1\)

The goal of this section is to take a single arc \(a \in A_1\) and plot the 2 arcs in \(A_2\) that circle around \(a\).

# arcmat1 is a single semicircle centered at (1,0)
arcmat1 = matrix( c(0,pi), nrow=1, ncol=2 )
circle  = circleofarcs( arcmat1, count=90 )
gif_file = GIFfromarclist( circle, arcmat1, index=1, vpsize=c(480,480) )
circle of arcs around a single arc
circle of arcs around a single arc

The original arc is drawn in blue, and shrunken a little so it does not overlap with the nearby pair of arcs.



A Circle of Arcs in \(A_3\) around Two Arcs in \(A_2\)

This section is the same as the previous one, except we bump up the complexity. Now \(a \in A_2\) is a pair of arcs, and we plot the 3 arcs in \(A_3\) that circle around \(a\).

# arcmat2 is: an arc filling quadrant #1, plus an arc filling quadrant #3
arcmat2 = matrix( c((1/4)*pi,pi/2, (5/4)*pi,pi/2), nrow=2, ncol=2, byrow=TRUE )
circle  = circleofarcs( arcmat2, count=90 )
gif_file = GIFfromarclist( circle, arcmat2, index=2, vpsize=c(480,480) )
circle of 3 arcs around a pair of arcs
circle of 3 arcs around a pair of arcs

The original pair of arcs are drawn in blue, and shrunken a little so they does not overlap with the nearby triple of arcs.



Empty Arc to Full Circle, and Back Again

In this one, the path in the sphere starts at the “south pole”, goes up through an arbitrary point along a great semicircle to the antipodal “north pole”, and then down the other side. The full path is a great circle, and is pieced together using the function slerp() (spherical linear interpolation) from [1].

poletopole <- function( arcmat, thetamax=pi/36, n=NULL )
    {
    u  = spherefromarcs( arcmat, n=n )

    #  make south and north poles
    m       = length(u)    
    south   = c( rep(0,m-1), -1 ) ;      north   = -south
    
    path1   = slerp( south, u, thetamax=thetamax )   #   from "south pole" to u
    path2   = slerp( u, north, thetamax=thetamax )   #   from u to "north pole"

    path    = rbind( path1, path2 ) # concatenate the 2 paths
    path    = rbind( path, -path )  # back down the other side to south pole again
    
    count   = nrow(path)
    out     = vector( count, mode='list' )
    for( i in 1:count )
        out[[i]] = arcsfromsphere( path[i, ] )

    names(out)  = sprintf( "y_%d = %.3f", m, path[ ,m] )

    return( out )    
    }
# arcmat3 is 3 arcs of different lengths
arcmat3 = matrix( c(0.375,0.75,  2.3,1.1,  4.6,2.8), ncol=2, byrow=TRUE )
arclist  = poletopole( arcmat3 )
gif_file = GIFfromarclist( arclist, arcmat3, index=3, fps=2, vpsize=c(480,480) )
empty to full circle, and back to empty on the other side
empty to full circle, and back to empty on the other side

The defining arcs are drawn in blue, and shrunken a little so they do not overlap with the arcs along the path. Note that at each step, there are 3 arcs, except at the poles, i.e. the empty arc and the full circle.

But it is easy to make an example where the number of arcs is not a constant.

# arcmat1 is a single arc, but it splits into 3 arcs on either side of the path from pole to pole
arcmat1 = matrix( c(1.5,2.9), ncol=2, byrow=TRUE )
arclist  = poletopole( arcmat1, n=3 )
gif_file = GIFfromarclist( arclist, arcmat1, index=4, fps=2, vpsize=c(480,480) )
empty to full circle, and back to empty on the other side
empty to full circle, and back to empty on the other side

The defining arc is drawn in blue, and shrunken a little so it does not overlap with the arcs along the path.



References

[1]
SHOEMAKE, Ken. Animating rotation with quaternion curves. International Conference on Computer Graphics and Interactive Techniques [online]. 1985, 19(3), 245. Available at: https://doi.org/10.1145/325165.325242



Session Information

This document was prepared Tue Jun 10, 2025 with the following configuration:
R version 4.5.0 (2025-04-11 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=C                          
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] gifski_1.32.0-2   flextable_0.9.7   polarzonoid_0.1-2

loaded via a namespace (and not attached):
 [1] katex_1.5.0             jsonlite_2.0.0          compiler_4.5.0         
 [4] equatags_0.2.1          Rcpp_1.0.14             zip_2.3.3              
 [7] xml2_1.3.8              jquerylib_0.1.4         fontquiver_0.2.1       
[10] systemfonts_1.2.3       textshaping_1.0.1       uuid_1.2-1             
[13] yaml_2.3.10             fastmap_1.2.0           R6_2.6.1               
[16] gdtools_0.4.2           curl_6.2.2              knitr_1.50             
[19] logger_0.4.0            openssl_2.3.2           bslib_0.9.0            
[22] rlang_1.1.6             V8_6.0.3                cachem_1.1.0           
[25] xfun_0.52               sass_0.4.10             cli_3.6.5              
[28] digest_0.6.37           grid_4.5.0              askpass_1.2.1          
[31] lifecycle_1.0.4         evaluate_1.0.3          glue_1.8.0             
[34] data.table_1.17.2       fontLiberation_0.1.0    officer_0.6.8          
[37] ragg_1.4.0              xslt_1.5.1              fontBitstreamVera_0.1.1
[40] rmarkdown_2.29          tools_4.5.0             htmltools_0.5.8.1      

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.