octrope_ropelength, octrope_struts, octrope_minrad, octrope_set_levels, octrope_strut_ends, octrope_est_mem, octrope_curvelength, octrope_strutfile_read, octrope_strutfile_write - fast library of functions for computing the ropelength of a polygonal knot or link
Octrope library (liboctrope, -loctrope)
#include<octrope.h>
double octrope_curvelength(const octrope_link *L);
double octrope_poca(const octrope_link *L, void *mem, const int memsize);
double octrope_minradval(const octrope_link *L);
double octrope_ropelength(const octrope_link *L, void *mem, const int memsize, const double lambda);
double octrope_thickness(const octrope_link *L, void *mem, const int memsize, const double lambda);
int octrope_struts(const octrope_link *L,
const double cutoff, const double epsilon, octrope_strut *strutlist, int sl_size, double *shortest,
void *mem, const int memsize);
double octrope_minrad(const octrope_link *L, const double cutoff, const double epsilon, octrope_mrloc *min_rad_locs, int mr_size, int *num_min_rad_locs);
void octrope(const octrope_link *L,
double *ropelength, double *thickness_ret, double *curve_len_ret, double *min_rad_ret, double *min_strut_ret,
const double mr_cutoff, const double mr_epsilon, octrope_mrloc *min_rad_locs, const int mr_size, int *num_min_rad_locs,
const double strut_cutoff, const double strut_epsilon, octrope_strut *strutlist, const int sl_size, int *num_strut_ret,
void *mem, const int memsize, const double lambda);
int octrope_est_mem(const int num_edges);
void octrope_set_levels(int levels);
void octrope_set_debug(int level);
void octrope_strut_ends(const octrope_link *L, const octrope_strut *S, octrope_vector se[2]);
void octrope_strutfile_read(octrope_link *L, int *n_struts,octrope_strut **strutlist, int *n_mrloc,octrope_mrloc **mrlist, FILE *file);
void octrope_strutfile_write(int n_struts,octrope_strut *strutlist, int n_mrloc,octrope_mrloc *mrlist, FILE *file);
The octrope library uses a fast octree-based algorithm known as Octrope to find the ropelength of a knot or link specified as a polygonal space curve. We use a variation of Rawdon's definition of ropelength for polygons (see Approximating Smooth Thickness, Eric Rawdon, J. Knot Theory Ramifications, 9(1):113-145, 2000 for details) with the modification that the ``stiffness'' of the rope is given by parameter lambda. lambda = 1.0 corresponds to ``perfectly flexible'' rope, while larger values of lambda are more stiff. Values of lambda below 1.0 correspond to unphysically ``floppy'' rope.
Ropelength is controlled by two things: local minima of the distance in space between pairs of points on the curve (pairs of closest approach, or POCAs) and the minimal (polygonal) radius of curvature of the curve (octrope_minradval()). The set of minimum length POCAs are the struts of the curve: they represent self-contacts of a tube around the curve.
Ropelength is computed from minradval, poca length and lambda by
MinRad = octrope_minradval(L); StrutLen = octrope_poca(L,NULL,0); Length = octrope_curvelength(L);
if ( StrutLen/2.0 < MinRad/lambda ) {
ropelength = 2.0*Length/StrutLen;
} else { ropelength = Length/( MinRad/lambda );
} This means that strut length is in control of thickness when it is smaller than the diameter of curvature (2*MinRad) scaled by 1/lambda and curvature is in control of thickness otherwise.
WARNING! The definition of thickness has changed in version 1.3. Until that time, octrope returned a diameter thickness and used it in calculating ropelength. It now uses radius thickness. Hence thickness will be half and ropelength double what they were previously.
The octrope algorithm works by grouping nearby edges of the link L into a tree structure. It then compares each edge to the rest of the tree by checking the intersections of groups of edges with the slab-like regions of space which might contain POCAs with the current edge. There is a balance between the number of levels in the tree (a higher-resolution tree can be used to eliminate more edges from contention before edge-edge checking begins) and the speed of edge-edge checks. In ordinary test cases, we have found that an n edge link runs fastest with a tree of 0.75*log2(n) levels, and has performance in O(n ln(n)).
However, in cases (such as certain types of random links) where the edges are not well-separated in space, or are very long with respect to the total size of the link, the octrope algorithm may not improve performance, and can even be slower than the standard ropelength algorithm. For this reason, we provide the function
void octrope_set_levels(int levels)
to allow the user to fine-tune the algorithm. Calling this function
with a positive argument sets the number of levels of the octree for
subsequent calls to all functions which find POCAs: octrope_poca(),
octrope_ropelength(), octrope_thickness(), octrope_struts()
and
octrope(). Calling the function with a zero argument allows these
functions to set their own bounds for the number of levels in the
octree. Calling octrope_set_levels(1)
reduces these procedures to a
relatively efficient version of the ordinary O(n^2)
ropelength-computation algorithm.
Like LAPACK, the library has a simplified interface for the most common calls and an advanced interface which allows full access to the details of the computation.
The simplified functions octrope_curvelength(), octrope_poca(),
octrope_minradval(), octrope_thickness(), and octrope_ropelength()
require only the link L, the stiffness parameter lambda and an
optional workspace specified by mem and memsize. If mem ==
NULL or memsize == 0, the library allocates its own
workspace. (See Memory Handling)
Certain curves (such as convex plane polygons) have no POCAs according
to octrope's definition. When no POCAs are found, octrope_poca()
returns
DBL_MAX. Similarly, some curves (such as straight line segments) have
infinite MinRad (or thickness). In these cases, we set the return value
to DBL_MAX as well. We note that octrope provides a DBL_MAX symbol
if its compilation environment does not.
The advanced interface consists of octrope_struts(),
octrope_minrad()
and octrope(). All of these functions take pointers
to return values or buffers. If the user is not interested in these values,
passing NULL pointers is encouraged since it can allow octrope to
save time by not computing values which are not of interest to the user.
octrope_struts()
This function accepts the lambda, mem, and memsize arguments as above. The new arguments are
The function finds all POCAs of L and returns the length of the shortest
POCA in shortest. All POCAs with length less than cutoff (if
nonzero) or shortest + epsilon (if cutoff is zero) are recorded in
strutlist, which is a buffer of sl_size entries of type octrope_strut,
allocated by the caller. The number of these POCAs is returned by the
function. If the function finds more struts than sl_size, it will throw out
the extra struts. The octrope_strut data type may be converted with
octrope_strut_ends()
into an array of two octrope_vector types which locate
the ends of the strut in space.
octrope_minrad()
The octrope_minrad()
function accepts the lambda, mem, and memsize
arguments as above. Its new arguments are
The function finds the smallest value of MinRad among all the vertices of L and returns it. All vertices with MinRad less than cutoff (if cutoff is nonzero) or within epsilon of the smallest MinRad (if cutoff is zero) are recorded in the min_rad_locs buffer. This buffer is an array of mr_size elements of type octrope_mrloc. The number of locations found is passed back in num_min_rad_locs. As above, if the number of vertices is larger than mr_size, the procedure will throw out the extra struts.
octrope()
The main octrope()
call uses all these arguments, and introduces several
new arguments as well:
The variables strut_cutoff and mr_cutoff play the role of cutoff in
the octrope_struts()
and octrope_minrad()
calls above. The same is true of
strut_epsilon and mr_epsilon. The other new variables are return values,
which are set to the values documented above. As before, if thickness_ret or
min_rad_ret are infinite, they are set to DBL_MAX. If no POCA lengths
are found, min_strut_ret is set to DBL_MAX.
Each of these routines sets the library global octrope_error_num to an integer greater than 0 if an error occurs during a run. In that case, the global octrope_error_str is set to a (hopefully) helpful error description. Calling procedures may count on library functions to return in all cases, but should not assume that the results are good without checking these error codes.
The octrope library is designed for situations when it will be called many times and spend only a small amount of time handling each call. In these circumstances, memory allocation and deallocation can consume a significant fraction of total runtime. To mitigate this effect, the design allows the user to pass a memory buffer mem of size mem_size bytes for octrope's workspace. To help estimate the correct size for mem, use octrope_est_mem(octrope_link_edges(L)), which returns a size in bytes for the given link. If the user-supplied buffer is too small, octrope will silently allocate its' own buffer and proceed. None of the buffers passed to library functions need to be zeroed before use.
All the procedures documented here take input of type octrope_link. The link structure (found in octrope.h) is documented separately (the octrope_link manpage), and interface functions are provided for link creation and destruction, as well as reading from vertices in the link structure. It is important to respect the interface for link creation and destruction as the library implementation of the link data structure actually allocates and frees slightly larger vertex buffers than the link appears to have. This allows the library to efficiently implement ``wraparound'' addressing for closed links.
The strut
data structure is as follows:
typedef struct strut_type { int component[2]; /* On which component the strut starts/ends */ int lead_vert[2]; /* Lead vertex of the edge on which it starts/ends */ double position[2]; /* How far along the starting/ending edge (0 to 1) */ double length; /* How long it is */ double compression; /* This is used by some ropelength minimizing algorithms */ /* to store a "Lagrange multiplier" associated to the strut. */ } strut;
The mrloc
data structure stores points where the MinRad of the
polygon is equal to its thickness:
typedef struct octrope_mrloc_type { int component; /* On which component the vertex is found */ int vert; /* The vertex number. */ double mr; /* The value of MinRad at this vertex. */ double compression; /* Again, a Lagrange multiplier */ /* Only set by external programs. */ } octrope_mrloc;
The lists of strut
and mrloc
structures returned by the various
octrope
functions can be written to disk with
octrope_strutfile_write
and read from disk by user applications
with octrope_strutfile_read
. Such files are human-readable text
files with the following header:
STRUTS <n_struts> <n_mrlocs> followed by C<n_struts> lines, each containing four C<ints>
<component[0]> <component[1]> <lead_vert[0]> <lead_vert[1]>
followed by four double
s
<position[0]> <position[1]> <length> <compression>
The file then contains n_mrlocs
lines, each containing two ints
<component> <vertex>
followed by two doubles
<mr> <compression>
These files may contain arbitrary amounts of whitespace, including line breaks. They may also be commented like a makefile or shell script: Any text between # and the end of a line is ignored.
The function octrope_set_debug(int level) allows the user to see debugging information printed from within the octrope library. The level is set between 0 and 9, with 9 being the most output and 0 the least. This feature is generally useful only when maintaining the library. The current debug level can be viewed with octrope_debug_level().
To compute ropelength of a link with library allocated storage:
ropelength = octrope_ropelength(L,NULL,0,1.0);
To compute ropelength with user-allocated storage:
void *mem; int memsize;
memsize = octrope_est_mem(octrope_link_edges(L)); mem = malloc(memsize,sizeof(char));
ropelength = octrope_ropelength(L,mem,memsize,1.0);
To find all struts with lengths within 0.0001 of the shortest strut for a link L using library allocated storage:
octrope_strut *strutlist; int sl_size; double shortest; int num_struts;
sl_size = 10*octrope_link_edges(L); /* A very conservative buffer size */ strutlist = malloc(sl_size,sizeof(octrope_strut);
num_struts = octrope_struts(L,0.0001,strutlist,sl_size,&shortest,NULL,0);
To find ropelength, struts within 0.0001 of the shortest strut, and vertices with MinRad within 0.1 of the smallest MinRad for a link L with stiffness 1.0 (perfectly flexible rope) using library-allocated storage:
octrope_strut *strutlist; int sl_size, num_struts; octrope_mrloc *min_rad_locs; int mr_size, num_min_rad_locs;
double ropelength;
sl_size = 10*octrope_link_edges(L); /* A very conservative buffer size */ strutlist = malloc(sl_size,sizeof(octrope_strut);
mr_size = octrope_link_edges(L) + L->nc; min_rad_locs = malloc(mr_size,sizeof(octrope_mrloc);
/* This buffer size is as least as large as the number of vertices of L. */
octrope(L,&ropelength,NULL,NULL,NULL,NULL, 0.1,min_rad_locs,mr_size,&num_min_rad_locs, 0.0001,strutlist,sl_size,&num_struts, NULL,0,1.0);
To check for errors after a library call and print the error message:
if (octrope_err_num > 0) {
fprintf(stderr,"%s",octrope_err_str); exit(1);
}
Finding struts can be very sensitive to small changes in epsilon, since many ropelength-optimized configurations contain POCAs with lengths very close to the shortest length.
We have not implemented octrope for any other curve models (splines, biarcs).
The computation for the number of levels in the octree should
adaptively tune itself to your system and dataset at compile-time (and
retune at run-time for ropelength minimization algorithms where
POCA-finding functions such as octrope_ropelength()
or octrope_struts()
are called often.
The version of the algorithm which runs after octrope_set_levels(1)
has some needless overhead involved in setting up an octree that's not
used in the calculation. This could be optimized out.
the octrope_link manpage, the struts manpage, the ropelength manpage
Ted Ashton and Jason Cantarella.
This library was first released in September 2004. It is described in the paper A Fast Octree-Based Algorithm for Computing Ropelength by Ashton and Cantarella, which is included in the library distribution as octrope-paper.pdf.
This library is covered by the GNU General Public License for free software, modified as follows. Any publication of computations performed using the Octrope library must include a citation of the paper A Fast Octree-Based Algorithm for Computing Ropelength mentioned above.