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)
}
\(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\).
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) )
The original arc is drawn in blue, and shrunken a little so it does not overlap with the nearby pair of arcs.
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) )
The original pair of arcs are drawn in blue, and shrunken a little so they does not overlap with the nearby triple of arcs.
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) )
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) )
The defining arc is drawn in blue, and shrunken a little so it does not overlap with the arcs along the path.
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