snappea-3.0d3/0040755000175000017500000000000007237315405011276 5ustar babbabsnappea-3.0d3/SnapPeaKernel/0040755000175000017500000000000007041625415013764 5ustar babbabsnappea-3.0d3/SnapPeaKernel/file_formats/0040755000175000017500000000000006742675732016454 5ustar babbabsnappea-3.0d3/SnapPeaKernel/file_formats/GeneratorsFileFormat0100444000175000017500000000713506742675763022466 0ustar babbab SnapPea Matrix Generators File Format A generators file must begin with the line % Generators to distinguish it from a triangulation file, which begins with "% Triangulation", or a link projection file which begins with "% Link Projection". Next comes an integer telling how many matrices are present. The matrices may by in either O(3,1) or SL(2,C). Orientation-reversing generators are allowed in O(3,1) but not in PSL(2,C). If the matrices are in SL(2,C), read_generators() will convert them to O(3,1). read_generators() can tell which format you are using by comparing the total number of matrix entries to the total number of matrices. (SL(2,C) matrices contain 8 real entries each, while O(3,1) matrices contain 16 real entries each.) In PSL(2,C), the entries of a matrix a b c d should be written as Re(a) Im(a) Re(b) Im(b) Re(c) Im(c) Re(d) Im(d). Actually, the arrangement of the white space (blanks, tabs and returns) is irrelevant, so if you prefer you may write a PSL(2,C) matrix as, say, Re(a) Im(a) Re(b) Im(b) Re(c) Im(c) Re(d) Im(d). In O(3,1) the entries of each matrix should be written as m00 m01 m02 m03 m10 m11 m12 m13 m20 m21 m22 m23 m30 m31 m32 m33 where the 0-th coordinate is the timelike one. Again, the arrangement of the white space is irrelevant. Here are two sample files. Sample #1. PSL(2,C) generators for the Borromean rings complement. % Generators 6 0.000000000000000 0.000000000000000 0.000000000000000 -1.000000000000000 0.000000000000000 -1.000000000000000 2.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 1.000000000000000 0.000000000000000 1.000000000000000 2.000000000000000 0.000000000000000 1.000000000000000 -1.000000000000000 0.000000000000000 -1.000000000000000 0.000000000000000 1.000000000000000 1.000000000000000 1.000000000000000 1.000000000000000 -1.000000000000000 0.000000000000000 1.000000000000000 0.000000000000000 -1.000000000000000 1.000000000000000 1.000000000000000 1.000000000000000 0.000000000000000 -2.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 1.000000000000000 0.000000000000000 1.000000000000000 0.000000000000000 0.000000000000000 0.000000000000000 -2.000000000000000 0.000000000000000 1.000000000000000 0.000000000000000 Sample #2. O(3,1) generators for a mirrored regular ideal tetrahedron. % Generators 4 1.25 -0.433012 -0.433012 -0.433012 0.433012 0.25 -0.75 -0.75 0.433012 -0.75 0.25 -0.75 0.433012 -0.75 -0.75 0.25 1.25 -0.433012 +0.433012 +0.433012 0.433012 0.25 +0.75 +0.75 -0.433012 +0.75 0.25 -0.75 -0.433012 +0.75 -0.75 0.25 1.25 +0.433012 -0.433012 +0.433012 -0.433012 0.25 +0.75 -0.75 0.433012 +0.75 0.25 +0.75 -0.433012 -0.75 +0.75 0.25 1.25 +0.433012 +0.433012 -0.433012 -0.433012 0.25 -0.75 +0.75 -0.433012 -0.75 0.25 +0.75 0.433012 +0.75 +0.75 0.25 (Note: I truncated sqrt(3)/4 = 0.433012701892219323... to 0.433012 to fit the above matrices within the width of this window. If you want to try out this example, please restore the high-precision value.) snappea-3.0d3/SnapPeaKernel/file_formats/LinkProjectionFileFormat0100444000175000017500000001073306742675763023305 0ustar babbab// SnapPea Link Projection File Format // // This document explains SnapPea's file format for storing // link projections. The link projections are written as text- // only files, to allow easy cross-platform file transfers, and // also to make it easy for other computer programs to create // link projection files. // // This document contains an annotated link projection file. // If you remove all comments (i.e. remove all text preceded by // a double slash) you'll be left with a SnapPea-readable link // projection file. // // This sample file describes the Hopf link. // // --------- // | | // | ----|---- // | | | | // ----|---- | // | | // --------- // // The horizontal axis runs left-to-right (as you would expect), // but the vertical axis runs top-to-bottom (as you may or may // not expect, depending on what window system you're used to). // The square's corner coordinates are at multiples of 50. // // Although this simple example doesn't illustrate it, the // file format allow "unfinished" link components. That is, // some or all of the link components may be topological // intervals instead of topological circles. Indeed, a link // component may consist of a single vertex. Morever, the // format allows for a "hot vertex". The hot vertex is the // vertex (if any) which the link editor will join to the next // point the user clicks. // // The file format doesn't require general position, but SnapPea's // triangulation algorithm does. To keep it happy, please don't // let edges pass through other vertices (they can come as close // as they want) and don't let crossings get too close to one // another (their coordinates will be rounded to integer values, // and in any case human viewers will appreciate having crossings // stay at least a half dozen pixels away from one another). // (As you may have guessed, the coordinates are pixel coordinates.) // // Now on to a line-by-line description of the file . . . // Every link projection file begins with the following line. // This tells SnapPea what file type to expect (link projection, // triangulation, or generators). % Link Projection // Components // // The '2' (see below) means this link has two components. // // The components are numbered implicitly, beginning at 0. // For each component, there is a line giving the indices of // the component's first and last vertices. For a circular // component the first and last vertex will be the same, and // may be any vertex in the component. For a linear component // the first and last vertices will be the endpoints. // So, in this example, we see that // // component 0 begins and ends at vertex 0, and // component 1 begins and ends at vertex 4. 2 0 0 4 4 // Vertices // // The '8' means that this example has 8 vertices. // // The vertices are numbered implicitly, beginning at 0. // For each vertex, there is a line giving the vertex's // coordinates. In this example, // // vertex 0 is at ( 50, 50) // vertex 1 is at (150, 50) // ... // vertex 7 is at (100, 200) // // Recall that the vertical axis (the second coordinate) is // directed top-to-bottom. 8 50 50 150 50 150 150 50 150 100 100 200 100 200 200 100 200 // Edges // // The next '8' means that this example has 8 edges. // // The edges are numbered implicitly, beginning at 0. // For each edge, there is a line giving the indices of the // edge's endpoints. In this example, // // edge 0 runs from vertex 0 to vertex 1 // edge 1 runs from vertex 1 to vertex 2 // ... // edge 7 runs from vertex 7 to vertex 4 // // All edges are directed, so the order of the vertices // is important. 8 0 1 1 2 2 3 3 0 4 5 5 6 6 7 7 4 // Crossings // // The following '2' means that this example has 2 crossings. // // For each crossing, there is a line giving the indices of the // underedge and the overedge. In this example // // edge 2 passes under edge 7 // edge 4 passes under edge 1 2 2 7 4 1 // If there is a "hot vertex" (cf. above) its index is given here. // If there is no hot vertex, a -1 appears here. // In this example, there is no hot vertex. (Indeed, a hot vertex // makes sense only when it's the endpoint of a linear component.) -1 // Technical note: Just because a component has the same first // and last vertex, you can't conclude that it's a circular // component. It could be a component consisting of a single // vertex and no edges. // The end. snappea-3.0d3/SnapPeaKernel/file_formats/ReadMe0100444000175000017500000000145606742675763017541 0ustar babbabSnapPea File Formats Strictly speaking, the SnapPea kernel is platform independent and defines no file formats. Nevertheless, while writing the Macintosh SnapPea UI I tried to design file formats which would be suitable on all platforms (Macintosh, Windows & Unix). In particular, all SnapPea files are standard text files. I strongly encourage the use of these file formats on all platforms. The "unix kit" provides unix-style code (fprintf/fscanf) for reading them and passing them to the SnapPea kernel. SnapPea uses three types of files: triangulation files matrix generator files link projection files Each is described in a separate document in this directory. The only format the user needs to be aware of is that for matrix generators, which is essentially just a list of matrices. snappea-3.0d3/SnapPeaKernel/file_formats/TriangulationFileFormat0100444000175000017500000001020306742675763023163 0ustar babbab// SnapPea Triangulation File Format // // This document contains an annotated triangulation file. If you remove // all comments (i.e. remove all text preceded by a double slash) you'll // be left with a SnapPea-readable triangulation file. The manifold is // a (1,1) Dehn filling on one cusp of the Whitehead link complement, which // turns out to be homeomorphic to the figure eight knot complement. // // The information on the hyperbolic structure (solution type, volume, // and tetrahedron shapes) is provided solely for human readers. // The SnapPea kernel ignores this information and recomputes the // hyperbolic structure from scratch. // // The meridian and longitude are optional. The SnapPea kernel will // use them if they are provided. If they are all zero, it will use // a default meridian and longitude. // // The numbers of torus and Klein bottle cusps are also optional. // You may set both to zero (and of course omit the cusp topology // and Dehn filling information) if you want SnapPea to figure out // the cusps for you and assign arbitrary indices. // // 97/12/6 This file format now allows finite ( = non ideal) vertices. // If you have set the number of torus and Klein bottle cusps to zero // (cf. preceding paragraph) SnapPea will figure out for itself which // vertices are ideal and which are finite (by checking the Euler // characteristic of each boundary component). // If you have manually specified the real cusps, then simply assign // an index of -1 for the "incident cusp" of each finite vertex. // Even if there is more than one finite vertex, all get cusp index -1. // // Any low-dimensional topologist should be able to understand the // header information. There is no need to understand the information // about each tetrahedron, but if you want to understand it anyhow you // should first read the file triangulation.h. % Triangulation // Every triangulation file must begin // with the header "% Triangulation". sample // name of manifold geometric_solution 2.02988321 // SolutionType and volume (cf. SnapPea.h) oriented_manifold // Orientability // oriented_manifold // or nonorientable_manifold // or unknown_orientability CS_known 0.00000000000000000000 // CS_known or CS_unknown // if CS_known, value is given 2 0 // number of torus and Klein bottle cusps torus 1.000000000000 1.000000000000 // topology and Dehn filling // for cusp #0 torus 0.000000000000 0.000000000000 // topology and Dehn filling // for cusp #1 // 0 0 means the cusp is unfilled 4 // number of tetrahedra 3 1 2 1 // neighbors (cf. Triangulation.h) 0132 0321 0132 3120 // gluings (in contrast to the old file format, // permutations are given in "forwards order", // e.g. 0123 is the identity) 1 1 0 1 // incident cusps 0 0 0 0 0 0 0 0 0 1 0 -1 -1 1 0 0 // meridian (right sheet) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // meridian (left sheet) 0 1 0 -1 0 0 -1 1 1 1 0 -2 -1 1 0 0 // longitude (right sheet) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // longitude (left sheet) 0.429304013127 0.107280447008 // tetrahedron shape // and similarly for the remaining three tetrahedra . . . 0 2 3 0 3120 1230 0132 0321 1 1 0 1 0 0 0 0 0 0 1 -1 1 0 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 -1 -1 0 2 -1 2 -1 0 -1 1 0 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.692440400998 0.318147957810 3 3 1 0 1023 0213 3012 0132 1 1 1 0 0 0 0 0 0 0 0 0 1 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 1 2 -2 0 0 0 -1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1.692440400998 0.318147957810 0 2 2 1 0132 1023 0213 0132 1 1 1 0 0 0 0 0 0 0 1 -1 1 -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 -2 1 -2 0 1 -1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.692440400998 0.318147957810 snappea-3.0d3/SnapPeaKernel/ReadMe0100444000175000017500000000121307001163657015035 0ustar babbabVirtually all of the kernel documentation is contained in the source code itself. If you're not sure where to begin, you should (1) Read SnapPea.h. This is technically not part of the kernel, but rather the protocol for how the kernel and the UI should communicate. (2) Read triangulation.h. It describes the data structure which SnapPea uses to pass manifolds from one module to another. (3) Read the source code in the files of greatest interest to you. Triangulation.h makes extensive references to what is found where in the remaining code. Please see detab.c for information on getting the tab stops to look right on a unix machine. snappea-3.0d3/SnapPeaKernel/detab.c0100444000175000017500000000452507043063601015203 0ustar babbab// detab.c // // SnapPea's source code looks good when tab stops are // set to 4 spaces. Unfortunately the unix tradition // is 8 space tab stops. So to view SnapPea code on // a unix machine, you should either // // (1) set your editor to use 4 space tab stops, or, // if that's not convenient, // // (2) use this program to replace the tab stops with // spaces. To do so, just type // // gcc -o detab detab.c // chmod 644 */*.c */*.h // detab */*.c */*.h // chmod 444 */*.c */*.h #include #define TAB_SIZE 4 #define SCRATCH_FILE "/tmp/detab.temp" int main(int argc, char *argv[]) { int i; FILE *fp1, *fp2; int pos, // position in line, modulo TAB_SIZE j; int ch; char scratch[128]; for (i = 1; i < argc; i++) { fp1 = fopen(argv[i], "r"); if (fp1 == NULL) { printf("couldn't open %s\n", argv[i]); continue; } fp2 = fopen(SCRATCH_FILE, "w"); if (fp2 == NULL) { printf("couldn't open %s\n", SCRATCH_FILE); fclose(fp1); continue; } pos = 0; while ( (ch = getc(fp1)) != EOF ) switch (ch) { case '\t': for (j = TAB_SIZE - pos%TAB_SIZE; --j >= 0; ) putc(' ', fp2); pos = 0; break; case '\n': putc(ch, fp2); pos = 0; break; default: putc(ch, fp2); pos++; break; } fclose(fp1); fclose(fp2); fp1 = fopen(argv[i], "w"); if (fp1 == NULL) { printf("couldn't open %s\n", argv[i]); continue; } fp2 = fopen(SCRATCH_FILE, "r"); if (fp2 == NULL) { printf("couldn't open %s\n", SCRATCH_FILE); fclose(fp1); continue; } while ((ch = getc(fp2)) != EOF) putc(ch, fp1); fclose(fp1); fclose(fp2); } strcpy(scratch, "rm -f "); strcat(scratch, SCRATCH_FILE); system(scratch); return 0; } snappea-3.0d3/SnapPeaKernel/unix_kit/0040755000175000017500000000000007204007700015606 5ustar babbabsnappea-3.0d3/SnapPeaKernel/unix_kit/ReadMe0100444000175000017500000000133406742676203016702 0ustar babbabSnapPea Unix Kit This directory provide some files which make it easy to call SnapPea functions from within your own C programs. (1) unix_sample_main.c provides a sample main(), which you can modify to suit your purposes. (2) unix_file_io.c provides the functions Triangulation *get_triangulation(char *file_name); void save_triangulation(Triangulation *manifold, char *file_name); for use in your own main(). You needn't modify (or even look at) this file. (3) unix_UI.c provides a set of functions through which the SnapPea kernel can pass messages to the user. You needn't modify (or even look at) this file. If you have questions, please contact me at weeks@geom.umn.edu. Jeff snappea-3.0d3/SnapPeaKernel/unix_kit/unix_cusped_census.c0100444000175000017500000001341707011565622021671 0ustar babbab/* * unix_cusped_census.c * * This function * * Triangulation *GetCuspedCensusManifold( * int aNumTetrahedra, * Orientability anOrientability, * int anIndex); * * provides an easy way for unix-style C programs to access the census * of cusped hyperbolic 3-manifolds. The arguments are interpreted * in the following not-so-natural way. The parameter aNumTetrahedra * must have the value 5, 6 or 7. If it's 5, you're asking for the * census of all cusped manifolds of 5 or fewer tetrahedra, and the * parameter anOrientability is ignored. If it's 6 (resp. 7) you're * asking for the census of cusped manifolds of exactly 6 (resp. 7) * tetrahedra, and the orientability must be given by anOrientability * (either oriented_manifold or nonorientable_manifold). * anIndex specifies a manifold within a census. For example, * GetCuspedCensusManifold(5, [ignored], 0) would return m000, which * happens to be the Gieseking manifold, while * GetCuspedCensusManifold(7, oriented_manifold, 3551) would * return the manifold SnapPea usually calls v3551. * * For a sample main(), please see unix_cusped_census_main.c. * * GetCuspedCensusManifold() will look for the files terse5, terse6o, * terse6n, terse7o and terse7n in the directory CuspedCensusData. * (The directory and file names may, of course, be changed below * if desired.) */ #include "SnapPea.h" #include "unix_cusped_census.h" #include #include #ifdef MACINTOSH #define FILE5 ":CuspedCensusData:terse5.bin" #define FILE6o ":CuspedCensusData:terse6o.bin" #define FILE6n ":CuspedCensusData:terse6n.bin" #define FILE7o ":CuspedCensusData:terse7o.bin" #define FILE7n ":CuspedCensusData:terse7n.bin" #else #define FILE5 "CuspedCensusData/terse5.bin" #define FILE6o "CuspedCensusData/terse6o.bin" #define FILE6n "CuspedCensusData/terse6n.bin" #define FILE7o "CuspedCensusData/terse7o.bin" #define FILE7n "CuspedCensusData/terse7n.bin" #endif /* * The following two arrays tell your program how many manifolds * are in each cusped census. They contain meaningful values * only for the indices 5, 6 and 7. They're useful when you want * to write a loop to iterate through all the manifolds in a census. * Note that the census of 5 or fewer tetrahedra includes both * orientable and nonorientable manifolds together, so * * gNumOrientableCuspedCensusMflds[5] * = gNumNonorientableCuspedCensusMflds[5] * = the total number of manifolds * = 415 */ int gNumOrientableCuspedCensusMflds[8] = {0, 0, 0, 0, 0, 415, 962, 3552}, gNumNonorientableCuspedCensusMflds[8] = {0, 0, 0, 0, 0, 415, 259, 887}; static TersestTriangulation *ReadCensusBuffer(char *aFileName, int aNumManifolds); Triangulation *GetCuspedCensusManifold( int aNumTetrahedra, Orientability anOrientability, int anIndex) { int theNumCensusManifolds; TersestTriangulation *theData; char theName[10]; int theCensus; Triangulation *theTriangulation; /* * Read the census data into the buffers only when necessary. */ static TersestTriangulation *theData5 = NULL, *theData6o = NULL, *theData6n = NULL, *theData7o = NULL, *theData7n = NULL; if (aNumTetrahedra < 5 || aNumTetrahedra > 7) return NULL; switch (anOrientability) { case oriented_manifold: theNumCensusManifolds = gNumOrientableCuspedCensusMflds [aNumTetrahedra]; break; case nonorientable_manifold: theNumCensusManifolds = gNumNonorientableCuspedCensusMflds[aNumTetrahedra]; break; default: return NULL; }; if (anIndex < 0 || anIndex >= theNumCensusManifolds) return NULL; switch (aNumTetrahedra) { case 5: if (theData5 == NULL) theData5 = ReadCensusBuffer(FILE5, theNumCensusManifolds); theData = theData5; sprintf(theName, "m%.3d", anIndex); theCensus = 5; break; case 6: switch (anOrientability) { case oriented_manifold: if (theData6o == NULL) theData6o = ReadCensusBuffer(FILE6o, theNumCensusManifolds); theData = theData6o; sprintf(theName, "s%.3d", anIndex); theCensus = 6; break; case nonorientable_manifold: if (theData6n == NULL) theData6n = ReadCensusBuffer(FILE6n, theNumCensusManifolds); theData = theData6n; sprintf(theName, "x%.3d", anIndex); theCensus = 8; /* this is how the kernel identifies the nonorientable 6-tet census */ break; default: return NULL; } break; case 7: switch (anOrientability) { case oriented_manifold: if (theData7o == NULL) theData7o = ReadCensusBuffer(FILE7o, theNumCensusManifolds); theData = theData7o; sprintf(theName, "v%.4d", anIndex); theCensus = 7; break; case nonorientable_manifold: if (theData7n == NULL) theData7n = ReadCensusBuffer(FILE7n, theNumCensusManifolds); theData = theData7n; sprintf(theName, "y%.3d", anIndex); theCensus = 9; /* this is how the kernel identifies the nonorientable 7-tet census */ break; default: return NULL; } break; default: return NULL; } if (theData == NULL) return NULL; rehydrate_census_manifold(theData[anIndex], theCensus, anIndex, &theTriangulation); set_triangulation_name(theTriangulation, theName); return theTriangulation; } static TersestTriangulation *ReadCensusBuffer( char *aFileName, int aNumManifolds) { FILE *fp; TersestTriangulation *theData; fp = fopen(aFileName, "rb"); if (fp == NULL) return NULL; theData = (TersestTriangulation *) malloc(aNumManifolds * sizeof(TersestTriangulation)); if (theData == NULL) uFatalError("ReadCensusBuffer", "unix_cusped_census"); if (fread(theData, sizeof(TersestTriangulation), aNumManifolds, fp) != aNumManifolds) uFatalError("ReadCensusBuffer", "unix_cusped_census"); fclose(fp); return theData; } snappea-3.0d3/SnapPeaKernel/unix_kit/unix_cusped_census.h0100444000175000017500000000066207001154611021663 0ustar babbab/* * unix_cusped_census.h * * Please see unix_cusped_census.c for documentation. * For a sample main(), see unix_cusped_census_main.c. */ #ifndef _unix_cusped_census_ #define _unix_cusped_census_ #include "SnapPea.h" extern int gNumOrientableCuspedCensusMflds[8], gNumNonorientableCuspedCensusMflds[8]; extern Triangulation *GetCuspedCensusManifold(int aNumTetrahedra, Orientability anOrientability, int anIndex); #endif snappea-3.0d3/SnapPeaKernel/unix_kit/unix_cusped_census_main.c0100444000175000017500000000151007001155103022650 0ustar babbab/* * unix_cusped_census_main.c * * The main() in this file illustrates the use of the unix-style * GetCuspedCensusManifold(). It reads all the manifolds of * 5 or fewer tetrahedra and prints their volumes. */ #include "SnapPea.h" #include "unix_cusped_census.h" #include #include int main(void) { int theIndex; Triangulation *theTriangulation; for (theIndex = 0; theIndex < gNumOrientableCuspedCensusMflds[5]; theIndex++) { theTriangulation = GetCuspedCensusManifold( 5, oriented_manifold /* ignored for 5-tet census */, theIndex); if (theTriangulation != NULL) { printf("%s %lf\n", get_triangulation_name(theTriangulation), volume(theTriangulation, NULL)); free_triangulation(theTriangulation); } else printf("Couldn't read census manifold.\n"); } return 0; } snappea-3.0d3/SnapPeaKernel/unix_kit/unix_file_io.c0100444000175000017500000002741307204007700020425 0ustar babbab/* * unix_file_io.c * * This hacked together file allows unix-style programs * to read and save Triangulations. */ #include #include #include #include "unix_file_io.h" #define READ_OLD_FILE_FORMAT 0 #define FALSE 0 #define TRUE 1 /* * gcc complains about the * * use of `l' length character with `f' type character * * in fprintf() calls. Presumably it considers the 'l' unnecessary * because even floats would undergo default promotion to doubles * in the function call (see section A7.3.2 in Appendix A of K&R 2nd ed.). * Therefore I've changed "%lf" to "%f" in all fprintf() calls. * If this makes trouble on your system, change it back, and please * let me know (weeks@northnet.org). */ static TriangulationData *ReadNewFileFormat(FILE *fp); static void WriteNewFileFormat(FILE *fp, TriangulationData *data); #if READ_OLD_FILE_FORMAT extern FuncResult read_old_manifold(FILE *fp, Triangulation **manifold); #endif Triangulation *get_triangulation( char *file_name) { FILE *fp; Boolean theNewFormat; Triangulation *manifold; /* * If the file_name is nonempty, read the file. * If the file_name is empty, read from stdin. */ if (strlen(file_name) > 0) { fp = fopen(file_name, "r"); if (fp == NULL) return NULL; /* * Take a peek at the first line to see whether this is * the new file format or the old one. */ theNewFormat = (getc(fp) == '%'); rewind(fp); } else { fp = stdin; theNewFormat = TRUE; /* read only the new format from stdin */ } if (theNewFormat == TRUE) { TriangulationData *theTriangulationData; theTriangulationData = ReadNewFileFormat(fp); data_to_triangulation(theTriangulationData, &manifold); free(theTriangulationData->name); free(theTriangulationData->cusp_data); free(theTriangulationData->tetrahedron_data); free(theTriangulationData); } else { #if READ_OLD_FILE_FORMAT read_old_manifold(fp, &manifold); #else fprintf(stderr, "The manifold is in the old file format.\n"); fprintf(stderr, "I recommend converting it to the new format.\n"); fprintf(stderr, "If absolutely necessary, I can provide code for reading the old format.\n"); fprintf(stderr, "Questions? Contact me at weeks@northnet.org.\n"); uFatalError("get_triangulation", "unix file io"); #endif } if (fp != stdin) fclose(fp); return manifold; } static TriangulationData *ReadNewFileFormat( FILE *fp) { char theScratchString[100]; TriangulationData *theTriangulationData; int theTotalNumCusps, i, j, k, v, f; /* * Read and ignore the header (% Triangulation). */ fgets(theScratchString, 100, fp); /* * Allocate the TriangulationData. */ theTriangulationData = (TriangulationData *) malloc(sizeof(TriangulationData)); if (theTriangulationData == NULL) uFatalError("ReadNewFileFormat", "unix file io"); theTriangulationData->name = NULL; theTriangulationData->cusp_data = NULL; theTriangulationData->tetrahedron_data = NULL; /* * Allocate and read the name of the manifold. */ theTriangulationData->name = (char *) malloc(100 * sizeof(char)); if (theTriangulationData->name == NULL) uFatalError("ReadNewFileFormat", "unix file io"); /* * The name will be on the first nonempty line. */ do fgets(theTriangulationData->name, 100, fp); while (theTriangulationData->name[0] == '\n'); /* * Overwrite the newline character. */ theTriangulationData->name[strlen(theTriangulationData->name) - 1] = 0; /* * Read the filled solution type. */ fscanf(fp, "%s", theScratchString); if (strcmp(theScratchString, "not_attempted") == 0) theTriangulationData->solution_type = not_attempted; else if (strcmp(theScratchString, "geometric_solution") == 0) theTriangulationData->solution_type = geometric_solution; else if (strcmp(theScratchString, "nongeometric_solution") == 0) theTriangulationData->solution_type = nongeometric_solution; else if (strcmp(theScratchString, "flat_solution") == 0) theTriangulationData->solution_type = flat_solution; else if (strcmp(theScratchString, "degenerate_solution") == 0) theTriangulationData->solution_type = degenerate_solution; else if (strcmp(theScratchString, "other_solution") == 0) theTriangulationData->solution_type = other_solution; else if (strcmp(theScratchString, "no_solution") == 0) theTriangulationData->solution_type = no_solution; else uFatalError("ReadNewFileFormat", "unix file io"); /* * Read the volume. */ fscanf(fp, "%lf", &theTriangulationData->volume); /* * Read the orientability. */ fscanf(fp, "%s", theScratchString); if (strcmp(theScratchString, "oriented_manifold") == 0) theTriangulationData->orientability = oriented_manifold; else if (strcmp(theScratchString, "nonorientable_manifold") == 0) theTriangulationData->orientability = nonorientable_manifold; else if (strcmp(theScratchString, "unknown_orientability") == 0) theTriangulationData->orientability = unknown_orientability; else uFatalError("ReadNewFileFormat", "unix file io"); /* * Read the Chern-Simons invariant, if present. */ fscanf(fp, "%s", theScratchString); if (strcmp(theScratchString, "CS_known") == 0) theTriangulationData->CS_value_is_known = TRUE; else if (strcmp(theScratchString, "CS_unknown") == 0) theTriangulationData->CS_value_is_known = FALSE; else uFatalError("ReadNewFileFormat", "unix file io"); if (theTriangulationData->CS_value_is_known == TRUE) fscanf(fp, "%lf", &theTriangulationData->CS_value); else theTriangulationData->CS_value = 0.0; /* * Read the number of cusps, allocate an array for the cusp data, * and read the cusp data. */ fscanf(fp, "%d%d", &theTriangulationData->num_or_cusps, &theTriangulationData->num_nonor_cusps); theTotalNumCusps = theTriangulationData->num_or_cusps + theTriangulationData->num_nonor_cusps; theTriangulationData->cusp_data = (CuspData *) malloc(theTotalNumCusps * sizeof(CuspData)); if (theTriangulationData->cusp_data == NULL) uFatalError("ReadNewFileFormat", "unix file io"); for (i = 0; i < theTotalNumCusps; i++) { if (fscanf(fp, "%s%lf%lf", theScratchString, &theTriangulationData->cusp_data[i].m, &theTriangulationData->cusp_data[i].l) != 3) uFatalError("ReadNewFileFormat", "unix file io"); switch (theScratchString[0]) { case 't': case 'T': theTriangulationData->cusp_data[i].topology = torus_cusp; break; case 'k': case 'K': theTriangulationData->cusp_data[i].topology = Klein_cusp; break; default: uFatalError("ReadNewFileFormat", "unix file io"); } } /* * Read the number of tetrahedra, allocate an array for the * tetrahedron data, and read the tetrahedron data. */ fscanf(fp, "%d", &theTriangulationData->num_tetrahedra); theTriangulationData->tetrahedron_data = (TetrahedronData *) malloc(theTriangulationData->num_tetrahedra * sizeof(TetrahedronData)); if (theTriangulationData->tetrahedron_data == NULL) uFatalError("ReadNewFileFormat", "unix file io"); for (i = 0; i < theTriangulationData->num_tetrahedra; i++) { /* * Read the neighbor indices. */ for (j = 0; j < 4; j++) { fscanf(fp, "%d", &theTriangulationData->tetrahedron_data[i].neighbor_index[j]); if (theTriangulationData->tetrahedron_data[i].neighbor_index[j] < 0 || theTriangulationData->tetrahedron_data[i].neighbor_index[j] >= theTriangulationData->num_tetrahedra) uFatalError("ReadNewFileFormat", "unix file io"); } /* * Read the gluings. */ for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) { fscanf(fp, "%1d", &theTriangulationData->tetrahedron_data[i].gluing[j][k]); if (theTriangulationData->tetrahedron_data[i].gluing[j][k] < 0 || theTriangulationData->tetrahedron_data[i].gluing[j][k] > 3) uFatalError("ReadNewFileFormat", "unix file io"); } /* * Read the cusp indices. * * 99/06/04 Allow an index of -1 on "cusps" that are * really finite vertices. */ for (j = 0; j < 4; j++) { fscanf(fp, "%d", &theTriangulationData->tetrahedron_data[i].cusp_index[j]); if (theTriangulationData->tetrahedron_data[i].cusp_index[j] < -1 || theTriangulationData->tetrahedron_data[i].cusp_index[j] >= theTotalNumCusps) uFatalError("ReadNewFileFormat", "unix file io"); } /* * Read the peripheral curves. */ for (j = 0; j < 2; j++) /* meridian, longitude */ for (k = 0; k < 2; k++) /* righthanded, lefthanded */ for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) fscanf(fp, "%d", &theTriangulationData->tetrahedron_data[i].curve[j][k][v][f]); /* * Read the filled shape (which the kernel ignores). */ fscanf(fp, "%lf%lf", &theTriangulationData->tetrahedron_data[i].filled_shape.real, &theTriangulationData->tetrahedron_data[i].filled_shape.imag); } return theTriangulationData; } void save_triangulation( Triangulation *manifold, char *file_name) { TriangulationData *theTriangulationData; FILE *fp; /* * If the file_name is nonempty, write the file. * If the file_name is empty, write to stdout. */ if (strlen(file_name) > 0) { fp = fopen(file_name, "w"); if (fp == NULL) { printf("couldn't open %s\n", file_name); return; } } else fp = stdout; triangulation_to_data(manifold, &theTriangulationData); WriteNewFileFormat(fp, theTriangulationData); free_triangulation_data(theTriangulationData); if (fp != stdout) fclose(fp); } static void WriteNewFileFormat( FILE *fp, TriangulationData *data) { int i, j, k, v, f; fprintf(fp, "%% Triangulation\n"); if (data->name != NULL) fprintf(fp, "%s\n", data->name); else fprintf(fp, "untitled"); switch (data->solution_type) { case not_attempted: fprintf(fp, "not_attempted"); break; case geometric_solution: fprintf(fp, "geometric_solution"); break; case nongeometric_solution: fprintf(fp, "nongeometric_solution"); break; case flat_solution: fprintf(fp, "flat_solution"); break; case degenerate_solution: fprintf(fp, "degenerate_solution"); break; case other_solution: fprintf(fp, "other_solution"); break; case no_solution: fprintf(fp, "no_solution"); break; } if (data->solution_type != not_attempted) fprintf(fp, " %.8f\n", data->volume); else fprintf(fp, " %.1f\n", 0.0); switch (data->orientability) { case oriented_manifold: fprintf(fp, "oriented_manifold\n"); break; case nonorientable_manifold: fprintf(fp, "nonorientable_manifold\n"); break; } if (data->CS_value_is_known == TRUE) fprintf(fp, "CS_known %.16f\n", data->CS_value); else fprintf(fp, "CS_unknown\n"); fprintf(fp, "\n%d %d\n", data->num_or_cusps, data->num_nonor_cusps); for (i = 0; i < data->num_or_cusps + data->num_nonor_cusps; i++) fprintf(fp, " %s %16.12f %16.12f\n", (data->cusp_data[i].topology == torus_cusp) ? "torus" : "Klein", data->cusp_data[i].m, data->cusp_data[i].l); fprintf(fp, "\n"); fprintf(fp, "%d\n", data->num_tetrahedra); for (i = 0; i < data->num_tetrahedra; i++) { for (j = 0; j < 4; j++) fprintf(fp, "%4d ", data->tetrahedron_data[i].neighbor_index[j]); fprintf(fp, "\n"); for (j = 0; j < 4; j++) { fprintf(fp, " "); for (k = 0; k < 4; k++) fprintf(fp, "%d", data->tetrahedron_data[i].gluing[j][k]); } fprintf(fp, "\n"); for (j = 0; j < 4; j++) fprintf(fp, "%4d ", data->tetrahedron_data[i].cusp_index[j]); fprintf(fp, "\n"); for (j = 0; j < 2; j++) /* meridian, longitude */ for (k = 0; k < 2; k++) /* righthanded, lefthanded */ { for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) fprintf(fp, " %2d", data->tetrahedron_data[i].curve[j][k][v][f]); fprintf(fp, "\n"); } if (data->solution_type != not_attempted) fprintf(fp, "%16.12f %16.12f\n\n", data->tetrahedron_data[i].filled_shape.real, data->tetrahedron_data[i].filled_shape.imag); else fprintf(fp, "%3.1f %3.1f\n\n", 0.0, 0.0); } } snappea-3.0d3/SnapPeaKernel/unix_kit/unix_sample_main.c0100444000175000017500000000342407001155555021307 0ustar babbab/* * unix_sample_main.c * * This file provides a sample main() illustrating how a unix-style * program can read individual manifolds from files and do computations * with them. This sample main just computes the volume, but you could * of course compute anything else available in the SnapPea kernel. * * unix_cusped_census_main.c unix_closed_census_main.c provide * unix-style code for looping through the census manifolds, * so your program can do computations in batch mode. * The new Python SnapPea is even more convenient for such purposes. */ #include #include #include "SnapPea.h" #include "unix_file_io.h" #define MANIFOLD_DIRECTORY "" #define DIRECTORY_NAME_LENGTH 0 #define MAX_MANIFOLD_NAME_LENGTH 50 static Triangulation *get_manifold(void); int main(void) { Triangulation *manifold; double vol; int digits; while ((manifold = get_manifold()) != NULL) { /* * Here's where you put your code to do whatever * you want with the manifold. */ vol = volume(manifold, &digits); printf("volume is %.*lf\n", digits, vol); /* * Free the manifold, and check for memory leaks. */ free_triangulation(manifold); verify_my_malloc_usage(); } return 0; } static Triangulation *get_manifold() { char manifold_name[MAX_MANIFOLD_NAME_LENGTH], path_name[DIRECTORY_NAME_LENGTH + MAX_MANIFOLD_NAME_LENGTH]; Triangulation *manifold; do { printf("\nmanifold name: "); fgets(manifold_name, MAX_MANIFOLD_NAME_LENGTH, stdin); manifold_name[strlen(manifold_name) - 1] = 0; /* overwrite the newline */ if (manifold_name[0] == 0) return NULL; strcpy(path_name, MANIFOLD_DIRECTORY); strcat(path_name, manifold_name); manifold = get_triangulation(path_name); } while (manifold == NULL); return manifold; } snappea-3.0d3/SnapPeaKernel/unix_kit/unix_UI.c0100444000175000017500000000265507056521144017345 0ustar babbab/* * unix_UI.c * * This file contains a quick and dirty implementation of some UI functions * required for using the SnapPea kernel in a stdio.h environment. * It's intended for use by mathematicians who want to call the SnapPea * kernel functions from within their own C code. */ #include "SnapPea.h" #include #include void uAcknowledge( const char *message) { fprintf(stderr, "%s\n", message); } void uFatalError( char *function, char *file) { fprintf( stderr, "A fatal error has occurred in the function %s() in the file %s.c.\n", function, file); exit(1); } void uAbortMemoryFull(void) { fprintf(stderr, "out of memory\n"); exit(2); } int uQuery( const char *message, const int num_responses, const char *responses[], const int default_response) { /* * If desired you could write this function to obtain a response from the user, * but for now it set up to return the default response, to facilitate batch * computations. */ fprintf(stderr, "Q: %s\nA: %s\n", message, responses[default_response]); return default_response; } /* * The "long computation" feature is unused, but we define its * global variables and functions to avoid a link error. */ Boolean gLongComputationInProgress, gLongComputationCancelled; void uLongComputationBegins( char *message, Boolean is_abortable) { } FuncResult uLongComputationContinues() { return func_OK; } void uLongComputationEnds() { } snappea-3.0d3/SnapPeaKernel/unix_kit/CuspedCensusData/0040755000175000017500000000000007011566033021011 5ustar babbabsnappea-3.0d3/SnapPeaKernel/unix_kit/CuspedCensusData/terse5.bin0100444000175000017500000001645606742675047022741 0ustar babbab76!34%).+57)%:7WUN9RFHEoՑdX:7BAM:p*n:3OIE<7KMAuUUUUUUa<7KJVUUUUUUb<7"HOǒDuK)<6VTC:4!FF7Wwrq<3IQM1:S<'$HO䏩56:.$TM:S<4TUI6).AH4VZi6$#POQ6hdjK,lgs JMK,lpd JN7Ww<7KL@ր7'HlmX)o܀.+Vwr.e ,Pod,avFmI#VMkmlI#V Merb5Ehhf83)hQhfO83.Lkgdぽk/,avMmʄSFhfv&ـRFlrʄ.Mojvnt`,Lkf5{fFX&7Lk5{fFX&Vf/gc"Ohڀ,.Rlm"Bo(ڀ3'Awv5i;&UVrFrQ.MvMoQ \+'Vwct=QFlRGo\.+Vw95 1MIms+KF܀.+Mlr_T0fROmkp +Chom6q>_/7hHomq>_UjHOw<ژ@ ;r76rfB x7Wlot7WqrwI(QfrHEkXx\v0sFqgr76rf,DN'/sVfvڀVM`ob X"W+,fFvmݨVj7WAk X"W+t,Fm۵T(ƺV'5EkbIhhLlIDlF cxwOJFo^7haf<(,mvcu+hoe(W'Zƃ7ohTVN)aVVhW.7r'rRHf &xwOJF/2xwFOJJGoPpslK+kn|OԶBpp%fKdkڀVHwh9y?wFOv y$tToVQwhBi't7amjboړcCOrh boړ50ngl5Upp`@Puu$BbdbK,wibJ7Lkiu"oOv%+6t=<|ʃ,LniusaT҃76fiZtK(Mvڦ1-ZVMk'c$ZVMk) Ĥ¸u2co/qܪFr76afQr3mAHA,:R|dԃLRfr(5|: $oHh~o9C؃SHmUUUUUUaSOmUUUUUU=уwkl \؃'Rha iGXMfFuMt5fEO {AZ0,mtŃ +mP̷҃,2a7󄖦,ft톝USฃlFRmg[htLR2r u7Pm7rrc\ ~[tLR/r]RVάK,l )mwK+w)m.WmOF~?7Wwr76af@j4U>,tvJ_(yt5fRO JƵ5Ń,vrbDL O r76aos#)IQfrz;1b ZVHkόxzI&F7WwtUH/cQ5=jEabJm.2>Ft+MmMdҔq-pJrXÀ,/tr# zkxI2Vm|')lORm qjVQwkpnr7WwKMvv/ 5Obf +ehkK#}ԃPHhvL᚟t+MmFV/wIԃKrvVCY n*/uoh<gKfh-=6,Fff=5ZVHkN%ndpMfvKMvo,UtI(Qr ۛ,MwuuchRjԃPHhv(~pt0fRRتfvr,2af#\F3r.ThF2)/Kvw؃'RhajڲYMF` f*3*:r)mVV[#?|&Ń,fv qYZVHw?ZVHwG'5>y!ZVHkt5fROcC W6Vdr3mVHьfy؃bVmHSHfq VR`)f̃+KwVSsVZUcf근VʃVkf.$(Qqhh=,chV˷ 7Oh,=! 6`\7vm(KAa,=! 6`7r3mAR]=)L7OoXhLhl7Wt7Wtr3mVV$E=wRlkt5fRR#/Bt+Qfee-xp;x+vVv-xp`DAf,Fm!߄(<"fdOibRLMml(vMmyX)7Wh.QqcB. $ں,FF&H0z7WkpMr7bh/@`oH@hR2qvsr3:R/qv-Π[?!̃+Kwvc6<2rOeDGsn(Qrfji15hf $DNbm`L2h#haMvFh5ϥ/HmdjORh9؃.vmeZDku oaԃL-h8)%߸2cOo <{4R/rf vmb VHwv<6wRlkW@wWKlLmRf $mAaʃ50ln6=0NMvMv bϱ>UffvD%UR/qv~"9eBуOlr4]NsoKqR/qh۽%/Vvs5QMvMj50ngdd_9Vni7Rrjz/ZK,wd97Lktd97fr jz/YOrF5Fq$VfvFq$R2qv-#ͣ6AuԃUohZQ9iwRlkLmRfh׆OJlA"eeOmVh#fh1`qW`wR2rm#Drn4TtVH]VYbFj5JWjO]VYeOpt`)6Qrm >g7FʃVkmi|HlqʃVkm](Q u,soʃVwh-E\t5UuuUhvLmRfSk@V0ʃVwhg6,ʃVkmC9W LmRf6FO`UsK.Hnd7KlvKHvKoԃ0ef@UsIUrh@vRIUsFsnappea-3.0d3/SnapPeaKernel/unix_kit/CuspedCensusData/terse6o.bin0100444000175000017500000004164406742675047023116 0ustar babbab7WwbD7WwDZ7W6wkz^W:Z7W6wAF0h`BQqvK>xcLK,d>xc̍+KFF{rl7W F{rl+Vmm cK\\+KOwcK\Z7W6w:r)mAH`p#ԍNortp#V+KwƗwSU,Kw@_V+Kw:0U,Kw2]TU7L , +W_ U,Kw3ò!U,KwU\+KkX4ݴUmOLYZ7W6w}=bwZ7W6w' "l7WDKp摮k"Rf1@~'rFrMl*QOw#I/57W6J<{Fe$hMv;aba>ЏfoYO'luV7WlMUV7WlrV!+KE)V U7Kk .Ε7K7)p _ +KSY,NV7LkIrvV7LkR!5LU,Kkn0".U,KkLpw٬+Kk[,rFh vu ,KG+YV+Kw2"UϯV+Kww%{U,Kwҁ_/>U,KwHo'ִ7e|alƍ7hoѾ5Wl=qOHw% hG+hjeUUUUUUUbWoRC̭nq7vm9b"*tqOHw9(O\&tRJwl:(e,mccNZx2+K2x 7WR 2r7]Ԏ+2h:vn-'Vm )XR^gkO/oZ׉ciAHl~izʨTLj5it+2veY}l=7W6rE+6r3Y$v +KJ &ō,frC]dQ" ,Ldv,mvn67K u a,e7oh2O[ |+Koq|YS 7W6tϱ1e,cfeCJLFԎKmVkO( G9b+hFr>ў.MvYh8L+KF}&e7ohQd%17W6_7[ Ƃ7W טMpLKCfqܐ8 T7Kb䴎7/vT[d7WjhKff]+th+tvl[qOHw&5ԕ,KJrT#ͤ7e] jJf7W6&i̸OOlq-,LӤ +K:4q5t72oeu!FTKrvS"k& +vve=bqAFl<g7K_y}qOHw1SN2,-rq=^WCy7Ke+K+sCR76j'7W6'%]PMrS^ƍ7ofpq<,KH)B CqOHw,XJ9+K-‰!80cE,ft%h7hJU} P? +WF򐣩qOHw,tJH`#We,rfVg?7vm^A6H7KHj7E7L_e k蕍7K72्7rhp>I,>4zЏHhvLo?GpKmFy=&~r76raq/ eō +hmUg&,PUl7WBayA̳qOFwn7K&K2ƍ7hon'ee,mcm 17Kw1;z7W`7vo$#7W6 ;ߖFL,/3?T7W"SU,K_7cb1)ƍ7ho=Sc54ە,K )!ixu2chDce7eh|I¦7vm? ]E+K/'-",7Wwע$Ԗ,L7K#N|iEOkqc~,*7W6CwRNzn~Ŗ+KA;o7KKkZ;K"HhnPT箖,L-lFHll5n ҍ,-ohR;HCȏbFoWobf7KsQQ̍7KF ? +K&] ܕ,K1'6IRpsan &7K#?Z+K-Vy,KA!ʖ+LO|'rRO[t76ak| |17W,Kp7r,=! 6`7e,rfV˷ *7Ww4H}*lFOfrj<Hl4q`ʍ7LFyN +KYAOlxeڎ,2aIuLa,K3yVyh+mtM:x.rEO]ܿ$oHvG&TPChv5c}{c3,K ᗛ~8XbjmBxI=_#D,KZxS2Ch(;h37a,̎L+Ov@ghSō7hoƍ7hmY{$J/qVTk%lsJ`7K ? ejQOwȕ,K\%o? ؍pfOer8b/8e,cf?.+KAm:LprVeR*C6Ҏ76f,ZZLK,VW1i߫qō,cvNPQxS2Co_}Ό̴5fEr[I*TK+6gi/&7v;&EFiRH`f]5'e7eh2)>S:qOFwPCe,rf^8PiRH`\-:@ջ7W6Pw䕍7KeygXSVar sgTЏRavO22NRIF7Wwm7Wwxd(vjɢW,mvB|7KBBxل,crM<ƍ7hosE9F +WೣSMv+LV^t$oHv1r#ō,fr9ac` Rō7ho[w#rō+mo^/Nhuco)Xʍ7Wi^Y`sioH .#6xe7vea Fl ̄_.MmϢt+KJō,frE?.,mr5y5,mvM eƍ7fhar3Bƍ ,ofyi,2HlL,li_k-,_e,cf#8 ԎPHc<t^0啍,KL2xqOFwܨ>i7hfgGqOFlҢ,Kia[,ō,fvAe7ehi}0dQ7K 0I mlB7W6U6:؎,2vL>Q7omgjd#,K4n/8l7K'ð;67K*$Qeh,L!|hChgQFa,K$HsD +K'ԋFH Tlr&H0zR76fB. $ں.VmB. $ں,K3z;,Lς:`ڏe,mcfD%Sdvj8_we7eht7K@3ޏt_qOFw s1,mv𫂺@wdЏVoOt4윿YAOluyTn7K lnYƍ7of: Ύt7/veP7Kciō,frCƩ˚a1,K֑9t"ƍ7hmdd$,KhSώ>؍+6fvEWqhR =GxKcfVa=,KJ Su̼e,cf*7J.,mvO֧g7K.+38,LZeaOo|ō,fr#A ,mvlq;ō7hm-VaǮP9bō +hmCy2z.,mrp9),K(̤KfOD%AjXƍ7ofR$TՌ7KA&o`ō,fvJx?",cHt cUlFrrcU7K,86qOFl4G,L*g4(h%lP'Thpzzhƍ7of\CBJ M]鞕,KFV,K*L!hHRaG7WaGƍ,vt" ЬVAmjMPM0T+2hMPM07߫_44؍WqoRj9hfR7/eoB1R ,mvI"7*7Ww6ZV +ȥ,cre T<ō,frP-cvQOwbt0BMvt0BVv~FFKewlkiō,fr?0{VFq$ʍ50lnFq$*7Wwfτ*7WwfτrHl.(ÁSjtSѻЏHhh\贱|71V \veQ9iLԎPHh>I[hChK_i*,Ll#\I.rE 5<1/nȏWoR]NUP WVAmj]NUP WShvtUB̝qэOlrk5^4+2oНr.MLwo+OJawV } '2Hlm":USjt5UUUUUUa*7WwDњI@p, /hChW삿Vo%+vWO1xk.MH2oMэOlrӒajGkwF HwvK4ۂE7 uRF%7eJP vЏHhh)1ҎMw>NZR,-o\ Qu -Vv !=eE,c)̥sE, !=d% ,h$gt/Fl.zэAlm|MhvФƔm΄Obr=OlKJ/r=OHlƁɀ#S@R,-opEZHlmClmN7sJwA>J`rw&3v 2Vakc +vj?D0ޗ4%,mdTlDd}qFr=V4@HFh ;HwgKHwvZuٯ\sHlfu ) U_эOlrO Mp/N<F +mFDPXKfO"P̎7Wlʒ]%Έ*,Lli[XBROkUfV D[7m%IT7P[>R,-ȯ%2$эOlr vtȏWa@ J_%,r}"|m*,LlʚU^̍+Kwv=fWUhz_TE,f;)ulwk3iT+HwTXlvx#2Vvu3PEE,5pbY%,c ŸaэOlr'tJ,L+Kl(._DHwPAJ̍P0|].tKcfV^zf/jd IMvMh`ߴIRcwR*,Ll E'1юOlU%,m"TE,fxpQŎ,cv8t\h ,r e ,raL][ht*7Ww *7WwFwܩv эO`r1ܩ!PMh"dE,fg {̍+KwvpBR+rFQyh>,r>2AFS,,dэO`rZ`^эO`r  )xHlm$в]QHrrM$̎7Wk%̎7W`J`8}Flwkryp[nsBsDn{UsR,-on4ƍ$"wg[[6g$m09ڽ3E7h9%7yDHwoYHlmmk,v1D;$p,9yHw ,YCӆ,r'lw\<[ҍHwrC.G7^Hwv-^1(ϤwRoŏ,rgZڂoO^юOwZ!,r@> CHwpX,rw\45Q`,v ?ʍ5Ppu[dEhIMt(KvFfGq7c1,d^K$LvAY+{K,2vAY+{',v0̆e)+ty]; 7m[˓nE,# #ABpMvhkS^$эO`rI'p R`f_P2,-r{cJwv|f,mf忮Q̍U0cjX7WwQ8{7WwQ8{HwvRÊ.Hwo*Ҿ%*ЏvhRxķOPb7Or]xķOuď+fu5xȏ"Ou5x8'ju5xďNrt= T7*ЏF= T7Pcfa=9C-,v$_x,퓑YjRJJT~# Hwv6 Zw?Z?7hK%,mJK7ra5GE7hGI{D#wRooz:k;vuԎKmrVpWshƎ7o \ȥ,r1~ )m0+CKfVa}Kz\p(m֞K8BэOlmzmНwЏhFf*7WwFD o%,mv?e`b%,cOMCJwv{7ZE,ck-up(l +mlHwvlVx*7Wwh!*7Wwh!эO`o>l)OFw%G<(ҎH`kU,r7ra9w((<,k.q%,m§ۅ,vSwЏoe/)+9юOw}_;Ffj}_;EэOlm# jRVPEhMvOB(эOlm4W'HE7oWE,f~ 2~* VkwN<E,sfwFw}i[pH/6`Ḵ)M`ḳE,f޼Hlf˫-Rƨ+Hr[5/"mFegsЏHaKe|@3qot$%,c)ՐxPMM ҍ,Qo <O<UE7h(<ٮ%,m%ٌ1iHj,S[:E,f k,r{ƞ쮌,vs$[@Å,vo#юOlASVlݗn"eOϤSVD юOl:f4fWvhzkCfTCazkC9эgiOum2,rުo%T'HvR'f%эO`oGhF%,m Whm5>SYKoL}ĭA%,cjJ٥57L$t$ho3$t7W0`3Q7mtb:QB!,/r7scЦʎQw"^ke%,mכ}ٺHwv*pэO`r0"TF.:sE,fjFw%oD%,cX6irrЏtv⣌+K缤t+эO`r =VE,f,d!eO1Vf[S/@cAlFS/@cAScv-Qy%,mHLh%`rOl(%,m%M]\S؍+tr]\E7hx0O2r7oOrnz5{ ]o؍DqF p;юO`B@-1E7h~L&Rj6 VюO`}0JЏ6VhS0ЏMVhS0冏7f v(FwC `bE,f.0˽юOl>SэO`rc3\ -,rfC[JwǟlYz2Rrj2rꪪTCUUUUUU=RFjTtUUUUUUЏ2MfK7ޮT#Fwv'=6%,c.͇,v+@ 7a袹(8uMo袹(dLf;袹'Ro Eݟ^4эipWuԅ,v E10Dvr)m%,czd%,cj_{ khGc7ruZ Vk*VE,f&^]mD%,c0 ,Q$"UPVMf5߇g 9E,f^ O驐`,vT6ȏ"O&J|,bOx6h̸KvMiSVSďlm^Pskm0k#ȏbFjAs:mmэO`oLVz]G# QA) Pz3r#Sďlm?R|UqW #g{OiюOlUb 3$hh'Fwfmvgꉋ0,vdW,v ]ďwvt\Eɍ MwЏJf,Uja2oD45rT",DэspT_bkwl*y^ďUhm.O\녏,rN\p%,cq],,vZXJ!#ohFh3f|j\*VnyW\j8WvQPkBďwvBuC ԎLmFwwy-!KmqDpwhfRJfDpwhLF0j9Yʍ7Wk`zpMImӌ+W3Imf4N(h2RhTCCccʍ7Wk$ȏBq[j+2b dbzB 7W6ϯϷ=Oh˩<,L:"Oh<,L<,L<,L<7W6Q;Ow<7W6=7K<7W6E=7h=,oȱQ;Ow=7W6ʷ=7W6ҷE>,fE=,f=7W6Է>,r>,r>,r<+K%>,m׭<+K<,KE>,fƷE>,fҦ<,Lϫ?7ֶƵ<,Lѫ<,Lʫ=7KϬ=7Kʬ=7KѬ<7Kѫ<7Kϫ ?7W<7Kʫ<+K<+K<+K<7WwϨ=Oh˦=,Kҷ=,Kʷ=,KԷ<7W<,K<,K<,K?7ή;Hw>+K=&r˲E>,f>7W6˷7oKe5wRlk:7v%>,m&=7v:7h=,rֵ%>7vӰ>rFrӣ?,̃ʵ;,r5wRlk<7W?,˃E>,f˫&;7v<7K<7WwԴ:7WwQ>Ow֢>rFrӲ:,f<,L:7h&;7v>7W6˷<,KE=,f͢>,rаE=,f=7K;,r:,m<7W6>7Kˬ>,LˬE;,f%>,m%;,m:,f<,LE>,f%;,m=,K>,Lˬ>,K˷>7KˬE;,f>7Kˬ>,Lˬ<7W6>,K˷>,K˷<7W6ϯ:7W6<,L:7Ww:7Ww?ʰs֠<,LȨ<,Lʪ=Oh:.Rh:.Rh=7W6=7W6<,L:,L;Iˢ<,L̬=7K˫<,L=7K;7K<7K̬>Mm׶:7K:7K<+K<,Lˠ=,K=,K=,K˫:,L:,L%>7vխ%>7vӭ=7K̠=7K;7K:+K:+K<,K:7K:7KR>,-o<7Kˠ:+K:+K:,K:,K=,Kנ=,K=,K<+K:7Ww:,K:,K<,KQ;OwQ;Ow*;7Ww*;7Ww;>w̶&=7vͭ&=7vͭ;>,rE=,fE=,fd>Hh̷*>,Ll>(Oϥ>OcщҮ=u=r?Cϥ`?KHԏī?o?o$?HѨ67r>,r>,rd;H`d;H`:Hl<,-o>ff;Rl`?ˑh ?K+ħħD?+,>+Kkɦ:H`d;H`=H6.ms7Kbp>fFm´`?Ohvͪ6.Ru?ʰs֠%>,mֶ%>,mֶ7SkR;,-o;wE>,fҲE>,fҲ:Kho%>,m;Iˢ:Hv>Hʤ;I̢$?HmШ<OwQ>OwE>,f>Wcˏ=H׳0?N͉Ϡ*;7Ww6'Hd<OwQ>Ow=Hׯ=Hׯ:Ol:7Ww*;7Ww6.Ru77h97vȳ6Ko;77Kid?mk:OwQ;OwQ;Ow:Hw;7o,>+KkӦE>,Q;OwT=Ooԭ<7o?OE;7h=7d>H`зL=+Kͤd>Ohġ<OwQ>Ow֮?hQ;OwQ;Ow=7l֮<,mƢ>,r=S=`̆>rr>7=hmhӡ=6m֪Q>Ow֮Q>Owȫ>,v67r?,˦=UO;.mf;,*>,Ll*77Ww=H67r<,fͤ>.uQ;Ow?I̧$?HѨ>,?Hҋ=UO?hh?ʆˆ=t;7m>tt%;,m>,Q;OwR>,-o?HaQ;Ow>IR7KbpE=,gͦ>Fwǵ³6Bbd6Ehh?=H>LE;,f:Fw<,-oרE;7h7+bh:O`?P?hƢ%;,c<Rl;H>"Oh=mַ7+bh?7˗>wsʡE>,gƩ>.Rũ;,r>,o=H7Fr$?HfШ=IRQ;Ow%;,m>&t*;7Ww7mV?,>ѩ?,˭P?dֳ?g˨:Ow?i˗ɳ;,v;7>UOE;7hE;7h?7חQ>OwǠ&>7v=H*;7WwQ7OwE;,f=l̷:,m:,mmp>w:7Ww:.Rh:.Rh%>,nͰE;7hE;7h:Ow>bͳ=/jlɰ=UO?hhQ>OwͬQ>OwҧQ;Ow7,L=IRš&>7vȩE>,f˦=,rֲ=w׷<Ow?ƭh<Ow=+Kjˤ?OE>7hǧp>Pp*;7Ww7Kid>`Ү>`Ղҩ?Hd?hρ=Ep:UOh>TwԷ>Qḻ6Oq5Oa<OwQ>Ow÷=UO?E̴=Mm>+K?Sɓ©=Ht7Kid=eTtԦ>+K:,f:,f;H=wjӥ>Kˬ=rђ?rŰ=tů?SȬ?t×?tå ?6V ?6V ?7WƳ:,m:,m?,˫<7h;H?,̫<OwQ;Ow=tŏ%>,mа?,:,f:,f>7=,rͩ>UOԱ;H>QlӢ>tt>wt>wt=r?vmɥ?IRʪ;,>,Lր=Mv֧*>7Ww®E>,f%>7v<,m=,oԣ,>/Hs>Bfƴ?7˗>/H>mpE=,fͦE>,fɩ?I?@kͱ<0ddԩ?Buψ?7ח>a֦Щ?`?Ieԡ:7Kl:Ow%>7vȩ>`Ү>`Ղҩ ?/Hsä<,f7wQ7Ow7w?lĭ?d;H`=2kצE;7hE;7h=7WaE=,f֮=UOť*;7WwH?lHE;7hE;7h`?mb:Ow?Vʠ6(pNP?@oԭ=l̷$?UO:,m:,m?Avրέ?Vȴ?H֎=SHĩh>Rff ?7W ?6V ?6V ?7Wˠ ?7W˴ë;t>gϠ=al<7Wh׷<3vv?PŶ=w׷7,L>7?Hϰh>'vi=`Ϩ>QEʬ=H̯>Hh:0Ff=/Hf:,m:,m?i>b>+K?>t<Ow?,˫>`ҭ>`Ɏҭ?,̫׶>pbDz???,̫>D?,̫ר:,f:,f<Ow7+k?TёХ=OvϮ;,4=UnOժp>g˪>cÈE;,fE;,fH?Ko>CH?*Ѫѩ=uҰQ;OwB?%˶Q;Ow ?7W ?7Wʪ ?7WϠ ?7Wʤ ?7W˨ ?7Wʫ ?7WӪ ?7W ?7WҴæ ?7WҴæ ?7Wմð ?7WҴæ ?7WҴæ ?7W˴ë>+K>Tiӯ;Tq=Hׯ(?Bw<.Ru<Owѷ:Pg=Lӈ>@`ח ?7WpÄ ?7Wu ?7W ?7Wĩ ?7WӴç>hmֶ>t԰>4ԠԴ?ľ=%@`D?Ol;Rrb??TUӫյ7Aff,>/HsҲԶ?Tַ;TfmwLQ;Ow?oсĶ?ӌ?Gʳ=sŠL7+Kqh;s3ò>7rĥQ;OwH?oK>+Kp=Is>bSԢ>T`Ƿ֦?Gˆі=/Hf:.Ov:.Ov(?1Uh<Owѷ>k̪=7r=fͨ0?QoêQ;Ow:7WlQ;OwB?2k>Tdķ=.Rī=.R·Q;Ow:oM>Udå<.Ru˫=WV"?)M"?)M`?Is?gВ ?/H>Tׄ?=`̒p>Wwè`?PpµH?0Ӯ ?/H<.Rb̠<.kRïQ;Ow?,oч?,oŇ(?1UՆh>HlŢ;O;lm=i“>sӤ??97Wh97Wcp;Od֥$?H=`̈>Ojפ:.Rh:.Rh>+K$?H$?H$?H$?HɃҧ$?HȄҨ$?HȂҨ$?HȩҨ$?HȫҨ?vѫP?qfխ"?)Mĵ"?)Mı"?)M"?)MϷ"?)M¶"?)M$?0FH?lHѬ ?/Hł>Vͯ>Ikij?Ʒ?qŵ?T``?T@ŷԲP?dֆϬh>'viנ$?HsΤ$?HҤ$?HҤ$?HՎҤ:7Wk;wv??>5Ԑ>,m̯Ѧ;+=oV=Ra>Aaa>oi?dL֡Ե:7Wo>,mƯd>Hoo=/Hf?tŰ>c@`?T@ŷԵ`?BǎB?4e״>Lɤ>wt`?Olũ?/hv׫<.Ruˠ?Nɮ=.Rө?/lh=/Hf>S>a?È̱9oH?Dg҅׭h>'viנ>wӨ?uvH?Hl;lf>cBɨ:Rf ?7Wԗ ?7W׷:UOd:UsO=Eh<.Ruˠ<7Wo<7Wch7KIo;wo;k=$l<Oo>LŌ?.O?.Oʧ?oсĭ>Wֵ?SƉϑ`?rS>eoͳ)a֤ǧ9Oo?t=gǩ<mw?oсĦ="u`?r`β;@q;ql`?RF̣>@rǡ=R=R=K=A=U>(qSȡ=)M?3ʠ?2ʣ?.ʧ?.?*ʪ:7Klsnappea-3.0d3/SnapPeaKernel/unix_kit/CuspedCensusData/terse7o.bin0100444000175000017500000017470006742675047023117 0ustar babbab7Ww^BQe7WwBQfZ7W6wq\1V ,LlFʼ@l+KkMί5@fHKo̠ѯ@f;V+Kk֕soá,avmsoáZ7W6wCy%E+fr˲F)pK+ײkF)Z7W6wn9-,of̶i# Q< \K+(kӲ# Q<袾7oͨ% ,rpV7WwZ B7 \\L,/l͏Z B7 8J,WbehV˶hb,r|jAqÕ,LjAq¬7Ww[攬7Ww AzU ,Ll{ޮZU7Wwד{ޮZ7K2TJDRjmփTpZ7W6w;S; Z7W6we_"ŏofa _S6ش7Wϊ_S6صZ7W6wg)иZ7W6w=k xwU,Ll TޟxKQfRN Tޟ\+K2l0}U7Wl̆0XV+KwƇ7V+Kw7U7Kk%yeU7KkF:hRoV7Wl/+wV7WlYԂwFU,Llȱ?;>%Z7W(w1?;>7W(m1%27(rҲŨm1%7Ww"IJg蝬7WwT U,Kk:*XYtK-VvjJlL+kOsj&U,KkV ,Wwu&d,7Wlm&V7LkP.^V ,Lk\~4U7Wlˆ\~YV7Llc {NH?V +Ww {NH>V7WlZөV7Wl0K6.xhjVMkͅZ΍FlK,wOZ΍ו+Lvg˖\ ,W·xvg˖U+Kwև4i[V ,Lk4iU+Wl!Z,W-k̆!U7KkGLU$U7KkeV7Llu _hKobu _ Z7W6k36 |Z7W6k]rشfaҬw;3+K,w;V ,WwgDZ5NHwrgDaU,LlOVV7WwVV,Lkͪ}DL+̦*}D7WwO%l7WwORK50l+WkOu*zZ'rRh`u*zYV+Kw*UoY\7WAlҪUoZ:V+KwIĠV+KwEӬU,Kk |U,Kk9F| l7WwO=(U7KkJY!l7WwO$z;bKU7KkDJZ,W2wcc9wtlK,lVc9w,7Wlms ]JB&Z7L6l ]JBLV7Lk$-9!V7Lk֯y'sfj_R/f7LĬR/BU,KwLȚU,Kw1ȓ?.l+WlOf0^[U,Kw]f0^[U,KwͅS<^V7LkS<8,mv˶͢@n5\K,2w"@n5V7LkܵӍV7LkƲ L_͇U7Kk#eoU7KkI]>X_V+WlV uNnlK,wO׆V uNV+KwZfZV+Kw֯<Lx.rFMb$l5ovmbb$lU,Kk  %U,Kk6øq7x.rFM֪?$?L+צ*?$?V+Kw)Ly7W6͠kdb7W6YҜ"tRJwӸؚ7W60lL,lO˲n\qOHwƓ?Tt?`e,rc͟oӸsZ,L-l.:ZzqOHwApKqd7W6͂XÀ+K֣BC%O;UZ,L-lփ~r箕7Wȝo2aM7WĵG(4yL,̭B+KƘ{Y/l7WwOBE7hυCKήV7Ww˅ƌqVh,m΍&LmƳ}`-,\+K2k˯)O/7a $V7Wl}V+Kkkd6I`+K z0RdwQre5aWR8Rin8V +KҾo:J7K~P7K֋:C*"V7WlqӮG8~2U7Kk xl ,W³݆0WЕ,Lĵ=17w1b,Lҙ@̵7KF绌AH7Wƚf7KƉbwC77vH7CfV ,Lkni䅧tRJw73cu7K/x35iA ,W[zs>dU,KkLt0 j^V7Wlol+K2Ý|fj,WlV[뻧Z,L-lϗx,.x,KsY+V+WlҜ0[b7r׏@4M&j,LlFצ]c~NV7Wl9ʧl+KkMWMHf,K˳)ϦhV+KwT^H7W(=1n5!hKmo׶BNu7hčʏ\WԺKcOddfCvdKh `x̯\+L?b 8U7Kk]7l+KkMj˩V7LlҥU ,Lk׭V3dU7Kk\ȕDfl,W2pO;flZ,L-lnb̶L,㻹dWV+KwDK,/ό/1\L,/w׭q^rI4+h˒rsmU+WlXZHV7LkOnfRU7Kk׭ c>CU,LlȻ긗qOHwқ8ֳ<6U7Kk ߹u\o57hmgmSwJs Wt7̼L,Rĵԕtr;U,Kkr!6wR׭ϛ*}QV+WlVO"U,KwtU,KkP+VV,Lk!qy,KteWƵ7hoϟs1J\K+6k8L EiAHwka^&"37KÉ|y+t 1;6h7LȏM[T{j7LwF̨Lp)V7LkҝMIUl+KlMPV+Wl˲̜{=71VU,KkKXT$p-7KldqOHwu|8d,KV+Kwzi% V+Ll̶]N|x,K,wv˱מj[JU7Klȸ6U,KwE8,nZ7W6w?nV!V,LkF,Kv˳?$d\L,2w¥yCuU,KkJA(7Lw6gFU,KwFk]/dKr{cl+KlMצ1-j7LwFצ %`U7Kl ']W6?ԵLmAcrU3^5,K,lfmgjg̦7vm,.Z,W2k̶ U+Lw ny\5NZ7W6w~@ Q1W8Z7W6wҐN*CK6ow%YV,KwצG6 ݰU7Ww |Q]0fEmcSyغ,/r4K鯕̖7WE bF}U7WwҵvGږ7WcBT V7Wl5\YbeZ7W6weZ42s7Hh̶ʂgV7WlȆzXHU7Kk/ޠU7KkEhƷ_U+Kkv7Wiv¯(pfRˈȥ.Q7W6J;]+Hlˇqq%}El+KkMӓ<8(c/V7Wl3HNsbU,Kka-WV7Wl )e,rfҭ4*l7WwO"`U ,Lk Gv U7Kk!okͰ9b7rhׯ0\9lL+kOn$EU7KkU7Kkr0l+WkO/rfbqOFwtl7WwOY\V ,Lk\&U(XwVʲlAjV'Ma̭@ZuU7Kkp+nJV+Kwͬ`Bv%V+KwJ\V7Lkͳl+KlFBe:ex,mƅ lL,wOY8#EU7KkGRJZQHklqjC V7LkyzɅ4U,Kk7'`jf U ,LkȆ\0pe7eh֔B5AU,Kk6/V7LkȲh`V7Wl5F3 (BU,Kk4 ;ך7W60ATf2dKfvEuSg<,L$bbʓ?V+KwWІE`U7Kk23! $,7Wlmy02*U,Kw(R6U,Kw0L/j7LkMֱ|h? x7W6,MRKrغDRq̲p_U,Ll-v U,Kk vV+Kw)y=s/U,Kk.CXV+Wlw{HV7LkmujtZ,L-lj1Sŵ,frҸݷ5U7Ww0q&U,Kk!R2ʪDQrfƷ͑1KV7WwMFbؑ0V+Ll}`,K,lf'l4<%VAV,LkRoP'U +Wwx!V+Wl͈Y 0,L˲s}xyU +WwaJ8HݢU,Kw KGp,mv{"U,Kk`!NpKV+Kw5-clFHlcp/YU7KkGQV7Lk/y,,dKfv͇\ۢ#U,Kw\:0Q:y,rR4Kmc>pBQf8|CU,LkҭET\\7K/k_b݅nU +Wl cU,Kw1 >,LlۑLH7W6Ja%&U7KlҤ&{QU7Lkl ݺ7K$$6]7eo{@a~V,Lk!p4brV,LkͬGAĸ'Varֳ,iVZ\L+6l}r\+K-lɇ7W6LU ))U,KwΡ"%V +WwFXU,Kk5$7Չ_U+Kw)!?lHU +Wl௔VZ,L-lOX+K&j4k`ro¬g7vmnĥ.j+cE-rY떵,LzͪԺIRr˯W6 DhlHf׼4iRH`Ə(V,Lwr>7W6֯n47Wϧ9IR7vbLmOFq%C1V7WliZVHkŰfpDb,Lŵ7ho-pCm7WK 7Kz=V7Wlr5.rC2,2rՃn_>,+KkQCU\\U7WwF ]qOFwͦ𺒖,Lj!R7WfpD`ʵ7WH` d$^ Rlɱ"fK je,rfπLCW72eˌ(XhG%+m›O2flŵ ,ofLE$`ʺVb4\~TLcr˦"{7mS,KFo殖+LGw7K8X97K5:47W'JUU7Klf2[:0< +K}1bn ,LτyWDžVU7Kk_UQB,L"ts7vm:WqOFl϶ʊ~,K)L曕,KW_[S,5U,KkN}U7Kk``  ,WƂ_rS7oˍfgn}ŵ7ho֝^0 6 +Kq17WҢzQwӕ,KNUOhv3 i(ԾV7LkT(TZ,L-lҡaDf7ra.j6@7Wwg_BK:i7Ww_BK: V7Llj_K7W(ϼ*amV+KwϡK&M;U7Kk|V&7rvyB,L GJPCh˭WuU١ +W5}7vm]^]s PƵ7ho+U7KkE ,KTz+U,Lw][Dȶ-U7Kl 09>O+LnziU,Kkة,ۣȷdFvQl+KkM;qedV+KwҾjd^ V+Kw4yw~GXYYAOlƭ;V7Wlɶ3r7U,Kk4|K 4Lcfרf1fQƜ7W2ܒbil_`,+KkuV+Wlzՙau V7Wlsw5\,L)lϚ9wOR̯b L+6=D߃^/U,KwGxS:Hl2Þ*V7Lk!DD% +vռS7r2U,Kkt}0+o˖ֵ˂SV+Wl|ux3+,KrNgLtU,KkrdGe,K!*2o$nV+KwF";re7eh =d,KSG G̶K,V v$3U,Kkk&ٱL+6VN~LȾKoFIvϲ׹dvj̨6WU7Kkx,;& +v"ۜl+KkM*"Հ2U,KwgMǙrU,Kwlv7LIPsɖ7WzD%֓V,Lk3mN"5ҵ,-of7y)U7Kk0p3jJV+KwA}VD7KÃΔkV7LkҚbjLC>+Kʹ wU,KwJaS|O\VL+WO;E}N$,Lyj{O/.To̲[DjU7Kk_V@XbU7Kl͙:jdU,KwԖ`Æd7KҍG]0NU,KkM4Tn6"oTI`)V,LkҿlLd*wRגʊZ7BU,KwIDDpl Mkh'ټV+WlҙbV{U,KkN8%^,+Kky fE7W6רI?%.V7LkS3b0\V7WlQLn?dLfv̨ 5T'U,KkJ0RqOFwZ6|7ofjO%U7Kl n4 t,Kq ߰;V+KwȾ|7fԉƵ7hot:m,L2Dv-'Vvl5z5V,Lk S'P鎕,K49 ŵ +hmh5bqOFwyrbe ,raҐ!02p(MojPhVЧʎoAf14Lmc̦͝k) ,Lҙm˷nVU,KwiP)mA̯G,ЙtKvV  +K7V txU7Klȶ)!< A,mvI>Pa,Ƶ7hmj)*lK+wOϬدȢIܕ7KI\ŵ,frJL:} .Hhu#.L,/x+KȖ媋V sl;WnkU7Kk>5NOuE0eEuEZŵ ,af"hpre̲ nke;r,2ao;?#7Kl;6,L͸Bͮ 7W(tTpNO7K˓k$ҵ76of'Mp'rRCVe,cfPDCj1>vvL7WRώR斶 +K݄֘A>J7Wd6=0NqOFw`*u|7K 0b,L6c<²'eRsҝ5#,KFYGW7veֆ 6,+Kk[1SF+LғžKە7K`8%^2hprr˶r ,K"iqV7LkІM(ژU,Kk7e7ehҕmɸwOR˯$p}* , +K<-:JqOFw'#V"صWqmO4=eՃg,LȚB2wnϚ7W65Q]!n7Kω\.ZlK,lV-`1S7KE,AH 7K F V7WlQuJ/&%e7eh7JvV7WlOrͬdd_^hOoҬdd_^,K"k،7Wǹ- mlFO˲9- mt7/veɇ:ڬ7Ww̯JC&-,KUpVl+KkMۭ$Y +Kȿp)-#V7LkҼ0`a[f7raajМ7K/RTB]fhwe ,ra6^7Kmz[5V+Kw31\.rRרһ4Z+'r,KĦ%EF̵L+Ocw$蕵,KI7Kf,mrFAvrsŵ,fr|^wFO.[6PU,Lkߊ&Ď,mv!-,mvz<PU,Kk,uV3x +KIbhP/LL,RӗR V+KwUUeib,hf\_ I +WzU7Kk>Jڛc+ +KB}r,,KHDeU7Kk($ymɺHl˶oRd,+Klr:&ŵ,frͺ)Wd̵7KFbɤ\LprfרVRhmU,Kw(4!,KKAB,L2 $ރa,KxS|B@̵K,Vt!,K{ò$7K8ʓ~ ,W]eHb[,mvk!ŵ,fr9@]`X~ ,W3bv7KσˋW ,rhzΧf/(U,Kwt~7V+Wl+".HoR3ʶ7WVKE" ,rhou/,7W6J ٕy7K֌&   ,WDTYWqBQrׯ4v(E +GKXWhqר7-XӫZV+Kwt/>Ƶ7ofx2UgKn +WGd)+Kҟðٸ$hTPi@""e,cfVm1Cŵ,fvE@ŵ +moK~k,$b,ahϵ3ŵ,fr 2,2r!"\,mvP~ޕSVv)nThv즵+tr\s[$:KfvVFN+mr^$6صwvJaH7W{$lOJר-\?MľKF $&s_& ,rŲ79V7W49t?ŵ +mo6_!E,fͥȑ̆8,mvE0D,KmZ0ݺdLcv˨yLͧ}tlOto y(fRƍyMFl yMLK+l͍yŵ,frzn.R76ֵt`)% +vרҊ]VY7W+K⠖7L`5bߔ+KkKwԵKcrV| /HHl:t<$bR7W6ec {ȷWh֯$2W1 +eo;TԄصlFJf>^8eF'eF}պJ7WHL 27Lk4صlFJfu..,2rh [QbUŵ,fvn<2lORfDHƵ7hm3f7aoEa;yIVkW!rrF̷(B74Ƶ7ofZW: 7W6ˬ5P/.Va]qe7raāϵ,Kb?x+6tfMmeF:Ƶ,vrhn]7K^/Е,K&;t`mҭ6Oŵ+fvŹ3pTKmr̲|صlFHf*f)ڕ,K"U h,LeC)Q"TлhOo˭O*+KJt+tfD}e ,ra7+7Kf; f7aoqP% ,Wρ"h,Lҽ|殲)aV.p;фصWhHo ųPa ,LˬҊųP,Crc*!q`V˶*!q`Е7KR ؐ7K1 *1ŵ,fr9lx?´7/eobe+K۪͢07K<4ЛPҚ",f̷P0wlk,Z@MK(.Vm,"&+v̲WO1ydFŵ +mo̕1Ӵ7WlXﲣ'eF˯V`/صwFHv[v7afj,v'敲'eF}7Ww˫6,Kx,s(t +K7>x= +W4=qʭT,K$ǔX9,KN[+K-cvZ,aZ#ZC,Kco̍"6HWYHlm wT;K,/9 $pʵ,WV+7,UCh׭SqKC7Ww˯FҬ7Ww׷F7W6;m677WײG[y8G,Kee f7ao\FǔR,-oC~„HF,oȯ)JdP_7WV.~7ao`pPw,Kςށ7WbWHJ,WV3>_7KhR&ڕ +voH{,LˠmLմLK,V5ãͰ(hR>vLHԜ7K/g%S0.fE̲BB0G +vqkK.ԺUHc˶+pU +Wnrs_ +Kyܘp% +vҭ܌ϫ|VJh\ϫ зjjRlzXd +KCη9=4LcfרLQ hKmo˲PW4DU,K vTLcrצvHWz7W̷=GF,K7%A,,KD?R\$(E7oͨ;ԵKcTh֮2Gi&7W.2Gi&K,/׷'mn+Lv'mn7K̠3&1ĖغlFJ̨6[i+W27$~ox+6tf#M/ߕ,Kw]k,fE$ks%+Lw$ks%LL,RGzŵ +moZ ,Lv?dqx+6mfAg^(,rˬ,|5̼L,RƬ|5472v˲P[>ԵKmrVP[>H`,^@Km׫959D޼F ,oZA[,W2\ֿu(7K/-f&ap,Kנ"Jܽ>Lзma0|L E,fʹ%\:7K/+:8oOho`z?ԕ37L̫z?ԕX,rƶ8F Bk,Kּ¬S4,L̠H]!ʕ,K_CB j47o׍CB jXԵKmrVS7+LiפM}[OhvkHvKɾbAj!'ViضBRr̙ò)mV˲E}wpp+K̷dpu3.7Kˬa,mr jϏ T,KȨuD +Wײ}7Kˬ,4=IRl¶tC;.LZHl˯UUUUUUb,LˬM jkvV"7h̷OBު7WJT7\7Kˬ  hChy`̵+Kwvַ^R F7o7f2'fF֦Al%7o|].u<Oϲ/ E,fHw iLK,V}wΔ[7KˬȘOnHlrРn +WRp ʶ7LFb\,K̷ʋ^1ķ'R͎RhHw֪1sQ,Kˬ¢+K˷ҋsE;bmճļJu'PE7h֏QQJo+A2,-hϯxpv,/vτ8t\hѵOlrdA~$h,-fצֿt`ˑhMC푄M%+v˯`^LOB67W6˷c1ZF7W6ˬ8!d7W6רȗӔǰ +Ẃ̷gIҵMkm͞7Unx"CccnފVӶn ,Lֵ?r&d½.BvI MJ&@ZO`̑hԯ*1W|prFr̯R!כෑFhȴR61lwkƬT DLv׫Ⱦ]GFR7W6˫ғgSwnPhHЏ϶7DsѺOl̯"[Gw]+Kk׶fCѵOlrҦ,_vԖ7W[[6gȻWoRײ09ڽX7W09ڽXrROrIPq7W6˫QMF`GLmE,fh:F7oŰѵOlr֝ c ѵOlrַ8WHJL,/ౚT+2hWu6ȽwFˆbƌѵOlm͆i HlmN! AHwv^7K%֖hE7T?Hw\2 E,fRR7ra\/-Nb5vȆų>F̶7Wl$fHlm 8Ph҈ \Hlm;B܀h F7o2t^Զ.jt"܂%,m)#}R,-o(8$*,Ll/ K+(O|ht ,L.B6"7K$ѽJQ7KO|ht7W6̠MSŃB2RǩRiy(WBlwkBԜ8+K˫ơlI*xprFr>R!Yp,K n*n,K7dMAOo׷%h$:J`rֳoi̬7Www;A7Ww;AෑMheرhTM%y7K˫ς$޴E,Ϣg1HCo}j=HwvMc ,LҳDsC.'Gn`C4E,f Z??7K3>63)`kd)寐h,o˫ѡG;奖,L]wԖ ,WHZN.nnҝIG+KÂrٌ6̼L,R˫WX.As&7r 8k~JwvA>fveq.U \[҆n,Lˬvx}/Z+K̷*҄%,m˯ɔѺAw̲9=E,f/J ,L<+K\JtHlmC߾Cj7K̬l_@ķKha汬+Kk̭9?cg7 ;,7Kˬ f#ĵ7KˬKo_7W6˫A2΍U7W˨j!,K2jDpVhm*Ќ+KՒM*Hwv֦i@7KˬMn7Kˬ"L: eφ,r̨mۭNHlm& Hw_d~7W6˦MsYflHlm T%UMNHlm64qлvhRҶ5B},W2͍9MhKoϲRu,K2Me,KlHlmfyw`R76aKe|@3ѵOlrrD^LK,/ +W2ʥ[skF,LˬUY/0,Lˬk7Kˬ i%7r7fkb+Kײ7㫕,KwV7W2薼,L̬9Mnq7K˫.Wp+̼+WOŊdp 7K/ѵw*,Ll-׶kPN7KˬL[ H+KфnW0L+KMZ?ìk7̶ZBU Hw\7x>E,f0d/GHlfhdD6+K1WN7K̬҄#댸67o̦͵K[5/,K˫13,LˬaQa7Wנj΋=h,K˷6qGI,K̷ p:̵+Kwv^ {q@E7o8 Z+Kk̶ơ[=Ar(+rȢh^Ur1Mfv"h^Ur1mHlmҠF7o˲ƪڀ7L6֟ZT ,WX4̺7Wl˦OSVD/*,Ll֡:rwHlm҉P7Wײϔ_C%7v̲yߙPb7r̒PLa?+K_qmLhnVTQ@++KרȟqtSE,fׯ+KLR,K˷ A,Kˬ4..+왺̶FlQrȲȧ",f̬ )Y<+Kנb7ȑңjzҵHwoax&(47K˷>`TJlprFrlٮ|Ś7W6˦Ҝ L*,LlϨLj׊,Kר{~t7W6˫,(HlmvE(g7K7ݦཬFr̒ϥ|MÕ7K˫:J;7,L׶Ϸ]Ħ&,K̠t:+L׷?^i-,Lˠ, blR7L6ˠրc=+(v˶֮"6"+Hwײq7XXhCh u+KˬqT/Ohˍºp+L̲͆FnƄR,-o.B$wRoo,`k T72eͬ|9 ѵO`r{̀@XBR/1ʺQw˭] L K,/׷3lcMբ,K׫&L/*(,K`*,LlQ1 +W̲n݈zOp2rM+AddfEV̨͕-O$+xJ7LF׷'BדHHlmk7nHl˯,Km׫{-VvȄ@z,m0M̈K VF,K%`SfɃ,KˠbaNѵ F`rYqmM·dlϒ"nrRRh@,](E7hM2z꘻$ho'5gY_R̦Χ5gY_d+v˖ҞTDU{88d,K˦g\7,K#VE+K׷ѓ.P +W̯dD-`.HRy4ZQKcOytؐ#&+vz*TdA,LˬOE7h,  ,K˷Ѫ'Ɉ,L̠7Äc&HwT',L̠ȏ_7hR,-aWpyKѶOlҷƼ}7W?:l1wDQ ~ȷ63ѵO`r͛9PB E,f̯XL\TKov.zARPR,-o׭1aVP߆7mǫ~p]7ǫͦ6|*,Lll\J%xHwoƠǢ; +WׯNc( R76a]HJwvq&T$7K/̬Voctt+K˦ҙXiiJ`C\bhh·,fhhn,K˷+W8,Lײ֨vHwv9?O07K̫lJ7LFנjzD7,K׷#Y7Kˠϕxl7W6׫8 (D@W+K̠Q:w 7K̷ߪ:aΠ,L̬DIyOҶ^_q7Ww̶ꪪĬ7Wwj`ˑh ,TwRooٟͅqR,-o˶IS෬FrmzPTS27(hrwZp/ߕ7K˫ϊ?aZE7h҇?P@,׭UPu&~;s,Kref8+(m˲ٙ|( 7Kˬϙ<=FOhגe/fvH{w?rn’0qˬN+Zs?Hlf6-Vvצȸj7Kˠzqq4%7K̠%r`%,c˭ƭ(=@B7K˫Yej-PHwvχ|y7Kʘ:U',r̲P։dHwvƟWidž7FU,ԛhTM˭1~r3 % ,r̭HS7K˷ώw=Hwo7@u7K͜) E:膻7o̭gGa/ѶOwȄ"a`97路KfV.pvE,c + JwײҵۭeT+-h7GrAL7Wwdjg57Wwjg7K̬qXKTHl̯.,ȱ3-ZѵOlmš 27K㚮R,-ouM \,K׫ҁGM7Ưp}HO`v֝3QF% +vYkTޖ+Kצȋ>r(qP8R,-o˲aAImb7K r,KˠIrbάѵOlm򆖿Sp7K̬ҎFdjl+KKGԛ%۽,ҋq*Jwi$p<2}$7KˠȌ?"c,LˠBH2HwvͬU Ɇ,ri?ɼȷ"Fy/KN7K̠Hs̺+Kw˲+K/Vd7KˬL1*HKfͧo,LƗQWl:B,K׫v$DzwHweG,L̬󌑷lwӒKhFׯrn0++KˬƕHfq̺Ow˦ҮڱftE7hϖ8q>•,K˦%k+ چ,r/B-E,f.uae,Lˠ@~պP7W6ˠWts;ך7o>'s9=7K˫5w,״!ME.7K̫Ҟf$LW,L˫@iO(Q,֨J&Px*,LlͳD+&,mײ"0..%ҺHwצ%DP&.fFˢ /,K̠!Qn`Mv˖e(SȻlHfׯ҇e(T,L˫֖k[<,K˫vh?,L̲Ҹp!Hݕ,KM{7 `̑h^ >i-E7;袹',+Kkϊ5IR`̡̪ %Kl<,K˨Ҿ5Dl&,Kר~: ,LQCymIRl̲̯B ,ѶOw־ "ǚQq˒ͭQsZ'Y,L̠pKb~7K̬8%fѵO`oJ6uFwօ\Aې_,r̶ Co̒,-f˦O$"X ,K˷~4 f,rר֤+@`DF,Ku'.3RFw4lȫ=ŝ~%7e˯\o2`̑h}dGXAZ,KˠQJ,Kֺ@Ky,K׷sbfH?s,Kss",`ˑh,g4LvR̶|d46=ѵOlm' > *,Ll$ͽeE7h͘`xI],K׷%R7Ww˲tzQ7WwzѺOl̯ϙ,̌Ʃ 3y%&Hwv#B(A;7W6˷Ƃj!KËR7KˠD~fNv7շ3f&4%,m˶R CTbĬ$HwCWi,Lˬ߿[W? Vk֣dX+KժHݐi¾ƒƲ!V&oĽLEeƲ2!V&o#F`ƴRWʔKmVkMo&LpѶOw+1,HfE,f0kc C-Hlf֟!a127W!h+Kר 7D& +vƭ:N]p,r;my[/ѶFlDM\7Ww˶ү go뜕,K˲͆}QE7h:L}MFׯn:L}&,mײֱ4I;9UapOe̫ɨBj ,rרq)8<,rƣaQZa^nXHw˦,Kנ҄ǵ$Z+,vҏ :k,K˫ ϩH9ȻWoOײgo*,Ll(7K̠)K7K BnѺOl̯z̲'T,K׫ȸ4*"ҺCkׯ<`ԕ7WׯG"w7K̫%%9,qE7=ۜ$Kf׬4e`o,YdUhmL:ӸѵO`rϔ2i]l7KˬH4E,f̲5,9lE7h̭gbz`Hwvh*Yg3Fhv׬oxagRbu̒|x_ѺOlׯҘw{7`hmoNVѶOly+ ľ`oҴȢ8(J`JcφdtʺVw˨0DIn,L̠ F ,oׯ s̶L,wȉsOhv5l䖼+Kצ۔^?HlfX,K׶-Μ!b,r־ s[,KZ CV7m˭Fp*qRȘd6{YFw˲țq,r0jjERjprRrר/E:+ɶAlv iĻKvO׭)5"+K>SIE,Qz*7Wwׅ\8IQw̶AubNѡkwlUE7h)Rr,K˨W$h.·,ofKiQt*,Ll2\%,mң1l`Hwo`]瑤7X1f2ѶO`OT)sp,v3< O=P7ivDz8J,rU*,Ll  IصwFooGSy H`"FUE,cWI(} ,fצ_/|^u,Lm̦͠q L%,m֯@@E,c̲+t S\jOrY,LҞ<=047mr|ƖE7h˲0!Ԫ,K˲֜:Hwviva `ˑhQ:@zǖ,LұػfZ9b,K˷k3[+FE7h̲ő[E,f̶or}[ѵO`r]0s'}&J`׶؅qVŔ1 6/MηLsB%,mkn& ,r5?p,r˨'3L,Kר-JoHwv o$ ʺQ`̭MٷR,-oײg"F,+CQg2ѶOw͹S婾X"ttKr(|"QOwph,PԆ7m̭>$(Rl $¾)ɎѺO`׶2fws$E,ҁZkȏ&dwDTIRhϥҦdwE,fֻ`cpF5,KmDvMѶOw5)sGDptOf׭ \}P̑} \}놻+t̨͝K2@ѵO`r;V7K̓P: 7L6̬kpLO',LˬֵvbE7V-y&7vŴϤ/<͒R>,v JYy&7E,fҿJ{ٛ@,rp"2,Lˠ֠EW+K-˫&6KѵO`ŕAФ+hpcc7Kˠf hHw֯B7Kˬn/n('&+m{a>e!?Oh׏a>e!?7W-q;R,-o̶Ʀ]t5D%,c˲jJkJwֹKo![Jwv)/ɛ Rlffu)Ad҈fu)Ɔ7mר1В76h#j pf/f2rhI *,LlitCJO`˲<'F ,oȨƅ{BQw{ص,Qm{BQw{lѶO`ҊEAzdUJ,K=۴7KˬdqȆ7mצF$ѺOl̲D8Äp>,L9h)kuѵO`r7F` is+KצN;#-HJ,WV̬:5=δE,f҅~S%,c˶~R,-oϱƞ:tp{ѶOw=2wϕ7Kˬ"h9,Kp* 7oҔ@F&,Lˬ$"^7K̫t$>,Kˠ2|C6&+vͭEzF7oȨňEzE,f˯ҴgB%,m͈q<ȠrȜğX00rh5qi,1*7K̬TF7KˠגD<;%,m˨ȊYҪmѺOl˯}R#E,fd_X,7K̬1,V,͛~`i+tͤk&|Ⱦdvƭσƒ5TIRhԯƒ5E,fȨ"tX̫HwvڑyYlE,f͑tE7h$O@7W6˭Ki#MN 7K̬҅@;7K̫LS*,Ll1.$O*,LlcTUI,L̠ٷ.)4lO˭o" ԛ,ӷHw̦^Kl7KJh[x̕,KG ,v˭ҥm@d%7v̲ҟ! R S7KIC=A?7W6˫D{}/B,r׶'B-Hwv{y-,K˷uq #$7f˦4xOV<7KˬG1ԏлe ͵E7ȶJN@ 7K̠yU,K˫Q敺<7K̠$Jt+K׷!O),L˫8C-6 [,KeDcE,:w[k%,mׯ֢OͅRl׶^|UE,K˷W af=7Kˬ! V,L˫֞'@vѶOlT-{3Jwײe.ָ:,K%XlFH Ci,Kˠl9A[RL7W(˫Ҵ{E{2Զ0eeϫ:9H "DLr׬X=UtV,K˭nRv&Fw;׸d%,c$B bs:%,mײֹy̚[% 7K̠% M$[ ,rײֲ?%,m d[A7ƭ`˖m̀E7hYIV-E,f׶{'38HImѶє,hDrF,%,mҴå,L?~\E,c=hѶOwvVw2OHIqvgVnFwȅު .E,fҫw9%E,c̨zz̨'veŵaWP ྈfĈʞ3%,m̨A,LˬK$6+rաʥ>&4Gs=,K ɗ ,K l7l D+oY%,m! (w|,K˲^2L_6B oƯ fjS,K׶6}D kƒD~_7K5^DE7hרҘ3[]d,K׫ 0,E,fQ\5hR,2fZQ\5hb,vcu `˲z ID VkHYM{7זM<"ˤTz,K˨4qGה ѶOw)l}6jE,fרґ{зje.&L/!kN' 7""L,̪pҭr,E7hGTb%,m̯ϬOӒlN,Kנ2gDz,r/E7h%gN}%,m}eL,v׶3)H+rR#(dѵO`oSwE7hֶΣidNE7h̨SS$,K˷9ũN,r֩&z 7a;us3d˒3*dH`̨d˒3*E,f ,ϯ1q,rH?Hw˲ȿ*B1 mӆoJVff+"{vϒ pgʹʦToJ{ѺAw̯̳[c&7r^̳[c&+tƥX>x,Kנ2!/FWL ,hVE,v҂Ap+e셻,r̨ϫE[R,-o˲ָd_q#,K׫ gA8ཬVv̲Х>gA8%,m˶t"h7K̬5Bv,]>i7K2;>z,LˠM߃,kNdDоvh̠˧g^!bѺO`׭ҡ:Gl,̀ƺ 2k& +HY4%,m\4u,Fhdžh+O˶}C|7Kˠ;xnpclРlz%,m e i'лRmpFjBk-Ѽgw¶Ţh?,v̦Xq`aE,f~N,v׶ҊԊQ->+rK~ F +hϯ່h˰SدVcE,L˫#DVnMvij˷!ײɸDr̲϶*+O*ffϸs쐿hvG6(8+6mt2,2rctE7h>({"(vm̶\Q,2½.ו ֓ACh̷b+YR 7Wֶ)lˡ\,Kˠgm]KD,L˫w %,mKT] 2,2rƇUD?h,m; oBys +KfzNhrlFfײ)aꅷ,r҂ִ(^.Tr͡xw^E7h͏Ţ v>hѺO`˶ҘS$.%7e᲋* Ң,hf ~Au% +vU,ٮ%7vִ ]>Pg̺7Wl˭͝+ s̺7Wl+ s7Wl,DC7Wlb,DC,K˫$p G,v7ppY lRf;£U,r˲= զѶOlOn صDqFȻ\D&o»˶U̱|[,r"pȷ+frmnu,K# q)`hvqyȻBF-}Lv%,m׶hj!WxPKfVl@Hi,K׷"@,,rȵ|THD./,Kˠl <7Kˬss$,v׭ֲiE,fZҵupQNXȆ7c*Fr5X,vҌ-NfΨE7h˲%I 8,K׷N=pVv]|`aF +hϯݡ|`aȆ,rͮO؅$Fw˯0*L䅷,ɿmصDqF. [ۢÅ,r˶x4w$4E,f'eR|\ K+ͨHE7h˭2$ jѺOl׶GOPOE,f˨%ҰR8+6m˲Fr9E,fA4 1셷,۶df֪27f˦=2:v׈Ю@%%,m˲9OmѶO`+GTѺOl̯?-'DE +h85:h˧ƱDzI.7K̫wص+Mhc˚PYE7h̲pdG`W&O`˯_yl^efגLȮ-ˡRlա̥Ȯ-E,f8B)/P$JVw>!`Щf@>!,PΔ0oH̅NÑud$n7K_l*%,c>DR?Shv]"Y4 %,m˨1 `hf,*PVˡ\[gPJ,Ll\[gu Flܖ[gPRl׶@tE7hQTBE7h̭3rlh%]ejmH"̠Wݏ.MD ȷWohx<"E7oȨUx;%,m'ܴ.mN~BྈV&84wRhσ1=EJwׯҼ's;/ѺO`̶I-[O໫cOo׶^靘ѼOwѠr0dγvĨ r/*7Ww %,m̲1o䇢.7K̠vEyҬ>E7hW$ycE,fײ?ԃx*z! Rʪ'Ej4+-e4g~! Rʯ 3tFw˭Ⱦ e=PE7h˨>U.%,mn4i&E,f8V%,m̨f <,Lˬ)Yis&ㆻ7m̦͏#WE,flXRQg%,m̯E4%,meݱ,r)$/໖qv:_໖fv]:^,ryKLUѺO`רHĚ E&E7hsU2eT{ Rl+OAě%,m/,^5Fw̦ϑ Iٮ 4ѺO`׶ut~+eHdv;E)2`M̛QOwδ r'\·$hf[ r'[E,f̯":G%,m. z'h.RoͿ(3ތApmJ'"\cŸ<ѺOl̶ubI ىA8лcVo>@#% ,rzME͔TLmؾ-.ķKrVmh@e3,K^tAi7oCc _Hʵ7WkWQ ?E,f˶ 2Yu7hZ.FE,cҲVk,K׫\g#fdSVAhC)ķ+hm*i)Bh%,mׯrP,VE,f˯f@#Ⱦbv׫Ϣs %,mֽNLwE +m̶+5]%,mca %pMs@,Kv˵Bs@,,Kנ_BvE,f 8u7OCy"U* FoƨP؃8 E7h˭5C5"»,ofȹ4%,c~-b7f˭⼂ѺO`צʗoKhӈȿ/O <VɶH/O مbdɲrToW˪ĎoʞHʨOp5Flhhʯ-.3Rlר6*ҝ&+m_jJ"F,ˠe_4,v˶h#jJwײϾLTx+zE,f˨1&3F+.#@E'JлajRϸqr%dSVƷ,5 lr˶m;Y)PhJβ/P{-ȷ0fv֯P{䌾K+ȶ 5ޖ:%,ct A`,ݮ,v˨Ҿ::$<%B`oȿ`YѻʺVwײ!?Khֶ EptKcČѠiI dn>4|؃DHw˭8խ*AE,fG?+E,c17JZxȾIӉօ=7rχ3>O໬F9~VDE,f@^,r̶l* E,fI,Fw˦npX F7%^%,mRI\j񠿖Ֆh)tF>AͣͲ䲞7˗ҹЕ7}7mצnN[6}'̎<34}L,pӭ}-Ч7f˦3n󅊷50nϔ:F7oF)_t_fEOR͐6ǠЫBO7HE,f O0E,fר8ADމ%,c7(a>ϙ27(rȭt1uj,r̲/ 7y Fl˪ӝPL20f/PL1akͦ jisu20FOɖ5u|,vq@=A4ѵsp-<޺O3Nt +o!ݔ '8ྖF̆Ҳ b&|E7h.]aE,f˶u4:"O6hP@3G,rlab,rV^*eྈh׈DV^*eBKחDV^*eaF @r+2bH>6E,f˲8 YV)꠿hhnHO6B%,mײInHE,fv"޼E,fkBnѵnl>o2E7XPXJPco"X7$MFӤEr(ѺOl̲Ee̴*໑Fe˦Da5셻,r̶2G߿? \,rײ _|HKbV¢ѦL%E7hר@ZpԎлhפ?a}0o5& +vԴV=,r׶KunjZuhİ++J50l̲q]ʙ7˗q]ʙ·kmϴ"%"bѭֵh,c+(ˬCrhwR @HtѺOl˶S=]Ҹ0E,f˨l~P}b7rhѥ1*,-ҖiؓyE,f% 3vt9Ⱦ,mFE,f˲ś˴FE7h̨oe[񠿃hRASZ4POFǵ³OVķSTvfAශcjMʓP=QOw1E褧,vרGIf,v8XkQpDE,f׶#YfitлVJhjo0,vC Ģ΍Z\)'ȷWO-G q%,cko̳ڲ)tMי?7֭ޅ,r8}97%ܚe󤆷71֬ $%,m4fp*7Ww<ΗU%,cׯ5|9w \E,f:r5 ,-ҥ~N[M;|$Hңzӣb.o˴ͅ,\O\o +Kȣƥ~N[M;W 7WƍZR,vB\wi^ĔлVcf֦oذfQYa2rOɤRX*tmQ-rQqu&ѰVOW@0 лqFOjwrF +hѴ 6yE,fי "OvʲM$=D'׬8{aP-'<<,rٯIB rkƒ  Hf:^j({ˀE,f˶XU)b·)m͛%F0IȻwooײ֒jxu$"wׯ8B~7ח8BYȷ$OJIPFоVT̩ž`aZ )XOU' "7ח ^\"J%,m˲BY%,c:D㖐rϕF ුthR `,v̨t/7,vצt)"Ƽ7oҨnIOB#%,m׶LgMZkлcVo+ Ԓ» `m̲%Q-|| Mnnr~D|76fȲ seE,fT',dza^`׳̈H'LI,v̦%q¶B(+v̬<È_(,o¥6 b,=E,f̲rB%,m̶Q*{E,f`smkrIJn+Ssw6*7Wwj4sKH0v˵֟k%,m̲傿.eè1ȷ"OUPPPt®Ӎp?jU*7Ww/ @E5لKׇήͯf@@<оHo׬ˬ噿U2wRթ噿TzwRFãeTÈlΌӱf@@<Ҷ'fa3I>R,-opо-Mˠ@^HZR,2oDlE, 48'nH(lJɉƯ\% +vե&Ϳq亂,d!໑Fe˭RL/cXE,f[+C }Vr3\ɴ+F׋8;nc,r2Y_GCFw˭0) E,f̶S z Xо2Mˠ^1EU +ĕձ֍#{$cnsͨ,bʵ7Wk{Bnw%,m֭e&Kձswb5`XbE,fײk!a ѺO`ר Z|#joˢ_'"-qV׍"fi;bɭ1{u$¾'·N-B{ʵ7Wk z*ZR.At G0Thϲ:עE,f b (YPˤF47hH̲>la`f.'+н2Rɫ]UFȗ,ͨ+Bзqj430@wRm+8tQOwCE g QffwB&· kvUͦ(,rڵL !loM-c{E,f˶Ho$Fh%`X"E,f˲aD+½3ײ&9ilK%7πE7hϨц& tAWvĆ֬;iQFh QԺLrvúSc0~7/27U»,fhIWj/uM,vר֒U7˗q9:fQHͷ<.ȷ"O@k!*&FʦRkzϙl̏ɩxm簒$KqˋѲVX|PhoVX|QOwpVX|ĻDoo.MO.нFmBp/л(Vr6hmէ͜RoIl,Y腷,rQkwqE,f˲(Kt f,of̭70<𩂿,òŷ0<+2cdؑ,vצY_+ L,ԆȞE (`rZELCгfĤßIF̧е|38,],v˨yTb;{,˫ϟO:TtƗ1(A7גo + S$h Ⲷ,NĻlm˭ƹͰcNĽwזқ ),7ח 4yPPtCķɧХPA! Rִͥq@^ʵ7WkX06tĻlm˲nMrgH2heA`jFֶ_RHoHaײE/"Xiڊ7W|2˳MFӤ*~PҘ$h 5rfŪ Gqa`Jӆş Gqa+_Ո&{KRoylV8lFO[nLZfrM߃':Fr2r:ow_XbjҊB&7vȭ, U»wo̲+»kf’S"+!":\Ļlm˲ RZhF7oȯޤ}* +oeO%,m [*Bлm̮Mez)Vغ.vm܁E"½ bЭ@ ,rֲ4LcOƭn7Q[Ľ`׍KwaօĻlm˨ [r(ȇ ,fȯߤ%37xns֓6q- ,յϭ?}ȻKoϨxulP, u`'lfE?PŴy`hf:F ,aȲS쵘,rp"o]·5FmgJQAwײĪκ{F+KyCO9x9%XBRͣ\o=aзvo_>x7ג6: f>HIqvmL@QffyKз2MmME7h<럮 ྫrm̷f7G4*Vry0!Ļlmרҥl{*50g׭ZYuhUff/].ȻSc҅1ÔxtĽ`̒80;0f2cȶn@T{Ļlm˭j RWhOcl*,o.G'hlH̡îGҺ,2oȰĽ`ג\ϖĽw׈*0]nphRгO]Wv[cuRũn#,ͫG  LRfρssE,oKb`Mh5"K+uɥЈyv{ml׉VUUUUUUྃvUUUUUT 7WoPFӢt$N&q̠©$N'KfΏ% Pv`vKvE7h6;'FĻ`oײҦT]w{.E +hѲa6{ߴp6t>iP-ƷC eђ,2և]+ݦ06oש?\O,˫+dAXp/fՔ֨:SCPo?XB&bVm1 l.|Ľ`ג֨H% L(SjӲԎcD,҉Ğ0Rlx1Z,̫L p6 {YUȻwoo̶LwGܪ7Ww zy̪7Wwʊ zy^p6tǓ.{Xh̩ȍ锾+-mŲpC&S9Y+K7ϭh2L½3˲ͭ ؍7ҡ9oyMrKn 8wORסgV)Uevҭtj:ahaĵ֟uFǕqcR"w8Rv<?ߑFhstta m?jgM+ĕձ8*0nwB$rDr֥f8dOķKVv$1l̦̀ Lt XWvAWBź,fk!=jAgw09ft,roŨzвA7RM0?8(KmԯPfEsWnQOw˨Otf,ЪFr˦@Otf, +@ttHIrʒ7>LĻwv˲1>·o #Ɓ8Wvrn>F]p7˗if)+/Ľl̖#s.zdsHw/ _O,rͦr(jI,=f+,ƭ~jcXоMaFijf;dg{·)mȻG9PrΖյϔ)9Z»$aw!^4fjT!^ZPcŏȲ!^4`ELw?.mmΫw!^YԼIRt!^4ѺOwDI߄ V҈Ԩ-|з(OfѼOwοP,{ ȷ.Vǂ9Ǘv! RֲxHlppoVĠϗPI#moנ(?CxBĽwזy]לĽ`׈Z&ٰĽl̖{k%+Kҋ5Ѡ˫Ljn|qעѼOwӳ +$+2r7rw_}J50ḽƴ< ,3)nֺK$4rȾ+v˫ěX_`-NнfMr̢][4(,ƶᶇ[Ļwv׭:~@P(rK_|7חȣ4HmxrPĻwv˭}ӗwĻwvרҞ 6ĻwoײҒhh3-vhoנ̠[bctנ[b`о2Fˠ׋[bƪĻwo˲XWcYo G7ɕ +Ϯ>[A`ϨѿA:TDyѼOw Ļlm˲J%VMMnȁU?ܢ"jJjǮw ȷwoo4̳؆ĻwvרO*T9Ļwv˲֪Fɛrɭm~"`aR׵Fm _)Cr֯T P*нReş!gFo(pNhscAġT`-- Vh4p:,˫e fqbfROˮ̵шHb\bTRl˨M ׍,˫7)UU=b7˗4uĻlf˭j4.c,̫ȗэC,̫>Pُ+K;Dbׁƛ,̫FSU'ķwϯ8, Rk\w оFt̆p\wjƺ7oȼ$ik{ ,9u_BwȻwoo̶-Mq»7ra]6W#̺+KwY7,̫^3~O+MEKR̯UP 7חϦ%YxQOwǑL3n཈hhL3nt˰Ʃ1Ļlm˭0[ཱིqF/Ƌ~ ,̫D<>ʮĻlm˲hF5#E,͹e:,̫>!_Ľ`̒ƌ|NbR,rťئjHH7˗VY%,˫ַ3AT4Ľ`גTZR2,ˠ6 i!h RRՍq$ρ"aפz$^(pRƯT%a7ˀfp`_1]$ǠΦ/phmo8Fd.mrĸFH3orB$נˆfQ,0,̫Ҷ}-L\`TзA Ļwv˭Tk(~KȾbFҴgnJd,̫Zw,˫/' +r,ohWvタ,ov䨽Im? =H,tˡhXp}|གྷmՖ͜kuwĽ`ג<Ļlf˭"RGmo6jbD,̫HS9݉pMͦѲ= 3sOؐq̆2= 3sO່h2= 3sOPhoβ= 3sOTCנ^ȥTC̫^ȥ:RF̠נxmw7[ZоhF\? ,Q̇#L!dH= 3sOH׏ˠǰM 7Wʪϲ= 3sO 7Wʪ= 3sOȽUh˨2= 3sO 7Wʡ2= 3sOB5MǰMHWדMǰMD5ׇMǰM»$MǰM 7WҴæǰ 7WҴæǰN 7Wȴáǰr Vkצ״8cN3,̠D,kkKmq"3.X2TMzQh%?Ļwv˲ͱ6k荚»7ra(Y,̫ =b,̠Ȓ#F1ĻwoרaCJfҎ7$ĻwvײI{LkB)cˈEHWĻlmײJ')SѼOwӳgT8о-Mˠ VS;^SнMVנ̊ġȻw7Wkġ;7Wk ġ;$HôҴ VS;^RQOdĪĻlmײD`оFt̆ҹ"`iԢ3AbZ$,˫Ϣk2O@,˫ɉgW8Ľ`̒@xt&ĻlfײV[5o-XuOF{e-$UOդRtwpQ׶X>»7rad@ 7Wަ^B 7WȡYf| ѼOw*sBPOv׮}bk oĻwv˨wCfw2q˒қm]\dw}ȻWHoרDaG^Nо2MˠAb+Ļwo׭ dz ĻwoײcH%&Ļlf׭Ңp[ ཈hvdI9 FlI8K+, eFiATh1t%pOcK¾'t˓ȭcqSȾlJ̏U;VĻlf׭8`'TLغ.vmHv<=,̠o;XĽl̒bW$C»7ra,$ꔉ0F̡Ż@mA`Tз-cĻwv˨Ʌ٤YĽ`̈=@joEEr̰ng#Eomį􋋡 ?,G[uGU,@[iizHm©Lhj+K-糗{&XlvHm糗{K.ReܫguֺĻwv׭Pn@}Z+KJBǙķw(7qAhaO.bv+_ȷwooI*\Ļwo˭V΋,pVčǫͱz~,˫=H㰻hJ̲Ƃ)u󀔾IRr˨ͦ)uĻlfײҥXa7ˀf-h&HdF҄<_dOQĻwo˲Ͳ{eBFJI,̠C8:@n,M.Ļwo׭/9m{P?7fhűV+k8ķl&o,̫ҴkĻwo˭ƥ;ྃt gQ!̖~̏Rr˷DLcȾ+vҋpx6Ļwv˭ͰeDU,˫րffbSʼ7Wk9`WLmvmU#*CĻwoײ[%Ⱦ+«h_!f6.hoU3PuOԴEAKD,˫{pJ HP"Tp}A ,̫Ҩ20̰Ľ+h̲[@,˫udVqrȆH;50D ySvզ 3\ja.RÊ3\jྖmˬUJ&d'mvȡٯK+M<Ļlf˲w-HJȻWHǫgͨy2Ļwo˨?9^i4IRq+;S(Ľl̈˖I!AAȾbЋ2P~/̼+Kl9WH,m˨5>^hwRFܽjĽ`̭/VDȽpr:z#!?غKhoQ^%fΠҜUĻlf˲޴X+Ļwv׭56$q ȽBfMfD+Ļwvרϧ5S_ 7Wֶȍv^VRT^c@¾ Ғx6`Hjh,JpE\w ķlG%y},̠1>i)vоJfǁIU6jbĻwo׭5[Uk+vhֶaGЂ `f荪 }]wwhƦϏWv%{,̠.ԘKVmն;X>jĽw˲ĥgV»7raқ;6:Ļwo˭Wlj{hh@"KqMdjvϿ)6`rMD-`ah=XXm"/ؼlFHvTr8:wv`S,˫־ hwRF`f-,˫>aX%AȾpTTdL+vhͲľUfjϨOFe˲Ϩ4ELĽl̖J# 4y_jme³ҧP<Q½)tϕUUUUUU= 7Wj 7WUUUUUUaнRCr֬AH\%3EOp1vj,RoƲelW2*wȨ 3o>oAȒ]JjddHl̲Φ*FL+Kl̨ΦUB9𺉘KĽw˭U9 ̼7Wl׶a-z54s`fEf"̳RO¹VScvĒʶ `"i$HϯYd,RoҲcqaY&#$Hrd{ 7Wͽ^QOl˲+/Y\RBwoͫo;(WTӤʭ rϤPcoÒ6RuyCn$htԴҶRuyCԺIRo6RuyC7LĥǶRuyCn 7WʡŶRuyC 7WȯI[HB lr֨LD,0fΊΩg0JgN)Ƚ,mϢ-`M( 7WѾ29!B,roƲҴ9H+dPhv46OQ=vwvƦDwHSoĭ|q-LnА2/A{106Ǯه5'ѮڊZ%̼7Wlרtܽ Ӌ̼7Wlܽ Ӌ䬺7Wlܽ Ӌ,7Wltܽ Ӌu$HϴƯ#B ,tf 7WŲʥџh~6]¾7rĎiAEo7OiAid;rԼ5r $sl̄taojOo $s)(bOˋKwoͲL@h vk?Ⱦ,mRBhO|LJi-OȲԨ=U1HAQOl˨ɪC &L,RoƲ͵QƥྖVf̢pP>]$"ϴkyf ,RoҲyR'2,RoҲ5>,roƲ4VOאg ps¾ҭyS*StHaЮȫ©C܈u֨^$KVm(nMΤjꍠpφl 7K?ہ`mTH.K.[ 50ƥ͛\e" 50ŲUUUUUU<snappea-3.0d3/SnapPeaKernel/unix_kit/CuspedCensusData/terse6n.bin0100444000175000017500000001106606742675047023110 0ustar babbab7Ww+KZ7W6wE7h,L7W6E,f,r7W6Z7W6w+KE,fl7WwOV7Wl,L7K7KV7WlU7Kk +Kl7WwOU,Kk,KV7Lk,KU7KkV+KwV7LkU,KkV+KwU,KwU,Kw/hh WuD7Wwqk7Ww*7Ww7Ww/hmhBff"OhHQOwOQOwQOwPp&7v&7v&7vQOwQOwQOwGirr,r,r,r,UO&rm*7Ww&o%,m%,m%,mE,fE,fE,fdHhh%,n wRlk7p7Ww*7WwSkHT5rrOwOw.Ru%7e&fhR,-o7oWbOw.QOw7Ww7Ww%,cOw.Rh.Rh7m,uHhR,-oRKK`SfVfE7hdH`dH`7v&7v,rh,v4UsOE7hE7h,m,m%,m.Ru,go7Wo7Wc,r,rPauE,fHlHHE7hX"ii+Ke,f,f.Lm.mgIRg%,m,m,mSk.ms,f,fQOwQOwa@T@TwwtDd7WtRRw67Wt&kwE,fSkOl+KHwVm'Hd0FIRkKK7W`7W`pTd7r7rOrFSk+K 'HdtT`+bh%i4UnOHb%r7Ww7Wl*sg IRsh1UhhwR7rViVqqpT`fibUnU7rk7rrhmIsL!A`O`Mub*snUffpPfflmddOo/Hfwv,SoAfftMM.OvrWOKidOL#CcO7W7Ww7Wl/rl(rkmhm!dlfOf Oj7rp7rk7r7r7r7rwoRtO0FrOf7rU-wl/rlKKo`KLowWwj@`&L@LKLl@`4Tt4Tt,snappea-3.0d3/SnapPeaKernel/unix_kit/unix_file_io.h0100444000175000017500000000041707001154267020433 0ustar babbab/* * unix_file_io.h * * These two functions allow unix-style programs * to read and save Triangulations. */ #include "SnapPea.h" extern Triangulation *get_triangulation(char *file_name); extern void save_triangulation(Triangulation *manifold, char *file_name); snappea-3.0d3/SnapPeaKernel/unix_kit/unix_closed_census_main.c0100444000175000017500000000456707001161744022665 0ustar babbab/* * unix_closed_census_main.c * * Reads input from stdin. * Each line of the input describes one closed manifold. * Here's a sample input file: 0.9427 5 003 -3 1 0.9813 5 003 -2 3 1.0149 5 007 3 1 * The first entry in each row is the manifold's volume * (for the benefit of human readers, not for the program itself). * The second entry says which SnapPea census of cusped manifolds * ( <=5 tetrahedra, 6 tetrahedra, or 7 tetrahedra ). * The third entry is the index of the given cusped manifold within the census. * The last two entries are the Dehn filling coefficients (m,l). * * Note: I have a huge file giving the data for vast numbers of * low-volume closed orientable hyperbolic 3-manifolds, and a more * modest collection of nonorientable manifolds. */ #include #include #include #include "SnapPea.h" #include "unix_cusped_census.h" #define FALSE 0 #define TRUE 1 static void do_something(Triangulation *aTriangulation); int main(void) { /* * main() just iterates through the manifolds. * The real action is in do_something() below. */ double theVolume; int theCensus, theIndex, m, l; Triangulation *theTriangulation; while (5 == scanf( "%lf%d%d%d%d", &theVolume, &theCensus, &theIndex, &m, &l)) { theTriangulation = GetCuspedCensusManifold( theCensus, oriented_manifold /* ignored for 5-tet census */, theIndex); if (theTriangulation != NULL) { if (m != 0 || l != 0) set_cusp_info(theTriangulation, 0, FALSE, m, l); else set_cusp_info(theTriangulation, 0, TRUE, m, l); switch (do_Dehn_filling(theTriangulation)) { case geometric_solution: case nongeometric_solution: do_something(theTriangulation); break; default: printf("Couldn't find hyperbolic structure for manifold %d %d(%d,%d).\n", theCensus, theIndex, m, l); printf("Expected volume was %lf.\n", theVolume); break; } free_triangulation(theTriangulation); } else printf("Couldn't read census manifold %d %d.\n", theCensus, theIndex); /* check for memory leaks */ verify_my_malloc_usage(); } return 0; } static void do_something( Triangulation *aTriangulation) { /* * Your code goes here, to do whatever * you want with each manifold. */ printf( "volume is %14.10lf\n", volume(aTriangulation, NULL)); } snappea-3.0d3/SnapPeaKernel/unix_kit/ClosedCensusData/0040755000175000017500000000000007011566405021002 5ustar babbabsnappea-3.0d3/SnapPeaKernel/unix_kit/ClosedCensusData/ClosedOrientableDistinct.txt0100444000175000017500000065511007001161544026460 0ustar babbab0.9427 5 003 -3 1 0.9813 5 003 -2 3 1.0149 5 007 3 1 1.2637 5 003 -4 3 1.2844 5 004 6 1 1.3985 5 004 1 2 1.4140 5 009 4 1 1.4140 5 003 -3 4 1.4236 5 003 -4 1 1.4406 5 004 3 2 1.4637 5 004 7 1 1.5294 5 004 5 2 1.5435 5 003 -5 3 1.5435 5 007 1 2 1.5831 5 007 4 1 1.5831 5 007 3 2 1.5886 5 006 3 1 1.5886 5 003 -5 4 1.6496 5 006 -3 2 1.7571 5 015 5 1 1.8243 5 007 -3 2 1.8319 5 009 5 1 1.8319 5 010 -2 3 1.8319 5 009 -5 1 1.8319 5 011 1 3 1.8319 5 006 1 3 1.8435 5 007 -5 1 1.8435 5 009 1 2 1.8854 5 016 -3 2 1.8854 5 007 5 1 1.8854 5 006 -1 3 1.8854 5 017 -3 2 1.8859 5 006 3 2 1.9108 5 010 -1 3 1.9122 5 011 2 3 1.9222 5 006 4 1 1.9415 5 006 -5 1 1.9415 5 009 3 2 1.9537 5 006 -2 3 1.9627 5 006 2 3 1.9627 5 017 -1 3 2.0143 5 023 -4 1 2.0259 5 007 5 2 2.0288 5 006 -5 2 2.0298 5 036 -3 2 2.0298 5 015 6 1 2.0298 5 016 -2 3 2.0298 5 010 -4 3 2.0298 5 019 3 2 2.0298 5 011 -3 1 2.0298 5 010 4 1 2.0555 5 007 -6 1 2.0584 5 010 3 2 2.0584 5 016 -4 1 2.0624 5 009 6 1 2.0624 5 009 -6 1 2.0656 5 007 -5 2 2.1030 5 015 -5 1 2.1086 5 010 1 3 2.1145 5 016 3 2 2.1145 5 015 3 2 2.1147 5 010 -5 1 2.1243 5 011 4 1 2.1280 5 010 -5 2 2.1340 5 016 4 1 2.1340 5 009 -5 2 2.1340 5 009 5 2 2.1557 5 017 1 3 2.1557 5 011 -2 3 2.1847 5 034 4 1 2.1933 5 010 -5 3 2.1959 5 034 -4 1 2.2004 5 010 -3 4 2.2076 5 034 -3 2 2.2082 5 011 -3 2 2.2102 5 011 4 3 2.2109 5 011 1 4 2.2267 5 015 -3 2 2.2267 5 015 7 1 2.2597 5 038 1 2 2.2662 5 015 5 2 2.2726 5 016 2 3 2.2726 5 026 -4 1 2.2726 5 015 -6 1 2.2726 5 017 2 3 2.2757 5 011 -1 4 2.2779 5 038 4 1 2.2944 5 023 -3 2 2.3126 5 023 -5 1 2.3126 5 022 2 3 2.3126 5 038 -5 1 2.3188 5 017 -5 1 2.3188 5 016 -5 1 2.3207 5 019 4 1 2.3380 5 022 1 3 2.3430 5 016 -4 3 2.3430 5 017 -4 3 2.3522 5 016 -1 4 2.3522 5 017 -1 4 2.3567 5 019 1 4 2.3627 5 015 8 1 2.3627 5 034 3 2 2.3641 5 019 -2 3 2.3705 5 022 5 1 2.3747 5 149 1 2 2.3803 5 019 -4 1 2.4224 5 022 5 2 2.4255 5 019 3 4 2.4255 5 022 4 3 2.4444 5 019 4 3 2.4540 5 022 -1 3 2.4631 5 026 4 1 2.4682 5 029 -3 2 2.4682 5 036 3 2 2.4682 5 207 1 2 2.4878 5 022 -5 1 2.4903 5 023 -6 1 2.5026 5 038 3 2 2.5065 5 034 -5 1 2.5144 5 034 5 1 2.5162 5 081 3 2 2.5274 5 070 -3 1 2.5274 5 038 -5 2 2.5274 5 036 -5 1 2.5274 5 032 6 1 2.5274 5 033 -2 3 2.5303 5 030 5 2 2.5415 5 023 -5 2 2.5495 5 038 5 1 2.5667 5 026 -5 1 2.5689 5 039 6 1 2.5689 5 035 -6 1 2.5689 5 037 2 3 2.5689 5 130 -3 1 2.5689 5 120 -4 1 2.5689 5 223 3 1 2.5689 5 038 -6 1 2.5689 5 036 -2 3 2.5689 5 160 1 2 2.5689 5 117 -4 1 2.5689 5 026 1 4 2.5689 5 140 -1 2 2.5689 5 027 1 4 2.5689 5 136 1 2 2.5751 5 036 -1 3 2.5751 5 029 -5 1 2.5751 5 030 2 3 2.5854 5 030 1 3 2.5953 5 160 -3 2 2.6091 5 026 -5 2 2.6095 5 036 -5 2 2.6095 5 032 -6 1 2.6095 5 033 2 3 2.6122 5 027 -4 1 2.6172 5 027 4 3 2.6244 5 081 1 3 2.6285 5 036 5 1 2.6294 5 032 5 2 2.6344 5 030 4 3 2.6356 5 034 -5 2 2.6414 5 034 -1 3 2.6447 5 149 -3 1 2.6447 5 081 2 3 2.6528 5 168 3 1 2.6536 5 036 1 3 2.6555 5 034 -2 3 2.6646 5 034 1 3 2.6667 5 036 -4 3 2.6667 5 140 -4 1 2.6667 5 135 -1 3 2.6667 5 135 1 3 2.6667 5 040 -4 3 2.6667 5 168 3 2 2.6667 5 140 4 1 2.6667 5 037 4 3 2.6735 5 160 2 1 2.6794 5 034 5 2 2.6822 5 032 7 1 2.6954 5 069 4 1 2.6954 5 069 -1 3 2.7067 5 030 5 3 2.7124 5 120 -3 2 2.7589 5 116 -1 3 2.7589 5 081 4 1 2.7654 5 116 -2 3 2.7725 5 081 -1 3 2.7818 5 081 -4 1 2.7818 5 116 -4 1 2.7818 5 130 3 1 2.7818 5 130 -1 3 2.7868 5 082 1 3 2.8022 5 160 -2 3 2.8117 5 145 1 3 2.8125 5 070 -3 2 2.8125 5 148 -1 2 2.8125 5 069 -3 2 2.8161 5 078 5 1 2.8281 5 221 3 1 2.8281 5 070 1 4 2.8281 5 139 2 3 2.8281 5 078 -5 1 2.8281 5 130 -3 2 2.8281 5 142 3 2 2.8281 5 140 3 2 2.8281 5 069 1 4 2.8281 5 082 -1 3 2.8281 5 147 3 1 2.8281 5 136 3 2 2.8281 5 140 -3 2 2.8281 5 206 1 2 2.8458 5 082 2 3 2.8465 5 168 2 3 2.8472 5 070 4 3 2.8472 5 069 4 3 2.8656 5 137 -5 1 2.8669 5 070 -2 3 2.8669 5 069 -2 3 2.8733 5 069 -4 1 2.8733 5 070 -4 1 2.8797 5 081 -2 3 2.8824 5 100 2 3 2.8824 5 069 3 4 2.9027 5 082 -2 3 2.9133 5 221 -1 2 2.9160 5 148 -5 1 2.9169 5 116 1 3 2.9185 5 078 1 3 2.9215 5 148 -3 2 2.9215 5 168 1 3 2.9349 5 078 -1 3 2.9356 5 120 -5 1 2.9379 5 135 -2 3 2.9379 5 135 2 3 2.9398 5 078 2 3 2.9400 5 145 2 3 2.9400 5 116 -4 3 2.9438 5 078 5 2 2.9441 5 130 -2 3 2.9441 5 130 3 2 2.9441 5 139 3 2 2.9441 5 139 -2 3 2.9471 5 148 1 2 2.9485 5 117 -2 3 2.9545 5 249 3 1 2.9582 5 145 3 2 2.9583 5 207 -1 3 2.9605 5 117 3 2 2.9607 5 117 -5 1 2.9670 5 117 4 1 2.9670 5 154 2 3 2.9696 5 078 -2 3 2.9703 5 148 5 1 2.9703 5 137 3 2 2.9709 5 100 -2 3 2.9760 5 117 1 3 2.9769 5 078 -5 2 2.9772 5 081 4 3 2.9781 5 142 4 1 2.9781 5 159 3 2 2.9868 5 137 5 1 2.9891 5 140 -5 1 2.9891 5 136 5 1 2.9891 5 140 5 1 2.9907 5 168 -3 1 3.0009 5 117 -5 2 3.0224 5 142 2 3 3.0224 5 149 -3 2 3.0263 5 116 3 2 3.0320 5 100 5 2 3.0385 5 120 5 1 3.0398 5 116 -5 2 3.0448 5 149 -4 1 3.0448 5 188 2 3 3.0448 5 247 -1 3 3.0448 5 141 -4 3 3.0448 5 159 2 3 3.0448 5 146 -4 1 3.0497 5 147 -4 1 3.0589 5 286 -4 1 3.0593 5 130 -4 1 3.0593 5 130 1 4 3.0593 5 155 1 3 3.0593 5 139 1 4 3.0593 5 139 -4 1 3.0593 5 373 -1 2 3.0603 5 115 5 2 3.0637 5 235 -1 2 3.0690 5 135 -1 4 3.0690 5 135 1 4 3.0717 5 145 -2 3 3.0717 5 116 4 1 3.0761 5 149 1 3 3.0761 5 116 2 3 3.0774 5 116 -5 1 3.0805 5 117 -4 3 3.0805 5 154 -2 3 3.0807 5 120 -5 2 3.0835 5 117 2 3 3.0835 5 149 -1 3 3.0838 5 137 -6 1 3.0871 5 119 3 2 3.0871 5 221 1 2 3.0871 5 118 3 2 3.0910 5 145 -4 1 3.0942 5 116 -5 3 3.0962 5 222 4 1 3.1011 5 155 2 3 3.1048 5 160 -4 1 3.1048 5 285 1 2 3.1101 5 141 3 2 3.1102 5 120 -1 3 3.1106 5 137 -5 2 3.1175 5 162 -3 2 3.1180 5 118 4 1 3.1180 5 119 4 1 3.1193 5 222 -4 1 3.1199 5 151 3 2 3.1213 5 139 4 1 3.1213 5 139 -1 4 3.1213 5 160 1 3 3.1213 5 151 -2 3 3.1213 5 130 -1 4 3.1213 5 130 4 1 3.1232 5 115 -5 2 3.1232 5 221 -3 2 3.1259 5 151 4 1 3.1271 5 120 -2 3 3.1281 5 118 2 3 3.1281 5 118 -5 2 3.1293 5 154 -3 2 3.1333 5 141 2 3 3.1333 5 148 -6 1 3.1333 5 149 2 3 3.1333 5 305 -1 2 3.1357 5 120 1 3 3.1391 5 223 3 2 3.1410 5 120 -6 1 3.1432 5 148 3 2 3.1440 5 145 4 1 3.1466 5 223 4 1 3.1472 5 141 -1 4 3.1485 5 140 1 3 3.1485 5 136 1 3 3.1485 5 140 -1 3 3.1505 5 151 1 3 3.1513 5 168 4 1 3.1553 5 147 3 2 3.1592 5 146 5 1 3.1592 5 149 5 1 3.1600 5 160 -1 4 3.1623 5 137 6 1 3.1639 5 140 5 2 3.1639 5 136 5 2 3.1639 5 140 -5 2 3.1663 6 119 4 1 3.1663 6 118 -4 1 3.1663 5 160 3 1 3.1663 5 159 4 1 3.1663 5 206 4 1 3.1663 5 207 1 3 3.1663 6 119 -4 1 3.1663 5 161 4 1 3.1696 5 141 -3 4 3.1729 5 147 -1 4 3.1772 5 141 4 1 3.1772 5 140 -6 1 3.1772 5 249 1 2 3.1772 6 254 -3 1 3.1772 6 479 -3 1 3.1772 5 146 -2 3 3.1772 5 188 4 1 3.1772 5 148 6 1 3.1772 5 149 -2 3 3.1772 5 136 6 1 3.1772 5 140 6 1 3.1772 5 322 1 2 3.1772 5 123 -4 1 3.1772 5 140 -2 3 3.1772 5 304 -3 1 3.1772 5 136 2 3 3.1772 5 199 -4 1 3.1772 5 122 -4 1 3.1772 5 303 -3 1 3.1772 5 206 3 2 3.1772 5 140 2 3 3.1772 5 159 -2 3 3.1822 5 137 -1 3 3.1847 5 141 -5 2 3.1900 5 160 -4 3 3.1923 5 148 -5 2 3.1934 5 147 4 1 3.1957 5 121 -4 3 3.1961 5 168 -1 3 3.2034 5 155 -2 3 3.2048 5 154 1 4 3.2069 5 157 3 2 3.2097 5 142 -5 1 3.2174 5 147 2 3 3.2187 5 142 5 1 3.2196 5 141 -5 3 3.2202 5 145 1 4 3.2217 5 168 4 3 3.2223 5 188 -3 1 3.2338 5 154 -1 4 3.2384 5 171 -2 3 3.2385 5 207 -2 3 3.2424 5 322 2 1 3.2428 5 170 -4 3 3.2444 5 157 -4 3 3.2462 5 147 -4 3 3.2487 5 160 -3 4 3.2516 5 146 5 2 3.2516 5 149 5 2 3.2529 6 090 5 1 3.2529 5 294 1 3 3.2529 5 151 2 3 3.2547 5 151 -4 3 3.2604 5 147 -3 4 3.2625 5 188 -1 3 3.2644 5 154 -4 1 3.2665 5 180 -4 3 3.2668 5 149 -5 1 3.2668 5 146 -5 1 3.2758 5 168 -3 2 3.2758 5 234 1 3 3.2758 5 221 4 1 3.2758 5 304 4 1 3.2758 5 294 2 3 3.2758 5 210 -3 2 3.2770 5 260 -4 1 3.2770 5 170 3 2 3.2773 5 260 -1 2 3.2829 5 155 -5 1 3.2829 5 249 -4 1 3.2837 5 155 5 1 3.2932 5 162 -5 1 3.2958 5 162 -1 3 3.2992 5 222 3 2 3.2994 5 178 -2 3 3.2994 5 168 3 4 3.2994 5 310 -4 1 3.2994 5 262 -3 2 3.3046 5 157 2 3 3.3072 5 170 2 3 3.3088 5 247 -3 2 3.3123 5 247 -2 3 3.3129 5 185 -4 1 3.3164 5 154 4 3 3.3239 5 168 1 4 3.3250 5 162 6 1 3.3276 5 199 5 1 3.3352 5 161 1 4 3.3352 5 159 1 4 3.3355 5 170 -1 4 3.3355 5 168 -4 1 3.3371 5 260 1 2 3.3379 5 160 -5 2 3.3380 5 155 4 3 3.3405 5 247 3 1 3.3405 5 162 4 3 3.3410 5 285 -4 1 3.3434 5 285 3 1 3.3434 5 175 -5 1 3.3457 5 168 -2 3 3.3465 5 170 4 1 3.3471 5 234 3 2 3.3511 5 178 1 4 3.3511 5 179 1 4 3.3512 5 184 4 3 3.3518 5 221 -5 1 3.3518 5 199 -3 2 3.3544 5 162 -2 3 3.3557 5 175 6 1 3.3620 5 190 4 3 3.3620 5 249 4 1 3.3620 5 195 4 1 3.3620 5 390 -1 2 3.3621 5 170 -3 4 3.3639 5 160 -5 3 3.3645 5 180 -1 4 3.3689 5 170 -5 2 3.3757 5 188 4 3 3.3790 5 180 -3 4 3.3809 5 188 1 4 3.3831 5 192 -5 1 3.3831 5 189 3 2 3.3831 5 289 5 1 3.3843 5 189 4 1 3.3844 5 185 -3 4 3.3849 5 286 -1 2 3.3874 5 189 -4 3 3.3882 5 183 5 1 3.3910 5 223 -4 1 3.3918 5 235 -4 1 3.3918 5 234 -1 3 3.3918 5 305 -4 1 3.3983 5 184 -4 1 3.3990 5 286 1 2 3.3992 5 195 -5 1 3.4004 5 222 -3 2 3.4026 5 222 5 1 3.4029 5 178 4 3 3.4029 5 208 1 3 3.4029 5 182 -4 3 3.4029 5 179 4 3 3.4029 5 208 -1 4 3.4029 5 208 -4 1 3.4038 5 185 3 2 3.4056 5 185 -4 3 3.4080 5 181 -5 2 3.4088 5 180 3 2 3.4089 5 175 -1 3 3.4101 5 389 3 1 3.4101 5 305 -3 2 3.4101 5 261 -5 1 3.4101 5 262 -1 3 3.4114 5 184 5 2 3.4115 5 178 -4 1 3.4147 5 181 -5 1 3.4172 5 183 5 2 3.4174 5 207 -1 4 3.4176 5 195 -5 2 3.4178 5 222 -5 1 3.4178 5 223 1 3 3.4201 5 189 1 3 3.4218 5 180 2 3 3.4266 5 184 5 1 3.4280 5 207 2 3 3.4288 5 189 -5 1 3.4306 5 179 -1 4 3.4306 5 178 -1 4 3.4347 5 189 -5 2 3.4358 5 180 -5 2 3.4424 5 234 2 3 3.4438 5 223 2 3 3.4458 5 199 -5 1 3.4480 5 179 3 4 3.4480 5 178 3 4 3.4490 5 371 -1 2 3.4490 5 369 1 2 3.4490 5 195 -4 3 3.4490 5 247 1 3 3.4507 5 234 -3 2 3.4521 5 234 4 1 3.4538 5 188 3 4 3.4543 5 286 -5 1 3.4543 5 261 -3 2 3.4572 5 180 -5 3 3.4594 5 220 -3 2 3.4594 5 221 3 2 3.4599 5 188 -3 2 3.4606 6 118 -5 1 3.4606 5 206 5 1 3.4606 6 119 -5 1 3.4606 6 118 5 1 3.4606 5 370 1 2 3.4606 5 275 3 2 3.4644 5 293 -3 1 3.4650 5 190 5 2 3.4651 5 262 -2 3 3.4660 5 188 5 2 3.4718 5 223 -3 2 3.4730 5 195 1 3 3.4742 5 337 -3 1 3.4742 6 117 -5 1 3.4742 5 275 -3 2 3.4742 5 206 1 3 3.4742 5 338 -3 1 3.4743 5 286 4 1 3.4749 5 185 4 1 3.4759 5 188 -2 3 3.4763 5 223 5 1 3.4796 5 232 4 1 3.4821 5 196 -1 4 3.4823 5 196 1 4 3.4861 5 188 5 1 3.4872 5 184 5 3 3.4902 5 210 -2 3 3.4906 5 410 3 1 3.4933 6 784 -2 1 3.4966 5 199 5 2 3.4980 5 190 1 4 3.4982 5 235 -3 2 3.4985 5 234 -4 1 3.5010 5 190 5 1 3.5012 5 190 -4 1 3.5019 6 173 -4 1 3.5079 5 199 1 3 3.5100 5 345 -4 1 3.5103 5 210 5 1 3.5137 5 207 1 4 3.5142 5 220 5 2 3.5142 5 350 3 1 3.5142 6 118 -2 3 3.5142 5 206 2 3 3.5142 5 221 -5 2 3.5142 5 349 -3 1 3.5142 6 118 2 3 3.5165 5 275 1 3 3.5179 5 247 -4 1 3.5185 5 190 5 3 3.5194 5 194 -5 1 3.5236 5 199 2 3 3.5236 5 189 2 3 3.5238 5 189 -5 3 3.5242 5 232 -5 1 3.5310 5 221 5 1 3.5310 5 220 -5 1 3.5335 5 198 -5 1 3.5348 5 216 -2 3 3.5349 5 199 6 1 3.5351 5 260 3 2 3.5409 5 260 -3 2 3.5433 5 240 -1 4 3.5440 5 223 -1 3 3.5463 5 260 -5 1 3.5504 5 192 -5 2 3.5505 5 195 -5 3 3.5515 5 199 -1 3 3.5530 5 234 -2 3 3.5563 5 322 -1 3 3.5570 5 302 4 1 3.5582 5 195 2 3 3.5616 5 342 3 1 3.5642 6 173 -3 2 3.5669 5 215 -1 4 3.5689 5 214 -5 2 3.5689 5 221 -1 3 3.5692 5 249 -5 1 3.5693 5 229 2 3 3.5696 5 210 -5 1 3.5736 5 213 -5 2 3.5736 5 220 6 1 3.5742 5 275 -1 3 3.5745 5 216 4 3 3.5760 5 249 3 2 3.5788 5 239 -4 1 3.5789 5 288 1 3 3.5793 5 216 5 1 3.5815 5 235 5 2 3.5816 5 356 -3 1 3.5817 5 247 -1 4 3.5817 6 118 5 2 3.5817 6 119 -5 2 3.5817 6 118 -5 2 3.5817 6 663 -3 1 3.5817 6 119 5 2 3.5817 5 206 5 2 3.5821 5 337 4 1 3.5831 5 249 -1 3 3.5831 5 212 -1 4 3.5835 5 356 -3 2 3.5863 5 241 1 4 3.5875 6 173 5 1 3.5878 5 261 3 2 3.5883 5 349 4 1 3.5892 5 223 5 2 3.5899 5 293 1 3 3.5912 5 289 -5 1 3.5923 5 275 -4 1 3.5927 5 235 1 3 3.5935 5 300 3 2 3.5935 5 262 1 3 3.5935 6 296 3 2 3.5951 5 222 6 1 3.5955 5 286 -3 2 3.5968 5 297 -4 1 3.5968 5 210 5 2 3.5990 5 303 -3 2 3.5990 5 249 -2 3 3.5990 5 261 5 1 3.5997 5 232 -5 2 3.6001 5 289 6 1 3.6031 5 260 6 1 3.6031 5 304 -4 1 3.6031 5 235 2 3 3.6036 5 243 -2 3 3.6038 5 293 -1 3 3.6043 5 275 4 1 3.6078 5 300 -1 3 3.6086 5 275 2 3 3.6086 5 222 -6 1 3.6086 5 340 5 1 3.6086 5 219 -1 4 3.6086 5 378 -3 1 3.6125 5 216 5 2 3.6157 5 222 5 2 3.6173 5 288 -3 2 3.6181 5 222 1 3 3.6181 5 223 -5 1 3.6186 5 322 -2 3 3.6223 5 290 3 1 3.6266 5 310 -5 1 3.6281 5 249 5 1 3.6283 5 247 3 2 3.6291 6 129 5 1 3.6293 5 288 4 1 3.6309 5 223 4 3 3.6314 5 235 -5 1 3.6319 5 220 -1 3 3.6319 5 221 1 3 3.6353 5 239 5 2 3.6360 5 238 4 3 3.6382 5 223 -2 3 3.6398 5 222 -1 3 3.6403 6 131 -5 1 3.6414 6 129 -4 3 3.6420 5 222 2 3 3.6429 5 261 -6 1 3.6455 6 254 -4 1 3.6455 5 232 2 3 3.6455 5 368 -3 1 3.6455 5 370 3 1 3.6470 5 240 2 3 3.6477 5 238 5 1 3.6479 5 239 4 3 3.6481 6 244 -4 1 3.6485 5 229 -4 3 3.6488 5 229 -5 2 3.6509 5 240 -3 4 3.6532 5 288 -4 1 3.6532 5 238 -2 3 3.6555 5 302 -4 1 3.6556 5 235 -1 3 3.6568 6 345 -4 1 3.6584 5 240 3 2 3.6586 5 247 2 3 3.6615 6 140 -5 1 3.6617 5 232 -4 3 3.6638 6 942 -2 1 3.6638 5 336 -1 3 3.6638 5 303 -1 3 3.6638 6 960 -1 2 3.6638 6 572 1 2 3.6638 5 293 4 1 3.6638 6 645 -1 2 3.6638 6 296 5 1 3.6638 6 779 2 1 3.6638 5 312 -1 3 3.6638 6 778 -3 1 3.6638 5 306 5 1 3.6638 6 595 3 1 3.6638 5 304 5 1 3.6638 6 254 -3 2 3.6638 6 775 -1 2 3.6638 6 705 1 2 3.6638 6 682 -3 1 3.6638 6 775 -3 1 3.6638 5 239 -2 3 3.6638 6 350 -4 1 3.6638 5 294 4 1 3.6638 6 495 1 2 3.6644 6 385 -4 1 3.6655 5 291 -1 3 3.6664 5 345 4 1 3.6679 5 235 6 1 3.6679 5 288 2 3 3.6679 5 303 -4 1 3.6682 5 372 -4 1 3.6698 5 234 1 4 3.6709 5 297 4 1 3.6734 5 288 -1 3 3.6743 5 275 -2 3 3.6749 6 313 4 1 3.6750 5 243 -4 1 3.6754 5 238 5 2 3.6756 5 293 2 3 3.6759 5 247 4 1 3.6784 5 253 3 2 3.6871 5 310 1 2 3.6871 6 479 -4 1 3.6871 5 289 -1 2 3.6871 6 478 4 1 3.6871 5 345 -3 2 3.6871 6 645 3 1 3.6871 5 389 -4 1 3.6890 5 291 4 1 3.6925 6 384 4 1 3.6958 6 148 -5 1 3.6987 5 305 4 1 3.6987 5 235 4 3 3.6987 5 234 5 1 3.7001 5 249 1 3 3.7018 6 175 -5 1 3.7028 5 345 1 2 3.7057 5 285 -1 3 3.7078 5 326 -3 1 3.7102 5 290 -4 1 3.7104 5 289 1 2 3.7114 5 285 4 1 3.7114 6 155 5 1 3.7131 5 240 1 4 3.7141 5 249 -5 2 3.7146 5 261 -5 2 3.7146 5 293 -3 2 3.7147 5 356 -1 3 3.7150 5 263 5 1 3.7161 5 253 4 1 3.7166 6 173 -1 3 3.7167 6 359 4 1 3.7169 5 286 3 2 3.7189 5 262 2 3 3.7193 5 297 -1 3 3.7199 5 286 -6 1 3.7199 6 451 4 1 3.7199 6 480 -3 2 3.7199 5 390 -3 2 3.7210 5 260 -6 1 3.7213 5 285 -5 1 3.7231 5 286 5 1 3.7231 5 285 -2 3 3.7254 6 297 3 2 3.7254 5 262 -5 1 3.7254 5 300 -3 2 3.7254 5 322 -4 1 3.7264 7 0248 -5 1 3.7267 5 303 4 1 3.7267 6 297 4 1 3.7283 5 290 1 3 3.7287 6 155 -6 1 3.7293 6 140 4 3 3.7328 6 185 5 1 3.7335 5 338 4 1 3.7340 5 243 -1 4 3.7352 5 260 5 2 3.7365 6 206 -5 1 3.7373 5 244 2 3 3.7376 5 253 -3 4 3.7383 6 183 5 1 3.7383 5 285 3 2 3.7388 5 253 2 3 3.7397 6 173 -5 1 3.7415 5 244 -5 3 3.7430 5 259 -4 1 3.7433 6 235 -4 1 3.7457 5 356 -2 3 3.7464 5 251 2 3 3.7495 5 259 -1 4 3.7506 5 342 -4 1 3.7540 5 335 -1 3 3.7549 6 165 -1 4 3.7573 5 297 -2 3 3.7588 6 297 1 3 3.7588 6 298 5 1 3.7588 6 594 1 2 3.7588 5 307 -5 1 3.7588 5 305 -5 1 3.7588 5 369 -1 3 3.7588 5 390 3 1 3.7588 5 293 -2 3 3.7588 5 303 1 3 3.7588 6 594 2 1 3.7588 6 480 3 1 3.7588 6 595 1 2 3.7588 6 235 -4 3 3.7588 6 287 -3 1 3.7588 5 290 -1 4 3.7631 5 262 -4 3 3.7636 5 261 6 1 3.7669 6 297 -2 3 3.7669 5 319 5 1 3.7669 5 303 -2 3 3.7672 5 289 -6 1 3.7673 5 410 -3 1 3.7675 5 260 7 1 3.7703 5 269 -3 4 3.7708 6 645 -2 1 3.7708 5 346 -3 2 3.7708 6 481 -1 3 3.7708 5 297 1 3 3.7708 5 369 -3 2 3.7708 5 371 3 2 3.7708 6 478 -1 2 3.7708 6 479 1 2 3.7708 5 289 7 1 3.7708 5 280 1 4 3.7718 5 291 -3 2 3.7730 6 175 6 1 3.7732 5 310 -1 2 3.7734 5 312 -2 3 3.7748 6 254 1 3 3.7771 5 310 6 1 3.7777 6 256 4 3 3.7792 5 261 -1 3 3.7792 5 304 -3 2 3.7792 5 340 -5 1 3.7798 5 279 -2 3 3.7816 5 312 4 1 3.7840 6 173 -2 3 3.7842 5 285 -5 2 3.7847 5 300 -2 3 3.7848 5 346 -1 3 3.7848 5 326 4 1 3.7855 5 288 -2 3 3.7892 5 262 -1 4 3.7901 5 294 -1 3 3.7911 6 254 5 1 3.7912 6 345 -1 3 3.7940 6 235 -3 4 3.7946 6 244 4 1 3.7950 5 322 3 1 3.7951 6 161 5 3 3.7951 5 285 1 3 3.7965 5 378 3 1 3.7973 6 417 -4 1 3.7973 5 261 -7 1 3.7998 6 359 -4 1 3.8009 5 272 2 3 3.8019 6 254 2 3 3.8030 5 279 1 4 3.8046 5 305 3 2 3.8046 5 279 -4 1 3.8049 5 335 -4 1 3.8070 5 289 -3 2 3.8073 5 289 3 2 3.8088 6 313 3 2 3.8132 5 312 -4 1 3.8138 5 294 1 4 3.8182 5 290 -3 4 3.8197 6 235 -1 4 3.8206 5 286 -5 2 3.8216 5 294 4 3 3.8216 5 370 -4 1 3.8216 5 368 4 1 3.8216 6 554 1 3 3.8216 5 369 3 1 3.8216 6 313 -2 3 3.8216 5 293 -4 1 3.8240 6 254 -1 3 3.8244 5 338 2 3 3.8258 6 345 -2 3 3.8268 5 302 1 3 3.8269 6 784 3 1 3.8292 5 269 -5 3 3.8301 5 294 3 4 3.8322 5 326 -1 3 3.8345 5 300 4 1 3.8358 5 290 -4 3 3.8369 5 291 -2 3 3.8371 5 291 -4 1 3.8394 5 275 -5 1 3.8409 5 335 -2 3 3.8418 5 322 1 3 3.8438 5 340 6 1 3.8445 6 256 -4 1 3.8449 6 847 -1 2 3.8465 5 389 -1 2 3.8465 6 478 1 2 3.8465 6 479 -1 2 3.8465 5 310 -6 1 3.8465 5 279 3 4 3.8473 5 345 -5 1 3.8473 5 280 -2 3 3.8475 5 335 3 2 3.8477 5 297 2 3 3.8515 6 244 -4 3 3.8520 5 276 -1 4 3.8528 5 285 -4 3 3.8533 5 400 3 1 3.8534 6 296 -5 1 3.8534 6 554 3 1 3.8534 5 304 -5 1 3.8534 5 306 -5 1 3.8534 5 307 -1 3 3.8534 6 594 -3 2 3.8543 5 350 -4 1 3.8553 5 291 1 4 3.8578 5 364 -3 1 3.8578 6 238 -1 4 3.8585 5 343 3 1 3.8615 5 319 -5 1 3.8615 5 307 -2 3 3.8615 5 305 -2 3 3.8618 5 350 -1 3 3.8618 5 360 -2 3 3.8630 5 302 5 1 3.8654 6 313 -5 1 3.8662 5 403 3 1 3.8668 5 302 2 3 3.8680 5 281 -2 3 3.8687 5 373 -3 2 3.8718 5 305 -5 2 3.8718 6 298 5 2 3.8718 5 307 -5 2 3.8730 5 297 -5 1 3.8730 5 346 3 2 3.8731 5 372 4 1 3.8733 5 337 -4 1 3.8743 5 320 -2 3 3.8767 5 300 -4 1 3.8779 5 389 4 1 3.8779 6 478 -4 1 3.8779 6 479 4 1 3.8785 6 256 5 2 3.8789 5 303 2 3 3.8789 6 297 2 3 3.8789 5 320 -5 1 3.8807 5 300 1 4 3.8807 6 254 5 2 3.8808 5 288 5 1 3.8810 5 378 1 3 3.8812 6 256 5 1 3.8830 6 403 -1 2 3.8830 6 645 3 2 3.8830 7 0543 -4 1 3.8830 6 448 -3 2 3.8830 5 340 3 2 3.8830 5 376 -3 2 3.8848 5 286 6 1 3.8871 5 338 -4 1 3.8873 5 291 4 3 3.8881 5 343 -4 1 3.8896 5 290 3 2 3.8904 6 254 -5 1 3.8904 6 227 -2 3 3.8904 5 286 -7 1 3.8904 5 285 2 3 3.8932 6 480 -4 1 3.8932 7 0953 -4 1 3.8932 6 254 -2 3 3.8932 5 390 -4 1 3.8935 5 336 -4 1 3.8935 5 310 5 2 3.8938 5 369 -2 3 3.8938 5 371 2 3 3.8938 6 313 1 3 3.8939 5 322 -4 3 3.8940 5 336 3 2 3.8941 5 289 -7 1 3.8963 6 287 3 4 3.8967 6 481 -2 3 3.8974 6 296 6 1 3.8974 5 306 6 1 3.8974 5 304 6 1 3.8985 5 400 1 3 3.8996 5 289 8 1 3.9002 5 288 -5 1 3.9011 5 342 3 2 3.9023 6 350 -5 1 3.9024 5 336 2 3 3.9026 5 304 5 2 3.9026 5 306 5 2 3.9026 6 296 5 2 3.9027 5 378 -3 2 3.9027 5 335 2 3 3.9037 5 338 -3 2 3.9045 5 310 -3 2 3.9063 5 372 -3 2 3.9066 6 528 -4 1 3.9074 5 302 -2 3 3.9091 6 403 -5 1 3.9096 6 238 1 4 3.9101 6 244 -5 1 3.9149 5 346 -2 3 3.9156 5 302 -5 1 3.9159 5 294 -3 2 3.9170 6 350 -3 2 3.9179 5 297 5 1 3.9193 5 342 1 3 3.9203 6 495 -2 3 3.9208 5 349 -4 1 3.9244 5 290 4 1 3.9246 6 489 -1 2 3.9252 5 338 -1 3 3.9252 5 337 5 1 3.9254 5 339 -2 3 3.9254 5 294 -4 1 3.9254 5 293 4 3 3.9254 5 358 3 1 3.9259 5 363 3 1 3.9260 5 356 3 1 3.9292 6 463 -2 3 3.9304 6 313 5 1 3.9310 5 356 1 3 3.9332 5 346 2 3 3.9339 5 304 1 3 3.9339 6 297 -5 1 3.9339 5 306 1 3 3.9339 5 307 5 1 3.9339 6 298 -5 1 3.9339 6 296 1 3 3.9339 5 305 1 3 3.9339 5 303 -5 1 3.9339 5 291 3 4 3.9344 5 293 5 1 3.9353 6 345 4 1 3.9356 6 256 3 4 3.9377 6 394 5 1 3.9378 5 373 -4 1 3.9379 5 310 7 1 3.9393 5 339 -4 1 3.9394 6 381 4 1 3.9394 5 358 -4 1 3.9394 6 254 4 3 3.9401 6 256 5 3 3.9414 5 297 -5 2 3.9414 5 294 5 1 3.9433 5 371 4 1 3.9433 5 369 -4 1 3.9434 5 337 -3 2 3.9440 5 350 3 2 3.9454 5 300 -1 4 3.9457 5 350 4 1 3.9465 5 297 -4 3 3.9466 6 385 5 1 3.9466 6 384 1 3 3.9478 7 0508 -5 1 3.9488 5 364 2 3 3.9500 5 345 5 1 3.9508 5 293 -1 4 3.9513 6 348 4 1 3.9530 5 342 4 1 3.9535 6 599 -3 1 3.9538 6 385 -5 1 3.9542 5 351 3 1 3.9548 5 306 2 3 3.9548 5 304 2 3 3.9548 6 296 2 3 3.9555 5 376 -4 1 3.9555 6 448 -4 1 3.9560 5 335 4 1 3.9566 5 400 3 2 3.9615 5 349 5 1 3.9615 5 350 1 3 3.9619 5 346 -4 1 3.9623 5 302 5 2 3.9629 5 350 -2 3 3.9631 6 667 3 1 3.9649 7 0508 5 1 3.9673 5 340 -6 1 3.9675 6 450 -1 3 3.9681 5 305 -4 3 3.9681 6 298 4 3 3.9681 5 307 -4 3 3.9682 6 298 6 1 3.9682 5 307 -6 1 3.9682 5 305 -6 1 3.9700 6 783 -3 1 3.9701 5 326 -3 2 3.9702 6 784 1 2 3.9702 6 594 -3 1 3.9702 5 306 -1 3 3.9702 5 303 5 1 3.9702 5 304 -1 3 3.9702 5 400 2 3 3.9702 6 296 -1 3 3.9702 6 297 5 1 3.9702 6 594 -1 3 3.9702 6 299 5 1 3.9702 5 376 3 2 3.9703 5 326 4 3 3.9710 6 287 -2 3 3.9733 5 320 -5 2 3.9740 5 343 1 3 3.9750 5 319 6 1 3.9762 5 373 4 1 3.9779 6 451 -4 1 3.9781 6 657 -1 2 3.9794 5 322 -1 4 3.9819 5 364 4 1 3.9822 6 345 -5 1 3.9846 6 350 5 2 3.9859 6 377 4 1 3.9861 5 410 4 1 3.9867 6 313 2 3 3.9894 5 345 3 2 3.9895 6 488 -3 2 3.9896 6 493 4 1 3.9904 6 313 -5 2 3.9912 6 384 5 1 3.9928 5 310 -7 1 3.9937 5 322 -5 2 3.9938 5 373 3 2 3.9938 6 493 -4 1 3.9940 6 384 2 3 3.9986 6 488 -2 3 3.9994 6 493 3 2 4.0001 5 337 5 2 4.0022 6 377 1 4 4.0034 6 481 3 1 4.0039 6 437 1 3 4.0039 5 372 -5 1 4.0039 5 373 -1 3 4.0043 6 345 -5 2 4.0044 5 403 1 3 4.0044 6 345 -4 3 4.0048 6 349 4 1 4.0054 6 359 5 1 4.0079 5 352 -4 1 4.0093 6 892 1 2 4.0094 5 319 5 2 4.0095 5 351 -4 1 4.0105 5 340 7 1 4.0114 6 572 -1 2 4.0120 5 346 4 1 4.0123 5 326 -2 3 4.0138 5 322 -3 4 4.0152 5 349 -3 2 4.0160 6 682 1 2 4.0166 5 326 1 4 4.0183 5 312 -1 4 4.0188 6 663 1 2 4.0188 6 400 5 1 4.0193 6 495 3 1 4.0195 5 336 4 1 4.0201 5 320 -4 3 4.0213 5 338 5 1 4.0213 5 337 -1 3 4.0232 5 378 3 2 4.0246 5 326 -4 1 4.0261 6 349 -4 3 4.0280 6 442 1 3 4.0280 6 704 -3 1 4.0286 5 322 3 2 4.0298 5 376 4 1 4.0298 6 448 4 1 4.0327 5 342 2 3 4.0335 5 319 -6 1 4.0341 5 336 -1 4 4.0343 6 349 -1 4 4.0356 6 350 7 1 4.0359 5 319 2 3 4.0366 5 322 -5 1 4.0371 5 340 -3 2 4.0395 6 381 1 4 4.0396 6 424 -1 3 4.0410 5 337 4 3 4.0417 5 378 2 3 4.0417 5 376 1 3 4.0427 5 349 1 3 4.0434 5 338 -2 3 4.0439 6 663 3 1 4.0439 6 479 -5 1 4.0439 6 478 5 1 4.0439 5 410 -1 2 4.0439 5 389 -5 1 4.0443 6 448 -1 3 4.0443 5 376 -1 3 4.0452 5 372 3 2 4.0463 6 521 4 1 4.0478 5 351 -4 3 4.0482 5 322 2 3 4.0492 5 322 -5 3 4.0495 6 381 4 3 4.0497 5 340 5 2 4.0502 6 657 1 3 4.0505 5 351 -1 4 4.0517 5 345 -5 2 4.0524 5 349 2 3 4.0524 6 424 -3 2 4.0529 6 705 -1 2 4.0530 6 385 -1 3 4.0530 6 384 -5 1 4.0536 5 342 -5 1 4.0541 6 359 -5 1 4.0545 6 348 -4 3 4.0557 6 348 -5 1 4.0557 5 370 -1 3 4.0557 5 368 1 3 4.0566 6 403 -3 2 4.0568 5 345 -6 1 4.0568 7 0819 4 1 4.0568 5 370 3 2 4.0568 5 368 -3 2 4.0571 5 378 -2 3 4.0573 6 481 1 3 4.0577 6 394 5 2 4.0585 6 599 -1 3 4.0593 6 350 -6 1 4.0596 6 400 -5 1 4.0597 6 912 0 1 4.0597 5 401 -2 3 4.0597 7 0825 4 1 4.0597 5 358 1 3 4.0597 6 705 -3 1 4.0597 5 371 -1 3 4.0597 5 368 -4 1 4.0597 5 370 4 1 4.0597 5 369 1 3 4.0597 6 423 -1 3 4.0597 6 779 -3 2 4.0597 6 772 -3 2 4.0597 6 817 -1 2 4.0597 6 424 -4 1 4.0597 6 818 -1 2 4.0597 6 778 1 2 4.0597 6 775 1 2 4.0597 6 778 -3 2 4.0597 6 779 1 2 4.0597 5 395 -2 3 4.0597 6 787 1 2 4.0597 6 440 -1 3 4.0604 6 490 3 2 4.0611 6 386 5 2 4.0612 5 326 5 2 4.0624 5 326 3 4 4.0638 5 363 -4 1 4.0641 5 352 4 1 4.0647 6 460 -4 1 4.0669 5 326 5 1 4.0683 5 336 -4 3 4.0685 6 417 4 1 4.0686 6 451 5 1 4.0693 6 648 -4 1 4.0709 5 349 5 2 4.0719 6 381 3 4 4.0721 5 356 -4 1 4.0724 6 377 -4 1 4.0733 5 385 -3 1 4.0739 6 704 4 1 4.0741 5 373 -2 3 4.0743 6 493 -3 2 4.0755 6 488 1 3 4.0761 6 869 -3 1 4.0764 5 336 1 4 4.0777 5 335 -5 1 4.0779 5 351 -3 4 4.0780 5 370 -2 3 4.0780 5 360 3 2 4.0780 6 349 -5 1 4.0801 6 377 3 4 4.0804 6 599 -3 2 4.0810 6 442 3 2 4.0823 5 373 1 3 4.0833 6 805 -2 1 4.0840 5 338 4 3 4.0841 5 335 -4 3 4.0845 6 381 -4 1 4.0846 6 554 2 3 4.0851 6 761 -3 1 4.0854 6 348 -5 2 4.0863 6 912 1 1 4.0865 5 350 2 3 4.0871 5 361 -4 1 4.0879 6 377 4 3 4.0881 6 489 5 1 4.0883 6 359 5 2 4.0887 6 386 6 1 4.0888 6 349 -5 2 4.0895 6 784 -3 1 4.0907 6 451 -3 2 4.0914 5 342 -4 3 4.0931 6 460 4 1 4.0932 5 337 -5 1 4.0933 5 338 5 2 4.0936 6 599 -2 3 4.0945 6 385 -2 3 4.0946 6 663 -3 2 4.0956 5 342 -5 2 4.0959 6 349 1 4 4.0974 6 423 4 3 4.0974 5 358 -4 3 4.0981 5 403 3 2 4.0982 6 667 -1 2 4.1006 5 374 -4 1 4.1009 5 389 3 2 4.1009 6 478 -3 2 4.1009 6 479 3 2 4.1009 5 340 -7 1 4.1014 5 339 1 4 4.1016 6 554 3 2 4.1025 6 719 4 1 4.1042 5 389 -3 2 4.1042 6 478 3 2 4.1042 6 479 -3 2 4.1042 6 403 -6 1 4.1045 6 793 1 2 4.1045 6 554 -3 1 4.1045 6 432 1 3 4.1048 6 438 -5 1 4.1074 6 657 2 3 4.1103 5 337 -2 3 4.1106 6 394 -5 1 4.1108 5 392 -4 1 4.1113 5 338 -5 1 4.1115 5 361 4 1 4.1128 5 378 -4 1 4.1132 6 490 1 3 4.1151 6 359 4 3 4.1152 6 572 3 2 4.1154 6 580 -4 1 4.1168 5 364 -3 2 4.1169 6 566 2 3 4.1169 7 2678 2 1 4.1169 6 500 4 1 4.1169 6 455 3 2 4.1169 6 741 -1 3 4.1169 7 2334 -1 2 4.1169 6 777 -4 1 4.1169 6 490 -4 1 4.1169 7 0819 3 2 4.1169 7 2203 -1 2 4.1169 6 897 -1 2 4.1174 6 417 -5 1 4.1177 5 339 -1 4 4.1201 6 384 5 2 4.1223 5 345 -1 3 4.1224 5 343 3 2 4.1226 5 345 -2 3 4.1231 6 450 -2 3 4.1235 6 448 -2 3 4.1235 5 376 -2 3 4.1244 5 360 -4 1 4.1249 7 0753 6 1 4.1249 7 0822 6 1 4.1285 5 349 -1 3 4.1285 5 350 -5 1 4.1287 5 372 5 1 4.1310 5 345 6 1 4.1310 5 385 -1 3 4.1310 6 385 6 1 4.1331 6 385 -5 2 4.1332 6 385 -6 1 4.1351 5 356 3 2 4.1352 5 346 -1 4 4.1355 5 356 -1 4 4.1356 5 356 2 3 4.1359 6 580 4 1 4.1364 6 417 -4 3 4.1368 6 566 1 3 4.1369 5 403 2 3 4.1369 5 376 2 3 4.1372 6 650 -3 1 4.1375 5 346 1 4 4.1375 6 730 -4 1 4.1381 6 572 -4 1 4.1385 6 704 -1 2 4.1394 5 370 1 3 4.1394 5 368 -1 3 4.1394 7 0785 -5 1 4.1397 6 869 3 1 4.1403 5 356 -4 3 4.1408 6 450 4 1 4.1422 6 488 3 2 4.1435 5 343 4 1 4.1435 6 758 -3 1 4.1458 6 437 3 2 4.1465 6 403 7 1 4.1474 6 394 6 1 4.1482 5 363 3 2 4.1484 5 349 -5 1 4.1485 5 363 2 3 4.1488 5 370 -5 1 4.1488 5 368 5 1 4.1488 6 442 4 1 4.1490 6 547 -4 1 4.1490 6 394 4 3 4.1525 5 364 -2 3 4.1533 5 360 -1 4 4.1543 6 451 1 3 4.1547 6 457 4 1 4.1553 6 469 6 1 4.1564 6 566 -3 1 4.1565 6 489 -5 1 4.1566 7 1315 -4 1 4.1566 5 389 5 1 4.1566 6 882 1 2 4.1566 6 478 -5 1 4.1566 6 479 5 1 4.1579 5 371 -3 2 4.1579 5 369 3 2 4.1579 6 440 -3 2 4.1602 6 417 -5 2 4.1610 5 410 -4 1 4.1621 5 352 -1 4 4.1633 6 554 -1 3 4.1634 6 432 -4 3 4.1634 5 371 -4 1 4.1634 5 369 4 1 4.1639 6 912 2 1 4.1642 6 412 -6 1 4.1642 6 581 -4 1 4.1644 5 349 4 3 4.1645 6 445 -3 2 4.1655 6 442 2 3 4.1655 5 390 4 1 4.1655 6 480 4 1 4.1655 7 0953 4 1 4.1663 6 709 -3 1 4.1666 5 363 -1 4 4.1699 6 480 -2 3 4.1699 6 547 4 1 4.1699 5 390 -2 3 4.1701 6 490 4 1 4.1713 6 645 1 3 4.1713 6 874 3 1 4.1713 6 599 3 1 4.1713 7 2018 -3 1 4.1713 7 1054 4 1 4.1713 6 649 -1 3 4.1713 6 667 1 2 4.1713 7 2643 -1 2 4.1726 7 0753 5 2 4.1727 6 850 -3 1 4.1727 6 915 1 2 4.1727 6 424 -2 3 4.1728 5 349 6 1 4.1736 7 2099 -1 2 4.1736 5 351 3 2 4.1766 6 451 2 3 4.1777 6 595 4 1 4.1791 5 351 2 3 4.1793 5 378 4 1 4.1795 6 463 -1 4 4.1796 6 432 -1 4 4.1802 7 2051 3 1 4.1808 5 373 2 3 4.1808 5 393 4 1 4.1810 5 350 5 1 4.1829 6 463 2 3 4.1840 7 1268 3 2 4.1852 6 457 -4 1 4.1862 6 477 1 3 4.1864 5 356 -3 4 4.1874 6 464 1 3 4.1883 6 480 3 2 4.1883 6 472 4 1 4.1883 6 437 2 3 4.1883 5 390 3 2 4.1885 6 423 1 4 4.1885 5 358 -1 4 4.1896 6 703 -4 1 4.1904 5 364 -4 1 4.1907 5 358 3 2 4.1907 6 423 -3 2 4.1925 6 663 -4 1 4.1938 5 363 -4 3 4.1939 5 395 -4 1 4.1944 6 450 -4 1 4.1950 7 0785 -5 2 4.1950 6 581 -3 2 4.1957 6 533 4 1 4.1991 6 481 -4 1 4.1991 6 424 5 1 4.1992 5 374 4 1 4.1992 6 467 3 2 4.1994 6 667 -5 1 4.1994 5 403 -3 2 4.2004 6 741 -2 3 4.2006 6 512 -4 1 4.2010 6 523 -6 1 4.2018 6 432 3 2 4.2026 5 372 -1 3 4.2034 6 488 -4 1 4.2037 6 437 4 1 4.2037 5 370 -5 2 4.2037 5 368 5 2 4.2044 6 879 1 2 4.2046 5 360 1 4 4.2047 5 358 -3 4 4.2047 6 423 3 4 4.2049 6 723 -3 2 4.2049 5 373 -5 1 4.2056 6 420 -4 3 4.2056 6 514 -5 1 4.2060 7 0753 -6 1 4.2075 6 519 6 1 4.2083 6 528 -5 1 4.2085 6 482 4 1 4.2086 7 0769 5 1 4.2086 6 420 -5 1 4.2093 6 432 -3 4 4.2095 6 445 5 2 4.2096 6 938 -3 1 4.2099 6 502 4 1 4.2099 5 358 -5 2 4.2099 6 423 5 2 4.2106 6 450 1 4 4.2112 5 352 -4 3 4.2115 5 361 -1 4 4.2115 5 374 -1 4 4.2117 6 806 2 3 4.2131 6 489 -3 2 4.2134 6 440 1 4 4.2134 5 371 1 4 4.2134 5 369 -1 4 4.2144 6 451 5 2 4.2147 6 490 2 3 4.2147 5 372 -6 1 4.2156 6 498 4 1 4.2172 7 0785 -4 3 4.2172 6 781 -3 2 4.2172 6 702 1 3 4.2172 6 786 1 2 4.2172 6 445 -1 3 4.2179 6 451 -5 1 4.2203 5 364 4 3 4.2218 6 668 4 1 4.2221 6 528 5 1 4.2221 5 392 3 2 4.2222 6 801 -4 1 4.2227 6 451 -1 3 4.2233 5 360 4 1 4.2240 6 423 -4 1 4.2240 5 358 4 1 4.2242 5 372 -5 2 4.2248 6 527 -4 1 4.2250 5 393 4 3 4.2262 6 528 -3 2 4.2263 6 464 5 1 4.2276 6 457 1 4 4.2277 5 371 -2 3 4.2277 5 369 2 3 4.2277 6 440 -2 3 4.2285 7 0960 -5 1 4.2291 6 648 5 1 4.2291 6 649 -2 3 4.2291 6 645 2 3 4.2291 6 646 2 3 4.2291 6 527 -3 2 4.2294 6 786 -3 2 4.2294 6 773 -3 2 4.2294 6 718 -4 1 4.2294 6 442 -5 1 4.2294 6 432 -5 1 4.2302 6 682 3 1 4.2306 6 480 -5 1 4.2306 7 0953 -5 1 4.2306 5 390 -5 1 4.2306 7 0825 2 3 4.2306 5 370 2 3 4.2306 5 368 -2 3 4.2315 5 364 1 4 4.2323 5 376 -5 1 4.2323 6 448 -5 1 4.2325 5 358 -5 1 4.2325 6 423 5 1 4.2325 6 882 3 1 4.2325 5 370 -4 3 4.2325 5 369 -5 1 4.2325 5 371 5 1 4.2325 5 368 4 3 4.2325 6 424 4 3 4.2328 5 372 1 3 4.2328 5 373 5 1 4.2333 6 458 -1 4 4.2334 5 372 -2 3 4.2334 5 356 4 1 4.2337 6 471 -4 1 4.2340 5 358 2 3 4.2340 6 423 -2 3 4.2341 6 440 4 3 4.2341 5 369 -4 3 4.2341 5 371 4 3 4.2346 6 437 -1 4 4.2348 6 488 2 3 4.2384 5 363 4 1 4.2396 5 374 1 4 4.2405 5 410 3 2 4.2405 6 495 1 3 4.2416 6 467 2 3 4.2421 6 702 -3 1 4.2421 5 385 1 4 4.2421 7 2101 1 2 4.2421 5 361 1 4 4.2425 6 488 4 1 4.2438 6 481 -1 4 4.2438 6 493 1 3 4.2448 6 615 4 1 4.2448 6 481 3 2 4.2453 6 464 2 3 4.2460 5 360 -4 3 4.2465 6 566 4 1 4.2467 6 424 1 4 4.2468 6 468 1 4 4.2483 6 432 -5 2 4.2485 5 363 -3 4 4.2486 6 513 -4 1 4.2495 5 395 3 2 4.2502 7 0959 5 1 4.2506 6 885 3 1 4.2506 6 502 -2 3 4.2507 7 1313 -4 1 4.2508 6 509 4 1 4.2512 5 393 3 4 4.2525 6 784 -1 2 4.2531 6 437 -5 1 4.2532 6 705 4 1 4.2543 5 393 1 4 4.2560 6 458 3 2 4.2560 6 777 4 1 4.2560 6 437 -5 2 4.2571 6 513 -4 3 4.2580 7 1148 -4 1 4.2586 6 763 -1 3 4.2595 6 595 3 2 4.2596 5 358 -5 3 4.2596 6 480 1 3 4.2596 6 518 4 1 4.2596 5 390 1 3 4.2596 6 423 5 3 4.2599 6 574 -2 3 4.2599 6 502 -4 1 4.2607 6 445 -5 1 4.2627 7 1534 4 1 4.2627 6 484 4 1 4.2628 5 364 5 1 4.2630 5 363 1 4 4.2636 6 481 2 3 4.2655 6 459 5 1 4.2656 7 1030 4 1 4.2658 6 467 4 1 4.2659 6 755 3 1 4.2665 5 378 1 4 4.2677 6 493 -1 3 4.2680 6 458 -3 4 4.2680 7 0940 -5 2 4.2680 6 469 5 2 4.2680 6 639 1 3 4.2680 6 764 -1 3 4.2680 7 1935 3 1 4.2680 7 1132 4 1 4.2680 7 2203 1 2 4.2680 6 897 1 2 4.2684 6 498 -4 1 4.2690 5 376 5 1 4.2690 6 448 5 1 4.2698 6 479 -6 1 4.2698 6 478 6 1 4.2698 5 389 -6 1 4.2703 6 457 -1 4 4.2708 7 0819 5 1 4.2709 5 402 -4 1 4.2710 6 495 -4 3 4.2714 6 455 2 3 4.2714 5 385 3 4 4.2718 6 691 -3 2 4.2726 6 513 -3 4 4.2727 6 745 -4 1 4.2742 6 451 6 1 4.2749 5 378 -1 4 4.2751 6 493 5 1 4.2754 6 552 -4 1 4.2760 5 374 -4 3 4.2766 5 364 5 2 4.2769 5 392 4 1 4.2778 6 763 -2 3 4.2779 7 1268 4 1 4.2786 6 652 -4 1 4.2809 6 704 3 2 4.2815 7 1030 3 2 4.2819 6 455 -5 2 4.2819 6 615 -3 2 4.2832 7 0953 -5 2 4.2832 6 480 -5 2 4.2832 7 0819 2 3 4.2832 5 390 -5 2 4.2834 6 493 -5 1 4.2866 5 372 2 3 4.2870 5 397 4 1 4.2871 7 1065 4 3 4.2874 6 463 1 4 4.2879 6 702 3 2 4.2881 7 1261 4 1 4.2883 6 682 -4 1 4.2886 6 912 3 1 4.2894 6 644 -1 3 4.2894 6 455 5 1 4.2896 6 450 4 3 4.2901 7 1030 -5 1 4.2909 5 400 -3 2 4.2909 6 489 6 1 4.2924 6 493 2 3 4.2948 6 451 -2 3 4.2949 5 401 -4 1 4.2952 6 525 4 1 4.2962 6 482 2 3 4.2963 6 513 -1 4 4.2967 6 518 -4 1 4.2988 6 784 4 1 4.3017 6 459 4 3 4.3017 5 410 5 1 4.3020 6 652 3 2 4.3027 5 392 2 3 4.3029 6 746 -3 2 4.3039 6 820 3 1 4.3042 5 403 4 1 4.3042 6 450 3 4 4.3042 6 764 -3 1 4.3045 6 836 -4 1 4.3057 7 1431 3 1 4.3062 5 400 4 1 4.3062 6 648 -5 1 4.3062 6 921 -3 1 4.3072 5 392 -4 3 4.3074 6 543 -1 3 4.3083 6 468 3 4 4.3090 6 540 2 3 4.3094 6 460 -5 1 4.3097 5 397 1 4 4.3097 6 488 -1 4 4.3102 5 385 -3 2 4.3106 6 468 -3 2 4.3114 6 535 1 3 4.3123 6 527 -1 3 4.3135 6 573 4 1 4.3135 6 572 5 1 4.3137 6 688 4 1 4.3137 5 385 5 2 4.3141 6 472 4 3 4.3146 6 469 7 1 4.3156 6 517 2 3 4.3158 6 682 -1 3 4.3162 6 639 2 3 4.3162 5 390 -4 3 4.3162 7 0953 -4 3 4.3162 6 480 -4 3 4.3166 6 644 -2 3 4.3169 6 667 -3 2 4.3174 6 468 -2 3 4.3175 6 467 -1 4 4.3175 6 650 4 1 4.3176 6 854 -4 1 4.3181 6 489 5 2 4.3184 6 512 5 1 4.3185 6 459 5 2 4.3187 6 460 -1 4 4.3187 6 691 -4 1 4.3196 6 529 4 1 4.3203 5 395 2 3 4.3210 7 1030 1 3 4.3220 7 1189 -5 1 4.3220 6 961 1 2 4.3220 5 400 -2 3 4.3220 6 755 -3 2 4.3221 6 525 -4 1 4.3225 6 482 -5 1 4.3228 7 1189 5 1 4.3228 6 467 -5 1 4.3243 6 471 1 4 4.3245 6 599 1 3 4.3246 6 541 4 1 4.3255 6 554 4 1 4.3257 5 395 4 1 4.3264 7 1316 4 1 4.3265 6 534 4 1 4.3268 6 533 -4 1 4.3286 6 467 -5 2 4.3291 6 667 4 1 4.3292 6 942 -3 1 4.3297 6 535 4 1 4.3297 6 847 3 1 4.3300 6 648 1 2 4.3301 6 481 -4 3 4.3303 5 385 -2 3 4.3305 6 493 -2 3 4.3311 5 401 3 2 4.3311 6 682 -2 3 4.3312 5 392 -1 4 4.3327 6 561 -4 1 4.3329 6 460 5 1 4.3333 6 615 3 2 4.3335 6 495 3 2 4.3337 5 403 -4 1 4.3337 6 477 -1 4 4.3346 5 389 6 1 4.3346 6 478 -6 1 4.3346 6 479 6 1 4.3360 6 554 1 4 4.3360 7 2656 3 1 4.3378 5 397 -2 3 4.3384 7 2051 -1 2 4.3390 6 472 -4 1 4.3392 6 783 -4 1 4.3393 6 481 4 1 4.3408 6 730 5 1 4.3408 6 657 -3 1 4.3415 6 489 -6 1 4.3418 7 1054 -4 1 4.3423 6 677 -1 3 4.3427 6 783 5 1 4.3430 5 402 -4 3 4.3430 5 389 -5 2 4.3430 6 478 5 2 4.3430 6 479 -5 2 4.3430 7 0940 -7 1 4.3438 5 400 1 4 4.3445 6 495 4 1 4.3456 6 496 3 2 4.3461 6 529 2 3 4.3462 6 495 -1 4 4.3463 6 481 -3 4 4.3474 6 497 1 4 4.3476 6 764 3 1 4.3482 6 552 4 1 4.3490 6 468 5 2 4.3504 6 517 4 1 4.3506 5 385 5 3 4.3507 6 546 -4 1 4.3511 6 820 1 3 4.3515 6 723 -1 3 4.3521 6 556 -4 1 4.3523 6 477 -3 4 4.3525 5 402 -1 4 4.3527 6 540 4 1 4.3533 5 390 2 3 4.3533 6 477 2 3 4.3533 6 480 2 3 4.3533 6 567 -4 1 4.3550 6 471 4 3 4.3551 6 482 -5 2 4.3575 6 827 1 3 4.3579 6 512 -2 3 4.3580 6 566 -1 3 4.3580 6 498 1 4 4.3583 6 556 -3 2 4.3588 6 474 1 4 4.3593 6 495 -3 4 4.3605 6 527 5 1 4.3607 6 746 -4 1 4.3613 6 806 3 2 4.3616 6 591 -1 3 4.3619 6 496 -1 4 4.3621 5 401 -1 4 4.3670 6 489 1 3 4.3671 6 495 -5 2 4.3676 5 393 -2 3 4.3677 6 495 -5 1 4.3678 6 657 4 1 4.3693 6 820 3 2 4.3693 6 702 2 3 4.3695 6 649 -4 1 4.3695 6 645 4 1 4.3695 6 646 4 1 4.3699 6 730 -1 2 4.3699 6 566 4 3 4.3701 6 496 4 1 4.3701 6 543 4 3 4.3703 6 496 -5 1 4.3712 6 534 -4 1 4.3715 6 481 1 4 4.3732 6 645 -3 1 4.3732 6 646 -3 1 4.3732 6 649 3 1 4.3740 6 474 4 3 4.3743 5 395 -1 4 4.3745 6 490 -5 1 4.3749 6 478 -1 3 4.3749 6 479 1 3 4.3749 6 699 3 2 4.3749 5 389 1 3 4.3749 6 698 3 2 4.3749 6 519 7 1 4.3749 6 719 5 1 4.3753 6 535 -4 3 4.3759 6 489 2 3 4.3760 6 554 -3 2 4.3760 6 500 5 1 4.3762 6 547 -2 3 4.3769 5 392 -3 4 4.3780 6 572 -3 2 4.3781 7 1181 -5 1 4.3786 6 719 -5 1 4.3786 6 530 4 1 4.3790 6 541 -4 1 4.3791 6 668 -4 1 4.3801 6 507 4 3 4.3804 6 726 5 1 4.3808 6 512 5 2 4.3814 6 556 -1 3 4.3835 6 560 4 1 4.3859 6 729 3 1 4.3864 6 541 1 4 4.3867 6 497 -2 3 4.3867 6 773 -4 1 4.3867 6 786 -4 1 4.3867 6 673 -4 1 4.3867 6 477 -5 3 4.3870 5 397 3 4 4.3874 7 1189 -3 2 4.3879 6 879 -3 1 4.3885 5 402 -3 4 4.3887 6 698 -3 2 4.3892 6 751 -4 1 4.3892 6 556 5 1 4.3906 6 805 2 3 4.3911 6 820 2 3 4.3913 6 502 1 4 4.3914 7 2099 3 1 4.3923 6 554 -2 3 4.3924 6 877 -3 1 4.3927 6 508 -1 4 4.3928 6 543 -3 2 4.3952 7 0984 -5 1 4.3959 6 650 2 3 4.3961 5 397 4 3 4.3962 7 1054 1 4 4.3966 6 594 -3 4 4.3966 7 2050 4 1 4.3966 6 594 -4 3 4.3966 6 855 3 2 4.3969 6 499 -5 1 4.3976 6 495 2 3 4.3981 6 698 4 1 4.3981 6 699 4 1 4.3983 6 626 -5 1 4.3983 6 496 2 3 4.3984 6 557 4 1 4.3990 6 640 -2 3 4.3995 6 500 -5 1 4.3998 6 539 -4 1 4.4000 6 493 5 2 4.4003 5 397 -4 1 4.4008 6 777 1 2 4.4008 7 1012 -1 4 4.4008 7 0974 1 4 4.4008 6 517 -1 4 4.4009 6 518 -1 4 4.4009 6 530 -1 4 4.4014 6 546 5 1 4.4016 6 723 -2 3 4.4031 6 594 1 3 4.4031 6 958 1 2 4.4031 6 594 3 1 4.4033 5 395 -4 3 4.4035 6 560 -5 1 4.4038 6 505 -4 3 4.4052 6 528 -6 1 4.4056 6 572 -5 1 4.4060 6 535 -5 1 4.4064 6 527 -2 3 4.4070 6 512 4 3 4.4073 7 2817 -3 1 4.4073 6 704 -4 1 4.4080 6 555 -4 1 4.4081 6 580 -5 1 4.4081 6 581 -1 3 4.4085 7 1171 -4 1 4.4094 6 528 6 1 4.4096 5 410 -3 2 4.4106 6 543 -4 1 4.4112 5 400 -4 1 4.4118 6 513 3 2 4.4119 6 529 -5 1 4.4120 7 1065 -4 1 4.4120 6 514 -6 1 4.4123 6 498 4 3 4.4125 7 1268 -4 1 4.4133 5 403 1 4 4.4137 6 618 -4 1 4.4151 7 1065 3 4 4.4153 6 869 -1 2 4.4153 6 861 3 1 4.4153 7 2101 -1 3 4.4153 6 775 3 1 4.4153 7 3210 -2 1 4.4153 6 779 3 1 4.4153 7 2380 3 1 4.4153 6 649 1 3 4.4153 6 772 3 1 4.4153 6 787 3 1 4.4153 7 2209 3 1 4.4153 6 778 -4 1 4.4153 7 2910 1 2 4.4153 6 645 -1 3 4.4153 6 646 -1 3 4.4153 6 779 -4 1 4.4153 6 775 -4 1 4.4153 6 778 3 1 4.4153 6 772 -4 1 4.4153 6 773 3 1 4.4153 6 786 3 1 4.4153 6 781 -4 1 4.4153 7 2642 3 1 4.4153 7 2641 3 1 4.4153 6 819 -4 1 4.4153 7 1125 -4 3 4.4156 5 410 -5 1 4.4161 6 650 -1 3 4.4161 6 543 5 2 4.4162 6 597 4 1 4.4163 6 518 1 4 4.4165 6 544 -2 3 4.4181 6 599 -4 1 4.4183 6 543 5 1 4.4184 6 509 1 4 4.4191 6 593 4 1 4.4191 7 1191 -5 1 4.4191 7 1076 -5 1 4.4194 6 618 4 1 4.4198 6 544 4 3 4.4204 6 571 -4 3 4.4206 6 504 4 3 4.4208 6 580 5 1 4.4213 6 502 4 3 4.4215 7 1190 4 1 4.4219 6 540 -1 4 4.4221 6 648 3 2 4.4224 5 402 3 2 4.4224 6 539 5 1 4.4226 6 533 1 4 4.4229 5 400 4 3 4.4234 6 527 5 2 4.4236 6 513 2 3 4.4237 7 1030 2 3 4.4238 6 507 3 4 4.4240 6 581 1 3 4.4244 6 626 3 2 4.4245 6 827 -3 1 4.4247 6 755 -1 3 4.4252 6 677 -2 3 4.4254 6 764 1 3 4.4257 6 544 -4 1 4.4261 7 1065 5 2 4.4263 6 547 -5 1 4.4272 6 813 1 3 4.4274 6 610 -4 1 4.4274 6 525 1 4 4.4276 6 819 -3 2 4.4276 6 508 3 4 4.4281 6 612 4 3 4.4284 6 835 1 3 4.4284 6 535 -5 2 4.4295 6 663 -1 3 4.4299 6 847 -3 1 4.4302 5 401 -4 3 4.4303 7 1065 1 4 4.4315 6 527 4 3 4.4317 6 528 1 3 4.4320 5 400 3 4 4.4327 6 613 -4 1 4.4328 7 1181 5 1 4.4329 6 535 2 3 4.4332 6 699 -4 1 4.4332 6 698 -4 1 4.4334 6 547 5 1 4.4335 6 764 -2 3 4.4351 6 704 5 1 4.4353 6 648 -1 2 4.4355 6 730 1 2 4.4367 5 402 2 3 4.4368 6 574 -4 1 4.4378 7 1161 -4 1 4.4382 6 648 6 1 4.4383 5 401 4 1 4.4389 6 566 -3 2 4.4390 6 567 4 1 4.4396 6 536 5 1 4.4406 7 1054 -1 4 4.4419 6 663 4 1 4.4424 6 690 -4 1 4.4427 7 1065 5 1 4.4428 6 847 1 3 4.4431 6 546 -2 3 4.4433 5 401 1 4 4.4434 6 528 5 2 4.4434 6 663 -2 3 4.4435 6 610 5 1 4.4447 6 673 -3 2 4.4447 6 818 4 1 4.4449 6 691 3 2 4.4452 6 529 -5 2 4.4454 6 539 -2 3 4.4464 6 826 3 1 4.4467 7 1125 4 1 4.4468 6 604 -4 1 4.4476 6 615 -5 1 4.4484 6 554 -4 1 4.4485 6 581 -5 1 4.4486 6 599 3 2 4.4493 7 1054 5 1 4.4498 6 580 -1 3 4.4502 6 652 5 1 4.4505 6 730 -5 1 4.4505 6 573 -5 1 4.4509 6 813 2 3 4.4513 7 1132 -5 1 4.4521 5 400 -1 4 4.4539 6 912 4 1 4.4539 6 709 1 3 4.4539 7 2643 -3 1 4.4541 6 745 4 1 4.4541 6 879 4 1 4.4541 6 878 -3 1 4.4543 6 594 -4 1 4.4543 6 702 -1 3 4.4543 6 594 -1 4 4.4544 7 2200 -3 2 4.4544 7 1398 4 1 4.4544 7 2643 1 2 4.4557 6 818 3 2 4.4557 6 718 -3 2 4.4557 6 650 -3 2 4.4557 6 572 1 3 4.4559 6 528 -1 3 4.4559 6 527 -5 1 4.4563 6 591 1 4 4.4585 6 612 3 4 4.4586 6 541 -1 4 4.4592 6 528 2 3 4.4595 6 580 1 3 4.4595 6 581 5 1 4.4595 6 533 5 1 4.4596 7 2380 -1 2 4.4596 6 686 4 1 4.4603 6 582 4 1 4.4623 6 718 5 1 4.4623 7 1315 5 1 4.4623 6 533 4 3 4.4626 6 845 -3 1 4.4628 6 928 -3 1 4.4629 7 1268 1 3 4.4630 6 850 -3 2 4.4632 6 543 -2 3 4.4632 7 1125 -1 4 4.4642 6 741 3 1 4.4643 7 1534 -4 1 4.4644 5 403 -1 4 4.4646 6 881 -1 3 4.4646 6 595 5 1 4.4646 6 915 3 1 4.4646 6 781 -2 3 4.4646 6 786 -1 3 4.4646 6 773 -1 3 4.4646 6 777 -5 1 4.4646 6 734 4 1 4.4646 6 915 -3 1 4.4646 6 595 1 3 4.4646 7 2787 -1 2 4.4646 6 924 3 1 4.4646 7 1408 4 1 4.4646 7 2787 1 2 4.4648 6 581 -2 3 4.4654 7 2585 1 2 4.4664 7 1065 5 3 4.4666 5 401 -3 4 4.4667 6 626 5 1 4.4668 6 784 -4 1 4.4671 6 719 1 2 4.4672 7 1315 -5 1 4.4678 6 871 3 2 4.4682 6 691 4 1 4.4691 6 572 2 3 4.4709 7 1171 -4 3 4.4713 7 1676 -4 1 4.4713 6 536 5 2 4.4716 6 698 -1 3 4.4716 6 699 -1 3 4.4719 6 539 1 4 4.4732 6 764 -3 2 4.4741 6 574 4 1 4.4746 6 702 4 1 4.4753 6 708 3 2 4.4755 7 1538 -4 1 4.4755 7 2018 3 1 4.4755 6 874 -3 1 4.4769 6 777 -1 2 4.4769 6 574 -1 4 4.4770 6 574 1 4 4.4773 7 1190 -4 1 4.4786 6 691 -1 3 4.4788 6 544 3 4 4.4789 6 552 1 4 4.4790 7 1262 -1 3 4.4791 6 538 -4 3 4.4795 6 554 -1 4 4.4797 6 530 -4 3 4.4810 6 832 -3 1 4.4828 6 841 4 1 4.4832 6 627 4 1 4.4838 6 849 -3 1 4.4842 6 686 3 2 4.4844 6 667 -6 1 4.4844 6 786 -2 3 4.4844 6 781 -1 3 4.4844 6 773 -2 3 4.4845 7 1143 4 3 4.4846 6 615 -1 3 4.4847 6 566 -4 1 4.4848 7 3246 1 2 4.4848 7 2871 -3 1 4.4853 6 690 3 1 4.4853 6 543 1 4 4.4855 6 686 -1 3 4.4857 6 612 1 4 4.4863 6 896 1 3 4.4885 6 648 -6 1 4.4893 6 884 1 3 4.4898 6 581 2 3 4.4898 6 850 3 1 4.4912 6 699 1 3 4.4912 6 698 1 3 4.4913 6 784 3 2 4.4915 6 556 -2 3 4.4929 6 572 5 2 4.4934 6 839 -3 1 4.4936 7 1125 -3 4 4.4938 6 544 5 1 4.4944 6 615 1 3 4.4950 6 939 -3 1 4.4954 6 705 -4 1 4.4960 7 1161 5 1 4.4963 6 734 -4 1 4.4964 6 539 5 3 4.4968 6 723 -4 1 4.4972 6 676 2 3 4.4975 6 703 -5 1 4.4976 6 751 5 1 4.4976 6 633 2 3 4.4979 7 2585 3 1 4.4982 6 555 4 3 4.4986 7 1859 -3 1 4.4986 7 1859 1 3 4.4986 7 1859 3 1 4.4989 6 587 4 3 4.5002 6 862 4 1 4.5006 6 599 2 3 4.5012 6 580 -2 3 4.5014 6 730 -3 2 4.5016 6 798 1 3 4.5016 6 599 -1 4 4.5020 6 719 -3 2 4.5026 7 1143 1 4 4.5026 6 641 -4 1 4.5028 6 543 5 3 4.5028 7 1730 1 2 4.5032 6 942 3 1 4.5033 6 877 -1 3 4.5036 6 835 -3 1 4.5036 6 858 3 1 4.5038 6 573 2 3 4.5038 6 566 5 2 4.5039 6 755 -2 3 4.5040 6 722 -5 1 4.5040 6 673 1 3 4.5042 6 561 1 4 4.5047 6 591 3 4 4.5058 6 611 4 3 4.5058 7 1268 2 3 4.5058 7 1315 -3 2 4.5058 6 802 4 1 4.5066 6 615 5 1 4.5072 7 1132 5 1 4.5075 6 566 -2 3 4.5082 5 410 6 1 4.5091 7 2623 3 1 4.5091 6 741 -4 1 4.5092 7 1148 -4 3 4.5094 6 686 -2 3 4.5095 6 566 3 4 4.5096 6 639 -4 1 4.5105 6 572 6 1 4.5108 6 861 3 2 4.5108 7 1368 5 1 4.5108 6 718 -5 1 4.5120 6 650 -4 1 4.5120 6 751 3 2 4.5123 6 801 -5 1 4.5123 6 691 1 3 4.5124 6 860 -4 1 4.5129 6 593 -5 1 4.5133 6 657 -1 3 4.5139 6 571 -3 4 4.5141 6 566 1 4 4.5152 6 676 -4 1 4.5154 6 723 1 3 4.5161 6 640 4 1 4.5165 6 827 2 3 4.5165 6 734 -1 3 4.5168 6 571 4 1 4.5169 6 640 -5 1 4.5169 6 639 -1 3 4.5169 6 677 1 3 4.5169 6 676 5 1 4.5171 6 580 2 3 4.5179 6 663 -5 1 4.5184 6 870 4 1 4.5186 6 690 1 3 4.5186 6 705 1 3 4.5188 7 1189 6 1 4.5195 6 645 4 3 4.5195 6 646 4 3 4.5195 6 649 -4 3 4.5196 7 1316 -4 1 4.5201 6 689 4 1 4.5208 6 753 4 1 4.5208 6 734 1 3 4.5209 6 801 5 1 4.5209 6 718 3 2 4.5212 6 561 5 1 4.5214 6 573 -5 2 4.5216 6 644 4 1 4.5216 6 616 -2 3 4.5216 6 566 5 1 4.5225 7 1191 -5 2 4.5227 6 761 -4 1 4.5227 6 595 2 3 4.5246 6 705 2 3 4.5248 6 593 -4 3 4.5254 6 666 4 1 4.5282 6 729 1 3 4.5283 6 813 -3 1 4.5285 7 1189 -6 1 4.5294 6 846 -3 1 4.5294 6 643 -4 3 4.5303 6 753 -1 3 4.5304 6 855 1 3 4.5304 7 1183 3 4 4.5307 6 571 -5 2 4.5313 6 703 3 2 4.5320 6 783 -1 2 4.5327 6 873 3 1 4.5329 7 1148 -5 1 4.5331 7 1534 3 2 4.5331 7 1935 -4 1 4.5333 6 758 4 1 4.5333 6 663 3 2 4.5334 6 573 -4 3 4.5343 7 1181 -5 2 4.5343 6 640 3 2 4.5346 7 1227 -4 1 4.5352 6 572 -1 3 4.5354 6 746 4 1 4.5359 7 1777 4 1 4.5359 7 1181 1 3 4.5361 6 763 3 1 4.5365 6 613 -1 4 4.5367 6 640 -5 2 4.5372 7 1189 5 2 4.5380 7 1190 1 4 4.5384 6 599 4 1 4.5386 7 2899 -1 2 4.5401 6 667 3 2 4.5403 6 703 5 1 4.5403 6 755 1 3 4.5405 6 652 -5 1 4.5406 6 668 5 1 4.5407 6 690 -1 4 4.5407 6 801 -1 2 4.5411 6 668 2 3 4.5415 6 820 -3 1 4.5428 6 587 -4 1 4.5429 6 571 -5 1 4.5432 6 677 3 2 4.5440 6 691 -2 3 4.5441 6 855 2 3 4.5442 6 615 -2 3 4.5442 5 410 5 2 4.5448 6 644 1 3 4.5451 6 891 -2 3 4.5452 7 2203 3 1 4.5452 6 593 2 3 4.5452 7 2202 -3 1 4.5452 6 854 -1 2 4.5452 6 897 3 1 4.5457 6 566 5 3 4.5457 6 903 3 1 4.5457 6 665 -4 1 4.5461 6 573 5 1 4.5461 6 640 1 3 4.5461 6 639 5 1 4.5464 6 643 3 2 4.5469 7 1534 -3 2 4.5471 6 639 -3 2 4.5472 6 673 2 3 4.5482 6 636 4 1 4.5487 6 640 -4 3 4.5488 6 582 -5 1 4.5488 6 599 -4 3 4.5492 6 827 3 2 4.5500 6 861 2 3 4.5502 6 593 -5 2 4.5515 7 1198 -4 1 4.5516 7 1143 3 4 4.5516 6 643 4 1 4.5520 7 1470 -1 3 4.5520 6 753 1 3 4.5521 7 1373 -1 3 4.5525 6 604 -1 4 4.5529 6 885 3 2 4.5532 6 823 -5 1 4.5532 6 650 -2 3 4.5534 5 410 1 3 4.5536 6 755 -4 1 4.5540 6 667 5 1 4.5559 7 2641 -4 1 4.5559 7 2642 -4 1 4.5559 6 745 3 2 4.5559 6 775 -1 3 4.5559 7 2553 -4 1 4.5559 6 778 -2 3 4.5559 6 779 -1 3 4.5559 6 907 -3 2 4.5559 6 772 -1 3 4.5559 6 787 -1 3 4.5559 6 659 -4 1 4.5559 7 2874 3 1 4.5559 6 806 -3 1 4.5559 6 775 -2 3 4.5559 6 779 -2 3 4.5559 6 772 -2 3 4.5559 6 778 -1 3 4.5559 6 787 -2 3 4.5559 7 3077 3 1 4.5561 6 722 3 2 4.5563 7 1148 -5 2 4.5571 6 879 3 2 4.5577 6 896 3 1 4.5579 6 839 4 1 4.5582 6 869 -4 1 4.5590 6 626 -1 3 4.5595 6 587 3 4 4.5601 6 657 4 3 4.5603 6 667 -5 2 4.5605 6 723 3 2 4.5609 6 721 -2 3 4.5615 7 1276 1 3 4.5615 6 673 -1 3 4.5619 6 709 -4 1 4.5623 7 1183 -4 1 4.5623 6 705 -3 2 4.5624 6 823 5 1 4.5627 7 1181 -6 1 4.5630 6 615 2 3 4.5633 6 688 5 1 4.5638 6 613 -4 3 4.5638 6 633 -1 4 4.5641 6 835 3 2 4.5652 6 746 3 2 4.5658 6 862 -5 1 4.5658 6 600 4 1 4.5661 6 599 -3 4 4.5665 6 699 -2 3 4.5665 6 698 -2 3 4.5666 6 708 5 1 4.5666 6 709 -3 2 4.5666 7 1171 4 1 4.5667 6 679 4 1 4.5673 6 652 1 3 4.5679 6 613 4 1 4.5685 6 644 3 2 4.5697 7 1313 -5 1 4.5698 6 688 3 2 4.5703 7 1184 -4 1 4.5709 6 604 1 4 4.5710 6 702 -3 2 4.5724 6 657 1 4 4.5724 7 1262 -3 2 4.5726 6 750 -4 1 4.5726 6 611 -2 3 4.5732 6 879 -1 2 4.5736 6 732 -3 2 4.5737 7 1249 -5 1 4.5737 7 2678 1 2 4.5741 6 793 3 1 4.5748 6 626 -2 3 4.5752 6 572 -6 1 4.5753 6 764 3 2 4.5756 7 1191 5 1 4.5756 6 704 -3 2 4.5759 5 410 2 3 4.5760 6 644 -4 3 4.5760 6 643 -5 1 4.5760 6 733 -5 1 4.5760 6 732 -1 3 4.5761 6 701 4 1 4.5763 6 676 5 2 4.5763 6 611 1 4 4.5765 6 686 1 3 4.5767 6 626 -5 2 4.5782 6 763 1 3 4.5782 7 1268 5 1 4.5783 6 751 -3 2 4.5784 6 616 1 4 4.5785 6 719 6 1 4.5789 6 682 1 3 4.5789 6 682 4 1 4.5790 7 1313 -2 3 4.5791 6 755 3 2 4.5792 6 642 -4 3 4.5793 6 746 -1 3 4.5793 6 597 1 4 4.5800 6 863 -1 3 4.5804 7 1183 -1 4 4.5805 6 726 6 1 4.5808 6 591 5 2 4.5808 6 616 4 3 4.5810 7 1498 1 3 4.5811 7 1191 -4 3 4.5817 6 719 -6 1 4.5820 6 709 -1 3 4.5822 6 840 -3 1 4.5823 6 686 -5 1 4.5824 6 705 5 1 4.5825 6 600 -4 3 4.5825 6 643 -5 2 4.5829 6 698 2 3 4.5829 6 699 2 3 4.5834 6 611 5 1 4.5834 6 730 3 2 4.5835 6 648 -3 2 4.5840 6 652 -1 3 4.5841 5 410 -6 1 4.5847 7 1189 1 3 4.5851 6 690 -4 3 4.5853 6 645 1 4 4.5853 6 646 1 4 4.5853 6 659 4 1 4.5853 6 649 -1 4 4.5859 6 722 5 1 4.5864 6 783 -5 1 4.5869 6 648 7 1 4.5870 6 597 -1 4 4.5870 6 663 1 3 4.5875 7 1249 5 1 4.5881 7 1281 5 1 4.5882 6 591 -2 3 4.5884 6 847 -1 3 4.5887 6 730 6 1 4.5893 6 783 6 1 4.5894 6 642 -3 4 4.5895 6 905 -3 1 4.5898 6 611 5 2 4.5900 6 644 -5 2 4.5906 6 641 4 1 4.5909 6 671 4 1 4.5919 6 639 4 3 4.5919 7 1732 -4 1 4.5920 7 1190 4 3 4.5921 6 777 3 2 4.5925 7 1261 5 1 4.5928 7 2086 1 2 4.5943 6 805 4 1 4.5943 7 1189 2 3 4.5946 6 726 3 2 4.5949 6 626 -6 1 4.5958 6 761 5 1 4.5960 5 410 -1 3 4.5966 6 633 4 1 4.5971 6 844 4 1 4.5971 6 677 -5 1 4.5971 6 674 -4 1 4.5980 7 1461 1 3 4.5988 7 2274 -1 2 4.5988 6 636 -1 4 4.5988 7 2274 1 2 4.5988 6 618 1 4 4.6003 6 928 -1 2 4.6003 6 720 -4 1 4.6006 6 611 3 4 4.6012 6 657 3 4 4.6021 6 784 5 1 4.6029 6 725 -1 4 4.6034 6 627 -1 4 4.6035 6 682 -4 3 4.6037 6 707 -2 3 4.6042 7 1228 -5 1 4.6042 6 798 3 2 4.6044 6 663 -5 2 4.6045 6 591 5 3 4.6045 6 688 -5 2 4.6046 6 885 1 3 4.6055 7 2876 -1 2 4.6058 6 682 -5 1 4.6064 6 594 3 2 4.6064 6 594 2 3 4.6064 6 758 -4 1 4.6071 6 639 5 2 4.6073 6 745 -5 1 4.6080 6 649 3 2 4.6080 6 645 -3 2 4.6080 6 646 -3 2 4.6082 6 731 -4 1 4.6086 6 678 4 1 4.6086 6 821 4 1 4.6086 6 734 2 3 4.6089 6 652 2 3 4.6098 6 826 1 3 4.6102 7 1262 -4 1 4.6108 6 682 3 2 4.6108 6 691 2 3 4.6108 6 597 -5 1 4.6110 6 884 2 3 4.6110 6 734 -2 3 4.6110 7 2517 1 2 4.6115 6 650 5 1 4.6116 7 1277 -1 4 4.6116 6 612 -2 3 4.6117 6 764 2 3 4.6118 6 613 1 4 4.6119 6 648 5 2 4.6120 6 840 -1 3 4.6124 6 835 2 3 4.6132 6 702 -4 1 4.6133 6 869 4 1 4.6142 6 613 -3 4 4.6144 6 758 -3 2 4.6148 6 688 -6 1 4.6150 7 1373 4 3 4.6151 6 626 1 3 4.6166 6 958 3 1 4.6171 7 1226 -4 1 4.6174 6 690 -3 4 4.6176 6 899 -4 1 4.6180 6 615 -5 2 4.6181 6 717 -4 1 4.6183 7 2086 -1 2 4.6187 6 850 -1 3 4.6188 6 872 3 1 4.6199 7 1534 5 1 4.6205 7 1228 5 2 4.6213 7 1219 5 1 4.6217 7 1374 5 1 4.6219 7 2623 -4 1 4.6222 6 746 -2 3 4.6229 6 691 -5 1 4.6231 7 1367 -1 4 4.6231 7 1448 4 1 4.6241 7 1348 5 1 4.6241 6 758 -1 3 4.6241 6 846 -1 3 4.6242 6 741 1 3 4.6246 7 2758 -3 1 4.6250 6 655 -4 1 4.6252 7 1788 -4 1 4.6252 6 618 -1 4 4.6252 6 645 -2 3 4.6252 6 646 -2 3 4.6252 6 648 -7 1 4.6252 6 649 2 3 4.6252 7 1809 -4 1 4.6254 6 668 -2 3 4.6261 6 750 -3 2 4.6261 7 1226 5 1 4.6262 7 1222 -5 1 4.6265 6 649 -3 4 4.6265 7 2218 -4 1 4.6265 7 2238 -1 2 4.6265 7 1241 1 4 4.6265 7 3517 2 1 4.6265 6 645 3 4 4.6265 6 646 3 4 4.6265 6 899 1 2 4.6270 6 668 -5 1 4.6271 6 832 4 1 4.6271 7 2101 -2 3 4.6278 6 723 4 1 4.6280 6 893 3 1 4.6281 6 655 -1 4 4.6285 6 817 -4 1 4.6285 6 818 -4 1 4.6288 6 884 3 2 4.6295 6 719 3 2 4.6303 6 650 4 3 4.6307 6 646 5 2 4.6307 6 645 5 2 4.6307 6 649 -5 2 4.6307 7 2050 -1 2 4.6307 6 899 4 1 4.6310 6 663 -4 3 4.6322 6 813 4 1 4.6322 7 2380 3 2 4.6322 7 1316 5 1 4.6325 7 1367 -4 1 4.6325 7 1262 5 1 4.6329 6 783 3 2 4.6329 6 763 -4 1 4.6334 7 1682 5 1 4.6337 7 1276 5 1 4.6337 6 673 -5 1 4.6342 6 657 -3 2 4.6347 6 704 1 3 4.6347 7 1422 -5 1 4.6349 6 615 -6 1 4.6354 6 702 -2 3 4.6356 7 2086 5 1 4.6364 6 676 4 3 4.6365 7 1262 -2 3 4.6367 6 938 3 1 4.6367 7 3144 3 1 4.6368 7 2101 -3 2 4.6368 6 793 -4 1 4.6371 6 652 5 2 4.6372 7 1638 4 1 4.6372 6 819 4 1 4.6374 6 707 -5 1 4.6376 7 1245 -5 1 4.6384 6 633 -3 4 4.6385 7 1730 5 1 4.6386 6 751 -5 1 4.6388 6 704 -5 1 4.6395 6 821 -1 3 4.6395 6 753 -2 3 4.6397 6 704 5 2 4.6398 7 1431 1 3 4.6399 6 881 -2 3 4.6399 7 1676 5 1 4.6400 6 863 -3 1 4.6400 7 2986 3 1 4.6405 7 1240 -3 2 4.6411 6 682 -5 2 4.6414 6 652 -2 3 4.6415 6 717 -1 4 4.6417 6 641 1 4 4.6420 6 657 -4 1 4.6420 6 805 -3 1 4.6424 6 641 -4 3 4.6426 6 633 1 4 4.6428 6 725 -3 4 4.6429 7 1422 5 1 4.6429 6 847 3 2 4.6430 6 912 5 1 4.6433 6 709 5 1 4.6434 7 1525 4 1 4.6437 6 705 -1 3 4.6439 6 916 -4 1 4.6439 6 626 6 1 4.6439 7 1276 2 3 4.6439 6 673 -2 3 4.6443 7 2671 1 2 4.6460 6 849 4 1 4.6463 7 1788 3 2 4.6463 7 2334 3 1 4.6468 6 917 -3 1 4.6470 7 1605 -4 1 4.6474 7 1538 5 1 4.6485 7 1808 4 1 4.6488 6 643 2 3 4.6491 7 1431 3 2 4.6494 6 668 5 2 4.6495 6 730 -6 1 4.6496 6 639 -2 3 4.6499 7 1398 -5 1 4.6500 7 2050 1 2 4.6503 6 652 6 1 4.6504 7 2573 -3 1 4.6504 6 708 -1 3 4.6506 6 705 5 2 4.6516 6 729 3 2 4.6517 6 746 1 3 4.6519 6 704 2 3 4.6520 6 688 -1 3 4.6522 6 650 5 2 4.6522 6 688 -2 3 4.6523 7 1761 -4 1 4.6524 7 1719 4 1 4.6526 7 1538 -3 2 4.6529 6 686 -5 2 4.6541 6 677 2 3 4.6541 6 695 -4 3 4.6543 7 1425 4 1 4.6547 6 844 -3 1 4.6550 7 1240 -2 3 4.6551 6 793 1 3 4.6552 7 1268 -5 1 4.6554 6 641 -3 4 4.6556 6 893 1 3 4.6561 6 777 -3 2 4.6564 6 678 -1 4 4.6564 7 1315 6 1 4.6569 6 870 -5 1 4.6570 6 657 5 1 4.6574 6 642 4 1 4.6579 6 640 2 3 4.6579 7 1431 4 1 4.6592 6 723 2 3 4.6595 6 702 1 4 4.6595 6 655 4 3 4.6596 6 704 6 1 4.6597 6 649 -5 1 4.6597 6 645 5 1 4.6597 6 646 5 1 4.6602 6 640 -5 3 4.6603 6 636 -4 3 4.6610 6 665 -1 4 4.6612 6 872 1 3 4.6621 6 657 5 2 4.6622 6 806 -1 3 4.6622 6 928 4 1 4.6625 7 1470 -2 3 4.6625 6 753 2 3 4.6626 6 673 5 2 4.6626 6 732 5 1 4.6631 6 695 4 1 4.6634 7 1261 5 2 4.6640 6 667 -7 1 4.6642 7 1268 5 2 4.6645 6 729 -4 3 4.6651 6 666 -1 4 4.6652 7 1313 5 1 4.6652 6 639 1 4 4.6652 7 1261 4 3 4.6655 6 928 1 2 4.6657 7 1315 -6 1 4.6658 6 678 -4 3 4.6662 6 855 4 1 4.6664 7 1367 4 1 4.6665 6 663 5 1 4.6665 6 845 -1 3 4.6666 6 686 2 3 4.6667 7 1281 -5 1 4.6672 6 718 6 1 4.6672 7 1368 -6 1 4.6676 6 643 -5 3 4.6677 6 755 4 1 4.6684 7 1356 -4 1 4.6686 6 819 3 2 4.6686 6 750 -1 3 4.6688 6 698 5 1 4.6688 6 938 -1 2 4.6688 6 699 5 1 4.6688 7 2242 4 1 4.6689 6 703 -1 3 4.6693 7 2343 1 2 4.6702 7 1788 4 1 4.6703 6 741 -1 4 4.6716 6 858 -4 1 4.6716 6 836 4 1 4.6718 7 1422 1 3 4.6727 7 1766 5 1 4.6730 7 1766 -4 1 4.6735 6 720 4 1 4.6736 6 746 -5 1 4.6737 6 717 4 1 4.6741 7 2217 -3 2 4.6748 6 784 -3 2 4.6753 6 677 -5 2 4.6753 6 813 -1 3 4.6753 6 916 1 2 4.6753 6 818 1 3 4.6753 6 817 1 3 4.6753 6 854 -5 1 4.6753 7 1640 -1 3 4.6755 6 798 2 3 4.6765 7 1260 5 2 4.6769 7 1373 -3 2 4.6770 6 733 -5 2 4.6770 6 677 -4 3 4.6775 6 761 -3 2 4.6775 6 686 -4 3 4.6777 7 1281 5 2 4.6779 7 1277 -4 3 4.6779 6 674 4 3 4.6784 6 805 -1 3 4.6786 6 873 1 3 4.6787 6 649 -5 3 4.6787 6 645 5 3 4.6787 6 646 5 3 4.6791 6 667 -1 3 4.6796 6 847 2 3 4.6803 6 705 4 3 4.6806 7 2725 -3 1 4.6809 6 755 2 3 4.6816 7 1431 -5 1 4.6820 7 1553 -5 1 4.6822 6 707 5 1 4.6831 6 703 -5 2 4.6838 6 703 -2 3 4.6841 6 699 -5 1 4.6841 6 698 -5 1 4.6843 6 827 -3 2 4.6845 7 1721 -3 1 4.6850 7 2585 -1 2 4.6850 6 793 -1 4 4.6860 7 1251 4 3 4.6860 6 900 -3 2 4.6860 7 3318 2 1 4.6860 6 890 3 2 4.6860 7 3318 -1 2 4.6860 7 2051 3 2 4.6860 6 666 -4 3 4.6860 7 1727 3 1 4.6860 7 2221 -3 2 4.6860 7 2203 -3 2 4.6860 7 2099 -3 2 4.6860 6 897 -3 2 4.6860 6 850 1 3 4.6860 7 2413 -3 2 4.6860 7 2254 -3 2 4.6861 6 708 -2 3 4.6866 7 1316 5 2 4.6867 6 817 -3 2 4.6867 6 818 -3 2 4.6869 6 709 4 3 4.6870 6 836 -5 1 4.6871 7 2861 1 2 4.6885 7 1315 5 2 4.6887 6 741 -4 3 4.6893 7 1721 -1 3 4.6893 6 890 4 1 4.6893 6 889 -3 1 4.6898 7 1268 4 3 4.6899 6 703 -6 1 4.6900 7 1316 4 3 4.6904 7 1313 -5 2 4.6906 6 687 1 4 4.6911 7 1373 5 2 4.6913 6 720 1 4 4.6913 6 709 5 2 4.6920 6 686 5 1 4.6924 6 865 1 3 4.6927 6 835 -2 3 4.6929 7 2051 -4 1 4.6933 6 903 1 3 4.6938 7 1368 6 1 4.6938 6 718 -6 1 4.6938 6 763 -4 3 4.6941 6 679 -5 1 4.6946 6 861 -1 3 4.6947 7 2200 -1 3 4.6959 6 671 -4 3 4.6959 6 679 -4 3 4.6965 7 1935 4 1 4.6966 7 2274 4 1 4.6966 7 2274 -4 1 4.6971 6 777 -6 1 4.6971 6 721 -5 1 4.6971 6 676 -2 3 4.6974 7 1366 -5 1 4.6989 6 729 -1 4 4.6991 6 709 -2 3 4.6991 6 840 4 1 4.6993 6 691 5 1 4.6994 6 850 -2 3 4.7000 7 1730 3 2 4.7002 6 729 2 3 4.7005 6 819 -1 3 4.7007 6 717 1 4 4.7009 7 1400 5 1 4.7012 7 1730 -5 1 4.7014 6 823 3 2 4.7019 6 745 1 3 4.7022 7 1434 -5 1 4.7030 7 1814 -3 2 4.7032 6 841 -5 1 4.7032 6 722 -6 1 4.7036 6 732 -2 3 4.7036 6 805 4 3 4.7044 7 2343 4 1 4.7044 6 692 -1 4 4.7048 7 1980 3 1 4.7057 7 1262 5 3 4.7060 7 1326 -5 1 4.7060 7 1398 5 1 4.7070 7 1315 1 3 4.7070 6 722 -5 2 4.7070 6 806 4 1 4.7073 6 766 4 1 4.7076 7 1525 -4 1 4.7088 6 704 -1 3 4.7088 7 1333 -5 1 4.7089 7 1373 -4 1 4.7089 7 1281 4 3 4.7090 6 873 -3 2 4.7090 7 1498 3 2 4.7090 6 722 -2 3 4.7092 6 745 5 1 4.7111 6 682 -1 4 4.7111 7 1366 5 1 4.7112 6 741 -3 4 4.7114 7 1373 5 1 4.7115 6 690 -5 2 4.7115 6 784 -5 1 4.7117 7 1491 -5 1 4.7119 6 764 -1 4 4.7121 7 1466 4 1 4.7121 7 1367 1 4 4.7131 6 806 1 4 4.7134 7 1629 4 1 4.7135 6 944 -1 2 4.7135 6 764 -4 1 4.7135 6 763 -1 4 4.7135 7 3493 1 2 4.7135 6 762 1 4 4.7135 7 2604 1 2 4.7135 7 2797 2 1 4.7135 7 1539 5 1 4.7135 6 789 -5 1 4.7135 7 2573 -3 2 4.7135 6 788 -1 3 4.7135 6 889 3 2 4.7135 7 2739 1 2 4.7135 6 903 3 2 4.7143 6 691 -5 2 4.7146 6 878 3 2 4.7146 6 896 -3 1 4.7146 6 908 3 2 4.7149 7 1583 4 1 4.7149 6 802 4 3 4.7151 6 727 -3 4 4.7152 6 677 -1 4 4.7155 6 835 -3 2 4.7156 6 695 -3 4 4.7157 6 751 6 1 4.7158 6 723 -4 3 4.7160 6 727 -1 4 4.7162 6 725 2 3 4.7165 6 719 -5 2 4.7179 6 909 3 1 4.7185 7 2057 3 1 4.7192 6 762 4 3 4.7193 7 2051 4 1 4.7193 6 949 -3 1 4.7194 7 1348 -5 1 4.7194 7 1513 -3 2 4.7197 7 1333 5 1 4.7200 6 729 4 1 4.7203 7 2334 -3 2 4.7208 6 801 -6 1 4.7210 6 802 -1 3 4.7221 7 1534 -5 1 4.7223 7 1425 -1 4 4.7224 6 938 -4 1 4.7227 6 896 2 3 4.7232 6 932 -3 1 4.7233 6 801 -3 2 4.7233 6 708 -5 2 4.7235 6 719 -7 1 4.7235 7 1516 4 3 4.7238 6 726 7 1 4.7239 6 801 6 1 4.7243 6 705 -5 1 4.7246 6 826 3 2 4.7246 6 703 1 3 4.7247 6 703 6 1 4.7252 6 719 7 1 4.7252 7 1373 -2 3 4.7254 6 882 4 1 4.7254 6 872 -2 3 4.7254 6 779 -4 3 4.7254 6 775 -4 3 4.7254 6 778 1 3 4.7254 6 772 -4 3 4.7254 6 773 -5 1 4.7254 6 786 -5 1 4.7254 6 787 -4 3 4.7254 6 781 4 1 4.7254 6 897 -4 1 4.7254 7 3128 1 2 4.7254 6 775 1 3 4.7254 7 2836 1 2 4.7254 6 778 -4 3 4.7254 7 3210 -1 2 4.7254 6 900 -4 1 4.7254 6 779 1 3 4.7254 6 772 1 3 4.7254 7 2530 3 1 4.7254 6 787 1 3 4.7254 7 3184 3 1 4.7254 7 2203 -4 1 4.7254 7 2202 4 1 4.7255 7 2556 -3 2 4.7258 7 1315 2 3 4.7258 6 702 4 3 4.7263 7 2051 -3 2 4.7265 6 832 -1 3 4.7267 7 1814 -1 3 4.7269 7 1461 -1 4 4.7270 7 1538 1 3 4.7274 7 2302 3 1 4.7283 7 2018 -3 2 4.7283 7 2254 -4 1 4.7283 6 874 3 2 4.7283 6 749 -2 3 4.7286 7 1373 1 4 4.7287 7 2086 -4 1 4.7288 6 734 5 1 4.7288 7 1408 5 1 4.7291 6 827 -2 3 4.7293 7 1814 -4 1 4.7305 6 949 3 1 4.7305 7 1308 5 3 4.7306 6 727 4 1 4.7306 7 1461 -3 4 4.7310 6 850 -4 1 4.7312 6 854 5 1 4.7314 6 885 -1 3 4.7321 7 1629 -4 1 4.7322 6 708 -6 1 4.7323 6 944 -4 1 4.7323 7 3246 -1 2 4.7323 7 2604 4 1 4.7326 6 723 -1 4 4.7326 6 783 -3 2 4.7329 6 798 -4 1 4.7329 7 2028 2 3 4.7331 7 2568 3 1 4.7336 6 818 2 3 4.7336 6 817 2 3 4.7339 6 729 -3 4 4.7342 6 705 -2 3 4.7344 6 702 5 1 4.7349 7 1498 4 1 4.7349 6 885 2 3 4.7351 6 708 2 3 4.7353 6 727 -5 2 4.7358 6 690 -5 1 4.7361 6 862 5 1 4.7362 7 1498 -1 4 4.7369 6 923 4 1 4.7370 6 722 1 3 4.7370 6 721 5 1 4.7372 6 773 4 1 4.7372 6 781 -5 1 4.7372 6 786 4 1 4.7376 7 2656 -4 1 4.7376 6 939 1 2 4.7380 6 885 -3 1 4.7381 7 1538 2 3 4.7382 6 768 -4 1 4.7390 6 921 -3 2 4.7394 7 1368 1 3 4.7394 6 718 -1 3 4.7400 6 730 -5 2 4.7413 6 821 -2 3 4.7413 7 1676 -5 1 4.7418 6 911 3 1 4.7421 6 707 -5 2 4.7422 7 1368 5 2 4.7422 6 718 -5 2 4.7425 7 1682 3 2 4.7426 6 727 2 3 4.7439 6 764 4 1 4.7439 6 763 -3 4 4.7442 6 734 -5 1 4.7442 7 1408 -5 1 4.7442 6 902 3 1 4.7444 6 746 2 3 4.7449 6 869 3 2 4.7464 6 819 -2 3 4.7470 7 1538 -5 1 4.7470 7 1422 2 3 4.7477 7 1937 5 1 4.7480 6 708 6 1 4.7492 6 733 -4 3 4.7494 6 907 -4 1 4.7494 6 874 4 1 4.7494 6 798 4 1 4.7494 7 2380 1 3 4.7494 7 2018 -4 1 4.7494 7 2715 -1 2 4.7494 7 3215 1 2 4.7494 6 942 1 2 4.7494 6 779 -5 2 4.7494 6 772 -5 2 4.7494 6 775 -5 2 4.7494 6 778 3 2 4.7494 6 787 -5 2 4.7494 7 2984 -3 1 4.7494 6 775 3 2 4.7494 7 2217 -4 1 4.7494 6 779 3 2 4.7494 6 772 3 2 4.7494 6 787 3 2 4.7494 7 3377 -1 2 4.7494 7 2876 1 2 4.7494 6 778 -5 2 4.7495 6 730 7 1 4.7498 6 784 1 3 4.7499 6 777 6 1 4.7504 6 758 -2 3 4.7505 6 902 -4 1 4.7511 7 2632 -4 1 4.7512 7 1585 -4 1 4.7517 7 3209 3 1 4.7517 7 2420 -3 1 4.7517 7 3208 3 1 4.7517 6 775 4 1 4.7517 6 778 -5 1 4.7517 6 779 4 1 4.7517 6 772 4 1 4.7517 6 781 -4 3 4.7517 6 786 1 3 4.7517 6 787 4 1 4.7517 6 773 1 3 4.7517 6 779 -5 1 4.7517 6 890 -4 1 4.7517 6 775 -5 1 4.7517 6 778 4 1 4.7517 6 889 -1 3 4.7517 6 787 -5 1 4.7517 6 772 -5 1 4.7517 6 819 -5 1 4.7517 7 1638 -5 1 4.7517 6 817 -1 3 4.7518 6 730 5 2 4.7524 6 871 -3 1 4.7526 6 847 -3 2 4.7526 6 762 3 4 4.7527 6 749 4 3 4.7531 7 1396 -1 4 4.7532 7 2874 -3 1 4.7532 7 1761 1 3 4.7532 6 874 -1 3 4.7532 7 2655 4 1 4.7532 7 2018 1 3 4.7535 6 741 3 2 4.7537 6 865 2 3 4.7541 7 2200 -2 3 4.7543 6 845 -3 2 4.7544 7 1676 -3 2 4.7545 6 743 4 1 4.7547 6 745 -5 2 4.7548 7 1534 -1 3 4.7549 6 733 2 3 4.7553 7 2413 -1 3 4.7553 6 727 -5 1 4.7562 7 1516 -4 1 4.7570 7 1543 4 1 4.7574 6 783 -6 1 4.7577 6 732 5 2 4.7578 6 793 -3 4 4.7582 6 733 5 1 4.7584 6 919 -3 1 4.7585 6 820 4 1 4.7588 6 823 -6 1 4.7590 7 2696 -2 3 4.7590 6 722 6 1 4.7593 6 786 -4 3 4.7593 6 773 -4 3 4.7593 6 781 1 3 4.7595 6 854 -3 2 4.7596 7 2696 -3 2 4.7598 6 882 3 2 4.7598 6 819 1 3 4.7598 6 818 5 1 4.7598 6 817 5 1 4.7604 7 1373 5 3 4.7606 7 1572 -4 1 4.7609 7 1732 4 1 4.7611 7 1589 -1 4 4.7612 7 1431 2 3 4.7625 6 841 5 1 4.7626 6 783 7 1 4.7629 7 1814 4 1 4.7636 6 850 3 2 4.7637 6 793 -4 3 4.7643 7 3511 -1 2 4.7643 7 2238 4 1 4.7646 7 1498 2 3 4.7650 6 763 3 2 4.7650 7 1585 5 1 4.7653 6 820 -3 2 4.7653 6 896 3 2 4.7654 7 2218 -1 2 4.7659 7 1368 2 3 4.7659 6 718 -2 3 4.7659 6 916 -3 2 4.7659 7 2099 -4 1 4.7659 7 2101 3 1 4.7659 6 957 1 2 4.7659 6 821 2 3 4.7659 6 960 1 2 4.7659 7 2787 -3 1 4.7659 6 823 6 1 4.7659 7 1644 -2 3 4.7659 7 2100 -3 1 4.7661 6 741 4 1 4.7664 6 761 -5 1 4.7665 6 768 -1 4 4.7669 7 1398 -5 2 4.7675 6 751 1 3 4.7677 7 1466 -1 4 4.7678 6 732 -5 1 4.7680 7 1367 -3 4 4.7680 7 1666 4 1 4.7681 6 907 3 1 4.7682 7 1448 -5 1 4.7683 6 879 -4 1 4.7683 6 878 -1 3 4.7683 6 746 -5 2 4.7684 6 749 1 4 4.7689 6 878 -3 2 4.7691 6 924 -4 1 4.7698 7 2209 3 2 4.7698 7 1859 -2 3 4.7698 7 1859 3 2 4.7698 7 1859 2 3 4.7698 7 1859 -3 2 4.7699 7 1513 -4 1 4.7710 7 1425 -4 3 4.7711 6 746 5 1 4.7715 7 2166 -4 1 4.7719 6 798 -1 4 4.7724 7 1700 5 1 4.7725 7 1470 5 1 4.7725 6 753 -5 1 4.7728 6 847 4 1 4.7729 7 1534 1 3 4.7729 6 751 -1 3 4.7734 6 745 2 3 4.7737 6 892 3 2 4.7739 6 751 5 2 4.7740 7 1540 5 1 4.7740 6 788 -5 1 4.7740 6 789 5 1 4.7740 7 1539 -5 1 4.7743 7 1809 -3 2 4.7747 6 737 4 3 4.7752 7 1461 2 3 4.7753 6 727 -5 3 4.7754 6 721 -4 3 4.7755 6 750 -2 3 4.7756 6 764 1 4 4.7759 7 1721 4 1 4.7759 6 888 -3 1 4.7761 7 1489 -5 1 4.7763 6 753 5 1 4.7763 7 1470 -5 1 4.7765 6 802 -3 2 4.7773 6 873 3 2 4.7774 7 1565 -5 1 4.7779 6 773 -5 2 4.7779 6 786 -5 2 4.7779 6 781 3 2 4.7782 6 870 5 1 4.7782 7 1491 -1 3 4.7789 6 846 -3 2 4.7789 7 1436 5 2 4.7793 6 763 2 3 4.7794 7 1433 5 1 4.7798 7 2050 5 1 4.7801 6 806 3 4 4.7809 7 1436 -5 1 4.7811 6 739 4 1 4.7813 7 1400 5 2 4.7821 6 826 2 3 4.7822 7 1761 3 2 4.7822 7 2846 -3 1 4.7823 7 1538 5 2 4.7824 7 1732 1 3 4.7826 6 844 -1 3 4.7828 7 1777 -4 1 4.7829 6 761 4 3 4.7831 7 2099 3 2 4.7835 6 813 4 3 4.7835 7 2543 4 1 4.7843 7 1585 4 3 4.7849 7 2568 1 3 4.7849 7 1516 5 1 4.7849 6 773 3 2 4.7849 6 781 -5 2 4.7849 6 786 3 2 4.7852 7 2086 3 2 4.7852 7 1585 5 2 4.7858 6 820 -2 3 4.7861 6 827 4 1 4.7861 7 1927 4 1 4.7866 6 784 2 3 4.7867 7 1516 5 2 4.7874 7 1721 1 4 4.7876 6 758 5 1 4.7879 6 805 1 4 4.7884 6 878 1 3 4.7884 7 2054 -5 1 4.7895 7 2716 -4 1 4.7896 6 741 -5 1 4.7898 7 1492 1 4 4.7900 7 1809 5 1 4.7904 6 750 4 3 4.7904 6 749 5 1 4.7904 6 837 -5 1 4.7904 6 788 -2 3 4.7904 6 862 -6 1 4.7908 7 1732 3 2 4.7917 6 820 1 4 4.7919 7 1513 -2 3 4.7919 7 1640 1 4 4.7933 6 903 2 3 4.7938 6 750 -5 1 4.7945 7 1831 -5 1 4.7947 6 942 -4 1 4.7948 6 846 4 1 4.7953 6 755 -1 4 4.7965 7 2371 -3 1 4.7969 6 802 -4 1 4.7969 7 2380 2 3 4.7969 6 835 4 1 4.7972 7 1631 -6 1 4.7972 6 761 -1 3 4.7973 6 927 3 1 4.7979 6 783 5 2 4.7979 6 802 5 2 4.7980 7 2040 5 1 4.7980 7 1603 5 1 4.7985 7 2173 -4 1 4.7986 7 1727 -4 3 4.7987 6 917 -1 3 4.7988 7 3011 3 1 4.7989 6 813 -3 2 4.7992 6 942 -1 2 4.7995 7 2638 -4 1 4.7995 7 2018 -1 3 4.7995 6 874 1 3 4.7999 7 1498 -4 3 4.8003 6 784 6 1 4.8009 6 847 -2 3 4.8015 6 761 6 1 4.8022 7 2389 -3 2 4.8023 7 1525 -1 4 4.8023 6 745 -6 1 4.8026 6 746 -4 3 4.8026 6 759 4 3 4.8027 7 1525 1 4 4.8030 7 1577 4 3 4.8033 6 869 -3 2 4.8037 7 1534 -2 3 4.8039 6 758 4 3 4.8040 6 755 -4 3 4.8040 7 1553 1 3 4.8045 7 1541 -4 3 4.8046 7 1491 6 1 4.8052 6 751 2 3 4.8061 6 827 1 4 4.8063 7 1492 -1 4 4.8070 7 2050 -3 2 4.8072 6 839 -1 3 4.8073 7 1801 -4 1 4.8075 7 1538 -1 3 4.8075 7 2543 -1 2 4.8078 7 1678 -1 4 4.8078 6 879 5 1 4.8079 6 860 -5 1 4.8082 6 939 -1 2 4.8083 7 1678 -4 1 4.8084 7 1875 4 1 4.8086 7 1626 -5 1 4.8088 7 2158 -1 3 4.8090 7 1577 5 2 4.8093 6 901 -3 2 4.8093 7 2920 4 1 4.8093 7 2919 4 1 4.8094 7 2218 1 2 4.8098 6 762 -4 1 4.8100 6 860 5 1 4.8101 7 1640 -3 2 4.8104 6 806 4 3 4.8112 6 758 1 4 4.8113 6 759 3 4 4.8115 6 858 1 3 4.8118 6 755 -5 1 4.8119 6 802 5 1 4.8133 6 805 3 4 4.8134 7 1613 4 3 4.8136 7 2462 -4 1 4.8136 7 2018 3 2 4.8136 6 874 -3 2 4.8136 6 826 4 1 4.8136 6 846 -2 3 4.8137 7 1578 -4 3 4.8138 6 832 -3 2 4.8145 7 2640 3 2 4.8145 7 1667 4 3 4.8145 7 2643 -3 2 4.8145 7 2302 1 3 4.8147 7 1466 1 4 4.8147 6 784 5 2 4.8148 7 1601 5 1 4.8163 6 784 -1 3 4.8165 7 1610 5 1 4.8167 6 844 4 3 4.8171 7 3077 -3 1 4.8171 7 1963 4 1 4.8171 7 2877 4 1 4.8179 6 771 -1 4 4.8183 7 1513 5 1 4.8199 7 1513 1 4 4.8203 7 2050 3 2 4.8204 7 2632 4 1 4.8207 7 1534 2 3 4.8208 7 1498 -5 1 4.8223 6 751 -2 3 4.8226 7 1541 -3 4 4.8230 7 1935 3 2 4.8232 7 1481 4 3 4.8233 7 2442 -3 1 4.8244 7 2876 -3 1 4.8245 7 1682 -6 1 4.8247 6 909 -4 1 4.8250 6 912 6 1 4.8251 7 1605 5 1 4.8253 6 840 -3 2 4.8257 6 847 -4 1 4.8258 7 2570 -3 1 4.8259 6 845 -2 3 4.8260 6 896 -1 3 4.8263 7 1766 -3 2 4.8265 6 827 -4 1 4.8267 6 907 -1 3 4.8279 6 806 -3 2 4.8281 6 873 2 3 4.8284 6 899 -3 2 4.8285 6 878 4 1 4.8286 6 849 -1 3 4.8288 7 1534 6 1 4.8295 6 815 -4 1 4.8301 6 835 1 4 4.8301 6 793 3 2 4.8301 7 1927 -4 1 4.8306 6 865 -4 1 4.8309 7 1534 5 2 4.8309 7 1520 -5 1 4.8313 6 801 5 2 4.8320 7 1513 3 4 4.8320 6 832 -4 1 4.8321 6 918 4 1 4.8321 7 2123 -4 1 4.8323 7 2050 -5 1 4.8324 6 865 -3 2 4.8330 6 863 -2 3 4.8331 7 3001 -1 2 4.8335 7 1516 1 4 4.8335 6 871 4 1 4.8337 7 1640 -4 1 4.8343 6 839 -4 1 4.8344 7 1695 -5 1 4.8345 6 873 -2 3 4.8346 6 904 -3 1 4.8350 6 836 -5 2 4.8350 6 850 2 3 4.8352 6 855 -1 3 4.8353 6 865 -1 3 4.8358 6 845 -4 1 4.8358 7 2739 -3 2 4.8361 6 952 3 1 4.8363 6 939 4 1 4.8367 7 1858 -5 1 4.8369 7 1601 4 3 4.8370 6 840 -2 3 4.8371 6 798 1 4 4.8372 7 1682 6 1 4.8379 7 1553 -5 2 4.8380 6 777 5 2 4.8400 6 813 1 4 4.8401 7 1703 -4 1 4.8402 7 1761 4 1 4.8409 6 889 2 3 4.8414 6 758 -5 1 4.8414 7 1538 6 1 4.8416 6 777 -5 2 4.8417 7 1589 1 4 4.8422 6 805 5 2 4.8435 6 886 -3 1 4.8450 7 2503 -3 1 4.8461 6 789 -5 2 4.8461 7 1539 5 2 4.8461 7 2671 -3 2 4.8461 7 1676 6 1 4.8462 7 1613 -4 1 4.8477 6 813 -2 3 4.8480 6 861 -3 2 4.8497 6 819 2 3 4.8500 7 1669 -5 2 4.8502 6 839 4 3 4.8505 6 814 4 1 4.8510 6 820 4 3 4.8511 6 944 -3 2 4.8511 7 2238 -5 1 4.8511 7 2874 1 2 4.8511 7 1721 3 4 4.8511 7 3209 1 2 4.8511 6 828 -4 3 4.8511 7 2604 3 2 4.8511 6 775 2 3 4.8511 6 779 2 3 4.8511 6 772 2 3 4.8511 6 778 -5 3 4.8511 6 787 2 3 4.8511 7 2265 4 1 4.8511 7 3377 1 2 4.8511 7 3200 -3 1 4.8511 6 779 -5 3 4.8511 7 2201 4 1 4.8511 6 772 -5 3 4.8511 6 775 -5 3 4.8511 6 778 2 3 4.8511 6 787 -5 3 4.8513 7 1727 -1 4 4.8514 7 1843 4 1 4.8515 7 1700 -1 3 4.8515 6 835 -1 4 4.8519 7 1721 4 3 4.8520 7 2200 3 1 4.8520 7 2542 -1 3 4.8523 7 1855 4 1 4.8525 7 1935 -5 1 4.8529 6 813 -4 1 4.8529 6 817 -2 3 4.8529 6 818 -2 3 4.8531 6 872 3 2 4.8531 6 892 1 3 4.8532 6 901 3 2 4.8539 7 2462 4 1 4.8543 6 863 -3 2 4.8546 6 839 -3 2 4.8548 6 771 3 4 4.8548 6 806 -2 3 4.8550 7 3007 -1 2 4.8550 6 932 3 1 4.8555 7 2100 -1 3 4.8555 7 2503 1 3 4.8555 7 2099 4 1 4.8555 7 2101 1 3 4.8560 6 894 -3 1 4.8565 7 3205 -2 1 4.8569 7 1814 -2 3 4.8569 7 1676 5 2 4.8570 7 1640 4 3 4.8571 6 846 1 4 4.8573 6 849 -4 1 4.8580 7 2274 -3 2 4.8580 7 2274 3 2 4.8584 6 840 1 4 4.8586 7 2642 4 1 4.8586 7 2641 4 1 4.8586 7 1601 5 2 4.8589 7 1640 -2 3 4.8594 6 832 4 3 4.8597 7 2515 3 1 4.8598 7 2722 4 1 4.8598 6 802 -2 3 4.8601 7 2078 -5 1 4.8601 7 2384 -4 1 4.8601 7 2517 3 1 4.8606 6 819 -5 2 4.8606 7 1600 -4 3 4.8610 6 885 4 1 4.8612 7 2986 -3 1 4.8614 6 820 3 4 4.8615 7 1777 5 1 4.8621 7 1788 2 3 4.8623 7 1538 4 3 4.8624 7 1809 1 3 4.8629 7 2230 1 3 4.8629 7 2229 -1 3 4.8630 7 1613 3 4 4.8631 7 1730 6 1 4.8632 6 817 -5 1 4.8632 6 818 -5 1 4.8635 6 835 -4 1 4.8642 7 1737 -5 1 4.8644 6 894 -1 3 4.8644 6 907 -2 3 4.8645 6 918 -4 1 4.8652 6 869 -5 1 4.8657 6 793 4 1 4.8659 6 850 4 1 4.8662 7 1583 -5 1 4.8663 6 801 -7 1 4.8663 6 802 5 3 4.8664 6 839 5 1 4.8665 7 1572 -1 4 4.8666 6 814 -4 3 4.8671 7 1644 5 1 4.8671 6 821 -5 1 4.8675 6 849 -3 2 4.8676 7 2542 -3 1 4.8678 6 861 4 1 4.8679 6 821 5 1 4.8679 7 1644 -5 1 4.8680 6 844 1 4 4.8688 7 2381 3 1 4.8688 7 2380 -3 1 4.8691 7 1638 5 1 4.8691 6 819 5 1 4.8693 6 805 -3 2 4.8693 7 2221 4 1 4.8696 7 2556 3 1 4.8697 6 781 -3 4 4.8697 6 773 -1 4 4.8697 6 786 -1 4 4.8698 6 813 3 4 4.8699 6 858 4 1 4.8699 6 872 2 3 4.8703 7 2750 -3 1 4.8707 7 1766 5 2 4.8711 6 893 3 2 4.8711 6 858 3 2 4.8722 6 832 1 4 4.8725 7 2265 3 2 4.8726 7 1678 4 1 4.8729 6 847 1 4 4.8730 6 904 -1 3 4.8731 7 1601 1 4 4.8733 7 2911 -4 1 4.8737 6 826 -1 4 4.8738 7 1980 1 3 4.8740 7 2656 4 1 4.8743 6 888 4 1 4.8744 6 870 -6 1 4.8745 7 2018 4 1 4.8745 6 874 -4 1 4.8754 7 1818 -5 1 4.8757 7 2914 2 3 4.8757 7 2281 4 1 4.8757 7 2422 3 1 4.8757 7 2911 4 1 4.8764 6 817 5 2 4.8764 6 818 5 2 4.8764 7 1652 -5 1 4.8767 7 2517 -4 1 4.8767 7 2166 5 1 4.8774 6 893 2 3 4.8775 6 805 5 3 4.8780 6 863 4 1 4.8783 6 861 -2 3 4.8784 7 1843 -4 1 4.8787 6 806 -4 1 4.8793 7 2447 1 3 4.8793 6 820 -4 1 4.8794 7 1802 4 1 4.8802 7 1605 -5 1 4.8804 7 1732 -5 1 4.8809 7 1589 -4 3 4.8810 7 1695 5 1 4.8811 6 917 -3 2 4.8811 6 915 -3 2 4.8811 7 1676 1 3 4.8811 6 915 3 2 4.8811 7 2158 -2 3 4.8812 6 827 -1 4 4.8813 7 2758 -3 2 4.8814 7 2028 4 1 4.8820 6 871 -1 3 4.8825 6 924 -3 2 4.8828 7 1697 -6 1 4.8829 7 1652 -5 2 4.8831 7 2238 -3 2 4.8833 6 838 -2 3 4.8839 7 1682 -5 2 4.8840 6 878 -4 1 4.8844 6 805 -2 3 4.8846 6 888 -1 3 4.8860 7 1801 4 1 4.8860 7 1761 2 3 4.8861 7 2272 1 2 4.8862 7 1809 -5 1 4.8862 7 2229 -2 3 4.8862 7 2230 2 3 4.8862 6 916 4 1 4.8862 7 1738 -4 1 4.8864 7 2678 3 1 4.8864 7 2209 1 3 4.8865 6 832 -2 3 4.8867 6 878 -2 3 4.8867 7 1629 -1 4 4.8869 7 1781 -5 1 4.8880 6 826 -4 3 4.8888 7 1640 3 4 4.8888 6 814 -3 4 4.8890 7 2862 3 1 4.8896 7 3184 -4 1 4.8897 7 2872 -3 2 4.8907 7 2846 1 2 4.8907 7 2543 3 2 4.8923 6 836 5 1 4.8923 6 928 -4 1 4.8924 6 837 5 1 4.8925 7 1676 2 3 4.8931 6 869 5 1 4.8932 7 1738 1 4 4.8937 7 1809 2 3 4.8937 7 1669 -4 3 4.8939 6 840 4 3 4.8940 7 2797 -1 3 4.8940 6 886 -1 3 4.8941 7 2722 3 2 4.8942 6 846 -4 1 4.8942 7 1678 1 4 4.8943 7 1730 -6 1 4.8946 7 1844 -4 1 4.8946 7 2846 -3 2 4.8946 6 924 4 1 4.8958 6 854 -6 1 4.8961 6 878 2 3 4.8964 7 1766 -5 1 4.8964 7 2998 1 2 4.8966 6 813 5 2 4.8973 7 1802 -4 1 4.8974 7 2465 1 3 4.8978 6 844 3 4 4.8981 7 2914 1 3 4.8981 7 2430 -4 1 4.8982 7 2447 -3 1 4.8982 6 925 -3 1 4.8984 7 2442 1 3 4.8985 6 840 -4 1 4.8988 6 929 -3 1 4.8995 7 2157 -1 3 4.8995 7 1585 5 3 4.8997 7 2272 -3 2 4.8998 7 1603 5 3 4.8998 6 890 -3 2 4.8998 7 1605 5 2 4.9000 6 873 -4 1 4.9001 7 1719 5 1 4.9002 7 2200 -4 1 4.9006 7 2914 -1 2 4.9006 7 2455 3 2 4.9009 7 1766 6 1 4.9009 7 2119 -5 1 4.9009 6 839 5 2 4.9010 7 1859 -4 1 4.9010 7 1859 1 4 4.9010 7 1859 4 1 4.9010 7 1859 -1 4 4.9014 7 1640 5 1 4.9014 7 3077 4 1 4.9014 7 1937 -6 1 4.9016 6 957 2 1 4.9016 6 960 2 1 4.9016 6 893 -2 3 4.9022 6 823 -7 1 4.9025 6 823 5 2 4.9026 7 2334 -4 1 4.9035 6 877 -4 1 4.9039 7 2217 4 1 4.9043 6 832 5 1 4.9048 7 2072 4 1 4.9048 7 2623 4 1 4.9050 7 2438 -4 1 4.9055 6 939 -4 1 4.9059 7 2413 3 1 4.9063 7 1761 -4 3 4.9064 6 823 7 1 4.9064 7 2556 -4 1 4.9066 7 1721 -3 2 4.9066 6 841 -1 3 4.9068 7 3215 3 1 4.9068 7 2771 -4 1 4.9068 7 2018 2 3 4.9068 6 933 -2 3 4.9068 6 855 -3 2 4.9068 6 874 -2 3 4.9068 7 2573 -1 3 4.9069 6 844 5 2 4.9069 7 1788 -5 1 4.9069 6 836 -6 1 4.9073 7 2986 -1 2 4.9073 6 841 -6 1 4.9073 6 899 3 2 4.9073 7 2100 4 1 4.9073 7 2101 -4 1 4.9076 6 855 -4 1 4.9076 6 849 4 3 4.9078 6 855 4 3 4.9080 7 1721 -2 3 4.9093 7 2099 -1 3 4.9094 7 2986 1 2 4.9094 7 2817 3 1 4.9098 6 862 6 1 4.9099 7 2272 5 1 4.9101 7 2062 -4 1 4.9104 6 892 4 1 4.9104 6 891 1 3 4.9105 7 2046 4 1 4.9108 7 1860 -3 4 4.9110 6 837 -5 2 4.9112 6 886 4 1 4.9117 7 2173 -3 2 4.9120 7 2099 1 3 4.9121 7 2209 4 1 4.9123 7 1938 1 4 4.9125 6 828 4 1 4.9126 6 863 1 4 4.9126 7 1732 2 3 4.9131 7 2200 1 3 4.9136 7 2447 2 3 4.9138 7 2528 -3 1 4.9140 7 2078 3 2 4.9141 6 882 1 3 4.9141 7 2297 -5 1 4.9141 7 2037 1 3 4.9141 7 2037 -1 3 4.9147 7 2218 -5 1 4.9150 7 2359 -1 3 4.9152 7 2078 5 1 4.9155 7 1818 -5 2 4.9159 7 2389 -4 1 4.9163 7 2343 3 2 4.9163 6 927 -4 1 4.9164 7 1676 -6 1 4.9164 7 2721 -4 1 4.9164 7 2018 -2 3 4.9164 6 874 2 3 4.9174 6 889 4 1 4.9178 6 861 1 4 4.9180 6 844 5 1 4.9187 7 2898 3 2 4.9190 7 1855 1 4 4.9190 7 3517 1 2 4.9192 6 832 3 4 4.9192 7 2086 6 1 4.9194 7 1983 4 1 4.9209 7 2254 -1 3 4.9209 7 1963 -4 1 4.9210 7 2725 3 1 4.9211 6 921 -4 1 4.9212 7 2430 3 1 4.9216 7 2632 -1 2 4.9216 6 844 -3 2 4.9218 7 1700 -2 3 4.9225 7 2775 -4 1 4.9233 7 1678 -3 4 4.9238 7 2335 -5 1 4.9240 6 918 3 2 4.9240 7 2642 -5 1 4.9240 7 2641 -5 1 4.9240 7 1808 5 1 4.9240 7 1809 -1 3 4.9245 6 841 2 3 4.9249 7 1700 5 2 4.9249 6 854 6 1 4.9255 7 2716 -1 2 4.9258 7 1777 5 2 4.9263 7 2553 -5 1 4.9265 7 2497 -1 3 4.9266 6 938 1 2 4.9267 7 2054 6 1 4.9274 6 929 2 3 4.9276 6 943 4 1 4.9285 7 1730 -5 2 4.9286 7 2568 3 2 4.9288 7 1788 5 1 4.9290 7 1730 5 2 4.9291 6 836 -4 3 4.9291 7 2656 -1 2 4.9293 6 845 1 4 4.9296 7 2573 -2 3 4.9297 6 870 3 2 4.9299 7 1678 -4 3 4.9310 7 2420 -1 3 4.9313 6 847 -1 4 4.9319 7 2671 3 1 4.9320 7 1725 1 4 4.9321 6 942 4 1 4.9322 6 849 5 1 4.9324 7 2636 -3 1 4.9336 7 2678 -3 2 4.9339 7 1761 -5 1 4.9341 7 1752 5 1 4.9341 6 862 -5 2 4.9341 7 2568 2 3 4.9346 7 2739 -2 3 4.9348 7 2157 4 1 4.9349 7 2395 3 1 4.9350 7 2770 -3 1 4.9350 7 1732 -4 3 4.9354 6 844 -4 1 4.9364 6 900 -1 3 4.9364 6 899 -5 1 4.9364 7 2203 -1 3 4.9364 7 2202 1 3 4.9364 6 884 4 1 4.9364 7 3492 1 2 4.9364 7 2238 3 2 4.9364 6 897 -1 3 4.9364 6 839 1 4 4.9365 7 2209 -4 1 4.9367 7 2123 -3 2 4.9369 6 917 -2 3 4.9372 7 1695 -5 2 4.9373 7 1801 -1 4 4.9374 6 957 -1 3 4.9374 6 929 1 3 4.9374 6 960 -1 3 4.9377 7 2465 4 1 4.9377 7 2307 -1 3 4.9378 7 2217 -1 3 4.9379 6 841 6 1 4.9382 6 934 3 1 4.9384 7 1808 -5 1 4.9385 7 2371 4 1 4.9386 6 841 -5 2 4.9389 7 1727 3 2 4.9389 6 873 4 1 4.9392 6 931 1 3 4.9402 6 911 -1 3 4.9407 7 1937 6 1 4.9407 7 1777 4 3 4.9407 7 2040 3 2 4.9408 7 2279 5 1 4.9415 7 2189 -4 1 4.9415 7 1766 -1 3 4.9418 7 2568 -3 1 4.9426 7 2076 -5 1 4.9432 7 1727 -5 2 4.9438 7 2465 2 3 4.9444 6 951 -3 1 4.9446 6 881 -1 4 4.9446 7 2123 5 1 4.9446 6 891 -1 4 4.9446 7 1855 -4 1 4.9451 6 872 -4 1 4.9476 7 2846 -2 3 4.9476 7 2302 3 2 4.9480 6 862 -7 1 4.9480 7 1809 5 2 4.9481 7 2344 -4 1 4.9484 7 3075 4 1 4.9486 6 840 -1 4 4.9488 7 2335 3 2 4.9489 7 2218 -3 2 4.9490 6 839 3 4 4.9491 7 2643 3 1 4.9491 7 2640 -3 1 4.9492 7 2062 -3 2 4.9494 7 1732 -5 2 4.9495 7 1858 -1 3 4.9495 6 845 5 1 4.9496 7 2203 4 1 4.9496 7 2202 -4 1 4.9496 6 897 4 1 4.9496 6 900 4 1 4.9496 7 2320 -4 1 4.9496 7 1935 5 1 4.9500 7 2242 5 1 4.9502 7 3158 1 2 4.9502 6 846 -1 4 4.9504 6 899 5 1 4.9504 7 3189 -1 2 4.9505 7 2438 -1 3 4.9506 7 2478 4 1 4.9508 7 2334 -1 3 4.9510 7 1736 1 4 4.9511 6 889 -3 2 4.9512 6 892 2 3 4.9513 6 846 4 3 4.9517 6 862 3 2 4.9527 6 901 -4 1 4.9530 6 900 3 2 4.9530 7 2202 -3 2 4.9530 7 2203 3 2 4.9530 6 897 3 2 4.9530 6 881 2 3 4.9530 7 2158 4 1 4.9533 7 1812 -2 3 4.9538 6 884 -3 2 4.9539 6 886 1 4 4.9547 6 871 1 4 4.9547 7 2313 -5 1 4.9547 7 2585 -4 1 4.9555 7 2889 4 1 4.9555 7 2028 -4 1 4.9555 7 2722 -3 1 4.9555 7 3080 4 1 4.9557 7 2770 4 1 4.9558 7 2086 -3 2 4.9558 7 2877 -4 1 4.9559 7 2623 -5 1 4.9562 7 1935 1 3 4.9564 6 855 5 1 4.9570 7 2871 1 2 4.9574 7 3007 3 2 4.9575 7 1761 -5 2 4.9576 7 3086 1 2 4.9597 7 2698 -3 1 4.9597 6 870 6 1 4.9600 7 2218 3 2 4.9602 7 2552 4 1 4.9606 6 858 -5 1 4.9607 7 2101 -1 4 4.9607 7 2100 1 4 4.9611 6 901 2 3 4.9612 6 855 1 4 4.9614 6 865 -2 3 4.9615 7 2051 1 3 4.9617 7 2040 -6 1 4.9619 6 929 4 1 4.9619 6 879 -3 2 4.9621 7 1736 -1 4 4.9628 7 2715 4 1 4.9630 6 858 2 3 4.9631 6 884 1 4 4.9632 7 2465 -3 1 4.9633 6 865 5 1 4.9633 7 2284 -6 1 4.9635 7 2245 -5 1 4.9639 6 901 -2 3 4.9640 7 3095 1 2 4.9649 7 2585 4 1 4.9651 6 915 4 1 4.9651 7 2347 -4 1 4.9651 6 915 -4 1 4.9651 7 2123 1 3 4.9652 7 2086 -5 1 4.9656 6 863 -1 4 4.9659 6 846 3 4 4.9660 6 849 -2 3 4.9662 6 877 -1 4 4.9662 7 2008 4 1 4.9664 6 872 -1 4 4.9667 6 849 1 4 4.9668 6 958 -3 1 4.9669 6 849 5 2 4.9672 7 3199 3 1 4.9673 7 1732 -1 4 4.9678 7 1978 4 1 4.9678 7 1858 1 3 4.9678 7 2871 -3 2 4.9695 7 2062 5 1 4.9702 7 1852 -1 4 4.9702 7 2841 3 1 4.9705 6 877 -4 3 4.9706 7 2442 2 3 4.9706 6 885 -3 2 4.9708 6 932 -1 3 4.9711 7 2315 -3 1 4.9711 6 885 -2 3 4.9711 7 2062 -1 3 4.9712 6 921 -1 3 4.9714 7 2217 -2 3 4.9717 7 2209 2 3 4.9718 6 889 -2 3 4.9722 7 1983 1 4 4.9722 6 861 -4 1 4.9725 6 884 -2 3 4.9729 7 2298 -4 1 4.9730 6 850 -1 4 4.9734 7 1831 -5 2 4.9737 7 2099 -2 3 4.9745 7 1860 2 3 4.9746 6 871 4 3 4.9746 7 2797 -2 3 4.9747 7 3412 3 1 4.9747 6 919 1 3 4.9747 7 2716 4 1 4.9748 7 1785 -4 3 4.9751 6 905 -4 1 4.9751 6 932 -3 2 4.9752 6 850 -4 3 4.9755 7 3068 -3 1 4.9755 7 2057 1 3 4.9756 7 2438 -2 3 4.9764 6 879 5 2 4.9764 6 944 1 2 4.9764 7 2604 -1 2 4.9764 7 2941 3 1 4.9770 7 3522 -1 2 4.9780 6 931 -3 1 4.9781 6 927 -1 3 4.9782 6 912 7 1 4.9786 6 854 5 2 4.9794 7 1980 -4 3 4.9796 6 923 -4 1 4.9803 7 1855 -1 4 4.9804 6 855 5 2 4.9804 6 863 -4 1 4.9805 7 2051 -1 3 4.9810 7 1831 -6 1 4.9814 7 2054 -6 1 4.9815 7 2265 -1 3 4.9817 6 854 -5 2 4.9818 6 905 1 3 4.9818 7 2254 -2 3 4.9821 6 879 2 3 4.9825 7 1983 -4 1 4.9832 6 905 2 3 4.9832 7 3511 2 1 4.9833 7 2395 1 3 4.9834 7 1801 -4 3 4.9834 6 855 3 4 4.9839 6 861 4 3 4.9844 7 2242 3 2 4.9844 7 2671 -4 1 4.9845 6 858 -4 3 4.9846 6 879 1 3 4.9846 7 2380 4 1 4.9846 7 2381 -4 1 4.9848 7 2335 -1 2 4.9848 7 1809 6 1 4.9851 6 855 -2 3 4.9854 6 901 4 1 4.9858 7 2041 4 1 4.9859 6 886 4 3 4.9862 7 2624 -4 1 4.9863 7 2503 2 3 4.9871 6 886 3 4 4.9874 7 2057 -4 3 4.9875 6 869 1 3 4.9883 7 2334 3 2 4.9885 7 2293 1 3 4.9885 6 890 5 1 4.9888 7 1858 6 1 4.9888 6 915 -1 3 4.9888 7 1858 -2 3 4.9888 6 915 1 3 4.9888 6 882 2 3 4.9888 7 2037 2 3 4.9888 7 2346 -5 1 4.9888 7 2037 -2 3 4.9889 7 1814 -5 1 4.9889 7 1882 -5 1 4.9890 7 2836 3 1 4.9893 7 2076 6 1 4.9895 7 2218 6 1 4.9895 6 849 3 4 4.9897 7 2051 -5 1 4.9897 7 2371 -1 3 4.9898 7 2486 -3 2 4.9898 7 2462 3 2 4.9898 6 900 -2 3 4.9898 7 2889 -4 1 4.9898 6 897 -2 3 4.9898 7 2528 4 1 4.9898 7 2203 -2 3 4.9898 7 3469 1 2 4.9898 7 2202 2 3 4.9904 7 2099 2 3 4.9904 7 2609 3 2 4.9909 7 2362 -5 1 4.9915 6 894 4 1 4.9918 6 890 1 3 4.9918 7 2284 5 1 4.9919 6 858 -1 4 4.9919 7 2293 -4 1 4.9921 7 2315 4 1 4.9924 7 3320 -3 1 4.9925 7 1809 -2 3 4.9927 7 2217 3 2 4.9928 6 860 -6 1 4.9933 7 1814 1 4 4.9946 6 928 3 2 4.9950 7 2302 2 3 4.9954 7 1980 -1 4 4.9954 6 860 -5 2 4.9958 7 2072 4 3 4.9959 7 2189 1 3 4.9959 7 2104 -4 3 4.9962 7 2221 -5 1 4.9963 7 1882 5 1 4.9966 7 2201 -1 3 4.9969 7 2725 -3 2 4.9972 7 2040 6 1 4.9973 7 2104 -3 4 4.9975 7 2189 -1 4 4.9975 6 954 3 1 4.9977 6 938 4 1 4.9978 7 2508 -1 2 4.9979 7 1938 4 3 4.9981 7 1980 3 2 4.9981 7 2051 5 1 4.9984 7 3425 -3 1 4.9984 7 2041 -4 3 4.9985 7 2123 2 3 4.9985 6 908 4 1 4.9985 7 2861 -1 2 4.9987 6 877 -3 4 4.9990 6 872 4 1 4.9991 7 2158 1 3 4.9997 6 882 5 1 4.9997 7 2037 5 1 4.9997 7 2230 -4 1 4.9997 7 2037 -5 1 4.9999 7 2604 -4 1 4.9999 6 944 4 1 4.9999 7 2046 2 3 4.9999 7 3068 -1 2 5.0001 7 2526 -1 3 5.0001 6 861 3 4 5.0003 7 2158 3 2 5.0004 7 1963 1 4 5.0006 7 2420 -3 2 5.0009 7 2320 5 1 5.0009 6 871 3 4 5.0010 6 860 6 1 5.0015 7 2298 -2 3 5.0016 7 2365 -5 1 5.0017 6 896 4 1 5.0020 7 2221 -1 3 5.0025 6 922 3 1 5.0027 6 938 -3 2 5.0027 7 2050 6 1 5.0027 7 2836 -3 2 5.0027 7 2806 -3 2 5.0032 6 879 -5 1 5.0033 7 2166 -3 2 5.0033 7 1831 6 1 5.0034 6 927 -2 3 5.0034 6 956 -3 1 5.0034 6 961 3 1 5.0041 7 2772 3 1 5.0042 6 896 1 4 5.0049 7 2375 -4 1 5.0052 7 2265 1 3 5.0052 7 2109 4 1 5.0053 6 900 1 3 5.0053 6 897 1 3 5.0053 7 2203 1 3 5.0053 7 2202 -1 3 5.0054 6 905 5 1 5.0054 7 2813 3 1 5.0055 6 896 -3 2 5.0055 7 2344 5 1 5.0059 7 1812 -6 1 5.0074 7 2104 -1 4 5.0076 6 893 -4 1 5.0084 6 885 1 4 5.0087 6 894 1 4 5.0088 7 1935 -5 2 5.0091 7 2221 3 2 5.0092 7 2334 1 3 5.0093 6 933 3 1 5.0095 7 2173 5 1 5.0096 7 1973 -4 1 5.0105 7 2086 5 2 5.0105 7 2898 1 3 5.0120 7 2817 -4 1 5.0123 7 2455 1 3 5.0126 7 1858 2 3 5.0126 7 1858 -6 1 5.0127 7 2293 2 3 5.0131 7 2388 -4 1 5.0133 7 2526 -4 1 5.0134 7 1858 5 2 5.0140 7 2334 -2 3 5.0141 6 869 -1 3 5.0142 6 903 4 1 5.0144 7 2515 -4 1 5.0146 7 2616 4 1 5.0147 6 928 5 1 5.0150 7 1856 3 4 5.0153 6 858 -5 2 5.0157 6 888 4 3 5.0158 6 861 -1 4 5.0161 7 1847 -4 3 5.0167 7 2772 1 3 5.0169 6 865 4 3 5.0174 6 877 3 2 5.0176 7 1845 -5 2 5.0181 7 2910 3 1 5.0182 7 1938 3 4 5.0184 7 2358 3 1 5.0190 7 2274 5 1 5.0190 7 2274 -5 1 5.0191 7 2293 5 1 5.0192 6 931 4 1 5.0194 7 1980 4 1 5.0195 7 3144 4 1 5.0196 6 890 2 3 5.0207 7 2140 -4 1 5.0207 7 2057 -5 1 5.0208 7 2254 4 1 5.0213 6 888 1 4 5.0213 7 2772 -3 1 5.0213 7 2413 -4 1 5.0227 7 2358 -4 1 5.0228 7 2221 -2 3 5.0229 6 872 1 4 5.0231 6 904 4 1 5.0232 7 2422 -4 1 5.0236 7 3126 3 1 5.0240 7 2384 -3 2 5.0247 7 2238 5 1 5.0251 6 879 6 1 5.0251 7 1855 4 3 5.0252 7 2166 -5 1 5.0258 7 2678 -4 1 5.0263 6 921 -2 3 5.0281 7 2242 -6 1 5.0283 7 2701 4 1 5.0286 7 2343 -5 1 5.0286 7 2163 -1 3 5.0286 7 2157 -3 2 5.0288 6 870 -7 1 5.0289 6 869 2 3 5.0292 6 871 -3 2 5.0294 6 893 4 1 5.0300 7 2057 4 1 5.0305 7 2254 1 3 5.0306 7 2140 4 1 5.0306 6 865 5 2 5.0308 7 1935 2 3 5.0308 7 2163 4 3 5.0312 7 2194 4 1 5.0314 7 2046 -5 1 5.0314 7 2050 -6 1 5.0323 7 2553 -3 2 5.0324 7 3031 -3 2 5.0325 7 2543 -4 1 5.0327 7 2846 -1 3 5.0328 7 1938 -1 4 5.0329 7 1938 -4 1 5.0334 6 877 2 3 5.0334 7 2996 3 1 5.0335 7 2254 3 2 5.0336 7 2343 5 1 5.0338 7 2442 4 1 5.0348 7 3511 1 2 5.0348 6 908 -2 3 5.0353 7 1937 -5 2 5.0353 7 2632 1 2 5.0354 7 2886 -3 2 5.0359 6 890 -5 1 5.0361 7 1915 -5 2 5.0362 6 862 7 1 5.0362 7 2190 4 1 5.0371 6 863 4 3 5.0371 7 1927 5 1 5.0373 6 896 -2 3 5.0376 6 926 -4 1 5.0377 6 888 -3 2 5.0381 7 2479 4 1 5.0383 7 2802 3 1 5.0386 7 2716 1 2 5.0387 7 2100 -3 2 5.0387 7 2101 3 2 5.0390 6 874 5 1 5.0390 7 2018 -5 1 5.0391 7 2573 3 1 5.0403 7 2914 3 2 5.0403 7 2542 -2 3 5.0409 7 2173 -1 3 5.0409 7 2041 -5 1 5.0415 7 2442 -1 3 5.0415 7 2062 5 2 5.0416 7 2750 -1 3 5.0420 7 2403 -3 1 5.0421 7 2272 -6 1 5.0422 7 2189 -3 4 5.0422 7 2057 -5 2 5.0427 6 909 1 3 5.0431 7 2623 -1 2 5.0438 7 2455 4 1 5.0438 7 2028 1 4 5.0442 6 907 1 3 5.0444 7 2623 1 2 5.0445 7 1980 -3 4 5.0447 7 2393 4 1 5.0448 7 2861 4 1 5.0448 7 2862 -3 1 5.0448 7 2123 -1 3 5.0453 6 904 1 4 5.0453 6 926 3 1 5.0454 7 1935 -6 1 5.0460 6 889 -4 1 5.0461 7 2428 4 1 5.0461 7 3320 3 1 5.0467 6 891 -3 4 5.0471 6 870 -5 2 5.0473 6 954 1 3 5.0477 7 2057 3 2 5.0477 7 2656 -3 2 5.0479 7 2939 -3 1 5.0480 7 1937 -7 1 5.0488 7 1980 2 3 5.0488 7 2392 1 3 5.0489 7 2946 -4 1 5.0489 7 2656 1 2 5.0490 7 2072 -2 3 5.0491 7 1980 -5 1 5.0493 7 1937 5 2 5.0504 7 2698 3 2 5.0509 6 873 1 4 5.0509 7 2307 1 4 5.0510 7 2217 -5 1 5.0511 7 2158 -5 1 5.0513 7 2491 5 1 5.0515 6 925 4 1 5.0520 7 2041 -5 2 5.0523 6 928 -3 2 5.0525 7 2460 -4 1 5.0527 6 905 -3 2 5.0528 6 885 -4 1 5.0533 7 3031 -3 1 5.0534 7 2339 -1 3 5.0534 7 2099 -5 1 5.0535 7 2898 2 3 5.0538 6 938 -5 1 5.0546 6 873 -1 4 5.0546 7 2157 1 4 5.0546 7 2210 1 3 5.0548 7 2402 3 2 5.0548 6 897 -5 1 5.0548 7 2203 -5 1 5.0548 7 2202 5 1 5.0548 6 900 -5 1 5.0548 7 2229 -5 1 5.0548 6 884 -4 1 5.0548 7 2230 5 1 5.0549 7 2791 4 1 5.0549 7 2078 -6 1 5.0554 7 2334 4 1 5.0555 7 2189 -4 3 5.0557 7 2389 4 1 5.0560 7 2072 3 4 5.0560 6 902 1 3 5.0561 7 2698 1 3 5.0566 7 2504 5 1 5.0567 7 3184 4 1 5.0569 6 869 -6 1 5.0569 7 1973 4 3 5.0576 6 943 -4 1 5.0576 7 2078 -1 3 5.0578 7 2750 -3 2 5.0578 7 2689 4 1 5.0578 7 2050 5 2 5.0579 7 1983 -1 4 5.0581 6 922 -4 1 5.0587 7 2062 4 3 5.0590 7 2694 3 1 5.0595 7 2758 3 1 5.0596 7 1980 -5 2 5.0598 7 2272 3 2 5.0600 7 2200 -4 3 5.0600 7 2584 -5 1 5.0604 7 2072 -3 2 5.0606 6 934 1 3 5.0611 6 925 -1 3 5.0612 6 890 -1 3 5.0613 6 885 4 3 5.0614 7 2308 5 1 5.0614 7 2388 5 1 5.0616 7 2354 -4 1 5.0616 6 881 1 4 5.0620 7 2173 -5 1 5.0620 7 2470 1 3 5.0622 6 871 -4 1 5.0628 7 2503 4 1 5.0633 7 2437 -5 1 5.0634 7 2101 2 3 5.0634 7 2100 -2 3 5.0636 7 2210 -4 3 5.0639 7 2201 -3 2 5.0641 7 1963 -1 4 5.0641 7 3327 -3 1 5.0642 6 894 4 3 5.0653 6 888 3 4 5.0653 6 871 -2 3 5.0654 7 2542 -3 2 5.0654 7 2750 3 1 5.0654 7 2230 -3 2 5.0654 7 2229 3 2 5.0656 6 869 -2 3 5.0658 7 2265 -2 3 5.0662 7 2217 1 3 5.0672 7 2201 -4 1 5.0675 7 3031 -2 3 5.0675 7 2585 3 2 5.0684 7 2389 -2 3 5.0686 7 2403 4 1 5.0691 6 869 6 1 5.0691 7 2262 -4 1 5.0700 6 893 1 4 5.0705 6 909 4 1 5.0705 7 2517 -1 3 5.0706 6 904 -3 2 5.0707 7 1963 5 1 5.0708 7 2413 1 3 5.0709 7 2806 -1 3 5.0713 7 2166 5 2 5.0717 6 902 4 1 5.0721 6 888 -2 3 5.0727 7 2123 -5 1 5.0727 6 894 3 4 5.0729 7 2078 -5 2 5.0730 6 932 3 2 5.0731 6 921 1 3 5.0732 6 923 5 1 5.0737 7 2380 -1 3 5.0737 7 2381 1 3 5.0742 7 2871 3 1 5.0744 7 2054 7 1 5.0745 6 921 3 2 5.0746 6 884 4 3 5.0746 7 2716 -3 2 5.0747 7 2422 1 3 5.0747 7 3216 -4 1 5.0747 7 3213 -3 1 5.0747 7 3210 3 1 5.0747 7 2636 2 3 5.0747 7 2417 -1 3 5.0749 7 2050 -5 2 5.0750 7 2420 -2 3 5.0751 6 916 3 2 5.0751 7 2346 3 2 5.0751 7 2345 3 2 5.0751 7 2402 5 1 5.0752 7 2254 -5 1 5.0754 7 2817 -3 2 5.0757 6 922 1 3 5.0757 7 2796 3 1 5.0757 7 2358 1 3 5.0759 6 894 -3 2 5.0759 7 2230 -1 3 5.0759 7 2229 1 3 5.0760 6 884 3 4 5.0767 7 2796 -1 3 5.0778 7 2119 -6 1 5.0782 6 902 -5 1 5.0782 7 2517 -2 3 5.0784 7 2057 -1 4 5.0785 6 889 1 4 5.0786 7 2679 4 1 5.0787 6 894 -2 3 5.0788 7 1983 4 3 5.0791 6 924 -5 1 5.0792 6 924 3 2 5.0793 7 2600 1 3 5.0801 6 915 2 3 5.0801 7 2339 2 3 5.0801 6 915 -2 3 5.0801 7 2078 -2 3 5.0804 7 2104 3 2 5.0808 6 886 -3 2 5.0810 7 3086 -3 2 5.0811 7 2157 -2 3 5.0811 7 2403 -1 3 5.0814 7 2123 5 2 5.0815 7 3353 3 1 5.0818 6 936 3 1 5.0822 7 2101 -4 3 5.0822 7 2100 4 3 5.0824 7 2878 3 1 5.0830 7 2780 -1 3 5.0830 7 2901 3 2 5.0831 6 904 -2 3 5.0834 7 2046 -4 3 5.0840 6 881 -3 4 5.0842 7 2062 -2 3 5.0843 7 2836 -4 1 5.0843 7 2636 4 1 5.0845 7 2028 4 3 5.0845 7 2428 -3 2 5.0851 6 885 -1 4 5.0854 6 917 -4 1 5.0855 6 879 4 3 5.0855 6 878 5 1 5.0855 6 905 5 2 5.0855 6 933 -4 1 5.0856 7 2231 -4 1 5.0856 6 884 -1 4 5.0859 7 2046 -5 2 5.0859 7 2076 2 3 5.0860 7 2553 1 2 5.0863 6 879 -1 3 5.0863 6 893 -1 4 5.0864 7 2478 -4 1 5.0865 7 1983 3 4 5.0873 7 2157 -4 1 5.0875 7 2101 4 1 5.0875 7 2100 -4 1 5.0878 7 2508 -5 1 5.0878 7 2100 3 4 5.0878 7 2101 -3 4 5.0879 7 2388 1 3 5.0881 7 2231 4 3 5.0882 7 2057 -3 4 5.0885 7 2384 5 1 5.0885 7 2166 6 1 5.0886 7 2395 -4 1 5.0886 6 908 -4 1 5.0889 6 902 -4 3 5.0893 6 911 4 1 5.0893 7 2526 -2 3 5.0895 6 935 -3 1 5.0899 6 896 -4 1 5.0899 7 2643 -1 3 5.0899 7 2640 1 3 5.0902 6 877 4 1 5.0906 6 888 -4 1 5.0908 6 909 3 2 5.0909 7 2238 -6 1 5.0912 7 2455 -4 1 5.0913 7 2265 2 3 5.0918 7 2297 3 2 5.0918 7 2296 3 2 5.0920 7 2243 1 4 5.0920 7 2412 -3 1 5.0921 7 2359 -3 2 5.0926 7 2293 -3 2 5.0930 7 2503 -1 3 5.0938 7 2775 -3 2 5.0938 7 2076 1 3 5.0945 6 907 4 1 5.0947 7 2344 -3 2 5.0949 7 2501 4 1 5.0949 7 2078 6 1 5.0949 7 2200 3 2 5.0950 6 886 -2 3 5.0955 6 907 -5 1 5.0956 7 2640 4 1 5.0956 7 2643 -4 1 5.0958 7 2358 -4 3 5.0967 7 2495 -3 1 5.0968 7 2857 -3 1 5.0973 7 2099 5 1 5.0975 7 2086 7 1 5.0975 6 932 2 3 5.0976 7 2568 -1 3 5.0989 7 2806 -2 3 5.0989 6 919 5 1 5.0990 6 944 -5 1 5.0990 7 2604 5 1 5.0992 7 2772 -1 3 5.0993 6 911 1 3 5.0995 7 2695 -3 2 5.0997 7 2984 -1 2 5.0997 7 2218 -6 1 5.0999 6 912 8 1 5.0999 6 911 3 2 5.0999 7 2320 -3 2 5.1009 7 2841 1 3 5.1014 7 2455 2 3 5.1015 6 907 3 2 5.1018 7 2420 4 1 5.1021 7 2556 -1 3 5.1024 7 2210 4 1 5.1025 7 2201 4 3 5.1026 7 2210 -5 1 5.1026 6 918 1 3 5.1028 7 3031 -1 3 5.1028 7 2393 -5 1 5.1030 6 932 -2 3 5.1030 6 903 -4 1 5.1032 6 926 1 3 5.1035 6 919 -4 1 5.1036 6 902 3 2 5.1038 7 2200 -1 4 5.1038 7 2163 1 4 5.1038 7 2296 5 1 5.1038 7 2632 -5 1 5.1043 6 903 1 4 5.1044 7 2656 -5 1 5.1044 7 2344 5 2 5.1044 7 2412 4 1 5.1047 7 2802 -1 3 5.1047 7 2163 3 4 5.1053 7 2078 1 3 5.1059 7 2315 4 3 5.1061 7 2157 4 3 5.1068 7 2371 -3 2 5.1068 7 2388 2 3 5.1068 7 2530 4 1 5.1073 7 2695 -1 3 5.1077 7 2302 -1 4 5.1079 7 2698 -1 3 5.1080 6 907 -4 3 5.1085 6 874 -1 4 5.1085 7 2018 1 4 5.1087 6 921 4 1 5.1088 7 3165 -3 1 5.1088 7 2886 -2 3 5.1089 6 917 4 1 5.1090 7 2358 -1 4 5.1092 6 900 2 3 5.1092 7 2202 -2 3 5.1092 7 2203 2 3 5.1092 6 897 2 3 5.1095 7 2076 -6 1 5.1096 6 939 3 2 5.1106 7 3247 1 2 5.1106 7 3001 2 3 5.1107 7 2051 5 2 5.1108 6 942 -5 1 5.1112 7 2923 3 1 5.1115 6 949 1 3 5.1121 7 2404 -1 3 5.1123 7 3178 -3 1 5.1124 7 2272 6 1 5.1125 7 2163 -4 1 5.1127 7 2051 -5 2 5.1127 6 958 3 2 5.1129 7 2307 -3 2 5.1129 7 3216 3 1 5.1129 7 2163 5 1 5.1139 7 2221 1 3 5.1142 7 2864 3 1 5.1142 7 2358 -3 4 5.1145 6 890 5 2 5.1145 7 2641 1 2 5.1145 7 2642 1 2 5.1151 7 2359 1 4 5.1151 7 2322 -4 3 5.1153 7 2297 -5 2 5.1153 7 2296 -5 2 5.1154 7 3215 -1 2 5.1154 7 2944 3 1 5.1158 7 2507 -4 1 5.1165 6 905 -1 3 5.1167 7 2974 -3 1 5.1168 7 2221 5 1 5.1168 7 2200 2 3 5.1169 7 2971 3 1 5.1171 7 2344 2 3 5.1171 7 2054 -7 1 5.1177 7 2750 1 3 5.1182 7 2641 -1 2 5.1182 7 2642 -1 2 5.1185 7 2735 3 2 5.1186 6 889 -1 4 5.1189 7 2997 -3 2 5.1190 7 2320 5 2 5.1191 7 2796 1 3 5.1193 7 2104 2 3 5.1194 7 2359 -2 3 5.1200 7 2194 3 4 5.1201 7 2173 -2 3 5.1202 7 2163 -3 2 5.1204 6 902 -5 2 5.1205 7 2442 -3 2 5.1205 7 2543 5 1 5.1209 7 2375 -4 3 5.1210 7 2500 -4 1 5.1212 7 2556 -2 3 5.1214 7 2163 5 2 5.1214 7 2620 -3 1 5.1214 7 2888 -4 1 5.1215 7 2104 4 1 5.1215 7 2303 4 1 5.1216 7 2871 -4 1 5.1216 7 2673 5 1 5.1238 7 2210 3 2 5.1238 7 2334 2 3 5.1240 7 2221 -5 2 5.1242 7 2072 -1 4 5.1245 6 918 5 1 5.1245 7 2279 6 1 5.1246 7 2412 4 3 5.1247 7 2553 5 1 5.1248 7 2158 2 3 5.1251 6 949 -1 3 5.1252 7 2735 1 3 5.1253 7 2974 3 2 5.1253 6 942 -3 2 5.1255 7 2393 3 2 5.1258 7 2758 -1 3 5.1259 7 3531 -3 1 5.1261 7 2735 -1 3 5.1261 6 927 1 3 5.1261 6 907 -5 2 5.1262 7 2841 3 2 5.1267 7 2210 -5 2 5.1267 7 2404 1 4 5.1269 6 946 4 1 5.1269 7 2655 -4 1 5.1269 7 2123 -2 3 5.1274 7 2272 -5 2 5.1274 7 2631 -4 1 5.1277 7 2428 -4 1 5.1278 7 2145 1 4 5.1280 6 946 -3 1 5.1280 7 2388 -3 2 5.1285 7 2344 1 3 5.1286 7 2584 3 2 5.1287 6 909 -5 1 5.1288 6 939 5 1 5.1288 7 2462 -5 1 5.1295 6 890 -2 3 5.1295 7 2703 4 1 5.1296 7 2302 -4 1 5.1298 7 2194 4 3 5.1300 7 2243 -1 4 5.1301 7 2901 4 1 5.1307 7 2627 5 1 5.1307 7 2855 -3 2 5.1308 6 905 4 3 5.1312 7 2242 6 1 5.1315 7 3051 1 3 5.1319 7 2378 -4 1 5.1319 6 950 3 1 5.1320 6 899 -6 1 5.1322 6 891 2 3 5.1325 7 2229 -5 2 5.1325 7 2230 5 2 5.1327 7 2588 -4 1 5.1330 7 2313 -6 1 5.1334 7 2780 -2 3 5.1335 7 2802 1 3 5.1335 7 2229 -4 3 5.1335 7 2231 5 1 5.1335 7 2230 4 3 5.1340 6 949 3 2 5.1343 7 3310 3 1 5.1343 7 2284 -7 1 5.1343 7 2297 -6 1 5.1343 7 2296 -6 1 5.1344 7 2403 4 3 5.1346 7 2584 -3 2 5.1346 7 2265 5 1 5.1348 7 2335 -3 2 5.1350 7 2158 -5 2 5.1360 7 2580 5 1 5.1361 7 2201 5 1 5.1362 7 2495 -1 3 5.1363 7 2157 3 4 5.1365 7 2395 -1 4 5.1369 7 2099 -5 2 5.1370 7 2369 1 4 5.1371 7 2384 -5 1 5.1373 6 949 -3 2 5.1374 7 2293 5 2 5.1375 7 2158 -4 3 5.1379 7 2238 -5 2 5.1379 7 3066 -1 2 5.1379 7 3066 1 2 5.1379 7 3528 3 1 5.1379 7 2920 -1 2 5.1379 7 2640 2 3 5.1379 7 3080 1 2 5.1379 7 3100 -3 1 5.1379 7 2346 -1 3 5.1379 7 2345 -1 3 5.1379 6 916 -1 3 5.1379 7 3469 3 1 5.1379 7 2919 -1 2 5.1379 7 3120 -1 3 5.1379 7 3106 1 3 5.1379 7 3076 -3 1 5.1379 7 2563 5 1 5.1379 7 2345 5 1 5.1379 7 2346 5 1 5.1379 6 916 5 1 5.1379 7 2874 4 1 5.1379 7 2642 5 1 5.1379 7 3214 -3 1 5.1379 7 2168 3 2 5.1379 7 2641 5 1 5.1379 7 2626 1 3 5.1380 6 918 -5 1 5.1380 7 2437 6 1 5.1381 7 2302 4 1 5.1382 7 2725 -1 3 5.1382 7 2335 5 2 5.1385 7 2572 -5 1 5.1386 7 2201 1 4 5.1387 6 947 -3 1 5.1393 7 2218 5 2 5.1396 7 2552 -6 1 5.1397 7 2585 -3 2 5.1398 6 896 -1 4 5.1400 7 2231 -2 3 5.1400 7 2242 -5 2 5.1407 7 2483 -1 3 5.1411 7 2500 -3 2 5.1412 7 2123 6 1 5.1414 6 904 4 3 5.1415 7 2403 1 4 5.1416 6 926 -4 3 5.1417 7 2609 5 1 5.1422 7 2231 5 2 5.1422 7 2573 1 3 5.1422 7 3215 -3 1 5.1423 7 2307 -2 3 5.1429 7 2785 -4 1 5.1429 7 2308 1 3 5.1430 6 904 3 4 5.1430 7 2404 4 3 5.1431 7 2293 -1 3 5.1436 6 900 -5 2 5.1436 7 2944 -3 2 5.1436 7 2202 5 2 5.1436 7 2203 -5 2 5.1436 6 897 -5 2 5.1438 7 2438 4 1 5.1439 7 2446 4 1 5.1439 6 899 6 1 5.1440 7 2401 4 3 5.1441 6 927 3 2 5.1442 7 2465 -1 3 5.1448 6 947 4 1 5.1454 7 3275 -3 1 5.1458 7 2462 5 1 5.1459 7 2354 -3 2 5.1460 6 935 4 1 5.1464 6 899 -5 2 5.1466 7 2371 -4 1 5.1466 7 2866 4 1 5.1467 7 2704 4 1 5.1467 6 936 1 3 5.1472 7 2217 -5 2 5.1474 7 2217 5 1 5.1474 7 2201 -2 3 5.1474 7 2245 -6 1 5.1475 7 2446 -3 2 5.1480 7 2168 2 3 5.1484 7 2626 3 1 5.1492 7 2430 1 3 5.1494 6 908 1 4 5.1494 7 2281 5 1 5.1494 7 2194 -4 1 5.1494 6 916 -2 3 5.1494 7 2297 -1 3 5.1494 7 2296 -1 3 5.1494 7 2547 5 1 5.1495 6 929 -1 3 5.1495 6 919 -3 2 5.1495 6 928 -5 1 5.1499 7 2200 4 1 5.1500 7 2530 -5 1 5.1500 7 2210 -1 4 5.1501 6 918 -2 3 5.1503 7 2297 -2 3 5.1503 7 2296 -2 3 5.1507 6 942 3 2 5.1510 6 909 -4 3 5.1515 7 2526 1 3 5.1516 7 2495 4 1 5.1517 7 2986 4 1 5.1520 6 919 -1 3 5.1524 7 2173 5 2 5.1524 6 899 -1 3 5.1526 7 2284 6 1 5.1527 7 2200 -3 4 5.1530 6 922 -4 3 5.1531 7 2478 2 3 5.1534 7 2557 -5 1 5.1540 7 2145 -1 4 5.1542 7 2378 -1 4 5.1545 7 3244 3 1 5.1545 7 2966 -3 1 5.1546 7 2254 -5 2 5.1546 7 2971 -3 1 5.1555 7 2392 -1 4 5.1558 7 2210 -3 4 5.1558 7 2254 2 3 5.1558 7 2947 4 1 5.1566 7 2265 -5 1 5.1566 7 2420 -4 1 5.1568 6 931 -1 3 5.1571 7 3205 -1 2 5.1572 7 2218 7 1 5.1573 7 2796 -3 2 5.1576 7 2742 4 1 5.1583 7 2457 3 2 5.1583 7 2715 -4 1 5.1583 7 2632 5 1 5.1586 7 2655 5 1 5.1586 7 2166 -1 3 5.1589 6 924 5 1 5.1590 7 2861 3 2 5.1590 7 2500 -1 3 5.1594 7 2339 -2 3 5.1596 7 3252 -3 1 5.1597 6 929 4 3 5.1600 7 2346 -5 2 5.1600 7 2308 -2 3 5.1600 6 916 -5 2 5.1600 7 2365 6 1 5.1610 7 2189 3 2 5.1611 7 2923 -3 2 5.1612 7 2322 4 1 5.1612 7 2403 3 4 5.1612 7 2623 5 1 5.1614 7 2716 -5 1 5.1623 7 2771 4 1 5.1625 7 3333 4 1 5.1626 7 2919 -4 1 5.1626 7 2920 -4 1 5.1628 7 2217 2 3 5.1629 7 2780 3 1 5.1631 7 2209 5 1 5.1631 7 2438 1 3 5.1634 6 919 4 3 5.1636 7 3458 -3 1 5.1636 6 904 -4 1 5.1640 6 934 -4 1 5.1640 7 2145 4 3 5.1642 6 896 3 4 5.1643 7 2497 4 1 5.1645 7 2362 -6 1 5.1646 7 2966 1 3 5.1647 7 2354 5 2 5.1647 6 927 4 1 5.1651 7 2194 -1 4 5.1652 7 2201 5 2 5.1653 7 2750 -2 3 5.1657 7 2200 -5 2 5.1664 7 2302 1 4 5.1664 7 2964 1 3 5.1667 7 3205 3 2 5.1672 6 922 -1 4 5.1678 7 2526 4 1 5.1686 7 2553 -6 1 5.1686 6 924 -1 3 5.1687 7 3015 1 3 5.1690 7 2660 4 1 5.1692 7 2158 5 1 5.1692 7 2315 3 4 5.1693 7 2381 -4 3 5.1693 7 2380 4 3 5.1694 7 2371 -2 3 5.1704 7 2460 2 3 5.1705 7 2526 3 2 5.1706 7 3157 -3 1 5.1706 7 3158 3 1 5.1706 7 3209 -3 1 5.1706 7 3208 -3 1 5.1708 7 2587 1 3 5.1709 7 2678 -1 3 5.1709 7 2920 1 2 5.1709 7 2919 1 2 5.1709 7 2586 -4 1 5.1709 6 918 2 3 5.1712 7 2203 5 1 5.1712 7 2202 -5 1 5.1712 6 897 5 1 5.1712 6 900 5 1 5.1712 6 899 1 3 5.1713 7 2671 -1 3 5.1713 7 2389 2 3 5.1714 7 3283 -3 1 5.1715 7 2609 -5 1 5.1716 7 2217 -4 3 5.1718 7 2528 -1 3 5.1724 7 3305 -1 2 5.1726 7 2368 -4 1 5.1734 7 2758 -2 3 5.1735 7 3327 1 2 5.1735 7 2354 -1 3 5.1737 7 2497 -2 3 5.1740 7 2971 1 3 5.1740 6 939 -3 2 5.1740 7 2404 3 4 5.1741 6 903 -1 4 5.1744 6 939 -5 1 5.1747 6 949 -4 1 5.1749 7 2281 -5 1 5.1750 6 926 -1 4 5.1751 7 2438 3 2 5.1752 6 952 -4 1 5.1752 6 899 -2 3 5.1753 7 2473 -5 1 5.1754 7 2486 1 3 5.1759 7 2497 -3 2 5.1761 7 2984 4 1 5.1762 7 2354 4 3 5.1767 7 2412 3 4 5.1768 7 2446 -2 3 5.1770 7 2411 -1 4 5.1772 6 903 4 3 5.1773 7 2203 -4 3 5.1773 7 2202 4 3 5.1773 6 900 -4 3 5.1773 6 897 -4 3 5.1777 7 2689 -4 1 5.1783 7 2274 -1 3 5.1783 7 2274 1 3 5.1784 7 2201 3 4 5.1785 6 949 4 1 5.1786 7 2573 -4 1 5.1786 7 2209 -5 1 5.1790 6 899 5 2 5.1792 7 2238 -1 3 5.1793 7 2380 -3 2 5.1793 7 2381 3 2 5.1793 7 2584 5 1 5.1795 7 2756 3 1 5.1795 7 2335 7 1 5.1796 6 902 -1 4 5.1801 7 2698 2 3 5.1802 7 2878 -4 1 5.1808 7 2238 1 3 5.1808 7 2417 -3 2 5.1810 6 909 -5 2 5.1822 7 2504 3 2 5.1824 7 2515 1 3 5.1824 6 958 4 1 5.1824 6 901 1 4 5.1829 6 933 1 3 5.1830 7 2307 -4 1 5.1831 7 2395 3 2 5.1832 7 2237 -1 3 5.1832 7 2262 4 3 5.1832 7 2641 -6 1 5.1832 7 2642 -6 1 5.1832 7 2254 -4 3 5.1833 7 2221 -4 3 5.1834 7 2599 4 1 5.1834 7 2320 -5 1 5.1837 6 936 -4 1 5.1838 7 3142 3 1 5.1839 7 2772 2 3 5.1839 7 2738 3 1 5.1839 7 3188 -1 2 5.1842 7 2437 -3 2 5.1849 7 2428 -2 3 5.1851 6 904 -1 4 5.1852 6 909 2 3 5.1855 7 3001 3 2 5.1858 7 2673 -3 2 5.1868 7 2430 -4 3 5.1873 6 951 2 3 5.1873 7 2889 1 2 5.1874 7 2824 -3 2 5.1875 7 2503 -3 2 5.1875 7 2447 -1 3 5.1876 6 907 2 3 5.1878 7 2242 -7 1 5.1880 7 2422 3 2 5.1882 7 2218 -5 2 5.1885 7 2872 3 1 5.1886 7 2673 3 2 5.1887 7 2638 4 1 5.1888 6 925 -3 2 5.1890 7 3056 1 3 5.1893 7 2772 3 2 5.1898 7 2909 -3 2 5.1899 6 905 -5 1 5.1903 6 950 -4 1 5.1904 7 2334 -5 1 5.1904 7 2307 3 4 5.1906 7 2388 5 2 5.1906 7 2344 -5 1 5.1907 7 3347 3 1 5.1910 7 2293 4 3 5.1911 6 917 -1 4 5.1916 7 2315 -3 2 5.1919 7 3100 3 1 5.1919 7 2988 -1 3 5.1920 7 2322 2 3 5.1921 7 2557 3 2 5.1922 7 2508 6 1 5.1923 6 908 4 3 5.1925 7 2947 -5 1 5.1926 7 2974 4 1 5.1927 7 2460 5 1 5.1929 7 2365 -6 1 5.1929 7 2932 3 1 5.1931 7 2315 -2 3 5.1933 7 2486 -5 1 5.1938 7 2274 -5 2 5.1938 7 2274 5 2 5.1942 7 2824 4 1 5.1942 7 2442 -4 1 5.1943 6 935 -1 3 5.1945 6 909 -1 4 5.1945 7 2384 -1 3 5.1946 7 2491 -6 1 5.1947 6 954 3 2 5.1949 7 2491 3 2 5.1952 7 2623 -6 1 5.1955 6 912 9 1 5.1957 7 2320 4 3 5.1959 7 2272 -7 1 5.1960 7 3092 3 2 5.1960 7 3347 -3 1 5.1961 7 2656 5 1 5.1962 7 2413 3 2 5.1963 6 951 4 1 5.1963 7 2486 -1 3 5.1964 7 2359 -4 1 5.1968 7 2802 -3 2 5.1973 7 2442 -2 3 5.1975 6 932 -4 1 5.1976 7 2393 1 3 5.1976 7 2552 5 1 5.1977 6 921 2 3 5.1979 7 2910 -3 2 5.1979 7 2339 -5 1 5.1980 7 2547 -6 1 5.1981 7 2395 2 3 5.1981 7 2322 -5 2 5.1982 6 916 -6 1 5.1982 7 2346 -6 1 5.1982 7 2345 -6 1 5.1982 7 2523 1 3 5.1984 7 3106 3 1 5.1984 7 3110 -3 1 5.1984 7 3103 4 1 5.1984 7 3102 -1 3 5.1984 7 3505 3 1 5.1984 7 3077 5 1 5.1984 7 2959 -3 1 5.1984 7 3320 -1 2 5.1987 7 2413 -4 3 5.1989 7 2808 -5 1 5.1989 7 2308 2 3 5.1989 6 942 5 1 5.1991 7 2689 -3 2 5.1992 7 2997 -1 3 5.2001 7 2417 -4 1 5.2001 6 933 -4 3 5.2005 6 924 -2 3 5.2005 7 2623 -3 2 5.2007 7 2759 -3 1 5.2007 7 3427 3 1 5.2007 7 2671 -2 3 5.2007 7 2381 -1 4 5.2007 7 2380 1 4 5.2009 7 2368 1 4 5.2009 7 3195 -4 1 5.2012 7 2587 -4 1 5.2013 7 2638 -5 1 5.2014 7 3077 -4 1 5.2016 7 2508 -3 2 5.2017 6 926 -3 4 5.2018 6 922 -3 4 5.2020 7 2474 -1 4 5.2022 7 2858 -5 1 5.2024 7 2322 -5 1 5.2024 7 3187 -4 1 5.2024 7 2789 3 2 5.2024 7 2787 -3 2 5.2024 7 2789 -3 2 5.2024 7 2787 3 2 5.2026 7 2344 4 3 5.2027 7 2841 2 3 5.2034 7 2411 -4 3 5.2036 7 2626 -4 1 5.2039 7 2320 -1 3 5.2044 7 2568 4 1 5.2045 7 2862 3 2 5.2046 7 2587 3 1 5.2047 7 2368 4 3 5.2048 7 2896 -5 1 5.2049 7 2457 1 3 5.2052 6 925 1 4 5.2061 7 2495 4 3 5.2063 6 907 -1 4 5.2063 7 2479 -2 3 5.2065 7 2293 -5 1 5.2067 7 2619 -4 1 5.2069 7 2634 -5 1 5.2071 7 2274 6 1 5.2071 7 2274 2 3 5.2071 7 2274 -6 1 5.2071 7 2274 -2 3 5.2073 7 3051 2 3 5.2073 7 2414 -5 1 5.2074 7 2413 -1 4 5.2075 7 2381 4 1 5.2075 7 2380 -4 1 5.2078 7 3017 1 3 5.2078 7 2661 -1 3 5.2080 7 2437 5 2 5.2082 7 2389 -5 1 5.2082 7 2388 -1 3 5.2086 7 2307 4 3 5.2087 6 929 -3 2 5.2087 7 2511 1 3 5.2089 7 2320 6 1 5.2093 7 2429 -1 4 5.2093 7 2422 -1 4 5.2093 7 2953 4 1 5.2094 7 2534 1 3 5.2097 7 2465 4 3 5.2101 7 2414 4 1 5.2102 7 2530 3 2 5.2104 7 2447 4 3 5.2106 7 2876 -3 2 5.2107 7 2616 -3 2 5.2110 7 2438 -5 1 5.2113 7 2237 1 3 5.2116 6 923 -1 3 5.2117 7 3142 -4 1 5.2118 7 2375 -1 4 5.2134 7 2775 4 1 5.2134 7 2447 5 1 5.2135 7 2517 4 1 5.2136 7 2984 -4 1 5.2141 6 950 1 3 5.2143 7 2343 -5 2 5.2144 7 2627 -5 1 5.2148 7 2725 -2 3 5.2149 7 3308 1 2 5.2156 6 954 2 3 5.2159 6 911 2 3 5.2160 7 2586 -3 2 5.2163 6 925 4 3 5.2164 7 2344 6 1 5.2168 7 2417 1 4 5.2168 6 923 -5 1 5.2171 7 2577 -4 1 5.2171 7 2238 2 3 5.2172 7 2503 -4 1 5.2174 6 951 -1 3 5.2175 7 2570 -4 1 5.2175 7 2447 -4 1 5.2176 7 2616 -1 3 5.2176 7 2872 -4 1 5.2177 7 2402 -6 1 5.2178 7 2245 2 3 5.2180 7 2588 4 1 5.2181 7 2493 5 1 5.2182 7 2401 1 4 5.2183 7 2616 -4 1 5.2183 7 2725 -4 1 5.2183 6 921 -5 1 5.2183 7 3499 -1 2 5.2183 7 2920 5 1 5.2183 7 2911 5 1 5.2183 7 2919 5 1 5.2184 7 2446 1 4 5.2184 7 2543 -3 2 5.2186 7 2640 -1 3 5.2186 7 2643 1 3 5.2188 7 2877 -1 2 5.2190 7 2469 3 2 5.2190 7 2468 3 2 5.2191 7 2600 -4 1 5.2191 7 2298 -5 1 5.2193 7 2758 -4 1 5.2196 6 915 5 1 5.2196 7 2347 -5 1 5.2196 6 915 -5 1 5.2196 7 2347 5 1 5.2196 7 2346 1 3 5.2196 7 2345 1 3 5.2196 7 2669 -5 1 5.2197 7 2392 -4 3 5.2200 7 2546 4 1 5.2200 7 2876 3 2 5.2201 7 3092 4 1 5.2204 7 2735 -3 2 5.2204 6 934 3 2 5.2210 7 2483 -4 1 5.2212 7 2422 -4 3 5.2215 7 2343 -6 1 5.2216 7 2343 -1 3 5.2217 7 2877 5 1 5.2217 7 2496 4 1 5.2217 6 927 -4 3 5.2220 7 2262 3 4 5.2221 7 2536 -4 1 5.2221 7 2422 4 1 5.2222 7 2774 4 1 5.2227 7 2497 1 4 5.2229 7 2711 -5 1 5.2230 6 917 1 4 5.2234 7 2695 -2 3 5.2235 7 3011 3 2 5.2235 7 2609 -3 2 5.2237 7 2511 -1 3 5.2237 7 2369 -1 4 5.2239 7 2375 -3 4 5.2240 7 2841 -1 3 5.2240 7 2683 -5 1 5.2241 7 2517 -5 1 5.2243 7 2603 -4 1 5.2244 7 2293 6 1 5.2247 7 2322 -1 4 5.2248 7 2698 -3 2 5.2252 7 2759 4 1 5.2252 7 2678 -2 3 5.2253 6 927 -5 1 5.2254 7 2515 3 2 5.2256 7 2891 1 3 5.2256 7 2371 1 4 5.2257 7 2343 6 1 5.2258 7 2715 -3 2 5.2260 7 2911 -5 1 5.2266 7 2633 -4 1 5.2268 7 2343 5 2 5.2269 7 2627 -3 2 5.2270 7 2343 1 3 5.2273 7 2725 1 3 5.2276 7 2392 -3 4 5.2278 7 2430 -5 1 5.2279 6 935 4 3 5.2283 7 3209 -1 2 5.2283 7 2593 4 1 5.2283 7 3208 -1 2 5.2283 7 3212 -1 2 5.2283 7 2392 4 1 5.2283 7 2344 -1 3 5.2285 7 2375 4 1 5.2287 7 2365 5 2 5.2292 7 2470 4 1 5.2294 6 923 5 2 5.2295 7 2556 4 1 5.2299 7 2371 5 1 5.2299 7 2395 -4 3 5.2300 7 3169 -3 2 5.2301 7 2738 -4 1 5.2303 7 2392 2 3 5.2303 6 952 3 2 5.2304 7 2414 -5 2 5.2304 7 2796 -2 3 5.2305 7 2587 -4 3 5.2305 7 2470 2 3 5.2307 7 3412 4 1 5.2309 7 2594 -5 1 5.2310 7 2765 -1 3 5.2311 7 3216 -1 2 5.2311 7 2874 -4 1 5.2311 7 2873 -4 1 5.2312 7 2780 -4 1 5.2312 7 2739 3 1 5.2312 7 2665 4 1 5.2312 7 2757 3 2 5.2315 7 2369 -4 1 5.2317 7 2546 1 4 5.2317 6 924 1 3 5.2318 7 2824 -4 1 5.2321 7 2378 1 4 5.2323 7 2369 4 3 5.2324 7 2430 4 1 5.2332 7 2711 6 1 5.2333 7 3143 -3 2 5.2334 6 929 -4 1 5.2334 7 2528 -3 2 5.2335 7 2515 4 1 5.2338 7 2862 1 3 5.2340 6 943 5 1 5.2342 7 2959 1 3 5.2344 7 2334 -4 3 5.2345 7 2563 -6 1 5.2352 7 2393 -4 3 5.2352 7 2558 -1 3 5.2355 7 2371 4 3 5.2355 6 928 6 1 5.2356 7 2359 -1 4 5.2356 7 2430 3 2 5.2357 6 938 5 1 5.2357 7 3066 4 1 5.2357 7 3066 -4 1 5.2359 7 2413 4 1 5.2359 7 2401 3 4 5.2367 6 961 1 3 5.2367 6 954 -2 3 5.2367 6 956 -1 3 5.2367 7 2380 3 4 5.2367 7 2381 -3 4 5.2368 7 2641 -3 2 5.2368 7 2642 -3 2 5.2369 7 2866 3 2 5.2373 7 2417 4 3 5.2376 7 2925 4 1 5.2380 7 2388 -5 1 5.2382 7 2470 -1 4 5.2382 7 2633 1 3 5.2383 7 2694 -4 1 5.2385 7 2315 5 2 5.2386 7 2462 1 3 5.2386 7 3144 -5 1 5.2387 7 2692 -4 1 5.2387 7 2739 -4 1 5.2387 6 925 -2 3 5.2390 7 2423 -4 1 5.2390 6 925 -4 1 5.2391 6 947 -1 3 5.2395 7 2486 2 3 5.2397 7 2359 3 4 5.2398 7 2568 -3 2 5.2398 7 3062 -1 3 5.2398 7 2772 -3 2 5.2399 7 2381 2 3 5.2399 7 2380 -2 3 5.2401 7 2825 -4 1 5.2401 7 2517 3 2 5.2404 7 3015 2 3 5.2404 7 2709 2 3 5.2404 7 2517 1 3 5.2404 7 2411 -3 4 5.2405 7 2481 -4 1 5.2411 7 3245 3 1 5.2411 7 3017 2 3 5.2416 7 2430 -5 2 5.2419 7 2437 -6 1 5.2424 7 3274 1 2 5.2426 7 2632 3 2 5.2426 7 2395 -3 4 5.2428 7 2507 4 1 5.2429 7 2585 -5 1 5.2429 7 2334 -5 2 5.2432 7 3198 -3 2 5.2433 7 2585 5 1 5.2434 6 946 -1 3 5.2434 7 2636 -1 3 5.2443 7 2438 -4 3 5.2448 7 2861 -4 1 5.2448 7 2862 -1 3 5.2448 7 2417 -2 3 5.2449 6 932 4 1 5.2449 7 2447 -3 2 5.2450 7 2369 3 4 5.2450 7 2679 -1 3 5.2452 7 3142 -3 2 5.2456 7 2508 5 2 5.2456 7 2359 4 3 5.2464 7 2587 -1 4 5.2465 7 2413 2 3 5.2467 7 2422 2 3 5.2469 7 2679 -3 2 5.2474 7 2757 4 1 5.2475 7 2673 -5 1 5.2476 7 2719 -4 1 5.2478 7 2599 -4 1 5.2482 7 2447 5 2 5.2485 6 947 4 3 5.2485 7 2620 -1 3 5.2488 7 2384 5 2 5.2488 7 2469 1 3 5.2488 7 2467 5 1 5.2488 7 2468 1 3 5.2490 6 922 3 2 5.2490 7 2787 4 1 5.2490 7 2789 -4 1 5.2490 7 2787 -4 1 5.2490 7 2789 4 1 5.2490 7 3126 3 2 5.2492 7 2486 5 1 5.2493 7 2501 -5 1 5.2493 6 931 4 3 5.2493 7 3272 -1 2 5.2496 7 2872 -1 3 5.2497 7 2478 5 1 5.2499 7 2523 -1 4 5.2501 7 2813 -4 1 5.2503 7 2423 3 4 5.2505 7 2378 -4 3 5.2505 7 2679 -4 1 5.2506 7 2877 1 2 5.2507 7 3010 -3 2 5.2508 6 924 -5 2 5.2511 6 929 5 2 5.2512 7 2542 4 1 5.2512 7 2413 -3 4 5.2514 7 2556 3 2 5.2515 7 2439 1 4 5.2518 7 2725 3 2 5.2520 7 2556 -5 1 5.2521 6 927 -5 2 5.2521 7 2734 4 1 5.2523 7 2465 -3 2 5.2526 7 2358 3 2 5.2528 7 2536 4 1 5.2529 7 2946 1 2 5.2535 7 3260 -1 2 5.2535 7 2511 -5 1 5.2536 7 2491 6 1 5.2540 7 2471 1 4 5.2541 6 927 2 3 5.2542 7 2428 1 4 5.2543 7 2556 1 3 5.2544 7 2507 2 3 5.2544 7 2570 -3 2 5.2548 7 3219 4 1 5.2548 7 2589 -3 4 5.2550 7 2557 -1 3 5.2551 7 2619 5 1 5.2552 7 2600 3 2 5.2553 7 2586 -1 3 5.2555 7 2696 3 1 5.2556 7 2402 -1 3 5.2560 7 2543 1 3 5.2560 7 2708 -5 1 5.2562 6 952 -2 3 5.2562 6 956 -3 2 5.2562 6 961 3 2 5.2564 7 2375 -5 2 5.2564 7 2401 -4 1 5.2565 7 2721 4 1 5.2565 7 2677 4 1 5.2567 7 2395 4 1 5.2568 7 2619 -3 2 5.2570 7 2736 3 1 5.2575 6 921 -4 3 5.2577 7 2528 -4 1 5.2581 6 949 2 3 5.2581 6 925 3 4 5.2583 7 2365 -5 2 5.2585 7 2384 -2 3 5.2585 7 2462 -1 3 5.2588 7 2701 -3 2 5.2590 7 2486 -2 3 5.2590 7 2947 -1 2 5.2592 7 2388 4 3 5.2593 6 934 2 3 5.2594 7 2855 -4 1 5.2595 7 2420 1 4 5.2598 7 2500 5 1 5.2598 6 929 5 1 5.2599 6 946 4 3 5.2600 7 2754 3 2 5.2602 7 2380 5 1 5.2602 7 2381 -5 1 5.2602 6 936 -1 4 5.2604 7 2402 6 1 5.2605 7 3191 3 1 5.2606 7 2542 -4 1 5.2608 7 2735 2 3 5.2609 7 2966 3 2 5.2610 6 938 3 2 5.2610 7 2876 4 1 5.2615 7 2605 -5 1 5.2615 7 2889 5 1 5.2616 6 928 1 3 5.2616 6 934 -1 4 5.2618 7 2727 -3 1 5.2618 7 2899 1 3 5.2620 7 2501 2 3 5.2624 7 2704 -5 1 5.2624 7 2517 -5 2 5.2625 7 2378 -3 4 5.2626 7 2429 1 4 5.2626 7 2503 -2 3 5.2628 7 2585 1 3 5.2631 7 2446 -4 1 5.2632 7 3398 1 2 5.2632 7 2504 6 1 5.2637 7 2465 5 2 5.2637 6 928 -1 3 5.2639 7 2851 5 1 5.2639 7 2368 3 4 5.2642 6 952 1 3 5.2642 7 2586 5 1 5.2642 7 2517 -4 3 5.2643 7 2696 -4 1 5.2646 7 2570 5 1 5.2647 7 2698 4 1 5.2651 7 2546 -3 2 5.2653 7 2640 -3 2 5.2653 7 2643 3 2 5.2654 7 2589 -4 3 5.2657 7 2493 4 3 5.2658 7 2465 5 1 5.2659 7 2623 3 2 5.2659 7 2988 -2 3 5.2659 7 2594 5 1 5.2661 7 2446 4 3 5.2664 7 2381 -5 2 5.2664 7 2380 5 2 5.2666 7 2656 3 2 5.2667 7 2511 2 3 5.2667 7 3200 4 1 5.2668 7 2393 5 1 5.2669 7 2543 2 3 5.2670 7 2430 -1 4 5.2670 6 931 -3 2 5.2670 7 2802 -2 3 5.2671 7 2438 -5 2 5.2672 7 2596 1 3 5.2672 7 2874 -3 2 5.2674 7 2534 4 1 5.2675 7 2558 -3 2 5.2676 7 2388 6 1 5.2677 7 2678 4 1 5.2682 7 2411 4 1 5.2682 6 949 -2 3 5.2685 7 2695 -4 1 5.2685 7 3188 -3 1 5.2687 7 2465 -4 1 5.2688 7 2572 -1 3 5.2688 7 2638 -1 3 5.2688 7 2594 -1 3 5.2688 7 2558 -4 1 5.2693 7 2756 1 3 5.2694 7 2422 -3 4 5.2696 7 2546 -2 3 5.2701 7 2553 -5 2 5.2702 7 2588 2 3 5.2702 7 2589 -1 4 5.2702 7 2824 3 2 5.2703 6 921 -5 2 5.2704 6 928 5 2 5.2705 7 2889 -5 1 5.2705 7 2604 -5 1 5.2705 6 944 5 1 5.2706 7 3247 -1 3 5.2706 7 2923 -2 3 5.2706 7 2771 -5 1 5.2709 7 2568 1 4 5.2709 7 2371 5 2 5.2709 7 2523 -3 4 5.2712 7 2403 -3 2 5.2712 6 924 -6 1 5.2712 7 2423 5 1 5.2713 6 922 2 3 5.2713 6 944 -5 2 5.2713 7 2604 5 2 5.2713 7 3184 -5 1 5.2716 7 2846 3 1 5.2718 7 2722 5 1 5.2721 7 2402 -2 3 5.2723 7 2736 -4 1 5.2725 7 2468 -4 3 5.2725 7 2469 -4 3 5.2727 7 2507 -1 4 5.2727 6 944 3 2 5.2727 7 2604 -3 2 5.2729 7 2417 5 1 5.2730 7 2442 4 3 5.2730 6 938 -6 1 5.2733 6 953 3 1 5.2734 7 2668 -5 1 5.2734 7 2384 6 1 5.2734 7 3462 -3 1 5.2735 7 2412 -2 3 5.2738 7 3252 -1 2 5.2738 7 2872 -2 3 5.2741 7 2695 4 1 5.2746 7 2971 -1 3 5.2747 7 2414 -3 4 5.2750 7 2515 -4 3 5.2751 7 2886 3 1 5.2751 7 2735 4 1 5.2751 7 2670 -1 3 5.2752 7 3245 1 2 5.2753 7 2941 3 2 5.2758 7 2543 -5 1 5.2759 7 2473 6 1 5.2759 7 3074 1 3 5.2760 6 923 6 1 5.2760 6 952 4 1 5.2763 7 2483 4 3 5.2763 6 935 1 4 5.2767 7 3189 3 2 5.2768 7 3260 3 2 5.2770 7 2481 1 4 5.2770 7 2772 -2 3 5.2770 7 2878 -1 3 5.2771 7 2716 5 1 5.2771 7 2530 -5 2 5.2773 7 2875 -1 3 5.2773 7 2875 1 3 5.2774 7 2698 -2 3 5.2776 7 2557 -2 3 5.2777 6 935 3 4 5.2781 7 2528 4 3 5.2781 7 2674 5 1 5.2782 7 2600 2 3 5.2782 7 2580 -6 1 5.2783 7 2642 3 2 5.2783 7 2641 3 2 5.2784 7 3247 -2 3 5.2784 7 2878 -2 3 5.2788 7 2442 1 4 5.2789 7 2420 -1 4 5.2791 7 2536 1 4 5.2793 7 2639 1 4 5.2795 7 3132 1 3 5.2795 7 3247 -3 2 5.2796 7 2758 1 3 5.2796 7 2553 3 2 5.2797 7 2500 -2 3 5.2798 7 3362 3 1 5.2799 7 2558 4 3 5.2800 7 3168 3 2 5.2800 7 2542 -1 4 5.2801 7 2389 -5 2 5.2802 7 2678 3 2 5.2802 7 2462 2 3 5.2804 7 2802 3 2 5.2806 7 2806 -4 1 5.2807 7 2404 5 2 5.2807 7 2508 -6 1 5.2807 7 2754 -5 1 5.2809 7 2497 -4 1 5.2810 7 2567 -4 1 5.2810 7 3162 3 2 5.2816 7 2534 3 2 5.2819 6 926 3 2 5.2824 7 2674 -5 1 5.2829 7 2534 -4 3 5.2831 7 2403 -2 3 5.2834 7 2596 -4 3 5.2836 7 2538 -4 1 5.2837 7 3185 3 1 5.2838 6 951 -3 2 5.2849 7 2775 3 2 5.2853 6 933 3 2 5.2855 7 3493 -2 1 5.2855 7 2703 -1 3 5.2855 7 2677 -5 1 5.2855 7 3491 -2 1 5.2855 7 2678 1 3 5.2856 7 2401 5 2 5.2860 7 2389 -4 3 5.2863 7 2946 5 1 5.2869 7 2735 -2 3 5.2871 7 2587 -3 4 5.2872 7 2417 3 4 5.2872 7 2687 3 2 5.2872 7 3266 3 1 5.2874 7 2460 -5 1 5.2874 7 3361 1 2 5.2876 7 3075 -4 1 5.2877 7 3245 -4 1 5.2878 7 2568 -2 3 5.2878 7 2722 1 3 5.2879 7 2430 -3 4 5.2879 7 2530 1 3 5.2879 7 2573 3 2 5.2879 7 2573 -4 3 5.2880 7 3195 4 1 5.2882 7 2496 -5 1 5.2883 6 926 -5 2 5.2883 7 2597 4 1 5.2884 7 2526 2 3 5.2884 7 2423 5 2 5.2887 7 2750 3 2 5.2887 6 953 -4 1 5.2889 7 2787 -1 3 5.2889 7 2503 1 4 5.2889 7 2789 1 3 5.2889 7 2787 1 3 5.2889 7 2789 -1 3 5.2890 7 3120 -2 3 5.2893 7 2801 4 1 5.2894 7 2467 4 3 5.2895 7 2719 1 3 5.2895 7 3198 3 1 5.2895 7 2874 3 2 5.2896 7 2438 2 3 5.2897 7 2437 7 1 5.2899 7 2584 -6 1 5.2900 7 2984 3 2 5.2900 7 2703 -3 2 5.2901 7 2570 -1 3 5.2904 7 2939 -3 2 5.2906 7 2543 5 2 5.2907 6 929 -2 3 5.2914 7 2572 5 1 5.2915 7 2692 4 1 5.2917 7 2483 -2 3 5.2918 7 2497 -1 4 5.2919 6 923 -2 3 5.2920 7 2857 4 1 5.2922 7 2413 -5 1 5.2923 7 2877 -5 1 5.2923 7 2716 3 2 5.2924 7 3075 5 1 5.2925 7 2422 -5 1 5.2928 7 3411 3 1 5.2931 7 3007 -3 1 5.2931 7 2696 1 3 5.2935 6 936 -4 3 5.2942 7 2986 -4 1 5.2942 7 3226 4 1 5.2948 7 2636 -4 1 5.2949 7 2671 4 1 5.2951 6 922 4 1 5.2952 7 2556 -5 2 5.2952 7 2538 -3 2 5.2953 7 2721 -5 1 5.2953 7 2577 4 1 5.2953 7 2467 5 2 5.2956 7 2704 3 2 5.2957 7 2446 3 4 5.2962 7 2428 5 1 5.2963 7 2817 4 1 5.2965 7 2748 -4 1 5.2965 7 2797 1 3 5.2969 7 2638 -2 3 5.2969 6 931 -4 1 5.2971 6 934 4 1 5.2972 7 2742 -1 3 5.2976 7 2479 5 1 5.2977 7 3202 -3 2 5.2983 6 928 2 3 5.2984 7 2429 -4 3 5.2984 7 3266 1 3 5.2985 7 2791 1 3 5.2987 7 2526 -5 1 5.2989 7 2842 4 1 5.2990 7 2465 1 4 5.2991 7 2709 -4 1 5.2993 7 2460 5 2 5.2995 7 2521 -1 4 5.2996 7 3325 3 1 5.2997 7 2689 -1 3 5.2999 7 2455 5 1 5.3000 7 2557 5 1 5.3000 7 2889 -1 2 5.3004 6 926 2 3 5.3005 7 3056 2 3 5.3005 7 3308 3 1 5.3007 7 2528 5 1 5.3007 7 2511 -2 3 5.3007 7 2570 4 3 5.3008 7 3353 -4 1 5.3008 7 2536 -1 4 5.3014 7 2534 -5 1 5.3015 7 2636 -3 2 5.3018 7 2572 -2 3 5.3019 7 2474 -3 4 5.3022 7 2564 5 1 5.3022 7 2442 5 1 5.3022 7 3157 4 1 5.3025 7 2470 -4 3 5.3028 7 2594 -2 3 5.3030 7 2447 1 4 5.3031 7 2930 3 1 5.3032 7 2596 4 1 5.3035 7 2744 3 2 5.3036 7 2596 3 2 5.3038 7 2775 -5 1 5.3039 7 2687 2 3 5.3039 7 2997 -2 3 5.3040 7 2570 5 2 5.3042 7 2558 5 1 5.3044 7 2715 5 1 5.3045 7 2538 5 1 5.3046 7 2722 2 3 5.3048 7 2515 -5 1 5.3048 7 2474 1 4 5.3052 6 929 3 4 5.3052 7 2439 -1 4 5.3053 7 2585 -1 3 5.3056 7 3283 -2 3 5.3056 7 2831 3 2 5.3056 7 2603 -1 4 5.3056 7 2739 1 3 5.3057 7 2556 -4 3 5.3059 7 2553 6 1 5.3062 7 2722 -4 1 5.3062 7 2750 2 3 5.3063 7 2939 -2 3 5.3068 7 2780 1 3 5.3071 7 3274 -3 2 5.3072 7 2473 -1 3 5.3072 7 2696 -4 3 5.3077 6 953 1 3 5.3078 7 2462 -2 3 5.3091 7 2457 2 3 5.3092 7 2481 -1 4 5.3097 6 953 -4 3 5.3100 7 2876 -4 1 5.3100 7 2471 3 4 5.3101 6 928 -2 3 5.3103 7 3068 4 1 5.3104 7 2465 -2 3 5.3107 7 2797 3 1 5.3107 7 2600 -1 4 5.3108 7 3147 1 3 5.3108 7 2886 1 3 5.3109 7 2770 -4 1 5.3109 7 2557 -5 2 5.3110 7 2677 3 2 5.3111 7 3008 -5 1 5.3112 7 2626 -1 4 5.3118 7 2855 3 2 5.3123 7 2944 -1 3 5.3123 7 2855 4 1 5.3123 7 2671 3 2 5.3126 6 938 -5 2 5.3126 7 2756 -4 1 5.3126 6 943 -5 1 5.3131 7 2633 -1 4 5.3133 7 3288 -3 1 5.3133 7 2446 -1 4 5.3134 7 2643 4 1 5.3134 7 2640 -4 1 5.3139 7 2796 3 2 5.3142 7 3320 1 2 5.3145 7 2439 4 3 5.3147 7 2802 2 3 5.3147 6 929 5 3 5.3148 7 2671 1 3 5.3149 7 2626 -4 3 5.3150 7 2740 -3 2 5.3150 7 2946 -5 1 5.3151 7 2862 -3 2 5.3153 7 2875 -3 2 5.3153 7 2875 3 2 5.3166 7 2971 3 2 5.3168 7 2720 1 3 5.3169 7 2866 -4 1 5.3169 7 2447 3 4 5.3172 7 3075 1 2 5.3172 7 2568 -4 1 5.3174 7 2457 5 1 5.3176 7 2478 -5 1 5.3179 7 2696 -3 4 5.3181 7 3238 -1 3 5.3181 7 2528 -2 3 5.3183 7 2462 -6 1 5.3184 7 3310 -5 1 5.3185 7 3045 3 2 5.3185 7 2791 2 3 5.3185 7 3046 4 1 5.3189 7 2542 1 4 5.3191 7 2871 -1 3 5.3191 7 2632 -6 1 5.3192 6 942 -6 1 5.3192 7 2585 2 3 5.3195 7 2717 -4 1 5.3197 7 2719 -4 3 5.3198 7 2528 5 2 5.3199 7 2738 1 3 5.3199 7 2604 6 1 5.3199 6 944 -6 1 5.3201 7 2696 -1 4 5.3201 7 2911 -1 2 5.3204 7 3074 -1 3 5.3207 7 2840 3 1 5.3209 7 2857 -1 3 5.3209 6 932 1 4 5.3209 6 933 -3 4 5.3214 7 2479 -5 1 5.3215 7 2573 -1 4 5.3217 7 2558 5 2 5.3220 6 931 5 2 5.3222 6 931 5 1 5.3222 7 2871 -2 3 5.3223 7 2552 -7 1 5.3226 7 2470 -3 4 5.3227 7 2691 -4 1 5.3227 7 2563 6 1 5.3228 6 936 -3 4 5.3228 7 2515 2 3 5.3230 7 2703 -4 1 5.3230 7 2866 -3 2 5.3231 7 2462 5 2 5.3232 6 939 6 1 5.3233 7 2910 3 2 5.3233 7 2642 6 1 5.3233 7 2641 6 1 5.3235 7 2573 2 3 5.3236 7 3310 4 1 5.3236 7 2687 -4 1 5.3237 7 2740 -1 3 5.3238 7 2580 6 1 5.3238 7 2547 6 1 5.3241 7 2727 4 1 5.3241 7 2538 1 4 5.3242 6 946 1 4 5.3245 7 2483 5 1 5.3249 7 2988 3 1 5.3249 6 931 -2 3 5.3250 7 2711 -3 2 5.3251 6 932 -1 4 5.3251 6 933 -1 4 5.3253 7 2616 5 1 5.3253 7 3477 -1 2 5.3256 7 2925 -4 1 5.3256 7 2840 1 3 5.3258 7 2864 1 3 5.3259 7 2909 -4 1 5.3260 7 2534 -1 4 5.3264 7 2656 -6 1 5.3265 7 2557 1 3 5.3269 7 2796 2 3 5.3269 7 2728 3 2 5.3269 7 2960 -4 1 5.3270 7 3068 3 2 5.3270 7 2959 4 1 5.3271 7 2501 5 1 5.3274 7 2596 -5 1 5.3275 7 2770 5 1 5.3277 7 3158 -3 2 5.3282 7 2947 1 2 5.3282 6 933 4 1 5.3283 7 2495 -3 2 5.3284 6 946 3 4 5.3284 7 2543 6 1 5.3285 7 2709 4 1 5.3288 7 2507 -4 3 5.3288 7 3105 -4 1 5.3289 7 2791 -4 1 5.3292 7 2671 -5 1 5.3292 7 2996 -3 2 5.3293 7 2530 5 1 5.3293 7 2971 2 3 5.3293 7 2503 4 3 5.3293 7 2957 4 1 5.3295 7 2946 -1 2 5.3295 7 3007 1 3 5.3297 7 2925 3 2 5.3298 7 2462 -5 2 5.3300 6 936 3 2 5.3300 7 3257 -4 1 5.3301 7 2615 3 2 5.3302 7 3144 5 1 5.3302 7 2840 -4 1 5.3305 7 3110 -2 3 5.3305 7 3106 2 3 5.3306 7 2729 -3 2 5.3307 7 2816 1 3 5.3308 7 2701 5 1 5.3308 6 931 1 4 5.3309 7 3007 4 1 5.3310 7 2503 5 1 5.3311 7 2846 -4 1 5.3314 7 2638 -5 2 5.3314 7 2515 -5 2 5.3315 7 2894 -5 1 5.3316 7 2725 4 1 5.3318 7 2538 5 2 5.3320 7 2546 4 3 5.3321 7 3027 -1 3 5.3321 7 2553 -7 1 5.3323 7 2546 3 4 5.3332 7 3283 -1 3 5.3334 7 3390 3 1 5.3334 7 3209 4 1 5.3334 7 2913 -3 2 5.3334 7 3210 -3 1 5.3334 7 2721 -2 3 5.3334 7 3207 -3 1 5.3334 7 3208 4 1 5.3334 7 3214 -3 2 5.3334 7 3109 4 1 5.3334 7 3377 -3 1 5.3334 7 2668 -2 3 5.3334 7 3106 -3 1 5.3334 7 3107 -4 1 5.3334 7 3378 -3 1 5.3334 7 3110 3 1 5.3334 7 3452 3 1 5.3334 7 3453 -3 1 5.3334 7 3217 3 1 5.3334 7 3505 -3 1 5.3334 7 3261 4 1 5.3334 7 3262 3 1 5.3334 7 3331 -2 3 5.3334 7 2683 -2 3 5.3334 7 2576 -5 1 5.3334 7 2575 -1 3 5.3334 7 2574 -1 3 5.3336 7 2523 3 2 5.3337 7 2541 1 4 5.3338 7 3086 3 1 5.3346 6 950 -1 4 5.3346 7 2660 -4 1 5.3352 7 2623 6 1 5.3354 7 2824 -1 3 5.3355 7 3178 -3 2 5.3360 7 2594 1 3 5.3361 7 2910 -4 1 5.3361 7 3434 3 1 5.3364 6 951 -4 1 5.3365 7 3238 -3 2 5.3367 7 2534 -5 2 5.3367 7 2511 5 2 5.3368 7 2572 -5 2 5.3368 7 2728 2 3 5.3368 7 2620 1 4 5.3370 7 2964 2 3 5.3371 7 2728 -4 1 5.3372 7 2564 4 3 5.3372 7 2500 4 3 5.3373 7 2620 3 4 5.3373 7 2596 -5 2 5.3374 7 2668 5 1 5.3378 7 3249 -4 1 5.3379 7 2851 -5 1 5.3384 7 2500 5 2 5.3386 7 2495 -2 3 5.3391 7 2592 1 4 5.3392 6 958 1 3 5.3401 7 2493 -2 3 5.3402 7 2616 -2 3 5.3402 6 933 -5 2 5.3403 7 3047 4 1 5.3403 7 2609 6 1 5.3408 7 2486 -5 2 5.3412 7 2742 -3 2 5.3412 7 2564 5 2 5.3412 7 2670 -3 2 5.3415 7 2627 6 1 5.3424 6 933 2 3 5.3432 7 2473 -6 1 5.3432 7 2478 5 2 5.3441 7 2632 -5 2 5.3442 7 2638 1 3 5.3446 7 3011 4 1 5.3448 7 2804 3 2 5.3448 7 2508 7 1 5.3449 7 2909 -1 3 5.3450 7 2481 4 3 5.3450 7 2889 3 2 5.3451 7 2899 2 3 5.3452 7 2735 -4 1 5.3452 7 2577 -1 4 5.3452 7 2925 -3 2 5.3454 7 2855 -1 3 5.3455 7 3007 2 3 5.3455 7 3008 5 1 5.3456 7 3126 -3 1 5.3456 7 2966 2 3 5.3458 6 935 -3 2 5.3459 7 2609 5 2 5.3461 7 2532 2 3 5.3462 7 2944 -4 1 5.3462 7 2910 1 3 5.3462 7 2750 -4 1 5.3462 7 2656 -5 2 5.3463 7 2515 -1 4 5.3463 7 2797 -4 1 5.3465 7 3086 -4 1 5.3465 7 2661 -3 2 5.3467 7 2530 -4 3 5.3467 7 2619 5 2 5.3467 7 2729 -2 3 5.3470 7 2500 -5 1 5.3473 7 2572 -6 1 5.3475 7 2836 -1 3 5.3476 7 2572 1 3 5.3478 7 2764 5 1 5.3481 7 2964 -1 3 5.3482 7 2698 -4 1 5.3482 7 2543 -1 3 5.3488 7 3490 3 1 5.3488 7 3492 3 1 5.3488 7 2677 1 3 5.3488 7 2678 -5 1 5.3490 7 2877 -3 2 5.3498 6 936 2 3 5.3502 7 2909 3 2 5.3503 7 2899 -1 3 5.3505 7 2523 4 1 5.3506 7 2538 3 4 5.3508 7 3128 3 1 5.3511 7 2592 -3 2 5.3513 7 2687 -1 4 5.3515 7 3102 -2 3 5.3516 7 2727 1 4 5.3521 7 2673 6 1 5.3528 7 2996 3 2 5.3529 6 934 -4 3 5.3529 7 3080 3 2 5.3532 7 2661 4 3 5.3533 7 2862 2 3 5.3539 7 2547 -7 1 5.3539 6 939 -6 1 5.3542 7 2930 -4 1 5.3544 7 2813 1 3 5.3545 7 2986 3 2 5.3555 7 2785 6 1 5.3556 7 2552 6 1 5.3558 7 2758 3 2 5.3561 7 2858 5 1 5.3561 7 3504 -3 1 5.3565 7 2715 1 3 5.3565 7 2600 4 1 5.3565 7 2983 -1 3 5.3566 7 2639 -3 2 5.3568 7 2627 5 2 5.3569 7 2875 -4 1 5.3569 7 2875 4 1 5.3570 7 2530 -6 1 5.3570 7 2574 -5 1 5.3570 7 2575 -5 1 5.3571 7 2808 5 1 5.3572 7 2592 -4 1 5.3576 7 2689 5 1 5.3577 6 944 -2 3 5.3577 7 2604 2 3 5.3579 7 2573 -3 4 5.3583 7 2912 4 1 5.3583 7 2517 2 3 5.3583 7 2694 3 2 5.3583 7 2759 -1 3 5.3584 7 2584 6 1 5.3584 7 2526 -4 3 5.3584 7 2523 -5 2 5.3585 7 2765 4 1 5.3586 7 2888 4 1 5.3588 7 2841 -3 2 5.3589 7 2817 -5 1 5.3589 7 3462 -1 2 5.3589 7 3047 3 2 5.3589 7 2640 -2 3 5.3589 7 2642 -7 1 5.3589 7 2641 -7 1 5.3589 7 3184 5 1 5.3589 7 3200 -4 1 5.3589 7 2586 5 2 5.3589 6 950 -4 3 5.3591 7 3276 2 3 5.3592 7 2747 -4 1 5.3593 7 2920 3 2 5.3593 7 2919 3 2 5.3594 7 2670 -4 1 5.3597 7 2534 -3 4 5.3600 7 2580 -5 2 5.3601 7 2557 -6 1 5.3603 7 2655 -5 1 5.3611 7 2593 -1 4 5.3612 7 2546 -4 1 5.3612 7 3187 4 1 5.3615 7 3191 1 3 5.3618 7 2655 5 2 5.3619 6 947 1 4 5.3620 7 2603 4 1 5.3620 7 2806 1 3 5.3624 7 2632 6 1 5.3624 7 2725 2 3 5.3624 7 3455 1 2 5.3626 7 2623 -7 1 5.3627 7 2511 6 1 5.3627 7 2941 -3 2 5.3629 7 2722 5 2 5.3632 7 3288 3 1 5.3636 6 961 2 3 5.3636 6 956 2 3 5.3637 7 2573 4 1 5.3641 6 938 -1 3 5.3642 7 2523 -5 1 5.3642 7 2615 4 1 5.3643 7 2586 -2 3 5.3643 7 2639 -2 3 5.3643 7 2910 -1 3 5.3645 7 2597 -1 4 5.3647 7 3001 -3 1 5.3648 6 947 3 4 5.3650 7 2948 -5 1 5.3650 7 2559 3 4 5.3651 7 2719 -1 4 5.3652 7 2722 -3 2 5.3655 7 2804 -4 1 5.3656 7 2742 -4 1 5.3666 7 2909 4 1 5.3666 7 2761 -1 3 5.3668 7 3076 1 3 5.3668 7 2819 4 1 5.3669 7 3325 1 3 5.3674 7 2851 3 2 5.3679 7 2584 -5 2 5.3680 7 2558 -2 3 5.3687 7 2665 -4 1 5.3688 7 3221 1 2 5.3688 7 3228 -1 2 5.3690 7 3012 -3 1 5.3691 6 944 -1 3 5.3691 7 2604 1 3 5.3692 7 3398 -3 1 5.3692 7 2626 2 3 5.3692 7 2626 3 2 5.3694 7 2639 3 4 5.3698 7 2609 -6 1 5.3700 7 2600 -4 3 5.3701 7 2861 5 1 5.3702 7 3335 -4 1 5.3703 7 3188 4 1 5.3703 7 3189 -3 1 5.3703 7 2546 -1 4 5.3705 7 3325 3 2 5.3705 7 2914 -3 1 5.3707 7 2615 2 3 5.3711 7 3240 3 1 5.3712 6 939 1 3 5.3714 7 2568 4 3 5.3717 7 2603 -4 3 5.3717 7 2741 -2 3 5.3718 7 2759 4 3 5.3721 7 3062 3 1 5.3722 7 2526 -1 4 5.3723 6 958 -4 1 5.3724 7 3143 -1 3 5.3724 7 3199 -5 1 5.3724 6 943 -2 3 5.3725 7 2556 2 3 5.3727 7 2577 1 4 5.3728 7 2729 1 4 5.3730 7 2585 -2 3 5.3730 6 942 6 1 5.3734 6 951 -2 3 5.3734 6 938 -2 3 5.3734 7 2694 -1 4 5.3735 7 2593 -4 1 5.3736 6 934 1 4 5.3736 7 2996 1 3 5.3737 7 2739 -1 4 5.3737 7 2599 1 4 5.3738 7 2817 -1 3 5.3738 7 2679 -2 3 5.3739 7 2639 4 3 5.3740 7 2631 -5 1 5.3740 7 3062 -2 3 5.3743 7 2691 4 1 5.3745 7 2794 -5 1 5.3747 7 2739 -4 3 5.3747 7 2717 4 1 5.3749 7 3514 -3 1 5.3749 7 3200 -1 2 5.3751 7 2836 4 1 5.3756 7 2636 5 1 5.3758 7 2655 2 3 5.3760 7 2891 3 2 5.3764 6 934 -3 4 5.3764 6 943 5 2 5.3767 7 3395 4 1 5.3768 7 2677 -5 2 5.3769 6 935 -2 3 5.3772 7 2785 -5 1 5.3773 7 3056 -3 1 5.3774 7 2579 -1 4 5.3778 7 2643 -4 3 5.3778 7 2640 4 3 5.3778 7 2754 5 1 5.3780 7 2626 -3 4 5.3782 7 2911 1 2 5.3782 7 2869 -4 1 5.3783 6 933 -5 3 5.3783 7 2661 1 4 5.3784 7 2679 5 1 5.3784 7 3340 3 1 5.3785 7 2623 -5 2 5.3788 7 2592 5 1 5.3791 7 2734 -3 2 5.3792 7 2720 -5 1 5.3792 6 952 2 3 5.3792 7 3008 -1 2 5.3794 7 2701 -1 3 5.3796 7 2559 5 2 5.3797 7 2634 -6 1 5.3797 7 2867 4 1 5.3799 7 2908 -4 1 5.3800 7 2738 -1 4 5.3800 7 2770 -3 2 5.3801 7 2736 -4 3 5.3802 7 3534 3 1 5.3804 7 3202 3 1 5.3810 7 3106 3 2 5.3810 7 3110 -3 2 5.3811 7 3209 3 2 5.3811 7 3027 -3 1 5.3811 7 3208 3 2 5.3811 7 3212 3 2 5.3812 6 942 1 3 5.3812 6 939 5 2 5.3813 7 2804 2 3 5.3814 7 3205 1 3 5.3815 7 2996 -1 3 5.3816 7 2836 -2 3 5.3818 6 942 -1 3 5.3819 7 3100 -1 3 5.3821 7 2655 -1 3 5.3821 7 2674 -1 3 5.3823 7 3015 -3 1 5.3824 6 942 -5 2 5.3825 7 2668 1 3 5.3827 7 2568 3 4 5.3829 7 3349 -3 2 5.3832 7 3017 4 1 5.3838 7 2716 -6 1 5.3838 7 2715 2 3 5.3838 7 2899 -3 2 5.3839 7 2741 -4 1 5.3840 7 2665 1 4 5.3841 7 2594 -5 2 5.3841 7 2671 -5 2 5.3842 7 3011 1 3 5.3854 7 2744 5 1 5.3854 7 2592 5 2 5.3858 7 2620 -3 2 5.3860 6 935 -4 1 5.3864 6 958 2 3 5.3866 7 3147 3 2 5.3867 7 2923 1 3 5.3870 7 2716 -5 2 5.3872 7 2789 -2 3 5.3872 7 2787 2 3 5.3872 7 2787 -2 3 5.3872 7 3259 3 1 5.3872 7 2789 2 3 5.3875 7 2691 -1 4 5.3876 7 3101 -4 1 5.3877 6 946 -3 2 5.3880 6 936 4 1 5.3882 7 3132 2 3 5.3882 7 2944 -2 3 5.3883 7 2791 5 1 5.3883 7 2670 5 1 5.3886 6 950 3 2 5.3886 6 939 -1 3 5.3889 7 2661 -2 3 5.3890 7 2923 -4 1 5.3892 7 2813 3 2 5.3894 7 3216 1 2 5.3894 7 2593 4 3 5.3895 6 947 -3 2 5.3895 7 3372 3 1 5.3896 7 2586 4 3 5.3896 7 2761 4 3 5.3897 7 2719 -3 4 5.3899 7 2588 -5 1 5.3900 7 2689 -2 3 5.3902 7 2877 3 2 5.3902 7 2655 6 1 5.3902 7 3128 -1 3 5.3904 7 2862 -2 3 5.3905 7 2728 -1 4 5.3905 7 2819 -3 2 5.3907 7 2640 5 1 5.3907 7 2643 -5 1 5.3910 7 2636 4 3 5.3911 7 2683 5 1 5.3913 7 2817 3 2 5.3915 7 2901 1 3 5.3916 7 2836 3 2 5.3917 7 2862 4 1 5.3919 7 2641 -5 2 5.3919 7 2642 -5 2 5.3920 7 2656 6 1 5.3921 7 2966 -1 3 5.3922 7 3102 1 3 5.3925 7 3126 1 3 5.3926 7 2586 -5 1 5.3926 7 2619 4 3 5.3927 7 2636 -2 3 5.3929 7 2911 -3 2 5.3936 7 3308 -3 2 5.3937 7 3205 2 3 5.3939 7 3199 4 1 5.3940 6 953 -3 4 5.3941 6 946 5 2 5.3941 7 2683 -5 2 5.3943 7 2796 -4 1 5.3943 7 2797 -1 4 5.3943 7 2795 -1 4 5.3943 7 3308 -4 1 5.3948 7 2802 -4 1 5.3952 7 2570 -2 3 5.3953 7 2947 5 1 5.3953 7 2783 -4 1 5.3954 6 938 6 1 5.3954 7 2791 -3 2 5.3954 7 2682 4 1 5.3958 6 947 5 2 5.3961 7 2883 -4 1 5.3964 7 3333 1 2 5.3964 7 3062 1 3 5.3964 7 2704 5 1 5.3967 6 954 -4 1 5.3969 7 2794 5 1 5.3970 7 2609 1 3 5.3970 7 2671 -4 3 5.3970 7 3066 -3 2 5.3970 7 3066 3 2 5.3972 7 2738 -4 3 5.3973 7 2756 -4 3 5.3974 7 2662 1 4 5.3975 7 2855 -2 3 5.3980 7 2715 -1 3 5.3983 7 3349 -1 3 5.3983 7 3318 -3 1 5.3983 7 3318 1 3 5.3985 7 2776 4 1 5.3987 7 2841 -2 3 5.3991 7 2616 5 2 5.3993 6 953 -1 4 5.3994 7 2901 -4 1 5.3994 7 2966 4 1 5.3996 7 2627 -6 1 5.4000 7 2897 -1 3 5.4001 7 2647 -4 3 5.4002 7 2596 2 3 5.4007 7 2674 2 3 5.4010 7 2587 2 3 5.4012 7 2944 1 3 5.4013 7 3349 -2 3 5.4015 7 2972 1 3 5.4017 6 954 4 1 5.4018 7 2661 -4 1 5.4018 6 957 1 3 5.4018 6 960 1 3 5.4018 6 955 1 3 5.4018 7 2588 -1 4 5.4018 7 2711 -6 1 5.4020 7 2824 -2 3 5.4022 7 3100 1 3 5.4022 7 2913 -2 3 5.4023 7 2609 2 3 5.4024 7 2603 1 4 5.4025 7 2670 5 2 5.4026 7 2813 4 1 5.4028 7 2736 -3 4 5.4035 7 3080 -4 1 5.4036 7 2941 2 3 5.4037 7 2971 -3 2 5.4038 6 950 -3 4 5.4039 7 3244 -3 1 5.4039 7 3246 -3 1 5.4039 7 2603 -3 4 5.4039 7 3244 3 2 5.4039 7 3246 3 2 5.4045 6 939 2 3 5.4045 7 2824 1 3 5.4046 7 2599 -1 4 5.4046 7 2749 -1 4 5.4049 7 2774 -3 2 5.4050 7 2647 -1 4 5.4051 7 3210 3 2 5.4051 7 3172 -3 2 5.4051 7 2832 4 3 5.4051 7 3213 -3 2 5.4051 7 3217 -3 2 5.4051 7 2600 -3 4 5.4052 7 2876 -1 3 5.4056 7 2638 5 1 5.4058 7 2775 -1 3 5.4058 7 2600 1 4 5.4062 7 2951 -4 1 5.4062 7 3266 2 3 5.4067 7 2619 -2 3 5.4070 7 2722 -1 3 5.4071 7 2817 -2 3 5.4072 7 2859 2 3 5.4073 7 2576 -5 2 5.4076 7 2957 -1 3 5.4078 7 2636 1 4 5.4079 7 2678 2 3 5.4079 7 2669 -6 1 5.4079 7 2694 4 1 5.4081 7 2694 -4 3 5.4086 7 2616 4 3 5.4088 7 3116 3 2 5.4091 7 2883 3 1 5.4096 7 2694 2 3 5.4098 7 2588 -4 3 5.4099 7 2721 -5 2 5.4105 7 2701 -5 1 5.4105 7 2703 -2 3 5.4105 7 3234 3 2 5.4105 7 3198 -4 1 5.4108 7 2620 5 2 5.4109 7 2771 5 1 5.4109 7 3276 1 3 5.4109 7 2909 -2 3 5.4111 7 2633 3 2 5.4112 7 3361 3 1 5.4115 7 2748 -4 3 5.4118 7 3080 5 1 5.4118 7 2866 -1 3 5.4118 7 2974 1 3 5.4120 7 2757 2 3 5.4121 7 2715 -5 1 5.4121 7 2711 5 2 5.4123 7 2772 4 1 5.4126 7 2988 -4 1 5.4129 7 3076 -1 3 5.4134 7 2660 4 3 5.4135 7 2920 -3 2 5.4135 7 2919 -3 2 5.4136 7 2627 1 3 5.4139 7 2584 1 3 5.4141 6 946 -4 1 5.4142 7 2711 7 1 5.4143 6 950 2 3 5.4145 7 3178 3 1 5.4156 7 2577 -4 3 5.4157 7 3465 -3 1 5.4157 6 946 5 1 5.4157 7 2640 5 2 5.4157 7 2643 -5 2 5.4159 7 2953 1 3 5.4161 7 2831 4 1 5.4166 7 2758 4 1 5.4166 7 2740 5 1 5.4168 7 2955 -3 1 5.4168 7 3219 -4 1 5.4171 7 2687 4 1 5.4171 7 2587 -5 2 5.4174 7 2848 -4 1 5.4174 7 2604 4 3 5.4174 7 3261 3 2 5.4174 6 944 -4 3 5.4176 7 2691 1 4 5.4176 7 2739 -3 4 5.4178 7 2620 -4 1 5.4180 7 2693 4 3 5.4186 7 2703 4 3 5.4186 7 2864 -4 1 5.4186 7 2744 -1 3 5.4187 7 2785 -3 2 5.4188 7 3068 -4 1 5.4189 7 2734 -4 1 5.4190 7 2947 -6 1 5.4194 7 2660 1 4 5.4195 7 2857 -3 2 5.4197 7 3010 -4 1 5.4198 7 2703 5 1 5.4202 7 2831 2 3 5.4202 7 2668 -5 2 5.4202 7 3105 4 1 5.4204 7 3327 -1 2 5.4205 6 958 -3 2 5.4205 7 3165 3 2 5.4206 7 2638 -6 1 5.4208 7 2901 2 3 5.4208 7 2855 1 3 5.4209 7 2734 1 4 5.4210 7 2619 -5 1 5.4212 7 2770 5 2 5.4214 7 2596 -5 3 5.4214 7 2858 -6 1 5.4215 7 2850 4 1 5.4216 7 3260 -3 1 5.4217 7 3398 -1 2 5.4218 7 2939 3 2 5.4220 7 2677 -4 3 5.4221 7 2597 3 4 5.4222 7 2704 1 3 5.4222 6 947 -2 3 5.4227 7 2670 -2 3 5.4228 6 949 -5 1 5.4229 7 2597 4 3 5.4230 7 2947 -3 2 5.4230 7 3358 1 3 5.4231 7 2682 2 3 5.4233 7 2891 2 3 5.4234 7 2678 -5 2 5.4235 7 2756 3 2 5.4237 7 2749 3 2 5.4237 7 2689 -5 1 5.4239 7 2627 2 3 5.4239 6 949 5 1 5.4240 7 2749 -4 3 5.4240 7 2620 -2 3 5.4241 7 2673 -6 1 5.4241 7 3178 -2 3 5.4241 7 3100 -3 2 5.4242 7 2715 5 2 5.4242 7 2808 -6 1 5.4244 7 2866 1 3 5.4245 7 2797 -4 3 5.4245 7 2661 3 4 5.4247 7 2744 -5 2 5.4248 7 2665 4 3 5.4249 7 3017 -3 1 5.4249 7 3221 4 1 5.4249 7 3228 -4 1 5.4250 7 3325 2 3 5.4252 6 947 -4 1 5.4253 7 2673 5 2 5.4253 7 2841 4 1 5.4254 7 3191 -3 1 5.4256 7 2861 -3 2 5.4257 7 2748 -3 4 5.4260 7 2925 -1 3 5.4262 7 2986 -3 2 5.4262 7 3009 -5 1 5.4263 7 2636 5 2 5.4264 7 2995 -4 1 5.4265 7 2772 -4 1 5.4267 7 3214 3 1 5.4267 7 3215 4 1 5.4267 7 3214 -1 3 5.4268 7 2738 -3 4 5.4269 7 2638 -4 3 5.4271 7 2756 -1 4 5.4275 7 2717 -1 4 5.4276 7 2864 3 2 5.4277 7 3486 -3 1 5.4280 7 3398 4 1 5.4280 7 2720 2 3 5.4281 7 2615 -5 1 5.4285 7 3510 -1 2 5.4285 7 3509 -1 2 5.4288 7 3260 4 1 5.4293 7 3051 -3 1 5.4293 7 3086 -1 3 5.4295 6 947 5 3 5.4297 7 2941 -2 3 5.4298 7 2633 2 3 5.4300 7 2698 1 4 5.4300 7 3172 -2 3 5.4300 7 2919 -5 1 5.4300 7 2920 -5 1 5.4304 7 2988 1 3 5.4304 7 3191 3 2 5.4307 7 2638 2 3 5.4309 7 2750 4 1 5.4309 7 2806 3 2 5.4311 7 2662 -1 4 5.4315 7 3049 1 3 5.4316 7 2932 3 2 5.4316 7 3147 2 3 5.4318 7 2846 -4 3 5.4319 7 2772 1 4 5.4321 7 2783 5 1 5.4321 7 2909 1 3 5.4321 7 3443 3 1 5.4323 7 2758 2 3 5.4323 7 3185 -4 1 5.4324 6 950 4 1 5.4324 7 3201 4 1 5.4327 7 2701 5 2 5.4327 7 3199 -1 2 5.4328 7 2836 -5 1 5.4329 7 2759 1 4 5.4329 7 2770 -1 3 5.4329 7 2875 -2 3 5.4329 7 2875 2 3 5.4330 7 2780 -4 3 5.4331 7 3009 5 1 5.4333 7 2639 -4 1 5.4334 7 2896 -6 1 5.4334 7 2683 -6 1 5.4336 7 2876 1 3 5.4343 7 3015 -1 3 5.4343 7 2930 1 3 5.4345 7 2671 2 3 5.4345 7 2744 -2 3 5.4345 7 2750 -1 4 5.4348 7 2871 4 1 5.4350 7 3143 3 1 5.4351 7 2872 1 3 5.4354 7 3107 -3 2 5.4354 7 3109 3 2 5.4357 7 2802 4 1 5.4357 7 3264 1 3 5.4359 7 2910 4 1 5.4360 7 2836 1 3 5.4363 7 2647 -5 1 5.4365 7 3077 6 1 5.4368 7 2903 -1 3 5.4370 7 2775 1 3 5.4372 7 3505 4 1 5.4375 7 2678 -4 3 5.4376 7 2946 3 2 5.4377 7 2998 4 1 5.4377 7 2997 1 3 5.4378 7 2694 -3 4 5.4381 7 3076 3 2 5.4384 7 2944 3 2 5.4384 7 3251 4 1 5.4384 7 2833 -1 3 5.4386 7 2998 -1 3 5.4392 7 2780 3 2 5.4395 7 2754 -5 2 5.4396 7 3172 1 3 5.4397 7 2660 5 1 5.4397 6 954 1 4 5.4399 7 2791 -1 3 5.4400 7 2742 -2 3 5.4401 7 2734 4 3 5.4401 7 3293 4 1 5.4403 7 2759 3 4 5.4405 7 2729 -4 1 5.4408 7 3132 -3 1 5.4411 7 2664 4 3 5.4411 7 3200 3 2 5.4412 7 3335 1 2 5.4412 7 2775 -2 3 5.4414 7 2774 4 3 5.4416 7 3353 1 2 5.4417 7 2687 1 4 5.4424 7 2664 1 4 5.4425 7 2722 4 3 5.4425 7 3245 -3 2 5.4425 7 3229 -2 3 5.4428 7 2979 -1 3 5.4429 7 2765 3 4 5.4429 7 3457 -3 1 5.4431 7 3014 -3 1 5.4431 7 3056 -1 3 5.4432 7 2734 -2 3 5.4432 7 2819 -2 3 5.4435 7 2846 1 3 5.4447 7 2951 1 3 5.4447 7 3001 -1 3 5.4450 7 2833 4 3 5.4450 7 2674 -2 3 5.4451 7 2955 4 1 5.4453 6 957 -1 4 5.4453 6 955 -1 4 5.4453 6 956 4 1 5.4453 6 961 4 1 5.4453 6 960 -1 4 5.4453 7 3493 -1 2 5.4453 7 3028 -1 3 5.4453 7 3019 -4 1 5.4453 7 3491 -1 2 5.4457 7 2953 2 3 5.4459 7 3141 4 1 5.4465 7 2740 -2 3 5.4475 7 2758 -4 3 5.4475 7 2774 -4 1 5.4478 6 952 -5 1 5.4479 7 2728 4 1 5.4481 6 954 -1 4 5.4482 7 3425 1 2 5.4482 6 957 3 1 5.4482 6 955 3 1 5.4482 6 960 3 1 5.4486 7 3010 -2 3 5.4486 7 2878 4 1 5.4487 7 3327 4 1 5.4487 7 2655 -2 3 5.4489 7 2692 -1 4 5.4489 7 2765 -3 2 5.4490 7 2911 -6 1 5.4490 7 2910 -2 3 5.4493 7 2888 -5 1 5.4495 7 3010 3 2 5.4499 7 3067 4 1 5.4501 7 2754 -1 3 5.4503 7 3011 2 3 5.4504 7 3543 -3 1 5.4504 7 3049 4 1 5.4504 7 2911 6 1 5.4504 7 2910 2 3 5.4505 7 2914 -1 3 5.4509 7 2765 -2 3 5.4511 7 2937 -4 1 5.4511 7 3445 -3 1 5.4511 7 3199 1 2 5.4511 7 2806 4 1 5.4516 7 2749 -3 4 5.4520 7 2742 5 1 5.4522 7 2757 -5 1 5.4522 7 2886 -4 1 5.4523 7 2971 -2 3 5.4525 7 2776 -1 4 5.4526 7 2862 -4 1 5.4526 7 2761 1 4 5.4526 7 2914 4 1 5.4532 7 2717 1 4 5.4535 7 3077 -5 1 5.4539 7 2775 5 1 5.4539 7 2964 -3 2 5.4541 7 2756 2 3 5.4541 7 2703 5 2 5.4541 7 2677 2 3 5.4542 7 2661 5 2 5.4543 7 2816 3 2 5.4545 7 2801 1 3 5.4545 7 3051 -1 3 5.4546 7 2756 -3 4 5.4547 7 2761 3 4 5.4551 7 3136 4 1 5.4555 6 949 1 4 5.4556 7 2979 -2 3 5.4557 7 3416 3 1 5.4558 7 3086 -2 3 5.4560 7 2744 -6 1 5.4561 7 2674 6 1 5.4561 7 2742 4 3 5.4561 7 3492 -1 2 5.4561 7 3074 3 2 5.4561 7 2795 -3 4 5.4561 7 2796 4 1 5.4561 7 2797 -3 4 5.4563 7 2687 -4 3 5.4563 7 3191 -1 3 5.4564 7 2925 1 3 5.4565 7 3266 3 2 5.4565 7 3128 -2 3 5.4567 7 2996 -4 1 5.4567 7 2748 3 2 5.4569 7 2974 2 3 5.4571 7 3109 -4 1 5.4571 7 3110 1 3 5.4571 7 3106 -1 3 5.4571 7 3107 4 1 5.4571 7 2874 5 1 5.4571 7 2873 5 1 5.4574 7 2908 1 3 5.4575 7 2871 -5 1 5.4577 7 3081 -5 1 5.4580 7 2743 -5 1 5.4583 7 2888 3 2 5.4583 7 3041 3 1 5.4586 7 3105 3 2 5.4587 7 2819 1 4 5.4589 7 2923 3 2 5.4593 7 2709 1 4 5.4595 7 3119 -3 1 5.4595 7 2709 -1 4 5.4597 7 2698 -1 4 5.4598 7 2679 5 2 5.4600 7 2701 -2 3 5.4600 7 2775 -5 2 5.4603 7 3215 3 2 5.4603 7 2783 4 3 5.4605 7 2932 2 3 5.4607 7 2763 3 2 5.4610 7 3277 3 1 5.4612 7 2957 -3 2 5.4612 7 3122 1 3 5.4614 7 2840 -1 4 5.4615 7 2964 4 1 5.4615 7 2877 6 1 5.4615 7 2876 -2 3 5.4615 7 3071 -2 3 5.4615 7 2716 6 1 5.4615 7 2715 -2 3 5.4616 7 2897 4 1 5.4619 7 3172 3 1 5.4619 7 2674 -6 1 5.4620 7 3266 -3 1 5.4623 7 2941 4 1 5.4624 7 3158 -4 1 5.4625 7 2695 -1 4 5.4626 7 2763 -1 4 5.4626 7 2738 3 2 5.4627 7 2704 -6 1 5.4628 7 2840 -4 3 5.4628 6 958 -1 3 5.4629 7 2994 4 1 5.4631 7 3289 -4 1 5.4633 7 3107 3 2 5.4633 7 3109 -3 2 5.4635 7 2979 4 1 5.4636 7 2689 5 2 5.4637 7 2819 -4 1 5.4638 7 3539 2 1 5.4640 7 3129 -4 1 5.4642 7 2998 3 2 5.4643 7 3074 -3 2 5.4645 7 2687 -3 4 5.4645 7 2984 -3 2 5.4647 7 3136 -4 1 5.4648 7 2673 -1 3 5.4649 7 3441 -3 1 5.4649 7 2899 -4 1 5.4649 7 2898 -1 3 5.4649 7 2772 -1 4 5.4652 7 3470 -1 2 5.4652 7 2874 -1 3 5.4652 7 3469 -1 2 5.4652 7 2816 -4 3 5.4652 7 3346 -4 1 5.4652 7 2828 4 3 5.4653 7 2889 6 1 5.4653 7 2857 -4 1 5.4655 7 2901 -3 2 5.4657 7 2960 -3 2 5.4660 7 3126 2 3 5.4661 6 951 5 1 5.4663 7 3028 4 1 5.4664 7 2972 -4 1 5.4666 7 2901 5 1 5.4666 7 2761 -3 2 5.4667 7 2754 -2 3 5.4669 7 3195 1 2 5.4671 7 3476 3 1 5.4671 7 2786 5 1 5.4673 7 2774 -2 3 5.4673 7 3482 3 1 5.4674 7 2749 2 3 5.4675 7 2914 1 4 5.4679 7 2783 5 2 5.4681 7 2804 4 1 5.4689 7 3320 -4 1 5.4689 7 3390 2 3 5.4691 7 3274 3 1 5.4692 6 951 1 4 5.4694 7 3257 4 1 5.4695 7 3143 -2 3 5.4695 7 2796 -1 4 5.4697 7 3444 3 1 5.4699 7 2729 -1 4 5.4703 7 2958 4 1 5.4704 7 2701 4 3 5.4705 7 2986 5 1 5.4706 7 2721 2 3 5.4708 7 3226 1 2 5.4714 7 2817 1 3 5.4715 7 3074 2 3 5.4717 7 2720 5 1 5.4722 7 2798 4 1 5.4724 7 3190 4 1 5.4730 7 3031 3 1 5.4730 7 3340 -4 1 5.4730 7 3045 2 3 5.4730 7 3008 3 2 5.4731 7 2728 1 4 5.4731 7 2749 4 1 5.4732 7 3170 -4 1 5.4732 7 2866 5 1 5.4733 7 2721 5 1 5.4734 7 3246 1 3 5.4734 7 3244 1 3 5.4734 7 2967 4 1 5.4735 7 2889 -6 1 5.4736 7 3216 -5 1 5.4736 7 2791 5 2 5.4739 7 3480 3 1 5.4740 7 3278 -4 1 5.4742 6 951 4 3 5.4743 7 2805 1 4 5.4750 7 2741 1 4 5.4751 7 2998 -2 3 5.4755 7 2984 5 1 5.4755 7 2846 -3 4 5.4756 7 3257 -3 2 5.4758 7 3248 -4 1 5.4759 7 2771 -5 2 5.4760 7 2920 6 1 5.4760 7 2919 6 1 5.4761 7 2761 -4 1 5.4762 7 2743 5 1 5.4762 7 2744 1 3 5.4764 7 2748 2 3 5.4768 7 2898 4 3 5.4769 7 3390 1 3 5.4770 7 2953 -3 2 5.4770 7 2908 -4 3 5.4770 7 2951 -3 2 5.4777 7 3216 -3 2 5.4777 7 2750 1 4 5.4779 7 2951 2 3 5.4780 7 2720 -5 2 5.4783 7 2931 4 1 5.4783 7 2739 3 2 5.4785 7 2770 4 3 5.4786 7 3189 4 1 5.4787 7 3536 -3 1 5.4787 7 2897 1 4 5.4790 7 2734 3 4 5.4792 7 2735 1 4 5.4803 7 2927 -5 1 5.4803 7 2929 -4 1 5.4805 7 2861 1 3 5.4808 7 3144 -6 1 5.4809 7 2917 -4 1 5.4810 7 2747 1 4 5.4811 7 2725 -5 1 5.4813 7 2913 -4 1 5.4813 7 2704 -4 3 5.4814 7 2922 4 1 5.4815 7 2780 4 1 5.4818 7 3486 1 2 5.4821 7 2953 -4 1 5.4823 7 2728 -4 3 5.4826 7 2960 5 1 5.4831 7 2959 -1 3 5.4831 7 2889 -3 2 5.4832 7 2759 -3 2 5.4833 7 2996 4 1 5.4834 7 2719 -5 2 5.4834 7 2719 3 2 5.4834 7 2747 -1 4 5.4835 7 2824 5 1 5.4835 7 2721 -4 3 5.4837 7 2939 -4 1 5.4837 7 3305 -4 1 5.4839 7 3443 -3 2 5.4840 7 2740 4 3 5.4840 7 2738 4 1 5.4846 7 2754 -6 1 5.4846 7 2914 3 4 5.4859 7 2771 -6 1 5.4860 7 2736 3 2 5.4862 7 2754 1 3 5.4863 7 2780 2 3 5.4864 7 2803 4 1 5.4865 7 2735 -1 4 5.4866 7 2780 -1 4 5.4867 7 2946 6 1 5.4867 7 3372 -3 1 5.4873 7 2851 6 1 5.4880 7 2869 5 1 5.4886 7 2725 -4 3 5.4886 7 2740 5 2 5.4887 7 2727 -3 2 5.4887 7 2935 4 1 5.4891 7 2996 2 3 5.4894 7 2824 2 3 5.4895 7 3012 4 1 5.4895 7 2899 -2 3 5.4897 6 958 5 1 5.4897 7 2763 4 1 5.4903 7 2974 -4 1 5.4903 7 2806 -4 3 5.4904 7 3015 4 1 5.4905 7 2804 -1 4 5.4907 7 3189 1 3 5.4907 7 3165 4 1 5.4908 7 2802 -1 4 5.4909 7 2742 5 2 5.4912 7 3100 3 2 5.4916 7 2774 1 4 5.4917 7 2824 -5 1 5.4917 7 3041 -4 1 5.4919 7 2722 6 1 5.4924 7 2725 -1 4 5.4925 7 2857 -2 3 5.4926 7 3191 2 3 5.4927 7 2897 3 4 5.4929 7 2874 1 3 5.4929 7 2873 1 3 5.4930 7 3122 4 1 5.4933 7 2939 2 3 5.4935 7 2763 -3 4 5.4936 7 2996 -2 3 5.4937 7 2929 -1 4 5.4938 7 2825 -5 1 5.4939 7 3011 -4 1 5.4941 7 2828 -3 2 5.4942 7 2859 4 1 5.4943 7 2757 5 1 5.4943 7 2736 2 3 5.4947 7 2719 2 3 5.4948 7 2763 -5 1 5.4951 7 3274 -1 3 5.4951 7 2861 -5 1 5.4953 7 2817 -5 2 5.4956 7 2891 -4 1 5.4957 7 2813 2 3 5.4957 7 3412 5 1 5.4957 7 3210 1 3 5.4957 7 3207 1 3 5.4957 7 3111 -4 1 5.4957 7 3213 -1 3 5.4957 7 3216 4 1 5.4957 7 3529 3 1 5.4959 7 2878 -5 1 5.4962 7 3320 4 1 5.4964 7 2866 2 3 5.4964 7 3011 -3 2 5.4964 7 3329 -4 1 5.4966 6 953 3 2 5.4967 7 2864 2 3 5.4969 7 2812 -3 2 5.4971 7 2978 -1 3 5.4973 7 2805 -4 1 5.4974 7 2758 -5 1 5.4975 7 2758 -1 4 5.4976 7 3120 3 1 5.4976 7 2761 5 1 5.4976 7 2756 4 1 5.4977 7 2759 5 2 5.4978 7 2761 5 2 5.4979 7 2796 1 4 5.4979 7 3346 -1 2 5.4979 7 3038 -4 1 5.4981 7 3009 -3 2 5.4982 7 2957 -4 1 5.4983 7 2874 -5 1 5.4983 7 2873 -5 1 5.4989 7 2828 1 4 5.4990 7 3103 5 1 5.4991 7 2878 3 2 5.4992 7 3264 -3 1 5.4993 7 2932 -4 1 5.4993 7 2877 -6 1 5.4993 7 2876 2 3 5.4996 7 2872 -4 3 5.4996 7 3275 4 1 5.4996 7 2729 4 3 5.4997 6 952 -4 3 5.4998 7 2955 -1 3 5.4999 7 2951 5 1 5.4999 7 2878 1 3 5.4999 7 3031 1 3 5.5000 7 3318 3 1 5.5000 7 3318 -1 3 5.5000 7 2848 -1 4 5.5002 7 2872 3 2 5.5005 7 2727 -2 3 5.5005 7 3081 5 1 5.5005 7 2763 2 3 5.5006 7 2850 -4 1 5.5007 7 2916 4 1 5.5007 7 2739 2 3 5.5008 7 2886 3 2 5.5009 7 2966 -3 2 5.5013 7 2759 -4 1 5.5015 7 3075 3 2 5.5015 7 2866 -2 3 5.5017 7 2886 -1 4 5.5019 7 2841 -4 1 5.5020 7 2871 3 2 5.5021 7 2742 1 4 5.5022 7 2897 4 3 5.5023 7 3259 -4 1 5.5023 7 2861 2 3 5.5023 7 2891 -1 4 5.5024 7 2791 4 3 5.5029 7 3425 -4 1 5.5029 7 2721 -6 1 5.5029 7 2836 -5 2 5.5030 7 2816 4 1 5.5031 7 2776 1 4 5.5031 7 2977 -4 1 5.5031 7 2739 -5 2 5.5032 7 2929 -4 3 5.5035 7 2974 -3 2 5.5037 7 2916 1 4 5.5038 7 3092 1 3 5.5039 7 2775 2 3 5.5039 7 2728 -3 4 5.5042 7 2911 3 2 5.5044 7 2841 1 4 5.5046 7 3076 -3 2 5.5047 7 3310 5 1 5.5047 7 3117 -1 3 5.5047 7 2944 4 1 5.5054 7 2846 -1 4 5.5054 7 3264 2 3 5.5056 7 2806 -1 4 5.5057 7 2719 -5 3 5.5057 7 2793 -5 1 5.5058 7 2950 -4 1 5.5061 7 2774 5 1 5.5061 7 2787 5 1 5.5061 7 2789 -5 1 5.5061 7 2787 -5 1 5.5061 7 2789 5 1 5.5061 7 2816 2 3 5.5063 7 2983 4 1 5.5064 7 3011 -1 3 5.5064 7 3327 -4 1 5.5064 7 2984 -5 1 5.5064 7 2784 -4 3 5.5065 7 2802 1 4 5.5067 7 3202 -1 3 5.5071 7 2946 -6 1 5.5072 7 2722 -2 3 5.5074 6 953 -5 2 5.5076 7 3154 -1 3 5.5084 7 3348 3 2 5.5085 7 2968 -1 3 5.5086 7 2748 4 1 5.5087 7 2923 4 1 5.5087 7 2738 -5 1 5.5088 6 953 2 3 5.5096 7 2794 -2 3 5.5096 7 2771 2 3 5.5096 7 3377 3 1 5.5096 7 3352 -3 1 5.5096 7 3448 3 2 5.5096 7 3214 -2 3 5.5099 7 3238 3 1 5.5101 7 2956 3 2 5.5102 7 2941 -4 1 5.5102 7 2816 -1 4 5.5102 7 3205 -3 1 5.5110 7 3274 -2 3 5.5111 7 2979 -5 1 5.5112 7 2759 5 1 5.5115 7 3274 -4 1 5.5118 7 2864 4 1 5.5120 7 3077 1 2 5.5121 7 2953 -1 3 5.5121 7 2972 -3 2 5.5126 7 3069 4 1 5.5128 7 2855 2 3 5.5132 7 2786 -5 1 5.5132 7 2758 -5 2 5.5133 7 2864 -1 4 5.5133 7 2886 2 3 5.5134 7 2935 -4 1 5.5138 7 3074 -2 3 5.5142 7 3132 4 1 5.5144 7 2806 2 3 5.5145 7 3076 4 1 5.5153 7 2775 -6 1 5.5154 7 3352 3 2 5.5156 7 3077 -1 2 5.5158 7 2782 3 2 5.5159 7 2958 2 3 5.5162 7 2774 5 2 5.5163 7 2955 3 4 5.5165 6 953 -5 3 5.5166 7 2891 4 1 5.5166 7 3353 -1 2 5.5167 7 2784 -1 4 5.5167 7 2793 5 1 5.5168 7 2901 -1 3 5.5171 7 3014 -1 3 5.5177 7 2871 1 3 5.5177 7 3288 1 3 5.5179 7 3358 3 1 5.5179 7 2929 -3 4 5.5179 7 3428 -4 1 5.5180 7 3445 -1 2 5.5185 7 2946 -3 2 5.5186 7 3078 5 1 5.5186 7 2833 -4 1 5.5187 7 2974 -1 3 5.5188 7 2955 4 3 5.5188 7 2955 1 4 5.5190 7 3314 -1 3 5.5190 7 2780 -5 2 5.5192 7 2894 -6 1 5.5193 7 2872 4 1 5.5193 7 2914 4 3 5.5195 7 2812 -2 3 5.5196 7 3087 -1 3 5.5197 7 2780 -5 1 5.5200 7 2801 5 1 5.5200 7 3001 4 1 5.5206 7 2750 -4 3 5.5206 7 3314 3 1 5.5207 7 2898 1 4 5.5207 7 2874 -2 3 5.5207 7 3489 -1 2 5.5207 7 2873 -2 3 5.5213 7 3126 -1 3 5.5216 7 2871 -5 2 5.5217 7 2785 7 1 5.5218 7 2770 -5 1 5.5219 7 2939 4 1 5.5220 7 2817 5 1 5.5220 7 2908 -1 4 5.5226 7 2833 -3 2 5.5231 7 2783 -2 3 5.5235 7 2813 -5 1 5.5239 7 2925 -2 3 5.5239 7 2782 2 3 5.5239 7 2795 2 3 5.5239 7 2797 2 3 5.5241 7 3010 4 1 5.5249 7 3062 -4 1 5.5250 7 3100 -2 3 5.5251 7 2964 -4 1 5.5252 7 2903 -3 2 5.5255 7 3168 1 3 5.5255 7 3458 -3 2 5.5259 7 3252 3 2 5.5260 7 3335 -3 2 5.5260 7 2850 1 4 5.5261 7 2828 -2 3 5.5261 7 2795 3 2 5.5261 7 2797 3 2 5.5263 7 3001 1 4 5.5265 7 2833 3 4 5.5265 7 3195 -3 2 5.5265 7 3184 -6 1 5.5268 7 2851 -6 1 5.5270 7 2904 -4 3 5.5273 7 3068 -3 2 5.5276 7 3092 2 3 5.5276 7 2871 -4 3 5.5280 7 3041 1 3 5.5282 7 2780 -3 4 5.5282 7 2858 6 1 5.5282 7 2964 -2 3 5.5282 7 3195 -5 1 5.5283 7 3143 -4 1 5.5287 7 2813 -4 3 5.5289 7 3095 3 1 5.5291 7 3045 4 1 5.5291 7 2840 -3 4 5.5295 7 2966 -4 1 5.5302 7 2833 1 4 5.5302 7 3324 -3 2 5.5304 7 2833 5 1 5.5304 7 2807 -4 3 5.5304 7 3143 1 3 5.5305 7 3049 2 3 5.5305 7 3226 -5 1 5.5305 7 3056 4 1 5.5307 7 3310 -6 1 5.5308 7 3304 4 1 5.5308 7 3240 -1 3 5.5308 7 3185 -1 3 5.5311 7 2770 -2 3 5.5312 7 2807 -5 1 5.5312 7 2855 -5 1 5.5313 7 3091 -1 3 5.5317 7 3008 -6 1 5.5318 7 2960 2 3 5.5320 7 3128 1 3 5.5321 7 3340 -3 2 5.5321 7 2808 6 1 5.5324 7 3205 4 1 5.5327 7 3017 -1 3 5.5328 7 2878 -4 3 5.5329 7 3522 -3 1 5.5330 7 2881 -1 4 5.5333 7 2979 3 2 5.5335 7 2925 5 1 5.5337 7 2785 -6 1 5.5338 7 3444 -4 1 5.5340 7 2972 -1 3 5.5342 7 2908 -3 4 5.5343 7 2967 1 4 5.5348 7 3397 3 1 5.5349 7 2909 2 3 5.5352 7 3120 -4 1 5.5354 7 3007 -1 3 5.5359 7 3277 -4 1 5.5363 7 2859 -1 4 5.5365 7 3075 -5 1 5.5368 7 3008 -3 2 5.5372 7 3361 -3 2 5.5374 7 2898 3 4 5.5374 7 3130 -4 1 5.5376 7 3047 1 3 5.5378 7 3359 4 1 5.5380 7 2833 5 2 5.5380 7 2828 -4 1 5.5382 7 2872 -5 1 5.5383 7 2881 1 4 5.5387 7 2971 4 1 5.5392 7 2870 3 2 5.5392 7 3045 -2 3 5.5392 7 3187 -5 1 5.5393 7 2917 -2 3 5.5394 7 3076 2 3 5.5394 7 3103 3 2 5.5399 7 2836 2 3 5.5399 7 2784 -5 1 5.5400 7 2944 2 3 5.5405 7 3122 2 3 5.5405 7 3158 -1 3 5.5406 7 3075 6 1 5.5409 7 3122 -1 3 5.5410 7 3091 -2 3 5.5411 7 3352 1 3 5.5414 7 2888 1 3 5.5414 7 3422 -4 1 5.5414 7 2888 -5 2 5.5418 7 2974 5 1 5.5418 7 2825 -2 3 5.5422 7 3133 4 1 5.5422 7 2861 5 2 5.5426 7 3221 -3 2 5.5426 7 3228 3 2 5.5426 7 2983 1 4 5.5430 7 3187 -3 2 5.5431 7 2791 -2 3 5.5433 7 2872 -5 2 5.5436 7 3229 1 3 5.5437 7 2804 1 4 5.5438 7 3427 -3 1 5.5441 7 2876 5 1 5.5443 7 2953 5 1 5.5443 7 3526 -3 1 5.5448 7 2886 -4 3 5.5449 7 2947 3 2 5.5450 7 2806 -5 1 5.5451 7 2801 2 3 5.5451 7 2836 -4 3 5.5454 7 2806 -3 4 5.5455 7 2791 -5 1 5.5455 7 2923 2 3 5.5455 7 3081 -3 2 5.5456 7 2925 2 3 5.5458 7 2998 1 3 5.5459 7 2840 3 2 5.5462 7 3202 -4 1 5.5464 7 3422 3 1 5.5471 7 3252 -4 1 5.5471 7 2870 4 1 5.5473 7 2959 -3 2 5.5477 7 3288 -1 3 5.5482 7 3353 4 1 5.5483 7 2813 -1 4 5.5490 7 3189 2 3 5.5490 7 3038 5 1 5.5492 7 2828 3 4 5.5496 7 3477 3 1 5.5500 7 2951 -1 3 5.5500 7 3144 6 1 5.5501 7 3219 3 2 5.5504 7 3178 1 3 5.5508 7 2804 -4 3 5.5510 7 3499 3 1 5.5512 7 2840 2 3 5.5512 7 2932 -1 4 5.5514 7 3381 4 1 5.5516 7 3008 6 1 5.5517 7 3005 -4 1 5.5520 7 3445 4 1 5.5521 7 3348 2 3 5.5523 7 2878 -5 2 5.5523 7 2983 -3 2 5.5527 7 2959 -4 1 5.5527 7 3095 1 3 5.5529 7 2930 -1 4 5.5530 7 3273 1 3 5.5531 7 2986 -5 1 5.5531 7 3017 4 3 5.5535 7 2903 -4 1 5.5535 7 2816 -3 4 5.5540 7 2874 2 3 5.5540 7 2873 2 3 5.5546 7 3394 4 1 5.5554 7 2866 -5 1 5.5556 7 2848 1 4 5.5558 7 3425 -1 2 5.5560 7 3080 -3 2 5.5561 7 3031 -4 1 5.5562 7 3076 -4 1 5.5563 7 2805 3 4 5.5566 7 2816 -5 1 5.5567 7 2785 1 3 5.5569 7 3339 -2 3 5.5569 7 3221 -5 1 5.5569 7 3228 5 1 5.5572 7 2812 -4 1 5.5573 7 2948 -6 1 5.5573 7 2794 -6 1 5.5578 7 2957 -2 3 5.5580 7 3068 1 3 5.5580 7 2816 -5 2 5.5580 7 3066 5 1 5.5580 7 3066 -5 1 5.5581 7 3541 -3 1 5.5584 7 2949 -3 2 5.5585 7 3362 -4 1 5.5589 7 3324 3 1 5.5590 7 3027 4 1 5.5591 7 2842 5 1 5.5592 7 3252 5 1 5.5596 7 2846 3 2 5.5599 7 2858 -5 2 5.5600 7 2828 5 2 5.5601 7 3258 5 1 5.5606 7 3477 -4 1 5.5607 7 2870 -5 1 5.5607 7 2870 -5 2 5.5612 7 3240 -4 1 5.5617 7 2930 -4 3 5.5617 7 2817 2 3 5.5618 7 2861 -1 3 5.5618 7 3128 -4 1 5.5619 7 3240 -2 3 5.5623 7 3025 -4 1 5.5624 7 2901 5 2 5.5628 7 2904 -1 4 5.5628 7 2869 -2 3 5.5629 7 2808 -1 3 5.5629 7 3452 1 2 5.5630 7 2817 -6 1 5.5632 7 2876 -5 1 5.5635 7 3219 5 1 5.5636 7 2831 -1 4 5.5636 7 3277 -2 3 5.5636 7 3259 -1 3 5.5636 7 3214 1 3 5.5636 7 3262 -1 3 5.5636 7 3215 -4 1 5.5637 7 2947 6 1 5.5638 7 2819 -1 4 5.5640 7 3010 2 3 5.5642 7 2969 5 1 5.5642 7 2817 -4 3 5.5644 7 2841 -1 4 5.5645 7 3120 1 3 5.5647 7 2971 -4 1 5.5647 7 2939 -1 4 5.5652 7 3165 1 3 5.5658 7 3126 4 1 5.5660 7 2956 2 3 5.5663 7 3486 -1 2 5.5663 7 3531 3 1 5.5664 7 3443 -4 1 5.5664 7 2915 -3 2 5.5665 7 2828 5 1 5.5669 7 3051 4 1 5.5670 7 3100 2 3 5.5674 7 3165 -3 2 5.5674 7 3297 4 1 5.5674 7 3335 4 1 5.5679 7 3116 -3 2 5.5681 7 3142 4 1 5.5682 7 3165 -4 1 5.5682 7 3244 2 3 5.5682 7 3246 2 3 5.5682 7 2855 5 1 5.5683 7 3119 4 1 5.5685 7 2886 -3 4 5.5686 7 3409 3 1 5.5686 7 3412 -5 1 5.5686 7 3411 3 2 5.5686 7 3091 4 1 5.5687 7 2807 -5 2 5.5695 7 2932 4 1 5.5697 7 3185 -2 3 5.5697 7 2983 -2 3 5.5702 7 2831 -4 3 5.5703 7 3433 -3 1 5.5707 7 3420 -3 1 5.5713 7 2861 6 1 5.5716 6 958 -2 3 5.5716 7 2891 1 4 5.5718 7 2950 5 1 5.5718 7 3226 5 1 5.5720 7 2966 -2 3 5.5720 7 3423 -4 1 5.5729 7 2900 -3 2 5.5736 7 2842 -5 1 5.5736 7 3183 -3 2 5.5736 7 3212 -4 1 5.5736 7 3217 1 3 5.5736 7 3469 -3 1 5.5736 7 3213 1 3 5.5736 7 3209 -4 1 5.5736 7 3207 -1 3 5.5736 7 3208 -4 1 5.5736 7 2843 -3 2 5.5736 7 2869 5 2 5.5739 7 2968 4 3 5.5741 7 3095 -4 1 5.5741 7 3031 -4 3 5.5742 7 3168 2 3 5.5745 7 2920 5 2 5.5745 7 2919 5 2 5.5747 7 2857 5 1 5.5753 7 3361 -4 1 5.5753 7 2925 -5 1 5.5756 7 2858 -7 1 5.5762 7 3243 -3 1 5.5762 7 3247 3 1 5.5763 7 2859 1 4 5.5766 7 3013 -1 3 5.5766 7 3089 -3 2 5.5769 7 2967 4 3 5.5781 7 2857 1 4 5.5784 7 3013 -4 1 5.5784 7 3202 -2 3 5.5784 7 2877 5 2 5.5785 7 3041 -4 3 5.5788 7 2956 4 1 5.5788 7 3132 -1 3 5.5789 7 2965 -4 1 5.5790 7 2889 5 2 5.5791 7 3434 -3 1 5.5793 7 3119 -1 3 5.5794 7 3190 1 3 5.5794 7 2888 -4 3 5.5795 7 2850 -1 4 5.5797 7 3087 4 1 5.5798 7 2851 -5 2 5.5799 7 2877 -5 2 5.5799 7 3157 -4 1 5.5799 7 3027 -2 3 5.5803 7 2846 4 1 5.5807 7 3025 4 1 5.5809 7 2963 1 4 5.5810 7 3027 1 4 5.5810 7 2960 -5 1 5.5810 6 958 5 2 5.5811 7 3246 -1 3 5.5811 7 3244 -1 3 5.5811 7 2872 2 3 5.5813 7 2930 3 2 5.5814 7 3199 -3 2 5.5816 7 2911 -5 2 5.5816 7 3007 4 3 5.5818 7 2947 -7 1 5.5828 7 3260 1 3 5.5831 7 2852 -2 3 5.5834 7 3184 6 1 5.5836 7 2978 -4 1 5.5836 7 3142 -5 1 5.5837 7 2851 5 2 5.5838 7 2986 1 3 5.5844 7 2841 4 3 5.5846 7 2979 1 3 5.5847 7 3092 5 1 5.5849 7 3493 3 2 5.5849 7 3491 3 2 5.5850 7 3400 3 2 5.5854 7 3457 -3 2 5.5854 7 3528 1 2 5.5854 7 3014 3 4 5.5854 7 2840 -5 2 5.5855 7 3248 4 1 5.5859 7 3013 -3 2 5.5859 7 3049 -2 3 5.5860 7 2960 -1 3 5.5863 7 2862 1 4 5.5864 7 2864 -4 3 5.5865 7 3031 -1 4 5.5869 7 3195 5 1 5.5871 7 3260 2 3 5.5875 7 3333 5 1 5.5878 7 2971 1 4 5.5879 7 3045 -4 1 5.5879 7 2897 -3 2 5.5880 7 3443 1 2 5.5881 7 2909 -5 1 5.5884 7 3031 -3 4 5.5887 7 3299 -4 1 5.5889 7 2988 3 2 5.5894 7 2967 3 4 5.5897 7 2979 -5 2 5.5899 7 3116 4 1 5.5901 7 3067 -1 3 5.5903 6 961 1 4 5.5903 6 956 1 4 5.5906 7 3014 1 4 5.5908 7 3089 -2 3 5.5909 7 3541 3 1 5.5910 7 3019 5 1 5.5910 7 2949 -4 1 5.5912 7 3160 4 1 5.5917 7 2978 4 3 5.5917 7 3441 -1 3 5.5918 7 3076 -2 3 5.5920 7 2888 5 1 5.5922 7 3217 -2 3 5.5922 7 3213 -2 3 5.5922 7 3210 2 3 5.5922 7 3207 2 3 5.5922 7 3156 4 1 5.5929 7 2896 -7 1 5.5930 7 3201 1 3 5.5930 7 2843 -2 3 5.5933 7 2997 3 2 5.5934 7 2915 -4 1 5.5934 7 2968 -4 1 5.5936 7 2953 5 2 5.5937 7 2906 -1 4 5.5941 7 2903 5 1 5.5944 7 3395 -4 1 5.5947 7 2987 -1 4 5.5948 7 3251 -4 1 5.5948 7 2869 4 3 5.5949 7 2903 -2 3 5.5949 7 2857 4 3 5.5950 7 3169 -1 3 5.5951 7 2968 -3 2 5.5952 7 2984 1 3 5.5953 7 3027 -3 2 5.5954 7 3411 -3 1 5.5955 7 2986 -1 3 5.5959 7 2923 -1 4 5.5959 7 3158 -2 3 5.5959 7 2856 4 1 5.5963 7 2964 1 4 5.5964 7 2879 2 3 5.5965 7 2904 -3 4 5.5971 7 2862 -1 4 5.5972 7 3028 -3 2 5.5973 7 3100 -4 1 5.5976 7 2867 -5 1 5.5978 7 2856 -5 2 5.5980 7 3075 -3 2 5.5980 7 2900 -4 1 5.5981 7 3012 1 4 5.5982 7 2959 4 3 5.5983 7 2968 5 1 5.5985 7 2903 5 2 5.5987 7 3200 5 1 5.5988 7 2886 4 1 5.5992 7 2911 -7 1 5.5994 6 958 -5 1 5.5997 7 3458 3 1 5.5998 6 958 4 3 5.5998 7 3434 3 2 5.6007 7 3092 -4 1 5.6007 7 3304 -4 1 5.6009 7 2897 -2 3 5.6010 7 2901 4 3 5.6011 7 3409 -1 3 5.6014 7 3288 -3 2 5.6015 7 2875 -5 1 5.6015 7 2875 5 1 5.6019 7 2935 1 4 5.6019 7 2919 -6 1 5.6019 7 2920 -6 1 5.6021 7 3333 -4 1 5.6022 7 3095 -1 4 5.6024 7 3397 1 3 5.6027 7 2850 4 3 5.6027 7 2864 -3 4 5.6027 7 2869 -5 1 5.6027 7 3476 1 2 5.6027 7 2987 -4 3 5.6029 7 2988 -4 3 5.6039 7 3510 1 3 5.6039 7 3509 1 3 5.6039 7 2898 5 2 5.6042 7 3117 4 1 5.6042 7 3007 -3 2 5.6042 7 2917 4 3 5.6044 7 2966 1 4 5.6049 7 2998 -5 1 5.6052 7 2903 1 4 5.6056 7 2950 4 3 5.6058 7 2911 7 1 5.6059 7 3068 2 3 5.6061 7 2866 5 2 5.6062 7 3158 3 2 5.6063 7 3188 1 3 5.6063 7 2926 5 1 5.6064 7 2889 7 1 5.6064 7 3245 4 1 5.6066 7 3441 3 1 5.6067 7 3158 4 1 5.6067 7 2951 5 2 5.6067 7 3051 1 4 5.6069 7 3169 -2 3 5.6070 7 3262 -3 2 5.6070 7 2967 -3 2 5.6072 7 2947 -5 2 5.6074 7 3328 -4 1 5.6074 7 3077 3 2 5.6078 7 2870 2 3 5.6080 7 3068 5 1 5.6084 7 2913 1 4 5.6087 7 3015 1 4 5.6087 7 2917 5 1 5.6087 7 3188 2 3 5.6090 7 2865 5 2 5.6091 7 3012 -3 2 5.6092 7 3046 5 1 5.6093 7 2910 -5 1 5.6096 7 3280 -4 1 5.6100 7 2999 -4 1 5.6105 7 3288 3 2 5.6107 7 3102 2 3 5.6109 7 3463 3 1 5.6110 7 2930 4 1 5.6111 7 3273 2 3 5.6111 7 2933 1 4 5.6111 7 2956 -4 3 5.6114 7 2913 -1 4 5.6119 7 2950 5 2 5.6122 7 3047 2 3 5.6127 7 3005 -1 4 5.6128 7 2950 -2 3 5.6128 7 3078 -3 2 5.6134 7 3123 1 3 5.6134 7 3427 3 2 5.6134 7 3467 3 2 5.6136 7 3162 -4 1 5.6136 7 3005 4 1 5.6137 7 3015 4 3 5.6138 7 2898 -3 2 5.6140 7 3107 1 3 5.6140 7 3109 -1 3 5.6140 7 3327 3 2 5.6141 7 2855 -4 3 5.6141 7 2974 5 2 5.6144 7 3333 3 2 5.6144 7 3439 3 2 5.6145 7 2877 7 1 5.6145 7 2988 -1 4 5.6152 7 3352 2 3 5.6154 7 2915 5 1 5.6156 7 2930 -3 4 5.6158 7 3077 7 1 5.6159 7 2908 3 2 5.6160 7 2953 4 3 5.6163 7 3423 -1 2 5.6163 7 3258 -5 1 5.6163 7 3469 3 2 5.6163 7 3217 -4 1 5.6163 7 3213 -4 1 5.6163 7 3210 4 1 5.6163 7 3207 4 1 5.6164 7 2932 1 4 5.6165 7 2889 -7 1 5.6166 7 3162 5 1 5.6166 7 3062 3 2 5.6171 7 3136 1 3 5.6174 7 2899 5 1 5.6174 7 3085 -4 1 5.6174 7 3347 3 2 5.6177 7 3202 1 3 5.6178 7 3262 3 2 5.6179 7 2957 5 1 5.6181 7 3069 -3 2 5.6181 7 3070 -4 1 5.6182 7 3337 -3 2 5.6182 7 2959 5 1 5.6184 7 2901 -2 3 5.6185 7 3013 5 1 5.6185 7 2909 5 1 5.6188 7 3238 1 3 5.6190 7 3498 4 1 5.6190 7 2953 -2 3 5.6191 7 2901 -5 1 5.6192 7 2972 5 2 5.6193 7 2993 -1 4 5.6195 7 2949 -2 3 5.6196 7 2968 5 2 5.6196 7 2984 2 3 5.6198 7 2899 1 4 5.6199 7 2873 5 2 5.6199 7 2874 5 2 5.6200 7 3268 1 3 5.6200 7 2965 5 1 5.6204 7 3086 1 3 5.6205 7 3106 -3 2 5.6205 7 3110 3 2 5.6206 7 3047 -2 3 5.6209 7 2930 2 3 5.6210 7 3528 -3 1 5.6210 7 3126 -3 2 5.6212 7 3372 -1 3 5.6213 7 2914 -3 2 5.6214 7 2875 -1 4 5.6214 7 2875 1 4 5.6216 7 3514 1 2 5.6217 7 3543 2 1 5.6219 7 2915 -2 3 5.6223 7 3226 -3 2 5.6227 7 2923 -4 3 5.6228 7 2949 5 1 5.6228 7 3012 -2 3 5.6231 7 3015 -3 2 5.6231 7 2960 5 2 5.6233 7 3091 -5 1 5.6234 7 3007 -4 1 5.6234 7 3137 -2 3 5.6235 7 3356 4 1 5.6238 7 3100 4 1 5.6238 7 2988 2 3 5.6246 7 3275 -4 1 5.6247 7 3012 4 3 5.6248 7 2997 -1 4 5.6249 7 3016 -2 3 5.6250 7 3374 4 1 5.6250 7 3520 4 1 5.6250 7 3444 -3 2 5.6250 7 3505 -4 1 5.6250 7 3519 -4 1 5.6250 7 3276 -3 1 5.6251 7 2979 -4 3 5.6253 7 2935 -1 4 5.6254 7 3074 -4 1 5.6254 7 3001 3 4 5.6256 7 2899 4 3 5.6256 7 2898 5 1 5.6258 7 3190 2 3 5.6258 7 3147 -3 2 5.6258 7 2894 7 1 5.6259 7 3062 2 3 5.6262 7 3016 -4 1 5.6264 7 3428 5 1 5.6266 7 3178 -4 1 5.6270 7 3198 -1 3 5.6271 7 3524 -3 1 5.6271 7 2944 -5 1 5.6273 7 2965 5 2 5.6273 7 2949 5 2 5.6275 7 2929 2 3 5.6276 7 3077 -6 1 5.6277 7 3425 4 1 5.6278 7 2904 -5 1 5.6280 7 3026 -4 3 5.6281 7 3123 -1 4 5.6283 7 2906 1 4 5.6283 7 3001 -3 2 5.6285 7 3046 -1 3 5.6286 7 3128 3 2 5.6287 7 2908 -5 2 5.6288 7 3022 4 1 5.6288 6 957 2 3 5.6288 6 955 2 3 5.6288 6 960 2 3 5.6289 7 3162 1 3 5.6290 7 2946 7 1 5.6291 7 3165 -1 3 5.6292 7 2929 3 2 5.6292 7 2904 -5 2 5.6293 7 3404 1 3 5.6294 7 3009 6 1 5.6295 7 3041 -1 4 5.6298 7 2971 -1 4 5.6300 7 3314 -2 3 5.6301 7 3056 -3 2 5.6302 7 3011 -2 3 5.6311 7 2903 3 4 5.6314 7 3266 -1 3 5.6319 7 2941 1 4 5.6321 7 3011 5 1 5.6323 7 2883 -5 2 5.6326 7 3074 4 1 5.6328 7 2959 -2 3 5.6328 7 2997 -4 3 5.6329 7 3268 3 1 5.6330 7 2997 2 3 5.6330 7 3107 -1 3 5.6330 7 3109 1 3 5.6336 7 3091 3 2 5.6340 7 2916 3 4 5.6341 7 3009 -6 1 5.6341 7 2986 2 3 5.6349 7 3071 1 3 5.6349 7 3408 -1 2 5.6350 7 2974 -2 3 5.6350 7 3028 -2 3 5.6351 7 3166 4 1 5.6351 7 2916 4 3 5.6351 7 2951 -5 1 5.6352 7 2997 4 1 5.6354 7 3479 3 1 5.6355 7 3314 1 3 5.6356 7 3217 3 2 5.6356 7 3210 -3 2 5.6356 7 3207 -3 2 5.6356 7 3213 3 2 5.6362 7 3455 3 1 5.6362 7 2949 1 4 5.6364 7 3147 4 1 5.6368 7 3106 4 1 5.6368 7 3110 -4 1 5.6369 7 3068 -1 3 5.6369 7 3038 5 2 5.6370 7 3245 -5 1 5.6373 7 3234 1 3 5.6373 7 3122 -4 1 5.6374 7 2978 5 1 5.6377 7 3187 5 1 5.6378 7 3056 1 4 5.6383 7 2939 1 4 5.6384 7 3013 5 2 5.6386 7 3284 3 1 5.6389 7 2946 5 2 5.6392 7 2941 -1 4 5.6395 7 3151 4 1 5.6395 7 2920 7 1 5.6395 7 2919 7 1 5.6397 7 3001 4 3 5.6398 7 3257 3 2 5.6398 7 3000 -3 4 5.6398 7 2993 -3 4 5.6402 7 3305 -3 2 5.6404 7 3201 -1 3 5.6406 7 3452 -3 2 5.6407 7 3028 4 3 5.6410 7 2978 1 4 5.6410 7 3015 -2 3 5.6410 7 2965 -2 3 5.6411 7 3093 5 1 5.6412 7 2931 4 3 5.6412 7 3169 4 1 5.6418 7 2988 4 1 5.6419 7 3087 -3 2 5.6422 7 2983 -4 1 5.6423 7 3026 4 1 5.6424 7 3105 -5 1 5.6424 7 2938 -4 1 5.6426 7 3007 5 2 5.6426 7 3106 1 4 5.6426 7 3110 -1 4 5.6427 7 2959 1 4 5.6428 7 3191 -3 2 5.6430 7 3165 2 3 5.6430 7 2930 -5 1 5.6432 7 2998 2 3 5.6433 7 3007 5 1 5.6434 7 2923 -5 1 5.6441 7 3047 5 1 5.6442 7 3179 4 1 5.6448 7 2944 -4 3 5.6449 7 2951 -2 3 5.6450 7 2974 4 3 5.6453 7 2946 -7 1 5.6453 7 2972 4 3 5.6455 7 3017 1 4 5.6456 7 3105 1 3 5.6458 7 3080 5 2 5.6460 7 3232 4 1 5.6460 7 3338 3 2 5.6463 7 2917 5 2 5.6463 7 3116 -4 1 5.6463 7 3345 -1 3 5.6466 7 3157 5 1 5.6470 7 2968 1 4 5.6471 7 2995 5 1 5.6476 7 3199 -6 1 5.6477 7 2987 -3 4 5.6479 7 3339 1 3 5.6480 7 3046 -5 1 5.6481 7 3080 -5 1 5.6483 7 2972 -2 3 5.6486 7 3129 1 3 5.6487 7 3439 1 3 5.6491 7 3187 3 2 5.6492 7 3085 -1 4 5.6493 7 3001 -2 3 5.6494 7 3028 1 4 5.6494 7 3264 4 1 5.6496 7 2930 -5 2 5.6497 7 3071 -5 1 5.6499 7 3105 -1 3 5.6500 7 3116 -2 3 5.6502 7 3043 4 3 5.6503 7 3041 -3 4 5.6505 7 2927 -6 1 5.6507 7 2958 -5 1 5.6507 7 2987 4 1 5.6510 7 3264 -1 3 5.6510 7 2984 -1 3 5.6511 7 3085 3 2 5.6512 7 3162 2 3 5.6512 7 2978 -2 3 5.6516 7 3421 -2 3 5.6516 7 3234 -4 1 5.6517 7 3102 -1 4 5.6520 7 3056 -2 3 5.6522 7 3022 -1 4 5.6523 7 2984 5 2 5.6523 7 2959 5 2 5.6525 7 2951 4 3 5.6526 7 3215 1 3 5.6527 7 3191 -2 3 5.6531 7 3320 -3 2 5.6533 7 3199 5 1 5.6533 7 3198 -2 3 5.6533 7 3109 5 1 5.6533 7 3107 -5 1 5.6536 7 3067 4 3 5.6540 7 3142 -1 3 5.6540 7 3177 -3 2 5.6541 7 3032 4 1 5.6542 7 3284 1 3 5.6547 7 3543 1 2 5.6550 7 3169 -5 1 5.6550 7 3168 -1 3 5.6552 7 3358 -1 3 5.6553 7 3231 4 1 5.6555 7 3120 -1 4 5.6560 7 3077 -3 2 5.6562 7 3375 -3 2 5.6562 7 3145 3 2 5.6562 7 3529 -1 2 5.6562 7 3181 -3 2 5.6562 7 3144 -7 1 5.6562 7 3519 -1 2 5.6562 7 3043 1 4 5.6562 7 3351 3 2 5.6562 7 3387 3 2 5.6562 7 3136 -1 3 5.6562 7 3378 -3 2 5.6562 7 3377 -3 2 5.6563 7 3025 -1 4 5.6563 7 3168 -3 2 5.6564 7 2983 4 3 5.6564 7 3093 -1 3 5.6566 7 3062 -1 4 5.6566 7 3194 3 1 5.6566 7 3262 2 3 5.6570 7 3201 -3 2 5.6572 7 3248 -1 3 5.6572 7 3214 -4 1 5.6573 7 3166 -3 2 5.6577 7 3516 1 2 5.6577 7 3188 -4 1 5.6577 7 3189 -1 3 5.6581 7 3009 5 2 5.6584 7 2986 -2 3 5.6587 7 3142 -2 3 5.6588 7 3106 -2 3 5.6588 7 3110 2 3 5.6590 7 3297 -4 1 5.6591 7 2978 5 2 5.6593 7 3295 3 1 5.6594 7 3263 5 1 5.6596 7 3043 -3 2 5.6597 7 3051 -3 2 5.6602 7 3069 -4 1 5.6602 7 3410 4 1 5.6603 7 3016 1 4 5.6604 7 3051 4 3 5.6611 7 2965 1 4 5.6615 7 2938 -1 4 5.6617 7 2966 5 1 5.6618 7 3346 3 2 5.6618 7 3439 -3 1 5.6620 7 3017 3 4 5.6620 7 2957 4 3 5.6621 7 3183 4 1 5.6621 7 2984 6 1 5.6627 7 3080 6 1 5.6628 7 3028 -4 1 5.6628 7 3381 -5 1 5.6631 7 3215 -3 2 5.6637 7 2955 -3 2 5.6637 7 3168 -4 1 5.6640 7 3310 6 1 5.6640 7 3017 5 2 5.6641 7 3085 2 3 5.6643 7 2960 -2 3 5.6645 7 3200 -5 1 5.6645 7 3420 3 2 5.6646 7 3261 5 1 5.6647 7 2983 3 4 5.6647 7 3200 -3 2 5.6651 7 3193 4 1 5.6651 7 3101 5 1 5.6651 7 3101 -5 1 5.6651 7 2968 -2 3 5.6655 7 3069 1 4 5.6657 7 3016 4 3 5.6658 7 3087 1 4 5.6658 7 3005 1 4 5.6664 7 3272 1 3 5.6664 7 3209 5 1 5.6664 7 3208 5 1 5.6664 7 3212 5 1 5.6669 7 3339 3 1 5.6670 7 3201 -4 1 5.6670 7 3137 4 1 5.6671 7 2958 -1 4 5.6673 7 3117 1 4 5.6673 7 3086 3 2 5.6673 7 2967 -4 1 5.6673 7 2957 1 4 5.6677 7 3185 4 1 5.6679 7 3067 -3 2 5.6679 7 3190 -1 3 5.6680 7 2986 6 1 5.6682 7 3051 -2 3 5.6686 7 2964 -1 4 5.6690 7 3288 2 3 5.6693 7 3477 1 2 5.6698 7 3509 3 1 5.6698 7 3510 3 1 5.6699 7 3078 5 2 5.6700 7 3086 4 1 5.6700 7 3057 4 3 5.6701 7 3382 -4 1 5.6702 7 3372 1 3 5.6702 7 2986 5 2 5.6703 7 3509 2 3 5.6703 7 3510 2 3 5.6705 7 3275 -3 2 5.6706 7 3539 1 2 5.6707 7 3022 -4 3 5.6710 7 3015 3 4 5.6714 7 3219 -5 1 5.6718 7 2944 -5 2 5.6718 7 3244 4 1 5.6718 7 3243 4 1 5.6718 7 3246 4 1 5.6718 7 3247 -4 1 5.6718 7 3505 5 1 5.6718 7 3492 -3 1 5.6718 7 3490 -3 1 5.6719 7 2999 5 1 5.6723 7 2993 4 1 5.6725 7 3365 3 2 5.6725 7 2960 6 1 5.6726 7 3408 -4 1 5.6729 7 3133 3 2 5.6733 7 2983 -1 4 5.6736 7 3086 -4 3 5.6736 7 3081 -6 1 5.6738 7 3305 3 2 5.6739 7 3398 3 2 5.6739 7 3486 4 1 5.6740 7 3157 -3 2 5.6741 7 2995 -5 1 5.6742 7 3136 2 3 5.6743 7 3019 5 2 5.6745 7 3026 -5 1 5.6745 7 3012 3 4 5.6745 7 3408 5 1 5.6748 7 2988 -3 4 5.6751 7 3095 -4 3 5.6752 7 3070 -2 3 5.6757 7 2978 3 4 5.6757 7 3071 -4 3 5.6758 7 3067 -4 1 5.6759 7 3122 -2 3 5.6759 7 3358 2 3 5.6761 7 3068 -5 1 5.6761 7 3238 -4 1 5.6761 7 3198 3 2 5.6764 7 3258 3 2 5.6765 7 3257 -5 1 5.6766 7 3324 1 3 5.6767 7 3221 5 1 5.6767 7 3228 -5 1 5.6768 7 3276 4 1 5.6773 7 3201 2 3 5.6774 7 2993 2 3 5.6775 7 2999 5 2 5.6778 7 3522 3 1 5.6781 7 3034 4 1 5.6782 7 3145 -4 1 5.6783 7 3092 -3 2 5.6789 7 3508 -3 1 5.6789 7 3511 -3 1 5.6790 7 3198 4 1 5.6791 7 3111 4 1 5.6794 7 3248 3 2 5.6795 7 2972 -5 1 5.6796 7 2998 -5 2 5.6797 7 3025 1 4 5.6797 7 2979 5 1 5.6798 7 3074 1 4 5.6799 7 3069 -2 3 5.6800 7 3051 3 4 5.6803 7 2998 5 1 5.6804 7 3092 5 2 5.6805 7 3246 -3 2 5.6805 7 3244 -3 2 5.6805 7 3091 1 3 5.6809 7 2959 3 4 5.6809 7 3008 5 2 5.6810 7 3043 -4 1 5.6813 7 3086 -5 1 5.6815 7 3012 -4 1 5.6815 7 3017 -3 2 5.6815 7 3476 -4 1 5.6816 7 2999 1 4 5.6817 7 3293 -4 1 5.6817 7 3392 -1 3 5.6818 7 3147 -2 3 5.6818 7 3154 4 1 5.6819 7 3190 -4 1 5.6821 7 3353 -5 1 5.6824 7 3026 -5 2 5.6826 7 3007 -2 3 5.6826 7 3008 -7 1 5.6836 7 3172 2 3 5.6837 7 3014 -3 2 5.6838 7 3027 4 3 5.6838 7 3107 2 3 5.6838 7 3109 -2 3 5.6839 7 3142 3 2 5.6840 7 3027 -1 4 5.6842 7 3126 -2 3 5.6845 7 3095 -3 4 5.6848 7 3172 -4 1 5.6850 7 3531 -4 1 5.6851 7 2998 -4 3 5.6851 7 2997 -5 1 5.6852 7 3194 -4 1 5.6854 7 3010 -5 1 5.6856 7 3019 -2 3 5.6857 7 3416 -4 1 5.6859 7 2969 5 3 5.6862 7 3137 -4 1 5.6863 7 3032 2 3 5.6866 7 3438 -3 1 5.6870 7 3098 4 1 5.6870 7 3105 5 1 5.6872 7 3001 -4 1 5.6872 7 3036 3 2 5.6872 7 3377 -1 3 5.6872 7 3513 3 1 5.6872 7 3378 -1 3 5.6872 7 3214 3 2 5.6872 7 3119 4 3 5.6874 7 3019 -5 1 5.6874 7 3337 -2 3 5.6883 7 2984 -6 1 5.6884 7 3539 -3 1 5.6891 7 3202 3 2 5.6892 7 3249 -2 3 5.6895 7 3119 1 4 5.6896 7 3089 -4 1 5.6908 7 3172 3 2 5.6909 7 3089 1 4 5.6909 7 3026 -1 4 5.6909 7 3457 -1 3 5.6910 7 2988 -5 1 5.6912 7 3017 5 1 5.6914 7 2994 -5 1 5.6917 7 3015 -4 1 5.6921 7 3178 3 2 5.6923 7 3258 -3 2 5.6924 7 3347 4 1 5.6925 7 3463 -3 2 5.6926 7 3492 3 2 5.6926 7 3490 3 2 5.6926 7 3019 4 3 5.6927 7 3362 -3 2 5.6927 7 3081 6 1 5.6928 7 3521 3 1 5.6929 7 2988 -5 2 5.6930 7 3177 -2 3 5.6930 7 3008 7 1 5.6932 7 3434 1 3 5.6934 7 3295 -4 1 5.6935 7 3130 3 2 5.6935 7 3441 -3 2 5.6936 7 3128 2 3 5.6936 7 3056 4 3 5.6937 7 3154 1 3 5.6939 7 3092 -1 3 5.6939 7 3234 2 3 5.6940 7 3162 -3 2 5.6940 7 3297 -3 2 5.6941 7 3467 -3 1 5.6945 7 2985 1 4 5.6945 7 3151 1 4 5.6946 7 3251 -3 2 5.6947 7 3000 2 3 5.6948 7 3071 -5 2 5.6950 7 3031 3 2 5.6951 7 3093 -5 1 5.6954 7 3195 3 2 5.6957 7 3016 5 1 5.6957 7 3278 4 1 5.6960 7 3229 3 2 5.6960 7 3199 3 2 5.6960 7 3262 -4 1 5.6964 7 3074 -1 4 5.6964 7 3318 2 3 5.6964 7 3318 -3 2 5.6965 7 3166 -2 3 5.6967 7 3288 -2 3 5.6968 7 3103 6 1 5.6968 7 3086 -5 2 5.6969 7 3078 -5 1 5.6969 7 3543 -3 2 5.6975 7 3087 -2 3 5.6976 7 3143 3 2 5.6980 7 3047 -5 1 5.6982 7 3075 5 2 5.6982 7 3062 4 1 5.6982 7 3129 -4 3 5.6983 7 3219 -3 2 5.6985 7 3529 1 2 5.6985 7 3310 -7 1 5.6985 7 3125 -1 4 5.6986 7 3032 -5 1 5.6988 7 3011 4 3 5.6989 7 3027 -4 1 5.6992 7 3091 -5 2 5.6993 7 3107 -2 3 5.6993 7 3109 2 3 5.6993 7 3283 3 1 5.6994 7 3013 -2 3 5.6999 7 3041 3 2 5.7003 7 2997 -5 2 5.7003 7 3183 -4 1 5.7004 7 3395 5 1 5.7004 7 3070 5 1 5.7004 7 3172 -1 4 5.7005 7 3083 -4 3 5.7005 7 3226 3 2 5.7005 7 3111 2 3 5.7006 7 3117 -3 2 5.7012 7 3033 3 2 5.7012 7 3027 3 4 5.7015 7 3442 3 1 5.7019 7 3247 1 3 5.7019 7 3243 -1 3 5.7020 7 3184 -7 1 5.7022 7 3007 1 4 5.7024 7 3212 1 3 5.7024 7 3209 1 3 5.7024 7 3269 4 1 5.7024 7 3208 1 3 5.7025 7 3409 -3 2 5.7028 7 3278 1 3 5.7033 7 3075 -6 1 5.7033 7 3031 2 3 5.7038 7 3275 1 3 5.7038 7 3392 3 1 5.7041 7 3067 1 4 5.7042 7 3046 -2 3 5.7045 7 3011 5 2 5.7048 7 3056 -4 1 5.7048 7 3221 3 2 5.7048 7 3228 -3 2 5.7049 7 3017 -4 1 5.7054 7 3008 -5 2 5.7056 7 3043 3 4 5.7056 7 3188 5 1 5.7056 7 3392 -4 1 5.7057 7 3209 -3 2 5.7057 7 3208 -3 2 5.7057 7 3212 -3 2 5.7058 7 3014 -2 3 5.7059 7 3105 -2 3 5.7061 7 3006 -4 3 5.7063 7 3144 7 1 5.7064 7 3416 -3 2 5.7064 7 3479 1 3 5.7064 7 3138 -4 3 5.7065 7 3142 -5 2 5.7066 7 3105 2 3 5.7067 7 3338 5 1 5.7068 7 3009 1 3 5.7069 7 3411 1 3 5.7071 7 3435 -3 1 5.7076 7 3304 -3 2 5.7078 7 3087 -4 1 5.7083 7 3185 3 2 5.7084 7 3124 3 2 5.7086 7 3234 -3 2 5.7088 7 3272 -4 1 5.7088 7 3273 -1 3 5.7092 7 3154 3 2 5.7096 7 3347 -4 1 5.7099 7 3124 -4 3 5.7099 7 3120 -4 3 5.7101 7 3344 3 1 5.7102 7 3308 4 1 5.7104 7 3347 -3 2 5.7104 7 3240 1 3 5.7104 7 3109 -5 1 5.7104 7 3107 5 1 5.7112 7 3028 3 4 5.7113 7 3484 -3 1 5.7115 7 3075 7 1 5.7119 7 3194 -4 3 5.7119 7 3126 -4 1 5.7124 7 3189 -3 2 5.7127 7 3215 2 3 5.7133 7 3299 4 1 5.7142 7 3043 -2 3 5.7146 7 3331 3 1 5.7147 7 3169 3 2 5.7148 7 3117 -2 3 5.7149 7 3128 4 1 5.7151 7 3185 1 3 5.7154 7 3154 -5 1 5.7157 7 3232 4 3 5.7158 7 3078 -1 3 5.7159 7 3017 5 3 5.7160 7 3284 -4 1 5.7161 7 3049 5 1 5.7162 7 3191 4 1 5.7162 7 3441 -2 3 5.7163 7 3412 6 1 5.7163 7 3144 -1 2 5.7164 7 3017 -2 3 5.7165 7 3433 -1 3 5.7167 7 3010 -4 3 5.7168 7 3234 5 1 5.7169 7 3085 -4 3 5.7170 7 3083 -3 4 5.7171 7 3117 4 3 5.7174 7 3066 -1 3 5.7174 7 3066 1 3 5.7175 7 3106 -4 1 5.7175 7 3110 4 1 5.7184 7 3229 -4 1 5.7185 7 3009 2 3 5.7191 7 3038 -5 1 5.7193 7 3261 -3 2 5.7194 7 3252 -3 2 5.7196 7 3045 1 4 5.7197 7 3249 4 1 5.7199 7 3362 4 1 5.7200 7 3382 5 1 5.7200 7 3067 3 4 5.7203 7 3016 5 2 5.7203 7 3030 -3 4 5.7205 7 3092 4 3 5.7205 7 3057 5 1 5.7208 7 3116 1 4 5.7211 7 3249 -5 1 5.7212 7 3007 5 3 5.7213 7 3119 3 4 5.7214 7 3320 3 2 5.7215 7 3198 1 3 5.7216 7 3144 1 2 5.7217 7 3098 -5 1 5.7222 7 3091 -4 3 5.7223 7 3041 2 3 5.7223 7 3068 -2 3 5.7223 7 3328 5 1 5.7227 7 3345 1 3 5.7228 7 3207 -2 3 5.7228 7 3210 -2 3 5.7228 7 3213 2 3 5.7228 7 3217 2 3 5.7229 7 3022 -3 4 5.7232 7 3262 -2 3 5.7234 7 3188 -3 2 5.7235 7 3147 -4 1 5.7237 7 3062 -4 3 5.7242 7 3358 3 2 5.7243 7 3209 2 3 5.7243 7 3313 3 1 5.7243 7 3208 2 3 5.7243 7 3216 -6 1 5.7243 7 3212 2 3 5.7244 7 3283 -4 1 5.7246 7 3365 -3 2 5.7247 7 3194 -3 4 5.7247 7 3161 4 3 5.7251 7 3132 -3 2 5.7254 7 3329 4 1 5.7257 7 3043 5 1 5.7257 7 3119 -3 2 5.7257 7 3314 -4 1 5.7259 7 3184 -1 2 5.7261 7 3069 4 3 5.7261 7 3268 -4 1 5.7262 7 3080 1 3 5.7262 7 3473 -3 1 5.7266 7 3205 -1 3 5.7267 7 3111 -4 3 5.7268 7 3415 4 1 5.7272 7 3215 5 1 5.7275 7 3189 -4 1 5.7276 7 3046 5 2 5.7276 7 3085 -3 4 5.7277 7 3033 2 3 5.7280 7 3156 -3 2 5.7283 7 3141 -4 1 5.7288 7 3151 -3 2 5.7288 7 3078 6 1 5.7289 7 3317 -3 1 5.7292 7 3318 -2 3 5.7292 7 3318 3 2 5.7293 7 3210 -4 1 5.7293 7 3207 -4 1 5.7293 7 3213 4 1 5.7293 7 3217 4 1 5.7293 7 3067 5 1 5.7297 7 3517 3 1 5.7298 7 3056 3 4 5.7304 7 3283 1 3 5.7305 7 3276 4 3 5.7306 7 3136 5 1 5.7306 7 3143 2 3 5.7306 7 3087 4 3 5.7307 7 3335 -5 1 5.7308 7 3204 -1 4 5.7314 7 3136 -2 3 5.7315 7 3523 -3 1 5.7315 7 3248 -2 3 5.7319 7 3392 -2 3 5.7319 7 3534 -3 1 5.7319 7 3132 1 4 5.7321 7 3283 -4 3 5.7322 7 3077 5 2 5.7324 7 3203 3 2 5.7326 7 3231 -4 1 5.7326 7 3041 -5 2 5.7328 7 3394 -5 1 5.7329 7 3066 -5 2 5.7329 7 3066 5 2 5.7331 7 3226 -6 1 5.7334 7 3162 5 2 5.7339 7 3216 5 1 5.7340 7 3141 4 3 5.7342 7 3216 3 2 5.7346 7 3130 -4 3 5.7348 7 3057 1 4 5.7348 7 3072 -3 4 5.7349 7 3204 2 3 5.7350 7 3240 3 2 5.7359 7 3373 -4 1 5.7360 7 3068 5 2 5.7361 7 3081 5 2 5.7363 7 3539 -3 2 5.7364 7 3165 -2 3 5.7366 7 3299 3 2 5.7367 7 3214 2 3 5.7367 7 3377 1 3 5.7367 7 3378 1 3 5.7369 7 3130 4 1 5.7369 7 3178 2 3 5.7370 7 3350 3 1 5.7370 7 3089 -1 4 5.7374 7 3076 1 4 5.7374 7 3043 5 2 5.7376 7 3057 5 2 5.7377 7 3124 4 1 5.7378 7 3205 4 3 5.7380 7 3386 -4 1 5.7390 7 3262 4 1 5.7391 7 3349 3 1 5.7391 7 3362 3 2 5.7392 7 3266 4 1 5.7392 7 3195 -6 1 5.7394 7 3051 -4 1 5.7395 7 3337 3 2 5.7396 7 3045 -1 4 5.7397 7 3439 2 3 5.7399 7 3093 -2 3 5.7399 7 3169 -5 2 5.7400 7 3147 1 4 5.7403 7 3365 1 3 5.7408 7 3229 2 3 5.7409 7 3062 1 4 5.7409 7 3170 4 1 5.7410 7 3313 1 3 5.7411 7 3328 -3 2 5.7414 7 3245 -1 3 5.7417 7 3102 1 4 5.7418 7 3080 2 3 5.7419 7 3203 -1 4 5.7419 7 3087 3 4 5.7421 7 3136 -5 1 5.7421 7 3067 -2 3 5.7423 7 3345 -2 3 5.7426 7 3435 4 1 5.7427 7 3177 -4 1 5.7431 7 3275 2 3 5.7431 7 3184 7 1 5.7433 7 3280 1 3 5.7438 7 3120 -3 4 5.7442 7 3070 5 2 5.7443 7 3067 5 2 5.7445 7 3198 -5 1 5.7445 7 3457 -2 3 5.7445 7 3138 -1 4 5.7445 7 3351 5 1 5.7446 7 3137 1 4 5.7447 7 3523 3 1 5.7450 7 3246 -2 3 5.7450 7 3244 -2 3 5.7450 7 3132 4 3 5.7451 7 3184 1 2 5.7452 7 3185 -5 1 5.7453 7 3404 4 1 5.7455 7 3234 -1 3 5.7455 7 3157 5 2 5.7457 7 3117 3 4 5.7458 7 3229 -1 4 5.7459 7 3202 4 1 5.7462 7 3066 2 3 5.7462 7 3066 -2 3 5.7462 7 3066 6 1 5.7462 7 3066 -6 1 5.7468 7 3129 -1 4 5.7469 7 3077 8 1 5.7472 7 3215 -1 3 5.7474 7 3409 -2 3 5.7478 7 3156 4 3 5.7481 7 3409 1 3 5.7482 7 3356 -5 1 5.7482 7 3457 3 1 5.7489 7 3410 -4 1 5.7490 7 3138 3 2 5.7491 7 3442 1 3 5.7491 7 3352 4 1 5.7493 7 3162 -1 3 5.7493 7 3532 3 1 5.7494 7 3276 -1 3 5.7495 7 3100 -1 4 5.7496 7 3251 5 1 5.7497 7 3071 2 3 5.7499 7 3327 5 1 5.7502 7 3120 3 2 5.7503 7 3345 -4 1 5.7507 7 3353 -3 2 5.7509 7 3408 3 2 5.7510 7 3237 -4 3 5.7511 7 3245 3 2 5.7512 7 3169 1 3 5.7512 7 3168 5 1 5.7515 7 3261 -5 1 5.7515 7 3121 1 4 5.7516 7 3346 5 1 5.7517 7 3130 -1 4 5.7520 7 3248 1 3 5.7528 7 3398 -4 1 5.7530 7 3085 4 1 5.7531 7 3477 -3 2 5.7535 7 3157 -1 3 5.7535 7 3158 -5 1 5.7537 7 3503 3 1 5.7541 7 3187 -6 1 5.7546 7 3078 4 3 5.7548 7 3047 5 2 5.7549 7 3415 -4 1 5.7550 7 3154 -4 3 5.7556 7 3455 -1 3 5.7557 7 3269 4 3 5.7559 7 3130 2 3 5.7560 7 3278 -2 3 5.7562 7 3345 3 2 5.7564 7 3070 1 4 5.7568 7 3249 3 2 5.7569 7 3151 -2 3 5.7569 7 3353 3 2 5.7570 7 3347 1 3 5.7571 7 3056 -1 4 5.7572 7 3397 3 2 5.7572 7 3076 -1 4 5.7573 7 3143 4 1 5.7575 7 3434 2 3 5.7577 7 3514 4 1 5.7578 7 3410 -3 2 5.7582 7 3080 -1 3 5.7582 7 3257 5 1 5.7585 7 3129 3 2 5.7585 7 3132 -4 1 5.7587 7 3069 3 4 5.7588 7 3086 2 3 5.7590 7 3331 1 3 5.7590 7 3158 2 3 5.7590 7 3240 4 1 5.7593 7 3308 3 2 5.7598 7 3095 3 2 5.7599 7 3289 1 3 5.7607 7 3141 5 1 5.7609 7 3178 -1 4 5.7610 7 3320 -5 1 5.7611 7 3372 3 2 5.7614 7 3124 -5 1 5.7615 7 3076 5 1 5.7617 7 3121 3 4 5.7618 7 3166 -4 1 5.7618 7 3100 1 4 5.7619 7 3216 -5 2 5.7621 7 3259 4 1 5.7621 7 3410 3 2 5.7621 7 3161 3 4 5.7622 7 3144 -3 2 5.7622 7 3119 -2 3 5.7623 7 3516 -4 1 5.7627 7 3201 5 1 5.7627 7 3151 4 3 5.7627 7 3469 4 1 5.7627 7 3470 4 1 5.7627 7 3305 5 1 5.7627 7 3184 -3 2 5.7630 7 3260 -4 1 5.7631 7 3226 6 1 5.7631 7 3170 -4 3 5.7633 7 3166 1 4 5.7635 7 3474 -3 1 5.7638 7 3272 5 1 5.7642 7 3081 -5 2 5.7642 7 3344 1 3 5.7643 7 3266 -3 2 5.7648 7 3098 2 3 5.7649 7 3519 1 2 5.7649 7 3154 -5 2 5.7652 7 3128 -4 3 5.7652 7 3308 -5 1 5.7653 7 3221 -6 1 5.7653 7 3228 6 1 5.7654 7 3143 -4 3 5.7654 7 3129 4 1 5.7655 7 3133 1 3 5.7655 7 3349 1 3 5.7660 7 3124 -5 2 5.7662 7 3248 -5 1 5.7663 7 3268 -1 4 5.7664 7 3289 4 1 5.7664 7 3188 -1 3 5.7666 7 3095 2 3 5.7667 7 3143 -1 4 5.7668 7 3273 -3 2 5.7671 7 3124 2 3 5.7672 7 3285 4 1 5.7674 7 3260 5 1 5.7679 7 3093 5 2 5.7679 7 3132 -2 3 5.7680 7 3293 5 1 5.7680 7 3340 4 1 5.7683 7 3165 5 1 5.7685 7 3191 1 4 5.7687 7 3412 -6 1 5.7694 7 3111 -1 4 5.7695 7 3203 2 3 5.7695 7 3288 -4 1 5.7697 7 3219 6 1 5.7697 7 3151 3 4 5.7700 7 3133 5 1 5.7701 7 3536 4 1 5.7703 7 3111 -5 1 5.7705 7 3178 4 1 5.7705 7 3120 2 3 5.7708 7 3106 -1 4 5.7708 7 3110 1 4 5.7709 7 3245 -2 3 5.7710 7 3202 2 3 5.7711 7 3258 6 1 5.7712 7 3399 4 1 5.7713 7 3122 5 1 5.7713 7 3458 -4 1 5.7714 7 3295 1 3 5.7718 7 3305 -5 1 5.7719 7 3126 1 4 5.7719 7 3128 -1 4 5.7721 7 3142 1 3 5.7723 7 3421 3 1 5.7724 7 3188 5 2 5.7724 7 3129 -5 1 5.7725 7 3462 3 2 5.7725 7 3387 2 3 5.7731 7 3126 4 3 5.7733 7 3435 2 3 5.7734 7 3327 -3 2 5.7736 7 3321 -4 1 5.7742 7 3293 -3 2 5.7742 7 3201 -2 3 5.7743 7 3193 -4 1 5.7744 7 3335 3 2 5.7748 7 3082 1 4 5.7751 7 3104 4 1 5.7753 7 3129 -3 4 5.7756 7 3085 1 4 5.7764 7 3509 3 2 5.7764 7 3510 3 2 5.7764 7 3119 -4 1 5.7765 7 3249 1 3 5.7767 7 3346 -5 1 5.7770 7 3404 -3 1 5.7770 7 3247 -1 4 5.7770 7 3243 1 4 5.7775 7 3138 2 3 5.7777 7 3125 3 2 5.7777 7 3181 -4 1 5.7779 7 3238 3 2 5.7780 7 3144 3 2 5.7781 7 3199 -5 2 5.7782 7 3280 3 2 5.7787 7 3117 -4 1 5.7788 7 3083 1 4 5.7790 7 3141 5 2 5.7791 7 3129 -5 2 5.7791 7 3264 -3 2 5.7792 7 3169 -4 3 5.7794 7 3145 4 1 5.7795 7 3284 -1 4 5.7798 7 3168 -2 3 5.7798 7 3320 5 1 5.7801 7 3395 3 2 5.7801 7 3259 1 3 5.7801 7 3238 -1 4 5.7803 7 3269 3 4 5.7803 7 3346 -3 2 5.7803 7 3103 5 2 5.7807 7 3190 5 1 5.7809 7 3351 -5 1 5.7809 7 3420 2 3 5.7810 7 3118 1 4 5.7817 7 3271 -1 4 5.7818 7 3272 -3 2 5.7820 7 3091 2 3 5.7820 7 3539 -2 3 5.7821 7 3124 -1 4 5.7824 7 3397 2 3 5.7824 7 3277 1 3 5.7827 7 3252 -5 1 5.7829 7 3313 -4 1 5.7832 7 3195 6 1 5.7834 7 3190 -2 3 5.7834 7 3288 4 1 5.7835 7 3170 -5 1 5.7835 7 3524 3 1 5.7836 7 3275 -1 3 5.7836 7 3144 -8 1 5.7841 7 3091 5 1 5.7842 7 3365 -1 3 5.7849 7 3521 -4 1 5.7850 7 3244 -4 1 5.7850 7 3246 -4 1 5.7850 7 3247 -4 3 5.7850 7 3243 4 3 5.7850 7 3365 4 1 5.7853 7 3105 -5 2 5.7855 7 3110 -3 4 5.7855 7 3106 3 4 5.7855 7 3232 3 4 5.7863 7 3104 -5 2 5.7866 7 3139 -3 4 5.7866 7 3427 1 3 5.7867 7 3482 -3 2 5.7877 7 3365 -4 1 5.7880 7 3142 -4 3 5.7881 7 3372 -3 2 5.7882 7 3397 -3 2 5.7882 7 3259 3 2 5.7883 7 3120 4 1 5.7886 7 3102 -3 4 5.7886 7 3411 2 3 5.7886 7 3161 -2 3 5.7888 7 3273 -4 1 5.7888 7 3314 3 2 5.7890 7 3266 1 4 5.7890 7 3142 5 1 5.7892 7 3188 4 3 5.7892 7 3189 5 1 5.7892 7 3214 4 1 5.7892 7 3329 -2 3 5.7895 7 3521 -3 2 5.7901 7 3134 -4 1 5.7903 7 3118 3 4 5.7905 7 3138 4 1 5.7905 7 3191 -4 1 5.7906 7 3428 1 2 5.7906 7 3111 -5 2 5.7909 7 3480 -4 1 5.7909 7 3327 -5 1 5.7911 7 3133 -4 3 5.7912 7 3193 1 4 5.7914 7 3398 5 1 5.7923 7 3118 -2 3 5.7925 7 3427 4 1 5.7930 7 3092 6 1 5.7931 7 3338 -3 2 5.7932 7 3274 1 3 5.7934 7 3107 -5 2 5.7934 7 3109 5 2 5.7934 7 3263 5 2 5.7934 7 3453 3 2 5.7934 7 3454 3 2 5.7937 7 3229 4 1 5.7942 7 3361 -1 3 5.7943 7 3198 -5 2 5.7944 7 3280 4 1 5.7944 7 3172 1 4 5.7946 7 3433 3 2 5.7948 7 3145 1 4 5.7948 7 3265 -4 3 5.7949 7 3283 -3 4 5.7949 7 3350 1 3 5.7952 7 3133 -6 1 5.7953 7 3156 -4 1 5.7953 7 3295 -4 3 5.7960 7 3337 2 3 5.7960 7 3157 4 3 5.7960 7 3138 -3 4 5.7962 7 3181 -1 4 5.7962 7 3137 -1 4 5.7962 7 3310 7 1 5.7963 7 3513 -3 2 5.7966 7 3138 -5 2 5.7966 7 3232 1 4 5.7968 7 3122 4 3 5.7971 7 3237 -1 4 5.7971 7 3200 5 2 5.7973 7 3324 -4 1 5.7976 7 3255 -2 3 5.7978 7 3420 -1 3 5.7978 7 3514 -1 2 5.7981 7 3381 5 1 5.7984 7 3313 -1 4 5.7987 7 3142 -6 1 5.7988 7 3455 -4 1 5.7990 7 3340 3 2 5.7991 7 3151 -4 1 5.7993 7 3252 6 1 5.7995 7 3130 -3 4 5.7996 7 3172 4 1 5.7998 7 3242 5 1 5.8001 7 3251 -1 3 5.8009 7 3308 -1 3 5.8012 7 3485 -4 1 5.8013 7 3168 4 3 5.8014 7 3275 5 1 5.8016 7 3141 1 4 5.8019 7 3438 -1 3 5.8022 7 3249 -5 2 5.8022 7 3200 6 1 5.8026 7 3415 -3 2 5.8027 7 3243 3 4 5.8027 7 3247 -3 4 5.8027 7 3340 -1 3 5.8030 7 3374 -4 1 5.8031 7 3162 4 3 5.8033 7 3132 5 1 5.8033 7 3277 -4 3 5.8036 7 3174 -4 1 5.8037 7 3195 -5 2 5.8038 7 3189 4 3 5.8041 7 3274 4 1 5.8041 7 3239 3 2 5.8041 7 3528 4 1 5.8041 7 3378 -2 3 5.8041 7 3377 -2 3 5.8041 7 3378 3 2 5.8041 7 3377 3 2 5.8044 7 3482 -4 1 5.8045 7 3189 -2 3 5.8045 7 3132 3 4 5.8046 7 3361 4 1 5.8047 7 3428 -5 1 5.8050 7 3433 -3 2 5.8051 7 3287 -4 1 5.8055 7 3514 -4 1 5.8055 7 3321 4 1 5.8056 7 3278 -5 1 5.8058 7 3193 -1 4 5.8060 7 3209 5 2 5.8060 7 3208 5 2 5.8060 7 3212 5 2 5.8062 7 3418 5 1 5.8065 7 3170 -5 2 5.8066 7 3283 -1 4 5.8070 7 3168 5 2 5.8071 7 3255 -3 2 5.8073 7 3209 -1 3 5.8073 7 3208 -1 3 5.8073 7 3212 -1 3 5.8074 7 3157 -5 1 5.8079 7 3411 -1 3 5.8079 7 3233 3 2 5.8079 7 3452 4 1 5.8082 7 3258 -6 1 5.8082 7 3138 -5 1 5.8083 7 3130 -5 1 5.8084 7 3532 1 2 5.8084 7 3532 -4 1 5.8086 7 3325 4 1 5.8086 7 3350 -4 1 5.8091 7 3479 3 2 5.8094 7 3421 -4 1 5.8094 7 3242 -5 1 5.8097 7 3125 2 3 5.8099 7 3166 4 3 5.8101 7 3344 -4 1 5.8102 7 3433 2 3 5.8102 7 3215 -5 1 5.8103 7 3390 -3 1 5.8104 7 3184 3 2 5.8105 7 3185 -4 3 5.8108 7 3178 -4 3 5.8109 7 3279 3 4 5.8110 7 3156 3 4 5.8111 7 3212 -5 1 5.8111 7 3209 -5 1 5.8111 7 3208 -5 1 5.8115 7 3462 4 1 5.8116 7 3187 -5 2 5.8116 7 3260 -1 3 5.8117 7 3289 -5 1 5.8118 7 3256 4 3 5.8119 7 3125 4 1 5.8120 7 3123 3 2 5.8122 7 3238 2 3 5.8130 7 3183 1 4 5.8134 7 3338 -5 1 5.8134 7 3185 -5 2 5.8135 7 3165 -5 1 5.8136 7 3493 1 3 5.8136 7 3317 4 3 5.8136 7 3315 4 1 5.8136 7 3491 1 3 5.8138 7 3347 -1 3 5.8140 7 3145 -4 3 5.8143 7 3150 -3 4 5.8143 7 3277 4 1 5.8146 7 3279 4 3 5.8147 7 3259 -5 1 5.8147 7 3418 -4 1 5.8149 7 3358 -3 2 5.8151 7 3448 -3 1 5.8152 7 3143 -5 1 5.8153 7 3511 -1 3 5.8153 7 3508 -1 3 5.8155 7 3267 -4 1 5.8156 7 3120 -5 1 5.8156 7 3329 -5 1 5.8158 7 3161 -4 1 5.8159 7 3266 -2 3 5.8160 7 3531 -1 2 5.8162 7 3489 3 2 5.8166 7 3329 3 2 5.8166 7 3462 -4 1 5.8167 7 3400 -4 1 5.8169 7 3352 -1 3 5.8171 7 3351 -3 2 5.8172 7 3331 -4 1 5.8176 7 3187 6 1 5.8178 7 3277 3 2 5.8180 7 3268 -4 3 5.8181 7 3245 -5 2 5.8181 7 3198 -4 3 5.8183 7 3214 -1 4 5.8185 7 3132 5 2 5.8185 7 3191 -1 4 5.8186 7 3260 -3 2 5.8194 7 3515 -4 1 5.8197 7 3386 4 1 5.8198 7 3489 4 1 5.8201 7 3308 -2 3 5.8201 7 3144 8 1 5.8203 7 3154 2 3 5.8204 7 3145 -3 4 5.8204 7 3252 5 2 5.8205 7 3394 5 1 5.8207 7 3205 -3 2 5.8211 7 3339 -4 1 5.8214 7 3160 5 1 5.8216 7 3260 4 3 5.8218 7 3259 -4 3 5.8220 7 3375 -5 1 5.8221 7 3199 6 1 5.8224 7 3465 3 2 5.8224 7 3147 -1 4 5.8226 7 3276 3 4 5.8227 7 3203 4 1 5.8228 7 3304 5 1 5.8230 7 3278 2 3 5.8230 7 3158 -4 3 5.8231 7 3290 3 2 5.8232 7 3353 5 1 5.8233 7 3256 3 4 5.8234 7 3158 -5 2 5.8235 7 3333 -3 2 5.8236 7 3162 -5 1 5.8244 7 3290 2 3 5.8245 7 3221 -5 2 5.8245 7 3228 5 2 5.8245 7 3397 -2 3 5.8246 7 3199 -7 1 5.8246 7 3198 2 3 5.8246 7 3242 -2 3 5.8247 7 3205 -4 1 5.8248 7 3237 3 2 5.8249 7 3428 3 2 5.8249 7 3147 4 3 5.8249 7 3240 -4 3 5.8252 7 3238 -4 3 5.8257 7 3274 -4 3 5.8259 7 3348 4 1 5.8260 7 3274 -5 1 5.8263 7 3466 3 2 5.8265 7 3205 1 4 5.8266 7 3356 3 2 5.8270 7 3260 5 2 5.8272 7 3184 -8 1 5.8272 7 3272 -1 3 5.8272 7 3143 -5 2 5.8272 7 3337 -4 1 5.8272 7 3314 2 3 5.8278 7 3485 4 1 5.8284 7 3310 -8 1 5.8287 7 3529 -4 1 5.8287 7 3233 2 3 5.8288 7 3261 5 2 5.8289 7 3425 3 2 5.8291 7 3189 5 2 5.8294 7 3264 -2 3 5.8298 7 3219 5 2 5.8302 7 3245 5 1 5.8302 7 3162 6 1 5.8304 7 3190 4 3 5.8307 7 3265 -1 4 5.8311 7 3190 5 2 5.8312 7 3237 -3 4 5.8313 7 3202 -4 3 5.8322 7 3205 3 4 5.8324 7 3386 3 2 5.8327 7 3347 2 3 5.8327 7 3372 -2 3 5.8330 7 3324 -1 4 5.8330 7 3205 5 2 5.8332 7 3200 1 3 5.8336 7 3248 5 1 5.8336 7 3513 -4 1 5.8339 7 3205 5 1 5.8346 7 3234 5 2 5.8349 7 3285 1 4 5.8349 7 3361 -2 3 5.8352 7 3272 5 2 5.8353 7 3185 2 3 5.8355 7 3202 -5 1 5.8355 7 3313 -4 3 5.8358 7 3158 5 1 5.8360 7 3177 -1 4 5.8360 7 3317 1 4 5.8362 7 3340 -5 1 5.8364 7 3425 -5 1 5.8365 7 3204 1 4 5.8367 7 3274 3 2 5.8374 7 3203 -3 4 5.8377 7 3538 -3 1 5.8377 7 3463 -1 3 5.8378 7 3270 3 2 5.8382 7 3295 -1 4 5.8388 7 3231 -1 4 5.8391 7 3302 4 3 5.8393 7 3267 1 4 5.8396 7 3295 -3 4 5.8400 7 3203 -4 3 5.8400 7 3479 2 3 5.8402 7 3204 4 1 5.8404 7 3166 -1 4 5.8405 7 3365 2 3 5.8405 7 3167 1 4 5.8407 7 3539 -1 3 5.8408 7 3264 -4 1 5.8408 7 3423 5 1 5.8409 7 3411 4 1 5.8411 7 3271 -4 3 5.8417 7 3215 -2 3 5.8418 7 3293 -1 3 5.8420 7 3232 -3 2 5.8421 7 3325 1 4 5.8421 7 3200 2 3 5.8424 7 3486 -4 1 5.8426 7 3257 -5 2 5.8428 7 3219 -6 1 5.8430 7 3214 -4 3 5.8430 7 3377 -4 1 5.8430 7 3386 1 3 5.8430 7 3183 -1 4 5.8431 7 3291 -2 3 5.8431 7 3266 -4 1 5.8432 7 3252 1 3 5.8433 7 3172 -4 3 5.8434 7 3345 4 1 5.8440 7 3453 -3 2 5.8440 7 3454 -3 2 5.8441 7 3284 -4 3 5.8441 7 3478 -1 3 5.8442 7 3240 2 3 5.8442 7 3318 -4 1 5.8442 7 3318 1 4 5.8446 7 3245 1 3 5.8446 7 3333 6 1 5.8450 7 3255 4 3 5.8450 7 3302 1 4 5.8452 7 3257 -1 3 5.8452 7 3324 3 2 5.8453 7 3238 4 1 5.8454 7 3270 -4 3 5.8454 7 3313 -3 4 5.8456 7 3251 -5 1 5.8460 7 3400 5 1 5.8461 7 3333 -5 1 5.8461 7 3277 -5 1 5.8464 7 3221 6 1 5.8464 7 3228 -6 1 5.8467 7 3261 1 3 5.8467 7 3395 -5 1 5.8470 7 3467 4 1 5.8471 7 3276 1 4 5.8473 7 3268 -3 4 5.8475 7 3240 -5 1 5.8478 7 3237 2 3 5.8479 7 3455 -2 3 5.8480 7 3178 -3 4 5.8484 7 3177 4 3 5.8487 7 3243 -3 2 5.8487 7 3247 3 2 5.8488 7 3264 4 3 5.8490 7 3184 -5 2 5.8492 7 3331 -1 4 5.8494 7 3261 2 3 5.8495 7 3335 5 1 5.8496 7 3226 -5 2 5.8499 7 3264 1 4 5.8502 7 3416 3 2 5.8508 7 3200 -6 1 5.8509 7 3372 2 3 5.8512 7 3473 4 1 5.8512 7 3268 2 3 5.8516 7 3280 -4 3 5.8518 7 3285 -1 4 5.8523 7 3272 4 3 5.8523 7 3273 5 1 5.8523 7 3434 4 1 5.8524 7 3425 -3 2 5.8525 7 3248 2 3 5.8530 7 3245 -6 1 5.8532 7 3454 -4 1 5.8532 7 3453 -4 1 5.8533 7 3523 -1 3 5.8534 7 3473 1 3 5.8534 7 3297 5 1 5.8534 7 3535 -3 1 5.8534 7 3238 -3 4 5.8534 7 3340 -2 3 5.8536 7 3268 3 2 5.8538 7 3259 -5 2 5.8540 7 3441 1 3 5.8542 7 3201 5 2 5.8544 7 3187 -1 3 5.8544 7 3471 3 1 5.8546 7 3246 1 4 5.8546 7 3244 1 4 5.8549 7 3234 -2 3 5.8549 7 3466 1 3 5.8551 7 3543 -1 3 5.8553 7 3231 1 4 5.8554 7 3249 -4 3 5.8556 7 3221 -1 3 5.8556 7 3228 1 3 5.8557 7 3484 -1 3 5.8559 7 3349 -4 1 5.8561 7 3219 1 3 5.8562 7 3508 -3 2 5.8562 7 3511 -3 2 5.8565 7 3465 4 1 5.8565 7 3273 -2 3 5.8566 7 3443 4 1 5.8568 7 3444 4 1 5.8570 7 3427 2 3 5.8570 7 3286 -3 4 5.8571 7 3328 -5 1 5.8573 7 3344 -1 4 5.8576 7 3308 -5 2 5.8578 7 3467 1 3 5.8579 7 3216 -1 3 5.8581 7 3215 5 2 5.8581 7 3340 1 3 5.8582 7 3372 4 1 5.8582 7 3210 1 4 5.8582 7 3207 1 4 5.8582 7 3213 -1 4 5.8582 7 3217 -1 4 5.8584 7 3184 8 1 5.8585 7 3331 -4 3 5.8593 7 3204 -4 3 5.8597 7 3277 -5 2 5.8598 7 3358 -2 3 5.8599 7 3251 5 2 5.8603 7 3229 1 4 5.8605 7 3189 1 4 5.8611 7 3270 2 3 5.8611 7 3285 -4 1 5.8612 7 3493 2 3 5.8612 7 3301 4 3 5.8612 7 3298 -4 1 5.8612 7 3491 2 3 5.8619 7 3416 4 1 5.8624 7 3345 2 3 5.8625 7 3416 -1 3 5.8631 7 3233 4 1 5.8632 7 3463 -4 1 5.8637 7 3466 -1 3 5.8638 7 3498 3 2 5.8639 7 3312 4 1 5.8639 7 3331 -3 4 5.8642 7 3273 4 3 5.8642 7 3329 1 3 5.8643 7 3307 -3 4 5.8644 7 3505 -5 1 5.8644 7 3458 -1 3 5.8647 7 3202 -1 4 5.8649 7 3271 -3 4 5.8652 7 3257 -2 3 5.8652 7 3261 6 1 5.8653 7 3350 -1 4 5.8654 7 3409 3 2 5.8657 7 3262 1 4 5.8658 7 3194 2 3 5.8661 7 3504 -4 1 5.8662 7 3536 1 2 5.8664 7 3216 -2 3 5.8664 7 3209 6 1 5.8664 7 3208 6 1 5.8664 7 3212 6 1 5.8664 7 3202 -5 2 5.8667 7 3267 -1 4 5.8670 7 3359 -4 1 5.8676 7 3194 3 2 5.8677 7 3274 -5 2 5.8681 7 3270 -3 4 5.8688 7 3252 2 3 5.8689 7 3234 -5 1 5.8690 7 3255 3 4 5.8690 7 3453 4 1 5.8690 7 3454 4 1 5.8694 7 3494 -4 1 5.8694 7 3495 4 1 5.8696 7 3439 4 1 5.8699 7 3275 -2 3 5.8701 7 3342 -4 1 5.8702 7 3462 -3 2 5.8702 7 3464 -4 1 5.8705 7 3308 1 3 5.8705 7 3445 -4 1 5.8706 7 3284 -3 4 5.8709 7 3474 4 1 5.8709 7 3304 -5 1 5.8709 7 3305 -1 3 5.8710 7 3204 -3 4 5.8710 7 3390 -1 3 5.8713 7 3188 -5 1 5.8715 7 3284 3 2 5.8715 7 3458 -2 3 5.8718 7 3265 -3 4 5.8719 7 3361 3 2 5.8721 7 3392 1 3 5.8724 7 3299 -5 1 5.8725 7 3439 -1 3 5.8728 7 3416 1 3 5.8736 7 3297 -2 3 5.8738 7 3390 4 1 5.8742 7 3476 4 1 5.8743 7 3226 -7 1 5.8745 7 3473 2 3 5.8745 7 3511 3 1 5.8746 7 3417 -3 1 5.8746 7 3423 -5 1 5.8749 7 3257 1 3 5.8749 7 3300 4 1 5.8754 7 3314 4 1 5.8757 7 3221 -2 3 5.8757 7 3228 2 3 5.8758 7 3275 -5 1 5.8758 7 3240 -5 2 5.8758 7 3428 6 1 5.8758 7 3337 4 1 5.8759 7 3431 3 2 5.8759 7 3217 -5 1 5.8759 7 3213 -5 1 5.8759 7 3212 4 3 5.8759 7 3209 4 3 5.8759 7 3210 5 1 5.8759 7 3387 -2 3 5.8759 7 3207 5 1 5.8759 7 3208 4 3 5.8760 7 3318 4 1 5.8760 7 3318 -1 4 5.8760 7 3191 4 3 5.8761 7 3433 -2 3 5.8767 7 3279 -3 2 5.8772 7 3339 2 3 5.8773 7 3387 4 1 5.8774 7 3237 4 1 5.8774 7 3420 4 1 5.8774 7 3284 2 3 5.8775 7 3232 -2 3 5.8777 7 3381 -6 1 5.8778 7 3219 2 3 5.8782 7 3257 -6 1 5.8783 7 3263 6 1 5.8790 7 3448 -1 3 5.8793 7 3349 -1 4 5.8793 7 3505 6 1 5.8794 7 3210 4 3 5.8794 7 3207 4 3 5.8794 7 3213 -4 3 5.8794 7 3217 -4 3 5.8795 7 3362 1 3 5.8795 7 3324 2 3 5.8804 7 3481 4 1 5.8806 7 3263 -5 1 5.8808 7 3352 -3 2 5.8811 7 3348 -3 2 5.8813 7 3280 2 3 5.8815 7 3464 3 1 5.8819 7 3333 5 2 5.8821 7 3239 4 1 5.8823 7 3422 -1 3 5.8823 7 3252 -1 3 5.8828 7 3232 5 2 5.8828 7 3480 -3 2 5.8828 7 3276 -3 2 5.8833 7 3427 -1 3 5.8838 7 3339 3 2 5.8840 7 3247 2 3 5.8840 7 3243 -2 3 5.8846 7 3229 -3 4 5.8848 7 3412 7 1 5.8848 7 3411 -3 2 5.8849 7 3232 -4 1 5.8850 7 3293 5 2 5.8850 7 3453 1 3 5.8850 7 3454 1 3 5.8853 7 3251 -2 3 5.8854 7 3420 -3 2 5.8857 7 3216 -7 1 5.8858 7 3302 3 4 5.8860 7 3339 -1 4 5.8867 7 3525 3 1 5.8868 7 3289 -5 2 5.8871 7 3428 -1 2 5.8872 7 3280 -5 1 5.8874 7 3399 -3 2 5.8877 7 3387 -3 2 5.8877 7 3305 1 3 5.8878 7 3237 -5 2 5.8882 7 3431 -2 3 5.8882 7 3244 4 3 5.8882 7 3243 -4 1 5.8882 7 3246 4 3 5.8882 7 3247 4 1 5.8882 7 3377 2 3 5.8882 7 3378 2 3 5.8882 7 3249 2 3 5.8887 7 3522 -3 2 5.8888 7 3266 3 4 5.8889 7 3279 -2 3 5.8891 7 3536 -4 1 5.8891 7 3356 5 1 5.8893 7 3457 1 3 5.8894 7 3266 4 3 5.8900 7 3325 4 3 5.8902 7 3503 -4 1 5.8905 7 3239 1 4 5.8905 7 3289 2 3 5.8906 7 3273 5 2 5.8906 7 3299 2 3 5.8906 7 3404 -1 3 5.8907 7 3265 4 1 5.8912 7 3258 5 2 5.8916 7 3255 -1 4 5.8916 7 3362 -5 1 5.8928 7 3465 -4 1 5.8929 7 3427 -3 2 5.8942 7 3372 -4 1 5.8944 7 3517 1 3 5.8945 7 3288 1 4 5.8947 7 3270 4 1 5.8950 7 3399 -4 1 5.8950 7 3422 -2 3 5.8957 7 3498 -4 1 5.8958 7 3275 5 2 5.8958 7 3320 -1 3 5.8962 7 3365 -2 3 5.8963 7 3415 1 3 5.8965 7 3444 -5 1 5.8968 7 3361 -5 1 5.8971 7 3282 -2 3 5.8973 7 3262 -1 4 5.8980 7 3255 -4 1 5.8981 7 3438 4 1 5.8986 7 3300 1 4 5.8986 7 3359 5 1 5.8990 7 3534 3 2 5.8991 7 3352 -4 1 5.8991 7 3328 1 3 5.8992 7 3504 4 1 5.8998 7 3434 -1 3 5.8998 7 3373 4 1 5.9009 7 3423 -3 2 5.9009 7 3362 -1 3 5.9011 7 3335 -5 2 5.9016 7 3282 -3 2 5.9020 7 3348 1 4 5.9026 7 3422 4 1 5.9028 7 3310 8 1 5.9029 7 3398 -3 2 5.9031 7 3422 -5 1 5.9032 7 3349 -4 3 5.9033 7 3259 2 3 5.9035 7 3251 4 3 5.9037 7 3256 -4 1 5.9038 7 3526 4 1 5.9042 7 3288 -1 4 5.9044 7 3291 -4 1 5.9044 7 3493 4 1 5.9044 7 3491 4 1 5.9057 7 3487 4 1 5.9061 7 3302 -2 3 5.9062 7 3495 -4 1 5.9062 7 3494 4 1 5.9062 7 3486 3 2 5.9063 7 3382 -5 1 5.9063 7 3325 -3 2 5.9072 7 3325 3 4 5.9080 7 3425 5 1 5.9082 7 3417 -1 3 5.9083 7 3297 -5 1 5.9085 7 3271 4 1 5.9088 7 3519 4 1 5.9088 7 3520 -4 1 5.9089 7 3278 -5 2 5.9090 7 3431 -4 1 5.9093 7 3358 1 4 5.9093 7 3347 -2 3 5.9095 7 3394 3 2 5.9096 7 3293 -5 1 5.9101 7 3314 -1 4 5.9104 7 3276 5 2 5.9104 7 3451 3 1 5.9108 7 3289 -4 3 5.9109 7 3325 -2 3 5.9112 7 3280 -1 4 5.9116 7 3349 -3 4 5.9118 7 3301 -3 2 5.9120 7 3327 1 3 5.9125 7 3274 -1 4 5.9130 7 3328 -1 3 5.9133 7 3264 3 4 5.9135 7 3301 -2 3 5.9143 7 3265 -5 2 5.9149 7 3278 5 1 5.9151 7 3290 4 1 5.9154 7 3280 -5 2 5.9155 7 3258 7 1 5.9155 7 3465 -3 2 5.9155 7 3277 2 3 5.9157 7 3443 -5 1 5.9167 7 3477 4 1 5.9168 7 3305 -2 3 5.9169 7 3471 -4 1 5.9170 7 3490 4 1 5.9170 7 3493 -3 1 5.9170 7 3491 -3 1 5.9175 7 3463 1 3 5.9175 7 3445 5 1 5.9175 7 3353 -6 1 5.9176 7 3516 4 1 5.9178 7 3264 5 1 5.9182 7 3304 -2 3 5.9182 7 3467 2 3 5.9182 7 3378 4 1 5.9182 7 3377 4 1 5.9184 7 3324 4 1 5.9185 7 3338 6 1 5.9185 7 3400 1 3 5.9187 7 3465 1 3 5.9187 7 3295 3 2 5.9187 7 3321 1 4 5.9192 7 3273 1 4 5.9196 7 3395 6 1 5.9201 7 3361 1 3 5.9205 7 3320 1 3 5.9207 7 3463 -2 3 5.9207 7 3408 -5 1 5.9209 7 3409 -4 1 5.9209 7 3283 3 2 5.9210 7 3512 -4 1 5.9215 7 3276 -2 3 5.9220 7 3290 -3 4 5.9223 7 3358 4 1 5.9225 7 3480 4 1 5.9227 7 3417 4 1 5.9230 7 3276 -4 1 5.9236 7 3412 -7 1 5.9238 7 3477 -5 1 5.9242 7 3350 -4 3 5.9244 7 3421 1 3 5.9245 7 3274 -3 4 5.9250 7 3272 -5 1 5.9252 7 3543 -2 3 5.9253 7 3500 -3 1 5.9258 7 3408 6 1 5.9261 7 3390 4 3 5.9262 7 3344 -4 3 5.9262 7 3410 -1 3 5.9264 7 3499 -4 1 5.9264 7 3395 -3 2 5.9265 7 3291 3 4 5.9266 7 3275 4 3 5.9269 7 3268 1 4 5.9276 7 3441 -4 1 5.9278 7 3409 2 3 5.9279 7 3261 -6 1 5.9287 7 3415 -1 3 5.9295 7 3310 -9 1 5.9296 7 3276 5 3 5.9301 7 3482 4 1 5.9313 7 3287 4 3 5.9314 7 3284 4 1 5.9314 7 3283 2 3 5.9315 7 3400 -3 2 5.9322 7 3499 -3 2 5.9323 7 3290 1 4 5.9326 7 3287 5 1 5.9330 7 3293 4 3 5.9334 7 3494 -3 2 5.9334 7 3495 3 2 5.9336 7 3374 5 1 5.9340 7 3511 1 3 5.9340 7 3484 2 3 5.9343 7 3417 1 4 5.9344 7 3452 -5 1 5.9344 7 3453 -1 3 5.9344 7 3470 1 3 5.9344 7 3454 -1 3 5.9344 7 3469 1 3 5.9345 7 3308 5 1 5.9350 7 3480 3 2 5.9359 7 3344 -3 4 5.9361 7 3350 -3 4 5.9365 7 3335 -1 3 5.9368 7 3483 3 2 5.9370 7 3328 6 1 5.9370 7 3327 2 3 5.9373 7 3302 -4 1 5.9376 7 3293 -2 3 5.9377 7 3320 -2 3 5.9378 7 3337 -1 4 5.9380 7 3511 -2 3 5.9380 7 3508 -2 3 5.9384 7 3531 4 1 5.9386 7 3418 -3 2 5.9388 7 3442 -4 1 5.9393 7 3305 2 3 5.9398 7 3382 6 1 5.9403 7 3448 4 1 5.9408 7 3492 1 3 5.9408 7 3490 1 3 5.9408 7 3298 4 3 5.9410 7 3394 -6 1 5.9410 7 3328 2 3 5.9410 7 3327 6 1 5.9416 7 3308 -4 3 5.9416 7 3335 -6 1 5.9419 7 3519 -3 2 5.9419 7 3520 3 2 5.9424 7 3358 -4 1 5.9426 7 3304 5 2 5.9426 7 3299 -4 3 5.9427 7 3382 -3 2 5.9427 7 3329 -5 2 5.9435 7 3400 2 3 5.9440 7 3483 -4 1 5.9440 7 3328 5 2 5.9444 7 3295 -5 2 5.9446 7 3442 3 2 5.9449 7 3408 -3 2 5.9450 7 3339 -4 3 5.9452 7 3331 3 2 5.9462 7 3338 5 2 5.9462 7 3427 -4 1 5.9468 7 3434 -3 2 5.9470 7 3320 -6 1 5.9471 7 3299 -5 2 5.9472 7 3335 -2 3 5.9472 7 3441 3 2 5.9479 7 3416 -2 3 5.9480 7 3351 6 1 5.9482 7 3299 5 1 5.9483 7 3313 3 2 5.9487 7 3305 6 1 5.9491 7 3463 3 2 5.9492 7 3435 -1 3 5.9494 7 3308 2 3 5.9496 7 3478 -3 2 5.9497 7 3327 5 2 5.9498 7 3340 -5 2 5.9498 7 3346 6 1 5.9501 7 3362 2 3 5.9503 7 3329 5 1 5.9505 7 3410 1 3 5.9506 7 3516 -3 2 5.9513 7 3295 4 1 5.9513 7 3314 -4 3 5.9515 7 3417 3 4 5.9518 7 3305 -6 1 5.9530 7 3313 2 3 5.9543 7 3457 -4 1 5.9545 7 3321 -1 4 5.9551 7 3470 2 3 5.9551 7 3469 2 3 5.9552 7 3291 -1 4 5.9554 7 3421 -4 3 5.9556 7 3344 3 2 5.9556 7 3312 1 4 5.9562 7 3381 3 2 5.9564 7 3397 4 1 5.9570 7 3451 1 3 5.9571 7 3347 5 1 5.9574 7 3483 4 1 5.9577 7 3481 1 3 5.9578 7 3390 1 4 5.9579 7 3340 2 3 5.9587 7 3331 2 3 5.9587 7 3404 4 3 5.9592 7 3483 1 3 5.9593 7 3442 2 3 5.9602 7 3296 -5 2 5.9604 7 3452 3 2 5.9605 7 3466 4 1 5.9605 7 3306 -4 1 5.9611 7 3320 6 1 5.9612 7 3324 -4 3 5.9616 7 3484 -3 2 5.9616 7 3438 -3 2 5.9620 7 3411 -2 3 5.9622 7 3324 -3 4 5.9627 7 3515 4 1 5.9628 7 3420 -2 3 5.9629 7 3317 -3 2 5.9632 7 3451 -4 1 5.9632 7 3327 -1 3 5.9642 7 3362 -2 3 5.9643 7 3320 -5 2 5.9644 7 3349 3 2 5.9657 7 3443 -2 3 5.9657 7 3338 1 3 5.9668 7 3390 3 4 5.9672 7 3309 5 1 5.9676 7 3348 -4 1 5.9677 7 3397 -4 1 5.9682 7 3362 5 1 5.9683 7 3512 4 1 5.9684 7 3417 4 3 5.9686 7 3529 -3 2 5.9687 7 3356 -6 1 5.9690 7 3329 2 3 5.9697 7 3415 2 3 5.9698 7 3462 1 3 5.9700 7 3352 -2 3 5.9703 7 3375 5 1 5.9704 7 3346 -6 1 5.9706 7 3410 5 1 5.9708 7 3420 -4 1 5.9714 7 3483 -1 3 5.9714 7 3344 2 3 5.9718 7 3478 2 3 5.9720 7 3351 5 2 5.9721 7 3492 2 3 5.9721 7 3490 2 3 5.9721 7 3315 -4 3 5.9722 7 3386 2 3 5.9724 7 3314 1 4 5.9725 7 3327 -6 1 5.9725 7 3328 -2 3 5.9727 7 3349 2 3 5.9727 7 3347 -5 1 5.9728 7 3444 -1 3 5.9734 7 3381 6 1 5.9735 7 3320 2 3 5.9736 7 3502 -4 1 5.9738 7 3337 1 4 5.9741 7 3356 -5 2 5.9743 7 3495 -3 2 5.9743 7 3494 3 2 5.9745 7 3466 2 3 5.9746 7 3346 5 2 5.9749 7 3524 -1 3 5.9752 7 3517 3 2 5.9758 7 3525 1 3 5.9759 7 3392 -4 3 5.9760 7 3453 2 3 5.9760 7 3454 2 3 5.9761 7 3471 1 3 5.9762 7 3411 -4 1 5.9764 7 3398 1 3 5.9766 7 3439 -3 2 5.9776 7 3351 -6 1 5.9776 7 3359 5 2 5.9778 7 3409 4 1 5.9779 7 3467 -1 3 5.9780 7 3352 1 4 5.9783 7 3348 4 3 5.9799 7 3445 1 3 5.9804 7 3339 4 1 5.9806 7 3339 -3 4 5.9808 7 3445 -3 2 5.9811 7 3472 -4 1 5.9815 7 3444 3 2 5.9818 7 3445 2 3 5.9819 7 3333 1 3 5.9820 7 3528 3 2 5.9820 7 3487 -5 1 5.9824 7 3443 -1 3 5.9825 7 3455 1 3 5.9829 7 3350 3 2 5.9835 7 3335 1 3 5.9838 7 3451 -1 4 5.9843 7 3481 -4 1 5.9849 7 3534 4 1 5.9851 7 3489 -4 1 5.9853 7 3325 -4 1 5.9854 7 3398 -5 1 5.9856 7 3352 4 3 5.9857 7 3500 4 1 5.9858 7 3434 -4 1 5.9860 7 3465 -1 3 5.9863 7 3524 -3 2 5.9864 7 3375 -1 3 5.9871 7 3514 3 2 5.9873 7 3392 3 2 5.9874 7 3338 -6 1 5.9874 7 3340 5 1 5.9875 7 3353 -5 2 5.9880 7 3469 -4 1 5.9880 7 3470 -4 1 5.9885 7 3399 -2 3 5.9885 7 3338 2 3 5.9886 7 3356 -1 3 5.9887 7 3339 1 4 5.9887 7 3382 5 2 5.9904 7 3499 4 1 5.9907 7 3416 2 3 5.9914 7 3387 1 4 5.9915 7 3498 5 1 5.9915 7 3410 -2 3 5.9921 7 3444 -2 3 5.9925 7 3422 3 2 5.9932 7 3526 -4 1 5.9936 7 3431 4 1 5.9944 7 3418 1 3 5.9945 7 3486 5 1 5.9946 7 3522 -1 3 5.9948 7 3400 5 2 5.9951 7 3358 -1 4 5.9960 7 3346 1 3 5.9960 7 3529 4 1 5.9961 7 3540 -3 1 5.9964 7 3466 -2 3 5.9971 7 3466 -4 1 5.9976 7 3531 -3 2 5.9979 7 3509 -3 1 5.9979 7 3510 -3 1 5.9981 7 3353 6 1 5.9983 7 3523 -3 2 5.9984 7 3361 -5 2 5.9984 7 3338 -1 3 5.9987 7 3348 3 4 5.9992 7 3481 -3 2 5.9992 7 3433 -4 1 5.9993 7 3350 2 3 5.9993 7 3504 -3 2 5.9998 7 3387 -4 1 5.9998 7 3375 -5 2 5.9998 7 3428 -6 1 5.9999 7 3354 -4 1 5.9999 7 3438 -2 3 6.0000 7 3458 4 1 6.0010 7 3443 3 2 6.0018 7 3433 4 1 6.0019 7 3394 6 1 6.0022 7 3416 -5 1 6.0027 7 3408 5 2 6.0028 7 3397 1 4 6.0029 7 3523 1 3 6.0030 7 3476 -5 1 6.0032 7 3418 5 2 6.0042 7 3455 4 1 6.0045 7 3415 5 1 6.0051 7 3345 -1 4 6.0052 7 3520 5 1 6.0052 7 3519 -5 1 6.0057 7 3355 -3 2 6.0066 7 3333 7 1 6.0069 7 3464 1 3 6.0070 7 3398 5 2 6.0071 7 3482 3 2 6.0075 7 3398 2 3 6.0079 7 3499 3 2 6.0080 7 3345 -5 1 6.0091 7 3463 4 1 6.0091 7 3428 5 2 6.0097 7 3422 -5 2 6.0097 7 3410 -5 1 6.0098 7 3375 1 3 6.0104 7 3386 -5 1 6.0104 7 3502 5 1 6.0113 7 3373 2 3 6.0113 7 3524 1 3 6.0116 7 3536 -1 2 6.0117 7 3359 -2 3 6.0120 7 3412 8 1 6.0120 7 3457 3 2 6.0122 7 3356 -2 3 6.0123 7 3352 5 1 6.0125 7 3427 -2 3 6.0127 7 3344 4 1 6.0127 7 3465 2 3 6.0132 7 3359 4 3 6.0136 7 3474 -1 3 6.0136 7 3415 -2 3 6.0138 7 3372 -1 4 6.0141 7 3443 -5 2 6.0149 7 3346 2 3 6.0150 7 3392 4 1 6.0153 7 3441 4 1 6.0155 7 3375 -2 3 6.0162 7 3398 6 1 6.0163 7 3381 -5 2 6.0165 7 3445 5 2 6.0167 7 3373 -5 1 6.0175 7 3387 -1 4 6.0181 7 3410 2 3 6.0188 7 3397 -1 4 6.0188 7 3400 -1 3 6.0190 7 3517 -3 2 6.0191 7 3348 -1 4 6.0193 7 3395 -6 1 6.0195 7 3418 2 3 6.0207 7 3409 -1 4 6.0210 7 3478 -2 3 6.0219 7 3351 1 3 6.0221 7 3395 5 2 6.0221 7 3435 -3 2 6.0222 7 3484 -2 3 6.0224 7 3435 4 3 6.0228 7 3415 -5 1 6.0232 7 3462 -1 3 6.0233 7 3486 -3 2 6.0234 7 3373 -4 3 6.0235 7 3356 1 3 6.0236 7 3467 -3 2 6.0237 7 3455 3 2 6.0238 7 3441 2 3 6.0247 7 3361 2 3 6.0247 7 3392 -5 2 6.0257 7 3458 3 2 6.0260 7 3392 -5 1 6.0262 7 3462 2 3 6.0262 7 3345 -4 3 6.0266 7 3372 1 4 6.0279 7 3481 -1 3 6.0284 7 3479 -3 2 6.0289 7 3361 5 1 6.0290 7 3355 -2 3 6.0292 7 3499 -1 3 6.0292 7 3439 -4 1 6.0295 7 3381 -7 1 6.0295 7 3531 -5 1 6.0308 7 3350 4 1 6.0311 7 3361 -4 3 6.0326 7 3365 5 1 6.0333 7 3371 4 1 6.0336 7 3470 -3 2 6.0336 7 3469 -3 2 6.0339 7 3451 -3 4 6.0341 7 3398 -1 3 6.0342 7 3487 3 2 6.0343 7 3351 2 3 6.0354 7 3482 -1 3 6.0356 7 3428 -3 2 6.0358 7 3531 1 2 6.0360 7 3453 -2 3 6.0360 7 3454 -2 3 6.0365 7 3422 1 3 6.0366 7 3353 1 3 6.0374 7 3523 -2 3 6.0380 7 3354 5 1 6.0381 7 3435 -4 1 6.0382 7 3374 -2 3 6.0383 7 3404 -3 2 6.0384 7 3374 5 2 6.0384 7 3374 -5 1 6.0386 7 3365 -5 1 6.0387 7 3522 -4 1 6.0388 7 3489 1 3 6.0388 7 3394 -5 2 6.0388 7 3375 -6 1 6.0388 7 3352 3 4 6.0389 7 3418 -5 1 6.0396 7 3451 -4 3 6.0397 7 3404 5 2 6.0397 7 3400 -5 1 6.0398 7 3423 6 1 6.0415 7 3412 -8 1 6.0418 7 3404 1 4 6.0419 7 3362 -5 2 6.0427 7 3444 -5 2 6.0427 7 3438 -4 1 6.0429 7 3458 1 3 6.0430 7 3418 6 1 6.0432 7 3392 2 3 6.0433 7 3428 7 1 6.0436 7 3439 -2 3 6.0436 7 3399 5 1 6.0441 7 3473 -1 3 6.0451 7 3476 3 2 6.0456 7 3517 2 3 6.0459 7 3421 3 2 6.0460 7 3505 -6 1 6.0466 7 3369 -3 2 6.0469 7 3434 -2 3 6.0473 7 3425 -6 1 6.0474 7 3441 -1 4 6.0474 7 3382 1 3 6.0475 7 3481 2 3 6.0475 7 3452 -1 3 6.0482 7 3390 -3 2 6.0494 7 3505 7 1 6.0502 7 3378 -1 4 6.0502 7 3377 -1 4 6.0505 7 3479 -2 3 6.0510 7 3448 1 4 6.0520 7 3433 1 4 6.0523 7 3478 4 1 6.0535 7 3442 -1 4 6.0542 7 3499 1 3 6.0552 7 3528 -4 1 6.0556 7 3442 4 1 6.0558 7 3463 2 3 6.0560 7 3382 2 3 6.0561 7 3541 -3 2 6.0565 7 3452 -2 3 6.0571 7 3448 4 3 6.0576 7 3448 -3 2 6.0577 7 3418 -1 3 6.0580 7 3386 5 1 6.0593 7 3438 1 4 6.0594 7 3514 5 1 6.0596 7 3404 5 1 6.0601 7 3510 -1 3 6.0601 7 3509 -1 3 6.0601 7 3421 -1 4 6.0601 7 3369 -2 3 6.0602 7 3541 -4 1 6.0604 7 3435 5 1 6.0605 7 3371 -5 2 6.0606 7 3464 -4 3 6.0610 7 3392 -1 4 6.0612 7 3400 6 1 6.0617 7 3373 -5 2 6.0617 7 3513 -1 3 6.0618 7 3371 -5 1 6.0622 7 3420 1 4 6.0623 7 3457 2 3 6.0625 7 3422 -4 3 6.0633 7 3480 1 3 6.0640 7 3375 2 3 6.0644 7 3467 -4 1 6.0656 7 3498 -3 2 6.0658 7 3404 -4 1 6.0659 7 3382 -6 1 6.0666 7 3423 5 2 6.0666 7 3431 1 4 6.0667 7 3390 -2 3 6.0670 7 3423 -6 1 6.0676 7 3452 5 1 6.0680 7 3421 -3 4 6.0694 7 3500 -1 3 6.0700 7 3374 4 3 6.0705 7 3395 7 1 6.0705 7 3478 -4 1 6.0705 7 3435 5 2 6.0706 7 3399 1 4 6.0712 7 3533 -4 1 6.0712 7 3439 1 4 6.0712 7 3403 4 1 6.0719 7 3489 -3 2 6.0723 7 3373 -1 4 6.0724 7 3522 -2 3 6.0728 7 3452 -5 2 6.0734 7 3404 3 4 6.0742 7 3411 1 4 6.0748 7 3412 1 2 6.0753 7 3480 -1 3 6.0755 7 3433 -1 4 6.0761 7 3513 4 1 6.0761 7 3378 1 4 6.0761 7 3377 1 4 6.0764 7 3427 5 1 6.0765 7 3404 -2 3 6.0768 7 3444 1 3 6.0774 7 3421 -5 2 6.0774 7 3458 -5 1 6.0774 7 3431 -1 4 6.0775 7 3473 -3 2 6.0781 7 3411 4 3 6.0790 7 3448 -2 3 6.0792 7 3421 4 1 6.0795 7 3443 -4 3 6.0796 7 3482 -2 3 6.0799 7 3469 5 1 6.0799 7 3469 -1 3 6.0805 7 3521 4 1 6.0809 7 3517 -2 3 6.0824 7 3412 -1 2 6.0824 7 3400 4 3 6.0827 7 3409 1 4 6.0830 7 3408 -6 1 6.0839 7 3444 5 1 6.0847 7 3394 -7 1 6.0853 7 3483 2 3 6.0853 7 3462 5 1 6.0856 7 3476 -1 3 6.0862 7 3445 -1 3 6.0870 7 3425 6 1 6.0873 7 3541 4 1 6.0874 7 3489 2 3 6.0874 7 3516 -5 1 6.0877 7 3485 -5 1 6.0878 7 3479 4 1 6.0885 7 3416 5 1 6.0889 7 3434 4 3 6.0893 7 3484 4 1 6.0896 7 3492 -4 1 6.0896 7 3493 -1 3 6.0896 7 3490 -4 1 6.0896 7 3491 -1 3 6.0900 7 3408 7 1 6.0908 7 3474 -3 2 6.0910 7 3536 5 1 6.0916 7 3492 -3 2 6.0916 7 3490 -3 2 6.0917 7 3477 3 2 6.0917 7 3473 -4 1 6.0917 7 3514 -5 1 6.0920 7 3523 3 2 6.0924 7 3483 -2 3 6.0927 7 3524 3 2 6.0930 7 3465 -2 3 6.0930 7 3495 1 3 6.0930 7 3494 -1 3 6.0931 7 3443 5 1 6.0936 7 3455 -5 1 6.0943 7 3439 4 3 6.0944 7 3421 2 3 6.0944 7 3425 1 3 6.0949 7 3434 1 4 6.0954 7 3458 -4 3 6.0962 7 3421 -5 1 6.0964 7 3423 1 3 6.0965 7 3462 -5 1 6.0972 7 3502 -3 2 6.0975 7 3516 3 2 6.0979 7 3445 4 3 6.0984 7 3486 -5 1 6.0997 7 3435 -2 3 6.1000 7 3457 -1 4 6.1002 7 3513 -2 3 6.1010 7 3445 -5 1 6.1011 7 3462 -2 3 6.1017 7 3457 4 1 6.1017 7 3412 3 2 6.1023 7 3417 -3 2 6.1042 7 3492 -1 3 6.1042 7 3490 -1 3 6.1051 7 3425 -5 2 6.1052 7 3485 5 1 6.1059 7 3495 -1 3 6.1059 7 3494 1 3 6.1059 7 3536 3 2 6.1061 7 3404 5 3 6.1066 7 3508 3 2 6.1066 7 3511 3 2 6.1072 7 3443 1 3 6.1073 7 3448 3 4 6.1076 7 3457 -4 3 6.1077 7 3442 1 4 6.1080 7 3425 -1 3 6.1081 7 3482 -5 1 6.1084 7 3416 -5 2 6.1085 7 3427 4 3 6.1095 7 3480 -5 1 6.1098 7 3444 -6 1 6.1099 7 3422 5 1 6.1102 7 3408 1 3 6.1108 7 3522 3 2 6.1114 7 3474 -4 1 6.1115 7 3472 -2 3 6.1116 7 3529 3 2 6.1118 7 3485 -1 3 6.1120 7 3476 -2 3 6.1122 7 3423 2 3 6.1124 7 3422 -6 1 6.1131 7 3535 4 1 6.1132 7 3434 5 1 6.1133 7 3410 5 2 6.1135 7 3420 4 3 6.1139 7 3464 -1 4 6.1143 7 3477 -1 3 6.1147 7 3524 -2 3 6.1151 7 3512 -1 3 6.1152 7 3471 -4 3 6.1153 7 3438 4 3 6.1157 7 3514 -3 2 6.1161 7 3482 1 3 6.1164 7 3471 -1 4 6.1201 7 3443 -6 1 6.1201 7 3454 -5 1 6.1201 7 3453 -5 1 6.1203 7 3467 -2 3 6.1203 7 3458 -5 2 6.1208 7 3435 1 4 6.1216 7 3454 5 1 6.1216 7 3452 1 3 6.1216 7 3453 5 1 6.1227 7 3484 -4 1 6.1231 7 3532 4 1 6.1232 7 3476 5 1 6.1233 7 3477 -5 2 6.1234 7 3522 1 3 6.1234 7 3421 -5 3 6.1248 7 3522 4 1 6.1254 7 3445 6 1 6.1255 7 3455 2 3 6.1258 7 3465 5 1 6.1258 7 3441 -4 3 6.1263 7 3538 -1 3 6.1265 7 3425 2 3 6.1266 7 3489 5 1 6.1267 7 3477 -2 3 6.1272 7 3471 3 2 6.1277 7 3520 -3 2 6.1277 7 3519 3 2 6.1280 7 3525 3 2 6.1281 7 3511 2 3 6.1281 7 3508 2 3 6.1283 7 3427 5 2 6.1285 7 3473 4 3 6.1287 7 3526 -3 2 6.1288 7 3499 -2 3 6.1290 7 3444 -4 3 6.1301 7 3448 -4 1 6.1304 7 3521 -5 1 6.1307 7 3515 -5 1 6.1323 7 3524 -4 1 6.1325 7 3479 1 4 6.1328 7 3439 5 1 6.1338 7 3428 -7 1 6.1338 7 3438 -1 4 6.1340 7 3485 1 3 6.1342 7 3503 4 1 6.1368 7 3442 -4 3 6.1384 7 3452 -6 1 6.1400 7 3455 -4 3 6.1410 7 3480 2 3 6.1423 7 3528 -3 2 6.1428 7 3464 -3 4 6.1429 7 3477 5 1 6.1433 7 3510 1 4 6.1433 7 3509 1 4 6.1434 7 3463 -5 1 6.1438 7 3480 -2 3 6.1450 7 3472 5 1 6.1458 7 3513 3 2 6.1459 7 3503 1 3 6.1466 7 3487 5 1 6.1467 7 3467 5 1 6.1473 7 3451 3 2 6.1475 7 3481 -2 3 6.1476 7 3489 -1 3 6.1476 7 3503 3 2 6.1476 7 3470 5 2 6.1476 7 3469 5 2 6.1485 7 3474 4 3 6.1486 7 3441 -3 4 6.1487 7 3464 3 2 6.1498 7 3509 4 1 6.1498 7 3508 -4 1 6.1498 7 3511 -4 1 6.1498 7 3510 4 1 6.1498 7 3477 -6 1 6.1500 7 3498 -5 1 6.1502 7 3465 -5 1 6.1514 7 3487 -1 3 6.1521 7 3473 5 1 6.1522 7 3504 1 3 6.1534 7 3521 -1 3 6.1534 7 3543 -4 1 6.1535 7 3494 -5 1 6.1535 7 3495 5 1 6.1556 7 3467 4 3 6.1564 7 3538 -3 2 6.1564 7 3470 4 3 6.1564 7 3469 4 3 6.1564 7 3493 4 3 6.1564 7 3491 4 3 6.1574 7 3539 3 1 6.1577 7 3458 2 3 6.1586 7 3532 -5 1 6.1588 7 3455 -5 2 6.1589 7 3513 -5 1 6.1591 7 3451 2 3 6.1604 7 3455 -1 4 6.1604 7 3504 -5 1 6.1615 7 3464 -5 2 6.1617 7 3521 -2 3 6.1621 7 3457 -3 4 6.1629 7 3540 -1 3 6.1631 7 3481 5 1 6.1633 7 3525 2 3 6.1636 7 3486 1 3 6.1644 7 3534 1 3 6.1645 7 3479 -4 1 6.1650 7 3471 4 1 6.1652 7 3471 2 3 6.1654 7 3541 3 2 6.1671 7 3495 2 3 6.1671 7 3494 -2 3 6.1673 7 3534 -4 1 6.1677 7 3452 -4 3 6.1678 7 3523 -4 1 6.1688 7 3499 2 3 6.1689 7 3536 -5 1 6.1689 7 3517 4 1 6.1690 7 3528 5 1 6.1694 7 3476 -5 2 6.1695 7 3474 -2 3 6.1695 7 3474 5 1 6.1714 7 3519 5 1 6.1714 7 3520 -5 1 6.1720 7 3485 -2 3 6.1721 7 3476 1 3 6.1724 7 3480 5 1 6.1725 7 3464 4 1 6.1735 7 3524 2 3 6.1736 7 3459 3 2 6.1738 7 3464 -5 1 6.1743 7 3495 -5 1 6.1743 7 3494 5 1 6.1744 7 3504 5 1 6.1750 7 3504 -1 3 6.1755 7 3471 -3 4 6.1763 7 3463 -4 3 6.1767 7 3473 5 2 6.1768 7 3529 -5 1 6.1770 7 3500 4 3 6.1770 7 3523 2 3 6.1791 7 3473 -2 3 6.1810 7 3502 1 3 6.1810 7 3467 5 2 6.1812 7 3516 5 1 6.1823 7 3459 2 3 6.1829 7 3505 -7 1 6.1830 7 3487 1 3 6.1831 7 3484 1 4 6.1833 7 3505 8 1 6.1839 7 3482 5 1 6.1848 7 3470 -2 3 6.1848 7 3469 -2 3 6.1848 7 3540 4 1 6.1849 7 3517 -4 1 6.1874 7 3495 -2 3 6.1874 7 3494 2 3 6.1876 7 3464 2 3 6.1892 7 3478 1 4 6.1897 7 3487 -2 3 6.1899 7 3523 4 1 6.1914 7 3512 -2 3 6.1920 7 3513 1 3 6.1931 7 3543 3 1 6.1933 7 3486 -1 3 6.1938 7 3487 -5 2 6.1943 7 3531 5 1 6.1968 7 3498 5 2 6.1974 7 3524 4 1 6.1974 7 3474 5 2 6.1984 7 3486 6 1 6.1995 7 3474 1 4 6.2012 7 3521 3 2 6.2015 7 3479 -1 4 6.2016 7 3476 -6 1 6.2021 7 3471 -5 1 6.2022 7 3535 -1 3 6.2022 7 3503 -5 1 6.2023 7 3487 -6 1 6.2027 7 3483 -5 1 6.2027 7 3482 2 3 6.2028 7 3500 -3 2 6.2029 7 3485 2 3 6.2030 7 3526 5 1 6.2033 7 3534 2 3 6.2036 7 3499 -5 1 6.2039 7 3486 2 3 6.2048 7 3492 5 1 6.2048 7 3490 5 1 6.2048 7 3512 -5 1 6.2053 7 3478 -1 4 6.2058 7 3471 -5 2 6.2058 7 3493 -3 2 6.2058 7 3491 -3 2 6.2060 7 3502 -5 1 6.2084 7 3498 1 3 6.2086 7 3483 5 1 6.2090 7 3472 -5 1 6.2091 7 3477 1 3 6.2099 7 3469 -5 1 6.2099 7 3470 -5 1 6.2114 7 3484 -1 4 6.2124 7 3498 6 1 6.2140 7 3502 5 2 6.2144 7 3502 2 3 6.2146 7 3486 5 2 6.2147 7 3534 -3 2 6.2156 7 3504 2 3 6.2161 7 3509 3 4 6.2161 7 3510 3 4 6.2163 7 3515 5 1 6.2173 7 3538 4 1 6.2191 7 3472 1 4 6.2204 7 3482 -5 2 6.2212 7 3493 -4 1 6.2212 7 3491 -4 1 6.2215 7 3512 2 3 6.2216 7 3525 -4 1 6.2218 7 3481 -5 1 6.2218 7 3535 -3 2 6.2229 7 3539 -4 1 6.2233 7 3515 -2 3 6.2234 7 3492 4 3 6.2234 7 3493 5 1 6.2234 7 3490 4 3 6.2234 7 3491 5 1 6.2243 7 3511 -1 4 6.2243 7 3508 -1 4 6.2244 7 3493 5 2 6.2244 7 3491 5 2 6.2247 7 3492 -2 3 6.2247 7 3490 -2 3 6.2255 7 3489 -5 1 6.2275 7 3500 1 4 6.2278 7 3516 -1 3 6.2282 7 3502 -1 3 6.2304 7 3503 -4 3 6.2309 7 3498 2 3 6.2312 7 3493 1 4 6.2312 7 3491 1 4 6.2316 7 3512 5 1 6.2322 7 3541 -1 3 6.2340 7 3520 6 1 6.2340 7 3519 -6 1 6.2376 7 3502 6 1 6.2385 7 3499 5 1 6.2391 7 3509 4 3 6.2391 7 3508 4 1 6.2391 7 3511 4 1 6.2391 7 3510 4 3 6.2401 7 3500 -4 1 6.2428 7 3504 -2 3 6.2428 7 3532 -1 3 6.2435 7 3489 5 2 6.2440 7 3489 -2 3 6.2458 7 3479 4 3 6.2459 7 3519 -5 2 6.2459 7 3520 5 2 6.2465 7 3480 -5 2 6.2474 7 3498 -1 3 6.2479 7 3509 -3 2 6.2479 7 3510 -3 2 6.2488 7 3500 -2 3 6.2492 7 3503 -5 2 6.2492 7 3487 2 3 6.2496 7 3522 2 3 6.2520 7 3521 -5 2 6.2526 7 3490 5 2 6.2526 7 3492 5 2 6.2531 7 3531 -6 1 6.2532 7 3486 -2 3 6.2541 7 3514 6 1 6.2569 7 3543 1 3 6.2570 7 3532 -2 3 6.2589 7 3526 -5 1 6.2599 7 3493 -2 3 6.2599 7 3491 -2 3 6.2600 7 3535 -4 1 6.2604 7 3513 -5 2 6.2606 7 3493 3 4 6.2606 7 3491 3 4 6.2618 7 3539 1 3 6.2638 7 3536 -3 2 6.2639 7 3516 -2 3 6.2646 7 3510 -2 3 6.2646 7 3509 -2 3 6.2651 7 3500 3 4 6.2666 7 3526 2 3 6.2672 7 3532 3 2 6.2674 7 3538 -2 3 6.2681 7 3516 1 3 6.2683 7 3529 5 1 6.2683 7 3528 1 3 6.2686 7 3526 -1 3 6.2687 7 3503 2 3 6.2700 7 3505 1 2 6.2712 7 3500 5 2 6.2716 7 3538 -4 1 6.2724 7 3517 1 4 6.2734 7 3500 5 1 6.2766 7 3525 4 1 6.2780 7 3523 -1 4 6.2792 7 3514 -6 1 6.2800 7 3539 -4 3 6.2803 7 3534 -1 3 6.2833 7 3505 -1 2 6.2834 7 3521 1 3 6.2836 7 3517 -1 4 6.2839 7 3533 -2 3 6.2844 7 3516 -5 2 6.2845 7 3511 1 4 6.2845 7 3508 1 4 6.2871 7 3505 -8 1 6.2873 7 3505 9 1 6.2875 7 3519 -1 3 6.2875 7 3520 1 3 6.2881 7 3516 -6 1 6.2887 7 3531 3 2 6.2895 7 3525 -1 4 6.2901 7 3514 5 2 6.2902 7 3540 -3 2 6.2912 7 3541 1 3 6.2920 7 3543 -4 3 6.2923 7 3514 1 3 6.2944 7 3513 -4 3 6.2951 7 3505 3 2 6.2958 7 3534 5 1 6.2962 7 3541 -2 3 6.2966 7 3528 -1 3 6.2967 7 3513 2 3 6.2984 7 3513 5 1 6.2985 7 3515 2 3 6.2989 7 3515 -5 2 6.2991 7 3502 -2 3 6.3015 7 3536 6 1 6.3017 7 3529 -1 3 6.3017 7 3528 -5 1 6.3023 7 3531 -5 2 6.3035 7 3522 -5 1 6.3061 7 3520 2 3 6.3061 7 3519 -2 3 6.3062 7 3521 5 1 6.3082 7 3528 2 3 6.3097 7 3539 -3 4 6.3133 7 3532 -5 2 6.3144 7 3521 -4 3 6.3149 7 3509 -4 1 6.3149 7 3511 -4 3 6.3149 7 3510 -4 1 6.3149 7 3508 -4 3 6.3150 7 3514 2 3 6.3217 7 3515 -6 1 6.3257 7 3520 -1 3 6.3257 7 3519 1 3 6.3263 7 3529 1 3 6.3264 7 3539 -1 4 6.3282 7 3514 -1 3 6.3287 7 3516 2 3 6.3305 7 3529 -2 3 6.3334 7 3525 1 4 6.3334 7 3523 1 4 6.3349 7 3521 -6 1 6.3359 7 3529 -5 2 6.3369 7 3535 -2 3 6.3373 7 3528 5 2 6.3384 7 3541 -5 1 6.3390 7 3533 -5 1 6.3390 7 3543 -1 4 6.3400 7 3524 -1 4 6.3435 7 3526 -2 3 6.3437 7 3533 5 1 6.3441 7 3540 -2 3 6.3483 7 3522 -4 3 6.3486 7 3532 5 1 6.3519 7 3540 -4 1 6.3567 7 3536 -6 1 6.3574 7 3522 5 1 6.3586 7 3526 5 2 6.3592 7 3524 1 4 6.3602 7 3532 1 3 6.3604 7 3541 5 1 6.3637 7 3528 -2 3 6.3673 7 3531 6 1 6.3711 7 3532 -6 1 6.3725 7 3543 -3 4 6.3749 7 3536 5 2 6.3753 7 3540 1 4 6.3760 7 3541 2 3 6.3768 7 3529 -6 1 6.3769 7 3528 6 1 6.3772 7 3529 2 3 6.3809 7 3534 -2 3 6.3814 7 3534 5 2 6.3865 7 3535 5 1 6.3881 7 3535 4 3 6.3945 7 3540 4 3 6.3973 7 3532 -4 3 6.4012 7 3534 -5 1 6.4030 7 3534 4 3 6.4035 7 3538 1 4 6.4038 7 3531 -7 1 6.4130 7 3539 3 2 6.4186 7 3543 -5 2 6.4192 7 3535 1 4 6.4236 7 3531 -2 3 6.4322 7 3543 3 2 6.4428 7 3539 2 3 6.4505 7 3543 -5 1 6.4534 7 3543 2 3 6.4535 7 3539 4 1 snappea-3.0d3/SnapPeaKernel/unix_kit/ClosedCensusData/ClosedNonorientableDistinct.txt0100444000175000017500000000050307001161434027157 0ustar babbab2.0298 5 018 1 0 2.5689 5 177 1 0 2.6667 5 153 1 0 2.8281 5 156 1 0 2.9891 5 254 1 0 3.1485 5 325 1 0 3.1639 5 265 1 0 3.1772 5 313 1 0 3.2758 5 311 1 0 3.3122 5 347 1 0 3.3317 5 321 1 0 3.3977 5 396 1 0 3.6638 5 394 1 0 3.6638 5 375 1 0 3.8232 5 308 1 0 3.8578 5 414 1 0 3.8578 5 411 1 0 snappea-3.0d3/SnapPeaKernel/headers/0040755000175000017500000000000007204006527015375 5ustar babbabsnappea-3.0d3/SnapPeaKernel/headers/Dirichlet.h0100444000175000017500000001004507046607540017456 0ustar babbab/* * Dirichlet.h * * For convenience and clarity, what might have been a single large * file Dirichlet.c has been organized as separate files: * * Dirichlet.c provides the global organization, * * Dirichlet_construction.c does the actual computation of * the Dirichlet domain, and * * Dirichlet_basepoint.c moves the basepoint to a local maximum * of the injectivity radius function. * * Dirichlet_precision.c makes an effort to keep roundoff error * under control whenever possible. * * Dirichlet_extras.c adds all the "bells and whistles" to a * Dirichlet domain once it's computed. * It works out faces identifications, * determines ideal vertices, etc. * * Dirichlet_rotate.c lets the UI spin the Dirichlet domain * in response to the user's mouse actions. * * This file (Dirichlet.h) provides the common declarations. */ #ifndef _Dirichlet_ #define _Dirichlet_ #include "kernel.h" /* * Two O(3,1) matrices are considered equal if and only if each pair * of corresponding entries are equal to within MATRIX_EPSILON. * * Technical notes: * * (1) Originally (on a 680x0 Mac) I had MATRIX_EPSILON set to 1e-8, * but it turned out that a product of two generators of an n-component * circular chain complement was the identity to a precision of 2e-8. * So I increased MATRIX_EPSILON. One doesn't want to make it too big, * though, just in case the basepoint happens to start on a short * geodesic of zero torsion. * * (2) When porting to other platforms (with lower floating point precision) * this number (and probably many other constants) must be changed. */ #define MATRIX_EPSILON 1e-5 /* * The MatrixPair data structure stores an O31Matrix and its inverse. */ typedef struct matrix_pair { /* * m[0] and m[1] are the two matrices which are inverses of one another. */ O31Matrix m[2]; /* * height is the hyperbolic cosine of the distance either matrix * translates the origin (1, 0, 0, 0) of hyperbolic space. * height == m[0][0][0] == m[1][0][0]. */ double height; /* * The left_ and right_child fields are used locally in * compute_all_products() in Dirichlet_compute.c to build a binary tree * of MatrixPairs. Normally MatrixPairs are kept on a doubly linked * list, using the prev and next fields. The next_subtree field is * used even more locally within tree-handling routines, to avoid doing * recursions on the system stack (for fear of stack/ heap collisions). */ struct matrix_pair *left_child, *right_child, *next_subtree; /* * Matrix pairs will be kept on doubly linked lists. */ struct matrix_pair *prev, *next; } MatrixPair; /* * A MatrixPairList is a doubly linked list of MatrixPairs. * It typically includes the identity MatrixPair. */ typedef struct { /* * begin and end are dummy nodes which serve to anchor * the doubly linked list. begin.prev and end.next * will always be NULL. The fields begin.m[], begin.dist, * end.m[] and end.dist are undefined and unused. */ MatrixPair begin, end; } MatrixPairList; extern WEPolyhedron *compute_Dirichlet_domain(MatrixPairList *gen_list, double vertex_epsilon); extern void all_edges_counterclockwise(WEFace *face, Boolean redirect_neighbor_fields); extern void redirect_edge(WEEdge *edge, Boolean redirect_neighbor_fields); extern void split_edge( WEEdge *old_edge, O31Vector cut_point, Boolean set_Dirichlet_construction_fields); extern FuncResult cut_face_if_necessary(WEFace *face, Boolean called_from_Dirichlet_construction); extern void maximize_the_injectivity_radius(MatrixPairList *gen_list, Boolean *basepoint_moved, DirichletInteractivity interactivity); extern void conjugate_matrices(MatrixPairList *gen_list, double displacement[3]); extern void free_matrix_pairs(MatrixPairList *gen_list); extern void precise_o31_product(O31Matrix a, O31Matrix b, O31Matrix product); extern void precise_generators(MatrixPairList *gen_list); extern FuncResult Dirichlet_bells_and_whistles(WEPolyhedron *polyhedron); #endif snappea-3.0d3/SnapPeaKernel/headers/SnapPea.h0100444000175000017500000024737707204003177017112 0ustar babbab/* * SnapPea.h * * This file defines the interface between SnapPea's comptational kernel * ("the kernel") and the user-interface ("the UI"). Both parts * must #include this file, and anything shared between the two parts * must be declared in this file. The only communication between the * two parts is via function calls -- no external variables are shared. * * All external symbols in the UI must begin with 'u' followed by a * capital letter. Nothing in the kernel should begin in this way. * * Typedef names use capitals for the first letter of each word, * e.g. Triangulation, CuspIndex. * * SnapPea 2.0 was funded by the University of Minnesota's * Geometry Center and the U.S. National Science Foundation. * SnapPea 3.0 is funded by the U.S. National Science Foundation * and the MacArthur Foundation. SnapPea and its source code may * be used freely for all noncommercial purposes. Please direct * questions, problems and suggestions to Jeff Weeks (weeks@northnet.org). * * Copyright 1999 by Jeff Weeks. All rights reserved. */ #ifndef _SnapPea_ #define _SnapPea_ /* * Note: values of the SolutionType enum are stored as integers in * the triangulation.doc file format. Changing the order of the * entries in the enum would therefore invalidate all previously stored * triangulations. */ typedef int SolutionType; enum { not_attempted, /* solution not attempted, or user cancelled */ geometric_solution, /* all positively oriented tetrahedra; not flat or degenerate */ nongeometric_solution, /* positive volume, but some negatively oriented tetrahedra */ flat_solution, /* all tetrahedra flat, but no shapes = {0, 1, infinity} */ degenerate_solution, /* at least one tetrahedron has shape = {0, 1, infinity} */ other_solution, /* volume <= 0, but not flat or degenerate */ no_solution /* gluing equations could not be solved */ }; typedef int FuncResult; enum { func_OK = 0, func_cancelled, func_failed, func_bad_input }; typedef struct { double real, imag; } Complex; #ifndef THINK_C typedef unsigned char Boolean; #endif /* * The values of MatrixParity should not be changed. * (They must correspond to the values in the parity[] table in tables.c.) */ typedef int MatrixParity; enum { orientation_reversing = 0, orientation_preserving = 1 }; /* * SnapPea represents a Moebius transformation as a matrix * in SL(2,C) plus a specification of whether the Moebius * transformation is orientation_preserving or orientation_reversing. * * If mt->parity is orientation_preserving, then mt->matrix is * interpreted in the usual way as the Moebius transformation * * az + b * f(z) = -------- * cz + d * * * If mt->parity is orientation_reversing, then mt->matrix is * interpreted as a function of the complex conjugate z' ("z-bar") * * az' + b * f(z) = --------- * cz' + d */ typedef Complex SL2CMatrix[2][2]; typedef struct { SL2CMatrix matrix; MatrixParity parity; } MoebiusTransformation; /* * Matrices in O(3,1) represent isometries in the Minkowski space * model of hyperbolic 3-space. The matrices are expressed relative * to a coordinate system in which the metric is * * -1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 1 * * That is, the first coordinate is timelike, and the remaining * three are spacelike. O(3,1) matrices represent both * orientation_preserving and orientation_reversing isometries. */ typedef double O31Matrix[4][4]; typedef double GL4RMatrix[4][4]; /* * An O31Vector is a vector in (3,1)-dimensional Minkowski space. * The 0-th coordinate is the timelike one. */ typedef double O31Vector[4]; /* * MatrixInt22 is a 2 x 2 integer matrix. A MatrixInt22 * may, for example, describe how the peripheral curves of * one Cusp map to those of another. */ typedef int MatrixInt22[2][2]; /* * An AbelianGroup is represented as a sequence of torsion coefficients. * A torsion coefficient of 0 represents an infinite cyclic factor. * For example, the group Z + Z + Z/2 + Z/5 is represented as the * sequence (0, 0, 2, 5). We make the convention that torsion coefficients * are always nonnegative. * * The UI may declare pointers to AbelianGroups, but only the kernel * may allocate or deallocate the actual memory used to store an * AbelianGroup. (This allows the kernel to keep track of memory * allocation/deallocation as a debugging aid.) */ typedef struct { int num_torsion_coefficients; /* number of torsion coefficients */ long int *torsion_coefficients; /* pointer to array of torsion coefficients */ } AbelianGroup; /* * A closed geodesic may be topologically a circle or a mirrored interval. */ typedef int Orbifold1; enum { orbifold1_unknown, orbifold_s1, /* circle */ orbifold_mI /* mirrored interval */ }; /* * The following 2-orbifolds may occur as the link of an * edge midpoint in a cell decomposition of a 3-orbifold. * * 94/10/4. The UI will see only types orbifold_nn * and orbifold_xnn. Edges of the other types have 0-cells * of the singular set at their midpoints, and are now * subdivided in Dirichlet_extras.c. JRW */ typedef int Orbifold2; enum { orbifold_nn, /* (nn) 2-sphere with two cone points (n may be 1) */ orbifold_no, /* (n|o) cross surface with cone point (n may be 1) */ orbifold_xnn, /* (*nn) disk with mirror boundary with two */ /* corner reflectors */ orbifold_2xn, /* (2*n) disk with order two cone point and mirror */ /* boundary with one corner reflector */ orbifold_22n /* (22n) sphere with three cone points */ }; /* * A MultiLength records the complex length of a geodesic together with a * parity telling whether it preserves or reverses orientation, a topology * telling whether it's a circle or a mirrored interval, and a multiplicity * telling how many distinct geodesics have that complex length, parity and * topology. */ typedef struct { Complex length; MatrixParity parity; Orbifold1 topology; int multiplicity; } MultiLength; /* * A CuspNbhdHoroball records a horoball to be drawn as part of a * picture of a cusp cross section. Only the kernel should allocate * and free CuspNbhdHoroballs and CuspNbhdHoroballLists. These * definitions are provided to the UI so it access the data easily. */ typedef struct { Complex center; double radius; int cusp_index; } CuspNbhdHoroball; typedef struct { /* * The horoball field points to an array * of num_horoballs CuspNbhdHoroballs. */ int num_horoballs; CuspNbhdHoroball *horoball; } CuspNbhdHoroballList; /* * A CuspNbhdSegment records a 1-cell to be drawn as part of a * picture of a cusp cross section. (Typically it's either part of * a triangulation of the cusp cross section, or part of a Ford domain.) * Only the kernel should allocate and free CuspNbhdSegments and * CuspNbhdSegmentLists. These definitions are provided to the UI * so it can easily access the data. * * JRW 99/03/17 When the CuspNbhdSegment describes a triangulation * (as opposed to a Ford domain), * * the start_index tells the edge index of the vertical edge * that runs from the given segment's beginning * to the viewer's eye, * * the middle_index tells the edge index of the given segment, and * * the end_index tells the edge index of the vertical edge * that runs from the given segment's end * to the viewer's eye. * * These indices let the viewer see how the horoball picture * "connects up" to form the manifold. */ typedef struct { Complex endpoint[2]; int start_index, middle_index, end_index; } CuspNbhdSegment; typedef struct { /* * segment is a pointer to an array of num_segments CuspNbhdSegments. */ int num_segments; CuspNbhdSegment *segment; } CuspNbhdSegmentList; typedef int Orientability; enum { oriented_manifold, nonorientable_manifold, unknown_orientability }; typedef int CuspTopology; enum { torus_cusp, Klein_cusp, unknown_topology }; typedef int DirichletInteractivity; enum { Dirichlet_interactive, Dirichlet_stop_here, Dirichlet_keep_going }; /* * An LRFactorization specifies the monodromy for a punctured torus * bundle over a circle. The factorization is_available whenever * (det(monodromy) = +1 and |trace(monodromy)| >= 2) or * (det(monodromy) = -1 and |trace(monodromy)| >= 1). * LR_factors points to an array of L's and R's, interpreted as factors * * L = ( 1 0 ) R = ( 1 1 ) * ( 1 1 ) ( 0 1 ) * * The factors act on a column vector, beginning with the last * (i.e. rightmost) factor. * * If negative_determinant is TRUE, the product is left-multiplied by * * ( 0 1 ) * ( 1 0 ) * * If negative_trace is TRUE, the product is left-multiplied by * * (-1 0 ) * ( 0 -1 ) * * When the factorization is unavailable, is_available is set to FALSE, * num_LR_factors is set to zero, and LR_factors is set to NULL. * But the negative_determinant and negative_trace flags are still set, * so the UI can display this information correctly. */ typedef struct { Boolean is_available, negative_determinant, negative_trace; int num_LR_factors; char *LR_factors; } LRFactorization; /* * The full definition of a Shingling appears near the top of shingling.c. * But computationally a Shingling is just a collection of planes in * hyperbolic space (typically viewed as circles on the sphere at infinity). * Each plane has an index (which defines the color of the circle at * infinity). */ typedef struct { /* * A plane in hyperbolic 3-space defines a hyperplane through * the origin in the Minkowski space model. Use the hyperplane's * normal vector to represent the original plane. [Note: the * normal is computed once, in the standard coordinate system, * and does not change as the UI rotates the polyhedron.] */ O31Vector normal; /* * A plane in hyperbolic 3-space intersects the sphere at infinity * in a circle. It's easy to draw the circle if we know its center * and two orthogonal "radials". (The 0-components of the center * and radials may be ignored.) [Note: the center and radials are * rotated in real time according to the polyhedron's current * position, and are scaled according to the window's pixel size.] */ O31Vector center, radialA, radialB; /* * The face planes of the original Dirichlet domain have index 0, * the face planes of the next layer (cf. shingling.c) have index 1, * and so on. */ int index; } Shingle; typedef struct { /* * A Shingling is just an array of Shingles. */ int num_shingles; Shingle *shingles; } Shingling; /* * The following are "opaque typedefs". They let the UI declare and * pass pointers to Triangulations, IsometryLists, etc. without * knowing what a Triangulation, IsometryList, etc. is. The definitions * of struct Triangulation, struct IsometryList, etc. are private to the * kernel. SymmetryLists and IsometryLists are represented by the same * data structure because Symmetries are just Isometries from a manifold * to itself. */ typedef struct Triangulation Triangulation; typedef struct IsometryList IsometryList; typedef struct SymmetryGroup SymmetryGroup; typedef struct SymmetryGroupPresentation SymmetryGroupPresentation; typedef struct DualOneSkeletonCurve DualOneSkeletonCurve; typedef struct TerseTriangulation TerseTriangulation; typedef struct GroupPresentation GroupPresentation; typedef struct CuspNeighborhoods CuspNeighborhoods; typedef struct NormalSurfaceList NormalSurfaceList; /* * winged_edge.h describes the winged edge data structure used * to describe Dirichlet domains. */ #include "winged_edge.h" /* * tersest_triangulation.h describes the most compressed form * for a Triangulation. The UI must have the actual definition * (not just an opaque typedef) because to read one from the * middle of a file it needs to know how long they are. */ #include "tersest_triangulation.h" /* * link_projection.h describes the format in which the UI passes * link projections to the kernel. */ #include "link_projection.h" /* * When the UI reads a Triangulation from disk, it passes the results * to the kernel using the format described in triangulation_io.h. */ #include "triangulation_io.h" /* * covers.h defines a representation of a manifold's fundamental group * into the symmetric group on n letters. */ #include "covers.h" /* To guarantee thread-safety, it's useful to declare */ /* global variables to be "const", for example */ /* */ /* static const Complex minus_i = {0.0, -1.0}; */ /* */ /* Unfortunately the current gcc compiler complains when */ /* non-const variables are passed to functions expecting */ /* const arguments. Obviously this is harmless, but gcc */ /* complains anyhow. So for now let's use the following */ /* CONST macro, to allow the const declarations to be */ /* reactivated if desired. */ #define CONST /* #define CONST const */ #ifdef __cplusplus extern "C" { #endif /************************************************************************/ /* * The UI provides the following functions for use by the kernel: */ extern void uAcknowledge(const char *message); /* * Presents the string *message to the user and waits for acknowledgment ("OK"). */ extern int uQuery(const char *message, const int num_responses, const char *responses[], const int default_response); /* * Presents the string *message to the user and asks the user to choose * one of the responses. Returns the number of the chosen response * (numbering starts at 0). In an interactive context, the UI should * present the possible responses evenhandedly -- none should be * presented as a default. However, in a batch context (when no human * is present), uQuery should return the default_response. */ extern void uFatalError(char *function, char *file); /* * Informs the user that a fatal error has occurred in the given * function and file, and then exits. */ extern void uAbortMemoryFull(void); /* * Informs the user that the available memory has been exhausted, * and aborts SnapPea. */ extern void uPrepareMemFullMessage(void); /* * uMemoryFull() is a tricky function, because the system may not find * enough memory to display an error message. (I tried having it stash * away some memory and then free it to support the desired dialog box, * but at least on the Mac this didn't work for some unknown reason.) * uPrepareMemFullMessage() gives the system a chance to prepare * a (hidden) dialog box. Call it once when the UI initializes. */ extern void uLongComputationBegins(char *message, Boolean is_abortable); extern FuncResult uLongComputationContinues(void); extern void uLongComputationEnds(void); /* * The kernel uses these three functions to inform the UI of a long * computation. The UI relays this information to the user in whatever * manner it considers appropriate. For example, it might wait a second * or two after the beginning of a long computation, and then display * a dialog box containing *message (a typical message might be * "finding canonical triangulation" or "computing hyperbolic structure"). * If is_abortable is TRUE, the dialog box would contain an abort button. * The reason for waiting a second or two before displaying the dialog * box is to avoid annoying the user with flashing dialog boxes for * computations which turn out not to be so long after all. * * The kernel is responsible for calling uLongComputationContinues() at * least every 1/60 second or so during a long computation. * uLongComputationContinues() serves two purposes: * * (1) It lets the UI yield time to its window system. (This is * crucial for smooth background operation in the Mac's * cooperative multitasking environment. I don't know whether * it is necessary in X or NeXT.) * * (2) If the computation is abortable, it checks whether the user * has asked to abort, and returns the result (func_cancelled * to abort, func_OK to continue). * * While the kernel is responsible for making sure uLongComputationContinues() * is called often enough, uLongComputationContinues() itself must take * responsibility for not repeating time-consuming operations too often. * For example, it might return immediately from a call if less than * 1/60 of a second has elapsed since the last time it carried out * its full duties. * * uLongComputationEnds() signals that the long computation is over. * The kernel must call uLongComputationEnds() even after an aborted * computation. ( uLongComputationContinues() merely informs the kernel * that the user punched the abort button. The kernel must still call * uLongComputationEnds() to dismiss the dialog box in the usual way.) * * If the UI receives a call to uLongComputationEnds() when no long * computation is in progress, or a call to uLongComputationBegins() * when a long computation is already in progress, it should notify * the user of the error and exit. * * If the UI receives a call to uLongComputationContinues() when in * fact no long computation is in progress, it should simply take * care of any background responsibilities (see (1) above) and not * complain. The reason for this provision is that the calls to * uLongComputationBegins() and uLongComputationEnds() occur in high * level functions, while the calls to uLongComputationContinues() * occur at the lowest level, perhaps in a different file. Someday * those low-level functions (for example, the routines for solving * simultaneous linear equations) might be called as part of some quick, * non-abortable computation. */ /************************************************************************/ /************************************************************************/ /* * The kernel provides the following functions for use by the UI. * * A brief specification follows each function prototype. * Complete documentation appears in the corresponding source file. */ /************************************************************************/ /* */ /* abelian_group.c */ /* */ /************************************************************************/ extern void expand_abelian_group(AbelianGroup *g); /* * Expands an AbelianGroup into its most factored form, * e.g. Z/2 + Z/2 + Z/4 + Z/3 + Z/9 + Z. * Each nonzero torsion coefficient is a power of a prime. */ extern void compress_abelian_group(AbelianGroup *g); /* * Compresses an AbelianGroup into its least factored form, * Z/2 + Z/6 + Z/36 + Z. * Each torsion coefficient divides all subsequent torsion coefficients. */ extern void free_abelian_group(AbelianGroup *g); /* * Frees the storage used to hold the AbelianGroup *g. */ /************************************************************************/ /* */ /* canonize.c */ /* canonize_part_1.c */ /* canonize_part_2.c */ /* */ /************************************************************************/ extern FuncResult canonize(Triangulation *manifold); /* * Replaces the given Triangulation with the canonical retriangulation * of the canonical cell decomposition. Returns func_OK upon success, * func_failed if it cannot find a hyperbolic structure for *manifold. */ extern FuncResult proto_canonize(Triangulation *manifold); extern void canonical_retriangulation(Triangulation *manifold); /* * These functions comprise the two halves of canonize() in canonize.c. * * proto_canonize() replaces a Triangulation by the canonical * triangulation of the same manifold (if the canonical cell * decomposition is a triangulation) or by an arbitrary subdivision * of the canonical cell decomposition into Tetrahedra (if the canonical * cell decomposition contains cells other than tetrahedra). * Returns func_OK upon success, func_failed if it cannot find a * hyperbolic structure for *manifold. * * canonical_retriangulation() replaces the given subdivision of the * canonical cell decomposition with the canonical retriangulation. * This operation introduces finite vertices whenever the canonical cell * decomposition is not a triangulation to begin with. The hyperbolic * structure is discarded. */ extern Boolean is_canonical_triangulation(Triangulation *manifold); /* * Given a subdivision of the canonical cell decomposition as produced * by proto_canonize(), says whether it is the canonical decomposition * itself. In other words, it says whether the canonical cell decomposition * is a triangulation. */ /************************************************************************/ /* */ /* change_peripheral_curves.c */ /* */ /************************************************************************/ extern FuncResult change_peripheral_curves( Triangulation *manifold, CONST MatrixInt22 change_matrices[]); /* * If all the change_matrices have determinant +1, installs the * corresponding new peripheral curves and returns func_OK. * (See change_peripheral_curves.c for details.) * Otherwise does nothing and returns func_bad_input. */ /************************************************************************/ /* */ /* chern_simons.c */ /* */ /************************************************************************/ extern void set_CS_value( Triangulation *manifold, double a_value); /* * Set the Chern-Simons invariant of *manifold to a_value. */ extern void get_CS_value( Triangulation *manifold, Boolean *value_is_known, double *the_value, int *the_precision, Boolean *requires_initialization); /* * If the Chern-Simons invariant of *manifold is known, sets * *value_is_known to TRUE and writes the current value and its precision * (the number of significant digits to the right of the decimal point) * to *the_value and *the_precision, respectively. * * If the Chern-Simons invariant is not known, sets *value_is_known to * FALSE, and then sets *requires_initialization to TRUE if the_value * is unknown because the computation has not been initialized, or * to FALSE if the_value is unknown because the solution contains * negatively oriented Tetrahedra. The UI might want to convey * these situations to the user in different ways. */ /************************************************************************/ /* */ /* complex.c */ /* */ /************************************************************************/ extern Complex complex_minus (Complex z0, Complex z1), complex_plus (Complex z0, Complex z1), complex_mult (Complex z0, Complex z1), complex_div (Complex z0, Complex z1), complex_sqrt (Complex z), complex_conjugate (Complex z), complex_negate (Complex z), complex_real_mult (double r, Complex z), complex_exp (Complex z), complex_log (Complex z, double approx_arg); extern double complex_modulus (Complex z); extern double complex_modulus_squared (Complex z); extern Boolean complex_nonzero (Complex z); extern Boolean complex_infinite (Complex z); /* * The usual complex arithmetic functions. * * Standard complex constants (Zero, One, etc.) are defined in the kernel. */ /************************************************************************/ /* */ /* complex_length.c */ /* */ /************************************************************************/ extern Complex complex_length_mt(MoebiusTransformation *mt); extern Complex complex_length_o31(O31Matrix m); /* * Computes the complex length of an isometry. Please see * complex_length.c for full definitions and explanations. * complex_length_mt() and complex_length_o31() are identical except * for the form in which the input is given. */ /************************************************************************/ /* */ /* continued_fractions.c */ /* */ /************************************************************************/ extern Boolean appears_rational(double x0, double x1, double confidence, long *num, long *den); /* * Checks whether a finite-precision real number x known to lie in the * interval (x0, x1) appears to be a rational number p/q. If it does, * it sets *num and *den to p and q, respectively, and returns TRUE. * Otherwise it sets *num and *den to 0 and returns FALSE. * The confidence parameter gives the maximal acceptable probability * of a "false positive". */ /************************************************************************/ /* */ /* core_geodesic.c */ /* */ /************************************************************************/ extern void core_geodesic( Triangulation *manifold, int cusp_index, int *singularity_index, Complex *core_length, int *precision); /* * Examines the Cusp of index cusp_index in *manifold. * * If the Cusp is unfilled or the Dehn filling coefficients are not * integers, sets *singularity_index to zero and leaves *core_length * undefined. * * If the Cusp has relatively prime integer Dehn filling coefficients, * sets *singularity_index to 1 and *core_length to the complex length * of the central geodesic. * * If the Cusp has non relatively prime integer Dehn filling coefficients, * sets *singularity_index to the index of the singular locus, and * *core_length to the complex length of the central geodesic in the * smallest manifold cover of a neighborhood of the singular set. * * In the latter two cases, if the precision pointer is not NULL, * *precision is set to the number of decimal places of accuracy in * the computed value of *core_length. * * core_geodesic() is intended for use by the UI. Kernel function may * find compute_core_geodesic() (declared in kernel_prototypes.h) more * convenient. */ /************************************************************************/ /* */ /* cover.c */ /* */ /************************************************************************/ Triangulation *construct_cover( Triangulation *base_manifold, RepresentationIntoSn *representation, int num_generators, int n); /* * Constructs the n-sheeted cover of the given base_manifold defined * by the given transitive representation. */ /************************************************************************/ /* */ /* current_curve_basis.c */ /* */ /************************************************************************/ extern void current_curve_basis( Triangulation *manifold, int cusp_index, MatrixInt22 basis_change); extern void install_current_curve_bases(Triangulation *manifold); /* * current_curve_basis() accepts a Triangulation and a cusp index, * and computes a 2 x 2 integer matrix basis_change with the property * that * * if the Cusp of index cusp_index is filled, and has * relatively prime integer Dehn filling coefficients, * * the first row of basis_change is set to the current * Dehn filling coefficients, and * the second row of basis_change is set to the shortest * curve which completes a basis. * * else * * basis_change is set to the identity * * install_current_curve_bases() installs the above basis * on all cusps of the manifold. */ /************************************************************************/ /* */ /* cusp_neighborhoods.c */ /* */ /************************************************************************/ extern CuspNeighborhoods *initialize_cusp_neighborhoods( Triangulation *manifold); /* * Initializes a CuspNeighborhoods data structure. * It works with a copy of manifold, leaving the original untouched. * It does all indicated Dehn fillings. * Returns a pointer to the CuspNeighborhoods structure upon success, * of NULL if the "manifold" isn't a cusped hyperbolic 3-manifold. */ extern void free_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods); /* * Frees the CuspNeighborhoods structure, including the copy of * the Triangulation it contains. */ extern int get_num_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the number of cusps. This will be the number of unfilled * cusps in the original manifold, which may be less than the total * number of cusps. */ extern CuspTopology get_cusp_neighborhood_topology( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the CuspTopology of the given cusp. */ extern double get_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the (linear) displacement of the horospherical cross * section of the given cusp from its home position. At the home * position the cusp cross section has area (3/8)sqrt(3) and * encloses a volume of (3/16)sqrt(3) in the cusp. At its home * position, a cusp cannot overlap itself, nor can it overlap any * other cusp which does not already overlap itself. Please see * cusp_neighborhoods.c for details. */ extern Boolean get_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Says whether this cusp's neighborhood is tied to other cusps'. */ extern double get_cusp_neighborhood_cusp_volume( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the volume enclosed by the horospherical cross section * of the given cusp. */ extern double get_cusp_neighborhood_manifold_volume( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the volume of the manifold. */ extern Triangulation *get_cusp_neighborhood_manifold( CuspNeighborhoods *cusp_neighborhoods); /* * Returns a pointer to a copy of the manifold. The UI may do as it * pleases with the copy, and should free it when it's done. */ extern double get_cusp_neighborhood_reach( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the displacement at which the cusp cross section first * bumps into itself. */ extern double get_cusp_neighborhood_max_reach( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the maximum reach over the whole manifold. */ extern double get_cusp_neighborhood_stopping_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); extern int get_cusp_neighborhood_stopper_cusp_index( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Return the displacement at which the cusp first bumps into another * cusp (or possibly into itself), and the cusp it bumps into. * Unlike the reach, the stopper and the stopping displacement depend * on the current displacements of all the cusps in the triangulation. * They vary dynamically as the user moves the cusp cross sections. */ extern void set_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, double new_displacement); /* * Sets the cusp neighborhood's displacement to the requested value, * clipping it to the range [0, stopping_displacement] if necessary. * Recomputes the canonical cell decomposition. */ extern void set_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean new_tie); /* * Tells the kernel whether this cusp's neighborhood should be * tied to other cusps (which have previously been "tied"). * The kernel makes all tied cusps have the same displacement. */ extern void get_cusp_neighborhood_translations( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Complex *meridian, Complex *longitude); /* * Returns the meridional and longitudinal translation vectors * for the given cusp cross section, taking into account its current * displacement. For a Klein bottle cusp, the longitudinal translation * will be that of the double cover. As a convenience, the longitude * will always point in the x-direction. */ extern CuspNbhdHoroballList *get_cusp_neighborhood_horoballs( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean full_list, double cutoff_height); /* * Returns a list of horoballs seen from the given cusp, taking into * account the cusp cross sections' current displacements. Only one * translate is given for each horoball -- to draw the full picture the * UI must find all visible translates using the meridian and longitude * provided by get_cusp_neighborhood_translations(). For a Klein bottle * cusp, get_cusp_neighborhood_horoballs() reports data for the double * cover. If full_list is TRUE, get_cusp_neighborhood_horoballs() * reports all horoballs whose Euclidean height in the upper half space * model is at least cutoff_height. If full_list is FALSE, it reports * only a few of the largest horoballs (the cutoff_height is ignored). * This lets the UI draw a simpler picture while the user is changing * something in real time, and then draw a more complete picture afterwards. */ extern void free_cusp_neighborhood_horoball_list( CuspNbhdHoroballList *horoball_list); /* * Frees a CuspNbhdHoroballList when the UI's done with it. */ extern CuspNbhdSegmentList *get_cusp_neighborhood_triangulation( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns a list of edges in the restriction of the canonical cell * decomposition to the cusp cross section, taking into account the * cusp cross section's current displacement. Only one translate is * given for each edge -- to draw the full picture the UI must find all * visible translates using the meridian and longitude provided by * get_cusp_neighborhood_translations(). For a Klein bottle cusp, * get_cusp_neighborhood_triangulation() reports data for the double cover. */ extern CuspNbhdSegmentList *get_cusp_neighborhood_Ford_domain( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns a list of edges in the Ford domain, taking into account the * cusp cross section's current displacement. Only one translate is * given for each edge -- to draw the full picture the UI must find all * visible translates using the meridian and longitude provided by * get_cusp_neighborhood_translations(). For a Klein bottle cusp, * get_cusp_neighborhood_Ford_domain() reports data for the double cover. */ extern void free_cusp_neighborhood_segment_list( CuspNbhdSegmentList *segment_list); /* * Frees a CuspNbhdSegmentList when the UI's done with it. */ /************************************************************************/ /* */ /* Dirichlet.c */ /* */ /************************************************************************/ extern WEPolyhedron *Dirichlet( Triangulation *manifold, double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Computes a Dirichlet domain for the given manifold or orbifold. * Returns NULL if the Dehn filling coefficients are not all integers, * of if roundoff errors lead to topological problems. * Returns a pointer to the Dirichlet domain otherwise. */ extern WEPolyhedron *Dirichlet_with_displacement( Triangulation *manifold, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Like Dirichlet(), only allows an arbitrary displacement * of the basepoint. The displacement is in tangent space * coordinates, so the distances can't be interpreted too literally. * Reasonable displacements are to the order of 0.1. * Large displacements are possible, but degrade the numerical * accuracy of the resulting Dirichlet domain. */ extern WEPolyhedron *Dirichlet_from_generators( O31Matrix generators[], int num_generators, double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Like Dirichlet(), only starts with a set of O(3,1) matrix generators * instead of a Triangulation. */ extern WEPolyhedron *Dirichlet_from_generators_with_displacement( O31Matrix generators[], int num_generators, double displacement[3], double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Combines the functionality of Dirichlet_with_displacement() and * Dirichlet_from_generators(). */ extern void change_basepoint( WEPolyhedron **polyhedron, Triangulation *manifold, O31Matrix *generators, int num_generators, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Reads the face pairing matrices from the polyhedron, shifts the * basepoint by the given displacement (optionally letting the basepoint * move to a local maximum of the injectivity radius function), and * recomputes the Dirichlet domain. * If *polyhedron is NULL, computes the Dirichlet domain directly from * the manifold, but with the given displacement of the initial basepoint. * In either case, a pointer to the resulting Dirichlet domain (or NULL * if an error occurs as described in Dirichlet() above) is written * to *polyhedron. */ extern void free_Dirichlet_domain(WEPolyhedron *Dirichlet_domain); /* * Frees the storage occupied by a WEPolyhedron. */ /************************************************************************/ /* */ /* Dirichlet_rotate.c */ /* */ /************************************************************************/ extern void set_identity_matrix(O31Matrix position); /* * Sets the matrix to the identity. */ extern void update_poly_position(O31Matrix position, O31Matrix velocity); /* * Multiplies the position by the velocity. */ extern void update_poly_vertices(WEPolyhedron *polyhedron, O31Matrix position, double scale); /* * Multiplies the standard vertex coordinates x[] by the position matrix * to obtain the rotated coordinates xx[], and then multiplies the * rotated coordinates by the constant "scale". */ extern void update_poly_visibility(WEPolyhedron *polyhedron, O31Matrix position, O31Vector direction); /* * Checks which vertices, edges and faces are visible to the user with * the polyhedron in its present position, and sets their visibility * fields accordingly. */ /************************************************************************/ /* */ /* Dirichlet_conversion.c */ /* */ /************************************************************************/ extern Triangulation *Dirichlet_to_triangulation(WEPolyhedron *polyhedron); /* * Converts a Dirichlet domain to a Triangulation, leaving the * Dirichlet domain unchanged. For closed manifolds, drills out * an arbitrary curve and expresses the manifold as a Dehn filling. */ /************************************************************************/ /* */ /* double_cover.c */ /* */ /************************************************************************/ extern Triangulation *double_cover(Triangulation *manifold); /* * Returns a pointer to the double cover of the nonorientable * Triangulation *manifold. */ /************************************************************************/ /* */ /* dual_curves.c */ /* */ /************************************************************************/ extern void dual_curves( Triangulation *manifold, int max_size, int *num_curves, DualOneSkeletonCurve ***the_curves); /* * Computes a reasonable selection of simple closed curves in * a manifold's dual 1-skeleton. */ extern void get_dual_curve_info( DualOneSkeletonCurve *the_curve, Complex *complete_length, Complex *filled_length, MatrixParity *parity); /* * Reports the complex length of a curve in the dual 1-skeleton, * relative to both the complete and filled hyperbolic structures, * and also its parity (orientation_preserving or orientation_reversing). */ extern void free_dual_curves( int num_curves, DualOneSkeletonCurve **the_curves); /* * Frees the array of curves computed by dual_curves(). */ /************************************************************************/ /* */ /* drilling.c */ /* */ /************************************************************************/ extern Triangulation *drill_cusp( Triangulation *old_manifold, DualOneSkeletonCurve *curve_to_drill, char *new_name); /* * Drills a curve out of the dual 1-skeleton of an n-cusp manifold to * create an (n+1)-cusp manifold. */ /************************************************************************/ /* */ /* filling.c */ /* */ /************************************************************************/ extern Triangulation *fill_cusps( Triangulation *manifold, Boolean fill_cusp[], char *new_name, Boolean fill_all_cusps); /* * Permanently fills k of the cusps of an n-cusp manifold. * Typically fill_all_cusps is FALSE, and the function returns * an ideal Triangulation of the resulting (n - k)-cusp manifold. * fill_cusp[] is a Boolean array specifying which k cusps (k < n) * are to be filled. * * In the exceptional case that fill_all_cusps is TRUE, the function * returns a triangulation with finite vertices only. * Such triangulations are unacceptable for most SnapPea routines, * and should be used only for writing to disk. When fill_all_cusps * is TRUE, fill_cusp is ignored and may be NULL. * * new_name is the name to be given to the new Triangulation. */ extern Triangulation *fill_reasonable_cusps(Triangulation *manifold); /* * Makes reasonable choices for fill_cusp[] and new_name, and calls * fill_cusps(). Specifically, it will fill all cusps with relatively * prime Dehn filling coefficients, unless this would leave no cusps * unfilled, in which case it leaves cusp 0 unfilled. It copies the * name from the original manifold. */ extern Boolean cusp_is_fillable(Triangulation *manifold, int cusp_index); /* * Returns TRUE if a cusp has relatively prime integer Dehn filling * coefficients, FALSE otherwise. */ extern Boolean is_closed_manifold(Triangulation *manifold); /* * Returns TRUE iff all cusps are filled and the coefficients * are relatively prime integers. */ /************************************************************************/ /* */ /* fundamental_group.c */ /* */ /************************************************************************/ extern GroupPresentation *fundamental_group( Triangulation *manifold, Boolean simplify_presentation, Boolean fillings_may_affect_generators, Boolean minimize_number_of_generators); /* * Computes the fundamental group of the manifold, taking into account * Dehn fillings, and returns a pointer to it. Please see * fundamental_group.c for an explanation of the arguments. */ extern int fg_get_num_generators(GroupPresentation *group); /* * Returns the number of generators in the GroupPresentation. */ extern Boolean fg_integer_fillings(GroupPresentation *group); /* * Says whether the underlying space is a manifold or orbifold, * as opposed to some other generalized Dehn filling. */ extern FuncResult fg_word_to_matrix( GroupPresentation *group, int *word, O31Matrix result_O31, MoebiusTransformation *result_Moebius); /* * Converts an abstract word in the fundamental group to a matrix * in the matrix representation. The abstract word is given as a * string of integers. The integer 1 means the first generator, * 2 means the second, etc., while -1 is the inverse of the first * generator, -2 is the inverse of the second, etc. The integer 0 * indicates the end of the string. The result is given both as * an O31Matrix and a MoebiusTransformation. Returns func_OK if * successful, or func_bad_input if the input word is not valid. */ extern int fg_get_num_relations(GroupPresentation *group); /* * Returns the number of relations in the GroupPresentation. */ extern int *fg_get_relation( GroupPresentation *group, int which_relation); /* * Returns the specified relation (using 0-based indexing). * It allocates the memory for it, so you should pass the pointer * back to fg_free_relation() when you're done with it. * Each relation is a string of integers. The integer 1 means * the first generator, 2 means the second, etc., while -1 is the * inverse of the first generator, -2 is the inverse of the second, etc. * The integer 0 indicates the end of the string. */ extern void fg_free_relation(int *relation); /* * Frees a relation allocated by fg_get_relation(). */ extern int fg_get_num_cusps(GroupPresentation *group); /* * Returns the number of cusps of the underlying manifold. * This *includes* the filled cusps. So, for example, if you do (5,1) * Dehn filling on the figure eight knot complement, you can see the * words in the fundamental group corresponding to the (former!) cusp's * meridian and longitude. */ extern int *fg_get_meridian( GroupPresentation *group, int which_cusp); extern int *fg_get_longitude( GroupPresentation *group, int which_cusp); /* * Returns the word corresponding to a meridian or longitude, in the * same format used by fg_get_relation() above. They allocate the * memory for the string of integers, so you should pass the pointer * back to fg_free_relation() when you're done with it. Meridians and * longitudes are available whether the cusps are filled or not, as * explained for fg_get_num_cusps() above. */ extern int *fg_get_original_generator( GroupPresentation *group, int which_generator); /* * Returns a word which expresses one of the standard geometric * generators (as defined in choose_generators.c) in terms of the * group presentation's generators. The word is in the same format * used by fg_get_relation() above. Note that which_generator is * given relative to 0-based indexing, but the letters in the word * you get out use 1-based numbering, as in fg_get_relation(). * Please free the word with fg_free_relation() when you're done. */ extern void free_group_presentation(GroupPresentation *group); /* * Frees the storage occupied by a GroupPresentation. */ /************************************************************************/ /* */ /* homology.c */ /* */ /************************************************************************/ extern AbelianGroup *homology(Triangulation *manifold); /* * If all Dehn filling coefficients are integers, returns a pointer to * the first homology group of *manifold. In particular, it will * happily compute homology groups of orbifolds. If one or more Dehn * filling coefficients are not integers, returns NULL. This function * allocates the memory for the AbelianGroup; the UI should call * free_abelian_group() (no pun intended) to release it. * * 96/12/11 Checks for overflows, and returns NULL if any occur. */ extern AbelianGroup *homology_from_fundamental_group( GroupPresentation *group); /* * Abelianizes a group presentation and returns the result. * Returns NULL if overflows occur. */ /************************************************************************/ /* */ /* hyperbolic_structure.c */ /* */ /************************************************************************/ extern SolutionType find_complete_hyperbolic_structure(Triangulation *manifold); /* * Attempts to find a complete hyperbolic structure for the * Triangulation *manifold. Sets the solution_type[complete] member of * *manifold to the type of solution found. If this type is anything * other than no_solution, stores the hyperbolic structure by setting * the *shape[complete] field of each Tetrahedron in the Triangulation. The * solution is also stored as the initial filled solution, by setting the * solution_type[filled] member of *manifold and the *shape[filled] fields * of the Tetrahedra; the is_complete flag of each Cusp is set to TRUE. * * The hyperbolic structure is computed using Newton's method, beginning * with all tetrahedra regular. * * Returns: the type of solution found. */ extern SolutionType do_Dehn_filling(Triangulation *manifold); /* * Attempts to find a hyperbolic structure for a *manifold, based on * the current Dehn filling coefficients. Sets the solution_type[filled] * member of *manifold to the type of solution found. If * this type is anything other than no_solution, stores the hyperbolic * structure by setting the *shape[filled] field of each Tetrahedron in * the Triangulation. * * The hyperbolic structure is computed using Newton's method; the * initial guess is the previous Dehn filled solution. * * Returns: the type of solution found. */ extern SolutionType remove_Dehn_fillings(Triangulation *manifold); /* * Removes all Dehn fillings. * * Returns: the type of solution restored. */ /************************************************************************/ /* */ /* index_to_hue.c */ /* */ /************************************************************************/ extern double index_to_hue(int index); /* * Maps the nonnegative integers to a set of easily distinguishable hues. * * index 0 1 2 3 4 5 6 . . . * hue 0 1/2 1/4 3/4 1/8 5/8 3/8 . . . */ extern double horoball_hue(int index); /* * Provides hand chosen hues for indices 0-5, and uses index_to_hue() * to interpolate thereafter. The hope is for nicer looking horoball * packings. */ /************************************************************************/ /* */ /* interface.c */ /* */ /************************************************************************/ extern char *get_triangulation_name(Triangulation *manifold); /* * Return a pointer to the name of the Triangulation *manifold. * The pointer points to the actual name, not a copy. */ extern void set_triangulation_name(Triangulation *manifold, char *new_name); /* * Sets the Triangulation's name to new_name. */ extern SolutionType get_complete_solution_type(Triangulation *manifold); /* * Returns the SolutionType of the complete structure. */ extern SolutionType get_filled_solution_type(Triangulation *manifold); /* * Returns the SolutionType of the current Dehn filling. */ extern int get_num_tetrahedra(Triangulation *manifold); /* * Returns the number of tetrahedra in the Triangulation *manifold. */ extern Orientability get_orientability(Triangulation *manifold); /* * Returns the orientability of *manifold. */ extern int get_num_cusps(Triangulation *manifold); /* * Returns the number of cusps in *manifold. */ extern int get_num_or_cusps(Triangulation *manifold); /* * Returns the number of orientable cusps in *manifold. */ extern int get_num_nonor_cusps(Triangulation *manifold); /* * Returns the number of nonorientable cusps in *manifold. */ extern int get_max_singularity(Triangulation *manifold); /* * Returns the maximum value of gcd(m,l) over all integer Dehn filling * coefficients (m,l) for filled cusps in *manifold. */ extern int get_num_generators(Triangulation *manifold); /* * Returns the number of generators being used to represent *manifold's * fundamental group. */ extern void get_cusp_info( Triangulation *manifold, int cusp_index, CuspTopology *topology, Boolean *is_complete, double *m, double *l, Complex *initial_shape, Complex *current_shape, int *initial_shape_precision, int *current_shape_precision, Complex *initial_modulus, Complex *current_modulus); /* * Provides information about the cusp whose index is cusp_index in * *manifold. (The cusp indices run from zero to one less than the * number of cusps.) * * *topology is set to torus_cusp, Klein_cusp, or unknown_topology. * *is_complete is set to TRUE if the cusp is not Dehn filled, and * FALSE if it is. * *m and *l are set to the current Dehn filling coefficients. * They will be meaningful only if the cusp is filled. * If the cusp is nonorientable, only *m will be meaningful * (because *l must be zero for a Klein bottle cusp -- see * the comment at the top of holonomy.c). * *initial_shape is set to the initial shape (longitude/meridian) of the * cusp, i.e. the shape it had when all cusps were unfilled. * *current_shape is set to the cusp's current shape if the cusp is_complete, * zero otherwise. * *initial_shape_precision is set to the number of decimal places of accuracy * in the computed value of initial_shape. * *current_shape_precision is set to the number of decimal places of accuracy * in the computed value of current_shape. * *initial_modulus is set to the modulus ( (second shortest translation)/ * (shortest translation) ) of the initial cusp shape. * *current_modulus is set to the modulus of the current cusp shape. * * You may pass NULL for pointers to values you aren't interested in. */ extern FuncResult set_cusp_info(Triangulation *manifold, int cusp_index, Boolean cusp_is_complete, double m, double l); /* * Looks for a cusp with index cusp_index in Triangulation *manifold. * If not found, * alerts the user and exits (this should never occur * unless there is a bug in the UI). * If found, * if cusp_is_complete is TRUE, * sets the is_complete field of the cusp to TRUE, and * sets the Dehn filling coefficients to 0.0, * if cusp_is_complete is FALSE * sets the is_complete field of the cusp to FALSE, and * sets the Dehn filling coefficients to m and l. * * set_cusp_info() checks for errors in the values of m and l. * The (0,0) Dehn filling is never allowed, and only (p,0) fillings are * allowed on nonorientable cusps. If an error is detected, the cusp * will be left unchanged. * * Returns: * func_OK for success * func_bad_input for illegal Dehn filling coefficients */ extern void get_holonomy( Triangulation *manifold, int cusp_index, Complex *meridional_holonomy, Complex *longitudinal_holonomy, int *meridional_precision, int *longitudinal_precision); /* * Passes back the holonomies of the meridian and longitude, * and an estimate of their precision (number of decimal * digits to the right of the decimal point). */ extern void get_tet_shape( Triangulation *manifold, int which_tet, Boolean fixed_alignment, double *shape_rect_real, double *shape_rect_imag, double *shape_log_real, double *shape_log_imag, int *precision_rect_real, int *precision_rect_imag, int *precision_log_real, int *precision_log_imag, Boolean *is_geometric); /* * Provides information about the shape of the Tetrahedron in * position which_tet in the linked list (which_tet takes a value * in the range [0, (#tetrahedra - 1)] ). (Note: which_tet * does not explicitly refer to the "index" field of the Tetrahedron * data structure, although in practice it will coincide.) * get_tet_shape() provides the shape of the Tetrahedron in both * rectangular and logarithmic forms, relative to whatever coordinate * system was used most recently. This means that the rectangular * form will satisfy |z| < 1 and |z - 1| < 1. The last four arguments * give the precision of the preceding four, expressed as the number * of significant deciomal digits following the decimal point. * (Warning: the precision is only a rough estimate. The last * digit or two may sometimes be incorrect.) The flag *is_geometric * is set to TRUE iff all dihedral angles lie in the range [0,pi]. */ extern int get_num_edge_classes( Triangulation *manifold, int edge_class_order, Boolean greater_than_or_equal); /* * If greater_than_or_equal == TRUE, returns the number of EdgeClasses * whose order is greater than or equal to edge_class_order. * If greater_than_or_equal == FALSE, returns the number of EdgeClasses * whose order is exactly edge_class_order. */ /************************************************************************/ /* */ /* isometry.c */ /* */ /************************************************************************/ extern FuncResult compute_isometries( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric, IsometryList **isometry_list, IsometryList **isometry_list_of_links); /* * Checks whether manifold0 and manifold1 are isometric (taking into * account the Dehn fillings). If manifold0 and manifold1 are cusped * manifolds, sets *isometry_list and *isometry_list_of_links as * in compute_cusped_isometries() below. Returns * func_OK if all goes well, * func_bad_input if some Dehn filling coefficients are not * relatively prime integers, * func_failed if it can't decide. */ extern int isometry_list_size(IsometryList *isometry_list); /* * Returns the number of Isometries in the IsometryList. */ extern int isometry_list_num_cusps(IsometryList *isometry_list); /* * Returns the number of cusps in each of the underlying manifolds. * If the IsometryList is empty (as would be the case when the * underlying manifolds have different numbers of cusps), then * isometry_list_num_cusps()'s return value is undefined. */ extern void isometry_list_cusp_action( IsometryList *isometry_list, int anIsometryIndex, int aCusp, int *cusp_image, int cusp_map[2][2]); /* * Fills in the cusp_image and cusp_map[2][2] to describe the action * of the given Isometry on the given Cusp. */ extern Boolean isometry_extends_to_link(IsometryList *isometry_list, int i); /* * Returns TRUE if Isometry i extends to the associated links (i.e. if it * takes meridians to meridians), FALSE if it doesn't. */ extern void isometry_list_orientations( IsometryList *isometry_list, Boolean *contains_orientation_preserving_isometries, Boolean *contains_orientation_reversing_isometries); /* * Says whether the IsometryList contains orientation-preserving * and/or orientation-reversing elements. Assumes the underlying * Triangulations are oriented. */ extern void free_isometry_list(IsometryList *isometry_list); /* * Frees the IsometryList. */ /************************************************************************/ /* */ /* isometry_cusped.c */ /* */ /************************************************************************/ extern Boolean same_triangulation( Triangulation *manifold0, Triangulation *manifold1); /* * Check whether manifold0 and manifold1 have combinatorially * equivalent triangulations (ignoring Dehn fillings). * This function is less versatile than a call to * compute_isometries(manifold0, manifold1, &are_isometric, NULL, NULL) * but it's useful for batch processing, when you want to avoid the * overhead of constantly recomputing canonical retriangulations. */ /************************************************************************/ /* */ /* length_spectrum.c */ /* */ /************************************************************************/ extern void length_spectrum( WEPolyhedron *polyhedron, double cutoff_length, Boolean full_rigor, Boolean multiplicities, double user_radius, MultiLength **spectrum, int *num_lengths); /* * Takes as input a manifold in the form of a Dirichlet domain, and * finds all geodesics of length less than or equal to cutoff_length. * Please length_spectrum.c for details. */ extern void free_length_spectrum(MultiLength *spectrum); /* * Deallocates the memory used to store the length spectrum. */ /************************************************************************/ /* */ /* link_complement.c */ /* */ /************************************************************************/ extern Triangulation *triangulate_link_complement( KLPProjection *aLinkProjection); /* * Triangulate the complement of aLinkProjection. */ /************************************************************************/ /* */ /* matrix_conversion.c */ /* */ /************************************************************************/ extern void Moebius_to_O31(MoebiusTransformation *A, O31Matrix B); extern void O31_to_Moebius(O31Matrix B, MoebiusTransformation *A); /* * Convert matrices back and forth between SL(2,C) and O(3,1). */ extern void Moebius_array_to_O31_array( MoebiusTransformation arrayA[], O31Matrix arrayB[], int num_matrices); extern void O31_array_to_Moebius_array( O31Matrix arrayB[], MoebiusTransformation arrayA[], int num_matrices); /* * Convert arrays of matrices back and forth between SL(2,C) and O(3,1). */ extern Boolean O31_determinants_OK( O31Matrix arrayB[], int num_matrices, double epsilon); /* * Returns TRUE if all the O31Matrices in the array have determinants * within epsilon of plus or minus one, and FALSE otherwise. */ /************************************************************************/ /* */ /* matrix_generators.c */ /* */ /************************************************************************/ extern void matrix_generators( Triangulation *manifold, MoebiusTransformation generators[], Boolean centroid_at_origin); /* * Computes the MoebiusTransformations representing the action * of the generators of a manifold's fundamental group on the sphere at * infinity. Writes the MoebiusTransformations to the array generators[], * which it assumes has already been allocated. You may use * get_num_generators() to determine how long an array to allocate. * If centroid_at_origin is TRUE, the initial tetrahedron is positioned * with its centroid at the origin; otherwise the initial tetrahedron * is positioned with its vertices at {0, 1, infinity, z}. */ /************************************************************************/ /* */ /* my_malloc.c */ /* */ /************************************************************************/ extern void verify_my_malloc_usage(void); /* * The UI should call verify_my_malloc_usage() upon exit to verify that * the number of calls to my_malloc() was exactly balanced by the number * of calls to my_free(). In case of error, verify_my_malloc_usage() * passes an appropriate message to uAcknowledge. */ /************************************************************************/ /* */ /* normal_surface_construction.c */ /* */ /************************************************************************/ extern FuncResult find_normal_surfaces( Triangulation *manifold, NormalSurfaceList **surface_list); /* * Tries to find connected, embedded normal surfaces of nonnegative * Euler characteristic. If spheres or projective planes are found, * then tori and Klein bottles aren't reported, because from the point * of view of the Geometrization Conjecture, one wants to cut along * spheres and projective planes first. Surfaces are guaranteed to be * connected. They aren't guaranteed to be incompressible, although * typically they are. There is no guarantee that all such normal * surfaces will be found. Returns its result as a pointer to a * NormalSurfaceList, the internal structure of which is private to * the kernel. To get information about the normal surfaces on the list, * use the functions below. To split along a normal surface, call * split_along_normal_surface(). When you're done with the * NormalSurfaceList, free it using free_normal_surfaces(). * * The present implementation works only for cusped manifolds. * Returns func_bad_input for closed manifolds, or non-manifolds. */ extern int number_of_normal_surfaces_on_list( NormalSurfaceList *surface_list); /* * Returns the number of normal surfaces contained in the list. */ extern Boolean normal_surface_is_orientable( NormalSurfaceList *surface_list, int index); extern Boolean normal_surface_is_two_sided( NormalSurfaceList *surface_list, int index); extern int normal_surface_Euler_characteristic( NormalSurfaceList *surface_list, int index); /* * Return information about a given normal surface on the list. * The indices run from 0 through (number of surfaces - 1). */ extern void free_normal_surfaces(NormalSurfaceList *surface_list); /* * Frees an array of NormalSurfaceLists. */ /************************************************************************/ /* */ /* normal_surface_splitting.c */ /* */ /************************************************************************/ extern FuncResult split_along_normal_surface( NormalSurfaceList *surface_list, int index, Triangulation *pieces[2]); /* * Splits the manifold (stored privately in the NormalSurfaceList) * along the normal surface of the given index (indices range from 0 to * (number of surfaces - 1)). If the normal surface is a 2-sided * projective plane, split_along_normal_surface() returns func_bad_input; * otherwise it returns func_OK. If the normal surface is a sphere or * 1-sided projective plane, the resulting spherical boundary component(s) * are capped off with 3-ball(s); otherwise the new torus or Klein bottle * boundary component(s) become cusp(s). If the normal surface is * nonseparating, the result is returned in pieces[0], and pieces[1] * is set to NULL. If the normal surface is separating, the two pieces * are returned in pieces[0] and pieces[1]. */ /************************************************************************/ /* */ /* o31_matrices.c */ /* */ /************************************************************************/ /* * Most of the functions in o31_matrices.c are private to the kernel. * The following have been made available to the UI as well. */ extern double gl4R_determinant(GL4RMatrix m); extern double o31_trace(O31Matrix m); /************************************************************************/ /* */ /* orient.c */ /* */ /************************************************************************/ extern void reorient(Triangulation *manifold); /* * Reverse a manifold's orientation. */ /************************************************************************/ /* */ /* punctured_torus_bundles.c */ /* */ /************************************************************************/ extern void bundle_LR_to_monodromy( LRFactorization *anLRFactorization, MatrixInt22 aMonodromy); /* * Multiplies out anLRFactorization to obtain aMonodromy. */ extern void bundle_monodromy_to_LR( MatrixInt22 aMonodromy, LRFactorization **anLRFactorization); /* * If (det(aMonodromy) = +1 and |trace(aMonodromy)| >= 2) or * (det(aMonodromy) = -1 and |trace(aMonodromy)| >= 1), * then bundle_monodromy_to_LR() conjugates aMonodromy to a * nonnegative or nonpositive matrix, and factors it as * anLRFactorization. These cases include all monodromies of * hyperbolic manifolds, as well as the nonhyperbolic cases * (det(aMonodromy) = +1 and |trace(aMonodromy)| = 2), which * the user might want to see factored just for fun. * Otherwise bundle_monodromy_to_LR() sets * (*anLRFactorization)->is_available to FALSE, but nevertheless * sets negative_determinant and negative_trace correctly in case * the UI wants to display them. The UI should indicate that the * factorization is not available (e.g. by displaying "N/A") so * the user doesn't confuse this case with an empty factorization. */ extern LRFactorization *alloc_LR_factorization(int aNumFactors); extern void free_LR_factorization(LRFactorization *anLRFactorization); /* * Allocates/frees LRFactorizations. */ extern Triangulation *triangulate_punctured_torus_bundle( LRFactorization *anLRFactorization); /* * If the manifold is hyperbolic (i.e. if the number of LR factors * is at least two for an orientable bundle, or at least one for a * nonorientable bundle), triangulates the complement and returns * a pointer to it. Otherwise returns NULL. */ /************************************************************************/ /* */ /* rehydrate_census.c */ /* */ /************************************************************************/ extern void rehydrate_census_manifold( TersestTriangulation tersest, int which_census, int which_manifold, Triangulation **manifold); /* * Rehydrates a census manifold from a tersest description, resolving * any ambiguities in the choice of peripheral curves for the cusps. */ /************************************************************************/ /* */ /* representations.c */ /* */ /************************************************************************/ RepresentationList *find_representations( Triangulation *manifold, int n, PermutationSubgroup range); /* * Finds all transitive representations of a manifold's fundamental * group into Z/n or S(n), for use in constructing n-sheeted covers. * To dispose of the RepresentationList when you're done, use * free_representation_list() below. */ void free_representation_list( RepresentationList *representation_list); /* * Frees a RepresentationList. */ /************************************************************************/ /* */ /* shingling.c */ /* */ /************************************************************************/ extern Shingling *make_shingling(WEPolyhedron *polyhedron, int num_layers); /* * Constructs the shingling defined by the given Dirichlet domain. * Please see the top of shingling.c for detailed documentation. */ extern void free_shingling(Shingling *shingling); /* * Releases the memory occupied by the shingling. */ extern void compute_center_and_radials( Shingle *shingle, O31Matrix position, double scale); /* * Uses shingle->normal along with the given position and scale to * compute shingle->center, single->radialA and shingle->radialB. */ /************************************************************************/ /* */ /* shortest_cusp_basis.c */ /* */ /************************************************************************/ extern Complex cusp_modulus(Complex cusp_shape); /* * Accepts a cusp_shape (longitude/meridian) and returns the cusp modulus. * Loosely speaking, the cusp modulus is defined as * (second shortest translation)/(shortest translation); it is a complex * number z lying in the region |Re(z)| <= 1/2 && |z| >= 1. If z lies * on the boundary of this region, we choose it so that Re(z) >= 0. */ extern void shortest_cusp_basis( Complex cusp_shape, MatrixInt22 basis_change); /* * Accepts a cusp_shape (longitude/meridian) and computes the 2 x 2 integer * matrix which transforms the old basis (u, v) = (meridian, longitude) * to the new basis (u', v') = (shortest, second shortest). */ extern Complex transformed_cusp_shape( Complex cusp_shape, CONST MatrixInt22 basis_change); /* * Accepts a cusp_shape and a basis_change, and computes the shape of the * cusp relative to the transformed basis. The transformed basis may or * may not be the (shortest, second shortest) basis. */ extern void install_shortest_bases( Triangulation *manifold); /* * Installs the (shortest, second shortest) basis on each torus Cusp * of manifold, but does not change the bases on Klein bottle cusps. */ /************************************************************************/ /* */ /* simplify_triangulation.c */ /* */ /************************************************************************/ extern void basic_simplification(Triangulation *manifold); /* * Simplifies the triangulation in a speedy yet effective manner. */ extern void randomize_triangulation(Triangulation *manifold); /* * Randomizes the Triangulation, and then resimplifies it. */ /************************************************************************/ /* */ /* sl2c_matrices.c */ /* */ /************************************************************************/ /* * Most of the functions in sl2c_matrices.c are private to the kernel. * The following has been made available to the UI as well. */ extern Complex sl2c_determinant(CONST SL2CMatrix m); /* * Returns the determinant of m. */ /************************************************************************/ /* */ /* symmetry_group.c */ /* */ /************************************************************************/ extern FuncResult compute_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link, Triangulation **symmetric_triangulation, Boolean *is_full_group); /* * Computes the SymmetryGroup of a closed or cusped manifold. * If the manifold is cusped, also computes the SymmetryGroup of the * corresponding link (defined at the top of symmetry_group_cusped.c). */ extern void free_symmetry_group(SymmetryGroup *symmetry_group); /* * Frees a SymmetryGroup. */ /************************************************************************/ /* */ /* symmetry_group_info.c */ /* */ /************************************************************************/ extern Boolean symmetry_group_is_abelian( SymmetryGroup *symmetry_group, AbelianGroup **abelian_description); /* * If the SymmetryGroup is abelian, sets *abelian_description to point * to the SymmetryGroup's description as an AbelianGroup, and returns TRUE. * Otherwise sets *abelian_description to NULL and returns FALSE. */ extern Boolean symmetry_group_is_dihedral(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is dihedral, FALSE otherwise. */ extern Boolean symmetry_group_is_polyhedral(SymmetryGroup *symmetry_group, Boolean *is_full_group, int *p, int *q, int *r); /* * Returns TRUE if the SymmetryGroup is polyhedral, FALSE otherwise. * If the SymmetryGroup is polyhedral, reports whether it's the full group * (binary polyhedral, not just plain polyhedral), and reports the values * for (p,q,r). The pointers for is_full_group, p, q and r may be NULL * if this information is not desired. */ extern Boolean symmetry_group_is_S5(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is the symmetric group on 5 letters, * FALSE otherwise. */ extern Boolean symmetry_group_is_direct_product(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is a nontrivial, nonabelian direct * product, FALSE otherwise. */ extern SymmetryGroup *get_symmetry_group_factor(SymmetryGroup *symmetry_group, int factor_number); /* * If the SymmetryGroup is a nontrivial, nonabelian direct product, * returns a pointer to factor "factor_number" (factor_number = 0 or 1). * Otherwise returns NULL. This is a pointer to the internal data * structure -- not a copy! -- so please don't free it. */ extern Boolean symmetry_group_is_amphicheiral(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup contains orientation-reversing * elements, FALSE otherwise. Assumes the underlying manifold is oriented. */ extern Boolean symmetry_group_invertible_knot(SymmetryGroup *symmetry_group); /* * Assumes the underlying manifold is oriented and has exactly * one Cusp. Returns TRUE if some Symmetry acts on the Cusp * via the matrix (-1, 0; 0, -1); returns FALSE otherwise. */ extern int symmetry_group_order(SymmetryGroup *symmetry_group); /* * Returns the order of the SymmetryGroup. */ extern int symmetry_group_product(SymmetryGroup *symmetry_group, int i, int j); /* * Returns the product of group elements i and j. We use the * convention that products of symmetries read right to left. * That is, the composition symmetry[i] o symmetry[j] acts by * first doing symmetry[j], then symmetry[i]. */ extern int symmetry_group_order_of_element(SymmetryGroup *symmetry_group, int i); /* * Returns the order of group element i. */ extern IsometryList *get_symmetry_list(SymmetryGroup *symmetry_group); /* * Returns the list of "raw" Isometries comprising a SymmetryGroup. */ extern SymmetryGroup *get_commutator_subgroup(SymmetryGroup *symmetry_group); extern SymmetryGroup *get_abelianization (SymmetryGroup *symmetry_group); /* * Compute the commutator subgroup [G,G] and the abelianization G/[G,G]. * The UI should eventually use free_symmetry_group() to free them. */ extern SymmetryGroup *get_center(SymmetryGroup *symmetry_group); /* * Computes the center of G, which is the subgroup consisting of * elements which commute with all elements in G. * The UI should eventually use free_symmetry_group() to free it. */ extern SymmetryGroupPresentation *get_symmetry_group_presentation( SymmetryGroup *symmetry_group); /* * Returns a presentation for the given SymmetryGroup. * The internal structure of the SymmetryGroupPresentation is private * to the kernel; use the functions below to get information about it. * When you're done with it, use free_symmetry_group_presentation() * to free the storage. */ extern int sg_get_num_generators(SymmetryGroupPresentation *group); /* * Returns the number of generators in the SymmetryGroupPresentation. */ extern int sg_get_num_relations(SymmetryGroupPresentation *group); /* * Returns the number of relations in the SymmetryGroupPresentation. */ extern int sg_get_num_factors( SymmetryGroupPresentation *group, int which_relation); /* * Returns the number of factors in the specified relation. * For example, the relation a^3 * b^-2 * c^5 has three factors. * The parameter which_relation uses 0-based indexing. */ extern void sg_get_factor( SymmetryGroupPresentation *group, int which_relation, int which_factor, int *generator, int *power); /* * Reports the generator and power of the specified factor in the * specified relation. For example, if relation 1 (i.e. the second * relation) is a^3 * b^-2 * c^5, then passing which_relation = 1 and * which_factor = 2 will cause it to report *generator = 2 and * *power = 5. */ extern void free_symmetry_group_presentation(SymmetryGroupPresentation *group); /* * Frees the storage occupied by a SymmetryGroupPresentation. */ /************************************************************************/ /* */ /* terse_triangulation.c */ /* */ /************************************************************************/ extern TerseTriangulation *tri_to_terse(Triangulation *manifold); extern TerseTriangulation *tri_to_canonical_terse( Triangulation *manifold, Boolean respect_orientation); /* * tri_to_terse() accepts a pointer to a Triangulation, computes * a corresponding TerseTriangulation, and returns a pointer to it. * tri_to_canonical_terse() is similar, but chooses the * TerseTriangulation which is "least" among all possible choices * of base Tetrahedron and base Permutation. */ extern Triangulation *terse_to_tri(TerseTriangulation *tt); /* * Accepts a pointer to a TerseTriangulation, expands it to a full * Triangulation, and returns a pointer to it. */ extern void free_terse_triangulation(TerseTriangulation *tt); /* * Releases the memory used to store a TerseTriangulation. */ /************************************************************************/ /* */ /* tersest_triangulation.c */ /* */ /************************************************************************/ extern void terse_to_tersest( TerseTriangulation *terse, TersestTriangulation tersest); /* * Converts a TerseTriangulation to a TersestTriangulation. */ extern void tersest_to_terse( TersestTriangulation tersest, TerseTriangulation **terse); /* * Converts a TersestTriangulation to a TerseTriangulation. * Allocates space for the result. */ extern void tri_to_tersest( Triangulation *manifold, TersestTriangulation tersest); /* * Composes tri_to_terse() and terse_to_tersest(). */ extern void tersest_to_tri( TersestTriangulation tersest, Triangulation **manifold); /* * Composes tersest_to_terse() and terse_to_tri(). */ /************************************************************************/ /* */ /* triangulations.c */ /* */ /************************************************************************/ extern void data_to_triangulation( TriangulationData *data, Triangulation **manifold_ptr); /* * Uses the TriangulationData (defined in triangulation_io.h) to * construct a Triangulation. Sets *manifold_ptr to point to the * Triangulation, or to NULL if it fails. */ extern void triangulation_to_data( Triangulation *manifold, TriangulationData **data_ptr); /* * Allocates the TriangulationData and writes in the data describing * the manifold. Sets *data_ptr to point to the result. The UI * should call free_triangulation_data() when it's done with the * TriangulationData. */ extern void free_triangulation_data(TriangulationData *data); /* * If the UI lets the kernel allocate a TriangulationData structure * (as in a call to triangulation_to_data()), then the UI should * call free_triangulation_data() to release it. * If the UI allocates its own TriangulationData structure (as in * preparing for a call to data_to_triangulation()), then the UI * should release the structure itself. */ extern void free_triangulation(Triangulation *manifold); /* * If manifold != NULL, frees up the storage associated with a * triangulation structure. * If manifold == NULL, does nothing. */ extern void copy_triangulation(Triangulation *source, Triangulation **destination); /* * Makes a copy of the Triangulation *source. */ /************************************************************************/ /* */ /* two_bridge.c */ /* */ /************************************************************************/ extern void two_bridge( Triangulation *manifold, Boolean *is_two_bridge, long int *p, long int *q); /* * Checks whether *manifold is the (conjectured) canonical triangulation * of a 2-bridge knot or link complement. If it is, sets *is_two_bridge * to TRUE and writes the fraction p/q describing the knot or link into * (*p)/(*q). If it's not, sets *is_two_bridge to FALSE and leaves *p * and *q undefined. */ /************************************************************************/ /* */ /* volume.c */ /* */ /************************************************************************/ extern double volume(Triangulation *manifold, int *precision); /* * Computes and returns the volume of the manifold. * If the pointer "precision" is not NULL, estimates the number * of decimal places of accuracy, and places the result in the * variable *precision. */ #ifdef __cplusplus } #endif #endif snappea-3.0d3/SnapPeaKernel/headers/canonize.h0100444000175000017500000000212006742676077017365 0ustar babbab/* * canonize.h * * In canonize_part_1.c, canonize_part_2.c, and canonize_result.c, the sum * of the tilts of two Tetrahedra relative to their common face is * considered to be zero iff it lies between -CONCAVITY_EPSILON and * +CONCAVITY_EPSILON. * * 95/10/20 cusp_neighborhoods.c now includes this file as well, * so it can supress the drawing of faces which serve only to subdivide * the canonical cell decomposition into tetrahedra. * * 97/3/1 The processing of the data for 16-crossing knots led * to several examples which sometimes were reported to have * canonical decompositions with cells other than tetrahedra, * and sometimes not. This led me to suspect that CONCAVITY_EPSILON * was too big, so I changed it from 1e-6 to 1e-7. (This problem * hadn't arisen with snappea 1.3.x because the latter normalized * cusp volumes to 1.0, whereas SnapPea 2.x normalizes them to * (3/16)sqrt(3).) Eventually I may need to adopt a more sophisticated * approach to using CONCAVITY_EPSILON, somehow taking into account * the volume of the cusp. */ #define CONCAVITY_EPSILON 1e-7 snappea-3.0d3/SnapPeaKernel/headers/covers.h0100444000175000017500000001033207204003363017034 0ustar babbab/* * covers.h * * SnapPea constructs an n-sheeted cover of a given manifold as follows. * First it creates n copies of a fundamental domain. For convenience it * uses the fundamental domain defined in choose_generators.c, which is * a union of the triangulation's tetrahedra. It assigns to each generator * (defined in choose_generators.c) a permutation of the n sheets, and * glues the sheets accordingly. The product of the permutations * surrounding an edge class must, of course, be the identity. * Algebraically this is equivalent to finding transitive representations * of the fundamental group into S(n), the symmetric group on n letters. * ("Transitive" means that the corresponding covering space is connected.) * * The algorithm for computing all connected n-sheeted covers of a * given manifold consists of two parts: * * (1) Find all transitive representations of the manifold's fundamental * group into S(n). [cf. representations.c] * * (2) For each representation, construct the corresponding cover. * [cover.c] * * This naive algorithm can, of course, be simplified. For example, * representations which are conjugate by an element of S(n) yield * equivalent covering spaces. The file representations.c describes * these optimizations. */ /* * This file (covers.h) is intended solely for inclusion in SnapPea.h. */ /* * A covering is "regular" iff for any two lifts of a point in the base * manifold, there is a covering transformation taking one to the other. * (An alternative definition is that the cover's fundamental group * projects down to a normal subgroup of the base manifold's fundamental * group. For a proof that the two definitions are equivalent, please * see page 362 of Rolfsen's Knots and Links.) * * All cyclic coverings are regular. */ typedef int CoveringType; enum { unknown_cover, irregular_cover, regular_cover, cyclic_cover }; typedef struct RepresentationIntoSn RepresentationIntoSn; typedef struct { /* * How many face pairs does the fundamental domain * (defined in choose_generators.c) have? */ int num_generators; /* * How many sheets does the covering have? */ int num_sheets; /* * How many cusps (filled or unfilled) does the manifold have? * (For use with primitive_Dehn_image below.) */ int num_cusps; /* * The representations themselves are kept on a NULL-terminated * singly linked list. */ RepresentationIntoSn *list; } RepresentationList; struct RepresentationIntoSn { /* * The permutation corresponding to generator i takes sheet j * of the cover to sheet image[i][j]. * * Note that the size of the image array depends on both the * num_generators and the num_sheets defined in the RepresentationList. */ int **image; /* * The algorithm in construct_cover() in cover.c would like to know * the permutation assigned to each "primitive" Dehn filling curve. * If the Dehn filling coefficients are (a,b), the primitive Dehn * filling curve is defined to be (a/c, b/c), where c = gcd(a,b). * (When the Dehn filling coefficients are relative prime -- as is * always the case for a manifold -- the primitive Dehn filling curve * is just the Dehn filling curve itself, and the assigned permutation * is perforce the identity. The concept of a primitive Dehn filling * curve is useful only for orbifolds.) * * The permutation corresponding to the primitive Dehn filling curve * on cusp i takes sheet j of the cover to sheet Dehn_image[i][j]. * (For unfilled cusps, the identity permutation is given instead.) */ int **primitive_Dehn_image; /* * Is the cover defined by this representation irregular, * regular or cyclic? */ CoveringType covering_type; /* * The RepresentationList keeps RepresentationIntoSn's on * a NULL-terminated singly linked list. */ RepresentationIntoSn *next; }; /* * find_representations() takes a PermutationSubgroup parameter * specifying the subgroup of the symmetric group S(n) into which * the representations are to be found. */ typedef int PermutationSubgroup; enum { permutation_subgroup_Zn, /* finds cyclic covers only */ permutation_subgroup_Sn /* finds all n-fold covers */ /* eventually an option for dihedral covers could be added */ }; snappea-3.0d3/SnapPeaKernel/headers/dual_one_skeleton_curve.h0100444000175000017500000000701006742676077022460 0ustar babbab/* * dual_one_skeleton_curve.h * * This file defines the data structure used to represent * a simple closed curve in the 1-skeleton of a Triangulation's * dual complex. * * The 0-skeleton of the Triangulation's dual complex consists * of one vertex in the interior of each Tetrahedron. * * The 1-skeleton of the Triangulation's dual complex (the * "dual 1-skeleton") consists of one edge transverse to each * face of each Tetrahedron (more precisely, to each pair * of identified faces in the Triangulation). * * To specify a curve in the dual 1-skeleton, we specify the * 2-cells which it intersects in the original Triangulation. * Specifically, for each Tetrahedron we keep four Boolean * flags which say whether the given dual curve intersects * each of the Tetrahedron's four faces. * * Note that a dual curve is described in terms of a specific * Triangulation. If the Triangulation is modified, the * description of the dual curve becomes meaningless. * * The file SnapPea.h contains the "opaque typedef" * * typedef struct DualOneSkeletonCurve DualOneSkeletonCurve; * * which lets the UI declare and pass pointers to DualOneSkeletonCurves * without actually knowing what they are. This file provides the kernel * with the actual definition. */ #ifndef _dual_one_skeleton_curve_ #define _dual_one_skeleton_curve_ #include "SnapPea.h" /* * An array of four Booleans represents the intersection * of a dual curve with the four faces of a given * Tetrahedron. The i-th Boolean is TRUE iff the dual * curve passes through the Tetrahedron's i-th face. */ typedef Boolean DualOneSkeletonCurvePiece[4]; /* * An array of DualOneSkeletonCurvePieces represents * a complete curve in the dual 1-skeleton. The i-th * element in the array describes the curve's intersection * with the Triangulation's i-th Tetrahedron. * * Note that this definition relies on the Tetrahedra * being consecutively indexed, e.g. by the kernel function * number_the_tetrahedra(). The numbering is that of * the indices rather than the position in the doubly- * linked list (normally the two will be the same, but * we don't assume this). */ struct DualOneSkeletonCurve { /* * tet_intersection will contain the address of * an array of n DualOneSkeletonCurvePieces, where n * is the number of Tetrahedra in the Triangulation. * tet_intersection[i][j] will be TRUE iff the dual * curve passes through face j of Tetrahedron i. */ DualOneSkeletonCurvePiece *tet_intersection; /* * Is this curve orientation_reversing or orientation_preserving? */ MatrixParity parity; /* * The length field will contain the complex length of * the geodesic in the homotopy class of the dual curve. * length[complete] and length[filled] give the length * relative to the complete and filled hyperbolic * structures, respectively. */ Complex length[2]; /* * The size field will contain the number * of segments in the curve. */ int size; /* * We'll be working with large numbers of DualOneSkeletonCurves, * many of which will be homotopic to each other, so for efficiency * in sorting them out we'll keep them on a binary tree, keyed by * complex length (i.e. keyed by length.real, with length.imag * considered in case of ties). The next_subtree field is used * locally for tree-handling operations to avoid doing recursions * on the system stack; the latter run the risk of stack/heap * collisions. */ DualOneSkeletonCurve *left_child, *right_child, *next_subtree; }; #endif snappea-3.0d3/SnapPeaKernel/headers/isometry.h0100444000175000017500000001411706742676077017443 0ustar babbab/* * isometry.h * * This file provides the definition of an IsometryList. It is private * to the kernel. * * An isometry is a map which takes the Tetrahedra of one Triangulation * to the Tetrahedra of another, preserving the gluings. Strictly speaking * this is a combinatorial equivalence of the two Triangulations, but * the Triangulations are typically the canonical ones for their respective * manifolds, so the notions of combinatorial equivalence and isometry * coincide. * * The two Triangulations need not be distinct -- if they're the same * then an "isometry" is what one normally calls a symmetry. * * An isometry may be represented in two ways: internally within the * Triangulation data structure, and externally using the Isometry data * structure. * * (1) Internal representation. The Triangulation data structure may * represent at most one isometry at a time. Each Tetrahedron contains * the fields * Tetrahedron *image; * Permutation map; * * The image field contains a pointer to the image of the given * Tetrahedron under the isometry. The map field says how the * vertices of the given Tetrahedron are taken to the vertices * of its image. E.g. if the Permutation is 2013 (meaning 3210 -> 2013) * then vertex 0 of the given Tetrahedron maps to vertex 3 of the image * Tetrahedron, vertex 1 maps to vertex 1 of the image, vertex 2 maps * to vertex 0, and vertex 3 maps to vertex 2. * * (2) External representation. The Isometry data structure is similar * to the internal representation described in the preceding paragraph, * only it refers to Tetrahedra by indices rather than pointers. * Each Tetrahedron in each manifold is numbered by setting its index * field equal to the Tetrahedron's position in its Triangulation's * doubly-linked list (the indices run from 0 through n-1). * * The images and maps are stored in the tet_image and tet_map arrays * in the Isometry data structure. The elements of each array are * implicitly indexed by the indices of the Tetrahedra in the domain * manifold. For example, if * * my_isometry->tet_image = {1, 0, 2} * and * my_isometry->tet_map = {3102, 3201, 1023}, * * then Tetrahedron #0 of the domain Triangulation will map to * Tetrahedron #1 of the image Triangulation via the Permutation * 3102; Tetrahedron #1 will map to Tetrahedron #0 via 3201; * and Tetrahedron #2 will map to Tetrahedron #2 via 1023. * * In addition, the Isometry data structure records the action * of the Isometry on the Cusps, as explained in the Isometry * definition below. * * The IsometryList data structure contains an array of pointers to * Isometries, an integer saying how many there are, and an integer * say how many Tetrahedra each Triangulation has. The file SnapPea.h * contains the "opaque typedef" * * typedef struct IsometryList IsometryList; * * which lets the UI declare and pass pointers to IsometryLists without * actually knowing what they are. This file provides the kernel with * the actual definition. * * The Isometry data structure also contains a "next" field, which the * function compute_cusped_isometries() uses internally while assembling * its IsometryList. Other functions ignore the "next" field. */ #ifndef _isometry_ #define _isometry_ #include "kernel.h" typedef struct Isometry Isometry; struct Isometry { /* * How many Tetrahedra and Cusps are there? */ int num_tetrahedra, num_cusps; /* * The Isometry sends Tetrahedron n in the domain * manifold to Tetrahedron tet_image[n] in the image manifold. */ int *tet_image; /* * The Isometry sends vertex v of Tetrahedron n in the * domain manifold to vertex EVALUATE(tet_map[n], v) of * Tetrahedron tet_image[n] in the image manifold. */ Permutation *tet_map; /* * The Isometry sends Cusp k of the domain manifold * to Cusp cusp_image[k] in the image manifold. */ int *cusp_image; /* * The matrix cusp_map[k][][] takes the peripheral curves * of Cusp k in the domain manifold to the peripheral curves * of Cusp cusp_image[k] in the image manifold. That is, * the image of a meridian of Cusp k in the domain is * cusp_matrix[k][M][M] meridians plus cusp_matrix[k][L][M] * longitudes in the image, and similarly for the image of * a longitude. * * Note that the cusp_map matrix is defined even for * nonorientable Cusps, since the peripheral curves are stored * in the Cusp's orientation double cover. For nonorientable * cusps, cusp_matrix[k][L][M] will be a diagonal matrix. * The entry cusp_matrix[k][L][L] tells whether the direction * of the cusp is reversed. Det(cusp_matrix[k]) tells whether * the Isometry acts in an orientation-preserving or orientation- * reversing way on the Cusp's orientation double cover (Question: * what is the significance of this information, if any?). * * This scheme applies only to the real cusps -- finite vertices * (whose cusp indices are negative) are ignored. */ MatrixInt22 *cusp_map; /* * Does this Isometry from one cusped manifold to another extend * to the closed manifolds obtained by meridional Dehn fillings? * If the cusp manifolds are link complements (in any manifolds, * not necessarily 3-spheres) this is equivalent to asking whether * the Isometry extends to a link homeomorphism. */ Boolean extends_to_link; /* * This "next" field is used internally in isometry_cusped.c * while assembling the IsometryList. (The Isometries are * temporarily stored on a linked list, then eventually * transferred to an array of pointers for external use.) */ Isometry *next; }; struct IsometryList { /* * How many Isometries are on the list? */ int num_isometries; /* * isometry[n] is a pointer to the n-th Isometry * on the list. (The "isometry" field itself * contains a pointer to an array. Each element of * the array is a pointer to an Isometry.) * * If there are no isometries (num_isometries == 0) * then the isometry field is set to NULL. That is, * we don't try to allocate an array of zero pointers. */ Isometry **isometry; }; #endif snappea-3.0d3/SnapPeaKernel/headers/kernel.h0100444000175000017500000000172207001152236017015 0ustar babbab/* * kernel.h * * This file #includes all header files needed for the kernel. * It should be #included in all kernel .c files, but nowhere else. */ #ifndef _kernel_ #define _kernel_ #include "SnapPea.h" #include #include #include #include /* Some C implementations define DBL_MAX, DBL_MIN, FLT_MAX, */ /* and FLT_MIN in limits.h as well as float.h, leading to */ /* "redefinition" warnings. If this is the case on your system, */ /* uncomment the following lines and insert them between */ /* "#include " and "#include " above. */ /* */ /* #undef DBL_MAX */ /* #undef DBL_MIN */ /* #undef FLT_MAX */ /* #undef FLT_MIN */ #include "kernel_typedefs.h" #include "triangulation.h" #include "positioned_tet.h" #include "isometry.h" #include "symmetry_group.h" #include "dual_one_skeleton_curve.h" #include "terse_triangulation.h" #include "kernel_prototypes.h" #include "tables.h" #endif snappea-3.0d3/SnapPeaKernel/headers/kernel_prototypes.h0100444000175000017500000011452107053771270021343 0ustar babbab/* * kernel_prototypes.h * * This file contains prototypes for functions which are externally * available within the kernel, but not available to the user-interface. */ #ifndef _kernel_prototypes_ #define _kernel_prototypes_ #include "SnapPea.h" #include "positioned_tet.h" /************************************************************************/ /* */ /* chern_simons.c */ /* */ /************************************************************************/ extern void compute_CS_value_from_fudge(Triangulation *manifold); extern void compute_CS_fudge_from_value(Triangulation *manifold); /* * Compute the Chern-Simons value in terms of the fudge factor, and * vice-versa. Please see chern_simons.c for details. */ /************************************************************************/ /* */ /* choose_generators.c */ /* */ /************************************************************************/ extern void choose_generators( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin); /* * Chooses a set of generators for the fundamental group of the * Triangulation *manifold. Various functions which use generators all * call choose_generators(), so they are sure to be using the same * generator set, and their results are directly comparable. * If compute_corners is TRUE, choose_generators() computes the location * on the sphere at infinity of each ideal vertex of each Tetrahedron, * using the hyperbolic structure of the Dehn filled manifold. * If centroid_at_origin is TRUE, the initial tetrahedron is positioned * with its centroid at the origin; otherwise the initial tetrahedron * is positioned with its vertices at {0, 1, infinity, z}. * If compute_corners is FALSE, centroid_at_origin is ignored. */ void compute_fourth_corner( Complex corner[4], VertexIndex missing_corner, Orientation orientation, ComplexWithLog cwl[3]); /* * Given the location on the sphere at infinity of three of a Tetrahedron's * ideal vertices, compute the location of the fourth. */ /************************************************************************/ /* */ /* close_cusps.c */ /* */ /************************************************************************/ extern void close_cusps(Triangulation *manifold, Boolean fill_cusp[]); /* * Permanently closes the cusps of *manifold for which fill_cusp[cusp->index] * is TRUE. Assumes *manifold is triangulated as in subdivide(). */ /************************************************************************/ /* */ /* complex.c */ /* */ /************************************************************************/ extern Complex Zero, One, Two, Four, MinusOne, I, TwoPiI, Infinity; /************************************************************************/ /* */ /* core_geodesics.c */ /* */ /************************************************************************/ extern void compute_core_geodesic( Cusp *cusp, int *singularity_index, Complex length[2]); /* * This function is similar to the function core_geodesic() defined in * SnapPea.h, only it accepts a Cusp pointer as input and returns * the complex length relative to the ultimate and penultimate * hyperbolic structures (rather than reporting a precision). */ /************************************************************************/ /* */ /* cusps.c */ /* */ /************************************************************************/ extern void create_cusps(Triangulation *manifold); /* * Creates Cusp data structures for a Triangulation with valid * neighbor and gluing fields (and perhaps nothing else). */ extern void error_check_for_create_cusps(Triangulation *manifold); /* * Checks that no Cusps are present, and that all tet->cusp[] * fields are NULL. */ extern void create_one_cusp(Triangulation *manifold, Tetrahedron *tet, Boolean is_finite, VertexIndex v, int cusp_index); /* * Creates a single Cusp, incident to the given Tetrahedron at * the given ideal vertex. Assumes ideal vertices which haven't * been assigned to Cusps have tetrahedron->cusp[vertex] == NULL. */ extern void create_fake_cusps(Triangulation *manifold); /* * Creates Cusp data structures for the "fake cusps" corresponding to * finite vertices. */ extern void count_cusps(Triangulation *manifold); /* * counts the Cusps of each CuspTopology, and sets manifold->num_cusps, * manifold->num_or_cusps and manifold->num_nonor_cusps. */ extern Boolean mark_fake_cusps(Triangulation *manifold); /* * Distinguishes real cusps from fake cusps ( = finite vertices) by * computing the Euler characteristic. Sets is_finite to TRUE for * fake cusps, and renumbers all cusps so that real cusps have * consecutive nonnegative indices beginning at 0 and fake cusps * have consecutive negative indices beginning at -1. */ /************************************************************************/ /* */ /* cusp_cross_sections.c */ /* */ /************************************************************************/ extern void allocate_cross_sections(Triangulation *manifold); /* * Allocates a VertexCrossSections structure for each tet->cross_section * in manifold. */ extern void free_cross_sections(Triangulation *manifold); /* * Frees the VertexCrossSections structure and resets the * tet->cross_section pointer to NULL for each tet in manifold. */ extern void compute_cross_sections(Triangulation *manifold); /* * Sets the (already allocated) VertexCrossSections to correspond to * cusp cross sections bounding equal volumes. In general these cross * sections will NOT represent maximal disjoint horoball neighborhoods * of the cusps. */ extern void compute_tilts(Triangulation *manifold); /* * Applies the Tilt Theorem to compute the tilts from the * VertexCrossSections. Assumes the TetShapes are correct. */ extern void compute_three_edge_lengths( Tetrahedron *tet, VertexIndex v, FaceIndex f, double known_length); /* * Sets tet->cross_section->edge_length[v][f] to known_length, computes * the remaining two edge_lengths at vertex v in terms of it, and sets * the has_been_set flag to TRUE. */ extern void compute_tilts_for_one_tet(Tetrahedron *tet); /* * Applies the Tilt Theorem to compute the tilts from the * VertexCrossSections. Assumes the TetShapes are correct. */ /************************************************************************/ /* */ /* cusp_neighborhoods.c */ /* */ /************************************************************************/ extern void cn_find_third_corner( Tetrahedron *tet, Orientation h, VertexIndex v, FaceIndex f0, FaceIndex f1, FaceIndex f2); /* * Given the locations of corners f0 and f1 on the given triangle, * compute and record the location of corner f2. * This function is in spirit local to cusp_neighborhoods.c, but we * want to make it available to the 2-3 and 3-2 simplifications * in simplify_triangulation.c. */ /************************************************************************/ /* */ /* cusp_shapes.c */ /* */ /************************************************************************/ extern void compute_cusp_shapes(Triangulation *manifold, FillingStatus which_structure); /* * Computes the shape of each unfilled cusp and stores the result in * cusp->cusp_shape[which_structure]. (which_structure = initial or * current) Stores the number of decimal places of accuracy in * cusp->shape_precision[which_structure]. */ /************************************************************************/ /* */ /* Dehn_coefficients.c */ /* */ /************************************************************************/ extern Boolean all_Dehn_coefficients_are_integers(Triangulation *manifold); /* * Returns FALSE if some cusp has noninteger Dehn filling coefficients. * Returns TRUE if each cusp is either unfilled, or has integer * Dehn filling coefficients. */ extern Boolean Dehn_coefficients_are_integers(Cusp *cusp); /* * Returns FALSE if the Dehn filling coefficients of *cusp are * nonintegers. * Returns TRUE if *cusp is unfilled, or has integer Dehn filling * coefficients. */ extern Boolean all_Dehn_coefficients_are_relatively_prime_integers( Triangulation *manifold); extern Boolean Dehn_coefficients_are_relatively_prime_integers(Cusp *cusp); /* * Same as above, but integer coefficients must be relatively prime. */ extern Boolean all_cusps_are_complete(Triangulation *manifold); extern Boolean all_cusps_are_filled(Triangulation *manifold); /* * Returns TRUE if all cusps are complete (resp. filled), FALSE otherwise. */ /************************************************************************/ /* */ /* direct_product.c */ /* */ /************************************************************************/ extern Boolean is_group_direct_product(SymmetryGroup *the_group); /* * Checks whether the_group is a nonabelian, nontrivial direct product, * and sets it is_direct_product and factor[] fields accordingly. */ /************************************************************************/ /* */ /* edge_classes.c */ /* */ /************************************************************************/ extern void create_edge_classes(Triangulation *manifold); /* * Adds EdgeClasses to a partially constructed manifold which does not * yet have them. */ extern void replace_edge_classes(Triangulation *manifold); /* * Removes all EdgeClasses from a manifold and addes fresh ones. */ extern void orient_edge_classes(Triangulation *manifold); /* * Orients a neighborhood of each EdgeClass, and fills in the fields * tet->edge_class[] to described how the EdgeClass views each * incident Tetrahedron. */ /************************************************************************/ /* */ /* elements_generate_group.c */ /* */ /************************************************************************/ extern Boolean elements_generate_group(SymmetryGroup *the_group, int num_possible_generators, int possible_generators[]); /* * The array possible_generators[] contains num_possible_generators * elements of the_group. Do these elements generate the_group? */ /************************************************************************/ /* */ /* find_cusp.c */ /* */ /************************************************************************/ extern Cusp *find_cusp(Triangulation *manifold, int cusp_index); /* * Converts a cusp_index to a Cusp pointer. */ /************************************************************************/ /* */ /* finite_vertices.c */ /* */ /************************************************************************/ extern void remove_finite_vertices(Triangulation *manifold); /* * Removes finite vertices from the manifold. */ /************************************************************************/ /* */ /* gcd.c */ /* */ /************************************************************************/ extern long int gcd(long int a, long int b); /* * Returns the greatest common divisor of two nonnegative long integers, * at least one of which is nonzero. */ extern long int euclidean_algorithm(long int m, long int n, long int *a, long int *b); /* * Returns the greatest common divisor of two long integers m and n, * and also finds long integers a and b such that am + bn = gcd(m,n). * The integers m and n may be negative, but may not both be zero. */ extern long int Zq_inverse(long int p, long int q); /* * Returns the inverse of p in the ring Z/q. Assumes p and q are * relatively prime integers satisfying 0 < p < q. */ /************************************************************************/ /* */ /* gluing_equations.c */ /* */ /************************************************************************/ extern void compute_gluing_equations(Triangulation *manifold); /* * compute_gluing_equations() computes the complex gluing equations * if Triangulation *manifold is orientable, and the real gluing * equations if it's nonorientable. It assumes that space for the * appropriate set of equations has already been assigned to the cusps * and edges. */ /************************************************************************/ /* */ /* holonomy.c */ /* */ /************************************************************************/ extern void compute_holonomies(Triangulation *manifold); /* * Computes the log of the holonomy of each cusp based on the current * shapes of the tetrahedra, and stores the results into the * holonomy[ultimate][] field of the Cusp data structure. The previous * contents of that field are transferred to holonomy[penultimate][]. * * compute_holonomies() is called whenever a hyperbolic structure is * computed. Therefore if you have a manifold with a hyperbolic structure * you may assume correct values of the holonomy are already in place. */ extern void compute_the_holonomies( Triangulation *manifold, Ultimateness which_iteration); /* * Computes the holonomies for either the ultimate or penultimate * solution, according to the value of which_iteration. */ extern void compute_edge_angle_sums(Triangulation *manifold); /* * For each EdgeClass, computes the sum of the logs of the complex * edge parameters and records the result in the edge_angle_sum field. * compute_edge_angle_sums() is used in finding the hyperbolic structure; * once the hyperbolic structure is found, each edge_angle_sum will of * course be 2 pi i. */ /************************************************************************/ /* */ /* hyperbolic_structure.c */ /* */ /************************************************************************/ extern void remove_hyperbolic_structures(Triangulation *manifold); /* * Frees the TetShapes (if any) pointed to by each tet->shape[] and sets * manifold->solution_type[complete] and manifold->solution_type[filled] * to not_attempted. */ extern void initialize_tet_shapes(Triangulation *manifold); /* * Sets all Tetrahedra to be regular ideal tetrahedra. Allocates the * TetShapes if necessary. Clears the shape_histories if necessary. */ extern void polish_hyperbolic_structures(Triangulation *manifold); /* * Attempts to increase the accuracy of both the complete and the Dehn * filled hyperbolic structures already present in *manifold. It's * designed to be called following retriangulation operations which * diminish the accuracy of the TetShapes. */ extern void copy_solution(Triangulation *manifold, FillingStatus source, FillingStatus dest); extern void complete_all_cusps(Triangulation *manifold); /* * These are low-level routines used mainly within hyperbolic_structure.c, * but also to transfer the Chern-Simons invariant in drilling.c. */ /************************************************************************/ /* */ /* identify_solution_type.c */ /* */ /************************************************************************/ extern void identify_solution_type(Triangulation *manifold); /* * Identifies the type of solution contained in the *tet->shape[filled] * structures of the Tetrahedra of Triangulation *manifold, and writes * the result to manifold->solution_type[filled]. Possible values are * given by the SolutionType enum. */ extern Boolean solution_is_degenerate(Triangulation *manifold); /* * Returns TRUE if any TetShape is close to {0, 1, infinity}. * Otherwise returns FALSE. */ extern Boolean tetrahedron_is_geometric(Tetrahedron *tet); /* * Returns TRUE if all tetrahedra are geometric; returns FALSE otherwise. * A tetrahedron is geometric iff all dihedral angles lie in the * range [-FLAT_EPSILON, pi + FLAT_EPSILON]. */ /************************************************************************/ /* */ /* intersection_numbers.c */ /* */ /************************************************************************/ extern void compute_intersection_numbers(Triangulation *manifold); /* * Computes the intersection numbers of the curves stored * in the scratch_curve[][][][][] fields of the Tetrahedra, and * writes the results to the intersection_number[][] fields of * the Cusps. Please see intersection_numbers.c for details. */ extern void copy_curves_to_scratch( Triangulation *manifold, int which_set, Boolean double_copy_on_tori); /* * Copies the current peripheral curves to the scratch_curves[which_set] * fields of the manifold's Tetrahedra. If double_copy_on_tori is TRUE, * it copies peripheral curves on orientable cusps to both sheets of * the Cusps' orientation double covers. */ /************************************************************************/ /* */ /* isometry_closed.c */ /* */ /************************************************************************/ extern FuncResult compute_closed_isometry( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric); /* * If it determines with absolute rigor that manifold0 and manifold1 are * isometric, sets *are_isometric to TRUE and returns func_OK. * If it determines with absolute rigor that manifold0 and manifold1 are * nonhomeomorphic, sets *are_isometric to FALSE and returns func_OK. * If it fails to decide, returns func_failed. */ /************************************************************************/ /* */ /* isometry_cusped.c */ /* */ /************************************************************************/ extern FuncResult compute_cusped_isometries( Triangulation *manifold0, Triangulation *manifold1, IsometryList **isometry_list, IsometryList **isometry_list_of_links); /* * Finds all isometries from manifold0 to manifold1 (ignoring Dehn * fillings), stores them in an IsometryList data structure, and sets * isometry_list to point to it. If isometry_list_of_links is not NULL, * it copies those Isometries which extend to Isometries of the associated * links (i.e. those which take meridians to plus-or-minus meridians) * onto a separate list, and stores its address in *isometry_list_of_links. * If manifold0 == manifold1, this function is finding all symmetries. */ /************************************************************************/ /* */ /* Moebius_transformations.c */ /* */ /************************************************************************/ extern MoebiusTransformation Moebius_identity; extern void Moebius_copy( MoebiusTransformation *dest, MoebiusTransformation *source); extern void Moebius_invert( MoebiusTransformation *mt, MoebiusTransformation *mt_inverse); extern void Moebius_product(MoebiusTransformation *a, MoebiusTransformation *b, MoebiusTransformation *product); /* * These functions do what you would expect. */ /************************************************************************/ /* */ /* my_malloc.c */ /* */ /************************************************************************/ extern void *my_malloc(size_t bytes); /* * Calls malloc() to request a block of memory of size "bytes". * If successful, returns a pointer to the memory. * If unsuccessful, calls uAcknowledge() to notify the user, then exits. */ extern void my_free(void *ptr); /* * Calls free() to deallocate the block of memory pointed to by ptr. */ extern int malloc_calls(void); /* * Returns the number of calls to my_malloc() minus the number of * calls to my_free(), for use in debugging. */ /************************************************************************/ /* */ /* normal_surface_construction.c */ /* */ /************************************************************************/ extern void recognize_embedded_surface( Triangulation *manifold, Boolean *connected, Boolean *orientable, Boolean *two_sided, int *Euler_characteristic); /* * Reports the connectedness, orientability, two-sidedness and Euler * characteristic of the normal surface described in the parallel_edge, * num_squares and num_triangles fields of the manifold's Tetrahedra. * The present implementation assumes the manifold has no filled cusps. */ /************************************************************************/ /* */ /* o31_matrices.c */ /* */ /************************************************************************/ extern O31Matrix O31_identity; extern void o31_copy(O31Matrix dest, O31Matrix source); extern void o31_invert(O31Matrix m, O31Matrix m_inverse); extern FuncResult gl4R_invert(GL4RMatrix m, GL4RMatrix m_inverse); extern void o31_product(O31Matrix a, O31Matrix b, O31Matrix product); extern Boolean o31_equal(O31Matrix a, O31Matrix b, double epsilon); extern double o31_deviation(O31Matrix m); extern void o31_GramSchmidt(O31Matrix m); extern void o31_conjugate(O31Matrix m, O31Matrix t, O31Matrix Tmt); extern double o31_inner_product(O31Vector u, O31Vector v); extern void o31_matrix_times_vector(O31Matrix m, O31Vector v, O31Vector product); extern void o31_constant_times_vector(double r, O31Vector v, O31Vector product); extern void o31_copy_vector(O31Vector dest, O31Vector source); extern void o31_vector_sum(O31Vector a, O31Vector b, O31Vector sum); extern void o31_vector_diff(O31Vector a, O31Vector b, O31Vector diff); /* * These functions all do what you would expect. * o31_conjugate() replaces m with (t^-1) m t. */ /************************************************************************/ /* */ /* orient.c */ /* */ /************************************************************************/ extern void orient(Triangulation *manifold); /* * Attempts to consistently orient the Tetrahedra of the * Triangulation *manifold. Sets manifold->orientability to * oriented_manifold or nonorientable_manifold, as appropriate. */ extern void extend_orientation( Triangulation *manifold, Tetrahedron *initial_tet); /* * Extends the orientation of the given initial_tet to the entire manifold. */ extern void fix_peripheral_orientations(Triangulation *manifold); /* * Makes sure each {meridian, longitude} pairs obeys the right-hand rule. * Should be called only for orientable manifolds, typically following * a call to orient(). */ /************************************************************************/ /* */ /* peripheral_curves.c */ /* */ /************************************************************************/ extern void peripheral_curves(Triangulation *manifold); /* * Puts a meridian and longitude on each cusp, and records each cusp's * CuspTopology in the field cusp->topology. If the manifold is * oriented, the meridian and longitude adhere to the usual * orientation convention (see peripheral_curves.c for details). */ extern void peripheral_curves_as_needed(Triangulation *manifold); /* * Like peripheral_curves(), but puts a meridian and longitude * only onto cusps which don't already have them. Pre-exisiting * meridians and longitudes are left untouched. */ /************************************************************************/ /* */ /* polyhedral_group.c */ /* */ /************************************************************************/ extern Boolean is_group_polyhedral(SymmetryGroup *the_group); /* * Checks whether the_group is a polyhedral group (i.e. (binary) dihedral, * tetrahedral, octahedral or icosahedral), and fills in the_group's * is_polyhedral, is_full_group, p, q and r fields accordingly. Assumes * the_group's is_dihedral field has already been set (but this * restriction could be eliminated -- see the documentation at the top * of triangle_group.c). */ /************************************************************************/ /* */ /* positioned_tet.c */ /* */ /************************************************************************/ extern void veer_left(PositionedTet *ptet); extern void veer_right(PositionedTet *ptet); /* * Accepts a PositionedTet and replaces it with the neighboring * tetrahedron incident to the left (resp. right) face. The positioning * is as if you rotated the old tetrahedron onto the new one about the * axis defined by the edge of the old tetrahedron lying between * near_face and left_face (resp. right_face). */ extern void veer_backwards(PositionedTet *ptet); /* * Accepts a PositionedTet and replaces it with the neighboring * tetrahedron incident to the near_face. The positioning * is as if you rotated the old tetrahedron onto the new one * about an axis running up the center of the near_face from * the bottom_face to the ideal vertex opposite the bottom_face. */ extern Boolean same_positioned_tet(PositionedTet *ptet0, PositionedTet *ptet1); /* * Returns TRUE if the two PositionedTets are equal, FALSE otherwise. */ extern void set_left_edge(EdgeClass *edge, PositionedTet *ptet); /* * Fills in the fields of *ptet so as to position the EdgeClass *edge * between the near_face and the left_face of *ptet. */ /************************************************************************/ /* */ /* precision.c */ /* */ /************************************************************************/ extern int decimal_places_of_accuracy(double x, double y); extern int complex_decimal_places_of_accuracy(Complex x, Complex y); /* * Returns the number of decimal places which x and y have in * common. Typically x and y will be two estimates of the same * computed quantity (e.g. the volume of a manifold), and * decimal_places_of_accuracy() will be used to tell the user * interface how many decimal places should be printed. */ /************************************************************************/ /* */ /* simplify_triangulation.c */ /* */ /************************************************************************/ /* * The high-level functions basic_simplification() and * randomize_triangulation() are declared in SnapPea.h. */ extern FuncResult cancel_tetrahedra(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); extern FuncResult three_to_two(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); extern FuncResult two_to_three(Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr); extern void one_to_four(Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index); /* * Low-level functions for * * cancelling two Tetrahedra which share a common edge of order 2, * * replacing three Tetrahedra surrounding a common edge with * two Tetrahedra sharing a common face * * replacing two Tetrahedra sharing a common face with * three Tetrahedra surrounding a common edge * * replacing a Tetrahedron with four Tetrahedra meeting a point. * * If an operation cannot be performed because of a topological or geometric * obstruction, the function does nothing and returns func_failed. * Otherwise, it performs the operation and returns func_OK. * * The function one_to_four() will always succeed, and therefore returns void. * It introduces a finite vertex at the center of the Tetrahedron, and therefore * cannot be used when a hyperbolic structure is present. * * The three_to_two(), two_to_three() and one_to_four() operations each correspond * to a projection of a 4-simplex. * * For further details, see the documentation in simplify_triangulation.c, * both at the top of file and immediately preceding each function. */ /************************************************************************/ /* */ /* sl2c_matrices.c */ /* */ /************************************************************************/ extern void sl2c_copy(SL2CMatrix dest, CONST SL2CMatrix source); extern void sl2c_invert(CONST SL2CMatrix a, SL2CMatrix inverse); extern void sl2c_complex_conjugate(CONST SL2CMatrix a, SL2CMatrix conjugate); extern void sl2c_product(CONST SL2CMatrix a, CONST SL2CMatrix b, SL2CMatrix product); extern void sl2c_adjoint(CONST SL2CMatrix a, SL2CMatrix adjoint); extern void sl2c_normalize(SL2CMatrix a); extern Boolean sl2c_matrix_is_real(CONST SL2CMatrix a); /************************************************************************/ /* */ /* solve_equations.c */ /* */ /************************************************************************/ extern FuncResult solve_complex_equations(Complex **complex_equations, int num_rows, int num_columns, Complex *solution); extern FuncResult solve_real_equations(double **real_equations, int num_rows, int num_columns, double *solution); /* * These functions solve num_rows linear equations in num_columns * variables. For more information, see solve_equations.c. */ /************************************************************************/ /* */ /* subdivide.c */ /* */ /************************************************************************/ extern Triangulation *subdivide(Triangulation *manifold, char *new_name); /* * Returns a pointer to a subdivision of *manifold. See subdivide.c for * more details. subdivide() does not change *manifold in any way. */ /************************************************************************/ /* */ /* symmetric_group.c */ /* */ /************************************************************************/ extern Boolean is_group_S5(SymmetryGroup *the_group); /* * Is the_group S5? */ /************************************************************************/ /* */ /* symmetry_group_cusped.c */ /* */ /************************************************************************/ extern FuncResult compute_cusped_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link); /* * Computes the SymmetryGroup of a cusped manifold, and also the * SymmetryGroup of the corresponding link (defined at the top of * symmetry_group_cusped.c). */ extern void compute_orders_of_elements(SymmetryGroup *the_group); /* * Assumes the_group->order and the_group->product[][] are already * in place, and computes the_group->order_of_element[]. */ extern void compute_inverses(SymmetryGroup *the_group); /* * Assumes the_group->order and the_group->product[][] are already * in place, and computes the_group->inverse[]. */ extern void recognize_group(SymmetryGroup *the_group); /* * Attempts to recognize the_group as abelian, cyclic, dihedral, * polyhedral or a direct product. */ /************************************************************************/ /* */ /* symmetry_group_closed.c */ /* */ /************************************************************************/ extern FuncResult compute_closed_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group); /* * Attempts to compute the symmetry group of a closed manifold. * Also provides a symmetry Dehn filling description. */ /************************************************************************/ /* */ /* terse_triangulation.c */ /* */ /************************************************************************/ extern TerseTriangulation *alloc_terse(int num_tetrahedra); /* * Allocates a TerseTriangulation. */ extern TerseTriangulation *tri_to_terse_with_base( Triangulation *manifold, Tetrahedron *base_tetrahedron, Permutation base_permutation); /* * Similar to tri_to_terse(), but allows an explicit choice * of base tetrahedron. */ /************************************************************************/ /* */ /* tet_shapes.c */ /* */ /************************************************************************/ extern void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1, Tetrahedron *tet2, EdgeIndex e2); /* * Add the angles of edge e0 of tet0 and edge e1 of tet1 and writes the * results to edge e2 of tet2. Accounts for edge_orientations. The * EdgeIndices should be in the range 0-5, not 0-2. Chooses arguments * in the range [(-1/2) pi, (3/2) pi], regardless of the angles of the * summands. */ extern Boolean angles_sum_to_zero(Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1); /* * angles_sum_to_zero() returns TRUE iff one of the angles * (shape[complete]->cwl[ultimate] or shape[filled]->cwl[ultimate]) * at edge e0 of tet0 cancels the corresponding angle at edge e1 * of tet1 (mod 2 pi). Accounts for edge_orientations. */ extern void compute_remaining_angles(Tetrahedron *tet, EdgeIndex e); /* * Assumes the angle at edge e is correct, and computes the remaining * angles in terms of it. Chooses arguments in the range [(-1/2) pi, * (3/2) pi]. */ /************************************************************************/ /* */ /* tidy_peripheral_curves.c */ /* */ /************************************************************************/ extern void tidy_peripheral_curves(Triangulation *manifold); /* * Replaces the existing peripheral curves on *manifold with an * equivalent set which is efficient and contains no trivial loops. */ /************************************************************************/ /* */ /* transcendentals.c */ /* */ /************************************************************************/ extern double safe_acos(double x); extern double safe_asin(double x); extern double safe_sqrt(double x); /* * These are like the usual acos(), asin() and sqrt(), * except that they round almost-legal values to legal ones. * E.g. safe_acos(1.00000001) = acos(1.0) = 0.0, not NaN. */ extern double arcsinh(double x); extern double arccosh(double x); /* * The inverse hyperbolic sine and cosine, which the standard ANSI * libraries lack. [Some but not all platforms now include asinh() * and acosh(), so I've renamed my own implementations arcsinh() * and arccosh() to avoid conflicts. JRW 2000/02/20] */ /************************************************************************/ /* */ /* triangulations.c */ /* */ /************************************************************************/ extern void initialize_triangulation(Triangulation *manifold); /* * Initializes the fields of *manifold to correspond to the * empty Triangulation. */ extern void initialize_tetrahedron(Tetrahedron *tet); extern void initialize_cusp(Cusp *cusp); extern void initialize_edge_class(EdgeClass *edge_class); /* * Initialize the fields of Tetrahedra, Cusps and EdgeClasses to the * most benign values possible. */ extern void free_tetrahedron(Tetrahedron *tet); /* * Frees a Tetrahedron and all attached data structures, but does NOT * remove the Tetrahedron from any doubly linked list it may be on. */ extern void clear_shape_history(Tetrahedron *tet); extern void copy_shape_history(ShapeInversion *source, ShapeInversion **dest); extern void clear_one_shape_history(Tetrahedron *tet, FillingStatus which_history); /* * What you'd expect. See triangulation.c for details. */ extern FuncResult check_Euler_characteristic_of_boundary(Triangulation *manifold); /* * Returns func_OK if the Euler characteristic of the total boundary of * the manifold is zero. Otherwise returns func_failed. */ extern void number_the_tetrahedra(Triangulation *manifold); /* * Sets each Tetrahedron's index field equal to its position in * the linked list. Indices range from 0 to (num_tetrahedra - 1). */ extern void number_the_edge_classes(Triangulation *manifold); /* * Sets each EdgeClass's index field equal to its position in the * linked list. Indices range from 0 to ((number of EdgeClasses) - 1). */ extern Permutation compose_permutations(Permutation p1, Permutation p0); /* * Returns the composition of two permutations. Permutations are * composed right-to-left: the composition p1 o p0 is what you get * by first doing p0, then p1. */ /************************************************************************/ /* */ /* update_shapes.c */ /* */ /************************************************************************/ extern void update_shapes(Triangulation *manifold, Complex *delta); /* * Updates the shapes of the tetrahedra in *manifold by the amounts * specified in the array delta. If necessary, delta is first scaled * so that no delta[i].real or delta[i].imag exceeds the limit * specified by the constant allowable_change (see update_shapes.c * for more details). The entries in delta are interpreted relative * to the coordinate system given by the coordinate_system field of * each Tetrahedron, and the indexing of delta is assumed to correspond * to the index field of each tetrahedron. */ /************************************************************************/ /* */ /* volume.c */ /* */ /************************************************************************/ extern double birectangular_tetrahedron_volume( O31Vector a, O31Vector b, O31Vector c, O31Vector d); /* * Computes the volume of a birectangular tetrahedron using Vinberg's * article. Please see volume.c for a citation to Vinberg's article. */ #endif snappea-3.0d3/SnapPeaKernel/headers/kernel_typedefs.h0100444000175000017500000003736007204006527020735 0ustar babbab/* * kernel_typedefs.h * * This file contains #defines, typedefs and enums common to * many parts of the SnapPea kernel (but hidden from the UI). * Typedefs for more complicated data structures are found in * separate files (e.g. triangulation.h). * * For C++ compatibility I've avoided definitions of the form * * typedef enum * { * foo_up, * foo_down * } Foo; * * in favor of * * typedef int Foo; * enum * { * foo_up, * foo_down * }; * * The problem with the former definition is that C++ insists on an * explicit typecast to assign an integer to a variable of type Foo, * so code like "for (foo = 0; foo < 2; foo++)" won't compile. */ #ifndef _kernel_typedefs_ #define _kernel_typedefs_ #include "SnapPea.h" #define NEW_STRUCT(struct_type) (struct_type *) my_malloc((size_t) sizeof(struct_type)) #define NEW_ARRAY(n, struct_type) (struct_type *) my_malloc((size_t) (n) * sizeof(struct_type)) #define INSERT_BEFORE(new, old) { \ (new)->next = (old); \ (new)->prev = (old)->prev; \ (new)->prev->next = (new); \ (new)->next->prev = (new); \ } #define INSERT_AFTER(new, old) { \ (new)->prev = (old); \ (new)->next = (old)->next; \ (new)->prev->next = (new); \ (new)->next->prev = (new); \ } #define REMOVE_NODE(node) { \ (node)->next->prev = (node)->prev; \ (node)->prev->next = (node)->next; \ } #define ABS(x) (((x) >= 0) ? (x) : -(x)) /* * Gluings of ideal tetrahedra. * * The neighbor and gluing fields of a Tetrahedron tell how the Tetrahedron * is glued to the other Tetrahedra in the Triangulation. The vertices of * each Tetrahedron in the Triangulation are implicitly indexed by the integers * {0,1,2,3}, and the faces are indexed according to the index of the opposite * vertex. The field tet->neighbor[i] contains a pointer to the Tetrahedron * which face i of Tetrahedron tet glues to. The field tet->gluing[i] describes * the gluing as a Permutation of the set {0,1,2,3}, using the following scheme * devised by Bill Thurston. If j {j = 0,1,2,3} does not equal i, then j is the * index of a vertex on face i of Tetrahedron tet, and the Permutation * tet->gluing[i] applied to j gives the index of the vertex of Tetrahedron * tet->neighbor[i] that vertex j glues to. If j equals i, then j is the index * of the vertex of Tetrahedron tet opposite face i, and the Permutation * tet->gluing[i] applied to j gives the index of the vertex of Tetrahedron * tet->neighbor[i] opposite the image of face i; equivalently -- and more * usefully -- j is the index of the given face on Tetrahedron tet, and the * Permutation applied to j gives the index of its image face on * tet->neighbor[i]. * * Each gluing field contains a permutation on {0,1,2,3}, represented in a * single byte as follows. The byte is conceptually divided into four two-bit * fields. The two high-order bits contain the image of 3 under the * permutation, the next two bits contain the image of 2, etc. So, for example, * the permutation which takes 3210 to 2013 would be represented as 10000111. * This representation is space-efficient, and also allows the definition of * the following macro for evaluating permuations. */ typedef unsigned char Permutation; /* * EVALUATE(p,n) is the image of n (n = 0,1,2,3) under the permutation p. */ #define EVALUATE(p,n) ( ((p) >> 2*(n)) & 0x03 ) /* * CREATE_PERMUTATION() is, roughly speaking, the inverse of EVALUATE(). * CREATE_PERMUTATION(a,pa,b,pb,c,pc,d,pd) creates the one-byte Permutation * which takes a to pa, b to pb, c to pc and d to pd. For example, * CREATE_PERMUTATION(1,1,3,2,0,3,2,0) defines the permutation 2013 (base 4), * which equals 10000111 (binary) or 0x87 (hex). Symbolically this is * the permutation {3->2, 2->0, 1->1, 0->3}. */ #define CREATE_PERMUTATION(a,pa,b,pb,c,pc,d,pd) ((Permutation) (((pa) << 2*(a)) + ((pb) << 2*(b)) + ((pc) << 2*(c)) + ((pd) << 2*(d)))) /* * For convenience, we #define the IDENTITY_PERMUTATION. */ #define IDENTITY_PERMUTATION 0xE4 /* = 11100100 = 3210 */ /* * FLOW(,) is used in reconstructing simple closed curves from * homological information. Specifically, let A be the number of times * a curve intersects one side of a triangle, and B be the number of times * it intersects a different side (of the same triangle). Then FLOW(A,B) * is the number of strands of the curve passing from the first side to * the second. */ #define FLOW(A,B) ( ((A<0)^(B<0)) ? \ (((A<0)^(A+B<0)) ? A : -B) : \ 0 ) #define MAX(A,B) ((A) > (B) ? (A) : (B)) #define MIN(A,B) ((A) < (B) ? (A) : (B)) #define DET2(M) ((M[0][0])*(M[1][1]) - (M[0][1])*(M[1][0])) /* Some unix C libraries define PI in math.h, */ /* and complain about a second definition. */ #ifndef PI #define PI 3.14159265358979323846 #endif #define TWO_PI 6.28318530717958647693 #define FOUR_PI 12.56637061435917295385 #define PI_OVER_2 1.57079632679489661923 #define PI_OVER_3 1.04719755119659774615 #define THREE_PI_OVER_2 4.71238898038468985769 #define ROOT_3_OVER_2 0.86602540378443864676 #define ROOT_3 1.73205080756887729352 #define TRUE 1 #define FALSE 0 typedef signed char VertexIndex, EdgeIndex, FaceIndex; /* * The Orientation of a tetrahedron is determined by placing your hand * inside the tetrahedron with your wrist at face 0, your thumb at face 1, * your index finger at face 2 and your middle finger at face 3. If this * is most comfortably accomplished with your right hand, then the * tetrahedron is right_handed. Otherwise it's left_handed. * * Portions of the code assume that right_handed == 0 and * left_handed ==1, so please don't change them. */ typedef int Orientation; enum { right_handed = 0, left_handed = 1, unknown_orientation }; typedef MatrixParity GluingParity; /* * The constants complete and filled facilitate reference * to the shape of a Tetrahedron as part of the complete or * Dehn filled hyperbolic structure, respectively. */ typedef int FillingStatus; enum { complete, filled }; /* * The constants initial and current are synonymous with complete * and filled, respectively. They are used to refer to cusp shapes. * That is, cusp_shape[initial] is the cusp shape defined by the * complete hyperbolic structure, and cusp_shape[current] is the * cusp shape defined by the current Dehn filling. In both cases * the cusp in question is complete (even though other cusps might * not be) which is why I decided to use the terms initial and current * instead of complete and filled. */ enum { initial, current }; /* * The constants ultimate and penultimate facilitate reference * to the approximate solutions at the ultimate and penultimate * iterations of Newton's method. */ typedef int Ultimateness; enum { ultimate, penultimate }; /* * The ShapeInversion data structure records the event that * a Tetrahedron changes it shape, i.e. goes from having z.real >= 0 * to z.real < 0. This allows the Chern-Simons code (and potentially * other, future additions to SnapPea) to work out the exact path * of the Tetrahedron shape through the parameter space, up to isotopy. * * Each ShapeInversion records which of the three edge parameters * (0, 1 or 2) passed through pi (mod 2 pi) as the Tetrahedron changed * shape. The other two parameters will have passed through 0 (mod 2 pi). * * A stack of ShapeInversions represents the history of a shape. The * stack is implemented as a NULL-terminated linked list. Two consecutive * ShapeInversions with the same edge parameter passing through pi * will cancel. * * Each Tetrahedron has two ShapeInversion stacks, one for the complete * hyperbolic structure and the other for the filled hyperbolic structure. * Both are computed relative to the ultimate hyperbolic structure. */ typedef struct ShapeInversion { /* * Which edge parameter passed through pi (mod 2 pi) ? */ EdgeIndex wide_angle; /* * The next field points to the next ShapeInversion on the * linked list, or is NULL if there are no more. */ struct ShapeInversion *next; } ShapeInversion; /* * The constants M and L provide indices for 2-element arrays * and 2 x 2 matrices which refer to peripheral curves. * * For example, the Tetrahedron data structure records a meridian * as curve[M] and a longitude as curve[L]. * * The file i/o routines assume M == 0 and L == 1, so please * don't change them. */ typedef int PeripheralCurve; enum { M = 0, L = 1 }; /* * The TraceDirection typedef is used to specify whether a curve * should be traced forwards or backwards. */ typedef int TraceDirection; enum { trace_forwards, trace_backwards }; /* * The GeneratorStatus typedef specifies the existence and direction * of a generator of a manifold's fundamental group. See choose_generators.c * for details. */ typedef int GeneratorStatus; enum { unassigned_generator, /* the algorithm has not yet considered the face */ outbound_generator, /* the generator is directed outwards */ inbound_generator, /* the generator is directed inwards */ not_a_generator /* the face does not correspond to a generator */ }; /* * The VertexCrossSections data structure represents a cross section * of each ideal vertex of the Tetrahedron which owns it. * The vertex cross section at vertex v of Tetrahedron tet is a * triangle. The length of its edge incident to face f of tet is * stored as tet->cross_section->edge_length[v][f]. (The edge_length * is undefined when v == f.) Please see cusp_cross_sections.c for * more details. * * By convention, * * when no cusp cross sections are in place, the cross_section field * of each Tetrahedron is set to NULL, and * * when cusp cross sections are created, the routine that creates * them must allocate the VertexCrossSections structures. * * Thus, routines which modify a triangulation (e.g. the two_to_three() * and three_to_two() moves) know that they must keep track of cusp cross * sections if and only if the cross_section fields of the Tetrahedra are * not NULL. */ typedef struct { double edge_length[4][4]; Boolean has_been_set[4]; } VertexCrossSections; /* * cusp_neighborhoods.c needs to maintain a consistent coordinate system * on each cusp cross section, so that horoballs etc. appear at consistent * positions even as the canonical Triangulation changes. * Each Tetrahedron's cusp_nbhd_position field keeps a pointer to a * struct CuspNbhdPosition while cusp neighborhoods are being maintained; * at all other times that pointer is set to NULL. This offers the same * two advantages described in triangulation.h for the TetShape pointer, * namely, * * (1) we save memory when CuspNbhdPositions aren't needed, and * * (2) the low-level retriangulation routines can tell whether * they need to maintain CuspNbhdPositions or not. * * * The cusp cross sections intersect each ideal tetrahedron in four small * triangles. The CuspNbhdPosition structure records the positions of * each of the four triangles' three vertices. Actually, we keep track * of the left_handed and right_handed sheets separately, so we can work * with the double covers of Klein bottle cusps; that way we're always * working with a torus. The indices of x[h][v][f] are as follows: * * h = left_handed or right_handed tells the sheet we're on, * v = 0,1,2,3 tells the vertex * f = 0,1,2,3 (for f != v) tells the face opposite the * ideal vertex of interest * * Note: The fields x[h][v][v], which are not needed for storing * the corner coordinates of triangles, are pressed into service in * get_cusp_neighborhood_Ford_domain() to store the locations of the * Ford domain vertices. Cf. the FORD_VERTEX(h,v) macro below. * * The Boolean field in_use[h][v] records which x[h][v][] fields * are actually in use. For a Klein bottle cusp, they all will be in use * (because we work with the double cover). For a torus cusp only half * will be in use (in an orientable manifold they'll be the ones on the * right_handed sheet, in a nonorientable manifold one sheet is chosen * arbitrarily). */ typedef struct { Complex x[2][4][4]; Boolean in_use[2][4]; } CuspNbhdPosition; #define FORD_VERTEX(x,h,v) x[h][v][v] /* * The CanonizeInfo data structure records information used by * canonical_retriangulation() in canonize_part_2.c. It is almost local * to that file, but not quite. The low-level retriangulation functions * two_to_three() and one_to_four() in simplify_triangulation.c need to be * able to check whether CanonizeInfo is present, and if so transfer the * information correctly to the new Tetrahedra they create. Thus we make * the convention that the canonize_info field of each Tetrahedron is NULL * if no CanonizeInfo is present, and points to a CanonizeInfo structure if * such information is present. */ typedef int FaceStatus; enum { opaque_face, /* The face lies in the 2-skeleton of the */ /* canonical cell decomposition. */ transparent_face, /* The face lies in the interior of a 3-cell */ /* in the canonical cell decomposition, but */ /* has not yet been worked into the coned */ /* polyhedron. */ inside_cone_face /* The face lies in the interior of a 3-cell */ /* in the canonical cell decomposition, and */ /* also lies in the interior of the coned */ /* polyhedron. */ }; typedef struct { FaceStatus face_status[4]; Boolean part_of_coned_cell; } CanonizeInfo; /* * In the definition of a Tetrahedron, it's useful to include a * general purpose pointer which different kernel modules may use * to temporarily append different data structures to the Tetrahedron. * Even though different modules will append different temporary * data structures, we must have a single global declaration of the * pointer in triangulation.h. One solution would be to declare the * pointer as a pointer to a void, but this leads to a lot of messy * typecasting. Instead, we use the following opaque typedef of a * (globally nonexistent) struct extra. Each module may then define * struct extra as it sees fit. There is no need for the definition * of struct extra found in one source file to be consistent with * that of another source file, although obviously when a function in * one file calls a function in another the programmer must be absolutely * certain that the files do not make conflicting definitions of * struct extra. * * As a guard against conflicts, we make the convention that the * Extra field in the Tetrahedron data structure is always set to NULL * when not in use. Any module that wants to use the fields first * checks whether it is NULL, and reports an error and exits if it isn't. */ typedef struct extra Extra; /* * Normally one expects all the code to be compiled with the same set * of calling conventions. An exception arises when using C++ * in conjunction with ANSI library routines that require callback functions. * Specifically, qsort() wants a comparision function declared using * C calling conventions. Typically this requires that the C++ code * declare the callback function as cdecl or _cdecl ("C declaration"). * If this doesn't work, try the following syntax (which will require * something like "#define CDECL_END }" to provide the closing bracket). * * extern "C" * { * int comp(const void *a, const void *b) * { * ... * } * } * * If all else fails, read the documentation for your C++ compiler, * and contact weeks@northnet.org if you have problems. If this * gets to be a headache, I can replace the standard library's qsort() * with a hard-coded qsort (such as the one on page 120 of K&R 2nd ed.). */ #ifdef __cplusplus #define CDECL _cdecl //#define CDECL cdecl #else #define CDECL /* We're not using C++, so CDECL may be empty. */ #endif #endif snappea-3.0d3/SnapPeaKernel/headers/link_projection.h0100444000175000017500000000741007203633277020743 0ustar babbab/* * link_projection.h * * This file provides the data format in which the UI passes * link projections to the kernel. All typedefs begin with * "KLP" ("Kernel Link Projection") to avoid name conflicts * with the UI's private link projection data structure. */ #ifndef _link_projection_ #define _link_projection_ typedef struct KLPCrossing KLPCrossing; typedef struct KLPProjection KLPProjection; /* * The KLPStrandType and KLPDirectionType enums are used to index * array entires, so their values must be 0 and 1. (But the code * does not rely on which has value 0 and which has value 1.) * * JRW 2000/11/12 Use fake "typedef enums" instead of real ones, * for the reasons explained at the top of kernel_typedefs.h. */ /* * If you view a crossing (from above) so that the strands go in the * direction of the postive x- and y-axes, then the strand going in * the x-direction is the KLPStrandX, and the strand going in the * y-direction is the KLPStrandY. Note that this definition does not * depend on which is the overstrand and which is the understrand. * * KLPStrandY * ^ * | * ----+---> KLPStrandX * | * | */ typedef int KLPStrandType; enum { KLPStrandX = 0, KLPStrandY, KLPStrandUnknown }; /* * The backward and forward directions are what you would expect. * * KLPBackward ---------> KLPForward */ typedef int KLPDirectionType; enum { KLPBackward = 0, KLPForward, KLPDirectionUnknown }; /* * A crossing is either a clockwise (CL) or counterclockwise (CCL) * half twist. * * _ _ _ _ * |\ /| |\ /| * \ / \ / * / \ * / \ / \ * / \ / \ * KLPHalfTwistCL KLPHalfTwistCCL */ typedef int KLPCrossingType; enum { KLPHalfTwistCL, KLPHalfTwistCCL, KLPCrossingTypeUnknown }; /* * A link projection is essentially an array of crossings. */ struct KLPProjection { /* * How many crossings are there? */ int num_crossings; /* * How many free loops (i.e. link components with no crossings) are * there? For a hyperbolic link, the number of free loops must be 0. */ int num_free_loops; /* * How many link components (including the free loops) are there? */ int num_components; /* * Here's a pointer to the array of crossings. */ KLPCrossing *crossings; }; /* * Each crossing has pointers to its four neighbors, * along with information about its own handedness. */ struct KLPCrossing { /* * The four neighbors are * * neighbor[KLPStrandX][KLPBackward] * neighbor[KLPStrandX][KLPForward ] * neighbor[KLPStrandY][KLPBackward] * neighbor[KLPStrandY][KLPForward ] * * For example, if you follow the x-strand in the backward direction, * you'll arrive at neighbor[KLPStrandX][KLPBackward]. */ KLPCrossing *neighbor[2][2]; /* * When you arrive at a neighbor, you could arrive at either the * x-strand or the y-strand. The strand[][] field says which it is. * * For example, if you follow the x-strand in the backward direction, * you'll arrive at strand[KLPStrandX][KLPBackward]. */ KLPStrandType strand[2][2]; /* * The crossing is either a clockwise or counterclockwise half twist. */ KLPCrossingType handedness; /* * To which component of the link does each strand belong? A link * component is given by an integer from 0 to (#components - 1). * For example, if component[KLPStrandX] == 0 and * component[KLPStrandY] == 2, then the x-strand is part of component * number 0 and the y-strand is part of component number 2. */ int component[2]; }; #endif snappea-3.0d3/SnapPeaKernel/headers/normal_surfaces.h0100444000175000017500000001050306742676077020746 0ustar babbab/* * normal_surfaces.h */ #ifndef _normal_surfaces_ #define _normal_surfaces_ #include "SnapPea.h" /* * What is a normal surface? * * Embedded surfaces will be described using normal surface theory. * A given normal surface intersects each ideal tetrahedron in a (possibly * empty) disjoint collection of topological squares and triangles. * * /|\ * / | \ * / | \ * / | \ * /####|####\ * / ####|#### \ * / ####|#### \ * ---####|####--- * \ ####|#### / * \ ####|#### / * \####|####/ * \ | / * \ | / * \ | / * \|/ * * Each square cuts across the ideal tetrahedron as shown above, so as to * separate the ideal tetrahedron's ideal vertices into two groups of two * ideal vertices each. There are three ways to do this, but a given ideal * tetrahedron may contain squares of only one type, because the surface * is embedded. * * /|\ * / | \ * / | \ * /###|###\ * / ##|## \ * / #|# \ * / | \ * -------|------- * \ | / * \ | / * \ | / * \ | / * \ | / * \ | / * \|/ * * Each triangle cuts across the ideal tetrahedron as shown above, * so as to separate one ideal vertex from the other three. There are * four ways to do this, and triangles of all four types may be present * simultaneously, because they don't intersect each other or the squares. * * * How is a normal surface modelled in a Triangulation? * * The squares and triangles are modelled in the Tetrahedron data structure * as follows. The field tet->parallel_edge contains the EdgeIndex * (= 0, 1 or 2) of an edge which does not intersect the squares. The * opposite edge (= 5, 4 or 3, respectively) will lie on the opposite * side of the squares, and all remaining edges will intersect the squares. * The field tet->num_squares tells how many (parallel) squares lie in * the Tetrahedron. The fields tet->num_triangles[v] tell how many * (parallel) triangles lie near the ideal vertex of VertexIndex v. * When several surfaces must be available simultaneously, the parallel_edge, * num_squares and num_triangles[] fields are copied to the corresponding * fields of a NormalSurface structure (below). */ /* * SnapPea.h contains an opaque typedef for a NormalSurfaceList. * Here we make a typedef for a NormalSurface, so the NormalSurfaceList * can refer to it. */ typedef struct NormalSurface NormalSurface; struct NormalSurfaceList { /* * The normal surfaces all share the same underlying triangulation. * This triangulation is obtained from the user's triangulation * by filling in all closed cusps. */ Triangulation *triangulation; /* * The NormalSurfaces are kept on an array. */ int num_normal_surfaces; NormalSurface *list; }; struct NormalSurface { /* * The UI should report the information on orientability, two-sidedness * and Euler characteristic to the user. In the present implementation * of find_normal_surfaces(), is_connected will always be TRUE. */ Boolean is_connected, is_orientable, is_two_sided; int Euler_characteristic; /* * The following fields describe the normal surface. * In the i-th Tetrahedron of the Triangulation, the squares (if any) * are parallel to the edge of EdgeIndex parallel_edge[i] * (= 0, 1 or 2). The number of such squares is num_squares[i]. * The number of triangles at vertex v is num_triangles[i][v]. * These fields record the values of the corresponding fields * in the Tetrahedron data structure. */ EdgeIndex *parallel_edge; int *num_squares, (*num_triangles)[4]; /* * find_normal_surfaces() temporarily keeps the NormalSurfaces on * a NULL-terminated singly linked list. As soon as it know how * many it has found, it transfers them to an array, and this * "next" field is ignored from then on. */ NormalSurface *next; /* used locally within find_normal_surfaces() */ }; #endif snappea-3.0d3/SnapPeaKernel/headers/positioned_tet.h0100444000175000017500000000157706742676077020627 0ustar babbab/* * positioned_tet.h * * The PositionedTet data structure records the position in which a * Tetrahedron is currently being considered. The file positioned_tet.c * contains routines for working with PositionedTets. * * Imagine a tetrahedron sitting on a table with one of its faces * facing toward you. The face on the table is bottom_face and the one * facing toward you is near_face. The faces facing away from you * are left_face and right_face. * * The orientation field specifies whether the numbering on the * tetrhedron is right_handed or left_handed when viewed in this position. */ #ifndef _positioned_tet_ #define _positioned_tet_ #include "SnapPea.h" #include "kernel_typedefs.h" #include "triangulation.h" typedef struct { Tetrahedron *tet; FaceIndex near_face, left_face, right_face, bottom_face; Orientation orientation; } PositionedTet; #endif snappea-3.0d3/SnapPeaKernel/headers/symmetry_group.h0100444000175000017500000000707206742676077020677 0ustar babbab/* * symmetry_group.h * * This file defines a general, not necessarily abelian group * for use in computing symmetry groups of manifolds. */ #ifndef _symmetry_group_ #define _symmetry_group_ #include "kernel.h" #include "isometry.h" /* * Symmetries are just Isometries from a manifold to itself. */ typedef Isometry Symmetry; typedef IsometryList SymmetryList; /* * The file SnapPea.h contains the "opaque typedef" * * typedef struct SymmetryGroup SymmetryGroup; * * which lets the UI declare and pass pointers to SymmetryGroups without * actually knowing what they are. This file provides the kernel with * the actual definition. */ /* * Group elements are represented by integers, beginning with 0. * By convention, 0 will always be the identity element. */ struct SymmetryGroup { /* * The order of the group. */ int order; /* * We want to keep the actual Symmetries around * to compute their fixed point sets, their action * on the cusps, etc. */ SymmetryList *symmetry_list; /* * product[][] is the multiplication table for the group. * That is, product[i][j] is the product of elements i and j. * * We compose Symmetries from right to left, so the product BA * is what you get by first doing Symmetry A, then Symmetry B. */ int **product; /* * order_of_element[i] is the order of element i. */ int *order_of_element; /* * inverse[i] is the inverse of element i. */ int *inverse; /* * Is this a cyclic group? * * If so the elements will be ordered as consecutive * powers of a generator. */ Boolean is_cyclic; /* * Is this a dihedral group? * * If so, the elements will be ordered as * I, R, R^2, . . . , R^(n-1), F, FR, . . . , FR^(n-1). */ Boolean is_dihedral; /* * Is this a spherical triangle group? * That is, is it one of the groups * * group (p, q, r) * * (binary) dihedral (2, 2, n) * (binary) tetrahedral (2, 3, 3) * (binary) octahedral (2, 3, 4) * (binary) icosahedral (2, 3, 5) * * If so, p, q and r will record the angles of the triangle * (pi/p, pi/q and pi/r), and is_binary_group will record * whether it's the binary polyhedral group as opposed to * the plain polyhedral group. * * When is_polyhedral is FALSE, p, q, r and is_binary_group * are undefined. */ Boolean is_polyhedral; Boolean is_binary_group; int p, q, r; /* * Is this the symmetric group S5? * (Eventually I'll write a more general treatment of symmetric * and alternating groups, but I want to get this much in place * before the Georgia conference so SnapPea can (I hope) recognize * the symmetry group of the totally symmetric 5-cusp manifold.) */ Boolean is_S5; /* * Is this an abelian group? * * If so, the elements will be ordered in a natural way, * and abelian_description will point to a description * of the group. * * If not, abelian_description will be set to NULL. */ Boolean is_abelian; AbelianGroup *abelian_description; /* * Is this group a nonabelian, nontrivial direct product? * * If so, factor[0] and factor[1] will point to the two * factors. Of course, each factor may itself be a nontrivial * direct product, and so on, leading to a tree structure * for the factorization. * * The symmetry_list field is defined only at the root level, * but all other fields are defined at all nodes. * * If the group is not a nonabelian, nontrivial direct product, * factor[0] and factor[1] will be set to NULL. */ Boolean is_direct_product; SymmetryGroup *factor[2]; }; #endif snappea-3.0d3/SnapPeaKernel/headers/tables.h0100444000175000017500000000176406742676077017046 0ustar babbab/* * tables.h * * The following tables are defined and documented in tables.c. * They are globally available within the kernel, but are not * available to the user interface. */ #ifndef _tables_ #define _tables_ #include "kernel_typedefs.h" extern const EdgeIndex edge3[6]; extern const EdgeIndex edge_between_faces[4][4]; extern const EdgeIndex edge3_between_faces[4][4]; extern const EdgeIndex edge_between_vertices[4][4]; extern const EdgeIndex edge3_between_vertices[4][4]; extern const FaceIndex one_face_at_edge[6]; extern const FaceIndex other_face_at_edge[6]; extern const VertexIndex one_vertex_at_edge[6]; extern const VertexIndex other_vertex_at_edge[6]; extern const FaceIndex remaining_face[4][4]; extern const FaceIndex face_between_edges[6][6]; extern const Permutation inverse_permutation[256]; extern const signed char parity[256]; extern const FaceIndex vt_side[4][3]; extern const Permutation permutation_by_index[24]; extern const char index_by_permutation[256]; #endif snappea-3.0d3/SnapPeaKernel/headers/terse_triangulation.h0100444000175000017500000001165406742676077021655 0ustar babbab/* * terse_triangulation.h * * This data structure describes a Triangulation in a "terse" format. * The data structure is intended to minimize storage requirements, * and contains only the minimum information required to reconstruct * the Triangulation in the full triangulation.h format. * * The data structure allows the option of including the value of the * Chern-Simons invariant (mod 1/2). * * Most often the TerseTriangulation will be used in conjunction with * the TersestTriangulation data format (cf. tersest_triangulation.h), * which represents the TerseTriangulation as a short sequence of * characters, suitable for storage in a file or transmission over * a network. * * Bill Thurston provided the original idea for this data structure. * The implementation has evolved from code written by Martin Hildebrand at * the Geometry Center (then the Geometry Supercomputer Project) in 1988. * * * [The following description of the TerseTriangulation assumes basic * familiarity with the discussion "Gluings of ideal tetrahedra" preceding * the Permutation typedef in kernel_typedefs.h.] * * The TerseTriangulation data structure provides the data for an * algorithm for constructing an ideal triangulation. The algorithm is * as follows. We begin with n ideal tetrahedra, which we are going * to assemble into an ideal triangulation. The vertices of each * tetrahedron are numbered 0, 1, 2 and 3, and the faces are numbered * according to the opposite vertices. The tetrahedra are all identical. * Take one tetrahedron and call it tetrahedron 0. Consider face 0 * of tetrahedron 0. It may glue to some other face of tetrahedron 0, * or it may glue to a different tetrahedron. The first entry of the * Boolean array glues_to_old_tet tells us which is the case. That is, * if glues_to_old_tet[0] is TRUE, then face 0 glues to some other face * of tetrahedron 0; if glues_to_old_tet[0] is FALSE, it glues to a * different tetrahedron. * * In the case that glues_to_old_tet[0] is TRUE, which_old_tet[0] will tell * us which old tetrahedron it glues to (here which_old_tet[0] must * obviously be 0, because tetrahedron 0 is the only tetrahedron we're * working with so far, but in a minute you'll see the full generality of * the which_old_tet array), and which_gluing[0] tells us the gluing * pattern (cf. the discussion "Gluings of ideal tetrahedra" in * kernel_prototypes.h). * * In the case that glues_to_old_tet[0] is FALSE, we attach one of the * as-yet-unused tetrahedra to face 0 of tetrahedron 0. The new * tetrahedron will be called tetrahedron 1, and its vertices will * inherit indices from the vertices of tetrahedron 0 in the canonical * way, by reflection across their common face. (The gluing will be * the identity Permutation.) * * Once we've taken care of face 0 of tetrahedron 0, we move on to * face 1 of tetrahedron 0. If it's already been glued (to face 0), * we do nothing. Otherwise we consult the next entry in the * glues_to_old_tet[] array: if it's TRUE, then the next entries in * the which_old_tet[] and which_gluing[] arrays tell us which tetrahedron * it glues to (maybe tetrahedron 0, or maybe tetrahedron 1), and what * the gluing Permutation is; if it's FALSE, we attach a new tetrahedron * as described above. * * We continue this process for faces 2 and 3 of tetrahedron 0, then * move on to faces 0, 1, 2 and 3 (in that order) of tetrahedron 1, * then tetrahedron 2, etc., until we have constructed the entire * triangulation. Note that this algorithm assumes the triangulation * is connected. * * If there are n tetrahedra, there'll be 4n faces, and (4n)/2 = 2n * decisions as to whether to glue to an old tetrahedron or a new one. * (The last two "decisions" will always be to glue to an old tetrahedron, * but we won't worry about that. The terse string data format does, * however, take this into account.) Precisely (n - 1) of those * decisions will be to add a new tetrahedron, and the remaining (n + 1) * decisions will be to glue to an old tetrahedron. This means that * the glues_to_old_tet[] array must have length 2n, while the * which_old_tet[] and which_gluing[] arrays must have length (n + 1). * * The file SnapPea.h contains the "opaque typedef" * * typedef struct TerseTriangulation TerseTriangulation; * * which lets the UI declare and pass pointers to TerseTriangulations * without actually knowing what they are. This file provides the * kernel with the actual definition. */ #ifndef _terse_triangulation_ #define _terse_triangulation_ #include "kernel.h" struct TerseTriangulation { /* * The first four fields provide the basic data described above. */ int num_tetrahedra; Boolean *glues_to_old_tet; int *which_old_tet; Permutation *which_gluing; /* * Optionally, we may wish to record the Chern-Simons invariant, * since it isn't computable from scratch (at least not in 1993). */ Boolean CS_is_present; double CS_value; }; #endif snappea-3.0d3/SnapPeaKernel/headers/tersest_triangulation.h0100444000175000017500000000741706742676077022226 0ustar babbab/* * tersest_triangulation.h * * This data structure -- which is really just a formatting convention for * an array of chars -- compresses the information in a TerseTriangulation * to the maximum extent possible. It works only for Triangulations of 7 * or fewer Tetrahedra. It is intended for use in storing libraries of * manifolds, such as the 5-, 6- and 7-tetrahedron censuses. * * Here's how the information from the TerseTriangulation data structure * is stored. The description is for a 7-tetrahedron Triangulation. * With fewer Tetrahedra some bits will be unused, but we always use * the same number of bytes. (The reason we always use the same number * of bytes is so that we can retrieve a manifold from the middle of a long * file without reading the whole file.) Recall that a TerseTriangulation * for n Tetrahedra has a glues_to_old_tet[] array of length 2n, and * which_old_tet[] and which_gluing[] arrays of length n+1. So for * 7 Tetrahedra, glues_to_old_tet[] has length 14, while which_old_tet[] * and which_gluing[] each have length 8. * * num_tetrahedra * * is redundant and is not stored at all. The number of Tetrahedra can * be deduced from the glues_to_old_tet array by keeping track of how * many free faces are available at each stage. * * glues_to_old_tet[] * * is stored in the first two bytes of the TersestTriangulation string. * * glues_to_old_tet[0] is stored in the lowest-order bit of byte 0, * . . . and so on until . . . * glues_to_old_tet[7] is stored in the highest-order bit of byte 0. * * glues_to_old_tet[8] is stored in the lowest-order bit of byte 1, * . . . and so on until . . . * glues_to_old_tet[13] is stored in the sixth bit of byte 1. * * Note that the two highest-order bits in byte 1 remain available for * other purposes. The highest-order bit tells whether a Chern-Simons * invariant is present (cf. below). * * which_old_tet[] * which_gluing[] * * which_old_tet[] and which_gluing[] are stored in tandem. * * which_old_tet[i] is stored in the three highest-order bits * of byte 2+i. which_old_tet[i] will always be an integer * in the range [0, 6], so it fits comfortably in three bits. * * which_gluing[i] is stored in the five lowest-order bits * of byte 2+i. Each Permutation is converted to an integer * in the range [0, 23], which fits comfortably in five bits. * The array index_by_permutation[] in tables.c does the conversion. * * CS_is_present * CS_value * * CS_is_present is stored in the highest-order bit of byte 1. * * if (CS_is_present == TRUE) * bytes 10 through 17 hold the CS_value, encoded as follows. * First add a multiple of 1/2, if necessary, so that the CS_value * lies in the range [-1/4, 1/4) Then mulitply by 2 and add 1/2 * so that it lies in the ragne [0, 1). The CS_value is a * double which on some machines (e.g. the Mac) has an 8-btye * mantissa. In binary, this is a number like 0.100101000..., with * 64 meaningful binary digits after the decimal point (the "binary * point" ?). The first 8 digits are stored in byte 10 of the * TersestTriangulation string, the next 8 digits are stored in * byte 11, and so on until the final 8 meaningful digits are stored * in byte 17. On some machines (e.g. on most UNIX workstations) * doubles have only 6-byte mantissas, in which case we just * zero-fill bytes 16 and 17 of the TersestTriangulation string * (in practice this shouldn't occur, because the libraries of * manifolds will be created on the Mac at full accuracy, and then * only read on less accurate platforms). * * if (CS_is_present == FALSE) * bytes 10 through 17 are unused. */ #ifndef _tersest_triangulation_ #define _tersest_triangulation_ typedef unsigned char TersestTriangulation[18]; #endif snappea-3.0d3/SnapPeaKernel/headers/triangulation.h0100444000175000017500000002765506742676077020463 0ustar babbab/* * triangulation.h * * This file defines the basic data structure for an ideal * triangulation. SnapPea's various modules communicate with each * other primarily by passing pointers to Triangulations. * * The Triangulation data structure consists of some global information * about the manifold (number of tetrahedra, number of cusps, etc.) * following by doubly linked lists of Tetrahedra, EdgeClasses, and Cusps. * As the triangulation varies dynamically (for example, during the * triangulation simplification algorithm) Tetrahedra, EdgeClasses and * Cusps may be easily inserted and deleted using the INSERT_BEFORE() * and REMOVE_NODE() macros found in kernel_typedefs.h. The Triangulation * data structure contains header and tailer nodes for each doubly linked * list, to avoid having to consider special cases while inserting and * deleting nodes. * * To keep the global structure of this file as clear as possible, * most of the local documentation appears elsewhere. The comment * next to each field says what .c file (if any) contains the * documentation for that field. * * Most fields are maintained globally. That is, you may assume they * contain correct values at all times, and you should update their * values if you change the triangulation. Fields which are used locally * within a single file or module are so indicated. They do not contain * correct values outside that module, and you need not maintain them. * * Note that SnapPea.h (the only header file common to the user interface * and the computational kernel) contains the opaque typedef * * typedef struct Triangulation Triangulation; * * This opaque typedef allows the user interface to declare and pass * pointers to Triangulations, without being able to access a * Triangulation's fields directly. This file provides the kernel with * the actual definition. * * The inclusion of lower-level data structures within higher-level ones * forces the following typedefs to be orangized in a bottom-up fashion, * beginning with the least significant data structure (ComplexWithLog) * and working towards the most significant one (Triangulation). I * therefore recommend that you start reading this file at the bottom * and work your way up. */ #ifndef _triangulation_ #define _triangulation_ #include "SnapPea.h" #include "kernel_typedefs.h" /* * Forward declarations. */ typedef struct ComplexWithLog ComplexWithLog; typedef struct TetShape TetShape; typedef struct Tetrahedron Tetrahedron; typedef struct EdgeClass EdgeClass; typedef struct Cusp Cusp; /* * ComplexWithLog stores a complex edge parameter in both rectangular * and logarithmic form. That is, the log field is always the complex * logarithm of the rect field. The imaginary part of the log varies * continuously during Dehn filling, and is not restricted to any * particular branch of the logarithm. * * The edge parameter is always expressed relative to the * right_handed orientation of the tetrahedron. */ struct ComplexWithLog { Complex rect; Complex log; }; /* * TetShape stores the complex edge parameters at edges 0,1 and 2 of * a given Tetrahedron. (See edge_classes.c for the edge indexing scheme.) * Edges 5, 4 and 3 are opposite 0, 1 and 2, respectively, and therefore * have equal edge parameters. The edge parameters are recorded at the * next-to-the-last as well as the last iteration of Newton's method in * the hyperbolic structures module, to allow the estimation of errors * in various computed quantities (volume, etc.). Warning: the true error * is usually greater than the error between the penultimate and ultimate * iterations of Newton's method. That is, if you switch to a different * triangulation of the same manifold, you'll find the volume, etc. differs * by more than it did between the last two iterations of Newton's method. * The edge parameters at the next-to-the-last iteration are stored as * cwl[penultimate][], while those at the last iteration are cwl[ultimate][]. * * * Note that the Tetrahedron structure (immediately below) contains pointers * to TetShapes, rather than the TetShapes themselves. The disadvantage * of this scheme is that the TetShapes must be allocated and deallocated * explicitly. The advantages are * * (1) Some functions which temporarily require large numbers of Tetrahedra * can get by with less memory if they don't require the TetShapes. * On a Mac, for example, the Tetrahedron structure itself requires * 242 bytes, while the TetShapes require an additional 576 bytes. * This difference can be significant. For example, the function which * computes an ideal triangulation for a partially filled multicusp * manifold will, when applied to a 100-Tetrahedron Triangulation, * temporarily create more than 3000 Tetrahedra. By omitting the * TetShapes, the memory requirement for these Tetrahedra drops * from 2.5 MB to 750 kB. * * (2) It's easy for the low-level retriangulation function (e.g. * the 2-3 and 3-2 moves) to determine whether the Tetrahedra * have shapes associated with them. If the pointers to TetShapes * are NULL, there are no shapes; otherwise there are. * * The TetShape corresponding to the complete (resp. Dehn filled) hyperbolic * structure is stored in the Tetrahedron data structure as tet->shape[complete] * (resp. tet->shape[filled]). By convention, TetShapes will be present iff * tet->solution_type[complete] and tet->solution_type[filled] are something * other than not_attempted. */ struct TetShape { ComplexWithLog cwl[2][3]; }; struct Tetrahedron { Tetrahedron *neighbor[4]; /* kernel_typedefs.h */ Permutation gluing[4]; /* kernel_typedefs.h */ Cusp *cusp[4]; /* the cusp containing each vertex */ int curve[2][2][4][4]; /* peripheral_curves.c */ int scratch_curve[2][2][2][4][4]; /* intersection_numbers.c (local) */ EdgeClass *edge_class[6]; /* edge_classes.c */ Orientation edge_orientation[6];/* edge_classes.c */ TetShape *shape[2]; /* see TetShape and ComplexWithLog above */ ShapeInversion *shape_history[2]; /* kernel_typedefs.h */ int coordinate_system; /* hyperbolic_structure.c (local) */ int index; /* hyperbolic_structure.c (local) */ GeneratorStatus generator_status[4];/* choose_generators.c (local) */ int generator_index[4]; /* choose_generators.c (local) */ GluingParity generator_parity[4];/* choose_generators.c (local) */ Complex corner[4]; /* choose_generators.c (local) */ FaceIndex generator_path; /* choose_generators.c (local) */ VertexCrossSections *cross_section; /* cusp_cross_section.c (local) */ double tilt[4]; /* cusp_cross_section.c (local) */ CanonizeInfo *canonize_info; /* canonize_part_2.c (local) */ Tetrahedron *image; /* symmetry.h (local) */ Permutation map; /* symmetry.h (local) */ Boolean tet_on_curve; /* dual_curves.c (local) */ Boolean face_on_curve[4]; /* dual_curves.c (local) */ CuspNbhdPosition *cusp_nbhd_position;/* cusp_neighborhoods.c (local) */ EdgeIndex parallel_edge; /* normal_surfaces.h (local) */ int num_squares, /* normal_surfaces.h (local) */ num_triangles[4]; /* normal_surfaces.h (local) */ Boolean has_correct_orientation; /* normal_surface_splitting.c (local) */ int flag; /* general purpose integer for local use as necessary */ Extra *extra; /* general purpose pointer for local use as necessary */ /* see Extra typedef in kernel_typedefs.h for details */ Tetrahedron *prev; /* previous tetrahedron on doubly linked list */ Tetrahedron *next; /* next tetrahedron on doubly linked list */ }; struct EdgeClass { int order; /* number of incident edges of tetrahedra */ Tetrahedron *incident_tet; /* one particular incident tetrahedron... */ EdgeIndex incident_edge_index; /* ...and the index of the incident edge */ int num_incident_generators;/* choose_generators.c (local) */ Boolean active_relation; /* choose_generators.c (local) */ Complex *complex_edge_equation; /* gluing_equations.c (used locally) */ double *real_edge_equation_re, /* gluing_equations.c (used locally) */ *real_edge_equation_im; /* gluing_equations.c (used locally) */ Complex edge_angle_sum; /* used locally in hyperbolic structures module */ int index; /* used locally for saving Triangulations to disk */ double intercusp_distance; /* cusp_neighborhoods.c (used locally) */ EdgeClass *prev; /* previous EdgeClass on doubly linked list */ EdgeClass *next; /* next EdgeClass on doubly linked list */ }; struct Cusp { CuspTopology topology; /* torus_cusp or Klein_cusp */ Boolean is_complete; /* is the cusp currently unfilled? */ double m, /* Dehn filling coefficient */ l; /* Dehn filling coefficient */ Complex holonomy[2][2]; /* holonomy.c */ Complex *complex_cusp_equation; /* gluing_equations.c (used locally) */ double *real_cusp_equation_re, /* gluing_equations.c (used locally) */ *real_cusp_equation_im; /* gluing_equations.c (used locally) */ Complex cusp_shape[2]; /* cusp_shapes.c */ int shape_precision[2]; /* cusp_shapes.c */ int index; /* cusp number, as perceived by user */ /* (numbering starts at zero) */ double displacement, /* cusp_neighborhoods.c (used globally) */ displacement_exp, /* cusp_neighborhoods.c (used globally) */ reach, /* cusp_neighborhoods.c (local) */ stopping_displacement; /* cusp_neighborhoods.c (local) */ Cusp *stopper_cusp; /* cusp_neighborhoods.c (local) */ Boolean is_tied; /* cusp_neighborhoods.c (local) */ Complex translation[2], /* cusp_neighborhoods.c (local) */ scratch; /* cusp_neighborhoods.c (local) */ double exp_min_d; /* cusp_neighborhoods.c (local) */ Tetrahedron *basepoint_tet; /* fundamental_group.c (semi-local) */ VertexIndex basepoint_vertex; /* fundamental_group.c (semi-local) */ Orientation basepoint_orientation; /* fundamental_group.c (semi-local) */ int intersection_number[2][2]; /* intersection_numbers.c (local) */ Boolean is_finite; /* finite_vertices.c (used locally) */ /* indices are negative, starting at -1 */ Cusp *matching_cusp; /* subdivide.c, finite_vertices.c, */ /* cover.c, normal_surface_splitting.c */ /* (used locally) */ int euler_characteristic; /* cusps.c (local) */ Cusp *prev; /* previous Cusp on doubly linked list */ Cusp *next; /* next Cusp on doubly linked list */ }; struct Triangulation { char *name; /* name of manifold */ int num_tetrahedra; /* number of tetrahedra */ SolutionType solution_type[2]; /* complete and filled */ Orientability orientability; /* Orientability of manifold */ int num_cusps, /* total number of cusps */ num_or_cusps, /* number of orientable cusps */ num_nonor_cusps; /* number of nonorientable cusps */ int num_generators; /* choose_generators.c (local) */ Boolean CS_value_is_known, /* Chern_Simons.c */ CS_fudge_is_known; /* Chern_Simons.c */ double CS_value[2], /* Chern_Simons.c */ CS_fudge[2]; /* Chern_Simons.c */ double max_reach, /* cusp_neighborhoods.c (local) */ tie_group_reach, /* cusp_neighborhoods.c (local) */ volume; /* cusp_neighborhoods.c (local) */ Tetrahedron tet_list_begin, /* header node for doubly linked list of Tetrahedra */ tet_list_end; /* tailer node for doubly linked list of Tetrahedra */ EdgeClass edge_list_begin,/* header node for doubly linked list of Edges */ edge_list_end; /* tailer node for doubly linked list of Edges */ Cusp cusp_list_begin,/* header node for doubly linked list of Cusps */ cusp_list_end; /* tailer node for doubly linked list of Cusps */ }; #endif snappea-3.0d3/SnapPeaKernel/headers/triangulation_io.h0100444000175000017500000000752006742676077021137 0ustar babbab/* * triangulation_io.h * * The question of file formats is tricky. Typically an application has a * different file format for each platform it runs on (Mac, Windows, etc.), * perhaps with some support for translating between them. Certainly * the easiest way to write Macintosh SnapPea would have been to use * the THINK Class Library's built-in file handling capabilities: * file i/o would have been trivial to program, and each kind of file * (triangulation, generators, link projection) would have its own * "file type" and icon. However, I chose to use straight ASCII text * files for the following reasons: * * (1) The files can be read and written on any platform. * For example, a Mac user can send a Triangulation file * to a friend using a unix version of SnapPea. * * (2) Human beings can read the files using any text editor. * * (3) Text-only files are best for people doing other programming * projects which use SnapPea's manifolds as input. (Again, * people do this work on a variety of platforms.) * * On the other hand, I think it would be a mistake to hard-code the * SnapPea kernel to read data from unix-style FILEs. When the UI * reads a triangulation file, it passes the data to the kernel using * the data structures defined below. And, of course, when the UI wants * to write a triangulation to a file, it requests the data from the * kernel in this same format. * * Notes: * * (1) This format is similar to that of the Triangulation data structure. * However, the present format is available to the UI, while * the Triangulation data structure is private to the kernel. * * (2) The new file format is not backward compatible to the old * file format, although they are similar. (I couldn't keep * the old file format because it doesn't work properly with * nonorientable manifolds.) SnapPea 2.0 will read (but not write) * the old file format, with the exception that peripheral curves * on nonorientable manifolds are recomputed from scratch. * * (3) If you (or your program) is writing TriangulationData structures * from scratch, note that not all fields are required. The fields * solution_type, volume, and the TetrahedronDatas' filled_shapes * are ignored by data_to_triangulation(), because it recomputes the * hyperbolic structure from scratch. The meridian and longitude * are optional: if you provide them, data_to_triangulation() * will use them; otherwise it provides a default basis. * (In the latter case, of course, you shouldn't specify any * Dehn fillings, because you aren't providing the basis relative * to which they are defined.) * * 96/9/17 If you set both num_or_cusps and num_nonor_cusps to zero, * data_to_triangulation() will create the Cusps for you. It will * also create the peripheral curves. * * 96/9/17 If you specify unknown_orientability, data_to_triangulation() * will attempt to orient the manifold. This will typically change * the indexing of the Tetrahedra's vertices. */ /* * This file (triangulation_io.h) is intended solely for inclusion * in SnapPea.h. It depends on some of the typedefs there. */ typedef struct TriangulationData TriangulationData; typedef struct CuspData CuspData; typedef struct TetrahedronData TetrahedronData; struct TriangulationData { char *name; int num_tetrahedra; SolutionType solution_type; double volume; Orientability orientability; Boolean CS_value_is_known; double CS_value; int num_or_cusps, num_nonor_cusps; CuspData *cusp_data; TetrahedronData *tetrahedron_data; }; struct CuspData { CuspTopology topology; double m, l; }; struct TetrahedronData { /* * Note: gluing[i][j] is the image of j under the i-th gluing permutation. */ int neighbor_index[4]; int gluing[4][4]; int cusp_index[4]; int curve[2][2][4][4]; Complex filled_shape; }; snappea-3.0d3/SnapPeaKernel/headers/winged_edge.h0100444000175000017500000005111206742676077020025 0ustar babbab/* * winged_edge.h * * This file defines the usual "winged edge" data structure for * representing a convex polyhedron, along with some extra * fields describing the polyhedron's position in the projective * model of hyperbolic 3-space. (The "projective model" is * the Minkowski space model projected onto the hyperplane * x[0] == 1.) * * This file is intended solely for #inclusion in SnapPea.h. * It needs some of the typedefs which occur there. */ #ifndef _winged_edge_ #define _winged_edge_ #define INFINITE_RADIUS 1e20 #define INFINITE_DISTANCE 1e20 #define INFINITE_LENGTH 1e20 /* * The values of the following two enums are used to index the * static arrays in make_cube() in Dirichlet_construction.c, * so their values shouldn't be changed. (They are also toggled in * Dirichlet_extras.c Dirichlet_conversion.c with the "not" operator '!'.) */ typedef int WEEdgeEnd; enum { tail = 0, tip = 1 }; typedef int WEEdgeSide; enum { left = 0, right = 1 }; /* * The WEEdge structure keeps pointers to Tetrahedra for local use * in Dirichlet_conversion.c. The internal structure of a Tetrahedron * is private to the kernel, so we must include an "opaque typedef" here. * (Indeed even the existence of the Tetrahedron structure is private to * the kernel, so we are "cheating" a bit even by including the typedef.) */ typedef struct Tetrahedron TetrahedronSneak; /* * Forward declarations. */ typedef struct WEVertex WEVertex; typedef struct WEEdge WEEdge; typedef struct WEFace WEFace; typedef struct WEVertexClass WEVertexClass; typedef struct WEEdgeClass WEEdgeClass; typedef struct WEFaceClass WEFaceClass; typedef struct WEPolyhedron WEPolyhedron; struct WEVertex { /* * The vector x gives the position of the WEVertex in the * projective model of hyperbolic 3-space. The projective * model is viewed as a subset of the Minkowski space model, * so x is a 4-element vector with x[0] == 1.0. */ O31Vector x; /* * The vector xx[] is an extra copy of x[] for local use in * the user interface. Even as the polyhedron spins, the UI * should not modify x[], but should write the coordinates * of the rotated vertices into xx[] instead. There are two * reasons for this: * * (1) This scheme avoids cumulative roundoff error in the * coordinates, which could in principle deform the * polyhedron. One can argue that on a 680x0 Mac, which * has 8-byte mantissas, a polyhedron could spin for a * million years before enough error would accumulate * to make a 1-pixel difference on the screen. However, * on an Iris, which uses floats instead of doubles, the * roundoff error is a real issue. In any case, good * programming style dictates that a routine for displaying * a polyhedron should not alter the polyhedron it's * displaying. * * (2) If the user changes the Dehn filling coefficients * slightly, say from (7,1) to (8,1), the change in the * polyhedron will be small, and we'd like the new polyhedron * to appear in roughly the same position as the old one, * so the continuity is visible. For this to occur, x[] * must always contain the polyhedron's initial position, * not its rotated position. */ O31Vector xx; /* * The distance from the vertex to the origin. If the vertex is ideal, * dist is set to INFINITE_DISTANCE. Even though just the distance is * given here, the UI may want to display cosh(dist) as well. */ double dist; /* * Is this an ideal vertex? */ Boolean ideal; /* * The solid angle at this vertex of the Dirichlet domain. */ double solid_angle; /* * The Dirichlet domain's face pairings group the vertices * into vertex classes. */ WEVertexClass *v_class; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the vertex is visible to the user. */ Boolean visible; /* * The distance_to_plane field is used locally within * Dirichlet_construction.c to record the inner product * of the WEVertex's location x[] with an arbitrary but * fixed normal vector to the hyperplane currently under * consideration. Thus, distance_to_plane is proportional * to the Euclidean distance in the projective model from * the point (x[1], x[2], x[3]) to the intersection of the * hyperplane with the projective model (at x[0] == 1.0). * * The which_side_of_plane field is +1, 0 or -1 according * to whether, after accounting for possible roundoff error, * distance_to_plane is positive, zero or negative, respectively. */ double distance_to_plane; int which_side_of_plane; /* * The zero_order field is used locally in check_topology_of_cut() * to verify that precisely zero or two 0-edges are incident to * each 0-vertex. */ int zero_order; /* * The WEVertices are kept on a doubly-linked list. */ WEVertex *prev, *next; }; struct WEEdge { /* * v[tail] and v[tip] are the vertices incident to the * tail and tip, respectively, of the directed WEEdge. */ WEVertex *v[2]; /* * e[tail][left] is the WEEdge incident to both v[tail] and f[left]. * e[tail][right] is the WEEdge incident to both v[tail] and f[right]. * e[tip ][left] is the WEEdge incident to both v[tip ] and f[left]. * e[tip ][right] is the WEEdge incident to both v[tip ] and f[right]. */ WEEdge *e[2][2]; /* * f[left] and f[right] are the faces incident to the * left and right sides, respectively, of the directed WEEdge. */ WEFace *f[2]; /* * The dihedral angle between edge->f[left] and edge->f[right]. */ double dihedral_angle; /* * dist_line_to_origin is the distance to the origin from the line * containing the edge. * * dist_edge_to_origin is the distance to the origin from the edge * itself. Usually this will be the same as dist_line_to_origin, * but occasionally the minimum distance from the line to the origin * will be realized at a point not on the edge itself. In the latter * case the minimum distance from the origin to the edge itself will * be realized at an endpoint. * * Note: The UI may want to display the squared hyperbolic cosines * of the above distances, as well as the distances themselves. * * closest_point_on_line and closest_point_on_edge record the points * at which the above minima are realized. (At present the Mac UI * ignores these, but eventually we may want to display them to the * user upon request.) */ double dist_line_to_origin, dist_edge_to_origin; O31Vector closest_point_on_line, closest_point_on_edge; /* * How long is this edge? * If it's infinite, the length is set to INFINITE_LENGTH. */ double length; /* * The Dirichlet domain's face pairings group the edges * into edge classes. */ WEEdgeClass *e_class; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the edge is visible to the user. */ Boolean visible; /* * The Dirichlet domain's face identifications determine which sets * of edges are identified to single edges in the manifold itself. * For each edge on the Dirichlet domain, * * edge->neighbor[left] tells the WEEdge to which the given edge * is mapped by edge->f[left]->group_element, * * edge->preserves_sides[left] tell whether the left side of the * given edge maps to the left side of the image, * * edge->preserves_direction[left] tells whether the mapping * preserves the direction of the edge, and * * edge->preserves_orientation[left] tells whether the mapping * preserves orientation, * * and similarly for edge->neighbor[right], etc. * * The preserves_sides, preserves_direction and preserves_orientation * fields are intentionally redundant. Any two determine the third. * * If the singular set is empty or consists of disjoint circles (as will * always be the case for Dehn fillings on cusped manifolds), then the * function Dirichlet_bells_and_whistles() will redirect WEEdges as * necessary so that the preserves_direction[] fields are all TRUE. * Even for orbifolds with more complicated singular sets, it will * give consistent directions to the edges whenever possible. */ WEEdge *neighbor[2]; Boolean preserves_sides[2], preserves_direction[2], preserves_orientation[2]; /* * The tet[][] fields are used locally in Dirichlet_conversion.c * to construct a Triangulation for the manifold represented by * the Dirichlet domain. Otherwise they may be ignored. * The four Tetrahedra incident to this WEEdge are tet[tail][left], * tet[tail][right], tet[tip][left], and tet[tip][right]. */ TetrahedronSneak *tet[2][2]; /* * The WEEdges are kept on a doubly-linked list. */ WEEdge *prev, *next; }; struct WEFace { /* * some_edge is an arbitrary WEEdge incident to the given WEFace. */ WEEdge *some_edge; /* * mate is the WEFace which is identified to this face under * the action of the covering transformation group. */ WEFace *mate; /* * group_element is the O(3,1) matrix which takes this face's mate * to this face. In other words, this face lies in the plane which * passes orthogonally through the midpoint of the segment connecting * the origin to its (the origin's) image under the group_element. */ O31Matrix *group_element; /* * The distance from the face plane to the origin. The point of * closest approach may or may not lie on the face itself. */ double dist; O31Vector closest_point; /* * The to_be_removed field is used locally in install_new_face() in * Dirichlet_construction.c to record which WEFaces are to be removed. */ Boolean to_be_removed; /* * The clean field is used locally in check_faces() in * Dirichlet_construction.c to record which WEFaces are known to be * subsets of their mates under the action of the group_element. */ Boolean clean; /* * The copied field is used locally in rewrite_gen_list() and * (independently) in poly_to_current_list() in Dirichlet_construction.c * to record which WEFaces have had their group_elements copied to the * MatrixPairList. */ Boolean copied; /* * The matched field show that the WEEdges incident to this face have * been matched with their neighbors incident to face->mate. */ Boolean matched; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the face is visible to the user. */ Boolean visible; /* * How many sides does this face have? */ int num_sides; /* * The face and its mate are assigned to the same WEFaceClass. * In the case of an orbifold, a face may be its own mate, in which * case the WEFaceClass will have only one element. */ WEFaceClass *f_class; /* * The WEFaces are kept on a doubly-linked list. */ WEFace *prev, *next; }; /* * The Dirichlet domain's face pairings identify the WEVertices, WEEdges * and WEFaces into WEVertexClasses, WEEdgeClasses and WEFaceClasses. * Each equivalence class is assigned an index. (The indices are * assigned consecutively, beginning at zero.) The index is used * to define a "hue", which is the suggested hue for that cell. * The function index_to_hue() in index_to_hue.c assigns hues in such * a way that the low-numbered cells' hues are all easily distinguishable * from one another. This is especially important for the WEFaceClasses. * If there are a large number of faces we can't possibly hope that all * the hues will be easily distinguishable, but we do want the hues of * the largest faces (which are closest to the origin and have the lowest * indices) to be easily distinguishable. */ struct WEVertexClass { /* * At present the WEVertexClasses are listed in arbitrary order, * but if necessary they could easily be sorted to provide * some control over their hues, as is done for WEFaceClasses. */ int index; double hue; int num_elements; /* * The total solid angle surrounding this vertex * (4pi for a manifold, 4pi/n for an orbifold). */ double solid_angle; /* * The "n" in the preceding 4pi/n is recorded as the singularity_order. * (For ideal vertices, n is set to zero.) */ int singularity_order; /* * Is this an ideal vertex class? */ Boolean ideal; /* * All the vertices in the vertex class should be the same distance from * the origin. Dirichlet_extras.c checks that the distances are indeed * approximately equal, and records their average here. */ double dist; /* * min_dist and max_dist are used locally in vertex_distances() * in Dirichlet_extras.c to check that the dist values of the * constituent vertices are consistent. */ double min_dist, max_dist; /* * belongs_to_region and is_3_ball are used locally in * compute_spine_radius() in Dirichlet_extras.c. * belongs_to_region keeps track of how various regions * have been united. is_3_ball records which such unified * regions are topologically 3-balls. */ WEVertexClass *belongs_to_region; Boolean is_3_ball; /* * The WEVertexClasses are kept on a doubly-linked list. */ WEVertexClass *prev, *next; }; struct WEEdgeClass { /* * At present the WEEdgeClasses are listed in arbitrary order, * but if necessary they could easily be sorted to provide * some control over their hues, as is done for WEFaceClasses. */ int index; double hue; int num_elements; /* * The total dihedral angle surrounding this edge * (2pi for a manifold, 2pi/n for an orbifold). */ double dihedral_angle; /* * The "n" in the preceding 2pi/n is recorded as the singularity_order. */ int singularity_order; /* * All the edges in the edge class should be the same distance from * the origin. Dirichlet_extras.c checks that the distances are indeed * approximately equal, and records their average here. */ double dist_line_to_origin, dist_edge_to_origin; /* * How long is the identified edge? * If it's infinite, the length is set to INFINITE_LENGTH. */ double length; /* * Performing the face identifications on the Dirichlet domain gives * a manifold or orbifold. The link of the midpoint of an edge will * be a 2-orbifold. */ Orbifold2 link; /* * min_line_dist and max_line_dist are used locally in edge_distances() * in Dirichlet_extras.c to check that the dist_line_to_origin values * of the constituent edges are consistent. */ double min_line_dist, max_line_dist; /* * min_length and max_length are used locally in edge_lengths() * in Dirichlet_extras.c to check that the length values * of the constituent edges are consistent. */ double min_length, max_length; /* * removed is used locally in compute_spine_radius() in Dirichlet_extras.c * to keep track of which 2-cells in the dual spine have been removed. */ Boolean removed; /* * The WEEdgeClasses are kept on a doubly-linked list. */ WEEdgeClass *prev, *next; }; struct WEFaceClass { /* * Indices are assigned to face classes in order of increasing * distance from the origin. The closest face class gets index 0, * the next closest gets index 1, etc. (The distance is actually the * distance from the origin to the plane containing the face, whether * or not the face happens to include the point where the plane is * closest to the origin.) * * Lemma. A face and its mate are the same distance from the origin. * * Proof. The face plane is midway between the origin and the * origin's image under the group_element. d(g^-1(origin), origin) * = d(origin, g(origin)). Q.E.D. * * The function index_to_hue() in index_to_hue.c insures that * the largest faces have easily distinguishable colors. For example, * a (37,1) Dehn surgery on the figure eight knot yields a Dirichlet * domain with a few large faces and many tiny ones. We wouldn't want * to color the large faces with, say, twelve different shades of blue. * The index-to-hue conversion scheme spreads their hues evenly through * the spectrum. */ int index; double hue; /* * Typically a WEFaceClass will have two elements, but if a face is * glued to itself (in an orbifold), then the WEFaceClass will have * only one element. */ int num_elements; /* * The distance from the face plane to the origin. The point of * closest approach may or may not lie on the face itself. */ double dist; /* * Is the gluing orientation_reversing or orientation_preserving? */ MatrixParity parity; /* * The WEFaceClasses are kept on a doubly-linked list. */ WEFaceClass *prev, *next; }; struct WEPolyhedron { int num_vertices, num_edges, num_faces; int num_finite_vertices, num_ideal_vertices; int num_vertex_classes, num_edge_classes, num_face_classes; int num_finite_vertex_classes, num_ideal_vertex_classes; /* * Because matrices in O(3,1) tend to accumulate roundoff error, it's * hard to get a good bound on the accuracy of the computed volume. * Nevertheless, the kernel computes the best value it can, with the * hope that it will aid the user in recognizing manifolds defined * by a set of generators. (The volume of a manifold defined by * Dehn filling a Triangulation can be computed directly to great * accuracy, using the kernel's volume() function.) */ double approximate_volume; /* * The inradius is the radius of the largest sphere (centered at the * basepoint) which can be inscribed in the Dirichlet domain. * The outradius is the radius of the smallest sphere (centered at the * basepoint) which can be circumscribed about the Dirichlet domain. * The outradius will be infinite for cusped manifolds, in which * case it's set to INFINITE_RADIUS. */ double inradius, outradius; /* * spine_radius is the infimum of the radii (measured from the origin) * of all spines dual to the Dirichlet domain. compute_spine_radius() * in Dirichlet_extras.c shows that spine_radius equals the maximum * of dist_edge_to_origin over all edge classes. The spine_radius * plays an essential role in the length spectrum routines. * * Note: In practice compute_spine_radius() removes selected 2-cells * from the spine to reduce its radius, and thereby reduce the time * required to compute length spectra. Please see compute_spine_radius() * in Dirichlet_extras.c for details. */ double spine_radius; /* * Each face pairing isometry is an element of SO(3,1), so the inner * products of its i-th column with its j-th column should be -1 (if * i = j = 0), +1 (if i = j != 0) or 0 (if i != j). The greatest * deviation from these values (over all faces) is recorded in the * deviation field. */ double deviation; /* * The geometric Euler characteristic of the quotient orbifold (i.e. the * orbifold obtained by doing the face identifications) is computed as * * c[0] - c[1] + c[2] - c[3] * * where * * c[0] = the sum of the solid angles at the vertices divided by 4 pi, * * c[1] = the sum of the dihedral angles at the edges divided by 2 pi, * * c[2] = half the number of faces of the Dirichlet domain, * * c[3] = the number of 3-cells, which is always one. * * This corresponds to the definition of the Euler characteristic of an * orbifold, as explained in Chapter 5 of the 1991 version of Thurston's * notes. It should, in theory, always come out to zero. But since * we compute it using floating point approximations to the solid and * dihedral angles, it provides a measure of the numerical inaccuracies * in the computation. */ double geometric_Euler_characteristic; /* * vertex_epsilon is used in the construction of the Dirichlet domain. * If the squared distance from a vertex to a hyperplane is within * vertex_epsilon of zero, the vertex is assumed to lie on the hyperplane. * If vertex_epsilon is too large, we won't be able to resolve the small * faces which occur in high order Dehn fillings. If vertex_epsilon is * too small, we'll get spurious Dirichlet plane intersections for * manifolds with simple, symmetrical, non-general-position covering * transformation groups. */ double vertex_epsilon; /* * The following dummy nodes serve as the beginnings and ends of the * doubly-linked lists of vertices, edges and faces. */ WEVertex vertex_list_begin, vertex_list_end; WEEdge edge_list_begin, edge_list_end; WEFace face_list_begin, face_list_end; /* * The following dummy nodes serve as the beginnings and ends of the * doubly-linked lists of vertex classes, edge classes and face classes. */ WEVertexClass vertex_class_begin, vertex_class_end; WEEdgeClass edge_class_begin, edge_class_end; WEFaceClass face_class_begin, face_class_end; }; #endif snappea-3.0d3/SnapPeaKernel/makefile-client0100644000175000017500000000274207041646544016750 0ustar babbab# You may modify this file to serve as a makefile # for your own program using the SnapPea kernel. # In the simplest case, all you need to do is insert # the name of your program (here) and the location # of the SnapPea kernel code (below). Your program # (and this makefile) can live in any directory -- # they needn't be with the SnapPea kernel. YOUR_PROGRAM = NameOfYourProgram # Where is the SnapPea kernel source code on your computer? SNAPPEA_KERNEL = /home/jeff/SnapPea/kernel # Compiler options. # -g debugging code # -O optimize CFLAGS = -g -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations # This tells the compiler where to look for the header files. HEADER_PATH = -I$(SNAPPEA_KERNEL)/headers/ -I$(SNAPPEA_KERNEL)/unix_kit/ $(YOUR_PROGRAM): $(YOUR_PROGRAM).o KernelObjects/BuildDate gcc -o $(YOUR_PROGRAM) $(CFLAGS) \ $(YOUR_PROGRAM).o \ KernelObjects/*.o \ -lm $(YOUR_PROGRAM).o: $(YOUR_PROGRAM).c gcc -c $(CFLAGS) $(HEADER_PATH) $(YOUR_PROGRAM).c KernelObjects/BuildDate: $(SNAPPEA_KERNEL)/code/*.c \ $(SNAPPEA_KERNEL)/headers/*.h \ $(SNAPPEA_KERNEL)/unix_kit/* mkdir -p KernelObjects cd KernelObjects; \ date > BuildDate; \ gcc -c $(CFLAGS) $(HEADER_PATH) \ $(SNAPPEA_KERNEL)/code/*.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_UI.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_cusped_census.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_file_io.c clean: rm -f $(YOUR_PROGRAM).o rm -rf KernelObjects rm -f *.pyc snappea-3.0d3/SnapPeaKernel/code/0040755000175000017500000000000007014026422014667 5ustar babbabsnappea-3.0d3/SnapPeaKernel/code/abelian_group.c0100444000175000017500000002067006742675501017661 0ustar babbab/* * abelian_group.c * * This file contains the following functions which the kernel * provides for the UI: * * void expand_abelian_group(AbelianGroup *g); * void compress_abelian_group(AbelianGroup *g); * void free_abelian_group(AbelianGroup *g); * * expand_abelian_group() expands an AbelianGroup into its most * factored form, e.g. Z/2 + Z/2 + Z/4 + Z/3 + Z/9 + Z. * Each nonzero torsion coefficient is a power of a prime. * These are the "primary invariants" of the group * (cf. Hartley & Hawkes' Rings, Modules and Linear Algebra, * Chapman & Hall 1970, page 154). * * compress_abelian_group compresses an AbelianGroup into its least * factored form, e.g. Z/2 + Z/6 + Z/36 + Z. * Each torsion coefficient divides all subsequent torsion coefficients. * These are the "torsion invariants" of the group * (cf. Hartley & Hawkes' Rings, Modules and Linear Algebra, * Chapman & Hall 1970, page 153). * * free_abelian_group() frees the memory used to hold the AbelianGroup *g. * As explained in the documentation preceding the definition of an * AbelianGroup in SnapPea.h, only the kernel may allocate or deallocate * AbelianGroups. */ #include "kernel.h" #include /* needed for qsort() */ typedef struct prime_power { int prime, power; struct prime_power *next; } PrimePower; static int CDECL compare_prime_powers(const void *pp0, const void *pp1); void expand_abelian_group( AbelianGroup *g) { PrimePower *prime_power_list, *new_prime_power, **array_of_pointers, *this_prime_power; int num_prime_powers, torsion_free_rank; long int m, p, this_coefficient; int i, count; /* * The documentation at the top of this file specifies the behavior * of expand_abelian_group(). */ /* * Ignore nonexistent groups. */ if (g == NULL) return; /* * The algorithm is to factor each torsion coefficient into prime * powers, combine the prime powers for all the torsion coefficients * into a single list, sort the list, and write the results back * to the torsion coefficients array (after allocating more memory * for the array, of course). */ prime_power_list = NULL; num_prime_powers = 0; torsion_free_rank = 0; for (i = 0; i < g->num_torsion_coefficients; i++) { /* * For notational convenience, let m be the torsion coefficient * under consideration. */ m = g->torsion_coefficients[i]; /* * If m is zero (indicating an infinite cyclic factor), make a * note of it and move on to the next torsion coefficient. */ if (m == 0L) { torsion_free_rank++; continue; } /* * Factor m. * (Much more efficient algorithms could be used to factor m, but at the * moment I'm assuming the numbers involved will be small, so the * simplicity of the code is more important than its efficiency.) */ /* * Consider each potential prime divisor p of m. * (We "accidently" consider composite divisors as well, * but the wasted effort shouldn't be too significant.) */ for (p = 2; m > 1L; p++) { /* * Does p divide m? * If so, then p must be prime, since otherwise some previous value * of p would have divided m, and would have been factored out. * Find the largest power of p which divides m, and record it on * the prime_power_list. */ if (m%p == 0L) { new_prime_power = NEW_STRUCT(PrimePower); new_prime_power->prime = p; new_prime_power->power = 0; new_prime_power->next = prime_power_list; prime_power_list = new_prime_power; num_prime_powers++; while (m%p == 0L) { m /= p; new_prime_power->power++; } } /* * If m is less than p^2, then m must be prime or one. * * if m is prime, we set p = m - 1, so that on the next * pass through the loop (after the "p++"), p will * equal m, and we'll finish up. * * If m is one, then the loop will terminate no matter * what p is, so there's no harm in setting p = m - 1. */ if (m < p * p) p = m - 1; } } /* * Set num_torsion_coefficients, and reallocate space * for the (presumably larger) array. */ g->num_torsion_coefficients = num_prime_powers + torsion_free_rank; if (g->torsion_coefficients != NULL) my_free(g->torsion_coefficients); if (g->num_torsion_coefficients > 0) g->torsion_coefficients = NEW_ARRAY(g->num_torsion_coefficients, long int); else g->torsion_coefficients = NULL; /* * If the list of PrimePowers is nonempty, sort it and read it * into the torsion_coefficients array. */ if (num_prime_powers > 0) { /* * Create an array of pointers to the PrimePowers. */ array_of_pointers = NEW_ARRAY(num_prime_powers, PrimePower *); for ( i = 0, this_prime_power = prime_power_list; i < num_prime_powers; i++, this_prime_power = this_prime_power->next) array_of_pointers[i] = this_prime_power; if (this_prime_power != NULL) uFatalError("expand_abelian_group", "abelian_group"); qsort(array_of_pointers, num_prime_powers, sizeof(PrimePower *), compare_prime_powers); for (i = 0; i < num_prime_powers; i++) { /* * Multiply out the current torsion coefficient . . . */ this_coefficient = 1L; for (count = 0; count < array_of_pointers[i]->power; count++) this_coefficient *= array_of_pointers[i]->prime; /* * . . . write it into the array . . . */ g->torsion_coefficients[i] = this_coefficient; /* * . . . and free the PrimePower. */ my_free(array_of_pointers[i]); } my_free(array_of_pointers); } /* * Write a torsion coefficient of zero for each infinite cyclic factor. */ for (i = num_prime_powers; i < g->num_torsion_coefficients; i++) g->torsion_coefficients[i] = 0L; } static int CDECL compare_prime_powers( const void *pp0, const void *pp1) { if ((*(PrimePower **)pp0)->prime < (*(PrimePower **)pp1)->prime) return -1; if ((*(PrimePower **)pp0)->prime > (*(PrimePower **)pp1)->prime) return +1; if ((*(PrimePower **)pp0)->power < (*(PrimePower **)pp1)->power) return -1; if ((*(PrimePower **)pp0)->power > (*(PrimePower **)pp1)->power) return +1; return 0; } void compress_abelian_group( AbelianGroup *g) { int i, ii, j; long int m, n, d; /* * The documentation at the top of this file specifies the behavior * of compress_abelian_group(). */ /* * Ignore nonexistent groups. */ if (g == NULL) return; /* * Beginning at the start of the list, adjust each torsion coefficient * so that it divides all subsequent torsion coefficients. */ for (i = 0; i < g->num_torsion_coefficients; i++) for (j = i + 1; j < g->num_torsion_coefficients; j++) { /* * For clarity, let the torsion coefficients under * consideration be called m and n. */ m = g->torsion_coefficients[i]; n = g->torsion_coefficients[j]; /* * If both m and n are zero, go on to the next * iteration of the loop. */ if (m == 0L && n == 0L) continue; /* * Compute the greatest common divisor of m and n. * Note that the greatest common divisor, which is * defined iff m and n are not both zero, will always * be nonzero. */ d = gcd(m, n); /* * Define m' = m/d. Replace m with d, and n with n * m'. * To convince yourself that this is valid, consider * the factorizations of m and n into powers of primes. * In effect, we are swapping the higher power of a given * prime from m to n, e.g. {m = 8, n = 4} -> {m = 4, n = 8}. */ n *= m / d; m = d; /* * Write the values of m and n back into the torsion_coefficients * array. */ g->torsion_coefficients[i] = m; g->torsion_coefficients[j] = n; } /* * Delete torsion coefficients of one, and adjust * g->num_torsion_coefficients accordingly. */ /* * First set ii to be the index of the first non-one, if any. */ for ( ii = 0; ii < g->num_torsion_coefficients && g->torsion_coefficients[ii] == 1L; ii++) ; /* * Now shift all the non-ones to the start of the array, * overwriting the ones. (Note that this algorithm is valid * even if the array contains all ones, or no ones.) */ for (i = 0; ii < g->num_torsion_coefficients; i++, ii++) g->torsion_coefficients[i] = g->torsion_coefficients[ii]; /* * Set g->num_torsion_coefficients to its correct value; */ g->num_torsion_coefficients = i; } void free_abelian_group( AbelianGroup *g) { if (g != NULL) { if (g->torsion_coefficients != NULL) my_free(g->torsion_coefficients); my_free(g); } } snappea-3.0d3/SnapPeaKernel/code/canonize.c0100444000175000017500000000236406742675501016660 0ustar babbab/* * canonize.c * * This file provides the function * * FuncResult canonize(Triangulation *manifold); * * canonize() is a shell, which calls the functions * * proto_canonize() [found in canonize_part_1.c] * canonical_retriangulation() [found in canonize_part_2.c] * * The purpose of these functions is explained in the code below. * For the mathematical details, please see canonize_part_1.c * and canonize_part_2.c. * * canonize() does not preserve the original Triangulation; * if you need to keep it, make a copy with copy_triangulation() * before calling canonize(). */ #include "kernel.h" FuncResult canonize( Triangulation *manifold) { /* * Apply the tilt theorem to compute a Triangulation * which is a subdivision of the canonical cell decomposition. * Please see canonize_part_1.c for details. */ if (proto_canonize(manifold) == func_failed) return func_failed; /* * Replace the given subdivision of the canonical cell * decomposition with the canonical retriangulation. * This operation introduces finite vertices whenever * the canonical cell decomposition is not a triangulation * to begin with. Please see canonize_part_2.c for details. */ canonical_retriangulation(manifold); return func_OK; } snappea-3.0d3/SnapPeaKernel/code/canonize_part_1.c0100444000175000017500000003660507041071076020121 0ustar babbab/* * canonize_part_1.c * * This file provides the function * * FuncResult proto_canonize(Triangulation *manifold); * * which replaces a Triangulation by the canonical triangulation * of the same manifold (if the canonical cell decomposition is a * triangulation) or by an arbitrary subdivision of the canonical * cell decomposition into Tetrahedra (if the canonical cell * decomposition contains cells other than tetrahedra). The * primary purpose of proto_canonize() is to be called by the * function canonize() (in canonize.c), although it can be called * for other reasons, too, such as checking whether a manifold * is a 2-bridge knot or link complement. * * proto_canonize() does not preserve the original Triangulation * (if you want to keep it, copy it with copy_triangulation() before * calling proto_canonize()). The Triangulation must admit a hyperbolic * structure. If one is not already present, proto_canonize() will call * find_complete_hyperbolic_structure() to compute it. * * proto_canonize() returns * * func_OK if the hyperbolic structure is of type * geometric_solution or nongeometric_solution * (in which case it will have found a subdivision * of the canonical cell decomposition) * * func_failed if the hyperbolic structure is of type * flat_solution, degenerate_solution, other_solution, * or no_solution (in which case it will not have * attempted the canonization) * * When proto_canonize() returns func_OK, a subdivision of the * canonical cell decomposition will be present, but because of the * possible need for random retriangulation (see below) I cannot * prove that proto_canonize() will terminate in all cases (but in * practice it always does). * * proto_canonize() uses the algorithm of * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * The Tilt Theorem (contained in the above paper) is generalized * and given a nicer proof in * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * Although the algorithm of "Convex hulls..." works fine in practice, * it has the aesthetic flaw that it does occasionally get stuck on * negatively oriented Tetrahedra, in which case it randomly retriangulates * the manifold and starts over. I've tried to find a new algorithm * which avoids this problem (while retaining the present algorithm's * virtues, namely that it's super fast, and provides an iron-clad * guarantee that the topology of the manifold does not change), but * so far without success. Maybe someday. * * Technical note: The canonization algorithm uses only the complete * hyperbolic structure, but the low-level retriangulation operations * (e.g. two_to_three()) will object if the filled structure is degenerate. * We take a strong precaution: we overwrite the filled structure with * the complete structure and let polish_hyperbolic_structures() recompute * the correct filled structure at the end. This really isn't as * inefficient as it sounds. Either the filled structure will be fairly * close to the complete structure, in which case it will be computed * quickly, or it will be far from the complete structure, in which * case the need to provide valid shape histories will force * polish_hyperbolic_structures() to recompute the filled structure * from scratch anyhow. * * Programming note: The old version of canonize.c shuffled the * tetrahedra about on various queues. The present code does not * do this. Instead, every time it makes some progress (cancellation * or a 2-3 or 3-2 move) it starts the loop over. This causes some * unnecessary scanning of the EdgeClass list, but I feel the wasted * time is small, and is more than compensated for by the reduced * complexity of the code. */ #include "kernel.h" #include "canonize.h" #define MAX_ATTEMPTS 64 #define MAX_RETRIANGULATIONS 64 #define ANGLE_EPSILON 1e-6 static FuncResult validate_hyperbolic_structure(Triangulation *manifold); static Boolean attempt_cancellation(Triangulation *manifold); static Boolean attempt_three_to_two(Triangulation *manifold); static Boolean concave_edge(EdgeClass *edge); static Boolean attempt_two_to_three(Triangulation *manifold); static Boolean concave_face(Tetrahedron *tet, FaceIndex f); static double sum_of_tilts(Tetrahedron *tet0, FaceIndex f0); static Boolean would_create_negatively_oriented_tetrahedra(Tetrahedron *tet0, FaceIndex f0); static Boolean validate_canonical_triangulation(Triangulation *manifold); FuncResult proto_canonize( Triangulation *manifold) { /* * 95/10/12 JRW * I added the needs_polishing flag so that the solution will be * polished iff it needs to be. In the past this was no big deal, * but now we want the cusp neighborhoods module to be able to * maintain a canonical triangulation in real time. If no changes * need to be made and all cusps are complete (so we don't have to * worry about restoring the filled solution), we want to get out * of here as quickly as possible. */ Boolean all_done, needs_polishing; int num_attempts; num_attempts = 0; needs_polishing = FALSE; do { /* * First make sure that a hyperbolic structure is present, and * all Tetrahedra are positively oriented. Overwrite the filled * structure with the complete one. */ if (manifold->solution_type[complete] == geometric_solution && all_cusps_are_complete(manifold) == TRUE) { /* * This is the express route. * * No polishing will be required if the triangulation is * already canonical, because the hyperbolic structure won't * change and there is no need to restore the filled solution. */ } else { /* * This is the generic algorithm. * * (validate_hyperbolic_structure() overwrites the filled * solution with the complete solution.) */ if (validate_hyperbolic_structure(manifold) == func_failed) { compute_CS_fudge_from_value(manifold); return func_failed; } needs_polishing = TRUE; } /* * Choose cusp cross sections bounding equal volumes, * and use the Tilt Theorem to compute the tilts. * (See "Convex hulls..." for details.) */ allocate_cross_sections(manifold); compute_cross_sections(manifold); compute_tilts(manifold); /* * Keep going through the following loop as long as we * continue to keep improving the triangulation. * Do not perform any operation (i.e. any two_to_three() * move) that would create negatively oriented Tetrahedra. */ while (TRUE) { /* * Cancel pairs of Tetrahedra sharing an EdgeClass * of order two. (Since the Triangulation contains * no negatively oriented Tetrahedra, Tetrahedra sharing * an EdgeClass of order two will be within epsilon of * being flat.) */ if (attempt_cancellation(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * Perform 3-2 moves whereever necessary. */ if (attempt_three_to_two(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * Perform 2-3 moves whereever necessary. */ if (attempt_two_to_three(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * We can't make any more progress. * Break out of the loop, and then check whether we've * really found a subdivision of the canonical cell * decomposition, or whether we've had the misfortune to * get stuck on (potential) negatively oriented Tetrahedra. */ break; } /* * We don't need the VertexCrossSections any more, so * we might as well get rid of them before (possibly) * randomizing the manifold. */ free_cross_sections(manifold); /* * Did we really find a subdivision of the canonical * cell decomposition? * Or did we just get stuck on (potential) negatively * oriented Tetrahedra? */ all_done = validate_canonical_triangulation(manifold); /* * If we got stuck on (potential) negatively oriented * Tetrahedra, randomize the Triangulation and try * again. */ if (all_done == FALSE) randomize_triangulation(manifold); /* * The documentation says that if a hyperbolic structure * with all positively oriented tetrahedra is present, then * proto_canonize() will never fail. And indeed with enough * random retriangulations it should always be able to find * a subdivision of the canonical cell decomposition. But * if a bug shows up somewhere we don't want this loop to run * forever, so if num_attempts exceeds MAX_ATTEMPTS we should * declare a fatal error and quit. */ if (++num_attempts > MAX_ATTEMPTS) uFatalError("proto_canonize", "canonize_part_1"); } while (all_done == FALSE); /* * Clean up. */ if (needs_polishing == TRUE) { /* * polish_hyperbolic_structures() takes responsibility for * the shape_histories. */ tidy_peripheral_curves(manifold); polish_hyperbolic_structures(manifold); /* * If the Chern-Simons invariant is present, update the fudge factor. */ compute_CS_fudge_from_value(manifold); } return func_OK; } static FuncResult validate_hyperbolic_structure( Triangulation *manifold) { int i; /* * First make sure some sort of solution is in place. */ if (manifold->solution_type[complete] == not_attempted) find_complete_hyperbolic_structure(manifold); /* * If the solution is something other than geometric_solution * or nongeometric_solution, we're out of luck. */ if (manifold->solution_type[complete] != geometric_solution && manifold->solution_type[complete] != nongeometric_solution) return func_failed; /* * Overwrite the filled structure with the complete structure * to keep the low-level retriangulation routines happy. * (See the technical note at the top of this file for a * more complete explanation.) */ copy_solution(manifold, complete, filled); /* * If all Tetrahedra are positively oriented, we're golden. */ if (manifold->solution_type[complete] == geometric_solution) return func_OK; /* * Try to find a geometric_solution by randomizing the Triangulation. * If we can't find one within MAX_RETRIANGULATIONS randomizations, * give up and return func_failed. */ for (i = 0; i < MAX_RETRIANGULATIONS; i++) { randomize_triangulation(manifold); if (manifold->solution_type[complete] == geometric_solution) return func_OK; } /* * Before we go, we'd better restore the filled solution. */ polish_hyperbolic_structures(manifold); return func_failed; } static Boolean attempt_cancellation( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 2) if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; return FALSE; } static Boolean attempt_three_to_two( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; /* * Note: It's easy to prove that if the three original Tetrahedra * are positively oriented, then the two new Tetrahedra must be * positively oriented as well. So we needn't worry about negatively * oriented Tetrahedra here. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 3) if (concave_edge(edge) == TRUE) { if (three_to_two(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * The only reason three_to_two() can fail is that the * three Tetrahedra surrounding the EdgeClass are not * distinct. But by Corolloary 3 of "Convex hulls..." * this cannot happen where the hull is concave. */ uFatalError("attempt_three_to_two", "canonize_part_1"); } return FALSE; } static Boolean concave_edge( EdgeClass *edge) { Tetrahedron *tet; FaceIndex f; /* * The hull in Minkowski space will be concave at an EdgeClass * of order 3 iff it is concave at each of the ideal 2-simplices * incident to the EdgeClass. */ tet = edge->incident_tet; f = one_face_at_edge[edge->incident_edge_index]; /* * According to Section 5 of "Convex hulls..." or Proposition 1.2 * of "Canonical cell decompositions...", a dihedral angle on the * hull will be concave iff the sum of the tilts is positive. * * We want to create a triangulation with as few Tetrahedra as possible, * so when the sum of the tilts is zero, we should return TRUE so the * three_to_two() move will be performed. Thus we return TRUE iff * the sum of the tilts is greater than some small negative epsilon. */ return ( sum_of_tilts(tet, f) > - CONCAVITY_EPSILON ); } static Boolean attempt_two_to_three( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (concave_face(tet, f) == TRUE && would_create_negatively_oriented_tetrahedra(tet, f) == FALSE) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * We should never get to this point. */ uFatalError("attempt_two_to_three", "canonize_part_1.c"); } return FALSE; } static Boolean concave_face( Tetrahedron *tet, FaceIndex f) { /* * According to Section 5 of "Convex hulls..." or Proposition 1.2 * of "Canonical cell decompositions...", a dihedral angle on the * hull will be concave iff the sum of the tilts is positive. * * If the sum of the tilts is zero we want to return FALSE, * to avoid an unnecessary two_to_three() move. * So we check whether the sum is greater than some small * positive epsilon. */ return ( sum_of_tilts(tet, f) > CONCAVITY_EPSILON ); } static double sum_of_tilts( Tetrahedron *tet0, FaceIndex f0) { Tetrahedron *tet1; FaceIndex f1; tet1 = tet0->neighbor[f0]; f1 = EVALUATE(tet0->gluing[f0], f0); return ( tet0->tilt[f0] + tet1->tilt[f1] ); } static Boolean would_create_negatively_oriented_tetrahedra( Tetrahedron *tet0, FaceIndex f0) { Permutation gluing; Tetrahedron *tet1; FaceIndex f1, side0, side1; gluing = tet0->gluing[f0]; tet1 = tet0->neighbor[f0]; f1 = EVALUATE(gluing, f0); /* * tet0 and tet1 meet at a common 2-simplex. For each edge * of that 2-simplex, add the incident dihedral angles of * tet0 and tet1. If any such sum is greater than pi, then * the two_to_three() move would create a negatively oriented * Tetrahedron on that side, and we return TRUE. Otherwise * no negatively oriented Tetrahedra will be created, and we * return FALSE. * * Choose ANGLE_EPSILON to allow the creation of Tetrahedra which * are just barely negatively oriented, but essentially flat. */ for (side0 = 0; side0 < 4; side0++) { if (side0 == f0) continue; side1 = EVALUATE(gluing, side0); if (tet0->shape[complete]->cwl[ultimate][edge3_between_faces[f0][side0]].log.imag + tet1->shape[complete]->cwl[ultimate][edge3_between_faces[f1][side1]].log.imag > PI + ANGLE_EPSILON) return TRUE; } return FALSE; } static Boolean validate_canonical_triangulation( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; /* * Check whether the sum of the tilts is nonnegative at each 2-simplex. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (concave_face(tet, f) == TRUE) return FALSE; return TRUE; } snappea-3.0d3/SnapPeaKernel/code/canonize_part_2.c0100444000175000017500000004603507041100144020105 0ustar babbab/* * canonize_part_2.c * * This file provides the function * * void canonical_retriangulation(Triangulation *manifold); * * which accepts an arbitrary subdivision of a canonical cell decomposition * into Tetrahedra, and replaces it with a canonical retriangulation * (defined below). If the canonical cell decomposition is itself a * triangulation (as is typically the case) then the canonical retriangulation * is just the canonical cell decomposition itself. If the canonical cell * decomposition is not a triangulation, the canonical retriangulation will * introduce a finite vertex at the center of each 3-cell. * canonical_retriangulation() is intended to follow a call to proto_canonize() * (cf. the function canonize() in canonize.c); it assumes the tet->tilt[] * fields are correct. * * If *manifold has a hyperbolic structure and/or VertexCrossSections, * they are discarded. * * * Definition of the canonical retriangulation. * * The canonical cell decomposition of a cusped hyperbolic 3-manifold * is defined in * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * and * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * If the canonical cell decomposition is a triangulation (as is * typically the case), then the canonical retriangulation is just * the canonical cell decomposition itself. Otherwise, the canonical * retriangulation is defined by the following two step procedure. * * Step #1. Subdivide each 3-cell by coning its boundary to a point * in its interior. * * Step #2. Each 2-cell in the canonical cell decomposition (just * the original 2-cells, not the 2-cells introduced in Step #1) * is the boundary between two 3-cells created at Step #1. * The union of the two 3-cells is the suspension of the * 2-cell between two of the vertices introduced in Step #1. * Triangulate this suspension in the "obvious" symmetrical way, * as n tetrahedra surrounding a common edge, where n is the * number of sides of the 2-cell, and the common edge runs * from one finite vertex to the other. * * This two-step procedure defines a canonical retriangulation of * the canonical cell decomposition. Note that it's not a subdivision. * * * Computing the canonical retriangulation. * * The following is a mathematical explanation of the algorithm. * The details of the implementation are documented in the code itself. * * We begin with an arbitrary subdivision of the canonical cell * decomposition into Tetrahedra. * * Definition. A 2-cell in the Triangulation is called "opaque" if * it lies in the 2-skeleton of the canonical cell decomposition, * and "transparent" if lies in the interior of a 3-cell of the * canonical decomposition. * * Step #1. * To cone a 3-cell to a point in its interior, first do a one-to-four * move to cone a single Tetrahedron to a point in its interior. * If that was the only Tetrahedron in the 3-cell, we're done. * Otherwise, perform a two-to-three move across a transparent face of * the coned tetrahedron. This yields a coned hexahedron. Continue * in this fashion until all the Tetrahedra in the 3-cell have been * absorbed into the coned polyhedron. This coned polyhedron may have * some pair of faces on its boundary identified to yield the original * 3-cell. Where this occurs, call cancel_tetrahedra() to simplify the * coned polyhedron. (This operation is documented more thoroughly * in the code itself.) * * Step #2. * Do a two-to-three move across each opaque face, then cancel * all pairs of Tetrahedra surrounding EdgeClasses of order 2. * You may take it as an exercise for the reader to prove that this * has the desired effect, or you may read the explanation provided * in the function step_two() below. * * * Programming note: I have coded this algorithm for simplicity * rather than speed. But even though the code is "less efficient" * because it does, e.g., some unnecessary rescanning of lists, I don't * think this will make a measurable difference in practice, and * in any case I think it's a small price to pay to keep the logic * of the code clean and simple. */ #include "kernel.h" #include "canonize.h" static void remove_vertex_cross_sections(Triangulation *manifold); static void attach_canonize_info(Triangulation *manifold); static void free_canonize_info(Triangulation *manifold); static void label_opaque_faces(Triangulation *manifold); static void step_one(Triangulation *manifold); static void initialize_tet_status(Triangulation *manifold); static Boolean cone_3_cell(Triangulation *manifold, int *num_finite_vertices); static Boolean find_unconed_tet(Triangulation *manifold, Tetrahedron **tet0); static Boolean expand_coned_region(Triangulation *manifold); static Boolean attempt_cancellation(Triangulation *manifold); static Boolean verify_coned_region(Triangulation *manifold); static void step_two(Triangulation *manifold); static Boolean eliminate_opaque_face(Triangulation *manifold); void canonical_retriangulation( Triangulation *manifold) { /* * Remove the hyperbolic structures and VertexCrossSections, if any. */ remove_hyperbolic_structures(manifold); remove_vertex_cross_sections(manifold); /* * If the canonical cell decomposition is a triangulation, we're done. * (Note: Comment out this line if you want to invoke the more * elaborate canonical retriangulation scheme for all manifolds, not * just those whose canonical cell decompositions contain cells other * than tetrahedra.) */ if (is_canonical_triangulation(manifold) == TRUE) return; /* * Add a CanonizeInfo field to each Tetrahedron to hold local variables. * These variables must be accessible to the low-level retriangulation * routines in simplify_triangulation.c, which is why we use the * special purpose pointer canonize_info in the Tetrahedron data * structure, rather than using the general purpose "extra" pointer. */ attach_canonize_info(manifold); /* * Note which 2-cells are opaque and which are transparent. */ label_opaque_faces(manifold); /* * Carry out the two step retriangulation algorithm described above. */ step_one(manifold); step_two(manifold); /* * Free the CanonizeInfo fields. */ free_canonize_info(manifold); /* * We can't possibly compute the Chern-Simons invariant * for a manifold with finite vertices, so we set * CS_fudge_is_known to FALSE. However, we can leave * CS_value_is_known as TRUE if it is already TRUE, since * the manifold is still the same. */ manifold->CS_fudge_is_known = FALSE; } static void remove_vertex_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->cross_section != NULL) { my_free(tet->cross_section); tet->cross_section = NULL; } } static void attach_canonize_info( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just to be safe . . . */ if (tet->canonize_info != NULL) uFatalError("attach_canonize_info", "canonize_part_2"); /* * Attach the CanonizeInfo. */ tet->canonize_info = NEW_STRUCT(CanonizeInfo); } } static void free_canonize_info( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the CanonizeInfo structure. */ my_free(tet->canonize_info); /* * Set the canonize_info pointer to NULL just to be safe. */ tet->canonize_info = NULL; } } static void label_opaque_faces( Triangulation *manifold) { Tetrahedron *tet, *nbr_tet; FaceIndex f, nbr_f; double sum_of_tilts; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); sum_of_tilts = tet->tilt[f] + nbr_tet->tilt[nbr_f]; tet->canonize_info->face_status[f] = sum_of_tilts < - CONCAVITY_EPSILON ? opaque_face : transparent_face; } } static void step_one( Triangulation *manifold) { int num_finite_vertices; /* * Initialize each part_of_coned_cell flag to FALSE. */ initialize_tet_status(manifold); /* * Keep track of the number of finite vertices that * have been introduced, so they can be given consecutive * negative integers as indices. */ num_finite_vertices = 0; /* * Cone each 3-cell of the canonical cell decomposition to a point * in its interior. Each call to cone_3_cell() cones a single 3-cell, * so we must keep calling cone_3_cell() until it returns FALSE. */ while (cone_3_cell(manifold, &num_finite_vertices) == TRUE) ; } static void initialize_tet_status( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->canonize_info->part_of_coned_cell = FALSE; } static Boolean cone_3_cell( Triangulation *manifold, int *num_finite_vertices) { Tetrahedron *tet0; /* * Is there a Tetrahedron which is not yet part of a coned cell? * If so, proceed. * If not, return FALSE. */ if (find_unconed_tet(manifold, &tet0) == FALSE) return FALSE; /* * Cone tet0 to its center. This will introduce a finite vertex. * Each of the four new Tetrahedra will have part_of_coned_cell * set to TRUE. The face_status for the "exterior" faces will * be preserved, and the face_status for the "interior" faces * will be set to inside_cone_face. */ one_to_four(tet0, &manifold->num_tetrahedra, -++*num_finite_vertices); /* * Expand the coned region. Whenever a Tetrahedron with * part_of_coned_cell == TRUE borders a Tetrahedron with * part_of_coned_cell == FALSE across a transparent face, * do a two-to-three move across the face to include the * (space occupied by the) latter Tetrahedron in the (growing) * coned region. Keep doing this as long as progress is * being made. The two-to-three move will set * part_of_coned_cell = TRUE for the three new Tetrahedra * it creates. It will preserve the face_status of the * "exterior" faces of the new Tetrahedra, and set the face_status * of the "interior" faces to inside_cone_face. * * (Yes, I know this isn't the optimally efficient way to do this, * but I don't think that's important. Please see the programming * note at the end of the documentation at the top of this file.) */ while (expand_coned_region(manifold) == TRUE) ; /* * If the initial, arbitrary subdivision of the 3-cell contained * edges in its interior (as would be the case with an octahedron, * for example), then the coned 3-cell we just produced will have * some identifications on its boundary (for example, in the case * of the octahedron we'd have a coned decahedron with two adjacent * boundary faces identified to yield the octahedron). Assuming at * least one pair of identified faces are adjacent, we can call * cancel_tetrahedra() to simplify the structure of the coned region * from a coned n-hedron with k pairs of faces identified to a coned * (n-2)-hedron with (k-1) pairs of faces identified. As long as some * pair of identified faces are adjacent (and identified in the obvious * way) we can keep repeating the simplification to arrive at a coned * (n-2k)-hedron with no faces identified, which is exactly what * we want in Step #1 of this algorithm. * * It's theoretically possible to have faces of a coned polyehdron * identified with no pair of identified faces adjacent to each other. * For example, consider the complement of the house-with-two-rooms * in the 3-sphere. Fortunately such examples are so rare and so * complicated that I doubt any will ever show up as triangulations * of 3-cells in canonical cell decompositions. If one did show up, * verify_coned_region() would detect the condition and call uFatalError(). * * Proposition. Identifying two adjacent faces (in the "obvious" way) * creates an EdgeClass of order 2, and this is the only way * EdgeClasses of order 2 arise. * * Proof. It's obvious that such an identification creates an * EdgeClass of order 2. We must prove that this is the only way * they may arise. Consider separately EdgeClasses from the * original Triangulation (i.e. from the original arbitrary subdivision * of the canonical cell decomposition), which connect one ideal * vertex to another, and EdgeClasses which are added during the * coning process, which connect an ideal vertex to a finite vertex. * * Case 1. Original EdgeClasses, which connect one ideal vertex * to another. There are no EdgeClasses of order 2 in the initial * Triangulation, because it is geometric. In a coned polyhedron * (ignoring boundary identifications for the moment) each * EdgeClass connecting one ideal vertex to another is incident * to precisely two of the coned polyhedron's Tetrahedra. If the * EdgeClass lies on the boundary of the 3-cell (of the canonical * cell decomposition) then its true order will be greater than two. * If it lies in the interior of the 3-cell, then the only way for * the order to be exactly two is to have the two adjacent faces * identified. * * Case 2. EdgeClasses added during the coning process, which * connect an ideal vertex to a finite vertex. We assume such * an EdgeClass has order 2, and will deduce a contradiction. * Consider the coned polyhedron, ignoring boundary identifications * for the moment. Consider the two faces on the boundary of * incident to the ideal endpoint of the given EdgeClass. These * faces are part of the original (geometric!) subdivision of the * canonical cell decomposition. They have all three ideal vertices * in common, therefore they must coincide. But this implies that * the boundary component at the ideal vertex of the given EdgeClass * is topologically a sphere. * * Q.E.D. */ while (attempt_cancellation(manifold) == TRUE) ; /* * As explained above, once we've cancelled all Tetrahedra incident * to EdgeClasses of order 2, we will almost surely have the required * coning of the 3-cell's boundary to a point in its interior, unless * we encounter a situation like the complement of the * house-with-two-rooms. verify_coned_region() checks that we * do in fact have a coning of the original 3-cell; that is, * no Tetrahedron with part_of_coned_cell == TRUE has a face with * face_status transparent_face. */ if (verify_coned_region(manifold) == FALSE) uFatalError("cone_3_cell", "canonize_part_2"); return TRUE; } static Boolean find_unconed_tet( Triangulation *manifold, Tetrahedron **tet0) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == FALSE) { *tet0 = tet; return TRUE; } return FALSE; } static Boolean expand_coned_region( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == TRUE) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == transparent_face) if (tet->neighbor[f]->canonize_info->part_of_coned_cell == FALSE) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* this should never occur */ uFatalError("expand_coned_region", "canonize_part_2"); } return FALSE; } static Boolean attempt_cancellation( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 2) { if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * I don't think failure is possible, but if it is * I want to know about it. */ uFatalError("attempt_cancellation", "canonize_part_2"); } return FALSE; } static Boolean verify_coned_region( Triangulation *manifold) { /* * Because one_to_four(), two_to_three() and cancel_tetrahedra() * all maintain the coned polyhedron as some sort of coned * polyhedron, all we need to check is that it has no remaining * identifications on its boundary. That is, we need to check * that the faces of each Tetrahedron with part_of_coned_cell == TRUE * have face_status opaque_face or inside_cone_face, but never * transparent_face. */ Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == TRUE) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == transparent_face) return FALSE; return TRUE; } static void step_two( Triangulation *manifold) { /* * Step #1 of the algorithm has already been carried out, so * we know that every 3-cell in the canonical cell decomposition * has been subdivided by coning the boundary to a point in * the interior. There are three types of EdgeClasses: * * (A) Those which lie in the 1-skeleton of the canonical * cell decomposition. They have order at least 6. * * (B) Those which lie in the 2-skeleton of the canonical * cell decomposition, but not in the 1-skeleton. They * serve to (artificially) subdivide the faces of the 3-cells * into triangles. Each has order precisely 4. * * (C) Those in the interior of the 3-cells. They connect ideal * vertices to finite vertices, and have order at least 3. * * Step #2 of the algorithm consists of two substeps: * * Substep A. Do a two-to-three move across every opaque face. * * Substep B. Perform all possible cancellations of Tetrahedra * surrounding EdgeClasses of order 2. * * After Substep A is complete, we know that * * EdgeClasses of type A will have order at least 3. * EdgeClasses of type B will have order precisely 2. * EdgeClasses of type C will have order at least 6. * The new EdgeClasses introduced in Substep A will have * order precisely 3. * * Therefore Substep B will eliminate precisely the EdgeClasses * of type B. It's easy to see that removing these EdgeClasses * creates the Triangulation specified in Step #2 of the definition * of the canonical retriangulation at the top of this file. */ while (eliminate_opaque_face(manifold) == TRUE) ; while (attempt_cancellation(manifold) == TRUE) ; } static Boolean eliminate_opaque_face( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == opaque_face) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* this should never occur */ uFatalError("expand_coned_region", "canonize_part_2"); } return FALSE; } snappea-3.0d3/SnapPeaKernel/code/canonize_result.c0100444000175000017500000000342206742675501020252 0ustar babbab/* * canonize_result.c * * The function * * Boolean is_canonical_triangulation(Triangulation *manifold); * * accepts a Triangulation which it assumes to be a subdivision of a canonical * cell decomposition (as produced by proto_canonize(), for example), and * says whether it is indeed the canonical cell decomposition itself. * In other words, is_canonical_triangulation() will return TRUE * when the canonical cell decomposition is a triangulation, and FALSE when * it is not. * * is_canonical_triangulation() assumes all Tetrahedra in the Triangulation * have valid "tilt" fields, as will be the case following a call to * proto_canonize(). */ #include "kernel.h" #include "canonize.h" Boolean is_canonical_triangulation( Triangulation *manifold) { Tetrahedron *tet, *nbr_tet; FaceIndex f, nbr_f; double sum_of_tilts; Boolean result; /* * We'll assume the canonical cell decomposition is a triangulation * unless we discover otherwise. */ result = TRUE; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); sum_of_tilts = tet->tilt[f] + nbr_tet->tilt[nbr_f]; /* * The sum of the tilts should never be positive, because * this would mean that we didn't have a subdivision of the * canonical cell decomposition after all. */ if (sum_of_tilts > CONCAVITY_EPSILON) uFatalError("is_canonical_triangulation", "canonize_result"); /* * If the sum of the tilts is zero, then the canonical cell * decomposition contains cell other than tetrahedra. */ if (sum_of_tilts > -CONCAVITY_EPSILON) result = FALSE; /* * Otherwise we're OK. */ } return result; } snappea-3.0d3/SnapPeaKernel/code/change_peripheral_curves.c0100444000175000017500000001536707041112700022064 0ustar babbab/* * change_peripheral_curves.c * * This file provides the function * * FuncResult change_peripheral_curves( * Triangulation *manifold, * CONST MatrixInt22 change_matrices[]); * * If all the change_matrices (there should be one for each Cusp) have * determinant +1, then on each Cusp change_peripheral_curves() replaces * * the meridian with change_matrices[i][0][0] meridians plus * change_matrices[i][0][1] longitudes, and * the longitude with change_matrices[i][1][0] meridians plus * change_matrices[i][1][1] longitudes, * * where i is the index of the Cusp. It returns func_OK. * * If some change_matrix has determinant != +1, change_peripheral_curves() * returns func_bad_input. */ #include "kernel.h" FuncResult change_peripheral_curves( Triangulation *manifold, CONST MatrixInt22 change_matrices[]) { int i, v, f, old_m, old_l; double old_m_coef, /* changed from int to double, JRW 2000/01/18 */ old_l_coef; Tetrahedron *tet; Cusp *cusp; Complex old_Hm, old_Hl; /* * First make sure all the change_matrices have determinant +1. */ for (i = 0; i < manifold->num_cusps; i++) if (DET2(change_matrices[i]) != +1) return func_bad_input; /* * The change_matrices for Klein bottle cusps must have zeros in the * off-diagonal entries. (Nothing else makes sense topologically.) */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->topology == Klein_cusp) for (i = 0; i < 2; i++) if (change_matrices[cusp->index][i][!i] != 0) uFatalError("change_peripheral_curves", "change_peripheral_curves"); /* * Change the peripheral curves according to the change_matrices. * As stated at the top of this file, the transformation rule is * * | new m | | | | old m | * | | = | change_matrices[i] | | | * | new l | | | | old l | */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) /* which orientation */ for (v = 0; v < 4; v++) /* which vertex */ for (f = 0; f < 4; f++) /* which side */ { old_m = tet->curve[M][i][v][f]; old_l = tet->curve[L][i][v][f]; tet->curve[M][i][v][f] = change_matrices[tet->cusp[v]->index][0][0] * old_m + change_matrices[tet->cusp[v]->index][0][1] * old_l; tet->curve[L][i][v][f] = change_matrices[tet->cusp[v]->index][1][0] * old_m + change_matrices[tet->cusp[v]->index][1][1] * old_l; } /* * Change the Dehn filling coefficients to reflect the new * peripheral curves. That is, we want the Dehn filling curves * to be topologically the same as before, even though the * coefficients will be different because we changed the basis. * * To keep our thinking straight, let's imagine all peripheral * curves -- old and new -- in terms of some arbitrary but * fixed basis for pi_1(T^2) = Z + Z. We never actually compute * such a basis, but it helps keep our thinking straight. * Relative to this fixed basis, we have * * old m = (old m [0], old m [1]) * old l = (old l [0], old l [1]) * new m = (new m [0], new m [1]) * new l = (new l [0], new l [1]) * * Note that these m's and l's are curves, not coefficients! * They are elements of pi_1(T^2) = Z + Z. * * We can then rewrite the above transformation rule, with * each peripheral curve (old m, old l, new m and new l) * appearing as a row in a 2 x 2 matrix: * * | <--new m--> | | | | <--old m--> | * | | = | change_matrices[i] | | | * | <--new l--> | | | | <--old l--> | * * We can invert the change_matrix to solve for the old curves * in terms of the new ones: * -1 * | <--old m--> | | | | <--new m--> | * | | = | change_matrices[i] | | | * | <--old l--> | | | | <--new l--> | * * The Dehn filling curve is * * old_m_coef * old_m + old_l_coef * old_l * * = old_m_coef * [ change_matrices[i][1][1] * new_m * - change_matrices[i][0][1] * new_l] * + old_l_coef * [- change_matrices[i][1][0] * new_m * + change_matrices[i][0][0] * new_l] * * = new_m * [ old_m_coef * change_matrices[i][1][1] * - old_l_coef * change_matrices[i][1][0] ] * + new_l * [- old_m_coef * change_matrices[i][0][1] * + old_l_coef * change_matrices[i][0][0] ] * * Therefore * * new_m_coef = old_m_coef * change_matrices[i][1][1] * - old_l_coef * change_matrices[i][1][0] * new_l_coef = - old_m_coef * change_matrices[i][0][1] * + old_l_coef * change_matrices[i][0][0] */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == FALSE) { old_m_coef = cusp->m; old_l_coef = cusp->l; cusp->m = old_m_coef * change_matrices[cusp->index][1][1] - old_l_coef * change_matrices[cusp->index][1][0]; cusp->l = - old_m_coef * change_matrices[cusp->index][0][1] + old_l_coef * change_matrices[cusp->index][0][0]; } /* * Update the holonomies according to the rule * * | new H(m) | | | | old H(m) | * | | = | change_matrices[i] | | | * | new H(l) | | | | old H(l) | * * (These are actually logs of holonomies, so it's correct to * add -- not multiply -- them.) * * For complete Cusps the holonomies should be zero, but that's OK. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_Hm = cusp->holonomy[i][M]; old_Hl = cusp->holonomy[i][L]; cusp->holonomy[i][M] = complex_plus( complex_real_mult( change_matrices[cusp->index][0][0], old_Hm ), complex_real_mult( change_matrices[cusp->index][0][1], old_Hl ) ); cusp->holonomy[i][L] = complex_plus( complex_real_mult( change_matrices[cusp->index][1][0], old_Hm ), complex_real_mult( change_matrices[cusp->index][1][1], old_Hl ) ); } /* * Update the cusp_shapes. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[initial] = transformed_cusp_shape( cusp->cusp_shape[initial], change_matrices[cusp->index]); if (cusp->is_complete == TRUE) cusp->cusp_shape[current] = transformed_cusp_shape( cusp->cusp_shape[current], change_matrices[cusp->index]); /* * else cusp->cusp_shape[current] == Zero, and needn't be changed. */ } return func_OK; } snappea-3.0d3/SnapPeaKernel/code/chern_simons.c0100444000175000017500000006334306742675501017545 0ustar babbab/* * chern_simons.c * * The computation of the Chern-Simons invariant is a little * delicate because the formula depends on a constant which * must initially be supplied by the user. * * For the UI, this file provides the functions * * void set_CS_value( Triangulation *manifold, * double a_value); * void get_CS_value( Triangulation *manifold, * Boolean *value_is_known, * double *the_value, * int *the_precision, * Boolean *requires_initialization); * * The UI calls set_CS_value() to pass to the kernel a user-supplied * value of the Chern-Simons invariant for the current manifold. * * The UI calls get_CS_value() to request the current value. If the * current value is known (or can be computed), get_CS_value() sets * *value_is_known to TRUE and writes the current value and its precision * (the number of significant digits to the right of the decimal point) * to *the_value and *the_precision, respectively. If the current value * is not known and cannot be computed, it sets *value_is_known to FALSE, * and then sets *requires_initialization to TRUE if the_value * is unknown because no fudge factor is available, or * to FALSE if the_value is unknown because the solution contains * negatively oriented Tetrahedra. The UI might want to convey * these situations to the user in different ways. * * get_CS_value() normalizes *the_value to the range (-1/4,+1/4]. * This is the ONLY point in code where such an adjustment is made; * all internal computations are done mod 0. * * * The kernel manages the Chern-Simons computation by keeping track of * both the current value and the arbitrary constant ("fudge factor") * which appears in the formula. It uses the following fields of * the Triangulation data structure: * * Boolean CS_value_is_known, * CS_fudge_is_known; * double CS_value[2], * CS_fudge[2]; * * The Boolean flags indicate whether the corresponding double is * presently known or unknown. To provide an estimate of precision, * CS_value[ultimate] and CS_value[penultimate] store the value of the * Chern-Simons invariant computed relative to the hyperbolic structure * at the ultimate and penultimate iterations of Newton's method, and * similarly for the fudge factor CS_fudge[]. * * For the kernel, this file provides the functions * * void compute_CS_value_from_fudge(Triangulation *manifold); * void compute_CS_fudge_from_value(Triangulation *manifold); * * compute_CS_value_from_fudge() computes the CS_value in terms of * CS_fudge, if CS_fudge_is_known is TRUE. (If CS_fudge_is_known is FALSE, * it sets CS_value_is_known to FALSE as well.) The kernel calls this * function when doing Dehn fillings on a fixed Triangulation, where * the CS_fudge will be known (and constant) but the CS_value will be * changing. * * compute_CS_fudge_from_value() computes the CS_fudge in terms of * CS_value, if CS_value_is_known is TRUE. (If CS_value_is_known is FALSE, * it sets CS_fudge_is_known to FALSE as well.) The kernel calls this * function when it changes a Triangulation without changing the manifold * it represents. * * * Bob Meyerhoff, Craig Hodgson and Walter Neumann have found at least * two different algorithms for computing the Chern-Simons invariant. * The following code allows easy substitution of algorithms, in the * function compute_CS(). * * 96/4/16 David Eppstein pointed out that when he does (1,0) Dehn filling * on m074(1,0), SnapPea quits with the message "The argument in the * dilogarithm function is too large to guarantee accuracy". I've modified * the code so that it displays the message "The argument in the dilogarithm * function is too large to guarantee an accurate value for the Chern-Simons * invariant" but does not quit. Instead it sets * * manifold->CS_value_is_known = FALSE; * or * manifold->CS_fudge_is_known = FALSE; * * (as appropriate) and continues normally. [By the way, I rejected the * idea of providing more coefficients for the series. The set of manifolds * for which the existing coefficients do not suffice is very, very small: * no problems arise for any of the manifolds in the cusped or closed censuses. * (Eppstein's example of m074(1,0) is a 3-sphere, but other descriptions * of the 3-sphere seem to work fine.) So I don't want to slow down the * computation of the Chern-Simons invariant in the generic case for the * sake of an almost vanishingly small set of exceptions.] */ #include "kernel.h" #define CS_EPSILON 1e-8 #define LOG_TWO_PI 1.83787706640934548356 static FuncResult compute_CS(Triangulation *manifold, double value[2]); static FuncResult algorithm_one(Triangulation *manifold, double value[2]); static Complex alg1_compute_Fu(Triangulation *manifold, int which_approximation, Boolean *Li2_error_flag); static Complex Li2(Complex w, ShapeInversion *z_history, Boolean *Li2_error_flag); static Complex log_w_minus_k_with_history(Complex w, int k, double regular_arg, ShapeInversion *z_history); static int get_history_length(ShapeInversion *z_history); static int get_wide_angle(ShapeInversion *z_history, int requested_index); void set_CS_value( Triangulation *manifold, double a_value) { manifold->CS_value_is_known = TRUE; manifold->CS_value[ultimate] = a_value; manifold->CS_value[penultimate] = a_value; compute_CS_fudge_from_value(manifold); } void get_CS_value( Triangulation *manifold, Boolean *value_is_known, double *the_value, int *the_precision, Boolean *requires_initialization) { if (manifold->CS_value_is_known) { *value_is_known = TRUE; *the_value = manifold->CS_value[ultimate]; *the_precision = decimal_places_of_accuracy( manifold->CS_value[ultimate], manifold->CS_value[penultimate]); *requires_initialization = FALSE; /* * Normalize reported value to the range (-1/4, 1/4]. */ while (*the_value < -0.25 + CS_EPSILON) *the_value += 0.5; while (*the_value > 0.25 + CS_EPSILON) *the_value -= 0.5; } else { *value_is_known = FALSE; *the_value = 0.0; *the_precision = 0; *requires_initialization = (manifold->CS_fudge_is_known == FALSE); } } void compute_CS_value_from_fudge( Triangulation *manifold) { double computed_value[2]; if (manifold->CS_fudge_is_known == TRUE && compute_CS(manifold, computed_value) == func_OK) { manifold->CS_value_is_known = TRUE; manifold->CS_value[ultimate] = computed_value[ultimate] + manifold->CS_fudge[ultimate]; manifold->CS_value[penultimate] = computed_value[penultimate] + manifold->CS_fudge[penultimate]; } else { manifold->CS_value_is_known = FALSE; manifold->CS_value[ultimate] = 0.0; manifold->CS_value[penultimate] = 0.0; } } void compute_CS_fudge_from_value( Triangulation *manifold) { double computed_value[2]; if (manifold->CS_value_is_known == TRUE && compute_CS(manifold, computed_value) == func_OK) { manifold->CS_fudge_is_known = TRUE; manifold->CS_fudge[ultimate] = manifold->CS_value[ultimate] - computed_value[ultimate]; manifold->CS_fudge[penultimate] = manifold->CS_value[penultimate] - computed_value[penultimate]; } else { manifold->CS_fudge_is_known = FALSE; manifold->CS_fudge[ultimate] = 0.0; manifold->CS_fudge[penultimate] = 0.0; } } static FuncResult compute_CS( Triangulation *manifold, double value[2]) { Cusp *cusp; /* * We can handle only orientable manifolds. */ if (manifold->orientability != oriented_manifold) return func_failed; /* * Cusps must either be complete, or have Dehn filling * coefficients which are relatively prime integers. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_relatively_prime_integers(cusp) == FALSE) return func_failed; /* * Here we plug in the algorithm of our choice. */ return algorithm_one(manifold, value); } static FuncResult algorithm_one( Triangulation *manifold, double value[2]) { Boolean Li2_error_flag; int i; Complex Fu[2], core_length_sum[2], complex_volume[2], length[2]; int singularity_index; Cusp *cusp; /* * This algorithm is taken directly from Craig Hodgson's * preprint "Computation of the Chern-Simons invariants". * It extends previous implementations in that it uses * the shape_histories of the Tetrahedra to compute * the dilogarithms, which allows solutions with negatively * oriented Tetrahedra. */ /* * To use the Chern-Simons formula, both the complete and filled * solutions must be geometric, nongeometric or flat. */ for (i = 0; i < 2; i++) /* i = complete, filled */ if (manifold->solution_type[i] != geometric_solution && manifold->solution_type[i] != nongeometric_solution && manifold->solution_type[i] != flat_solution) return func_failed; /* * Initialize the Li2_error_flag to FALSE. * If the coefficients in Li2() don't suffice to compute the dilogaritm * to full precision, Li2() will set Li2_error_flag to TRUE. */ Li2_error_flag = FALSE; /* * Compute F(u) relative to the ultimate and penultimate * hyperbolic structures, to allow an estimatation of precision. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ Fu[i] = alg1_compute_Fu(manifold, i, &Li2_error_flag); /* * If Li2() failed, return func_failed; */ if (Li2_error_flag == TRUE) { uAcknowledge("An argument in the dilogarithm function is too large to guarantee an accurate value for the Chern-Simons invariant."); return func_failed; } /* * F(u) is * * (complex volume) + pi/2 (sum of complex core lengths) * * So we subtract off the complex lengths of the core geodesics * to be obtain the complex volume. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ core_length_sum[i] = Zero; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { compute_core_geodesic(cusp, &singularity_index, length); switch (singularity_index) { case 0: /* * The cusp is complete. Do nothing. */ break; case 1: /* * Add this core length to the sum. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ core_length_sum[i] = complex_plus( core_length_sum[i], length[i]); break; default: /* * We should never arrive here. */ uFatalError("algorithm_one", "chern_simons"); } } /* * (complex volume) = F(u) - (pi/2)(sum of core lengths) */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { complex_volume[i] = complex_minus( Fu[i], complex_real_mult( PI_OVER_2, core_length_sum[i] ) ); value[i] = complex_volume[i].imag / (2.0 * PI * PI); } return func_OK; } static Complex alg1_compute_Fu( Triangulation *manifold, int which_approximation, /* ultimate or penultimate */ Boolean *Li2_error_flag) { Complex Fu; Tetrahedron *tet; static const Complex minus_i = {0.0, -1.0}; /* * We compute the function F(u), which Yoshida has proved holomorphic. * (See Craig's preprint mentioned above.) */ /* * Initialize F(u) to Zero. */ Fu = Zero; /* * Add up the log terms. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { Fu = complex_minus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][0].log, tet->shape[ filled ]->cwl[which_approximation][1].log ) ); Fu = complex_plus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][0].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][1].log) ) ); Fu = complex_minus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][1].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][0].log) ) ); Fu = complex_minus( Fu, complex_mult( tet->shape[complete]->cwl[which_approximation][0].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][1].log) ) ); } /* * Multiply through by one half. */ Fu = complex_real_mult(0.5, Fu); /* * Add in the dilogarithms. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * To compute the dilogarithm of z, Li2() wants to be * passed w = log(z) / 2 pi i and the shape_history of z. */ Fu = complex_plus ( Fu, Li2 ( complex_div ( tet->shape[filled]->cwl[which_approximation][0].log, TwoPiI ), tet->shape_history[filled], Li2_error_flag ) ); } /* * Multiply by -i. */ Fu = complex_mult(minus_i, Fu); return Fu; } static Complex Li2( Complex w, ShapeInversion *z_history, Boolean *Li2_error_flag) { /* * Compute the dilogarithm of z = exp(2 pi i w) as explained * in Craig's preprint mentioned above. Note that we use * the variable w instead of the z which appears in Craig's * preprint, to avoid confusion with the z which appears * in the formula for F(u). * * The term Craig calls "S" we compute in two parts * * s0 = sum from i = 1 to infinity . . . * s1 = sum from k = 1 to N . . . + Nw * * The remaining part of the formula we call * * t = pi^2/6 + 2 pi i w - 2 pi i w log(-2 pi i w) + (pi w)^2 */ Complex s0, s1, s, t, w_squared, two_pi_i_w, kk, k_plus_w, k_minus_w, result; int i, k; static const Complex pi_squared_over_6 = {PI*PI/6.0, 0.0}, four_pi_i = {0.0, 4.0*PI}, minus_pi_i = {0.0, -PI}, log_minus_two_pi_i = {LOG_TWO_PI, -PI_OVER_2}; /* * The array a[] contains the coefficients for the infinite series * in s0. The constant num_terms tells how many we need to use to * insure accuracy (see Analysis of Convergence below). * * The following Mathematica code computed these coefficients * for N = 2. (I use "n" where Craig used "N" to conform both * to proramming conventions regarding capital letters, and also * to Mathematica's conventions.) * * a[i_, n_] := * N[(Zeta[2i] - Sum[k^(-2i), {k,1,n}]) / (2i(2i + 1)), 60] * a2[i_] := a[i, 2] * Array[a2, 30] * * Note that to get 20 significant digits in a2[30] = 6.4e-33, we * must request at least 53 decimal places of accuracy from * Mathematica, and probably a little more since the accuracy we * request is the accuracy to which the intermediate calculations * are truncated -- the final accuracy could be a little worse. * By the way, we really do need a lot of that accuracy even in * the tiny coefficients, because they will be multiplied by high * powers of w, and |w| may be greater than one. */ static const int num_terms = 30; static const int n = 2; static const double a[] ={ 0.0, 6.58223444747044060787e-2, 9.91161685556909575800e-4, 4.09062377249795170123e-5, 2.37647497144915803729e-6, 1.63751161982593974054e-7, 1.24738994105660169102e-8, 1.01418480335632980259e-9, 8.62880373230578403363e-11, 7.59064144690016509252e-12, 6.85041587014555123901e-13, 6.30901702974110744035e-14, 5.90712644809102073367e-15, 5.60732930747841393884e-16, 5.38501558411235458177e-17, 5.22344536523359867175e-18, 5.11092595568460128406e-19, 5.03912265560217431595e-20, 5.00200835767964640183e-21, 4.99518851712940000071e-22, 5.01545492014257760830e-23, 5.06048349504093155712e-24, 5.12862546072263579933e-25, 5.21876054821516289501e-26, 5.33019249317297967524e-27, 5.46257395282628942810e-28, 5.61585233364316675625e-29, 5.79023077981676178469e-30, 5.98614037451033538648e-31, 6.20422080422041301360e-32, 6.44530754870034591211e-33}; /* * Analysis of convergence. * * The i-th coefficient in the (partial) zeta function is * * (N+1)^(-2i) + (N+2)^(-2i) + (N+3)^(-2i) + ... * * Lemma. For large i, this series may be approximated by its first * term (N+1)^(-2i). * * Proof. [Probably not worth reading, but I figured I ought to * include it.] Get an upper bound on the sum of the neglected * terms by comparing them to an integral: * * (N+2)^(-2i) + (N+3)^(-2i) + ... * < (N+2)^(-2i) + integral from x = N+2 to infinity of x^(-2i) dx * = (N+2)^(-2i) + (N+2)^(-2i+1)/(2i-1) * < (N+2)^(-2i) + (N+2)^(-2i) * = 2 (N+2)^(-2i) * * Therefore the ratio (error)/(first term) is less than * * [2 (N+2)^(-2i)] / [(N+1)^(-2i)] * = 2 ((N+1)/(N+2))^(2i) * * Thus, for example, if N = 2 and i >= 10, the ratio * (error)/(first term) will be less than 2 (3/4)^10 = 1%. * When N = 4 we need i >= 15 to obtain 1% accuracy. * Q.E.D. * * * The preceding lemma implies that the infinite series * for S has the same convergence behavior as the series * * S' = (w/(N+1))^2i / i^2 * * so we analyze S' instead of S. The error introduced * by truncating the series after some i = i0 is bounded * by the corresponding error in the geometric series * * S" = |w/(N+1)|^2i / i0^2 * * The latter error is * * (first neglected term) / (1 - ratio) * * = (|w/(N+1)|^2i0 / i0^2) / (1 - |w/(N+1)|^2) * * = |w/(N+1)|^2i0 / (i0^2 (1 - |w/(N+1)|^2)) * * A quick calculcation in Mathematica shows that if * we are willing to calculate 30 terms in the series, * then |w/(N+1)| < 0.5 implies the error will be * less than 1e-20. In other words, the series can * be used successfully for |w| < (N+1)/2. What values * of z (i.e. what actual simplex shapes) does this * correspond to? * * Letting w = x + iy, we get * * z = exp(2 pi i (x + i y)) * = exp(-2 pi y + 2 pi i x) * = exp(-2 pi y) * (cos(2 pi x) + i sin(2 pi x)) * * In other words, at an argument of 2 pi x, the acceptable * parameters z are those with moduli between * exp(-2 pi sqrt(((N+1)/2)^2 - x^2)) and * exp(+2 pi sqrt(((N+1)/2)^2 - x^2)). * * For N = 2: * When x = 0 we get values of z along the positive real axis * from 0.00008 to 12000. * When x = 1/2 we get values of z along the negative real axis * from -0.0001 to -7000. * When x = 1 we get values of z along the positive real axis * from 0.0008 to 1000. * * This is good news: it means that the 30-term series for S will * be accurate to 1e-20 for all (reasonable) nondegenerate values * of z. I don't foresee the need for a greater radius of * convergence, but if one is ever needed, just switch to N = 4. */ /* * According to the preceding Analysis of Convergence, our * computations will be accurate to 1e-20 whenever * |w| < (N+1)/2 = 3/2. */ if (complex_modulus(w) > 1.5) { *Li2_error_flag = TRUE; return Zero; } /* * Note the values of w^2 and 2 pi i w. */ w_squared = complex_mult(w, w); two_pi_i_w = complex_mult(TwoPiI, w); /* * Compute t. * * In the third term, - 2 pi i w will lie in the strip * 0 < Im(- 2 pi i w) < - pi i, so we choose the argument * in its log to be in the range (0, - pi). */ t = pi_squared_over_6; t = complex_plus( t, two_pi_i_w ); t = complex_minus( t, complex_mult( two_pi_i_w, complex_plus( log_minus_two_pi_i, log_w_minus_k_with_history(w, 0, 0.0, z_history) ) ) ); t = complex_plus( t, complex_real_mult(PI * PI, w_squared) ); /* * Compute s0. * * Start with the high order terms and work backwards. * It's a little faster, because fewer multiplications * are required, and might also be a little more accurate. */ s0 = Zero; for (i = num_terms; i > 0; --i) { s0.real += a[i]; s0 = complex_mult(s0, w_squared); } s0 = complex_mult(s0, w); /* * Compute s1. */ s1 = Zero; for (k = 1; k <= n; k++) { kk = complex_real_mult(k, One); k_plus_w = complex_plus (kk, w); k_minus_w = complex_minus(kk, w); s1 = complex_plus( s1, complex_real_mult(log(k), w) ); s1 = complex_minus( s1, complex_real_mult( 0.5, complex_mult( k_plus_w, log_w_minus_k_with_history(w, -k, 0.0, z_history) ) ) ); s1 = complex_plus( s1, complex_real_mult( 0.5, complex_mult( k_minus_w, /* * We write Craig's log(k - w), which had an * argument of 0 for the regular case, as * log(-1) + log(w - k), and choose arg(log(-1)) = -pi * and arg(log(w - k)) = +pi for the regular case. */ complex_plus( minus_pi_i, log_w_minus_k_with_history(w, k, PI, z_history) ) ) ) ); } s1 = complex_plus( s1, complex_real_mult(n, w) ); /* * Add t + (4 pi i)(s0 + s1) to get the final answer. */ s = complex_plus(s0, s1); result = complex_plus( t, complex_mult(four_pi_i, s) ); return result; } static Complex log_w_minus_k_with_history( Complex w, int k, double regular_arg, ShapeInversion *z_history) { int which_strip; double estimated_argument; int i; /* * This function computes log(w - k), taking into account the "history" * of the shape z from which w is derived (z = exp(2 pi i w), as * explained above). That is, it takes into account z's precise * path through the parameter space, up to isotopy. * * regular_arg supplies the correct argument for the case of a * regular ideal tetrahedron, with z = (1/2) + (sqrt(3)/2)i, * w = 1/6, and a trivial "history". Typically regular_arg * will be 0 for k <= 0, and +pi for k > 0. * * To understand what's going on here, it will be helpful to make * yourself pictures of the z- and w-planes, as follows: * * z-plane. Draw axes for the complex plane representing z. * Draw small circles at 0 and 1 to show where * z is singular. * Color the real axis blue from -infinity to 0, and label * it '0' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 0. * Color the real axis red from 1 to +infinity, and label * it '1' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 1. * Color the real axis green from 0 to 1, and label * it '2' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 2. * * w-plane. Draw the preimage of the z-plane picture under the * map z = exp(2 pi i w). * The singularities occur at the integer points on * the real axis. * Red half-lines labeled 1 extend from each singularity * downward to infinity. * Green half-lines labeled 2 extend from each singularity * upward to infinity. * Blue lines labelled 0 pass vertically through each * half-integer point on the real axis. * * We will use the z_history to trace the path of w through the * w-plane picture, keeping track of the argument of log(w - k) * as we go. We begin with the shape of a regular ideal tetrahedron, * namely z = (1/2) + (sqrt(3)/2) i, w = 1/6 + 0 i. * * It suffices to keep track of the approximate argument to the * nearest multiple of pi, since the true argument will be within * pi/2 of that estimate. * * The vertical strips in the w-plane (which are preimages of * the halfplane z.imag > 0 and z.imag < 0 in the z-plane) * are indexed by integers. Strip n is the strip extending * from w.real = n/2 to w.real = (n+1)/2. */ /* * We begin at w = 1/6, and set the estimated_argument to * regular_arg (this will typically be 0 if k <= 0, or pi if k > 0, * corresponding to Walter and Craig's choices for the case of * positively oriented Tetrahedra). */ which_strip = 0; estimated_argument = regular_arg; /* * Now we read off the z_history, adjusting which_strip * and estimated_argument accordingly. * * Typically the z_history will be NULL, so nothing happens here. * * Technical note: this isn't the most efficient way to read * a linked list backwards, but clarity is more important than * efficiency here, but the z_histories are likely to be so short. */ for (i = 0; i < get_history_length(z_history); i++) switch (get_wide_angle(z_history, i)) { case 0: /* * If we're in an even numbered strip, move to the right. * If we're in an odd numbered strip, move to the left. * The estimated_argument does not change. */ if (which_strip % 2 == 0) which_strip++; else which_strip--; break; case 1: /* * If we're in an even numbered strip, move to the left, * and if we pass under the singularity at k, * subtract pi from the estimated_argument. * If we're in an odd numbered strip, move to the right, * and if we pass under the singularity at k, * add pi to the estimated_argument. */ if (which_strip % 2 == 0) { which_strip--; if (which_strip == 2*k - 1) estimated_argument -= PI; } else { which_strip++; if (which_strip == 2*k) estimated_argument += PI; } break; case 2: /* * If we're in an even numbered strip, move to the left, * and if we pass over the singularity at k, * add pi to the estimated_argument. * If we're in an odd numbered strip, move to the right, * and if we pass over the singularity at k, * subtract pi from the estimated_argument. */ if (which_strip % 2 == 0) { which_strip--; if (which_strip == 2*k - 1) estimated_argument += PI; } else { which_strip++; if (which_strip == 2*k) estimated_argument -= PI; } break; default: uFatalError("log_w_minus_k_with_history", "chern_simons"); } /* * Compute log(w - k) using the estimated_argument. */ return ( complex_log( complex_minus( w, complex_real_mult((double)k, One) ), estimated_argument ) ); } static int get_history_length( ShapeInversion *z_history) { int length; length = 0; while (z_history != NULL) { length++; z_history = z_history->next; } return length; } static int get_wide_angle( ShapeInversion *z_history, int requested_index) { while (--requested_index >= 0) z_history = z_history->next; return z_history->wide_angle; } snappea-3.0d3/SnapPeaKernel/code/choose_generators.c0100444000175000017500000007072307071716524020564 0ustar babbab/* * choose_generators.c * * This file contains the function * * void choose_generators( Triangulation *manifold, * Boolean compute_corners, * Boolean centroid_at_origin) * * which chooses a set of generators for the fundamental group * of the Triangulation *manifold. (The Dehn filling coefficients * are irrelevant.) * * A function which needs to use the generating set must first call * choose_generators(). [Note that this differs from the previous * SnapPea 2.0- convention, under which all functions which changed the * triangulation were responsible for calling choose_generators(). * The old convention was more efficient at runtime, but the new one * makes programming easier.] * * The algorithm begins with an arbitrary Tetrahedron, and recursively * attaches neighboring Tetrahedra to create a fundamental domain for * the manifold which is topologically a ball. Whenever a face of a * Tetrahedron lies in the interior of this fundamental domain, * tet->generator_status[face] is set to not_a_generator. Faces on the * exterior of the fundmental domain correspond to active generators, * and will have status outbound_generator or inbound_generator, depending * on how a particular generator is oriented (one face of a matching pair * will have status outbound_generator, and the other inbound_generator). * * The algorithm simplifies the generating set in two ways: * * (1) When it finds an EdgeClass with only one incident 2-cell which * is dual to an active generator, it does a "handle cancellation" * to eliminate that generator, and also sets the EdgeClass's * active_relation field to FALSE. The algorithm continues doing * this type of simplification until it can make no further progress. * * (2) At this point the boundary of the fundamental domain is likely * to contain groups of faces which are essentially n-gons (n > 3) * arbitrarily divided into triangles. The generators for such * triangular faces are all equivalent, and get merged. The * active_relation fields of the interior EdgeClasses are set to * FALSE. * * active_relation fields which are not set to FALSE in (1) or (2) * are set to TRUE. Each EdgeClass's num_incident_generators field * says, not surprisingly, how many active generators it is incident to. * Note that num_incident_generators becomes 0 when a handle cancellation * occurs in (1) above, but num_incident_generators remains 2 when the * EdgeClass's active_relation field is set to FALSE in (2) (the rationale * is that there are still two active incident generators, even though * they happen to be the same (yeah, it sounds suspicious to me too, but * that's how it is)). * * Each generator is a 1-cell in the dual to the Triangulation. * The generator dual to a given face of a given Tetrahedron is * described by three variables: * * tet->generator_status[face] takes the value * * outbound_generator if the generator is directed from * tet towards its neighbor, * inbound_generator if the generator is directed from * the neighbor towards tet, * not_a_generator if no generator corresponds to this * face (more on this in a minute), and * unassigned_generator if the algorithm hasn't gotten around * to considering this face yet. * * tet->generator_index[face] tells the index of the generator. * The numbering runs from 0 to (number-of-generators - 1). * tet->generator_index[face] is defined iff tet->generator_status[face] * is outbound_generator or inbound_generator. * * tet->generator_parity[face] tells whether the generator is * orientation_preserving or orientation_reversing. * * The field tet->generator_path lets you reconstruct the complete path of * a generator: it says by which face the given Tetrahedron was added to the * fundamental domain (cf. the recursive algorithm described above). The * central Tetrahedron used to begin the recursion has tet->generator_path = -1. * * If compute_corners is TRUE, * choose_generators() also computes the location on the sphere at infinity * of each ideal vertex of each Tetrahedron in the fundamental domain, and * stores it in the field tet->corner[vertex]. That is, tet->corner[vertex] * contains the complex number representing the location of the vertex in * the boundary of the upper half space model. The (relative) locations of * the corners are computed using the hyperbolic structure of the Dehn filled * manifold. If centroid_at_origin is TRUE, the initial tetrahedron is * positioned with its centroid at the origin; otherwise the initial tetrahedron * is positioned with its vertices at {0, 1/sqrt(z), sqrt(z), infinity}. */ #include "kernel.h" static void initialize_flags(Triangulation *manifold); static void visit_tetrahedra(Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin); static void initial_tetrahedron(Triangulation *manifold, Tetrahedron **tet, Boolean compute_corners, Boolean centroid_at_origin); static void count_incident_generators(Triangulation *manifold); static void eliminate_trivial_generators(Triangulation *manifold); static void kill_the_incident_generator(Triangulation *manifold, EdgeClass *edge); static void merge_equivalent_generators(Triangulation *manifold); static void merge_incident_generators(Triangulation *manifold, EdgeClass *edge); void choose_generators( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin) { /* * To compute the corners we need some sort of geometric structure. */ if (compute_corners == TRUE && manifold->solution_type[filled] == not_attempted) uFatalError("choose_generators", "choose_generators.c"); /* * For each Tetrahedron tet, set tet->flag to unknown_orientation * to indicate that the Tetrahedron has not yet been visited, and * set each tet->generator_status[i] to unassigned_generator to * indicate that no generator has yet been assigned to any face. */ initialize_flags(manifold); /* * Start a recursion which visits each tetrahedron, assigns * generators to its faces, and recursively visits any unvisited * neighbors. */ visit_tetrahedra(manifold, compute_corners, centroid_at_origin); /* * The number_of_generators should be one plus the number of tetrahedra. */ if (manifold->num_generators != manifold->num_tetrahedra + 1) uFatalError("choose_generators", "choose_generators"); /* * At this point we have a valid set of generators, but it's * not as simple as it might be. We'll perform two types of * simplifications. First we need to count how many of the * faces incident to each EdgeClass correspond to active generators. * Initialize all the active_relation flags to TRUE while we're at it. */ count_incident_generators(manifold); /* Now look for EdgeClasses in the Triangulation (2-cells in the * dual complex) which show that a single generator is homotopically * trivial, and eliminate the trivial generator. Topologically, this * corresponds to folding together two adjacent triangular faces * on the boundary of the fundamental domain (the close-the-book * move). Geometrically, this corresponds to realizing that two * faces of the (geometric) fundamental domain are in fact already * superimposed on each other. In Heegaard terms, it's a handle * cancellation. */ eliminate_trivial_generators(manifold); /* * At this point the boundary of the fundamental domain is likely * to contain groups of faces which are essentially n-gons (n > 3) * arbitrarily divided into triangles. The generators for such * triangular faces are all equivalent, and can be merged. They * can be recognized by looking for EdgeClasses with exactly two * incident (and distinct) generators. */ merge_equivalent_generators(manifold); } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; FaceIndex face; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { tet->flag = unknown_orientation; for (face = 0; face < 4; face++) { tet->generator_status[face] = unassigned_generator; tet->generator_index[face] = -2; /* garbage value */ } } } static void visit_tetrahedra( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin) { Tetrahedron **queue, *tet; int queue_first, queue_last; Tetrahedron *nbr_tet; Permutation gluing; FaceIndex face, nbr_face; int i; VertexIndex nbr_i; /* * choose_generators() has already called initialize_flags(). */ /* * Initialize num_generators to zero. */ manifold->num_generators = 0; /* * Allocate space for a queue of pointers to the Tetrahedra. * Each Tetrahedron will appear on the queue exactly once, * so an array of length manifold->num_tetrahedra will be just right. */ queue = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); /* * Initialize the queue. */ queue_first = 0; queue_last = 0; /* * Choose the initial Tetrahedron according to some criterion. * If compute_corners is TRUE, position its corners. * 2000/4/2 The choice of initial tetrahedron is independent * of compute_corners. */ initial_tetrahedron(manifold, &queue[0], compute_corners, centroid_at_origin); /* * Mark the initial Tetrahedron as visited. */ queue[0]->generator_path = -1; queue[0]->flag = right_handed; /* * Start processing the queue. */ do { /* * Pull a Tetrahedron off the front of the queue. */ tet = queue[queue_first++]; /* * Look at the four neighboring Tetrahedra. */ for (face = 0; face < 4; face++) { /* * Note who the neighbor is, and which of * its faces we're glued to. */ nbr_tet = tet->neighbor[face]; gluing = tet->gluing[face]; nbr_face = EVALUATE(gluing, face); /* * If nbr_tet hasn't been visited, set the appropriate * generator_statuses to not_a_generator, and then put * nbr_tet on the back of the queue. */ if (nbr_tet->flag == unknown_orientation) { tet ->generator_status[face] = not_a_generator; nbr_tet->generator_status[nbr_face] = not_a_generator; tet ->generator_index[face] = -1; /* garbage value */ nbr_tet->generator_index[nbr_face] = -1; nbr_tet->generator_path = nbr_face; nbr_tet->flag = (parity[gluing] == orientation_preserving) ? tet->flag : ! tet->flag; if (compute_corners) { for (i = 0; i < 4; i++) { if (i == face) continue; nbr_i = EVALUATE(gluing, i); nbr_tet->corner[nbr_i] = tet->corner[i]; } compute_fourth_corner( nbr_tet->corner, /* array of corner coordinates */ nbr_face, /* the corner to be computed */ nbr_tet->flag, /* nbr_tet's current orientation */ nbr_tet->shape[filled]->cwl[ultimate]); /* shapes */ } queue[++queue_last] = nbr_tet; } /* * If nbr_tet has been visited, check whether a generator * has been assigned to common face, and if not, assign one. */ else if (tet->generator_status[face] == unassigned_generator) { tet ->generator_status[face] = outbound_generator; nbr_tet->generator_status[nbr_face] = inbound_generator; tet ->generator_index[face] = manifold->num_generators; nbr_tet->generator_index[nbr_face] = manifold->num_generators; tet ->generator_parity[face] = nbr_tet->generator_parity[nbr_face] = ((parity[gluing] == orientation_preserving) == (tet->flag == nbr_tet->flag)) ? orientation_preserving : orientation_reversing; manifold->num_generators++; } } } while (queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); /* * An "unnecessary" (but quick) error check. */ if ( queue_first != manifold->num_tetrahedra || queue_last != manifold->num_tetrahedra - 1) uFatalError("visit_tetrahedra", "choose_generators"); } static void initial_tetrahedron( Triangulation *manifold, Tetrahedron **initial_tet, Boolean compute_corners, Boolean centroid_at_origin) { VertexIndex v[4]; Complex z, sqrt_z, w[4]; Tetrahedron *tet; EdgeIndex best_edge, edge; /* * Set a default choice of tetrahedron and edge. */ *initial_tet = manifold->tet_list_begin.next; best_edge = 0; /* * 2000/02/11 JRW Can we choose the initial tetrahedron in such * a way that if we happen to have the canonical triangulation * of a 2-bridge knot or link complement, the basepoint falls * at a center of D2 symmetry? That is, can we find a Tetrahedron * that looks like the "top of the tower" in the canonical * triangulation of a 2-bridge knot or link complement? */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (edge = 0; edge < 6; edge++) if (tet->neighbor[one_face_at_edge [edge]] == tet->neighbor[other_face_at_edge[edge]]) { *initial_tet = tet; best_edge = edge; } if (compute_corners) { if (centroid_at_origin == TRUE) { /* * Proposition. For any value of w, positioning the corners at * * corner[0] = w * corner[1] = w^-1 * corner[2] = -w^-1 * corner[3] = -w * * defines a tetrahedron with its centroid at the "origin" and * the common perpendiculars between pairs of opposite edges * coincident with the "coordinate axes". [In the Klein model, * the tetrahedron is inscribed in a rectangular box whose faces * are parallel to the coordinate axes.] * * Proof: Use the observation that the line from a0 to a1 will * intersect the line from b0 to b1 iff the cross ratio * * (b0 - a0) (b1 - a1) * ------------------- * (b1 - a0) (b0 - a1) * * of the tetrahedron they span is real, and they will be * orthogonal iff the cross ratio is -1. * * [-w, w] is orthogonal to [0, infinity] because * * (0 - -w) (infinity - w) * ----------------------- = -1 * (infinity - -w) (0 - w) * * and similarly for [-w^-1, w^-1] and [0, infinity]. * * [w^-1, w] is orthogonal to [-1, 1] because * * (-1 - w^-1) (1 - w) * ------------------- = -1 * (1 - w^-1) (-1 - w) * * and similarly for [-w^-1, -w] and [-1, 1]. * * [-w^-1, w] is orthogonal to [-i, i] because * * (-i - -w^-1) (i - w) * -------------------- = -1 * (i - -w^-1) (-i - w) * * and similarly for [w^-1, -w] and [-i, i]. * * Q.E.D. * * * The tetrahedron will have the correct cross ratio z iff * * (w - -w^-1) (w^-1 - -w ) (w + w^-1)^2 * z = -------------------------- = -------------- * (w - -w ) (w^-1 - -w^-1) 4 * * Solving for w in terms of z gives the four possibilities * * w = +- (sqrt(z) +- sqrt(z - 1)) * * Note that sqrt(z) + sqrt(z - 1) and sqrt(z) - sqrt(z - 1) are * inverses of one another. We can choose any of the four solutions * to be "w", and the other three will automatically become w^-1, * -w, and -w^-1. * * Comment: This position for the initial corners brings out * nice numerical properties in the O(3,1) matrices for manifolds * composed of regular ideal tetrahedra (cf. the proofs in the * directory "Tilings of H^3", which aren't part of SnapPea, but * I could give you a copy). */ z = (*initial_tet)->shape[filled]->cwl[ultimate][0].rect; w[0] = complex_plus( complex_sqrt(z), complex_sqrt(complex_minus(z, One)) ); w[1] = complex_div(One, w[0]); w[2] = complex_negate(w[1]); w[3] = complex_negate(w[0]); (*initial_tet)->corner[0] = w[0]; (*initial_tet)->corner[1] = w[1]; (*initial_tet)->corner[2] = w[2]; (*initial_tet)->corner[3] = w[3]; } else { /* * Originally this code positioned the Tetrahedron's vertices * at {0, 1, z, infinity}. As of 2000/02/04 I modified it * to put the vertices at {0, 1/sqrt(z), sqrt(z), infinity} instead, * so that the basepoint (0,0,1) falls at the midpoint * of the edge extending from 0 to infinity, and the * tetrahedron's symmetry axis lies parallel to the x-axis. * To convince yourself that the tetrahedron's axis of * symmetry does indeed pass through that point, note * that a half turn around the axis of symmetry factors * as a reflection in the plane |z| = 1 followed by * a reflection in the vertical plane sitting over x-axis. */ /* * Order the vertices so that the tetrahedron is positively * oriented, and the selected edge is between vertices * v[0] and v[1]. */ v[0] = one_vertex_at_edge[best_edge]; v[1] = other_vertex_at_edge[best_edge]; v[2] = remaining_face[v[1]][v[0]]; v[3] = remaining_face[v[0]][v[1]]; /* * Set the coordinates of the corners. */ z = (*initial_tet)->shape[filled]->cwl[ultimate][edge3[best_edge]].rect; sqrt_z = complex_sqrt(z); (*initial_tet)->corner[v[0]] = Infinity; (*initial_tet)->corner[v[1]] = Zero; (*initial_tet)->corner[v[2]] = complex_div(One, sqrt_z); (*initial_tet)->corner[v[3]] = sqrt_z; } } } void compute_fourth_corner( Complex corner[4], VertexIndex missing_corner, Orientation orientation, ComplexWithLog cwl[3]) { int i; VertexIndex v[4]; Complex z[4], cross_ratio, diff20, diff21, numerator, denominator; /* * Given the locations on the sphere at infinity in * the upper half space model of three of a Tetrahedron's * four ideal vertices, compute_fourth_corner() computes * the location of the remaining corner. * * corner[4] is the array which contains the three known * corners, and into which the fourth will be * written. * * missing_corner is the index of the unknown corner. * * orientation is the Orientation with which the Tetrahedron * is currently being viewed. * * cwl[3] describes the shape of the Tetrahedron. */ /* * Set up an indexing scheme v[] for the vertices. * * If some vertex (!= missing_corner) is positioned at infinity, let its * index be v0. Otherwise choose v0 arbitrarily. Then choose * v2 and v3 so that the Tetrahedron looks right_handed relative * to the v[]. */ v[3] = missing_corner; v[0] = ! missing_corner; for (i = 0; i < 4; i++) if (i != missing_corner && complex_infinite(corner[i])) v[0] = i; if (orientation == right_handed) { v[1] = remaining_face[v[3]][v[0]]; v[2] = remaining_face[v[0]][v[3]]; } else { v[1] = remaining_face[v[0]][v[3]]; v[2] = remaining_face[v[3]][v[0]]; } /* * Let z[i] be the location of v[i]. * The z[i] are known for i < 3, unknown for i == 3. */ for (i = 0; i < 3; i++) z[i] = corner[v[i]]; /* * Note the cross_ratio at the edge connecting v0 to v1. */ cross_ratio = cwl[edge3_between_faces[v[0]][v[1]]].rect; if (orientation == left_handed) cross_ratio = complex_conjugate(complex_div(One, cross_ratio)); /* * The cross ratio is defined as * * (z3 - z1) (z2 - z0) * cross_ratio = ----------------------- * (z2 - z1) (z3 - z0) * * Solve for z3. * * z1*(z2 - z0) - cross_ratio*z0*(z2 - z1) * z3 = ----------------------------------------- * (z2 - z0) - cross_ratio*(z2 - z1) * * If z0 is infinite, this reduces to * * z3 = z1 + cross_ratio * (z2 - z1) * * which makes sense geometrically. */ if (complex_infinite(z[0]) == TRUE) z[3] = complex_plus( z[1], complex_mult( cross_ratio, complex_minus(z[2], z[1]) ) ); else { diff20 = complex_minus(z[2], z[0]); diff21 = complex_minus(z[2], z[1]); numerator = complex_minus( complex_mult(z[1], diff20), complex_mult( cross_ratio, complex_mult(z[0], diff21) ) ); denominator = complex_minus( diff20, complex_mult(cross_ratio, diff21) ); z[3] = complex_div(numerator, denominator); /* will handle division by Zero correctly */ } corner[missing_corner] = z[3]; } static void count_incident_generators( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; FaceIndex face, face1; /* * For each EdgeClass, initialize num_incident_generators to zero. * Initialize all the active_relation flags to TRUE while we're at it. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->num_incident_generators = 0; edge->active_relation = TRUE; } /* * For each face of a Tetrahedron dual to an outbound_generator, * increment the num_incident_generators count of the three * adjacent EdgeClasses. Ignore inbound_generators, to avoid * counting each generator twice. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) if (tet->generator_status[face] == outbound_generator) for (face1 = 0; face1 < 4; face1++) if (face1 != face) tet->edge_class[edge_between_faces[face][face1]]->num_incident_generators++; } static void eliminate_trivial_generators( Triangulation *manifold) { Boolean progress; EdgeClass *edge; do { progress = FALSE; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->num_incident_generators == 1) { kill_the_incident_generator(manifold, edge); progress = TRUE; } } while (progress == TRUE); } static void kill_the_incident_generator( Triangulation *manifold, EdgeClass *edge) { PositionedTet ptet, ptet0; int dead_index; Tetrahedron *tet, *nbr_tet; Permutation gluing; FaceIndex face, nbr_face; /* * The EdgeClass edge is incident to a unique generator. * Find it. */ set_left_edge(edge, &ptet0); ptet = ptet0; while (TRUE) { /* * If we've found the active generator, * break out of the while loop. Otherwise . . . */ if (ptet.tet->generator_status[ptet.near_face] != not_a_generator) break; /* * . . . move on to the next Tetrahedron incident to the EdgeClass. */ veer_left(&ptet); /* * If we've come all the way around the EdgeClass without * finding a generator, something has gone terribly wrong. */ if (same_positioned_tet(&ptet, &ptet0)) uFatalError("kill_the_incident_generator", "choose_generators"); } /* * Note the index of the about to be killed generator . . . */ dead_index = ptet.tet->generator_index[ptet.near_face]; /* * . . . then kill it. */ nbr_tet = ptet.tet->neighbor[ptet.near_face]; gluing = ptet.tet->gluing[ptet.near_face]; nbr_face = EVALUATE(gluing, ptet.near_face); ptet.tet->generator_status[ptet.near_face] = not_a_generator; nbr_tet ->generator_status[nbr_face] = not_a_generator; ptet.tet->generator_index[ptet.near_face] = -1; /* garbage value */ nbr_tet ->generator_index[nbr_face] = -1; /* * The EdgeClass no longer represents an active relation. */ edge->active_relation = FALSE; /* * Decrement the num_incident_generators count at each of * the incident EdgeClasses. */ ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.left_face] ]->num_incident_generators--; ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face] ]->num_incident_generators--; ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->num_incident_generators--; /* * Decrement *number_of_generators. */ manifold->num_generators--; /* * If dead_index was not the highest numbered generator, then removing * it will have left a gap in the numbering scheme. Renumber the highest * numbered generator to keep the numbering contiguous. */ if (dead_index != manifold->num_generators) { for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) if (tet->generator_index[face] == manifold->num_generators) { if (tet->generator_status[face] == not_a_generator) uFatalError("kill_the_incident_generator", "choose_generators"); nbr_tet = tet->neighbor[face]; gluing = tet->gluing[face]; nbr_face = EVALUATE(gluing, face); tet ->generator_index[face] = dead_index; nbr_tet->generator_index[nbr_face] = dead_index; /* * Rather than worrying about breaking out of a * double loop, let's just return from here. */ return; } /* * The program should return from within the above double loop. */ uFatalError("kill_the_incident_generator", "choose_generators"); } else /* dead_index == manifold->num_generators, so nothing else to do */ return; } static void merge_equivalent_generators( Triangulation *manifold) { EdgeClass *edge; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->num_incident_generators == 2) merge_incident_generators(manifold, edge); } static void merge_incident_generators( Triangulation *manifold, EdgeClass *edge) { PositionedTet ptet, ptet0; Tetrahedron *tetA, *tetB, *tet; FaceIndex faceA, faceB, face; int indexA, indexB; Boolean generator_A_has_been_found, directions_agree; /* * Find the two incident generators by letting ptet * rotate around the EdgeClass. The first time we * encounter a nontrivial generator, call it * faceA of tetA; the second time, faceB of tetB. */ set_left_edge(edge, &ptet0); ptet = ptet0; generator_A_has_been_found = FALSE; while (TRUE) { /* * If we've found an active generator, record it. * If this is the second one we've found, break out of the loop. */ if (ptet.tet->generator_status[ptet.near_face] != not_a_generator) { if (generator_A_has_been_found == FALSE) { tetA = ptet.tet; faceA = ptet.near_face; generator_A_has_been_found = TRUE; } else { tetB = ptet.tet; faceB = ptet.near_face; break; } } /* * Move on to the next Tetrahedron incident to the EdgeClass. */ veer_left(&ptet); /* * If we've come all the way around the EdgeClass without * finding both generators, something has gone terribly wrong. */ if (same_positioned_tet(&ptet, &ptet0)) uFatalError("kill_the_incident_generator", "choose_generators"); } /* * If the two generators are the same, then either their product is * aA (in which case there is no further work to be done) or aa (in * which case they cannot be merged). Either way, we simply return. * [JRW 95/1/19. Actually, I don't think the first case (aA) is * likely to occur. The n-gons which are subdivided into triangles * have no interior vertices, so under normal circumstances the * generators we're merging should be distinct. If they're not, * it means we have a "face" which is topologically a cylinder, * or something weird like that. At any rate, we should return * without taking any action.] */ indexA = tetA->generator_index[faceA]; indexB = tetB->generator_index[faceB]; if (indexA == indexB) return; /* * Do the directions of the generators agree or disagree? * Note that the generator will point in the same direction * relative to the boundary of the fundamental domain iff * one is an outbound_generator and the other is an inbound_generator * relative to the preceding cyclic traversal around the EdgeClass. */ directions_agree = (tetA->generator_status[faceA] != tetB->generator_status[faceB]); /* * If directions_agree is FALSE, reverse the direction of generator A. * Then let generator A inherit the index of generator B. * * Let the highest numbered generator inherit the former index * of generator A, and decrement the number_of_generators count. * * Even in the special cases where indexA or indexB is the highest * index, generators A and B get merged, and the previously highest * index will no longer occur. This keeps the indices contiguous. */ manifold->num_generators--; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) { if (tet->generator_index[face] == indexA) { if (directions_agree == FALSE) { if (tet->generator_status[face] == outbound_generator) tet->generator_status[face] = inbound_generator; else if (tet->generator_status[face] == inbound_generator) tet->generator_status[face] = outbound_generator; else uFatalError("merge_incident_generators", "choose_generators"); } tet->generator_index[face] = indexB; } if (tet->generator_index[face] == manifold->num_generators) tet->generator_index[face] = indexA; } /* * The EdgeClass no longer represents an active relation. */ edge->active_relation = FALSE; } snappea-3.0d3/SnapPeaKernel/code/close_cusps.c0100444000175000017500000013461207041114151017354 0ustar babbab/* * close_cusps.c * * This file contains the function * * void close_cusps(Triangulation *manifold, Boolean fill_cusp[]); * * which is used by the function fill_cusps() to permanently * close the indicated cusps (those for which fill_cusp[cusp->index] * is TRUE). The Triangulation *manifold must be a manifold with * finite vertices, prepared as in subdivide(). Specifically, we * assume that the Tetrahedra incident to the cusps form disjoint * regular neighborhoods of the cusps in the manifold. close_cusps() * removes the regular neighborhood of each cusp, and fills in the * hole in such a way that the given Dehn filling curve (as specified * by cusp->m and cusp->l) becomes a trivial curve in the new manifold. * I.e. it does the Dehn filling. We assume the Dehn filling coefficients * are relatively prime integers (fill_cusps() checks this). * * * The remainder of this comment briefly explains the algorithm used * to fill the cusps. It is not a polished exposition, but I hope it * is mathematically clear and complete. * * We consider the 2-dimensional triangulation of a boundary component * of the manifold obtained when the regular neighborhood of a cusp * (as mentioned above) is stripped off. The basic idea is to fold * together adjacent triangles (which are really exposed faces of * tetrahedra) using a "close-the-book" move. Proposition 1 below * gives a sufficient condition that the folding does not change the * topology of either the manifold or its boundary. Proposition 2 * proves that if we keep folding as long as possible (i.e. as long * as we can find a pair of triangles satisfying the hypothesis of * Proposition 1), we will end up with no more than six triangles in * the (2-dimensional) triangulation. Further fussing around will * reduce the number of triangles to two. * * Before stating Propositions 1 and 2, we need a definition. * * Consider a triangle, along with one of its neighbors, which may or * may not be distinct from the first triangle. The neighbor won't be * distinct iff the first triangle is glued to itself along the given edge. * Strictly speaking, the illustration below lies in the universal cover. * * o * /|\ * / | \ * / | \ * A o | o B * \ | / * \ | / * \|/ * o * * Definition. When discussing two adjacent (but not necessarily * distinct) triangles sharing a common edge, the vertices further * from the common edge are called the "opposite vertices". In the * above illustration, the opposite vertices are labelled A and B. * * Proposition A. The close-the-book move is valid if the * opposite vertices are distinct. * * Proposition B. If we apply the close-the-book move * until there are no more adjacent triangles with * distinct opposite vertices, then we will have reached * a triangulation with at most 6 triangles. Attaching * one or two tetrahedra to the boundary lets us further * simplify the triangulation to have exactly two triangles. * * First a few preparatory lemmas. * * Lemma 1. If the opposite vertices are distinct, * then the triangles are distinct too. * * Proof. There are two ways a triangle may be glued * to itself (orientation preserving and orientation * reversing). It's trivial to check each case, and see * that the vertices opposite the identifed edge are * themselves identified. [In the figure below, I * attempted to draw arrows showing the edge identifications.] * * B A * o o * / \ / \ * _/ \_ _/ \ * /| |\ /| _\| * / \ / \ * A o---------o A A o---------o A * * * Lemma 2. The number of vertices in a triangulation of a * torus or Klein bottle is half the number of triangles. * * Proof. This is an easy Euler characteristic argument. * * chi = 0 = v - e + f = v - (3/2)f + f * * => v = f/2. * * Comment: We will think of the close-the-book move in * purely two-dimensional terms. We think of it as a two-step * process. First we collapse a line segment connecting the * opposite vertices, then we collapse the two resulting bigons. * * draw the line * connecting collapse collapse * opposite the the * vertices line bigons * o o o o * /|\ / \ / \ | * / | \ / \ | | | * / | \ / \ \ / | * A o | o B A o-------o B o A = B o * \ | / \ / / \ | * \ | / \ / | | | * \|/ \ / \ / | * o o o o * * Proposition A addresses the question of when these operations * are valid. * * Proof of Proposition A. * By Lemma 1 the triangles are distinct, so the * segment connecting A to B is an embedded arc with distinct * endpoints, and may therefore by collapsed to a point as * shown in the above illustration. * The two edges of the upper bigon (see the third frame * of the above illustration) are distinct, since otherwise * the top two edges in the second frame of the illustration * would be identified, and Lemma 1 would imply A = B. * Similarly, the two edges of the lower bigon are distinct. * It's possible that one edge of the upper bigon is identified * with an edge of the lower bigon, but both edges of the upper * bigon cannot be identified to edges of the lower, since that * would imply that the original triangulation contained only the * two triangles shown, and Lemma 2 would then imply that there * is only one vertex. It follows that the bigons may be collapsed. * Q.E.D. * * Comment. The converse to the Proposition A is almost true. If the * opposite vertices are not distinct (A = B in the above * illustration) then the line connecting them is a circle. * If this circle is homotopically nontrivial, then it certainly * cannot be collapsed to a point. However, if it's homotopically * trivial, then a modification of the close-the-book move is still * possible (but is not used in SnapPea's algorithm). * * Proof of Proposition B. * If opposite vertices are equivalent for each pair of adjacent * triangles, then all triangles will have the same set of vertices * (the torus or Klein bottle is connected). * Therefore the triangulation contains at most three vertices, * and, by Lemma 2, at most six triangles. * We now address the question of how to reduce the triangulation * to only two vertices. The basic idea is to find an edge connecting * two inequivalent vertices, and attach a (solid!) tetrahedron * so as to implement a "two-to-two move" in the triangulation of the * boundary. * * attach a * tetrahedron * to alter the * triangulation * as shown * o o * / \ /|\ * / \ / | \ * / \ / | \ * A o-------o B A o | o B * \ / \ | / * \ / \ | / * \ / \|/ * o o * * Clearly an edge connecting inequivalent vertices must exist * (by the connectivity of the torus or Klein bottle). The only * question is whether the two incident triangles are distinct. * If they weren't distinct, then the edge identifications would * have to have the pattern shown on the left side of the * illustration accompanying the proof of Lemma 1. (They couldn't * have the pattern shown on the right side of that illustration, * because then there wouldn't be two inequivalent vertices.) * Vertex B in the left side of the illustration accompanying * the proof of Lemma 1 would be isolated from the rest of the * boundary manifold. Therefore it would be opposite a distinct * vertex, and the close-the-book move would still be possible. * Q.E.D. * * Technical note: close_cusps() always performs the two-to-two * move in such a way that the subsequent close-the-book move does * not fold together two faces of the same tetrahedron. This * avoids needlessly creating an EdgeClass of order one in the * manifold. Maybe such an EdgeClass would do no harm, but who knows? */ #include "kernel.h" struct extra { VertexIndex ideal_vertex_index; int Dehn_filling_curve[4]; }; static void transfer_to_short_list(Triangulation *manifold, Boolean fill_cusp[], Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean incident_to_filled_cusp(Tetrahedron *tet, Boolean fill_cusp[]); static void simplify_cusps(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void fold_boundary(Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean cancel_triangles(Tetrahedron *tet, FaceIndex f0); static Boolean further_simplification(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean two_to_two(Triangulation *manifold, Tetrahedron *tet, FaceIndex f0, Boolean require_distinct_edges); static void transfer_curves(Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void standard_form(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void standard_torus_form(Triangulation *manifold, Tetrahedron *tet); static int max_abs_intersection_number(Tetrahedron *tet); static void apply_two_to_two_to_eliminate(Triangulation *manifold, Tetrahedron *tet, int target); static void standard_Klein_bottle_form(Triangulation *manifold, Tetrahedron *tet); static void fold_cusps(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void fold_one_cusp(Triangulation *manifold, Tetrahedron *tet0); static void replace_fake_cusps(Triangulation *manifold); static void renumber_real_cusps(Triangulation *manifold); void close_cusps( Triangulation *manifold, Boolean fill_cusp[]) { Tetrahedron short_list_begin, short_list_end; /* * Move the Tetrahedra incident to cusps-to-be-filled onto a * separate short list, so we don't have to be constantly sifting * through vast numbers of irrelevant Tetrahedra. Attach and * initialize an Extra structure on each Tetrahedron on the * short list. */ transfer_to_short_list(manifold, fill_cusp, &short_list_begin, &short_list_end); /* * Simplify the cusps to be filled until each is triangulated * by at most two triangles. Ignore (1) fake "Cusps" at finite * vertices and (2) EdgeClasses not incident to a real Cusp; * we'll fix them up at the end. We maintain the tet->edge_class() * fields for edges incident to real Cusps so that we can tell * whether two EdgeClasses are distinct. The fields within an * EdgeClass are not maintained. */ simplify_cusps(manifold, &short_list_begin, &short_list_end); /* * Transfer the Dehn filling curves to tet->extra->Dehn_filling_curve[]. */ transfer_curves(&short_list_begin, &short_list_end); /* * Put each boundary triangulation at each cusp to be filled into * the standard form: * * torus Klein bottle * ------>>----- ------>------ * | @ /| | @ /| * | @ / | | @ / | * | @ | | @ | * | / @ | | / @ | * | / @| | / @| * ^@ / ^ ^@ / ^ * | @ / | | @ / ^ * | @/ | | @/ | * | /@ | | /@ | * | / @ | | / @ | * |/ @ | |/ @ | * ------>>----- ------>>----- * * * where the line of @'s is the Dehn filling curve. */ standard_form(manifold, &short_list_begin, &short_list_end); /* * Collapse each cusp by folding along the diagonal in * the above illustrations. */ fold_cusps(manifold, &short_list_begin, &short_list_end); /* * Get rid of the old EdgeClasses and install new ones. */ replace_edge_classes(manifold); /* * Get rid of the old fake Cusps and install new ones. */ replace_fake_cusps(manifold); /* * Renumber the remaining real Cusps, so the indices * are contiguous. */ renumber_real_cusps(manifold); /* * 96/9/28 I haven't actually observed any incorrect behavior * (and I don't think there is any) but I was looking through * the kernel code to see how orient() was being used, and I * got to wondering whether close_cusps() is guaranteed to preserve * the orientation. Just to make sure it does, call orient() now, * to transfer the orientation from one of the untouched tetrahedra * (i.e. an original tetrahedron not incident to one of the * cusps-to-be-filled) to all remaining tetrahedra, including the * new ones. * * 96/9/30 After adding the call to orient() I rechecked all * Chern-Simons value for the cusped census, and they are all correct. */ orient(manifold); } static void transfer_to_short_list( Triangulation *manifold, Boolean fill_cusp[], Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet, *this_tet; /* * Initialize the short list. */ short_list_begin->prev = NULL; short_list_begin->next = short_list_end; short_list_end ->prev = short_list_begin; short_list_end ->next = NULL; /* * Transfer Tetrahedra incident to cusps-to-be-filled * to the short list. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (incident_to_filled_cusp(tet, fill_cusp) == TRUE) { this_tet = tet; tet = tet->prev; /* so the loop proceeds correctly */ REMOVE_NODE(this_tet); INSERT_BEFORE(this_tet, short_list_end); manifold->num_tetrahedra--; } } static Boolean incident_to_filled_cusp( Tetrahedron *tet, Boolean fill_cusp[]) { int i; for (i = 0; i < 4; i++) if (tet->cusp[i]->is_finite == FALSE && fill_cusp[tet->cusp[i]->index] == TRUE) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("incident_to_filled_cusp", "close_cusps"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); /* * Record for posterity the index of the ideal vertex. */ tet->extra->ideal_vertex_index = i; return TRUE; } return FALSE; } static void simplify_cusps( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { /* * simplify_cusps() simplifies a triangulation until each * boundary component is triangulated by exactly two triangles. * * It calls two other functions: * * fold_boundary() folds together adjacent boundary * triangles whereever possible, as explained in the proofs * at the top of this file. It is guaranteed to reduce the * triangulation of each boundary component to at most six * triangles. * * further_simplification() look for an edge (in the 2-dimensional * triangulation of the boundary) with distinct endpoints. * When it finds one it does a two-to-two move, as shown in * the illustration below, so that fold_boundary() can * make further progress. Because the number of vertices is * exactly half the number of triangles, further_simplification() * is guaranteed to make progress as long as the number of * triangles in a given boundary component exceeds two. * * before after * /\ c /|\ c * / \ / | \ * / \ / | \ * / \ / | \ * a /________\ b a / | \ b * \ / \ | / * \ / \ | / * \ / \ | / * \ / \ | / * \/ c \|/ c * * Note that this algorithm risks the creation of edges of order one * in the (3-dimenisional) triangulation of the manifold. But we * bravely press on, confident that if we get into trouble further * down the road, we can call a general purpose simplification * routine to remove the offending edges. */ do { fold_boundary(short_list_begin, short_list_end); } while (further_simplification(manifold, short_list_begin, short_list_end) == TRUE); } static void fold_boundary( Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; FaceIndex f; /* * Scan down the list of boundary Tetrahedra, looking for * one which can be cancelled with one of its neighbors. * (One expects almost any pair of adjacent boundary Tetraheda * to be cancellable.) When one is found, do the cancellation * and resume the search from the start of the list. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) for (f = 0; f < 4; f++) { if (f == tet->extra->ideal_vertex_index) continue; if (cancel_triangles(tet, f) == TRUE) { tet = short_list_begin; break; } } } static Boolean cancel_triangles( Tetrahedron *tet, FaceIndex f0) { Tetrahedron *nbr_tet, *t, *nbr_t; FaceIndex f[4], nbr_f[4], v[4], nbr_v[4]; EdgeIndex edge, nbr_edge; EdgeClass *e_class, *nbr_class; int i, ii, j; int b[2], c[2], delta[2][2]; PositionedTet ptet, ptet0; /* * f0 will be part of an array. */ f[0] = f0; /* * Find the neighbor adjacent to face f[0]. */ nbr_tet = tet->neighbor[f[0]]; nbr_f[0] = EVALUATE(tet->gluing[f[0]], f[0]); /* * Note the base of each Tetrahedron. */ f[1] = tet->extra->ideal_vertex_index; nbr_f[1] = nbr_tet->extra->ideal_vertex_index; /* * Do a quick error check. */ if (nbr_f[1] != EVALUATE(tet->gluing[f[0]], f[1])) uFatalError("cancel_triangles", "close_cusps"); /* * Note the EdgeIndices of the "vertical" edges farthest from * the common face. According to the propositions at the top * of this file, the Tetrahedra may be cancelled iff these * two edges belong to different EdgeClasses. */ edge = edge_between_vertices[f[0]][f[1]]; nbr_edge = edge_between_vertices[nbr_f[0]][nbr_f[1]]; e_class = tet->edge_class[edge]; nbr_class = nbr_tet->edge_class[nbr_edge]; if (e_class == nbr_class) return FALSE; /* * According to the propositions at the top of this file, * the fact that the EdgeClasses are distinct implies that * the Tetrahedra are distinct as well. Let's check, just * to be sure. */ if (tet == nbr_tet) uFatalError("cancel_triangles", "close_cusps"); /* * The following line isn't really necessary, but it serves to avoid * creating EdgeClasses of order one. If further_simplification() * has just laid down a Tetrahedron to implement a two-to-two * move, we want to avoid folding that Tetrahedron onto itself. * By checking cases (cf. the documentation and illustrations * in two_to_two() below) it's easy to see that some other * call to cancel_triangles() (not involving gluing a Tetrahedron * to itself) must succeed. */ if (tet->neighbor[f[1]] == nbr_tet->neighbor[nbr_f[1]]) return FALSE; /* * Adjust the peripheral curves so that when we collapse * the Tetrahedra, the curves match up correctly. * * /\ * / \ * a / \ b * / \ * /________\ * \ / * \ / * d \ / c * \ / * \/ E * * We want to insure that b = -c, which automatically imples a = -d. * Geometrically, all strands at c should pass to b, and all strands * at d should pass to a. Nothing should cut across the middle * from c to a, or from d to b. To accomplish this, we subtract * (b + c) all the way around the (vertical) EdgeClass E. * * As long as we're travelling around the EdgeClass, set the * tet->edge_class pointers to the address of the EdgeClass * this one is merging into. */ f[2] = remaining_face[f[1]][f[0]]; f[3] = remaining_face[f[0]][f[1]]; nbr_f[2] = EVALUATE(tet->gluing[f[0]], f[2]); nbr_f[3] = EVALUATE(tet->gluing[f[0]], f[3]); for (i = 0; i < 2; i++) /* which sheet */ { ii = (parity[tet->gluing[f[0]]] == orientation_preserving) ? i : !i; for (j = 0; j < 2; j++) /* which curve */ { b[j] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[2]]; c[j] = tet->curve[j][i ][ f[1]][ f[2]]; delta[j][i] = b[j] + c[j]; } } ptet0.tet = tet; ptet0.near_face = f[2]; ptet0.left_face = f[3]; ptet0.right_face = f[0]; ptet0.bottom_face = f[1]; ptet0.orientation = right_handed; ptet = ptet0; do { for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; for (j = 0; j < 2; j++) /* which curve */ { ptet.tet->curve[j][i][ptet.bottom_face][ptet.left_face] += delta[j][ii]; ptet.tet->curve[j][i][ptet.bottom_face][ptet.near_face] -= delta[j][ii]; } } ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.left_face]] = nbr_class; veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); /* * Imagine removing tet and nbr_tet from the manifold. * Glues together the three exposed pairs of faces. * * Miraculously, this code is correct even in degenerate * cases (corresponding to when the bigons collapse in * the proofs at the top of this file). */ for (i = 1; i < 4; i++) { t = tet->neighbor[f[i]]; nbr_t = nbr_tet->neighbor[nbr_f[i]]; for (j = 0; j < 4; j++) { v[j] = EVALUATE(tet->gluing[f[i]], f[j]); nbr_v[j] = EVALUATE(nbr_tet->gluing[nbr_f[i]], nbr_f[j]); } t->neighbor[v[i]] = nbr_t; nbr_t->neighbor[nbr_v[i]] = t; t->gluing[v[i]] = CREATE_PERMUTATION(v[0], nbr_v[0], v[1], nbr_v[1], v[2], nbr_v[2], v[3], nbr_v[3]); nbr_t->gluing[nbr_v[i]] = CREATE_PERMUTATION(nbr_v[0], v[0], nbr_v[1], v[1], nbr_v[2], v[2], nbr_v[3], v[3]); } /* * Free tet and nbr_tet. */ REMOVE_NODE(tet); REMOVE_NODE(nbr_tet); free_tetrahedron(tet); free_tetrahedron(nbr_tet); return TRUE; } static Boolean further_simplification( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; FaceIndex f; /* * Scan down the list of boundary Tetrahedra, looking for * an edge (in the 2-dimensional triangulation of the boundary) * with distinct endpoints. If one is found, do the necessary * retriangulation (as illustrated in simplify_cusps() above) * and return TRUE. If none are found, return FALSE. * * Note that at most one retriangulation will be performed in * a single call to further_simplification(). */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) for (f = 0; f < 4; f++) { if (f == tet->extra->ideal_vertex_index) continue; if (two_to_two(manifold, tet, f, TRUE) == TRUE) return TRUE; } return FALSE; } /* * two_to_two() is called from two different parts of close_cusps(). * * (1) further_simplification() calls it to alter a triangulation * so that the number of triangles in the triangulation of * a boundary component can be reduced from 4 or 6 to 2. * In this case, two_to_two() should do nothing and return * FALSE if a certain pair of EdgeClasses are not distinct. * * (2) standard_form() calls it (indirectly) to put 2-triangle * triangulations into the standard form. Here there is no * need for any EdgeClasses to be distinct (in fact, they never * will be). * * The argument require_distinct_edges says whether distinct EdgeClasses * should be required. */ static Boolean two_to_two( Triangulation *manifold, Tetrahedron *tet, FaceIndex f0, Boolean require_distinct_edges) { int i, ii, j; FaceIndex f[4], nbr_f[4]; Tetrahedron *nbr_tet, *new_tet, *tetA, *tetB; EdgeClass *e_class[4]; /* * Get set up as in cancel_triangles() above. */ f[0] = f0; nbr_tet = tet->neighbor[f[0]]; nbr_f[0] = EVALUATE(tet->gluing[f[0]], f[0]); f[1] = tet->extra->ideal_vertex_index; nbr_f[1] = nbr_tet->extra->ideal_vertex_index; if (nbr_f[1] != EVALUATE(tet->gluing[f[0]], f[1])) uFatalError("two_to_two", "close_cusps"); f[2] = remaining_face[f[1]][f[0]]; f[3] = remaining_face[f[0]][f[1]]; nbr_f[2] = EVALUATE(tet->gluing[f[0]], f[2]); nbr_f[3] = EVALUATE(tet->gluing[f[0]], f[3]); /* * Note the EdgeClasses. */ e_class[0] = tet->edge_class[ edge_between_faces[ f[2]][ f[3]] ]; e_class[1] = nbr_tet->edge_class[ edge_between_faces[nbr_f[2]][nbr_f[3]] ]; e_class[2] = tet->edge_class[ edge_between_faces[ f[0]][ f[2]] ]; e_class[3] = tet->edge_class[ edge_between_faces[ f[0]][ f[3]] ]; /* * If require_distinct_edges is TRUE, check the EdgeClasses. * (See comment preceeding this function.) * further_simplification() can make progress iff e_class[2] != e_class[3]. */ if (require_distinct_edges == TRUE && e_class[2] == e_class[3]) return FALSE; /* * In further_simplification() . . . * * We now know that no triangle in the * 2-dimensional triangulation of the boundary is * glued to itself, because if it were glued to itself with * * an orientation preserving gluing, then there would * be an isolated vertex, and further progress would * have been possible in fold_boundary() * * an orientation reversing gluing, then there would be * only one vertex in the triangulation of the boundary, * and class2 would have equalled class3. (Recall * that when fold_boundary() can make no more * progress, all boundary triangles have the same set * of vertices, including multiplicity.) * * In standard_form() . . . * * We know that no triangle can be glued to itself, because * otherwise the boundary would be a Klein bottle already in * standard form. */ if (tet == nbr_tet) uFatalError("two_to_two", "close_cusps"); /* * [This comment applies on if we were called from * further_simplification().] * * Continuing with the idea that all boundary triangles * have the same vertex set, it follows that each triangle * must be of the form * * A A * o o * / \ / \ * / \ OR / \ * / \ / \ * / \ / \ * B o---------o B B o---------o C * * In the first case, the boundary component is formed * by identifying sides of the square * * B o-------------o B * |\ /| * | \ / | * | \ / | * | \ / | * | \ / | * | \ / | * | A o | * | / \ | * | / \ | * | / \ | * | / \ | * | / \ | * |/ \| * B o-------------o B * * and in the second case by identifying sides of the * hexagon * * B ____________ C * /\ /\ * / \ / \ * / \ / \ * / \ / \ * / \ / \ * C /_________A\/__________\ B * \ /\ / * \ / \ / * \ / \ / * \ / \ / * \ / \ / * \/__________\/ * B C * * It's easy to figure out that there are three possible * gluing patterns for the square (xyXY, xyXy and xxyy) * and two for the hexagon (xyzXYZ and xyzXzy). In all * but one case (the xxyy gluing of the square) we may * conclude that tet and nbr_tet's neighbors are distinct * from tet and nbr_tet (except for obvious place they meet). * Even in the exceptional case, there are other places * where we could do the two-to-two move where the * neighbors are distinct from tet and nbr_tet. So * if the neighbors aren't distinct from tet and nbr_tet, * we simply return FALSE and wait for one of those * more convenient places to show up. */ /* * First label everything in sight. * Use macros rather than writing quantities into * arrays, so that in cases where tet and nbr_tet are glued * nontrivially to each other, the results will be correct. * That is, we want the quantities to be reevaluated every * time they are used. */ #define TET_T(i) tet->neighbor[f[i]] #define NBR_T(i) nbr_tet->neighbor[nbr_f[i]] #define TET_V(i,j) EVALUATE(tet->gluing[f[i]], f[j]) #define NBR_V(i,j) EVALUATE(nbr_tet->gluing[nbr_f[i]], nbr_f[j]) /* * In the event we were called from further_simplification, * check that the TET_T(i) and NBR_T(i) are distinct from * tet and nbr_tet. This avoids unnecessarily creating an * EdgeClass of order one (maybe it doesn't matter, but * why risk it?). */ if (require_distinct_edges == TRUE) for (i = 2; i < 4; i++) if (TET_T(i) == tet || TET_T(i) == nbr_tet || NBR_T(i) == tet || NBR_T(i) == nbr_tet) return FALSE; /* * We introduce a new Tetrahedron which realizes the * two-to-two move on the boundary triangulation, and * adjust tet and nbr_tet to sit correctly above it. * * We could just reuse the tet and nbr_tet structures, but * then there'd be problems if either is glued to itself or * the other (aside from the obvious place they're glued). * So we replace tet and nbr_tet with tetA and tetB * respectively. * * You might want to draw a picture to keep track of what's * going on. * * Vertex 0 of the new_tet sits at vertex (not face) f[0] of tet. * Vertex 1 of the new_tet sits at vertex (not face) nbr_f[0] of nbr_tet. * Vertex 2 of the new_tet sits at vertex (not face) f[3] of tet. * Vertex 3 of the new_tet sits at vertex (not face) f[2] of tet. * * Vertex 0 of tetA will be over vertex 0 of new_tet. * Vertex 1 of tetA will be at the cusp. * Vertex 2 of tetA will be over vertex 1 of new_tet. * Vertex 3 of tetA will be over vertex 2 of new_tet. * * Vertex 0 of tetB will be over vertex 1 of new_tet. * Vertex 1 of tetB will be at the cusp. * Vertex 2 of tetB will be over vertex 3 of new_tet. * Vertex 3 of tetB will be over vertex 0 of new_tet. * * Take a deep breath and set all the necessary fields . . . */ new_tet = NEW_STRUCT(Tetrahedron); tetA = NEW_STRUCT(Tetrahedron); tetB = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); initialize_tetrahedron(tetA); initialize_tetrahedron(tetB); new_tet->cusp[0] = tet->cusp[ f[0]]; new_tet->cusp[1] = nbr_tet->cusp[nbr_f[0]]; new_tet->cusp[2] = tet->cusp[ f[3]]; new_tet->cusp[3] = tet->cusp[ f[2]]; new_tet->neighbor[0] = NBR_T(1); new_tet->neighbor[1] = TET_T(1); new_tet->gluing[0] = CREATE_PERMUTATION( 0, NBR_V(1, 1), 1, NBR_V(1, 0), 2, NBR_V(1, 3), 3, NBR_V(1, 2)); new_tet->gluing[1] = CREATE_PERMUTATION( 0, TET_V(1, 0), 1, TET_V(1, 1), 2, TET_V(1, 3), 3, TET_V(1, 2)); NBR_T(1)->neighbor[NBR_V(1, 1)] = new_tet; TET_T(1)->neighbor[TET_V(1, 1)] = new_tet; NBR_T(1)->gluing[NBR_V(1, 1)] = inverse_permutation[new_tet->gluing[0]]; TET_T(1)->gluing[TET_V(1, 1)] = inverse_permutation[new_tet->gluing[1]]; new_tet->neighbor[2] = tetB; new_tet->neighbor[3] = tetA; new_tet->gluing[2] = CREATE_PERMUTATION(0, 3, 1, 0, 2, 1, 3, 2); new_tet->gluing[3] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 3, 3, 1); tetA->neighbor[1] = new_tet; tetB->neighbor[1] = new_tet; tetA->gluing[1] = inverse_permutation[new_tet->gluing[3]]; tetB->gluing[1] = inverse_permutation[new_tet->gluing[2]]; tetA->neighbor[2] = TET_T(2); tetA->gluing[2] = CREATE_PERMUTATION(0, TET_V(2, 0), 1, TET_V(2, 1), 2, TET_V(2, 2), 3, TET_V(2, 3)); TET_T(2)->neighbor[TET_V(2, 2)] = tetA; TET_T(2)->gluing[TET_V(2, 2)] = inverse_permutation[tetA->gluing[2]]; tetA->neighbor[0] = NBR_T(2); tetA->gluing[0] = CREATE_PERMUTATION(0, NBR_V(2, 2), 1, NBR_V(2, 1), 2, NBR_V(2, 0), 3, NBR_V(2, 3)); NBR_T(2)->neighbor[NBR_V(2, 2)] = tetA; NBR_T(2)->gluing[NBR_V(2, 2)] = inverse_permutation[tetA->gluing[0]]; tetB->neighbor[0] = TET_T(3); tetB->gluing[0] = CREATE_PERMUTATION(0, TET_V(3, 3), 1, TET_V(3, 1), 2, TET_V(3, 2), 3, TET_V(3, 0)); TET_T(3)->neighbor[TET_V(3, 3)] = tetB; TET_T(3)->gluing[TET_V(3, 3)] = inverse_permutation[tetB->gluing[0]]; tetB->neighbor[3] = NBR_T(3); tetB->gluing[3] = CREATE_PERMUTATION(0, NBR_V(3, 0), 1, NBR_V(3, 1), 2, NBR_V(3, 2), 3, NBR_V(3, 3)); NBR_T(3)->neighbor[NBR_V(3, 3)] = tetB; NBR_T(3)->gluing[NBR_V(3, 3)] = inverse_permutation[tetB->gluing[3]]; tetA->neighbor[3] = tetB; tetB->neighbor[2] = tetA; tetA->gluing[3] = CREATE_PERMUTATION(0, 3, 1, 1, 2, 0, 3, 2); tetB->gluing[2] = inverse_permutation[tetA->gluing[3]]; for (j = 0; j < 2; j++) /* which curve */ for (i = 0; i < 2; i++) /* which sheet */ { ii = (parity[CREATE_PERMUTATION(0, f[0], 1, f[1], 2, f[2], 3, f[3])] == 0) ? i : !i; tetA->curve[j][i][1][2] = tet->curve[j][ii][f[1]][f[2]]; ii = (parity[CREATE_PERMUTATION(0, nbr_f[2], 1, nbr_f[1], 2, nbr_f[0], 3, nbr_f[3])] == 0) ? i : !i; tetA->curve[j][i][1][0] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[2]]; tetA->curve[j][i][1][3] = - (tetA->curve[j][i][1][2] + tetA->curve[j][i][1][0]); ii = (parity[CREATE_PERMUTATION(0, f[3], 1, f[1], 2, f[2], 3, f[0])] == 0) ? i : !i; tetB->curve[j][i][1][0] = tet->curve[j][ii][f[1]][f[3]]; ii = (parity[CREATE_PERMUTATION(0, nbr_f[0], 1, nbr_f[1], 2, nbr_f[2], 3, nbr_f[3])] == 0) ? i : !i; tetB->curve[j][i][1][3] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[3]]; tetB->curve[j][i][1][2] = - (tetB->curve[j][i][1][0] + tetB->curve[j][i][1][3]); } tetA->edge_class[edge_between_faces[2][3]] = e_class[0]; tetA->edge_class[edge_between_faces[3][0]] = e_class[1]; tetA->edge_class[edge_between_faces[0][2]] = e_class[2]; tetB->edge_class[edge_between_faces[0][2]] = e_class[0]; tetB->edge_class[edge_between_faces[2][3]] = e_class[1]; tetB->edge_class[edge_between_faces[3][0]] = e_class[3]; tetA->cusp[1] = tet->cusp[f[1]]; tetB->cusp[1] = tet->cusp[f[1]]; tetA->cusp[0] = new_tet->cusp[0]; tetA->cusp[2] = new_tet->cusp[1]; tetA->cusp[3] = new_tet->cusp[2]; tetB->cusp[0] = new_tet->cusp[1]; tetB->cusp[2] = new_tet->cusp[3]; tetB->cusp[3] = new_tet->cusp[0]; tetA->extra = NEW_STRUCT(Extra); tetB->extra = NEW_STRUCT(Extra); tetA->extra->ideal_vertex_index = 1; tetB->extra->ideal_vertex_index = 1; if (require_distinct_edges == FALSE) { tetA->extra->Dehn_filling_curve[2] = tet->extra->Dehn_filling_curve[ f[2]]; tetB->extra->Dehn_filling_curve[0] = tet->extra->Dehn_filling_curve[ f[3]]; tetA->extra->Dehn_filling_curve[0] = nbr_tet->extra->Dehn_filling_curve[nbr_f[2]]; tetB->extra->Dehn_filling_curve[3] = nbr_tet->extra->Dehn_filling_curve[nbr_f[3]]; tetA->extra->Dehn_filling_curve[3] = - (tetA->extra->Dehn_filling_curve[2] + tetA->extra->Dehn_filling_curve[0]); tetB->extra->Dehn_filling_curve[2] = - (tetB->extra->Dehn_filling_curve[0] + tetB->extra->Dehn_filling_curve[3]); } INSERT_BEFORE(new_tet, &manifold->tet_list_end); /* * To avoid screwing up linked lists of Tetrahedra * (i.e. the short list, which standard_form() traverses * in a for(;;) loop), we copy tetA and tetB onto the * storage formerly used by tet and nbr_tet. * In spirit they are new Tetrahedra, but we want * them to occupy the same physical memory as the old * Tetrahedra, so as not to screw up a higher level * function which holds a pointer to one of the old * Tetrahedra. */ tetA->prev = tet->prev; tetB->prev = nbr_tet->prev; tetA->next = tet->next; tetB->next = nbr_tet->next; my_free( tet->extra); my_free(nbr_tet->extra); *tet = *tetA; *nbr_tet = *tetB; /* * If they are glued to themselves, correct the * neighbor fields. */ for (i = 0; i < 4; i++) { if (tet->neighbor[i] == tetA) tet->neighbor[i] = tet; if (tet->neighbor[i] == tetB) tet->neighbor[i] = nbr_tet; if (nbr_tet->neighbor[i] == tetA) nbr_tet->neighbor[i] = tet; if (nbr_tet->neighbor[i] == tetB) nbr_tet->neighbor[i] = nbr_tet; } /* * Correct the neighbor fields for remaining neighbors. */ for (i = 0; i < 4; i++) { tet->neighbor[i]->neighbor[EVALUATE( tet->gluing[i],i)] = tet; nbr_tet->neighbor[i]->neighbor[EVALUATE(nbr_tet->gluing[i],i)] = nbr_tet; } my_free(tetA); my_free(tetB); manifold->num_tetrahedra++; return TRUE; } static void transfer_curves( Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; VertexIndex v; Cusp *cusp; int i, j; /* * Transfer the Dehn filling curves to tet->extra->Dehn_filling_curve[]. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) { v = tet->extra->ideal_vertex_index; cusp = tet->cusp[v]; for (i = 0; i < 4; i++) { if (i == v) continue; tet->extra->Dehn_filling_curve[i] = 0; for (j = 0; j < 2; j++) tet->extra->Dehn_filling_curve[i] += (int)cusp->m * tet->curve[M][j][v][i] + (int)cusp->l * tet->curve[L][j][v][i]; } } } static void standard_form( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; /* * See the documentation in close_cusps() for an illustration of * the standard forms. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) if (tet->cusp[tet->extra->ideal_vertex_index]->topology == torus_cusp) standard_torus_form(manifold, tet); else standard_Klein_bottle_form(manifold, tet); } static void standard_torus_form( Triangulation *manifold, Tetrahedron *tet) { int max; /* * The idea here is to modify the triangulation of the * boundary torus so that the Dehn filling curve * looks simpler. Geometrically, we are going to do Dehn * twists which realize the Euclidean algorithm, but you * needn't think in terms of Dehn twists as you read the * following code. * * The Dehn filling curve will intersect the sides of a * boundary triangle with intersection numbers a, b and c, * where a + b + c = 0. If, say, c has the greatest absolute * value, then a and b will have the same sign, and c = -(a + b). * The intersection numbers on the other triangle in the triangulation * are of course the negatives of these. * * If we do a two-to-two move across the edge with intersection * number c, then the new intersection numbers will be a, -b and * (b - a). Each time we do this we reduce the absolute value of * the largest intersection number, until we reach a state where * one of the intersection numbers is zero and the other two are * negatives of each other. The latter two must be +1 and -1, * because the Dehn filling curve is a simple closed curve. * Thus, we eventually reach a state where the intersection * numbers are {0, +1, -1}. * * The state just before this (if any) must have been {1, 1, 2}. * {1, 1, 2} is the standard form. * * So . . . the algorithm is * * if (state = {0, +1, -1}) * back up to {1, 1, 2} * else * while (state is not {1, 1, 2}) * apply a two-to-two move to reduce the absolute value * of the largest intersection number * * Technical comment: in the case where we have to back up * to {1, 1, 2}, when we seal the cusp we'll be creating an * EdgeClass of order 1 in the 3-manifold. */ max = max_abs_intersection_number(tet); if (max == 1) apply_two_to_two_to_eliminate(manifold, tet, 0); else while (max > 2) { apply_two_to_two_to_eliminate(manifold, tet, max); max = max_abs_intersection_number(tet); } } static int max_abs_intersection_number( Tetrahedron *tet) { VertexIndex v; int max; int i; v = tet->extra->ideal_vertex_index; max = 0; for (i = 0; i < 4; i++) { if (i == v) continue; if (ABS(tet->extra->Dehn_filling_curve[i]) > max) max = ABS(tet->extra->Dehn_filling_curve[i]); } return max; } static void apply_two_to_two_to_eliminate( Triangulation *manifold, Tetrahedron *tet, int target) { VertexIndex v; FaceIndex f; /* * apply_two_to_two_to_eliminate() applies a two-to-two move * to alter the boundary triangulation in such a way as to * eliminate the edge whose intersection number with the * Dehn filling curve has absolute value target. */ v = tet->extra->ideal_vertex_index; /* * Find the FaceIndex f of the face of tet corresponding to the * 2-d edge we want to eliminate. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (ABS(tet->extra->Dehn_filling_curve[f]) == target) break; } if (f == 4) /* didn't find the right f */ uFatalError("apply_two_to_two_to_eliminate", "close_cusps"); (void) two_to_two(manifold, tet, f, FALSE); } static void standard_Klein_bottle_form( Triangulation *manifold, Tetrahedron *tet) { VertexIndex v; FaceIndex f; v = tet->extra->ideal_vertex_index; /* * Consider the triangle corresponding to tet in the * (2-dimensional) triangulation of the boundary Klein bottle. * The triangulation of the Klein bottle is in the standard * form iff this triangle has two sides glued to each other. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (tet->neighbor[f] == tet) return; } /* * The boundary triangulation must have one of the following * two forms, where the line of @'s is the meridian. * (In fact the two forms represent the same triangulation. * I've drawn them separately to convince the reader--and * myself--that this is the only triangulation other than * than the standard one.) * * ------>>----- -----<<------ * | /| | @ /| * | / | | @ / | * | / | | @ / | * | / | | @ / | * | / | | @ / | * ^@@@@@/@@@@@^ ^@ / @^ * | / | | / @ ^ * | / | | / @ | * | / | | / @ | * | / | | / @ | * |/ | |/ @ | * ------<<----- ------<------ * * A two-to-two move across the edge disjoint from the meridian * will put the Klein bottle into standard form. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (tet->extra->Dehn_filling_curve[f] == 0) { (void) two_to_two(manifold, tet, f, FALSE); break; } } } static void fold_cusps( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { while (short_list_begin->next != short_list_end) fold_one_cusp(manifold, short_list_begin->next); } static void fold_one_cusp( Triangulation *manifold, Tetrahedron *tet0) { Tetrahedron *tet[2], *nbr[2]; FaceIndex f[2][4], nf[2][4]; int i, j; int abs_int_num; Cusp *dead_cusp; /* * The illustrations in close_cusps() show the standard forms * for torus and Klein bottle cusps. We are going to fold * along the diagonal. Note that the absolute value of the * intersection number of the Dehn filling curve with the edge * we're folding along is 2 for a torus and 0 for a Klein bottle. * For all other edges it's 1. */ /* * f[0][0] will be the FaceIndex of the bottom face of tet[0] (the * one furthest from the ideal vertex). f[0][1] will be the face * incident to the edge we're folding along. f[0][2] and f[0][3] * will be the remaining faces. f[1][0-3] will be the corresponding * faces of tet[1], the other Tetrahedron at this cusp. */ tet[0] = tet0; f[0][0] = tet[0]->extra->ideal_vertex_index; for (f[0][1] = 0; f[0][1] < 4; f[0][1]++) { if (f[0][1] == f[0][0]) continue; abs_int_num = ABS(tet[0]->extra->Dehn_filling_curve[f[0][1]]); if (abs_int_num == 2 || abs_int_num == 0) break; } if (f[0][1] == 4) uFatalError("fold_one_cusp", "close_cusps"); f[0][2] = remaining_face[f[0][0]][f[0][1]]; f[0][3] = remaining_face[f[0][1]][f[0][0]]; tet[1] = tet[0]->neighbor[f[0][1]]; for (i = 0; i < 4; i++) f[1][i] = EVALUATE(tet[0]->gluing[f[0][1]], f[0][i]); /* * nbr[0] (resp. nbr[1]) is the Tetrahedron (with all finite vertices) * which sits underneath tet[0] (resp. tet[1]). Their FaceIndices * are nf[0][] and nf[1][], and are indexed in the natural way * relative to tet[0] and tet[1]. */ for (i = 0; i < 2; i++) { nbr[i] = tet[i]->neighbor[f[i][0]]; for (j = 0; j < 4; j++) nf[i][j] = EVALUATE(tet[i]->gluing[f[i][0]], f[i][j]); } /* * To fold the cusp, we simply identify similarly indexed * vertices of nbr[0] and nbr[1]. */ for (i = 0; i < 2; i++) { nbr[i]->neighbor[nf[i][0]] = nbr[!i]; nbr[i]->gluing[nf[i][0]] = CREATE_PERMUTATION( nf[i][0], nf[!i][0], nf[i][1], nf[!i][1], nf[i][2], nf[!i][2], nf[i][3], nf[!i][3]); } /* * Discard tet[0] and tet[1]. */ dead_cusp = tet[0]->cusp[f[0][0]]; if (dead_cusp->topology == torus_cusp) manifold->num_or_cusps--; else manifold->num_nonor_cusps--; manifold->num_cusps--; REMOVE_NODE(dead_cusp); my_free(dead_cusp); for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } } static void replace_fake_cusps( Triangulation *manifold) { Tetrahedron *tet; int i; Cusp *cusp, *dead_cusp; /* * Set to NULL all tet->cusp pointers which point to fake Cusps. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) if (tet->cusp[i]->is_finite == TRUE) tet->cusp[i] = NULL; /* * Free the fake Cusps. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == TRUE) { dead_cusp = cusp; cusp = cusp->prev; /* so the loop will proceed correctly */ REMOVE_NODE(dead_cusp); my_free(dead_cusp); } /* * Assign new fake Cusps. */ create_fake_cusps(manifold); } static void renumber_real_cusps( Triangulation *manifold) { Cusp *cusp; int cusp_count; cusp_count = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE) cusp->index = cusp_count++; } snappea-3.0d3/SnapPeaKernel/code/complex.c0100444000175000017500000000674206742675501016525 0ustar babbab/* * complex.c * * This file provides the standard complex arithmetic functions: * * Complex complex_minus (Complex z0, Complex z1), * complex_plus (Complex z0, Complex z1), * complex_mult (Complex z0, Complex z1), * complex_div (Complex z0, Complex z1), * complex_sqrt (Complex z), * complex_conjugate (Complex z), * complex_negate (Complex z), * complex_real_mult (double r, Complex z), * complex_exp (Complex z), * complex_log (Complex z, double approx_arg); * double complex_modulus (Complex z); * double complex_modulus_squared (Complex z); * Boolean complex_nonzero (Complex z); * Boolean complex_infinite (Complex z); */ #include "kernel.h" Complex Zero = { 0.0, 0.0}; Complex One = { 1.0, 0.0}; Complex Two = { 2.0, 0.0}; Complex Four = { 4.0, 0.0}; Complex MinusOne = {-1.0, 0.0}; Complex I = { 0.0, 1.0}; Complex TwoPiI = { 0.0, TWO_PI}; Complex Infinity = {1e34, 0.0}; Complex complex_plus( Complex z0, Complex z1) { Complex sum; sum.real = z0.real + z1.real; sum.imag = z0.imag + z1.imag; return sum; } Complex complex_minus( Complex z0, Complex z1) { Complex diff; diff.real = z0.real - z1.real; diff.imag = z0.imag - z1.imag; return diff; } Complex complex_div( Complex z0, Complex z1) { double mod_sq; Complex quotient; mod_sq = z1.real * z1.real + z1.imag * z1.imag; if (mod_sq == 0.0) { if (z0.real != 0.0 || z0.imag != 0.0) return Infinity; else uFatalError("complex_div", "complex"); } quotient.real = (z0.real * z1.real + z0.imag * z1.imag)/mod_sq; quotient.imag = (z0.imag * z1.real - z0.real * z1.imag)/mod_sq; return quotient; } Complex complex_mult( Complex z0, Complex z1) { Complex product; product.real = z0.real * z1.real - z0.imag * z1.imag; product.imag = z0.real * z1.imag + z0.imag * z1.real; return product; } Complex complex_sqrt( Complex z) { double mod, arg; Complex root; mod = sqrt(complex_modulus(z)); /* no need for safe_sqrt() */ if (mod == 0.0) return Zero; arg = 0.5 * atan2(z.imag, z.real); root.real = mod * cos(arg); root.imag = mod * sin(arg); return root; } Complex complex_conjugate( Complex z) { z.imag = - z.imag; return z; } Complex complex_negate( Complex z) { z.real = - z.real; z.imag = - z.imag; return z; } Complex complex_real_mult( double r, Complex z) { Complex multiple; multiple.real = r * z.real; multiple.imag = r * z.imag; return multiple; } Complex complex_exp( Complex z) { double modulus; Complex result; modulus = exp(z.real); result.real = modulus * cos(z.imag); result.imag = modulus * sin(z.imag); return result; } Complex complex_log( Complex z, double approx_arg) { Complex result; if (z.real == 0.0 && z.imag == 0.0) { uAcknowledge("log(0 + 0i) encountered"); result.real = - DBL_MAX; result.imag = approx_arg; return result; } result.real = 0.5 * log(z.real * z.real + z.imag * z.imag); result.imag = atan2(z.imag, z.real); while (result.imag - approx_arg > PI) result.imag -= TWO_PI; while (approx_arg - result.imag > PI) result.imag += TWO_PI; return result; } double complex_modulus( Complex z) { return sqrt(z.real * z.real + z.imag * z.imag); /* no need for safe_sqrt() */ } double complex_modulus_squared( Complex z) { return (z.real * z.real + z.imag * z.imag); } Boolean complex_nonzero( Complex z) { return (z.real || z.imag); } Boolean complex_infinite( Complex z) { return (z.real == Infinity.real && z.imag == Infinity.imag); } snappea-3.0d3/SnapPeaKernel/code/complex_length.c0100444000175000017500000005575207053771567020100 0ustar babbab/* * complex_length.c * * This file provides the functions * * Complex complex_length_mt(MoebiusTransformation *mt); * Complex complex_length_o31(O31Matrix *m); * * They are identical except that in one case the isometry is specified * by a MoebiusTransformation, and in the other by an O31Matrix. * * If the isometry is orientation-preserving, then complex_length_*() * returns a complex (length + i torsion) with the usual interpretation: * * If neither length nor torsion is zero, * the isometry is loxodromic. It's a screw motion with the given * length and torsion. * * If the torsion is zero, * the isometry is hyperbolic. It's a translation along a * geodesic. The translation distance is the reported length. * * If the length is zero, * the isometry is a rotation about an axis. The torsion gives * the rotation angle. * * If both length and torsion are zero, * the isometry is a parabolic (perhaps the identity). * * If the isometry is orientation-reversing, then we use the definition * * Definition. An orientation-reversing isometry of H^3 is * elliptic, parabolic or hyperbolic iff it factors as a * reflection in a plane, followed by an orientation-preserving * elliptic, parabolic or hyperbolic isometry, respectively, * which fixes that plane (setwise). * * The section "Interpreting the trace" (cf. below) explains this * definition and proves the necessary supporting lemmas. For now, * note that orientation-reversing loxodromics do not exist. So a * single real parameter suffices to fully describe an orientation- * reversing isometry. complex_length_*() returns a nonzero length or * a nonzero torsion (but not both) with the following interpretations: * * If the torsion is zero, * the isometry is hyperbolic. It's a glide reflection along a * geodesic. The translation distance is the reported length. * * If the length is zero, * the isometry is a reflection in a plane followed by a rotation * about an axis orthogonal to that plane. The torsion gives * the rotation angle. * * If both length and torsion are zero, * the isometry is a reflection in a plane, followed by a * parabolic (perhaps the identity) which fixes that plane setwise. * * I recommend that when the torsion is zero, the user interface not * display it to the user. For example, the presence or absence of a * torsion value is a good easy way to see which geodesics are * orientation_preserving and which are orientation_reversing. * * * Classification of isometries. * * We must analyze what kinds of isometries are possible in hyperbolic * space. In the orientable case the answers are part of the standard * lore of hyperbolic geometry, but standard textbooks omit the * nonorientable case. So here we'll go ahead and do the orientable and * nonorientable cases both. * * Let g be an isometry of H^3. * * Case 1. g has a fixed point x in H^3. * * g maps a unit sphere centered at x to itself. * * Case 1.1. g is orientation-preserving. * * It's a standard theorem of geometry that an orientation- * preserving isometry from a 2-sphere to itself is a rotation * about some axis. * * 1.1.1. In the upper half space model we may choose coordinates * so that g has the matrix * * exp(i*theta/2) 0 * 0 exp(-i*theta/2) * * The trace is exp(i*theta/2) + exp(-i*theta/2) = 2 cos(theta/2). * * 1.1.2. In the Minkowski space model we may choose coordinates * so that g has the matrix * * 1 0 0 0 * 0 1 0 0 * 0 0 cos(theta) -sin(theta) * 0 0 sin(theta) cos(theta) * * The trace is 2 + 2 cos(theta). * * Case 1.2. g is orientation-reversing. * * Let h be reflection through x. Then g = g(hh) = (gh)h. * The map gh is orientation preserving, so must have a matrix * as in 1.1.2. The coordinate system of 1.1.2 puts the fixed * point x at the origin (1, 0, 0, 0), so h has matrix * * 1 0 0 0 * 0 -1 0 0 * 0 0 -1 0 * 0 0 0 -1 * * and therefore g = (gh)h has matrix * * 1 0 0 0 * 0 -1 0 0 * 0 0 -cos(phi) sin(phi) * 0 0 -sin(phi) -cos(phi) * * The trace is -2 cos(phi). * * For most purposes it's more convenient to factor g as (gh')h', * where h' is a reflection in the plane through x orthogonal * to the rotation axis. The rotation gh' has the same axis as gh, * but its rotation angle theta is phi + pi. Thus gh' has matrix * * 1 0 0 0 * 0 -1 0 0 * 0 0 cos(theta) -sin(theta) * 0 0 sin(theta) cos(theta) * * The trace is 2 cos(theta). * * Case 2. g has no fixed point in H^3. * * By the Brouwer fixed point theorem, g must have at least one * fixed point on the sphere at infinity. * * Case 2.1. g has at least three fixed points on the sphere at infinity. * * g fixes the ideal triangle spanned by the three points. Ideal * triangles are rigid, so it follows that g fixes the entire plane * spanned by the three points. Either g is the identity with * matrix * 1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 1 * * and trace 4, or g is a reflection in the fixed plane, with * matrix conjugate to * * 1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 -1 * * and trace 2. * * Case 2.2. g has exactly two fixed points on the sphere at infinity. * * Let L be the line spanned by the two fixed points. g fixes * L as a set, but not pointwise (because throughout case 2 we * are assuming g has no fixed point in H^3). Similarly, g must * preserve the direction of L, since otherwise there'd be a * fixed point somewhere on L. * * Case 2.2.1. g is orientation-preserving. * * g is a translation along L combined with a rotation about L. * * 2.2.1.1. If we choose coordinates in the upper half space * model so that one fixed point is at 0 and the other at * infinity, then g(z) = kz, the matrix of g is * * sqrt(k) 0 * 0 1/sqrt(k) * * and the trace is sqrt(k) + 1/sqrt(k). Here it's more * convenient to work with the square of the trace, which * is k + 2 + 1/k. * * 2.2.1.2. In the Minkowski space model we may choose * coordinates so that g has matrix * * cosh s sinh s 0 0 * sinh s cosh s 0 0 * 0 0 cos t -sin t * 0 0 sin t cos t * * and trace 2 cosh s + 2 cos t. Note that it's not so easy * to analyze the trace in this case. If we computed the * characteristic polynomial we could factor it to obtain * |s| and |t|, but this won't be necessary because in the * orientation-preserving case we'll stick to PSL(2,C) where * we can also obtain the sign (handedness) of the twist. * * Case 2.2.2. g is orientation-reversing. * * g is a translation along L combined with a reflection * in a plane through L. * * In the Minkowski space model we may choose coordinates * so that g has matrix * * cosh s sinh s 0 0 * sinh s cosh s 0 0 * 0 0 -1 0 * 0 0 0 1 * * with trace 2 cosh s. * * Case 2.3. g has exactly one fixed point on the sphere at infinity. * * Let x be the fixed point on the sphere at infinity, and let * H be any horosphere centered at x. * * Lemma. g maps H to itself. * * Proof. Since x is a fixed point, we know g must send H to * some horosphere H' centered at x. Let p be the map which * projects H' onto H radially from x. The restriction of g to * H is an isometry from H to H', but if H' != H then the * projection p from H' back to H is a similarity which expands * distances by some factor r != 1. Let f be p composed with * the restriction of g to H, and let y be an arbitrary point * on H. I claim f has a fixed point. If r < 1, consider the * sequence of points {y, f(y), f(f(y)), ...}. The distances * between consecutive points form a convergent geometric series, * so the points themselves converge to a fixed point z. If * r > 1, the same argument applies using f^-1 instead of f. * The (ideal) point x and the (finite) point z determine a * geodesic which is fixed (setwise, not pointwise) by g. * The geodesic's other endpoint is therefore a second fixed * point on the sphere at infinity, contradicting the Case 2.3 * assumption that g has a unique fixed point on the sphere at * infinity. Therefore our assumption that H' != H must have * been wrong. Q.E.D. * * In view of the preceding Lemma, it suffices to analyze the * action of g on a horosphere H centered at x. * * Case 2.3.1. g is orientation-preserving. * * Lemma. g|H is a pure translation. * * Proof. If g|H had a rotational component, then for any * point y on H, the points {y, g(y), g(g(y))} would not be * colinear (g couldn't map the vector from y to g(y) to a * parallel vector). The perpendicular bisectors of the * segments from y to g(y) and from g(y) to g(g(y)) would * intersect at a fixed point, which is impossible because * a fixed point on H would imply a second fixed point on * the sphere at infinity, as in the Lemma above. Q.E.D. * * 2.3.1.1. In PSL(2,C) any translation is conjugate to * * 1 1 * 0 1 * * which has trace 2. * * 2.3.1.2. In O(3,1) any translation is conjugate to * * 3/2 -1/2 1 0 * 1/2 1/2 1 0 * 1 -1 1 0 * 0 0 0 1 * * which has trace 4. * * Case 2.3.2. g is orientation-reversing. * * Lemma. g perserves some direction on H. * * Proof. Draw a clock face on H (the old-fashioned analog * kind -- no digital clocks please) and look at its image * under g. As the second hand sweeps clockwise on the * original clock, its image under g sweeps counterclockwise. * Within 30 seconds the second hand and its image will point * in the same direction. Q.E.D. (It's a good thing we used * a second hand instead of an hour hand, or this might have * been a much longer proof.) * * Because g is orientation-reversing, it flips lines * perpendicular to the fixed direction. It follows that * g is a glide reflection. That is, it's a parabolic as * in Case 2.3.1 above, composed with a reflection in the * perpendicular direction. In O(3,1) its matrix is * * 3/2 -1/2 1 0 * 1/2 1/2 1 0 * 1 -1 1 0 * 0 0 0 -1 * * and the trace is 2. * * * Interpreting the trace. * * PSL(2,C) * * Orientation-preserving isometries. * * Here it's best to look at the square of the trace rather than * the trace itself, because the trace itself is well-defined * only up to sign. The preceding classification of isometries * show that * * g is elliptic => trace^2(g) = 4 (cos(theta/2))^2 * = 4 (cosh(i theta/2))^2 * g is parabolic => trace^2(g) = 4 * g is hyperbolic => trace^2(g) = 4 (cosh(length/2))^2 * g is loxodromic => trace^2(g) = 4 (cosh((length + i theta)/2))^2 * * This chart is good news. By computing the trace^2, we can * deduce the type of isometry, and excluding the parabolic case * we can even deduce the length and torsion! * * Note that elliptic and hyperbolic isometries are special cases * of loxodromic one. * * By recalling the origin of the trace^2 as k + 2 + 1/k in the * loxodromic case, we see that g is strictly loxodromic iff * the trace^squared does not fall on the nonnegative real axis. * * Orientation-reversing isometries. * * PSL(2,C) is not well suited to orientation-reversing * isometries (you need to work with z-bar instead of z) * so we won't bother with this case. * * O(3,1) * * Orientation-preserving isometries. * * As remarked in 2.2.1.2 above, the trace alone does not provide * enough information in O(3,1). Indeed, we can hardly expect * a single real number to provide both length and torsion * information. The characteristic polynomial would give us the * magnitudes of both the length and the torsion, but we still * wouldn't know the handedness of the twist (because it's not a * conjugacy invariant in a group like O(3,1) which contains * orientation-reversing elements). So it's best to use PSL(2,C) * in the orientation-preserving case. * * Orientation-reversing isometries. * * Here the terminology is not well established, so let me first * say what I mean by elliptic, parabolic and hyperbolic isometries. * * Lemma. Each orientation-reversing isometry of H^3 is a * reflection in a plane, followed by an orientation-preserving * isometry which fixes that plane (setwise, not pointwise!). * * Proof. Follows from the classification of isometries * given above. Q.E.D. * * Definition. An orientation-reversing isometry of H^3 is * elliptic, parabolic or hyperbolic iff it factors as a * reflection in a plane, followed by an orientation-preserving * elliptic, parabolic or hyperbolic isometry, respectively, * which fixes that plane (setwise). * * Comment. A simple reflection in a plane corresponds to the * identity in the orientation-preserving case. So it may be * considered a degenerate elliptic, parabolic or hyperbolic * isometry. * * Lemma. Orientation-reversing isometries have traces as follows: * * type of isometry trace * ---------------- ----- * elliptic 2 cosh(i theta) = 2 cos(theta) * parabolic 2 * hyperbolic 2 cosh(length) * * Proof. Follows from the classification of isometries * given above. Q.E.D. * * Corollary. We're in luck again. The type of an orientation- * reversing isometry may be recognized from the trace of its * matrix in O(3,1). * * Comment. Orientation-reversing loxodromics do not exist. * This is a good thing. An orientation-reversing isometry is * fully specified up to conjugacy by a single real parameter, * and that single real parameter is easily computed from the * (real valued!) trace of the matrix in O(3,1). */ #include "kernel.h" #define TRACE_ERROR_EPSILON 1e-3 #define TORSION_EPSILON 1e-5 static Complex orientation_preserving_complex_length(MoebiusTransformation *mt); static Complex orientation_reversing_complex_length(O31Matrix m); static Complex signed_rotation_angle(MoebiusTransformation *mt); Complex complex_length_mt( MoebiusTransformation *mt) { O31Matrix m; Complex length; /* * Unfortunately we have to split into two cases, depending * on the parity of the MoebiusTransformation. In the * orientation_preserving case we work with the SL2CMatrix * directly. In the orientation_reversing case we convert * to an O31Matrix. * * SL2CMatrices can't be used in the orientation_reversing * case because they are incapable of representing * orientation_reversing isometries. O31Matrices are inadequate * for the orientation_preserving case because their * characteristic polynomials carry the length and the magnitude * of the torsion, but not the sign of the torsion (indeed, it * couldn't possibly carry the sign, because the sign of the * torsion is not a conjugacy invariant relative to a group such * as O(3,1) which contains orientation_reversing elements). */ if (mt->parity == orientation_preserving) { length = orientation_preserving_complex_length(mt); } else { Moebius_to_O31(mt, m); length = orientation_reversing_complex_length(m); } return length; } Complex complex_length_o31( O31Matrix m) { MoebiusTransformation mt; Complex length; /* * This is the same as complex_length_mt() above, * only the input matrix is given in O(3,1). */ if (gl4R_determinant(m) > 0.0) { O31_to_Moebius(m, &mt); length = orientation_preserving_complex_length(&mt); } else { length = orientation_reversing_complex_length(m); } return length; } static Complex orientation_preserving_complex_length( MoebiusTransformation *mt) { Complex trace, trace_squared, k, length; /* * The complex length depends on the trace, as explained below. */ trace = complex_plus(mt->matrix[0][0], mt->matrix[1][1]); trace_squared = complex_mult(trace, trace); /* * 96/1/12 Craig has requested that for flat solutions SnapPea * provide consistent signs for rotation angles of elliptic * isometries of H^2. To avoid confusing flat solutions with nearly * flat solutions, do_Dehn_filling() in hyperbolic_structure.c now * sets the imaginary parts to zero when a solution is provably flat. */ if (sl2c_matrix_is_real(mt->matrix) == TRUE && trace_squared.real < 4.0) return signed_rotation_angle(mt); /* * If the isometry represented by the MoebiusTransformation * is hyperbolic (i.e. a translation along a geodesic, with * a possible rotation), then it's conjugate to the isometry * f(z) = kz, for some complex number k. The complex log * of k gives the complex length of the geodesic. * * As a matrix, f() is written as * * k 0 * 0 1 * * When the determinant is normalized to One, * this becomes * * sqrt(k) 0 * 0 1/sqrt(k) * * This matrix is well-defined up to sign; therefore * the square of its trace is completely well-defined. * Because the trace is a conjugacy invariant, the * trace squared of this matrix equals that of the matrix m. */ /* * We can now use the relationship * * trace = sqrt(k) + 1/sqrt(k) * * to solve for k in terms of the trace_squared. * We follow our noses: * * trace^2 = (sqrt(k) + 1/sqrt(k))^2 * * trace^2 = k + 1/k + 2 * * k^2 + (2 - trace^2)k + 1 = 0 * * (trace^2 - 2) +- sqrt(tr^2(tr^2 - 4)) * k = -------------------------------------- * 2 * * It doesn't matter which of the two possible values * of k we choose, since they are inverses of one another, * and therefore their complex logs are negatives of * one another. */ k = complex_real_mult( 0.5, complex_plus ( complex_minus(trace_squared, Two), complex_sqrt( complex_mult( trace_squared, complex_minus(trace_squared, Four) ) ) ) ); /* * Now compute the complex length as log(k); */ length = complex_log(k, 0.0); /* * Make sure the length is positive. */ if (length.real < 0.0) length = complex_negate(length); /* * We want torsions of +-pi to be reported consistently * as +pi, rather than letting roundoff error choose * between +pi and -pi. */ if (length.imag < - PI + TORSION_EPSILON) length.imag += TWO_PI; /* * If the isometry represented by mt is parabolic, * then it is conjugate to * * 1 1 * 0 1 * * The above computation gives * * trace_squared = 4 * k = 1 * length = 0.0 + 0.0 i * * which is a sensible answer. */ return length; } static Complex signed_rotation_angle( MoebiusTransformation *mt) { /* * Here we handle the special case of an orientation preserving * isometry whose matrix is all real and whose trace is in the * range (-2, 2). (This is an elliptic isometry of H^2.) * We want to compute the correct sign for the rotation, for use * in studying Seifert fibered spaces' base orbifolds. * * Thanks to Craig Hodgson for suggesting this improvement, * and providing the means to implement it. */ /* * Let the isometry be * * az + b * f(z) = ------ * cz + d * * We'll think of this both as an isometry of the upper half space * model of H^3, and also (when z is real) as an isometry of the * copy of H^2 which lies above the real axis. The fact that * the trace is in the range (-2, 2) implies this is an elliptic * isometry. Its rotation axis must be perpendicular to the * aforementioned copy of H^2. The rotation axis terminates in * a pair of fixed points on the boundary of upper half space. * The rotation angle (in H^2) is arg(f'(w)) = -arg(f'(w-bar)), * where w (resp. w-bar) is the fixed point with positive (resp. * negative) imaginary part. Let's compute arg(f'(w)). * * First solve for w. * * aw + b * w = ------ * cw + d * => * c w^2 + (d - a)w - b = 0 * => * (a - d) +- sqrt((a - d)^2 + 4bc) * w = -------------------------------- * 2c * => * (using ad - bc = 1) * * (a - d) +- i sqrt(4 - (a + d)^2) * w = -------------------------------- * 2c * * Important note: to insure that w has positive imaginary part, * we must choose the + sign (in the "+-") when c > 0, and * the - sign when c < 0. (The case c = 0 can't occur when det = 1 * and trace^2 < 4.) * * Now compute * 1 * f'(z) = ---------- * (cz + d)^2 * * Substitute in the above value for w to get * * 1 * f'(w) = ---------- * (cw + d)^2 * * 4 * = ------------------------------------ * ((a + d) +- i sqrt(4 - (a + d)^2))^2 * * 4 * = -------------------------- * (tr +- i sqrt(4 - tr^2))^2 * * (tr -+ i sqrt(4 - tr^2))^2 * = -------------------------- * 4 * * tr^2 tr * = ---- - 1 -+ i -- sqrt(4 - tr^2) * 2 2 * * It follows that the magnitude of the angle is * * tr^2 * acos( ---- - 1 ) * 2 * * If c > 0 (resp. c < 0) then the angle and the trace will have * opposite (resp. the same) signs. */ double tr, c; Complex length; tr = mt->matrix[0][0].real + mt->matrix[1][1].real; c = mt->matrix[1][0].real; length.real = 0.0; length.imag = safe_acos(0.5*tr*tr - 1.0); /* in the range (0, +pi] */ if ((c > 0.0) == (tr > 0.0)) length.imag = - length.imag; return length; } static Complex orientation_reversing_complex_length( O31Matrix m) { double trace; Complex length; int i; /* * The section "Interpreting the trace" in the top-of-file * documentation gives the trace of an orientation-reversing * isometry as * * type of isometry trace * ---------------- ----- * elliptic 2 cosh(i theta) = 2 cos(theta) * parabolic 2 * hyperbolic 2 cosh(length) */ trace = 0.0; for (i = 0; i < 4; i++) trace += m[i][i]; if (trace < 2.0 - TRACE_ERROR_EPSILON) { /* * elliptic */ length.real = 0.0; length.imag = safe_acos(trace/2.0); } else if (trace > 2.0 + TRACE_ERROR_EPSILON) { /* * hyperbolic * * The standard value of acosh() is nonnegative. */ length.real = arccosh(trace/2.0); length.imag = 0.0; } else { /* * parabolic */ length.real = 0.0; length.imag = 0.0; } return length; } snappea-3.0d3/SnapPeaKernel/code/continued_fractions.c0100444000175000017500000002016506742675501021111 0ustar babbab/* * continued_fractions.c * * This file provides the function * * Boolean appears_rational( double x0, double x1, * double confidence, * long *num, long *den); * * which checks whether a finite-precision real number x known to lie * in the interval (x0, x1) appears to be a rational number p/q. If it * does, it sets *num and *den to p and q, respectively, and returns TRUE. * Otherwise it sets *num and *den to 0 and returns FALSE. The confidence * parameter gives the maximal acceptable probability of a "false positive". * (The following description of the algorithm explains the confidence * parameter more precisely.) By the way, it's OK to pass x0 and x1 in * the "wrong order": if x0 > x1, appears_rational() swaps them before * proceeding. But appears_rational() always returns FALSE for x0 == x1. * * First recall that a continued fraction expansion is an expression like * * 1 * 48/11 = 4 + ------------- * 1 * 2 + -------- * 1 * 1 + --- * 3 * * The algorithm to compute a continued fraction expansion, given a * floating point approximation x, is roughly the following: * * do * { * subtract off integer part * invert fractional part * } until (fractional part is zero) * * The only tricky part is the numerical analysis. Rather than working * with a single number x, we'll work with an interval [x0, x1] in which * x is known to lie (the width of the interval corresponds to the * precision with which we know x). The above algorithm becomes * * while (the interval [x0, x1] does not contain an integer) * { * subtract off the integer part * invert, i.e. map [x0, x1] -> [1/x1, 1/x0] * } * * Proposition. For any interval of positive width, the algorithm * converges in a finite number of steps. * * Proof. Each iteration expands the width of the interval, because the * deriviate of the function f(x) = 1/x is f'(x) = -1/x^2, which implies * |f'(x)| > 1 on the interval (0,1). However, what we'd really like to * show is that it expands the interval by some factor r > 1. Even though * this isn't quite true (as x -> 1, |f'(x)| -> 1), it is true that * two consecutive iterations must increase the width of the interval * by a factor of r^2, for some r > 1. To see this, note that after * subtracting off the integer part, the interval will lie in the range * (1/(n+1), 1/n] for some n (otherwise its image under the inversion * will include an integer and the algorithm will terminate). The first * inversion maps x to 1/x. 1/x lies in the range [n, n+1), so its * integer part is n. Subtracting off this integer part takes 1/x to * 1/x - n. The second inversion maps 1/x - n to 1/(1/x - n) = x/(1 - xn). * The derivative of this function is (1 - nx)^-2. Because x is in the * range (1/(n+1), 1/n], it follows that (1 - nx)^-2 > (1 - n(1/(n+1)))^-2 * = (n+1)^2. So even in the worst case of n = 1, two iterations of the * mapping expand by a factor of (1+1)^2 = 4. In terms of the preceding * notation, we may choose r = 2. Q.E.D. * * Corollary. The number of iterations of the algorithm is bounded * by the number of significant binary digits in x after the decimal * point. Typically, of course, the number of iterations will be less. * (However, the worst case would be realized by a floating-point * approximation to (sqrt(5) - 1)/2.) * * * We have shown that the algorithm will ALWAYS yield a rational * approximation to x, i.e. a rational number in the interval [x0, x1]. * How do we know whether x is "really" a rational number or whether * we just stumbled on a rational by chance? The width of the interval * gives the probability of stumbling on a rational approximation by * chance. For example, if we begin with an arbitrary interval (not * chosen to represent a rational number) and after some number of * iterations the interval has expanded to a width of 0.3, then there * is a 30% probability that the interval will contain an integer. * Our approach, therefore, is to assume that if a rational approximation * if found while the interval is still small (e.g. 1e-4), then it must * be because the original interval [x0, x1] represented a rational * number. But if no rational approximation is found until the interval * is large (e.g. 0.3), then we assume that we found the approximation * by chance, and that the original interval [x0, x1] does not represent * a rational. In practical terms, the calling function passes a value * for the confidence parameter. If the width of the interval exceeds * this confidence parameter before a rational approximation is found, * then we assume that the original interval [x0, x1] does not represent * a rational, and we return FALSE. */ #include "kernel.h" /* * MAX_ITERATIONS puts an upper bound on the number of iterations * of the algorithm. It should never come into play -- it's only * a safety net in case of disaster. Here's how I came up with the * value 64. Assuming doubles have mantissas with 8 bytes or less, * the smallest interval you'd reasonably expect to start with would * have width at least 2^-64. As shown above, the width of the interval * will, on average, at least double with each iteration. So after * 64 iterations the interval would have width 1 and you'd have found * a rational approximation (or, more likely, you'd have exceeded * the confidence parameter). */ #define MAX_ITERATIONS 64 Boolean appears_rational( double x0, double x1, double confidence, long *num, long *den) { int i, j; long a[MAX_ITERATIONS], p, q, t; double n, temp; /* * Make sure x0 <= x1. */ if (x0 > x1) { temp = x0; x0 = x1; x1 = temp; } /* * Make sure the width of the interval [x0, x1] is less than * the confidence parameter. */ if (x1 - x0 >= confidence) { *num = 0; *den = 0; return FALSE; } /* * We expect the function to return() from within this loop. * We should never reach i == MAX_ITERATIONS. */ for (i = 0; i < MAX_ITERATIONS; i++) { /* * If x0 and x1 were initially equal, or were so close that they * became equal during the course of the computation, return FALSE. * (E.g. if x0 = -1e-15 and x1 = -1e-15 + 1e-25, then * x0 + 1.0 == x1 + 1.0 == 1.0 - 1e-15.) */ if (x1 == x0) { *num = 0; *den = 0; return FALSE; } /* * Let a[i] be the integer part of x1. * Subtract it from both x0 and x1. * * Technical note: n is a double; a[i] is a long int. */ n = floor(x1); a[i] = (long int) n; x0 -= n; x1 -= n; /* * x1 is now in the range [0,1). * [x0, x1] will contain the origin iff x0 is nonpositive. */ if (x0 <= 0.0) { /* * Success! * * The interval [x0, x1] contains the origin, * yet its width is still less than the confidence parameter. * * We may now reconstruct the rational approximation from * the a[i]. */ /* * Set p/q = a[i], the most recently subtracted integer. */ p = a[i]; q = 1; /* * For each preceding a[j], invert p/q and add a[j]. */ for (j = i; --j >= 0; ) { /* * Invert p/q. */ t = p; p = q; q = t; /* * Add a[j]. * p p q * a[j] p + q * a[j] * Note that --- + a[j] = --- + ---------- = -------------- * q q q q */ p += q * a[j]; } /* * Set *num and *den and return TRUE. */ *num = p; *den = q; return TRUE; } /* * Map [x0, x1] to [1/x1, 1/x0]. */ temp = x0; x0 = 1.0 / x1; x1 = 1.0 / temp; /* * If the width of the interval exceeds the confidence parameter, * then we've failed to find a meaningful rational approximation. */ if (x1 - x0 > confidence) { *num = 0; *den = 0; return FALSE; } } /* * As shown in the documentation above, an interval of * nontrivial width must expand to width at least one * within MAX_ITERATIONS of the algorithm. So we should * never reach this point. */ uFatalError("appears_rational", "continued_fractions"); /* * We'll never get past the uFatalError() call, * but we must provide a return value to keep the * compiler happy. */ return FALSE; } snappea-3.0d3/SnapPeaKernel/code/core_geodesics.c0100444000175000017500000001502606742675501020026 0ustar babbab/* * core_geodesics.c * * This file provides the function * * void core_geodesic( Triangulation *manifold, * int cusp_index, * int *singularity_index, * Complex *core_length, * int *precision); * * which computes the core_length and singularity_index for the * Cusp of index cusp_index in Triangulation *manifold. The * singularity_index describes the nature of the cusp: * * If the Cusp is unfilled or the Dehn filling coefficients are * not integers, then * * *singularity_index is set to zero, * *core_length is undefined. * * If the Cusp is filled and the Dehn filling coefficients are * relatively prime integers (so we have a manifold locally), then * * *singularity_index is set to one, * *core_length is set to the complex length * of the central geodesic, * * If the Cusp is filled and the Dehn filling coefficients are * non relatively prime integers (so we have an orbifold locally), then * * *singularity_index is set to the index of the singular locus, * *core_length is set to the complex length of the central * geodesic in the smallest manifold cover of * a neighborhood of the singular set. * * If the precision pointer is not NULL, *precision is set to the * number of decimal places of accuracy in the computed value of * core_length. * * Klein bottle cusps are OK. Their torsion will always be zero. * * This file also provides the function * * void compute_core_geodesic( Cusp *cusp, * int *singularity_index, * Complex length[2]); * * for use within the kernel. It is similar to core_geodesic(), * only it takes a Cusp pointer as input, and outputs the complex * lengths relative to the ultimate and penultimate hyperbolic * structures, rather than reporting a precision. * * * The Algorithm. * * Say we're doing (p, q) Dehn filling on some Cusp. The (closed) core * geodesic lifts to a set of (infinite) geodesics in the universal cover. * Let L be one such (infinite) geodesic in the univeral cover, and * consider the group G of covering transformations fixing L (setwise). * G is generated by the holonomies of the meridian and longitude, * which we denote H(m) and H(l), subject to the single relation * p H(m) + q H(l) = 0. We would like to find new generators g and h * for the group such that the relation takes the form n g + 0 h = 0. * The generator g will be the purely rotational part of the group G * (n will be the order of the singular locus), and h will generate * the translational part of G. * * One approach to finding g and h would be to proceed in the general * context of finitely generated abelian groups, and apply the Euclidean * algorithm to the relation p H(m) + q H(l) = 0, changing bases until * the relation simplifies down to something of the form n g + 0 h = 0. * But rather than messing with the bookkeeping of this approach, we'll * use a more pedestrian method. * * Let (a, b; c, d) be the matrix expressing (g, h) in terms * of (H(m), H(l)): * * | g | | a b | | H(m) | * | | = | | | | * | h | | c d | | H(l) | * * Because 0 = n g = n (a H(m) + b H(l)) = na H(m) + nb H(l) is * the identity, and p H(m) + q H(l) is the only relation in the * presentation of the group, it follows that (a, b) must be * proportional to (p, q). Furthermore, since the matrix (a, b; c, d) * has determinant one, a and b must be relatively prime, so therefore * (a, b) = (p, q)/gcd(p,q). It now follows that c and d are integers * satisfying * 1 = a d - b c = d p/gcd(p,q) - c q/gcd(p,q) * <=> * d p - c q = gcd(p,q) * * We can find such integers using SnapPea's standard * euclidean_algorithm() function. */ #include "kernel.h" #define TORSION_EPSILON 1e-5 void core_geodesic( Triangulation *manifold, int cusp_index, int *singularity_index, Complex *core_length, int *precision) { Cusp *cusp; Complex length[2]; cusp = find_cusp(manifold, cusp_index); /* * Compute the complex length relative to the ultimate * and penultimate hyperbolic structures. */ compute_core_geodesic(cusp, singularity_index, length); /* * Package up the results. */ if (*singularity_index != 0) { *core_length = length[ultimate]; if (precision != NULL) *precision = complex_decimal_places_of_accuracy( length[ultimate], length[penultimate]); } else { *core_length = Zero; if (precision != NULL) *precision = 0; } } void compute_core_geodesic( Cusp *cusp, int *singularity_index, Complex length[2]) { int i; long int positive_d, negative_c; double pi_over_n; /* * If the Cusp is unfilled or the Dehn filling coefficients aren't * integers, then just write in some zeros (as explained at the top * of this file) and return. */ if (cusp->is_complete == TRUE || Dehn_coefficients_are_integers(cusp) == FALSE) { *singularity_index = 0; length[ultimate] = Zero; length[penultimate] = Zero; return; } /* * The euclidean_algorithm() will give the singularity index * directly (as the g.c.d.), and the coefficients lead to the * complex length (cf. the explanation at the top of this file). */ *singularity_index = euclidean_algorithm( (long int) cusp->m, (long int) cusp->l, &positive_d, &negative_c); for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { /* * length[i] = c H(m) + d H(l) * * (The holonomies are already in logarithmic form.) */ length[i] = complex_plus( complex_real_mult( (double) (- negative_c), cusp->holonomy[i][M] ), complex_real_mult( (double) positive_d, cusp->holonomy[i][L] ) ); /* * Make sure the length is positive. */ if (length[i].real < 0.0) length[i] = complex_negate(length[i]); /* * We want to normalize the torsion to the range * [-pi/n + epsilon, pi/n + epsilon], where n is * the order of the singular locus. */ pi_over_n = PI / *singularity_index; while (length[i].imag < - pi_over_n + TORSION_EPSILON) length[i].imag += 2 * pi_over_n; while (length[i].imag > pi_over_n + TORSION_EPSILON) length[i].imag -= 2 * pi_over_n; /* * In the case of a Klein bottle cusp, H(m) will be purely * rotational and H(l) will be purely translational * (cf. the documentation at the top of holonomy.c). * But the longitude used in practice is actually the * double cover of the true longitude, so we have to * divide the core_length by two to compensate. */ if (cusp->topology == Klein_cusp) length[i].real /= 2.0; } } snappea-3.0d3/SnapPeaKernel/code/cover.c0100444000175000017500000004202507041126645016157 0ustar babbab/* * cover.c * * construct_cover() constructs the n-sheeted cover of a base_manifold * defined by a transitive RepresentationIntoSn. Please see covers.h * for details. * * construct_cover() assumes the base_manifold's generators are present, * and correspond to the generators in the representation. The * representation is assumed to be transitive, so the cover is connected. * construct_cover() also assumes that each Cusp's basepoint_tet, * basepoint_vertex and basepoint_orientation are those which were * recorded when the fundamental group was computed. All these assumptions * will be true if the Triangulation has not been changed since the * RepresentationIntoSn was computed (and of course the UI takes * responsibility for recomputing the representations whenever the * triangulation changes). */ #include "kernel.h" #define NAME_SUFFIX " cover" Triangulation *construct_cover( Triangulation *base_manifold, RepresentationIntoSn *representation, int num_generators, int n) { Triangulation *covering_manifold; Tetrahedron ***covering_tetrahedra, *base_tetrahedron, **lifts; int i, j, count, face, nbr_index, gen_index, sheet, nbr_sheet, covering_cusp_index, the_gcd, base_singularity, covering_singularity, index, index0, *primitive_permutation; VertexIndex v; FaceIndex f; MatrixInt22 *change_matrices; Orientation handedness; Cusp *base_cusp, *covering_cusp; /* * Allocate and initialize the Triangulation structure. */ covering_manifold = NEW_STRUCT(Triangulation); initialize_triangulation(covering_manifold); /* * Fill in some of the global information. */ if (base_manifold->name == NULL) uFatalError("construct_cover", "cover"); covering_manifold->name = NEW_ARRAY(strlen(base_manifold->name) + strlen(NAME_SUFFIX) + 1, char); strcpy(covering_manifold->name, base_manifold->name); strcat(covering_manifold->name, NAME_SUFFIX); covering_manifold->num_tetrahedra = n * base_manifold->num_tetrahedra; covering_manifold->solution_type[complete] = base_manifold->solution_type[complete]; covering_manifold->solution_type[ filled ] = base_manifold->solution_type[ filled ]; covering_manifold->orientability = unknown_orientability; /* * Make sure the base_manifold's Tetrahedra have indices. */ number_the_tetrahedra(base_manifold); /* * Allocate a 2-dimensional array of Tetrahedra for the covering_manifold. */ covering_tetrahedra = NEW_ARRAY(base_manifold->num_tetrahedra, Tetrahedron **); for (i = 0; i < base_manifold->num_tetrahedra; i++) { covering_tetrahedra[i] = NEW_ARRAY(n, Tetrahedron *); for (j = 0; j < n; j++) { covering_tetrahedra[i][j] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(covering_tetrahedra[i][j]); INSERT_BEFORE(covering_tetrahedra[i][j], &covering_manifold->tet_list_end); } } /* * Copy information from the Tetrahedra of the base_manifold to * the Tetrahedra of the covering_manifold. */ for ( base_tetrahedron = base_manifold->tet_list_begin.next, count = 0; base_tetrahedron != &base_manifold->tet_list_end; base_tetrahedron = base_tetrahedron->next, count++) { if (base_tetrahedron->index != count) uFatalError("construct_cover", "cover"); lifts = covering_tetrahedra[base_tetrahedron->index]; /* * First figure out the neighbor fields. * This is the only part that uses the RepresentationIntoSn. */ for (face = 0; face < 4; face++) { nbr_index = base_tetrahedron->neighbor[face]->index; gen_index = base_tetrahedron->generator_index[face]; switch (base_tetrahedron->generator_status[face]) { case not_a_generator: for (sheet = 0; sheet < n; sheet++) lifts[sheet]->neighbor[face] = covering_tetrahedra[nbr_index][sheet]; break; case outbound_generator: for (sheet = 0; sheet < n; sheet++) lifts[sheet]->neighbor[face] = covering_tetrahedra[nbr_index][representation->image[gen_index][sheet]]; break; case inbound_generator: for (nbr_sheet = 0; nbr_sheet < n; nbr_sheet++) lifts[representation->image[gen_index][nbr_sheet]]->neighbor[face] = covering_tetrahedra[nbr_index][nbr_sheet]; break; default: uFatalError("construct_cover", "cover"); } } /* * Lift the gluings straight from the base tetrahedron. */ for (sheet = 0; sheet < n; sheet++) for (face = 0; face < 4; face++) lifts[sheet]->gluing[face] = base_tetrahedron->gluing[face]; for (sheet = 0; sheet < n; sheet++) for (i = 0; i < 2; i++) /* complete, filled */ { lifts[sheet]->shape[i] = NEW_STRUCT(TetShape); *lifts[sheet]->shape[i] = *base_tetrahedron->shape[i]; copy_shape_history(base_tetrahedron->shape_history[i], &lifts[sheet]->shape_history[i]); } } /* * Create and orient the EdgeClasses. * * Note: These functions require only the tet->neighbor and * tet->gluing fields. * Note: orient_edge_classes() assigns an arbitrary orientation * to each edge class. If the manifold is orientable, * orient() (cf. below) will replace each arbitrary orientation * with the right_handed one. */ create_edge_classes(covering_manifold); orient_edge_classes(covering_manifold); /* * Create the covering_manifold's Cusps in the "natural" order. That is, * Cusps which project to the base_manifold's Cusp 0 should come first, * then Cusps which project to the base_manifold's Cusp 1, etc. */ error_check_for_create_cusps(covering_manifold); /* unnecessary */ covering_cusp_index = 0; /* running count */ for (base_cusp = base_manifold->cusp_list_begin.next; base_cusp != &base_manifold->cusp_list_end; base_cusp = base_cusp->next) { /* * We don't know a priori how many Cusps in the covering_manifold * will project down to the given Cusp in the base_manifold. * So we examine each of the n lifts of the given ideal vertex, * and assign Cusps to those which haven't already been assigned * a Cusp at an earlier iteration of the loop. Let each new * Cusp's matching_cusp field store a pointer to the corresponding * cusp in the base_manifold. Note that we are working with * the basepoint_tets and basepoint_vertices relative to which * the fundamental_group() computed the meridians and longitudes; * we'll need to rely on this fact later on. */ lifts = covering_tetrahedra[base_cusp->basepoint_tet->index]; for (sheet = 0; sheet < n; sheet++) if (lifts[sheet]->cusp[base_cusp->basepoint_vertex] == NULL) { create_one_cusp( covering_manifold, lifts[sheet], FALSE, base_cusp->basepoint_vertex, covering_cusp_index++); lifts[sheet]->cusp[base_cusp->basepoint_vertex]->matching_cusp = base_cusp; } } /* * Install an arbitrary set of peripheral curves. * * Notes: * * (1) After the manifold is oriented we'll replace these * arbitrary curves with a set in which the Dehn filling * curve (if any) is the meridian, and the {meridian, * longitude} pair adhere to the right hand rule. * * (2) peripheral_curves() will determine the CuspTopology of * each Cusp, and write it into the cusp->topology field. */ peripheral_curves(covering_manifold); /* * Count the total number of Cusps, and also the number * with torus and Klein bottle CuspTopology. */ count_cusps(covering_manifold); /* * The Dehn filling curve on each cusp in the covering_manifold * should be a lift of the Dehn filling curve from the corresponding * cusp in the base_manifold. Note that this algorithm works * correctly even for orbifolds: for example, a singular circle * of order 6 in the base_manifold might be covered by singular * circles of order 2 and 3 in a 5-fold covering_manifold, and our * algorithm will provide the correct Dehn filling curve on each one. * * Conceptually we want to trace each Dehn filling curve in the * base_manifold, lifting it to the covering_manifold as we go. * Computationally it's simpler to compute the entire preimage * of the Dehn filling curve in the covering_manifold -- which gives * some number of parallel copies of the true Dehn filling curve -- * and then use the RepresentationIntoSn to deduce the correct multiple. */ /* * Copy the covering_manifold's Dehn filling curves into * scratch_curve[0]. Note that the double_copy_on_tori option * is TRUE, so we'll get copies of the Dehn filling curves on * both the left_ and right_handed sheets of a torus cusp. */ copy_curves_to_scratch(covering_manifold, 0, TRUE); /* * Copy the complete preimage of all the base_manifold's Dehn filling * curves into the scratch_curve[1][M] fields in the covering_manifold. * The scratch_curve[1][L] curves won't be used, so set them to zero. */ for ( base_tetrahedron = base_manifold->tet_list_begin.next, count = 0; base_tetrahedron != &base_manifold->tet_list_end; base_tetrahedron = base_tetrahedron->next, count++) { if (base_tetrahedron->index != count) uFatalError("construct_cover", "cover"); lifts = covering_tetrahedra[base_tetrahedron->index]; for (sheet = 0; sheet < n; sheet++) for (handedness = 0; handedness < 2; handedness++) /* right_handed, left_handed */ for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) { lifts[sheet]->scratch_curve[1][M][handedness][v][f] = base_tetrahedron->cusp[v]->is_complete ? 0 : /* representations.c has already checked that */ /* all Dehn filling coefficients are integers. */ ( (int)base_tetrahedron->cusp[v]->m * base_tetrahedron->curve[M][handedness][v][f] + (int)base_tetrahedron->cusp[v]->l * base_tetrahedron->curve[L][handedness][v][f] ); lifts[sheet]->scratch_curve[1][L][handedness][v][f] = 0; } } /* * Compute the intersection numbers of the preimages of the Dehn * filling curves (in scratch[1]) with the covering_manifold's * peripheral curves (in scratch[0]). */ compute_intersection_numbers(covering_manifold); /* * For each cusp in the covering_manifold, use the intersection * numbers to get the Dehn filling coefficients (relative to the * covering_manifold's peripheral curves) of the complete preimage * of the base_manifold's Dehn filling curve. Divide through by * the great common divisor to get the coefficients for a "primitive" * Dehn filling curve. The paragraph after this one will figure out * what multiple of the primitive curve is correct. */ for (covering_cusp = covering_manifold->cusp_list_begin.next; covering_cusp != &covering_manifold->cusp_list_end; covering_cusp = covering_cusp->next) { covering_cusp->is_complete = covering_cusp->matching_cusp->is_complete; if (covering_cusp->is_complete == FALSE) { covering_cusp->m = covering_cusp->intersection_number[L][M]; covering_cusp->l = -covering_cusp->intersection_number[M][M]; the_gcd = gcd((long int)covering_cusp->m, (long int)covering_cusp->l); covering_cusp->m /= the_gcd; covering_cusp->l /= the_gcd; } } /* * Now's lets figure out the correct multiple of the primitive Dehn * filling curve. The following illustration shows a cross-section * of the universal cover of a tubular neighborhood of one of the * base_manifold's singular circles. In this example the singularity * has order 6. * * \ d / * \ / * e \ / c * _____\/_____ * /\ * f / \ b * / \ * / a \ * * The six labelled points are preimages of a single point in the * base_manifold. They may, however, be preimages of several different * points in the covering_manifold. The primitive_Dehn_image will * tell us which images are distinct. For example, if the * primitive_Dehn_image assigns a permutation which is a cycle of * order 3, then points a, b, and c will be preimages of distinct * points in the covering_manifold, while d, e and f are additional * preimages of those same three points. Note that the nature of * the covering may vary from one sheet of the covering_manifold * to another. For example, the permutation (01)(234) would define * a covering_manifold with two singular circles, one of which has * order 3 and maps nearby points 2-to-1 onto the base_manifold, * and the other of which has order 2 and maps nearby points 3-to-1 * onto the base_manifold. * * In practical terms, we have two questions to answer: * * (1) What is the order of the singularity in the base_manifold? * * (2) What is the smallest power of the primitive Dehn permutation * which takes a given sheet of the covering back to itself? * * The answer to question (1) divided by the answer to question (2) * will tell us the order of the singularity in the covering_manifold. */ for (covering_cusp = covering_manifold->cusp_list_begin.next; covering_cusp != &covering_manifold->cusp_list_end; covering_cusp = covering_cusp->next) { if (covering_cusp->is_complete == FALSE) { /* * Recall that we stashed away a pointer to the base_cusp. */ base_cusp = covering_cusp->matching_cusp; /* * The answer to question (1) is easy. */ base_singularity = gcd((long int)base_cusp->m, (long int)base_cusp->l); /* * The basepoint used for defining the meridian and longitude * (in the base_manifold's cusp) has n lifts to the covering * manifold, implicitly labelled by the indices 0, 1, ..., n-1. * Find the index of a lift which belongs to the present * covering_cusp. */ lifts = covering_tetrahedra[base_cusp->basepoint_tet->index]; v = base_cusp->basepoint_vertex; for (index = 0; index < n; index++) if (lifts[index]->cusp[v] == covering_cusp) break; if (index == n) uFatalError("construct_cover", "cover"); /* * How many times must we apply the primitive Dehn permutation * to the index before it returns to its original value? * This answers question (2). */ primitive_permutation = representation->primitive_Dehn_image[base_cusp->index]; index0 = index; count = 0; do { index = primitive_permutation[index]; count++; } while (index != index0); /* * Divide answer (1) by answer (2) to get the order of * the singularity in the covering_manifold. */ if (base_singularity % count != 0) uFatalError("construct_cover", "cover"); covering_singularity = base_singularity / count; /* * Multiply the primitive Dehn filling curve * by the covering_singularity. */ covering_cusp->m *= covering_singularity; covering_cusp->l *= covering_singularity; } } /* * Free the 2-dimensional array used to keep track of the Tetrahedra. */ for (i = 0; i < base_manifold->num_tetrahedra; i++) my_free(covering_tetrahedra[i]); my_free(covering_tetrahedra); /* * Attempt to orient the manifold. Note that orient() may change * the vertex/face indexing, so we call it after we've lifted all * the information we need from the base_manifold. */ orient(covering_manifold); /* * If the covering_manifold is oriented, orient() moves all * peripheral curves to the right_handed sheets. Thus some * {meridian, longitude} pairs may no longer adhere to the * right-hand rule. Correct them, and adjust the Dehn filling * coefficients accordingly. */ if (covering_manifold->orientability == oriented_manifold) fix_peripheral_orientations(covering_manifold); /* * Normally the holonomies and cusp shapes are computed as part of * the computation of the hyperbolic structure. But we've lifted * the hyperbolic structure directly from the base_manifold. So * we compute the holonomies and cusp shapes explicitly. */ compute_the_holonomies(covering_manifold, ultimate); compute_the_holonomies(covering_manifold, penultimate); compute_cusp_shapes(covering_manifold, initial); compute_cusp_shapes(covering_manifold, current); /* * Lift the Chern-Simons value (if any) from the base manifold, * and use it to compute the fudge factor. */ covering_manifold->CS_value_is_known = base_manifold->CS_value_is_known; if (base_manifold->CS_value_is_known) { covering_manifold->CS_value[ultimate] = n * base_manifold->CS_value[ultimate]; covering_manifold->CS_value[penultimate] = n * base_manifold->CS_value[penultimate]; } compute_CS_fudge_from_value(covering_manifold); /* * If the covering_manifold is hyperbolic, install a set of shortest * basis curves on all cusps. (The shortest curves on filled cusps * will be replaced below by curves for which the Dehn filling curve * is a multiple of the meridian.) */ switch (covering_manifold->solution_type[complete]) { case geometric_solution: case nongeometric_solution: install_shortest_bases(covering_manifold); break; } /* * On filled cusps, install a basis in which the Dehn filling curves * are (perhaps multiples of) a meridian. */ change_matrices = NEW_ARRAY(covering_manifold->num_cusps, MatrixInt22); for (i = 0; i < covering_manifold->num_cusps; i++) current_curve_basis(covering_manifold, i, change_matrices[i]); change_peripheral_curves(covering_manifold, change_matrices); my_free(change_matrices); return covering_manifold; } snappea-3.0d3/SnapPeaKernel/code/current_curve_basis.c0100444000175000017500000001105707041126764021113 0ustar babbab/* * current_curve_basis.c * * This file provides the functions * * void current_curve_basis( Triangulation *manifold, * int cusp_index, * MatrixInt22 basis_change); * * void install_current_curve_bases( Triangulation *manifold); * * * current_curve_basis() accepts a Triangulation and a cusp index, * and computes a 2 x 2 integer matrix basis_change with the property that * * if the Cusp of index cusp_index is filled, and has * integer Dehn filling coefficients, * * the first row of basis_change is set to the current * Dehn filling coefficients (divided by their gcd), and * the second row of basis_change is set to the shortest * curve which completes a basis. * * else * * basis_change is set to the identity * * Note that for nonorientable cusps, the only possible Dehn * filling coefficients are +/- (m,0), and +/- the longitude will be the * shortest curve which completes the basis. * * install_current_curve_bases() installs the current curve basis * on each Cusp of manifold. * * 96/9/28 Modified to accept non relatively prime integer coefficients. * For example, (15, 20) is treated the same as (3,4). Thus in * the new basis the surgery coefficients will be of the form (m,0), * where m is the gcd of the original coefficients. * * 99/11/05 Added install_current_curve_bases(). */ #include "kernel.h" #define EPSILON 1e-5 #define BIG_MODULUS 1e+5 static void current_curve_basis_on_cusp(Cusp *cusp, MatrixInt22 basis_change); void current_curve_basis( Triangulation *manifold, int cusp_index, MatrixInt22 basis_change) { current_curve_basis_on_cusp( find_cusp(manifold, cusp_index), basis_change); } static void current_curve_basis_on_cusp( Cusp *cusp, MatrixInt22 basis_change) { int m_int, l_int, the_gcd; long a, b; Complex new_shape; int multiple; int i, j; m_int = (int) cusp->m; l_int = (int) cusp->l; if (cusp->is_complete == FALSE /* cusp is filled and */ && m_int == cusp->m /* coefficients are integers */ && l_int == cusp->l) { /* * Find a and b such that am + bl = gcd(m, l). */ the_gcd = euclidean_algorithm(m_int, l_int, &a, &b); /* * Divide through by the g.c.d. */ m_int /= the_gcd; l_int /= the_gcd; /* * Set basis_change to * * m l * -b a */ basis_change[0][0] = m_int; basis_change[0][1] = l_int; basis_change[1][0] = -b; basis_change[1][1] = a; /* * Make sure the new longitude is as short as possible. * The ratio (new longitude)/(new meridian) should have a * real part in the interval (-1/2, +1/2]. */ /* * Compute the new_shape, using the tentative longitude. */ new_shape = transformed_cusp_shape( cusp->cusp_shape[initial], basis_change); /* * 96/10/1 There is a danger that for nonhyperbolic solutions * the cusp shape will be ill-defined (either very large or NaN). * However for some nonhyperbolic solutions (flat solutions * for example) it may make good sense. So we attempt to * make the longitude short iff the new_shape is defined and * not outrageously large; otherwise we're content with an * arbitrary longitude. */ if (complex_modulus(new_shape) < BIG_MODULUS) { /* * Figure out how many meridians we need to subtract * from the longitude. */ multiple = (int) floor(new_shape.real - (-0.5 + EPSILON)); /* * longitude -= multiple * meridian */ for (j = 0; j < 2; j++) basis_change[1][j] -= multiple * basis_change[0][j]; } } else { /* * Set basis_change to the identity. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = (i == j); } } void install_current_curve_bases( Triangulation *manifold) { Cusp *cusp; MatrixInt22 *change_matrices; /* * Allocate an array to store the change of basis matrices. */ change_matrices = NEW_ARRAY(manifold->num_cusps, MatrixInt22); /* * Compute the change of basis matrices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->index < 0 || cusp->index >= manifold->num_cusps) uFatalError("install_current_curve_bases", "current_curve_basis"); current_curve_basis_on_cusp(cusp, change_matrices[cusp->index]); } /* * Install the change of basis matrices. */ if (change_peripheral_curves(manifold, change_matrices) != func_OK) uFatalError("install_current_curve_bases", "current_curve_basis"); /* * Free the array used to store the change of basis matrices. */ my_free(change_matrices); } snappea-3.0d3/SnapPeaKernel/code/cusp_cross_sections.c0100444000175000017500000003653506742675501021153 0ustar babbab/* * cusp_cross_sections.c * * This file provides the high-level functions * * void allocate_cross_sections(Triangulation *manifold); * void free_cross_sections(Triangulation *manifold); * void compute_cross_sections(Triangulation *manifold); * void compute_tilts(Triangulation *manifold); * * for use within the kernel, in particular by canonize(). * * It also provides the low-level functions * * void compute_three_edge_lengths(Tetrahedron *tet, VertexIndex v, * FaceIndex f, double known_length); * void compute_tilts_for_one_tet(Tetrahedron *tet); * * for its own use, and for the use of two_to_three() and * three_to_two() in simplify_triangulation.c (so they can * maintain cusp cross sections and tilts correctly). * Further documentation of compute_three_edge_lengths() * and compute_tilts_for_one_tet() appears in the code itself. * * The cusp cross section functions, as well as canonize(), use the * concepts and terminology of * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * The Tilt Theorem (contained in the above paper) is generalized * and given a nicer proof in * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * compute_cross_sections() and compute_tilts() set the cross_section * and tilt fields, respectively, of the Tetrahedron data structure. * * The vertex cross section at vertex v of Tetrahedron tet is a * triangle. The length of its edge incident to face f of tet is * stored as tet->cross_section->edge_length[v][f]. (The edge_length * is undefined when v == f.) * * tet->tilt[f] stores the tilt of the Tetrahedron tet relative to face f. * * By convention, * * when no cusp cross sections are in place, the cross_section field * of each Tetrahedron is set to NULL, and * * when cusp cross sections are created, the routine that creates * them must allocate the VertexCrossSections structures. * * Thus, routines which modify a triangulation (e.g. the two_to_three() * and three_to_two() moves) know that they must keep track of cusp cross * sections if and only if the cross_section fields of the Tetrahedra are * not NULL. * * allocate_cross_sections() and free_cross_sections() allocate and * free the VertexCrossSections. * * compute_cross_sections() sets the (already allocated) VertexCrossSections * to correspond to cusp cross sections of area (3/8)sqrt(3). As explained * in cusp_neighborhoods.c, such cusp cross sections will always have * nonoverlapping interiors. * * compute_tilts() applies the Tilt Theorem (see "Convex hulls...") * to compute the tilts from the VertexCrossSections. * * The standard way to use these functions is * * allocate_cross_sections(manifold); * compute_cross_sections(manifold); * compute_tilts(manifold); * *** Do stuff with the tilts, possibly including calls to *** * *** two_to_three() and three_to_two(), which update the *** * *** cross_sections and tilts correctly whenever the *** * *** cross_section pointers are not NULL. *** * free_cross_sections(manifold); */ #include "kernel.h" #define CIRCUMRADIUS_EPSILON 1e-10 typedef struct ideal_vertex { Tetrahedron *tet; VertexIndex v; struct ideal_vertex *next; } IdealVertex; static void initialize_flags(Triangulation *manifold); static void cross_section(Triangulation *manifold, Cusp *cusp); static void find_starting_point(Triangulation *manifold, Cusp *cusp, Tetrahedron **tet0, VertexIndex *v0); static double vertex_area(IdealVertex *ideal_vertex); static void normalize_cusp(Triangulation *manifold, Cusp *cusp, double cusp_area); void allocate_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure no VertexCrossSections * are already allocated. */ if (tet->cross_section != NULL) uFatalError("allocate_cross_sections", "cusp_cross_sections"); /* * Allocate a VertexCrossSections structure. */ tet->cross_section = NEW_STRUCT(VertexCrossSections); } } void free_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure the VertexCrossSections * really are there. */ if (tet->cross_section == NULL) uFatalError("free_cross_sections", "cusp_cross_sections"); /* * Free the VertexCrossSections structure, and set the pointer * to NULL. */ my_free(tet->cross_section); tet->cross_section = NULL; } } void compute_cross_sections( Triangulation *manifold) { Cusp *cusp; /* * Initialize cross_section->has_been_set flags to FALSE. */ initialize_flags(manifold); /* * Compute a cross section of area (3/8)sqrt(3) for each cusp. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cross_section(manifold, cusp); } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cross_section->has_been_set[v] = FALSE; } static void cross_section( Triangulation *manifold, Cusp *cusp) { double cusp_area; Tetrahedron *tet0, *nbr_tet; VertexIndex v0, nbr_v; FaceIndex f, nbr_f; IdealVertex *vertex_stack, *initial_vertex, *this_vertex, *nbr_vertex; Permutation gluing; /* * The plan is to compute an arbitrary cross section of the * cusp, and then normalize it to have area (3/8)sqrt(3). */ /* * The variable cusp_area will keep track of the area of the * cusp cross section. Initialize it to zero. */ cusp_area = 0.0; /* * Find an ideal vertex belonging to this cusp. */ find_starting_point(manifold, cusp, &tet0, &v0); /* * Set the edge_length of some edge of the initial vertex cross section * to some arbitrary value, say 1.0, and compute the other two * edge_lengths at the initial vertex in terms of it. * Set the has_been_set flag to TRUE. */ compute_three_edge_lengths(tet0, v0, !v0, 1.0); /* * At this point the simplest thing would be to write a * recursive function to set the edge_lengths of the remaining * vertices. However, recursive functions can cause trouble * (e.g. stack/heap collisions) if the recursion is exceptionally * deep, so I'll create my own stack explicitly. The stack will * contain vertices whose edge_lengths are set, but whose neighbors * have not yet been checked. Each ideal vertex experiences the * following operations in the following order: * * (1) edge_lengths are computed * (2) has_been_set flag is set to TRUE * (3) IdealVertex is put on stack * (4) IdealVertex comes off stack * (5) area of vertex cross section is added to cusp_area * (6) neighboring ideal vertices are checked, and * added to stack as necessary * (7) IdealVertex data structure is destroyed * * Proposition. Each ideal vertex goes onto the stack exactly once. * Proof. No ideal vertex can go onto the stack more than once, * because once its has_been_set flag is TRUE it is excluded from * further consideration. When a vertex comes off the * stack its neighbors are considered for addition to the stack, * therefore because the cusp is connected all its ideal vertices * will eventually go onto the stack. */ initial_vertex = NEW_STRUCT(IdealVertex); initial_vertex->tet = tet0; initial_vertex->v = v0; initial_vertex->next = NULL; vertex_stack = initial_vertex; while (vertex_stack != NULL) { /* * Pull an IdealVertex off the vertex_stack. */ this_vertex = vertex_stack; vertex_stack = vertex_stack->next; /* * Add the area of the vertex cross section to cusp_area. */ cusp_area += vertex_area(this_vertex); /* * Check the three neighbors of this IdealVertex. */ for (f = 0; f < 4; f++) { if (f == this_vertex->v) continue; /* * Locate this_vertex's neighbor by face f. */ gluing = this_vertex->tet->gluing[f]; nbr_tet = this_vertex->tet->neighbor[f]; nbr_v = EVALUATE(gluing, this_vertex->v); /* * If the neighbor's edge_lengths have not yet been computed, * compute them and add the neighbor to the stack. */ if (nbr_tet->cross_section->has_been_set[nbr_v] == FALSE) { /* * Find the face of nbr_tet which glues to * face f of this_vertex->tet. */ nbr_f = EVALUATE(gluing, f); /* * Set the edge_lengths of vertex nbr_v of Tetrahedron * nbr_tet, and set its has_been_set flag to TRUE. */ compute_three_edge_lengths( nbr_tet, nbr_v, nbr_f, this_vertex->tet->cross_section->edge_length[this_vertex->v][f]); /* * Add the neighbor to the stack. */ nbr_vertex = NEW_STRUCT(IdealVertex); nbr_vertex->tet = nbr_tet; nbr_vertex->v = nbr_v; nbr_vertex->next = vertex_stack; vertex_stack = nbr_vertex; } } /* * Free this IdealVertex. */ my_free(this_vertex); } /* * We have constructed a cusp cross section of area cusp_area. * To normalize it to have area (3/8)sqrt(3), we must multiply all * edge_lengths by sqrt( (3/8)sqrt(3) / cusp_area ). */ normalize_cusp(manifold, cusp, cusp_area); } static void find_starting_point( Triangulation *manifold, Cusp *cusp, Tetrahedron **tet0, VertexIndex *v0) { for (*tet0 = manifold->tet_list_begin.next; *tet0 != &manifold->tet_list_end; *tet0 = (*tet0)->next) for (*v0 = 0; *v0 < 4; (*v0)++) if ((*tet0)->cusp[*v0] == cusp) return; /* * We should never get to this point. */ uFatalError("find_starting_point", "cusp_cross_sections"); } /* * compute_three_edge_lengths() sets tet->cross_section->edge_length[v][f] * to known_length, computes the remaining two edge_lengths at vertex v * in terms of it, and sets the has_been_set flag to TRUE. */ void compute_three_edge_lengths( Tetrahedron *tet, VertexIndex v, FaceIndex f, double known_length) { double *this_triangle; FaceIndex left_face, right_face; /* * For convenience, note which triangle we're working with. */ this_triangle = tet->cross_section->edge_length[v]; /* * Set the given edge_length. */ this_triangle[f] = known_length; /* * Find the left and right edges of the triangle, corresponding * to the left_face and right_face of the Tetrahedron, in the * imagery of positioned_tet.h. Work relative to the right_handed * Orientation of the Tetrahedron, since that's how the TetShapes * are defined. */ left_face = remaining_face[v][f]; right_face = remaining_face[f][v]; /* * The real part of the logarithmic form of the angle between the * near and left faces gives us the log of the ratio of the lengths * of the near and left sides of this_triangle, and similarly for * the right side. */ this_triangle[left_face] = known_length * exp(tet->shape[complete]->cwl[ultimate][edge3_between_faces[f][left_face ]].log.real); this_triangle[right_face] = known_length / exp(tet->shape[complete]->cwl[ultimate][edge3_between_faces[f][right_face]].log.real); /* * Set the has_been_set flag to TRUE. */ tet->cross_section->has_been_set[v] = TRUE; } static double vertex_area( IdealVertex *ideal_vertex) { /* * We compute the area of a triangular vertex cross section * using Heron's formula * * area = sqrt( s * (s - a) * (s - b) * (s - c) ) * * where a, b and c are the length of the triangle's sides, * and s is the semiperimeter (a + b + c)/2. */ double *this_triangle, a, b, c, s, area; VertexIndex v; FaceIndex face_a, face_b, face_c; v = ideal_vertex->v; face_a = ! v; face_b = remaining_face[v][face_a]; face_c = remaining_face[face_a][v]; this_triangle = ideal_vertex->tet->cross_section->edge_length[v]; a = this_triangle[face_a]; b = this_triangle[face_b]; c = this_triangle[face_c]; s = 0.5 * (a + b + c); area = safe_sqrt( s * (s - a) * (s - b) * (s - c) ); return area; } static void normalize_cusp( Triangulation *manifold, Cusp *cusp, double cusp_area) { double factor; Tetrahedron *tet; VertexIndex v; FaceIndex f; /* * The given cusp has area cusp_area. * Multiply all the edge_lengths by sqrt( (3/8)sqrt(3) / cusp_area ) * to normalize the area to (3/8)sqrt(3). */ factor = safe_sqrt(0.375 * ROOT_3 / cusp_area); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) for (f = 0; f < 4; f++) if (f != v) tet->cross_section->edge_length[v][f] *= factor; } void compute_tilts( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) compute_tilts_for_one_tet(tet); } void compute_tilts_for_one_tet( Tetrahedron *tet) { double factor, R[4]; int i, j; /* * Theorem 2 of "Convex hulls..." gives the tilts in terms * of the circumradii. A generalization of the theorem and * a cleaner proof appear in "Canonical cell decompositions...". * * We may compute the circumradius of a triangle in terms * of the length of any side c and its opposite angle C, * according to the formula * * R = c / (2 sin(C)) * * We must be careful in the case of flat (or almost flat) * ideal Tetrahedra. As sin(C) goes to zero, the circumradii * and the tilts go to infinity. We must take care that the * numerical values computed for the circumradii are in * proportion to the linear dimensions of the four vertex * cross sections. That way even though the numerical values * of the tilts will be very large numbers, they will have * the correct signs, and the canonization algorithm will proceed * correctly. To insure that the circumradii are computed * correctly, we use a fixed value for sin(C) (rather than reading * the sines of different angles at different vertex cross sections), * and we make sure its value exceeds some small epsilon (in * particular, we don't want it to be zero). */ /* * Compute the circumradii. */ /* * Let factor = 2 sin(C), where C is the angle at edge 0. * Make sure factor is at least CIRCUMRADIUS_EPSILON. */ factor = 2 * sin(tet->shape[complete]->cwl[ultimate][0].log.imag); if (factor < CIRCUMRADIUS_EPSILON) factor = CIRCUMRADIUS_EPSILON; /* * Use the relationship R = c / factor (cf. above) to compute * the circumradii. */ R[0] = tet->cross_section->edge_length[0][1] / factor; R[1] = tet->cross_section->edge_length[1][0] / factor; R[2] = tet->cross_section->edge_length[2][3] / factor; R[3] = tet->cross_section->edge_length[3][2] / factor; /* * 95/9/19 JRW * Scale the circumradii according to the cusps' displacements. * As explained in cusp_neighborhoods.c, a cusp's linear * dimensions vary as the exponential of the displacement. */ for (i = 0; i < 4; i++) R[i] *= tet->cusp[i]->displacement_exp; /* * Apply the Tilt Theorem to compute the tilts in terms * of the circumradii. */ for (i = 0; i < 4; i++) { tet->tilt[i] = 0.0; for (j = 0; j < 4; j++) if (j == i) tet->tilt[i] += R[j]; else tet->tilt[i] -= R[j] * cos(tet->shape[complete]->cwl[ultimate][edge3_between_vertices[i][j]].log.imag); } } snappea-3.0d3/SnapPeaKernel/code/cusp_neighborhoods.c0100444000175000017500000037216307204002574020731 0ustar babbab/* * cusp_neighborhoods.c * * This file provides the following functions for creating and manipulating * horospherical cross sections of a manifold's cusps, and computing the * triangulation dual to the corresponding Ford complex. These functions * communicate with the UI by passing pointers to CuspNeighborhoods * data structures; but even though the UI may keep pointers to * CuspNeighborhoods, the structure's internal details are private * to this file. * * CuspNeighborhoods *initialize_cusp_neighborhoods( * Triangulation *manifold); * * void free_cusp_neighborhoods( * CuspNeighborhoods *cusp_neighborhoods); * * [Many other externally available functions are provided -- please see * SnapPea.h for details.] * * When the canonical cell decomposition dual to the Ford complex is not * a triangulation, it is arbitrarily subdivided into tetrahedra. * * Note: This file uses the fields "displacement" and "displacement_exp" * in the Cusp data structure to keep track of where the cusp cross * sections are (details appear below). Manifolds not under the care * of a CuspNeighborhoods structure should keep the "displacement" set * to 0 and the "displacement_exp" set to 1 at all times, so that * canonical cell decompositions will be computed relative to cusps * cross sections of equal volume. */ /* * Proposition 1. The area of a horospherical cusp cross section is * exactly twice the volume it contains. * * Proof. Do an integral in the upper half space model of hyperbolic * 3-space. Consider a unit square in the horosphere z == 1, and calculate * the volume lying above it as the integral of 1/z^3 dz from z = 1 to * z = infinity. QED * * Comment. This proposition relies on the hyperbolic manifold having * curvature -1. If the curvature had some other value, the proportionality * constant would be something other than 2. * * Comment. The proportionality constant has units of 1 / distance. * Normally, though, one doesn't have to think about units in hyperbolic * geometry, because one uses the canonical ones. I just wanted to make * sure that nobody is bothered by the fact that we're specifying an area * as twice a volume. * * Comment. We can measure the size of a cusp cross section by area or * by volume. The two measures are the same modulo a factor of two. * * * Proposition 2. If we choose a manifold's cusp cross sections to each * have area (3/8)sqrt(3), then their interiors cannot overlap themselves * or each other. * * Proof. By Lemma A below, we can choose a set of cusp cross sections * with nonoverlapping interiors. Advance each cusp cross section into * the fat part of the manifold, until it bumps into itself or another * cross section. Look at the horoball packing as seen from a given cusp. * Because the cusp is tangent to itself or some other cusp, there'll be * a maximally large horoball. If we draw the given cusp as the plane * z == 1 in the upper half space model, the maximal horoball (and each of * its translates) will appear as a sphere of diameter 1. The view as * seen from the given cusp therefore includes a packing of disjoint circles * of diameter 1/2. If it's a hexagonal packing the area of the cusp will * equal the area of a hexagon of outradius 1/2, which works out to be * (3/8)sqrt(3). If it's not a hexagonal packing, the cusp's area will be * even greater. In the latter case, we retract the cusp cross section * until its area is exactly (3/8)sqrt(3). QED * * Lemma A. We can choose a set of cusp cross sections with nonoverlapping * interiors. * * Comment. One expects the proof of this lemma to be completely trivial, * but I don't think it is. * * Proof #1. Lemma A follows directly from the Margulis Lemma. The * required cusp cross sections are portions of the boundary of the thin * part of the manifold. QED * * Proof #2. Start with any decomposition of the manifold into positively * oriented 3-cells. For example, we could start with the canonical cell * decomposition constructed in * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * Choose arbitrary cusp cross sections. Retract each cusp as necessary * so that its intersection with each 3-cell is "standard". (I don't want * to spend a lot of time fussing over the wording of this -- the idea is * that the cross section shouldn't be so far forward that it has * unnecessary intersections with other faces of the 3-cell.) Then further * retract each cusp cross section so that it doesn't intersect the other * cross sections incident to the same 3-call. This gives the nonoverlapping * cross sections, as required. QED * * * Definition. The "home position" of a cusp cross section is the one * at which its area is (3/8)sqrt(3) and its enclosed volume is (3/16)sqrt(3). * * By Proposition 2 above, when all the cusps are at their home positions, * their interiors are disjoint. * * The displacement field in the Cusp data structure measures how far * a cusp cross section is from its home position. The displacement is * measured towards the fat part of the manifold, so a positive displacement * means the cusp cross section is larger, and a negative displacement * means it is smaller. * * If we visualize a cusp's home position as a plane at height z == 1 in * the upper half space model, then after a displacement d > 0 it will be * at some height h < 1. Set d equal to the integral of dz/z from z = h * to z = 1 to obtain d = - log h, or h = exp(-d). It follows that a cusp's * linear dimensions vary as exp(d), while its area (and therefore its * enclosed volume) vary as exp(2d). The Cusp data structure stores the * quantity exp(d) in its displacement_exp field, to avoid excessive * recomputation. * * * Definition. The "reach" of a cusp is the distance from the cross * section's home position to the position at which it first bumps into * itself. * * Note that the reach is half the distance from the cusp to itself, * measured along the shortest homotopically nontrivial path. * Proposition 2 implies that the reach of each cusp will be nonnegative. * * Definition. As a given cusp cross section moves forward into the * fat part of the manifold, the first cusp cross section it bumps into * is called its "stopper". The displacement (measured from the home * position) at which the given cusp meets its stopper is called the * "stopping displacement". * * Comment. Unlike the reach, the stopper and the stopping displacement * depend on the current displacements of all the cusps in the triangulation. * They vary dynamically as the user moves the cusp cross sections. * * * Sometimes the user may wish to change two or more cusp displacements * in unison. The Cusp's is_tied field supports this. The displacements * of "tied" cusps always stay the same -- when one changes they all do. * The tie_group_reach keeps track of the reach of the tied cusps: * it tells the displacement at which some cusp in the group first * bumps into itself or some other cusp in the group. Note that the * tie_group_reach might be less than the stopping displacement of any * of its constituent cusps; this is because when a cusp moves forward * its (ordinary) stopper stays still, but members of its tie group * move towards it. */ #include "kernel.h" #include "canonize.h" #include /* needed for qsort() and rand() */ /* * Report all horoballs higher than the requested cutoff_height * minus CUTOFF_HEIGHT_EPSILON. For example, if the user wants to see * all horoballs of height at least 0.25, we should report a horoball * of height 0.249999999963843. */ #define CUTOFF_HEIGHT_EPSILON 1e-6 /* * A horoball is considered to be "maximal" iff it's distance from a fixed * cusp is within INTERCUSP_EPSILON of being minimal. (The idea is that * if there are several different maximal cusps, whose distances from the * fixed cusp differ only by roundoff error, we want to consider all them * to be maximal.) */ #define INTERCUSP_EPSILON 1e-6 /* * If a given cusp does not have a maximal horoball, all other cusp cross * sections are retracted in increments of DELTA_DISPLACEMENT until it does. * The value of DELTA_DISPLACEMENT should be large enough that the algorithm * has a fair shot at the getting a maximal horoball on the first try, * but not so large that the canonization algorithm has to do a lot of * thrasing around (in particular, we don't want it to have to randomize * very often). */ #define DELTA_DISPLACEMENT 0.5 /* * If the longitudinal translation has length zero, * something has gone very, very wrong. */ #define LONGITUDE_EPSILON 1e-2 /* * contains_north_pole() uses NORTH_POLE_EPSILON to decide when a face * of a tetrahedron stands vertically over a vertex. */ #define NORTH_POLE_EPSILON 1e-6 /* * A complex number of modulus greater than KEY_INFINITY is considered * to be infinite, at least for the purpose of computing key values. */ #define KEY_INFINITY 1e+6 /* * tiling_tet_on_tree() will compare two TilingTets iff their key values * are within KEY_EPSILON of each other. KEY_EPSILON can be fairly large; * other than a loss of speed there is no harm in having the program make * some occasional unnecessary comparisons. */ #define KEY_EPSILON 1e-4 /* * Two TilingTets are considered equivalent under the Z + Z action of * the cusp translations iff their corresponding (transformed) corners * lie within CORNER_EPSILON of each other. */ #define CORNER_EPSILON 1e-6 /* * cull_duplicate_horoballs() checks whether two horoballs are equivalent * iff their radii differ by less than DUPLICATE_RADIUS_EPSILON. * We should make DUPLICATE_RADIUS_EPSILON fairly large, to be sure we * don't miss any horoballs even when their precision is low. */ #define DUPLICATE_RADIUS_EPSILON 1e-3 typedef int MinDistanceType; enum { dist_self_to_self, dist_self_to_any, dist_group_to_group, dist_group_to_any }; typedef struct { Tetrahedron *tet; Orientation h; VertexIndex v; } CuspTriangle; typedef struct TilingHoroball { CuspNbhdHoroball data; struct TilingHoroball *next; } TilingHoroball; typedef struct TilingTet { /* * Which Tetrahedron in the original manifold lifts to this TilingTet? */ Tetrahedron *underlying_tet; /* * Does it appear with the left_handed or right_handed orientation? */ Orientation orientation; /* * Where are its four corners on the boundary of upper half space? */ Complex corner[4]; /* * What is the Euclidean diameter of the horoball at each corner? */ double horoball_height[4]; /* * If the neighboring TilingTet incident to face f has already been * found, neighbor_found[f] is set to TRUE so we won't waste time * finding it again. More importantly, we won't have to worry about * the special case of "finding" the initial TilingTets incident to * the "horoball of infinite Euclidean radius". */ Boolean neighbor_found[4]; /* * Pointer for the NULL-terminated queue. */ struct TilingTet *next; /* * Pointers for the tree. */ /* * The left child and right child pointers implement the binary tree. */ struct TilingTet *left, *right; /* * The sort key is a continuous function of the TilingTet's corners, * and is well defined under the Z + Z action of the group of * covering transformations of the cusp. */ double key; /* * We don't want our tree handling functions to be recursive, * for fear of stack/heap collisions. So we implement them using * our own private stack, which is a NULL-terminated linked list * using the next_subtree pointer. Unlike the "left" and "right" * fields (which are maintained throughout the algorithm) the * "next_subtree" field is used only locally within a given tree * handling function. */ struct TilingTet *next_subtree; } TilingTet; typedef struct { TilingTet *begin, *end; } TilingQueue; static void initialize_cusp_displacements(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_reaches(CuspNeighborhoods *cusp_neighborhoods); static void compute_one_reach(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp); static void compute_tie_group_reach(CuspNeighborhoods *cusp_neighborhoods); static Cusp *some_tied_cusp(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_stoppers(CuspNeighborhoods *cusp_neighborhoods); static void compute_intercusp_distances(Triangulation *manifold); static void compute_one_intercusp_distance(EdgeClass *edge); static double compute_min_dist(Triangulation *manifold, Cusp *cusp, MinDistanceType min_distance_type); static void initialize_cusp_ties(CuspNeighborhoods *cusp_neighborhoods); static void initialize_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static void allocate_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static Boolean contains_meridian(Tetrahedron *tet, Orientation h, VertexIndex v); static void set_one_component(Tetrahedron *tet, Orientation h, VertexIndex v, int max_triangles); static CuspNbhdHoroballList *get_quick_horoball_list(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp); static void get_quick_edge_horoballs(Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball); static void get_quick_face_horoballs(Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball); static CuspNbhdHoroballList *get_full_horoball_list(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp, double cutoff_height); static void compute_exp_min_d(Triangulation *manifold); static void compute_parallelogram_to_square(Complex meridian, Complex longitude, double parallelogram_to_square[2][2]); static void read_initial_tetrahedra(Triangulation *manifold, Cusp *cusp, TilingQueue *tiling_queue, TilingTet **tiling_tree_root, TilingHoroball **horoball_linked_list, double cutoff_height); static TilingTet *get_tiling_tet_from_queue(TilingQueue *tiling_queue); static void add_tiling_tet_to_queue(TilingTet *tiling_tet, TilingQueue *tiling_queue); static void add_tiling_horoball_to_list(TilingTet *tiling_tet, VertexIndex v, TilingHoroball **horoball_linked_list); static Boolean face_contains_useful_edge(TilingTet *tiling_tet, FaceIndex f, double cutoff_height); static TilingTet *make_neighbor_tiling_tet(TilingTet *tiling_tet, FaceIndex f); static void prepare_sort_key(TilingTet *tiling_tet, double parallelogram_to_square[2][2]); static Boolean tiling_tet_on_tree(TilingTet *tiling_tet, TilingTet *tiling_tree_root, Complex meridian, Complex longitude); static Boolean same_corners(TilingTet *tiling_tet1, TilingTet *tiling_tet2, Complex meridian, Complex longitude); static void add_tiling_tet_to_tree(TilingTet *tiling_tet, TilingTet **tiling_tree_root); static void add_horoball_if_necessary(TilingTet *tiling_tet, TilingHoroball **horoball_linked_list, double cutoff_height); static Boolean contains_north_pole(TilingTet *tiling_tet, VertexIndex v); static void free_tiling_tet_tree(TilingTet *tiling_tree_root); static CuspNbhdHoroballList *transfer_horoballs(TilingHoroball **horoball_linked_list); static int CDECL compare_horoballs(const void *horoball0, const void *horoball1); static void cull_duplicate_horoballs(Cusp *cusp, CuspNbhdHoroballList *aHoroballList); /* * Conceptually, the CuspNeighborhoods structure stores cross sections * of a manifold's cusps, and also keeps a Triangulation dual to the * corresponding Ford complex. In the present implementation, the * information about the cross sections is stored entriely within the * copy of the triangulation (specifically, in the Cusp's displacment, * displacement_exp and reach fields, the EdgeClass's intercusp_distance * field, and the Triangulation's max_reach field). * * SnapPea.h (the only header file common to the user interface and the * computational kernel) contains the opaque typedef * * typedef struct CuspNeighborhoods CuspNeighborhoods; * * This opaque typedef allows the user interface to declare and pass * a pointer to a CuspNeighborhoods structure, without being able to * access a CuspNeighborhoods structure's fields directly. Here is * the actual definition, which is private to this file. */ struct CuspNeighborhoods { /* * We'll keep our own private copy of the Triangulation, to avoid * messing up the original one. */ Triangulation *its_triangulation; }; /* * Technical musings. * * There are different approaches to maintaining a canonical * triangulation as the cusp displacements change. * * Low-level approach. * Handle the 2-3 and 3-2 moves explicitly. Calculate which * move will be required next as the given cusp moves towards * the requested displacement. * * High-level approach. * Set the requested cusp displacement directly, and call the * standard proto_canonize() function to compute the corresponding * canonical triangulation. * * The low-level approach would be much more efficient at run time. * The overhead of setting up the cusp cross sections at the beginning, * and polishing the hyperbolic structure at the end, would be done * only once. It would also be efficient in that it tracks the convex * hull (i.e. the canonical triangulation) precisely as the cusp moves * toward the requested displacement. (At each step it finds the next * 2-3 or 3-2 move which would be required as the cusp cross section * moves continuously towards the requested displacement.) * * The drawback of the low-level approach is that it would require * a lot of low-level programming, which is time consuming, tends to * make a mess, and can be error prone. The high-level approach keeps * the code cleaner, even though it's less efficient at run time. * * For now I have implemented the high-level approach. If it turns * out that it is too slow, I can consider replacing it with the * low-level approach. An even better approach might be to make * some simple changes to speed up the high-level approach. For example, * I was concerned that for large manifolds proto_canonize()'s bottleneck * might be polishing the hyperbolic structure at the end. I modified * proto_canonize() to polish the hyperbolic structure iff the * triangulation has been changed. */ CuspNeighborhoods *initialize_cusp_neighborhoods( Triangulation *manifold) { Triangulation *simplified_manifold; CuspNeighborhoods *cusp_neighborhoods; /* * If the space isn't a manifold, return NULL. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) return NULL; /* * Get rid of "unnecessary" cusps. * If we encounter topological obstructions, return NULL. */ simplified_manifold = fill_reasonable_cusps(manifold); if (simplified_manifold == NULL) return NULL; /* * If the manifold is closed, free it and return NULL. */ if (all_cusps_are_filled(simplified_manifold) == TRUE) { free_triangulation(simplified_manifold); return NULL; } /* * Attempt to canonize the manifold. */ if (proto_canonize(simplified_manifold) == func_failed) { free_triangulation(simplified_manifold); return NULL; } /* * Our manifold has passed all its tests, * so set up a CuspNeighborhoods structure. */ cusp_neighborhoods = NEW_STRUCT(CuspNeighborhoods); /* * Install our private copy of the triangulation. */ cusp_neighborhoods->its_triangulation = simplified_manifold; simplified_manifold = NULL; /* * Most likely the displacements will be zero already, * but we set them anyhow, just to be safe. */ initialize_cusp_displacements(cusp_neighborhoods); /* * Compute all cusp reaches. */ compute_cusp_reaches(cusp_neighborhoods); /* * Find the stoppers. */ compute_cusp_stoppers(cusp_neighborhoods); /* * Initially no cusps are tied. */ initialize_cusp_ties(cusp_neighborhoods); /* * Set up an implicit coordinate system on each cusp cross section * so that we can report the position of horoballs etc. consistently, * even as the canonical triangulation changes. */ initialize_cusp_nbhd_positions(cusp_neighborhoods); /* * Record the volume so we don't have to recompute it * over and over in real time. */ cusp_neighborhoods->its_triangulation->volume = volume(cusp_neighborhoods->its_triangulation, NULL); /* * Done. */ return cusp_neighborhoods; } void free_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods) { if (cusp_neighborhoods != NULL) { free_triangulation(cusp_neighborhoods->its_triangulation); my_free(cusp_neighborhoods); } } static void initialize_cusp_displacements( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->displacement = 0.0; cusp->displacement_exp = 1.0; } } static void compute_cusp_reaches( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; cusp_neighborhoods->its_triangulation->max_reach = 0.0; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { compute_one_reach(cusp_neighborhoods, cusp); if (cusp->reach > cusp_neighborhoods->its_triangulation->max_reach) cusp_neighborhoods->its_triangulation->max_reach = cusp->reach; } } static void compute_one_reach( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp) { /* * The key observation is the following. Think of a horoball * packing corresponding to the cusp cross sections in their home * positions, with the given cusp lifting to the plane z == 1 in * the upper half space model. The vertical line passing through * the top of a maximally (Eucliean-)large round horoball is * guaranteed to be an edge in the canonical triangulation. * (Proof: As the horoballs expand equivariantly, the largest round * horoball(s) is(are) the first one(s) to touch the z == 1 horoball.) * So by measuring the distance between cusp cross sections along the * edges of the canonical triangulation, we can deduce the distance * from the given cusp to the largest round horoball(s). If a largest * round horoball corresponds to the given cusp, then we know the * cusp's reach and we're done. If the largest horoballs all belong * to other cusps, then we retract the other cusps a bit (i.e. give * them a negative displacement) and try again. Eventually a horoball * corresponding to the given cusp will be maximal. */ Triangulation *triangulation_copy; Cusp *cusp_copy, *other_cusp; double dist_any, dist_self; /* * Make a copy of the triangulation, so we don't disturb the original. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &triangulation_copy); cusp_copy = find_cusp(triangulation_copy, cusp->index); /* * Carry out the algorithm described above. */ while (TRUE) { /* * Compute the distances between cusp cross sections along each * edge of the (already canonical) triangulation, and store the * results in the EdgeClass's intercusp_distance field. * * Technical note: There is a small inefficiency here in that * proto_canonize() creates and discards the cusp cross sections, * and here we create and discard them again. If this turns out * to be a problem we could have proto_canonize() compute the * intercusp distances when it does the canonization, but for * now I'll put up with the inefficiency to keep the code clean. */ compute_intercusp_distances(triangulation_copy); /* * Does a maximally large round horoball belong to the given cusp? * If so, we know the reach and we're done. */ dist_self = compute_min_dist(triangulation_copy, cusp_copy, dist_self_to_self); dist_any = compute_min_dist(triangulation_copy, cusp_copy, dist_self_to_any); if (dist_self < dist_any + INTERCUSP_EPSILON) { cusp->reach = 0.5 * dist_self; break; } /* * Otherwise, retract all cross sections except the given one, * recanonize, and continue with the loop. * * Note: initialize_cusp_neighborhoods() has already checked * that the manifold is hyperbolic, so proto_canonize() should * not fail. */ for (other_cusp = triangulation_copy->cusp_list_begin.next; other_cusp != &triangulation_copy->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp != cusp_copy) { other_cusp->displacement -= DELTA_DISPLACEMENT; other_cusp->displacement_exp = exp(other_cusp->displacement); } if (proto_canonize(triangulation_copy) != func_OK) uFatalError("compute_one_reach", "cusp_neighborhoods.c"); } /* * Free the copy of the triangulation. */ free_triangulation(triangulation_copy); } static void compute_tie_group_reach( CuspNeighborhoods *cusp_neighborhoods) { /* * This function is similar to compute_one_reach(), but instead of * computing the reach of a single cusp, it computes the reach of * a group of tied cusps (that is a group of cusp neighborhoods which * move forward and backward in unison). Please see compute_one_reach() * above for detailed documentation. */ Triangulation *triangulation_copy; double dist_any, dist_self; Cusp *cusp; /* * If no cusps are tied, there is nothing to be done. */ if (some_tied_cusp(cusp_neighborhoods) == NULL) { cusp_neighborhoods->its_triangulation->tie_group_reach = 0.0; return; } /* * Make a copy of the triangulation, so we don't disturb the original. * copy_triangulation() copies the is_tied field, even though it is * in some sense private to this file. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &triangulation_copy); /* * Carry out the algorithm described in compute_one_reach(). */ while (TRUE) { compute_intercusp_distances(triangulation_copy); dist_self = compute_min_dist(triangulation_copy, NULL, dist_group_to_group); dist_any = compute_min_dist(triangulation_copy, NULL, dist_group_to_any); if (dist_self < dist_any + INTERCUSP_EPSILON) { cusp_neighborhoods->its_triangulation->tie_group_reach = some_tied_cusp(cusp_neighborhoods)->displacement + 0.5 * dist_self; break; } for (cusp = triangulation_copy->cusp_list_begin.next; cusp != &triangulation_copy->cusp_list_end; cusp = cusp->next) if (cusp->is_tied == FALSE) { cusp->displacement -= DELTA_DISPLACEMENT; cusp->displacement_exp = exp(cusp->displacement); } if (proto_canonize(triangulation_copy) != func_OK) uFatalError("compute_tie_group_reach", "cusp_neighborhoods.c"); } free_triangulation(triangulation_copy); } static Cusp *some_tied_cusp( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) if (cusp->is_tied) return cusp; return NULL; } static void compute_cusp_stoppers( CuspNeighborhoods *cusp_neighborhoods) { /* * Think of a horoball packing corresponding to the cusp cross sections * in their current positions, with a given cusp lifting to the plane * z == 1 in the upper half space model. The vertical line passing * through the top of a maximally (Eucliean-)large round horoball is * guaranteed to be an edge in the canonical cell decomposition. * (Proof: As the horoballs expand equivariantly, the largest * round horoballs will be the first to touch the z == 1 horoball.) * * Case 1. The maximal horoball belongs to the given cusp. * * In this case, the given cusp is its own stopper, and the * stopping displacement is its reach. * * Case 2. The maximal horoball belongs to some other cusp. * * The displacement at which the given cusp meets the other cusp * may or may not be less than the given cusp's reach. * (A less-than-maximal horoball belonging to the given cusp may * overtake a formerly maximal cusp, because horoballs belonging * to the given cusp grow as the given cusp moves forward, while * other horoballs do not.) If the stopping displacement is * less than the given cusp's reach, then we've found a stopper * cusp and stopping displacement (the stopping displacement is * unique, even though the stopper cusp may not be). If the * stopping is greater than or equal to the given cusp's reach, * then the cusp is its own stopper, as in case 1. */ Cusp *cusp, *c[2]; EdgeClass *edge; int i; double possible_stopping_displacement; /* * Initialize each stopper to be the cusp itself, and the stopping * displacement to be its reach. */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->stopper_cusp = cusp; cusp->stopping_displacement = cusp->reach; } /* * Now look at each edge of the canonical triangulation, to see * whether some other cusp cross section is closer. * * cusp_neighborhoods->its_triangulation is always the canonical * triangulation (or an arbitrary subdivision of the canonical * cell decomposition). */ compute_intercusp_distances(cusp_neighborhoods->its_triangulation); for (edge = cusp_neighborhoods->its_triangulation->edge_list_begin.next; edge != &cusp_neighborhoods->its_triangulation->edge_list_end; edge = edge->next) { c[0] = edge->incident_tet->cusp[ one_vertex_at_edge[edge->incident_edge_index]]; c[1] = edge->incident_tet->cusp[other_vertex_at_edge[edge->incident_edge_index]]; for (i = 0; i < 2; i++) { possible_stopping_displacement = c[i]->displacement + edge->intercusp_distance; if (possible_stopping_displacement < c[i]->stopping_displacement) { c[i]->stopping_displacement = possible_stopping_displacement; c[i]->stopper_cusp = c[!i]; } } } } static void compute_intercusp_distances( Triangulation *manifold) { /* * In the present context we may assume the triangulation is * canonical (although all we really need to know is that it * has a geometric_solution). */ EdgeClass *edge; /* * Set up the cusp cross sections. */ allocate_cross_sections(manifold); compute_cross_sections(manifold); /* * Compute the intercusp_distances. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) compute_one_intercusp_distance(edge); /* * Release the cusp cross sections. */ free_cross_sections(manifold); } static void compute_one_intercusp_distance( EdgeClass *edge) { int i, j; Tetrahedron *tet; EdgeIndex e; VertexIndex v[2]; FaceIndex f[2]; double length[2][2], product; /* * Find an arbitrary Tetrahedron incident to the given EdgeClass. */ tet = edge->incident_tet; e = edge->incident_edge_index; /* * Note which vertices and faces are incident to the EdgeClass. */ v[0] = one_vertex_at_edge[e]; v[1] = other_vertex_at_edge[e]; f[0] = one_face_at_edge[e]; f[1] = other_face_at_edge[e]; /* * The vertex cross section at each vertex v[i] is a triangle. * Note the lengths of the triangle's edges incident to the EdgeClass. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) length[i][j] = tet->cusp[v[i]]->displacement_exp * tet->cross_section->edge_length[v[i]][f[j]]; /* * Our task is to compute the distance between the vertex cross sections * as a function of the length[][]'s. Fortunately this is easier than * you might except. I recommend you make sketches for yourself as * you read through the following. (It's much simpler in pictures * than it is in words.) * * Proposition. There is a unique common perpendicular to a pair of * opposite edges of an ideal tetrahedron. * * Proof. Consider the line segment which minimizes the distance * between the two opposite edges. If it weren't perpendicular to * each edge, then a shorter line segement could be found. QED * * Definition. The "midpoint" of an edge of an ideal tetrahedron is * the point where the edge intersects the unique common perpendicular * to the opposite edge. * * Proposition. A half turn about the aforementioned unique common * perpendicular is a symmetry of the ideal tetrahedron. * * Proof. It preserves (setwise) a pair of opposite edges. Therefore * it preserves (setwise) the tetrahedron's four ideal vertices, and * therefore the whole tetrahedron. QED * * Proposition. Consider a vertex cross section which passes through * the midpoint of an edge. The two sides of the vertex cross section * which are incident to the given edge of the tetrahedron have lengths * which are reciprocals of one another. * * Proof. Position the tetrahedron in the upper half space model so * that the given edge is vertical and its midpoint is at height one. * * Let P1 be the unique plane which contains the aforementioned unique * common perpendicular and also contains the edge itself. * * Let P2 be the unique plane which contains the aforementioned unique * common perpendicular and is orthogonal to the edge itself. * * Let S be the symmetry defined by a reflection in P1 followed by a * reflection in P2. * * S is equivalent to a half turn about the unique common perpendicular * (proof: P1 and P2 are orthogonal to each other, and both contain * the unique common perpendicular). Therefore S is a symmetry of the * ideal tetrahedron, by the preceding proposition. * * Let L1 and L2 be the lengths of the two sides of the vertex cross * section which are incident to the given edge. Because the vertex * cross section is at height one in the upper half space model, * L1 and L2 also represent the Euclidean lengths of two sides of the * triangle obtained by projecting the ideal tetrahedron onto the * plane z == 0 in the upper half space model. Reflection in the * plane P1 does not change the lengths of those two sides of the * triangle, while reflection in the plane P2 (which, in Euclidean * terms, is inversion in a hemisphere of radius one) sends each * length to its inverse. Since the composition S of the two * reflections preserves the triangle, it follows that L1 and L2 * must be inverses of one another. QED * * If a vertex cross section passes through the midpoint of an edge, * then the product of the lengths L1 and L2 (using the notation of * the preceding proof) is L1 L2 = 1. Now consider a vertex cross * section which is a distance d away from the midpoint (towards * the fat part of the manifold if d is positive, towards the cusp * if d is negative). According to the documentation at the top of * this file, a cusp cross section's linear dimensions vary as exp(d), * so the lengths of the corresponding sides of the new vertex cross * section will be exp(d)L1 and exp(d)L2. Their product is * exp(d)L1 exp(d)L2 = exp(2d) L1 L2 = exp(2d). * * If the lengths of the sides of the vertex cross section at the * other end of the given edge are exp(d')L1 and exp(d')L2, then * their product is exp(2d'). The product of all four lengths is * * exp(d)L1 exp(d)L2 exp(d')L1 exp(d')L2 = exp(2(d + d')). * * This is exactly what we need to know: d + d' is the negative * of the intercusp distance. (Note that the midpoint has dropped * out of the picture!) */ product = 1.0; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) product *= length[i][j]; edge->intercusp_distance = -0.5 * log(product); } static double compute_min_dist( Triangulation *manifold, Cusp *cusp, /* ignored for tie group distances */ MinDistanceType min_distance_type) { /* * This function assumes the intercusp_distances * have already been computed. */ double min_dist; EdgeClass *edge; Cusp *cusp1, *cusp2; min_dist = DBL_MAX; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { cusp1 = edge->incident_tet->cusp[ one_vertex_at_edge[edge->incident_edge_index]]; cusp2 = edge->incident_tet->cusp[other_vertex_at_edge[edge->incident_edge_index]]; if (edge->intercusp_distance < min_dist) switch (min_distance_type) { case dist_self_to_self: if (cusp == cusp1 && cusp == cusp2) min_dist = edge->intercusp_distance; break; case dist_self_to_any: if (cusp == cusp1 || cusp == cusp2) min_dist = edge->intercusp_distance; break; case dist_group_to_group: if (cusp1->is_tied && cusp2->is_tied) min_dist = edge->intercusp_distance; break; case dist_group_to_any: if (cusp1->is_tied || cusp2->is_tied) min_dist = edge->intercusp_distance; break; } } return min_dist; } int get_num_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods) { if (cusp_neighborhoods == NULL) return 0; else return get_num_cusps(cusp_neighborhoods->its_triangulation); } CuspTopology get_cusp_neighborhood_topology( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->topology; } double get_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->displacement; } Boolean get_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->is_tied; } double get_cusp_neighborhood_cusp_volume( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { /* * As explained in the documentation at the top of this file, * the volume will be the volume enclosed by the cusp in its * home position, multiplied by exp(2 * displacement). */ return 0.1875 * ROOT_3 * exp(2 * find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->displacement); } double get_cusp_neighborhood_manifold_volume( CuspNeighborhoods *cusp_neighborhoods) { return cusp_neighborhoods->its_triangulation->volume; } Triangulation *get_cusp_neighborhood_manifold( CuspNeighborhoods *cusp_neighborhoods) { Triangulation *manifold_copy; Cusp *cusp; /* * Make a copy of its_triangulation. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &manifold_copy); /* * Reset the cusp displacements to zero, so if a canonical triangulation * is needed later it will be computed relative to cusp cross sections * of equal volume. */ for (cusp = manifold_copy->cusp_list_begin.next; cusp != &manifold_copy->cusp_list_end; cusp = cusp->next) { cusp->displacement = 0.0; cusp->displacement_exp = 1.0; } return manifold_copy; } double get_cusp_neighborhood_reach( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->reach; } double get_cusp_neighborhood_max_reach( CuspNeighborhoods *cusp_neighborhoods) { return cusp_neighborhoods->its_triangulation->max_reach; } double get_cusp_neighborhood_stopping_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->stopping_displacement; } int get_cusp_neighborhood_stopper_cusp_index( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->stopper_cusp->index; } void set_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, double new_displacement) { Cusp *cusp, *other_cusp; /* * Get a pointer to the cusp whose displacement is being changed. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Clip the displacement to the feasible range. */ if (new_displacement < 0.0) new_displacement = 0.0; if (cusp->is_tied == FALSE) { /* * The stopping_displacement has already been set to be less than or * equal to the reach, so by clipping to the stopping_displacement * we know the cusp neighborhood won't overlap itself or any * other cusp neighborhood. */ if (new_displacement > cusp->stopping_displacement) new_displacement = cusp->stopping_displacement; } else /* cusp->is_tied == TRUE */ { /* * Make sure the new_displacement doesn't exceed the tie_group_reach. * Other cusps in the tie group will be coming at us as we move * toward them, so collisions might not be detected by the * stopping_displacement alone. (The latter assumes the other * cusp is stationary.) */ if (new_displacement > cusp_neighborhoods->its_triangulation->tie_group_reach) new_displacement = cusp_neighborhoods->its_triangulation->tie_group_reach; /* * Don't overlap untied stoppers either. */ for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied && new_displacement > other_cusp->stopping_displacement) new_displacement = other_cusp->stopping_displacement; } /* * Set the new displacement. */ if (cusp->is_tied == FALSE) { cusp->displacement = new_displacement; cusp->displacement_exp = exp(new_displacement); } else /* cusp->is_tied == TRUE */ { for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied) { other_cusp->displacement = new_displacement; other_cusp->displacement_exp = exp(new_displacement); } } /* * Compute the canonical cell decomposition * relative to the new displacement. */ if (proto_canonize(cusp_neighborhoods->its_triangulation) != func_OK) uFatalError("set_cusp_neighborhood_displacement", "cusp_neighborhoods"); /* * The cusp reaches won't have changed, but the stoppers might have. */ compute_cusp_stoppers(cusp_neighborhoods); } void set_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean new_tie) { Cusp *cusp, *other_cusp; double min_displacement; /* * Get a pointer to the cusp which is being tied or untied. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Tie or untie the cusp. */ cusp->is_tied = new_tie; /* * If the cusp is being tied, bring it and its mates into line. */ if (cusp->is_tied == TRUE) { /* * Find the minimum displacement for a tied cusp . . . */ min_displacement = DBL_MAX; for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied && other_cusp->displacement < min_displacement) min_displacement = other_cusp->displacement; /* * . . . and set all tied cusps to that minimum value. */ for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied) { other_cusp->displacement = min_displacement; other_cusp->displacement_exp = exp(min_displacement); } /* * Compute the canonical cell decomposition * relative to the minimum displacement. */ if (proto_canonize(cusp_neighborhoods->its_triangulation) != func_OK) uFatalError("set_cusp_neighborhood_tie", "cusp_neighborhoods"); /* * The cusp reaches won't have changed, * but the stoppers might have. */ compute_cusp_stoppers(cusp_neighborhoods); } /* * How far can the group of tied cusps go before bumping into itself? */ compute_tie_group_reach(cusp_neighborhoods); } static void initialize_cusp_ties( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; /* * Initially no cusps are tied . . . */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) cusp->is_tied = FALSE; /* * . . . and the tie_group_reach is undefined. */ cusp_neighborhoods->its_triangulation->tie_group_reach = 0.0; } static void initialize_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { /* * Install VertexCrossSections so that we know the size of each * vertex cross section in the cusp's home position. */ allocate_cross_sections(cusp_neighborhoods->its_triangulation); compute_cross_sections(cusp_neighborhoods->its_triangulation); /* * Allocate storage for the CuspNbhdPositions . . . */ allocate_cusp_nbhd_positions(cusp_neighborhoods); /* * . . . and then compute them. */ compute_cusp_nbhd_positions(cusp_neighborhoods); /* * Free the VertexCrossSections now that we're done with them. * (proto_canonize() will of course need them again, but it likes * to allocate them for itself -- this keeps its interaction with * the rest of the kernel cleaner.) */ free_cross_sections(cusp_neighborhoods->its_triangulation); } static void allocate_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { Tetrahedron *tet; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure no CuspNbhdPositions * are already allocated. */ if (tet->cusp_nbhd_position != NULL) uFatalError("allocate_cusp_nbhd_positions", "cusp_neighborhoods"); /* * Allocate a CuspNbhdPosition structure. */ tet->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); } } static void compute_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { Tetrahedron *tet; Orientation h; VertexIndex v; int max_triangles; Cusp *cusp; PeripheralCurve c; Complex (*x)[4][4], *translation; Boolean (*in_use)[4]; FaceIndex f, f0, f1, f2; int strands1, strands2, flow; double length; Complex factor; /* * Initialize all the tet->in_use[][] fields to FALSE, * and all tet->x[][][] to Zero. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ for (v = 0; v < 4; v++) { for (f = 0; f < 4; f++) tet->cusp_nbhd_position->x[h][v][f] = Zero; tet->cusp_nbhd_position->in_use[h][v] = FALSE; } /* * For each vertex cross section which has not yet been set, set the * positions of its three vertices, and then recursively set the * positions of neighboring vertex cross sections. The positions * are relative to each cusp cross section's home position. * (Recall that initialize_cusp_nbhd_positions() has already called * compute_cross_sections() for us.) For torus cusps, do only the * sheet of the double cover which contains the peripheral curves * (this will be the right_handed sheet if the manifold is orientable). */ max_triangles = 2 * 4 * cusp_neighborhoods->its_triangulation->num_tetrahedra; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp_nbhd_position->in_use[right_handed][v] == FALSE && tet->cusp_nbhd_position->in_use[ left_handed][v] == FALSE) { /* * Use the sheet which contains the peripheral curves. * If neither does, do nothing for now. They'll show * up eventually. */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ if (contains_meridian(tet, h, v) == TRUE) { set_one_component(tet, h, v, max_triangles); break; } } /* * Compute the meridional and longitudinal translation on each * cusp cross section. For Klein bottle cusps, the longitude * will actually be that of the double cover. The translations * are stored in the Cusp data structure as translation[M] and * translation[L]. */ /* * The Algorithm * * The calls to set_one_component() have assigned coordinates to all * the triangles in the induced triangulation of the cusp cross section. * The problem is that these coordinates are well defined only up * to translations in the covering transformation group (or the * orientation preserving subgroup, in the case of a Klein bottle cusp). * So we want an algorithm which uses only the local coordinates within * each triangle, without requiring global consistency. * * Imagine following a peripheral curve around the cusp cross section, * and look at the sides of the triangles it passes through. As we * go along, we can keep track of the coordinates of the left and * right hand edges. When we "veer left" the left hand endpoint stays * constant, while the right hand endpoint moves forward, and vice * versa when we "veer right". By adding up all the displacements to * each endpoint, by the time we get back to our starting point we will * have computed the total translation along the curve. Actually, * it suffices to compute the total displacement for only one endpoint * (left or right) since both will give the same answer. * * Finally, note that it doesn't matter in what order we sum the * displacements. We can just iterate through all tetrahedra in the * triangulation without explicitly tracing curves. */ /* * Initialize all translations to (0.0, 0.0), and then . . . */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) for (c = 0; c < 2; c++) cusp->translation[c] = Zero; /* * . . . add in the contribution of each piece of each curve. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { cusp = tet->cusp[v]; for (c = 0; c < 2; c++) { translation = &cusp->translation[c]; for (f0 = 0; f0 < 4; f0++) { if (f0 == v) continue; /* * Relative to the right_handed Orientation, the faces * f0, f1 and f2 are arranged around the ideal vertex v * like this * * /\ * f1 / \ f0 * /____\ * f2 * * The triangles corners inherit the indices of the * opposite sides. */ f1 = remaining_face[f0][v]; f2 = remaining_face[v][f0]; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; strands1 = tet->curve[c][h][v][f1]; strands2 = tet->curve[c][h][v][f2]; flow = FLOW(strands2, strands1); /* * We're interested only in displacements of the * left hand endpoint (cf. above), which occur when * the flow is negative (if h == right_handed) or * the flow is positive (if h == left_handed). */ if ((h == right_handed) ? (flow < 0) : (flow > 0)) *translation = complex_plus( *translation, complex_real_mult( flow, complex_minus(x[h][v][f2], x[h][v][f1]))); } } } } } /* * Rotate the coordinates so that the longitudes point in the * direction of the positive x-axis. */ /* * Find the rotation needed for each cusp, * and use it to rotate the meridian and longitude. */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->scratch = cusp->translation[L]; length = complex_modulus(cusp->scratch); if (length < LONGITUDE_EPSILON) uFatalError("compute_cusp_nbhd_positions", "cusp_neighborhoods"); cusp->scratch = complex_real_mult(1.0/length, cusp->scratch); cusp->scratch = complex_div(One, cusp->scratch); cusp->translation[M] = complex_mult(cusp->scratch, cusp->translation[M]); cusp->translation[L] = complex_mult(cusp->scratch, cusp->translation[L]); cusp->translation[L].imag = 0.0; /* kill the roundoff error */ } /* * Use the same rotation (stored in cusp->scratch) to rotate * the coordinates in the triangulation of the cusp. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ for (v = 0; v < 4; v++) { if (in_use[h][v] == FALSE) continue; factor = tet->cusp[v]->scratch; for (f = 0; f < 4; f++) { if (f == v) continue; x[h][v][f] = complex_mult(factor, x[h][v][f]); } } } } static Boolean contains_meridian( Tetrahedron *tet, Orientation h, VertexIndex v) { /* * It suffices to check any two sides, because the meridian * can't possibly intersect only one side of a triangle. * (These are signed intersection numbers.) */ VertexIndex w0, w1; w0 = ! v; w1 = remaining_face[v][w0]; return (tet->curve[M][h][v][w0] != 0 || tet->curve[M][h][v][w1] != 0); } static void set_one_component( Tetrahedron *tet, Orientation h, VertexIndex v, int max_triangles) { /* * FaceIndices are the natural way to index the corners * of a vertex cross section. * * The VertexIndex v tells which vertex cross section we're at. * The vertex cross section is (a triangular component of) the * intersection of a cusp cross section with the ideal tetrahedron. * Each side of the triangle is the intersection of the cusp cross * section with some face of the ideal tetrahedron, so FaceIndices * may naturally be used to index them. Each corner of the triangle * then inherits the FaceIndex of the opposite side. */ FaceIndex f[3], ff, nbr_f[3]; int i; CuspTriangle *queue, tri, nbr; int queue_begin, queue_end; Permutation gluing; CuspNbhdPosition *our_data, *nbr_data; /* * Find the three FaceIndices for the corners of the triangle. * (f == v is excluded.) */ for ( i = 0, ff = 0; i < 3; i++, ff++) { if (ff == v) ff++; f[i] = ff; } /* * Let the corner f[0] be at the origin. */ tet->cusp_nbhd_position->x[h][v][f[0]] = Zero; /* * Let the corner f[1] be on the positive x-axis. */ tet->cusp_nbhd_position->x[h][v][f[1]].real = tet->cross_section->edge_length[v][f[2]]; tet->cusp_nbhd_position->x[h][v][f[1]].imag = 0.0; /* * Use the TetShape to find the position of corner f[2]. */ cn_find_third_corner(tet, h, v, f[0], f[1], f[2]); /* * Mark this triangle as being in_use. */ tet->cusp_nbhd_position->in_use[h][v] = TRUE; /* * We'll now "recursively" set the remaining triangles of this * cusp cross section. We'll keep a queue of the triangles whose * positions have been set, but whose neighbors have not yet * been examined. */ queue = NEW_ARRAY(max_triangles, CuspTriangle); queue[0].tet = tet; queue[0].h = h; queue[0].v = v; queue_begin = 0; queue_end = 0; while (queue_begin <= queue_end) { /* * Pull a CuspTriangle off the queue. */ tri = queue[queue_begin++]; /* * Consider each of its three neighbors. */ for (ff = 0; ff < 4; ff++) { if (ff == tri.v) continue; gluing = tri.tet->gluing[ff]; nbr.tet = tri.tet->neighbor[ff]; nbr.h = (parity[gluing] == orientation_preserving) ? tri.h : ! tri.h; nbr.v = EVALUATE(gluing, tri.v); our_data = tri.tet->cusp_nbhd_position; nbr_data = nbr.tet->cusp_nbhd_position; /* * If the neighbor hasn't been set . . . */ if (nbr_data->in_use[nbr.h][nbr.v] == FALSE) { /* * . . . set it . . . */ f[0] = remaining_face[tri.v][ff]; f[1] = remaining_face[ff][tri.v]; f[2] = ff; for (i = 0; i < 3; i++) nbr_f[i] = EVALUATE(gluing, f[i]); for (i = 0; i < 2; i++) nbr_data->x[nbr.h][nbr.v][nbr_f[i]] = our_data->x[tri.h][tri.v][f[i]]; cn_find_third_corner(nbr.tet, nbr.h, nbr.v, nbr_f[0], nbr_f[1], nbr_f[2]); nbr_data->in_use[nbr.h][nbr.v] = TRUE; /* * . . . and put it on the queue. */ queue[++queue_end] = nbr; } } } /* * An "unnecessary" error check. */ if (queue_begin > max_triangles) uFatalError("set_one_component", "cusp_neighborhoods"); /* * Free the queue. */ my_free(queue); } void cn_find_third_corner( Tetrahedron *tet, /* which tetrahedron */ Orientation h, /* right_handed or left_handed sheet */ VertexIndex v, /* which ideal vertex */ FaceIndex f0, /* known corner */ FaceIndex f1, /* known corner */ FaceIndex f2) /* corner to be computed */ { /* * We want to position the Tetrahedron so that the following * two conditions hold. * * (1) The corners f0, f1 and f2 are arranged counterclockwise * around the triangle's perimeter. * * f2 * / \ * / \ * f0------f1 * * (2) The cusp cross section is seen with its preferred orientation. * (Cf. the discussion in the second paragraph of section (2) in * the documentation at the top of the file peripheral_curves.c.) * If this is the right handed sheet (h == right_handed), * the Tetrahedron should appear right handed. * (Cf. the definition of Orientation in kernel_typedefs.h.) * If this is the left handed sheet (h == left_handed), the * Tetrahedron should appear left handed (the left_handed sheet has * the opposite orientation of the Tetrahedron, so if this is the * left handed sheet and the Tetrahedron is viewed in a left handed * position, the sheet will be appear right handed -- got that?). * * Of course these two conditions may not be compatible. * If we position the corners as in (1) and then find that (2) doesn't * hold (or vice versa), then we must swap the indices f0 and f1. * * Note: We could force the conditions to hold by making our * recursive calls carefully and consistently, but fixing the * ordering of f0 and f1 as needed is simpler and more robust. */ Orientation tet_orientation; FaceIndex temp; Complex s, t, z; /* * Position the tetrahedron as in Condition (1) above. * If the tetrahedron appears in its right_handed Orientation, * then remaining_face[f0][f1] == f2, according to the definition of * remaining_face[][] in tables.c. If the tetrahedron appears in * its left_handed Orientation, then remaining_face[f0][f1] == v. */ tet_orientation = (remaining_face[f0][f1] == f2) ? right_handed : left_handed; /* * Does the vertex cross section appear with its preferred orientation, * as discussed in Condition (2) above? If not, fix it. */ if (h != tet_orientation) { temp = f0; f0 = f1; f1 = temp; tet_orientation = ! tet_orientation; } /* * Let s be the vector from f0 to f1, * t be the vector from f0 to f2, * z be the complex edge angle v/u. */ s = complex_minus( tet->cusp_nbhd_position->x[h][v][f1], tet->cusp_nbhd_position->x[h][v][f0]); /* * TetShapes are always stored relative to the right_handed Orientation. * If we're viewing the tetrahedron relative to the left_handed * Orientation, we need to use the conjugate-inverse instead. */ z = tet->shape[complete]->cwl[ultimate][edge3_between_vertices[v][f0]].rect; if (tet_orientation == left_handed) z = complex_conjugate(complex_div(One, z)); t = complex_mult(z, s); tet->cusp_nbhd_position->x[h][v][f2] = complex_plus(tet->cusp_nbhd_position->x[h][v][f0], t); } void get_cusp_neighborhood_translations( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Complex *meridian, Complex *longitude) { Cusp *cusp; cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); *meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); *longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); } CuspNbhdSegmentList *get_cusp_neighborhood_triangulation( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { Cusp *cusp; CuspNbhdSegmentList *theSegmentList; CuspNbhdSegment *next_segment; Tetrahedron *tet, *nbr_tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v; Orientation h; FaceIndex f, nbr_f; /* * Make sure the EdgeClasses are numbered. */ number_the_edge_classes(cusp_neighborhoods->its_triangulation); /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Allocate the wrapper for the array. */ theSegmentList = NEW_STRUCT(CuspNbhdSegmentList); /* * We don't know ahead of time exactly how many CuspNbhdSegments * we'll need. Torus cusps report each segment once, but Klein * bottle cusps report each segment twice, once for each sheet. * * To get an upper bound on the number of segments, * assume all cusps are Klein bottle cusps. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * 3 sides/triangle * / 2 sides/visible side (no need to draw each edge twice) * * = 12n visible sides */ theSegmentList->segment = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdSegment); /* * Keep a pointer to the first empty CuspNbhdSegment. */ next_segment = theSegmentList->segment; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; for (f = 0; f < 4; f++) { if (f == v) continue; nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); /* * We want to report each segment only once, so we * make the (arbitrary) convention that we report * a segment only from the Tetrahedron whose address * in memory is less. In the case of a Tetrahedron * glued to itself, we report it from the lower * FaceIndex. */ if (tet > nbr_tet || (tet == nbr_tet && f > nbr_f)) continue; /* * Don't report edges which are part of the arbitrary * subdivision of the canonical cell decomposition * into tetrahdra. We rely on the fact that * proto_canonize() has computed the tilts and left * them in place. The sum of the tilts will never be * positive for a subdivision of the canonical cell * decomposition. If it's close to zero, ignore that * face. */ if (tet->tilt[f] + nbr_tet->tilt[nbr_f] > -CONCAVITY_EPSILON) continue; /* * This edge has passed all its tests, so record it. */ next_segment->endpoint[0] = complex_real_mult(cusp->displacement_exp, x[h][v][remaining_face[f][v]]); next_segment->endpoint[1] = complex_real_mult(cusp->displacement_exp, x[h][v][remaining_face[v][f]]); next_segment->start_index = tet->edge_class[edge_between_vertices[v][remaining_face[f][v]]]->index; next_segment->middle_index = tet->edge_class[edge_between_faces[v][f]]->index; next_segment->end_index = tet->edge_class[edge_between_vertices[v][remaining_face[v][f]]]->index; /* * Move on. */ next_segment++; } } } } /* * How many segments did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdSegment).) */ theSegmentList->num_segments = next_segment - theSegmentList->segment; /* * Did we find more segments than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theSegmentList->num_segments > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_triangulation", "cusp_neighborhoods"); return theSegmentList; } void free_cusp_neighborhood_segment_list( CuspNbhdSegmentList *segment_list) { if (segment_list != NULL) { if (segment_list->segment != NULL) my_free(segment_list->segment); my_free(segment_list); } } CuspNbhdHoroballList *get_cusp_neighborhood_horoballs( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean full_list, double cutoff_height) { Cusp *cusp; CuspNbhdHoroballList *theHoroballList; /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Provide a small margin to allow for roundoff error. */ cutoff_height -= CUTOFF_HEIGHT_EPSILON; /* * Use the appropriate algorithm for finding * the quick or full list of horoballs. */ if (full_list == FALSE) theHoroballList = get_quick_horoball_list(cusp_neighborhoods, cusp); else theHoroballList = get_full_horoball_list(cusp_neighborhoods, cusp, cutoff_height); /* * Sort the horoballs in order of increasing size. */ qsort( theHoroballList->horoball, theHoroballList->num_horoballs, sizeof(CuspNbhdHoroball), &compare_horoballs); /* * There's a chance that get_full_horoball_list() may produce duplicate * horoballs (when a 2-cell passes through a horoball's north pole) or * that get_quick_horoball_list() may produce duplicatate horoballs * (when face horoballs coincide). Remove any such duplications. */ cull_duplicate_horoballs(cusp, theHoroballList); return theHoroballList; } static CuspNbhdHoroballList *get_quick_horoball_list( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp) { CuspNbhdHoroballList *theHoroballList; CuspNbhdHoroball *next_horoball; /* * Allocate the wrapper for the array. */ theHoroballList = NEW_STRUCT(CuspNbhdHoroballList); /* * We don't know ahead of time exactly how many CuspNbhdHoroballs * we'll need. Torus cusps report each horoball once, but Klein * bottle cusps report each horoball twice, once for each sheet. * To get an upper bound on the number of horoballs, assume all * cusps are Klein bottle cusps. We report two types of horoballs. * * Edge Horoballs * * Edge horoballs are horoballs which the given cusp sees along an * edge of the canonical triangulation (i.e. along a vertical edge * in the usual upper half space picture). The total number of * edges in the canonical triangulation is the same as the number * of tetrahedra (by an Euler characteristic argument), so the * following gives an upper bound on the number of edge horoballs. * * n edges * * 2 endpoints/edge * * 2 sheets/endpoint (left_handed and right_handed) * * = 4n edge horoballs * * Face Horoballs * * Face horoballs are horoballs which the given cusp sees across * a face of the canonical triangulation. The number of triangles * in the cusp triangulation provides an upper bound on the number * of face horoballs. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * = 8n visible sides * * Therefore the total number of horoballs we will report will be * at most 4n + 8n = 12n. (The maximum will be realized in the case * of a manifold like the Gieseking with one nonorientable cusp.) */ theHoroballList->horoball = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdHoroball); /* * Keep a pointer to the first empty CuspNbhdHoroball. */ next_horoball = theHoroballList->horoball; /* * Find the edge horoballs. */ get_quick_edge_horoballs( cusp_neighborhoods->its_triangulation, cusp, &next_horoball); /* * Find the face horoballs. */ get_quick_face_horoballs( cusp_neighborhoods->its_triangulation, cusp, &next_horoball); /* * How many horoballs did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdHoroball).) */ theHoroballList->num_horoballs = next_horoball - theHoroballList->horoball; /* * Did we find more horoballs than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theHoroballList->num_horoballs > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_triangulation", "cusp_neighborhoods"); return theHoroballList; } static void get_quick_edge_horoballs( Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball) { EdgeClass *edge; double radius; Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v[2]; int i; int other_index; Orientation h; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * Consider a horosphere of Euclidean height h in the upper half * space model. Integrate along a vertical edge connecting the * horosphere to the horosphere at infinity to compute the distance * between the two as * * d = integral of dz/z from z=h to z=1 * = log 1 - log h * = - log h * or * h = exp(-d) * * set_cusp_neighborhood_displacement() calls compute_cusp_stoppers(), * which in turn calls compute_intercusp_distances(), so we may use * the edge->intercusp_distance fields for d. */ radius = 0.5 * exp( - edge->intercusp_distance); /* * Dereference tet, x and in_use for clarity. */ tet = edge->incident_tet; x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; /* * Consider each of the edge's endpoints. */ v[0] = one_vertex_at_edge[edge->incident_edge_index]; v[1] = other_vertex_at_edge[edge->incident_edge_index]; for (i = 0; i < 2; i++) { /* * Are we at the right cusp? */ if (tet->cusp[v[i]] != cusp) continue; /* * What is the index of the other cusp? */ other_index = tet->cusp[v[!i]]->index; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v[i]] == FALSE) continue; (*next_horoball)->center = complex_real_mult(cusp->displacement_exp, x[h][v[i]][v[!i]]); (*next_horoball)->radius = radius; (*next_horoball)->cusp_index = other_index; (*next_horoball)++; } } } } static void get_quick_face_horoballs( Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball) { /* * There are several ways we might find the location and size of * the face horoballs. * * (1) Use the TetShape to locate the center, and then use the * lemma below to find the size. * * This method is fairly efficient computationally, and lets * us use the existing function compute_fourth_corner() from * choose_generators.c. * * (2) Ignore the TetShape, and rely entirely on the intercusp_distances * to find both the location and size. * * This method is conceptually straightforward. Using the lemma * below, one obtains three equations involving the location (x,y) * and the height h of the face horoball. The equations are * quadratic in x and y, but they are monic, so subtracting * equations gives linear dependencies between x, y and h. * One can solve for x and y in terms of h, and obtain a quadratic * equation to solve for h. It's easy to prove that the lesser * value of h will be the desired solution. Confession: I haven't * actually worked out the equation for h. It seems like it would * be messy. * * (3) Work in the Minkowski space model, and use linear algebra * to compute the horoball as a vector on the light cone. * * For background ideas, see * * Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149 * and * Sakuma and Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * The method might prove to be more-or-less equivalent to (2). * By Lemma 4.2(c) of Weeks, the equation = constant gives * all the horospheres v a fixed distance from a horosphere u. * So to find a horosphere a given distance from three given * horospheres, one ends up intersecting three hyperplanes in * E^(3,1) to get a line, and then intersecting the line with the * upper light cone. As in approach (2), the calculations are * initially linear, but become quadratic at the end. Again, I * haven't worked through the details. * * (4) Find a matrix in PSL(2,C) which takes an ideal tetrahedron * in standard position to the desired ideal tetrahedron. * * This is the approach used in snappea 1.3. The formulas are * simpler than you might expect. The main disadvantage is that * the 1.3 treatment applies only to orientable manifolds. It * might be possible to fix it up using MoebiusTransformations. * * We use method (1), because it seems simplest. * * Lemma. Consider two horospheres of Euclidean height h1 and h2 (resp.) * in the upper half space model of hyperbolic 3-space. If the * Euclidean distances between their centers (on the sphere at infinity) * is c, then the hyperbolic distance d between the horospheres is * * d = log( c^2 / h1*h2 ) * * Proof. Draw yourself a picture of the horospheres (or horocycles -- * a 2D cross sectional picture will serve just as well). Label the * distances h1, h2, c and d. Now sketch a Euclidean hemisphere of * radius c centered at the base of the first horosphere; this is * a plane in hyperbolic space. Reflect the whole picture in this * plane (in Euclidean terms, the reflection is an inversion in the * hemisphere). One of the horospheres gets taken to a horizontal * Euclidean plane at height c^2/h1. The other horosphere remains * (setwise) invariant. It is now obvious that the shortest distance * from one horosphere to the other is along the vertical arc connecting * them. The distance is the integral of dz/z from h=h2 to h=c^2/h1, * which works out to be log( c^2 / h1*h2 ). QED * * Comment. We don't need it for the present code, but I can't * resist pointing out that the above lemma has a nice intrinsic * formulation, which doesn't rely on the upper half space model. * Let H be the horosphere which appears as a horizontal plane z == 1 * in the upper half space model, and draw in the vertical geodesics * connecting it to each of the two horospheres mentioned in the lemma. * Let a = -log(h1) and b = -log(h2) be the respective distances from * H to each of the old horospheres. Interpret c as the distance along * H from one of those segments to the other. Now redraw the picture * in, say, the Poincare ball model. It'll be more symmetric now, * since there's no longer a preferred "horosphere at infinity". * You'll have an ideal triangle, with a horosphere at each vertex. * The quantities a, b and d are the length of the shortest geodesics * between horospheres, while c is the distance along a horosphere * between two such geodesics. The above lemma becomes * * Lemma. 2 log c = d - a - b. * * With better notation, namely a, b and c are the distances between * cusp cross sections, and A, B and C are the distances along the * cusps, the lemma becomes * * 2 log A = a - b - c * 2 log B = b - c - a * 2 log C = c - a - b * * Add two of those equations (say the first two) to get * * log AB = -c * * As a special case, when c == 0, AB = 1. */ Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex u, v, w, missing_corner; Permutation gluing; Complex corner[4]; Orientation h; double height_u, exp_d, c_squared; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * Are we at the right cusp? */ if (tet->cusp[v] != cusp) continue; gluing = tet->gluing[v]; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; /* * Prepare for a call to compute_fourth_corner(). */ for (w = 0; w < 4; w++) if (w != v) corner[EVALUATE(gluing, w)] = complex_real_mult(cusp->displacement_exp, x[h][v][w]); missing_corner = EVALUATE(gluing, v); /* * Call compute_fourth_corner() to compute * corner[missing_corner]. */ compute_fourth_corner( corner, missing_corner, (parity[gluing] == orientation_preserving) ? h : !h, tet->neighbor[v]->shape[complete]->cwl[ultimate]); /* * The missing_corner gives us the horoball's center. */ (*next_horoball)->center = corner[missing_corner]; /* * Prepare to use the above lemma to compute the radius. */ /* * Let u be any vertex of the original Tetrahedron except v. */ u = !v; /* * According to the explanation in get_quick_edge_horoballs(), * the height of the edge horoball at vertex u is * exp( - intercusp_distance). */ height_u = exp( - tet->edge_class[edge_between_vertices[u][v]]->intercusp_distance); /* * A different intercusp_distance gives the distance d * in the lemma. */ exp_d = exp(tet->neighbor[v]->edge_class[edge_between_vertices[EVALUATE(gluing,u)][missing_corner]]->intercusp_distance); /* * Compute the squared distance between the edge horoball * at vertex u and the face horoball we are interested in. */ c_squared = complex_modulus_squared(complex_minus( (*next_horoball)->center, complex_real_mult(cusp->displacement_exp, x[h][v][u]))); /* * Apply the lemma. * * exp(d) = c^2 / h1*h2 * => * h1 = c^2 / exp(d)*h2 */ (*next_horoball)->radius = 0.5 * c_squared / (exp_d * height_u); /* * Note the cusp index of the new horoball. */ (*next_horoball)->cusp_index = tet->neighbor[v]->cusp[missing_corner]->index; /* * Move on. */ (*next_horoball)++; } } } } static CuspNbhdHoroballList *get_full_horoball_list( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp, double cutoff_height) { /* * We want to find all horoballs of Euclidean height at least * cutoff_height, up to the Z + Z action of the group of covering * transformations of the cusp. (We work with the double cover * of Klein bottle cusps, so in effect all cusps are torus cusps.) * * Let M' be H^3 / (Z + Z), where the Z + Z is the group of covering * transformations of the cusp. Visualize M' as a chimney in the * upper half space model; when its sides are glued together its * parallelogram cross section becomes the torus cross section * of the cusp. * * Our plan is to lift ideal tetrahedra from the original manifold M * to the chimney manifold M'. We begin with the tetrahedra incident * to the chimney's cusp (i.e. its top end), and then gradually tile * our way downward. Whenever a new tetrahedron introduces a new * ideal vertex, we consider the horoball centered at that vertex. * If its Euclidean height is greater than cutoff_height, we add it * to a list. Our challenge is to find an algorithm which does as * little tiling as possible, yet still finds all horoballs higher * than the cutoff_height. * * The Naive Algorithm * * The naive algorithm is to consider the neighbors of each tetrahedron * already in the tiling. If adding a neighbor would introduce no * new vertices, add it. If adding a neighbor would introduce a new * vertex, add it iff the horoball at the new vertex is higher than * the cutoff_height. * * Unfortunately the naive algorithm fails. The Whitehead link * provides a counterexample. Visualize the Whitehead link as an * octahedron with faces identified. The ideal vertices at the * "north and south poles" form one cusp ("the red cusp") while the * "equatorial ideal vertices" form the other cusp ("the blue cusp"). * Push the blue cusp cross section forward until it meets itself, * but retract the red cusp cross section until it's tiny. The * canonical cell decomposition is a subdivision of the octahedron * into two square pyramids (a "northern" and a "southern" one). * SnapPea will, of course, arbitrarily subdivide each pyramid into * two tetrahedra. Now consider what happens when we apply the naive * algorithm to this example, with the red cusp at infinity. Each * of the initial tetrahedra has a red vertex at infinity, and three * blue vertices on the horizontal plane. Its three neighbors to the * sides are other tetrahedra of the same type (red at infinity and * blue on the horizontal plane). Its underneath neighbor shares the * same three blue vetices, and introduce a new red vertex on the * horizontal plane. But because the red horoball is tiny, the naive * algorithm will say not to add this tetrahedron. So no new tetrahedra * will be added, and the algorithm will terminate. The naive * algorithm has therefore failed, because it's missed blue horoballs * of varying sizes. (Assuming we've chosen the size of the tiny * red cusp cross section to be small enough that the largest red * horoballs are smaller than the medium sized blue one.) * * The naive algorithm's failure was the bad news. The good news * is that if we take into account the varying sizes of the horoballs, * the algorithm can be patched up and made to work. First a few * background lemmas. * * Lemma 1. For each horoball H, there is (a lift of) an edge * of the canonical cell decompostion which connects H to some * larger horoball H'. * * Proof. The horoball H is surrounded by (lifts of) 2-cells * of the Ford complex. Consider a 2-cell F which lies above some * point of H (in the upper half space model). F is dual to an edge * of the canonical cell decomposition which connects H to some other * horoball H'. F lies above H, so by Lemma 2 below, H' is larger * than H. QED * * Lemma 2. Consider two horoballs H and H'. If H' has a larger * Euclidean height than H when viewed in some fixed way in the upper * half space model of hyperbolic 3-space, then the plane P lying * midway between them appears as a Euclidean hemisphere enclosing * H and excluding H'. In particular, every point of H is directly * below some point of P, while no point of H' is. * * Proof. Draw the horoballs and construct P. QED * * Definition. Two horoballs are "edge-connected" if (a lift of) an * edge of the canonical cell decomposition connects one to the other. * * Lemma 3. Let H' be a horoball which is edge-connected to a smaller * horoball H. Then the Euclidean distance c between their centers * (on the boundary plane of the upper half space model) is * * c = sqrt( a * b * exp(d) ) * * where * a = Euclidean height of H' * b = Euclidean height of H * d = hyperbolic distance from H' to H. * * Proof. The lemma in get_quick_face_horoballs() says that * d = log(c^2 / a*b). Solve for c = sqrt( a * b * exp(d) ). QED * * Lemma 4. Let H' be a horoball which is edge-connected to a smaller * horoball H. If the Euclidean height of H is at least cutoff_height, * then the Euclidean distance c between the centers of H and H' is * at least * c >= sqrt( a * cutoff_height * exp(min_d) ) * * where a is the height of H' and min_d is the least distance from * the horoball H' to any other horoball. * * Proof. Follows immediately from Lemma 3. * * Comment. The exp(min_d) factor makes H' act like a bigger horoball * than it really is. If you were to increase the cusp displacement * by min_d, the height of H' would increase to a*exp(min_d). * * Definition. (A lift of) an edge of the canonical triangulation * is "potentially useful" if one endpoint lies at the center of * a horoball H' of height at least cutoff_height, and the distance * between its two endpoints is at least c (as defined in Lemma 4). * (As a special case, vertical edges (in the upper half space) are * always "potentially useful". The informal justification for this * is that the horosphere at infinity is infinity large and its center * is infinitely far away.) * * Definition. (A lift of) an ideal tetrahedron is "potentially useful" * iff it contains at least one potentially useful edge. * * The Corrected Algorithm * * As before, begin with the tetrahedra incident to the chimney's cusp * and gradually tile downward. For each tetrahedron already in the * tiling, consider its four neighbors and add those which are * potentially useful. * * Lemma 5. Let H' be a horoball higher than the cutoff_height. * If the Corrected Algorithm adds one potentially useful tetrahedron * incident to H', then it adds them all. * * Proof. Look at the surface of the horoball H', which intrinsically * is a Euclidean plane E. An edge of the triangulation intersects * the plane E in point P. The edge is potentially useful iff P lies * within a disk D (of intrinsic radius a/c in the Euclidean geometry * of the horosphere E, but we don't need that fact). A tetrahedron * incident to H' is potentially useful iff it intersects the disk D. * The set of all such tetrahedra forms a connected set (this follows * from the path connectedness of the disk D). Therefore if the * algorithm adds one such tetrahedron, it will add them all. QED * * Proposition 6. The Corrected Algorithm finds all horoballs higher * than the cutoff_height. * * Proof. Let H be a horoball of maximal height (greater than the * cutoff_height) which the algorithm missed. By Lemma 1, there * is a higher horoball H', and an edge connecting H' to H. The * edge is potentially useful, by Lemma 4 and the definition of a * potentially useful edge. By the assumed maximal height of H * (among all horoballs which the Corrected Algorithm should have * found but didn't), we know that the algorithm did find H', i.e. * it added some potentially useful tetrahedron incident to the center * of H'. By Lemma 5, it must have added all potentially useful * tetrahedra incident to H', and therefore must have found H. QED * * Corollary 7. We can refine the Corrected Algorithm as follows. * For each tetrahedron T already added, we consider only those * neighbors T' incident to a face of T which contains at least * one potentially useful edge. * * Proof. The proof of Proposition 6 still works. QED */ TilingHoroball *horoball_linked_list; TilingQueue tiling_queue; TilingTet *tiling_tree_root, *tiling_tet, *tiling_nbr; Complex meridian, longitude; double parallelogram_to_square[2][2]; FaceIndex f; CuspNbhdHoroballList *theHoroballList; /* * We don't know a priori how many horoballs we'll find. * So we temporarily keep them on a NULL-terminated linked list, * and transfer them to an array when we're done. * * To avoid recording multiple copies of each horoball, we make the * convention that each horoball is recorded only by the TilingTet * which contains its north pole. If the north pole lies on the * boundary of two TilingTets, they both record it. * get_cusp_neighborhood_horoballs() will remove the duplications. * If three or more TilingTets meet at the north pole, then a vertical * edge connects the horoball to infinity in the upper half space model; * read_initial_tetrahedra() records such horoballs without duplication. * (Other strategies are possible, like preferring the Tetrahedron * with the lower address in memory, but the present approach is * least vulnerable to roundoff error.) */ horoball_linked_list = NULL; /* * We'll need to store the potentially useful tetrahedra in two ways. * * Queue * The Tetrahedra which have been added, but whose neighbors have * not been examined, go on a queue, so we know which one * to process next. When we remove a tetrahedron from the queue * we examine its neighbors. We use a queue rather than a stack * so that we tile generally downwards (rather than snaking around) * in hopes of obtaining the best numerical precision. * * Tree * All tetrahedra which have been added are kept on a tree, so that * we can tell whether new tetrahedra are duplications of old ones * or not. (Note: Checking whether a tetrahedron is "the same as" * an old one means checking whether they are equivalent under * the Z + Z action of the covering transformations. * * The TilingTet structure supports both the queue and the tree, * simultaneously and independently. */ /* * Initialize the data structures. */ tiling_queue.begin = NULL; tiling_queue.end = NULL; tiling_tree_root = NULL; /* * For each cusp, compute the quantity exp(min_d) needed in Lemma 4. */ compute_exp_min_d(cusp_neighborhoods->its_triangulation); /* * Compute the current meridional and longitudinal translations. */ meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); /* * prepare_sort_key() will need a linear transformation which * maps a fundamental parallelogram for the cusp (or the double * cover, in the case of a Klein bottle cusp) to the unit square. */ compute_parallelogram_to_square(meridian, longitude, parallelogram_to_square); /* * Read in the tetrahedra incident to the vertex at infinity, * and record the incident horoballs. * * Note: We check the horoballs when we put TilingTets onto the * tiling_queue (rather than when we pull it off) so we can handle * the special case of the initial tetrahedra more efficiently. */ read_initial_tetrahedra( cusp_neighborhoods->its_triangulation, cusp, &tiling_queue, &tiling_tree_root, &horoball_linked_list, cutoff_height); /* * Carry out the Corrected Algorithm, refined as in Lemma 7. */ while (tiling_queue.begin != NULL) { tiling_tet = get_tiling_tet_from_queue(&tiling_queue); for (f = 0; f < 4; f++) if (tiling_tet->neighbor_found[f] == FALSE && face_contains_useful_edge(tiling_tet, f, cutoff_height) == TRUE) { tiling_nbr = make_neighbor_tiling_tet(tiling_tet, f); prepare_sort_key(tiling_nbr, parallelogram_to_square); if (tiling_tet_on_tree(tiling_nbr, tiling_tree_root, meridian, longitude) == FALSE) { add_horoball_if_necessary(tiling_nbr, &horoball_linked_list, cutoff_height); add_tiling_tet_to_tree(tiling_nbr, &tiling_tree_root); add_tiling_tet_to_queue(tiling_nbr, &tiling_queue); } else my_free(tiling_nbr); } } /* * Free the TilingTets. */ free_tiling_tet_tree(tiling_tree_root); /* * Transfer the horoballs from the linked list * to a CuspNbhdHoroballList, and free the linked list. */ theHoroballList = transfer_horoballs(&horoball_linked_list); return theHoroballList; } static void compute_exp_min_d( Triangulation *manifold) { /* * Compute the quantity exp(min_d) needed * in Lemma 4 of get_full_horoball_list(). */ Cusp *cusp; EdgeClass *edge; double exp_d; VertexIndex v[2]; int i; /* * Initialize all exp_min_d's to infinity. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->exp_min_d = DBL_MAX; /* * The closest horoball to a given cusp will lie along an edge * of the canonical cell decomposition, so look at all edges * to find the true exp_min_d's. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * set_cusp_neighborhood_displacement() calls compute_cusp_stoppers(), * which in turn calls compute_intercusp_distances(), so we may use * the edge->intercusp_distance fields for exp_d. */ exp_d = exp(edge->intercusp_distance); v[0] = one_vertex_at_edge[edge->incident_edge_index]; v[1] = other_vertex_at_edge[edge->incident_edge_index]; for (i = 0; i < 2; i++) { cusp = edge->incident_tet->cusp[v[i]]; if (cusp->exp_min_d > exp_d) cusp->exp_min_d = exp_d; } } } static void compute_parallelogram_to_square( Complex meridian, Complex longitude, double parallelogram_to_square[2][2]) { /* * prepare_sort_key() needs a linear transformation which takes * a meridian to (1,0) and a longitude to (0,1), so TilingTets which * are equivalent under the Z + Z action of the group of covering * translations of the cusp be assigned corner coordinates which * differ by integers. The required linear transformation is * the inverse of * * ( meridian.real longitude.real ) * ( meridian.imag longitude.imag ) */ double det; det = meridian.real * longitude.imag - meridian.imag * longitude.real; parallelogram_to_square[0][0] = longitude.imag / det; parallelogram_to_square[0][1] = - longitude.real / det; parallelogram_to_square[1][0] = - meridian.imag / det; parallelogram_to_square[1][1] = meridian.real / det; } static void read_initial_tetrahedra( Triangulation *manifold, Cusp *cusp, TilingQueue *tiling_queue, TilingTet **tiling_tree_root, TilingHoroball **horoball_linked_list, double cutoff_height) { Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v, w; Orientation h; TilingTet *tiling_tet; EdgeIndex edge_index; EdgeClass *edge; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; tiling_tet = NEW_STRUCT(TilingTet); tiling_tet->underlying_tet = tet; tiling_tet->orientation = h; for (w = 0; w < 4; w++) if (w != v) { /* * Please see get_quick_edge_horoballs() for * an explanation of the horoball height. */ edge_index = edge_between_vertices[v][w]; edge = tet->edge_class[edge_index]; tiling_tet->corner[w] = complex_real_mult(cusp->displacement_exp, x[h][v][w]); tiling_tet->horoball_height[w] = exp( - edge->intercusp_distance); tiling_tet->neighbor_found[w] = TRUE; /* * To avoid duplications, record the TilingHoroball * iff this is the preferred tet and edge_index * to see it from. */ if (edge->incident_tet == tet && edge->incident_edge_index == edge_index && tiling_tet->horoball_height[w] >= cutoff_height) add_tiling_horoball_to_list(tiling_tet, w, horoball_linked_list); } else { tiling_tet->corner[w] = Infinity; tiling_tet->horoball_height[w] = DBL_MAX; tiling_tet->neighbor_found[w] = FALSE; } /* * Give each tiling_tet a random value of the sort key, * to keep the tree broad. */ tiling_tet->key = 0.5 * ((double) rand() / (double) RAND_MAX); add_tiling_tet_to_queue(tiling_tet, tiling_queue); add_tiling_tet_to_tree(tiling_tet, tiling_tree_root); } } } } static TilingTet *get_tiling_tet_from_queue( TilingQueue *tiling_queue) { TilingTet *tiling_tet; tiling_tet = tiling_queue->begin; if (tiling_queue->begin != NULL) tiling_queue->begin = tiling_queue->begin->next; if (tiling_queue->begin == NULL) tiling_queue->end = NULL; return tiling_tet; } static void add_tiling_tet_to_queue( TilingTet *tiling_tet, TilingQueue *tiling_queue) { tiling_tet->next = NULL; if (tiling_queue->end != NULL) { tiling_queue->end->next = tiling_tet; tiling_queue->end = tiling_tet; } else { tiling_queue->begin = tiling_tet; tiling_queue->end = tiling_tet; } } static void add_tiling_horoball_to_list( TilingTet *tiling_tet, VertexIndex v, TilingHoroball **horoball_linked_list) { TilingHoroball *tiling_horoball; tiling_horoball = NEW_STRUCT(TilingHoroball); tiling_horoball->data.center = tiling_tet->corner[v]; tiling_horoball->data.radius = 0.5 * tiling_tet->horoball_height[v]; tiling_horoball->data.cusp_index = tiling_tet->underlying_tet->cusp[v]->index; tiling_horoball->next = *horoball_linked_list; *horoball_linked_list = tiling_horoball; } static Boolean face_contains_useful_edge( TilingTet *tiling_tet, FaceIndex f, double cutoff_height) { /* * Note: We may assume that the face f has no vertices at the point * at infinity in upper half space. The reason is that the intial * tetrahedra have neighbor_found[] == TRUE for their side faces, and * get_full_horoball_list() calls us only if neighbor_found[f] is FALSE. */ /* * How many vertices incident to face f have horoballs * higher than cutoff_height? */ int num_big_horoballs; VertexIndex v, big_vertex; double min_separation_sq; num_big_horoballs = 0; for (v = 0; v < 4; v++) { if (v == f) continue; if (tiling_tet->horoball_height[v] > cutoff_height) { num_big_horoballs++; big_vertex = v; } } /* * If there are no big horoballs, * the face cannot contain a useful edge. */ if (num_big_horoballs == 0) return FALSE; /* * If there are two or more big horoballs, * the face must contain a useful edge. */ if (num_big_horoballs >= 2) return TRUE; /* * At this point we know that the unique large horoball lies * at the vertex big_vertex. There will be a useful edge iff * the distance from big_vertex to some other vertex of face f * is at least sqrt( height_of_big_vertex * cutoff_height * exp(min_d) ). * For a detailed explanation, please see Lemma 4 and the definition * of "useful edge" in get_full_horoball_list(). */ min_separation_sq = tiling_tet->horoball_height[big_vertex] * cutoff_height * tiling_tet->underlying_tet->cusp[big_vertex]->exp_min_d; for (v = 0; v < 4; v++) { if (v == f || v == big_vertex) continue; if (complex_modulus_squared( complex_minus( tiling_tet->corner[big_vertex], tiling_tet->corner[v] ) ) > min_separation_sq) return TRUE; } return FALSE; } static TilingTet *make_neighbor_tiling_tet( TilingTet *tiling_tet, FaceIndex f) { Tetrahedron *tet, *nbr; Permutation gluing; TilingTet *tiling_nbr; VertexIndex v, w, ff, some_vertex; double exp_d, c_squared; /* * Find the underlying tetrahedra and the gluing between them. */ tet = tiling_tet->underlying_tet; nbr = tet->neighbor[f]; gluing = tet->gluing[f]; /* * Set up the new TilingTet. */ tiling_nbr = NEW_STRUCT(TilingTet); tiling_nbr->underlying_tet = nbr; tiling_nbr->orientation = (parity[gluing] == orientation_preserving) ? tiling_tet->orientation : ! tiling_tet->orientation; for (v = 0; v < 4; v++) { if (v == f) continue; w = EVALUATE(gluing, v); tiling_nbr->corner[w] = tiling_tet->corner[v]; tiling_nbr->horoball_height[w] = tiling_tet->horoball_height[v]; tiling_nbr->neighbor_found[w] = FALSE; } /* * Deal with the remaining corner. */ ff = EVALUATE(gluing, f); /* * Call compute_fourth_corner() to locate the remaining ideal vertex. */ compute_fourth_corner( tiling_nbr->corner, ff, tiling_nbr->orientation, nbr->shape[complete]->cwl[ultimate]); /* * Use the lemma from get_quick_face_horoballs() to compute * the height of the remaining horoball. */ some_vertex = ! ff; exp_d = exp(nbr->edge_class[edge_between_vertices[ff][some_vertex]]->intercusp_distance); c_squared = complex_modulus_squared(complex_minus( tiling_nbr->corner[ff], tiling_nbr->corner[some_vertex])); tiling_nbr->horoball_height[ff] = c_squared / (exp_d * tiling_nbr->horoball_height[some_vertex]); /* * Don't backtrack to the TilingTet we just came from. */ tiling_nbr->neighbor_found[ff] = TRUE; /* * get_full_horoball_list() will decide whether to add tiling_nbr * to the linked list and tree, and whether to add the new horoball * to the horoball list. */ tiling_nbr->next = NULL; tiling_nbr->left = NULL; tiling_nbr->right = NULL; tiling_nbr->key = 0.0; return tiling_nbr; } static void prepare_sort_key( TilingTet *tiling_tet, double parallelogram_to_square[2][2]) { VertexIndex v; Complex transformed_corner[4]; static const double coefficient[4][2] = {{37.0, 25.0}, {43.0, 13.0}, {2.0, 29.0}, {11.0, 7.0}}; /* * Special case: To avoid questions of numerical accuracy, assign * the "illegal" key value of -1 to TilingTets incident to infinity * in upper half space. read_initial_tetrahedra() puts all such * TilingTets on the tree, so none need be added again. */ for (v = 0; v < 4; v++) if (complex_modulus(tiling_tet->corner[v]) > KEY_INFINITY) { tiling_tet->key = -1.0; return; } /* * Recall that we are tiling H^3 / (Z + Z), where the Z + Z is * the group of covering transformations of the cusp. In other words, * two TilingTets are equivalent iff corresponding corners differ * by some combination of meridional and/or longitudinal translations. * The linear transformation parallelogram_to_square maps a meridian * to (1,0) and a longitude to (0,1). We apply it to the TilingTets' * corners, so corresponding corners will differ by integers. */ for (v = 0; v < 4; v++) { transformed_corner[v].real = parallelogram_to_square[0][0] * tiling_tet->corner[v].real + parallelogram_to_square[0][1] * tiling_tet->corner[v].imag; transformed_corner[v].imag = parallelogram_to_square[1][0] * tiling_tet->corner[v].real + parallelogram_to_square[1][1] * tiling_tet->corner[v].imag; } /* * To implement a binary tree, we need a search key which is well * defined under the action of the meridional and longitudinal * translations. In terms of the transformed_corners, it should be * well defined under integer translations. Any integer linear * combination of the real and imaginary parts of the transformed * corners will do. We choose a random looking one, to reduce the * chances that distinct points will be assigned the same value of * the search key. (Of course the algorithm works correctly in any * case -- it's just faster if all the search key values are distinct.) * The linear combination provides a continuous map from the transformed * corners modulo integers to the reals modulo integers, i.e. to the * circle. We then map the circle to the interval [0, 1/2] in a * continuous way. (It's a two-to-one map, but that's unavoidable.) */ /* * Form a random looking integer combination of the corner coordinates. */ tiling_tet->key = 0.0; for (v = 0; v < 4; v++) { tiling_tet->key += coefficient[v][0] * transformed_corner[v].real; tiling_tet->key += coefficient[v][1] * transformed_corner[v].imag; } /* * Take the fractional part. */ tiling_tet->key -= floor(tiling_tet->key); /* * Fold the unit interval [0,1] onto the half interval [0, 1/2] * in ensure continuity. */ if (tiling_tet->key > 0.5) tiling_tet->key = 1.0 - tiling_tet->key; } static Boolean tiling_tet_on_tree( TilingTet *tiling_tet, TilingTet *tiling_tree_root, Complex meridian, Complex longitude) { TilingTet *subtree_stack, *subtree; double delta; Boolean left_flag, right_flag; FaceIndex f; /* * As a special case, TilingTets incident to infinity in upper half * space are already all on the tree. prepare_sort_key() marks * duplicates of such TilingTets with a key value of -1. (Computing * and comparing the usual key value is awkward when some of the * numbers are infinite.) */ if (tiling_tet->key == -1.0) return TRUE; /* * Reliability is our first priority. Speed is second. So if * tiling_tet->key and subtree->key are close, we want to search both * the left and right subtrees. Otherwise we search only one or the * other. We implement the recursion using our own stack, rather than * the system stack, to avoid the possibility of a stack/heap collision * during deep recursions. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = tiling_tree_root; if (tiling_tree_root != NULL) tiling_tree_root->next_subtree = NULL; /* * Process the subtrees on the stack, * adding additional subtrees as needed. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * Compare the key values of the tiling_tet and the subtree's root. */ delta = tiling_tet->key - subtree->key; /* * Which side(s) should we search? */ left_flag = (delta < +KEY_EPSILON); right_flag = (delta > -KEY_EPSILON); /* * Put the subtrees we need to search onto the stack. */ if (left_flag && subtree->left) { subtree->left->next_subtree = subtree_stack; subtree_stack = subtree->left; } if (right_flag && subtree->right) { subtree->right->next_subtree = subtree_stack; subtree_stack = subtree->right; } /* * Check this TilingTet if the key values match. */ if (left_flag && right_flag) /* * Are the TilingTets translations of one another? */ if (same_corners(tiling_tet, subtree, meridian, longitude)) { /* * *subtree is a TilingTet which may or may not have been * processed yet. If not, then when we do process it, we * know there's no need to recreate tiling_tet's "parent". */ for (f = 0; f < 4; f++) subtree->neighbor_found[f] |= tiling_tet->neighbor_found[f]; return TRUE; } } return FALSE; } static Boolean same_corners( TilingTet *tiling_tet1, TilingTet *tiling_tet2, Complex meridian, Complex longitude) { /* * Are tiling_tet1 and tiling_tet2 translations of the same tetrahedron * in H^3/(Z + Z) ? * * Note: This function does *not* take into account the size of the * TilingTets. Two TilingsTets which were very tiny and very close * could cause a false positive, and such TilingTets could be mistakenly * omitted from the tiling. But that's not likely to happen for any * computationally feasible value of cutoff_epsilon. */ Complex offset, fractional_part, diff; double num_meridians, num_longitudes, error; VertexIndex v; /* * Is the offset between a pair of corresponding vertices * an integer combination of meridians and longitudes? */ offset = complex_minus( tiling_tet2->corner[0], tiling_tet1->corner[0]); fractional_part = offset; num_meridians = floor(fractional_part.imag / meridian.imag + 0.5); fractional_part = complex_minus( fractional_part, complex_real_mult(num_meridians, meridian)); num_longitudes = floor(fractional_part.real / longitude.real + 0.5); fractional_part = complex_minus( fractional_part, complex_real_mult(num_longitudes, longitude)); if (complex_modulus(fractional_part) > CORNER_EPSILON) return FALSE; /* * Do all pairs of corresponding vertices differ by the same offset? */ for (v = 1; v < 4; v++) { diff = complex_minus( tiling_tet2->corner[v], tiling_tet1->corner[v]); error = complex_modulus(complex_minus(offset, diff)); if (error > CORNER_EPSILON) return FALSE; } return TRUE; } static void add_tiling_tet_to_tree( TilingTet *tiling_tet, TilingTet **tiling_tree_root) { /* * tiling_tet_on_tree() has already checked that tiling_tet is not * a translation of any TilingTet already on the tree. So here we * just add it in the appropriate spot, based on the key value. */ TilingTet **location; location = tiling_tree_root; while (*location != NULL) { if (tiling_tet->key <= (*location)->key) location = &(*location)->left; else location = &(*location)->right; } *location = tiling_tet; tiling_tet->left = NULL; tiling_tet->right = NULL; } static void add_horoball_if_necessary( TilingTet *tiling_tet, TilingHoroball **horoball_linked_list, double cutoff_height) { VertexIndex v; for (v = 0; v < 4; v++) { /* * Ignore horoballs which are too small. */ if (tiling_tet->horoball_height[v] < cutoff_height) continue; /* * Recall the convention made in get_full_horoball_list() that * each horoball is recorded only by the TilingTet * which contains its north pole. If the north pole lies on the * boundary of two TilingTets, they both record it. */ if (contains_north_pole(tiling_tet, v) == TRUE) add_tiling_horoball_to_list(tiling_tet, v, horoball_linked_list); } } static Boolean contains_north_pole( TilingTet *tiling_tet, VertexIndex v) { /* * Check whether vertex v lies within the triangle defined * by the remaining three vertices. */ int i; VertexIndex w[3]; Complex u[3]; double s[3], det; /* * Label the remaining three vertices w[0], w[1] and w[2] * as you go counterclockwise around the triangle they define * on the boundary of upper half space. * * w[2] * / \ * / \ * / \ * w[0]-------w[1] * * If v lies inside that triangle we'll return TRUE; * otherwise we'll return FALSE. */ w[0] = !v; if (tiling_tet->orientation == right_handed) { w[1] = remaining_face[v][w[0]]; w[2] = remaining_face[w[0]][v]; } else { w[1] = remaining_face[w[0]][v]; w[2] = remaining_face[v][w[0]]; } /* * The vector u[i] runs from v to w[i]. * * w[2] * / | \ * / v \ * / / \ \ * w[0]-------w[1] */ for (i = 0; i < 3; i++) u[i] = complex_minus(tiling_tet->corner[w[i]], tiling_tet->corner[v]); /* * s[i] is the squared length of the triangle's i-th side. */ for (i = 0; i < 3; i++) s[i] = complex_modulus_squared(complex_minus(tiling_tet->corner[w[(i+1)%3]], tiling_tet->corner[w[i]])); /* * If v lies in the triangle's interior, we of course return TRUE. * But if v lies (approximately) on one of the triangle's sides, we * also want to return TRUE, so that in ambiguous cases horoballs are * recorded twice, not zero times. * * We need a scale invariant measure of the signed distance from v * to each side of the triangle, so that we can apply our error epsilon * in a meaningful way. (We don't want to return TRUE for *all* tiny * triangles, simply because they are tiny!) The determinant * * | u[i].real u[i+1].real | * det = | | * | u[i].imag u[i+1].imag | * * gives twice the area of the triangle (v, w[i], w[i+1]). * Therefore det/dist(w[i], w[i+1]) gives the triangle's altitude, * and det/dist(w[i], w[i+1])^2 = det/s[i] gives the ratio of * the altitude to the length of the side. If that ratio is at least * -NORTH_POLE_EPSILON for all sides, we return TRUE. */ for (i = 0; i < 3; i++) { det = u[i].real * u[(i+1)%3].imag - u[i].imag * u[(i+1)%3].real; if (det / s[i] < -NORTH_POLE_EPSILON) return FALSE; } return TRUE; } static void free_tiling_tet_tree( TilingTet *tiling_tree_root) { TilingTet *subtree_stack, *subtree; /* * Implement the recursive freeing algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = tiling_tree_root; if (tiling_tree_root != NULL) tiling_tree_root->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left != NULL) { subtree->left->next_subtree = subtree_stack; subtree_stack = subtree->left; } if (subtree->right != NULL) { subtree->right->next_subtree = subtree_stack; subtree_stack = subtree->right; } /* * Free the subtree's root node. */ my_free(subtree); } } static CuspNbhdHoroballList *transfer_horoballs( TilingHoroball **horoball_linked_list) { CuspNbhdHoroballList *theHoroballList; TilingHoroball *the_tiling_horoball, *the_dead_horoball; int i; /* * Allocate the wrapper. */ theHoroballList = NEW_STRUCT(CuspNbhdHoroballList); /* * Count the horoballs. */ for ( the_tiling_horoball = *horoball_linked_list, theHoroballList->num_horoballs = 0; the_tiling_horoball != NULL; the_tiling_horoball = the_tiling_horoball->next, theHoroballList->num_horoballs++) ; /* * If we found some horoballs, allocate an array * for the CuspNbhdHoroballs. */ if (theHoroballList->num_horoballs > 0) theHoroballList->horoball = NEW_ARRAY(theHoroballList->num_horoballs, CuspNbhdHoroball); else theHoroballList->horoball = NULL; /* * Copy the data from the linked list to the array. */ for ( the_tiling_horoball = *horoball_linked_list, i = 0; the_tiling_horoball != NULL; the_tiling_horoball = the_tiling_horoball->next, i++) theHoroballList->horoball[i] = the_tiling_horoball->data; /* * Free the linked list. */ while (*horoball_linked_list != NULL) { the_dead_horoball = *horoball_linked_list; *horoball_linked_list = the_dead_horoball->next; my_free(the_dead_horoball); } return theHoroballList; } void free_cusp_neighborhood_horoball_list( CuspNbhdHoroballList *horoball_list) { if (horoball_list != NULL) { if (horoball_list->horoball != NULL) my_free(horoball_list->horoball); my_free(horoball_list); } } static int CDECL compare_horoballs( const void *horoball0, const void *horoball1) { if (((CuspNbhdHoroball *)horoball0)->radius < ((CuspNbhdHoroball *)horoball1)->radius) return -1; else if (((CuspNbhdHoroball *)horoball0)->radius > ((CuspNbhdHoroball *)horoball1)->radius) return +1; else return 0; } static void cull_duplicate_horoballs( Cusp *cusp, CuspNbhdHoroballList *aHoroballList) { int original_num_horoballs, i, j, k; Complex meridian, longitude, delta; double cutoff_radius, mult; Boolean distinct; /* * Note the meridional and longitudinal translations. */ meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); /* * Examine each horoball on the list. * If it's distinct from all previously examined horoballs, keep it. * Otherwise ignore it. * * We could implement this algorithm by copying the horoballs * we want to keep from the array aHoroballList->horoball onto * a new array. But it's simpler just to copy the array onto itself. * (This sounds distressing at first, but if you think it through * you'll realize that it's perfectly safe.) * * The index i keeps track of the horoball we're examining. * The index j keeps track of where we're writing it to. */ original_num_horoballs = aHoroballList->num_horoballs; for (i = 0, j = 0; j < original_num_horoballs; j++) { /* * If the j-th horoball is distinct from all previous ones, copy * it into the i-th position of the array. In practice, of course, * we compare it only to previous horoballs of the same radius. * We may assume that get_cusp_neighborhood_horoballs() has * already sorted the horoballs in order of increasing size. */ /* * Assume the j-th horoball is distinct from horoballs * 0 through i - 1, unless we discover otherwise. */ distinct = TRUE; /* * What is the smallest radius we should consider? */ cutoff_radius = aHoroballList->horoball[j].radius - DUPLICATE_RADIUS_EPSILON; /* * Start with horoball i - 1, and work downwards until either * we reach horoball 0, or the radii drop below the cutoff_radius. */ for (k = i; --k >= 0; ) { /* * If horoball k is too small, there is no need to examine * the remaining ones, which are even smaller. */ if (aHoroballList->horoball[k].radius < cutoff_radius) break; /* * Let delta be the difference between the center of j and * the center of k, modulo the Z + Z action of the group * of covering transformations of the cusp. */ delta = complex_minus(aHoroballList->horoball[j].center, aHoroballList->horoball[k].center); mult = floor(delta.imag / meridian.imag + 0.5); delta = complex_minus(delta, complex_real_mult(mult, meridian)); mult = floor(delta.real / longitude.real + 0.5); delta = complex_minus(delta, complex_real_mult(mult, longitude)); /* * If the distance between the centers of horoballs j and k is * less than the radius, then the horoballs must be equivalent. */ if (complex_modulus(delta) < cutoff_radius) { distinct = FALSE; break; } } if (distinct == TRUE) { aHoroballList->horoball[i] = aHoroballList->horoball[j]; i++; } else aHoroballList->num_horoballs--; } } CuspNbhdSegmentList *get_cusp_neighborhood_Ford_domain( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { Cusp *cusp; CuspNbhdSegmentList *theSegmentList; CuspNbhdSegment *next_segment; Tetrahedron *tet, *nbr_tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v, nbr_v, u, nbr_u, w[3]; Orientation h, nbr_h; FaceIndex f, nbr_f; Permutation gluing; int i; Complex corner[3], delta, inward_normal, offset, p; double length, tilt, a[2], b[2], c[2], det; /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Allocate the wrapper for the array. */ theSegmentList = NEW_STRUCT(CuspNbhdSegmentList); /* * We don't know ahead of time exactly how many CuspNbhdSegments * we'll need. Torus cusps report each segment once, but Klein * bottle cusps report each segment twice, once for each sheet. * * To get an upper bound on the number of segments, * assume all cusps are Klein bottle cusps. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * 3 sides/triangle * / 2 Ford edges/side (no need to draw each edge twice) * * = 12n Ford edges */ theSegmentList->segment = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdSegment); /* * Keep a pointer to the first empty CuspNbhdSegment. */ next_segment = theSegmentList->segment; /* * Compute the Ford domain's vertices. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; /* * There are at least two ways to locate the Ford vertex. * * (1) Use Theorem 3.1 of * * M. Sakuma and J. Weeks, The generalized tilt * formula, Geometriae Dedicata 55 (115-123) 1995, * * which states that the Euclidean distance in the * cusp from (the projection of) the Ford vertex * to a side of the enclosing triangle equals the * tilt on that side. (Or better yet, see the * preprint version of the article, which has a lot * more pictures and fuller explanations.) * * (2) Write down the equations for the three planes * which lie halfway between the cusp at infinity * and the cusp at each of the three remaining ideal * vertices. Each such plane appears at a Euclidean * hemisphere. Subtracting the equations for two * such hemispheres gives a linear equation, and * two such linear equations may be solved * simultaneously to locate the Ford vertex. * * Either of the above approaches should work fine. * Here we choose approach (1) because it looks a tiny * bit simpler numerically. */ /* * Label the triangles corners w[0], w[1] and w[2], * going counterclockwise around the triangle. * * w[2] * / \ * / \ * / \ * w[0]-------w[1] */ w[0] = !v; if (h == right_handed) { w[1] = remaining_face[w[0]][v]; w[2] = remaining_face[v][w[0]]; } else { w[1] = remaining_face[v][w[0]]; w[2] = remaining_face[w[0]][v]; } /* * Record the triangle's corners. */ for (i = 0; i < 3; i++) corner[i] = complex_real_mult(cusp->displacement_exp, x[h][v][w[i]]); /* * w[2] * / \ * ------/---*--\------- * / \ * w[0]-------w[1] * * The Ford vertex lies on a line parallel to a side of * the triangle at a distance "tilt" away (by Theorem 3.1 * of Sakuma & Weeks). Find the equations of such lines * (the third is redundant -- it could perhaps be used * to enhance accuracy if desired). */ for (i = 0; i < 2; i++) { /* * Make yourself a sketch as you follow along. */ delta = complex_minus(corner[(i+1)%3], corner[i]); inward_normal.real = +delta.imag; inward_normal.imag = -delta.real; length = complex_modulus(inward_normal); tilt = tet->tilt[w[(i+2)%3]]; offset = complex_real_mult(tilt/length, inward_normal); p = complex_plus(corner[i], offset); /* * The equation of the desired line is * * y - p.imag delta.imag * ---------- = ---------- * x - p.real delta.real * * Cross multiply to get * * delta.imag * x - delta.real * y * = delta.imag * p.real - delta.real * p.imag * * This last equation also has a natural cross product * interpretation: delta X (x,y) = p X (x,y). * * Record the equation as ax + by = c. */ a[i] = delta.imag; b[i] = -delta.real; c[i] = delta.imag * p.real - delta.real * p.imag; } /* * Solve the matrix equation * * ( a[0] b[0] ) (x) = (c[0]) * ( a[1] b[1] ) (y) (c[1]) * => * (x) = _1_ ( b[1] -b[0] ) (c[0]) * (y) det (-a[1] a[0] ) (c[1]) */ det = a[0]*b[1] - a[1]*b[0]; FORD_VERTEX(x,h,v).real = (b[1]*c[0] - b[0]*c[1]) / det; FORD_VERTEX(x,h,v).imag = (a[0]*c[1] - a[1]*c[0]) / det; } } } /* * Record the Ford domain edges. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; for (f = 0; f < 4; f++) { if (f == v) continue; gluing = tet->gluing[f]; nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(gluing, f); /* * We want to report each segment only once, so we * make the (arbitrary) convention that we report * a segment only from the Tetrahedron whose address * in memory is less. In the case of a Tetrahedron * glued to itself, we report it from the lower * FaceIndex. */ if (tet > nbr_tet || (tet == nbr_tet && f > nbr_f)) continue; /* * Don't report Ford edges dual to 2-cells which are * part of the arbitrary subdivision of the canonical * cell decomposition into tetrahdra. (They'd have * length zero anyway, but we want to be consistent * with how we report the triangulation. We rely on * the fact that proto_canonize() has computed the * tilts and left them in place. The sum of the tilts * will never be positive for a subdivision of the * canonical cell decomposition. If it's close to * zero, ignore the Ford edge dual to that face. */ if (tet->tilt[f] + nbr_tet->tilt[nbr_f] > -CONCAVITY_EPSILON) continue; /* * This edge has passed all its tests, so record it. * Keep in mind that the coordinate systems in * neighboring Tetrahedra may differing by translations. */ nbr_v = EVALUATE(gluing, v); nbr_h = (parity[gluing] == orientation_preserving) ? h : !h; next_segment->endpoint[0] = FORD_VERTEX( tet->cusp_nbhd_position->x, h, v); next_segment->endpoint[1] = FORD_VERTEX(nbr_tet->cusp_nbhd_position->x, nbr_h, nbr_v); /* * The segment indices are currently used only * for the triangulation, not the Ford domain. */ next_segment->start_index = -1; next_segment->middle_index = -1; next_segment->end_index = -1; /* * Compensate for the (possibly) translated * coordinate systems. Compare the position of * a vertex u as seen by tet and nbr_tet. */ u = remaining_face[v][f]; nbr_u = EVALUATE(gluing, u); next_segment->endpoint[1] = complex_plus ( next_segment->endpoint[1], complex_real_mult ( cusp->displacement_exp, complex_minus ( tet->cusp_nbhd_position->x[ h][ v][ u], nbr_tet->cusp_nbhd_position->x[nbr_h][nbr_v][nbr_u] ) ) ); /* * Move on. */ next_segment++; } } } } /* * How many segments did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdSegment).) */ theSegmentList->num_segments = next_segment - theSegmentList->segment; /* * Did we find more segments than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theSegmentList->num_segments > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_Ford_domain", "cusp_neighborhoods"); return theSegmentList; } snappea-3.0d3/SnapPeaKernel/code/cusp_shapes.c0100444000175000017500000003440706742675501017372 0ustar babbab/* * cusp_shapes.c * * This file provides the function * * void compute_cusp_shapes(Triangulation *manifold, * FillingStatus which_structure); * * which computes the shape of each unfilled cusp. The shape of an * orientable cusp is defined to be the ratio of the complex numbers * representing the longitudinal and meridional translations * (i.e. longitude/meridian). The shape of a nonorientable cusp is * defined to be the shape of the cusp's orientation double cover; * it is always pure imaginary. (Another reasonable definition for * the shape of a nonorientable cusp would be to divide the shape * of the orientation double cover by two, to correspond more closely * to the geometry of the nonorientable cusp's fundamental domain, * but I decided not to do this.) * * compute_cusp_shapes() computes the cusp shapes using the which_structure * (initial or current) hyperbolic structure, and stores each computed cusp * shape in the cusp_shape[which_structure] field of the Cusp data structure. * * To estimate the computed cusp shape's precision, compute_cusp_shapes() * computes the cusp shape using both the ultimate and penultimate values of * the Tetrahedron shapes. The number of digits to which the answers agree * is stored in the shape_precision[which_structure] field of the Cusp data * structure. * * do_Dehn_filling() calls compute_cusp_shapes() to maintain correct values * in the cusp_shape[current] field of each unfilled cusp whenever a * hyperbolic structure is present. find_complete_hyperbolic_structure() * takes responsibility for copying the original shape of each cusp into * the cusp_shape[initial] field. * * Cusp shapes are computed iff manifold->solution_type[which_structure] * is geometric_solution, nongeometric_solution, flat_solution, or * (tentatively) other_solution. For other solution types (degenerate_solution, * etc.) all cusp shapes are set to zero. * * Technical note: with the usual orientation conventions for the * longitude and meridian, we must view the cusp from the fat part * of the manifold looking out towards infinity in order to have the * imaginary part of the cusp shape be positive. The code * in compute_translation() views the cusp from infinity looking in * towards the fat part of the manifold (as usual), and then * compute_one_cusp_shape() takes the complex conjugate of the shape * at the very end. * * The function shortest_cusp_basis() in shortest_cusp_basis.c converts * the (meridian, longitude) basis to the (shortest, second shortest) * basis. cusp_modulus() uses the latter to compute the cusp modulus * as (second shortest translation)/(shortest translation). * * 96/9/27 Added the which_structure parameter to compute_cusp_shapes() * so it can compute the cusp shapes for either the initial (complete) * or the current (filled) hyperbolic structure. Previously it used only * the current structure. */ #include "kernel.h" static void compute_the_cusp_shapes(Triangulation *manifold, FillingStatus which_structure); static void set_all_shapes_to_zero(Triangulation *manifold, FillingStatus which_structure); static void compute_one_cusp_shape(Triangulation *manifold, Cusp *cusp, FillingStatus which_structure); static void compute_translation(PositionedTet *initial_ptet, PeripheralCurve which_curve, TraceDirection which_direction, Complex translation[2], FillingStatus which_structure); static PositionedTet find_start(Triangulation *manifold, Cusp *cusp); void compute_cusp_shapes( Triangulation *manifold, FillingStatus which_structure) { /* * If we have some reasonable (or semi-reasonable) hyperbolic * structure, then compute the cusp shapes. Otherwise, fill in * all shapes with zeros. */ switch (manifold->solution_type[which_structure]) { case geometric_solution: case nongeometric_solution: case flat_solution: case other_solution: /* we'll give the cusps of other_solution a try */ /* other_solution can be moved below if its */ /* cusp shapes can't be computed meaningfully */ compute_the_cusp_shapes(manifold, which_structure); break; case not_attempted: case degenerate_solution: case no_solution: set_all_shapes_to_zero(manifold, which_structure); break; } } static void compute_the_cusp_shapes( Triangulation *manifold, FillingStatus which_structure) { Cusp *cusp; /* * Call compute_one_cusp_shape() for each unfilled cusp. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if ( which_structure == initial || (which_structure == current && cusp->is_complete)) compute_one_cusp_shape(manifold, cusp, which_structure); else { cusp->cusp_shape[which_structure] = Zero; cusp->shape_precision[which_structure] = 0; } } static void set_all_shapes_to_zero( Triangulation *manifold, FillingStatus which_structure) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[which_structure] = Zero; cusp->shape_precision[which_structure] = 0; } } static void compute_one_cusp_shape( Triangulation *manifold, Cusp *cusp, FillingStatus which_structure) { PositionedTet initial_ptet; TraceDirection direction[2]; /* direction[M/L] */ Complex translation[2][2], /* translation[M/L][ultimate/penultimate] */ shape[2]; /* shape[ultimate/penultimate] */ int i; /* * Compute the longitudinal and meridional translations, and * divide them to get the cusp shape. * * Do parallel computations for the ultimate and penultimate shapes, * to estimate the accuracy of the final answer. */ /* * Find and position a tetrahedron so that the near edge of the top * vertex intersects both the meridian and the longitude. */ initial_ptet = find_start(manifold, cusp); for (i = 0; i < 2; i++) /* which curve */ { /* * Decide whether the meridian and longitude cross the near edge of the * top vertex in a forwards or backwards direction. */ direction[i] = (initial_ptet.tet->curve[i][initial_ptet.orientation] [initial_ptet.bottom_face] [initial_ptet.near_face] > 0) ? trace_forwards: trace_backwards; /* * Compute the translation. */ compute_translation(&initial_ptet, i, direction[i], translation[i], which_structure); } /* * Compute the cusp shape. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ shape[i] = complex_div(translation[L][i], translation[M][i]); /* will handle division by Zero correctly */ /* * Record the cusp shape and its accuracy. */ cusp->cusp_shape[which_structure] = shape[ultimate]; cusp->shape_precision[which_structure] = complex_decimal_places_of_accuracy(shape[ultimate], shape[penultimate]); /* * Adjust for the fact that the meridian and/or the longitude may have * been traced backwards. */ if (direction[M] != direction[L]) { cusp->cusp_shape[which_structure].real = - cusp->cusp_shape[which_structure].real; cusp->cusp_shape[which_structure].imag = - cusp->cusp_shape[which_structure].imag; } /* * As explained at the top of this file, the usual convention for the * cusp shape requires viewing the cusp from the fat part of * the manifold looking out, rather than from the cusp looking in, as * in done in the rest of SnapPea. For this reason, we must take the * complex conjugate of the final cusp shape. */ cusp->cusp_shape[which_structure].imag = - cusp->cusp_shape[which_structure].imag; } static void compute_translation( PositionedTet *initial_ptet, PeripheralCurve which_curve, TraceDirection which_direction, Complex translation[2], /* returns translations based on ultimate */ /* and penultimate shapes */ FillingStatus which_structure) { PositionedTet ptet; int i, initial_strand, strand, *this_vertex, near_strands, left_strands; Complex left_endpoint[2], /* left_endpoint[ultimate/penultimate] */ right_endpoint[2], /* right_endpoint[ultimate/penultimate] */ old_diff, new_diff, rotation; /* * Place the near edge of the top vertex of the initial_ptet in the * complex plane with its left endpoint at zero and its right endpoint at one. * Trace the curve which_curve in the direction which_direction, using the * shapes of the ideal tetrahedra to compute the position of endpoints of * each edge we cross. When we return to our starting point in the manifold, * the position of the left endpoint (or the position of the right endpoint * minus one) will tell us the translation. * * Note that we are working in the orientation double cover of the cusp. * * Here's how we keep track of where we are. At each step, we are always * at the near edge of the top vertex (i.e. the truncated vertex opposite * the bottom face) of the PositionedTet ptet. The curve (i.e. the * meridian or longitude) may cross that edge several times. The variable * "strand" keeps track of which intersection we are at; 0 means we're at * the strand on the far left, 1 means we're at the next strand, etc. */ ptet = *initial_ptet; initial_strand = 0; strand = initial_strand; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { left_endpoint[i] = Zero; right_endpoint[i] = One; } do { /* * Note the curve's intersection numbers with the near side and left side. */ this_vertex = ptet.tet->curve[which_curve][ptet.orientation][ptet.bottom_face]; near_strands = this_vertex[ptet.near_face]; left_strands = this_vertex[ptet.left_face]; /* * If we are tracing the curve backwards, negate the intersection numbers * so the rest of compute_translation() can enjoy the illusion that we * are tracing the curve forwards. */ if (which_direction == trace_backwards) { near_strands = - near_strands; left_strands = - left_strands; } /* * Does the current strand bend to the left or to the right? */ if (strand < FLOW(near_strands, left_strands)) { /* * The current strand bends to the left. */ /* * The left_endpoint remains fixed. * Update the right_endpoint. * * The plan is to compute the vector old_diff which runs * from left_endpoint to right_endpoint, multiply it by the * complex edge parameter to get the vector new_diff which * runs from left_endpoint to the new value of right_endpoint, * and then add new_diff to left_endpoint to get the new * value of right_endpoint itself. * * Note that the complex edge parameters are always expressed * relative to the right_handed Orientation, so if we are * viewing this Tetrahedron relative to the left_handed * Orientation, we must take the conjugate-inverse of the * edge parameter. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_diff = complex_minus(right_endpoint[i], left_endpoint[i]); rotation = ptet.tet->shape[which_structure]->cwl[i][edge3_between_faces[ptet.near_face][ptet.left_face]].rect; if (ptet.orientation == left_handed) { rotation = complex_div(One, rotation); /* invert . . . */ rotation.imag = - rotation.imag; /* . . . and conjugate */ } new_diff = complex_mult(old_diff, rotation); right_endpoint[i] = complex_plus(left_endpoint[i], new_diff); } /* * strand remains unchanged. */ /* * Move the PositionedTet onward, following the curve. */ veer_left(&ptet); } else { /* * The current strand bends to the right. * * Proceed as above, but note that * * (1) We now divide by the complex edge parameter * instead of multiplying by it. * * (2) We must adjust the variable "strand". Some of the strands * from the near edge may be peeling off to the left (in which * case left_strands is negative), or some strands from the left * edge may be joining those from the near edge in passing to * the right edge (in which case left_strands is positive). * Either way, the code "strand += left_strands" is correct. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_diff = complex_minus(left_endpoint[i], right_endpoint[i]); rotation = ptet.tet->shape[which_structure]->cwl[i][edge3_between_faces[ptet.near_face][ptet.right_face]].rect; if (ptet.orientation == left_handed) { rotation = complex_div(One, rotation); rotation.imag = - rotation.imag; } new_diff = complex_div(old_diff, rotation); left_endpoint[i] = complex_plus(right_endpoint[i], new_diff); } strand += left_strands; veer_right(&ptet); } } while ( ! same_positioned_tet(&ptet, initial_ptet) || strand != initial_strand); /* * Write the computed translations, and return. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ translation[i] = left_endpoint[i]; } static PositionedTet find_start( Triangulation *manifold, Cusp *cusp) { Tetrahedron *tet; VertexIndex vertex; Orientation orientation; FaceIndex side; PositionedTet ptet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (vertex = 0; vertex < 4; vertex++) { if (tet->cusp[vertex] != cusp) continue; for (orientation = right_handed; orientation <= left_handed; orientation++) for (side = 0; side < 4; side++) if (side != vertex && tet->curve[M][orientation][vertex][side] != 0 && tet->curve[L][orientation][vertex][side] != 0) { /* * Record the current position of the Tetrahedron in * a PositionedTet structure . . . */ ptet.tet = tet; ptet.bottom_face = vertex; ptet.near_face = side; if (orientation == right_handed) { ptet.left_face = remaining_face[ptet.bottom_face][ptet.near_face]; ptet.right_face = remaining_face[ptet.near_face][ptet.bottom_face]; } else { ptet.left_face = remaining_face[ptet.near_face][ptet.bottom_face]; ptet.right_face = remaining_face[ptet.bottom_face][ptet.near_face]; } ptet.orientation = orientation; /* * . . . and return it. */ return ptet; } } /* * The program should never get to this point. */ uFatalError("find_start", "cusp_shapes"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return ptet; } snappea-3.0d3/SnapPeaKernel/code/cusps.c0100444000175000017500000002266206742675501016212 0ustar babbab/* * cusps.c * * This file contains the functions * * void create_cusps(Triangulation *manifold); * void create_fake_cusps(Triangulation *manifold); * void count_cusps(Triangulation *manifold); * Boolean mark_fake_cusps(Triangulation *manifold); * * create_cusps() is used within the kernel to assign Cusp data * structures to Triangulations. It assumes the neighbor and gluing * fields have been set (all other fields are optional). It assigns * cusp indices, but does not install peripheral curves, determine * the CuspTopologies, or count the cusps. You should call * peripheral_curves() to install the peripheral curves and determine * the CuspTopologies, then call count_cusps() to set num_cusps, * num_or_cusps and num_nonor_cusps. * * create_fake_cusps() is used within the kernel to assign Cusp data * structures to the "fake cusps" corresponding to finite vertices. * It assumes fake cusps are indicated by tet->cusp[v] fields of NULL. * The fake cusps are numbered -1, -2, etc. As explained in the * documentation at the top of finite_vertices.c, finite vertices use * only the is_finite, index, prev and next fields of the Cusp data * structure. create_fake_cusps() does not disturb the real cusps or * the non-NULL tet->cusp[v] fields. * * count_cusps() counts the Cusps of each CuspTopology, and sets * manifold->num_cusps, manifold->num_or_cusps and manifold->num_nonor_cusps. * * mark_fake_cusps() distinguishes real cusps from fake cusps * ( = finite vertices) by computing the Euler characteristic. * Sets is_finite to TRUE for fake cusps, and renumbers all cusps so that * real cusps have consecutive nonnegative indices beginning at 0 and * fake cusps have consecutive negative indices beginning at -1. * Returns TRUE if fake cusps are present, FALSE otherwise. */ #include "kernel.h" typedef struct { Tetrahedron *tet; VertexIndex v; } IdealVertex; static void compute_cusp_Euler_characteristics(Triangulation *manifold); void create_cusps( Triangulation *manifold) { int count; Tetrahedron *tet; VertexIndex v; /* * Make sure no Cusps are present, and everything is neat and tidy. */ error_check_for_create_cusps(manifold); /* * The variable "count" will keep track of the next index * to be assigned. The first Cusp we create will have * index 0, the next will have 1, and so on. */ count = 0; /* * We look at each vertex of each Tetrahedron, and whenever we * encounter a vertex with no assigned Cusp, we create a Cusp * for it and recursively assign it to neighboring ideal vertices. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == NULL) create_one_cusp(manifold, tet, FALSE, v, count++); } void error_check_for_create_cusps( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; if (manifold->num_cusps != 0 || manifold->num_or_cusps != 0 || manifold->num_nonor_cusps != 0 || manifold->cusp_list_begin.next != &manifold->cusp_list_end) uFatalError("error_check_for_create_cusps", "cusps"); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] != NULL) uFatalError("error_check_for_create_cusps", "cusps"); } void create_fake_cusps( Triangulation *manifold) { int count; Tetrahedron *tet; VertexIndex v; /* * The variable "count" will keep track of the (negative) index * most recently assigned. The first finite vertex we create * will have index -1, the next will have -2, and so on. */ count = 0; /* * We look at each vertex of each Tetrahedron, and whenever we * encounter an ideal vertex with no assigned Cusp, we create a Cusp * for it and assign it recursively to neighboring ideal vertices. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == NULL) create_one_cusp(manifold, tet, TRUE, v, --count); } void create_one_cusp( Triangulation *manifold, Tetrahedron *tet, Boolean is_finite, VertexIndex v, int cusp_index) { Cusp *cusp; IdealVertex *queue; int queue_first, queue_last; Tetrahedron *tet1, *nbr; VertexIndex v1, nbr_v; FaceIndex f; /* * Create the cusp, add it to the list, and set * the is_finite and index fields. */ cusp = NEW_STRUCT(Cusp); initialize_cusp(cusp); INSERT_BEFORE(cusp, &manifold->cusp_list_end); cusp->is_finite = is_finite; cusp->index = cusp_index; /* * We don't set the topology, is_complete, m, l, holonomy, * cusp_shape or shape_precision fields. * * For "real" cusps the calling routine may * * (1) call peripheral_curves() to set the cusp->topology, * * (2) keep the default values of cusp->is_complete, * cusp->m and cusp->l as set by initialize_cusp(), and * * (3) let the holonomy and cusp_shape be computed automatically * when hyperbolic structure is computed. * * Alternatively, the calling routine may set these fields in other * ways, as it sees fit. * * If we were called by create_fake_cusps(), then the above fields * are all irrelevant and ignored. */ /* * Set the tet->cusp field at all vertices incident to the new cusp. */ /* * Allocate space for a queue of pointers to the IdealVertices. * Each IdealVertex will appear on the queue at most once, so an * array of length 4 * manifold->num_tetrahedra will suffice. */ queue = NEW_ARRAY(4 * manifold->num_tetrahedra, IdealVertex); /* * Set the cusp of the given IdealVertex... */ tet->cusp[v] = cusp; /* * ...and put it on the queue. */ queue_first = 0; queue_last = 0; queue[0].tet = tet; queue[0].v = v; /* * Start processing the queue. */ do { /* * Pull an IdealVertex off the front of the queue. */ tet1 = queue[queue_first].tet; v1 = queue[queue_first].v; queue_first++; /* * Look at the three neighboring IdealVertices. */ for (f = 0; f < 4; f++) { if (f == v1) continue; nbr = tet1->neighbor[f]; nbr_v = EVALUATE(tet1->gluing[f], v1); /* * If the neighbor's cusp hasn't been set... */ if (nbr->cusp[nbr_v] == NULL) { /* * ...set it... */ nbr->cusp[nbr_v] = cusp; /* * ...and add it to the end of the queue. */ ++queue_last; queue[queue_last].tet = nbr; queue[queue_last].v = nbr_v; } } } while (queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); } void count_cusps( Triangulation *manifold) { Cusp *cusp; manifold->num_cusps = 0; manifold->num_or_cusps = 0; manifold->num_nonor_cusps = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { manifold->num_cusps++; switch (cusp->topology) { case torus_cusp: manifold->num_or_cusps++; break; case Klein_cusp: manifold->num_nonor_cusps++; break; default: uFatalError("count_cusps", "cusps"); } } } Boolean mark_fake_cusps( Triangulation *manifold) { int real_cusp_count, fake_cusp_count; Cusp *cusp; compute_cusp_Euler_characteristics(manifold); real_cusp_count = 0; fake_cusp_count = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) switch (cusp->euler_characteristic) { case 0: cusp->is_finite = FALSE; cusp->index = real_cusp_count++; break; case 2: cusp->is_finite = TRUE; cusp->index = --fake_cusp_count; break; default: uFatalError("mark_fake_cusps", "cusps"); } return (fake_cusp_count < 0); } static void compute_cusp_Euler_characteristics( Triangulation *manifold) { Cusp *cusp; EdgeClass *edge; Tetrahedron *tet; VertexIndex v, v0, v1; /* * Initialize all Euler characteristics to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->euler_characteristic = 0; /* * It'll be easier to count the edges twice (once from each side) * so compute twice the Euler characteristic and divide by two * at the end. */ /* * Count the vertices in the triangulation of the boundary, * which come from edges in the manifold itself. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { tet = edge->incident_tet; v0 = one_vertex_at_edge[edge->incident_edge_index]; v1 = other_vertex_at_edge[edge->incident_edge_index]; tet->cusp[v0]->euler_characteristic += 2; tet->cusp[v1]->euler_characteristic += 2; } /* * Count the edges in the triangulation of the boundary, * which come from faces in the manifold itself. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cusp[v]->euler_characteristic -= 3; /* * Count the faces in the triangulation of the boundary, * which come from tetrahedra in the manifold itself. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cusp[v]->euler_characteristic += 2; /* * Divide by two (cf. above). */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->euler_characteristic % 2 != 0) uFatalError("compute_cusp_Euler_characteristics", "cusps"); cusp->euler_characteristic /= 2; } } snappea-3.0d3/SnapPeaKernel/code/Dehn_coefficients.c0100444000175000017500000000413706742675500020450 0ustar babbab/* * This file contains the functions * * Boolean all_Dehn_coefficients_are_integers(Triangulation *manifold); * Boolean Dehn_coefficients_are_integers(Cusp *cusp); * * Boolean all_Dehn_coefficients_are_relatively_prime_integers(Triangulation *manifold); * Boolean Dehn_coefficients_are_relatively_prime_integers(Cusp *cusp); * * Boolean all_cusps_are_complete(Triangulation *manifold); * Boolean all_cusps_are_filled(Triangulation *manifold); * * which are used within the kernel to test whether Dehn filling coefficients * are (relatively prime) integers. */ #include "kernel.h" Boolean all_Dehn_coefficients_are_integers( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_integers(cusp) == FALSE) return FALSE; return TRUE; } Boolean all_Dehn_coefficients_are_relatively_prime_integers( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_relatively_prime_integers(cusp) == FALSE) return FALSE; return TRUE; } Boolean Dehn_coefficients_are_integers( Cusp *cusp) { return ( cusp->is_complete == TRUE || ( cusp->m == (double)(int)cusp->m && cusp->l == (double)(int)cusp->l ) ); } Boolean Dehn_coefficients_are_relatively_prime_integers( Cusp *cusp) { return ( cusp->is_complete == TRUE || ( cusp->m == (double)(int)cusp->m && cusp->l == (double)(int)cusp->l && gcd((long int)cusp->m, (long int)cusp->l) == 1 ) ); } Boolean all_cusps_are_complete( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == FALSE) return FALSE; return TRUE; } Boolean all_cusps_are_filled( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == TRUE) return FALSE; return TRUE; } snappea-3.0d3/SnapPeaKernel/code/direct_product.c0100444000175000017500000010025406742675501020061 0ustar babbab/* * direct_product.c * * This file contains the function * * Boolean is_group_direct_product(SymmetryGroup *the_group); * * which checks whether the given group is a nonabelian, nontrivial direct * product. If so, it sets the_group's is_direct_product field to TRUE, * sets factor[0] and factor[1] to point to the factors, and calls * recognize_group() for each factor. factor[0] and factor[1] may * themselves be nonabelian, nontrivial direct products, leading to a * tree structure for the factorization. * * If the_group is not a nonabelian, nontrivial direct product, then * is_group_direct_product() sets the_group's is_direct_product field to * FALSE, and factor[0] and factor[1] to NULL. * * is_group_direct_product() assumes all of the_group's fields except * is_direct_product and factor[] have already been set. * * * Overview of algorithm. * * The plan is to find all normal subgroups of the_group, then look * for a pair satisfying * * Theorem A. Let H and K be normal subgroups of a group G. * If H and K satisfy * * (1) The intersection of H and K contains only the identity. * * (2) For each h in H and k in K, hk = kh. * * (3) |G| = |H| |K|. * * then G is isomorphic to H x K. * * Proof. Define a map f:H x K -> G by sending (h, k) to hk. * * Hypothesis (2) implies that f is a homomorphism. * f( (h, k) (h', k') ) = f((hh', kk')) = hh'kk' = hkh'k' = * f((h, k)) f((h', k')). * * Hypothesis (1) implies that f is one-to-one. f((h, k)) = id * => hk = id => h = k^-1 => h (resp. k) is an element of both * H and K, so therefore must be the identity => (h, k) = (id, id). * * Hypothesis (3) implies that f is onto. |H x K| = |H| |K| = |G|, * so surjectivity follows from injectivity by the pidgeonhole * principle. * * Q.E.D. * * * Terminology. * * Throughout this file, WE CONSIDER ONLY NORMAL SUBGROUPS. For example, * when we talk about the subgroup generated by an element, we mean the * smallest normal subgroup containing the element. * * H < K means the subgroup H is contained in the subgroup K. * * [g1, g2, ... , gn] means the subgroup generated by the elements * {g1, g2, ... , gn}. * * We define the "rank" of a subgroup to be the smallest number of elements * required to generate it. (As a special case, the subgroup {id} has * rank zero.) I'm not sure whether H < K implies rank(H) <= rank(K), * so we make no assumptions on this matter. * * * Finding all normal subgroups. * * Step 0. The trivial subgroup {id} has rank 0. * * Step 1. Find all subgroups generated by single elements. This gives * all subgroups of rank <= 1. * * Step 2. Find all subgroups generated by the union of two rank 1 * subgroups. This gives all subgroups of rank <= 2. * * Step n. Find all subgroups generated by the union of a rank n subgroup and * a rank 1 subgroup. This gives all subgroups of rank <= n + 1. * * The algorithm terminates when the only subgroup of some rank n is * the group itself. * * Theorem B. In the above algorithm, a subgroup of rank n will appear at * step n, but not sooner. * * Proof. First note that no rank n subgroup can appear before step n, * because each subgroup appearing at step n is the smallest normal subgroup * containing the generators of the n subgroups of rank 1 used to create it. * To prove that every subgroup of rank n does indeed appear at step n, * we induct on n. The theorem is clearly true for n = 0 and n = 1, * so let H be a subgroup of rank n > 1. H is the smallest normal subgroup * generated by some n-element set {h1, h2, ... , hn}. In symbols, * H = [h1, h2, ... , hn]. Consider K = [h1, h2, ... , h(n-1)]. * K has rank exactly n-1 (clearly k has rank at most n-1, because it's * generated by n-1 elements, and if it were generated by fewer than * n-1 elements, say K = [k1, k2, ... , k(n-2)], then we'd have * H = [k1, k2, ... , k(n-2), hn], contradicting the assumption that * H has rank n). By induction, K appeared at step n-1. Therefore H will * appear at step n, as the smallest normal subgroup containing the * rank n-1 subgroup K and the rank 1 subgroup [hn]. * Note that the algorithm will not terminate while nontrivial * subgroups remain unaccounted for, because the existence of a nontrivial * subgroup of rank n implies the existence of a nontrivial subgroup * of rank n-1 (as shown in the parenthetical remark in the preceding * paragraph). Q.E.D. * * Comment. Even though subgroups of rank n first appear at step n, * they may appear again at subsequent steps. */ #include "kernel.h" typedef struct symmetry_subgroup { /* * A subgroup of a group of order n is represented as a Boolean array * "contains" of length n. The element contains[i] will be TRUE iff * the subgroup contain element i. */ Boolean *contains; /* * How many elements does the whole group contain? * In other words, what's the length of the contains[] array? */ int group_order; /* * How many elements does this subgroup contain? * In other words, how many of the elements in the contains[] * array are TRUE? */ int subgroup_order; /* * We'll keep subgroups on NULL-terminated linked lists. */ struct symmetry_subgroup *next; } SymmetrySubgroup; static Boolean group_is_abelian_or_polyhedral(SymmetryGroup *the_group); static SymmetrySubgroup **new_subgroup_array(int the_length); static void free_subgroup_array(SymmetrySubgroup **the_array, int the_length); static SymmetrySubgroup *new_symmetry_subgroup(int order_of_group); static void free_symmetry_subgroup(SymmetrySubgroup *the_subgroup); static void compute_rank_zero_subgroup(SymmetryGroup *the_group, SymmetrySubgroup **the_list); static void compute_rank_one_subgroups(SymmetryGroup *the_group, SymmetrySubgroup **the_list); static void compute_rank_n_plus_one_subgroups(SymmetryGroup *the_group, SymmetrySubgroup **subgroup_of_rank, int n); static void find_subgroup_generated(SymmetryGroup *the_group, SymmetrySubgroup *the_subset); static void add_conjugates(SymmetryGroup *the_group, SymmetrySubgroup *the_subset); static void add_products(SymmetryGroup *the_group, SymmetrySubgroup *the_subset); static Boolean subgroup_on_some_list(SymmetrySubgroup *the_subgroup, SymmetrySubgroup **subgroup_of_rank); static Boolean subgroup_on_list(SymmetrySubgroup *the_subgroup, SymmetrySubgroup *the_list); static Boolean same_subgroup(SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB); static Boolean is_subset(SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB); static SymmetrySubgroup *find_union(SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB); static void add_subgroup_to_list(SymmetrySubgroup *the_subgroup, SymmetrySubgroup **the_list); static void sort_by_order(int array_length, SymmetrySubgroup **subgroup_of_rank, SymmetrySubgroup **subgroup_of_order); static Boolean search_for_direct_product(SymmetryGroup *the_group, SymmetrySubgroup **subgroup_of_order); static Boolean theoremA(SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K); static Boolean condition1(SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K); static Boolean condition2(SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K); static Boolean condition3(SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K); static SymmetryGroup *subgroup_to_group(SymmetryGroup *the_whole_group, SymmetrySubgroup *the_subgroup); static void set_up_index_conversion(SymmetrySubgroup *the_subgroup, int **new_to_old_indices, int **old_to_new_indices); static void copy_symmetries(SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]); static void copy_one_symmetry(Symmetry *source, Symmetry **dest); static void copy_multiplication_table(SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]); static void copy_element_orders(SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]); static void copy_inverses(SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]); Boolean is_group_direct_product( SymmetryGroup *the_group) { SymmetrySubgroup **subgroup_of_rank, **subgroup_of_order; int n; Boolean success; /* * If the group is known to be abelian or polyhedral, * then it's not a nonabelian, nontrivial direct product. */ if (group_is_abelian_or_polyhedral(the_group) == TRUE) return FALSE; /* * During the first half of the algorithm, the SymmetrySubgroups * will be organized by rank. subgroup_of_rank[] will be an array * of pointers of length (the_group->order + 1). subgroup_of_rank[n] * will point to the first element in the NULL-terminated linked list * of SymmetrySubgroups of rank n. Obviously all but the first * few elements in the subgroup_of_rank[] array will be NULL, since * there won't be any subgroups of very high rank. * * During the second half of the algorithm, a similar system will * organize the subgroups by order (i.e. number of elements) on the * array subgroup_of_order[]. subgroup_of_order[n] will point to the * first element in the NULL-terminated linked list of SymmetrySubgroups * of order n. For most values of n there won't be any subgroups of * that order, so most elements of subgroup_of_order[] will be NULL. */ subgroup_of_rank = new_subgroup_array(the_group->order + 1); subgroup_of_order = new_subgroup_array(the_group->order + 1); /* * The present algorithm has no use for the rank 0 subgroup {id}, * but we compute it anyhow just in case this code is ever needed * for any other purpose. */ compute_rank_zero_subgroup(the_group, &subgroup_of_rank[0]); /* * Compute all rank 1 subgroups. */ compute_rank_one_subgroups(the_group, &subgroup_of_rank[1]); /* * Find all subgroups generated by the union of a rank n subgroup and a * rank 1 subgroup, and store them on the list of rank n+1 subgroups. * Keep going as long as we keep finding new subgroups. */ for (n = 1; subgroup_of_rank[n] != NULL; n++) compute_rank_n_plus_one_subgroups(the_group, subgroup_of_rank, n); /* * Sort the subgroups by their orders. We no longer care about * the ranks. */ sort_by_order(the_group->order + 1, subgroup_of_rank, subgroup_of_order); /* * Try to find a pair of subgroups satisfying Theorem A. */ success = search_for_direct_product(the_group, subgroup_of_order); free_subgroup_array(subgroup_of_rank, the_group->order + 1); free_subgroup_array(subgroup_of_order, the_group->order + 1); return success; } static Boolean group_is_abelian_or_polyhedral( SymmetryGroup *the_group) { if (the_group->is_abelian == TRUE || the_group->is_polyhedral == TRUE) { the_group->is_direct_product = FALSE; the_group->factor[0] = NULL; the_group->factor[1] = NULL; return TRUE; } else return FALSE; } static SymmetrySubgroup **new_subgroup_array( int the_length) { SymmetrySubgroup **the_array; int i; the_array = NEW_ARRAY(the_length, SymmetrySubgroup *); for (i = 0; i < the_length; i++) the_array[i] = NULL; return the_array; } static void free_subgroup_array( SymmetrySubgroup **the_array, int the_length) { int i; SymmetrySubgroup *dead_subgroup; for (i = 0; i < the_length; i++) while(the_array[i] != NULL) { dead_subgroup = the_array[i]; the_array[i] = the_array[i]->next; free_symmetry_subgroup(dead_subgroup); } my_free(the_array); } static SymmetrySubgroup *new_symmetry_subgroup( int order_of_group) { /* * Allocate a new SymmetrySubgroup and initialize it to the empty * subgroup. order_of_group is the order of the whole group, * not the subgroup. */ SymmetrySubgroup *new_subgroup; int i; new_subgroup = NEW_STRUCT(SymmetrySubgroup); new_subgroup->contains = NEW_ARRAY(order_of_group, Boolean); new_subgroup->group_order = order_of_group; new_subgroup->subgroup_order = 0; new_subgroup->next = NULL; for (i = 0; i < new_subgroup->group_order; i++) new_subgroup->contains[i] = FALSE; return new_subgroup; } static void free_symmetry_subgroup( SymmetrySubgroup *the_subgroup) { my_free(the_subgroup->contains); my_free(the_subgroup); } static void compute_rank_zero_subgroup( SymmetryGroup *the_group, SymmetrySubgroup **the_list) { /* * There's exactly one rank 0 subgroup, namely {id}. */ /* * First an error check. */ if (*the_list != NULL) uFatalError("compute_rank_zero_subgroup", "direct_product"); /* * Allocate the SymmetrySubgroup and initialize it * to the empty subgroup. */ (*the_list) = new_symmetry_subgroup(the_group->order); /* * Add the identity element. */ (*the_list)->contains[0] = TRUE; (*the_list)->subgroup_order = 1; (*the_list)->next = NULL; } static void compute_rank_one_subgroups( SymmetryGroup *the_group, SymmetrySubgroup **the_list) { SymmetrySubgroup *the_subgroup; int g; /* * First an error check. */ if (*the_list != NULL) uFatalError("compute_rank_one_subgroups", "direct_product"); /* * For each element of the_group we compute the smallest normal * subgroup containing it. If the subgroup is not already on the_list, * we add it. We do not consider the subgroup containing only the * identity, because we want only rank 1 subgroups, not rank 0. */ for (g = 1; g < the_group->order; g++) { /* * Allocate and initialize the_subgroup. */ the_subgroup = new_symmetry_subgroup(the_group->order); /* * Add the identity and the element g. */ the_subgroup->contains[0] = TRUE; the_subgroup->contains[g] = TRUE; the_subgroup->subgroup_order = 2; /* * Compute the smallest normal subgroup containing g. */ find_subgroup_generated(the_group, the_subgroup); /* * Add it to the_list if it's not already there. * Otherwise destroy it. */ if (subgroup_on_list(the_subgroup, *the_list) == FALSE) add_subgroup_to_list(the_subgroup, the_list); else free_symmetry_subgroup(the_subgroup); } } static void compute_rank_n_plus_one_subgroups( SymmetryGroup *the_group, SymmetrySubgroup **subgroup_of_rank, int n) { SymmetrySubgroup *rank_n_subgroup, *rank_one_subgroup, *the_union; /* * Consider all rank n subgroups. */ for ( rank_n_subgroup = subgroup_of_rank[n]; rank_n_subgroup != NULL; rank_n_subgroup = rank_n_subgroup->next) /* * Consider all rank one subgroups. */ for ( rank_one_subgroup = subgroup_of_rank[1]; rank_one_subgroup != NULL; rank_one_subgroup = rank_one_subgroup->next) { /* * If one of {rank_n_subgroup, rank_one_subgroup} is * contained in the other, move on. */ if (is_subset(rank_one_subgroup, rank_n_subgroup) == TRUE || is_subset(rank_n_subgroup, rank_one_subgroup) == TRUE) continue; /* * Find the union of the rank_n_subgroup and the * rank_one_subgroup . . . */ the_union = find_union(rank_n_subgroup, rank_one_subgroup); /* * . . . and then find the smallest normal subgroup * which contains it. */ find_subgroup_generated(the_group, the_union); /* * Add it to the list of rank (n+1) subgroups iff it hasn't * already been computed as a subgroup of rank (n+1) or less. * Otherwise destroy it. */ if (subgroup_on_some_list(the_union, subgroup_of_rank) == FALSE) add_subgroup_to_list(the_union, &subgroup_of_rank[n+1]); else free_symmetry_subgroup(the_union); } } static void find_subgroup_generated( SymmetryGroup *the_group, SymmetrySubgroup *the_subset) { /* * This routine takes a subset of a group and finds the smallest normal * subgroup containing it. The result is computed "in place", that is, * the original subset is replaced by the normal subgroup it generates. * * * We want to insure that the_subset is closed under multiplication * (so it's a subgroup) and closed under conjugation (so it's normal). * * Lemma. It suffices to first take the closure under conjugation, * and then take the closure under multiplication. In other words, * taking the closure under multiplication won't destroy the closure * under conjugacy. * * Proof. After first taking the closure of the_subset under conjugation, * we get a set of elements of the form {caC, dbD, ...}. Taking the * closure under multiplication gives all possible products of these * elements: a typical element has the form (caC)...(dbD). If we * conjugate such an element by any other element e in the group we * get something of the form e((caC)...(dbD))E = (ecaCE)...(edbDE) * = (ec)a(CE)...(ed)b(DE) which is again a product of conjugates of * elements of the original subset. Q.E.D. */ add_conjugates(the_group, the_subset); add_products(the_group, the_subset); } static void add_conjugates( SymmetryGroup *the_group, SymmetrySubgroup *the_subset) { int g, h, ghg; /* * Add all conjugates of all elements of the_subset. * This algorithm is a little simpler than the one in add_products(). * Here we needn't worry about conjugates of conjugates * (because b(ax(a^-1))(b^-1) = (ba)x(ba)^-1), whereas in * add_products() we did need to worry about products of products. */ /* * Begin with a quick error check. */ if (the_group->order != the_subset->group_order) uFatalError("add_conjugates", "direct_product"); /* * For each element h in the_subset . . . */ for (h = 0; h < the_group->order; h++) { if (the_subset->contains[h] == FALSE) continue; /* * . . . and each element g in the whole group . . . */ for (g = 0; g < the_group->order; g++) { /* * . . . compute gh(g^-1). */ ghg = the_group->product [the_group->product[g][h]] [the_group->inverse[g]]; /* * If gh(g^-1) isn't already in the subset, add it. */ if (the_subset->contains[ghg] == FALSE) { the_subset->contains[ghg] = TRUE; the_subset->subgroup_order++; } } } } static void add_products( SymmetryGroup *the_group, SymmetrySubgroup *the_subset) { int *the_queue, the_product, i, j, count; /* * We want to insure that the final subset contains the products * of all pairs of its elements, not merely pairs of elements of * the original subset. Furthermore, don't want to multiply any * particular pair of elements more than one. The way to do this * is to put the elements of the_subset onto a queue. We'll work * our way along the queue, checking the product of each successive * element with all preceding elements (as well as with itself). * As new elements are found, they are added to the end of the queue. */ /* * Begin with a quick error check. */ if (the_group->order != the_subset->group_order) uFatalError("add_products", "direct_product"); /* * Allocate space for the_queue. */ the_queue = NEW_ARRAY(the_group->order, int); /* * Copy the original elements of the_subset onto the_queue. */ for (i = 0, count = 0; i < the_group->order; i++) if (the_subset->contains[i] == TRUE) the_queue[count++] = i; /* * Make sure we found the number of elements we expected. */ if (count != the_subset->subgroup_order) uFatalError("add_products", "direct_product"); /* * For each element on the_queue, compute the product with all * preceding elements (and with the element itself), and see whether * any new elements are generated. * * Technical note: the_subset->subgroup_order may increase as we * go through the loop, but will never exceed the_subset->group_order. */ for (i = 0; i < the_subset->subgroup_order; i++) for (j = 0; j <= i; j++) { the_product = the_group->product[the_queue[i]][the_queue[j]]; if (the_subset->contains[the_product] == FALSE) { the_subset->contains[the_product] = TRUE; the_queue[the_subset->subgroup_order++] = the_product; } the_product = the_group->product[the_queue[j]][the_queue[i]]; if (the_subset->contains[the_product] == FALSE) { the_subset->contains[the_product] = TRUE; the_queue[the_subset->subgroup_order++] = the_product; } } /* * Yet another error check. * The subgroup_order should not exceed the group order, and * the subgroup_order should divide the group_order. */ if (the_subset->subgroup_order > the_subset->group_order || the_subset->group_order % the_subset->subgroup_order != 0) uFatalError("add_products", "direct_product"); /* * Free the_queue. */ my_free(the_queue); } static Boolean subgroup_on_some_list( SymmetrySubgroup *the_subgroup, SymmetrySubgroup **subgroup_of_rank) { int i; for (i = 0; i <= the_subgroup->group_order; i++) { /* * If there are no rank i subgroups, there won't be any of * rank greater than i either. */ if (subgroup_of_rank[i] == NULL) return FALSE; /* * Check whether the_subgroup is already on the rank i list. */ if (subgroup_on_list(the_subgroup, subgroup_of_rank[i]) == TRUE) return TRUE; } /* * We should always return from within the above loop, * because there can't possibly be a subgroup whose rank equals * the rank of the group. (The identity doesn't count as a generator, * so in a group of order n it's a priori impossible to have more * than (n-1) generators.) */ uFatalError("subgroup_on_some_list", "direct_product"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return TRUE; } static Boolean subgroup_on_list( SymmetrySubgroup *the_subgroup, SymmetrySubgroup *the_list) { SymmetrySubgroup *a_subgroup; for ( a_subgroup = the_list; a_subgroup != NULL; a_subgroup = a_subgroup->next) if (same_subgroup(the_subgroup, a_subgroup) == TRUE) return TRUE; return FALSE; } static Boolean same_subgroup( SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB) { int i; if (subgroupA->group_order != subgroupB->group_order) uFatalError("same_subgroup", "direct_product"); for (i = 0; i < subgroupA->group_order; i++) if (subgroupA->contains[i] != subgroupB->contains[i]) return FALSE; return TRUE; } static Boolean is_subset( SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB) { int i; if (subgroupA->group_order != subgroupB->group_order) uFatalError("is_subset", "direct_product"); for (i = 0; i < subgroupA->group_order; i++) if (subgroupA->contains[i] == TRUE && subgroupB->contains[i] == FALSE) return FALSE; return TRUE; } static SymmetrySubgroup *find_union( SymmetrySubgroup *subgroupA, SymmetrySubgroup *subgroupB) { SymmetrySubgroup *the_union; int i; if (subgroupA->group_order != subgroupB->group_order) uFatalError("find_union", "direct_product"); the_union = new_symmetry_subgroup(subgroupA->group_order); for (i = 0; i < the_union->group_order; i++) { the_union->contains[i] = subgroupA->contains[i] || subgroupB->contains[i]; if (the_union->contains[i]) the_union->subgroup_order++; } return the_union; } static void add_subgroup_to_list( SymmetrySubgroup *the_subgroup, SymmetrySubgroup **the_list) { the_subgroup->next = *the_list; *the_list = the_subgroup; } static void sort_by_order( int array_length, SymmetrySubgroup **subgroup_of_rank, SymmetrySubgroup **subgroup_of_order) { int i; SymmetrySubgroup *the_subgroup; for (i = 0; i < array_length; i++) while (subgroup_of_rank[i] != NULL) { the_subgroup = subgroup_of_rank[i]; subgroup_of_rank[i] = subgroup_of_rank[i]->next; add_subgroup_to_list( the_subgroup, &subgroup_of_order[the_subgroup->subgroup_order]); } } static Boolean search_for_direct_product( SymmetryGroup *the_group, SymmetrySubgroup **subgroup_of_order) { /* * Look for subgroups H and K satisfying Theorem A * of the documentation at the top of this file. * * Let n = |H| and m = |K|. Without loss of generality, assume n <= m. * * We need consider only those values of n satisfying n^2 <= |G|. */ int n, m; SymmetrySubgroup *subgroup_H, *subgroup_K; for (n = 2; n*n <= the_group->order; n++) { /* * By LaGrange's Theorem, n must divide the order of the group. */ if (the_group->order % n != 0) continue; m = the_group->order / n; /* * Consider all normal subgroups H . . . */ for ( subgroup_H = subgroup_of_order[n]; subgroup_H != NULL; subgroup_H = subgroup_H->next) /* * . . . and K. */ for ( subgroup_K = subgroup_of_order[m]; subgroup_K != NULL; subgroup_K = subgroup_K->next) if (theoremA(the_group, subgroup_H, subgroup_K) == TRUE) { the_group->is_direct_product = TRUE; the_group->factor[0] = subgroup_to_group(the_group, subgroup_H); the_group->factor[1] = subgroup_to_group(the_group, subgroup_K); return TRUE; } } /* * No luck. */ the_group->is_direct_product = FALSE; the_group->factor[0] = NULL; the_group->factor[1] = NULL; return FALSE; } static Boolean theoremA( SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K) { if (subgroup_H->group_order != subgroup_K->group_order) uFatalError("theoremA", "direct_product"); return( condition1(the_group, subgroup_H, subgroup_K) && condition2(the_group, subgroup_H, subgroup_K) && condition3(the_group, subgroup_H, subgroup_K)); } static Boolean condition1( SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K) { /* * Does the intersection of H and K contain only the identity? */ int g; /* * First make sure both H and K do in fact contain the identity. */ if (subgroup_H->contains[0] == FALSE || subgroup_K->contains[0] == FALSE) uFatalError("condition1", "direct_product"); /* * Now check that they have no other elements in common. */ for (g = 1; g < subgroup_H->group_order; g++) if (subgroup_H->contains[g] == TRUE && subgroup_K->contains[g] == TRUE) return FALSE; return TRUE; } static Boolean condition2( SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K) { /* * Check that for each h in H and k in K, hk = kh. */ int h, k; for (h = 0; h < subgroup_H->group_order; h++) { if (subgroup_H->contains[h] == FALSE) continue; for (k = 0; k < subgroup_K->group_order; k++) { if (subgroup_K->contains[k] == FALSE) continue; if (the_group->product[h][k] != the_group->product[k][h]) return FALSE; } } return TRUE; } static Boolean condition3( SymmetryGroup *the_group, SymmetrySubgroup *subgroup_H, SymmetrySubgroup *subgroup_K) { /* * Condition 3 should already have been implicitly verified * in search_for_direct_product(). */ if (subgroup_H->subgroup_order * subgroup_K->subgroup_order != the_group->order) uFatalError("condition3", "direct_product"); return TRUE; } static SymmetryGroup *subgroup_to_group( SymmetryGroup *the_whole_group, SymmetrySubgroup *the_subgroup) { SymmetryGroup *the_group; int *new_to_old_indices, *old_to_new_indices; /* * subgroup_to_group() converts a SymmetrySubgroup * to a free-standing SymmetryGroup. */ /* * Allocate the SymmetryGroup. */ the_group = NEW_STRUCT(SymmetryGroup); /* * Set the_group's order. */ the_group->order = the_subgroup->subgroup_order; /* * Allocate temporary arrays which will give the old element indices * (i.e. in the_whole_group) in terms of the new indices (i.e. in * the_group), and vice versa. */ set_up_index_conversion( the_subgroup, &new_to_old_indices, &old_to_new_indices); /* * Copy the appropriate Symmetries from the_whole_group to the_group */ copy_symmetries( the_whole_group, the_group, new_to_old_indices, old_to_new_indices); /* * Copy the appropriate element in the multiplication table. */ copy_multiplication_table( the_whole_group, the_group, new_to_old_indices, old_to_new_indices); /* * Copy the appropriate element orders. */ copy_element_orders(the_whole_group, the_group, new_to_old_indices, old_to_new_indices); /* * Copy the appropriate inverses. */ copy_inverses( the_whole_group, the_group, new_to_old_indices, old_to_new_indices); /* * Attempt to recognize the_group. */ recognize_group(the_group); /* * Free the temporary arrays. */ my_free(new_to_old_indices); my_free(old_to_new_indices); return the_group; } static void set_up_index_conversion( SymmetrySubgroup *the_subgroup, int **new_to_old_indices, int **old_to_new_indices) { int old_index, new_index; *new_to_old_indices = NEW_ARRAY(the_subgroup->subgroup_order, int); *old_to_new_indices = NEW_ARRAY(the_subgroup->group_order, int); for ( old_index = 0, new_index = 0; old_index < the_subgroup->group_order; old_index++) if (the_subgroup->contains[old_index] == TRUE) { (*new_to_old_indices)[new_index] = old_index; (*old_to_new_indices)[old_index] = new_index; new_index++; } if (new_index != the_subgroup->subgroup_order) uFatalError("set_up_index_conversion", "direct_product"); } static void copy_symmetries( SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]) { int i; /* * 96/11/30 Allow for the possibility that the_whole_group * has no SymmetryList. */ if (the_whole_group->symmetry_list == NULL) { the_subgroup->symmetry_list = NULL; return; } the_subgroup->symmetry_list = NEW_STRUCT(SymmetryList); the_subgroup->symmetry_list->num_isometries = the_subgroup->order; the_subgroup->symmetry_list->isometry = NEW_ARRAY( the_subgroup->order, Isometry *); for (i = 0; i < the_subgroup->order; i++) copy_one_symmetry( the_whole_group->symmetry_list->isometry[new_to_old_indices[i]], &the_subgroup->symmetry_list->isometry[i]); } static void copy_one_symmetry( Symmetry *source, Symmetry **dest) { int i, j, k; *dest = NEW_STRUCT(Isometry); (*dest)->num_tetrahedra = source->num_tetrahedra; (*dest)->num_cusps = source->num_cusps; (*dest)->tet_image = NEW_ARRAY(source->num_tetrahedra, int); (*dest)->tet_map = NEW_ARRAY(source->num_tetrahedra, Permutation); for (i = 0; i < source->num_tetrahedra; i++) { (*dest)->tet_image[i] = source->tet_image[i]; (*dest)->tet_map[i] = source->tet_map[i]; } (*dest)->cusp_image = NEW_ARRAY(source->num_cusps, int); (*dest)->cusp_map = NEW_ARRAY(source->num_cusps, MatrixInt22); for (i = 0; i < source->num_cusps; i++) { (*dest)->cusp_image[i] = source->cusp_image[i]; for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) (*dest)->cusp_map[i][j][k] = source->cusp_map[i][j][k]; } (*dest)->extends_to_link = source->extends_to_link; (*dest)->next = NULL; } static void copy_multiplication_table( SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]) { int i, j; /* * Allocate space for the multiplication table. */ the_subgroup->product = NEW_ARRAY(the_subgroup->order, int *); for (i = 0; i < the_subgroup->order; i++) the_subgroup->product[i] = NEW_ARRAY(the_subgroup->order, int); /* * Fill in the entries. */ for (i = 0; i < the_subgroup->order; i++) for (j = 0; j < the_subgroup->order; j++) the_subgroup->product[i][j] = old_to_new_indices[ the_whole_group->product[new_to_old_indices[i]] [new_to_old_indices[j]]]; } static void copy_element_orders( SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]) { int i; /* * Allocate the array which will hold the orders of the elements. */ the_subgroup->order_of_element = NEW_ARRAY(the_subgroup->order, int); /* * Copy in the elements. */ for (i = 0; i < the_subgroup->order; i++) the_subgroup->order_of_element[i] = the_whole_group-> order_of_element[new_to_old_indices[i]]; } static void copy_inverses( SymmetryGroup *the_whole_group, SymmetryGroup *the_subgroup, int new_to_old_indices[], int old_to_new_indices[]) { int i; /* * Allocate the array which will hold the inverses of the elements. */ the_subgroup->inverse = NEW_ARRAY(the_subgroup->order, int); /* * Copy in the elements. */ for (i = 0; i < the_subgroup->order; i++) the_subgroup->inverse[i] = old_to_new_indices[ the_whole_group->inverse[new_to_old_indices[i]]]; } snappea-3.0d3/SnapPeaKernel/code/Dirichlet.c0100444000175000017500000005622307077144154016761 0ustar babbab/* * Dirichlet.c * * This file provides the functions * * WEPolyhedron *Dirichlet( Triangulation *manifold, * double vertex_epsilon, * Boolean centroid_at_origin, * DirichletInteractivity interactivity, * Boolean maximize_injectivity_radius); * * WEPolyhedron *Dirichlet_from_generators( * O31Matrix generators[], * int num_generators, * double vertex_epsilon, * DirichletInteractivity interactivity, * Boolean maximize_injectivity_radius); * * void change_basepoint( * WEPolyhedron **polyhedron, * Triangulation *manifold, * O31Matrix *generators, * int num_generators, * double displacement[3], * double vertex_epsilon, * Boolean centroid_at_origin, * DirichletInteractivity interactivity, * Boolean maximize_injectivity_radius); * * void free_Dirichlet_domain(WEPolyhedron *polyhedron); * * Dirichlet() computes a Dirichlet domain for the given manifold. * The Dirichlet domain will be centered at a local maximum of the * injectivity radius function, so as to bring out the manifold's * symmetry. The Dirichlet domain will be for the current Dehn * filled solution; the Dehn filling coefficients of filled cusps * must be integers, but they need not be relatively prime. That is, * the code works for orbifolds as well as manifolds. Throughout * this documentation when I say "manifold" I really mean "manifold * or orbifold". If the Dehn filling coefficients are all integers, * Dirichlet() computes the Dirichlet domain in a winged edge data * structure (cf. winged_edge.h) and returns a pointer to it. If the * Dehn filling coefficients aren't all integers, or if the algorithm * fails, Dirichlet() returns NULL. The documentation at the top of * the file Dirichlet_construction.c explains why any Dirichlet domain * algorithm must fail for sufficiently difficult manifolds, and how an * appropriate choice of vertex_epsilon minimizes the chances of failure. * If DirichletInteractivity == Dirichlet_interactive, the algorithm * queries the user for how to proceed when the basepoint happens to be * at a critical point; otherwise it either computes the Dirichlet * domain based at that point or moves on, according to whether * DirichletInteractivity is Dirichlet_stop_here or Dirichlet_keep_going. * * Dirichlet_from_generators() computes a Dirichlet domain directly * from a set of generators. Dirichlet() (see code below) does nothing * but compute a set of generators for the manifold and pass them to * Dirichlet_from_generators(). Dirichlet_from_generators() is externally * available so the UI can compute a Dirichlet domain from an explicit list * of matrix generators. Such an approach is essential for orbifolds whose * singular sets are more complicated than just a collection of circles. * * change_basepoint() reads the face pairing matrices from the polyhedron * (if *polyhedron != NULL), shifts the basepoint by the given displacement, * lets the basepoint move to a local maximum of the injectivity radius * function, and recomputes the Dirichlet domain. * If *polyhedron is NULL, it computes the Dirichlet domain directly from * the given manifold or generators, but with the given displacement of * the initial basepoint. In either case, a pointer to the resulting * Dirichlet domain (or NULL if an error occurs as described in Dirichlet() * above) is written to *polyhedron. * * free_Dirichlet_domain() frees the storage occupied by a WEPolyhedron. */ #include "kernel.h" /* * The Dirichlet domain code is divided among several files. The header * file Dirichlet.h explains the organization of the three files, and * provides the common definitions and declarations. */ #include "Dirichlet.h" /* * simplify_generators() considers one MatrixPair to be simpler than * another if its height is at least SIMPLIFY_EPSILON less. */ #define SIMPLIFY_EPSILON 1e-2 /* * Two vectors are considered linearly dependent iff the length of their * cross product is less than LENGTH_EPSILON. LENGTH_EPSILON can be fairly * large, because (1) the vectors involved are normals to faces and are * unlikely to be almost but not quite linearly dependent, and (2) if we * don't like one face normal, we'll move on to the next one. */ #define LENGTH_EPSILON 1e-2 /* * An O(3,1) matrix is considered to fix the basepoint (1, 0, 0, 0) iff its * (0,0)th entry is less than 1.0 + FIXED_BASEPOINT_EPSILON. When choosing * FIXED_BASEPOINT_EPSILON, recall that a point d units from the basepoint * lies at a height cosh(d), which for small d is approximately 1 + (1/2)d^2. * So, for example, to detect points within a distance of about 1e-3 of * the basepoint, you should set FIXED_BASEPOINT_EPSILON to 1e-6. */ #define FIXED_BASEPOINT_EPSILON 1e-6 static void array_to_matrix_pair_list(O31Matrix generators[], int num_generators, MatrixPairList *gen_list); static Boolean is_matrix_on_list(O31Matrix m, MatrixPairList *gen_list); static void insert_matrix_on_list(O31Matrix m, MatrixPairList *gen_list); static void simplify_generators(MatrixPairList *gen_list); static Boolean generator_fixes_basepoint(MatrixPairList *gen_list); static double product_height(O31Matrix a, O31Matrix b); static void generators_from_polyhedron(WEPolyhedron *polyhedron, O31Matrix **generators, int *num_generators); WEPolyhedron *Dirichlet( Triangulation *manifold, double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius) { double null_displacement[3] = {0.0, 0.0, 0.0}; return Dirichlet_with_displacement( manifold, null_displacement, vertex_epsilon, centroid_at_origin, interactivity, maximize_injectivity_radius); } WEPolyhedron *Dirichlet_from_generators( O31Matrix generators[], int num_generators, double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius) { double null_displacement[3] = {0.0, 0.0, 0.0}; return Dirichlet_from_generators_with_displacement( generators, num_generators, null_displacement, vertex_epsilon, interactivity, maximize_injectivity_radius); } WEPolyhedron *Dirichlet_with_displacement( Triangulation *manifold, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius) { MoebiusTransformation *Moebius_generators; O31Matrix *o31_generators; WEPolyhedron *polyhedron; /* * Make sure we have a hyperbolic manifold. */ if (get_filled_solution_type(manifold) != geometric_solution && get_filled_solution_type(manifold) != nongeometric_solution) return NULL; /* * Make sure all the Dehn filling coefficients are integers. * This will ensure that we are working with a manifold or * orbifold, and the group is discrete. */ if ( ! all_Dehn_coefficients_are_integers(manifold) ) return NULL; /* * Compute a set of generators, and convert them from * MoebiusTransformations to O31Matrices. */ choose_generators(manifold, FALSE, FALSE); /* counts the generators so we can allocate the arrays */ Moebius_generators = NEW_ARRAY(manifold->num_generators, MoebiusTransformation); o31_generators = NEW_ARRAY(manifold->num_generators, O31Matrix); matrix_generators(manifold, Moebius_generators, centroid_at_origin); Moebius_array_to_O31_array(Moebius_generators, o31_generators, manifold->num_generators); /* * Compute the Dirichlet domain. */ polyhedron = Dirichlet_from_generators_with_displacement( o31_generators, manifold->num_generators, displacement, vertex_epsilon, interactivity, maximize_injectivity_radius); /* * Free the generators. */ my_free(Moebius_generators); my_free(o31_generators); /* * Return a pointer to the Dirichlet domain. */ return polyhedron; } WEPolyhedron *Dirichlet_from_generators_with_displacement( O31Matrix generators[], int num_generators, double displacement[3], double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius) { MatrixPairList gen_list; WEPolyhedron *polyhedron; Boolean basepoint_moved; double small_displacement[3] = {0.01734, 0.02035, 0.00721}; /* * Convert the array of generators to a MatrixPairList. */ array_to_matrix_pair_list(generators, num_generators, &gen_list); /* * Roundoff error tends to accumulate throughout this algorithm. * In general we can't eliminate roundoff error, but in many of * the nicest examples (e.g. those coming from triangulations with * 60-60-60 or 45-45-90 ideal tetrahedra) the matrix entries are * quarter integer multiples of 1, sqrt(2), and sqrt(3). In these * cases, if we can recognize a matrix entry to be a nice number to * fairly good precision, we can set it equal to that number to full * precision. If we do so after each matrix multiplication, the * roundoff error will stay under control. Please see * Dirichlet_precision.c for details. */ precise_generators(&gen_list); /* * Displace the basepoint as required. */ conjugate_matrices(&gen_list, displacement); /* * There is a danger that when initial_polyhedron() in * Dirichlet_construction.c slices its cube with the elements of * gen_list, the resulting set of face->group_elements will not suffice * to generate the full group. So we simplify the generators ahead of * time to try to avoid this problem. Please see simplify_generators() * for more details. */ simplify_generators(&gen_list); /* * If the singular set of an orbifold passes through the initial * basepoint, we won't be able to compute the Dirichlet domain. * So if any element of gen_list fixes the basepoint, we must give * the basepoint a small arbitrary displacement. Thereafter * the algorithm for maximizing the injectivity radius will * automatically keep the basepoint away from the singular set. * (Note that this approach is not foolproof. It's possible -- but * I hope unlikely -- that some product of the generators will fix * the basepoint even though no generator alone does. If this happens, * compute_Dirichlet_domain() will fail. My hope is that the * preceding call to simplify_generators() will find any elements * fixing the basepoint, if they aren't already explicity included * in the gen_list.) */ if (generator_fixes_basepoint(&gen_list) == TRUE) conjugate_matrices(&gen_list, small_displacement); while (TRUE) { /* * Compute the Dirichlet domain relative to the current basepoint. * compute_Dirichlet_domain() modifies gen_list to correspond * to the face pairings of the Dirichlet domain. The generators * are giving in order of increasing image height (see * Dirichlet_basepoint.c for the definition of "image height"). */ polyhedron = compute_Dirichlet_domain(&gen_list, vertex_epsilon); /* * If we ran into problems with loss of numerical accuracy, * return NULL. */ if (polyhedron == NULL) { free_matrix_pairs(&gen_list); return NULL; } /* * If necessary, move the basepoint to a local maximum of * the injectivity radius function. Set the flag * basepoint_moved to indicate whether a change of basepoint * was both requested and necessary. */ if (maximize_injectivity_radius == TRUE) /* Move the basepoint to a local maximum */ /* of the injectivity radius function. */ maximize_the_injectivity_radius(&gen_list, &basepoint_moved, interactivity); else /* Leave the basepoint where it is. */ basepoint_moved = FALSE; /* * If the basepoint was already at a local maximum of the * injectivity radius, clean up and go home. */ if (basepoint_moved == FALSE) { free_matrix_pairs(&gen_list); if (Dirichlet_bells_and_whistles(polyhedron) == func_OK) return polyhedron; else { free_Dirichlet_domain(polyhedron); return NULL; } } /* * We're not at a local maximum, so discard the Dirichlet domain * and repeat the loop. */ free_Dirichlet_domain(polyhedron); } /* * The program will never reach this point. */ } static void array_to_matrix_pair_list( O31Matrix generators[], int num_generators, MatrixPairList *gen_list) { int i; /* * Initialize the list. */ gen_list->begin.prev = NULL; gen_list->begin.next = &gen_list->end; gen_list->end .prev = &gen_list->begin; gen_list->end .next = NULL; /* * We make no assumption about whether the array "generators" * includes the identity, but we do want to insure that the * gen_list does, so we insert the identity matrix now. */ insert_matrix_on_list(O31_identity, gen_list); /* * Add the matrices from the array "generators" to the * MatrixPairList. We make no assumptions about whether * inverses are or are not present in the array "generators". */ for (i = 0; i < num_generators; i++) if (is_matrix_on_list(generators[i], gen_list) == FALSE) insert_matrix_on_list(generators[i], gen_list); } static Boolean is_matrix_on_list( O31Matrix m, MatrixPairList *gen_list) { MatrixPair *matrix_pair; int i; for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) for (i = 0; i < 2; i++) if (o31_equal(m, matrix_pair->m[i], MATRIX_EPSILON)) return TRUE; return FALSE; } static void insert_matrix_on_list( O31Matrix m, MatrixPairList *gen_list) { O31Matrix m_inverse; MatrixPair *new_matrix_pair, *mp; o31_invert(m, m_inverse); new_matrix_pair = NEW_STRUCT(MatrixPair); o31_copy(new_matrix_pair->m[0], m); o31_copy(new_matrix_pair->m[1], m_inverse); new_matrix_pair->height = m[0][0]; /* * Find the MatrixPair mp immediately following the point where * we want to insert the new_matrix_pair into the gen_list. */ mp = gen_list->begin.next; while (mp != &gen_list->end && mp->height < new_matrix_pair->height) mp = mp->next; INSERT_BEFORE(new_matrix_pair, mp); } void free_matrix_pairs( MatrixPairList *gen_list) { MatrixPair *dead_node; while (gen_list->begin.next != &gen_list->end) { dead_node = gen_list->begin.next; REMOVE_NODE(dead_node); my_free(dead_node); } } void free_Dirichlet_domain( WEPolyhedron *polyhedron) { WEVertex *dead_vertex; WEEdge *dead_edge; WEFace *dead_face; WEVertexClass *dead_vertex_class; WEEdgeClass *dead_edge_class; WEFaceClass *dead_face_class; if (polyhedron == NULL) uFatalError("free_Dirichlet_domain", "Dirichlet"); while (polyhedron->vertex_list_begin.next != &polyhedron->vertex_list_end) { dead_vertex = polyhedron->vertex_list_begin.next; REMOVE_NODE(dead_vertex); my_free(dead_vertex); } while (polyhedron->edge_list_begin.next != &polyhedron->edge_list_end) { dead_edge = polyhedron->edge_list_begin.next; REMOVE_NODE(dead_edge); my_free(dead_edge); } while (polyhedron->face_list_begin.next != &polyhedron->face_list_end) { dead_face = polyhedron->face_list_begin.next; REMOVE_NODE(dead_face); if (dead_face->group_element != NULL) my_free(dead_face->group_element); my_free(dead_face); } while (polyhedron->vertex_class_begin.next != &polyhedron->vertex_class_end) { dead_vertex_class = polyhedron->vertex_class_begin.next; REMOVE_NODE(dead_vertex_class); my_free(dead_vertex_class); } while (polyhedron->edge_class_begin.next != &polyhedron->edge_class_end) { dead_edge_class = polyhedron->edge_class_begin.next; REMOVE_NODE(dead_edge_class); my_free(dead_edge_class); } while (polyhedron->face_class_begin.next != &polyhedron->face_class_end) { dead_face_class = polyhedron->face_class_begin.next; REMOVE_NODE(dead_face_class); my_free(dead_face_class); } my_free(polyhedron); } static void simplify_generators( MatrixPairList *gen_list) { /* * There is a danger that when we slice the cube with elements of * gen_list, the resulting set of face->group_elements will not suffice * to generate the full group. To see this problem more clearly, forget * hyperbolic manifolds for a moment and consider the canonical Z + Z * action on the Euclidean plane. The final Dirichlet domain should be * the square Max(|x|, |y|) <= 1/2. Say we propose to compute it by * starting with the square Max(|x|, |y|) <= 3 and slicing it with the * halfplanes defined by the generators alpha: (x,y) -> (x+2, y+1) and * beta: (x,y) -> (x+5, x+3). Once we've sliced the cube by alpha, * there'll be nothing left for beta to slice off, and beta will be * lost. To avoid this problem, we simplify our initial set of * generators. Whenever the product alpha*beta of two generators * moves the basepoint less than one of the factors (alpha or beta), * we replace that factor with the product. The procedure is similar * in spirit to simplifying the above generators of Z + Z by repeatedly * subtracting one from the other: {(2,1), (5,3)} -> {(2,1), (1,1)} * -> {(1,0), (1,1)} -> {(1,0), (0,1)}. * * Note, by the way, that the hyperbolic case is even worse than the * Euclidean case. In the Euclidean case, the halfplane corresponding * to one primitive generator cannot be a subset of the halfplane * corresponding to another, but in the hyperbolic case it can. */ MatrixPair *aA, *bB, *best_aA; O31Matrix *best_aA_factor, *best_bB_factor; double max_improvement, improvement; int i, j; /* * Definition: We'll say that one MatrixPair is "greater than" * another iff the value of its height field is greater than that * of the other. */ /* * Roughly speaking, the idea is to look for MatrixPairs {a, A} and * {b, B} whose product is less than {a, A}, and replace {a, A} with * the product. (Technically speaking, there are four different ways * to form the product, as explained in the code below.) Unfortunately, * the roundoff error in the product is roughly the sum of the * roundoff errors in {a, A} and {b, B}, so we want to minimize the * number of matrix multiplications we do. For this reason we look * for the {a, A} and {b, B} whose product is less than {a, A} by the * greatest amount possible (this will be the max_improvement * variable below). * * We keep replacing generators by products until no further * improvement is possible. Whenever the identity matrix occurs, * we remove it. */ /* * We'll break out of the while() loop when no further * progress is possible. */ while (TRUE) { /* * Find the MatrixPairs {a, A} and {b, B} which offer the * greatest possible height decrease. */ max_improvement = 0.0; for (aA = gen_list->begin.next; aA != &gen_list->end; aA = aA->next) for (bB = gen_list->begin.next; bB != &gen_list->end; bB = bB->next) { /* * We certainly don't want to replace a MatrixPair * with its square, so skip the case {a, A} == {b, B}. */ if (aA == bB) continue; /* * We want aA to be the larger of the two. If bB is larger, * skip it, knowing that {a, A} and {b, B} will eventually * show up in the opposite order, if they haven't already. * (If they happen to be exactly equal, we consider them. * Better to consider a pair twice than not at all!) */ if (aA->height < bB->height) continue; /* * There are four possible products to consider: * {ab, BA}, {aB, bA}, {Ab, Ba} and {AB, ba}. * For computational efficiency we check the height of each * possibility without doing the full matrix multiplication. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { improvement = aA->height - product_height(aA->m[i], bB->m[j]); if (improvement > max_improvement) { max_improvement = improvement; best_aA = aA; // Cater to a DEC compiler error that chokes on &(array)[i] // best_aA_factor = &aA->m[i]; // best_bB_factor = &bB->m[j]; best_aA_factor = aA->m + i; best_bB_factor = bB->m + j; } } } /* * If no improvement is possible, we're done. */ if (max_improvement < SIMPLIFY_EPSILON) break; /* * Replace the contents of best_aA with the appropriate product. */ precise_o31_product(*best_aA_factor, *best_bB_factor, best_aA->m[0]); o31_invert(best_aA->m[0], best_aA->m[1]); best_aA->height = best_aA->m[0][0][0]; /* * If best_aA is the identity, remove it. */ if (o31_equal(best_aA->m[0], O31_identity, MATRIX_EPSILON) == TRUE) { REMOVE_NODE(best_aA); my_free(best_aA); } } } static Boolean generator_fixes_basepoint( MatrixPairList *gen_list) { MatrixPair *matrix_pair; for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) if (matrix_pair->m[0][0][0] < 1.0 + FIXED_BASEPOINT_EPSILON) if (o31_equal(matrix_pair->m[0], O31_identity, MATRIX_EPSILON) == FALSE) return TRUE; return FALSE; } static double product_height( O31Matrix a, O31Matrix b) { /* * We don't need the whole product of a and b, just the [0][0] entry. */ double height; int i; height = 0.0; for (i = 0; i < 4; i++) height += a[0][i] * b[i][0]; return height; } void change_basepoint( WEPolyhedron **polyhedron, Triangulation *manifold, O31Matrix *generators, int num_generators, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius) { O31Matrix *gen; int num_gen; /* * If polyhedron is not NULL, the plan is to read the generators * from the polyhedron, displace them, and recompute the * Dirichlet domain. * * If *polyhedron is NULL (as would be the case if * compute_Dirichlet_domain() failed with the previous basepoint), * then we must compute the generators directly from the * Triangulation if one is present, or otherwise from the * explicitly provided generators. */ if (*polyhedron != NULL) { generators_from_polyhedron(*polyhedron, &gen, &num_gen); free_Dirichlet_domain(*polyhedron); (*polyhedron) = Dirichlet_from_generators_with_displacement( gen, num_gen, displacement, vertex_epsilon, interactivity, maximize_injectivity_radius); my_free(gen); } else { if (manifold != NULL) (*polyhedron) = Dirichlet_with_displacement( manifold, displacement, vertex_epsilon, centroid_at_origin, interactivity, maximize_injectivity_radius); else if (generators != NULL && num_generators > 0) (*polyhedron) = Dirichlet_from_generators_with_displacement( generators, num_generators, displacement, vertex_epsilon, interactivity, maximize_injectivity_radius); else uFatalError("change_basepoint", "Dirichlet"); } } static void generators_from_polyhedron( WEPolyhedron *polyhedron, O31Matrix **generators, int *num_generators) { WEFace *face; int i; *num_generators = polyhedron->num_faces; *generators = NEW_ARRAY(*num_generators, O31Matrix); for (face = polyhedron->face_list_begin.next, i = 0; face != &polyhedron->face_list_end; face = face->next, i++) o31_copy((*generators)[i], *face->group_element); if (i != *num_generators) uFatalError("generators_from_polyhedron", "Dirichlet"); } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_basepoint.c0100444000175000017500000013657407056520021021021 0ustar babbab/* * Dirichlet_basepoint.c * * The Dirichlet domain code is divided among several files. The header * file Dirichlet.h explains the organization of the three files, and * provides the common definitions and declarations. * * This file provides the function * * void maximize_the_injectivity_radius( * MatrixPairList *gen_list, * Boolean *basepoint_was_moved, * DirichletInteractivity interactivity; * * Throughout this file, let "image height" refer to the height (i.e. * zeroth coordinate) of the image of the basepoint (1, 0, 0, 0) under * the action of a given matrix. The image height is the hyperbolic cosine * of the distance from the origin to its image. Note that the two matrices * of a MatrixPair have the same image height. * * maximize_the_injectivity_radius() assumes the MatrixPairs are given * in order of increasing image height. It moves the basepoint (i.e. it * conjugates the matrices) so that the smallest image height (excluding * the identity) is maximized subject to the constraint that no other image * height be less than it. This is a nonlinear programming problem. To * solve it, we replace it with its linear approximation, which is a linear * programming problem in the tangent space to H^3 at (1, 0, 0, 0), and * then iterate the linear problem as many times as necessary to converge * to the true solution. * * Comments on saddle points. * * In three dimensions a saddle point may have * * (A) a 2-dimenionsal set of uphill directions and * a 1-dimensional set of downhill directions, or * * (B) a 1-dimenionsal set of uphill directions and * a 2-dimensional set of downhill directions. * * If the basepoint happens to be at a type (A) saddle point, this * code will notice and ask the user whether s/he would like to see * the Dirichlet domain based at that point or move on to a local * maximum of the injectivity radius. * * At present the code does not recognize type (B) saddle points; * it simply moves on. To write code to recognize type (B) saddle * points, one would need to check whether the gradients of all * the "h" functions are coplanar. * * Note: The check for a type (A) saddle point -- seeing whether * a constraint takes the form "objective function == 0" -- sometimes * flags a local maximum as a saddle. (Because even though the full * set of constraints define a local maximum, a subset defines a * saddle.) This is no big deal, just so you're aware it happens. * * The injectivity radius isn't differentiable (only piecewise * differentiable), but it's continuous, which is all that matters. * * End of comment on saddle points. * * Digression on choice of variables. * * One could work in terms of the actual distance from the basepoint * to its images instead of the image height. The latter is the * hyperbolic cosine of the former. In an abstract mathematical sense, * both approaches are of course equivalent. The questions are * * (1) which approach yields simpler code, and * * (2) which approach gives faster convergence. * * The image height approach is slighly simpler algebraically, and * therefore yields slightly simpler code. But the difference is * small, so if using the actual distances turned out to be faster, * that would be the best approach. At the moment, I can't decide * which is likely to converge faster. So I'll go with the simpler * image height approach, and perhaps later will do some experimental * tests to see which converges faster. * * End of digression. * * * A translation through a distance dx in the x-direction is given * exactly by the matrix * * cosh dx sinh dx 0 0 * sinh dx cosh dx 0 0 * 0 0 1 0 * 0 0 0 1 * * For small dx, this matrix may be replaced with its linear approximation * * 1 dx 0 0 * dx 1 0 0 * 0 0 1 0 * 0 0 0 1 * * More generally, a translation (dx, dy, dz) has linear approximation * * 1 dx dy dz * dx 1 0 0 * dy 0 1 0 * dz 0 0 1 * * Such matrices behave nicely under multiplication: the dx, dy and dz * terms simply add, as you would hope. * * Lemma. If T is a translation matrix such as the one shown immediately * above, then conjugating all matrices M in a covering transformation * group G by T (i.e. replacing each M by (T^-1)(M)(T)), has the effect of * moving the basepoint to (1, dx, dy, dz). (The vector (1, dx, dy, dz) is * expressed in the old coordinate system. In the new coordinate system * the basepoint will of course be (1, 0, 0, 0).) * * Proof. Hmmm . . . visually this seems plausible, but at the moment * I'm having trouble making my thoughts more precise. * * * Conjugating an arbitrary group element with matrix M by the above * linear approximation T to a translation matrix gives a matrix * (T^-1)(M)(T) = * * 1 -dx -dy -dz m00 m01 m02 m03 1 +dx +dy +dz * -dx 1 0 0 m10 m11 m12 m13 +dx 1 0 0 * -dy 0 1 0 m20 m21 m22 m23 +dy 0 1 0 * -dz 0 0 1 m30 m31 m32 m33 +dz 0 0 1 * * whose image height, which is just the (0,0)th entry, is * * h = m00 + (m01 - m10)dx + (m02 - m20)dy + (m03 - m30)dz. * * We don't care about the remaining fifteen entries. * * From the above expression for h, it's easy to read off the partial * derivatives * * dh/dx = m01 - m10 * dh/dy = m02 - m20 * dh/dz = m03 - m30 */ #include "kernel.h" #include "Dirichlet.h" #include /* needed for qsort() */ /* * If an iteration of the linear programming algorithm moves the basepoint * a distance less than BASEPOINT_EPSILON, we assume we've converged to * a solution. */ #define BASEPOINT_EPSILON (1e4 * DBL_EPSILON) /* * In low precision, non general position situations we may never * get a solution accurate to within BASEPOINT_EPSILON. So we make the * convention that if we move a distance less than BIG_BASEPOINT_EPSILON * twice in a row, we assume we have converged. */ #define BIG_BASEPOINT_EPSILON 1e-5 /* * MAX_TOTAL_DISTANCE is the greatest cumulative distance we're willing * to move the basepoint before recomputing the Dirichlet domain to get * a fresh set of generators. */ #define MAX_TOTAL_DISTANCE 0.25 /* * The derivative of each image height is typically nonzero. If it's ever * less than DERIVATIVE_EPSILON, ask the user how to proceed. */ #define DERIVATIVE_EPSILON 1e-6 /* * MAX_STEP_SIZE defines the largest step size the linear programming * algorithm will take. If it's too large, the algorithm will go * beyond the region in which the linear approximation is meaningful. * If it's too small, the algorithm will converge unnecessarily slowly. */ #define MAX_STEP_SIZE 0.1 /* * The identity MatrixPair is recognized by the fact that its height * is less than 1.0 + IDENTITY_EPSILON. (Since cosh(dx) ~ 1 + (1/2)dx^2, * a MatrixPair which translates the origin a distance dx will have * height (1/2)dx^2.) */ #define IDENTITY_EPSILON 1e-6 /* * A Constraint is considered to be satisfied at a point iff it evaluates * to at most CONSTRAINT_EPSILON. If CONSTRAINT_EPSILON is too small, * bad things are likely to happen at points where more than three * constraint planes intersect. If CONSTRAINT_EPSILON is too large, we * could lose accuracy in delicate situations. The latter possibility * seems much less likely than the former (indeed we know the former occurs * for many of the most beautiful Dirichlet domains), so let's go with a * fairly large value of CONSTRAINT_EPSILON. */ #define CONSTRAINT_EPSILON 1e-6 /* * Two vectors u and v are considered parallel (resp. antiparallel) iff * the cosine of the angle between them is greater than 1.0 - SADDLE_EPSILON * (resp. less than -1.0 + SADDLE_EPSILON). */ #define SADDLE_EPSILON 1e-6 /* * A constraint is considered to have zero derivative iff the norm of * its gradient vector is less than ZERO_DERIV_EPSILON. */ #define ZERO_DERIV_EPSILON 1e-6 /* * A set of three linear equations in three variables is considered * degenerate iff some pivot has absolute value less than MIN_PIVOT. */ #define MIN_PIVOT (1e5 * DBL_EPSILON) #define ROOT3OVER2 0.86602540378443864676 /* * We want to evaluate Constraints quickly, without the overhead of a * function call, but we don't want a lot of messy code. So let's define * a macro for testing the value of an equation at a given point. * This macro will also test the value of an ObjectiveFunction at a point. */ #define EVALUATE_EQN(eqn, pt) \ (eqn[0]*pt[0] + eqn[1]*pt[1] + eqn[2]*pt[2] + eqn[3]) /* * An ObjectiveFunction is a 4-element vector (a, b, c, k). * The linear_programming() function tries to maximize * a*dx + b*dy + c*dz + k subject to the given constraints. */ typedef double ObjectiveFunction[4]; /* * A constraint is a 4-element vector (a, b, c, k) * interpreted as the inequality a*dx + b*dy + c*dz + k <= 0. */ typedef double Constraint[4]; /* * A solution is a vector (dx, dy, dz) which maximizes the * objective function subject to the constraints. */ typedef double Solution[3]; static int count_matrix_pairs(MatrixPairList *gen_list); static void verify_gen_list(MatrixPairList *gen_list, int num_matrix_pairs); static FuncResult set_objective_function(ObjectiveFunction objective_function, MatrixPair *matrix_pair); static void step_size_constraints(Constraint *constraints, ObjectiveFunction objective_function); static void regular_constraints(Constraint *constraints, MatrixPairList *gen_list, ObjectiveFunction objective_function, Boolean *may_be_saddle_point); static void linear_programming(ObjectiveFunction objective_function, int num_constraints, Constraint *constraints, Solution solution); static Boolean apex_is_higher(double height1, double height2, Solution apex1, Solution apex2); static FuncResult solve_three_equations(Constraint *equations[3], Solution solution); static void initialize_t2(Solution solution, O31Matrix t2); static void sort_gen_list(MatrixPairList *gen_list, int num_matrix_pairs); static int CDECL compare_image_height(const void *ptr1, const void *ptr2); static double length3(double v[3]); static double inner3(double u[3], double v[3]); static void copy3(Solution dest, const Solution source); void maximize_the_injectivity_radius( MatrixPairList *gen_list, Boolean *basepoint_moved, DirichletInteractivity interactivity) { int num_matrix_pairs; double distance_moved, prev_distance_moved, total_distance_moved; Boolean keep_going; ObjectiveFunction objective_function; int num_constraints; Constraint *constraints; Solution solution; Boolean may_be_saddle_point, saddle_query_given; int choice; static const Solution zero_solution = {0.0, 0.0, 0.0}, small_displacement = {0.001734, 0.002035, 0.000721}; const static char *saddle_message = "The basepoint may be at a saddle point of the injectivity radius function."; const static int num_saddle_responses = 2; const static char *saddle_responses[2] = { "Continue On", "Stop Here and See Dirichlet Domain"}; const static int saddle_default = 1; const static char *zero_deriv_message = "The derivative of the distance to the closest translate of the basepoint is zero."; const static char num_zero_deriv_responses = 2; const static char *zero_deriv_responses[2] = { "Displace Basepoint and Continue On", "Stop Here and See Dirichlet Domain"}; const static int zero_deriv_default = 1; /* * Count the number of MatrixPairs. */ num_matrix_pairs = count_matrix_pairs(gen_list); /* * Make sure that * * (1) the identity and at least two other MatrixPairs are present, * * (2) the MatrixPairs are in order of increasing height. * * Technical notes: We don't really need to have the gen_list * completely sorted -- it would be enough to have the identity come * first and the element of lowest image height come immediately after. * But I think the algorithm will run a tad faster if all elements are * in order of increasing image height. That way we get to the * meaningful constraints first. */ verify_gen_list(gen_list, num_matrix_pairs); /* * Initialize *basepoint_moved to FALSE. * If we later move the basepoint, we'll set *basepoint_moved to TRUE. */ *basepoint_moved = FALSE; /* * Keep track of the total distance we've moved the basepoint. * We don't want to go too far without recomputing the Dirichlet * domain to get a fresh set of group elements. */ total_distance_moved = 0.0; /* * Some ad hoc code for handling low precision situations * needs to keep track of the prev_distance_moved. */ prev_distance_moved = DBL_MAX; /* * We don't want to bother the user with the saddle query * more than once. We initialize saddle_query_given to FALSE, * and then set it to TRUE if and when the query takes place. */ saddle_query_given = FALSE; /* * We want to move the basepoint to a local maximum of the injectivity * radius function. Solve the linear approximation to this problem, * and repeat until a solution is found. */ do { /* * Set the objective function using the first nonidentity matrix * on the list. If the derivative of the objective function is * nonzero, proceed normally. Otherwise ask the user how s/he * would like to proceed. */ if (set_objective_function(objective_function, gen_list->begin.next->next) == func_OK) { /* * Allocate space for the Constraints. * There'll be num_matrix_pairs - 2 regular constraints * (one for each MatrixPair, excluding the identity and the * MatrixPair used to define the objective function), * preceded by three constraints which limit the step size * to MAX_STEP_SIZE. */ num_constraints = (num_matrix_pairs - 2) + 3; constraints = NEW_ARRAY(num_constraints, Constraint); /* * Set up the three step size constraints. */ step_size_constraints(constraints, objective_function); /* * Set up the regular constraints. */ regular_constraints(constraints, gen_list, objective_function, &may_be_saddle_point); /* * If we're not near an apparent saddle point, * do the linear programming. */ if (may_be_saddle_point == FALSE) linear_programming(objective_function, num_constraints, constraints, solution); /* * Otherwise ask the user whether s/he would like * to continue on normally or stop here. */ else { switch (interactivity) { case Dirichlet_interactive: if (saddle_query_given == FALSE) { choice = uQuery( saddle_message, num_saddle_responses, saddle_responses, saddle_default); saddle_query_given = TRUE; } else choice = 0; /* continue on */ break; case Dirichlet_stop_here: choice = 1; /* stop here */ break; case Dirichlet_keep_going: choice = 0; /* continue on */ break; } switch (choice) { case 0: /* * Continue on normally. */ linear_programming(objective_function, num_constraints, constraints, solution); break; case 1: /* * Stop here, set *basepoint_moved to FALSE * to force an exit from the loop, and look at * the Dirichlet domain. */ copy3(solution, zero_solution); *basepoint_moved = FALSE; break; } } /* * Free the Constraint array. */ my_free(constraints); } else { /* * The derivative of the objective function is zero. * * Ask the user whether to use this basepoint, or move on in * search of a local maximum of the injectivity radius. */ switch (interactivity) { case Dirichlet_interactive: choice = uQuery( zero_deriv_message, num_zero_deriv_responses, zero_deriv_responses, zero_deriv_default); break; case Dirichlet_stop_here: choice = 1; /* stop here */ break; case Dirichlet_keep_going: choice = 0; /* continue on */ break; } switch (choice) { case 0: /* * Displace the basepoint and continue on. */ copy3(solution, small_displacement); break; case 1: /* * We want to stay at this point, so set the solution * to (0, 0, 0), and set *basepoint_moved to FALSE * to force an exit from the loop. */ copy3(solution, zero_solution); *basepoint_moved = FALSE; break; } } /* * Use the solution to conjugate the MatrixPairs. */ conjugate_matrices(gen_list, solution); /* * Resort the gen_list according to increasing image height. */ sort_gen_list(gen_list, num_matrix_pairs); /* * How far was the basepoint moved this time? */ distance_moved = length3(solution); /* * What is the total distance we've moved the basepoint? */ total_distance_moved += distance_moved; /* * If the basepoint moved any meaningful distance, * set *basepoint_moved to TRUE. */ if (distance_moved > BASEPOINT_EPSILON) { *basepoint_moved = TRUE; /* * If we move too far from the original basepoint, we should * recompute the Dirichlet domain to get a fresh set of * group elements. Otherwise we keep going. */ keep_going = (total_distance_moved < MAX_TOTAL_DISTANCE); } else keep_going = FALSE; /* * The preceding code works great when the constraints are * either in general position, or are given to moderately high * precision. But for low precision, non general position * constraints (e.g. for an 8-component circular chain with no * twist), the algorithm can knock around forever making * changes on the order of 1e-9. The following code lets the * algorithm terminate in those cases. */ if (prev_distance_moved < BIG_BASEPOINT_EPSILON && distance_moved < BIG_BASEPOINT_EPSILON) { /* * For sure we don't want to keep going after making * two fairly small changes in a row. */ keep_going = FALSE; /* * If the total_distance_moved is less than BIG_BASEPOINT_EPSILON, * then we want to set *basepoint_moved to FALSE to prevent * recomputation of the Dirichlet domain. Note that we still * allow the possibility that *basepoint_moved is TRUE even when * the total_distance_moved is greater than BIG_BASEPOINT_EPSILON, * as could happen if preceding code artificially set *basepoint_moved * to FALSE to force an exit from the loop. */ if (total_distance_moved < BIG_BASEPOINT_EPSILON) *basepoint_moved = FALSE; } prev_distance_moved = distance_moved; } while (keep_going == TRUE); } static int count_matrix_pairs( MatrixPairList *gen_list) { int num_matrix_pairs; MatrixPair *matrix_pair; num_matrix_pairs = 0; for ( matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) num_matrix_pairs++; return num_matrix_pairs; } static void verify_gen_list( MatrixPairList *gen_list, int num_matrix_pairs) { MatrixPair *matrix_pair; /* * Does the list have at least two elements beyond the identity? * Algebraically, we'll need an objective function * and at least one constraint. * Geometrically, the Dirichlet domain which provided these * generators must have at least two pairs of faces. */ if (num_matrix_pairs < 2) uFatalError("verify_gen_list", "Dirichlet_basepoint"); /* * The first MatrixPair on gen_list should be the identity. */ if (gen_list->begin.next->height > 1.0 + IDENTITY_EPSILON) uFatalError("verify_gen_list", "Dirichlet_basepoint"); /* * We want the MatrixPairs to be in order of increasing image height. * (Note that this loop starts at the second MatrixPair on the list.) */ for ( matrix_pair = gen_list->begin.next->next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) if (matrix_pair->height < matrix_pair->prev->height) uFatalError("verify_gen_list", "Dirichlet_basepoint"); } static FuncResult set_objective_function( ObjectiveFunction objective_function, MatrixPair *matrix_pair) { int i; /* * Read the objective function from the first matrix on the list. * Recall from above that we want to maximize * * h = m00 + (m01 - m10)dx + (m02 - m20)dy + (m03 - m30)dz. * * Note that the object function is the same for matrix_pair[0] and * matrix_pair[1], because they are inverses. (This follows from the * rule for computing inverses in O(3,1) (see o31_invert() in * o31_matrices.c), as well as from the geometrical fact that an * isometry and its inverse must translate the basepoint equal amounts.) * * Return * func_OK if the objective function's derivative is nonzero, or * func_failed if it isn't. */ /* * Compute the objective function. */ for (i = 0; i < 3; i++) objective_function[i] = matrix_pair->m[0][0][i+1] - matrix_pair->m[0][i+1][0]; objective_function[3] = matrix_pair->m[0][0][0]; /* * Check whether the derivative is nonzero. */ if (length3(objective_function) > DERIVATIVE_EPSILON) return func_OK; else return func_failed; } static void step_size_constraints( Constraint *constraints, ObjectiveFunction objective_function) { int i, j, i0; double v[3][3], w[3][3], max_abs, length; /* * The three step size constraints will be faces of a cube, * with the vector for the objective_function pointing in the * direction of the cube's corner where the three faces intersect. */ /* * First find an orthonormal basis {v[0], v[1], v[2]} with * v[0] equal to the vector part of the objective function. */ /* * Let v[0] be the vector part of the objective function, * normalized to have length one. (Elsewhere we checked that * its length is at least DERIVATIVE_EPSILON.) */ length = length3(objective_function); for (i = 0; i < 3; i++) v[0][i] = objective_function[i] / length; /* * Let v[1] be a unit vector orthogonal to v[0]. */ /* * Let i0 be the index of the component of v[0] which has the greatest * absolute value. (In particular, its absolute value is sure to be * nonzero.) */ max_abs = 0.0; for (i = 0; i < 3; i++) if (fabs(v[0][i]) > max_abs) { i0 = i; max_abs = fabs(v[0][i]); } /* * Write down a nonzero v[1] orthogonal to v[0] . . . */ v[1][i0] = -v[0][(i0+1)%3] / v[0][i0]; v[1][(i0+1)%3] = 1.0; v[1][(i0+2)%3] = 0.0; /* * . . . and normalize its length to 1.0. */ length = length3(v[1]); for (i = 0; i < 3; i++) v[1][i] /= length; /* * Let v[2] = v[0] x v[1]. */ for (i = 0; i < 3; i++) v[2][i] = v[0][(i+1)%3] * v[1][(i+2)%3] - v[0][(i+2)%3] * v[1][(i+1)%3]; /* * Use the orthonormal basis {v[0], v[1], v[2]} to find another basis * {w[0], w[1], w[2]} whose elements symmetrically surround v[0]. * * w[0] = v[0] + v[1] * w[1] = v[0] + ( -1/2 v[1] + sqrt(3)/2 v[2] ) * w[2] = v[0] + ( -1/2 v[1] - sqrt(3)/2 v[2] ) */ for (j = 0; j < 3; j++) { w[0][j] = v[0][j] + v[1][j]; w[1][j] = v[0][j] + (-0.5*v[1][j] + ROOT3OVER2*v[2][j]); w[2][j] = v[0][j] + (-0.5*v[1][j] - ROOT3OVER2*v[2][j]); } /* * Use the basis {w[0], w[1], w[2]} to write down * the three step size constraints. * * Technical note: If you move in the direction of the objective * function vector v[0], the three constraints will be exactly * satisfied at a distance MAX_STEP_SIZE from the origin. That is, the * inner product of (MAX_STEP_SIZE, 0, 0) with with each of (1, 1, 0), * (1, -1/2, sqrt(3)/2) and (1, -1/2, -sqrt(3)/2) is exactly * MAX_STEP_SIZE. However, if you move to the side (orthogonally to * v[0]) it's possible to move a distance 2*MAX_STEP_SIZE. E.g. the * inner product of (0, -2*MAX_STEP_SIZE, 0) with with each of * (1, 1, 0), (1, -1/2, sqrt(3)/2) and (1, -1/2, -sqrt(3)/2) is * -2*MAX_STEP_SIZE, MAX_STEP_SIZE and MAX_STEP_SIZE, respectively, so * it satisfies all three constraints. But typically we won't be * moving to the side, and in any case all we really care about anyhow * is the order of magnitude of MAX_STEP_SIZE. A factor of two isn't * important. */ for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) constraints[i][j] = w[i][j]; constraints[i][3] = -MAX_STEP_SIZE; } } static void regular_constraints( Constraint *constraints, MatrixPairList *gen_list, ObjectiveFunction objective_function, Boolean *may_be_saddle_point) { /* * The documentation at the top of this file shows that the image * height h corresponding to a matrix m is * * h = m00 + (m01 - m10)dx + (m02 - m20)dy + (m03 - m30)dz. * * Each regular constraint will say that the image height h for the * given matrix must remain greater than the image height h' of the * matrix used for the objective function. In symbols, h >= h'. * Since a Constraint says that some quantity must remain negative, * we express the constraint as h' - h <= 0. * * The Boolean *may_be_saddle_point will be set to TRUE if some * constraint suggests a saddle point. Otherwise it gets set to FALSE. */ int i; MatrixPair *matrix_pair; Constraint *constraint; double h[4], c; /* * Assume we're not at a saddle point unless we encounter * evidence to the contrary. */ *may_be_saddle_point = FALSE; /* * Skip the identity and the MatrixPair used to define the objective * function, and begin with the next MatrixPair on the list. * Write a constraint for it and each successive MatrixPair. * * Skip the first three Constraints in the constraints array. * They contain the step size constraints. */ for ( matrix_pair = gen_list->begin.next->next->next, constraint = constraints + 3; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next, constraint++) { /* * Compute h. (As explained in set_objective_function(), * it doesn't matter which of the two matrices we use.) */ for (i = 0; i < 3; i++) h[i] = matrix_pair->m[0][0][i+1] - matrix_pair->m[0][i+1][0]; h[3] = matrix_pair->m[0][0][0]; /* * Set the constraint to h' - h. * (The objective function is h' in the above notation.) */ for (i = 0; i < 4; i++) (*constraint)[i] = objective_function[i] - h[i]; /* * Does the constraint plane pass through the origin? */ if ((*constraint)[3] > - CONSTRAINT_EPSILON) { /* * Does the constraint have nonzero derivative? * If not, then h and h' must have equal but nonzero derivatives. * We know h' has nonzero derivative because we checked it * when we computed the objective function. * Its OK for h and h' to have equal but nonzero derivatives -- * it simply means that as we move avoid from the closest * translate of the basepoint, we're moving away from some * other translate as well -- be we don't want to divide by * length3(*constraint). */ if (length3(*constraint) > ZERO_DERIV_EPSILON) { /* * Check whether the constraint plane is parallel * to the level sets of the objective function. * * Use the formula = |u| |v| cos(angle). */ c = inner3(objective_function, *constraint) / (length3(objective_function) * length3(*constraint)); /* * If it is parallel, set *may_be_saddle_point to TRUE. */ if (fabs(c) > 1.0 - SADDLE_EPSILON) *may_be_saddle_point = TRUE; /* * If necessary we could be more sophisticated at this point, * and check whether the gradients of h and h' are parallel * or antiparallel. Typically one expects them to be * antiparallel (the MatrixPairs are, after all, the face * pairings of a Dirichlet domain, so we don't have to worry * about squares of a matrix), but if they were parallel one * might want to ask which is longer (depending on which is * longer, you will or will not be able to move the basepoint * in that direction). */ } } } } static void linear_programming( ObjectiveFunction objective_function, int num_constraints, Constraint *constraints, Solution solution) { int i, j, k; Constraint *active_constraints[3], *new_constraints[3]; Solution apex, new_apex, max_apex; double apex_height, new_height, max_height; int inactive_constraint_index; /* * Initialize the three active_constraints to be the first three * constraints on the list. (In the present context these are the * step size constraints, but let's write the code so as not to * rely on this knowledge.) Visually, we think of the intersection * of the halfspaces defined by the three active_constraints as * a pyramid, oriented so that the gradient of objective function * points up. */ for (i = 0; i < 3; i++) active_constraints[i] = constraints + i; /* * Initialize the apex to be the vertex defined by the intersection * of the three step size constraints. * * Important note: We assume the maximum value for the objective * function, subject to the active_constraints, occurs at the apex. * (In the present context this is true by virtue of the way the * step size constraints were written.) */ if (solve_three_equations(active_constraints, apex) == func_failed) uFatalError("linear_programming", "Dirichlet_basepoint"); /* * For future reference, set apex_height to the value of the * objective function at the apex. */ apex_height = EVALUATE_EQN(objective_function, apex); /* * Go down the full list of constraints and see whether the apex * satisfies all of them. * * If it does, we've solved the linear programming problem * and we're done. * * If it doesn't, then slice the pyramid defined by the * active_constraints with the constraint which isn't satisfied. * Let the new apex be the highest point on the truncated pyramid, * and the new active_constraints be the three faces of the truncated * pyramid incident to the new apex. Repeat this procedure until * all constraints are satisfied. Note that we have to start from * the beginning of the constraint list each time, since even if the * old apex satisfied a given constraint, the new apex might not. * * If candidate apexes are always at distinct heights, then it's easy * to prove that this algorithm will terminate in a finite number of * steps. But if different candidate apexes sometimes lie at the * same height (as would happen if a constraint function were parallel * to the level sets of the objective function, for example) then we * cannot prove that the algorithm terminates. Indeed, the example * given in the documentation at the end of this function shows how * the algorithm might get into an infinite loop. To avoid this * problem, we add an infinitessimal perturbation to the objective * function. Say we add a term epsilon*dx to the objective function, * where epsilon is a true mathematical infinitessimal, not just a very * small number. Then if two heights are precisely equal as floating * point numbers, the one with the greater dx coordinate will be * considered greater than the one with the smaller dx coordinate. * But what if their dx coordinates are equal, too? And dy and dz? * Our official theoretical definition for the objection function is * * (objective function as computed) + dx*epsilon * + dy*(epsilon^2) * + dz*(epsilon^3) * * In practice, this means that we first compare heights based on the * objective function as computed. If they come out exactly equal, * then we compare based on dx coordinates. If the dx's are equal, * then we compare dy's. If the dy's are equal we compare dz's. If * all those things are equal, then the points coincide, and it makes * sense that their heights should be equal. Since there are only * finite number of possible apexes (one for each triple of constraints), * it follows that if the height is reduced at each step, then the * algorithm must terminate after a finite number of steps. */ for (i = 0; i < num_constraints; i++) if (EVALUATE_EQN(constraints[i], apex) > CONSTRAINT_EPSILON) { /* * Uh-oh. The apex doesn't satisfy constraints[i]. * Slice the pyramid with constraints[i], and see which * new vertex is highest. The new set of active constraints * will include two of the old active constraints, plus * constraints[i]. */ /* * Initialize max_height to -1.0. */ max_height = -1.0; for (j = 0; j < 3; j++) max_apex[j] = 0.0; /* * Replace each active_constraint, in turn, with constraints[i]. * The variable j will be the index of the active_constraint * currently being replaced with constraints[i]. */ for (j = 0; j < 3; j++) { /* * Assemble the candidate set of new_constraints. */ for (k = 0; k < 3; k++) new_constraints[k] = // Cater to a DEC compiler error that chokes on &(array)[i] // (k == j ? &constraints[i] : active_constraints[k]); (k == j ? constraints + i : active_constraints[k]); /* * Find the common intersection of the new_constraints. * * The equations can't possibly be underdetermined, because * if the solution set were 1-dimensional it would have to * include the apex, which is known not to satisfy * constraints[i]. * * The equations might, however, be inconsistent. In * this case, we continue with the loop, as if new_height * were greater than apex_height (cf. below). If the * equations are in principle inconsistent but roundoff * error gives a solution, that's OK too: the solution * will yield a new_height near +infinity or -infinity, * and new_apex will be ignored or fail to be maximal, * respectively. */ if (solve_three_equations(new_constraints, new_apex) == func_failed) continue; /* * Compute the value of the objective function * at the new apex. */ new_height = EVALUATE_EQN(objective_function, new_apex); /* * If new_height is greater than apex_height, then new_apex * is above apex, and is not actually a vertex of the * truncated pyramid. We ignore it and move on. (It's * easy to prove that *some* j will yield a valid maximum * height. When we slice the pyramid with constraints[i] * the resulting solid will somewhere obtain a maximum * height. The old apex is gone, so it can't be there. * And the origin is still present, so it must be higher * than the origin. The infinitessimal correction to the * objective function insures that no planes or lines are * ever truly horizontal, so the maximum must occur at a * vertex, where constraints[i] and two of the old * constraints intersect.) * * If new_height == apex_height, apex_is_higher() applies * the infinitessimal correction to the objective function, * as explained above. */ if (apex_is_higher(new_height, apex_height, new_apex, apex) == TRUE) continue; /* * Is new_height greater than max_height? */ if (apex_is_higher(new_height, max_height, new_apex, max_apex) == TRUE) { inactive_constraint_index = j; max_height = new_height; for (k = 0; k < 3; k++) max_apex[k] = new_apex[k]; } } /* * Swap constraints[i] into the active_constraints array * at index inactive_constraint_index. */ // Cater to a DEC compiler error that chokes on &(array)[i] // active_constraints[inactive_constraint_index] = &constraints[i]; active_constraints[inactive_constraint_index] = constraints + i; /* * Set the apex to max_apex and apex_height to max_height. * * Note that we preserve the condition (cf. above) that the * maximum value of the objective function on the pyramid * occurs at the apex. */ for (j = 0; j < 3; j++) apex[j] = max_apex[j]; apex_height = max_height; /* * We've fixed up the active_constraints and the apex, * so now recheck the other constraints. Set i = -1, * so that after the i++ in the for(;;) statement it will * be back to i = 0. */ i = -1; } /* * Hooray. All the constraints are satisfied. * Set the solution equal to the apex and we're done. */ for (i = 0; i < 3; i++) solution[i] = apex[i]; return; /* * Here's an example in which an unperturbed objective function * may lead to an infinite loop. * * Make a sketch (in (dx, dy, dz) space) showing the following points: * * s0 = (2, 0, 0) * s1 = (-1, sqrt(3), 0) [twice a primitive cube root of unity] * s2 = (-1, -sqrt(3), 0) * * t0 = (1, 0, 1) * t1 = (-1, sqrt(3)/2, 1) * t2 = (-1, -sqrt(3)/2, 1) * * u = (0, 0, 2) * * The objective function is 0*dx + 0*dy + 1*dz + constant. * * The constraints are defined by the following planes. (I'll give * a spanning set for each plane. The constraint itself will be an * equation saying that (dx, dy, dz) must lie on the same side of the * plane as the origin (0, 0, 0) (or on the plane itself is OK too).) * Sketch each of these planes in your picture. * * a0 = {s1, s2, u} * a1 = {s2, s0, u} * a2 = {s0, s1, u} * * b = {t0, t1, t2} * * c0 = {s0, t1, t2} * c1 = {t0, s1, t2} * c2 = {t0, t1, s2} * * Now look what happens in the naive linear programming algorithm. * Say we start with constraints a0, a1 and a2, so the apex is at * point u. Then we consider constraint b. Constraint b will * replace one of {a0, a1, a2}; w.l.o.g. say it's a2. So we're * left with constraints {a0, a1, b} and the apex is at t2. The point * t2 satisfies all the constraints except c2, so eventually the * algorithm will swap c2 for either a0 or a1 (w.l.o.g. say its a1) * and we're left with equations {a0, c2, b}, and the apex moves to * t1. The point t1 satisfies all the constraints except c1, so * c1 gets swapped for either a0 or c2, the constraint set becomes * either {c1, c2, b} or {a0, c1, b}, and the apex moves to either * t0 or t2. This is the critical juncture for the algorithm. If * it swapped c1 for a0, then on the next iteration of the algorithm * it swaps c0 for b, and the constraint set {c1, c2, c0} gives us * the true solution. But if it swapped c1 for c2, then we run the * risk of getting into an infinite loop. But with the perturbed * objective function this will never happen, because we'll never * visit the same vertex twice. */ } static Boolean apex_is_higher( double height1, double height2, Solution apex1, Solution apex2) { int i; if (height1 > height2) return TRUE; if (height1 < height2) return FALSE; for (i = 0; i < 3; i++) { if (apex1[i] > apex2[i]) return TRUE; if (apex1[i] < apex2[i]) return FALSE; } return FALSE; } static FuncResult solve_three_equations( Constraint *equations[3], Solution solution) { /* * If the system of equations has a unique solution, * write it into the solution parameter and return func_OK. * * Otherwise return func_failed. */ int r, c, p; double equation_storage[3][4], *eqn[3], *temp, pivot_value; /* * We store the set of equations as an array of three pointers, * to facilitate easy row swapping. Initialize eqn[i] to point * to equation_storage[i]. */ for (r = 0; r < 3; r++) eqn[r] = equation_storage[r]; /* * Copy the original equations to avoid trashing them. * Note that the constants are in eqn[r][3]. */ for (r = 0; r < 3; r++) for (c = 0; c < 4; c++) eqn[r][c] = (*equations[r])[c]; /* * Do the forward part of Gaussian elimination. */ /* * For each pivot position eqn[p][p] . . . */ for (p = 0; p < 3; p++) { /* * Find the pivot row and swap it with row p. */ for (r = p + 1; r < 3; r++) if (fabs(eqn[r][p]) > fabs(eqn[p][p])) { temp = eqn[p]; eqn[p] = eqn[r]; eqn[r] = temp; } /* * Note the pivot value. */ pivot_value = eqn[p][p]; /* * If the pivot_value is close to zero, * the equations won't have a stable, unique solution. * Return func_failed. */ if (fabs(pivot_value) < MIN_PIVOT) return func_failed; /* * Divide the pivot row through by the pivot_value. * * The entries with c <= p needn't be computed, * since we know what they are. */ for (c = p + 1; c < 4; c++) eqn[p][c] /= pivot_value; /* * Subtract multiples of row p from all subsequent rows, * so that only zeros appear below the pivot entry. * * The entries with c <= r needn't be computed, since * we know they'll all be zero. However, if we were * explicitly changing eqn[r][p], then we'd have to save * a copy of its initial value in a separate variable, * e.g. we'd replace * * eqn[r][c] -= eqn[r][p] * eqn[p][c]; * with * eqn[r][c] -= multiple * eqn[p][c]; */ for (r = p + 1; r < 3; r++) for (c = p + 1; c < 4; c++) eqn[r][c] -= eqn[r][p] * eqn[p][c]; } /* * Do the back substitution part of Gaussian elimination. * * We needn't bother computing the zeros which goes in the matrix. * We just need the constants. */ for (c = 3; --c >= 0; ) for (r = c; --r >= 0; ) eqn[r][3] -= eqn[r][c] * eqn[c][3]; /* * Read off the solution. * * Note that the constants sit on the left side of the constraint * equations, so Gaussian elimination has brought us to the system * * 1.0 dx + 0.0 dy + 0.0 dz + eqn[0][3] = 0.0 * 0.0 dx + 1.0 dy + 0.0 dz + eqn[1][3] = 0.0 * 0.0 dx + 0.0 dy + 1.0 dz + eqn[2][3] = 0.0 * * so * * dx = - eqn[0][3] * dy = - eqn[1][3] * dz = - eqn[2][3] * * Note the minus signs. */ for (r = 0; r < 3; r++) solution[r] = - eqn[r][3]; return func_OK; } void conjugate_matrices( MatrixPairList *gen_list, double displacement[3]) { /* * We want to conjugate each MatrixPair on the gen_list so as to move * the basepoint the distance given by the displacement. The * displacement, which is a translation (dx, dy, dz) in the tangent * space to H^3 at (1, 0, 0, 0), is a linear approximation to where the * basepoint ought to be. * * It won't do simply to conjugate by the matrix T1 = * * 1 dx dy dz * dx 1 0 0 * dy 0 1 0 * dz 0 0 1 * * Even though T1 is a linear approximation to the desired translation, * it's columns are not quite orthonormal (they are orthonormal to a * first order approximation, but not exactly). We need to use a * translation matrix which is exactly orthonormal, so that the * MatrixPairs on the gen_list remain elements of O(3,1) to full * accuracy. * * One approach would be to apply the Gram-Schmidt process to find an * element of O(3,1) close to T1. The problem here is that the * Gram-Schmidt process itself may introduce additional error. * * Better to start instead with a second order approximation to the * translation matrix, given by T2 = * * 1 + (dx^2 + dy^2 + dz^2)/2 dx dy dz * dx 1 + (dx^2)/2 (dx dy)/2 (dx dz)/2 * dy (dx dy)/2 1 + (dy^2)/2 (dy dz)/2 * dz (dx dz)/2 (dy dz)/2 1 + (dz^2)/2 * * Note that T2 is actually orthonormal to third order; that is, * its columns fail to be orthonormal only by fourth order terms. * * We will start with the second order approximation T2 and apply * Gram-Schmidt to it. The correction terms -- all of fourth order -- * will not significantly affect the closeness of the approximation. * * Digression. * * You may be wondering where the matrix T2 came from. * Drop down a dimension, and consider the product of a translation * (dx, 0) and a translation (0, dy). The result you get depends * on the order of the factors. * * ( 1 dx 0) ( 1 0 dy) ( 1 dx dy ) * ( dx 1 0) ( 0 1 0 ) = ( dx 1 dxdy) * ( 0 0 1) ( dy 0 1 ) ( dy 0 1 ) * * ( 1 0 dy) ( 1 dx 0) ( 1 dx dy ) * ( 0 1 0 ) ( dx 1 0) = ( dx 1 0 ) * ( dy 0 1 ) ( 0 0 1) ( dy dxdy 1 ) * * Averaging the two results leads to the matrix T2 shown above. * * End of digression. */ O31Matrix t2; MatrixPair *matrix_pair; /* * Initialize t2 to be the second order approximation shown above. */ initialize_t2(displacement, t2); /* * Apply the Gram-Schmidt process to bring t2 to a nearby element * of O(3,1). */ o31_GramSchmidt(t2); /* * For each MatrixPair on gen_list . . . */ for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) { /* * Conjugate m[0] by t2. * That is, replace m[0] by (t2^-1) m[0] t2. */ o31_conjugate(matrix_pair->m[0], t2, matrix_pair->m[0]); /* * Recompute m[1] as the inverse of m[0]. */ o31_invert(matrix_pair->m[0], matrix_pair->m[1]); /* * Set the height. */ matrix_pair->height = matrix_pair->m[0][0][0]; } } static void initialize_t2( Solution solution, O31Matrix t2) { /* * Initialize t2 to be the second order approximation to the * translation matrix, as explain in conjugate_matrices() above. * * This code is not optimized for computational efficiency, but * that's OK because it's called only once for each iteration * of the algorithm. */ int i, j; /* * Set the linear terms. */ for (i = 0; i < 3; i++) t2[0][i+1] = t2[i+1][0] = solution[i]; /* * Set t2[0][0]. */ t2[0][0] = 1.0; for (i = 0; i < 3; i++) t2[0][0] += 0.5 * solution[i] * solution[i]; /* * Set the remaining second order terms. */ for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) t2[i+1][j+1] = (i == j ? 1.0 : 0.0) + 0.5 * solution[i] * solution[j]; } static void sort_gen_list( MatrixPairList *gen_list, int num_matrix_pairs) { MatrixPair **array, *matrix_pair; int i; /* * Allocate an array to hold the addresses of the MatrixPairs. */ array = NEW_ARRAY(num_matrix_pairs, MatrixPair *); /* * Copy the addresses into the array. */ for (matrix_pair = gen_list->begin.next, i = 0; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next, i++) array[i] = matrix_pair; /* * Do a quick error check to make sure we copied * the right number of elements. */ if (i != num_matrix_pairs) uFatalError("sort_gen_list", "Dirichlet_basepoint"); /* * Sort the array of pointers. */ qsort( array, num_matrix_pairs, sizeof(MatrixPair *), compare_image_height); /* * Adjust the MatrixPairs' prev and next fields * to reflect the new ordering. */ gen_list->begin.next = array[0]; array[0]->prev = &gen_list->begin; array[0]->next = array[1]; for (i = 1; i < num_matrix_pairs - 1; i++) { array[i]->prev = array[i-1]; array[i]->next = array[i+1]; } array[num_matrix_pairs - 1]->prev = array[num_matrix_pairs - 2]; array[num_matrix_pairs - 1]->next = &gen_list->end; gen_list->end.prev = array[num_matrix_pairs - 1]; /* * Free the array. */ my_free(array); } static int CDECL compare_image_height( const void *ptr1, const void *ptr2) { double diff; diff = (*((MatrixPair **)ptr1))->height - (*((MatrixPair **)ptr2))->height; if (diff < 0.0) return -1; if (diff > 0.0) return +1; return 0; } static double length3( double v[3]) { double length; int i; length = 0.0; for (i = 0; i < 3; i++) length += v[i] * v[i]; length = sqrt(length); /* no need for safe_sqrt() */ return length; } static double inner3( double u[3], double v[3]) { double sum; int i; sum = 0.0; for (i = 0; i < 3; i++) sum += u[i] * v[i]; return sum; } static void copy3( Solution dest, const Solution source) { int i; for (i = 0; i < 3; i++) dest[i] = source[i]; } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_construction.c0100444000175000017500000027177407040647405021602 0ustar babbab/* * Dirichlet_construction.c * * The Dirichlet domain code is divided among several files. The header * file Dirichlet.h explains the organization of the three files, and * provides the common definitions and declarations. * * This file provides the function * * WEPolyhedron *compute_Dirichlet_domain( MatrixPairList *gen_list, * double vertex_epsilon); * * compute_Dirichlet_domain() computes the Dirichlet domain defined by * the list of generators, and sets the list of generators equal to * the face pairings of the Dirichlet domain, sorted by order of increasing * image height (see Dirichlet_basepoint.c for the definition of "image * height"). It does not set certain cosmetic fields (vertex->ideal, etc.) * which aren't needed for the computation; it assumes the calling * function will set them at the very end (cf. bells_and_whistles() in * Dirichlet_extras.c). If compute_Dirichlet_domain() fails (as explained * immediately below) it returns NULL. * * Error detection. No Dirichlet domain algorithm can be perfect. * If you give it, say, the generators for a (p,q) Dehn surgery on the * figure eight knot, then it will surely fail for large enough p and q, * because the true Dirichlet domain will have a vast number of tiny * faces, and the numerical precision of the underlying hardware won't * be good enough to resolve them all correctly. The present algorithm * will attempt to resolve the faces as best it can, but will return * NULL if it fails. Its strategy is to start with a cube and intersect * it with appropriate half planes, one at a time. At each step it checks * how the slicing plane intersects the existing polyhedron. Vertices * lying very close to the slicing plane are assumed to lie on it (the * precise interpretation of "very close" is defined by vertex_epsilon). * Each edge passing from one side of the slicing plane to the other is * divided in two by introducing a new vertex at the point where the edge * intersects the slicing plane. Similarly, each face which passes through * the slicing plane is divided in two. The program then checks rigorously * that the slicing plane divides the surface of the polyhedron into two * combinatorial disks. If it does, the vertices, edges and faces on * the "far" side of the slicing plane are discarded, a new face is * introduced, and we know for sure that the surface of the polyhedron * is still a combinatorial ball. If the slicing plane does not divide * the surface of the polyhedron into two combinatorial disk (as could * happen when the vertices are so close together that roundoff error * introduces inconsistent combinatorial relations), the program gives up * and returns NULL. * * The basic approach of the algorithm is to first use the given generators * to find a polyhedron which * * (1) is a superset of the true Dirichlet domain, and * * (2) does not extend beyond the sphere at infinity. * * Once it has such a polyhedron, it checks whether the faces match correctly. * If they all do, it's done. Otherwise it uses the pairs of nonmatching * faces to find new group elements which are guaranteed to slice off at * least one vertex of the candidate polyhedron. In this way it is guaranteed * to arrive at the true Dirichlet domain in a finite (and typically small) * number of steps. The algorithm is described in more detail in the code * itself. * * For closed manifolds the algorithm works quickly and reliably. For * large cusped manifolds, computing a candidate polyhedron satisfying * (1) and (2) above is a bottleneck. I hope to eventually find a quicker * way to compute it. * * The Dirichlet domain algorithm uses matrices in O(3,1). Despite their * many theoretical advantages, they lead to rapidly accumulating numerical * errors when the manifold is at all large (see Dirichlet_precision.c for * a more detailed discussion of the problem). Occasionally the algorithm will * encounter topological inconsistencies and fail, and will return NULL. * Fortunately the problem can usually be corrected. The root of the problem * is the need to decide whether two closely spaced vertices should be * interpreted as distinct vertices of the Dirichlet domain, or as two images * of a single vertex, differing only by roundoff error. The decision is * guided by the value of the vertex_epsilon parameter. Use a small value * (e.g. 1e-12) to compute Dirichlet domains with many small faces, e.g. a * (100,1) Dehn surgery on the figure eight knot. Use a larger value when * computing simple Dirichlet domains for larger manifolds. (Larger manifolds * tend to have less accurately defined face pairings, due to the numerical * problems in O(3,1).) The most extreme vertex resolution values (say less * than 1e-16 or more than 1e-2) usually fail, so try to stay within that * range unless you're really desperate. * * Technical note: the WEPolyhedron's num_vertices, num_edges and num_faces * fields are not maintained during the construction, but are set at the end. * * By the way, compute_Dirichlet_domain() assumes no nontrivial group element * fixes the origin. In other words, it assumes that the basepoint does not * lie in an orbifold's singular set. Dirichlet_from_generators_with_displacement() * in Dirichlet.c makes sure that doesn't happen. */ #include "kernel.h" #include "Dirichlet.h" #include /* needed for qsort() */ /* * The Dirichlet domain computation begins with a large cube enclosing the * projective model of hyperbolic space, and intersects it with the half * spaces corresponding to the initial set of generators. CUBE_SIZE * is half the length of the cube's side. CUBE_SIZE must be at least 1.0 * so that the Dirichlet domain fits inside, but it shouldn't be so large * that numerical accuracy suffers. */ #define CUBE_SIZE 2.0 /* * If the distance between the basepoint (1, 0, 0, 0) and one * of its translates is less than about ERROR_EPSILON, * compute_normal_to_Dirichlet_plane() returns func_failed. If it * weren't for roundoff error this should never happen, since we * take care to move t moves towards a maximum of the injectivity * radius it should go still further from the singular set. But * roundoff error may produce elements which should be the identity, * but aren't close enough to have been recognized as such. * Note that ERROR_EPSILON needs to be coordinated with MATRIX_EPSILON * in Dirichlet.h, but there not much slack between them. */ #define ERROR_EPSILON 1e-4 /* * A vertex is considered hyperideal iff o31_inner_product(vertex->x, vertex->x) * is greater than HYPERIDEAL_EPSILON. Recall that vertex->x[0] is always 1, * so that if the vertex is at a Euclidean distance (1 + epsilon) from the origin * in the Klein model, o31_inner_product(vertex->x, vertex->x) will be * -1 + (1 + epsilon)^2 = 2*epsilon. * * 96/9/5 Changed HYPERIDEAL_EPSILON from 1e-4 to 1e-3 to avoid * infinite loop when computing Dirichlet domain for x004 with * "coarse" vertex resolution. */ #define HYPERIDEAL_EPSILON 1e-3 /* * verify_group() considers one O31Matrix to be simpler than * another if its height is at least VERIFY_EPSILON less. */ #define VERIFY_EPSILON 1e-4 /* * intersect_with_halfspaces() will report a failure if the MatrixPair * it is given deviates from O(3,1) by more than DEVIATION_EPSILON. */ #define DEVIATION_EPSILON 1e-3 static WEPolyhedron *initial_polyhedron(MatrixPairList *gen_list, double vertex_epsilon); static WEPolyhedron *new_WEPolyhedron(void); static void make_cube(WEPolyhedron *polyhedron); static FuncResult slice_polyhedron(WEPolyhedron *polyhedron, MatrixPairList *gen_list); static FuncResult intersect_with_halfspaces(WEPolyhedron *polyhedron, MatrixPair *matrix_pair); static Boolean same_image_of_origin(O31Matrix m0, O31Matrix m1); static FuncResult slice_with_hyperplane(WEPolyhedron *polyhedron, O31Matrix m, WEFace **new_face); static FuncResult compute_normal_to_Dirichlet_plane(O31Matrix m, O31Vector normal_vector); static void compute_vertex_to_hyperplane_distances(WEPolyhedron *polyhedron, O31Vector normal_vector); static Boolean positive_vertices_exist(WEPolyhedron *polyhedron); static void cut_edges(WEPolyhedron *polyhedron); static FuncResult cut_faces(WEPolyhedron *polyhedron); static FuncResult check_topology_of_cut(WEPolyhedron *polyhedron); static void install_new_face(WEPolyhedron *polyhedron, WEFace *new_face); static Boolean face_still_exists(WEPolyhedron *polyhedron, WEFace *face); static Boolean has_hyperideal_vertices(WEPolyhedron *polyhedron); static void compute_all_products(WEPolyhedron *polyhedron, MatrixPairList *product_list); static void poly_to_current_list(WEPolyhedron *polyhedron, MatrixPairList *current_list); static void current_list_to_product_tree(MatrixPairList *current_list, MatrixPair **product_tree); static Boolean already_on_product_tree(O31Matrix product, MatrixPair *product_tree); static void add_to_product_tree(O31Matrix product, MatrixPair **product_tree); static void product_tree_to_product_list(MatrixPair *product_tree, MatrixPairList *product_list); static void append_tree_to_list(MatrixPair *product_tree, MatrixPair *list_end); static FuncResult check_faces(WEPolyhedron *polyhedron); static FuncResult pare_face(WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared); static FuncResult pare_mated_face(WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared); static FuncResult pare_mateless_face(WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared); static FuncResult try_this_alpha(O31Matrix *alpha, WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared); static void count_cells(WEPolyhedron *polyhedron); static void sort_faces(WEPolyhedron *polyhedron); static int CDECL compare_face_distance(const void *ptr1, const void *ptr2); static Boolean verify_faces(WEPolyhedron *polyhedron); static FuncResult verify_group(WEPolyhedron *polyhedron, MatrixPairList *gen_list); static void rewrite_gen_list(WEPolyhedron *polyhedron, MatrixPairList *gen_list); WEPolyhedron *compute_Dirichlet_domain( MatrixPairList *gen_list, double vertex_epsilon) { WEPolyhedron *polyhedron; /* * Initialize the polyhedron to be the intersection of the * half spaces defined by the elements of the gen_list. */ polyhedron = initial_polyhedron(gen_list, vertex_epsilon); /* * If topological problems caused by roundoff errors get * in the way, report a failure. */ if (polyhedron == NULL) return NULL; /* * Check whether pairs of faces match. If a pair fails to * match exactly, use the corresponding group elements to * create a new face plane which slices off another bit of * the WEPolyhedron. Perform a similar operation if some * face lacks a mate. If roundoff errors get in the way, * free the polyhedron and return NULL. */ if (check_faces(polyhedron) == func_failed) { free_Dirichlet_domain(polyhedron); return NULL; } /* * Count the number of vertices, edges and faces. */ count_cells(polyhedron); /* * Put the faces in order of increasing distance from the basepoint. */ sort_faces(polyhedron); /* * Count the number of edges incident to each face. If a face and * its mate don't have the same number of incident edges, free the * polyhedron and return NULL. */ if (verify_faces(polyhedron) == func_failed) { free_Dirichlet_domain(polyhedron); return NULL; } /* * Verify that the face pairings generate the group as * originally specified by gen_list. This ensures that * we have a Dirichlet domain for the manifold itself, * not some finite-sheeted cover. */ if (verify_group(polyhedron, gen_list) == func_failed) { free_Dirichlet_domain(polyhedron); return NULL; } /* * Discard the old list to generators and replace it with the * set of face pairing transformations (plus the identity). * They will be in ascending order, because we've already sorted * the face list. */ rewrite_gen_list(polyhedron, gen_list); return polyhedron; } static WEPolyhedron *initial_polyhedron( MatrixPairList *gen_list, double vertex_epsilon) { WEPolyhedron *polyhedron; MatrixPairList product_list; /* * Allocate a WEPolyhedron data structure, and initialize * its doubly-linked lists of vertices, edges and faces. */ polyhedron = new_WEPolyhedron(); /* * Set the vertex_epsilon; */ polyhedron->vertex_epsilon = vertex_epsilon; /* * Initialize the polyhedron to be a big cube. */ make_cube(polyhedron); /* * Intersect the cube with the halfspaces defined by the elements * of gen_list. If topological problems due to roundoff error * get in the way, free the polyhedron and return NULL. */ if (slice_polyhedron(polyhedron, gen_list) == func_failed) { free_Dirichlet_domain(polyhedron); return NULL; } /* * While hyperideal vertices remain (i.e. vertices lying beyond the * sphere at infinity), compute all products of face->group_elements, * and intersect the polyhedron with the corresponding half spaces. */ while (has_hyperideal_vertices(polyhedron) == TRUE) { compute_all_products(polyhedron, &product_list); if (slice_polyhedron(polyhedron, &product_list) == func_failed) { free_matrix_pairs(&product_list); free_Dirichlet_domain(polyhedron); return NULL; } free_matrix_pairs(&product_list); } return polyhedron; } static WEPolyhedron *new_WEPolyhedron() { WEPolyhedron *new_polyhedron; new_polyhedron = NEW_STRUCT(WEPolyhedron); new_polyhedron->num_vertices = 0; new_polyhedron->num_edges = 0; new_polyhedron->num_faces = 0; new_polyhedron->vertex_list_begin.prev = NULL; new_polyhedron->vertex_list_begin.next = &new_polyhedron->vertex_list_end; new_polyhedron->vertex_list_end .prev = &new_polyhedron->vertex_list_begin; new_polyhedron->vertex_list_end .next = NULL; new_polyhedron->edge_list_begin.prev = NULL; new_polyhedron->edge_list_begin.next = &new_polyhedron->edge_list_end; new_polyhedron->edge_list_end .prev = &new_polyhedron->edge_list_begin; new_polyhedron->edge_list_end .next = NULL; new_polyhedron->face_list_begin.prev = NULL; new_polyhedron->face_list_begin.next = &new_polyhedron->face_list_end; new_polyhedron->face_list_end .prev = &new_polyhedron->face_list_begin; new_polyhedron->face_list_end .next = NULL; new_polyhedron->vertex_class_begin.prev = NULL; new_polyhedron->vertex_class_begin.next = &new_polyhedron->vertex_class_end; new_polyhedron->vertex_class_end .prev = &new_polyhedron->vertex_class_begin; new_polyhedron->vertex_class_end .next = NULL; new_polyhedron->edge_class_begin.prev = NULL; new_polyhedron->edge_class_begin.next = &new_polyhedron->edge_class_end; new_polyhedron->edge_class_end .prev = &new_polyhedron->edge_class_begin; new_polyhedron->edge_class_end .next = NULL; new_polyhedron->face_class_begin.prev = NULL; new_polyhedron->face_class_begin.next = &new_polyhedron->face_class_end; new_polyhedron->face_class_end .prev = &new_polyhedron->face_class_begin; new_polyhedron->face_class_end .next = NULL; return new_polyhedron; } static void make_cube( WEPolyhedron *polyhedron) { /* * This function accepts a pointer to an empty WEPolyhedron (i.e. the * WEPolyhedron data structure is allocated and the doubly linked lists * are initialized, but the WEPolyhedron has no vertices, edges or * faces), and sets it to be a cube of side twice CUBE_SIZE. Each face * of the cube has its group_element field set to NULL to indicate that * it doesn't correspond to any element of the group. The vertices, * faces and edges of the cube are indexed as shown in the following * diagram of a cut open cube. * * 1-------5->-------3 * | | * | | * 2 3 * | 5 | * V V * | | * | | * 1-------2->-------5-------7->-------7-------<-3-------3 * | | | | * | | | | * ^ ^ ^ ^ * | 2 | 1 | 3 | * 8 9 11 10 * | | | | * | | | | * 0-------0->-------4-------6->-------6-------<-1-------2 * | | * | | * ^ ^ * | 4 | * 0 1 * | | * | | * 0-------4->-------2 * | | * | | * 8 10 * | 0 | * V V * | | * | | * 1-------5->-------3 */ int i, j, k; WEVertex *initial_vertices[8]; WEEdge *initial_edges[12]; WEFace *initial_faces[6]; const static int evdata[12][2] = { {0, 4}, {2, 6}, {1, 5}, {3, 7}, {0, 2}, {1, 3}, {4, 6}, {5, 7}, {0, 1}, {4, 5}, {2, 3}, {6, 7} }; const static int eedata[12][2][2] = { {{ 8, 4}, { 9, 6}}, {{ 4, 10}, { 6, 11}}, {{ 5, 8}, { 7, 9}}, {{10, 5}, {11, 7}}, {{ 0, 8}, { 1, 10}}, {{ 8, 2}, {10, 3}}, {{ 9, 0}, {11, 1}}, {{ 2, 9}, { 3, 11}}, {{ 4, 0}, { 5, 2}}, {{ 0, 6}, { 2, 7}}, {{ 1, 4}, { 3, 5}}, {{ 6, 1}, { 7, 3}} }; const static int efdata[12][2] = { {2, 4}, {4, 3}, {5, 2}, {3, 5}, {4, 0}, {0, 5}, {1, 4}, {5, 1}, {0, 2}, {2, 1}, {3, 0}, {1, 3} }; const static int fdata[6] = {4, 6, 0, 1, 0, 2}; /* * If we were keeping track of the number of vertices, edges and faces, * we'd want to set the counts here. But we're not, so we won't. * * polyhedron->num_vertices = 8; * polyhedron->num_edges = 12; * polyhedron->num_faces = 6; */ for (i = 0; i < 8; i++) { initial_vertices[i] = NEW_STRUCT(WEVertex); INSERT_BEFORE(initial_vertices[i], &polyhedron->vertex_list_end); } for (i = 0; i < 12; i++) { initial_edges[i] = NEW_STRUCT(WEEdge); INSERT_BEFORE(initial_edges[i], &polyhedron->edge_list_end); } for (i = 0; i < 6; i++) { initial_faces[i] = NEW_STRUCT(WEFace); INSERT_BEFORE(initial_faces[i], &polyhedron->face_list_end); } for (i = 0; i < 8; i++) { initial_vertices[i]->x[0] = 1.0; initial_vertices[i]->x[1] = (i & 4) ? CUBE_SIZE : -CUBE_SIZE; initial_vertices[i]->x[2] = (i & 2) ? CUBE_SIZE : -CUBE_SIZE; initial_vertices[i]->x[3] = (i & 1) ? CUBE_SIZE : -CUBE_SIZE; } for (i = 0; i < 12; i++) { for (j = 0; j < 2; j++) /* j = tail, tip */ initial_edges[i]->v[j] = initial_vertices[evdata[i][j]]; for (j = 0; j < 2; j++) /* j = tail, tip */ for (k = 0; k < 2; k++) /* k = left, right */ initial_edges[i]->e[j][k] = initial_edges[eedata[i][j][k]]; for (j = 0; j < 2; j++) /* j = left, right */ initial_edges[i]->f[j] = initial_faces[efdata[i][j]]; } for (i = 0; i < 6; i++) { initial_faces[i]->some_edge = initial_edges[fdata[i]]; initial_faces[i]->mate = NULL; initial_faces[i]->group_element = NULL; } } static FuncResult slice_polyhedron( WEPolyhedron *polyhedron, MatrixPairList *gen_list) { MatrixPair *matrix_pair; /* * Intersect the polyhedron with the pair of halfspaces corresponding * to each MatrixPair on gen_list, except for the identity. * * Technical note: intersect_with_halfspaces() may set some faces' * face->clean fields to FALSE. We aren't using the face->clean field * at this point, so these operations are unnecessary but harmless. * check_faces() uses the face->clean fields, and calls low-level * functions which call intersect_with_halfspaces(). */ for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) if (o31_equal(matrix_pair->m[0], O31_identity, MATRIX_EPSILON) == FALSE) if (intersect_with_halfspaces(polyhedron, matrix_pair) == func_failed) return func_failed; return func_OK; } static FuncResult intersect_with_halfspaces( WEPolyhedron *polyhedron, MatrixPair *matrix_pair) { /* * Intersect the polyhedron with the halfspaces corresponding * to each of the O31Matrices in the matrix_pair. This will create * 0, 1 or 2 new faces, depending on whether the hyperplanes actually * slice off a nontrivial part of the polyhedron. */ int i; WEFace *new_face[2]; /* * If the given MatrixPair deviates too far from O(3,1), report a failure. * (It suffices to check matrix_pair->m[0], because matrix_pair->m[1] is * computed as the transpose of matrix_pair->m[0] with appropriate * elements negated.) */ if (o31_deviation(matrix_pair->m[0]) > DEVIATION_EPSILON) return func_failed; /* * To allow for orbifolds as well as manifolds, we must be prepared * for the possibility that matrix_pair->m[0] and matrix_pair->m[1] * define the same hyperplane, which will be its own mate. Let * f0 and f1 be the isometries of hyperbolic 3-space defined by * matrix_pair->m[0] and matrix_pair->m[1], respectively. * * Proposition. The following are equivalent. * * (1) The hyperplanes defined by f0 and f1 are equal. * * (2) f0(origin) = f1(origin) * * (3) f0(f0(origin)) = origin * * (4) f0 is a half turn about an axis which passes orthogonally * through the midpoint of the segment connecting the * origin to f0(origin). * or * f0 is a reflection in the plane which passes orthogonally * through the midpoint of the segment connecting the * origin to f0(origin). * or * f0 is a reflection through the midpoint of the segment * connecting the origin to f0(origin). * * (5) f0 = f1 * * Proof. Throughout this proof, keep in mind that we first gave the * basepoint a small random displacement, and then perhaps moved it * towards a point of maximum injectivity radius, so we may assume * that its images under the covering transformation group are distinct. * * (1 => 2). Obvious. * * (2 => 3). f0(f0(origin)) = f0(f1(origin)) = origin, because * f0 and f1 are inverses of one another. * * (3 => 4). f0 interchanges the origin and f0(origin). Therefore * if fixes the midpoint M of the segment S connecting the origin * to f0(origin). Furthermore, the plane P which passes through * M and is orthogonal to S will be taken to itself (setwise, but * probably not pointwise). Consider the possibilites for the * action of f0 on P. * * Case 1. f0 preserves the orientation of hyperbolic 3-space, and * therefore reverses the orientation of P. Because f0 fixes the * point M, it must act on P as a reflection in some line L which * contains M and is contained in P. It follows that f0 acts on * hyperbolic 3-space as a half turn about the line L. * * Case 2. f0 reverses the orientation of hyperbolic 3-space, and * therefore preserves the orientation of P. Because f0 fixes the * point M, it must act on P as a rotation about M through some angle * theta. This implies that f0^2 acts on hyperbolic 3-space as a * rotation about the line containing S through an angle 2*theta. * But the nondegenerate choice of basepoint (cf. the comments at the * beginning of this proof) implies that 2*theta = 0 (mod 2pi). * So theta = 0 or pi (mod 2pi). If theta = 0, f0 acts on hyperbolic * 3-space as a reflection in the plane P. If theta = pi, f0 acts on * hyperbolic 3-space as a reflection in the point M. * * (4 => 5). In each of the three cases lists (half turn about axis, * reflection in plane, reflection in point) f0 is its own inverse. * Therefore f0 = f1. * * (5 => 1). Trivial. * * Q.E.D. */ if (same_image_of_origin(matrix_pair->m[0], matrix_pair->m[1]) == TRUE) { /* * The images of the origin are the same, so the above proposition * implies that the matrices must be the same as well. As a * guard against errors, let's check that this really is the case. */ if (o31_equal(matrix_pair->m[0], matrix_pair->m[1], MATRIX_EPSILON) == FALSE) uFatalError("intersect_with_halfspaces", "Dirichlet_construction"); /* * For documentation on these operations, * please see the generic case below. */ if (slice_with_hyperplane(polyhedron, matrix_pair->m[0], &new_face[0]) == func_failed) return func_failed; if (new_face[0] != NULL) new_face[0]->mate = new_face[0]; return func_OK; } /* * Slice the polyhedron with each of the two hyperplanes, and * record the newly created face, if any. If no new face is created, * slice_with_hyperplane() sets new_face[i] to NULL. If error * conditions (e.g. due to roundoff error) are encountered, return * func_failed. */ for (i = 0; i < 2; i++) if (slice_with_hyperplane(polyhedron, matrix_pair->m[i], &new_face[i]) == func_failed) return func_failed; /* * The first hyperplane might have created a new face, only to have it * completely removed by the second hyperplane. (Technical note: * this check relies on the fact that slice_with_hyperplane() frees the * faces it removes AFTER allocating the new face.) */ if (new_face[0] != NULL && face_still_exists(polyhedron, new_face[0]) == FALSE) new_face[0] = NULL; /* * Tell the new_faces about each other. */ for (i = 0; i < 2; i++) if (new_face[i] != NULL) new_face[i]->mate = new_face[!i]; return func_OK; } static Boolean same_image_of_origin( O31Matrix m0, O31Matrix m1) { int i; for (i = 0; i < 4; i++) if (fabs(m0[i][0] - m1[i][0]) > MATRIX_EPSILON) return FALSE; return TRUE; } static FuncResult slice_with_hyperplane( WEPolyhedron *polyhedron, O31Matrix m, WEFace **new_face) { /* * Remove the parts of the polyhedron cut off by the hyperplane P * determined by the matrix m. Set all fields except the mate field * of the newly created face, which is temporarily set to NULL here, * and set correctly by the calling routine. */ O31Vector normal_vector; /* * Initialize *new_face to NULL, just in case we detect an error * condition and return before creating *new_face. */ *new_face = NULL; /* * Compute a vector normal to the hyperplane P determined by * the matrix m. The normal vector is, of course, defined relative * to the Minkowski space metric, not the Euclidean metric. */ if (compute_normal_to_Dirichlet_plane(m, normal_vector) == func_failed) return func_failed; /* * Let P' be the restriction of the hyperplane P to the projective * model, that is, to the 3-space x[0] == 1. Measure the distance * from P' to each vertex of the polyhedron, and store the result * in the vertex's distance_to_plane field. (Actually we compute * numbers which are proportional to the vertices' distances to P', * but that's good enough.) In addition, set each vertex's * which_side_of_plane field to +1, 0 or -1 according to whether, * after accounting for possible roundoff error, the distance_to_plane * is positive, zero or negative, respectively. */ compute_vertex_to_hyperplane_distances(polyhedron, normal_vector); /* * If no vertices have which_side_of_plane == +1, then there is * nothing to be cut off, so return func_OK immediately. * If no vertices have which_side_of_plane == -1, then we're * cutting off everything, which merits a call to uFatalError(). */ if (positive_vertices_exist(polyhedron) == FALSE) return func_OK; /* * Introduce a new vertex whereever an edge crosses the plane P'. * Such edges can be recognized by the fact that one endpoint * has which_side_of_plane == +1 while the other has * which_side_of_plane == -1. */ cut_edges(polyhedron); /* * Introduce a new edge whereever a face crosses the plane P'. * If any face has more than two 0-vertices (as might occur * due to roundoff error) return func_failed. */ if (cut_faces(polyhedron) == func_failed) return func_failed; /* * Check that the curve we're cutting along is a single, * simple closed curve. * * Proposition. The cut locus divides the surface of the ball into * two combinatorial disks. All the interior vertices on one side * are positive (which_side_of_plane == +1) and all those on the * other side are negative (which_side_of_plane == -1). * * Proof. check_topology_of_cut() insures that the cut locus is a * simple closed curve. The Schoenflies theorem says that a simple * closed curve divides the surface of a 2-sphere into two disks. * The function positive_vertices_exist() (cf. above) has already * checked that both positive and negative vertices exist. The * function cut_edges() (cf. above) guarantees that no edge connects * a positive vertex to a negative one. Therefore it follows that * one of the Schoenflies disks contains only positive vertices in its * interior, and the other contains only negative vertices. Q.E.D. * * Corollary. If we remove * * (1) all positive vertices, * (2) all edges incident to at least one positive vertex, and * (3) all faces incident to at least one positive vertex, * * and replace them with a single new face, the new domain will still * be a combinatorial ball. * * Proof. This is almost obvious as a corollary of the above * proposition. We just need to verify that conditions (1)-(3) * characterize the vertices, edges and face in the interior of * the positive Schoenflies disk. * * (1) We've already seen that all interior vertices must be positive. * * (2) An interior edge can't have two vertices on the boundary of the * Schoenflies disk, because if it did it would be a 0-edge and * would be part of the cut locus. Therefore at least one vertex * is positive. * * (3) If all the vertices of a given face were 0-vertices, its * boundary would have to be the entire cut locus, and no vertices * could exist in its interior. This would contradict the * existence of both positive and negative vertices. Therefore * at least one vertex of an interior face must be positive. * * Q.E.D. */ if (check_topology_of_cut(polyhedron) == func_failed) return func_failed; /* * Allocate the new_face. */ *new_face = NEW_STRUCT(WEFace); /* * Set its mate field to NULL and its clean field to FALSE. */ (*new_face)->mate = NULL; (*new_face)->clean = FALSE; /* * Allocate space for its group_element, and copy in the matrix m. */ (*new_face)->group_element = NEW_STRUCT(O31Matrix); o31_copy(*(*new_face)->group_element, m); /* * Throw away the vertices, edges and faces which are no longer * needed, and install the new face. */ install_new_face(polyhedron, *new_face); return func_OK; } static FuncResult compute_normal_to_Dirichlet_plane( O31Matrix m, O31Vector normal_vector) { /* * Compute a vector normal to the hyperplane P determined by * the matrix m. The normal vector is, of course, defined relative * to the Minkowski space metric, not the Euclidean metric. * * The group element represented by the matrix m takes the basepoint * b = (1, 0, 0, 0) to one of its translates m(b). The hyperplane P * determined by m has normal vector m(b) - b. Note that m(b) is * just the first column of the matrix m. * * For numerical reasons, we want to scale the normal vector so that * its largest entries have absolute value one. This gives * compute_vertex_to_hyperplane_distances() a consistent idea of * what sort of accuracy to expect when it computes the inner product * of the normal vector with the vertices of the polyhedron. * The absolute values of the entries in the (unscaled) normal vector * grow exponentially with the translation distance, so without the * scaling the magnitude of the anticipated error would also grow * exponentially. With the scaling, the anticipated error will be * on the order of DBL_EPSILON. (This is just the error in the inner * product computation -- other errors are of course introduced * elsewhere!) */ int i; double max_abs; /* * Read m(b) from the first column of the matrix m. */ for (i = 0; i < 4; i++) normal_vector[i] = m[i][0]; /* * Subtract off b = (1, 0, 0, 0) to get the normal_vector. */ normal_vector[0] -= 1.0; /* * Scale the normal_vector so that the largest absolute value of its * entries is one. * * Technical digression. Except for very small translation distances * it would be good enough to simply divide through by * fabs(normal_vector[0]). This is because normal_vector[] is * numerically similar to a lightlike vector, with a large [0] * component whose square almost cancels the squares of the [1], [2] * and [3] components. (Proof: m(b) is timelike while m(b) - b = * m(b) - 1.0 is spacelike.) Here, though, we'll take a more * pedestrian approach. */ /* * Compute the largest absolute value of an entry. */ max_abs = 0.0; for (i = 0; i < 4; i++) if (fabs(normal_vector[i]) > max_abs) max_abs = fabs(normal_vector[i]); /* * If max_abs is close to zero, something has gone wrong. * In particular, we don't allow m(b) = b. For an orbifold, we should * have already moved the basepoint away from the singular set, and * as it moves towards a maximum of the injectivity radius it should * go still further from the singular set. */ if (max_abs < ERROR_EPSILON) return func_failed; /* * Divide the normal vector by max_abs. */ for (i = 0; i < 4; i++) normal_vector[i] /= max_abs; return func_OK; } static void compute_vertex_to_hyperplane_distances( WEPolyhedron *polyhedron, O31Vector normal_vector) { WEVertex *vertex; /* * Compute the inner product of each vertex's coordinates x[] with the * normal_vector. Because each vertex lies in the 3-space x[0] == 1, * the inner product is proportional to the * 3-dimensional Euclidean distance in the projective model from the * vertex to the Dirichlet plane defined by the normal_vector. * * For numerical considerations, see compute_normal_to_Dirichlet_plane() * above. (I may eventually also have to do some additional numerical * thinking here.) */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { vertex->distance_to_plane = o31_inner_product(vertex->x, normal_vector); /* * Decide whether the vertex lies beyond (+1), beneath (-1), * or on (0) the Dirichlet plane. Allow for possible roundoff * error. */ if (vertex->distance_to_plane > polyhedron->vertex_epsilon) vertex->which_side_of_plane = +1; else if (vertex->distance_to_plane < - polyhedron->vertex_epsilon) vertex->which_side_of_plane = -1; else vertex->which_side_of_plane = 0; } } static Boolean positive_vertices_exist( WEPolyhedron *polyhedron) { WEVertex *vertex; Boolean positive_vertices_exist, negative_vertices_exist; positive_vertices_exist = FALSE; negative_vertices_exist = FALSE; for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { if (vertex->which_side_of_plane == +1) positive_vertices_exist = TRUE; if (vertex->which_side_of_plane == -1) negative_vertices_exist = TRUE; } if (negative_vertices_exist == FALSE) uFatalError("positive_vertices_exist", "Dirichlet_construction"); return positive_vertices_exist; } static void cut_edges( WEPolyhedron *polyhedron) { WEEdge *edge; int i, j; double t; O31Vector cut_point; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (i = 0; i < 2; i++) if (edge->v[ i]->which_side_of_plane == -1 && edge->v[!i]->which_side_of_plane == +1) { /* * Place the new vertex at the point where * the edge crosses the Dirichlet plane. */ t = ( 0.0 - edge->v[tail]->distance_to_plane) / (edge->v[tip]->distance_to_plane - edge->v[tail]->distance_to_plane); for (j = 0; j < 4; j++) cut_point[j] = (1.0 - t) * edge->v[tail]->x[j] + t * edge->v[tip]->x[j]; split_edge(edge, cut_point, TRUE); } } void split_edge( WEEdge *old_edge, O31Vector cut_point, Boolean set_Dirichlet_construction_fields) { /* * This function is also called in Dirichlet_extras.c. 94/10/4 JRW */ /* * Introduce a new vertex in old_edge at the cut_point. * * before after * * \ / \ / * \ / \ / * | | * | old | * | edge ^ * | | * old | | new * edge ^ o vertex * | | * | new | * | edge ^ * | | * | | * / \ / \ * / \ / \ */ WEEdge *new_edge, *left_neighbor, *right_neighbor; WEVertex *new_vertex; /* * Allocate space for the new_edge and new_vertex. */ new_edge = NEW_STRUCT(WEEdge); new_vertex = NEW_STRUCT(WEVertex); /* * Set the fields of the new_edge. */ new_edge->v[tail] = old_edge->v[tail]; new_edge->v[tip] = new_vertex; new_edge->e[tail][left] = old_edge->e[tail][left]; new_edge->e[tail][right] = old_edge->e[tail][right]; new_edge->e[tip ][left] = old_edge; new_edge->e[tip ][right] = old_edge; new_edge->f[left] = old_edge->f[left]; new_edge->f[right] = old_edge->f[right]; /* * Adjust the fields of the old_edge. */ old_edge->v[tail] = new_vertex; old_edge->e[tail][left] = new_edge; old_edge->e[tail][right] = new_edge; /* * Adjust the fields of the other two edges which "see" new_edge. */ left_neighbor = new_edge->e[tail][left]; if (left_neighbor->v[tip] == new_edge->v[tail]) left_neighbor->e[tip][left] = new_edge; else if (left_neighbor->v[tail] == new_edge->v[tail]) left_neighbor->e[tail][right] = new_edge; else uFatalError("split_edge", "Dirichlet_construction"); right_neighbor = new_edge->e[tail][right]; if (right_neighbor->v[tip] == new_edge->v[tail]) right_neighbor->e[tip][right] = new_edge; else if (right_neighbor->v[tail] == new_edge->v[tail]) right_neighbor->e[tail][left] = new_edge; else uFatalError("split_edge", "Dirichlet_construction"); /* * Set the fields for the new vertex. */ o31_copy_vector(new_vertex->x, cut_point); if (set_Dirichlet_construction_fields) { new_vertex->distance_to_plane = 0.0; new_vertex->which_side_of_plane = 0; } /* * Insert new_edge and new_vertex on their respective lists. * Note that the new_edge goes on the list just before the old_edge; * this is fine because we don't need to check the new_edge in the * loop in cut_edges() above, or in subdivide_edges_where_necessary() * in Dirichlet_extras.c. */ INSERT_BEFORE(new_edge, old_edge); INSERT_BEFORE(new_vertex, old_edge->v[tip]); /* * Dirichlet_construction.c isn't maintaining the faces' num_sides * fields, but Dirichlet_extras.c is. */ old_edge->f[left] ->num_sides++; old_edge->f[right]->num_sides++; } static FuncResult cut_faces( WEPolyhedron *polyhedron) { WEFace *face; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (cut_face_if_necessary(face, TRUE) == func_failed) return func_failed; return func_OK; } /* * cut_face_if_necessary() returns func_failed if any topological * abnormality is found. See details below. * It returns func_OK otherwise. */ FuncResult cut_face_if_necessary( WEFace *face, Boolean called_from_Dirichlet_construction) { /* * Dirichlet_extras.c also calls cut_face_if_necessary(). * The argument called_from_Dirichlet_construction tells who's called us, * so we can handle the face pairing accordingly. 94/10/5 JRW */ int i, count; WEEdge *edge, *edge_before_vertex[2], *edge_after_vertex[2], *temp_edge; WEEdge *new_edge; WEFace *new_face; /* * Edges passing from the negative to the positive side of the * hyperplane have already been cut in cut_edges(), so we expect * each face to be of one of the following types. A "0-vertex" is * a vertex whose which_side_of_plane field is 0. * * No 0-vertices. The face should be left alone. * * One 0-vertex. The face should be left alone. * * Two 0-vertices. * * If the 0-vertices are adjacent, the face should be left alone. * * If the 0-vertices are not adjacent, the face should be cut. * * In each of the above cases, we return func_OK. * If more than two 0-vertices occur, we return func_failed. * (For example, roundoff errors in especially tiny faces might * cause a face to have three 0-vertices.) */ /* * To simplify the subsequent code, reorient the WEEdges so all are * directed counterclockwise around the face. */ all_edges_counterclockwise(face, FALSE); /* * Count the number of 0-vertices. */ count = 0; edge = face->some_edge; do { if (edge->v[tip]->which_side_of_plane == 0) { /* * If this is our third 0-vertex, something has gone * wrong with the roundoff errors. */ if (count == 2) return func_failed; /* * Note which edge precedes the 0-vertex. */ edge_before_vertex[count] = edge; count++; } edge = edge->e[tip][left]; } while (edge != face->some_edge); /* * If there are fewer than two 0-vertices, return. */ if (count < 2) return func_OK; /* * Note which edges follow the 0-vertices. */ for (i = 0; i < 2; i++) edge_after_vertex[i] = edge_before_vertex[i]->e[tip][left]; /* * If the 0-vertices are adjacent, return. */ for (i = 0; i < 2; i++) if (edge_after_vertex[i] == edge_before_vertex[!i]) return func_OK; /* * Cut the face in two by introducing a new edge connecting * the two 0-vertices. * * Before: * / \ * edge_before_vertex[1] \ * / \_ * |/_ |\ edge_after_vertex[0] * / \ * / \ * / face \ * \ / * \ / * \ _/ * _\| /| edge_before_vertex[0] * \ / * edge_after_vertex[1] / * \ / * * * After: * / \ * edge_before_vertex[1] \ * / \_ * |/_ |\ edge_after_vertex[0] * / new_face \ * / \ * /____________/______________\ * \ \ new_edge / * \ / * \ face _/ * _\| /| edge_before_vertex[0] * \ / * edge_after_vertex[1] / * \ / */ /* * First, for convenience, make sure the lower half is the * half we want to keep. (Because there are precisely two * incident 0-vertices, and no edge connects minus directly to plus, * we know that all the signs on a given side of the new_edge * must be the same.) */ if (edge_before_vertex[0]->v[tail]->which_side_of_plane == -1 && edge_after_vertex [0]->v[tip] ->which_side_of_plane == +1) { /* * Great. This is what we want. Do nothing. */ } else if (edge_before_vertex[0]->v[tail]->which_side_of_plane == +1 && edge_after_vertex [0]->v[tip] ->which_side_of_plane == -1) { /* * Swap the sides. */ temp_edge = edge_before_vertex[0]; edge_before_vertex[0] = edge_before_vertex[1]; edge_before_vertex[1] = temp_edge; temp_edge = edge_after_vertex[0]; edge_after_vertex[0] = edge_after_vertex[1]; edge_after_vertex[1] = temp_edge; } else /* * The two sides have the same signs. Something has gone wrong. */ return func_failed; /* * Now allocate and install the new_edge and new_face. */ new_edge = NEW_STRUCT(WEEdge); new_face = NEW_STRUCT(WEFace); new_edge->v[tail] = edge_before_vertex[0]->v[tip]; new_edge->v[tip] = edge_before_vertex[1]->v[tip]; new_edge->e[tail][left] = edge_before_vertex[0]; new_edge->e[tail][right] = edge_after_vertex[0]; new_edge->e[tip] [left] = edge_after_vertex[1]; new_edge->e[tip] [right] = edge_before_vertex[1]; edge_before_vertex[0]->e[tip] [left] = new_edge; edge_after_vertex [0]->e[tail][left] = new_edge; edge_before_vertex[1]->e[tip] [left] = new_edge; edge_after_vertex [1]->e[tail][left] = new_edge; for (edge = edge_after_vertex[0]; edge != new_edge; edge = edge->e[tip][left]) edge->f[left] = new_face; new_edge->f[left] = face; new_edge->f[right] = new_face; new_face->some_edge = new_edge; face ->some_edge = new_edge; /* * How we handle the face pairings depends on whether we were called * from Dirichlet_construction.c or Dirichlet_extras.c. */ if (called_from_Dirichlet_construction == TRUE) { /* * new_face will be removed when we clear out all the vertices, edges * and faces detached by the cut. So we set its mate and group_element * fields to NULL. * * face keeps its same mate, which may or may not survive the cut. * (But at least if face->mate gets cut in two, the half that's * retained will still be called face->mate.) */ new_face->mate = NULL; new_face->group_element = NULL; /* * If face has a mate, we can no longer guarantee that it's a subset * of face under the action of the group_element. */ if (face->mate != NULL) face->mate->clean = FALSE; } else /* called from Dirichlet_extras.c */ { face->mate = new_face; new_face->mate = face; new_face->group_element = NEW_STRUCT(O31Matrix); o31_copy(*new_face->group_element, *face->group_element); } /* * We may insert new_face before face, because there is no need for * cut_faces() to check it. new_edge may go anywhere. */ INSERT_BEFORE(new_edge, edge_before_vertex[0]); INSERT_BEFORE(new_face, face); return func_OK; } void all_edges_counterclockwise( WEFace *face, Boolean redirect_neighbor_fields) { WEEdge *edge; edge = face->some_edge; do { if (edge->f[left] != face) redirect_edge(edge, redirect_neighbor_fields); edge = edge->e[tip][left]; } while (edge != face->some_edge); } void redirect_edge( WEEdge *edge, Boolean redirect_neighbor_fields) { WEVertex *temp_vertex; WEEdge *temp_edge; WEFace *temp_face; /* * Swap the tip and tail vertices. */ temp_vertex = edge->v[tail]; edge->v[tail] = edge->v[tip]; edge->v[tip] = temp_vertex; /* * Swap the tail-left and tip-right edges. */ temp_edge = edge->e[tail][left]; edge->e[tail][left] = edge->e[tip][right]; edge->e[tip][right] = temp_edge; /* * Swap the tail-right and tip-left edges. */ temp_edge = edge->e[tail][right]; edge->e[tail][right] = edge->e[tip][left]; edge->e[tip][left] = temp_edge; /* * Swap the left and right faces. */ temp_face = edge->f[left]; edge->f[left] = edge->f[right]; edge->f[right] = temp_face; /* * When called from Dirichlet_extras.c, * we need to preserve some additional fields. */ if (redirect_neighbor_fields) { WEEdge *nbr_edge; WEEdgeSide side, nbr_side; WEEdge *temp_edge; Boolean temp_boolean; /* * Note that the following code works even if * * (1) the two sides of the edge are glued to each other, or * * (2) one or both of the sides is glued to itself. * * To convince yourself of this, trace through the following code * and note that in the degenerate cases the preserves_sides and * preserves_direction flags each get toggled twice. */ /* * Fix up the neighbors. * Toggle their preserves_sides and preserves_direction fields, * but leave their neighbor and preserves_orientation field alone. */ for (side = 0; side < 2; side++) { nbr_edge = edge->neighbor[side]; nbr_side = (edge->preserves_sides[side] ? side : !side); nbr_edge->preserves_sides[nbr_side] = ! nbr_edge->preserves_sides[nbr_side]; nbr_edge->preserves_direction[nbr_side] = ! nbr_edge->preserves_direction[nbr_side]; } /* * Toggle our own preserves_sides and preserves_direction fields. */ for (side = 0; side < 2; side++) { edge->preserves_sides[side] = ! edge->preserves_sides[side]; edge->preserves_direction[side] = ! edge->preserves_direction[side]; } /* * Swap the left and right values. */ temp_edge = edge->neighbor[left]; edge->neighbor[left] = edge->neighbor[right]; edge->neighbor[right] = temp_edge; temp_boolean = edge->preserves_sides[left]; edge->preserves_sides[left] = edge->preserves_sides[right]; edge->preserves_sides[right] = temp_boolean; temp_boolean = edge->preserves_direction[left]; edge->preserves_direction[left] = edge->preserves_direction[right]; edge->preserves_direction[right] = temp_boolean; temp_boolean = edge->preserves_orientation[left]; edge->preserves_orientation[left] = edge->preserves_orientation[right]; edge->preserves_orientation[right] = temp_boolean; } } static FuncResult check_topology_of_cut( WEPolyhedron *polyhedron) { int num_zero_edges, count; WEVertex *vertex, *tip_vertex; WEEdge *edge, *starting_edge; /* * Define a 0-vertex to be a vertex with which_side_of_plane == 0. * * Define a 0-edge to be an edge incident to two 0-vertices. * * We'll be cutting along the union of the 0-edges. We want to * verify that it's a simple closed curve. We do this in two steps: * * (1) Verify that precisely two 0-edges are incident to each 0-vertex. * This insures that the cut locus is a (possibly empty) union of * simple closed curves. * * (2) Verify that the cut locus is connected. */ /* * Initialize the zero_order fields to 0. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->zero_order = 0; /* * Count the number of 0-edges adjacent to each 0-vertex. * * While we're at it, keep a global count of the number of the * number of 0-edges, for use below in checking that the cut locus * is connected. */ num_zero_edges = 0; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->v[tail]->which_side_of_plane == 0 && edge->v[tip] ->which_side_of_plane == 0) { edge->v[tail]->zero_order++; edge->v[tip] ->zero_order++; num_zero_edges++; } /* * Check that all 0-vertices are incident to precisely two 0-edges. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) if (vertex->which_side_of_plane == 0 && vertex->zero_order != 2) return func_failed; /* * We now know that the cut locus is a union of simple * closed curves. We want to check that it is connected. */ /* * Find a starting edge. */ starting_edge = NULL; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->v[tail]->which_side_of_plane == 0 && edge->v[tip] ->which_side_of_plane == 0) { starting_edge = edge; break; } /* * If we didn't find a starting edge, something went horribly wrong. */ if (starting_edge == NULL) uFatalError("check_topology_of_cut", "Dirichlet_construction"); /* * Traverse the cut locus, beginning at the starting_edge. * Count the number of edges in the locus. */ count = 0; edge = starting_edge; do { /* * Count this edge. */ count++; /* * Move on to the next edge. * To do so, rotate clockwise until we find another 0-edge. * For convenience, we reorient the edges as necessary as we * go along, so they always point towards the tip_vertex. * Once we finally get to another 0-edge we reorient it once * more, to preserve the direction of the curve (i.e. we now * want to point away from the old tip_vertex, and towards * what will become the new tip_vertex). */ tip_vertex = edge->v[tip]; do { edge = edge->e[tip][left]; if (edge->v[tip] != tip_vertex) redirect_edge(edge, FALSE); } while (edge->v[tail]->which_side_of_plane != 0); redirect_edge(edge, FALSE); } while (edge != starting_edge); /* * The cut locus will be connected iff count == num_zero_edges. */ if (count == num_zero_edges) return func_OK; else return func_failed; } static void install_new_face( WEPolyhedron *polyhedron, WEFace *new_face) { WEFace *face; WEEdge *edge; WEVertex *vertex; WEFace *dead_face; WEEdge *dead_edge; WEVertex *dead_vertex; WEVertex *tip_vertex; WEEdge *left_edge, *right_edge; int i; /* * The overall plan here is to remove those faces, edges and vertices * (in that order) which have been cut off by the slicing plane, * and install the new_face. */ /* * We'll want to remove precisely those faces which are incident to * at least one positive vertex. (For a proof, see the corollary * in the documentation in slice_with_hyperplane().) To find these * faces, we'll scan the list of edges, and whenever an edge is * incident to at least one positive vertex, we'll see the neighboring * faces' to_be_removed fields to TRUE. (This is easier than * traversing the perimeter of each face explicitly, worrying about * the directions of the edges.) */ /* * Initialize all faces' to_be_removed fields to FALSE. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->to_be_removed = FALSE; /* * Scan the edge list to locate edges -- and hence faces -- which * are incident to at least one positive vertex. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->v[tail]->which_side_of_plane == +1 || edge->v[tip] ->which_side_of_plane == +1) { edge->f[left] ->to_be_removed = TRUE; edge->f[right]->to_be_removed = TRUE; } /* * Set the edge->f[] fields for edges which will be incident to the * new_face. Also make sure the new_face's some_edge field sees * an appropriate edge (note that it's easier to keep setting * new_face->some_edge over and over than to check whether it's * already been set). */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->v[tail]->which_side_of_plane == 0 && edge->v[tip] ->which_side_of_plane == 0) for (i = 0; i < 2; i++) /* i = left, right */ if (edge->f[i]->to_be_removed == TRUE) { edge->f[i] = new_face; new_face->some_edge = edge; } /* * Delete the faces which are being removed. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->to_be_removed == TRUE) { /* * If the face has a mate, set the mate's mate field to NULL * and its clean field to FALSE. */ if (face->mate != NULL && face->mate != face) { face->mate->mate = NULL; face->mate->clean = FALSE; } /* * If the face has a group_element, free it. */ if (face->group_element != NULL) my_free(face->group_element); /* * Remove the face from the doubly-linked list. First set * face to face->prev so the loop will continue correctly. */ dead_face = face; face = face->prev; REMOVE_NODE(dead_face); /* * Delete the face. */ my_free(dead_face); } /* * Delete the edges which are being removed. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->v[tail]->which_side_of_plane == +1 || edge->v[tip] ->which_side_of_plane == +1) { /* * If this edge is indicent to a 0-vertex, * fix up the neighbors' edge->e[][] fields. */ /* * If it's the tail which is incident to a 0-vertex, call * redirect_edge(). This simplifies the subsequent code, * because we may assume that if a 0-vertex is incident to the * edge, it will be at the tip. Note that redirect_edge() will * be interchanging some dangling WEFace pointers, but that's OK. */ if (edge->v[tail]->which_side_of_plane == 0) redirect_edge(edge, FALSE); /* * Is there a 0-vertex at the tip? */ tip_vertex = edge->v[tip]; if (tip_vertex->which_side_of_plane == 0) { /* * Who are the neighbors? */ left_edge = edge->e[tip][left]; right_edge = edge->e[tip][right]; /* * Let the left_edge see the right_edge. */ if (left_edge->v[tip] == tip_vertex) left_edge->e[tip][right] = right_edge; else if (left_edge->v[tail] == tip_vertex) left_edge->e[tail][left] = right_edge; else uFatalError("install_new_face", "Dirichlet_construction"); /* * Let the right_edge see the left_edge. */ if (right_edge->v[tip] == tip_vertex) right_edge->e[tip][left] = left_edge; else if (right_edge->v[tail] == tip_vertex) right_edge->e[tail][right] = left_edge; else uFatalError("install_new_face", "Dirichlet_construction"); } /* * Remove the edge from the doubly-linked list. First set * edge to edge->prev so the loop will continue correctly. */ dead_edge = edge; edge = edge->prev; REMOVE_NODE(dead_edge); /* * Delete the edge. */ my_free(dead_edge); } /* * Delete the vertices which are being removed. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) if (vertex->which_side_of_plane == +1) { /* * Remove the vertex from the doubly-linked list. First set * vertex to vertex->prev so the loop will continue correctly. */ dead_vertex = vertex; vertex = vertex->prev; REMOVE_NODE(dead_vertex); /* * Delete the vertex. */ my_free(dead_vertex); } /* * Install the new_face in the doubly-linked list. */ INSERT_BEFORE(new_face, &polyhedron->face_list_end); } static Boolean face_still_exists( WEPolyhedron *polyhedron, WEFace *face0) { /* * Check whether face0 is still part of the polyhedron. */ WEFace *face; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face == face0) return TRUE; return FALSE; } static Boolean has_hyperideal_vertices( WEPolyhedron *polyhedron) { WEVertex *vertex; for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) if (o31_inner_product(vertex->x, vertex->x) > HYPERIDEAL_EPSILON) return TRUE; return FALSE; } static void compute_all_products( WEPolyhedron *polyhedron, MatrixPairList *product_list) { MatrixPairList current_list; MatrixPair *product_tree; /* * Compute a MatrixPairList containing all current face pairings. */ poly_to_current_list(polyhedron, ¤t_list); /* * Compute all products of two group elements from the current_list. * * Record the products on a binary tree instead of a doubly-linked list, * so that: * * (1) We can check for duplications in log(n) time instead * of linear time. * * (2) The final list will be sorted by height (cf. the height field * in WEFace). My expectation is that intersecting the polyhedron * with the lowest height group elements first will minimize the * time spent slicing. That is, we'll make the most important * cuts first, rather than creating a lot of more distant faces * which are themselves later cut off. (I don't have any firm * evidence that this will be the case, but it seems likely, and * in any case it costs us nothing.) */ current_list_to_product_tree(¤t_list, &product_tree); /* * Transfer the products from the product_tree to the product_list. */ product_tree_to_product_list(product_tree, product_list); /* * We're done with the current_list; */ free_matrix_pairs(¤t_list); } static void poly_to_current_list( WEPolyhedron *polyhedron, MatrixPairList *current_list) { WEFace *face; MatrixPair *matrix_pair; /* * Initialize the current_list. */ current_list->begin.prev = NULL; current_list->begin.next = ¤t_list->end; current_list->end .prev = ¤t_list->begin; current_list->end .next = NULL; /* * Use the face->copied fields to avoid copying both a face and its * mate as separate MatrixPairs. First initialize all face->copied * fields to FALSE. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->copied = FALSE; /* * Go down the list of faces, skipping faces of the original cube. * For each face which hasn't already been done, copy it's group_element * to a MatrixPair and append the MatrixPair to the current_list. * Set the face->copied field to TRUE, and if the face has a mate, * set its mate's copied field to TRUE too. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->group_element != NULL && face->copied == FALSE) { matrix_pair = NEW_STRUCT(MatrixPair); o31_copy(matrix_pair->m[0], *face->group_element); o31_invert(matrix_pair->m[0], matrix_pair->m[1]); matrix_pair->height = matrix_pair->m[0][0][0]; INSERT_BEFORE(matrix_pair, ¤t_list->end); face->copied = TRUE; if (face->mate != NULL) face->mate->copied = TRUE; } } static void current_list_to_product_tree( MatrixPairList *current_list, MatrixPair **product_tree) { MatrixPair *matrix_pair_a, *matrix_pair_b; int i, j; O31Matrix product; /* * Initialize the product_tree to NULL. */ *product_tree = NULL; /* For each pair {a, A}, {b, B} of MatrixPairs on the current_list, * add the MatrixPairs {ab, BA}, {aB, bA}, {Ab, Ba} and {AB, ba} * to product_list if they aren't already there. * * Note that once we've considered the ordered pair ({a, A}, {b, B}) * of MatrixPairs, there is no need to consider the ordered pair * ({b, B}, {a, A}). It would generate the MatrixPairs * {ba, AB}, {bA, aB}, {Ba, Ab} and {BA, ab}, which are the same as * those generated by ({a, A}, {b, B}), only within each resulting * MatrixPair the order of the O31Matrices is reversed. Therefore we * consider only order pairs ({a, A}, {b, B}) where the the MatrixPair * {a, A} occurs no later than {b, B} in the current_list. */ for (matrix_pair_a = current_list->begin.next; matrix_pair_a != ¤t_list->end; matrix_pair_a = matrix_pair_a->next) for (matrix_pair_b = matrix_pair_a; matrix_pair_b != ¤t_list->end; matrix_pair_b = matrix_pair_b->next) for (i = 0; i < 2; i++) /* which element of matrix_pair_a */ for (j = 0; j < 2; j++) /* which element of matrix_pair_b */ { precise_o31_product(matrix_pair_a->m[i], matrix_pair_b->m[j], product); if (already_on_product_tree(product, *product_tree) == FALSE) add_to_product_tree(product, product_tree); } } static Boolean already_on_product_tree( O31Matrix product, MatrixPair *product_tree) { MatrixPair *subtree_stack, *subtree; int i; /* * Does the O31Matrix product already appear on the product_tree? */ /* * Implement the recursive search algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole product_tree. */ subtree_stack = product_tree; if (product_tree != NULL) product_tree->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the product could possible appear on the left and/or right * subtrees, add them to the stack. */ if (subtree->left_child != NULL && product[0][0] < subtree->height + MATRIX_EPSILON) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL && product[0][0] > subtree->height - MATRIX_EPSILON) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Check the subtree's root. */ for (i = 0; i < 2; i++) if (o31_equal(product, subtree->m[i], MATRIX_EPSILON) == TRUE) return TRUE; } return FALSE; } static void add_to_product_tree( O31Matrix product, MatrixPair **product_tree) { MatrixPair **home; double product_height; /* * We need to find a home for the O31Matrix product on the product_tree. * * We assume the product does not already appear on the product_tree. * (The call to already_on_product_tree() must return FALSE in * current_list_to_product_tree() for this function to be called.) */ product_height = product[0][0]; home = product_tree; while (*home != NULL) { if (product_height < (*home)->height) home = &(*home)->left_child; else home = &(*home)->right_child; } (*home) = NEW_STRUCT(MatrixPair); o31_copy((*home)->m[0], product); o31_invert((*home)->m[0], (*home)->m[1]); (*home)->height = (*home)->m[0][0][0]; (*home)->left_child = NULL; (*home)->right_child = NULL; (*home)->next_subtree = NULL; (*home)->prev = NULL; (*home)->next = NULL; } static void product_tree_to_product_list( MatrixPair *product_tree, MatrixPairList *product_list) { /* * Initialize the product_list. */ product_list->begin.prev = NULL; product_list->begin.next = &product_list->end; product_list->end .prev = &product_list->begin; product_list->end .next = NULL; /* * Transfer the MatrixPairs from the product_tree to the product_list, * maintaining their order. Set the left_child and right_child fields * to NULL as we go along. */ append_tree_to_list(product_tree, &product_list->end); } static void append_tree_to_list( MatrixPair *product_tree, MatrixPair *list_end) { MatrixPair *subtree_stack, *subtree; /* * Implement the recursive tree traversal using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole product_tree. */ subtree_stack = product_tree; if (product_tree != NULL) product_tree->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If it has no further subtrees, append it to the list. * * Otherwise break it into three chunks: * * the left subtree * this node * the right subtree * * and push them onto the stack in reverse order, so that they'll * come off in the correct order. Set this node's left_child and * right_child fields to NULL, so the next time it comes off the * stack we'll know the subtrees have been accounted for. */ if (subtree->left_child == NULL && subtree->right_child == NULL) { INSERT_BEFORE(subtree, list_end); } else { /* * Push the right subtree (if any) onto the stack. */ if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; subtree->right_child = NULL; } /* * Push this node onto the stack. * (Its left_child and right_child fields will soon be NULL.) */ subtree->next_subtree = subtree_stack; subtree_stack = subtree; /* * Push the left subtree (if any) onto the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; subtree->left_child = NULL; } } } } static FuncResult check_faces( WEPolyhedron *polyhedron) { WEFace *face; Boolean face_was_pared; /* * The face->clean fields keep track of which faces are known to be * subsets of their mates under the action of the group_element. * When all face->clean fields are TRUE, we've found the Dirichlet * domain. */ /* * Initialize all face->clean fields to FALSE. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->clean = FALSE; /* * Go down the doubly-linked list of faces, looking for a dirty one. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) /* * If a dirty one is found . . . */ if (face->clean == FALSE) { /* * . . . see whether some group element will pare away * some or all of it. * * There are two possibilities. * * (1) The face was clean after all, and we just didn't know it. * In this case, pare_face() sets face->clean to TRUE and * *face_was_pared to FALSE, and returns func_OK. The * polyhedron remains unchanged. We keep going with the * for(;;) loop. * * (2) The face really was dirty. In this case, pare_face() * slices the polyhedron with a plane which lops off a * nontrivial piece. If pare_face() encounters topological * problems due to roundoff error, it returns func_failed, * and so do we. Otherwise it sets the face->clean fields * to FALSE for all affected faces, sets *face_was_pared to * TRUE, and returns func_OK. At this point we have no * idea which faces have been eliminated entirely, so we * restart the for(;;) loop. */ if (pare_face(face, polyhedron, &face_was_pared) == func_failed) return func_failed; if (face_was_pared == TRUE) face = &polyhedron->face_list_begin; } /* * The above for(;;) loop will terminate only when all the face->clean * fields are TRUE. So we can return func_OK. */ return func_OK; } static FuncResult pare_face( WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared) { /* * pare_face()'s mission is to decide whether face is a subset of its * mate under the action of the group_element. (If its mate does not * exist, then clearly it's not a subset.) * * If face is a subset of its mate, set face->clean to TRUE and * *face_was_pared to FALSE. (Setting *face_was_pared to FALSE * signifies that we didn't have to do anything: the face was * already a subset of its mate under the action of the group_element, * even though we didn't know it.) * * If face is not a subset of its mate, find a new group element * which cuts off a part of face. Leave face->clean FALSE, and * set *face_was_pared to TRUE. * * If we encounter topological problems due to roundoff error, * return func_failed. Otherwise return func_OK. * * We split into two cases, according to whether face has a mate. */ if (face->mate != NULL) return pare_mated_face(face, polyhedron, face_was_pared); else return pare_mateless_face(face, polyhedron, face_was_pared); } static FuncResult pare_mated_face( WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared) { /* * The face and its mate are both convex sets -- both are * intersections of half planes. So to check that the face is * contained in the image of its mate under the action of the * group_element, it suffices to check that all the face's vertices * are contained in the image of the mate. * * Schematically, the polyhedron looks like this. Note that the * group_element takes the mate to the face, not the other way around. * * ____________ * / face \ * / \ * / ^ \ * / | \ * / group_element \ * \ / / * \ / / * mate \ / * \ / * \____________/ * * Look at the image of the mate under the action of the group_element. * The figure shows what happens when the face is not a subset of * the image of the mate. * * image * \ of / * \ mate / * \_________/ * ____________ * / face \ * / \ * / ^ \ * / | \ * / group_element \ * \ / / * \ / / * mate \ / * \ / * \____________/ * * One of the vertices of the face extends beyond one of the sides * of the image of the mate. Let alpha be the group_element * belonging to the face adjacent to that side of mate. * * image \ conjugate * \ of \/ of * \ mate /\ alpha * \_________/ _\| * ____________ * / face \ * / \ * / ^ \ * / | \ * / group_element \ * \ / / * \ / / * mate \ / * \ | / * \_____|______/ * | * alpha * | * V * * Consider the product beta = (group_element)(alpha). (Matrices act * on column vectors on the left, so (group_element)(alpha) means * first do alpha, then do group_element.) * * Let V be the vertex in question -- the vertex of face which lies * outside the image of mate. * * distance(V, origin) = distance(V, group_element(origin)) * * because V lies on face. * * distance(V, group_element(origin)) > distance(V, beta(origin)) * * because V lies beyond that side of the image of the mate. * * Combining the above two observations yields * * distance(V, origin) > distance(V, beta(origin)) * * which implies that the half space defined by beta will cut the * vertex V off of the polyhedron. * * So our algorithm will be * * for (each neighbor of the mate) * compute beta = group_element * alpha * for (each vertex V of the face) * if (beta cuts off V) * add faces determined by beta and its inverse * to the polyhedron * if roundoff-related errors occured * return func_failed * otherwise * set *face_was_pared to TRUE * return func_OK * set face->clean to TRUE * set *face_was_pared to FALSE * return func_OK * * * Technical digression. * * The old version of SnapPea used a "clever trick" to check that * a face is a subset of its mate. I've chosen not to use that * trick here, and in this digression I will briefly explain why. * Feel free to skip this digression is you want. I'm writing it * mainly for my own good, so if I come back a few years from now * and wonder why I didn't use the trick, I'll at least know what * I was thinking at the time. * * The "clever trick" was to try to simultaneously traverse the * perimeters of the face and its mate, checking that the group * element beta (defined above) is already the group element * corresponding to one of the face's neighbors. If this didn't * work, either because the faces didn't in fact match up, or * (less likely) because the edges weren't of order 3, then the * old code would fall back to an algorithm like the one described * above. * * I've chosen not to use this "clever trick" in the present code * for the following reasons. * * (1) For nonorientable manifolds, the code would get messier * because we wouldn't know a priori which way we need to * traverse the faces. * * (2) The code wasn't that much more efficient to begin with. * To match two n-gons, it would have to do n matrix * multiplications. Each matrix multiplication requires * about 4^3 operations, so the run time is about O(n * 4^3). * The simpler algorithm (the one used here) requires, in * addition, the computation of n^2 inner products, to * check whether each of n points lies on the correct side * of n hyperplanes. This requires a time O(n^2 * 4). * At first glance O(n^2) looks worse than O(n), but we * have to remember that n is not a large number. One can * prove that for a typical polyhedron, the average value * of n is around 6. (For an atypical polyhedron it's even * less.) So the simpler algorithm requires little additional * time. Indeed, if one wanted to streamline it, once could * compute only the first column of beta, unless it turned * out that you needed the whole matrix to add a new face. * So the simpler algorithm might be even faster. * * (3) Whether the old algorithm is faster than the new one isn't * completely clear, but given that the run times will be * similar, I feel that the simplicity of the code should * be the top priority. * * End of technical digression. */ WEEdge *edge; O31Matrix *alpha; /* * Consider each neighbor of face->mate. */ edge = face->mate->some_edge; do { /* * First an error check. */ if (edge->f[left] == edge->f[right]) uFatalError("pare_mated_face", "Dirichlet_construction"); /* * Find the element alpha defined above. */ if (edge->f[left] == face->mate) alpha = edge->f[right]->group_element; else alpha = edge->f[left] ->group_element; /* * Check whether this alpha defines a beta which cuts a vertex off * of face. If so, intersect the polyhedron with the faces defined * by beta and its inverse. * * If topological problems due to roundoff error are * encountered, return func_failed. */ if (try_this_alpha(alpha, face, polyhedron, face_was_pared) == func_failed) return func_failed; /* * If the face was actually pared, return func_OK. * Otherwise keep going with the loop. */ if (*face_was_pared == TRUE) return func_OK; /* * Move on to the next edge. */ if (edge->f[left] == face->mate) edge = edge->e[tip][left]; else edge = edge->e[tail][right]; } while (edge != face->mate->some_edge); /* * The face is a subset of the image of its mate. */ face->clean = TRUE; /* * We didn't have to do anything to the polyhedron, * so set *face_was_pared to FALSE. */ *face_was_pared = FALSE; /* * Given that we modified nothing, we could hardly have encountered * topological problems due to roundoff errors, so return func_OK. */ return func_OK; } static FuncResult pare_mateless_face( WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared) { /* * The situation here is similar to that in pare_mated_face() above. * (In particular, you should understand pare_mated_face()'s * documentation before you read this.) The difference is that * mate has been entirely cut off by other group elements. * (Well, almost entirely cut off. A 1-dimensional subset might * still remain, but no 2-dimensional subset.) In the following * diagram, the plane of the nonexistant mate is shown by a line * of stars. (By the "plane of the nonexistant mate", we mean the * image of the plane of face under the action of the inverse of * face->group_element.) * * \ / * \ / * \ / * image of mate * ************* * ____________ * / face \ * / \ * / ^ \ * | | \ * | group_element \ * * | / / * * |_________________/ * mate * * * * * * * Let P be a point which lies in the image of face under the action of * the inverse of face->group_element, but is not a point of the * polyhedron. Some face plane of the polyhedron, with group element * alpha, must exclude P from the polyhedron. (Proof: If P were on * the "correct" side of all face planes, then it would be contained * in the polyhedron, since the polyhedron is the intersection of the * halfspaces corresponding to its face planes.) * * conjugate * \ --/--> of * \ / alpha * \ / * image of mate * ************* * _________P'_ * / face \ * / \ * / ^ \ * | | \ * | group_element \ * * | / / * * |________|________/ * mate * | * P | alpha * * V * * This says that * * distance(P, origin) > distance(P, alpha(origin)) * * Let P' be the image of P under the action of face->group_element, * that is, P' = face->group_element(P). * * Let face->group_element act on the preceding inequality: * * distance(P', group_element(origin)) * > distance(P', group_element(alpha(origin))) * * Because P' lies on face, * * distance(P', origin) = distance(P', group_element(origin)) * * Substituting this equation into the preceding inequality yields * * distance(P', origin) > distance(P', group_element(alpha(origin))) * * This implies that beta = group_element * alpha will define a * plane which cuts P' off of the polyhedron. Because face is the * convex hull of its vertices, beta must also remove some vertex V * from the polyhedron. * * How do we find the group element alpha? I don't know of any * clever way to find it, so we use brute force. We look at each * face in turn until we find one that works. Here's the algorithm: * * for each alpha belonging to a face of the polyhedron * compute beta = group_element * alpha * for (each vertex V of the face) * if (beta cuts off V) * add faces determined by beta and its inverse * to the polyhedron * if roundoff-related errors occured * return func_failed * otherwise * set *face_was_pared to TRUE * return func_OK * report a roundoff error, because we proved above that a * suitable alpha must exist */ WEFace *face1; O31Matrix *alpha; /* * Consider each face1 of the polyhedron. */ for (face1 = polyhedron->face_list_begin.next; face1 != &polyhedron->face_list_end; face1 = face1->next) { /* * Find alpha. */ alpha = face1->group_element; /* * Check whether this alpha defines a beta which cuts a vertex off * of face. If so, intersect the polyhedron with the faces defined * by beta and its inverse. * * If topological problems due to roundoff error are * encountered, return func_failed. */ if (try_this_alpha(alpha, face, polyhedron, face_was_pared) == func_failed) return func_failed; /* * If the face was actually pared, return func_OK. * Otherwise keep going with the loop. */ if (*face_was_pared == TRUE) return func_OK; } /* * We should never reach this point, because in the above documentation * we proved that some alpha must define a beta which cuts off a vertex. * So if we do end up at this point, it means that roundoff errors * have corrupted our polyhedron. */ return func_failed; } static FuncResult try_this_alpha( O31Matrix *alpha, WEFace *face, WEPolyhedron *polyhedron, Boolean *face_was_pared) { /* * try_this_alpha() performs the calculations common to * pare_mated_face() and pare_mateless_face(). * * It computes beta = face->group_element * alpha (cf. the * documentation at the beginning of pare_mated_face()) and * checks whether beta cuts off any vertices of face. * * If beta cuts off vertices, then * if topological problems due to roundoff error are * encountered, it returns func_failed * else it sets *face_was_pared to TRUE and returns func_OK * else * it sets *face_was_pared to FALSE and returns func_OK. */ WEVertex *vertex; WEEdge *edge; O31Matrix beta; O31Vector normal; MatrixPair matrix_pair; /* * Compute beta = (group_element)(alpha) as explained in the * documentation in pare_mated_face(). * * (If you have an overwhelming urge to optimize you could * compute only the first column of beta until you need the * rest, but for now I'll prefer simplicity over speed.) */ precise_o31_product(*face->group_element, *alpha, beta); /* * Compute the normal vector to the hyperplane defined by beta. */ compute_normal_to_Dirichlet_plane(beta, normal); /* * Consider each vertex of face. * (Technically speaking, we consider each edge, but then look * at the vertex on its counterclockwise end.) */ edge = face->some_edge; do { /* * Look at the vertex at the counterclockwise end of edge. */ if (edge->f[left] == face) vertex = edge->v[tip]; else vertex = edge->v[tail]; /* * Does the vertex lie beyond the hyperplane determined by beta? */ if (o31_inner_product(vertex->x, normal) > polyhedron->vertex_epsilon) { /* * Great. * We've found a vertex which will be cut off by beta. */ /* * Set up matrix_pair.m[0] and matrix_pair.m[1]. */ o31_copy(matrix_pair.m[0], beta); o31_invert(matrix_pair.m[0], matrix_pair.m[1]); /* * The other fields in matrix_pair aren't needed here. */ matrix_pair.height = 0.0; matrix_pair.prev = NULL; matrix_pair.next = NULL; /* * Intersect the polyhedron with the half spaces. * If roundoff errors cause topological problems, * return func_failed. */ if (intersect_with_halfspaces(polyhedron, &matrix_pair) == func_failed) return func_failed; /* * We've modified the polyhedron, * so set *face_was_pared to TRUE. */ *face_was_pared = TRUE; /* * intersect_with_halfspaces() encountered no topological * problems due to roundoff errors, so return func_OK. */ return func_OK; } /* * Move on to the next vertex. */ if (edge->f[left] == face) edge = edge->e[tip][left]; else edge = edge->e[tail][right]; } while (edge != face->some_edge); /* * Beta didn't cut off any vertices, so set *face_was_pared to FALSE. */ *face_was_pared = FALSE; /* * We didn't cut the polyhedron, so we could hardly have encountered * topological problems due to roundoff error. Return func_OK. */ return func_OK; } static void count_cells( WEPolyhedron *polyhedron) { WEVertex *vertex; WEEdge *edge; WEFace *face; /* * The counts were intialized in new_WEPolyhedron(), * but we'll reinitialize them here just for good form. */ polyhedron->num_vertices = 0; polyhedron->num_edges = 0; polyhedron->num_faces = 0; /* * Count the vertices. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) polyhedron->num_vertices++; /* * Count the edges. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) polyhedron->num_edges++; /* * Count the faces. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) polyhedron->num_faces++; /* * Check the Euler characteristic. */ if (polyhedron->num_vertices - polyhedron->num_edges + polyhedron->num_faces != 2) uFatalError("count_cells", "Dirichlet_construction"); } static void sort_faces( WEPolyhedron *polyhedron) { /* * Sort the faces by order of increasing distance from the basepoint. * * Note: sort_faces() assumes polyhedron->num_faces is correct, which * is true in the present context because compute_Dirichlet_domain() * calls count_cells() before sort_faces(). */ WEFace **array, *face; int i; /* * This code assumes the polyhedron has at least two WEFaces. * But as long as we're doing an error check, let's insist that * the polyhedron have at least four faces. */ if (polyhedron->num_faces < 4) uFatalError("sort_faces", "Dirichlet_construction"); /* * Allocate an array to hold the addresses of the WEFaces. */ array = NEW_ARRAY(polyhedron->num_faces, WEFace *); /* * Copy the addresses into the array. */ for (face = polyhedron->face_list_begin.next, i = 0; face != &polyhedron->face_list_end; face = face->next, i++) array[i] = face; /* * Do a quick error check to make sure we copied * the right number of elements. */ if (i != polyhedron->num_faces) uFatalError("sort_faces", "Dirichlet_construction"); /* * Sort the array of pointers. */ qsort( array, polyhedron->num_faces, sizeof(WEFace *), compare_face_distance); /* * Adjust the WEFaces' prev and next fields to reflect the new ordering. */ polyhedron->face_list_begin.next = array[0]; array[0]->prev = &polyhedron->face_list_begin; array[0]->next = array[1]; for (i = 1; i < polyhedron->num_faces - 1; i++) { array[i]->prev = array[i-1]; array[i]->next = array[i+1]; } array[polyhedron->num_faces - 1]->prev = array[polyhedron->num_faces - 2]; array[polyhedron->num_faces - 1]->next = &polyhedron->face_list_end; polyhedron->face_list_end.prev = array[polyhedron->num_faces - 1]; /* * Free the array. */ my_free(array); } static int CDECL compare_face_distance( const void *ptr1, const void *ptr2) { double diff; diff = (*(*((WEFace **)ptr1))->group_element)[0][0] - (*(*((WEFace **)ptr2))->group_element)[0][0]; if (diff < 0.0) return -1; if (diff > 0.0) return +1; return 0; } static Boolean verify_faces( WEPolyhedron *polyhedron) { WEEdge *edge; WEFace *face; /* * Initialize each face->num_sides to 0. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->num_sides = 0; /* * Add the contribution of each edge to the adjacent face->num_sides. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { edge->f[left ]->num_sides++; edge->f[right]->num_sides++; } /* * Check that each face and its mate have the same num_sides. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->num_sides != face->mate->num_sides) return func_failed; return func_OK; } static FuncResult verify_group( WEPolyhedron *polyhedron, MatrixPairList *gen_list) { /* * Check that the face pairing isometries generate all the generators * on the original gen_list, to be sure that we have a Dirichlet domain * for the manifold/orbifold itself and not some finite-sheeted cover. * This check should proceed quickly, because * * (1) The gen_list was simplified in the function * Dirichlet_from_generators_with_displacement() in Dirichlet.c, so * typically the elements on gen_list will all be face pairing * isometries to begin with, or close to it. * * (2) compute_Dirichlet_domain() has already called sort_faces() to * sort the polyhedron's faces in order of increasing basepoint * translation distance, so the face pairings we're most likely to * need will be encountered near the beginning of the list. */ MatrixPair *matrix_pair; O31Matrix m, candidate; Boolean progress; WEFace *face; double verify_epsilon; for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) { o31_copy(m, matrix_pair->m[0]); verify_epsilon = VERIFY_EPSILON; while (o31_equal(m, O31_identity, MATRIX_EPSILON) == FALSE) { progress = FALSE; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { o31_product(m, *face->group_element, candidate); if (m[0][0] - candidate[0][0] > verify_epsilon) { o31_copy(m, candidate); progress = TRUE; break; } } if (progress == FALSE) { /* * There are two possibilities, either * * (1) We have a Dirichlet domain for a finite-sheeted cover * of the manifold/orbifold, or * * (2) We have an orbifold -- perhaps one whose basepoint * is only slightly displaced from a fixed point -- * with nontrivial group elements which move the basepoint * only a small distance. * * To decide which case we're in and respond appropriately, * we set verify_epsilon to 0.0 and keep going. If we still * can't make progress, then we know we are in case (1). */ if (verify_epsilon > 0.0) verify_epsilon = 0.0; else { uAcknowledge("Please tell Jeff Weeks that SnapPea seems to have computed a Dirichlet domain for a finite-sheeted cover of the manifold/orbifold."); return func_failed; } } } } return func_OK; } static void rewrite_gen_list( WEPolyhedron *polyhedron, MatrixPairList *gen_list) { WEFace *face, *mate; MatrixPair *new_matrix_pair; /* * First discard the gen_list's present contents. */ free_matrix_pairs(gen_list); /* * Add the identity. */ new_matrix_pair = NEW_STRUCT(MatrixPair); o31_copy(new_matrix_pair->m[0], O31_identity); o31_copy(new_matrix_pair->m[1], O31_identity); new_matrix_pair->height = 1.0; INSERT_BEFORE(new_matrix_pair, &gen_list->end); /* * Use the face->copied fields to avoid copying both a face and its * mate as separate MatrixPairs. First initialize all face->copied * fields to FALSE. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->copied = FALSE; /* * Go down the list of faces, and for each face which hasn't already * been done, copy it's and it's mate's group_elements to a MatrixPair, * and append the MatrixPair to the gen_list. * * Note that the gen_list will be presorted, * because the list of faces has been sorted. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->copied == FALSE) { mate = face->mate; new_matrix_pair = NEW_STRUCT(MatrixPair); o31_copy(new_matrix_pair->m[0], *face->group_element); o31_copy(new_matrix_pair->m[1], *mate->group_element); new_matrix_pair->height = (*face->group_element)[0][0]; INSERT_BEFORE(new_matrix_pair, &gen_list->end); face->copied = TRUE; mate->copied = TRUE; } } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_conversion.c0100444000175000017500000002210206742675501021216 0ustar babbab/* * Dirichlet_conversion.c * * This file contains the function * * Triangulation *Dirichlet_to_triangulation(WEPolyhedron *polyhedron); * * which converts a Dirichlet domain to a Triangulation, leaving the * Dirichlet domain unchanged. For closed manifolds, drills out * an arbitrary curve and expresses the manifold as a Dehn filling. * The polyhedron must be a manifold; returns NULL for orbifolds. */ /* * The Algorithm * * When subdividing a Dirichlet domain into tetrahedra, one faces a * tradeoff between the ease of programming and efficiency of the resulting * triangulation. Plan A is the cleanest to implement, but uses twice * as many tetrahedra as Plan B, and four times as many as plan C. * * Plan A * * The vertices of each tetrahedron are as follows. * * index location * 0 at a vertex of the Dirichlet domain * 1 at the midpoint of an edge incident to the aforementioned vertex * 2 at the center of a face incident to the aforementioned edge * 3 at the center of the Dirichlet domain * * As a mnemonic, note that vertex i lies at the center of a cell * of dimension i. Make yourself a sketch of the Dirichlet domain, * and it will be obvious that tetrahedra of the above form triangulate * the manifold. Moreover, all tet->gluing[]'s are the identity (!) * so the implementation need only be concerned with the tet->neighbor[]'s. * * Plan B * * Fuse together pairs of tetrahedra from Plan A across their faces * of FaceIndex 0. This halves the number of tetrahedra required, * but makes the programming more complicated. * * Plan C * * Fuse together pairs of tetrahedra from Plan B across their faces * of FaceIndex 3 (i.e. across the faces of the original Dirichlet domain). * This halves the number of tetrahedra required, but makes the programming * more complicated. * * The Choice * * For now I will go with Plan A because it's simplest. * If memory use turns out to be a problem (which I suspect it won't) * I can rewrite the code to use Plan B or C instead. In any case, * note that the large number of Tetrahedra are required only temporarily, * and don't have TetShapes attached. The only remaining danger with this * plan is that in the case of a closed manifold, the drilled curve might * not be isotopic to the geodesic in its isotopy class. */ #include "kernel.h" #define DEFAULT_NAME "no name" #define MAX_TRIES 16 static Triangulation *try_Dirichlet_to_triangulation(WEPolyhedron *polyhedron); static Boolean singular_set_is_empty(WEPolyhedron *polyhedron); Triangulation *Dirichlet_to_triangulation( WEPolyhedron *polyhedron) { /* * When the polyhedron represents a closed manifold, * try_Dirichlet_to_triangulation() drills out an arbitrary curve * to express the manifold as a Dehn filling. Usually the arbitrary * curve turns out to be isotopic to a geodesic, but not always. * Here we try several repetitions of try_Dirichlet_to_triangulation(), * if necessary, to obtain a hyperbolic Dehn filling. * Fortunately in almost all cases (about 95% in my informal tests) * try_Dirichlet_to_triangulation() get a geodesic on its first try. */ int count; Triangulation *triangulation; triangulation = try_Dirichlet_to_triangulation(polyhedron); count = MAX_TRIES; while ( --count >= 0 && triangulation != NULL && triangulation->solution_type[filled] != geometric_solution && triangulation->solution_type[filled] != nongeometric_solution) { free_triangulation(triangulation); triangulation = try_Dirichlet_to_triangulation(polyhedron); } return triangulation; } static Triangulation *try_Dirichlet_to_triangulation( WEPolyhedron *polyhedron) { /* * Implement Plan A as described above. */ Triangulation *triangulation; WEEdge *edge, *nbr_edge, *mate_edge; WEEdgeEnd end; WEEdgeSide side; Tetrahedron *new_tet; FaceIndex f; /* * Don't attempt to triangulate an orbifold. */ if (singular_set_is_empty(polyhedron) == FALSE) return NULL; /* * Set up the Triangulation. */ triangulation = NEW_STRUCT(Triangulation); initialize_triangulation(triangulation); /* * Allocate and copy the name. */ triangulation->name = NEW_ARRAY(strlen(DEFAULT_NAME) + 1, char); strcpy(triangulation->name, DEFAULT_NAME); /* * Allocate the Tetrahedra. */ triangulation->num_tetrahedra = 4 * polyhedron->num_edges; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (end = 0; end < 2; end++) /* = tail, tip */ for (side = 0; side < 2; side++) /* = left, right */ { new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); INSERT_BEFORE(new_tet, &triangulation->tet_list_end); edge->tet[end][side] = new_tet; } /* * Initialize neighbors. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (end = 0; end < 2; end++) /* = tail, tip */ for (side = 0; side < 2; side++) /* = left, right */ { /* * Neighbor[0] is associated to this same WEEdge. * It lies on the same side (left or right), but * at the opposite end (tail or tip). */ edge->tet[end][side]->neighbor[0] = edge->tet[!end][side]; /* * Neighbor[1] lies on the same face of the Dirichlet * domain, but at the "next side" of that face. */ nbr_edge = edge->e[end][side]; if (nbr_edge->v[!end] == edge->v[end]) /* edge and nbr_edge point in the same direction */ edge->tet[end][side]->neighbor[1] = nbr_edge->tet[!end][side]; else if (nbr_edge->v[end] == edge->v[end]) /* edge and nbr_edge point in opposite directions */ edge->tet[end][side]->neighbor[1] = nbr_edge->tet[end][!side]; else uFatalError("Dirichlet_to_triangulation", "Dirichlet_conversion"); /* * Neighbor[2] is associated to this same WEEdge. * It lies at the same end (tail or tip), but * on the opposite side (left or right). */ edge->tet[end][side]->neighbor[2] = edge->tet[end][!side]; /* * Neighbor[3] lies on this face's "mate" elsewhere * on the Dirichlet domain. */ mate_edge = edge->neighbor[side]; edge->tet[end][side]->neighbor[3] = mate_edge->tet [edge->preserves_direction[side] ? end : !end ] [edge->preserves_sides[side] ? side : !side]; } /* * Initialize all gluings to the identity. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (end = 0; end < 2; end++) /* = tail, tip */ for (side = 0; side < 2; side++) /* = left, right */ for (f = 0; f < 4; f++) edge->tet[end][side]->gluing[f] = IDENTITY_PERMUTATION; /* * Set up the EdgeClasses. */ create_edge_classes(triangulation); orient_edge_classes(triangulation); /* * Attempt to orient the manifold. */ orient(triangulation); /* * Set up the Cusps, including "fake cusps" for the finite vertices. * Then locate and remove the fake cusps. If the manifold is closed, * drill out an arbitrary curve to express it as a Dehn filling. * Finally, determine the topology of each cusp (torus or Klein bottle) * and count them. */ create_cusps(triangulation); mark_fake_cusps(triangulation); peripheral_curves(triangulation); remove_finite_vertices(triangulation); count_cusps(triangulation); /* * Try to compute a hyperbolic structure, first for the unfilled * manifold, and then for the closed manifold if appropriate. */ find_complete_hyperbolic_structure(triangulation); do_Dehn_filling(triangulation); /* * If the manifold is hyperbolic, install a shortest basis on each cusp. */ if ( triangulation->solution_type[complete] == geometric_solution || triangulation->solution_type[complete] == nongeometric_solution) install_shortest_bases(triangulation); /* * All done! */ return triangulation; } static Boolean singular_set_is_empty( WEPolyhedron *polyhedron) { /* * Check whether the singular set of this orbifold is empty. */ WEVertexClass *vertex_class; WEEdgeClass *edge_class; WEFaceClass *face_class; for (vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) if (vertex_class->singularity_order >= 2) return FALSE; /* * Dirichlet_construction.c subdivides Dirichlet domains for * orbifolds so that the k-skeleton of the singular set lies * in the k-skeleton of the Dirichlet domain (k = 0,1,2). * Thus if there are no singular VertexClasses, there can't be * any singular EdgeClasses or FaceClasses either. */ for (edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) if (edge_class->singularity_order >= 2) uFatalError("singular_set_is_empty", "Dirichlet_conversion"); for (face_class = polyhedron->face_class_begin.next; face_class != &polyhedron->face_class_end; face_class = face_class->next) if (face_class->num_elements != 2) uFatalError("singular_set_is_empty", "Dirichlet_conversion"); /* * No singularities are present. */ return TRUE; } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_extras.c0100444000175000017500000024031607053771513020344 0ustar babbab/* * Dirichlet_extras.c */ #include "kernel.h" #include "Dirichlet.h" /* * The distances from the origin to points identified by face pairing * isometries must agree to within DIST_EPSILON. */ #define DIST_EPSILON 1e-3 /* * The length of identified edges must agree to within LENGTH_EPSILON. */ #define LENGTH_EPSILON 1e-3 /* * A vertex is considered ideal iff o31_inner_product(vertex->x, vertex->x) * is within IDEAL_EPSILON of zero. (Recall that vertex->x[0] is always 1.) * The choice of IDEAL_EPSILON as 4e-7 is explained below in the * documentation in compute_vertex_distance(). */ #define IDEAL_EPSILON 4e-7 /* * The O(3,1) trace of an elliptic involution must be an integer * (-2, 0 or 2) to within TRACE_ERROR_EPSILON. */ #define TRACE_ERROR_EPSILON 1e-2 /* * A neighborhood of a vertex class will be considered nonsingular iff the * vertex class's solid angle is at least 4*pi - PI_EPSILON, and a * neighborhood of an edge class will be considered nonsingular iff the * edge class's dihedral angle is at least 2*pi - PI_EPSILON. We can * afford to make PI_EPSILON large, because the next smallest possible * value of the solid angle (resp. dihedral angle) is 2*pi (resp. pi). */ #define PI_EPSILON 1e-1 /* * solid_angles() sets a vertex class's singularity_order to 0 * when the total solid angle is less than SOLID_ANGLE_EPSILON. */ #define SOLID_ANGLE_EPSILON 1e-4 static void face_classes(WEPolyhedron *polyhedron); static void edge_classes(WEPolyhedron *polyhedron); static void initialize_edge_classes(WEPolyhedron *polyhedron); static void find_edge_mates(WEPolyhedron *polyhedron); static void match_incident_edges(WEFace *face); static void mI_edge_classes(WEPolyhedron *polyhedron, int *count); static void make_mI_edge_class(WEPolyhedron *polyhedron, WEEdge *edge, WEEdgeSide side, int index); static void S1_edge_classes(WEPolyhedron *polyhedron, int *count); static void make_S1_edge_class(WEPolyhedron *polyhedron, WEEdge *edge, int index); static void vertex_classes(WEPolyhedron *polyhedron); static void create_vertex_class(WEPolyhedron *polyhedron, WEVertex *vertex); static void subdivide_edges_where_necessary(WEPolyhedron *polyhedron); static void subdivide_faces_where_necessary(WEPolyhedron *polyhedron); static void cone_face_to_center(WEFace *face, WEPolyhedron *polyhedron); static void bisect_face(WEFace *face, WEPolyhedron *polyhedron); static void delete_face_classes(WEPolyhedron *polyhedron); static void delete_edge_classes(WEPolyhedron *polyhedron); static void delete_vertex_classes(WEPolyhedron *polyhedron); static void dihedral_angles(WEPolyhedron *polyhedron); static void solid_angles(WEPolyhedron *polyhedron); static FuncResult vertex_distances(WEPolyhedron *polyhedron); static void compute_vertex_distance(WEVertex *vertex); static FuncResult edge_distances(WEPolyhedron *polyhedron); static void compute_edge_distance(WEEdge *edge); static void face_distances(WEPolyhedron *polyhedron); static FuncResult edge_lengths(WEPolyhedron *polyhedron); static void compute_edge_length(WEEdge *edge); static void compute_approx_volume(WEPolyhedron *polyhedron); static void compute_inradius(WEPolyhedron *polyhedron); static void compute_outradius(WEPolyhedron *polyhedron); static void compute_spine_radius(WEPolyhedron *polyhedron); static void attempt_free_edge_removal(WEPolyhedron *polyhedron); static void compute_deviation(WEPolyhedron *polyhedron); static void compute_geometric_Euler_characteristic(WEPolyhedron *polyhedron); FuncResult Dirichlet_bells_and_whistles( WEPolyhedron *polyhedron) { /* * Compute supplementary information about the Dirichlet domain. * * Some of the following functions use the results of the others, * so please avoid changing their order. */ face_classes(polyhedron); edge_classes(polyhedron); vertex_classes(polyhedron); /* * An orbifold's singular set will always be on the Dirichlet * domain's boundary. But it may or may not be a subcomplex * of the Dirichlet domain's 2-skeleton. It can happen that * a 0-cell of the singular set lies at the midpoint of an edge * of the Dirichlet domain, or in the interior of a face of the * Dirichlet. A 1-cell of the singular set may bisect a face * of the Dirichlet domain. We subdivide the Dirichlet domain * to contain the singular set as a subcomplex, not just a subspace. * If changes are made, we recompute the face_classes(), * edge_classes() and vertex_classes(). * * 94/10/4 JRW */ subdivide_edges_where_necessary(polyhedron); subdivide_faces_where_necessary(polyhedron); dihedral_angles(polyhedron); solid_angles(polyhedron); if (vertex_distances(polyhedron) == func_failed) return func_failed; if (edge_distances(polyhedron) == func_failed) return func_failed; face_distances(polyhedron); if (edge_lengths(polyhedron) == func_failed) return func_failed; compute_approx_volume(polyhedron); compute_inradius(polyhedron); compute_outradius(polyhedron); compute_spine_radius(polyhedron); compute_deviation(polyhedron); compute_geometric_Euler_characteristic(polyhedron); return func_OK; } static void face_classes( WEPolyhedron *polyhedron) { /* * Set the index and hue fields for each face. */ WEFace *face; int count; /* * Initialize all f_class fields to NULL to show they haven't been set. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->f_class = NULL; /* * Now go through the list again, and for each face whose f_class has * not yet been set, set both it and its mate. (Faces will typically * be found consecutively with their mates, but not always, because * if two or more pairs of faces are the same distance from the * origin, they will be sorted by roundoff error.) */ count = 0; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->f_class == NULL) { face->f_class = NEW_STRUCT(WEFaceClass); face->mate->f_class = face->f_class; face->f_class->index = count++; face->f_class->hue = index_to_hue(face->f_class->index); face->f_class->num_elements = (face->mate == face) ? 1 : 2; face->f_class->parity = gl4R_determinant(*face->group_element) > 0.0 ? orientation_preserving : orientation_reversing; INSERT_BEFORE(face->f_class, &polyhedron->face_class_end); } /* * Set the num_face_classes field. */ polyhedron->num_face_classes = count; } static void edge_classes( WEPolyhedron *polyhedron) { int count; /* * Initialize all e_class fields to NULL to show they * have not yet been set. */ initialize_edge_classes(polyhedron); /* * Initialize the edge count. * We'll pass its address to mI_edge_classes() and S1_edge_classes() * so they can assign indices consistently. */ count = 0; /* * Determine which edges are identified to which under the action * of the face pairings. */ find_edge_mates(polyhedron); /* * The link of an edge (in the manifold or orbifold obtained by gluing * the Dirichlet domain's matching faces) may be either a circle or * an interval mI with mirror endpoints. First find the all mI edge * classes. The remaining edge classes must then be circular. */ mI_edge_classes(polyhedron, &count); S1_edge_classes(polyhedron, &count); /* * Record the number of edge classes. */ polyhedron->num_edge_classes = count; } static void initialize_edge_classes( WEPolyhedron *polyhedron) { WEEdge *edge; int i; /* * Initialize each edge->e_class to NULL to show it hasn't been set. * While we're at it, we might as well initialize the neighbor, * preserves_direction and preserves orientation fields as a guard * against programmer error. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { edge->e_class = NULL; for (i = 0; i < 2; i++) { edge->neighbor[i] = NULL; edge->preserves_direction[i] = -1; edge->preserves_orientation[i] = -1; } } } static void find_edge_mates( WEPolyhedron *polyhedron) { WEFace *face; /* * Initialize the face->matched flags to FALSE. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->matched = FALSE; /* * For each face which hasn't yet been matched, match it with its * mate and fill in the incident WEEdges' neighbor, preserves_direction * and preserves_orientation fields. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { match_incident_edges(face); face->matched = TRUE; face->mate->matched = TRUE; } } static void match_incident_edges( WEFace *face) { O31Vector *face_vertices, *mate_vertices; WEEdge *edge, *face_edge, *mate_edge; WEVertex *vertex; int count, i, j, offset, best_offset; double min_error, error, diff; WEEdgeSide face_side, mate_side; Boolean traverse_clockwise, sides_preserved, orientation_preserved, direction_preserved; /* * verify_faces() in Dirichlet_construction.c has already checked * that matching faces have the same number of sides. But it's * cheap and easy to check again. */ if (face->num_sides != face->mate->num_sides) uFatalError("match_incident_edges", "Dirichlet_extras"); /* * Allocate space for the coordinates of this face's vertices, * and for the images of face->mate's vertices. */ face_vertices = NEW_ARRAY(face->num_sides, O31Vector); mate_vertices = NEW_ARRAY(face->num_sides, O31Vector); /* * Copy the coordinates of this face's vertices, beginning at the * clockwise-most vertex of face->some_edge, and proceeding * counterclockwise around the face. */ edge = face->some_edge; count = 0; do { vertex = (edge->f[left] == face) ? edge->v[tail] : edge->v[tip]; o31_copy_vector(face_vertices[count++], vertex->x); edge = (edge->f[left] == face) ? edge->e[tip][left] : edge->e[tail][right]; } while (edge != face->some_edge); if (count != face->num_sides) uFatalError("match_incident_edges", "Dirichlet_extras"); /* * If face->group_element is orientation-preserving, we'll traverse * face->mate beginning at the counterclockwise-most vertex of * face->mate->some_edge and proceeding clockwise. * * If face->group_element is orientation-reversing, we'll traverse * face->mate beginning at the clockwise-most vertex of * face->mate->some_edge and proceeding counterclockwise. * * To decide whether face->group_element preserves or reverses * orientatation, check its determinant. The determinant will be * +1 or -1, so we needn't worry about roundoff errors. * * Rather than copy face->mate's vertex coordinates directly, * we'll apply the face pairing isometry to them, so they can be * compared directly to the coordinates of face's vertices. * Note that a vertex's coordinates don't lie on the hyperboloid * itself; instead they follow the convention that x[0] == 1.0. */ traverse_clockwise = (gl4R_determinant(*face->group_element) > 0.0); edge = face->mate->some_edge; count = 0; do { vertex = (edge->f[traverse_clockwise ? right : left] == face->mate) ? edge->v[tail] : edge->v[tip]; o31_matrix_times_vector(*face->group_element, vertex->x, mate_vertices[count]); for (i = 1; i < 4; i++) mate_vertices[count][i] /= mate_vertices[count][0]; mate_vertices[count][0] = 1.0; count++; edge = traverse_clockwise ? ( edge->f[right] == face->mate ? edge->e[tip][right] : edge->e[tail][left] ) : ( edge->f[left] == face->mate ? edge->e[tip][left] : edge->e[tail][right] ); } while (edge != face->mate->some_edge); if (count != face->mate->num_sides) uFatalError("match_incident_edges", "Dirichlet_extras"); /* * face_vertices[] will coincide with mate_vertices[] as sets, but * there'll be some offset in the ordering. For example, if the * offset is 3 and face->num_sides == 5, then * * face_vertices[0] == mate_vertices[3] * face_vertices[1] == mate_vertices[4] * face_vertices[2] == mate_vertices[0] * face_vertices[3] == mate_vertices[1] * face_vertices[4] == mate_vertices[2] * * Of course the coordinates won't match precisely because of roundoff * error, but we don't know just how big the roundoff error will be. * So we try all possible values for the offset, and see which one * produces the least error. (We compute the error as the sum of the * squares of the Euclidean distances from face_vertices[i] to * mate_vertices[i + offset] in the projective model.) */ min_error = DBL_MAX; for (offset = 0; offset < face->num_sides; offset++) { error = 0.0; for (i = 0; i < face->num_sides; i++) for (j = 1; j < 4; j++) { diff = face_vertices[i][j] - mate_vertices[(i + offset)%face->num_sides][j]; error += diff * diff; } if (error < min_error) { best_offset = offset; min_error = error; } } /* * We now know the relative orientation of face and face->mate, and * the offset needed to get them to match up. So we can tell their * incident edges about each other by setting their neighbor, * preserves_direction and preserves_orientation fields. We'll * traverse face and face->mate simultaneously, with face_edge and * mate_edge recording the edges currently being matched. */ /* * Set face_edge and mate_edge to the default starting edges. */ face_edge = face->some_edge; mate_edge = face->mate->some_edge; /* * Advance mate_edge to account for the offset. */ for (i = 0; i < best_offset; i++) mate_edge = traverse_clockwise ? ( mate_edge->f[right] == face->mate ? mate_edge->e[tip][right] : mate_edge->e[tail][left] ) : ( mate_edge->f[left] == face->mate ? mate_edge->e[tip][left] : mate_edge->e[tail][right] ); /* * Traverse face and face->mate simultaneously, matching up the * corresponding edges. */ do { /* * Which side of face_edge (left or right) lies on face? * Which side of mate_edge (left or right) lies on face->mate? */ face_side = (face_edge->f[left] == face) ? left : right; mate_side = (mate_edge->f[left] == face->mate) ? left : right; /* * Does face_side == mate_side? */ sides_preserved = (face_side == mate_side); /* * When we set traverse_clockwise above, we checked whether the * gluing preserves or reverses orientation. */ orientation_preserved = traverse_clockwise; /* * In the orientation preserving case, face_edge and mate_edge * point in the same direction iff (face_side != mate_side). * * In the orientation reversing case, face_edge and mate_edge * point in the same direction iff (face_side == mate_side). */ direction_preserved = (orientation_preserved ^ sides_preserved); /* * Tell face_edge and mate_edge about each other. */ face_edge->neighbor[face_side] = mate_edge; mate_edge->neighbor[mate_side] = face_edge; face_edge->preserves_sides[face_side] = sides_preserved; mate_edge->preserves_sides[mate_side] = sides_preserved; face_edge->preserves_direction[face_side] = direction_preserved; mate_edge->preserves_direction[mate_side] = direction_preserved; face_edge->preserves_orientation[face_side] = orientation_preserved; mate_edge->preserves_orientation[mate_side] = orientation_preserved; /* * Advance face_edge to the next position. */ face_edge = (face_edge->f[left] == face) ? face_edge->e[tip][left] : face_edge->e[tail][right]; /* * Advance mate_edge to the next position. */ mate_edge = traverse_clockwise ? ( mate_edge->f[right] == face->mate ? mate_edge->e[tip][right] : mate_edge->e[tail][left] ) : ( mate_edge->f[left] == face->mate ? mate_edge->e[tip][left] : mate_edge->e[tail][right] ); } while (face_edge != face->some_edge); /* * Free the local arrays. */ my_free(face_vertices); my_free(mate_vertices); } static void mI_edge_classes( WEPolyhedron *polyhedron, int *count) { WEEdge *edge; WEEdgeSide side; /* * Look for edges which have not been assigned to edge classes, * and which glue to themselves on a single side. Such edges * occur when the link of the edge's midpoint is one of the orbifolds * *nn, 2*n or 22n. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (side = 0; side < 2; side++) /* side = left, right */ if (edge->e_class == NULL && edge->neighbor[side] == edge && edge->preserves_sides[side] == TRUE) make_mI_edge_class(polyhedron, edge, side, (*count)++); } static void make_mI_edge_class( WEPolyhedron *polyhedron, WEEdge *edge, WEEdgeSide side, int index) { WEEdgeClass *new_class; WEEdge *this_edge, *next_edge; WEEdgeSide leading_side; /* * Allocate and initialize the new WEEdgeClass. */ new_class = NEW_STRUCT(WEEdgeClass); new_class->index = index; new_class->hue = index_to_hue(index); new_class->num_elements = 0; INSERT_BEFORE(new_class, &polyhedron->edge_class_end); /* * Start with "edge" and work our way around the edge class. * * We need to exit the loop at a different point from where we enter, * so we must use a "while (TRUE) {}" loop and break from the middle. * * At each step, this_edge will be the edge currently under * consideration, and leading_side will be the side (left or right) * where the next_edge is attached. */ this_edge = edge; leading_side = ! side; while (TRUE) { /* * Assign the edge class. */ this_edge->e_class = new_class; /* * Increment the count. */ new_class->num_elements++; /* * Which edge is next? */ next_edge = this_edge->neighbor[leading_side]; /* * If next_edge == this_edge, we note the topology of the edge * class and then break from the while (TRUE) {} loop. */ if (next_edge == this_edge) { /* * Check the topology. */ if (edge->preserves_direction[side] == TRUE) { if (this_edge->preserves_direction[leading_side] == TRUE) new_class->link = orbifold_xnn; else new_class->link = orbifold_2xn; } else /* edge->preserves_direction[side] == FALSE */ { if (this_edge->preserves_direction[leading_side] == TRUE) new_class->link = orbifold_2xn; else new_class->link = orbifold_22n; } /* * Exit the "while (TRUE) {}" loop. */ break; } /* * We want the edge directions to be consistent whenever possible. * So if this_edge and next_edge aren't consistently directed, * reverse the direction of next_edge. */ if (this_edge->preserves_direction[leading_side] == FALSE) redirect_edge(next_edge, TRUE); /* * We now know that preserves_direction is TRUE, so * * leading_side will change * iff preserves_orientation is FALSE * iff preserves_sides is TRUE */ if (this_edge->preserves_orientation[leading_side] == FALSE) leading_side = ! leading_side; /* * Move on to the next_edge, and continue with the loop. */ this_edge = next_edge; } } static void S1_edge_classes( WEPolyhedron *polyhedron, int *count) { WEEdge *edge; /* * Look for edges which have not been assigned to edge classes. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->e_class == NULL) make_S1_edge_class(polyhedron, edge, (*count)++); } static void make_S1_edge_class( WEPolyhedron *polyhedron, WEEdge *edge, int index) { WEEdgeClass *new_class; WEEdge *this_edge, *next_edge; WEEdgeSide leading_side; /* * The cases where the link of the edge's midpoint is (*nn), (2*n) * or (22n) have already been handled as mI edge classes. * Here we treat the case where the link of the midpoint is a sphere * or cross surface. */ /* * Allocate and initialize the new WEEdgeClass. */ new_class = NEW_STRUCT(WEEdgeClass); new_class->index = index; new_class->hue = index_to_hue(index); new_class->num_elements = 0; INSERT_BEFORE(new_class, &polyhedron->edge_class_end); /* * Start with "edge" and work our way around the edge class. * * We need to exit the loop at a different point from where we enter, * so we must use a "while (TRUE) {}" loop and break from the middle. * * At each step, this_edge will be the edge currently under * consideration, and leading_side will be the side (left or right) * where the next_edge is attached. */ this_edge = edge; leading_side = left; while (TRUE) { /* * Assign the edge class. */ this_edge->e_class = new_class; /* * Increment the count. */ new_class->num_elements++; /* * Which edge is next? */ next_edge = this_edge->neighbor[leading_side]; /* * If the next_edge is the original edge we started with, we * note the topology of the edge class and then break from the * while (TRUE) {} loop. */ if (next_edge == edge) { /* * Check the topology. */ if (this_edge->preserves_direction[leading_side] == TRUE) new_class->link = orbifold_nn; /* sphere */ else new_class->link = orbifold_no; /* cross surface */ /* * Exit the "while (TRUE) {}" loop. */ break; } /* * We want the edge directions to be consistent whenever possible. * So if this_edge and next_edge aren't consistently directed, * reverse the direction of next_edge. */ if (this_edge->preserves_direction[leading_side] == FALSE) redirect_edge(next_edge, TRUE); /* * We now know that preserves_direction is TRUE, so * * leading_side will change * iff preserves_orientation is FALSE * iff preserves_sides is TRUE */ if (this_edge->preserves_orientation[leading_side] == FALSE) leading_side = ! leading_side; /* * Move on to the next_edge, and continue with the loop. */ this_edge = next_edge; } } static void vertex_classes( WEPolyhedron *polyhedron) { WEVertex *vertex; /* * Initialize polyhedron->num_vertex_classes to zero. */ polyhedron->num_vertex_classes = 0; /* * Initialize all vertex->v_class fields to NULL so we can tell which * ones have been set and which haven't. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->v_class = NULL; /* * Create a vertex class for each vertex which doesn't yet have one, * and assign that class to all equivalent vertices. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) if (vertex->v_class == NULL) create_vertex_class(polyhedron, vertex); } static void create_vertex_class( WEPolyhedron *polyhedron, WEVertex *vertex) { WEVertexClass *new_class; Boolean progress; WEEdge *edge; WEEdgeEnd which_end; WEEdgeSide which_side; WEEdge *nbr_edge; WEEdgeEnd nbr_end; /* * Create the new class. * Don't worry about the solid angles for now; * they'll be computed later. */ new_class = NEW_STRUCT(WEVertexClass); new_class->index = polyhedron->num_vertex_classes++; new_class->hue = index_to_hue(new_class->index); new_class->num_elements = 0; INSERT_BEFORE(new_class, &polyhedron->vertex_class_end); /* * Assign the initial vertex to the new_class. */ vertex->v_class = new_class; new_class->num_elements++; /* * Find all other vertices belong to this class. * One could write an "efficient" algorithm to do this -- by carefully * locating the given vertex's neighbors and then continuing * recursively -- but for any reasonable polyhedron it will be just * as fast to simply keep scanning the edge list looking for * unassigned neighbors, and the code for this will be much simpler. * If this algorithm ever proves to be too slow, we can switch to * the more sophisticated approach. */ do { /* * We'll repeat the loop as long as we keep making progress. * Initialize progress to FALSE, and then set it to TRUE if and * when we assign the new_class to a previously unclassified vertex. */ progress = FALSE; /* * Look for edges which identify a new_class vertex to an * unassigned vertex. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (which_end = 0; which_end < 2; which_end++) /* which_end = tail, tip */ if (edge->v[which_end]->v_class == new_class) for (which_side = 0; which_side < 2; which_side++) /* which_side = left, right */ { nbr_edge = edge->neighbor[which_side]; nbr_end = edge->preserves_direction[which_side] ? which_end : ! which_end; if (nbr_edge->v[nbr_end]->v_class == NULL) { nbr_edge->v[nbr_end]->v_class = new_class; new_class->num_elements++; progress = TRUE; } } } while (progress == TRUE); } static void subdivide_edges_where_necessary( WEPolyhedron *polyhedron) { Boolean changes_made; WEEdge *edge; changes_made = FALSE; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) switch (edge->e_class->link) { /* * In the following three cases there is a covering * transformation which reverses the direction of the edge. * The covering transformation fixes the point closest to * the origin. We want to split the edge at that point. */ case orbifold_no: case orbifold_2xn: case orbifold_22n: compute_edge_distance(edge); split_edge(edge, edge->closest_point_on_edge, FALSE); polyhedron->num_vertices++; polyhedron->num_edges++; changes_made = TRUE; break; /* * In the following two cases the direction of the edge * is not reversed, so there is no need to subdivide it. */ case orbifold_nn: case orbifold_xnn: /* * Do nothing. */ break; default: uFatalError("subdivide_edges_where_necessary", "Dirichlet_extras"); } if (changes_made == TRUE) { delete_face_classes(polyhedron); delete_edge_classes(polyhedron); delete_vertex_classes(polyhedron); face_classes(polyhedron); edge_classes(polyhedron); vertex_classes(polyhedron); } } static void subdivide_faces_where_necessary( WEPolyhedron *polyhedron) { Boolean changes_made; WEFace *face; double trace; changes_made = FALSE; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (face->mate == face) { /* * A point P in the interior of a 2-cell on the boundary * of the Dirichlet domain is equidistant from the basepoint * and precisely one of the basepoint's translates. Therefore * P is fixed by the identity and at most one other covering * transformation. (Recall that SnapPea chooses a basepoint * which does not lie in the singular set, and therefore is * not fixed by any covering transformation.) * * Proposition. The non-identity covering transformation may be * * (1) a reflection through a point, * (2) a reflection through a line, or * (3) a reflection across a plane. * * Proof: The classification of isometries in complex_length.c * shows that an elliptic isometry of H^3 is a rotation about * axis, possibly followed by reflection in a plane orthogonal * to that axis. The only such isometries of order two are * the three ones listed above. Q.E.D. * * In case (1) there is an isolated cone point at the * center of the face. * In case (2) there is an order two cone axis bisecting * the face. * In case (3) the entire face is a mirror reflector. * * The three cases may be distinguished by the traces of * the covering transformation. * * In case (1) the trace is -2. * In case (2) the trace is 0. * In case (3) the trace is +2. */ trace = o31_trace(*face->group_element); if (fabs(fmod(fabs(trace) + 0.5, 1.0) - 0.5) > TRACE_ERROR_EPSILON) uFatalError("subdivide_faces_where_necessary", "Dirichlet_extras"); switch ((int) floor(trace + 0.5)) { case -2: cone_face_to_center(face, polyhedron); changes_made = TRUE; break; case 0: bisect_face(face, polyhedron); changes_made = TRUE; break; case +2: /* * The whole face is a mirror reflector. * No subdivision is needed. */ break; default: uFatalError("subdivide_faces_where_necessary", "Dirichlet_extras"); } } if (changes_made == TRUE) { delete_face_classes(polyhedron); delete_edge_classes(polyhedron); delete_vertex_classes(polyhedron); face_classes(polyhedron); edge_classes(polyhedron); vertex_classes(polyhedron); } } static void cone_face_to_center( WEFace *face, WEPolyhedron *polyhedron) { int old_num_sides; WEEdge **side_edge, **radial_edge; WEFace **new_face; WEVertex *central_vertex; O31Vector fixed_point; int i; /* * Note how many sides the face has before we subdivide. */ old_num_sides = face->num_sides; if (old_num_sides % 2 != 0) uFatalError("cone_face_to_center", "Dirichlet_extras"); /* * In this case there is no pre-existing function in * Dirichlet_construction.c for us to call, so I'll * write the low-level code here. The basic idea is * to replace * this with this * ______ ______ * / \ /\ /\ * / \ / \ / \ * / \ /____\/____\ * \ / \ /\ / * \ / \ / \ / * \______/ \/____\/ */ /* * To simplify the subsequent code, reorient the WEEdges so all are * directed counterclockwise around the face. */ all_edges_counterclockwise(face, TRUE); /* * Allocate some arrays to keep track of the edges and faces. */ side_edge = NEW_ARRAY(old_num_sides, WEEdge *); radial_edge = NEW_ARRAY(old_num_sides, WEEdge *); new_face = NEW_ARRAY(old_num_sides, WEFace *); /* * Record the side_edges. */ { WEEdge *edge; int count; edge = face->some_edge; count = 0; do { side_edge[count++] = edge; edge = edge->e[tip][left]; } while (edge != face->some_edge); if (count != old_num_sides) uFatalError("cone_face_to_center", "Dirichlet_extras"); } /* * Allocate the radial_edges. */ for (i = 0; i < old_num_sides; i++) { radial_edge[i] = NEW_STRUCT(WEEdge); INSERT_BEFORE(radial_edge[i], &polyhedron->edge_list_end); } /* * Allocate the new_faces. */ for (i = 0; i < old_num_sides; i++) { new_face[i] = NEW_STRUCT(WEFace); INSERT_BEFORE(new_face[i], face); } /* * Allocate the central_vertex. */ central_vertex = NEW_STRUCT(WEVertex); INSERT_BEFORE(central_vertex, &polyhedron->vertex_list_end); /* * The only field we need to set for the central_vertex is * its position. The central_vertex will lie halfway between * the basepoint (1,0,0,0) and its image under face->group_element. * The image under face->group_element is just the first column * of the group_element matrix. * * (To convince yourself that the following calculation is correct, * change coordinates so that the fixed point is at the north pole * in the Minkowski space model.) */ for (i = 0; i < 4; i++) fixed_point[i] = (i == 0 ? 1.0 : 0.0) + (*face->group_element)[i][0]; o31_constant_times_vector(1.0/fixed_point[0], fixed_point, central_vertex->x); /* * The new edges will be numbered and oriented as illustrated * below for the case old_num_sides == 6. All radial edges * will be directed inwards towards the central vertex, and will * inherit the index from the preceeding (i.e. immediately * counterclockwise) side edge. The faces are then numbered in * the obvious way, i.e. face i is incident to both radial_edge[i] * and side_edge[i]. * * ____/3____ * /\ \ /\ * 4/ \3 / \_ * |/_ _\| |/_2 |\2 * / \ / \ * /___4\___\/___/____\ * \ / /\ \1 / * \ _/ \_ _/ * 5_\| 5/| |\ /|1 * \ / 0\ / * \/____\___\/ * 0/ */ for (i = 0; i < old_num_sides; i++) { int ip, im, io; ip = (i + 1) % old_num_sides; /* i + 1 */ im = (i - 1 + old_num_sides) % old_num_sides; /* i - 1 */ io = (i + (old_num_sides/2)) % old_num_sides; /* i opposite */ radial_edge[i]->v[tail] = side_edge[i]->v[tip]; radial_edge[i]->v[tip] = central_vertex; radial_edge[i]->e[tail][left] = side_edge[i]; radial_edge[i]->e[tail][right] = side_edge[ip]; radial_edge[i]->e[tip ][left] = radial_edge[im]; radial_edge[i]->e[tip ][right] = radial_edge[ip]; radial_edge[i]->f[left] = new_face[i]; radial_edge[i]->f[right] = new_face[ip]; side_edge[i]->e[tail][left] = radial_edge[im]; side_edge[i]->e[tip ][left] = radial_edge[i]; side_edge[i]->f[left] = new_face[i]; new_face[i]->some_edge = side_edge[i]; new_face[i]->mate = new_face[io]; new_face[i]->group_element = NEW_STRUCT(O31Matrix); o31_copy(*new_face[i]->group_element, *face->group_element); new_face[i]->num_sides = 3; } /* * Remove the original face. * * (subdivide_faces_where_necessary() takes responsibility for * freeing its WEFaceClass.) */ REMOVE_NODE(face); my_free(face->group_element); my_free(face); face = NULL; /* * Adjust cell counts. */ polyhedron->num_vertices++; polyhedron->num_edges += old_num_sides; polyhedron->num_faces += old_num_sides - 1; /* * Free the arrays. */ my_free(side_edge); my_free(radial_edge); my_free(new_face); } static void bisect_face( WEFace *face, WEPolyhedron *polyhedron) { int count, current_side; WEEdge *edge; int old_num_sides; /* * We want to let cut_face_if_necessary() in Dirichlet_construction.c * do the low-level work. We set the incident vertices' * which_side_of_plane fields to show where the cut should be made. */ /* * To simplify the subsequent code, reorient the WEEdges so all are * directed counterclockwise around the face. */ all_edges_counterclockwise(face, TRUE); /* * Mark the vertices where the order 2 axis meets the * face's perimeter by setting their which_side_of_plane * fields to 0. (Note that the order 2 axis must meet * the perimeter at vertices -- not midpoints of edges -- * because we've already bisected such edges.) * * We assign which_side_of_plane = -1 and which_side_of_plane = +1 * to appropriate vertices by arbitrarily starting with * current_side = -1, and toggling it whenever we pass a vertex with * which_side_of_plane = 0. */ count = 0; current_side = -1; edge = face->some_edge; do { WEEdge *next_edge; next_edge = edge->e[tip][left]; if (edge->neighbor[left] == next_edge) { edge->v[tip]->which_side_of_plane = 0; count++; current_side = -current_side; } else edge->v[tip]->which_side_of_plane = current_side; edge = next_edge; } while (edge != face->some_edge); if (count != 2) uFatalError("bisect_face", "Dirichlet_extras"); /* * Note how many sides the face has before we make the cut. */ old_num_sides = face->num_sides; if (old_num_sides % 2 != 0) uFatalError("bisect_face", "Dirichlet_extras"); /* * Now we can make the call to cut_face_if_necessary(). * (Here, of course, the cut will be necessary!) */ cut_face_if_necessary(face, FALSE); /* * Adjust num_sides. */ face->num_sides = face->mate->num_sides = (old_num_sides + 2) / 2; /* * Adjust cell counts. */ polyhedron->num_edges++; polyhedron->num_faces++; } static void delete_face_classes( WEPolyhedron *polyhedron) { WEFaceClass *dead_face_class; WEFace *face; while (polyhedron->face_class_begin.next != &polyhedron->face_class_end) { dead_face_class = polyhedron->face_class_begin.next; REMOVE_NODE(dead_face_class); my_free(dead_face_class); } for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) face->f_class = NULL; } static void delete_edge_classes( WEPolyhedron *polyhedron) { WEEdgeClass *dead_edge_class; WEEdge *edge; while (polyhedron->edge_class_begin.next != &polyhedron->edge_class_end) { dead_edge_class = polyhedron->edge_class_begin.next; REMOVE_NODE(dead_edge_class); my_free(dead_edge_class); } for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) edge->e_class = NULL; } static void delete_vertex_classes( WEPolyhedron *polyhedron) { WEVertexClass *dead_vertex_class; WEVertex *vertex; while (polyhedron->vertex_class_begin.next != &polyhedron->vertex_class_end) { dead_vertex_class = polyhedron->vertex_class_begin.next; REMOVE_NODE(dead_vertex_class); my_free(dead_vertex_class); } for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->v_class = NULL; } static void dihedral_angles( WEPolyhedron *polyhedron) { WEEdgeClass *edge_class; WEEdge *edge; int i, j; O31Matrix *m[2]; O31Vector normal[2]; double length, angle_between_normals; /* * Initialize the total dihedral angle at each edge class to zero. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) edge_class->dihedral_angle = 0.0; /* * Compute the dihedral angle at each edge * and add it to the running total for its edge class. * * Proposition. The dihedral angle between two faces is the angle * between their normal vectors. * * Proof. Change coordinates so that the line of intersection * passes through the origin. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { /* * Compute the outward pointing normal to each face, and normalize * its length to one (it's guaranteed to be spacelike). */ for (i = 0; i < 2; i++) { /* * Let m[i] be the group_element at edge->f[i]. */ m[i] = edge->f[i]->group_element; /* * The first column of m[i] gives the image of the origin. */ for (j = 0; j < 4; j++) normal[i][j] = (*m[i])[j][0]; /* * Subtract off the coordinates of the basepoint (1, 0, 0, 0) * to get an outward pointing normal vector to face i. * (To see why this is correct, shift coordinates so that * the point midway between the origin and the origin's image * under the group_element lies at the origin.) */ normal[i][0] -= 1.0; /* * Normalize the normal vector to have length one. * (And forgive the two different uses of the word "normal".) */ length = safe_sqrt(o31_inner_product(normal[i], normal[i])); for (j = 0; j < 4; j++) normal[i][j] /= length; } /* * Use = |u| |v| cos(angle) to compute the angle * between normal[left] and normal[right]. * We know |u| = |v| = 1 because we've normalized the normals. */ angle_between_normals = safe_acos(o31_inner_product(normal[left], normal[right])); /* * The interior angle is pi minus the exterior angle. */ edge->dihedral_angle = PI - angle_between_normals; /* * Add this to the total for the edge class. */ edge->e_class->dihedral_angle += edge->dihedral_angle; } /* * Compute the singularity_order for each edge class as * 2pi/dihedral_angle, rounded to the nearest integer. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) edge_class->singularity_order = (int) floor((TWO_PI / edge_class->dihedral_angle) + 0.5); } static void solid_angles( WEPolyhedron *polyhedron) { WEVertex *vertex; WEEdge *edge; WEEdgeEnd which_end; WEVertexClass *vertex_class; /* * Compute the solid angle at each vertex. * * The solid angle is the total curvature of the link of the vertex. * For a finite vertex in a manifold, the link will be a 2-sphere, * and the total solid angle in the vertex class will be 4pi (orbifolds * admit other possibilities). For an ideal vertex in a manifold, * the link will be a torus or Klein bottle, and the total solid * angle will be zero (orbifolds admit other possible links, but all * will have zero Euler characteristic and zero total solid angle). * * Use the formula * * solid angle = (sum of incident dihedral angles) - (n - 2)pi * * Computationally, the plan is to first initialze the solid angle * at each vertex to 2pi, then add in (dihedral angle - pi) for each * incident edge. */ /* * Initialize each solid angle to 2pi. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->solid_angle = TWO_PI; /* * Go down the list of edges, adding (dihedral angle - pi) to the * solid angles of the incident vertices. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) for (which_end = 0; which_end < 2; which_end++) /* which_end = tail, tip */ edge->v[which_end]->solid_angle += edge->dihedral_angle - PI; /* * Initialize the total solid angle at each vertex class to zero. */ for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) vertex_class->solid_angle = 0.0; /* * Add the solid angle at each vertex to the total for its class. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->v_class->solid_angle += vertex->solid_angle; /* * Compute the singularity_order for each vertex class as * 4pi/solid_angle, rounded to the nearest integer. * 94/10/2 JRW * * Set vertex_class->singularity_order to zero for ideal vertices. * The vertex_class->ideal field hasn't yet been set, so we must * decide whether the vertex is ideal based on the solid angle. * 96/1/4 JRW */ for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) { if (vertex_class->solid_angle > SOLID_ANGLE_EPSILON) vertex_class->singularity_order = (int) floor((FOUR_PI / vertex_class->solid_angle) + 0.5); else vertex_class->singularity_order = 0; } } static FuncResult vertex_distances( WEPolyhedron *polyhedron) { WEVertex *vertex; WEVertexClass *vertex_class; /* * Compute the distances to the individual vertices. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) compute_vertex_distance(vertex); /* * Initialize the dist field in the vertex class to zero. * Initialize min_dist to INFINITE_DISTANCE and max_dist to zero. */ for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) { vertex_class->dist = 0.0; vertex_class->min_dist = INFINITE_DISTANCE; vertex_class->max_dist = 0.0; } /* * Initialize the global vertex counts to zero. */ polyhedron->num_finite_vertices = 0; polyhedron->num_ideal_vertices = 0; polyhedron->num_finite_vertex_classes = 0; polyhedron->num_ideal_vertex_classes = 0; /* * Use the dist field to record the sum of the distances of the * individual vertices. * * Note the minimum and maximum values. * * Count the finite and ideal vertices. * * Note whether the vertex class is ideal. (If some vertices were * ideal and some weren't, the error would be caught when comparing * vertex_class->min_dist and vertex_class->max_dist below. A finite * vertex will have a distance of at most about 17, as explained in * compute_vertex_distance() below.) */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { vertex->v_class->dist += vertex->dist; if (vertex->dist < vertex->v_class->min_dist) vertex->v_class->min_dist = vertex->dist; if (vertex->dist > vertex->v_class->max_dist) vertex->v_class->max_dist = vertex->dist; if (vertex->ideal == FALSE) polyhedron->num_finite_vertices++; else polyhedron->num_ideal_vertices++; vertex->v_class->ideal = vertex->ideal; } /* * For each vertex class, divide the sum of the individual distances * by the number of vertices in the class to get the average distance. * * Check that the minimum and maximum values are sufficiently close. * * Increment num_finite_vertex_classes or num_ideal_vertex_classes, * as appropriate. */ for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) { vertex_class->dist /= vertex_class->num_elements; if (vertex_class->max_dist - vertex_class->min_dist > DIST_EPSILON) return func_failed; if (vertex_class->ideal == FALSE) polyhedron->num_finite_vertex_classes++; else polyhedron->num_ideal_vertex_classes++; } /* * A quick error check, just to be safe. */ if (polyhedron->num_finite_vertex_classes + polyhedron->num_ideal_vertex_classes != polyhedron->num_vertex_classes) uFatalError("vertex_distances", "Dirichlet_extras"); return func_OK; } static void compute_vertex_distance( WEVertex *vertex) { /* * Compute the distance from the vertex to the origin, * and decide whether the vertex is ideal. * * For simplicity, consider a point d units from the origin on the * 1-dimensional "hyperbolic line" in (1,1)-dimensional Minkowski space. * The analysis for a point in 3-dimensional hyperbolic space in * (3,1)-dimensional Minkowski space is essentially the same, but * messier to write down. The point will have coordinates * (cosh d, sinh d). The first coordinate is timelike, the second * spacelike. * * Recall that all vertices have been normalized to have x[0] == 1.0. * Normalizing (cosh d, sinh d) to x[0] == 1 gives coordinates * * (cosh d, sinh d) * ---------------- = (1, tanh d) = x[]. * cosh d * * The squared norm of (cosh d, sinh d) is -1, so the squared norm of * (1, tanh d) will be -1/(cosh d)^2. The latter may be computed * directly as , so we can turn things around to solve * for cosh d. * * = -1/(cosh d)^2 * * cosh d = sqrt( -1 / ) * * Computationally speaking, the vertex will appear ideal iff the * square of its norm cannot be distinguished from zero. How far will * the vertex be from the origin when this occurs? It's easy to find * an approximation to tanh d for large d: * * e^d - e^-d 1 - e^-2d * tanh d = ------------ = ------------ ~ 1 - 2 e^-2d * e^d + e^-d 1 + e^-2d * * The squared norm of (1, 1 - 2 e^-2d) is * * -1 + (1 - 2 e^-2d)^2 ~ -1 + (1 - 4 e^-2d) * * This number ceases to be computable when -1 and (1 - 4 e^-2d) * become numerically indistinguishable. Of course numerical accuracy * begins to suffer long before that point. For what value of d * does this occur? On a 680x0 Macintosh, DBL_EPSILON is about 1e-19 * (on most other platforms -- where doubles have 6-byte rather than * 8-byte mantissas -- DBL_EPSILON will be more like 7e-15). To get * any reasonable accuracy, we'd need to have 4 e^-2d greater than, * say, 1e5 * DBL_EPSILON. This works out to about d = 17 on the * 680x0 Mac, or d = 11 on other platforms. * * The d = 17 (or d = 11) estimate is the farthest vertex distance we * could possibly hope to compute. In practice the vertex coordinates * won't be known to full accuracy, so ideal vertices may appear to * be closer. For example, some ideal vertices of L110123 appear at * distance d = 14 on a Mac. To be safe, we'll consider all vertices * at distance d > 8 to be ideal. This gives 4 e^-2d ~ 4e-7. * * These considerations lead us to declare the vertex to be ideal iff * its squared norm (which is a negative number) is greater than * - IDEAL_EPSILON = -4e-7. */ double norm_squared; norm_squared = o31_inner_product(vertex->x, vertex->x); if (norm_squared < - IDEAL_EPSILON) { vertex->dist = arccosh( safe_sqrt( -1.0 / norm_squared ) ); vertex->ideal = FALSE; } else { vertex->dist = INFINITE_DISTANCE; vertex->ideal = TRUE; } } static FuncResult edge_distances( WEPolyhedron *polyhedron) { WEEdge *edge; WEEdgeClass *edge_class; /* * Compute the distances to the individual edges. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) compute_edge_distance(edge); /* * Initialize the dist_line_to_origin and dist_edge_to_origin fields * in the edge class to zero. Initialize min_line_dist to * INFINITE_DISTANCE and max_line_dist to zero. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) { edge_class->dist_line_to_origin = 0.0; edge_class->dist_edge_to_origin = 0.0; edge_class->min_line_dist = INFINITE_DISTANCE; edge_class->max_line_dist = 0.0; } /* * Use the distance fields to record the sum of the distances to the * individual edges. Also note the minimum and maximum values. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { edge->e_class->dist_line_to_origin += edge->dist_line_to_origin; edge->e_class->dist_edge_to_origin += edge->dist_edge_to_origin; if (edge->dist_line_to_origin < edge->e_class->min_line_dist) edge->e_class->min_line_dist = edge->dist_line_to_origin; if (edge->dist_line_to_origin > edge->e_class->max_line_dist) edge->e_class->max_line_dist = edge->dist_line_to_origin; } /* * For each edge class, divide the sum of the individual distances * by the number of edges in the class to get the average distances. * * Check that the minimum and maximum values are sufficiently close. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) { edge_class->dist_line_to_origin /= edge_class->num_elements; edge_class->dist_edge_to_origin /= edge_class->num_elements; if (edge_class->max_line_dist - edge_class->min_line_dist > DIST_EPSILON) return func_failed; } return func_OK; } static void compute_edge_distance( WEEdge *edge) { O31Vector p[2], v[2], w, u, component; double length, projection, c[3], u_coord, p0_coord, p1_coord, basepoint[4] = {1.0, 0.0, 0.0, 0.0}; /* * We want to find the minimum distance from the basepoint to the line * containing the given edge, and decide whether that minimum occurs * within the edge itself. The basepoint and the line lie in a plane * in H^3 (the plane is not unique if the line passes through the * basepoint, but our algorithm works correctly in that case too). * The plane in H^3 determines a 3-dimensional subspace of Minkowski * space spanned by edge->v[tail], edge->v[tip] and the basepoint. * We will restrict out attention (and our sketches, which you should * make as you read along) to that 3-dimensional subspace. The * endpoints edge->v[tail] and edge->v[tip] may be either finite * vertices (timelike vectors) or ideal vertices (lightlike vectors) * independently of one another. */ o31_copy_vector(p[0], edge->v[tail]->x); o31_copy_vector(p[1], edge->v[tip ]->x); /* * To avoid fussing over whether the endpoints are finite or ideal, * we'll switch to a more convenient basis. Define * * v[0] = p[1] + p[0] * v[1] = p[1] - p[0] */ o31_vector_sum (p[1], p[0], v[0]); o31_vector_diff(p[1], p[0], v[1]); /* * Lemma. v[0] is timelike. * * Proof. Draw p[0] with its tail at the basepoint. Its tip lies * inside (resp. on) the forward light cone when p[0] is a finite * (resp. ideal) vertex. Now draw p[1] with its tail at the tip of * p[0], and draw a forward light cone centered at the p[1]'s tail. * If p[0] is timelike, then the forward lightcone at p[1]'s tail must * lie completely within the forward lightcone at p[0]'s tail, and * therefore p[0] + p[1] must be timelike. If p[0] is lightlike, * then the forward lightcone at p[1]'s tail intersects the forward * lightcone at p[0]'s tail along the ray determined by p[0]. But * p[0] and p[1] represent distinct points in H^3, so p[1] cannot * also lie along that ray. Therefore p[0] + p[1] must be timelike. * Q.E.D. * * Lemma. v[1] is spacelike. * * Proof. We've normalized the x[] coordinates of each vertex to * have x[0] == 1. So v[1][0] == 0. Q.E.D. * * Note: The first Lemma expresses a general fact about points in * Minkowski space. The second Lemma relies on our normalization * convention. */ /* * Normalize v[0] to unit length. */ length = safe_sqrt( - o31_inner_product(v[0], v[0]) ); o31_constant_times_vector(1.0/length, v[0], v[0]); /* * Make v[1] orthogonal to v[0]. */ projection = - o31_inner_product(v[0], v[1]); o31_constant_times_vector(projection, v[0], component); o31_vector_diff(v[1], component, v[1]); /* * Normalize v[1] to unit length. */ length = safe_sqrt(o31_inner_product(v[1], v[1])); o31_constant_times_vector(1.0/length, v[1], v[1]); /* * Express the basepoint as a linear combination * c[0]v[0] + c[1]v[1] + c[2]v[2], where v[2] is a spacelike unit * vector orthogonal to both v[0] and v[1]. (If the basepoint lies * in the plane spanned by v[0] and v[1], then c[2] = 0 and * v[2] is undefined.) */ o31_copy_vector(w, basepoint); c[0] = - o31_inner_product(w, v[0]); o31_constant_times_vector(c[0], v[0], component); o31_vector_diff(w, component, w); c[1] = o31_inner_product(w, v[1]); o31_constant_times_vector(c[1], v[1], component); o31_vector_diff(w, component, w); c[2] = safe_sqrt(o31_inner_product(w, w)); /* * If c[2] == 0, then the basepoint = c[0]v[0] + c[1]v[1] actually lies * on the given line, so the distance is zero. * * Otherwise, consider the 2-plane in Minkowski space spanned by the * basepoint and v[2]. (The vectors w and v[2] cannot be colinear * because one is timelike and the other spacelike.) This 2-plane * defines a line in H^3 which passes through w and is orthogonal to * the given line (proof: rotate coordinates so that the 2-plane is * vertical in your picture and v[2] remains horizontal). It follows * that the distance from the point to the line is sinh(c[2]), and the * point of closest approach is c[0]v[0] + c[1]v[1]. */ /* * Compute u = c[0]v[0] + c[1]v[1] = basepoint - w and * normalize the zeroth coordinate to one. */ o31_vector_diff(basepoint, w, u); o31_constant_times_vector(1.0/u[0], u, u); o31_copy_vector(edge->closest_point_on_line, u); /* * Record the distance from the basepoint to the line. */ edge->dist_line_to_origin = arcsinh(c[2]); /* * u lies between p[0] and p[1] as points in H^3 * * iff u lies between p[0] and p[1] as points in the projective model * (i.e. projected into the hyperplane with zeroth coordinate one) * * iff the v[1]-coordinate of u lies between * the v[1]-coordinates of p[0] and p[1]. */ u_coord = o31_inner_product(v[1], u); p0_coord = o31_inner_product(v[1], p[0]); p1_coord = o31_inner_product(v[1], p[1]); /* * Technical note: The construction of v[1] guarantees that the * v[1]-coordinate of p[1] exceeds that of p[0]. */ if (p0_coord >= p1_coord) uFatalError("compute_edge_distance", "Dirichlet_extras"); if (u_coord < p0_coord) { o31_copy_vector(edge->closest_point_on_edge, p[0]); edge->dist_edge_to_origin = edge->v[tail]->dist; } else if (u_coord > p1_coord) { o31_copy_vector(edge->closest_point_on_edge, p[1]); edge->dist_edge_to_origin = edge->v[tip]->dist; } else { o31_copy_vector(edge->closest_point_on_edge, edge->closest_point_on_line); edge->dist_edge_to_origin = edge->dist_line_to_origin; } } static void face_distances( WEPolyhedron *polyhedron) { /* * Comute the distance from the origin to the face plane. * The point closest to the origin may or may not lie on the face itself. * * The first column of the group_element gives the image of the * origin (1, 0, 0, 0) under the face pair isometry. Hence * group_element[0][0] equals cosh(2*dist). */ WEFace *face; int i; O31Vector the_image, the_sum, the_midpoint; O31Vector the_origin = {1.0, 0.0, 0.0, 0.0}; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { /* * Compute the distance to the face plane. */ face->dist = 0.5 * arccosh((*face->group_element)[0][0]); face->f_class->dist = face->dist; /* * Find the point on the face plane which realizes the distance. */ /* * Find the image of the origin under the action of the group_element. */ for (i = 0; i < 4; i++) the_image[i] = (*face->group_element)[i][0]; /* * Find the point midway between the origin (1,0,0,0) and the_image. * (Conceptually we should think of the midpoint as being halfway * between the_origin and the_image in H^3 itself, not in the * ambient E^(3,1). But either way determines the same ray through * (0,0,0,0). Proof: visualize the construction in a coordinate * system in which the_midpoint lies on the positive * 0-th coordinate axis.) */ o31_vector_sum(the_origin, the_image, the_sum); o31_constant_times_vector(0.5, the_sum, the_midpoint); /* * Normalize the_midpoint to have zeroth coordinate 1.0. * (Normalizing it to have length one might make more sense, * but we want to be consistent with how other points are recorded.) */ o31_constant_times_vector( 1.0 / the_midpoint[0], the_midpoint, face->closest_point); } } static FuncResult edge_lengths( WEPolyhedron *polyhedron) { WEEdge *edge; WEEdgeClass *edge_class; /* * Compute the lengths of the individual edges. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) compute_edge_length(edge); /* * Initialize each edge class's length field to zero. * Initialize min_length to INFINITE_LENGTH and max_length to zero. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) { edge_class->length = 0.0; edge_class->min_length = INFINITE_LENGTH; edge_class->max_length = 0.0; } /* * Use the length field to record the sum of the lengths of the * individual edges. Also note the minimum and maximum values. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { edge->e_class->length += edge->length; if (edge->length < edge->e_class->min_length) edge->e_class->min_length = edge->length; if (edge->length > edge->e_class->max_length) edge->e_class->max_length = edge->length; } /* * For each edge class, divide the sum of the individual lengths * by the number of edges in the class to get the average length. * * Check that the minimum and maximum values are sufficiently close. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) { edge_class->length /= edge_class->num_elements; if (edge_class->max_length - edge_class->min_length > LENGTH_EPSILON) return func_failed; } return func_OK; } static void compute_edge_length( WEEdge *edge) { if (edge->v[tail]->dist == INFINITE_DISTANCE || edge->v[tip ]->dist == INFINITE_DISTANCE) edge->length = INFINITE_LENGTH; else edge->length = arccosh( -o31_inner_product(edge->v[tail]->x, edge->v[tip]->x) / ( safe_sqrt(-o31_inner_product(edge->v[tail]->x, edge->v[tail]->x)) * safe_sqrt(-o31_inner_product(edge->v[tip ]->x, edge->v[tip ]->x)) )); } static void compute_approx_volume( WEPolyhedron *polyhedron) { /* * The plan is to decompose the Dirichlet domain into "birectangular * tetrahedra", whose volumes may be computed using the formula in * * E. B. Vinberg, Ob'emy neevklidovykh mnogogrannikov, * Uspekhi Matematicheskix Nauk, May(?) 1993, 17-46. * * Each birectangular tetrahedron has vertices at * * (1) the origin (1,0,0,0), * (2) a point which realizes the minimum distance from a face * plane to the origin (this point may or may not lie within * the face itself), * (3) a point which realizes the minimum distance from one of * the face's edges to the origin (this point may or may not * lie within the edge itself), and * (4) one of the edge's endpoints. * * I recommend that you make yourself a sketch to see how the above * definition serves to divide the Dirichlet domain into birectangular * tetrahedra. The fact that the points in (2) and (3) minimize * the distance from a face or edge to the origin insures that all * the necessary right angles are present. * * If some of the points in (2) and (3) lie outside their respective * faces and edges, then some of the birectangular tetrahedra will * be negatively oriented. But if we keep track of which are * positively oriented and which are negatively oriented, we can still * compute a correct volume. Note that Vinberg's formula does not * "automatically" work for negatively oriented tetrahedra, which is * why we must keep track of the orientations ourselves. */ double total_volume, tetrahedron_volume; WEEdge *edge; int i, j, k; Boolean nominal_orientation, actual_orientation; /* * The {a, b, c, d} correspond to Vinberg's notation. */ O31Vector a, /* at vertex = (4) above */ b, /* on edge = (3) above */ c, /* on face = (2) above */ d; /* at origin = (1) above */ GL4RMatrix abcd; O31Vector origin = {1.0, 0.0, 0.0, 0.0}; o31_copy_vector(d, origin); total_volume = 0.0; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) { o31_copy_vector(b, edge->closest_point_on_line); for (i = 0; i < 2; i++) /* i = left, right */ { o31_copy_vector(c, edge->f[i]->closest_point); for (j = 0; j < 2; j++) /* j = tail, tip */ { o31_copy_vector(a, edge->v[j]->x); /* * If the tetrahedron's actual orientation matches its * nominal orientation, we add its volume to the total. * Otherwise we subtract it. */ /* * We don't have to strain our brains figuring out which * orientation should be called positive and which should * be called negative. All that matters is that the * nominal_orientation and actual_orientation are computed * consistently. That will ensure that the we end up with * either the true volume or its negative. (If we end * up with its negative, I'll come back and change the * definition of the nominal_orientation to the opposite * of what it was before.) */ /* * The nominal_orientation toggles if we toggle i * (leaving j fixed) or toggle j (leaving i fixed). */ nominal_orientation = (i != j); /* * The determinant toggles when the actual_orientation * toggles, so we may use the former to compute the latter. */ for (k = 0; k < 4; k++) { abcd[0][k] = a[k]; abcd[1][k] = b[k]; abcd[2][k] = c[k]; abcd[3][k] = d[k]; } actual_orientation = (gl4R_determinant(abcd) > 0.0); tetrahedron_volume = birectangular_tetrahedron_volume(a, b, c, d); if (nominal_orientation == actual_orientation) total_volume += tetrahedron_volume; else total_volume -= tetrahedron_volume; } } } polyhedron->approximate_volume = total_volume; } static void compute_inradius( WEPolyhedron *polyhedron) { /* * Definition. The "inradius" is the radius of the largest sphere * centered at the basepoint which may be inscribed in the Dirichlet * domain. * * Definition. A "face plane" is a plane containing a face of * the Dirichlet domain. * * Proposition. The inradius is the minimum distance from the * basepoint to a face plane. * * Comment. We care only about the distance from the origin to the * face plane. We don't care whether that minimum occurs within the * face itself. * * Proof. The Dirichlet domain is the intersection of the halfspaces * determined by the face planes. Therefore a sphere centered at the * basepoint will be contained in the Dirichlet domain iff it is * contained in all the aforementioned halfspaces. The sphere will be * contained in all the aforementioned halfspaces iff its radius is at * most the distance from the origin to the closest face plane. Q.E.D. */ WEFace *face; double min_value; /* * The distance from the origin to a face plane is * 0.5 * arccosh(face->group_element[0][0]). So we look for the * minimum value of face->group_element[0][0]. */ min_value = INFINITE_RADIUS; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if ((*face->group_element)[0][0] < min_value) min_value = (*face->group_element)[0][0]; /* * Convert min_value to the true hyperbolic distance. */ polyhedron->inradius = 0.5 * arccosh(min_value); } static void compute_outradius( WEPolyhedron *polyhedron) { /* * The Dirichlet domain is convex, so the outradius will be * the maximum distance from a vertex to the origin. */ WEVertex *vertex; double max_projective_distance, projective_distance; /* * First find the maximum distance from the basepoint (1, 0, 0, 0) * to the point (1, x, y, z) relative to the Euclidean metric of * the projective model. (Actually we'll compute the square of * the distance.) */ max_projective_distance = 0.0; for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { /* * We assume the vertex->ideal field has already been set. * If this is an ideal vertex, the outradius is infinite * and we're done. */ if (vertex->ideal == TRUE) { polyhedron->outradius = INFINITE_RADIUS; return; } /* * Compute the squared Euclidean distance to the origin * in the projective model. */ projective_distance = vertex->x[1] * vertex->x[1] + vertex->x[2] * vertex->x[2] + vertex->x[3] * vertex->x[3]; if (projective_distance > max_projective_distance) max_projective_distance = projective_distance; } /* * Convert the squared projective distance to the true hyperbolic * distance. Let d denote the (unsquared) projective distance. * The true hyperbolic distance will be the same as the distance * from (1, 0) to (1, d) in the 1-dimensional projective model. * To compute that distance, transfer the points to the hyperbolic line * H^1 = {(y,x) | -y^2 + x^2 = -1} in the Minkowski space model. * * (1, 0) maps to (1, 0) * (1, d) maps to (1/sqrt(1 - d^2), d/sqrt(1 - d^2)) * * Recall that for two points u and v in the Minkowski space model * of H^n, cosh(dist(u,v)) = -. So the distance between the two * above points is therefore arccosh(1/sqrt(1 - d^2)). */ polyhedron->outradius = arccosh( 1.0 / safe_sqrt(1.0 - max_projective_distance) ); } static void compute_spine_radius( WEPolyhedron *polyhedron) { /* * Parts of the following documentation appear in the paper * * C. Hodgson and J. Weeks, Symmetries, isometries and length * spectra of closed hyperbolic 3-manifolds, to appear in * Experimental Mathematics. */ /* * Definition. The "spine radius" is the infimum of the radii (measured * from the origin) of all spines dual to the Dirichlet domain. * * Definition (local to the following proposition). The "maximin * edge distance" is the maximum over all edges of the minimum distance * from the edge to the origin. (Computationally, it's the maximum * value of dist_edge_to_origin over all edge classes.) * * Proposition. The spine radius equals the maximin edge distance. * * Proof. Any spine dual to the Dirichlet domain must intersect every * edge, so the spine radius is greater than or equal to the maximin * edge distance. It remains to show that for any epsilon greater than * zero, we can construct a spine whose radius is within epsilon of the * maximin edge distance. * * Step 1. On each edge, mark the point closest to the origin. * If that point is at an endpoint, displace it a distance * epsilon into the interior of the edge. Note that * (a) the edge identifications respect the marked points, and * (b) the marked points all lie within the maximin edge * distance plus epsilon of the origin. * * Step 2. On each face, mark the point closest to the origin. * If that point is on the boundary, displace it a distance * epsilon into the interior of the face. Note that * (a) the face identifications respect the marked points, and * (b) the marked points all lie within the maximin edge * distance plus epsilon of the origin. * * Step 3. Draw lines from the marked point in the interior of each * face to the marked points on the incident edges. Note that * (a) the face identifications respect the lines, and * (b) the lines all lie within the maximin edge * distance plus epsilon of the origin. * * Step 4. Cone the complex created in steps (1), (2) and (3) to * the origin. This gives a spine which is dual to the * Dirichlet domain and lies within the maximin edge * distance plus epsilon of the origin. * * Q.E.D. * * * Modifications to the above construction. * * The spine radius discussed above works correctly for all manifolds, * but it can be large for a cusped manifold whose Dirichlet * domain contains a vertex lying "out in the cusp". For example, * the manifold m015 has a vertex at a distance 3.29 from the center * of the Dirichlet domain. This yields a large value for the spine * radius, which in turn makes the (exponential time) length spectrum * algorithm run very slowly. Fortunately, such a large spine radius * is unnecessary. Roughly speaking, vertices "out in the cusp" should * be considered part of the cusp. We can make this idea rigorous * as follows. [Note: After applying the following modifications, * the spine radius for m015 went from 3.29 down to 0.84. Therefore * the tiling radius for a length spectrum to L = 1.0 went from over 6 * to about 2, i.e. from nearly impossible to almost instanteneous.] * * Definition. In the following discussion, "the space" means * the manifold or orbifold obtained by gluing the faces of the * Dirichlet domain. * * The spine divides the space into 3-dimensional regions dual * to a vertices of the Dirichlet domain. In a manifold, a region * dual to a finite vertex will be a 3-ball, but in an orbifold * a region dual to a finite vertex could be a cone on any spherical * 2-orbifold. Similarly, a region dual to an ideal vertex will be * either a torus or Klein bottle cross a half line, but in an orbifold * it may be any Euclidean 2-orbifold cross a half line. * * Proposition. If a 2-cell in the spine separates two distinct * regions, at least one of which is topologically a 3-ball, then * we may remove the 2-cell and still retain the essential property * of the spine, namely that every closed geodesic must intersect it. * * Proof. Obvious. Q.E.D. * * Definition. A "free edge" of a 2-cell in a spine is an edge * which is adjacent to no other 2-cells (nor to any other edges * of the given 2-cell). Initially there are no free edges, but * some may be created as 2-cells are eliminated as in the above * proposition. * * Proposition. If a 2-cell in the spine is dual to a nonsingular * edge (in the Dirichlet complex) and has a free edge (in the spine), * then we may remove the 2-cell and still retain the essential property * of the spine, namely that every closed geodesic must intersect it. * * Proof. The 2-cell is a disk. Isotope the free edge across * the 2-cell to eliminate both. The topology of the incident region * does not change, so neither does the fact that every geodesic * must intersect the spine. Q.E.D. * * Proposition. If, after applying the above propositions, we find * a 1-cell in the spine with no incident 2-cells, we may remove the * 1-cell and still retain the essential property of the spine, namely * that every closed geodesic must intersect it. * * Proof. We may locally isotop a geodesic to avoid naked 1-cells. * If the geodesic had no other intersections with the spine, then * it would lie entirely within a single region, and therefore * couldn't be a geodesic. Q.E.D. * * Comment. We remove the naked 1-cells only after we've finished * removing 2-cells. * * Proposition. After removing some 2-cells and 1-cells from a spine * as in the preceding propositions, the radius of the remaining spine * will be the "maximin" edge distance (defined above), taken over the * edges which are dual to the remaining 2-cells (i.e. excluding edges * dual to 2-cells which have been removed). * * Proof. This is almost an immediate consequence of the algorithm for * constructing the spine. The only situation that could get us into * trouble would be a naked 1-cell in the spine, but the previous * proposition shows that we may remove them. Q.E.D. * * Our algorithm will be to look at an edge for which * edge_class->dist_edge_to_origin is a maximum. If it does not * connect two distinct regions, one of which is a 3-ball, then * we look for free edges. If that fails, then edge_class-> * dist_edge_to_origin is the spine_radius and we're done. * If the edge does connect a 3-ball to some other region, we remove * the dual 2-cell and continue with the edge having the next greatest * value of edge_class->dist_edge_to_origin, and so on until we reach * a 2-cell which cannot be removed, at which point we're done. */ WEEdgeClass *edge_class; WEVertexClass *vertex_class, *vc[2], *region[2]; double max_value; WEEdge *edge, *max_edge; Boolean union_is_3_ball; /* * Intialize all edge_class->removed flags to FALSE. */ for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) edge_class->removed = FALSE; /* * Initially the region dual to each vertex belongs * to itself (i.e. none have been merged). A region * is a 3-ball iff its solid_angle is 4pi. */ for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) { vertex_class->belongs_to_region = vertex_class; vertex_class->is_3_ball = (vertex_class->solid_angle > 4.0*PI - PI_EPSILON); } /* * Look at each edge class in turn, starting with the one furthest * from the origin. If its dual 2-cell may be removed, remove it. * Otherwise set the spine_radius and return. */ while (TRUE) { /* * Find a representative of the furthest edge class * which has not already been removed. */ max_value = 0.0; for ( edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->e_class->removed == FALSE && edge->e_class->dist_edge_to_origin > max_value) { max_edge = edge; max_value = edge->e_class->dist_edge_to_origin; } if (max_value == 0.0) uFatalError("compute_spine_radius", "Dirichlet_extras"); /* * Note the vertex classes at max_edge's endpoints. */ vc[0] = max_edge->v[0]->v_class; vc[1] = max_edge->v[1]->v_class; /* * If the regions dual to max_edge's endpoints are distinct, * and at least one is a 3-ball, then max_edge may be removed. */ if (vc[0]->belongs_to_region != vc[1]->belongs_to_region && (vc[0]->is_3_ball || vc[1]->is_3_ball)) { /* * We found a removable edge! */ /* * Remove the edge. */ max_edge->e_class->removed = TRUE; /* * Annex vc[1]'s region to vc[0]'s. */ region[0] = vc[0]->belongs_to_region; region[1] = vc[1]->belongs_to_region; for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) if (vertex_class->belongs_to_region == region[1]) vertex_class->belongs_to_region = region[0]; /* * Is the union of the two regions a 3-ball? */ union_is_3_ball = (vc[0]->is_3_ball && vc[1]->is_3_ball); for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) if (vertex_class->belongs_to_region == region[0]) vertex_class->is_3_ball = union_is_3_ball; } else { /* * If we're lucky, some free edge removal might get rid * of the 2-cell dual to max_edge. */ attempt_free_edge_removal(polyhedron); /* * Did free edge removal do the trick? */ if (max_edge->e_class->removed == TRUE) { /* * Great. This edge is gone. * Continue with the loop to examine the next edge. */ } else { /* * We found a nonremovable edge. */ polyhedron->spine_radius = max_value; return; } } } /* * The function returns from within the above loop. */ } static void attempt_free_edge_removal( WEPolyhedron *polyhedron) { WEFace *face; WEEdge *edge, *remaining_edge; int count; /* * Examine each of the polyhedron's faces. */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { /* * Count how many of the incident edges have not yet * been removed. If a non-removed edge is found, remember it. */ count = 0; remaining_edge = NULL; edge = face->some_edge; do { /* * Has this edge been removed? */ if (edge->e_class->removed == FALSE) { count++; remaining_edge = edge; } /* * Advance counterclockwise to the next edge. */ if (edge->f[left] == face) edge = edge->e[tip][left]; else edge = edge->e[tail][right]; } while (edge != face->some_edge); /* * If precisely one incident edge has a dual 2-cell which * has not been removed, then we have a free edge. */ if (count == 1) { /* * We may isotope the free edge across the dual 2-cell * iff the edge is nonsingular. */ if (remaining_edge->e_class->dihedral_angle > 2.0*PI - PI_EPSILON) { /* * Remove the edge. * (The incident 3-cell's belongs_to_region and * is_3_ball fields are not affected.) */ remaining_edge->e_class->removed = TRUE; /* * Set face = &polyhedron->face_list_begin to restart * the loop, just in case removing this edge allows * other edges to be removed. */ face = &polyhedron->face_list_begin; } } } } static void compute_deviation( WEPolyhedron *polyhedron) { /* * Each face->group_element is, in theory, an element of SO(3,1). * Record the greatest deviation from O(3,1) in polyhedron->deviation, * so the UI has some idea how precise the calculations are. */ WEFace *face; double the_deviation; polyhedron->deviation = 0.0; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { the_deviation = o31_deviation(*face->group_element); if (the_deviation > polyhedron->deviation) polyhedron->deviation = the_deviation; } } static void compute_geometric_Euler_characteristic( WEPolyhedron *polyhedron) { /* * As explained in winged_edge.h the geometric Euler characteristic is * * c[0] - c[1] + c[2] - c[3] * * where * * c[0] = the sum of the solid angles at the vertices divided by 4 pi, * * c[1] = the sum of the dihedral angles at the edges divided by 2 pi, * * c[2] = half the number of faces of the Dirichlet domain, * * c[3] = the number of 3-cells, which is always one. */ double c[4]; WEVertexClass *vertex_class; WEEdgeClass *edge_class; /* * Compute c[0]. */ c[0] = 0.0; for ( vertex_class = polyhedron->vertex_class_begin.next; vertex_class != &polyhedron->vertex_class_end; vertex_class = vertex_class->next) c[0] += vertex_class->solid_angle; c[0] /= 4.0 * PI; /* * Compute c[1]. */ c[1] = 0.0; for ( edge_class = polyhedron->edge_class_begin.next; edge_class != &polyhedron->edge_class_end; edge_class = edge_class->next) c[1] += edge_class->dihedral_angle; c[1] /= 2.0 * PI; /* * Compute c[2]. */ c[2] = (double)polyhedron->num_faces / 2.0; /* * "Compute" c[3]. */ c[3] = 1.0; /* * Compute the geometric Euler characteristic of the quotient * manifold obtained by identifying faces of the Dirichlet domain. */ polyhedron->geometric_Euler_characteristic = c[0] - c[1] + c[2] - c[3]; } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_precision.c0100444000175000017500000001407306742675501021034 0ustar babbab/* * Dirichlet_precision.c * * This file provides the functions * * void precise_generators(MatrixPairList *gen_list); * void precise_matrix(O31Matrix m); * Boolean precise_double(double *x); * * which address the nasty issue of roundoff error in O31Matrices. * * The root of the problem is that large numbers may occur in the O(3,1) * matrices, and when you multiply two large numbers x and y with * respective errors dx and dy, you get (x + dx)(y + dy) = xy + ydx + xdy * + (ignore second order term). The errors ydx and xdy aren't so bad * compared to the product xy (if x and y are in the thousands, xy will be * in the millions). But when you do the matrix multiplication you end up * summing four such terms, and the four xy's largely cancel, so you're * left with a sum of reasonable magnitude (same magnitude as x and y). * But the error is still of magnitude ydx or xdy. In other words, each * time you do a matrix multiplication, the error is multiplied by the * magnitude of the entries! For matrices with large entries (10^5 or * 10^6 is not extreme), these errors become completely unacceptable after * only a few matrix multiplications. * * In general, there is no satisfactory solution to avoiding the roundoff * error. Fortunately, in many of the nicest examples (e.g. those coming * from triangulations with 60-60-60 or 45-45-90 ideal tetrahedra) the * matrix entries are quarter integer multiples of 1, sqrt(2), and sqrt(3). * (I'm still investigating this, and working on proofs. I might add to * this documentation later.) In these cases, if we can recognize a matrix * entry to be a nice number to fairly good precision, we can set it equal * to that number to full precision. If we do so after each matrix * multiplication, the roundoff error will stay under control. * * precise_o31_product() multiplies two O31Matrices with an eye to * recognizes nice numbers. precise_generators() tries to recognize * nice numbers in an initial list of generators. */ #include "kernel.h" #include "Dirichlet.h" #define EPSILON (1e2 * DBL_EPSILON) #define DEFAULT_EPSILON (1e6 * DBL_EPSILON) #define ROOT2 1.41421356237309504880 #define ROOT3 1.73205080756887729352 static void precise_matrix(O31Matrix m); static Boolean precise_trace(O31Matrix m); static Boolean precise_double(double *x, double epsilon); void precise_o31_product( O31Matrix a, O31Matrix b, O31Matrix product) { int i, j, k; double sum, abs_sum, term; O31Matrix temp; /* * If a and b don't have precise traces, then we don't waste our time * looking for precise entries. */ if (precise_trace(a) == FALSE || precise_trace(b) == FALSE) { o31_product(a, b, product); return; } /* * As explained at the top of this file, the product of two numbers * x and y with respective roundoff errors dx and dy will be * (x + dx)(y + dy) = xy + xdy + ydx + (ignore second order term). * If the original factors are known to maximum precision, then * dx ~ x*DBL_EPSILON and dy ~ y*DBL_EPSILON, so the expected error * in the product will be about x*y*DBL_EPSILON. That is, the * product will be known to (almost) maximum precision when the two * factors are. * * The error in a matrix multiplication lies not in the * multiplication of the entries, but in the addition of the products * of entries. Several large numbers may partially cancel each other * when added, giving a sum whose absolute value is much less than that * of any of the factors. Unfortunately, the errors don't cancel so * nicely, so we sometimes end up with a small sum and a large error. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { sum = 0.0; abs_sum = 0.0; for (k = 0; k < 4; k++) { term = a[i][k] * b[k][j]; sum += term; abs_sum += fabs(term); } precise_double(&sum, abs_sum*EPSILON); temp[i][j] = sum; } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) product[i][j] = temp[i][j]; } void precise_generators( MatrixPairList *gen_list) { MatrixPair *matrix_pair; for (matrix_pair = gen_list->begin.next; matrix_pair != &gen_list->end; matrix_pair = matrix_pair->next) { precise_matrix(matrix_pair->m[0]); o31_invert(matrix_pair->m[0], matrix_pair->m[1]); } } static void precise_matrix( O31Matrix m) { int i, j; /* * First check the trace of m. * If the trace isn't a nice number, give up now. */ if (precise_trace(m) == FALSE) return; /* * Look at each entry in the O31Matrix m, and see whether it's a nice * number. If so, write in the exact value to full precision. We don't * know where this matrix came from, so use the DEFAULT_EPSILON. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) (void) precise_double(&m[i][j], fabs(m[i][j])*DEFAULT_EPSILON); } static Boolean precise_trace( O31Matrix m) { int i; double trace, sum_abs; trace = 0.0; sum_abs = 0.0; for (i = 0; i < 4; i++) { trace += m[i][i]; sum_abs += fabs(m[i][i]); } return precise_double(&trace, sum_abs*DEFAULT_EPSILON); } static Boolean precise_double( double *x, double epsilon) { double x4, x_int, x_root2, x_root2_int, x_root3, x_root3_int; /* * If *x is nonzero, then the given value of epsilon will be fine. * But it may not work so well for recognizing zero, so we check * for zero as a special case. */ if (fabs(*x) < DEFAULT_EPSILON) { *x = 0.0; return TRUE; } /* * We're interested in quarter integer multiples of 1, sqrt(2) and * sqrt(3), so multiply *x by 4. */ x4 = 4 * (*x); /* * Is x4 an integer? */ x_int = floor(x4 + 0.5); if (fabs(x4 - x_int) < epsilon) { *x = x_int / 4.0; return TRUE; } /* * Is x4 an integer multiple of sqrt(2)? */ x_root2 = x4 / ROOT2; x_root2_int = floor(x_root2 + 0.5); if (fabs(x_root2 - x_root2_int) < epsilon) { *x = (x_root2_int / 4.0) * ROOT2; return TRUE; } /* * Is x4 an integer multiple of sqrt(3)? */ x_root3 = x4 / ROOT3; x_root3_int = floor(x_root3 + 0.5); if (fabs(x_root3 - x_root3_int) < epsilon) { *x = (x_root3_int / 4.0) * ROOT3; return TRUE; } return FALSE; } snappea-3.0d3/SnapPeaKernel/code/Dirichlet_rotate.c0100444000175000017500000001342006742675501020332 0ustar babbab/* * Dirichlet_rotate.c * * This file provides the following functions for the UI, to allow * convenient Dirichlet domain rotation. * * void set_identity_matrix(O31Matrix position); * void set_poly_velocity(O31Matrix velocity, double theta[3]); * void update_poly_position(O31Matrix position, O31Matrix velocity); * void update_poly_vertices(WEPolyhedron *polyhedron, * O31Matrix position, double scale); * void update_poly_visibility(WEPolyhedron *polyhedron, * O31Matrix position, O31Vector direction); * * set_identity_matrix() sets a position or velocity matrix to the identity. * * set_poly_velocity() takes as input the small rotation angles theta[] * about each of the coordinate axes, and computes the corresonding * rotation matrix. set_poly_velocity() may be used to update the * velocity when the user drags the polyhedron with the mouse. * For example, say the user interface is using a coordinate system * in which the x-axis points to the right, the y-axis points down, * and the z-axis points into the screen, and the user drags the * mouse a small distance (dx, dy). The dx corresponds to a small * clockwise rotation about the y-axis. The dy corresponds to a small * counterclockwise rotation about the x-axis. So we pass the vector * theta[3] = (dy, -dx, 0) to set_poly_velocity() and get back the * corresponding velocity matrix. * * update_poly_position() updates the position by left-multiplying by the * velocity. Call it to keep the polyhedron moving. * * update_poly_vertices() multiplies the standard vertex coordinates x[] * by the position matrix to obtain the rotated coordinates xx[]. * It then multiplies the rotated coordinates by the constant "scale"; * this lets the UI scale the rotated coordinates to match the window * coordinates (if you don't need this feature, pass scale = 1.0). * * update_poly_visibility() checks which vertices, edges and faces are * visible to the user with the polyhedron in its present position, * and sets their visibility fields accordingly. The direction vector * points from the center of the polyhedron to the user. Note that * the direction vector is an O31Vector; just set the 0-th component * to 0.0. */ #include "kernel.h" static void set_face_visibility(WEPolyhedron *polyhedron, O31Matrix position, O31Vector direction); static void set_edge_visibility(WEPolyhedron *polyhedron); static void set_vertex_visibility(WEPolyhedron *polyhedron); void set_identity_matrix( O31Matrix m) { /* * This function is just a wrapper around a call to * o31_copy(position, O31_identity), to avoid giving * the UI access to the kernel's O(3,1) matrix library. */ o31_copy(m, O31_identity); } void update_poly_position( O31Matrix position, O31Matrix velocity) { /* * Multiply the position by the velocity to get the new position. */ o31_product(velocity, position, position); } void update_poly_vertices( WEPolyhedron *polyhedron, O31Matrix position, double scale) { WEVertex *vertex; for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { o31_matrix_times_vector(position, vertex->x, vertex->xx); o31_constant_times_vector(scale, vertex->xx, vertex->xx); } } void update_poly_visibility( WEPolyhedron *polyhedron, O31Matrix position, O31Vector direction) { /* * Check which vertices, edges and faces are visible to the user with * the polyhedron in its present position, and set their visibility * fields accordingly. The direction vector points from the center * of the polyhedron to the user. */ /* * direction[0] is probably zero already, but just in case it's not, * set it to zero explicitly. (We're using O31Vectors, but we care * only about the 3-dimensional part.) */ direction[0] = 0.0; /* * Set the face visibility first. */ set_face_visibility(polyhedron, position, direction); /* * An edge is visible iff it lies on a visible face. */ set_edge_visibility(polyhedron); /* * A vertex is visible iff it lies on a visible edge. */ set_vertex_visibility(polyhedron); } static void set_face_visibility( WEPolyhedron *polyhedron, O31Matrix position, O31Vector direction) { WEFace *face; O31Vector old_normal, new_normal; int i; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { /* * The first column of the group_element provides a normal * vector relative to the polyhedron's original position. */ for (i = 0; i < 4; i++) old_normal[i] = (*face->group_element)[i][0]; /* * Apply the position matrix to find the normal vector relative * to the polyhedron's current position. */ o31_matrix_times_vector(position, old_normal, new_normal); /* * The face will be visible iff the new_normal has a positive * component in the given direction. */ face->visible = (o31_inner_product(direction, new_normal) > 0.0); } } static void set_edge_visibility( WEPolyhedron *polyhedron) { WEEdge *edge; for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) edge->visible = (edge->f[left]->visible || edge->f[right]->visible); } static void set_vertex_visibility( WEPolyhedron *polyhedron) { WEVertex *vertex; WEEdge *edge; /* * Initialize all vertex visibilities to FALSE. */ for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) vertex->visible = FALSE; /* * The endpoints of each visible edge will be visible vertices. */ for (edge = polyhedron->edge_list_begin.next; edge != &polyhedron->edge_list_end; edge = edge->next) if (edge->visible) { edge->v[tail]->visible = TRUE; edge->v[tip ]->visible = TRUE; } } snappea-3.0d3/SnapPeaKernel/code/double_cover.c0100444000175000017500000002516306742675501017524 0ustar babbab/* * double_cover.c * * This file provides the function * * Triangulation *double_cover(Triangulation *manifold); * * which computes the orientable double cover of a nonorientable manifold. */ #include "kernel.h" typedef Tetrahedron *TetPtrPair[2]; typedef int IntPair[2]; static void allocate_storage(Triangulation *manifold, Triangulation **the_cover, TetPtrPair **new_tet); static void set_neighbors_and_gluings(Triangulation *manifold, TetPtrPair *new_tet); static void lift_peripheral_curves(Triangulation *manifold, TetPtrPair *new_tet); static void lift_tet_shapes(Triangulation *manifold, TetPtrPair *new_tet); static void set_cusp_data(Triangulation *manifold, Triangulation *the_cover, TetPtrPair *new_tet); Triangulation *double_cover( Triangulation *manifold) { Triangulation *the_cover; TetPtrPair *new_tet; /* * Make sure the manifold is nonorientable. */ if (manifold->orientability != nonorientable_manifold) uFatalError("double_cover", "double_cover"); /* * Number the manifold's Tetrahedra for easy reference. */ number_the_tetrahedra(manifold); /* * Allocate space for the_cover. * * The i-th tetrahedron in manifold will have two preimages in * the_cover, which we'll record as new_tet[i][0] and new_tet[i][1]. */ allocate_storage(manifold, &the_cover, &new_tet); /* * Set the neighbor and gluing fields of the Tetrahedra. */ set_neighbors_and_gluings(manifold, new_tet); /* * Lift the peripheral curves to the double cover. * The lifted {meridian, longitude} will adhere to the right hand * rule relative to the local orientation on each Cusp's double * cover (cf. peripheral_curves.c). The manifold does not yet * have a global orientation. */ lift_peripheral_curves(manifold, new_tet); /* * Copy the TetShapes and shape histories. */ lift_tet_shapes(manifold, new_tet); /* * Set some global information. */ the_cover->num_tetrahedra = 2 * manifold->num_tetrahedra; the_cover->solution_type[complete] = manifold->solution_type[complete]; the_cover->solution_type[filled] = manifold->solution_type[filled]; /* * Create Cusps for the_cover, make sure they're in the same order * as their images in the manifold, copy in the Dehn filling * coefficients, and set the CuspTopology to torus_cusp. * We must do this before orienting the_cover, or else the * vertex indices on the_cover will no longer coincide with * those on the manifold. * Count the cusps. */ create_cusps(the_cover); set_cusp_data(manifold, the_cover, new_tet); count_cusps(the_cover); /* * Orient the_cover. */ orient(the_cover); if (the_cover->orientability != oriented_manifold) uFatalError("double_cover", "double_cover"); /* * orient() moves all peripheral curves to the right_handed sheets * of the Cusps local double covers. Thus some {meridian, longitude} * pairs may no longer adhere to the right-hand rule. Correct them, * and adjust the Dehn filling coefficients accordingly. */ fix_peripheral_orientations(the_cover); /* * Create and orient the EdgeClasses. */ create_edge_classes(the_cover); orient_edge_classes(the_cover); /* * In principle we could lift the holonomies and cusp shapes from * the manifold to the_cover, but the code would be delicate, difficult * to write, and bug-prone (it would be sensitive to the details of * which orientation the_cover was given and which meridians or * longitudes were reversed, so changing other parts of the algorithm * would most likely introduce errors here). A more robust approach * is to let polish_hyperbolic_structures() "unnecessarily" refine * the hyperbolic structure. It will automatically compute the * holonomies and cusp shapes. The price we pay is that in all cases * we'll have one "unnecessary" iteration of Newton's method, and * in cases where the shape histories are nontrivial, we'll end up * computing the hyperbolic structure from scratch. */ polish_hyperbolic_structures(the_cover); /* * Free local arrays. */ my_free(new_tet); return the_cover; } static void allocate_storage( Triangulation *manifold, Triangulation **the_cover, TetPtrPair **new_tet) { int i, j; /* * Allocate space for the new Triangulation. */ *the_cover = NEW_STRUCT(Triangulation); initialize_triangulation(*the_cover); /* * Allocate space for the new Tetrahedra, and the array * which organizes them. */ *new_tet = NEW_ARRAY(manifold->num_tetrahedra, TetPtrPair); for (i = 0; i < manifold->num_tetrahedra; i++) for (j = 0; j < 2; j++) { (*new_tet)[i][j] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron((*new_tet)[i][j]); INSERT_BEFORE((*new_tet)[i][j], &(*the_cover)->tet_list_end); } } static void set_neighbors_and_gluings( Triangulation *manifold, TetPtrPair *new_tet) { int i, j; Tetrahedron *old_tet; VertexIndex v; for (old_tet = manifold->tet_list_begin.next, i = 0; old_tet != &manifold->tet_list_end; old_tet = old_tet->next, i++) { if (old_tet->index != i) uFatalError("set_neighbors_and_gluings", "double_cover"); for (j = 0; j < 2; j++) for (v = 0; v < 4; v++) { new_tet[i][j]->neighbor[v] = new_tet [old_tet->neighbor[v]->index] [parity[old_tet->gluing[v]] == orientation_preserving ? j : !j]; new_tet[i][j]->gluing[v] = old_tet->gluing[v]; } } } static void lift_peripheral_curves( Triangulation *manifold, TetPtrPair *new_tet) { int i; Tetrahedron *old_tet; PeripheralCurve c; VertexIndex v; FaceIndex f; /* * Lift the peripheral curves from manifold to the_cover. * * The curves on a torus cusp lift to both preimages in the_cover. * In the original nonorientable manifold, the curves are stored on * one sheet of the cusp's double cover (cf. the top-of-file * documentation in peripheral curves.c). We don't know a priori * which sheet it is, but the other sheet will be empty, so we can * safely lift both sheets to the_cover. We lift the right_handed * (resp. left_handed) sheet at each vertex in the manifold to the * right_handed (resp. left_handed) sheet at the corresponding vertex * in each of the corresponding tetrahedra in the_cover to insure that * the peripheral curves obey the right hand rule relative to the * orientation of each cusp's double cover (cf. the top-of-file * documentation in peripheral curves.c). * * The curves on a Klein bottle cusp are already described as a single * lift to the double cover (cf. the top-of-file ocumentation in * peripheral curves.c). So we copy the contents of the * * old_tet->curve[][right_handed][][] * to new_tet[][0]->curve[][right_handed][][], * and * old_tet->curve[][ left_handed][][] * to new_tet[][1]->curve[][ left_handed][][]. * * The fields * new_tet[][1]->curve[][right_handed][][] * and * new_tet[][0]->curve[][ left_handed][][] * * are left zero. * * Note that the curves match up across gluings (compare the convention * for matching curves in peripheral_curves.c with the convention for * assigning neighbors in set_neighbors_and_gluings() above). */ for (old_tet = manifold->tet_list_begin.next, i = 0; old_tet != &manifold->tet_list_end; old_tet = old_tet->next, i++) for (c = 0; c < 2; c++) for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) if (old_tet->cusp[v]->topology == torus_cusp) { new_tet[i][0]->curve[c][right_handed][v][f] = new_tet[i][1]->curve[c][right_handed][v][f] = old_tet->curve[c][right_handed][v][f]; new_tet[i][0]->curve[c][ left_handed][v][f] = new_tet[i][1]->curve[c][ left_handed][v][f] = old_tet->curve[c][ left_handed][v][f]; } else { new_tet[i][0]->curve[c][right_handed][v][f] = old_tet->curve[c][right_handed][v][f]; new_tet[i][1]->curve[c][ left_handed][v][f] = old_tet->curve[c][ left_handed][v][f]; } } static void lift_tet_shapes( Triangulation *manifold, TetPtrPair *new_tet) { int i, j, k; Tetrahedron *old_tet; for (old_tet = manifold->tet_list_begin.next, i = 0; old_tet != &manifold->tet_list_end; old_tet = old_tet->next, i++) for (k = 0; k < 2; k++) /* k = complete, filled */ if (old_tet->shape[k] != NULL) for (j = 0; j < 2; j++) /* j = which lift */ { new_tet[i][j]->shape[k] = NEW_STRUCT(TetShape); *new_tet[i][j]->shape[k] = *old_tet->shape[k]; copy_shape_history(old_tet->shape_history[k], &new_tet[i][j]->shape_history[k]); } } static void set_cusp_data( Triangulation *manifold, Triangulation *the_cover, TetPtrPair *new_tet) { int i, j, cusp_count; IntPair *old_to_new; CuspTopology topology; Boolean is_complete; double m, l; Tetrahedron *old_tet; VertexIndex v; /* * Set up the correspondence between the old indices and the new ones. * Old tori will lift to two new tori, while old Klein bottles each * lift to a single new one. */ old_to_new = NEW_ARRAY(manifold->num_cusps, IntPair); cusp_count = 0; for (i = 0; i < manifold->num_cusps; i++) { get_cusp_info(manifold, i, &topology, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (topology == torus_cusp) { old_to_new[i][0] = cusp_count++; old_to_new[i][1] = cusp_count++; } else { old_to_new[i][0] = cusp_count++; old_to_new[i][1] = old_to_new[i][0]; } } /* * Now assign the indices to the newly computed cusps. * This algorithm is highly redundant -- each index will be set * once for every ideal vertex incident to the cusp -- but the * inefficiency is insignificant. A cusp and its mate (i.e. * the two cusps in the double cover which project down to a given * cusp in the original manifold) may swap indices many times during * this redundant procedure, as their indices get overwritten. * * While we're here, let's set the topology of each Cusp to torus_cusp. */ for (old_tet = manifold->tet_list_begin.next, i = 0; old_tet != &manifold->tet_list_end; old_tet = old_tet->next, i++) for (v = 0; v < 4; v++) for (j = 0; j < 2; j++) { new_tet[i][j]->cusp[v]->index = old_to_new [old_tet->cusp[v]->index] [j]; new_tet[i][j]->cusp[v]->topology = torus_cusp; } /* * Set the is_complete, m and l fields of the new cusps. */ for (i = 0; i < manifold->num_cusps; i++) { get_cusp_info(manifold, i, &topology, &is_complete, &m, &l, NULL, NULL, NULL, NULL, NULL, NULL); if (topology == torus_cusp) { set_cusp_info(the_cover, old_to_new[i][0], is_complete, m, l); set_cusp_info(the_cover, old_to_new[i][1], is_complete, m, l); } else { set_cusp_info(the_cover, old_to_new[i][0], is_complete, m, l); } } /* * Free the local storage. */ my_free(old_to_new); } snappea-3.0d3/SnapPeaKernel/code/drilling.c0100444000175000017500000012064606742675501016662 0ustar babbab/* * drilling.c * * This file contains the function * * Triangulation *drill_cusp( Triangulation *old_manifold, * DualOneSkeletonCurve *curve_to_drill, * char *new_name); * * which the kernel provides to the UI to drill out a simple closed curve * in a manifold's dual 1-skeleton. Please see dual_one_skeleton_curve.h * for a description of the DualOneSkeletonCurve. drill_cusp() accepts * as input the original n-cusp manifold and a DualOneSkeletonCurve to * be drilled. If the drilling curve is not boundary parallel * (i.e. if the curve is not parabolic) drill_cusp() returns a pointer * to the resulting (n+1)-cusp manifold. If the drilling curve is * boundary parallel, drill_cusp() returns NULL. (If the drilling curve * is boundary parallel, but in a nonobvious way, then drill_cusp() will * succeed and return a pointer to a nonhyperbolic manifold.) In * practice, I recommend that the UI not even offer the user the option * of drilling out parabolics. * * The original manifold is not altered. * * The meridian on the new cusp is chosen so that meridional Dehn * filling (i.e. (1,0) Dehn filling) restores the original manifold. * * We assume the Tetrahedra in old_manifold are numbered, e.g. * by the kernel function number_the_tetrahedra(), and that the * curve_to_drill conforms to this numbering. * * Note that an arbitrary curve in the dual 1-skeleton may or may not * be knotted. By definition (my definition, anyhow) a simple closed curve * in a hyperbolic 3-manifold is unknotted iff it is isotopic to the unique * geodesic in its homotopy class. * * * The remainder of this comment describes drill_cusp()'s algorithm. * * Tetrahedra which don't intersect the drilling curve are left alone. * * Each tetrahedron which does intersect the drilling curve is subdivided * into four small Tetrahedra by coning to the center. (I recommend you * draw sketches as you read this.) The drilling curve intersects the * interior of exactly two of the four small Tetrahedra -- throw those two * away and keep the two which don't intersect it. Each of the two * remaining small Tetrahedra has an ideal vertex at the center of * the original large Tetrahedron -- visualize them as truncated ideal * vertices. (Time to revise your sketch!) It may be helpful to color * one truncated vertex red and the other blue. The red and blue * triangles (of the truncated vertices) together determine a tiny * tetrahedron at the center of the original Tetrahedron. Draw its * entire 1-skeleton in black. Now -- this is the crucial observation -- * the tiny tetrahedron is a scaled down version of the big Tetrahedron. * So, thinking of the manifold globally now, glue together pairs * of exposed faces of small Tetrahedra in the natural way. * Just as the set of large Tetrahedra intersecting the drilling * curve forms a solid torus in the manifold (possibly with * self-intersections on its boundary, which don't concern us), the * tiny tetrahedra composed of truncated ideal vertices piece together * to form a tiny torus. The tiny torus is just a scaled down version * of the large one. * * I claim that the Triangulation we have just created will be * homeomorphic to the original Triangulation with the drilling * curve removed iff the drilling curve is not "obviously" parallel * to the boundary (in a moment the meaning of this statement will * be made more precise). Consider each large Tetrahedron intersecting * the drilling curve in the original Triangulation, and its subdivision * into four small Tetrahedra, two of which get thrown away. Each * vertex cross section of such a large Tetrahedron is a triangle, * which is subdivided into three smaller triangles by the small * Tetrahedra. At two of the large Tetrahedron's vertices, two * small triangles will be retained, and one will have been discarded * (when we discarded two of the four small Tetrahedra), * while at the the remaining two vertices, one small triangle will * be retained and two will have been discarded. Now look at an entire * torus or Klein bottle boundary component. If the drilling * curve is blatently parallel to this boundary component, then * when you glue the faces of the small Tetrahedra as explained * in the preceeding paragraph, the image of the drilling curve * on the boundary gets pinched off (draw yourself a picture). * This increases the Euler characteristic of the boundary, and * the function check_Euler_characteristic_of_boundary() flags the error. * If, on the other hand, the drilling curve follows this * boundary component only along isolated intervals, then it's * easy to see that when the faces of the small Tetrahedra are * glued as defined above, the holes are filled in correctly (again, * draw yourself a picture), and the topology of the manifold is preserved. */ #include "kernel.h" /* * If you are not familiar with SnapPea's "Extra" field in * the Tetrahedron data structure, please see the explanation * preceding the Extra typedef in kernel_typedefs.h. * * drill_cusp() attaches an Extra field to each old Tetrahedron * to keep track of the new Tetrahedra associated with it. */ struct extra { /* * Does the drilling curve pass through this Tetrahedron? */ Boolean drilling_curve_intersects_tet; /* * Does the drilling curve pass through the given face? */ Boolean drilling_curve_intersects_face[4]; /* * If the drilling curve does not pass through this Tetrahedron, * the new Triangulation will contain a single Tetrahedron * corresponding to this one. extra->big_tet will point to it. */ Tetrahedron *big_tet; /* * If the drilling curve does pass through this Tetrahedron, it * will cross exactly two faces. There will be a small Tetrahedron * associated with each face which does not intersect the * drilling curve. Two of the following pointers will point to * those Tetrahedra; the other two will be NULL. */ Tetrahedron *small_tet[4]; /* * index[] says which of the two small_tet's are actually in use. */ FaceIndex index[2]; }; /* * The functions which find the meridian and longitude on the new * Cusp use a MeridionalAnnulus data structure. * * Recall from above that the each old Tetrahedron intersecting * the drilling curve contributes two small new Tetrahedra to * the Triangulation of the new manifold. These two small new * Tetrahedra contibute a degenerate meridional annulus to the * new boundary component. In terms of the above imagery, the * degenerate meridional annulus consists of the small red triangle, * the small blue triangle, and the black line segment connecting * the far vertices. The annulus is degenerate because the black * segment is only a segment, but nevertheless it should be clear * how these annuli piece together to form the new boundary component. * * The MeridionalAnnulus consists of a PositionedTet, plus a * current_position field. * * I'm sorry to have to do this to you, but please imagine the * PositionedTet with the bottom_face down ("on the table"), * the near_face away from you, the left_face towards you and on * the right, and the right_face towards you and on the left. * I.e. rotate it a half turn about the vertical axis, relative * to the way you usually imagine it. * * We make the convention that the drilling curve passes through * the left_ and right_faces, while new small Tetrahedra are located * at the near_ and bottom_faces. (The reason for the nonstandard * positioning described in the previous paragraph is that it gives * a good view of the truncated vertices of the new Cusp.) * On the new Cusp, "northward" is from the vertex on the bottom_face * towards the vertex on the near_face (i.e. "up"), while "eastward" * is from the side near the right_face towards the side near * left_face (i.e. to the right). The meridian on the new Cusp will * run north, while the longitude runs east (this corresponds to the * usual convention). * * The current_position field says where we are in the PositionedTet. * If current_position is * 0 we're on the degenerate edge, * 1 we're on the truncated ideal vertex sitting over the bottom_face, * 2 we're on the truncated ideal vertex sitting over the near_face. */ typedef struct { PositionedTet ptet; int current_position; } MeridionalAnnulus; typedef int DirectionToTravel; enum { to_the_east, to_the_west }; static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void mark_drilling_curve(Triangulation *old_manifold, DualOneSkeletonCurve *curve_to_drill); static void set_up_new_triangulation(Triangulation *old_manifold, Triangulation **new_manifold, char *new_name); static void allocate_new_tetrahedra(Triangulation *old_manifold, Triangulation *new_manifold); static void set_neighbors_and_gluings(Triangulation *old_manifold); static void set_big_tet_neighbors_and_gluings(Tetrahedron *old_tet); static void set_small_tet_neighbors_and_gluings(Tetrahedron *old_tet); static void set_cusps(Triangulation *old_manifold, Triangulation *new_manifold); static void copy_old_peripheral_curves(Triangulation *old_manifold, Triangulation *new_manifold); static void create_new_peripheral_curves(Triangulation *old_manifold, Triangulation *new_manifold); static void create_new_meridian(PositionedTet ptet); static void create_new_longitude(PositionedTet ptet, CuspTopology *cusp_topology); static void move_sideways(MeridionalAnnulus *ma, DirectionToTravel direction); static void transfer_CS(Triangulation *old_manifold, Triangulation *new_manifold); Triangulation *drill_cusp( Triangulation *old_manifold, DualOneSkeletonCurve *curve_to_drill, char *new_name) { Triangulation *new_manifold; /* * Attach an Extra field to each old Tetrahedron to keep * track of the new Tetrahedra associated with it. */ attach_extra(old_manifold); /* * Determine the exact path of the drilling curve in * the dual 1-skeleton of the old_manifold. */ mark_drilling_curve(old_manifold, curve_to_drill); /* * Set up the global data for the new_manifold. */ set_up_new_triangulation(old_manifold, &new_manifold, new_name); /* * Allocate space for the new Tetrahedra, and associate them * with the corresponding old Tetrahedra. */ allocate_new_tetrahedra(old_manifold, new_manifold); /* * Set the neighbors and gluings. */ set_neighbors_and_gluings(old_manifold); /* * Make copies of the old cusps, and create a new cusp. */ set_cusps(old_manifold, new_manifold); /* * Add the bells and whistles. * Note that it isn't necessary to call orient(). The new_manifold * will automatically be oriented iff the old_manifold was oriented. */ create_edge_classes(new_manifold); orient_edge_classes(new_manifold); /* * The algorithm will have failed * iff the Euler characteristic of the boundary is positive * iff the drilling curve was parallel to the (original) boundary. * Cf. the discussion at the top of this file. */ if (check_Euler_characteristic_of_boundary(new_manifold) == func_failed) { free_triangulation(new_manifold); free_extra(old_manifold); return NULL; } /* * Copy the peripheral curves from the old_manifold for the * preexisting cusps, and make a new set of peripheral curves * for the brand new cusp. It's essential that we do * the peripheral curves AFTER checking the Euler characteristic * of the boundary. */ copy_old_peripheral_curves(old_manifold, new_manifold); create_new_peripheral_curves(old_manifold, new_manifold); /* * Free the Extra fields. */ free_extra(old_manifold); /* * Simplify the triangulation. (Usually it's pretty good * to begin with, but EdgeClasses of order 2 or 3 may * occasionally appear.) * * basic_simplification() will call tidy_peripheral_curves(). * Otherwise we'd do it here. */ basic_simplification(new_manifold); /* * If the old_manifold had a hyperbolic structure, * try to find one for the new_manifold as well. */ if (old_manifold->solution_type[complete] != not_attempted) { find_complete_hyperbolic_structure(new_manifold); do_Dehn_filling(new_manifold); /* * If the old_manifold had a known Chern-Simons invariant, * try to transfer it to the new_manifold. */ transfer_CS(old_manifold, new_manifold); } return new_manifold; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "drilling"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void mark_drilling_curve( Triangulation *old_manifold, DualOneSkeletonCurve *curve_to_drill) { Tetrahedron *tet; int i; for (tet = old_manifold->tet_list_begin.next; tet != &old_manifold->tet_list_end; tet = tet->next) { tet->extra->drilling_curve_intersects_tet = FALSE; for (i = 0; i < 4; i++) { tet->extra->drilling_curve_intersects_face[i] = curve_to_drill->tet_intersection[tet->index][i]; if (tet->extra->drilling_curve_intersects_face[i] == TRUE) tet->extra->drilling_curve_intersects_tet = TRUE; } } } static void set_up_new_triangulation( Triangulation *old_manifold, Triangulation **new_manifold, char *new_name) { /* * Allocate memory for the new_manifold. */ *new_manifold = NEW_STRUCT(Triangulation); /* * Call the generic initialization routine. */ initialize_triangulation(*new_manifold); /* * Copy in the name requested by the UI. */ (*new_manifold)->name = NEW_ARRAY(strlen(new_name) + 1, char); strcpy((*new_manifold)->name, new_name); /* * The triangulation algorithm guarantees that the new_manifold * will be oriented iff the old one was. */ (*new_manifold)->orientability = old_manifold->orientability; /* * For now we set the number of cusps equal to the number in the * old manifold. We'll increment the appropriate numbers once * we discover whether the new cusp is orientable or nonorientable. */ (*new_manifold)->num_cusps = old_manifold->num_cusps; (*new_manifold)->num_or_cusps = old_manifold->num_or_cusps; (*new_manifold)->num_nonor_cusps = old_manifold->num_nonor_cusps; } static void allocate_new_tetrahedra( Triangulation *old_manifold, Triangulation *new_manifold) { Tetrahedron *tet; int i, count; for (tet = old_manifold->tet_list_begin.next; tet != &old_manifold->tet_list_end; tet = tet->next) if (tet->extra->drilling_curve_intersects_tet == FALSE) { tet->extra->big_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet->extra->big_tet); INSERT_BEFORE(tet->extra->big_tet, &new_manifold->tet_list_end); new_manifold->num_tetrahedra++; for (i = 0; i < 4; i++) tet->extra->small_tet[i] = NULL; } else { tet->extra->big_tet = NULL; count = 0; for (i = 0; i < 4; i++) if (tet->extra->drilling_curve_intersects_face[i] == FALSE) { tet->extra->small_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet->extra->small_tet[i]); INSERT_BEFORE(tet->extra->small_tet[i], &new_manifold->tet_list_end); new_manifold->num_tetrahedra++; tet->extra->index[count++] = i; } else tet->extra->small_tet[i] = NULL; } } static void set_neighbors_and_gluings( Triangulation *old_manifold) { Tetrahedron *old_tet; /* * The VertexIndices of the new Tetrahedra are inherited from * those of the old Tetrahedra in the obvious, canonical way. * Thus, except for the "internal" gluings between two small * new Tetrahedra associated with the same old Tetrahedron, * all the new gluings are the same as the corresponding old * gluings. * * We don't explicitly set inverses -- they'll be taken care * of when the for(;;) loop comes around to them. */ for (old_tet = old_manifold->tet_list_begin.next; old_tet != &old_manifold->tet_list_end; old_tet = old_tet->next) if (old_tet->extra->drilling_curve_intersects_tet == FALSE) set_big_tet_neighbors_and_gluings(old_tet); else set_small_tet_neighbors_and_gluings(old_tet); } static void set_big_tet_neighbors_and_gluings( Tetrahedron *old_tet) { int i; /* * Set the neighbors and gluings for the four faces * of the new big Tetrahedron. */ for (i = 0; i < 4; i++) { /* * Check whether the neighbor is a big tet or a small tet, * and set the neighbor field accordingly. */ old_tet->extra->big_tet->neighbor[i] = (old_tet->neighbor[i]->extra->drilling_curve_intersects_tet == FALSE) ? old_tet->neighbor[i]->extra->big_tet : old_tet->neighbor[i]->extra->small_tet[EVALUATE(old_tet->gluing[i], i)]; /* * The gluing is independent of whether the neighbor is a * big tet or a small tet. */ old_tet->extra->big_tet->gluing[i] = old_tet->gluing[i]; } } static void set_small_tet_neighbors_and_gluings( Tetrahedron *old_tet) { int i, j; FaceIndex f0, f1, f2, f3; PositionedTet ptet; /* * Set the neighbors and gluings for the two new small Tetrahedra * associated with old_tet. */ /* * First take care of the faces of the new Tetrahedra which coincide * with faces of old_tet. */ for (i = 0; i < 2; i++) { /* * Let f0 be the actual index of the small Tetrahedron * under consideration. */ f0 = old_tet->extra->index[i]; /* * Check whether the neighbor is a big tet or a small tet, * and set the neighbor field accordingly. */ old_tet->extra->small_tet[f0]->neighbor[f0] = (old_tet->neighbor[f0]->extra->drilling_curve_intersects_tet == FALSE) ? old_tet->neighbor[f0]->extra->big_tet : old_tet->neighbor[f0]->extra->small_tet[EVALUATE(old_tet->gluing[f0], f0)]; /* * The gluing is independent of whether the neighbor is a * big tet or a small tet. */ old_tet->extra->small_tet[f0]->gluing[f0] = old_tet->gluing[f0]; } /* * Glue the two small Tetrahedra to each other. * * Let f0 and f1 be the indices of the two small Tetrahedra under * consideration, and f2 and f3 be the unused indices. */ f0 = old_tet->extra->index[0]; f1 = old_tet->extra->index[1]; f2 = remaining_face[f0][f1]; f3 = remaining_face[f1][f0]; old_tet->extra->small_tet[f0]->neighbor[f1] = old_tet->extra->small_tet[f1]; old_tet->extra->small_tet[f1]->neighbor[f0] = old_tet->extra->small_tet[f0]; old_tet->extra->small_tet[f0]->gluing[f1] = old_tet->extra->small_tet[f1]->gluing[f0] = CREATE_PERMUTATION(f0, f1, f1, f0, f2, f2, f3, f3); /* * Now set the neighbors and gluings for the two remaining * faces of each of the two small Tetrahedra by swinging around * the appropriate edge of the old_tet until a noncollapsed * small Tetrahedron is found. (To see why this is correct, * think of the extra small Tetrahedra collapsing to triangles, * as described in the documentation at the top of this file.) */ for (i = 0; i < 2; i++) { /* * Let f0 be the index of the small Tetrahedron under * consideration, and f1 be the index of the other small * Tetrahedron. */ f0 = old_tet->extra->index[i]; f1 = old_tet->extra->index[!i]; for (j = 0; j < 2; j++) { /* * Let f2 be the face whose neighbor and gluing we'll * determine, and f3 be the face left over. */ f2 = (j ? remaining_face[f0][f1] : remaining_face[f1][f0]); f3 = (j ? remaining_face[f1][f0] : remaining_face[f0][f1]); /* * Set up a PositionedTet which we'll rotate around until * we find a match for the face under consideration. */ ptet.tet = old_tet; ptet.near_face = f0; ptet.left_face = f2; ptet.right_face = f1; ptet.bottom_face = f3; ptet.orientation = (f2 == remaining_face[f0][f1]) ? right_handed : left_handed; /* * Veer_left() as long as necessary until we find a small * Tetrahedron to glue to. */ do veer_left(&ptet); while (ptet.tet->extra->drilling_curve_intersects_face[ptet.left_face] == TRUE); /* * Set the neighbor and gluing fields. */ old_tet->extra->small_tet[f0]->neighbor[f2] = ptet.tet->extra->small_tet[ptet.left_face]; old_tet->extra->small_tet[f0]->gluing[f2] = CREATE_PERMUTATION( f0, ptet.left_face, f1, ptet.right_face, f2, ptet.near_face, f3, ptet.bottom_face); } } } static void set_cusps( Triangulation *old_manifold, Triangulation *new_manifold) { int i, j; FaceIndex f; Cusp **new_cusp_addresses, *old_cusp, *new_cusp, *brand_new_cusp; Tetrahedron *old_tet; /* * Make copies of the old Cusps. * Record the addresses of the new Cusps in an array for * later convenience. */ new_cusp_addresses = NEW_ARRAY(old_manifold->num_cusps, Cusp *); for (old_cusp = old_manifold->cusp_list_begin.next; old_cusp != &old_manifold->cusp_list_end; old_cusp = old_cusp->next) { new_cusp = NEW_STRUCT(Cusp); *new_cusp = *old_cusp; new_cusp_addresses[new_cusp->index] = new_cusp; INSERT_BEFORE(new_cusp, &new_manifold->cusp_list_end); } /* * Create a brand new Cusp for the drilling curve. */ brand_new_cusp = NEW_STRUCT(Cusp); initialize_cusp(brand_new_cusp); INSERT_BEFORE(brand_new_cusp, &new_manifold->cusp_list_end); brand_new_cusp->index = old_manifold->num_cusps; /* * Set the new_tet->cusp[] fields. */ for (old_tet = old_manifold->tet_list_begin.next; old_tet != &old_manifold->tet_list_end; old_tet = old_tet->next) if (old_tet->extra->drilling_curve_intersects_tet == FALSE) for (i = 0; i < 4; i++) old_tet->extra->big_tet->cusp[i] = new_cusp_addresses[old_tet->cusp[i]->index]; else for (i = 0; i < 2; i++) { f = old_tet->extra->index[i]; for (j = 0; j < 4; j++) old_tet->extra->small_tet[f]->cusp[j] = (j == f) ? brand_new_cusp : new_cusp_addresses[old_tet->cusp[j]->index]; } /* * Free the array used to hold the new Cusp addresses. */ my_free(new_cusp_addresses); } static void copy_old_peripheral_curves( Triangulation *old_manifold, Triangulation *new_manifold) { Tetrahedron *old_tet, *new_tet; int i, ii, j, k, l; FaceIndex f; EdgeClass *new_edge; VertexIndex v0, v1; PositionedTet ptet0, ptet; int in_hand[2][2]; /* * First copy the peripheral curves onto the edges of the * new boundary triangulation which correspond exactly with * edges of the old boundary triangulation. */ for (old_tet = old_manifold->tet_list_begin.next; old_tet != &old_manifold->tet_list_end; old_tet = old_tet->next) if (old_tet->extra->drilling_curve_intersects_tet == FALSE) for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) old_tet->extra->big_tet->curve[i][j][k][l] = old_tet->curve[i][j][k][l]; else for (i = 0; i < 2; i++) { f = old_tet->extra->index[i]; for (j = 0; j < 4; j++) if (j != f) for (k = 0; k < 2; k++) for (l = 0; l < 2; l++) old_tet->extra->small_tet[f]->curve[k][l][j][f] = old_tet->curve[k][l][j][f]; } /* * At this point it's helpful to draw the old boundary triangulation * at a given cusp, with the new boundary triangulation superimposed * in green. At ideal vertices of old tetrahedra not intersecting * the drilling curve, the new green triagle will coincide with the * old plain triangle. At ideal vertices of old tetrahedra which do * intersect the drilling curve, either a single small green triangle * will occupy a third of the old plain triangle, or two small green * triangles will occupy two-thirds of the old plain triangle, depending * on which vertex you're at. Consider the gaps where the new green * triangles don't cover the old plain ones. If the drilling curve * had been boundary parallel, the gaps would form a topological * annulus, but by the time this function is called the program will * have already checked the Euler characteristic of the boundary, so * we know this can't occur. Instead, the gaps form topological disks * on the boundary. When the new small Tetrahedra are glued to each * other, the small green triangles on the boundary come together to * form a disk, thereby closing the gaps. In my illustration, this * disk looks like a green pizza. The peripheral curves will be * correct around the circumferences of such pizzas. The purpose of * the remainder of this function is to adjust them in the interior * of the pizza, i.e. between the slices. We'll walk around * the circumference of each pizza, hooking up incoming strands * on one part of the circumference to outgoing strands on another. */ /* * Check each new EdgeClass which connects an old cusp * to the brand new cusp. */ for (new_edge = new_manifold->edge_list_begin.next; new_edge != &new_manifold->edge_list_end; new_edge = new_edge->next) { /* * Does new_edge have one endpoint on an old cusp and one * on the brand new cusp? If not, skip this EdgeClass. */ new_tet = new_edge->incident_tet; v0 = one_vertex_at_edge[new_edge->incident_edge_index]; v1 = other_vertex_at_edge[new_edge->incident_edge_index]; if ((new_tet->cusp[v0]->index < old_manifold->num_cusps) == (new_tet->cusp[v1]->index < old_manifold->num_cusps)) continue; /* * Set up a PositionedTet, which we'll rotate about the * center of the green pizza descibed above. */ ptet0.tet = new_tet; ptet0.right_face = (new_tet->cusp[v0]->index < old_manifold->num_cusps) ? v1 : v0; ptet0.bottom_face = (new_tet->cusp[v0]->index < old_manifold->num_cusps) ? v0 : v1; ptet0.near_face = remaining_face[ptet0.bottom_face][ptet0.right_face]; ptet0.left_face = remaining_face[ptet0.right_face][ptet0.bottom_face]; ptet0.orientation = right_handed; /* * In_hand will record how many strands of each curve * (meridian, longitude) on each sheet (right_handed, * left_handed) we'll be carrying with us "in hand" * as we progress to the next slice of pizza. Initialize * it to zero. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) in_hand[i][j] = 0; /* * Circumnavigate the pizza, correctly setting the peripheral * curves between slices. */ ptet = ptet0; do { /* * Update the value of in_hand to account for the strands which * just entered or left through the circumference of the pizza. */ for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; for (j = 0; j < 2; j++) in_hand[j][i] += ptet.tet->curve[j][ii][ptet.bottom_face][ptet.right_face]; } /* * Adjust the leading edge of this slice. */ for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; for (j = 0; j < 2; j++) ptet.tet->curve[j][ii][ptet.bottom_face][ptet.left_face] = - in_hand[j][i]; } /* * Move on to the next slice. */ veer_left(&ptet); /* * Adjust the trailing edge of this slice. */ for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; for (j = 0; j < 2; j++) ptet.tet->curve[j][ii][ptet.bottom_face][ptet.near_face] = in_hand[j][i]; } /* * Quit if we're back to the slice we started on. * Otherwise, continue with the loop. */ } while ( ! same_positioned_tet(&ptet0, &ptet)); /* * Check that all incoming and outgoing strands * did in fact cancel out. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (in_hand[i][j] != 0) uFatalError("copy_old_peripheral_curves", "drilling"); } } static void create_new_peripheral_curves( Triangulation *old_manifold, Triangulation *new_manifold) { Tetrahedron *old_tet; PositionedTet ptet; CuspTopology cusp_topology; /* * We want to be sure to be sure to get the relative * orientation of the meridian and longitude correct * (cf. the orientation convention at the top of * peripheral_curves.c, which coincides with the usual * orientation convention for meridians and longitudes * on knot complements). Here we find an old Tetrahedron * which intersects the drilling curve, orient it, and * pass it to functions which actually find the meridian * and the longitude. We use the PositionedTet structure * as a bookkeeping device. */ for (old_tet = old_manifold->tet_list_begin.next; old_tet != &old_manifold->tet_list_end; old_tet = old_tet->next) if (old_tet->extra->drilling_curve_intersects_tet == TRUE) { /* * Set up the PositionedTet. */ ptet.tet = old_tet; ptet.near_face = old_tet->extra->index[0]; ptet.bottom_face = old_tet->extra->index[1]; ptet.left_face = remaining_face[ptet.bottom_face][ptet.near_face]; ptet.right_face = remaining_face[ptet.near_face][ptet.bottom_face]; ptet.orientation = right_handed; /* * Compute the new peripheral curves. */ create_new_meridian (ptet); create_new_longitude(ptet, &cusp_topology); /* * Record the topology of the new cusp. */ if (cusp_topology == torus_cusp) { old_tet->extra->small_tet[old_tet->extra->index[0]] ->cusp[old_tet->extra->index[0]]->topology = torus_cusp; new_manifold->num_or_cusps++; } else { old_tet->extra->small_tet[old_tet->extra->index[0]] ->cusp[old_tet->extra->index[0]]->topology = Klein_cusp; new_manifold->num_nonor_cusps++; } new_manifold->num_cusps++; return; } /* * We should have returned from within the above loop. */ uFatalError("create_new_peripheral_curves", "drilling"); } static void create_new_meridian( PositionedTet ptet) { MeridionalAnnulus ma0, ma; int steps_northward, steps_eastward; /* * Note that the meridian is set using "+=" rather than just "=". * This is becase the algorithm proceeds in the universal cover, * and the curve might pass over itself in the manifold itself. * (Such precautions aren't necessary for the longitude.) */ ma0.ptet = ptet; ma0.current_position = 1; ma = ma0; steps_northward = 0; steps_eastward = 0; /* * Move three steps northward, moving east as necessary. */ while (steps_northward < 3) { /* * Move east until current_position == 1. * (If this were not possible, the manifold would have * already failed check_Euler_characteristic_of_boundary(), * and we wouldn't be at this point.) */ while (ma.current_position != 1) { if (ma.current_position == 2) ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[M] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.left_face] += -1; move_sideways(&ma, to_the_east); if (ma.current_position == 1) ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[M] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.right_face] += 1; if (ma.current_position == 2) ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[M] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.right_face] += 1; steps_eastward++; } /* * Move north one step. */ ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[M] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.near_face] += -1; ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[M] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.bottom_face] += 1; ma.current_position = 2; steps_northward++; } /* * Take as many steps back to the west as we took to the east. */ while (--steps_eastward >= 0) { if (ma.current_position == 1) ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[M] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.right_face] += -1; if (ma.current_position == 2) ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[M] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.right_face] += -1; move_sideways(&ma, to_the_west); if (ma.current_position == 1) ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[M] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.left_face] += 1; if (ma.current_position == 2) ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[M] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.left_face] += 1; } /* * Just in case . . . */ if ( ! same_positioned_tet(&ma.ptet, &ma0.ptet) || ma.current_position != ma0.current_position) uFatalError("create_new_meridian", "drilling"); } static void create_new_longitude( PositionedTet ptet, CuspTopology *cusp_topology) { MeridionalAnnulus ma0, ma; Boolean enters_north, leaves_north; /* * We make the following convention in passing the longitude * from one MeridionalAnnulus to the next. If the Meridional * Annuli are not aligned, then the longitude passes across * the unique edge which is degenerate for neither of them. * Otherwise it passes across the more northerly of the two * nondegenerate edges. * * Technical note: create_new_longitude() doesn't actually * use the degenerate_index field of the MeridionalAnnulus. * That field is included for the convenience of create_new_meridian(), * with which create_new_longitude() shares the move_sideways() function. */ /* * We assume the Cusp is orientable unless we discover otherwise. */ *cusp_topology = torus_cusp; ma0.ptet = ptet; ma0.current_position = 0; /* will be ignored */ ma = ma0; do { /* * See where the longitude enters on the west. */ if (ma.ptet.tet->neighbor[ma.ptet.right_face] ->extra->drilling_curve_intersects_face [EVALUATE( ma.ptet.tet->gluing[ma.ptet.right_face], ma.ptet.near_face) ] == FALSE) { enters_north = TRUE; ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[L] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.right_face] = 1; } else { enters_north = FALSE; ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[L] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.right_face] = 1; } /* * See where the longitude leaves on the east. */ if (ma.ptet.tet->neighbor[ma.ptet.left_face] ->extra->drilling_curve_intersects_face [EVALUATE( ma.ptet.tet->gluing[ma.ptet.left_face], ma.ptet.near_face) ] == FALSE) { leaves_north = TRUE; ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[L] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.left_face] = -1; } else { leaves_north = FALSE; ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[L] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.left_face] = -1; } /* * Do we cross the edge between the northern and southern triangles? */ if (enters_north != leaves_north) { ma.ptet.tet->extra->small_tet[ma.ptet.near_face]->curve[L] [ma.ptet.orientation][ma.ptet.near_face][ma.ptet.bottom_face] = (enters_north == TRUE) ? -1 : 1; ma.ptet.tet->extra->small_tet[ma.ptet.bottom_face]->curve[L] [ma.ptet.orientation][ma.ptet.bottom_face][ma.ptet.near_face] = (enters_north == TRUE) ? 1 : -1; } /* * Move on to the next MeridionalAnnulus. */ move_sideways(&ma, to_the_east); /* * If we've come around to the original Tetrahedron, but * with the opposite orientation, then we know the cusp is * nonorientable. */ if ( ma.ptet.tet == ma0.ptet.tet && ma.ptet.orientation != ma0.ptet.orientation) *cusp_topology = Klein_cusp; } while ( ! same_positioned_tet(&ma.ptet, &ma0.ptet)); } static void move_sideways( MeridionalAnnulus *ma, DirectionToTravel direction) { MeridionalAnnulus new_ma; Permutation gluing; FaceIndex old_leading_face, old_trailing_face, *new_leading_face, *new_trailing_face; /* * Create references to the leading and trailing faces, * according to which direction we're going. */ if (direction == to_the_east) { old_leading_face = ma->ptet.left_face; old_trailing_face = ma->ptet.right_face; new_leading_face = &new_ma.ptet.left_face; new_trailing_face = &new_ma.ptet.right_face; } else { old_leading_face = ma->ptet.right_face; old_trailing_face = ma->ptet.left_face; new_leading_face = &new_ma.ptet.right_face; new_trailing_face = &new_ma.ptet.left_face; } /* * Find the new Tetrahedron. */ new_ma.ptet.tet = ma->ptet.tet->neighbor[old_leading_face]; /* * For convenience, record the pertinent gluing. */ gluing = ma->ptet.tet->gluing[old_leading_face]; /* * Find the new_trailing_face. */ *new_trailing_face = EVALUATE(gluing, old_leading_face); /* * The values of the remaining _faces will depend on which is degenerate. */ if (new_ma.ptet.tet->extra->drilling_curve_intersects_face [EVALUATE(gluing, old_trailing_face)] == TRUE) { *new_leading_face = EVALUATE(gluing, old_trailing_face); new_ma.ptet.bottom_face = EVALUATE(gluing, ma->ptet.bottom_face); new_ma.ptet.near_face = EVALUATE(gluing, ma->ptet.near_face); new_ma.current_position = ma->current_position; } if (new_ma.ptet.tet->extra->drilling_curve_intersects_face [EVALUATE(gluing, ma->ptet.bottom_face)] == TRUE) { *new_leading_face = EVALUATE(gluing, ma->ptet.bottom_face); new_ma.ptet.bottom_face = EVALUATE(gluing, ma->ptet.near_face); new_ma.ptet.near_face = EVALUATE(gluing, old_trailing_face); new_ma.current_position = (ma->current_position + 2) % 3; } if (new_ma.ptet.tet->extra->drilling_curve_intersects_face [EVALUATE(gluing, ma->ptet.near_face)] == TRUE) { *new_leading_face = EVALUATE(gluing, ma->ptet.near_face); new_ma.ptet.bottom_face = EVALUATE(gluing, old_trailing_face); new_ma.ptet.near_face = EVALUATE(gluing, ma->ptet.bottom_face); new_ma.current_position = (ma->current_position + 1) % 3; } /* * Set the orientation in the ptet. */ new_ma.ptet.orientation = (parity[gluing] == orientation_preserving) ? ma->ptet.orientation : ! ma->ptet.orientation; /* * Copy the new data to the original data structure. */ *ma = new_ma; } static void transfer_CS( Triangulation *old_manifold, Triangulation *new_manifold) { Triangulation *old_copy, *new_copy; /* * If the old_manifold doesn't have a known CS value, * then we certainly can't find one for the new_manifold. */ if (old_manifold->CS_fudge_is_known == FALSE) return; /* * To minimize the potential trouble with negatively * oriented Tetrahedra, we transfer the CS_value from * the complete structure on the old_manifold to the * (,)(,)...(,)(1,0) Dehn filling on the new_manifold. */ /* * First make copies of the old_manifold and the new_manifold, * so we can feel free to mess 'em up. */ copy_triangulation(old_manifold, &old_copy); copy_triangulation(new_manifold, &new_copy); /* * Restore the complete solution on old_copy, * and complete all the cusps. */ copy_solution(old_copy, complete, filled); complete_all_cusps(old_copy); /* * Attempt to compute the CS_value for old_copy * based on its CS_fudge. */ compute_CS_value_from_fudge(old_copy); /* * If no CS_value has materialized (e.g. due to * negatively oriented tetrahedra), we're out of luck. */ if (old_copy->CS_value_is_known == FALSE) { free_triangulation(old_copy); free_triangulation(new_copy); return; } /* * Restore the complete solution on new_copy, * complete all the cusps, and then do a (1,0) * Dehn filling on the recently drilled cusp * to obtain a manifold isometric to old_copy. */ copy_solution(new_copy, complete, filled); complete_all_cusps(new_copy); set_cusp_info(new_copy, new_copy->num_cusps - 1, FALSE, 1.0, 0.0); do_Dehn_filling(new_copy); /* * Transfer the CS_value from old_copy to new_copy. */ new_copy->CS_value_is_known = TRUE; new_copy->CS_value[ultimate] = old_copy->CS_value[ultimate]; new_copy->CS_value[penultimate] = old_copy->CS_value[penultimate]; /* * With luck, we can convert the CS_value into a CS_fudge. * (Without luck, CS_fudge_is_known will be set to FALSE, * and subsequent operations will be vacuous.) */ compute_CS_fudge_from_value(new_copy); /* * Transfer the CS_fudge from new_copy to new_manifold. */ new_manifold->CS_fudge_is_known = new_copy->CS_fudge_is_known; new_manifold->CS_fudge[ultimate] = new_copy->CS_fudge[ultimate]; new_manifold->CS_fudge[penultimate] = new_copy->CS_fudge[penultimate]; /* * If everything is still hanging together, we can * use the CS_fudge to compute the CS_value. */ compute_CS_value_from_fudge(new_manifold); /* * Free the copies. */ free_triangulation(old_copy); free_triangulation(new_copy); } snappea-3.0d3/SnapPeaKernel/code/dual_curves.c0100444000175000017500000007335207041100441017346 0ustar babbab/* * dual_curves.c * * This file provides the functions * * void dual_curves( Triangulation *manifold, * int max_size, * int *num_curves, * DualOneSkeletonCurve ***the_curves); * * void get_dual_curve_info( * DualOneSkeletonCurve *the_curve, * Complex *complete_length, * Complex *filled_length, * MatrixParity *parity) * * void free_dual_curves( * int num_curves, * DualOneSkeletonCurve **the_curves); * * dual_curves() computes a reasonable selection of simple closed curves * in a manifold's dual 1-skeleton. The meaning of "reasonable selection" * will be clarified below in the description of the algorithm. * * Input arguments: * * manifold is a pointer to the Triangulation of interest. * * max_size is the maximum number of segments in the curves * to be considered. Six is a reasonable value. * * Output aguments: * * *num_curves will be set to the number of curves the function finds. * * *the_curves will be set to the address of an array containing * pointers to the DualOneSkeletonCurves. That is, * (*the_curves)[i] will be a pointer to the i-th curve. * If no nonparabolic curves are found (as happens * with the Gieseking), *the_curves will be set to NULL. * * get_dual_curve_info() reports the complex length of a curve * in the dual 1-skeleton, relative to both the complete and filled * hyperbolic structures, and also its parity (orientation_preserving * or orientation_reversing). * * free_dual_curves() releases the array of DualOneSkeletonCurves * allocated by dual_curves(). (It releases both the * DualOneSkeletonCurves and the array of pointers to them.) * * * Terminology: Throughout this file we will flip-flop freely between * the description of a curve as vertices and edges in the dual 1-skeleton * and its dual description as Tetrahedra and 2-cells in the original * Triangulation. Please don't let this confuse you. * * * The algorithm. * * The set of all simple closed curves in the dual 1-skeleton * divides naturally into homotopy classes. Ideally, we'd like to * compute precisely one representative of each homotopy class, * and we'd like that representative to be unknotted in the sense * that it's isotopic to the unique geodesic in its homotopy class. * We won't always achieve this goal, but we'll do the best we can. * * The main obstacle to achieving the goal is the vast number of * simple closed curves to be considered, and the large number of * curves within each homotopy class. Even for a given curve of * size n, we could start traversing it at any of its n vertices, * and in either of two directions. To avoid this last problem, we * number all the Tetrahedra in the Triangulation and make two conventions: * * Convention #1: Each curve will have a "base Tetrahedron" which * is the lowest-numbered Tetrahedron on the curve. * * Convention #2: Each curve is traversed by starting at its * base Tetrahedron and going in the direction which takes you * through the face of lower index (of the two faces of the base * Tetrahedron which intersect the curve). For example, if a * curve intersects faces 1 and 3 of its base Tetrahedron, then * the canonical direction to traverse it is to start off through * face 1, traverse the whole curve, and return through face 3. * * It's easy enough to detect whether two different curves are * homotopic in the universal cover (they'll have the same Moebius * transformation) but it's not so easy to detect when one is homotopic * to a translate of the other. For this reason we keep only one curve * for any given complex length. An unfortunate side effect of this * decision is that when a manifold contains two geodesics of the * same length, we'll be able to drill out only one of them (in most * cases geodesics of the same length will be equivalent under some * symmetry of the manifold, but nevertheless it would have been nice * not to have had to impose this restriction). For each complex length, * the curve we keep will have minimal combinatorial size, to minimize * the chance of choosing a knotted representative of the homotopy class. * * [Modified 93/9/14 by JRW to compare the complex lengths of curves * in the filled structure as well as in the complete structure. * A curve will be discarded only if it has the same complex length * as some other curve, relative to both the complete and the filled * hyperbolic structures.] * * Within this file we are interested in the complex lengths of * geodesics relative to the complete structure on the manifold, * because curves which are parabolics relative to the complete * structure will either be obviously parallel to the boundary * (in which case drill_cusp() will fail), or not-so-obviously * parallel to the boundary, in which case drill_cusp() will yield * a nonhyperbolic manifold. But we also compute the complex * lengths of geodesics relative to the filled structure, for * the convenience of the user (e.g. the user might want to drill * out a geodesic of minimal length in a certain closed manifold, * perhaps as part of an effort to prove that two closed manifolds * are isometric [symmetry_group_closed.c now does this automatically]). */ /* * 95/10/1 JRW * I was concerned about stack/heap collisions caused by recursive * functions, and was going through the SnapPea kernel revising * recursive algorithms to use heap space instead. However, I decided * not to revise consider_its_neighbor() for the following reasons. * * (1) It's fairly complicated. Revising it would be time-consuming * and error-prone. * * (2) The recursion is very "shallow". It never goes more than * max_size levels deep. Typically max_size is about 6, or at * most 8 or 10, because the number of curves grows exponentially * with max_size. In other words, this is a harmless recursion. */ #include "kernel.h" /* * BIG_MODULUS is used to recognize when a complex number * represents the point at infinity on the Riemann sphere. * I haven't given much thought to the best value for * BIG_MODULUS, because in all common cases a number * will either clearly be infinite or clearly not be * infinite. * * 93/10/9. An error message provided the occasion to think * about the best value for BIG_MODULUS. Amazingly enough, after * doing several dozen Dehn fillings on each of thousands of cusped * census manifolds, I got the * * uFatalError("verify_mt_action", "dual_curves"); * * which indicated the previous value of BIG_MODULUS (namely 1e10) * was not big enough. The offending (revealing?) example * is v2395(-1,1). The value of fz and w are * * fz = 1.2656500338200879e+8 + i 1.89154960743708773e+9 * w = 1.2656500287902592e+8 + i 1.89154960735500588e+9 * * Later another example occurred with * * fz = -6.19138762819279958e+7 + i -6.56272444717916559e+7 * w = -6.19138762819635613e+7 + i -6.5627244471300831e+7 * * For most purposes I think I'll leave BIG_MODULUS at 1e10 * (this ensures accurate computations), but for the error check * in verify_mt_action() I'll make a provision that if fz and w * have moduli in the range 1e5 - 1e10 and differ by a small * percentage, they should be considered equal. */ #define BIG_MODULUS 1e10 #define BIG_MODULUS1 1e5 #define FRACTIONAL_DIFF 1e4 /* * MoebiusTransformations which translate a distance less * than PARABOLIC_EPSILON are judged to be parabolics. I haven't * given a lot of thought to the best value for PARABOLIC_EPSILON, * because in practice I expect that in all common examples * the MoebiusTransformations will be clearly parabolic or * clearly not parabolic. */ #define PARABOLIC_EPSILON 1e-2 /* * Two complex lengths are considered equal iff their real * and imaginary parts are within LENGTH_EPSILON of each other. */ #define LENGTH_EPSILON 1e-5 /* * If the MoebiusTransformation's action on the base * Tetrahedron's fourth vertex isn't correct to within * MINIMAL_ACCURACY, a fatal error is generated. */ #define MINIMAL_ACCURACY 1e-6 static void initialize_flags(Triangulation *manifold); static void consider_its_neighbor(Tetrahedron *tet, FaceIndex face, int size, Complex corners[2][4], Orientation orientation, Tetrahedron *tet0, FaceIndex face0, int max_size, Triangulation *manifold, DualOneSkeletonCurve **curve_tree); static void compute_corners(Complex corners[4], Complex nbr_corners[4], FaceIndex face, FaceIndex entry_face, Permutation gluing, Orientation nbr_orientation, ComplexWithLog cwl[3]); static void compute_Moebius_transformation(Tetrahedron *tet, Orientation orientation, Complex corners[4], MoebiusTransformation *mt); static void verify_mt_action(MoebiusTransformation *mt, Complex z, Complex w); static void add_curve_to_tree(Triangulation *manifold, DualOneSkeletonCurve **curve_tree, MatrixParity parity, Complex cl[2], int size); static DualOneSkeletonCurve *package_up_the_curve(Triangulation *manifold, MatrixParity parity, Complex cl[2], int size); static void replace_contents_of_node(DualOneSkeletonCurve *node, Triangulation *manifold, MatrixParity parity, Complex cl[2], int size); static void convert_tree_to_pointer_array( DualOneSkeletonCurve *curve_tree, int *num_curves, DualOneSkeletonCurve ***the_curves); static int count_the_curves(DualOneSkeletonCurve *curve_tree); static void write_node_addresses(DualOneSkeletonCurve *curve_tree, DualOneSkeletonCurve **the_array, int *count); void dual_curves( Triangulation *manifold, int max_size, int *num_curves, DualOneSkeletonCurve ***the_curves) { Tetrahedron *tet0; FaceIndex face0; Complex corners0[2][4]; DualOneSkeletonCurve *curve_tree; int i; /* * If the manifold does not have a hyperbolic * structure, return no curves. */ if ( ( manifold->solution_type[complete] != geometric_solution && manifold->solution_type[complete] != nongeometric_solution ) || ( manifold->solution_type[filled] != geometric_solution && manifold->solution_type[filled] != nongeometric_solution && manifold->solution_type[filled] != flat_solution ) ) { *num_curves = 0; *the_curves = NULL; return; } /* * Make sure the Tetrahedra are numbered. */ number_the_tetrahedra(manifold); /* * curve_tree is a pointer to the root of a binary * tree containing all the curves found so far. * Initialize it to NULL. */ curve_tree = NULL; /* * Set the tet_on_curve and face_on_curve[] flags * to FALSE to show that the curve is initially empty. */ initialize_flags(manifold); /* * Consider each possible base Tetrahedron. */ for (tet0 = manifold->tet_list_begin.next; tet0 != &manifold->tet_list_end; tet0 = tet0->next) { /* * Mark the base Tetrahedron. */ tet0->tet_on_curve = TRUE; /* * Put the corners of the base Tetrahedron * in the standard position. */ for (i = 0; i < 2; i++) /* i = complete, filled */ { corners0[i][0] = Infinity; corners0[i][1] = Zero; corners0[i][2] = One; corners0[i][3] = tet0->shape[i]->cwl[ultimate][0].rect; } /* * Consider each possible initial face for the curve. * By Convention #2 above, we may assume the initial * face is not face 3. */ for (face0 = 0; face0 < 3; face0++) consider_its_neighbor( tet0, face0, 1, corners0, right_handed, tet0, face0, max_size, manifold, &curve_tree); /* * Unmark the base Tetrahedron. */ tet0->tet_on_curve = FALSE; } /* * curve_tree will now point to a binary tree containing * the DualOneSkeletonCurves. Write the addresses of the * nodes into an array, as specified in the documentation * at the top of this file. */ convert_tree_to_pointer_array(curve_tree, num_curves, the_curves); } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { tet->tet_on_curve = FALSE; for (i = 0; i < 4; i++) tet->face_on_curve[i] = FALSE; } } static void consider_its_neighbor( Tetrahedron *tet, FaceIndex face, int size, Complex corners[2][4], Orientation orientation, Tetrahedron *tet0, FaceIndex face0, int max_size, Triangulation *manifold, DualOneSkeletonCurve **curve_tree) { Tetrahedron *nbr; Permutation gluing; FaceIndex nbr_face, entry_face; Orientation nbr_orientation; Complex nbr_corners[2][4]; MoebiusTransformation mt[2]; Complex cl[2]; int i; /* * We want to examine the Tetrahedron incident * to face "face" of Tetrahedron "tet". */ nbr = tet->neighbor[face]; gluing = tet->gluing[face]; entry_face = EVALUATE(gluing, face); nbr_orientation = (parity[gluing] == orientation_preserving) ? orientation : ! orientation; /* * Is nbr the base Tetrahedron? * If so, process the curve and return. */ if (nbr == tet0) { /* * We've found a curve adhering to Convention #2 * iff we're reentering the base Tetrahedron at * a higher numbered face than the one we left at. */ if (entry_face > face0) { /* * Process this curve. */ /* * Compute the locations of the nbr_corners. */ for (i = 0; i < 2; i++) /* i = complete, filled */ compute_corners(corners[i], nbr_corners[i], face, entry_face, gluing, nbr_orientation, nbr->shape[i]->cwl[ultimate]); /* * For the complete structure and also for the filled structure, * compute the MoebiusTransformation which takes the original * base Tetrahedron to nbr. */ for (i = 0; i < 2; i++) /* i = complete, filled */ compute_Moebius_transformation( nbr, nbr_orientation, nbr_corners[i], &mt[i]); /* * The computation of the MoebiusTransformation used * only the location of corners 0, 1 and 2. As a check * against errors, let's see whether the MoebiusTransformation * also takes corner 3 to the right place. */ for (i = 0; i < 2; i++) /* i = complete, filled */ verify_mt_action( &mt[i], tet0->shape[i]->cwl[ultimate][0].rect, nbr_corners[i][3]); /* * Compute the complex length of the geodesic corresponding * to the covering transformation represented by mt. */ for (i = 0; i < 2; i++) /* i = complete, filled */ cl[i] = complex_length_mt(&mt[i]); /* * Ignore parabolics (relative to the complete structure). */ if (fabs(cl[complete].real) < PARABOLIC_EPSILON) return; /* * Add the final segment to close the curve. */ tet->face_on_curve[face] = TRUE; nbr->face_on_curve[entry_face] = TRUE; /* * Add the curve to the list, unless a curve of * equal complex length and smaller or equal * combinatorial size is already there. */ add_curve_to_tree(manifold, curve_tree, mt[0].parity, cl, size); /* * Remove the final segment of the curve before * continuing on to look for other possibilities. */ tet->face_on_curve[face] = FALSE; nbr->face_on_curve[entry_face] = FALSE; } return; } /* * Is nbr a Tetrahedron which has already been visited * (other than the base Tetrahedron, which was handled above)? * If so, return. */ if (nbr->tet_on_curve == TRUE) return; /* * If nbr's index is less than the index of the base Tetrahedron, * then Convention #1 dictates that we return without doing anything. */ if (nbr->index < tet0->index) return; /* * If size == max_size, then we should stop the recursion. */ if (size == max_size) return; /* * nbr has passed all the above tests, so add it to the curve... */ nbr->tet_on_curve = TRUE; tet->face_on_curve[face] = TRUE; nbr->face_on_curve[entry_face] = TRUE; /* * ...compute the positions of its corners... */ for (i = 0; i < 2; i++) /* i = complete, filled */ compute_corners(corners[i], nbr_corners[i], face, entry_face, gluing, nbr_orientation, nbr->shape[i]->cwl[ultimate]); /* * ...recursively consider each of its neighbors... */ for (nbr_face = 0; nbr_face < 4; nbr_face++) if (nbr_face != entry_face) consider_its_neighbor( nbr, nbr_face, size + 1, nbr_corners, nbr_orientation, tet0, face0, max_size, manifold, curve_tree); /* * ...and remove it from the curve. */ nbr->tet_on_curve = FALSE; tet->face_on_curve[face] = FALSE; nbr->face_on_curve[entry_face] = FALSE; } static void compute_corners( Complex corners[4], Complex nbr_corners[4], FaceIndex face, FaceIndex entry_face, Permutation gluing, Orientation nbr_orientation, ComplexWithLog cwl[3]) { int i; /* * Knock off the three easy ones. */ for (i = 0; i < 4; i++) if (i != face) nbr_corners[EVALUATE(gluing, i)] = corners[i]; /* * Then call compute_fourth_corner() to find the * fourth corner in terms of the first three. */ compute_fourth_corner( nbr_corners, entry_face, nbr_orientation, cwl); } static void compute_Moebius_transformation( Tetrahedron *tet, Orientation orientation, Complex corners[4], MoebiusTransformation *mt) { /* * The base Tetrahedron originally had its corners * at (infinity, 0, 1, z). We've now traced out a * curve which, when lifted to the universal cover, * leads to a translate of the base Tetrahedron with * corner coordinates given by the array corners[]. * The associated covering transformation will be * the Moebius transformation which takes * * infinity -> corners[0] * 0 -> corners[1] * 1 -> corners[2] * * and has the specified Orientation. * * Because {infinity, 0, 1} are invariant under complex * conjugation, we can compute the SL2CMatrix without * worrying about the Orientation. (The Orientation * will, of course, determine the parity field of the * MoebiusTransformation.) * * An SL2CMatrix taking * * infinity -> c0 * 0 -> c1 * 1 -> c2 * * is given by * * c0(c2 - c1) z + c1(c0 - c2) * w = ----------------------------- * (c2 - c1) z + (c0 - c2) * * (This matrix must, of course, be normalized to have * determinant one.) * * In the special case that c0 is infinite, the formula * reduces to * * w = (c2 - c1) z + c1 * * In the special case that c1 is infinite, the formula * reduces to * * c0 z + (c2 - c0) * w = ------------------ * z * * In the special case that c2 is infinite, the formula * reduces to * * c0 z - c1 * w = ----------- * z - 1 * */ /* * Evaluate the appropriate formula from above. * Don't worry yet about normalizing to have * determinant one. */ if (complex_modulus(corners[0]) > BIG_MODULUS) { /* * c0 is infinite. * Use the special formula * * w = (c2 - c1) z + c1 */ mt->matrix[0][0] = complex_minus(corners[2], corners[1]); mt->matrix[0][1] = corners[1]; mt->matrix[1][0] = Zero; mt->matrix[1][1] = One; } else if (complex_modulus(corners[1]) > BIG_MODULUS) { /* * c1 is infinite. * Use the special formula * * c0 z + (c2 - c0) * w = ------------------ * z */ mt->matrix[0][0] = corners[0]; mt->matrix[0][1] = complex_minus(corners[2], corners[0]); mt->matrix[1][0] = One; mt->matrix[1][1] = Zero; } else if (complex_modulus(corners[2]) > BIG_MODULUS) { /* * c2 is infinite. * Use the special formula * * c0 z - c1 * w = ----------- * z - 1 */ mt->matrix[0][0] = corners[0]; mt->matrix[0][1] = complex_negate(corners[1]); mt->matrix[1][0] = One; mt->matrix[1][1] = MinusOne; } else { /* * None of {c0, c1, c2} is infinite. * Use the general formula * * c0(c2 - c1) z + c1(c0 - c2) * w = ----------------------------- * (c2 - c1) z + (c0 - c2) * * Note that for computational efficiency we * evaluate the terms in the denominator first. */ mt->matrix[1][0] = complex_minus(corners[2], corners[1]); mt->matrix[1][1] = complex_minus(corners[0], corners[2]); mt->matrix[0][0] = complex_mult(corners[0], mt->matrix[1][0]); mt->matrix[0][1] = complex_mult(corners[1], mt->matrix[1][1]); } /* * Normalize matrix to have determinant one. */ sl2c_normalize(mt->matrix); /* * Set the MoebiusTransformation's parity. * The base Tetrahedron had the right_handed Orientation, * so the MoebiusTransformation will be orientation_preserving * iff the translate of the base Tetrahedron also has the * right_handed Orientation. */ mt->parity = (orientation == right_handed) ? orientation_preserving : orientation_reversing; } static void verify_mt_action( MoebiusTransformation *mt, Complex z, Complex w) { Complex fz; /* * Does mt take z to w? */ /* * If the MoebiusTransformation is orientation_reversing, * we must first replace z by its complex conjugate. */ if (mt->parity == orientation_reversing) z = complex_conjugate(z); /* Evaluate * * f(z) = (az + b)/(cz + d) * * and compare the result to w. */ fz = complex_div( complex_plus( complex_mult(mt->matrix[0][0], z), mt->matrix[0][1] ), complex_plus( complex_mult(mt->matrix[1][0], z), mt->matrix[1][1] ) ); /* * fz and w should either be very close, or * both should be infinite. Flag an error * if this is not the case. */ if ( complex_modulus(complex_minus(fz, w)) > MINIMAL_ACCURACY && ( complex_modulus(fz) < BIG_MODULUS || complex_modulus(w) < BIG_MODULUS ) && ( complex_modulus(fz) < BIG_MODULUS1 || complex_modulus(w) < BIG_MODULUS1 || complex_modulus(complex_div(complex_minus(fz, w), fz)) > FRACTIONAL_DIFF ) ) uFatalError("verify_mt_action", "dual_curves"); } static void add_curve_to_tree( Triangulation *manifold, DualOneSkeletonCurve **curve_tree, MatrixParity parity, Complex cl[2], /* complex length of geodesic */ int size) /* combinatorial size of curve */ { DualOneSkeletonCurve *node; int position; /* * First check for the special case that the * curve_tree might be empty. */ if (*curve_tree == NULL) { *curve_tree = package_up_the_curve(manifold, parity, cl, size); return; } /* * if (the tree does not yet contain a curve of * the given complex length) * add the current curve to the tree * else * if the current curve is combinatorially shorter * than the old curve of the same length, * replace it. */ node = *curve_tree; while (TRUE) { /* * Set the integer "position" to -1 if we belong * somewhere to the left of this node, to +1 if we * belong to the right, and to 0 if we belong here. * * Modified 93/9/14 by JRW. If two curves have the same complex * length in the complete structure but different complex lengths * in the filled structure, then we want to list them separately. * * Modified 94/10/8 by JRW. Sort first by filled length, * then by complete length. The user is probably paying more * attention to filled lengths than complete ones. */ if (cl[filled].real < node->length[filled].real - LENGTH_EPSILON) position = -1; else if (cl[filled].real > node->length[filled].real + LENGTH_EPSILON) position = +1; else if (cl[filled].imag < node->length[filled].imag - LENGTH_EPSILON) position = -1; else if (cl[filled].imag > node->length[filled].imag + LENGTH_EPSILON) position = +1; else if (cl[complete].real < node->length[complete].real - LENGTH_EPSILON) position = -1; else if (cl[complete].real > node->length[complete].real + LENGTH_EPSILON) position = +1; else if (cl[complete].imag < node->length[complete].imag - LENGTH_EPSILON) position = -1; else if (cl[complete].imag > node->length[complete].imag + LENGTH_EPSILON) position = +1; else position = 0; switch (position) { case -1: if (node->left_child != NULL) node = node->left_child; else { node->left_child = package_up_the_curve(manifold, parity, cl, size); return; } break; case +1: if (node->right_child != NULL) node = node->right_child; else { node->right_child = package_up_the_curve(manifold, parity, cl, size); return; } break; case 0: if (size < node->size) replace_contents_of_node(node, manifold, parity, cl, size); return; } } /* * The program never reaches this point. */ } static DualOneSkeletonCurve *package_up_the_curve( Triangulation *manifold, MatrixParity parity, Complex cl[2], int size) { DualOneSkeletonCurve *node; node = NEW_STRUCT(DualOneSkeletonCurve); node->tet_intersection = NEW_ARRAY(manifold->num_tetrahedra, DualOneSkeletonCurvePiece); replace_contents_of_node(node, manifold, parity, cl, size); node->left_child = NULL; node->right_child = NULL; return node; } static void replace_contents_of_node( DualOneSkeletonCurve *node, Triangulation *manifold, MatrixParity parity, Complex cl[2], int size) { Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) node->tet_intersection[tet->index][i] = tet->face_on_curve[i]; node->parity = parity; node->length[complete] = cl[complete]; node->length[filled] = cl[filled]; node->size = size; } static void convert_tree_to_pointer_array( DualOneSkeletonCurve *curve_tree, int *num_curves, DualOneSkeletonCurve ***the_curves) { int count; /* * First handle the special case that no curves were found. */ if (curve_tree == NULL) { *num_curves = 0; *the_curves = NULL; return; } /* * Count the curves. */ *num_curves = count_the_curves(curve_tree); /* * Allocate the array for the pointers. */ *the_curves = NEW_ARRAY(*num_curves, DualOneSkeletonCurve *); /* * Write the addresses of the nodes into the array. */ count = 0; write_node_addresses(curve_tree, *the_curves, &count); /* * A quick error check. */ if (count != *num_curves) uFatalError("convert_tree_to_pointer_array", "dual_curves"); } static int count_the_curves( DualOneSkeletonCurve *curve_tree) { DualOneSkeletonCurve *subtree_stack, *subtree; int num_curves; /* * Initialize the stack to contain the whole tree. */ subtree_stack = curve_tree; if (curve_tree != NULL) curve_tree->next_subtree = NULL; /* * Initialize the count to zero. */ num_curves = 0; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Count the subtree's root node. */ num_curves++; } return num_curves; } static void write_node_addresses( DualOneSkeletonCurve *curve_tree, DualOneSkeletonCurve **the_array, int *count) { DualOneSkeletonCurve *subtree_stack, *subtree; /* * Implement the recursive tree traversal using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole product_tree. */ subtree_stack = curve_tree; if (curve_tree != NULL) curve_tree->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If it has no further subtrees, append it to the array. * * Otherwise break it into three chunks: * * the left subtree * this node * the right subtree * * and push them onto the stack in reverse order, so that they'll * come off in the correct order. Set this node's left_child and * right_child fields to NULL, so the next time it comes off the * stack we'll know the subtrees have been accounted for. */ if (subtree->left_child == NULL && subtree->right_child == NULL) { the_array[(*count)++] = subtree; } else { /* * Push the right subtree (if any) onto the stack. */ if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; subtree->right_child = NULL; } /* * Push this node onto the stack. * (Its left_child and right_child fields will soon be NULL.) */ subtree->next_subtree = subtree_stack; subtree_stack = subtree; /* * Push the left subtree (if any) onto the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; subtree->left_child = NULL; } } } } void get_dual_curve_info( DualOneSkeletonCurve *the_curve, Complex *complete_length, Complex *filled_length, MatrixParity *parity) { if (complete_length != NULL) *complete_length = the_curve->length[complete]; if (filled_length != NULL) *filled_length = the_curve->length[filled]; if (parity != NULL) *parity = the_curve->parity; } void free_dual_curves( int num_curves, DualOneSkeletonCurve **the_curves) { int i; /* * If num_curves is zero, then no storage should * have been allocated in the first place. */ if (num_curves == 0) { if (the_curves == NULL) return; else uFatalError("free_dual_curves", "dual_curves"); } /* * Free each DualOneSkeletonCurve. */ for (i = 0; i < num_curves; i++) { my_free(the_curves[i]->tet_intersection); my_free(the_curves[i]); } /* * Free the pointer array. */ my_free(the_curves); } snappea-3.0d3/SnapPeaKernel/code/edge_classes.c0100444000175000017500000001454306742675501017475 0ustar babbab/* * edge_classes.c * * This file provides the functions * * void create_edge_classes(Triangulation *manifold); * void replace_edge_classes(Triangulation *manifold); * void orient_edge_classes(Triangulation *manifold); * * which are used within the kernel. * * create_edge_classes() adds EdgeClasses to a partially * constructed manifold which does not yet have them. * It assumes the tet->neighbor and tet->gluing fields * contain correct values. * * replace_edge_classes() removes all EdgeClasses from a manifold * and adds fresh ones. replace_edge_classes() is typically called * by functions which would rather replace invalid EdgeClasses * at the end of an algorithm rather than try to maintain them * as they go along. * * orient_edge_classes() orients a neighborhood of each EdgeClass. * Relative to this orientation, each of the incident tetrahedra * will be seen as right_ or left_handed. * * The edges of a tetrahedron are indexed according to the following table: * * lies lies * edge between between * faces vertices * 0 0,1 2,3 * 1 0,2 1,3 * 2 0,3 1,2 * 3 1,2 0,3 * 4 1,3 0,2 * 5 2,3 0,1 * * orient_edge_classes() sets the field tet->edge_orientation[e] to be the * orientation of Tetrahedron tet as seen by EdgeClass tet->edge_class[e]. * In an oriented manifold, all edge_orientations will be right_handed. * * orient_edge_classes() should be called as soon as a Triangulation * is created, and functions which modify a Triangulation should * maintain the edge_orientation[] fields. * * As explained in the documentation at the top of orient.c, orient() * and orient_edge_classes() may be called in either order, but both * should be called. */ #include "kernel.h" static void initialize_tet_edge_classes(Triangulation *manifold); static void create_one_edge_class(Triangulation *manifold, Tetrahedron *tet, EdgeIndex e); void create_edge_classes( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; /* * First set tet->edge_class[] to NULL for all * edges of all Tetrahedra. */ initialize_tet_edge_classes(manifold); /* * Go down the list of Tetrahedra, and whenever * an edge is found with no EdgeClass, create one * for it. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) if (tet->edge_class[e] == NULL) create_one_edge_class(manifold, tet, e); } static void initialize_tet_edge_classes( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) tet->edge_class[e] = NULL; } static void create_one_edge_class( Triangulation *manifold, Tetrahedron *tet, EdgeIndex e) { EdgeClass *new_edge_class; FaceIndex front, back, temp; Permutation gluing; Tetrahedron *tet0; EdgeIndex e0; /* * Create the new EdgeClass and add it to the list. */ new_edge_class = NEW_STRUCT(EdgeClass); initialize_edge_class(new_edge_class); INSERT_BEFORE(new_edge_class, &manifold->edge_list_end); /* * Initialize the fields of the EdgeClass. */ new_edge_class->order = 0; new_edge_class->incident_tet = tet; new_edge_class->incident_edge_index = e; /* * Walk around the edge class, setting the tet->edge_class * field for each edge we encounter. */ front = one_face_at_edge[e]; back = other_face_at_edge[e]; tet0 = tet; e0 = e; do { /* * Set the edge_class pointer . . . */ tet->edge_class[e] = new_edge_class; /* * . . . increment new_edge_class->order . . . */ new_edge_class->order++; /* * . . . and move on to the next edge. */ gluing = tet->gluing[front]; tet = tet->neighbor[front]; temp = front; front = EVALUATE(gluing, back); back = EVALUATE(gluing, temp); e = edge_between_faces[front][back]; } while (tet != tet0 || e != e0); } void replace_edge_classes( Triangulation *manifold) { EdgeClass *dead_edge_class; /* * Remove all existing EdgeClasses . . . */ while (manifold->edge_list_begin.next != &manifold->edge_list_end) { dead_edge_class = manifold->edge_list_begin.next; REMOVE_NODE(dead_edge_class); my_free(dead_edge_class); } /* * . . . and add fresh ones. */ create_edge_classes(manifold); } void orient_edge_classes( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; FaceIndex front, back, temp; Orientation relative_orientation; Permutation gluing; int count; /* * For each EdgeClass in the Triangulation . . . */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * Find an incident edge. */ tet = edge->incident_tet; e = edge->incident_edge_index; front = one_face_at_edge[e]; back = other_face_at_edge[e]; /* * View the incident Tetrahedron relative to the * right_handed Orientation. */ relative_orientation = right_handed; /* * We'll walk around the EdgeClass, setting * the Orientation of each incident edge. */ for (count = edge->order; --count >= 0; ) { /* * Set the edge_orientation of the present edge . . . */ tet->edge_orientation[e] = relative_orientation; /* * . . . and move on to the next edge. */ gluing = tet->gluing[front]; tet = tet->neighbor[front]; temp = front; front = EVALUATE(gluing, back); back = EVALUATE(gluing, temp); e = edge_between_faces[front][back]; /* * Change the relative_orientation iff the new * new Tetrahedron is oriented differently than * the old one. */ if (parity[gluing] == orientation_reversing) relative_orientation = ! relative_orientation; } /* * When we return to the initial Tetrahedron, * the relative_orientation should again be right_handed. * If it isn't, the triangulation defines an orbifold * with a cone-on-a-projective-plane at the center of * the current edge class. This error should be rare -- * in fact is should be possible only for hand-coded * Triangulations. */ if (relative_orientation != right_handed) { uAcknowledge("The triangulation has a cone-on-a-projective-plane singularity at the midpoint of an edge class."); uFatalError("orient_edge_classes", "edge_classes"); } } } snappea-3.0d3/SnapPeaKernel/code/elements_generate_group.c0100444000175000017500000001064406742675501021754 0ustar babbab/* * elements_generate_group.c * * This file provides the utility * * Boolean elements_generate_group( * SymmetryGroup *the_group, * int num_possible_generators, * int possible_generators[]); * * which the SymmetryGroup recognition functions use to check whether a * map from a well-known group to the_group is surjective. * * The array possible_generators[] contains num_possible_generators * elements of the_group. If those elements generate the_group, then * elements_generate_group() returns TRUE. Otherwise it returns FALSE. */ #include "kernel.h" Boolean elements_generate_group( SymmetryGroup *the_group, int num_possible_generators, int possible_generators[]) { /* * Do the elements listed in the array possible_generators[] * generate the_group? * * We use a queue to keep track of which elements are in the * subgroup generated by possible_generators[]. As new elements * are discovered, they go on the back of the queue. We also * keep track of an initial segment of the queue containing those * elements which are "fully processed". An element has been fully * processed once we've considered its product with all other fully * processed elements (and with itself). If any such products * yield elements not yet on the queue, we add them to the queue. * Once all the elements on the queue are fully processed, we'll * have the smallest subgroup containing the possible_generators[]. * (Proof: Clearly we'll have the smallest subset which is closed * under multiplication. For finite groups, closure under * multiplication implies closure under inverses -- the subgroup * generated by a single element is finite cyclic, so an element's * inverse is one of its powers.) * * The variable total_elements keeps track of the total number of * elements on the queue, while fully_processed keeps track of the * number which have been fully processed. For example, at some point * in the computation, the queue might look like * * 0 7 2 12 5 6 14 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 * * total_elements will equal 7, indicating that so far we've discovered * 7 elements of the subgroup generated by possible_generators[]. * * fully_processed might equal, say, 4, indicating that all possible * products of the elements {0, 7, 2, 12} are among the 7 elements on * the queue. * * Naively this would be a cubic time algorithm, but we can do it in * quadratic time if we maintain a Boolean array recording which * elements are on the queue. That is, array[i] == TRUE iff element i * is on the queue. */ int *queue, total_elements, fully_processed; Boolean *array; int g, product; int i; /* * Allocate space for the queue and the array. */ queue = NEW_ARRAY(the_group->order, int); array = NEW_ARRAY(the_group->order, Boolean); /* * Initialize the queue and the array. */ for (i = 0; i < the_group->order; i++) { queue[i] = -1; array[i] = FALSE; } /* * Add the possible_generators[] to the queue. */ for (i = 0; i < num_possible_generators; i++) { queue[i] = possible_generators[i]; array[possible_generators[i]] = TRUE; } /* * Initialize the counts. */ total_elements = num_possible_generators; fully_processed = 0; /* * Process the elements on the queue. */ while (fully_processed < total_elements) { /* * For convenience, call the next element on the queue "g". * Increment fully_processed, so that in the next step g will * be multiplied by itself as well as all preceding elements. */ g = queue[fully_processed++]; /* * Consider the products (on the left and on the right) of * g with all fully processed elements (including g itself). */ for (i = 0; i < fully_processed; i++) { /* * If g * queue[i] isn't already on the queue, add it. */ product = the_group->product[g][queue[i]]; if (array[product] == FALSE) { queue[total_elements++] = product; array[product] = TRUE; } /* * If queue[i] * g isn't already on the queue, add it. */ product = the_group->product[queue[i]][g]; if (array[product] == FALSE) { queue[total_elements++] = product; array[product] = TRUE; } } } /* * Free local storage. */ my_free(queue); my_free(array); /* * possible_generators[] generate the whole group iff all the * group elements ended up on the queue. */ return (total_elements == the_group->order); } snappea-3.0d3/SnapPeaKernel/code/filling.c0100444000175000017500000001732306742675501016477 0ustar babbab/* * filling.c * * This file contains the functions * * Triangulation *fill_cusps(Triangulation *manifold, * Boolean fill_cusp[], * char *new_name, * Boolean fill_all_cusps); * * Triangulation *fill_reasonable_cusps(Triangulation *manifold); * * Boolean cusp_is_fillable(Cusp *cusp); * Boolean is_closed_manifold(Triangulation *manifold); * * which the kernel provides to the UI. * * fill_cusps() permanently fills k of the cusps of an n-cusp manifold. * It returns an ideal Triangulation of the resulting (n - k)-cusp manifold. * * 99/06/04 Previous versions of fill_cusps() insisted that at least * one cusp be left unfilled. The current version allows all cusps * to be filled, in which case it produces a finite triangulation. * Warning: Most SnapPea functions insist on an ideal triangulation -- * the finite triagulation is provided mainly for writing to disk. * * Arguments: * * manifold is the original manifold. The Dehn filling * coefficients cusp->m and cusp->l specify how * each cusp is to be filled. * * fill_cusp says which cusps are to be filled. The cusp * of index i will be filled iff fill_cusp[i] is TRUE. * If fill_all_cusps (see below) is TRUE, then * fill_cusp is ignored and may be NULL. * * new_name provides the name for the new Triangulation. * * fill_all_cusps says whether to fill all the cusps, producing * a triangulation with finite vertices only. * Usually fill_all_cusps is FALSE. * * The UI should decide how to present fill_cusps() to the user. * Should all currently Dehn filled cusps be filled at once? * Should the user be presented with a list of check boxes to * specify which cusps to fill? Should cusps be filled one at a time? * My hope is that fill_cusps() is sufficiently general to support * whatever approach the UI developer prefers. * * Having said that, let me now mention fill_reasonable_cusps(), which * makes a decision about which cusps to fill, and then makes a call * to fill_cusp(). fill_reasonable_cusps() will fill all cusps which * have relatively prime Dehn filling coefficients, unless this would * leave no unfilled cusps, in which case it leaves cusp 0 unfilled. * It copies the name from the manifold being filled. * * cusp_is_fillable() determines whether an individual cusp is fillable. * * The original manifold is always left unaltered. * * The files subdivide.c, close_cusps.c, and remove_finite_vertices.c * document the algorithm in detail. */ #include "kernel.h" static Boolean check_fill_cusp_array(Triangulation *manifold, Boolean fill_cusp[]); static Boolean cusp_is_fillable_x(Cusp *cusp); static Boolean no_cusps_to_be_filled(int num_cusps, Boolean fill_cusp[]); Triangulation *fill_cusps( Triangulation *manifold, Boolean fill_cusp[], char *new_name, Boolean fill_all_cusps) { Triangulation *new_triangulation; Boolean at_least_one_cusp_is_left; Boolean *all_true; int i; /* * 95/10/1 JRW * The following algorithm works correctly even if no cusps are * to be filled, but we can speed it up a bit by simply copying * the Triangulation. */ if (fill_all_cusps == FALSE && no_cusps_to_be_filled(manifold->num_cusps, fill_cusp) == TRUE) { copy_triangulation(manifold, &new_triangulation); return new_triangulation; } /* * First let's do a little error checking on the fill_cusp[] array. */ if (fill_all_cusps == FALSE) { /* * Check that Dehn filling coefficients are relatively prime integers, * and also that at least one cusp is left unfilled. */ at_least_one_cusp_is_left = check_fill_cusp_array(manifold, fill_cusp); if (at_least_one_cusp_is_left == FALSE) uFatalError("fill_cusps", "filling"); } else { /* * Check that Dehn filling coefficients are relatively prime integers. */ all_true = NEW_ARRAY(manifold->num_cusps, Boolean); for (i = 0; i < manifold->num_cusps; i++) all_true[i] = TRUE; (void) check_fill_cusp_array(manifold, all_true); /* * Do NOT free all_true just yet. */ } /* * Subdivide the triangulation, introducing finite vertices. * Note that the original triangulation is left unharmed. */ new_triangulation = subdivide(manifold, new_name); /* * Close the Cusps specified in the fill_cusp[] array. */ close_cusps(new_triangulation, fill_all_cusps ? all_true : fill_cusp); /* * We're done with the all_true array. */ if (fill_all_cusps == TRUE) my_free(all_true); /* * Retriangulate with no finite vertices. */ if (fill_all_cusps == FALSE) remove_finite_vertices(new_triangulation); /* includes basic_simplification() */ else basic_simplification(new_triangulation); /* * If the old manifold had a hyperbolic structure, * try to find one for the new_triangulation as well. */ if (fill_all_cusps == FALSE && manifold->solution_type[complete] != not_attempted) { find_complete_hyperbolic_structure(new_triangulation); do_Dehn_filling(new_triangulation); /* * If the old manifold had a known Chern-Simons invariant, * pass it to the new_triangulation. */ if (manifold->CS_value_is_known == TRUE) { new_triangulation->CS_value_is_known = manifold->CS_value_is_known; new_triangulation->CS_value[ultimate] = manifold->CS_value[ultimate]; new_triangulation->CS_value[penultimate] = manifold->CS_value[penultimate]; /* * The solution_type may or may not be good enough to compute * the fudge factor, but we'll let compute_CS_fudge_from_value() * worry about that. */ compute_CS_fudge_from_value(new_triangulation); } } return new_triangulation; } Triangulation *fill_reasonable_cusps( Triangulation *manifold) { Boolean *fill_cusp; Cusp *cusp; int i; Boolean all_cusps_are_fillable; Triangulation *new_triangulation; /* * Allocate the fill_cusp[] array. */ fill_cusp = NEW_ARRAY(manifold->num_cusps, Boolean); /* * See which cusps are fillable. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) fill_cusp[cusp->index] = cusp_is_fillable_x(cusp); /* * If all the cusps are fillable, leave cusp 0 unfilled. */ all_cusps_are_fillable = TRUE; for (i = 0; i < manifold->num_cusps; i++) if (fill_cusp[i] == FALSE) all_cusps_are_fillable = FALSE; if (all_cusps_are_fillable == TRUE) fill_cusp[0] = FALSE; /* * Call fill_cusps(). */ new_triangulation = fill_cusps(manifold, fill_cusp, manifold->name, FALSE); /* * Free the fill_cusp[] array. */ my_free(fill_cusp); /* * Done. */ return new_triangulation; } static Boolean check_fill_cusp_array( Triangulation *manifold, Boolean fill_cusp[]) { Boolean at_least_one_cusp_is_left; Cusp *cusp; at_least_one_cusp_is_left = FALSE; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (fill_cusp[cusp->index]) { if (cusp_is_fillable_x(cusp) == FALSE) uFatalError("check_fill_cusp_array", "filling"); } else at_least_one_cusp_is_left = TRUE; return at_least_one_cusp_is_left; } Boolean cusp_is_fillable( /* For external use */ Triangulation *manifold, int cusp_index) { return cusp_is_fillable_x(find_cusp(manifold, cusp_index)); } static Boolean cusp_is_fillable_x( /* For internal use */ Cusp *cusp) { return( cusp->is_complete == FALSE && Dehn_coefficients_are_relatively_prime_integers(cusp) == TRUE); } static Boolean no_cusps_to_be_filled( int num_cusps, Boolean fill_cusp[]) { int i; for (i = 0; i < num_cusps; i++) if (fill_cusp[i] == TRUE) return FALSE; return TRUE; } Boolean is_closed_manifold( Triangulation *manifold) { return (all_cusps_are_filled(manifold) && all_Dehn_coefficients_are_relatively_prime_integers(manifold)); } snappea-3.0d3/SnapPeaKernel/code/find_cusp.c0100444000175000017500000000177206742675501017026 0ustar babbab/* * find_cusp.c * * This function provides the following utility for use within the kernel. * * Cusp *find_cusp(Triangulation *manifold, int cusp_index); * * The UI refers to cusps by their indices, because it doesn't know about * the Cusp data structure. When a kernel function receives a cusp_index, * it may call the function find_cusp() to convert the index to an actual * pointer to the corresponding Cusp data structure. If find_cusp() cannot * find a Cusp with the given cusp_index, it calls uFatalError(). */ #include "kernel.h" Cusp *find_cusp( Triangulation *manifold, int cusp_index) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->index == cusp_index) return cusp; /* * No Cusp with the given index was found. */ uFatalError("find_cusp", "find_cusp"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return NULL; } snappea-3.0d3/SnapPeaKernel/code/finite_vertices.c0100444000175000017500000004304007063516742020226 0ustar babbab/* * finite_vertices.c * * Certain routines make temporary use of finite (as opposed to ideal) * vertices; e.g. they are used in triangulating a link complement, * in retriangulating a partially filled multicusp manifold, and in * splitting along a normal surface. SnapPea represents a finite vertex * as a cusp whose is_finite field is set to TRUE. Except for the * is_finite, index, prev and next fields, all other fields of the Cusp * data structure are ignored. The indices are negative integers. * Functions which do not use finite vertices may safely ignore the * is_finite field, and assume no finite vertices are present. * Finite vertices are never counted in the num_cusps, num_or_cusps, * or num_nonor_cusps fields of a Triangulation. * * This file contains the function * * void remove_finite_vertices(Triangulation *manifold); * * which is used within the kernel to retriangulate the manifold * to remove the finite vertices. If manifold has no real cusps, * a single real cusp is created (it may be either a torus or * Klein bottle cusp). * * Technical note: It's OK to pass a manifold with some or all * of the peripheral curves missing, the cusp topologies unknown, * and num_or_cusps and/or num_nonor_cusps not set. (For example, * normal_surface_splitting.c does exactly that.) Of course any * peripheral curves which are known will be preserved. */ /* * The Algorithm * * Overview * * A finite vertex is represented by a "cusp" whose cross section is * topologically a sphere (in contrast to real cusps, whose cross sections * are always tori or Klein bottles). Throughout this documentation, * please imagine all tetrahedra to have truncated vertices, so that * a finite vertex appears as a spherical boundary component, and a * real cusp appears as a torus or Klein bottle boundary component. * To "remove a finite vertex", we'll modify the triangulation so as * to drill out a tube connecting a spherical boundary component to * a nearby torus or Klein bottle boundary component. We'll repeat * the procedure until no spherical boundary components remain. * * Details * * The manifold is assumed to be connected, so as long as spherical * boundary components remain we may find an edge E (in the triangulation * of the manifold) connecting a spherical boundary component to a * torus or Klein bottle boundary component. Let T be any triangle * (in the triangulation of the manifold) incident to E. * * If we cut along the triangle T, insert a triangular pillow, and reglue, * the topology of the manifold doesn't change. But instead of inserting * an ordinary triangular pillow, we'll insert a triangular pillow from * which a "tunnel" has been drilled out, so as to connect one of its * truncated vertices to another. (The tunnel is unknotted, but it * really doesn't matter.) * * A triangular-pillow-with-tunnel may be constructed from only two * ideal tetrahedra, according to the following gluings (the notation * is as in the file TriangulationFileFormat). * * tetrahedron 0 * 1 free free 1 * 0213 ---- ---- 1023 * * tetrahedron 1 * 0 1 1 0 * 0213 0213 0213 1023 * * Note: In my drawing the two tetrahedra are glued together along * face 0 to form a hexahedron. Vertex 0 of tetrahedron 0 appears * at the "north pole", vertex 0 of tetrahedron 1 appears at the * "south pole", and the remain three vertices appear along the "equator". * * Closed Manifolds * * If the triangulation has no real cusps, then an arbitrary spherical * boundary component is selected, and all other spherical boundary * components are connected to it as above. This yields a manifold * with a single spherical boundary component. An additional * triangular-pillow-with-tunnel is added to convert the spherical * boundary component to a torus or Klein bottle boundary. * * Acknowledgements * * My path to this algorithm was indirect. I thank Sergei Matveev * for suggesting I think about manifolds in terms of spines, and * I thank Carlo Petronio for helpful discussions which led me in * the direction of this construction. */ #include "kernel.h" static void initialize_matching_cusps(Triangulation *manifold, Cusp **special_fake_cusp); static void merge_cusps(Triangulation *manifold); static void drill_tube(Triangulation *manifold, Tetrahedron *tet, EdgeIndex e, Boolean creating_new_cusp); static void set_real_cusps(Triangulation *manifold, Cusp *special_fake_cusp); void remove_finite_vertices( Triangulation *manifold) { Cusp *special_fake_cusp; /* * Simplify the triangulation before we begin. * basic_simplification() should work OK even with finite vertices. */ basic_simplification(manifold); /* * The matching_cusp field of each fake cusp records the real cusp * to which the fake cusp has been connected. It's initialized to * NULL to indicate that the fake cusp has not yet been connected * to anything. For real cusps, it's convenient to have the * matching_cusp field always point to the cusp itself. If the * manifold has no real cusps, then choose a "special fake cusp" * to which all other fake cusps will be connected, and set its * matching_cusp field to point to itself. */ initialize_matching_cusps(manifold, &special_fake_cusp); /* * Keep merging fake cusps with real cusps until no further progress * is possible. */ merge_cusps(manifold); /* * Ideal vertices which used to be incident to fake cusps are * now all incident to real cusps. Update the tet->cusp[] fields, * and free the fake cusps (except for the special_fake_cusp, if any). */ set_real_cusps(manifold, special_fake_cusp); /* * If the manifold is closed (no real cusps) it will, at this point, * have one spherical "cusp", namely the special_fake_cusp. * Drill out a tube connecting the special_fake_cusp to itself, * to convert it from a sphere to a torus or Klein bottle. */ if (special_fake_cusp != NULL) { /* * Simplify the triangulation before drilling, * to increases the chances that the drilled out tube will * follow a topologically nontrivial loop through the manifold. * (This is essential if we want to express the resulting * closed manifold as a hyperbolic Dehn filling.) */ basic_simplification(manifold); drill_tube(manifold, manifold->tet_list_begin.next, 0, TRUE); } /* * The triangulation is now correct, but it is horribly inefficient. * Simplify it. * * Note: basic_simplification() calls tidy_peripheral_curves() * and compute_CS_fudge_from_value(). */ basic_simplification(manifold); } static void initialize_matching_cusps( Triangulation *manifold, Cusp **special_fake_cusp) { Boolean has_real_cusp; Cusp *cusp; has_real_cusp = FALSE; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite) cusp->matching_cusp = NULL; else { cusp->matching_cusp = cusp; has_real_cusp = TRUE; } if (has_real_cusp == FALSE) { *special_fake_cusp = manifold->cusp_list_begin.next; (*special_fake_cusp)->matching_cusp = *special_fake_cusp; } else *special_fake_cusp = NULL; } static void merge_cusps( Triangulation *manifold) { Boolean progress; EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; Cusp *one_cusp, *other_cusp; do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { tet = edge->incident_tet; e = edge->incident_edge_index; one_cusp = tet->cusp[one_vertex_at_edge[e]]; other_cusp = tet->cusp[other_vertex_at_edge[e]]; if (one_cusp->matching_cusp == NULL && other_cusp->matching_cusp != NULL) { one_cusp->matching_cusp = other_cusp->matching_cusp; drill_tube(manifold, tet, e, FALSE); progress = TRUE; } if (other_cusp->matching_cusp == NULL && one_cusp->matching_cusp != NULL) { other_cusp->matching_cusp = one_cusp->matching_cusp; drill_tube(manifold, tet, e, FALSE); progress = TRUE; } } } while (progress == TRUE); } static void drill_tube( Triangulation *manifold, Tetrahedron *tet, EdgeIndex e, Boolean creating_new_cusp) { /* * Insert a triangular-pillow-with-tunnel (as described at the top * of this file) so as to connect the boundary component at one * end of the given edge to the boundary component at the other end. * The orientation on the triangular pillow will match the * orientation on tet, so that the orientation on the manifold * (if there is one) will be preserved. Edge orientations are also * respected. */ VertexIndex v0, v1, v2, vv0, vv1, vv2; FaceIndex f, ff; Tetrahedron *nbr_tet, *new_tet0, *new_tet1; Permutation gluing; EdgeClass *edge0, *edge1, *edge2, *new_edge; Orientation edge_orientation0, edge_orientation1, edge_orientation2; PeripheralCurve c; Orientation h; int num_strands, intersection_number[2], the_gcd; Cusp *unique_cusp; MatrixInt22 basis_change[1]; /* * Relative to the orientation of tet, the vertices v0, v1 and v2 * are arranged in counterclockwise order around the face f. */ v0 = one_vertex_at_edge[e]; v1 = other_vertex_at_edge[e]; v2 = remaining_face[v1][v0]; f = remaining_face[v0][v1]; /* * Note the matching face and its vertices. */ nbr_tet = tet->neighbor[f]; gluing = tet->gluing[f]; ff = EVALUATE(gluing, f); vv0 = EVALUATE(gluing, v0); vv1 = EVALUATE(gluing, v1); vv2 = EVALUATE(gluing, v2); /* * Note the incident EdgeClasses (which may or may not be distinct). */ edge0 = tet->edge_class[e]; edge1 = tet->edge_class[edge_between_vertices[v1][v2]]; edge2 = tet->edge_class[edge_between_vertices[v2][v0]]; /* * Construct the triangular-pillow-with-tunnel, as described * at the top of this file. */ new_tet0 = NEW_STRUCT(Tetrahedron); new_tet1 = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet0); initialize_tetrahedron(new_tet1); INSERT_BEFORE(new_tet0, &manifold->tet_list_end); INSERT_BEFORE(new_tet1, &manifold->tet_list_end); manifold->num_tetrahedra += 2; new_edge = NEW_STRUCT(EdgeClass); initialize_edge_class(new_edge); INSERT_BEFORE(new_edge, &manifold->edge_list_end); new_tet0->neighbor[0] = new_tet1; new_tet0->neighbor[1] = NULL; /* assigned below */ new_tet0->neighbor[2] = NULL; /* assigned below */ new_tet0->neighbor[3] = new_tet1; new_tet1->neighbor[0] = new_tet0; new_tet1->neighbor[1] = new_tet1; new_tet1->neighbor[2] = new_tet1; new_tet1->neighbor[3] = new_tet0; new_tet0->gluing[0] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet0->gluing[1] = 0x00; /* assigned below */ new_tet0->gluing[2] = 0x00; /* assigned below */ new_tet0->gluing[3] = CREATE_PERMUTATION(0, 1, 1, 0, 2, 2, 3, 3); new_tet1->gluing[0] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[1] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[2] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[3] = CREATE_PERMUTATION(0, 1, 1, 0, 2, 2, 3, 3); new_tet0->edge_class[0] = edge1; new_tet0->edge_class[1] = edge1; new_tet0->edge_class[2] = edge0; new_tet0->edge_class[3] = edge2; new_tet0->edge_class[4] = edge0; new_tet0->edge_class[5] = edge0; new_tet1->edge_class[0] = edge1; new_tet1->edge_class[1] = edge1; new_tet1->edge_class[2] = edge0; new_tet1->edge_class[3] = new_edge; new_tet1->edge_class[4] = edge0; new_tet1->edge_class[5] = edge0; edge0->order += 6; edge1->order += 4; edge2->order += 1; new_edge->order = 1; new_edge->incident_tet = new_tet1; new_edge->incident_edge_index = 3; edge_orientation0 = tet->edge_orientation[e]; edge_orientation1 = tet->edge_orientation[edge_between_vertices[v1][v2]]; edge_orientation2 = tet->edge_orientation[edge_between_vertices[v2][v0]]; new_tet0->edge_orientation[0] = edge_orientation1; new_tet0->edge_orientation[1] = edge_orientation1; new_tet0->edge_orientation[2] = edge_orientation0; new_tet0->edge_orientation[3] = edge_orientation2; new_tet0->edge_orientation[4] = edge_orientation0; new_tet0->edge_orientation[5] = edge_orientation0; new_tet1->edge_orientation[0] = edge_orientation1; new_tet1->edge_orientation[1] = edge_orientation1; new_tet1->edge_orientation[2] = edge_orientation0; new_tet1->edge_orientation[3] = right_handed; new_tet1->edge_orientation[4] = edge_orientation0; new_tet1->edge_orientation[5] = edge_orientation0; new_tet0->cusp[0] = tet->cusp[v0]; new_tet0->cusp[1] = tet->cusp[v0]; new_tet0->cusp[2] = tet->cusp[v0]; new_tet0->cusp[3] = tet->cusp[v2]; new_tet1->cusp[0] = tet->cusp[v0]; new_tet1->cusp[1] = tet->cusp[v0]; new_tet1->cusp[2] = tet->cusp[v0]; new_tet1->cusp[3] = tet->cusp[v2]; /* * Install the triangular-pillow-with-tunnel. */ tet->neighbor[f] = new_tet0; tet->gluing[f] = CREATE_PERMUTATION(f, 2, v0, 0, v1, 1, v2, 3); new_tet0->neighbor[2] = tet; new_tet0->gluing[2] = inverse_permutation[tet->gluing[f]]; nbr_tet->neighbor[ff] = new_tet0; nbr_tet->gluing[ff] = CREATE_PERMUTATION(ff, 1, vv0, 0, vv1, 2, vv2, 3); new_tet0->neighbor[1] = nbr_tet; new_tet0->gluing[1] = inverse_permutation[nbr_tet->gluing[ff]]; /* * Typically creating_new_cusp is FALSE, meaning that we are * connecting a spherical boundary component to a torus or * Klein bottle boundary component, and we simply extend the * existing peripheral curves across the new tetrahedra. * * In the exceptional case that creating_new_cusp is TRUE, * meaning that the manifold has no real cusps and we are * connecting the "special fake cusp" to itself, we must * install a meridian and longitude, and set up the Dehn filling. */ if (creating_new_cusp == FALSE) { /* * Extend the peripheral curves across the boundary of the * triangular-pillow-with-tunnel. * * Note: The orientations of new_tet0 and new_tet1 match that * of tet, so the right_handed and left_handed sheets match up * in the obvious way. */ for (c = 0; c < 2; c++) /* c = M, L */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { num_strands = tet->curve[c][h][v0][f]; new_tet0->curve[c][h][0][2] = -num_strands; new_tet0->curve[c][h][0][1] = +num_strands; num_strands = tet->curve[c][h][v1][f]; new_tet0->curve[c][h][1][2] = -num_strands; new_tet0->curve[c][h][1][0] = +num_strands; new_tet1->curve[c][h][2][0] = -num_strands; new_tet1->curve[c][h][2][1] = +num_strands; new_tet1->curve[c][h][1][2] = -num_strands; new_tet1->curve[c][h][1][0] = +num_strands; new_tet0->curve[c][h][2][0] = -num_strands; new_tet0->curve[c][h][2][1] = +num_strands; num_strands = tet->curve[c][h][v2][f]; new_tet0->curve[c][h][3][2] = -num_strands; new_tet0->curve[c][h][3][1] = +num_strands; } } else /* creating_new_cusp == TRUE */ { /* * We have just installed a tube connecting the (unique) * spherical "cusp" to itself, to convert it to a torus or * Klein bottle. */ unique_cusp = tet->cusp[v0]->matching_cusp; unique_cusp->is_complete = TRUE; /* to be filled below */ unique_cusp->index = 0; unique_cusp->is_finite = FALSE; manifold->num_cusps = 1; /* * Install an arbitrary meridian and longitude. */ peripheral_curves(manifold); count_cusps(manifold); /* * Two sides of the (truncated) vertex 0 of new_tet0 * (namely the sides incident to faces 1 and 2 of new_tet0) * define the Dehn filling curve by which we can recover * the closed manifold. Count how many times the newly * installed meridian and longitude cross this Dehn filling curve. * To avoid messy questions about which sheet of the cusp's * double cover we're on, use two (parallel) copies of the * Dehn filling curve, one on each sheet of the cover. * Ultimately we're looking for a linear combination of the * meridian and longitude whose intersection number with * the Dehn filling curve is zero, so it won't matter if * we're off by a factor of two. */ for (c = 0; c < 2; c++) /* c = M, L */ { intersection_number[c] = 0; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { intersection_number[c] += new_tet0->curve[c][h][0][1]; intersection_number[c] += new_tet0->curve[c][h][0][2]; } } /* * Use the intersection numbers to deduce * the desired Dehn filling coefficients. */ the_gcd = gcd(intersection_number[M], intersection_number[L]); unique_cusp->is_complete = FALSE; unique_cusp->m = -intersection_number[L] / the_gcd; unique_cusp->l = +intersection_number[M] / the_gcd; /* * Switch to a basis in which the Dehn filling curve is a meridian. */ unique_cusp->cusp_shape[initial] = Zero; /* force current_curve_basis() to ignore the cusp shape */ current_curve_basis(manifold, 0, basis_change[0]); if (change_peripheral_curves(manifold, basis_change) != func_OK) uFatalError("drill_tube", "finite_vertices"); } } static void set_real_cusps( Triangulation *manifold, Cusp *special_fake_cusp) { Tetrahedron *tet; int i; Cusp *cusp, *dead_cusp; /* * Update the cusp fields. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) tet->cusp[i] = tet->cusp[i]->matching_cusp; /* * Free the Cusp structures which had been used for finite vertices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == TRUE && cusp != special_fake_cusp) { dead_cusp = cusp; cusp = cusp->prev; /* so the loop will proceed correctly */ REMOVE_NODE(dead_cusp); my_free(dead_cusp); } } snappea-3.0d3/SnapPeaKernel/code/fundamental_group.c0100444000175000017500000035156007071663223020563 0ustar babbab/* * fundamental_group.c * * This file exports the following functions to the UI: * * GroupPresentation *fundamental_group( * Triangulation *manifold, * Boolean simplify_presentation, * Boolean fillings_may_affect_generators, * Boolean minimize_number_of_generators); * * int fg_get_num_generators (GroupPresentation *group); * Boolean fg_integer_fillings (GroupPresentation *group); * int fg_get_num_relations (GroupPresentation *group); * int *fg_get_relation (GroupPresentation *group, * int which_relation); * int fg_get_num_cusps (GroupPresentation *group); * int *fg_get_meridian (GroupPresentation *group, * int which_cusp); * int *fg_get_longitude (GroupPresentation *group, * int which_cusp); * void fg_free_relation (int *relation); * * void free_group_presentation(GroupPresentation *group); * * The UI will call fundamental_group() to compute a GroupPresentation, * then make calls as needed to the fg_get_.../fg_free... functions, * and finally call free_group_presentation() to release the memory. * The actual structure of a GroupPresentation is private to this file; * SnapPea.h contains an "opaque typedef" which lets the UI pass pointers * to GroupPresentations without knowing their internal structure. * * fundamental_group() computes the fundamental group of the manifold, * and returns a pointer to it. It takes into account Dehn fillings * with relatively prime integer coefficients, but ignores all * other Dehn fillings. The Boolean arguments correspond * to the fields in the GroupPresentation typedef below. * * fg_get_num_generators() returns the number of generators in the * GroupPresentation. * * fg_integer_fillings() says whether the space is a manifold or orbifold, * as opposed to some other generalized Dehn filling. * * fg_get_num_relations() returns the number of relations in the * GroupPresentation. * * fg_get_relation() returns the specified relation. Its allocate * the memory for it, so you should pass the pointer back to * fg_free_relation() when you're done with it. * Each relation is a string of integers. The integer 1 means the * first generator, 2 means the second, etc., while -1 is the inverse * of the first generator, -2 is the inverse of the second, etc. * The integer 0 indicates the end of the string. * * fg_get_num_cusps() returns the number of cusps of the underlying * manifold. This *includes* the filled cusps. So, for example, * if you do (5,1) Dehn filling on the figure eight knot complement, * you can see the words in the fundamental group corresponding to * the (former!) cusp's meridian and longitude. * * fg_get_meridian() and fg_get_longitude() return the word corresponding * to a meridian or longitude, in the same format used by * fg_get_relation() above. They allocate the memory for the string * of integers, so you should pass the pointer back to * fg_free_relation() when you're done with it. Meridians and * longitudes are available whether the cusps are filled or not as * explained for fg_get_num_cusps() above. * * fg_free_relation() frees relations allocated by fg_get_relation(). * * free_group_presentation() frees the memory occupied by a GroupPresentation. */ /* * 96/9/13 There are two sets of generators kicking around: * * (1) the "geometric generators" defined in choose_generators.c, and * * (2) the "simplified generators" used by fundamental_group() * in its final simplified presentation. * * The algorithm in representations.c needs to express the former as words * in the latter, so we now keep track of that information. As with * the expressions for meridians and longitudes, we use words with well * defined basepoints, and don't do any cyclic cancellations. * * 96/9/29 fundamental_group() nows records the basepoint of each Cusp * in the Cusp's basepoint_tet, basepoint_vertex and basepoint_orientation * fields. The basepoint is the point where the meridian and longitude * meet; the meridional and longitudinal words are computed relative to * this point to guarantee that they compute. */ /* * Visualizing the fundamental group. * * Sometimes we'll think of a presentation of a manifold's * fundamental group not just as an abstract presentation, but as a * sort of Heegaard diagram. To be precise, we'll think of it as a * handlebody with a collection of disjoint simple closed curves showing * where (thickened) disks are to be attached. In a true Heegaard * diagram the boundary of the handlebody-with-disks-attached is * a single 3-sphere, but in our case the boundary will consist of * a 2-torus or Klein bottle for each unfilled cusp, and a 2-sphere * for each filled cusp. The number of relations may be less than, * equal to, or greater than the genus of the handlebody. * * Constructing the pseudo-Heegaard diagram. * * The manifold's Triangulation provides a pseudo-Heegaard diagram, * which we use to obtain the initial, unsimplified presentation * of the fundamental group. * * The handlebody is the thickened 1-skeleton of the ideal * triangulation's dual complex. (Please see choose_generators.c for an * explanation of how a set of generators is chosen for the handlebody, * and how the generators are represented internally.) There are two * types of relations. Each thickened edge (in the original ideal * triangulation, not the dual) is a relation (note that a thickened edge * is topologically the same as a thickened disk), and each Dehn filling * curve specifies a relation. * * Visualizing the pseudo-Heegaard diagram. * * Visualize the pseudo-Heegaard diagram as follows. Start with the * pseudo-Heegaard diagram drawn as an actual handlebody, then make * a meridional cut through each handle, so that the handlebody opens * into a 3-ball. Label the cut-disks A+ and A-, B+ and B-, etc., * so that identifying A+ to A-, B+ to B-, etc. restores the original * handlebody. The relation aBc would then be a curve which goes in A+ * and comes out A-, goes in B- and comes out B+, then goes in C+ and * comes out C- to its starting point. * * Operations which do or do not respect the Heegaard diagram. * * My original hope in developing this code was to provide an option * whereby the algorithm uses only simplifications which respect the * Heegaard diagram. This would have let us conclude, for example, * that a manifold with a genus zero presentation is a topological * 3-sphere, a manifold with a genus one presentation is a lens space, * etc. Some simplifications (e.g. handle slides) can rigorously * be interpreted as operations on the pseudo-Heegaard diagram. * Others, unfortunately, are slipperier. For example, cancellation * of inverses (e.g. "abBcAd" -> "acAd") is almost always a valid * operation on the Heegaard diagram -- just isotope the little loop * "bB" across the B- disk -- but one has to worry about whether the * loop encloses other disks. I had worked out an algorithm to * removed any such other disks (by isotoping them across the B- * disk) but then the proof that the algorithm terminates was no * longer so clear. At that point I finally decided to abandon the * Heegaard interpretation of the simplifications. I was feeling too * uncomfortable working with a data structure (the GroupPresentation) * which contained only a subset of the information needed for the * calculations and for the proofs that the calculations are correct. * Solid, reliable code requires a data structure which models the * underlying mathematics as directly as possible. * * If you want a topological description of the space, you can compute * an unsimplified presentation (which *does* correspond to a pseudo- * Heegaard diagram, as explained above) and pass it to John Berge's * program "Heegaard". "Heegaard" does an excellent job of recognizing * lens space, for example. If you give it a sufficiently complicated * presentation, it can even distinguish, say, L(5,1) from L(5,2)! * (Note: The unsimplified presentation uses the standard generators * defined in choose_generators.c. The code in representations.c * relies on this fact.) * * * Conventions. * * (1) The generators of an abstract group presentation are represented * by lowercase letters. Their inverses are respresented by the * corresponding uppercase letters. For example, "A" is the inverse * of "a". * * (2) Words in an abstract group presentation are read left to right. * For example, "abC" means do "a", "b" and "c inverse", in that order. * However, O(3,1) matrices act on column vectors (matrix times column * vector equals column vector), so products of such matrices are read * right to left. For example, (M2)(M1) means do matrix M1, then do * matrix M2. */ #include "kernel.h" #include typedef struct Letter { /* * itsValue contains the index of a generator. * The generators are implicitly numbered 1, 2 ..., n, and their * inverses are -1, -2, ..., -n, where n is the number of generators. */ int itsValue; /* * Letters are kept on circular doubly-linked lists. */ struct Letter *prev, *next; } Letter; typedef struct CyclicWord { /* * itsLength gives the number of Letters in the CyclicWord. */ int itsLength; /* * itsLetters points to an arbitrary Letter in the CyclicWord. * The Letters are kept on a circular doubly-linked list. * * If a Cyclic Word is empty, itsLength is set to 0 and * itsLetters is set to NULL. */ Letter *itsLetters; /* * is_Dehn_relation says whether this relation comes from a Dehn * filling. When group->fillings_may_affect_generators is FALSE, * such relations may not influence the choice of generators. */ Boolean is_Dehn_relation; /* * The "next" field points to the next CyclicWord * in the GroupPresentation. */ struct CyclicWord *next; } CyclicWord; struct GroupPresentation { /* * How many generators does the GroupPresentation have? * (Geometrically, what is the genus of the handlebody in * the pseudo-Heegaard diagram?) */ int itsNumGenerators; /* * We maintain an array of matrices, one for each generator, * which defines the representation of the fundamental group * into Isom(H^3). The matrices could be given in either O(3,1) * or PSL(2,C); I chose the former because it handles orientation- * reversing isometries more naturally. */ O31Matrix *itsMatrices; /* * How many relations does the presentation have? * (Geometrically, how many thickened disks are glued to the * boundary of the handlebody? See "Constructing the pseudo-Heegaard * diagram" above for more details.) */ int itsNumRelations; /* * itsRelations points to a NULL-terminated, singly-linked list * of relations. Typically the relations are interpreted as curves * on a handlebody, as explained above. */ CyclicWord *itsRelations; /* * Is this space a manifold or orbifold (e.g. FigureEight(5,1) * or FigureEight(6,3)) as opposed to some other generalized * Dehn filling (e.g. FigureEight(5.01, 1.0))? We compute * Dehn relations only for cusps with integer coefficients, * and ignore other (generalized) Dehn fillings. The UI should * display the relations only for integer fillings, but should * display the matrix representation for all generalized * Dehn fillings. */ Boolean integer_fillings; /* * We keep track of the words corresponding to the meridians and * longitudes. Note that * * (1) we keep track of the meridian and longitude even * on filled cusps, * * (2) the two longitudes on a Klein bottle are not homotopic to one * another, so we report the lift to the orientation double cover, * * (3) the words have well defined basepoints -- cyclic cancellations * are not allowed. */ int itsNumCusps; CyclicWord *itsMeridians, *itsLongitudes; /* * We keep track of words which express each of the original generators * (the ones defined in choose_generators.c) as products of the * current generators. The words have well defined basepoints -- * cyclic cancellations are not allowed. */ int itsNumOriginalGenerators; CyclicWord *itsOriginalGenerators; /* * Should we simplify the presentation? * * For most purposes simplify_presentation should be TRUE, but * occasionally the user may want access to the unsimplified * presentation. For example, you can pass an unsimplified * presentation of a lens space to John Berge's program Heegaard, * and it will most likley be able to recognize the exact lens space. * Yes, it distinguishes L(5,1) from L(5,2), but only if it * begins with a sufficiently complicated presentation. * Passing it the presentation < a | a^5 = 1 > isn't good enough! * * 96/9/11 The code in representations.c relies on the fact that * the unsimplified presentation uses the standard generators * defined in choose_generators.c. */ Boolean simplify_presentation; /* * Is it OK for the choice of generators to depend on the Dehn fillings? * Sometimes the user may want this flag to be FALSE, for example * when he or she is studying how the matrix generators vary across * a Dehn filling plane, and wants a consistent choice of generators. * Other times the user may want this flag to be TRUE, for example * when he or she wants to see which Dehn fillings give lens spaces. */ Boolean fillings_may_affect_generators; /* * If minimize_number_of_generators is TRUE, simplify_presentation() * will try to reduce the number of generators at the expense of * increasing the total length of the relations. If it's FALSE, * it does the opposite. */ Boolean minimize_number_of_generators; }; static GroupPresentation *compute_unsimplified_presentation(Triangulation *manifold); static void compute_matrix_generators(Triangulation *manifold, GroupPresentation *group); static void compute_relations(Triangulation *manifold, GroupPresentation *group); static void compute_edge_relations(Triangulation *manifold, GroupPresentation *group); static void compute_one_edge_relation(EdgeClass *edge, GroupPresentation *group); static void compute_Dehn_relations(Triangulation *manifold, GroupPresentation *group); static void compute_peripheral_word(Triangulation *manifold, Cusp *cusp, PeripheralCurve which_curve, CyclicWord **word_list); static void find_standard_basepoint(Triangulation *manifold, Cusp *cusp); static void find_curve_start(Cusp *cusp, PeripheralCurve which_curve, PositionedTet *ptet); static void compute_Dehn_word(CyclicWord *meridian, CyclicWord *longitude, int m, int l, CyclicWord **word_list); static void append_copies(CyclicWord *source, int n, CyclicWord *dest); static void append_word(CyclicWord *source, CyclicWord *dest); static void append_inverse(CyclicWord *source, CyclicWord *dest); static void initialize_original_generators(GroupPresentation *group, int num_generators); static void simplify(GroupPresentation *group); static void insert_basepoints(GroupPresentation *group); static void insert_basepoints_on_list(CyclicWord *list); static void insert_basepoint_in_word(CyclicWord *word); static void remove_basepoints(GroupPresentation *group); static void remove_basepoints_on_list(CyclicWord *list); static void remove_basepoint_in_word(CyclicWord *word); static Boolean word_length_one(GroupPresentation *group); static Boolean word_length_two(GroupPresentation *group); static Boolean try_handle_slides(GroupPresentation *group); static Boolean substring_occurs_in_group(GroupPresentation *group, int a, int b); static Boolean substring_occurs_in_word(CyclicWord *word, int a, int b); static Boolean handle_slide_improves_presentation(GroupPresentation *group, int a, int b); static void evaluate_handle_slide_in_group(GroupPresentation *group, int a, int b, int *shortest_nonempty_relation_before, int *shortest_nonempty_relation_after, int *change_in_total_length, int *change_in_num_runs); static void evaluate_handle_slide_on_word(CyclicWord *word, int a, int b, int *shortest_nonempty_relation_before, int *shortest_nonempty_relation_after, int *change_in_total_length, int *change_in_num_runs); static int compute_delta_length(CyclicWord *word, int a, int b); static int compute_delta_runs(CyclicWord *word, int a, int b); static Boolean two_singletons_in_group(GroupPresentation *group); static Boolean generator_occurs_as_two_singletons_in_group(GroupPresentation *group, int value, CyclicWord **word_containing_singletons); static Boolean generator_occurs_as_two_singletons_in_word(CyclicWord *word, int value); static Boolean generator_occurs_in_no_other_word_in_group(GroupPresentation *group, int value, CyclicWord *word_containing_singletons); static Boolean generator_occurs_in_word(CyclicWord *word, int value); static void make_singletons_adjacent(GroupPresentation *group, int value, CyclicWord *word); static Boolean eliminate_word_in_group(GroupPresentation *group); static CyclicWord *shortest_word_in_which_generator_occurs_precisely_once(GroupPresentation *group, int generator); static Boolean generator_occurs_precisely_once_in_word(CyclicWord *word, int generator); static int occurrences_in_group(GroupPresentation *group, int generator); static int occurrences_in_word(CyclicWord *word, int generator); static void eliminate_word(GroupPresentation *group, CyclicWord *word, int generator); static Boolean remove_empty_relations(GroupPresentation *group); static Boolean insert_word_from_group(GroupPresentation *group); static Boolean insert_word_into_group(GroupPresentation *group, CyclicWord *word); static Boolean insert_word_into_list(CyclicWord *list, CyclicWord *word); static Boolean insert_word_into_word(CyclicWord *word, CyclicWord *target); static Boolean insert_word_forwards(CyclicWord *word, CyclicWord *target); static Boolean insert_word_backwards(CyclicWord *word, CyclicWord *target); static Boolean simplify_one_word_presentations(GroupPresentation *group); static Boolean word_contains_pattern(CyclicWord *word, Letter *unmatched_letter, int period, int repetitions); static CyclicWord *introduce_generator(GroupPresentation *group, Letter *substring, int length); static void lens_space_recognition(GroupPresentation *group); static int count_runs(CyclicWord *word); static Boolean lens_space_recognition_using_generator(GroupPresentation *group, int generator0); static Boolean invert_generators_where_necessary(GroupPresentation *group); static void count_signed_occurrences_in_group(GroupPresentation *group, int a, int *positive_occurrences, int *negative_occurrences); static void increment_signed_occurrences_in_group(GroupPresentation *group, int a, int *positive_occurrences, int *negative_occurrences); static void increment_signed_occurrences_in_word(CyclicWord *word, int a, int *positive_occurrences, int *negative_occurrences); static int count_signed_occurrences_in_word(CyclicWord *word, int a); static void invert_generator_in_group(GroupPresentation *group, int a); static void invert_generator_on_list(CyclicWord *list, int a); static void invert_generator_in_word(CyclicWord *word, int a); static Boolean invert_words_where_necessary(GroupPresentation *group); static Boolean invert_word_if_necessary(CyclicWord *word); static int sum_of_powers(CyclicWord *word); static void invert_word(CyclicWord *word); static void choose_word_starts(GroupPresentation *group); static void choose_word_start(CyclicWord *word); static void conjugate_peripheral_words(GroupPresentation *group); static Boolean conjugate_peripheral_pair(CyclicWord *word0, CyclicWord *word1); static void conjugate_word(CyclicWord *word, int value); static void cancel_inverses(GroupPresentation *group); static void cancel_inverses_word_list(CyclicWord *list); static void cancel_inverses_word(CyclicWord *word); static void handle_slide(GroupPresentation *group, int a, int b); static void handle_slide_word_list(CyclicWord *list, int a, int b); static void handle_slide_word(CyclicWord *word, int a, int b); static void handle_slide_matrices(GroupPresentation *group, int a, int b); static void cancel_handles(GroupPresentation *group, CyclicWord *word); static void remove_word(GroupPresentation *group, CyclicWord *word); static void remove_generator(GroupPresentation *group, int dead_generator); static void remove_generator_from_list(CyclicWord *list, int dead_generator); static void remove_generator_from_word(CyclicWord *word, int dead_generator); static void renumber_generator(GroupPresentation *group, int old_index, int new_index); static void renumber_generator_on_word_list(CyclicWord *list, int old_index, int new_index); static void renumber_generator_in_word(CyclicWord *word, int old_index, int new_index); static int *fg_get_cyclic_word(CyclicWord *list, int which_relation); static void free_word_list(CyclicWord *aWordList); static void free_cyclic_word(CyclicWord *aCyclicWord); GroupPresentation *fundamental_group( Triangulation *manifold, Boolean simplify_presentation, Boolean fillings_may_affect_generators, Boolean minimize_number_of_generators) { GroupPresentation *group; /* * Read a group presentation from the manifold, without worrying * about simplifying it. This group presentation will be that * of a pseudo-Heegaard diagram, as discussed above. */ group = compute_unsimplified_presentation(manifold); /* * Note the user's preferences. * * (Please see the GroupPresentation typedef above for * an explanation of these flags.) */ group->simplify_presentation = simplify_presentation; group->fillings_may_affect_generators = fillings_may_affect_generators; group->minimize_number_of_generators = minimize_number_of_generators; /* * Simplify the group presentation if requested to do so. */ if (group->simplify_presentation == TRUE) simplify(group); return group; } static GroupPresentation *compute_unsimplified_presentation( Triangulation *manifold) { GroupPresentation *group; group = NEW_STRUCT(GroupPresentation); choose_generators(manifold, FALSE, FALSE); group->itsNumGenerators = manifold->num_generators; compute_matrix_generators(manifold, group); compute_relations(manifold, group); initialize_original_generators(group, group->itsNumGenerators); group->integer_fillings = all_Dehn_coefficients_are_integers(manifold); return group; } static void compute_matrix_generators( Triangulation *manifold, GroupPresentation *group) { /* * Pass centroid_at_origin = FALSE to matrix_generators() * so the initial Tetrahedron will be positioned with vertices * at {0, 1, infinity, z}. This brings out nice number theoretic * properties in the matrix generators, and also forces Triangulations * with all flat tetrahedra to lie in a coordinate plane. */ group->itsMatrices = NEW_ARRAY(manifold->num_generators, O31Matrix); if (get_filled_solution_type(manifold) != not_attempted && get_filled_solution_type(manifold) != no_solution) { MoebiusTransformation *moebius_generators; moebius_generators = NEW_ARRAY(manifold->num_generators, MoebiusTransformation); matrix_generators(manifold, moebius_generators, FALSE); Moebius_array_to_O31_array( moebius_generators, group->itsMatrices, manifold->num_generators); my_free(moebius_generators); } else { int i; for (i = 0; i < manifold->num_generators; i++) o31_copy(group->itsMatrices[i], O31_identity); } } static void compute_relations( Triangulation *manifold, GroupPresentation *group) { group->itsNumRelations = 0; group->itsRelations = NULL; /* * Compute the Dehn relations first, so they appear * on the linked list *after* the edge relations. */ compute_Dehn_relations(manifold, group); compute_edge_relations(manifold, group); } static void compute_edge_relations( Triangulation *manifold, GroupPresentation *group) { EdgeClass *edge; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) compute_one_edge_relation(edge, group); } static void compute_one_edge_relation( EdgeClass *edge, GroupPresentation *group) { CyclicWord *new_word; PositionedTet ptet0, ptet; Letter dummy_letter, *new_letter; int index; /* * Ignore EdgeClasses which choose_generators() has already * eliminated via handle cancellations or handle merging. * (choose_generators() doesn't eliminate them from the Triangulation; * it just eliminates them from its picture of the pseudo-Heegaard * diagram.) */ if (edge->active_relation == FALSE) return; /* * choose_generator()'s algorithm ensures that each active relation * has at least two letters. (They may cancel, but there are * nominally at least two of them.) */ if (edge->num_incident_generators < 2) uFatalError("compute_one_edge_relation", "fundamental_group"); /* * Initialize the new_word, and install it on the linked list. */ new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = 0; new_word->is_Dehn_relation = FALSE; new_word->next = group->itsRelations; group->itsRelations = new_word; group->itsNumRelations++; /* * We'll use a temporary dummy_letter to initialize * the circular doubly linked list. */ dummy_letter.next = &dummy_letter; dummy_letter.prev = &dummy_letter; /* * Traverse the EdgeClass, recording each generator we find. */ set_left_edge(edge, &ptet0); ptet = ptet0; do { /* * Are we passing a generator? * If so, convert from the generator_index's 0-based numbering * to the GroupPresentation's 1-based numbering (with negative * numbers for inverses). inbound_generators are considered * positively oriented (for later consistency with the conventions * for storing peripheral curves). */ switch (ptet.tet->generator_status[ptet.near_face]) { case inbound_generator: index = ptet.tet->generator_index[ptet.near_face] + 1; break; case outbound_generator: index = -(ptet.tet->generator_index[ptet.near_face] + 1); break; case not_a_generator: index = 0; break; default: uFatalError("compute_one_edge_relation", "fundamental_group"); } if (index != 0) { new_letter = NEW_STRUCT(Letter); new_letter->itsValue = index; INSERT_BEFORE(new_letter, &dummy_letter); new_word->itsLength++; } veer_left(&ptet); } while (same_positioned_tet(&ptet, &ptet0) == FALSE); /* * Did we find the right number of generators? */ if (new_word->itsLength != edge->num_incident_generators) uFatalError("compute_one_edge_relation", "fundamental_group"); /* * Give new_word a valid pointer to the circular doubly linked list * of Letters, and then remove the temporary dummy_letter. */ new_word->itsLetters = dummy_letter.next; REMOVE_NODE(&dummy_letter); } static void compute_Dehn_relations( Triangulation *manifold, GroupPresentation *group) { Cusp *cusp; int i; group->itsNumCusps = manifold->num_cusps; group->itsMeridians = NULL; group->itsLongitudes = NULL; /* * Examine the cusps in reverse order, so the Dehn filling relations * get pushed onto the relation list in the correct order. */ for (i = manifold->num_cusps; --i >= 0; ) { cusp = find_cusp(manifold, i); /* * First compute the meridian and longitude... */ find_standard_basepoint(manifold, cusp); compute_peripheral_word(manifold, cusp, M, &group->itsMeridians); compute_peripheral_word(manifold, cusp, L, &group->itsLongitudes); /* * ...and then, if the Dehn coefficients are integers, * compute the Dehn relation by concatenating copies * of the meridian and longitude. */ if (cusp->is_complete == FALSE && Dehn_coefficients_are_integers(cusp) == TRUE) { compute_Dehn_word( group->itsMeridians, group->itsLongitudes, (int) cusp->m, (int) cusp->l, &group->itsRelations); group->itsNumRelations++; } } } static void compute_peripheral_word( Triangulation *manifold, Cusp *cusp, PeripheralCurve which_curve, CyclicWord **word_list) { /* * Note that the triangulation.h data structure works with the * orientation double cover of each cusp, so both torus and Klein * bottle cusps appear as tori, and can be handled the same. * The only difference is that the "longitude" of a Klein bottle * cusp is actually the double cover of a longitude. Please * see peripheral_curves.c for a careful and complete discussion * of these issues. */ PositionedTet ptet0, ptet; int strand0, strand, near_strands, left_strands; CyclicWord *new_word; Letter dummy_letter, *new_letter; int index; /* * Initialize the new_word, and install it on the linked list. * * Use a temporary dummy_letter to initialize * the circular doubly linked list. */ new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = 0; new_word->itsLetters = &dummy_letter; dummy_letter.next = &dummy_letter; dummy_letter.prev = &dummy_letter; new_word->is_Dehn_relation = TRUE; new_word->next = *word_list; *word_list = new_word; /* * Start where the meridian and longitude intersect. * This insures that * * (1) the meridian and longitude commute, and * * (2) we can form linear combinations of meridians and * longitudes by concatenation. */ find_curve_start(cusp, which_curve, &ptet0); /* * Here's how we keep track of where we are. At each step, we are * always at the near edge of the top vertex (i.e. the truncated vertex * opposite the bottom face) of the PositionedTet ptet (please see * positioned_tet.h if necessary). The curve may cross that edge * several times. The variable "strand" keeps track of which * intersection we are at; 0 means we're at the strand on the * far left, 1 means we're at the next strand, etc. */ /* * Start at the leftmost strand. */ strand0 = 0; ptet = ptet0; strand = strand0; do { /* * Record the generator (if any) corresponding to the near_face. */ switch (ptet.tet->generator_status[ptet.near_face]) { case inbound_generator: index = ptet.tet->generator_index[ptet.near_face] + 1; break; case outbound_generator: index = -(ptet.tet->generator_index[ptet.near_face] + 1); break; case not_a_generator: index = 0; break; default: uFatalError("compute_peripheral_word", "fundamental_group"); } if (index != 0) { new_letter = NEW_STRUCT(Letter); new_letter->itsValue = index; INSERT_BEFORE(new_letter, &dummy_letter); new_word->itsLength++; } /* * Decide whether to veer_left() or veer_right(). */ /* * Note the curve's intersection numbers * with the near side and the left side. */ near_strands = ptet.tet->curve [which_curve] [ptet.orientation] [ptet.bottom_face] [ptet.near_face]; left_strands = ptet.tet->curve [which_curve] [ptet.orientation] [ptet.bottom_face] [ptet.left_face]; /* * Does the current strand bend to the left or to the right? */ if (strand < FLOW(near_strands, left_strands)) { /* * The current strand bends to the left. */ /* * Some of the near strands may branch off towards the * right, or some strands may come in from the right and * join the near strands as they head out to the left. * But either way, the variable "strand" remains unchanged. */ /* * Move the PositionedTet onward, following the curve. */ veer_left(&ptet); } else { /* * The current strand bends to the right. */ /* * Some strands from the near edge may have gone off * to the left edge, in which case the variable "strand" * should be decreased by that amount. * Alternatively, some strands may have come in from the * left edge, joining us at the right edge, in which case * the variable "strand" should be increased by that amount. * The code "strand += left_strands" works for both cases, * because left_strands will be negative in the former case * and positive in the latter. */ strand += left_strands; /* * Move the PositionedTet onward, following the curve. */ veer_right(&ptet); } } while ( same_positioned_tet(&ptet, &ptet0) == FALSE || strand != strand0); /* * Give new_word a valid pointer to the circular doubly linked list * of Letters, and then remove the temporary dummy_letter. * * Note that for meridians and longitudes, new_word->itsLetters * is set to the beginning of the based word, so the basepoints * for the meridian and longitude are the same, and the words will * commute. */ new_word->itsLetters = dummy_letter.next; REMOVE_NODE(&dummy_letter); } static void find_standard_basepoint( Triangulation *manifold, Cusp *cusp) { /* * Find an ideal vertex where both the meridian and longitude * both pass through, and let an arbitrary point in its interior * be the basepoint for the cusp. */ FaceIndex face; for (cusp->basepoint_tet = manifold->tet_list_begin.next; cusp->basepoint_tet != &manifold->tet_list_end; cusp->basepoint_tet = cusp->basepoint_tet->next) for (cusp->basepoint_vertex = 0; cusp->basepoint_vertex < 4; cusp->basepoint_vertex++) { if (cusp->basepoint_tet->cusp[cusp->basepoint_vertex] != cusp) continue; for (face = 0; face < 4; face++) { if (face == cusp->basepoint_vertex) continue; for (cusp->basepoint_orientation = 0; cusp->basepoint_orientation < 2; cusp->basepoint_orientation++) if (cusp->basepoint_tet->curve [M] [cusp->basepoint_orientation] [cusp->basepoint_vertex] [face] != 0 && cusp->basepoint_tet->curve [L] [cusp->basepoint_orientation] [cusp->basepoint_vertex] [face] != 0) /* * We found the basepoint! */ return; } } /* * If we get to this point, it means that no intersection * was found, which is impossible. */ uFatalError("find_standard_basepoint", "fundamental_group"); } static void find_curve_start( Cusp *cusp, PeripheralCurve which_curve, PositionedTet *ptet) { /* * We assume the standard basepoint has already been found by a * previous call to find_standard_basepoint(), and use it to * find the PositionedTet where the requested curve leaves * the standard basepoint. (So ptet->tet typically will NOT be * cusp->basepoint_tet.) */ /* * Temporarily set ptet to be the standard basepoint. * We'll change it in a moment. */ ptet->tet = cusp->basepoint_tet; ptet->bottom_face = cusp->basepoint_vertex; ptet->orientation = cusp->basepoint_orientation; /* * Let near_face be where the requested curve leaves the triangle. */ for (ptet->near_face = 0; ptet->near_face < 4; ptet->near_face++) { if (ptet->near_face == ptet->bottom_face) continue; if (0 > ptet->tet->curve[which_curve][ptet->orientation][ptet->bottom_face][ptet->near_face]) { /* * We've found where the curve leaves the triangle. * Our starting ptet will be just on the other side. * * First fill in the remaining details for this ptet... */ if (ptet->orientation == right_handed) { ptet->left_face = remaining_face[ptet->bottom_face][ptet->near_face]; ptet->right_face = remaining_face[ptet->near_face][ptet->bottom_face]; } else /* ptet->orientation == left_handed */ { ptet->left_face = remaining_face[ptet->near_face][ptet->bottom_face]; ptet->right_face = remaining_face[ptet->bottom_face][ptet->near_face]; } /* * ...then turn around. */ veer_backwards(ptet); /* * Make sure it worked out like we planned. */ if (0 >= ptet->tet->curve[which_curve][ptet->orientation][ptet->bottom_face][ptet->near_face]) uFatalError("find_curve_start", "fundamental_group"); return; } } /* * We should find a negative intersection number somewhere within * the above loop, so we should never get to this point. */ uFatalError("find_curve_start", "fundamental_group"); } static void compute_Dehn_word( CyclicWord *meridian, CyclicWord *longitude, int m, int l, CyclicWord **word_list) { CyclicWord *new_word; Letter dummy_letter; /* * We should never be passed (m,l) = (0,0) when * cusp->is_complete is FALSE, but we check anyhow. */ if (m == 0 && l == 0) uFatalError("compute_Dehn_word", "fundamental_group"); /* * Initialize the new_word, and install it on the linked list. * * Use a temporary dummy_letter to initialize * the circular doubly linked list. */ new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = 0; new_word->itsLetters = &dummy_letter; dummy_letter.next = &dummy_letter; dummy_letter.prev = &dummy_letter; new_word->is_Dehn_relation = TRUE; new_word->next = *word_list; *word_list = new_word; /* * Append m meridians and l longitudes to new_word, * taking into account the signs of m and l. */ append_copies(meridian, m, new_word); append_copies(longitude, l, new_word); /* * Give new_word a valid pointer to the circular doubly linked list * of Letters, and then remove the temporary dummy_letter. * * Note that for meridians and longitudes, new_word->itsLetters * is set to the beginning of the based word, so the basepoints * for the meridian and longitude are the same, and the words will * commute. */ new_word->itsLetters = dummy_letter.next; REMOVE_NODE(&dummy_letter); } static void append_copies( CyclicWord *source, int n, CyclicWord *dest) { int i; for (i = 0; i < ABS(n); i++) if (n > 0) append_word(source, dest); else append_inverse(source, dest); } static void append_word( CyclicWord *source, CyclicWord *dest) { int i; Letter *letter, *letter_copy; for ( letter = source->itsLetters, i = 0; i < source->itsLength; letter = letter->next, i++) { letter_copy = NEW_STRUCT(Letter); letter_copy->itsValue = letter->itsValue; INSERT_BEFORE(letter_copy, dest->itsLetters); dest->itsLength++; } } static void append_inverse( CyclicWord *source, CyclicWord *dest) { int i; Letter *letter, *letter_copy; for ( letter = source->itsLetters->prev, i = 0; i < source->itsLength; letter = letter->prev, i++) { letter_copy = NEW_STRUCT(Letter); letter_copy->itsValue = - letter->itsValue; INSERT_BEFORE(letter_copy, dest->itsLetters); dest->itsLength++; } } static void initialize_original_generators( GroupPresentation *group, int num_generators) { int index; Letter *new_letter; CyclicWord *new_word; group->itsNumOriginalGenerators = num_generators; /* * Initially the original generators are the current generators. * Put the highest numbered generator on the linked list first, * and work backwards, so that they will appear in the correct order. */ group->itsOriginalGenerators = NULL; for (index = num_generators; index >= 1; --index) { new_letter = NEW_STRUCT(Letter); new_letter->itsValue = index; new_letter->prev = new_letter; new_letter->next = new_letter; new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = 1; new_word->itsLetters = new_letter; new_word->is_Dehn_relation = FALSE; new_word->next = group->itsOriginalGenerators; group->itsOriginalGenerators = new_word; } } static void simplify( GroupPresentation *group) { /* * The Induction Variable * * If group->minimize_number_of_generators is TRUE, then * each operation in the simplification algorithm decreases * the value of the ordered quintuple * * (number of generators, * length of shortest nonempty relation, * total length of all relations, * total number of runs in all relations, * total length of all meridians and longitudes) * * relative to the lexicographic ordering. In other words, each * operation either decreases the number of generators, or leaves * the number of generators constant while decreasing the length * of the shortest nonempty relation, or leaves both the number of * generators and the length of the shortest relation constant while * decreasing the total length of all relations, etc. This provides * a simple proof that the algorithm terminates in a finite number * of steps. * * If group->minimize_number_of_generators is FALSE, then we ignore * the number of generators and try to minimize the ordered quadruple * * (total length of all relations, * total number of runs in all relations, * length of shortest nonempty relation, * total length of all meridians and longitudes). * * By the "total number of runs" I mean that "aaabb" is simpler than * "aabAb" because the former has two runs ("aaa" and "bb") while * the latter has four ("aa", "b", "A" and "b"). The two words are * equivalent via a handle slide. Actually, for technical simplicity * we count the number of transitions from one run to another. * For words with at least two runs, the number of runs equals the * number of transitions. But a word with only one run has no * transitions. * * "length of shortest nonempty relation" may be defined as zero * if there are no nonempty relations. */ /* * Comment: eliminate_word_in_group() is called after * try_handle_slides() on the assumption that the former is more * likely to make a mess of the presentation than the latter, but I * don't have any hard evidence to support this assumption. */ /* * Insert a dummy basepoint Letter into each meridian and longitude, * and also into the expressions for the original generators, to make * sure they remain based at the same point. The basepoint has * itsValue == 0 so that it can't possibly cancel with anything. */ insert_basepoints(group); /* * Cancel obvious inverses in each CyclicWord, * e.g. "abCcBefA" -> "ef". Hereafter, each low-level function * which changes the GroupPresentation (e.g. handle_slide() etc.) * will call cancel_inverses() before returning. cancel_inverses() * decreases the total length of all relations without increasing * any other component of The Induction Variable. */ cancel_inverses(group); /* * The following while() loop call various mid-level functions * in the preferred order. As soon as some mid-level function * returns TRUE, the while() loop begins again at the start of * the list. The idea is that we want to do the more basic * simplifications before considering the fancier ones, and when we * do make some progress with the fancier ones, we want to try the * basic ones again. */ while ( remove_empty_relations(group) /* * If there is a relation of length one, e.g. "a", do a handle * slide to cancel the relation (which is topologically a * thickened disk) with the generator (which is topologically * a handle of the handlebody). word_length_one() decreases both * the number of generators and the total length of all relations. */ || word_length_one(group) /* * If there is a relation of the form "ab", do the handle slide * and then a handle cancellation. word_length_two() decreases both * the number of generators and the total length of all relations. */ || word_length_two(group) /* * Consider all possible handle slides. If we find one which * reduces The Induction Variable (cf. above), do it. */ || try_handle_slides(group) /* * If a generator occurs in precisely one word, and occurs in that * word precisely twice, both times with the same sign, then we may * do handle slides to make the two occurrences of the generator * adjacent to one another. This decreases the number of runs * without increasing any other component of The Induction Variable. * For example, we could simplify the word "aabAAb" to "aaaabb". */ || two_singletons_in_group(group) /* * Look for a word in which a generator occurs precisely once, * and use that word to eliminate the generator. (Say the word * is "bcacb". First do handle slides to reduce it to "a", and * then do a handle cancellation.) * If group->minimize_number_of_generators is FALSE, a word will * be eliminated only if it does not increase the total length * of all relations. */ || eliminate_word_in_group(group) /* * Try to insert a copy of one word into another so that * after cancellations the second word is shorter than it * used to be. This reduces the total length of all * relations without increasing either the number of * generators or the length of the shortest nonempty * relation (unless a relation is eliminated entirely, * but that's OK). */ || insert_word_from_group(group) /* * If we have a GroupPresentation with exactly one word, * we can looks for patterns in that word, and introduce * a new generator which simplifies the presentation. E.g. * {(bbaa)b(bbaa)(bbaa)} -> {Cbbaa, (bbaa)b(bbaa)(bbaa)} * -> {Cbbaa, bccc} -> {CCCCCCCaa}. This approach is * particular useful for recognizing fundamental groups * of torus knots as a^n = b^m. */ || simplify_one_word_presentations(group) ) ; /* * Try to simplify presentations of finite cyclic groups. */ lens_space_recognition(group); /* * If a generator appears more often as an inverse than as a * positive power, replace it with its inverse. * E.g. {AAbbc, abCCC, cB} -> {aabbC, Abccc, CB}. * * If a word contains more inverses than positive powers, * invert it. E.g. {aabbC, Abccc, CB} -> {aabbC, Abccc, bc}. * * Repeat as necessary. */ while ( invert_generators_where_necessary(group) == TRUE || invert_words_where_necessary(group) == TRUE) ; /* * The starting point of a cyclic word is arbitrary. * Choose it to meet the following aesthetic criteria, * in descending order of importance: * * (1) Don't start a word in the middle of a run. * (2) Start with a positive power of a generator. * (3) Start with the lowest-numbered generator. * * These criteria are applied only to the relations, * not to the peripheral curves, because we want the * latter to commute. */ choose_word_starts(group); /* * Can we conjugate any (meridian, longitude) pairs * to shorten their length? E.g. (CBCbcc, Cac) -> (BCbc, a) * or (cbbc, Caac) -> (ccbb, aa). */ conjugate_peripheral_words(group); /* * Remove the dummy basepoint Letters from the meridians and longitudes, * and from the expressions for the original generators. */ remove_basepoints(group); } static void insert_basepoints( GroupPresentation *group) { insert_basepoints_on_list(group->itsMeridians); insert_basepoints_on_list(group->itsLongitudes); insert_basepoints_on_list(group->itsOriginalGenerators); } static void insert_basepoints_on_list( CyclicWord *list) { CyclicWord *word; for (word = list; word != NULL; word = word->next) insert_basepoint_in_word(word); } static void insert_basepoint_in_word( CyclicWord *word) { Letter *basepoint; basepoint = NEW_STRUCT(Letter); basepoint->itsValue = 0; if (word->itsLength > 0) INSERT_BEFORE(basepoint, word->itsLetters) else { basepoint->prev = basepoint; basepoint->next = basepoint; } word->itsLetters = basepoint; word->itsLength++; } static void remove_basepoints( GroupPresentation *group) { remove_basepoints_on_list(group->itsMeridians); remove_basepoints_on_list(group->itsLongitudes); remove_basepoints_on_list(group->itsOriginalGenerators); } static void remove_basepoints_on_list( CyclicWord *list) { CyclicWord *word; for (word = list; word != NULL; word = word->next) remove_basepoint_in_word(word); } static void remove_basepoint_in_word( CyclicWord *word) { Letter *letter, *basepoint; int i; /* * Find the basepoint. * There should be precisely one. */ basepoint = NULL; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if (letter->itsValue == 0) { /* * Report an error if we've already found a basepoint before this one. */ if (basepoint != NULL) uFatalError("remove_basepoint_in_word", "fundamental_group"); basepoint = letter; } /* * Report an error if we found no basepoint. */ if (basepoint == NULL) uFatalError("remove_basepoint_in_word", "fundamental_group"); if (word->itsLength > 1) { word->itsLetters = basepoint->next; REMOVE_NODE(basepoint); } else word->itsLetters = NULL; my_free(basepoint); word->itsLength--; } static Boolean word_length_one( GroupPresentation *group) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (word->itsLength == 1) { cancel_handles(group, word); return TRUE; } return FALSE; } static Boolean word_length_two( GroupPresentation *group) { CyclicWord *word; int a, b; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (word->itsLength == 2) { a = word->itsLetters->itsValue; b = word->itsLetters->next->itsValue; if (a != b && a != -b) { handle_slide(group, a, b); cancel_handles(group, word); return TRUE; } } return FALSE; } static Boolean try_handle_slides( GroupPresentation *group) { /* * We want to consider all possible handles slides. * As explained in handle_slide(), a handle slide is determined by * two generators. For example, if the generators are {a, b}, * the potential handle slides are * * (BB) BA Ba (Bb) * AB (AA) (Aa) Ab * aB (aA) (aa) ab * (bB) bA ba (bb) * * Geometrically impossible combinations are shown in parentheses. * In a GroupPresentation with n generators, the above chart would have * (2n)*(2n) entries, (2n)2 of which are impossible, leaving 4n(n-1) * potential handle slides to consider. */ int a, b; /* * Abuse notation and let "ab" be a generic entry in the above table. * That is, "ab" will range over all possible handle slides. */ for (a = - group->itsNumGenerators; a <= group->itsNumGenerators; a++) { if (a == 0) /* There is no generator 0. */ continue; for (b = - group->itsNumGenerators; b <= group->itsNumGenerators; b++) { if (b == 0) /* There is no generator 0. */ continue; if (b == a || b == -a) /* Geometrically meaningless, cf. above. */ continue; if (substring_occurs_in_group(group, a, b) == TRUE && handle_slide_improves_presentation(group, a, b) == TRUE) { handle_slide(group, a, b); return TRUE; } } } return FALSE; } static Boolean substring_occurs_in_group( GroupPresentation *group, int a, int b) { CyclicWord *word; /* * a and b should be distinct generators. */ if (a == b || a == -b) uFatalError("substring_occurs_in_group", "fundamental_group"); /* * Does the substring "ab" occur somewhere in the group? */ for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (substring_occurs_in_word(word, a, b) == TRUE) return TRUE; return FALSE; } static Boolean substring_occurs_in_word( CyclicWord *word, int a, int b) { Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if ((letter->itsValue == a && letter->next->itsValue == b) || (letter->itsValue == -a && letter->prev->itsValue == -b)) return TRUE; return FALSE; } static Boolean handle_slide_improves_presentation( GroupPresentation *group, int a, int b) { /* * We want to evaluate the effect of the handle slide "ab". * As explained in handle_slide(), the handle slide "ab" acts by * * "a" -> "aB" * "A" -> "bA" * * If group->minimize_number_of_generators is TRUE, we want to see * whether the handle slide would decrease The Induction Variable * defined in simplify() as * * (number of generators, * length of shortest nonempty relation, * total length of all relations, * total number of runs in all relations, * total length of all meridians and longitudes). * * If group->minimize_number_of_generators is FALSE, we want to see * whether the handle slide would decrease The Induction Variable * * (total length of all relations, * total number of runs in all relations, * length of shortest nonempty relation, * total length of all meridians and longitudes). * * Handle slides never change the number of generators, * so we may ignore that component of The Induction Variable. * We check how the handle slide would change the other components * of The Induction Variable, and return TRUE if it would be improved, * or FALSE if it would be the same or worse. */ int shortest_nonempty_relation_before, shortest_nonempty_relation_after, change_in_total_length, change_in_num_runs; shortest_nonempty_relation_before = INT_MAX; shortest_nonempty_relation_after = INT_MAX; change_in_total_length = 0; change_in_num_runs = 0; evaluate_handle_slide_in_group( group, a, b, &shortest_nonempty_relation_before, &shortest_nonempty_relation_after, &change_in_total_length, &change_in_num_runs); if (group->minimize_number_of_generators == TRUE) { if (shortest_nonempty_relation_after < shortest_nonempty_relation_before) return TRUE; if (shortest_nonempty_relation_after > shortest_nonempty_relation_before) return FALSE; } if (change_in_total_length < 0) return TRUE; if (change_in_total_length > 0) return FALSE; if (change_in_num_runs < 0) return TRUE; if (change_in_num_runs > 0) return FALSE; if (group->minimize_number_of_generators == FALSE) { if (shortest_nonempty_relation_after < shortest_nonempty_relation_before) return TRUE; if (shortest_nonempty_relation_after > shortest_nonempty_relation_before) return FALSE; } /* * The value of The Induction Variable wouldn't change, * so return FALSE. */ return FALSE; } static void evaluate_handle_slide_in_group( GroupPresentation *group, int a, int b, int *shortest_nonempty_relation_before, int *shortest_nonempty_relation_after, int *change_in_total_length, int *change_in_num_runs) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) evaluate_handle_slide_on_word( word, a, b, shortest_nonempty_relation_before, shortest_nonempty_relation_after, change_in_total_length, change_in_num_runs); } static void evaluate_handle_slide_on_word( CyclicWord *word, int a, int b, int *shortest_nonempty_relation_before, int *shortest_nonempty_relation_after, int *change_in_total_length, int *change_in_num_runs) { int delta_length, delta_runs, old_length, new_length; delta_length = compute_delta_length(word, a, b); delta_runs = compute_delta_runs (word, a, b); old_length = word->itsLength; new_length = old_length + delta_length; if (old_length < *shortest_nonempty_relation_before) *shortest_nonempty_relation_before = old_length; if (new_length < *shortest_nonempty_relation_after) *shortest_nonempty_relation_after = new_length; *change_in_total_length += delta_length; *change_in_num_runs += delta_runs; } static int compute_delta_length( CyclicWord *word, int a, int b) { /* * Lemma. Handle slides may cause cancellations, but they don't * cause "secondary cancellations". That is, when you do "a" -> "aB" * you may find that the following letter is a 'b', so you get * "ab" -> "aBb" -> "a" (indeed, this is the whole purpose of * handle slides). However, the "Bb" cancellation won't allow * any additional cancellations to occur. If you had "abA", * you'd just get "abA" -> "aBbbA" -> "abA". This last example * is an important special case in the following code. */ int delta_length, i; Letter *letter; delta_length = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { /* * When we see 'a' or 'A' we increment the length of the word * to account for the handle slide. (Cancellations will be * dealt with momentarily.) */ if (letter->itsValue == a || letter->itsValue == -a) delta_length++; /* * When we see a 'b' immediately preceded by an 'a' we decrement * the length of the word by two to account for the cancellation, * and similarly for a 'B' immediately followed by an 'A'. * As explained in the above lemma, these are the only * cancellations which may occur. */ if ((letter->itsValue == b && letter->prev->itsValue == a) || (letter->itsValue == -b && letter->next->itsValue == -a)) delta_length -= 2; } return delta_length; } static int compute_delta_runs( CyclicWord *word, int a, int b) { /* * Counting the change in the number of runs is trickier than * counting the change in the length. * * Recall that the number of runs is actually the number of * transitions from one run to another; i.e. a word with one * run has no transitions. * * The number of transitions can change just after an 'a' or just * before an 'A', but nowhere else. Typically a handle slide * introduces one new transition after an 'a': "ac" -> "aBc". * The exceptional cases appear in the table below, along with * the number of new transitions each introduces. * * "aa" -> "aBa" +2 * "aA" (shouldn't occur) * "aba" -> "aa" -2 * "abA" -> "abA" 0 * "abb" -> "ab" 0 * "abB" (shouldn't occur) * "abc" -> "ac" -1 * "aB" -> "aBB" 0 * "ac" -> "aBc" +1 * * A similar table lists the possible transition changes * preceding an 'A'. * * "AA" -> "AbA" +2 * "aA" (shouldn't occur) * "ABA" -> "AA" -2 * "aBA" -> "aBA" 0 * "BBA" -> "BA" 0 * "bBA" (shouldn't occur) * "CBA" -> "CA" -1 * "bA" -> "bbA" 0 * "CA" -> "CbA" +1 * * The only case where we have to worry about "double counting" * is "aBA" -> "aBA". It gets considered once when we examine * the 'a' and again when we examine the 'A'. Fortunately the * net change in transitions is zero, so this "double counting" * is harmless. */ int delta_runs, i; Letter *letter; delta_runs = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue == a) { if (letter->next->itsValue == a) /* * "aa" -> "aBa" */ delta_runs += 2; else if (letter->next->itsValue == -a) /* * "aA" should not occur. */ uFatalError("compute_delta_runs", "fundamental_group"); else if (letter->next->itsValue == b) /* * "ab" -> "aBb" -> "a" * * Break into cases according to the value * of the next letter. */ { if (letter->next->next->itsValue == a) /* * "aba" -> "aBba" -> "aa" */ delta_runs -= 2; else if (letter->next->next->itsValue == -a) /* * "abA" -> "aBbbA" -> "abA" */ delta_runs += 0; else if (letter->next->next->itsValue == b) /* * "abb" -> "aBbb" -> "ab" */ delta_runs += 0; else if (letter->next->next->itsValue == -b) /* * "abB" should not occur. */ uFatalError("compute_delta_runs", "fundamental_group"); else /* * "abc" -> "aBbc" -> "ac" */ delta_runs -= 1; } else if (letter->next->itsValue == -b) /* * "aB" -> "aBB" */ delta_runs += 0; else /* * "ac" -> "aBc" */ delta_runs += 1; } if (letter->itsValue == -a) { if (letter->prev->itsValue == -a) /* * "AA" -> "AbA" */ delta_runs += 2; else if (letter->prev->itsValue == a) /* * "aA" should not occur. */ uFatalError("compute_delta_runs", "fundamental_group"); else if (letter->prev->itsValue == -b) /* * "BA" -> "BbA" -> "A" * * Break into cases according to the value * of the preceding letter. */ { if (letter->prev->prev->itsValue == -a) /* * "ABA" -> "ABbA" -> "AA" */ delta_runs -= 2; else if (letter->prev->prev->itsValue == a) /* * "aBA" -> "aBBbA" -> "aBA" */ delta_runs += 0; else if (letter->prev->prev->itsValue == -b) /* * "BBA" -> "BBbA" -> "BA" */ delta_runs += 0; else if (letter->prev->prev->itsValue == b) /* * "bBA" should not occur. */ uFatalError("compute_delta_runs", "fundamental_group"); else /* * "CBA" -> "CBbA" -> "CA" */ delta_runs -= 1; } else if (letter->prev->itsValue == b) /* * "bA" -> "bbA" */ delta_runs += 0; else /* * "CA" -> "CbA" */ delta_runs += 1; } } return delta_runs; } static Boolean two_singletons_in_group( GroupPresentation *group) { /* * If a generator occurs in precisely one word, and occurs in that * word precisely twice, both times with the same sign, * then we may do handle slides to make the two occurrences of the * generator adjacent to one another, without lengthening the word. * For example, we could simplify the word "aabAAb" to "aaaabb". * * This is a geometric operation. It preserves the pseudo-Heegaard * diagram discussed at the top of this file. */ int i; CyclicWord *word_containing_singletons; for (i = 1; i <= group->itsNumGenerators; i++) if (generator_occurs_as_two_singletons_in_group(group, i, &word_containing_singletons) && generator_occurs_in_no_other_word_in_group(group, i, word_containing_singletons)) { make_singletons_adjacent(group, i, word_containing_singletons); return TRUE; } return FALSE; } static Boolean generator_occurs_as_two_singletons_in_group( GroupPresentation *group, int value, CyclicWord **word_containing_singletons) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (generator_occurs_as_two_singletons_in_word(word, value) == TRUE) { *word_containing_singletons = word; return TRUE; } *word_containing_singletons = NULL; return FALSE; } static Boolean generator_occurs_as_two_singletons_in_word( CyclicWord *word, int value) { int num_plus, num_minus, i; Letter *letter; num_plus = 0; num_minus = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { /* * Count positive occurrences. */ if (letter->itsValue == value) num_plus++; /* * Count negative occurrences. */ if (letter->itsValue == - value) num_minus++; /* * Reject consecutive occurrences. */ if ( ( letter->itsValue == value || letter->itsValue == - value) && ( letter->next->itsValue == value || letter->next->itsValue == - value) ) return FALSE; } return ((num_plus == 2 && num_minus == 0) || (num_plus == 0 && num_minus == 2)); } static Boolean generator_occurs_in_no_other_word_in_group( GroupPresentation *group, int value, CyclicWord *word_containing_singletons) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (word != word_containing_singletons && generator_occurs_in_word(word, value) == TRUE) return FALSE; return TRUE; } static Boolean generator_occurs_in_word( CyclicWord *word, int value) { Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if (letter->itsValue == value || letter->itsValue == - value) return TRUE; return FALSE; } static void make_singletons_adjacent( GroupPresentation *group, int value, CyclicWord *word) { /* * Other functions have already verified that value occurs exactly * twice in word, both times with the same sign, and occurs nowhere * else in the GroupPresentation. */ /* * Advance word->itsLetters to point at an occurrence of value * or its inverse. */ while ( word->itsLetters->itsValue != value && word->itsLetters->itsValue != - value) word->itsLetters = word->itsLetters->next; /* * Do handle slides until the two occurrence of value are adjacent. */ while (word->itsLetters->itsValue != word->itsLetters->next->itsValue) handle_slide( group, word->itsLetters->itsValue, word->itsLetters->next->itsValue); } static Boolean eliminate_word_in_group( GroupPresentation *group) { /* * Look for a generator which occurs precisely once in some word, * and use the word to eliminate the generator. For example, * say the word is "bcacb". First do handle slides to reduce it * to "a", and then do a handle cancellation. * * Try to choose a generator and a word which increase the total * length of all relations as little as possible. * If group->minimize_number_of_generators is FALSE, insist that * the total length of all relations must decrease. * * Return TRUE if successful, FALSE if no such word exists. */ int delta_length, best_delta, best_generator, m, n, generator; CyclicWord *word_with_singleton, *best_word; best_delta = INT_MAX; best_generator = 0; best_word = NULL; for (generator = 1; generator <= group->itsNumGenerators; generator++) { word_with_singleton = shortest_word_in_which_generator_occurs_precisely_once (group, generator); if (word_with_singleton != NULL) { /* * By how much would the total length of all relations increase * if we eliminated this generator via this word? */ m = word_with_singleton->itsLength; n = occurrences_in_group(group, generator); delta_length = n*(m-1) /* effect of handle slides */ - 2*(m-1) /* effect of cancellations in this word */ - n; /* effect of eliminating generator */ if (delta_length < best_delta) { best_delta = delta_length; best_generator = generator; best_word = word_with_singleton; } } } if ( best_word != NULL && ( group->minimize_number_of_generators == TRUE || best_delta < 0 ) ) { eliminate_word(group, best_word, best_generator); return TRUE; } else return FALSE; } static CyclicWord *shortest_word_in_which_generator_occurs_precisely_once( GroupPresentation *group, int generator) { /* * Find the shortest word in which the generator occurs precisely once. */ CyclicWord *word, *best_word; best_word = NULL; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (generator_occurs_precisely_once_in_word(word, generator) == TRUE) if (best_word == NULL || word->itsLength < best_word->itsLength) best_word = word; return best_word; } static Boolean generator_occurs_precisely_once_in_word( CyclicWord *word, int generator) { return (occurrences_in_word(word, generator) == 1); } static int occurrences_in_group( GroupPresentation *group, int generator) { int occurrences; CyclicWord *word; occurrences = 0; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) occurrences += occurrences_in_word(word, generator); return occurrences; } static int occurrences_in_word( CyclicWord *word, int generator) { int i, num_occurrences; Letter *letter; num_occurrences = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if (letter->itsValue == generator || letter->itsValue == - generator) num_occurrences++; return num_occurrences; } static void eliminate_word( GroupPresentation *group, CyclicWord *word, int generator) { Letter *letter; /* * eliminate_word_in_group() should have already checked * that generator occurs precisely once in word. */ if (generator_occurs_precisely_once_in_word(word, generator) == FALSE) uFatalError("eliminate_word", "fundamental_group"); /* * Find the Letter containing the unique occurrence of the generator. */ for ( letter = word->itsLetters; letter->itsValue != generator && letter->itsValue != - generator; letter = letter->next ) ; /* * Do handle slides until only "letter" is left, then * do a handle cancellation. */ while (word->itsLength > 1) handle_slide(group, letter->itsValue, letter->next->itsValue); cancel_handles(group, word); } static Boolean remove_empty_relations( GroupPresentation *group) { Boolean words_were_removed; CyclicWord **list, *dead_word; words_were_removed = FALSE; list = &group->itsRelations; while (*list != NULL) { if ((*list)->itsLength == 0) { dead_word = *list; *list = (*list)->next; free_cyclic_word(dead_word); group->itsNumRelations--; words_were_removed = TRUE; } else list = &(*list)->next; } return words_were_removed; } static Boolean insert_word_from_group( GroupPresentation *group) { /* * Try to insert a copy of one word into another so that after * cancellations the second word is shorter than it used to be. * For example, in the presentation * * BaBABABaB * BaBAA * * we can substitute the inverse of the second word into the * first word to obtain * * BaBA(abAba)BABaB * BaBAA * * which simplifies to * * aBABaB * BaBAA * * Do it again to obtain * * aBABaB(bAbaa) * BaBAA * * which simplifies to * * aBa * BaBAA * * The usual geometric simplifications now reduce this presentation to * * AAAAA */ CyclicWord *word; /* * Consider all possible words which we might want to insert * into another word. */ for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) if (insert_word_into_group(group, word) == TRUE) return TRUE; return FALSE; } static Boolean insert_word_into_group( GroupPresentation *group, CyclicWord *word) { /* * The "word" shouldn't be a Dehn relation if * group->fillings_may_affect_generators is FALSE, * but the "target" can always be any kinds of relation. * Simplifying the Dehn relations won't have any effect * on either the choice of generators or the edge relations. */ return ( insert_word_into_list(group->itsRelations, word) == TRUE || insert_word_into_list(group->itsMeridians, word) == TRUE || insert_word_into_list(group->itsLongitudes, word) == TRUE || insert_word_into_list(group->itsOriginalGenerators, word) == TRUE); } static Boolean insert_word_into_list( CyclicWord *list, CyclicWord *word) { CyclicWord *target; for (target = list; target != NULL; target = target->next) if (insert_word_into_word(word, target) == TRUE) return TRUE; return FALSE; } static Boolean insert_word_into_word( CyclicWord *word, CyclicWord *target) { int i, j; /* * Don't insert a word into itself. */ if (word == target) return FALSE; /* * One CyclicWord may be inserted into another in many different * ways. For example, if word = "abc" and target = "defg", there * are 24 ways to insert word into target: * * (abc)defg d(abc)efg de(abc)fg def(abc)g * (bca)defg d(bca)efg de(bca)fg def(bca)g * (cab)defg d(cab)efg de(cab)fg def(cab)g * * (CBA)defg d(CBA)efg de(CBA)fg def(CBA)g * (ACB)defg d(ACB)efg de(ACB)fg def(ACB)g * (BAC)defg d(BAC)efg de(BAC)fg def(BAC)g * * Comment: The algorithm considers cancellations at only one end * of the inserted word. For example, when it inserts "abcd" into * "ADCef" to obtain "A(abcd)DCef", the program will consider the * cancellations "cdDC", but will ignore the "Aa". This is OK. * At some other point in its nested loops it will consider * "(bcda)ADCef", thereby recognizing the fullest cancellation. * * We use word->itsLetters and target->itsLetters to mark * the possible insertion points. Note too that the following * code automatically ignores relations of length zero. */ for (i = 0; i < word->itsLength; i++) { for (j = 0; j < target->itsLength; j++) { if (insert_word_forwards(word, target) == TRUE || insert_word_backwards(word, target) == TRUE) return TRUE; target->itsLetters = target->itsLetters->next; } word->itsLetters = word->itsLetters->next; } return FALSE; } static Boolean insert_word_forwards( CyclicWord *word, CyclicWord *target) { /* * If, say, word = "abc" and target = "defg", * would replacing target with "abcdefg" reduce the length * of target after cancelling inverses? */ int remaining_cancellations, i; Letter *next_letter_in_word, *next_letter_in_target, *letter, *letter_copy; /* * insert_word_into_word guarantees that * both word and target are nonempty. */ if (word->itsLength == 0 || target->itsLength == 0) uFatalError("insert_word_forwards", "fundamental_group"); /* * More than half the Letters in word must cancel with letters in target. */ remaining_cancellations = (word->itsLength + 2) / 2; /* * If target isn't long enough, let's give up now so we can ignore * the cyclic nature of the target in the code below. (Otherwise * a target "ab" might gives the illusion of completely cancelling * a word "BABABABABA".) */ if (target->itsLength < remaining_cancellations) return FALSE; /* * Check whether the last remaining_cancellations Letters in word * cancel with the first remaining_cancellations Letters in target. * (See the comment in insert_word_into_word() for an explanation * of why it's OK to ignore possible cancellations of letters * at the beginning of word with letters at the end of target.) */ next_letter_in_word = word->itsLetters->prev; next_letter_in_target = target->itsLetters; while (remaining_cancellations > 0) { if (next_letter_in_word->itsValue + next_letter_in_target->itsValue == 0) { remaining_cancellations--; next_letter_in_word = next_letter_in_word ->prev; next_letter_in_target = next_letter_in_target->next; } else return FALSE; } /* * Great! They cancel! * Let's insert a copy of word into target, and do the cancellations. * If we're lucky, we may even get more cancellations than just the * minimum. */ /* * Insert the copy. */ for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { letter_copy = NEW_STRUCT(Letter); letter_copy->itsValue = letter->itsValue; INSERT_BEFORE(letter_copy, target->itsLetters); target->itsLength++; } /* * Do the cancellations. */ cancel_inverses_word(target); return TRUE; } static Boolean insert_word_backwards( CyclicWord *word, CyclicWord *target) { /* * If, say, word = "abc" and target = "defg", * would replacing target with "CBAdefg" reduce the length * of target after cancelling inverses? */ int remaining_cancellations, i; Letter *next_letter_in_word, *next_letter_in_target, *letter, *letter_copy; /* * insert_word_into_word guarantees that * both word and target are nonempty. */ if (word->itsLength == 0 || target->itsLength == 0) uFatalError("insert_word_backwards", "fundamental_group"); /* * More than half the Letters in word must cancel with letters in target. */ remaining_cancellations = (word->itsLength + 2) / 2; /* * If target isn't long enough, let's give up now so we can ignore * the cyclic nature of the target in the code below. (Otherwise * a target "ab" might gives the illusion of completely cancelling * a word "abababababab".) */ if (target->itsLength < remaining_cancellations) return FALSE; /* * Check whether the first remaining_cancellations Letters in word * match the first remaining_cancellations Letters in target. * (See the comment in insert_word_into_word() for an explanation * of why it's OK to ignore possible matches of letters at the ends * of word and target.) */ next_letter_in_word = word->itsLetters; next_letter_in_target = target->itsLetters; while (remaining_cancellations > 0) { if (next_letter_in_word->itsValue == next_letter_in_target->itsValue) { remaining_cancellations--; next_letter_in_word = next_letter_in_word ->next; next_letter_in_target = next_letter_in_target->next; } else return FALSE; } /* * Great! They cancel! * Let's insert a copy of word's inverse into target, and do the * cancellations. If we're lucky, we may even get more cancellations * than just the minimum. */ /* * Insert the copy. */ for ( letter = word->itsLetters->prev, i = 0; i < word->itsLength; letter = letter->prev, i++) { letter_copy = NEW_STRUCT(Letter); letter_copy->itsValue = - letter->itsValue; INSERT_BEFORE(letter_copy, target->itsLetters); target->itsLength++; } /* * Do the cancellations. */ cancel_inverses_word(target); return TRUE; } static Boolean simplify_one_word_presentations( GroupPresentation *group) { /* * In general we'd like one-word presentations to be as simple * as possible. In particular, we'd like to display the fundamental * group of a torus knot as a^n = b^m, so the user can recognize * it easily. * * Often the presentation for a torus knot group is something like * bbaabbbaabbaa. We can use the repeating pattern (bbaa)b(bbaa)(bbaa) * to simplify the presentation. Introduce c = bbaa, so the * presentation becomes {Cbbaa, bccc}, then eliminate b = CCC to get * {CCCCCCCaa}. */ CyclicWord *word, *new_word; int num_matched_letters, period, repetitions; Letter *unmatched_letter; int i, j; /* * If this isn't a one-word presentation with at least two generators, * don't do anything. */ if (group->itsNumRelations != 1 || group->itsNumGenerators < 2) return FALSE; /* * Find the unique relation. */ word = group->itsRelations; /* * Is it OK for this relation to influence the choice of generators? */ if (word->is_Dehn_relation == TRUE && group->fillings_may_affect_generators == FALSE) return FALSE; /* * Terminology. In the example (bbaa)b(bbaa)(bbaa), the 'b' not * in parentheses is called the "unmatched" letter. The remaining * letters are "matched". The period is 4, and the number of * repetitions is 3. */ /* * We can ignore patterns of period one, because they will be either of * the form "aaaaaa" (which needs no simplification) or "baaaaaa" (which * would have already been eliminated by eliminate_word_in_group()). * * We can ignore patterns of period two, because they will be of the * form "bbabababa", which would have already been simplified by * try_handle_slides() -- this is a one-word presentation. * * We can ignore patterns of period three, for the same reason. * * Therefore we look only for patterns of period four or greater. */ num_matched_letters = word->itsLength - 1; for (period = 4; period < num_matched_letters; period++) if (num_matched_letters % period == 0) { repetitions = num_matched_letters / period; /* * Try all possibilities for the unmatched_letter. */ for ( unmatched_letter = word->itsLetters, i = 0; i < word->itsLength; unmatched_letter = unmatched_letter->next, i++) if (word_contains_pattern( word, unmatched_letter, period, repetitions) == TRUE) { /* * Create the new word, * e.g. {bbaabbbaabbaa} -> {bbaabbbaabbaa, Cbbaa}. */ new_word = introduce_generator( group, unmatched_letter->next, period); /* * Insert the new word into the old, * e.g. {bbaabbbaabbaa, Cbbaa} -> {cbcc, Cbbaa}. */ for (j = 0; j < repetitions; j++) if (insert_word_into_word(new_word, word) == FALSE) uFatalError("simplify_one_word_presentations", "fundamental_group"); /* * Eliminate the original word, * e.g. {cbcc, Cbbaa} -> {CCCCCCCaa}. */ eliminate_word(group, word, unmatched_letter->itsValue); /* * All done. */ return TRUE; } } return FALSE; } static Boolean word_contains_pattern( CyclicWord *word, Letter *unmatched_letter, int period, int repetitions) { int i, j, k; Letter *letter, *image; for ( i = 0, letter = unmatched_letter->next; i < period; i++, letter = letter->next) { image = letter; for ( j = 0; j < repetitions; j++) { if (image->itsValue != letter->itsValue) return FALSE; for (k = 0; k < period; k++) image = image->next; } } return TRUE; } static CyclicWord *introduce_generator( GroupPresentation *group, Letter *substring, int length) { /* * Introduce a new generator into the group presentation. * It will be of the form c = baaba, where baaba is a substring of * some other word; the function argument "substring" points to the * first letter in the substring, and "length" gives its length. */ O31Matrix *new_array, the_inverse; int i; Letter *letter, *new_generator_letter, *letter_copy; CyclicWord *new_word; /* * Should the new relation be an edge relation or a Dehn relation? * Really, of course, it is neither. * But to have gotten this far, either * group->fillings_may_affect_generators is TRUE, or it is FALSE and * the only relation is an edge relation. So it is safe to call * the new relation an edge relation. */ if ( group->fillings_may_affect_generators == FALSE && ( group->itsNumRelations != 1 || group->itsRelations->is_Dehn_relation == TRUE ) ) uFatalError("introduce_generator", "fundamental_group"); /* * Create the new generator. */ /* * Allocate a bigger array for the matrix generators, * copy in the existing values, and free the old array. */ new_array = NEW_ARRAY(group->itsNumGenerators + 1, O31Matrix); for (i = 0; i < group->itsNumGenerators; i++) o31_copy(new_array[i], group->itsMatrices[i]); my_free(group->itsMatrices); group->itsMatrices = new_array; /* * Create the new matrix. */ o31_copy(group->itsMatrices[group->itsNumGenerators], O31_identity); for ( i = 0, letter = substring; i < length; i++, letter = letter->next) if (letter->itsValue > 0) o31_product(group->itsMatrices[group->itsNumGenerators], group->itsMatrices[letter->itsValue - 1], group->itsMatrices[group->itsNumGenerators]); else { o31_invert( group->itsMatrices[(-letter->itsValue) - 1], the_inverse); o31_product(group->itsMatrices[group->itsNumGenerators], the_inverse, group->itsMatrices[group->itsNumGenerators]); } /* * Increment group->itsNumGenerators. */ group->itsNumGenerators++; /* * Create the new relation. * * If the new word is, say, c = babba, we'll construct it as Cbabba. * Note that it begins with the inverse of the new generator. */ new_generator_letter = NEW_STRUCT(Letter); new_generator_letter->itsValue = - group->itsNumGenerators; new_generator_letter->next = new_generator_letter; new_generator_letter->prev = new_generator_letter; new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = length + 1; new_word->itsLetters = new_generator_letter; for ( i = 0, letter = substring; i < length; i++, letter = letter->next) { letter_copy = NEW_STRUCT(Letter); letter_copy->itsValue = letter->itsValue; INSERT_BEFORE(letter_copy, new_generator_letter); } /* * The new_word may be considered an edge relation, as explained above. */ new_word->is_Dehn_relation = FALSE; new_word->next = group->itsRelations; group->itsRelations = new_word; group->itsNumRelations++; return new_word; } static void lens_space_recognition( GroupPresentation *group) { /* * We want to be able to recognize a presentation like * * bbbaaaa * bbbbbbbbaaa * * as a lens space. We use the Euclidean algorithm. * (In this example it would be more efficient to start with the a's, * but I'll start with the b's instead so I can show two iterations * of the basic process.) * Interpret the first relation as bbb = AAAA, and substitute it * into the second relation. * * bbbaaaa * AAAAAAAAbbaaa * * Cancel AAA with aaa. * * bbbaaaa * AAAAAbb * * Now interpret the second relation as bb = aaaaa and substitute * it back into the first relation. * * baaaaaaaaa * AAAAAbb * * Interpret the first relation as b = AAAAAAAAA and substitute it * into the second relation. * * baaaaaaaaa * AAAAAAAAAAAAAAAAAAAAAAA * * Eliminate generator b. * * AAAAAAAAAAAAAAAAAAAAAAA * * This shows that the group is Z/23. * * (The actual code in lens_space_recognition_using_generator() uses * a slightly different implementation, but this is the general idea.) */ int generator; /* * Do we have permission to reduce the number of generators? */ if (group->fillings_may_affect_generators == FALSE) return; /* * A more general version of this code would apply to free * products of finite cyclic groups with other groups. * For now we insist that the group as a whole is finite cyclic. */ /* * Are there exactly two generators? */ if (group->itsNumGenerators != 2) return; /* * Are there exactly two relations? * (Note: By this point empty relations will have been eliminated.) */ if (group->itsNumRelations != 2) return; /* * Do a quick error check. */ if (group->itsRelations == NULL || group->itsRelations->next == NULL || group->itsRelations->next->next != NULL) uFatalError("lens_space_recognition", "fundamental_group"); /* * Are both words of the form a^m = b^n ? */ if (count_runs( group->itsRelations ) > 2 || count_runs( group->itsRelations->next ) > 2) return; /* * Try the Euclidean algorithm on each generator in turn. * Break if successful. */ for (generator = 1; generator <= 2; generator++) if (lens_space_recognition_using_generator(group, generator) == TRUE) break; } static int count_runs( CyclicWord *word) { int num_runs, i; Letter *letter; num_runs = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if (letter->itsValue != letter->next->itsValue) num_runs++; return num_runs; } static Boolean lens_space_recognition_using_generator( GroupPresentation *group, int generator0) { int i, j, value_of_b, power_of_b, occurrences[2], generator[2], n[2][2]; long int p, q; CyclicWord *word[2], *new_word; Letter *letter_a, *letter_b; /* * Note the two words. */ word[0] = group->itsRelations; word[1] = group->itsRelations->next; /* * How many times does the generator occur in each word? */ for (i = 0; i < 2; i++) occurrences[i] = occurrences_in_word(word[i], generator0); /* * If this generator occurs in only one word, give up. */ for (i = 0; i < 2; i++) if (occurrences[i] == 0) return FALSE; /* * If the number of occurrences are not relatively prime, * give up. */ if (gcd(occurrences[0], occurrences[1]) > 1) return FALSE; /* * Think of the given generator as 'a' and the * other generator as 'b'. */ generator[0] = generator0; generator[1] = 3 - generator0; /* * Count the number of signed occurrences * of each generator in each word. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) n[i][j] = count_signed_occurrences_in_word(word[i], generator[j]); /* * The relations are * * a^n[0][0] * b^n[0][1] = 1 * a^n[1][0] * b^n[1][1] = 1 * or * a^n[0][0] = b^-n[0][1] * a^n[1][0] = b^-n[1][1] * * Find p and q such that p*n[0][0] + q*n[1][0] = gcd(n[0][0], n[1][0]) = 1. */ (void) euclidean_algorithm(n[0][0], n[1][0], &p, &q); /* * a = a^1 * = a^(p*n[0][0] + q*n[1][0]) * = a^(p*n[0][0]) * a^(q*n[1][0]) * = (a^n[0][0])^p * (a^n[1][0])^q * = (b^-n[0][1])^p * (b^-n[1][1])^q * = b^(-p*n[0][1]) * b^(-q*n[1][1]) * = b^-(p*n[0][1] + q*n[1][1]) * * Add the redundant relation * * a * b^(p*n[0][1] + q*n[1][1]) = 1 * * (Remember -- nongeometric operations are OK.) */ value_of_b = (p*n[0][1] + q*n[1][1] >= 0) ? generator[1] : -generator[1]; power_of_b = ABS(p*n[0][1] + q*n[1][1]); new_word = NEW_STRUCT(CyclicWord); new_word->itsLength = 1 + power_of_b; new_word->is_Dehn_relation = FALSE; new_word->next = group->itsRelations; group->itsRelations = new_word; group->itsNumRelations++; letter_a = NEW_STRUCT(Letter); letter_a->itsValue = generator[0]; letter_a->prev = letter_a; letter_a->next = letter_a; new_word->itsLetters = letter_a; for (i = 0; i < power_of_b; i++) { letter_b = NEW_STRUCT(Letter); letter_b->itsValue = value_of_b; INSERT_AFTER(letter_b, letter_a); } /* * Use the new_word to eliminate generator 'a'. */ eliminate_word(group, new_word, generator[0]); /* * We're left with two relations of the form * * b^r = 1 * b^s = 1 * * Keep substituting one into the other until one * becomes trivial and can be removed. */ while (remove_empty_relations(group) == FALSE) if (insert_word_from_group(group) == FALSE) uFatalError("lens_space_recognition_using_generator", "fundamental_group"); return TRUE; } static Boolean invert_generators_where_necessary( GroupPresentation *group) { /* * If a generator appears more often as an inverse than as a * positive power, replace it with its inverse. * E.g. {AAbbc, abCCC} -> {aabbC, Abccc}. */ int a, positive_occurrences, negative_occurrences; Boolean progress; progress = FALSE; for (a = 1; a <= group->itsNumGenerators; a++) { count_signed_occurrences_in_group( group, a, &positive_occurrences, &negative_occurrences); if (negative_occurrences > positive_occurrences) { invert_generator_in_group(group, a); progress = TRUE; } } return progress; } static void count_signed_occurrences_in_group( GroupPresentation *group, int a, int *positive_occurrences, int *negative_occurrences) { *positive_occurrences = 0; *negative_occurrences = 0; increment_signed_occurrences_in_group( group, a, positive_occurrences, negative_occurrences); } static void increment_signed_occurrences_in_group( GroupPresentation *group, int a, int *positive_occurrences, int *negative_occurrences) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) increment_signed_occurrences_in_word( word, a, positive_occurrences, negative_occurrences); } static void increment_signed_occurrences_in_word( CyclicWord *word, int a, int *positive_occurrences, int *negative_occurrences) { Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue == a) (*positive_occurrences)++; if (letter->itsValue == -a) (*negative_occurrences)++; } } static int count_signed_occurrences_in_word( CyclicWord *word, int a) { Letter *letter; int i, num_occurrences; num_occurrences = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue == a) num_occurrences++; if (letter->itsValue == -a) num_occurrences--; } return num_occurrences; } static void invert_generator_in_group( GroupPresentation *group, int a) { if (a < 1 || a > group->itsNumGenerators) uFatalError("invert_generator_in_group", "fundamental_group"); o31_invert(group->itsMatrices[a - 1], group->itsMatrices[a - 1]); invert_generator_on_list(group->itsRelations, a); invert_generator_on_list(group->itsMeridians, a); invert_generator_on_list(group->itsLongitudes, a); invert_generator_on_list(group->itsOriginalGenerators, a); } static void invert_generator_on_list( CyclicWord *list, int a) { CyclicWord *word; for (word = list; word != NULL; word = word->next) invert_generator_in_word(word, a); } static void invert_generator_in_word( CyclicWord *word, int a) { Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue == a) letter->itsValue = -a; else if (letter->itsValue == -a) letter->itsValue = a; } } static Boolean invert_words_where_necessary( GroupPresentation *group) { /* * Design decision: I considered inverting peripheral words where * necessary as well, but decided against it. Inverting a peripheral * word corresponds to reorienting the curve it represents. We * would still get the same length and torsion, so the reorientation * may be harmless, but it seems like a less than robust approach. * Who knows what somebody may someday do with this code. I'd hate * to introduce a bizarre bug. Better to just live with peripheral * curves which may contain more inverses than absolutely necessary. */ CyclicWord *word; Boolean progress; progress = FALSE; for (word = group->itsRelations; word != NULL; word = word->next) /* * Fix the word no matter what, but ... */ if (invert_word_if_necessary(word) == TRUE) /* * ...set progress = TRUE iff this inversion might allow * some generator to be profitably inverted as well. */ if (word->is_Dehn_relation == FALSE || group->fillings_may_affect_generators == TRUE) progress = TRUE; return progress; } static Boolean invert_word_if_necessary( CyclicWord *word) { if (sum_of_powers(word) < 0) { invert_word(word); return TRUE; } else return FALSE; } static int sum_of_powers( CyclicWord *word) { Letter *letter; int i, sum; sum = 0; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue > 0) sum++; if (letter->itsValue < 0) sum--; } return sum; } static void invert_word( CyclicWord *word) { /* * For each Letter we must * * (1) negate itsValue, and * * (2) switch prev and next. * * The tricky part is keeping track of the Letters * while their prev and next fields are in flux. */ Letter *letter, *temp; if (word->itsLength == 0) return; letter = word->itsLetters; do { letter->itsValue = - letter->itsValue; temp = letter->prev; letter->prev = letter->next; letter->next = temp; /* * This is the delicate step. * We've swapped prev and next, so to move on to what used * to be the next Letter, we follow the prev pointer. */ letter = letter->prev; } while (letter != word->itsLetters); } static void choose_word_starts( GroupPresentation *group) { CyclicWord *word; for (word = group->itsRelations; word != NULL; word = word->next) choose_word_start(word); } static void choose_word_start( CyclicWord *word) { /* * The starting point of a cyclic word is arbitrary. * Choose it to meet the following criteria, in descending * order of importance: * * (1) Don't start a word in the middle of a run. * (2) Start with a positive power of a generator. * (3) Start with the lowest-numbered generator. * * Note the following code considers the letters of the CyclicWord * in order, so once it switches word->itsLetters to the beginning * of a run, it will always be at the beginning of a run. * Similarly, once it switches to a positive value, it will always * be at a positive value. */ Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) if ( ( word->itsLetters->itsValue == word->itsLetters->prev->itsValue && letter->itsValue != letter->prev->itsValue) || ( word->itsLetters->itsValue < 0 && letter->itsValue > 0) || ( letter->itsValue > 0 && letter->itsValue < word->itsLetters->itsValue) || ( letter->itsValue < 0 && letter->itsValue > word->itsLetters->itsValue) ) word->itsLetters = letter; } static void conjugate_peripheral_words( GroupPresentation *group) { /* * Can we conjugate any (meridian, longitude) pairs * to shorten their length? E.g. (CBCbcc, Cac) -> (BCbc, a) * or (cbbc, Caac) -> (ccbb, aa). */ int i; CyclicWord *theMeridian, *theLongitude; for ( i = 0, theMeridian = group->itsMeridians, theLongitude = group->itsLongitudes; i < group->itsNumCusps; i++, theMeridian = theMeridian ->next, theLongitude = theLongitude->next ) { if (theMeridian == NULL || theLongitude == NULL) uFatalError("conjugate_peripheral_words", "fundamental_group"); /* * Conjugate as necessary. */ while ( conjugate_peripheral_pair(theMeridian, theLongitude) == TRUE || conjugate_peripheral_pair(theLongitude, theMeridian) == TRUE) ; } } static Boolean conjugate_peripheral_pair( CyclicWord *word0, CyclicWord *word1) { int value; /* * For each peripheral word, set itsLetters * to point to the dummy basepoint Letter. */ while (word0->itsLetters->itsValue != 0) word0->itsLetters = word0->itsLetters->next; while (word1->itsLetters->itsValue != 0) word1->itsLetters = word1->itsLetters->next; if /* If... */ ( /* ...word0 contain more than just the dummy */ /* basepoint letter... */ word0->itsLength > 1 && /* ...the ends of word0 are inverses of one another... */ word0->itsLetters->next->itsValue + word0->itsLetters->prev->itsValue == 0 && /* ...and at least one end matches the corresponding */ /* end of word1, or word1 contains only the dummy */ /* basepoint Letter, ... */ ( word0->itsLetters->next->itsValue == word1->itsLetters->next->itsValue || word0->itsLetters->prev->itsValue == word1->itsLetters->prev->itsValue || word1->itsLength == 1 ) ) { /* * ...do the conjugation. */ value = -word0->itsLetters->next->itsValue; conjugate_word(word0, value); conjugate_word(word1, value); return TRUE; } else return FALSE; } static void conjugate_word( CyclicWord *word, int value) { Letter *new_letter; /* * This function assumes word->itsLetters is already * pointing to the dummy basepoint Letter. */ if (word->itsLetters->itsValue != 0) uFatalError("conjugate_word", "fundamental_group"); new_letter = NEW_STRUCT(Letter); new_letter->itsValue = value; INSERT_AFTER(new_letter, word->itsLetters); new_letter = NEW_STRUCT(Letter); new_letter->itsValue = -value; INSERT_BEFORE(new_letter, word->itsLetters); word->itsLength += 2; cancel_inverses_word(word); } static void cancel_inverses( GroupPresentation *group) { /* * This routine cancels subwords of the form "aA". * It works "recursively", so that "bAaBc" gets cancelled properly. */ cancel_inverses_word_list(group->itsRelations); /* * The meridians and longitudes aren't part of the pseudo-Heegaard * diagram, but we want to simplify them anyhow, and similarly * for the expressions for the original generators. */ cancel_inverses_word_list(group->itsMeridians); cancel_inverses_word_list(group->itsLongitudes); cancel_inverses_word_list(group->itsOriginalGenerators); } static void cancel_inverses_word_list( CyclicWord *list) { CyclicWord *word; for (word = list; word != NULL; word = word->next) cancel_inverses_word(word); } static void cancel_inverses_word( CyclicWord *word) { /* * Attempt to verify that no cancellations are possible, * by checking that each letter is not followed by its inverse. * * If some letter is followed by its inverse, cancel them * and reset the loop counter. */ int i; Letter *letter, *dead_letter; /* * Look for adjacent Letters which cancel. * Continue cyclically until we've checked itsLength * consecutive Letters which don't cancel. * * Meridians and longitudes use a temporary "basepoint" * Letter with itsValue == 0. Don't let it cancel with itself * if the word becomes trivial! */ for ( i = 0, letter = word->itsLetters; i < word->itsLength; i++, letter = letter->next) if (letter->itsValue + letter->next->itsValue == 0 && letter->itsValue != 0) { if (word->itsLength == 2) { my_free(letter->next); my_free(letter); word->itsLetters = NULL; word->itsLength = 0; break; } else { /* * Remove the letter following the current one. */ dead_letter = letter->next; REMOVE_NODE(dead_letter); my_free(dead_letter); /* * Back up one space. * (We hit some letter other than the one we're cancelling, * because word->itsLength > 2.) */ letter = letter->prev; /* * Remove what used to be the current letter. */ dead_letter = letter->next; REMOVE_NODE(dead_letter); my_free(dead_letter); /* * Make sure word->itsLetters isn't left dangling. */ word->itsLetters = letter; /* * The word is now two letters shorter. */ word->itsLength -= 2; /* * We want to resume the for(;;) loop at i == 0. * If we set i to -1, the i++ in the for(;;) statement * will immediately increment i to 0. */ i = -1; } } } static void handle_slide( GroupPresentation *group, int a, int b) { /* * Visualize the pseudo-Heegaard diagram as in "Visualizing * the pseudo-Heegaard diagram" at the top of this file. * If some word contains the substring "ab", this means that * a curve runs from the disk A- to the disk B+. At this point * it may be helpful to draw yourself an illustration showing * a curve running into A+, out of A-, into B+ and out of B-. * For future reference it will also be helpful to draw a few * other random curves going in or out of A-. Slide the disk A- * into B+ so that it comes out at B-, and redraw your sketch to * show this. Note that the existence of the substring "ab" * guarantees that we can do this "handle slide" without crossing * any curves. Now look what's happened to the random curves * coming in an out of A-. Each curve which comes out of A- now * goes into B- and out of B+ before proceeding on its way. * Algebraically, this means each occurrence of the letter "a" * is replaced by "aB". Each curve going into A- now goes first * into B+ and out of B-. Algebraically, this means that "A" * is replaced by "bA". Symbolically, * * "a" -> "aB" * "A" -> "bA" * * Note that the original substring "ab" which got us started * is replaced by "aBb", which simplifies to "a". */ /* * We assume that the calling function has already checked * that "ab" occurs as a substring of some word. */ /* * a and b should be distinct generators. */ if (a == b || a == -b) uFatalError("handle_slide", "fundamental_group"); /* * Fix up the relations. */ handle_slide_word_list(group->itsRelations, a, b); /* * The meridians and longitudes aren't part of the pseudo-Heegaard * diagram, but we want to keep track of them anyhow, and similarly * for the expressions for the original generators. */ handle_slide_word_list(group->itsMeridians, a, b); handle_slide_word_list(group->itsLongitudes, a, b); handle_slide_word_list(group->itsOriginalGenerators, a, b); /* * Fix up the matrices. */ handle_slide_matrices(group, a, b); /* * Cancel any pairs of inverses we may have created. */ cancel_inverses(group); } static void handle_slide_word_list( CyclicWord *list, int a, int b) { CyclicWord *word; for (word = list; word != NULL; word = word->next) handle_slide_word(word, a, b); } static void handle_slide_word( CyclicWord *word, int a, int b) { Letter *letter, *new_letter; if (word->itsLength > 0) { letter = word->itsLetters; do { if (letter->itsValue == a) { new_letter = NEW_STRUCT(Letter); new_letter->itsValue = -b; INSERT_AFTER(new_letter, letter); word->itsLength++; } if (letter->itsValue == -a) { new_letter = NEW_STRUCT(Letter); new_letter->itsValue = b; INSERT_BEFORE(new_letter, letter); word->itsLength++; } letter = letter->next; } while (letter != word->itsLetters); } } static void handle_slide_matrices( GroupPresentation *group, int a, int b) { /* * Initially, generators a, b, etc. may be visualized as curves * in the interior of the handlebody which pass once around their * respective handles. Now assume we are doing a handle slide * as described in handle_slide() above. Generator b is not affected, * but the curve (in the interior of the handle body) corresponding * to generator a gets dragged across handle b. In otherwords, it * takes the trip a'B, where a' is the loop which passes once * around handle a in its new location, avoiding all other handles. * Symbolically, a = a'B. This corresponds to the matrix equation * M(a) = M(a') M(B). (As explained in fg_word_to_matrix(), the * order of the factors is reversed for two different reasons, so * it doesn't get reversed at all.) Solve for M(a') = M(a) M(B)^-1 * or M(a') = M(a) M(b). */ O31Matrix temp; /* * Split into four cases, according to whether a and b * are positive or negative. */ if (a > 0) { if (b > 0) { /* * Use M(a') = M(a) M(b). */ o31_product( group->itsMatrices[a-1], group->itsMatrices[b-1], group->itsMatrices[a-1]); } else /* b < 0 */ { /* * Use M(a') = M(a) [M(B)^-1]. */ o31_invert( group->itsMatrices[(-b)-1], temp); o31_product( group->itsMatrices[a-1], temp, group->itsMatrices[a-1]); } } else /* a < 0 */ { if (b > 0) { /* * Use M(A') = [M(b)^-1] M(A) */ o31_invert( group->itsMatrices[b-1], temp); o31_product( temp, group->itsMatrices[(-a)-1], group->itsMatrices[(-a)-1]); } else /* b < 0 */ { /* * Use M(A') = M(B) M(A) */ o31_product( group->itsMatrices[(-b)-1], group->itsMatrices[(-a)-1], group->itsMatrices[(-a)-1]); } } } static void cancel_handles( GroupPresentation *group, CyclicWord *word) { /* * cancel_handles() cancels a relation of length one (a 2-handle) * with its corresponding generator (a 1-handle). This is a * geometric operation, in the sense that it corresponds to a * simplification of the pseudo-Heegaard diagram discussed in * "Visualizing the fundamental group" at the top of this file. * The proof is trivial when you visualize the pseudo-Heegaard diagram * as in "Visualizing the pseudo-Heegaard diagram" at the top of * this file (but oddly, the proof is less obvious when you visualize * the pseudo-Heegaard diagram as a handlebody). * * Comment: The 1-handle must be orientable, even in a nonorientable * manifold. Proof #1: The boundary of the thickened disk is a * cylinder, so the handle's "A+" and "A-" disks must be identified * in an orientation-preserving way. Proof #2: The handle's core * curve is homotopically trivial, so it must be orientation-preserving. */ int dead_generator; /* * Double check that the word has length one. */ if (word->itsLength != 1) uFatalError("cancel_handles", "fundamental_group"); /* * Which generator is being cancelled? */ dead_generator = ABS(word->itsLetters->itsValue); /* * Remove the word from the GroupPresentation, and decrement * group->itsNumRelations. */ remove_word(group, word); /* * Remove all occurences of the generator from all other words. * Note that even if a word becomes empty, it is *not* deleted, * because empty words have geometrical significance in the * pseudo-Heegaard diagram. For example, S^2 X S^1 has a presentation * with one generator and one empty word. */ remove_generator(group, dead_generator); /* * The highest numbered generator should assume the index of the * dead_generator, to keep the indexing contiguous. */ renumber_generator(group, group->itsNumGenerators, dead_generator); o31_copy( group->itsMatrices[dead_generator - 1], group->itsMatrices[group->itsNumGenerators - 1]); group->itsNumGenerators--; /* * Cancel any adjacent inverses which may have been created. */ cancel_inverses(group); } static void remove_word( GroupPresentation *group, CyclicWord *word) { CyclicWord **list; list = &group->itsRelations; while (*list != NULL) { if (*list == word) { *list = word->next; free_cyclic_word(word); group->itsNumRelations--; return; } list = &(*list)->next; } uFatalError("remove_word", "fundamental_group"); } static void remove_generator( GroupPresentation *group, int dead_generator) { remove_generator_from_list( group->itsRelations, dead_generator); /* * Strictly speaking, the peripheral curves and the original * generator expressions are not part of the pseudo-Heegaard * diagram, but we want to keep them up to date. */ remove_generator_from_list( group->itsMeridians, dead_generator); remove_generator_from_list( group->itsLongitudes, dead_generator); remove_generator_from_list( group->itsOriginalGenerators, dead_generator); } static void remove_generator_from_list( CyclicWord *list, int dead_generator) { CyclicWord *word; for (word = list; word != NULL; word = word->next) remove_generator_from_word(word, dead_generator); } static void remove_generator_from_word( CyclicWord *word, int dead_generator) { /* * We want to keep looking at Letters until the number of non-removed * Letters equals the length of the word. A "non-removed Letter" is * one which we have examined and found to be something other than * dead_generator. */ Letter *letter; int nonremoved; for ( letter = word->itsLetters, nonremoved = 0; nonremoved < word->itsLength; ) { if (letter->itsValue == dead_generator || letter->itsValue == -dead_generator) { if (word->itsLength > 1) { word->itsLetters = letter->next; REMOVE_NODE(letter); my_free(letter); letter = word->itsLetters; } else { word->itsLetters = NULL; my_free(letter); } word->itsLength--; } else { nonremoved++; letter = letter->next; } } } static void renumber_generator( GroupPresentation *group, int old_index, int new_index) { /* * Each occurrence of the old_index should be replaced * with the new_index. */ renumber_generator_on_word_list(group->itsRelations, old_index, new_index); /* * Strictly speaking, the peripheral curves and the original * generator expressions are not part of the pseudo-Heegaard * diagram, but we want to keep them up to date. */ renumber_generator_on_word_list(group->itsMeridians, old_index, new_index); renumber_generator_on_word_list(group->itsLongitudes, old_index, new_index); renumber_generator_on_word_list(group->itsOriginalGenerators, old_index, new_index); } static void renumber_generator_on_word_list( CyclicWord *list, int old_index, int new_index) { CyclicWord *word; for (word = list; word != NULL; word = word->next) renumber_generator_in_word(word, old_index, new_index); } static void renumber_generator_in_word( CyclicWord *word, int old_index, int new_index) { Letter *letter; int i; for ( letter = word->itsLetters, i = 0; i < word->itsLength; letter = letter->next, i++) { if (letter->itsValue == old_index) letter->itsValue = new_index; if (letter->itsValue == - old_index) letter->itsValue = - new_index; } } int fg_get_num_generators( GroupPresentation *group) { return group->itsNumGenerators; } Boolean fg_integer_fillings( GroupPresentation *group) { return group->integer_fillings; } FuncResult fg_word_to_matrix( GroupPresentation *group, int *word, O31Matrix result_O31, MoebiusTransformation *result_Moebius) { /* * In an abstract presentation of the fundamental group, * the word "ab" means "do 'a', then do 'b'". However, * when we map elements of the abstract group to elements * of the group of covering transformations, we find that * the isometry corresponding to "ab" is obtained by first * doing 'b', then 'a'. (Try it out in the case where * 'a' and 'b' are generators of the fundamental group * of an octagon-with-opposite-sides-glued, and all will * be clear.) Matrix products are read right-to-left, * so the isometry "'b' followed by 'a'" is M(a)M(b), * where M(a) and M(b) are the matrices representing * 'a' and 'b'. In summary, the order of the factors * is reversed for two different reasons, so therefore * it remains the same: "ab" maps to M(a)M(b). */ /* * Alan Reid and Craig Hodgson have pointed out that sometimes one * wants to look at the trace of a product of generators in SL(2,C) * (not just PSL(2,C)). To accomodate this, fg_word_to_matrix() now * computes the value of a word not only as an O31Matrix, but also * as a MoebiusTransformation, taking care to do the inversions and * matrix multiplications in SL(2,C). * * A more ambitious project would be to provide a consistent * representation into SL(2,C) whenever one is possible, but this * has *not* been implemented in the current code. * * JRW 96/1/6 */ /* * When input word is not valid, returns func_bad_input instead * of posting a fatal error. JRW 99/10/30 */ MoebiusTransformation *theMoebiusGenerators, theMoebiusFactor; O31Matrix theO31Factor; theMoebiusGenerators = NEW_ARRAY(group->itsNumGenerators, MoebiusTransformation); O31_array_to_Moebius_array(group->itsMatrices, theMoebiusGenerators, group->itsNumGenerators); o31_copy (result_O31, O31_identity ); Moebius_copy(result_Moebius, &Moebius_identity); for ( ; *word != 0; word++) { /* * Find the matrix corresponding to this letter in the word... */ if (*word > 0 && *word <= group->itsNumGenerators) { o31_copy ( theO31Factor, group->itsMatrices [*word - 1]); Moebius_copy(&theMoebiusFactor, &theMoebiusGenerators[*word - 1]); } else if (*word < 0 && *word >= - group->itsNumGenerators) { o31_invert ( group->itsMatrices [-(*word) - 1], theO31Factor ); Moebius_invert(&theMoebiusGenerators[-(*word) - 1], &theMoebiusFactor); } else { my_free(theMoebiusGenerators); return func_bad_input; } /* * ...and right-multiply it onto the matrix_generator. */ o31_product (result_O31, theO31Factor, result_O31); Moebius_product(result_Moebius, &theMoebiusFactor, result_Moebius); } my_free(theMoebiusGenerators); return func_OK; } int fg_get_num_relations( GroupPresentation *group) { return group->itsNumRelations; } int fg_get_num_cusps( GroupPresentation *group) { return group->itsNumCusps; } int *fg_get_relation( GroupPresentation *group, int which_relation) { if (which_relation < 0 || which_relation >= group->itsNumRelations) uFatalError("fg_get_relation", "fundamental_group"); return fg_get_cyclic_word(group->itsRelations, which_relation); } int *fg_get_meridian( GroupPresentation *group, int which_cusp) { if (which_cusp < 0 || which_cusp >= group->itsNumCusps) uFatalError("fg_get_meridian", "fundamental_group"); return fg_get_cyclic_word(group->itsMeridians, which_cusp); } int *fg_get_longitude( GroupPresentation *group, int which_cusp) { if (which_cusp < 0 || which_cusp >= group->itsNumCusps) uFatalError("fg_get_longitude", "fundamental_group"); return fg_get_cyclic_word(group->itsLongitudes, which_cusp); } int *fg_get_original_generator( GroupPresentation *group, int which_generator) { if (which_generator < 0 || which_generator >= group->itsNumOriginalGenerators) uFatalError("fg_get_original_generator", "fundamental_group"); return fg_get_cyclic_word(group->itsOriginalGenerators, which_generator); } static int *fg_get_cyclic_word( CyclicWord *list, int which_relation) { int i; CyclicWord *word; Letter *letter; int *result; word = list; for (i = 0; i < which_relation; i++) if (word != NULL) word = word->next; if (word == NULL) uFatalError("fg_get_cyclic_word", "fundamental_group"); result = NEW_ARRAY(word->itsLength + 1, int); for ( i = 0, letter = word->itsLetters; i < word->itsLength; i++, letter = letter->next) { result[i] = letter->itsValue; } result[word->itsLength] = 0; return result; } void fg_free_relation( int *relation) { my_free(relation); } void free_group_presentation( GroupPresentation *group) { if (group != NULL) { if (group->itsMatrices != NULL) my_free(group->itsMatrices); free_word_list(group->itsRelations); free_word_list(group->itsMeridians); free_word_list(group->itsLongitudes); free_word_list(group->itsOriginalGenerators); my_free(group); } } static void free_word_list( CyclicWord *aWordList) { CyclicWord *theDeadWord; while (aWordList != NULL) { theDeadWord = aWordList; aWordList = aWordList->next; free_cyclic_word(theDeadWord); } } static void free_cyclic_word( CyclicWord *aCyclicWord) { Letter *theDeadLetter; while (aCyclicWord->itsLength > 1) { theDeadLetter = aCyclicWord->itsLetters; aCyclicWord->itsLetters = aCyclicWord->itsLetters->next; REMOVE_NODE(theDeadLetter); my_free(theDeadLetter); aCyclicWord->itsLength--; } /* * The preceding code would typically work fine right down to * aCyclicWord->itsLength == 1, but I don't want to write code which * depends on the implementation of the REMOVE_NODE() macro, so I'll * go ahead and make a special case for aCyclicWord->itsLength == 1. */ if (aCyclicWord->itsLength == 1) my_free(aCyclicWord->itsLetters); my_free(aCyclicWord); } snappea-3.0d3/SnapPeaKernel/code/gcd.c0100444000175000017500000000707007041100501015556 0ustar babbab/* * gcd.c * * This file contains the kernel functions * * long int gcd(long int a, long int b); * long int euclidean_algorithm(long int m, long int n, long int *a, long int *b); * long int Zq_inverse(long int p, long int q); * * gcd() returns the greatest common divisor of two long integers, * at least one of which is nonzero. * * euclidean_algorithm() returns the greatest common divisor of two long * integers m and n, and also finds long integers a and b such that * am + bn = gcd(m,n). The integers m and n may be negative, but cannot * both be zero. * * Zq_inverse() returns the inverse of p in the ring Z/q. Assumes p and q * are relatively prime integers satisfying 0 < p < q. * * 97/3/31 gcd() modified to accept negative integers. */ #include "kernel.h" long int gcd( long int a, long int b) { a = ABS(a); b = ABS(b); if (a == 0) { if (b == 0) uFatalError("gcd", "gcd"); else return b; } while (TRUE) { if ((b = b%a) == 0) return a; if ((a = a%b) == 0) return b; } } long int euclidean_algorithm( long int m, long int n, long int *a, long int *b) { /* * Given two long integers m and n, use the Euclidean algorithm to * find integers a and b such that a*m + b*n = g.c.d.(m,n). * * Recall the Euclidean algorithm is to keep subtracting the * smaller of {m, n} from the larger until one of them reaches * zero. At that point the other will equal the g.c.d. * * As the algorithm progresses, we'll use the coefficients * mm, mn, nm, and nn to express the current values of m and n * in terms of the original values: * * current m = mm*(original m) + mn*(original n) * current n = nm*(original m) + nn*(original n) */ long int mm, mn, nm, nn, quotient; /* * Begin with a quick error check. */ if (m == 0 && n == 0) uFatalError("euclidean_algorithm", "gcd"); /* * Initially we have * * current m = 1 (original m) + 0 (original n) * current n = 0 (original m) + 1 (original n) */ mm = nn = 1; mn = nm = 0; /* * It will be convenient to work with nonnegative m and n. */ if (m < 0) { m = - m; mm = -1; } if (n < 0) { n = - n; nn = -1; } while (TRUE) { /* * If m is zero, then n is the g.c.d. and we're done. */ if (m == 0) { *a = nm; *b = nn; return n; } /* * Let n = n % m, and adjust the coefficients nm and nn accordingly. */ quotient = n / m; nm -= quotient * mm; nn -= quotient * mn; n -= quotient * m; /* * If n is zero, then m is the g.c.d. and we're done. */ if (n == 0) { *a = mm; *b = mn; return m; } /* * Let m = m % n, and adjust the coefficients mm and mn accordingly. */ quotient = m / n; mm -= quotient * nm; mn -= quotient * nn; m -= quotient * n; } /* * We never reach this point. */ } long int Zq_inverse( long int p, long int q) { long int a, b, g; /* * Make sure 0 < p < q. */ if (p <= 0 || p >= q) uFatalError("Zq_inverse", "gcd"); /* * Find a and b such that ap + bq = gcd(p,q) = 1. */ g = euclidean_algorithm(p, q, &a, &b); /* * Check that p and q are relatively prime. */ if (g != 1) uFatalError("Zq_inverse", "gcd"); /* * ap + bq = 1 * => ap = 1 (mod q) * => The inverse of p in Z/q is a. * * Normalize a to the range (0, q). * * [My guess is that a must always fall in the range -q < a < q, * in which case the follwing code would simplify to * * if (a < 0) * a += q; * * but I haven't worked out a proof.] */ while (a < 0) a += q; while (a > q) a -= q; return a; } snappea-3.0d3/SnapPeaKernel/code/orient.c0100444000175000017500000005203307040650110016325 0ustar babbab/* * orient.c * * This file provides the functions * * void orient(Triangulation *manifold); * void reorient(Triangulation *manifold); * void fix_peripheral_orientations(Triangulation *manifold); * * orient() attempts to consistently orient the Tetrahedra of the * Triangulation *manifold. It begins with manifold->tet_list_begin.next * and recursively reorients its neighbors as necessary until either * all the tetrahedra are consistently oriented, or it discovers that * the manifold is nonorientable. (In particular, the Orientation of * an orientable Triangulation will match the initial Orientation of * manifold->tet_list_begin.next, which is important in subdivide.c and * terse_triangulation.c.) orient() sets the manifold->orientability * field to oriented_manifold or nonorientable_manifold, as appropriate. * Its method for reorienting a tetrahedron is to swap the indices * of vertices 2 and 3, and adjust the relevant fields accordingly * (including the gluing fields of neighboring tetrahedra). * It is aware of the fact that some fields may not yet be set, * and doesn't try to modify data structures if they aren't present * (all it requires are the neighbor and gluing fields). * * If the manifold is orientable, then * * (1) All peripheral curves (i.e. the meridians and longitudes) * are transferred to the right_handed sheets of the orientation * double covers of the cusps. This makes their holonomies complex * analytic functions of the tetrahedron shapes, rather than * complex conjugates of complex analytic functions. (However, * the {meridian, longitude} pair may no longer be right-handed. * To fix that, call fix_peripheral_orientations().) * * (2) All edge_orientations are set to right_handed. * * * orient() and orient_edge_classes() may be called in either order. * * If orient() is called first, then * If the manifold is orientable, * orient() will set all edge_orientations to right_handed, and then * orient_edge_classes() will (redundantly) do the same thing. * If the manifold is nonorientable, * orient() won't set the edge_orientations, but * orient_edge_classes() will. * * If orient_edge_classes() is called first, then * If the manifold is orientable, * orient_edge_classes() will assign an arbitrary Orientation * to each EdgeClass, and then * orient() will overwrite it with the right_handed Orientation. * If the manifold is nonorientable, * orient_edge_classes() will assign an arbitrary Orientation * to each EdgeClass, and * orient() will preserve it. * * * orient() could be made available to the UI with no modifications * beyond moving the prototype. * * * reorient() reverses the orientation of all the Tetrahedra * in the Triangulation, by swapping VertexIndices 2 and 3 as described * above. If the manifold is orientable, reorient() also * transfers the peripheral curves to the right_handed sheets of the * double covers and sets all edge_orientations to right_handed, as * described above. It reverses the directions of all meridians, so the * peripheral curves will continue to adhere to the standard orientation * convention. reorient() is intended for oriented manifolds, but * nothing terrible will happen if you pass it a nonorientable manifold. * * fix_peripheral_orientations() makes sure each {meridian, longitude} * pairs obeys the right-hand rule. It should be called only for * orientable manifolds, typically following a call to orient(). * * Note to myself: Eventually I may want to make transfer_peripheral_curves() * responsible for making the peripheral curves adhere to the standard * orientation convention. If this is done, reorient() won't have to * explicitly change the directions of meridians. (However, this * change would require that orient() check whether Cusps are present.) */ #include "kernel.h" static void reverse_orientation(Tetrahedron *tet); static void renumber_neighbors_and_gluings(Tetrahedron *tet); static void renumber_cusps(Tetrahedron *tet); static void renumber_peripheral_curves(Tetrahedron *tet); static void renumber_edge_classes(Tetrahedron *tet); static void renumber_shapes(Tetrahedron *tet); static void renumber_shape_histories(Tetrahedron *tet); static void renumber_one_part(ComplexWithLog edge_parameters[3]); static void swap_rows(int m[4][4], int a, int b); static void swap_columns(int m[4][4], int a, int b); static void swap_sheets(int m[2][4][4]); static void transfer_peripheral_curves(Triangulation *manifold); static void make_all_edge_orientations_right_handed(Triangulation *manifold); static void reverse_all_meridians(Triangulation *manifold); void orient( Triangulation *manifold) { /* * Pick an arbitrary initial Tetrahedron, * and try to extend its orientation to the whole manifold. */ extend_orientation(manifold, manifold->tet_list_begin.next); } void extend_orientation( Triangulation *manifold, Tetrahedron *initial_tet) { Tetrahedron **queue, *tet; int queue_first, queue_last; FaceIndex f; /* * Set all the tet->flag fields to FALSE, * to show that no Tetrahedra have been visited. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->flag = FALSE; /* * Tentatively assume the manifold is orientable. */ manifold->orientability = oriented_manifold; /* * Allocate space for a queue of pointers to the Tetrahedra. * Each Tetrahedron will appear on the queue exactly once, * so an array of length manifold->num_tetrahedra will be just right. */ queue = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); /* * Put the initial Tetrahedron on the queue, * and mark it as visited. */ queue_first = 0; queue_last = 0; queue[0] = initial_tet; queue[0]->flag = TRUE; /* * Start processing the queue. */ do { /* * Pull a Tetrahedron off the front of the queue. */ tet = queue[queue_first++]; /* * Look at the four neighboring Tetrahedra. */ for (f = 0; f < 4; f++) { /* * If the neighbor hasn't been visited... */ if (tet->neighbor[f]->flag == FALSE) { /* * ...reverse its orientation if necessary... */ if (parity[tet->gluing[f]] == orientation_reversing) reverse_orientation(tet->neighbor[f]); /* * ...mark it as visited... */ tet->neighbor[f]->flag = TRUE; /* * ...and put it on the back of the queue. */ queue[++queue_last] = tet->neighbor[f]; } /* * If the neighbor has been visited . . . */ else { /* * ...check whether its orientation is consistent. */ if (parity[tet->gluing[f]] == orientation_reversing) manifold->orientability = nonorientable_manifold; } } } /* * Keep going until either we discover the manifold is nonorientable, * or the queue is exhausted. */ while (manifold->orientability == oriented_manifold && queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); /* * An "unnecessary" (but quick) error check. */ if (manifold->orientability == oriented_manifold && ( queue_first != manifold->num_tetrahedra || queue_last != manifold->num_tetrahedra - 1)) uFatalError("orient", "orient"); /* * Another error check. * We should have oriented a manifold before attempting to * compute the Chern-Simons invariant. */ if (manifold->CS_value_is_known || manifold->CS_fudge_is_known) uFatalError("orient", "orient"); /* * Respect the conventions for peripheral curves and * edge orientations in oriented manifolds. */ if (manifold->orientability == oriented_manifold) { transfer_peripheral_curves(manifold); make_all_edge_orientations_right_handed(manifold); } } /* * reverse_orientation() reverses the orientation of a Tetrahedron * by swapping the indices of vertices 2 and 3. It adjusts all * relevant fields, including the gluing fields of neighboring * tetrahedra. */ static void reverse_orientation( Tetrahedron *tet) { renumber_neighbors_and_gluings(tet); renumber_cusps(tet); renumber_peripheral_curves(tet); renumber_edge_classes(tet); renumber_shapes(tet); renumber_shape_histories(tet); } static void renumber_neighbors_and_gluings( Tetrahedron *tet) { Tetrahedron *temp_neighbor; Permutation temp_gluing; int i, j, d[4], temp_digit; Tetrahedron *nbr_tet; /* * Renumbering the neighbors is easy: we simply swap * neighbor[2] and neighbor[3]. */ temp_neighbor = tet->neighbor[2]; tet->neighbor[2] = tet->neighbor[3]; tet->neighbor[3] = temp_neighbor; /* * Renumbering the gluings is trickier, because three * changes are required: * * Change A: Swap gluing[2] and gluing[3]. * * Change B: Within each gluing of tet, swap the image of * vertex 2 and the image of vertex 3, e.g. 0312 -> 3012. * * Change C: For each gluing of a face (typically of a Tetrahedron * other than tet) that glues to tet, interchange * 2 and 3, e.g. 0312 -> 0213. */ /* * Change A: Swap gluing[2] and gluing[3]. */ temp_gluing = tet->gluing[2]; tet->gluing[2] = tet->gluing[3]; tet->gluing[3] = temp_gluing; /* * Changes B and C are carried out for each of the four gluings of tet. */ for (i = 0; i < 4; i++) { /* * Change B: Swap the image of vertex 2 and the image of vertex 3. */ /* * Unpack the digits of the gluing. */ for (j = 0; j < 4; j++) { d[j] = tet->gluing[i] & 0x3; tet->gluing[i] >>= 2; } /* * Swap the digits in positions 2 and 3. */ temp_digit = d[3]; d[3] = d[2]; d[2] = temp_digit; /* * Repack the digits. */ for (j = 4; --j >= 0; ) { tet->gluing[i] <<= 2; tet->gluing[i] += d[j]; } /* * Change C: Fix up the inverse of tet->gluing[i]. * * If tet->neighbor[i] != tet, we simply write the inverse of * tet->gluing[i] into the appropriate gluing field of the neighbor. * * If tet->neighbor[i] == tet, the simple approach doesn't work * because of the messy interaction between Changes B and C. * Instead, we exploit the fact that tet->gluing[i] is the inverse * of some other gluing of tet, and apply Change C directly to * tet->gluing[i] rather than its inverse. */ nbr_tet = tet->neighbor[i]; if (nbr_tet != tet) /* * Write the inverse directly. */ nbr_tet->gluing[EVALUATE(tet->gluing[i],i)] = inverse_permutation[tet->gluing[i]]; else { /* * Perform Change C on tet->gluing[i]. */ /* * Unpack the digits. */ for (j = 0; j < 4; j++) { d[j] = tet->gluing[i] & 0x3; tet->gluing[i] >>= 2; } /* * Swap 2 and 3 in the images. */ for (j = 0; j < 4; j++) switch (d[j]) { case 0: case 1: /* leave d[j] alone */ break; case 2: d[j] = 3; break; case 3: d[j] = 2; break; } /* * Repack the digits. */ for (j = 4; --j >= 0; ) { tet->gluing[i] <<= 2; tet->gluing[i] += d[j]; } } } } static void renumber_cusps( Tetrahedron *tet) { Cusp *temp_cusp; temp_cusp = tet->cusp[2]; tet->cusp[2] = tet->cusp[3]; tet->cusp[3] = temp_cusp; } static void renumber_peripheral_curves( Tetrahedron *tet) { int i, j; for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { swap_rows (tet->curve[i][j], 2, 3); swap_columns(tet->curve[i][j], 2, 3); } swap_sheets(tet->curve[i]); } } static void renumber_edge_classes( Tetrahedron *tet) { EdgeClass *temp_edge_class; Orientation temp_edge_orientation; EdgeIndex i; temp_edge_class = tet->edge_class[1]; tet->edge_class[1] = tet->edge_class[2]; tet->edge_class[2] = temp_edge_class; temp_edge_class = tet->edge_class[3]; tet->edge_class[3] = tet->edge_class[4]; tet->edge_class[4] = temp_edge_class; for (i = 1; i < 5; i++) if (tet->edge_class[i] != NULL) { tet->edge_class[i]->incident_tet = tet; tet->edge_class[i]->incident_edge_index = i; } temp_edge_orientation = tet->edge_orientation[1]; tet->edge_orientation[1] = tet->edge_orientation[2]; tet->edge_orientation[2] = temp_edge_orientation; temp_edge_orientation = tet->edge_orientation[3]; tet->edge_orientation[3] = tet->edge_orientation[4]; tet->edge_orientation[4] = temp_edge_orientation; for (i = 0; i < 6; i++) tet->edge_orientation[i] = ! tet->edge_orientation[i]; } static void renumber_shapes( Tetrahedron *tet) { /* * Renumber the TetShapes iff they are actually present. */ int i, j; if (tet->shape[complete] != NULL) for (i = 0; i < 2; i++) /* i = complete, filled */ for (j = 0; j < 2; j++) /* j = ultimate, penultimate */ renumber_one_part(tet->shape[i]->cwl[j]); } static void renumber_one_part( ComplexWithLog edge_parameters[3]) { ComplexWithLog temp; int i; /* * Swap the indices on edges 1 and 2. */ temp = edge_parameters[1]; edge_parameters[1] = edge_parameters[2]; edge_parameters[2] = temp; /* * Invert the modulus of each edge parameter, but leave * the angles the same. */ for (i = 0; i < 3; i++) { edge_parameters[i].log.real = - edge_parameters[i].log.real; edge_parameters[i].rect = complex_exp(edge_parameters[i].log); } } static void renumber_shape_histories( Tetrahedron *tet) { int i; ShapeInversion *shape_inversion; for (i = 0; i < 2; i++) for ( shape_inversion = tet->shape_history[i]; shape_inversion != NULL; shape_inversion = shape_inversion->next) switch (shape_inversion->wide_angle) { case 0: shape_inversion->wide_angle = 0; break; case 1: shape_inversion->wide_angle = 2; break; case 2: shape_inversion->wide_angle = 1; break; } } static void swap_rows( int m[4][4], int a, int b) { int j, temp; for (j = 0; j < 4; j++) { temp = m[a][j]; m[a][j] = m[b][j]; m[b][j] = temp; } } static void swap_columns( int m[4][4], int a, int b) { int i, temp; for (i = 0; i < 4; i++) { temp = m[i][a]; m[i][a] = m[i][b]; m[i][b] = temp; } } static void swap_sheets( int m[2][4][4]) { int i, j, temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { temp = m[right_handed][i][j]; m[right_handed][i][j] = m[left_handed ][i][j]; m[left_handed ][i][j] = temp; } } /* * We want the peripheral curves of an oriented manifold to lie * on the right_handed sheets of the orientation double covers * of the cusps. transfer_peripheral_curves() adds the curves * from the left_handed sheet to the right_handed sheet, and * sets the contents of the left_handed sheet to zero. */ static void transfer_peripheral_curves( Triangulation *manifold) { Tetrahedron *tet; PeripheralCurve c; VertexIndex v; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (c = 0; c < 2; c++) for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) { tet->curve[c][right_handed][v][f] += tet->curve[c][left_handed][v][f]; tet->curve[c][left_handed] [v][f] = 0; } } static void make_all_edge_orientations_right_handed( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) tet->edge_orientation[e] = right_handed; } void reorient( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) reverse_orientation(tet); if (manifold->orientability == oriented_manifold) { /* * The peripheral curves haven't gone anywhere, * but the sheets they are on are now considered * the left_handed sheets rather than the right_handed * sheets. So transfer them to what used to be the * left_handed sheets but are now the right_handed sheets. */ transfer_peripheral_curves(manifold); /* * To adhere to the orientation conventions for peripheral curves * (see the documentation at the top of peripheral_curves.c) * we must reverse the directions of all meridians. * * Note that it was the act of transferring the peripheral curves * from the left_handed to right_handed sheets -- note the reversal * of the Tetrahedra -- that caused the violation of the orientaiton * convention. In particular, curves in nonorientable manifold, even * on (double covers of) Klein bottle cusps, still respect the convention. */ reverse_all_meridians(manifold); /* * Adjust the edge orientations, too. */ make_all_edge_orientations_right_handed(manifold); } /* * The Chern-Simons invariant of the manifold will be negated, * and the fudge factor will be different. */ if (manifold->CS_value_is_known) { manifold->CS_value[ultimate] = - manifold->CS_value[ultimate]; manifold->CS_value[penultimate] = - manifold->CS_value[penultimate]; } compute_CS_fudge_from_value(manifold); } static void reverse_all_meridians( Triangulation *manifold) { Tetrahedron *tet; Cusp *cusp; int i, j, k; /* * Change the directions of all meridians. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->curve[M][i][j][k] = - tet->curve[M][i][j][k]; /* * Negating the m coefficient of all Dehn fillings compensates for * the fact that we reversed the meridian, and gives us the same * (oriented) Dehn filling curve as before. However, this curve * will now wind clockwise around the core geodesics, relative to * the new orientation on the manifold. This causes a whole new * solution to be found to the gluing equations. To avoid this, * we reverse the direction of the Dehn filling curve (i.e. we * negate both the m and l coefficients). The net effect is that * we negate the l coefficient. * * This reversal of the Dehn filling curve is not really * necessary, and could be eliminated if it's ever causes problems. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->l = - cusp->l; /* * Adjust all cusp_shapes. * (The current cusp_shape of a filled Cusp will be Zero, but that's OK.) */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = initial, current */ cusp->cusp_shape[i].real = - cusp->cusp_shape[i].real; /* * Adjust the holonomies. * * Changing the orientation of the manifold negates the imaginary * parts of log(H(m)) and log(H(l)). * * But we also reversed the direction of the meridian, which * negates both the real and imaginary parts of log(H(m)), so * the net effect on log(H(m)) is that its real part is negated. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { cusp->holonomy[i][M].real = - cusp->holonomy[i][M].real; cusp->holonomy[i][L].imag = - cusp->holonomy[i][L].imag; } } void fix_peripheral_orientations( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; FaceIndex f; Cusp *cusp; /* * This function should get called only for orientable manifolds. */ if (manifold->orientability != oriented_manifold) uFatalError("fix_peripheral_orientations", "orient"); /* * Compute the intersection number of the meridian and longitude. */ copy_curves_to_scratch(manifold, 0, FALSE); copy_curves_to_scratch(manifold, 1, FALSE); compute_intersection_numbers(manifold); /* * Reverse the meridian on cusps with intersection_number[L][M] == -1. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (v = 0; v < 4; v++) if (tet->cusp[v]->intersection_number[L][M] == -1) /* which side of the vertex */ for (f = 0; f < 4; f++) if (v != f) { tet->curve[M][right_handed][v][f] = - tet->curve[M][right_handed][v][f]; if (tet->curve[M][left_handed][v][f] != 0.0 || tet->curve[L][left_handed][v][f] != 0.0) uFatalError("fix_peripheral_orientations", "orient"); } /* * When we reverse the meridian we must also negate the meridional * Dehn filling coefficient in order to maintain the same (oriented) * Dehn filling curve as before. However, this Dehn filling curve * will wind clockwise around the core geodesics, relative to * the global orientation on the manifold (because the global * orientation disagrees with the local orientation we had been using * on the nonorientable manifold's torus cusp). This forces a whole * new solution to be found to the gluing equations. To avoid this, * we reverse the direction of the Dehn filling curve (i.e. we * negate both the m and l coefficients). The net effect is that * we negate the l coefficient. * * This reversal of the Dehn filling curve is not really * necessary, and could be eliminated if it's ever causes problems. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->intersection_number[L][M] == -1) cusp->l = - cusp->l; } snappea-3.0d3/SnapPeaKernel/code/gluing_equations.c0100444000175000017500000003536506742675501020436 0ustar babbab/* * gluing_equations.c * * This file provides the function * * void compute_gluing_equations(Triangulation *manifold); * * which the function do_Dehn_filling() in hyperbolic_structure.c calls * to compute the edge and cusp equations and their derivatives. * It computes complex gluing equations for oriented manifolds, and * real gluing equations for nonoriented manifolds. It assumes that * space for the equations has already been assigned to the cusps and * edges, and that a coordinate system has been chosen for each * tetrahedron (cf. allocate_equations() and choose_coordinate_system() * in hyperbolic_structures.c). */ #include "kernel.h" static void initialize_gluing_equations(Triangulation *manifold); static void compute_derivative(Triangulation *manifold); static void compute_rhs(Triangulation *manifold); void compute_gluing_equations( Triangulation *manifold) { compute_holonomies(manifold); compute_edge_angle_sums(manifold); initialize_gluing_equations(manifold); compute_derivative(manifold); compute_rhs(manifold); } static void initialize_gluing_equations( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; int i; /* * Initialize edge equations. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) for (i = 0; i < manifold->num_tetrahedra; i++) if (manifold->orientability == oriented_manifold) edge->complex_edge_equation[i] = Zero; else { edge->real_edge_equation_re[2*i] = 0.0; edge->real_edge_equation_re[2*i + 1] = 0.0; edge->real_edge_equation_im[2*i] = 0.0; edge->real_edge_equation_im[2*i + 1] = 0.0; } /* * Initialize cusp equations. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < manifold->num_tetrahedra; i++) if (manifold->orientability == oriented_manifold) cusp->complex_cusp_equation[i] = Zero; else { cusp->real_cusp_equation_re[2*i] = 0.0; cusp->real_cusp_equation_re[2*i + 1] = 0.0; cusp->real_cusp_equation_im[2*i] = 0.0; cusp->real_cusp_equation_im[2*i + 1] = 0.0; } } /* * The coordinate system for the parameter space of each * tetrahedron has already been chosen as explained in * the comment preceding the function choose_coordinate_system() * in the file hyperbolic_structure.c. The derivatives computed * in that comment may be expressed as * * d(log z0) d(log z0) d(log z0) -1 * --------- = 1 --------- = -z2 --------- = -- * d(log z0) d(log z1) d(log z2) z1 * * d(log z1) -1 d(log z1) d(log z1) * --------- = -- --------- = 1 --------- = -z0 * d(log z0) z2 d(log z1) d(log z2) * * d(log z2) d(log z2) -1 d(log z2) * --------- = -z1 --------- = -- --------- = 1 * d(log z0) d(log z1) z0 d(log z2) * * compute_derivative() uses these forms to compute the entries * of the derivative matrix. If the manifold is oriented, these complex * numbers are added directly to the appropriate entries in the matrix. * If the manifold is unoriented, each complex number (a + bi) is * converted to a 2 x 2 real matrix * * a -b * * b a * * This matrix mimics the action of the complex derivative. That is, * (a + bi)(dx + i dy) = (a dx - b dy) + i(b dx + a dy), and * * | a dx - b dy | | a -b | | dx | * | | = | | | | * | b dx + a dy | | b a | | dy | * * If the Tetrahedron is seen as right_handed by its EdgeClass, then * the above matrix is added directly to the appropriate 2 x 2 block * in the derivative matrix. If the Tetrahedron is seen as left_handed * by it EdgeClass, then we must account for the fact that the EdgeClass * sees the conjugate-inverse of the edge parameter. That is, the * imaginary part of the log (i.e. the angle) will be the same, but * the real part of the log (i.e. the compression/expansion factor) * will be negated. We therefore use the following matrix instead. * * -a b * * b a * */ static void compute_derivative( Triangulation *manifold) { Tetrahedron *tet; Complex z[3], d[3], *eqn_coef = NULL, dz[2]; EdgeIndex e; VertexIndex v; FaceIndex initial_side, terminal_side; int init[2][2], term[2][2]; double m, l, a, b, *eqn_coef_00 = NULL, *eqn_coef_01 = NULL, *eqn_coef_10 = NULL, *eqn_coef_11 = NULL; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Note the three edge parameters. */ for (i = 0; i < 3; i++) z[i] = tet->shape[filled]->cwl[ultimate][i].rect; /* * Set the derivatives of log(z0), log(z1) and log(z2) * with respect to the given coordinate system, as * indicated by the above table. */ switch (tet->coordinate_system) { case 0: d[0] = One; d[1] = complex_div(MinusOne, z[2]); d[2] = complex_minus(Zero, z[1]); break; case 1: d[0] = complex_minus(Zero, z[2]); d[1] = One; d[2] = complex_div(MinusOne, z[0]); break; case 2: d[0] = complex_div(MinusOne, z[1]); d[1] = complex_minus(Zero, z[0]); d[2] = One; break; } /* * Record this tetrahedron's contribution to the edge equations. */ for (e = 0; e < 6; e++) /* Look at each of the six edges. */ { /* * Find the matrix entry(ies) corresponding to the * derivative of the edge equation with respect to this * tetrahedron. If the manifold is oriented it will be * a single entry in the complex matrix. If the manifold * is unoriented it will be a 2 x 2 block in the real matrix. */ if (manifold->orientability == oriented_manifold) eqn_coef = &tet->edge_class[e]->complex_edge_equation[tet->index]; else { eqn_coef_00 = &tet->edge_class[e]->real_edge_equation_re[2 * tet->index]; eqn_coef_01 = &tet->edge_class[e]->real_edge_equation_re[2 * tet->index + 1]; eqn_coef_10 = &tet->edge_class[e]->real_edge_equation_im[2 * tet->index]; eqn_coef_11 = &tet->edge_class[e]->real_edge_equation_im[2 * tet->index + 1]; } /* * Add in the derivative of the log of the edge parameter * with respect to the chosen coordinate system. Please * see the comment preceding this function for details. */ if (manifold->orientability == oriented_manifold) *eqn_coef = complex_plus(*eqn_coef, d[edge3[e]]); else { /* * These are the same a and b as in the comment * preceding this function. */ a = d[edge3[e]].real; b = d[edge3[e]].imag; if (tet->edge_orientation[e] == right_handed) { *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 += b; *eqn_coef_11 += a; } else { *eqn_coef_00 -= a; *eqn_coef_01 += b; *eqn_coef_10 += b; *eqn_coef_11 += a; } } } /* * Record this tetrahedron's contribution to the cusp equations. */ for (v = 0; v < 4; v++) /* Look at each ideal vertex. */ { /* * Note the Dehn filling coefficients on this cusp. * If the cusp is complete, use m = 1.0 and l = 0.0. */ if (tet->cusp[v]->is_complete) { m = 1.0; l = 0.0; } else { m = tet->cusp[v]->m; l = tet->cusp[v]->l; } /* * Find the matrix entry(ies) corresponding to the * derivative of the cusp equation with respect to this * tetrahedron. If the manifold is oriented it will be * a single entry in the complex matrix. If the manifold * is unoriented it will be a 2 x 2 block in the real matrix. */ if (manifold->orientability == oriented_manifold) eqn_coef = &tet->cusp[v]->complex_cusp_equation[tet->index]; else { eqn_coef_00 = &tet->cusp[v]->real_cusp_equation_re[2 * tet->index]; eqn_coef_01 = &tet->cusp[v]->real_cusp_equation_re[2 * tet->index + 1]; eqn_coef_10 = &tet->cusp[v]->real_cusp_equation_im[2 * tet->index]; eqn_coef_11 = &tet->cusp[v]->real_cusp_equation_im[2 * tet->index + 1]; } /* * Each ideal vertex contains two triangular cross sections, * one right_handed and the other left_handed. We want to * compute the contribution of each angle of each triangle * to the holonomy. We begin by considering the right_handed * triangle, looking at each of its three angles. A directed * angle is specified by its initial and terminal sides. * We find the number of strands of the Dehn filling curve * passing from the initial side to the terminal side; * it is m * (number of strands of meridian) * + l * (number of strands of longitude), where (m,l) are * the Dehn filling coefficients (in practice, m and l need * not be integers, but it's simpler to imagine them to be * integers as you try to understand the following code). * The number of strands of the Dehn filling curves passing * from the initial to the terminal side is multiplied by * the derivative of the log of the complex angle, to yield * the contribution to the derivative matrix. If the manifold * is oriented, that complex number is added directly to * the relevant matrix entry. If the manifold is unoriented, * we convert the complex number to a 2 x 2 real matrix * (cf. the comments preceding this function) and add it to * the appropriate 2 x 2 block of the real derivative matrix. * The 2 x 2 matrix for the left_handed triangle is modified * to account for the fact that although the real part of the * derivative of the log (i.e. the compression/expansion * factor) is the same, the imaginary part (i.e. the rotation) * is negated. [Note that in computing the edge equations * the real part was negated, while for the cusp equations * the imaginary part is negated. I will leave an explanation * of the difference as an exercise for the reader.] * * Note that we cannot possibly handle curves on the * left_handed sheet of the orientation double cover of * a cusp of an oriented manifold. The reason is that the * log of the holonomy of the Dehn filling curve is not * a complex analytic function of the shape of the tetrahedron * (it's the complex conjugate of such a function). I.e. * it doesn't have a derivative in the complex sense. This * is why we make the convention that all peripheral curves * in oriented manifolds lie on the right_handed sheet of * the double cover. */ for (initial_side = 0; initial_side < 4; initial_side++) { if (initial_side == v) continue; terminal_side = remaining_face[v][initial_side]; /* * Note the intersection numbers of the meridian and * longitude with the initial and terminal sides. */ for (i = 0; i < 2; i++) { /* which curve */ for (j = 0; j < 2; j++) { /* which sheet */ init[i][j] = tet->curve[i][j][v][initial_side]; term[i][j] = tet->curve[i][j][v][terminal_side]; } } /* * For each triangle (right_handed and left_handed), * multiply the number of strands of the Dehn filling * curve running from initial_side to terminal_side * by the derivative of the log of the edge parameter. */ for (i = 0; i < 2; i++) /* which sheet */ dz[i] = complex_real_mult( m * FLOW(init[M][i],term[M][i]) + l * FLOW(init[L][i],term[L][i]), d[ edge3_between_faces[initial_side][terminal_side] ] ); /* * If the manifold is oriented, the Dehn filling curve * must lie of the right_handed sheet of the orientation * double cover (cf. above). Add its contributation to * the cusp equation. */ if (manifold->orientability == oriented_manifold) *eqn_coef = complex_plus(*eqn_coef, dz[right_handed]); /* "else" follows below */ /* * If the manifold is unoriented, treat the right_ and * left_handed sheets separately. Add in the contribution * of the right_handed sheet normally. For the left_handed * sheet, we must account for the fact that even though * the modulus of the derivative (i.e. the expansion/ * contraction factor) is correct, its argument (i.e. the * angle of rotation) is the negative of what it should be. */ else { a = dz[right_handed].real; b = dz[right_handed].imag; *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 += b; *eqn_coef_11 += a; a = dz[left_handed].real; b = dz[left_handed].imag; *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 -= b; *eqn_coef_11 -= a; } } } } } /* * compute_complex_rhs() assumes that compute_holonomies() and * compute_edge_angle_sums() have already been called. */ static void compute_rhs( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; Complex desired_holonomy, current_holonomy, rhs; /* * The right hand side of each equation will be the desired value * of the edge angle sum or the holonomy (depending on whether it's * an edge equation or a cusp equation) minus the current value. * Thus, when the equations are solved and the Shapes of the * Tetrahedra are updated, the edge angle sums and the holonomies * will take on their desired values, to the accuracy of the * linear approximation. */ /* * Set the right hand side (rhs) of each edge equation. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * The desired value of the sum of the logs of the complex * edge parameters is 2 pi i. The current value is * edge->edge_angle_sum. */ rhs = complex_minus(TwoPiI, edge->edge_angle_sum); if (manifold->orientability == oriented_manifold) edge->complex_edge_equation[manifold->num_tetrahedra] = rhs; else { edge->real_edge_equation_re[2 * manifold->num_tetrahedra] = rhs.real; edge->real_edge_equation_im[2 * manifold->num_tetrahedra] = rhs.imag; } } /* * Set the right hand side (rhs) of each cusp equation. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { /* * For complete cusps we want the log of the holonomy of the * meridian to be zero. For Dehn filled cusps we want the * log of the holonomy of the Dehn filling curve to be 2 pi i. */ if (cusp->is_complete) { desired_holonomy = Zero; current_holonomy = cusp->holonomy[ultimate][M]; } else { desired_holonomy = TwoPiI; current_holonomy = complex_plus( complex_real_mult(cusp->m, cusp->holonomy[ultimate][M]), complex_real_mult(cusp->l, cusp->holonomy[ultimate][L]) ); } rhs = complex_minus(desired_holonomy, current_holonomy); if (manifold->orientability == oriented_manifold) cusp->complex_cusp_equation[manifold->num_tetrahedra] = rhs; else { cusp->real_cusp_equation_re[2 * manifold->num_tetrahedra] = rhs.real; cusp->real_cusp_equation_im[2 * manifold->num_tetrahedra] = rhs.imag; } } } snappea-3.0d3/SnapPeaKernel/code/holonomy.c0100444000175000017500000001614506742675501016720 0ustar babbab/* * holonomy.c * * This file provides the functions * * void compute_holonomies(Triangulation *manifold); * void compute_edge_angle_sums(Triangulation *manifold); * * which the functions compute_complex_equations() and * compute_real_equations() in gluing_equations.c call. They store * their results directly into Triangulation *manifold, so whenever * a hyperbolic structure has been found, you may also assume the * holonomies are present. (The edge angle sums will be present too, * but since they'll all be 2 pi i they won't be very interesting.) * * The most accurate holonomies are stored in holonomy[ultimate][M] * and holonomy[ultimate][L]. The fields holonomy[penultimate][M] * and holonomy[penultimate][L] store the holonomies from the * penultimate iteration of Newton's method, for use in estimating * the numerical error. * * * The holonomy of a Klein bottle cusp deserves special discussion. * * (1) The holonomy of the meridian may have a rotational part, but * never has a contraction part. I.e. it's log is pure imaginary. * Proof: the meridian is freely homotopic to it's own inverse. * * (2) The holonomy of the longitude doesn't even make sense. As * you tilt the angle of the curve at the basepoint, the angle * of the next lift down the line rotates in the opposite * direction. The rotational part of the holonomy is not an * isotopy invariant for an orientation-reversing curve. The * contraction is an isotopy invariant, but it's cleaner and * simpler to work with the preimage of the longitude in the * Klein bottle's orientation double cover, which is a torus. * The curve on the double cover must have zero rotational part, * because the orientation-reversing covering transformation * takes the curve to itself. * * These observations imply that a Dehn filling on a Klein bottle cusp * must be of the form (m,0). * * * 96/9/27 I split compute_holonomies() into separate functions * copy_holonomies_ultimate_to_penultimate() and compute_the_holonomies(), * so that other functions (e.g. cover.c) can call compute_the_holonomies() * to compute the penultimate holonomies directly. */ #include "kernel.h" static void copy_holonomies_ultimate_to_penultimate(Triangulation *manifold); void compute_holonomies( Triangulation *manifold) { copy_holonomies_ultimate_to_penultimate(manifold); compute_the_holonomies(manifold, ultimate); } static void copy_holonomies_ultimate_to_penultimate( Triangulation *manifold) { /* * Copy holonomy[ultimate][] into holonomy[penultimate][]. */ Cusp *cusp; int i; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ cusp->holonomy[penultimate][i] = cusp->holonomy[ultimate][i]; } void compute_the_holonomies( Triangulation *manifold, Ultimateness which_iteration) { Cusp *cusp; Tetrahedron *tet; Complex log_z[2]; VertexIndex v; FaceIndex initial_side, terminal_side; int init[2][2], term[2][2]; int i, j; /* * Initialize holonomy[which_iteration][] to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ cusp->holonomy[which_iteration][i] = Zero; /* * Now add the contribution of each tetrahedron. * * The cross section of the ideal vertex v is the union * of two triangles, one with the right_handed orientation * and one with the left_handed orientation (please see the * documentationvat the top of peripheral_curves.c for details). * * This loop is similar to the loop in compute_complex_derivative() * in gluing_equations.c. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) for (initial_side = 0; initial_side < 4; initial_side++) { if (initial_side == v) continue; terminal_side = remaining_face[v][initial_side]; /* * Note the log of the complex edge parameter, * for use with the right_handed triangle. */ log_z[right_handed] = tet->shape[filled]->cwl[which_iteration][ edge3_between_faces[initial_side][terminal_side] ].log; /* * The conjugate of log_z[right_handed] will apply to * the left_handed triangle. */ log_z[left_handed] = complex_conjugate(log_z[right_handed]); /* * Note the intersection numbers of the meridian and * longitude with the initial and terminal sides. */ for (i = 0; i < 2; i++) { /* which curve */ for (j = 0; j < 2; j++) { /* which sheet */ init[i][j] = tet->curve[i][j][v][initial_side]; term[i][j] = tet->curve[i][j][v][terminal_side]; } } /* * holonomy[which_iteration][i] += * FLOW(init[i][right_handed], term[i][right_handed]) * log_z[right_handed] * + FLOW(init[i][left_handed ], term[i][left_handed ]) * log_z[left_handed ]; */ for (i = 0; i < 2; i++) /* which curve */ #if 0 The stupid Symantec C compiler is choking on the following expression. So I am breaking it into two parts to make its work easier. original version: tet->cusp[v]->holonomy[which_iteration][i] = complex_plus( tet->cusp[v]->holonomy[which_iteration][i], complex_plus( complex_real_mult( FLOW(init[i][right_handed], term[i][right_handed]), log_z[right_handed] ), complex_real_mult( FLOW(init[i][left_handed], term[i][left_handed]), log_z[left_handed] ) ) ); modified version: #else { Complex temp; temp = complex_plus( tet->cusp[v]->holonomy[which_iteration][i], complex_plus( complex_real_mult( FLOW(init[i][right_handed], term[i][right_handed]), log_z[right_handed] ), complex_real_mult( FLOW(init[i][left_handed], term[i][left_handed]), log_z[left_handed] ) ) ); tet->cusp[v]->holonomy[which_iteration][i] = temp; } #endif } } void compute_edge_angle_sums( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; /* * Initialize all edge_angle_sums to zero. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) edge->edge_angle_sum = Zero; /* * Add in the contribution of each edge of each tetrahedron. * * If the EdgeClass sees the Tetrahedron as right_handed, * add in the log of the edge parameter directly. If it sees * it as left_handed, add in the log of the conjugate-inverse * (i.e. add the imaginary part of the log as usual, but subtract * the real part). */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) { tet->edge_class[e]->edge_angle_sum.imag += tet->shape[filled]->cwl[ultimate][edge3[e]].log.imag; if (tet->edge_orientation[e] == right_handed) tet->edge_class[e]->edge_angle_sum.real += tet->shape[filled]->cwl[ultimate][edge3[e]].log.real; else tet->edge_class[e]->edge_angle_sum.real -= tet->shape[filled]->cwl[ultimate][edge3[e]].log.real; } } snappea-3.0d3/SnapPeaKernel/code/homology.c0100444000175000017500000007062206742675501016711 0ustar babbab/* * homology.c * * This file contains the following functions which the kernel * provides for the UI: * * AbelianGroup *homology(Triangulation *manifold); * * AbelianGroup *homology_from_fundamental_group( * GroupPresentation *group); * * If all Dehn filling coefficients are integers, homology() returns * a pointer to the first homology group of *manifold. Otherwise * it returns NULL. Note that homology() will compute homology groups * of orbifolds as well as manifolds. * * 96/12/11 homology() and homology_from_fundamental_group() now * check for overflows, and return NULL if any occur. * * The comments in homology() below describe the algorithm. * * homology() returns NULL if some Dehn filling coefficients * are not integers (or if overflows occur). * * homology_from_fundamental_group() is a variant of homology which * computes the homology by abelianizing a presentation of the * fundamental group. */ #include "kernel.h" /* * To minimize the possibility of overflows, we use long integers instead * of regular integers to do the matrix computations. Take ENTRY_MIN * to be -LONG_MAX instead of LONG_MIN, to minimize the (unlikely) * possibility that negation causes an overflow (i.e. -LONG_MIN * = -(0x80000000) = (0x80000000) = LONG_MIN). */ typedef long int MatrixEntry; #define ENTRY_MAX LONG_MAX #define ENTRY_MIN (-LONG_MAX) /* * The number of meaningful rows and columns in a RelationMatrix are * are given by num_rows and num_columns, respectively. max_rows * records the original number of rows allocated, so we know how many * rows to free at the end. */ typedef struct { int num_rows, num_columns, max_rows; MatrixEntry **relations; } RelationMatrix; static void group_to_relation_matrix(GroupPresentation *group, RelationMatrix *relation_matrix, Boolean *overflow); static void allocate_relation_matrix_from_group(GroupPresentation *group, RelationMatrix *relation_matrix); static void read_relations_from_group(GroupPresentation *group, RelationMatrix *relation_matrix, Boolean *overflow); static void find_relations(Triangulation *manifold, RelationMatrix *relation_matrix, Boolean *overflow); static void allocate_relation_matrix(Triangulation *manifold, RelationMatrix *relation_matrix); static void initialize_relations(RelationMatrix *relation_matrix); static void find_edge_relations(Triangulation *manifold, RelationMatrix *relation_matrix); static void find_cusp_relations(Triangulation *manifold, RelationMatrix *relation_matrix, Boolean *overflow); static void eliminate_generators(RelationMatrix *relation_matrix, Boolean *overflow); static void delete_empty_relations(RelationMatrix *relation_matrix); static void compute_homology_group(RelationMatrix *relation_matrix, AbelianGroup **g, Boolean *overflow); static void add_row_multiple(RelationMatrix *relation_matrix, int src, int dst, MatrixEntry mult, Boolean *overflow); static void add_column_multiple(RelationMatrix *relation_matrix, int src, int dst, MatrixEntry mult, Boolean *overflow); static MatrixEntry safe_addition(MatrixEntry a, MatrixEntry b, Boolean *overflow); static MatrixEntry safe_multiplication(MatrixEntry a, MatrixEntry b, Boolean *overflow); static void free_relations(RelationMatrix *relation_matrix); AbelianGroup *homology( Triangulation *manifold) { Boolean overflow; RelationMatrix relation_matrix; AbelianGroup *g; /* * Make sure all the Dehn filling coefficients are integers. */ if ( ! all_Dehn_coefficients_are_integers(manifold) ) return NULL; /* * Compute a set of generators. */ choose_generators(manifold, FALSE, FALSE); /* * The overflow flag keeps track of whether an overflow has occurred. */ overflow = FALSE; /* * Read the edge and cusp relations out of the manifold. * Each row of the relation_matrix represents a relation; * each column corresponds to a generator. For example, * the relation X0 - X5 + 2*X8 = 0 would be encoded as the * row 1 0 0 0 0 -1 0 0 2 0. The edge relations are read * into the matrix before the cusp relations because they * are simpler (typically 3 to 5 nonzero entries); this * minimizes the fill-in during the presimplification phase * below. */ find_relations(manifold, &relation_matrix, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); return NULL; } /* * Presimplify the relations. * * First check each relation to see whether some generator * appears with coefficient +1 or -1. If one does, * use the relation to substitute out the generator. * For example, in the matrix * * 0 0 1 0 0 -1 2 0 0 1 0 * 0 1 0 0 0 0 0 1 1 0 0 * 1 0 1 0 0 0 1 0 0 0 1 * 0 1 -1 0 1 0 1 0 0 0 0 * * we can use the first row to eliminate the third generator, * yielding * * 0 0 1 0 0 -1 2 0 0 1 0 * 0 1 0 0 0 0 0 1 1 0 0 <-- no change * 1 0 0 0 0 1 -1 0 0 -1 1 <-- first row was subtracted * 0 1 0 0 1 -1 3 0 0 1 0 <-- first row was added * * We can now eliminate both the first row and the third column. * In practice, the third column would be overwritten with the * contents of the last column to keep the data contiguous. * For the moment the first row is left with all zeros. * * 0 0 0 0 0 0 0 0 0 0 * 0 1 0 0 0 0 0 1 1 0 * 1 0 1 0 0 1 -1 0 0 -1 * 0 1 0 0 1 -1 3 0 0 1 * * The cusp relations (which typically have many nonzero entries) * appear at the bottom of the matrix, after the gluing relations * (which typically have 3 to 5 nonzero entries, regardless of the * size of the manifold). This minimizes the "fill-in" (overwriting * of zeros with nonzero values) during the presimplification process. */ eliminate_generators(&relation_matrix, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); return NULL; } /* * delete_empty_relations() now removes rows containing only * zeros. This includes the rows of zeros created by * eliminate_generators() as well as the rows which were initially * all zeros. */ delete_empty_relations(&relation_matrix); /* * Apply a general algorithm to the relations to compute * the homology group. * * The torsion coefficients of the homology group are the * invariant factors of the relation matrix. (A torsion * coefficient of 0 indicates an infinite cyclic factor.) * That is, one performs row and column operations on the * matrix to put it into diagonal form, then reads the * torsion coefficients directly from the diagonal entries. * It's not hard to convince oneself that both row and column * operations are "legal"; for the full story on invariant * factors, see Hartley & Hawkes' Rings, Modules and Linear * Algebra, Chapman & Hall 1970. * * compute_homology_group() uses the following algorithm. * It performs row and column operations as necessary to * create a matrix element which divides its entire row * and column. For example, if it starts with the element * 10 in the first row (see below), it would notice that 10 does not * divide the 28 in the first row, so it would subtract twice the * second column from the last column, and continue with the 8 * at the end of the first row as its candidate. But this 8 does * not divide the 10 in the first row, so it would subtract the * last column from the second column, and continue with the 2 * as its candidate. * * 0 10 0 28 0 10 0 8 0 2 0 8 * 3 0 2 6 3 0 2 6 3 -6 2 6 * 2 10 4 16 2 10 4 -4 2 14 4 -4 * 0 2 0 8 0 2 0 4 0 -2 0 4 * * The 2 in the first row now divides its entire row, and, as it * turns out, its entire column as well. If it didn't divide each * entry in its column, similar operations would be performed * repeatedly until it divided both row and column. Note that * the absolute value of the candidate decreases at each step, * so this process is sure to succeed in a finite number of steps. * * Once we have an element that divides its row and column, we * perform row operations to clear out its column * * 0 2 0 8 * 3 0 2 30 <-- 3 times the first row was added * 2 0 4 -60 <-- -7 times the first row was added * 0 0 0 12 <-- 1 times the first row was added * * and column operations to clear out its row * * 0 2 0 0 * 3 0 2 30 * 2 0 4 -60 * 0 0 0 12 * * We can now read off a torsion coefficient (in this case, 2), * and eliminate a row and column. In practice, the deleted * row is swapped with the last row, and the deleted column * is overwritten with the contents of the last column, to keep * the matrix contiguous. * * swap rows: overwrite column: reduce matrix dimensions: * * 0 0 0 12 0 12 0 12 0 12 0 * 3 0 2 30 3 30 2 30 3 30 2 * 2 0 4 -60 2 -60 4 -60 2 -60 4 * 0 2 0 0 0 0 0 0 * * We repeat this process until no relations remain. If there * are any generators left over, they will correspond to infinite * cyclic factors of the group (represented as torsion coefficients * of 0). */ compute_homology_group(&relation_matrix, &g, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); free_abelian_group(g); return NULL; } /* * Clean up. */ free_relations(&relation_matrix); return g; } AbelianGroup *homology_from_fundamental_group( GroupPresentation *group) { /* * This function is a variation on the above homology() function. * The difference is that here we use a presentation of the fundamental * group as our starting point, instead of a triangulation. * Please see homology() for a complete explanation of the algorithm. */ Boolean overflow; RelationMatrix relation_matrix; AbelianGroup *g; overflow = FALSE; group_to_relation_matrix(group, &relation_matrix, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); return NULL; } eliminate_generators(&relation_matrix, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); return NULL; } delete_empty_relations(&relation_matrix); compute_homology_group(&relation_matrix, &g, &overflow); if (overflow == TRUE) { free_relations(&relation_matrix); free_abelian_group(g); return NULL; } free_relations(&relation_matrix); return g; } static void group_to_relation_matrix( GroupPresentation *group, RelationMatrix *relation_matrix, Boolean *overflow) { allocate_relation_matrix_from_group(group, relation_matrix); initialize_relations(relation_matrix); read_relations_from_group(group, relation_matrix, overflow); } static void allocate_relation_matrix_from_group( GroupPresentation *group, RelationMatrix *relation_matrix) { int i; relation_matrix->max_rows = fg_get_num_relations (group); relation_matrix->num_rows = fg_get_num_relations (group); relation_matrix->num_columns = fg_get_num_generators(group); if (relation_matrix->max_rows > 0) relation_matrix->relations = NEW_ARRAY(relation_matrix->max_rows, MatrixEntry *); else relation_matrix->relations = NULL; for (i = 0; i < relation_matrix->max_rows; i++) relation_matrix->relations[i] = NEW_ARRAY(relation_matrix->num_columns, MatrixEntry); } static void read_relations_from_group( GroupPresentation *group, RelationMatrix *relation_matrix, Boolean *overflow) { int i, j, *relation; for (i = 0; i < relation_matrix->num_rows; i++) { relation = fg_get_relation(group, i); for (j = 0; relation[j] != 0; j++) { if (ABS(relation[j]) > relation_matrix->num_columns) uFatalError("read_relations_from_group", "homology"); if (relation[j] > 0) { if (relation_matrix->relations[i][ relation[j] - 1] < ENTRY_MAX) relation_matrix->relations[i][ relation[j] - 1]++; else *overflow = TRUE; } else /* relation[j] < 0 */ { if (relation_matrix->relations[i][-relation[j] - 1] > ENTRY_MIN) relation_matrix->relations[i][-relation[j] - 1]--; else *overflow = TRUE; } } fg_free_relation(relation); } } static void find_relations( Triangulation *manifold, RelationMatrix *relation_matrix, Boolean *overflow) { allocate_relation_matrix(manifold, relation_matrix); initialize_relations(relation_matrix); find_edge_relations(manifold, relation_matrix); find_cusp_relations(manifold, relation_matrix, overflow); } static void allocate_relation_matrix( Triangulation *manifold, RelationMatrix *relation_matrix) { int i; /* * There will be, at most, one relation for each EdgeClass and one * relation for each Cusp. We'll worry about the exact number of * relations later. By an Euler characteristic argument, * the number of EdgeClasses equals the number of Tetrahedra. * * relation_matrix->num_rows records the number of active rows, * which is initially zero. * * The number of generators is found in the manifold->num_generators field. */ relation_matrix->max_rows = manifold->num_tetrahedra + manifold->num_cusps; relation_matrix->num_rows = 0; relation_matrix->num_columns = manifold->num_generators; /* * Allocate storage for the relations. */ relation_matrix->relations = NEW_ARRAY(relation_matrix->max_rows, MatrixEntry *); for (i = 0; i < relation_matrix->max_rows; i++) relation_matrix->relations[i] = NEW_ARRAY(relation_matrix->num_columns, MatrixEntry); } static void initialize_relations( RelationMatrix *relation_matrix) { int i, j; for (i = 0; i < relation_matrix->max_rows; i++) for (j = 0; j < relation_matrix->num_columns; j++) relation_matrix->relations[i][j] = 0; } static void find_edge_relations( Triangulation *manifold, RelationMatrix *relation_matrix) { EdgeClass *edge; PositionedTet ptet, ptet0; MatrixEntry *entry; /* * We compute a relation for each EdgeClass in the manifold. * The functions set_left_edge(), veer_left() and same_positioned_tet() * (from positioned_tet.c -- see documentation in kernel_prototypes.h) * walk a PositionedTet around an EdgeClass. At each step, we examine * the generator (see choose_generators.c) dual to ptet.near_face. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { set_left_edge(edge, &ptet0); ptet = ptet0; do { entry = &relation_matrix->relations [relation_matrix->num_rows] [ptet.tet->generator_index[ptet.near_face]]; switch (ptet.tet->generator_status[ptet.near_face]) { case outbound_generator: *entry += 1; break; case inbound_generator: *entry -= 1; break; case not_a_generator: /* do nothing */ break; default: uFatalError("find_edge_relations", "homology"); } veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0) ); relation_matrix->num_rows++; } } static void find_cusp_relations( Triangulation *manifold, RelationMatrix *relation_matrix, Boolean *overflow) { Tetrahedron *tet; VertexIndex vertex; FaceIndex side; Orientation orientation; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (vertex = 0; vertex < 4; vertex++) { if (tet->cusp[vertex]->is_complete) continue; for (side = 0; side < 4; side++) { if (side == vertex) continue; if (tet->generator_status[side] != inbound_generator) continue; for (orientation = 0; orientation < 2; orientation++) /* orientation = right_handed, left_handed */ /* * Here's the version of the code which didn't * check for overflows: * relation_matrix->relations [relation_matrix->num_rows + tet->cusp[vertex]->index] [tet->generator_index[side]] += (MatrixEntry) tet->cusp[vertex]->m * tet->curve[M][orientation][vertex][side] + (MatrixEntry) tet->cusp[vertex]->l * tet->curve[L][orientation][vertex][side]; * * Here's the current version: */ { relation_matrix->relations [relation_matrix->num_rows + tet->cusp[vertex]->index] [tet->generator_index[side]] = safe_addition( relation_matrix->relations [relation_matrix->num_rows + tet->cusp[vertex]->index] [tet->generator_index[side]], safe_multiplication((MatrixEntry) tet->cusp[vertex]->m, (MatrixEntry) tet->curve[M][orientation][vertex][side], overflow), overflow); relation_matrix->relations [relation_matrix->num_rows + tet->cusp[vertex]->index] [tet->generator_index[side]] = safe_addition( relation_matrix->relations [relation_matrix->num_rows + tet->cusp[vertex]->index] [tet->generator_index[side]], safe_multiplication((MatrixEntry) tet->cusp[vertex]->l, (MatrixEntry) tet->curve[L][orientation][vertex][side], overflow), overflow); /* * If an overflow occurs, don't worry about it here. * Nothing bad will happen. Just keep going, and let * the calling function deal with it. */ } } } relation_matrix->num_rows += manifold->num_cusps; } static void eliminate_generators( RelationMatrix *relation_matrix, Boolean *overflow) { int i, j, ii, jj; MatrixEntry mult; /* * eliminate_generators() tries to substitute out as many generators * as possible. To do so, it looks at the rows of the matrix one * at a time, and whenever it finds a row containing +1 or -1 times * a generator, it eliminates that generator. */ for (i = 0; i < relation_matrix->num_rows; i++) for (j = 0; j < relation_matrix->num_columns; j++) if (relation_matrix->relations[i][j] == 1 || relation_matrix->relations[i][j] == -1) { /* * Substitute this generator out of all the other rows. */ for (ii = 0; ii < relation_matrix->num_rows; ii++) if (ii != i && relation_matrix->relations[ii][j]) { mult = (relation_matrix->relations[i][j] == -1) ? relation_matrix->relations[ii][j] : - relation_matrix->relations[ii][j]; add_row_multiple(relation_matrix, i, ii, mult, overflow); if (*overflow == TRUE) return; } /* * Make this row all zeros. */ for (jj = 0; jj < relation_matrix->num_columns; jj++) relation_matrix->relations[i][jj] = 0; /* * Generator j may now be eliminated. Let the highest * numbered generator inherit its index (i.e. overwrite * column j with with contents of the last column), and * decrement num_columns. */ relation_matrix->num_columns--; for (ii = 0; ii < relation_matrix->num_rows; ii++) relation_matrix->relations[ii][j] = relation_matrix->relations[ii][relation_matrix->num_columns]; /* * Break out of the j loop, and move on to the next i. */ break; } } static void delete_empty_relations( RelationMatrix *relation_matrix) { int i, j; Boolean all_zeros; MatrixEntry *temp; /* * Eliminate rows consisting entirely of zeros. */ /* * For each row . . . */ for (i = 0; i < relation_matrix->num_rows; i++) { /* * . . . check whether all entries are zero . . . */ all_zeros = TRUE; for (j = 0; j < relation_matrix->num_columns; j++) if (relation_matrix->relations[i][j]) { all_zeros = FALSE; break; } /* * . . . and if so, eliminate the row. */ if (all_zeros) { /* * Decrement num_rows . . . */ relation_matrix->num_rows--; /* * . . . and swap the zero row with the last active row * of the matrix. (Because num_rows was decremented, * the zero row will be "invisible" in its new location, * but its storage will still be there to be freed when * the time comes.) */ temp = relation_matrix->relations[i]; relation_matrix->relations[i] = relation_matrix->relations[relation_matrix->num_rows]; relation_matrix->relations[relation_matrix->num_rows] = temp; /* * We want to consider the new row we just swapped into position i. * The following i-- will "cancel" the i++ in the for(;;) loop. */ i--; } } } static void compute_homology_group( RelationMatrix *relation_matrix, AbelianGroup **g, Boolean *overflow) { int i, /* i and j are dummy variables, as usual. */ j, ii, /* ii and jj are the indices of the entry which is */ jj; /* supposed to divide all the other entries in its */ /* row and column. */ Boolean all_zeros, desired_entry_has_been_found; MatrixEntry **m, *temp, mult; /* * Allocate space for the AbelianGroup data structure. */ *g = NEW_STRUCT(AbelianGroup); /* * Initialize (*g)->num_torsion_coefficients to zero, and * allocate enough space for the array of torsion coefficients. * We probably won't need all the space we're allocating, but * given that the relation_matrix is presimplified, we shouldn't * be overshooting by too much. * * Note that NEW_ARRAY uses my_malloc(), which will gracefully handle * a request for zero bytes when relation_matrix->num_columns is zero. */ (*g)->num_torsion_coefficients = 0; (*g)->torsion_coefficients = NEW_ARRAY(relation_matrix->num_columns, long int); /* * Let "m" (for "matrix") be a synonym for relation_matrix->relations, * to make the following code more legible. */ m = relation_matrix->relations; /* * Note that the following code will work fine even if * relation_matrix->num_columns == 0. (It will keep * decrementing relation_matrix->num_rows until it * reaches zero.) */ while (relation_matrix->num_rows > 0) { /* * If the last row contains all zeros, eliminate it. * Otherwise, perform matrix operations as necessary * to create a nonzero MatrixEntry which divides both * its row and its column. */ all_zeros = TRUE; ii = relation_matrix->num_rows - 1; for (jj = 0; jj < relation_matrix->num_columns; jj++) if (m[ii][jj]) { all_zeros = FALSE; break; } if (all_zeros) relation_matrix->num_rows--; else { /* * Find an entry (ii,jj) which divides every entry * in its row and column. */ do { desired_entry_has_been_found = TRUE; /* Does entry (ii,jj) divide its row? */ for (j = 0; j < relation_matrix->num_columns; j++) if (m[ii][j] % m[ii][jj]) { mult = - (m[ii][j] / m[ii][jj]); add_column_multiple(relation_matrix, jj, j, mult, overflow); if (*overflow == TRUE) return; jj = j; desired_entry_has_been_found = FALSE; break; } /* Does entry (ii,jj) divide its column? */ for (i = 0; i < relation_matrix->num_rows; i++) if (m[i][jj] % m[ii][jj]) { mult = - (m[i][jj] / m[ii][jj]); add_row_multiple(relation_matrix, ii, i, mult, overflow); if (*overflow == TRUE) return; ii = i; desired_entry_has_been_found = FALSE; break; } } while ( ! desired_entry_has_been_found); /* * Use row ii to eliminate generator jj from all other rows. */ for (i = 0; i < relation_matrix->num_rows; i++) if (i != ii) { mult = - (m[i][jj] / m[ii][jj]); add_row_multiple(relation_matrix, ii, i, mult, overflow); if (*overflow == TRUE) return; } /* * Pretend we also zeroed out the entries in row ii, * except for entry (ii,jj) itself. (There is no need * to do actually write the zeros.) */ /* * Write the torsion coefficient iff it isn't 1, * and increment num_torsion_coefficients. */ if (ABS(m[ii][jj]) != 1) (*g)->torsion_coefficients[(*g)->num_torsion_coefficients++] = ABS(m[ii][jj]); /* * Overwrite column jj with the last column, and decrement num_columns. */ relation_matrix->num_columns--; for (i = 0; i < relation_matrix->num_rows; i++) m[i][jj] = m[i][relation_matrix->num_columns]; /* * Eliminate row ii by swapping it with the last active row, * and decrementing num_rows. */ relation_matrix->num_rows--; temp = m[ii]; m[ii] = m[relation_matrix->num_rows]; m[relation_matrix->num_rows] = temp; } } /* * Now that all the relations are gone, any remaining * generators represent torsion coefficients of zero. */ while (relation_matrix->num_columns--) (*g)->torsion_coefficients[(*g)->num_torsion_coefficients++] = 0L; } static void add_row_multiple( RelationMatrix *relation_matrix, int src, int dst, MatrixEntry mult, Boolean *overflow) { MatrixEntry factor_max, term0, term1, sum; int j; /* * If we weren't concerned about overflows, * this function could be written * for (j = 0; j < relation_matrix->num_columns; j++) relation_matrix->relations[dst][j] += mult * relation_matrix->relations[src][j]; * */ /* * If mult == 0 there's no work to be done, * so return now and avoid having to worry about special cases. */ if (mult == 0) return; /* * Let factor_max be the largest number you can multiply * times "mult" without getting an overflow. * (Division is slow compared to multiplication, but we only * do one division for the whole row, so it won't be noticable * for large matrices.) */ factor_max = ENTRY_MAX / ABS(mult); for (j = 0; j < relation_matrix->num_columns; j++) { if (ABS(relation_matrix->relations[src][j]) <= factor_max) { term0 = relation_matrix->relations[dst][j]; term1 = mult * relation_matrix->relations[src][j]; sum = term0 + term1; if ( (term0 > 0 && term1 > 0 && sum < 0) || (term0 < 0 && term1 < 0 && (sum > 0 || sum == LONG_MIN))) /* * The addition would cause an overflow. * Set *overflow to TRUE and let the calling function * decide what to do about it. */ *overflow = TRUE; else relation_matrix->relations[dst][j] = sum; } else { /* * The multiplication would cause an overflow. * Set *overflow to TRUE and let the calling function * decide what to do about it. */ *overflow = TRUE; } } } static void add_column_multiple( RelationMatrix *relation_matrix, int src, int dst, MatrixEntry mult, Boolean *overflow) { MatrixEntry factor_max, term0, term1, sum; int i; /* * If we weren't concerned about overflows, * this function could be written * for (i = 0; i < relation_matrix->num_rows; i++) relation_matrix->relations[i][dst] += mult * relation_matrix->relations[i][src]; * */ /* * If mult == 0 there's no work to be done, * so return now and avoid having to worry about special cases. */ if (mult == 0) return; /* * Let factor_max be the largest number you can multiply * times "mult" without getting an overflow. * (Division is slow compared to multiplication, but we only * do one division for the whole row, so it won't be noticable * for large matrices.) */ factor_max = ENTRY_MAX / ABS(mult); for (i = 0; i < relation_matrix->num_rows; i++) { if (ABS(relation_matrix->relations[i][src]) <= factor_max) { term0 = relation_matrix->relations[i][dst]; term1 = mult * relation_matrix->relations[i][src]; sum = term0 + term1; if ( (term0 > 0 && term1 > 0 && sum < 0) || (term0 < 0 && term1 < 0 && (sum > 0 || sum == LONG_MIN))) /* * The addition would cause an overflow. * Set *overflow to TRUE and let the calling function * decide what to do about it. */ *overflow = TRUE; else relation_matrix->relations[i][dst] = sum; } else { /* * The multiplication would cause an overflow. * Set *overflow to TRUE and let the calling function * decide what to do about it. */ *overflow = TRUE; } } } static MatrixEntry safe_addition( MatrixEntry a, MatrixEntry b, Boolean *overflow) { /* * If we weren't concerned about overflows, * this function would be simple indeed: * return a + b; * */ MatrixEntry sum; sum = a + b; if ( (a > 0 && b > 0 && sum < 0) || (a < 0 && b < 0 && (sum > 0 || sum == LONG_MIN))) { *overflow = TRUE; return 0; } else return sum; } static MatrixEntry safe_multiplication( MatrixEntry a, MatrixEntry b, Boolean *overflow) { /* * If we weren't concerned about overflows, * this function would be simple indeed: * return a * b; * */ MatrixEntry factor_max; if (a == 0) return 0; /* * Division is slow compared to multiplication, * but safe_multiplication() isn't used in any time-critical functions. */ factor_max = ENTRY_MAX / ABS(a); if (ABS(b) <= factor_max) return a * b; else { *overflow = TRUE; return 0; } } static void free_relations( RelationMatrix *relation_matrix) { int i; for (i = 0; i < relation_matrix->max_rows; i++) my_free(relation_matrix->relations[i]); if (relation_matrix->relations != NULL) my_free(relation_matrix->relations); } snappea-3.0d3/SnapPeaKernel/code/hyperbolic_structure.c0100444000175000017500000012555007046116435021327 0ustar babbab/* * hyperbolic_structure.c * * This file contains the following functions which the kernel * provides for the UI: * * SolutionType find_complete_hyperbolic_structure(Triangulation *manifold); * SolutionType do_Dehn_filling(Triangulation *manifold); * SolutionType remove_Dehn_fillings(Triangulation *manifold); * * Their use is described in SnapPea.h. * * This file also provides the following functions for use * within the kernel * * void remove_hyperbolic_structures(Triangulation *manifold); * void polish_hyperbolic_structures(Triangulation *manifold); * * remove_hyperbolic_structures() frees the TetShapes (if any) pointed to * by each tet->shape[] and sets manifold->solution_type[complete] and * manifold->solution_type[filled] to not_attempted. * * polish_hyperbolic_structures() attempts to increase the accuracy of * both the complete and the Dehn filled hyperbolic structures already * present in *manifold. It's designed to be called following * retriangulation operations which diminish the accuracy of the TetShapes. * * * SnapPea uses Newton's method to solve the gluing equations (see * Thurston's notes for an explanation of the gluing equations). The * linear equations generated at each iteration of Newton's method are * solved using Gaussian elimination with partial pivoting. * * The number of gluing equations is (number of tetrahedra + number of cusps), * while the number of variables is just the number of tetrahedra. * Unlike previous versions of SnapPea, this version does not select out * a linearly independent subset of the gluing equations, but rather * solves the whole system. My hope is that in cases where the gluing * equations are degenerate (or nearly so) the pivoting will tend to * select a more robust subset of the equations. In any case, once the * equations have been solved, there will be some rows of zeros at the * bottom, one for each cusp. The constants on the right hand side of * these zero rows provide a measure of how accurately the equations were * solved. For example, in the case of the Whitehead link complement, * which has four tetrahedra and two cusps, the matrix will reduce to * * 1 0 0 0 a <- solution * 0 1 0 0 b * 0 0 1 0 c * 0 0 0 1 d * 0 0 0 0 e <- should be zero * 0 0 0 0 f * * where the constants a - d represent the solution to the equations, * and the constants e - f (which will be close to zero) measure the * solution's accuracy. * * The coordinate systems used to parameterize the shapes of the * tetrahedra are chosen dynamically so as to avoid singularities. * The comment preceding the function choose_coordinate_system() * (see below) explains the underlying mathematics. * * The gluing equations are written in terms of complex variables, * namely the edge parameters of the tetrahedra. If the manifold is * oriented, they are analytic functions of these variables, and * Newton's method is applied directly. If the manifold is unoriented, * they are almost analytic, but not quite: they are analytic functions * of the variables and their complex conjugates. (Reversing the * orientation of a tetrahedron replaces its edge parameter with the * inverse of its complex conjugate.) Newton's method is applied by * writing the n x m system of complex equations as a 2n x 2m system of * real equations. * * [One could of course use real equations for oriented manifolds as * well, but the speed suffers. The arithmetic involved in the row * operations (mulitplying an entry in one row by a constant and adding * it to the corresponding entry in another row) is four times faster * for real numbers than for complex numbers, but a 2n x 2m real system * requires eight times as many such steps as does an n x m complex system. * Hence the speed decreases by a factor of two. This is why SnapPea * handles oriented and unoriented manifolds differently. Other than * loss of speed, there is no harm in passing an unoriented (but * orientable) manifold, with manifold->orientability == * unknown_orientability).] * * do_Dehn_filling() computes the shape of each unfilled cusp and * stores it in the field cusp->cusp_shape[current]. * find_complete_hyperbolic_structure(), after calling do_Dehn_filling(), * copies cusp->cusp_shape[current] to cusp->cusp_shape[initial]. */ #include "kernel.h" const static ComplexWithLog regular_shape = { {0.5, ROOT_3_OVER_2}, {0.0, PI_OVER_3} }; /* * RIGHT_BALLPARK must be set fairly large to allow for degenerate * solutions, which cannot be computed to great accuracy. */ #define RIGHT_BALLPARK 1e-2 #define QUADRATIC_THRESHOLD 1e-4 /* * If the solution is degenerate and Newton's method has been * iterated at least DEGENERACY_ITERATIONS times, then * do_Dehn_filling() will keep going iff the distance to * the solution decreases by a factor of at least DEGENERACY_RATIO * each time. */ #define DEGENERACY_ITERATIONS 10 #define DEGENERACY_RATIO 0.9 /* * If we haven't converged and aren't making progress after * ITERATION_LIMIT iterations, we give up. */ #define ITERATION_LIMIT 101 /* * The CuspInfo and ChernSimonsInfo data structures are * used only in polish_hyperbolic_structures(). */ typedef struct { Boolean is_complete; double m, l; } CuspInfo; typedef struct { Boolean CS_value_is_known, CS_fudge_is_known; double CS_value[2], CS_fudge[2]; } ChernSimonsInfo; static void allocate_cusp_status_arrays(Triangulation *manifold, Boolean **is_complete_array, double **m_array, double **l_array); static void free_cusp_status_arrays(Boolean *is_complete_array, double *m_array, double *l_array); static void record_cusp_status(Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]); static void restore_cusp_status(Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]); static void copy_tet_shapes(Triangulation *manifold, FillingStatus source, FillingStatus dest); static void copy_cusp_shapes(Triangulation *manifold, FillingStatus source, FillingStatus dest); static void verify_coefficients(Triangulation *manifold); static void allocate_equations(Triangulation *manifold, Complex ***complex_equations, double ***real_equations, int *num_rows, int *num_columns); static void free_equations(Triangulation *manifold, Complex **complex_equations, double **real_equations, int num_rows); static void allocate_complex_equations(Triangulation *manifold, Complex ***complex_equations, int *num_rows, int *num_columns); static void allocate_real_equations(Triangulation *manifold, double ***real_equations, int *num_rows, int *num_columns); static void free_complex_equations(Complex **complex_equations, int num_rows); static void free_real_equations(double **real_equations, int num_rows); static void associate_complex_eqns_to_edges_and_cusps(Triangulation *manifold, Complex **complex_equations); static void associate_real_eqns_to_edges_and_cusps(Triangulation *manifold, double **real_equations); static void dissociate_eqns_from_edges_and_cusps(Triangulation *manifold); static void choose_coordinate_system(Triangulation *manifold); static Boolean check_convergence(Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, double *distance_to_solution, Boolean *convergence_is_quadratic, double *distance_ratio); static double compute_distance_complex(Complex **complex_equations, int num_rows, int num_columns); static double compute_distance_real(double **real_equations, int num_rows, int num_columns); static FuncResult solve_equations(Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, Complex *solution); static void convert_solution(double *real_solution, Complex *solution, int num_columns); static void save_chern_simons(Triangulation *manifold, ChernSimonsInfo *chern_simons_info); static void restore_chern_simons(Triangulation *manifold, ChernSimonsInfo *chern_simons_info); static void allocate_arrays(Triangulation *manifold, TetShape **save_shapes, CuspInfo **save_cusp_info); static void save_filled_solution(Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info); static void restore_filled_solution(Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info); static void validate_null_history(Triangulation *manifold); static void free_arrays(TetShape *save_shapes, CuspInfo *save_cusp_info); static void copy_ultimate_to_penultimate(Triangulation *manifold); static void suppress_imaginary_parts(Triangulation *manifold); SolutionType find_complete_hyperbolic_structure( Triangulation *manifold) { Boolean *is_complete_array; double *m_array, *l_array; /* * Set all Tetrahedra to be regular ideal tetrahedra. * Allocate the TetShapes if necessary. * Clear the shape_histories if necessary. */ initialize_tet_shapes(manifold); /* * We don't want to destroy any preexisting Dehn filling * coefficients, so copy them out to arrays. */ allocate_cusp_status_arrays(manifold, &is_complete_array, &m_array, &l_array); record_cusp_status(manifold, is_complete_array, m_array, l_array); /* * Complete all the cusps. */ complete_all_cusps(manifold); /* * Call do_Dehn_filling(). * In general it thinks it's finding a filled hyperbolic structure, * but since all the cusps are complete it's really finding the * complete hyperbolic structure. */ do_Dehn_filling(manifold); /* * Copy the "filled solution" (which is really the complete * solution) to where the complete solution belongs. */ copy_solution(manifold, filled, complete); /* * Restore the preexisting Dehn filling coefficients. */ restore_cusp_status(manifold, is_complete_array, m_array, l_array); free_cusp_status_arrays(is_complete_array, m_array, l_array); /* * Done. */ return manifold->solution_type[complete]; } void initialize_tet_shapes( Triangulation *manifold) { Tetrahedron *tet; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { for (i = 0; i < 2; i++) /* i = complete, filled */ { if (tet->shape[i] == NULL) tet->shape[i] = NEW_STRUCT(TetShape); for (j = 0; j < 3; j++) tet->shape[i]->cwl[ultimate][j] = regular_shape; } clear_shape_history(tet); } } static void allocate_cusp_status_arrays( Triangulation *manifold, Boolean **is_complete_array, double **m_array, double **l_array) { *is_complete_array = NEW_ARRAY(manifold->num_cusps, Boolean); *m_array = NEW_ARRAY(manifold->num_cusps, double); *l_array = NEW_ARRAY(manifold->num_cusps, double); } static void free_cusp_status_arrays( Boolean *is_complete_array, double *m_array, double *l_array) { my_free(is_complete_array); my_free(m_array); my_free(l_array); } static void record_cusp_status( Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { is_complete_array[cusp->index] = cusp->is_complete; m_array[cusp->index] = cusp->m; l_array[cusp->index] = cusp->l; } } static void restore_cusp_status( Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->is_complete = is_complete_array[cusp->index]; cusp->m = m_array[cusp->index]; cusp->l = l_array[cusp->index]; } } void complete_all_cusps( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; } } void copy_solution( Triangulation *manifold, FillingStatus source, /* complete or filled */ FillingStatus dest) /* filled or complete */ { copy_tet_shapes(manifold, source, dest); copy_cusp_shapes(manifold, source, dest); manifold->solution_type[dest] = manifold->solution_type[source]; } static void copy_tet_shapes( Triangulation *manifold, FillingStatus source, /* complete or filled */ FillingStatus dest) /* filled or complete */ { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { *tet->shape[dest] = *tet->shape[source]; clear_one_shape_history(tet, dest); copy_shape_history(tet->shape_history[source], &tet->shape_history[dest]); } } static void copy_cusp_shapes( Triangulation *manifold, FillingStatus source, /* complete/initial or filled/current */ FillingStatus dest) /* filled/current or complete/initial */ { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[dest] = cusp->cusp_shape[source]; cusp->shape_precision[dest] = cusp->shape_precision[source]; } } /* * do_Dehn_filling() uses complex gluing equations for oriented * manifolds and real gluing equations for unoriented manifolds. * To keep the structure of its algorithm as clear as possible, * do_Dehn_filling() passes variables for both the complex_equations * and real_equations to the lower level routines, and lets the lower * level routines sort out which is the correct one to use for the * given manifold. */ SolutionType do_Dehn_filling( Triangulation *manifold) { Complex **complex_equations, *delta; double **real_equations, distance_to_solution, distance_ratio; int num_rows, num_columns, iterations, result; Boolean convergence_is_quadratic, solution_was_found, iteration_limit_exceeded; /* * Notify the UI that a potentially long computation is beginning. * The user may abort the computation if desired. */ uLongComputationBegins("Computing hyperbolic structure . . .", TRUE); /* * Check that the Dehn filling coefficients are valid. */ verify_coefficients(manifold); /* * Number the Tetrahedra. This implicitly assigns each Tetrahedron * to one of the complex variables. */ number_the_tetrahedra(manifold); /* * The following call to compute_holonomies() will rarely be needed, * but it guarantees holonomy[penultimate][] will be correct * even if Newton's method terminates after only one iteration. */ compute_holonomies(manifold); /* * allocate_equations() not only allocates the appropriate * set of equations, it also associates each equation to an edge * or cusp in the manifold. This is why the equations are not * explicitly passed to compute_equations(). */ allocate_equations( manifold, &complex_equations, &real_equations, &num_rows, &num_columns); /* * Allocate an array to hold the changes to the Tetrahedron shapes * specified by Newton's method. */ delta = NEW_ARRAY(manifold->num_tetrahedra, Complex); /* * distance_to_solution is initialized to RIGHT_BALLPARK * to get the proper behavior the first time through the loop. */ distance_to_solution = RIGHT_BALLPARK; convergence_is_quadratic = FALSE; iterations = 0; iteration_limit_exceeded = FALSE; do { choose_coordinate_system(manifold); compute_gluing_equations(manifold); /* * We're done if either * * (1) the solution has converged, or * * (2) the solution is degenerate (in which case it * would take a long, long time to converge). */ if ( check_convergence( manifold->orientability, complex_equations, real_equations, num_rows, num_columns, &distance_to_solution, &convergence_is_quadratic, &distance_ratio) || ( solution_is_degenerate(manifold) && iterations > DEGENERACY_ITERATIONS && distance_ratio > DEGENERACY_RATIO ) ) { solution_was_found = TRUE; break; /* break out of the do {} while (TRUE) loop */ } /* * iterations almost never exceeds ITERATION_LIMIT. * In fact, SnapPea was used for years without this check, and * it always found solutions. The first examples where the * solutions didn't converge were the meridional Dehn fillings * on the nonorientable 6-tetrahedron census manifolds * x045, x048, x063, x084 and x175. For further comments, * please see the file "failure to solve gluing eqns". */ if (iterations > ITERATION_LIMIT && distance_ratio >= 1.0) { iteration_limit_exceeded = TRUE; solution_was_found = FALSE; break; /* break out of the do {} while (TRUE) loop */ } result = solve_equations( manifold->orientability, complex_equations, real_equations, num_rows, num_columns, delta); if (result == func_cancelled || result == func_failed) { solution_was_found = FALSE; break; /* break out of the do {} while (TRUE) loop */ } update_shapes(manifold, delta); iterations++; } while (TRUE); /* The loop terminates in one of the break statements. */ /* * In the rare case that distance_to_solution is exactly zero, * copy the ultimate solution to the penultimate one, to indicate * that we've solved the equations to full accuracy. */ if (distance_to_solution == 0.0) copy_ultimate_to_penultimate(manifold); free_equations(manifold, complex_equations, real_equations, num_rows); my_free(delta); if (solution_was_found == TRUE) identify_solution_type(manifold); else if (iteration_limit_exceeded == TRUE) manifold->solution_type[filled] = no_solution; else switch (result) { case func_cancelled: manifold->solution_type[filled] = not_attempted; break; case func_failed: manifold->solution_type[filled] = no_solution; break; } /* * 96/1/12 Craig has requested that for flat solutions SnapPea's * complex length function provide consistent signs for rotation * angles of elliptic isometries (see complex_length.c). I was * concerned about distinguishing flat solutions from almost flat * solutions, so here we check whether the solution is provably flat, * and if so set the imaginary parts of all tet shapes to zero. * * Proposition. If a solution (to the gluing equations) is * almost flat and the Dehn filling coefficients are all integers, * then the solution obtained by setting the imaginary parts * of all tetrahedron shapes to zero is stable, in the sense that * Newton's method would keep all imaginary parts zero. * * Proof. In Newton's method, both the derivative matrix and the * "right hand side" would be real, so the computed array "delta" * would also be real. QED */ if (manifold->solution_type[filled] == flat_solution && all_Dehn_coefficients_are_integers(manifold) == TRUE) suppress_imaginary_parts(manifold); compute_cusp_shapes(manifold, current); compute_CS_value_from_fudge(manifold); uLongComputationEnds(); return manifold->solution_type[filled]; } /* * verify_coefficients() alerts the user and exits if the current set * of Dehn filling coefficients includes * * (0,0) Dehn filling on any cusp, or * * (p,q) Dehn filling, with q != 0, on a nonorientable cusp. * * set_cusp_info() should have already checked the coefficients * for errors, so verify_coefficients() should be unnecessary. It is * included to guard against programming errors (e.g. passing a manifold * whose coefficients have not been set at all), not user errors. */ static void verify_coefficients( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if ( cusp->is_complete ? cusp->m != 0.0 || cusp->l != 0.0 : (cusp->m == 0.0 && cusp->l == 0.0) || (cusp->topology == Klein_cusp && cusp->l != 0.0) ) uFatalError("verify_coefficients", "hyperbolic_structure"); } /* * allocate_equations() allocates space for the equations as a matrix, * and also associates each equation to an edge or cusp in the manifold. */ static void allocate_equations( Triangulation *manifold, Complex ***complex_equations, double ***real_equations, int *num_rows, int *num_columns) { if (manifold->orientability == oriented_manifold) { real_equations = NULL; allocate_complex_equations(manifold, complex_equations, num_rows, num_columns); associate_complex_eqns_to_edges_and_cusps(manifold, *complex_equations); } else { complex_equations = NULL; allocate_real_equations(manifold, real_equations, num_rows, num_columns); associate_real_eqns_to_edges_and_cusps(manifold, *real_equations); } } static void free_equations( Triangulation *manifold, Complex **complex_equations, double **real_equations, int num_rows) { if (manifold->orientability == oriented_manifold) free_complex_equations(complex_equations, num_rows); else free_real_equations(real_equations, num_rows); dissociate_eqns_from_edges_and_cusps(manifold); } /* * allocate_complex_equations() sets *num_rows and *num_columns, * and allocates memory for a complex matrix of dimensions * (*num_rows) x (*num_columns + 1). The extra column will * hold the constant on the right hand side of the equations. */ static void allocate_complex_equations( Triangulation *manifold, Complex ***complex_equations, int *num_rows, int *num_columns) { int i; /* * We'll have an equation for each edge, and also an equation * for each cusp. The number of edges in an ideal triangulation * equals the number of tetrahedra, by an Euler characteristic * argument. */ *num_rows = manifold->num_tetrahedra + manifold->num_cusps; /* * We'll have one complex variable for each ideal tetrahedron. */ *num_columns = manifold->num_tetrahedra; /* * The matrix is stored as an array of row pointers. */ *complex_equations = NEW_ARRAY(*num_rows, Complex *); for (i = 0; i < *num_rows; i++) (*complex_equations)[i] = NEW_ARRAY(*num_columns + 1, Complex); } /* * allocate_real_equations() sets *num_rows and *num_columns, * and allocates memory for a real matrix of dimensions * 2*(*num_rows) x 2*(*num_columns + 1). The extra column will * hold the constant on the right hand side of the equations. */ static void allocate_real_equations( Triangulation *manifold, double ***real_equations, int *num_rows, int *num_columns) { int i; /* * Cf. allocate_complex_equations() above. */ *num_rows = 2 * (manifold->num_tetrahedra + manifold->num_cusps); *num_columns = 2 * manifold->num_tetrahedra; *real_equations = NEW_ARRAY(*num_rows, double *); for (i = 0; i < *num_rows; i++) (*real_equations)[i] = NEW_ARRAY(*num_columns + 1, double); } /* * free_complex_equations() frees the memory allocated * in allocate_complex_equations(). */ static void free_complex_equations( Complex **complex_equations, int num_rows) { int i; for (i = 0; i < num_rows; i++) my_free(complex_equations[i]); my_free(complex_equations); } /* * free_real_equations() frees the memory allocated * in allocate_real_equations(). */ static void free_real_equations( double **real_equations, int num_rows) { int i; for (i = 0; i < num_rows; i++) my_free(real_equations[i]); my_free(real_equations); } /* * associate_complex_eqns_to_edges_and_cusps() associates the first * num_tetrahedra equations to edge classes, and the remaining * num_cusps equations to cusps. */ static void associate_complex_eqns_to_edges_and_cusps( Triangulation *manifold, Complex **complex_equations) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = *complex_equations++; edge->real_edge_equation_re = NULL; edge->real_edge_equation_im = NULL; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = *complex_equations++; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; } } /* * associate_real_eqns_to_edges_and_cusps() associates the first * 2*num_tetrahedra equations to edge classes, and the remaining * 2*num_cusps equations to cusps. */ static void associate_real_eqns_to_edges_and_cusps( Triangulation *manifold, double **real_equations) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = NULL; edge->real_edge_equation_re = *real_equations++; edge->real_edge_equation_im = *real_equations++; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = *real_equations++; cusp->real_cusp_equation_im = *real_equations++; } } /* * dissociate_eqns_from_edges_and_cusps() dissociates the gluing * equations from the edges and cusps. Note that this function * works for both complex and real equations. */ static void dissociate_eqns_from_edges_and_cusps( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = NULL; edge->real_edge_equation_re = NULL; edge->real_edge_equation_im = NULL; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; } } /* * The shape of an ideal tetrahedron is traditionally parameterized * by one of the three forms of its cross ratio. Cross ratios of * 0, 1 and infinity represent degenerate tetrahedra. Near these * points, bad things happen. The two main problems are that (1) some * of the entries in the derivative matrix (used in Newton's method) * approach infinity, and (2) incrementing the solution can move it * too close to a singularity, resulting in wild swings in the arguments * of the cross ratios. Switching the coordinates from the cross * ratio to the log of the cross ratio helps a bit. Rather than having * two singularities (0 and 1) embedded in the parameter space, you * have only one (the singularity which used to be at 1 is now at 0, * but the singularity which used to be at 0 has been happily pushed * out to infinity). * * This scheme can be further improved by choosing a (logarithmic) * coordinate system based on the current shape of * the tetrahedron. The coordinate system is chosen so that the * current shape of the tetrahedron stays away from the singularity * in the parameter space. Specifically, let * * z0 = z * * 1 * z1 = ----- * 1 - z * * z - 1 * z2 = ----- * z * * and divide the complex plane into three regions: * * region A: |z-1| > 1 && Re(z) < 1/2 * region B: |z| > 1 && Re(z) > 1/2 * region C: |z-1| < 1 && |z| < 1 * * Viewed on the Riemann sphere, the singularities are equally * spaced points on the equator, and the regions are separated * by meridians spaced 120 degrees apart. The points along the * boundaries may be arbitrarily assigned to either neighboring region. * * In region A, use log(z0) coordinates. * In region B, use log(z1) coordinates. * In region C, use log(z2) coordinates. * * Each entry in the derivative matrix used in Newton's method is * a linear combination of the derivatives of log(z0), log(z1) * and log(z2). The above choice of coordinates implies that each * such derivative will have modulus less than or equal to one. * Here's the proof. First compute * * d(log z0) 1 * --------- = - * dz z * * d(log z1) 1 * --------- = ----- * dz 1 - z * * d(log z2) 1 * --------- = -------- * dz z(z - 1) * * Now take ratios of the above to compute * * d(log z0) d(log z0) 1 - z d(log z0) * --------- = 1 --------- = ----- --------- = z - 1 * d(log z0) d(log z1) z d(log z2) * * d(log z1) z d(log z1) d(log z1) * --------- = ----- --------- = 1 --------- = -z * d(log z0) 1 - z d(log z1) d(log z2) * * d(log z2) 1 d(log z2) -1 d(log z2) * --------- = ----- --------- = ----- --------- = 1 * d(log z0) z - 1 d(log z1) z d(log z2) * * Say z lies in region A, and we have chosen log(z0) coordinates * as indicated previously. The derivatives in the first column of the * above table have modulus less than or equal to 1. This is obvious * for the first entry in the column. For the third entry it's an * immediate consequence of the condition |1 - z| > 1. For the second * entry, note that * * | Im(z) | = | Im(1 - z) | * and * | Re(z) | < | Re(1 - z) | iff Re(z) < 1/2 * * hence |z| < |1-z|. * * Similar arguments show that when z lies in region B (resp. region C) * the derivatives in the second column (resp. third column) have * modulus less than or equal to 1. (In fact, the derivatives all * lie in region C, as can be seen from the fact that the two nonconstant * derivatives in each column sum to -1. For our purposes, though, it's * enough just to know that the derivatives are bounded, so the entries * in the derivative matrix used in Newton's method cannot diverge to * infinity.) * * Theoretical note: I briefly entertained the idea of finding a * single coordinate system which avoids all three singularities. * Picard's Little Theorem shows that this is not possible for an * analytic function. It might be possible for a nonanalytic function * (perhaps a simple function of z and z-bar?) but I haven't pursued * this, and in any case such a function wouldn't be conformal. * However, each Tetrahedron's shape_history fields record the topological * information such a master coordinate system would contain. */ static void choose_coordinate_system( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if ( tet->shape[filled]->cwl[ultimate][0].log.real < 0.0 /* |z| < 1 */ && tet->shape[filled]->cwl[ultimate][1].log.real > 0.0 /* |z-1| < 1 */ ) tet->coordinate_system = 2; /* region C, log(z2) coordinates */ else if (tet->shape[filled]->cwl[ultimate][0].rect.real > 0.5) /* Re(z) < 1/2 */ tet->coordinate_system = 1; /* region B, log(z1) coordinates */ else tet->coordinate_system = 0; /* region A, log(z0) coordinates */ } /* * check_convergence() checks whether Newton's method has converged to * a solution. We check for convergence in the range rather than the * domain. In other words, we check how precisely the gluing equations * are satisfied, without regard to whether the logs of the tetrahedra's * edge parameters are converging. The reason for this is that degenerate * equations will be satisfied more and more precisely by edge parameters * whose logs are diverging to infinity. * * We know Newton's method has converged when it begins making * small random changes. We check this by seeing whether * * (1) it's in the right ballpark (meaning it should be * converging quadratically), and * * (2) the new distance is greater than the old one. * * We also offer a shortcut, to avoid the possibility of having to * wait through several essentially random iterations of Newton's * method which just happen to decrease the distance to the solution * each time. The shortcut is that we note when quadratic convergence * begins, and then as soon as it ends we know we've converged. * * Finally, if the equations are satisfied perfectly, we return TRUE. * I realize this is not very likely, but it makes the function * logically correct. (Without this provision a perfect solution * would cycle endlessly through Newton's method.) * * check_convergence() returns TRUE when it considers Newton's method * to have converged, and FALSE otherwise. */ static Boolean check_convergence( Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, double *distance_to_solution, Boolean *convergence_is_quadratic, double *distance_ratio) { double old_distance; old_distance = *distance_to_solution; *distance_to_solution = orientability == oriented_manifold ? compute_distance_complex(complex_equations, num_rows, num_columns) : compute_distance_real(real_equations, num_rows, num_columns); *distance_ratio = *distance_to_solution / old_distance; if (*distance_ratio < QUADRATIC_THRESHOLD) *convergence_is_quadratic = TRUE; return ( (*distance_to_solution < RIGHT_BALLPARK && *distance_ratio > 1.0) || (*convergence_is_quadratic && *distance_ratio > 0.5) || (*distance_to_solution == 0.0) /* seems unlikely, but who knows */ ); } static double compute_distance_complex( Complex **complex_equations, int num_rows, int num_columns) { double distance_squared; int i; distance_squared = 0.0; for (i = 0; i < num_rows; i++) distance_squared += complex_modulus_squared(complex_equations[i][num_columns]); return sqrt(distance_squared); /* no need for safe_sqrt() */ } static double compute_distance_real( double **real_equations, int num_rows, int num_columns) { double distance_squared; int i; distance_squared = 0.0; for (i = 0; i < num_rows; i++) distance_squared += real_equations[i][num_columns] * real_equations[i][num_columns]; return sqrt(distance_squared); /* no need for safe_sqrt() */ } /* * In practice a typecast would suffice to convert the real_solution * to the Complex solution, since an array of n Complex numbers is stored * as an array of 2n reals. But we do an explicit conversion anyhow, * in the interest of good style and robust code (and also in the * interest of maintaining solve_real_equations() as a general purpose * routine for solving real equations). */ static FuncResult solve_equations( Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, Complex *solution) { double *real_solution; FuncResult result; if (orientability == oriented_manifold) result = solve_complex_equations(complex_equations, num_rows, num_columns, solution); else { real_solution = NEW_ARRAY(num_columns, double); result = solve_real_equations(real_equations, num_rows, num_columns, real_solution); if (result == func_OK) convert_solution(real_solution, solution, num_columns); my_free(real_solution); } return result; } static void convert_solution( double *real_solution, Complex *solution, int num_columns) { int count; for (count = num_columns/2; --count >= 0; ) { solution->real = *real_solution++; solution->imag = *real_solution++; solution++; } } void remove_hyperbolic_structures( Triangulation *manifold) { Tetrahedron *tet; int i; /* * If TetShapes are present, remove them. */ if (manifold->solution_type[complete] != not_attempted) for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { for (i = 0; i < 2; i++) /* i = complete, filled */ { my_free(tet->shape[i]); tet->shape[i] = NULL; } clear_shape_history(tet); } /* * Set solution_type[complete] and solution_type[filled] * to not_attempted. */ for (i = 0; i < 2; i++) /* i = complete, filled */ manifold->solution_type[i] = not_attempted; } void polish_hyperbolic_structures( Triangulation *manifold) { TetShape *save_shapes; CuspInfo *save_cusp_info; ChernSimonsInfo chern_simons_info; if (manifold->solution_type[complete] == not_attempted) uFatalError("polish_hyperbolic_structures", "polish_hyperbolic_structures"); save_chern_simons(manifold, &chern_simons_info); allocate_arrays(manifold, &save_shapes, &save_cusp_info); save_filled_solution(manifold, save_shapes, save_cusp_info); complete_all_cusps(manifold); copy_tet_shapes(manifold, complete, filled); validate_null_history(manifold); do_Dehn_filling(manifold); copy_solution(manifold, filled, complete); restore_filled_solution(manifold, save_shapes, save_cusp_info); validate_null_history(manifold); do_Dehn_filling(manifold); free_arrays(save_shapes, save_cusp_info); restore_chern_simons(manifold, &chern_simons_info); } static void save_chern_simons( Triangulation *manifold, ChernSimonsInfo *chern_simons_info) { /* * Why do we need to save and restore the Chern-Simons info? * * polish_hyperbolic_structures() is called just after a * Triangulation has been modified (e.g. by basic_simplification() * or randomize_triangulation()). At this point the TetShapes are * slightly inaccurate, the CS_value is accurate, and the * CS_fudge is completely wrong. We don't want to call * compute_CS_fudge_from_value() just yet, because then the * CS_fudge would inherit the inaccuracies of the TetShapes. * But it we call find_complete_hyperbolic_structure() or * do_Dehn_filling() right way, they will recompute the CS_value * based on the completely wrong CS_fudge. So we save the * CS_value until after we've polished the hyperbolic structure, * then we restore it and compute the CS_fudge using the accurate * TetShapes. */ /* * Record the Chern-Simons data. */ chern_simons_info->CS_value_is_known = manifold->CS_value_is_known; chern_simons_info->CS_fudge_is_known = manifold->CS_fudge_is_known; chern_simons_info->CS_value[ultimate] = manifold->CS_value[ultimate]; chern_simons_info->CS_value[penultimate] = manifold->CS_value[penultimate]; chern_simons_info->CS_fudge[ultimate] = manifold->CS_fudge[ultimate]; chern_simons_info->CS_fudge[penultimate] = manifold->CS_fudge[penultimate]; /* * Pretend it's no longer there, to save some useless computations. */ manifold->CS_value_is_known = FALSE; manifold->CS_fudge_is_known = FALSE; } static void restore_chern_simons( Triangulation *manifold, ChernSimonsInfo *chern_simons_info) { manifold->CS_value_is_known = chern_simons_info->CS_value_is_known; manifold->CS_fudge_is_known = chern_simons_info->CS_fudge_is_known; manifold->CS_value[ultimate] = chern_simons_info->CS_value[ultimate]; manifold->CS_value[penultimate] = chern_simons_info->CS_value[penultimate]; manifold->CS_fudge[ultimate] = chern_simons_info->CS_fudge[ultimate]; manifold->CS_fudge[penultimate] = chern_simons_info->CS_fudge[penultimate]; /* * It might makes sense to call compute_CS_fudge_from_value() at * this point, but in the interest of modularity I decided not to. */ } static void allocate_arrays( Triangulation *manifold, TetShape **save_shapes, CuspInfo **save_cusp_info) { *save_shapes = NEW_ARRAY(manifold->num_tetrahedra, TetShape); *save_cusp_info = NEW_ARRAY(manifold->num_cusps, CuspInfo); } static void save_filled_solution( Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info) { int i; Tetrahedron *tet; Cusp *cusp; /* * Save the Tetrahedron shapes. */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) save_shapes[i] = *tet->shape[filled]; /* * Save the Cusp information. */ for (i = 0, cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; i++, cusp = cusp->next) { save_cusp_info[i].is_complete = cusp->is_complete; save_cusp_info[i].m = cusp->m; save_cusp_info[i].l = cusp->l; } } static void restore_filled_solution( Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info) { int i; Tetrahedron *tet; Cusp *cusp; /* * Restore the Tetrahedron shapes. */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) *tet->shape[filled] = save_shapes[i]; /* * Restore the Cusp information. */ for (i = 0, cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; i++, cusp = cusp->next) { cusp->is_complete = save_cusp_info[i].is_complete; cusp->m = save_cusp_info[i].m; cusp->l = save_cusp_info[i].l; } } static void validate_null_history( Triangulation *manifold) { /* * The basic_simplification() and randomize_triangulation() * functions can't be guaranteed to find correct arguments * for the logarithmic forms of the TetShapes, let alone produce * a valid history for each new Tetrahedron it introduces. * * validate_null_history() enforces a trivial shape_history * for each Tetrahedron. It does this by * * (1) clearing all shape_histories, * * (2) making sure all Tetrahedra are positively oriented, and * * (3) making sure all logs lie in the range (0, pi). * * In the nice case that these conditions are all already met, * validate_null_history() doesn't change anything, and * polish_hyperbolic_structures() ends up making only small * changes to the hyperbolic structures ("polishing" them). * * If the conditions are not met, validate_null_history() sets * the offending Tetrahedron shapes to something acceptable, and * in effect the hyperbolic structure is recomputed from scratch. */ Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { clear_one_shape_history(tet, filled); /* * The algorithm in update_shapes() guarantees that the * three shapes (for edge j = 0, 1, 2) will have the same sign * for rect.imag, regardless of roundoff errors, so if. * * Only the ultimate shapes are relevant. The penultimate * shapes are ignored. */ for (i = 0; i < 3; i++) { if (tet->shape[filled]->cwl[ultimate][i].rect.imag <= 0.0) tet->shape[filled]->cwl[ultimate][i] = regular_shape; tet->shape[filled]->cwl[ultimate][i].log = complex_log( tet->shape[filled]->cwl[ultimate][i].rect, PI_OVER_2); } } } static void free_arrays( TetShape *save_shapes, CuspInfo *save_cusp_info) { my_free(save_shapes); my_free(save_cusp_info); } static void copy_ultimate_to_penultimate( Triangulation *manifold) { Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) tet->shape[filled]->cwl[penultimate][i] = tet->shape[filled]->cwl[ultimate][i]; } static void suppress_imaginary_parts( Triangulation *manifold) { Tetrahedron *tet; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) /* ultimate, penultimate */ for (j = 0; j < 3; j++) { tet->shape[filled]->cwl[i][j].rect.imag = 0.0; tet->shape[filled]->cwl[i][j].log = complex_log( tet->shape[filled]->cwl[i][j].rect, tet->shape[filled]->cwl[i][j].log.imag); } } extern SolutionType remove_Dehn_fillings(Triangulation *manifold) { /* * Set all cusps to be unfilled. */ complete_all_cusps(manifold); /* * Copy the complete solution to the "filled" solution. */ copy_solution(manifold, complete, filled); /* * Call do_Dehn_filling(), to insure that all internal * data (such as the Chern-Simons invariant) are updated * correctly. This invokes an unnecessary computation, * but it keeps the code simple, and guarantees that * all internal data will be up-to-date. */ return do_Dehn_filling(manifold); } snappea-3.0d3/SnapPeaKernel/code/identify_solution_type.c0100444000175000017500000001026606742675501021662 0ustar babbab/* * identify_solution_type.c * * This file provides the function * * void identify_solution_type(Triangulation *manifold); * * which identifies the type of solution contained in the * tet->shape[filled] structures of the Tetrahedra of Triangulation *manifold, * and writes the result to manifold->solution_type[filled]. Possible * values are given by the SolutionType enum (see SnapPea.h). * * Its subroutine * * Boolean solution_is_degenerate(Triangulation *manifold); * * Is also available within the kernel, so do_Dehn_filling() can tell * whether it is converging towards a degenerate structure. */ #include "kernel.h" /* * A solution must have volume at least VOLUME_EPSILON to count * as a positive volume solution. Otherwise the volume will be * considered zero or negative. */ #define VOLUME_EPSILON 1e-2 /* * DEGENERACY_EPSILON defines how close a tetrahedron shape must * be to zero to count as zero. It is given in logarithmic form. * E.g., if DEGENERACY_EPSILON is -6, then the tetrahedron shape * (in rectangular form) must lie within a distance exp(-6) = 0.0024... * of the origin. */ #define DEGENERACY_EPSILON -6 /* * A solution is considered flat iff it's not degenerate and the * argument of each edge parameter is within FLAT_EPSILON of 0.0 or PI. */ #define FLAT_EPSILON 1e-2 static Boolean solution_is_flat(Triangulation *manifold); static Boolean solution_is_geometric(Triangulation *manifold); void identify_solution_type( Triangulation *manifold) { if (solution_is_degenerate(manifold)) { manifold->solution_type[filled] = degenerate_solution; return; } if (solution_is_flat(manifold)) { manifold->solution_type[filled] = flat_solution; return; } if (solution_is_geometric(manifold)) { manifold->solution_type[filled] = geometric_solution; return; } if (volume(manifold, NULL) > VOLUME_EPSILON) { manifold->solution_type[filled] = nongeometric_solution; return; } manifold->solution_type[filled] = other_solution; } Boolean solution_is_degenerate( Triangulation *manifold) { Tetrahedron *tet; int i; /* * If any complex edge parameter of any Tetrahedron is * close to zero, return TRUE. Otherwise return FALSE. * * Note that it's enough to check for shapes close to * zero: if an edge parameter is close to one or infinity, * then some other edge parameter of the same Tetrahedron * will be close to zero. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) if (tet->shape[filled]->cwl[ultimate][i].log.real < DEGENERACY_EPSILON) return TRUE; return FALSE; } static Boolean solution_is_flat( Triangulation *manifold) { Tetrahedron *tet; int i; double the_angle; /* * If any edge parameter has angle more than FLAT_EPSILON away * from 0.0 or PI, return FALSE. Otherwise, return TRUE. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) { the_angle = tet->shape[filled]->cwl[ultimate][i].log.imag; if (fabs(the_angle) > FLAT_EPSILON && fabs(the_angle - PI) > FLAT_EPSILON) return FALSE; } return TRUE; } static Boolean solution_is_geometric( Triangulation *manifold) { Tetrahedron *tet; /* * If any edge parameter has argument less than minus FLAT_EPSILON * or greater than PI + FLAT_EPSILON, return FALSE. * Otherwise, return TRUE. * * This allows a solution with some flat tetrahedra to count as geometric. * However, if all the tetrahedra were flat, the SolutionType would have * been previously identified as flat_solution, and we wouldn't have * gotten to this function. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tetrahedron_is_geometric(tet) == FALSE) return FALSE; return TRUE; } Boolean tetrahedron_is_geometric( Tetrahedron *tet) { int i; double the_angle; /* * See comments in solution_is_geometric() above. */ for (i = 0; i < 3; i++) { the_angle = tet->shape[filled]->cwl[ultimate][i].log.imag; if (the_angle < - FLAT_EPSILON || the_angle > PI + FLAT_EPSILON) return FALSE; } return TRUE; } snappea-3.0d3/SnapPeaKernel/code/index_to_hue.c0100444000175000017500000000341706742675501017524 0ustar babbab/* * index_to_hue.c * * This file provides the function * * double index_to_hue(int index); * * which maps the nonnegative integers to a set of easily distinguishable * hues. The rule for computing the hue is to write the index in binary, * reverse the order of the digits, and prepend a decimal point. Here are * the first few values: * * index hue * decimal binary binary decimal fraction * 0 0 0.000 0.000 0 * 1 1 0.100 0.500 1/2 * 2 10 0.010 0.250 1/4 * 3 11 0.110 0.750 3/4 * 4 100 0.001 0.125 1/8 * 5 101 0.101 0.625 5/8 * 6 110 0.011 0.375 3/8 * 7 111 0.111 0.875 7/8 */ #include "kernel.h" double index_to_hue( int index) { /* * To maximize speed and avoid unnecessary roundoff error, * compute the hue as a rational number num/den, and divide * to get the floating point equivalent at the last moment. */ unsigned int num, den; num = 0; den = 1; while (index) { num <<= 1; den <<= 1; if (index & 0x1) num++; index >>= 1; } return ( (double)num / (double)den ); } double horoball_hue( int index) { /* * The index_to_hue() colors don't look so nice for horoballs, * mainly because the yellowish green color looks sickly. * Here we provide hand chosen colors for up to six colors, * and use index_to_hue() to interpolate thereafter. * These colors look nice on my monitor, but they could * look different on other monitors. And, of course, beauty * is in the eye of the beholder. */ const static int base_hue[6] = { 0, /* red */ 3, /* cyan */ 2, /* green */ 4, /* blue */ 5, /* magenta */ 1 }; /* yellow */ return (base_hue[index%6] + index_to_hue(index/6)) / 6.0; } snappea-3.0d3/SnapPeaKernel/code/interface.c0100444000175000017500000002767007041100655017003 0ustar babbab/* * interface.c * * This file contains the following functions, which the * user interface uses to read the fields in the Triangulation * data structure. * * char *get_triangulation_name(Triangulation *manifold); * char set_triangulation_name(Triangulation *manifold, char *new_name); * SolutionType get_complete_solution_type(Triangulation *manifold); * SolutionType get_filled_solution_type(Triangulation *manifold); * int get_num_tetrahedra(Triangulation *manifold); * Orientability get_orientability(Triangulation *manifold); * int get_num_cusps(Triangulation *manifold); * int get_num_or_cusps(Triangulation *manifold); * int get_num_nonor_cusps(Triangulation *manifold); * int get_max_singularity(Triangulation *manifold); * int get_num_generators(Triangulation *manifold); * void get_cusp_info( Triangulation *manifold, * int cusp_index, * CuspTopology *topology, * Boolean *is_complete, * double *m, * double *l, * Complex *initial_shape, * Complex *current_shape, * int *initial_shape_precision, * int *current_shape_precision, * Complex *initial_modulus, * Complex *current_modulus); * FuncResult set_cusp_info( Triangulation *manifold, * int cusp_index, * Boolean cusp_is_complete, * double m, * double l); * void get_holonomy( Triangulation *manifold, * int cusp_index, * Complex *meridional_holonomy, * Complex *longitudinal_holonomy, * int *meridional_precision, * int *longitudinal_precision); * void get_tet_shape( Triangulation *manifold, * int which_tet, * Boolean fixed_alignment, * double *shape_rect_real, * double *shape_rect_imag, * double *shape_log_real, * double *shape_log_imag, * int *precision_rect_real, * int *precision_rect_imag, * int *precision_log_real, * int *precision_log_imag, * Boolean *is_geometric); * int get_num_edge_classes( * Triangulation *manifold, * int edge_class_order, * Boolean greater_than_or_equal); * * The Triangulation data structure itself, as well as its * component data structures, remain private to the kernel. * * These functions are documented more thoroughly in SnapPea.h. */ #include "kernel.h" static int longest_side(Tetrahedron *tet); char *get_triangulation_name( Triangulation *manifold) { return manifold->name; } void set_triangulation_name( Triangulation *manifold, char *new_name) { /* * Free the old name, if there is one. */ if (manifold->name != NULL) my_free(manifold->name); /* * Allocate space for the new name . . . */ manifold->name = NEW_ARRAY(strlen(new_name) + 1, char); /* * . . . and copy it in. */ strcpy(manifold->name, new_name); } SolutionType get_complete_solution_type( Triangulation *manifold) { return manifold->solution_type[complete]; } SolutionType get_filled_solution_type( Triangulation *manifold) { return manifold->solution_type[filled]; } int get_num_tetrahedra( Triangulation *manifold) { return manifold->num_tetrahedra; } Orientability get_orientability( Triangulation *manifold) { return manifold->orientability; } int get_num_cusps( Triangulation *manifold) { return manifold->num_cusps; } int get_num_or_cusps( Triangulation *manifold) { return manifold->num_or_cusps; } int get_num_nonor_cusps( Triangulation *manifold) { return manifold->num_nonor_cusps; } int get_max_singularity( Triangulation *manifold) { Cusp *cusp; int m, l, singularity, max_singularity; max_singularity = 1; for ( cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->is_complete == FALSE) { m = (int) cusp->m; l = (int) cusp->l; if ( cusp->m == (double) m && cusp->l == (double) l) { singularity = gcd(m, l); if (max_singularity < singularity) max_singularity = singularity; } } } return max_singularity; } int get_num_generators( Triangulation *manifold) { return manifold->num_generators; } void get_cusp_info( Triangulation *manifold, int cusp_index, CuspTopology *topology, Boolean *is_complete, double *m, double *l, Complex *initial_shape, Complex *current_shape, int *initial_shape_precision, int *current_shape_precision, Complex *initial_modulus, Complex *current_modulus) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); /* * Write information corresponding to nonNULL pointers. */ if (topology != NULL) *topology = cusp->topology; if (is_complete != NULL) *is_complete = cusp->is_complete; if (m != NULL) *m = cusp->m; if (l != NULL) *l = cusp->l; if (initial_shape != NULL) *initial_shape = cusp->cusp_shape[initial]; /* = Zero if initial hyperbolic structure is degenerate */ if (current_shape != NULL) *current_shape = cusp->cusp_shape[current]; /* = Zero if cusp is filled or hyperbolic structure is degenerate */ if (initial_shape_precision != NULL) *initial_shape_precision = cusp->shape_precision[initial]; /* = 0 if initial hyperbolic structure is degenerate */ if (current_shape_precision != NULL) *current_shape_precision = cusp->shape_precision[current]; /* = 0 if cusp is filled or hyperbolic structure is degenerate */ if (initial_modulus != NULL) { if (cusp->shape_precision[initial] > 0) *initial_modulus = cusp_modulus(cusp->cusp_shape[initial]); else *initial_modulus = Zero; } if (current_modulus != NULL) { if (cusp->shape_precision[current] > 0) *current_modulus = cusp_modulus(cusp->cusp_shape[current]); else *current_modulus = Zero; } } FuncResult set_cusp_info( Triangulation *manifold, int cusp_index, Boolean cusp_is_complete, double m, double l) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); /* * Write the given Dehn coefficients into the cusp. */ if (cusp_is_complete) { cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; } else { /* * Check the input. * * The comment at the top of holonomy.c explains why only * (m,0) Dehn fillings are possible on nonorientable cusps. */ if (m == 0.0 && l == 0.0) { uAcknowledge("Can't do (0,0) Dehn filling."); return func_bad_input; } if (cusp->topology == Klein_cusp && l != 0.0) { uAcknowledge("Only (p,0) Dehn fillings are possible on a nonorientable cusp."); return func_bad_input; } /* * Copy the input into the cusp. */ cusp->is_complete = FALSE; cusp->m = m; cusp->l = l; } return func_OK; } void get_holonomy( Triangulation *manifold, int cusp_index, Complex *meridional_holonomy, Complex *longitudinal_holonomy, int *meridional_precision, int *longitudinal_precision) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); if (meridional_holonomy != NULL) *meridional_holonomy = cusp->holonomy[ultimate][M]; if (longitudinal_holonomy != NULL) { *longitudinal_holonomy = cusp->holonomy[ultimate][L]; /* * Longitudes on Klein bottle cusps are stored as their * double covers (cf. peripheral_curves.c), so we divide * by two to compensate. (Recall that this isn't actually * the holonomy, but the log of the holonomy, i.e. the * complex length.) * * As explained at the top of holonomy.c, the holonomy * in this case must be real, so we clear any roundoff * error in the imaginary part. */ if (cusp->topology == Klein_cusp) { longitudinal_holonomy->real /= 2.0; longitudinal_holonomy->imag = 0.0; } } if (meridional_precision != NULL) *meridional_precision = complex_decimal_places_of_accuracy( cusp->holonomy[ ultimate ][M], cusp->holonomy[penultimate][M]); if (longitudinal_precision != NULL) *longitudinal_precision = complex_decimal_places_of_accuracy( cusp->holonomy[ ultimate ][L], cusp->holonomy[penultimate][L]); } void get_tet_shape( Triangulation *manifold, int which_tet, Boolean fixed_alignment, double *shape_rect_real, double *shape_rect_imag, double *shape_log_real, double *shape_log_imag, int *precision_rect_real, int *precision_rect_imag, int *precision_log_real, int *precision_log_imag, Boolean *is_geometric) { int count, the_coordinate_system; Tetrahedron *tet; ComplexWithLog *ultimate_shape, *penultimate_shape; /* * If no solution is present, return all zeros. */ if (manifold->solution_type[filled] == not_attempted) { *shape_rect_real = 0.0; *shape_rect_imag = 0.0; *shape_log_real = 0.0; *shape_log_imag = 0.0; *precision_rect_real = 0; *precision_rect_imag = 0; *precision_log_real = 0; *precision_log_imag = 0; *is_geometric = FALSE; return; } /* * Check that which_tet is within bounds. */ if (which_tet < 0 || which_tet >= manifold->num_tetrahedra) uFatalError("get_tet_shape", "interface"); /* * Find the Tetrahedron in position which_tet. */ for (tet = manifold->tet_list_begin.next, count = 0; tet != &manifold->tet_list_end && count != which_tet; tet = tet->next, count++) ; /* * If we went all the way through the list of Tetrahedra * without finding position which_tet, then something * is very wrong. */ if (tet == &manifold->tet_list_end) uFatalError("get_tet_shape", "interface"); /* * If fixed_alignment is TRUE, use a fixed coordinate system. * Otherwise choose the_coordinate_system so that the longest side * of the triangle is the initial side of the angle. */ if (fixed_alignment == TRUE) the_coordinate_system = 0; else the_coordinate_system = (longest_side(tet) + 1) % 3; /* * Note the addresses of the ultimate and penultimate shapes. */ ultimate_shape = &tet->shape[filled]->cwl[ ultimate ][the_coordinate_system]; penultimate_shape = &tet->shape[filled]->cwl[penultimate][the_coordinate_system]; /* * Report the ultimate shapes. */ *shape_rect_real = ultimate_shape->rect.real; *shape_rect_imag = ultimate_shape->rect.imag; *shape_log_real = ultimate_shape->log.real; *shape_log_imag = ultimate_shape->log.imag; /* * Estimate the precision. */ *precision_rect_real = decimal_places_of_accuracy(ultimate_shape->rect.real, penultimate_shape->rect.real); *precision_rect_imag = decimal_places_of_accuracy(ultimate_shape->rect.imag, penultimate_shape->rect.imag); *precision_log_real = decimal_places_of_accuracy(ultimate_shape->log.real, penultimate_shape->log.real); *precision_log_imag = decimal_places_of_accuracy(ultimate_shape->log.imag, penultimate_shape->log.imag); /* * Check whether the tetrahedron is geometric. */ *is_geometric = tetrahedron_is_geometric(tet); } static int longest_side( Tetrahedron *tet) { int i, desired_index; double sine[3], max_sine; /* * longest_side() returns the index (0, 1 or 2) of the edge opposite * the longest side of tet's triangular vertex cross section. * * We'll use the Law of Sines, which says that the lengths of a triangle's * sides are proportional to the sines of the opposite angles. * * We take the absolute value of each sine, just in case the * Tetrahedron is negatively oriented. */ for (i = 0; i < 3; i++) sine[i] = fabs(tet->shape[filled]->cwl[ultimate][i].rect.imag) / complex_modulus(tet->shape[filled]->cwl[ultimate][i].rect); max_sine = -1.0; for (i = 0; i < 3; i++) if (sine[i] > max_sine) { max_sine = sine[i]; desired_index = i; } return desired_index; } int get_num_edge_classes( Triangulation *manifold, int edge_class_order, Boolean greater_than_or_equal) { int count; EdgeClass *edge; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if ( greater_than_or_equal ? edge->order >= edge_class_order : edge->order == edge_class_order) count++; return count; } snappea-3.0d3/SnapPeaKernel/code/intersection_numbers.c0100444000175000017500000001774706742675501021326 0ustar babbab/* * intersection_numbers.c * * This file provides the kernel function * * void compute_intersection_numbers(Triangulation *manifold); * * which computes the intersection numbers of the curves stored * in the scratch_curve[][][][][] fields of the Tetrahedra, and * writes the results to the intersection_number[][] fields of * the Cusps. That is, * * intersection_number[M][M] will be the intersection number of * scratch_curve[0][M] with scratch_curve[1][M], * * intersection_number[M][L] will be the intersection number of * scratch_curve[0][M] with scratch_curve[1][L], * * intersection_number[L][M] will be the intersection number of * scratch_curve[0][L] with scratch_curve[1][M], * * intersection_number[L][L] will be the intersection number of * scratch_curve[0][L] with scratch_curve[1][L]. * * Each intersection number is the algebraic sum of the crossing * numbers. As viewed from infinity (looking toward the fat * part of the manifold), each crossing of the form * * scratch_curve[1] * ^ * | contributes +1, * ----|---> scratch_curve[0] * | * | * * while each crossing of the form * * * scratch_curve[0] * ^ * | contributes -1, * ----|---> scratch_curve[1] * | * | * * This file also provides the utility * * void copy_curves_to_scratch( Triangulation *manifold, * int which_set, * Boolean double_copy_on_tori); * * which copies the current peripheral curves to the scratch_curves[which_set] * fields of the manifold's Tetrahedra. If double_copy_on_tori is TRUE, * it copies peripheral curves on orientable cusps to both sheets of * the Cusps' orientation double covers. * * * Overview of Intersection Number Algorithm. * * Consider the triangulation of the boundary components by the * triangles at the (truncated) ideal vertices. As explained * in peripheral_curves.c, we work in the orientation double cover, * so in fact each ideal vertex contributes two triangles, one * left_handed and the other right_handed. Relative to the * orientation on the cusp (and even nonorientable cusps are * effectively oriented, since we work in the orientation double * cover) we imagine each scratch_curve[0] entering a given triangle * on the right side of a given edge, and each scratch_curve[1] * entering on the left: * * /\ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / 1 \ / 0 \ * /__________\/__________\ * \ /\ / * \ 0 / \ 1 / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \/ * * Of necessity, the curves must cross on the edge (if both are * nonzero). There may be additional crossings in the interior of * the triangle, depending on where the various curves are entering * and exiting. To avoid counting the intersections on the edges * twice (once for each of the two incident triangles) we make the * convention to count edge crossings only where scratch_curve[0] is * entering (not exiting) the triangle. */ #include "kernel.h" void compute_intersection_numbers( Triangulation *manifold) { Cusp *cusp; Tetrahedron *tet; int f, g, h, i, j, face_on_the_left, face_on_the_right; /* * Initialize all the intersection numbers to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ for (j = 0; j < 2; j++) /* j = M, L */ cusp->intersection_number[i][j] = 0; /* * Count the intersections on the edges. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) /* which side of the vertex */ for (j = 0; j < 4; j++) { if (i == j) continue; /* which sheet (right_handed or left_handed) */ for (f = 0; f < 2; f++) /* which scratch_curve[0] (meridian or longitude) */ for (g = 0; g < 2; g++) /* which scratch_curve[1] (meridian or longitude) */ for (h = 0; h < 2; h++) /* * Recall the convention (described at the top * of this file) that edge crossings are counted * only where scratch_curve[0] is entering -- * not exiting -- the triangle. */ if (tet->scratch_curve[0][g][f][i][j] > 0) tet->cusp[i]->intersection_number[g][h] += tet->scratch_curve[0][g][f][i][j] * tet->scratch_curve[1][h][f][i][j]; } /* * Count the intersections in the interiors of triangles. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) /* which side of the vertex */ for (j = 0; j < 4; j++) { if (i == j) continue; /* * Name the two remaining faces of the Tetrahedron * according to the right_handed orientation of * the Tetrahedron. */ face_on_the_left = remaining_face[i][j]; face_on_the_right = remaining_face[j][i]; /* which scratch_curve[0] (meridian or longitude) */ for (g = 0; g < 2; g++) /* which scratch_curve[1] (meridian or longitude) */ for (h = 0; h < 2; h++) { /* * We'll count only those intersections on the * strand of scratch_curve[0] running from the * current side of the triangle (side j) towards * the right. The other possibilities will be * handled by other values of j. * * When we see the Tetrahedron as left_handed * relative to the Orientation of the cusp, the * face on the right is face_on_the_left. * Got that? */ tet->cusp[i]->intersection_number[g][h] += FLOW(tet->scratch_curve[0][g][right_handed][i][j], tet->scratch_curve[0][g][right_handed][i][face_on_the_right]) * tet->scratch_curve[1][h][right_handed][i][face_on_the_right]; tet->cusp[i]->intersection_number[g][h] += FLOW(tet->scratch_curve[0][g][left_handed][i][j], tet->scratch_curve[0][g][left_handed][i][face_on_the_left]) * tet->scratch_curve[1][h][left_handed][i][face_on_the_left]; } } } void copy_curves_to_scratch( Triangulation *manifold, int which_set, Boolean double_copy_on_tori) { Tetrahedron *tet; int i, j, k, l; /* * When computing intersection numbers on orientable cusps * (especially in nonorientable manifolds) there is a danger that * scratch_curves[0] will lie on one sheet of the Cusp's orientation * double cover while scratch_curves[1] lies on the other sheet. * To avoid this danger, copy_curves_to_scratch() offers the * option to copy the peripheral curves on orientable cusps to * both sheets of the double cover. You should use this option * for one set of scratch_curves (e.g. scratch_curves[0]) but not * the other (scratch_curves[1]) to guarantee correct intersection * numbers. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) if (tet->cusp[k]->topology == torus_cusp && double_copy_on_tori == TRUE) tet->scratch_curve[which_set][i][right_handed][k][l] = tet->scratch_curve[which_set][i][ left_handed][k][l] = tet->curve[i][right_handed][k][l] + tet->curve[i][ left_handed][k][l]; else /* * tet->cusp[k]->topology == Klein_cusp * || double_copy_on_tori == FALSE */ for (j = 0; j < 2; j++) tet->scratch_curve[which_set][i][j][k][l] = tet->curve[i][j][k][l]; } snappea-3.0d3/SnapPeaKernel/code/isometry.c0100444000175000017500000002365206742675502016731 0ustar babbab/* * isometry.c * * This file provides the functions * * FuncResult compute_isometries( * Triangulation *manifold0, * Triangulation *manifold1, * Boolean *are_isometric, * IsometryList **isometry_list, * IsometryList **isometry_list_of_links); * * Checks whether manifold0 and manifold1 are isometric * (taking into account the Dehn fillings) and sets * the flag *are_isometric accordingly. If manifold0 and * manifold1 are cusped manifolds, sets *isometry_list and * *isometry_list_of_links as in compute_cusped_isometries() * in isometry_cusped.c. You may pass NULL for isometry_list * and isometry_list_of_links if you aren't interested in the * IsometryLists. But if you do take them, be sure to free * them with free_isometry_list() when you're done. * * compute_isometries() results are 100% rigorous. * It reports an isometry only when it has found identical * Triangulations (with identical Dehn fillings in the case * of closed manifolds). It reports nonisometry only when * some discrete invariant (number of cusps or first homology * group) distinguishes the manifolds. [96/12/6 This doesn't * seem to be the case. It looks like the code is willing * to report a nonisometry when canonical cell decompositions * fail to match. That's plenty rigorous, but the canonical * decomposition does rely on the accuracy of the hyperbolic * structure.] * * Returns * * func_OK if all goes well, * * func_bad_input if some Dehn filling coefficients are not * relatively prime integers, * * func_failed if it can't decide. * * int isometry_list_size(IsometryList *isometry_list); * * Returns the number of Isometries in the IsometryList. * * int isometry_list_num_cusps(IsometryList *isometry_list); * * Returns the number of cusps in each of the underlying * manifolds. If the IsometryList is empty (as would be the * case when the underlying manifolds have different numbers * of cusps), then isometry_list_num_cusps()'s return value * is undefined. * * void isometry_list_cusp_action( IsometryList *isometry_list, * int anIsometryIndex, * int aCusp, * int *cusp_image, * int cusp_map[2][2]); * * Fills in the cusp_image and cusp_map[2][2] to describe the * action of the given Isometry on the given Cusp. * * Boolean isometry_extends_to_link( IsometryList *isometry_list, * int i); * * Returns TRUE if Isometry i extends to the associated links * (i.e. if it takes meridians to meridians), FALSE if it doesn't. * * void isometry_list_orientations( * IsometryList *isometry_list, * Boolean *contains_orientation_preserving_isometries, * Boolean *contains_orientation_reversing_isometries); * * Says whether the IsometryList contains orientation-preserving * and/or orientation-reversing elements. Assumes the underlying * Triangulations are oriented. * * void free_isometry_list(IsometryList *isometry_list); * * Frees the IsometryList. * * The UI never explicitly looks at an IsometryList. It only passes * them to the above kernel functions to obtain information. */ #include "kernel.h" #define CRUDE_VOLUME_EPSILON 0.01 static int count_unfilled_cusps(Triangulation *manifold); static Boolean same_homology(Triangulation *manifold0, Triangulation *manifold1); static void free_isometry(Isometry *isometry); FuncResult compute_isometries( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric, IsometryList **isometry_list, IsometryList **isometry_list_of_links) { Triangulation *simplified_manifold0, *simplified_manifold1; IsometryList *the_isometry_list, *the_isometry_list_of_links; FuncResult result; /* * Make sure the variables used to pass back our results * are initially empty. */ if ((isometry_list != NULL && *isometry_list != NULL) || (isometry_list_of_links != NULL && *isometry_list_of_links != NULL)) uFatalError("compute_isometries", "isometry"); /* * If one of the spaces isn't a manifold, return func_bad_input. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold0) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold1) == FALSE) return func_bad_input; /* * Check whether the manifolds are obviously nonhomeomorphic. * * (In the interest of a robust, beyond-a-shadow-of-a-doubt algorithm, * stick to discrete invariants like the number of cusps and the * first homology group, and avoid real-valued invariants like * the volume which require judging when two floating point numbers * are equal.) [96/12/6 Comparing canonical triangulations * relies on having at least a vaguely correct hyperbolic structure, * so it should be safe to reject manifolds whose volumes differ * by more than, say, 0.01.] */ if (count_unfilled_cusps(manifold0) != count_unfilled_cusps(manifold1) || same_homology(manifold0, manifold1) == FALSE || ( manifold0->solution_type[filled] == geometric_solution && manifold1->solution_type[filled] == geometric_solution && fabs(volume(manifold0, NULL) - volume(manifold1, NULL)) > CRUDE_VOLUME_EPSILON)) { *are_isometric = FALSE; return func_OK; } /* * Whether the actual manifolds (after taking into account Dehn * fillings) have cusps or not, we want to begin by getting rid * of "unnecessary" cusps. */ simplified_manifold0 = fill_reasonable_cusps(manifold0); if (simplified_manifold0 == NULL) return func_failed; simplified_manifold1 = fill_reasonable_cusps(manifold1); if (simplified_manifold1 == NULL) return func_failed; /* * Split into cases according to whether the manifolds are * closed or cusped (i.e. whether all cusps are filled or not). * The above tests insure that either both are closed or both * are cusped. */ if (all_cusps_are_filled(simplified_manifold0) == TRUE) result = compute_closed_isometry( simplified_manifold0, simplified_manifold1, are_isometric); else { result = compute_cusped_isometries( simplified_manifold0, simplified_manifold1, &the_isometry_list, &the_isometry_list_of_links); if (result == func_OK) { *are_isometric = the_isometry_list->num_isometries > 0; if (isometry_list != NULL) *isometry_list = the_isometry_list; else free_isometry_list(the_isometry_list); if (isometry_list_of_links != NULL) *isometry_list_of_links = the_isometry_list_of_links; else free_isometry_list(the_isometry_list_of_links); } } /* * We no longer need the simplified manifolds. */ free_triangulation(simplified_manifold0); free_triangulation(simplified_manifold1); return result; } static int count_unfilled_cusps( Triangulation *manifold) { int num_unfilled_cusps; Cusp *cusp; num_unfilled_cusps = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == TRUE) num_unfilled_cusps++; return num_unfilled_cusps; } static Boolean same_homology( Triangulation *manifold0, Triangulation *manifold1) { AbelianGroup *g0, *g1; Boolean groups_are_isomorphic; int i; /* * Compute the homology groups. */ g0 = homology(manifold0); g1 = homology(manifold1); /* * compute_isometries() has already checked that both manifolds * really are manifolds, so neither g0 nor g1 should be NULL. */ if (g0 == NULL || g1 == NULL) uFatalError("same_homology", "isometry"); /* * Put the homology groups into a canonical form. */ compress_abelian_group(g0); compress_abelian_group(g1); /* * Compare the groups. */ if (g0->num_torsion_coefficients != g1->num_torsion_coefficients) groups_are_isomorphic = FALSE; else { groups_are_isomorphic = TRUE; /* innocent until proven guilty */ for (i = 0; i < g0->num_torsion_coefficients; i++) if (g0->torsion_coefficients[i] != g1->torsion_coefficients[i]) groups_are_isomorphic = FALSE; } /* * Free the Abelian groups. */ free_abelian_group(g0); free_abelian_group(g1); return groups_are_isomorphic; } int isometry_list_size( IsometryList *isometry_list) { return isometry_list->num_isometries; } int isometry_list_num_cusps( IsometryList *isometry_list) { return isometry_list->isometry[0]->num_cusps; } void isometry_list_cusp_action( IsometryList *isometry_list, int anIsometryIndex, int aCusp, int *cusp_image, int cusp_map[2][2]) { Isometry *the_isometry; int i, j; the_isometry = isometry_list->isometry[anIsometryIndex]; *cusp_image = the_isometry->cusp_image[aCusp]; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) cusp_map[i][j] = the_isometry->cusp_map[aCusp][i][j]; } Boolean isometry_extends_to_link( IsometryList *isometry_list, int i) { return isometry_list->isometry[i]->extends_to_link; } void isometry_list_orientations( IsometryList *isometry_list, Boolean *contains_orientation_preserving_isometries, Boolean *contains_orientation_reversing_isometries) { /* * This function assumes the underlying Triangulations * are oriented. */ int i; *contains_orientation_preserving_isometries = FALSE; *contains_orientation_reversing_isometries = FALSE; for (i = 0; i < isometry_list->num_isometries; i++) if (parity[isometry_list->isometry[i]->tet_map[0]] == 0) *contains_orientation_preserving_isometries = TRUE; else *contains_orientation_reversing_isometries = TRUE; } void free_isometry_list( IsometryList *isometry_list) { int i; if (isometry_list != NULL) { for (i = 0; i < isometry_list->num_isometries; i++) free_isometry(isometry_list->isometry[i]); if (isometry_list->num_isometries != 0) my_free(isometry_list->isometry); my_free(isometry_list); } } static void free_isometry( Isometry *isometry) { my_free(isometry->tet_image); my_free(isometry->tet_map); my_free(isometry->cusp_image); my_free(isometry->cusp_map); my_free(isometry); } snappea-3.0d3/SnapPeaKernel/code/isometry_closed.c0100444000175000017500000002043006742675502020251 0ustar babbab/* * isometry_closed.c * * This file provides the function * * FuncResult compute_closed_isometry( Triangulation *manifold0, * Triangulation *manifold1, * Boolean *are_isometric); * * If compute_closed_isometry() determines with absolute rigor that * manifold0 and manifold1 are isometric, it sets *are_isometric * to TRUE and returns func_OK. * * If it determines with absolute rigor that manifold0 and manifold1 are * nonhomeomorphic, sets *are_isometric to FALSE and returns func_OK. * [But at present this case doesn't occur -- see below.] * * If it fails to decide, it returns func_failed. * * AT PRESENT compute_closed_isometry() WILL NEVER REPORT THE MANIFOLDS * TO BE NONISOMETRIC. It relies on compute_isometries() to detect * different numbers of cusps or different first homology. * This insures that the reported results are always 100% rigorous, * whenever they are reported at all. * * Technical details: In the interest of speed and robustness, * compute_closed_isometry() does not at present use a length spectrum. * So if it drills the unique geodesic of a given length from each of * the two manifolds and finds the complements are nonhomeomorphic, it * will *not* report the nonhomeomorphism. If need be, this capability * could be added later. */ #include "kernel.h" #define MAX_DUAL_CURVE_LENGTH 8 /* * We can afford to make LENGTH_EPSILON and TORSION_EPSILON large, * because the only danger is a loss of speed resulting from unnecessary * comparisons, and even that is unlikely. In particular, * compute_closed_isometry() will never report incorrect results, * no matter how large LENGTH_EPSILON and TORSION_EPSILON are. */ #define LENGTH_EPSILON 1e-3 #define TORSION_EPSILON 1e-3 static Boolean manifolds_are_isometric(Triangulation *original_manifold0, Triangulation *original_manifold1, DualOneSkeletonCurve *curve0, DualOneSkeletonCurve *curve1); static void change_Dehn_filling_description(Triangulation **manifold, DualOneSkeletonCurve *curve); FuncResult compute_closed_isometry( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric) { int num_curves0, num_curves1; DualOneSkeletonCurve **the_curves0, **the_curves1; int singularity_index; Complex length0, length1; int i, j; /* * We assume the calling function (e.g. compute_isometries()) * has checked that the manifolds are not obviously nonhomeomorphic, * so we don't repeat the check here. * * We also assume that the manifolds have one cusp each, * and are Dehn filled. */ if (get_num_cusps(manifold0) != 1 || all_cusps_are_filled(manifold0) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold0) == FALSE || get_num_cusps(manifold1) != 1 || all_cusps_are_filled(manifold1) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold1) == FALSE) { uFatalError("compute_closed_isometry", "isometry_closed"); } /* * For later convenience, change the bases on the cusps * so that the Dehn filling curves become meridians. */ { MatrixInt22 basis_change[1]; current_curve_basis(manifold0, 0, basis_change[0]); change_peripheral_curves(manifold0, basis_change); current_curve_basis(manifold1, 0, basis_change[0]); change_peripheral_curves(manifold1, basis_change); } /* * See what curves are drillable. */ dual_curves(manifold0, MAX_DUAL_CURVE_LENGTH, &num_curves0, &the_curves0); dual_curves(manifold1, MAX_DUAL_CURVE_LENGTH, &num_curves1, &the_curves1); /* * Compare each drillable curve in manifold0 (including the core * geodesic of the given Dehn filling description) to each drillable * curve in manifold1 (including the core geodesic of the given * Dehn filling description). If the complex lengths match, drill * out each curve (if it's not already the core geodesic) and see * whether the complements match by a meridian-preserving isometry. * * In the following code, the case i = -1 (resp. j = -1) handles * the original core geodesic of manifold0 (resp. manifold1). */ *are_isometric = FALSE; /* assume FALSE until proven TRUE */ for (i = -1; i < num_curves0 && *are_isometric == FALSE; i++) { /* * Get the length of curve i in manifold0. */ if (i == -1) /* get the length of the core geodesic */ core_geodesic(manifold0, 0, &singularity_index, &length0, NULL); else /* get the length of the_curves0[i] */ get_dual_curve_info(the_curves0[i], NULL, &length0, NULL); for (j = -1; j < num_curves1 && *are_isometric == FALSE; j++) { /* * Get the length of curve j in manifold1. */ if (j == -1) /* get the length of the core geodesic */ core_geodesic(manifold1, 0, &singularity_index, &length1, NULL); else /* get the length of the_curves1[j] */ get_dual_curve_info(the_curves1[j], NULL, &length1, NULL); /* * If the lengths and absolute values of torsions match, * drill out the corresponding curves and check for a * meridian-preserving isometry. */ if (fabs( length0.real - length1.real ) < LENGTH_EPSILON && fabs(fabs(length0.imag) - fabs(length1.imag)) < TORSION_EPSILON) if (manifolds_are_isometric( manifold0, manifold1, (i != -1) ? the_curves0[i] : NULL, (j != -1) ? the_curves1[j] : NULL) == TRUE) *are_isometric = TRUE; } } /* * Free the lists of drillable curves. */ free_dual_curves(num_curves0, the_curves0); free_dual_curves(num_curves1, the_curves1); if (*are_isometric == TRUE) return func_OK; else return func_failed; } static Boolean manifolds_are_isometric( Triangulation *original_manifold0, Triangulation *original_manifold1, DualOneSkeletonCurve *curve0, DualOneSkeletonCurve *curve1) { /* * manifolds_are_isometric() returns * * TRUE if the manifolds are definitely isometric, or * FALSE if it can't tell. * * It never reports a definite nonisometry. */ Triangulation *manifold0, *manifold1; IsometryList *isometry_list, *isometry_list_of_links; Boolean result; /* * Make copies of the manifolds so we don't trash the originals. */ copy_triangulation(original_manifold0, &manifold0); copy_triangulation(original_manifold1, &manifold1); /* * Drill out the given curves if necessary. * * If manifold0 (resp. manifold1) requires no drilling, * curve0 (resp. curve1) will be NULL. * * If change_Dehn_filling_description() fails, it frees the manifold * and sets the pointer to NULL. */ change_Dehn_filling_description(&manifold0, curve0); change_Dehn_filling_description(&manifold1, curve1); /* * Check for a failure. */ if (manifold0 == NULL || manifold1 == NULL) { free_triangulation(manifold0); /* NULL is OK */ free_triangulation(manifold1); /* NULL is OK */ return FALSE; } /* * Have we got an isometry? */ if (compute_cusped_isometries( manifold0, manifold1, &isometry_list, &isometry_list_of_links) == func_OK) { result = (isometry_list_of_links->num_isometries > 0); free_isometry_list(isometry_list); free_isometry_list(isometry_list_of_links); } else result = FALSE; free_triangulation(manifold0); free_triangulation(manifold1); return result; } static void change_Dehn_filling_description( Triangulation **manifold, DualOneSkeletonCurve *curve) { Triangulation *new_manifold; Boolean fill_cusp[2] = {TRUE, FALSE}; /* * compute_closed_isometry() pass NULL to manifolds_are_isometric() * to indicate that it should keep the existing core curve, and * manifolds_are_isometric() pass that value on to us. */ if (curve == NULL) return; /* * Drill out the indicated curve. */ new_manifold = drill_cusp(*manifold, curve, "no name"); free_triangulation(*manifold); *manifold = new_manifold; if (*manifold == NULL) return; new_manifold = NULL; /* * Set the new Dehn filling coefficient to (1, 0) * to recover the closed manifold. */ set_cusp_info(*manifold, 1, FALSE, 1.0, 0.0); do_Dehn_filling(*manifold); /* * Permanently fill the original cusp. */ new_manifold = fill_cusps(*manifold, fill_cusp, "no name", FALSE); free_triangulation(*manifold); *manifold = new_manifold; new_manifold = NULL; /* * *manifold may or may not be NULL, but we've done our best * so we return. (Actually, fill_cusps() is unlikely to fail.) */ } snappea-3.0d3/SnapPeaKernel/code/isometry_cusped.c0100444000175000017500000006331607041100747020260 0ustar babbab/* * isometry_cusped.c * * This file provides the function * * FuncResult compute_cusped_isometries( * Triangulation *manifold0, * Triangulation *manifold1, * IsometryList **isometry_list, * IsometryList **isometry_list_of_links); * * compute_cusped_isometries() computes a list of all Isometries from * manifold0 to manifold1 and stores its address in *isometry_list. * If isometry_list_of_links is not NULL, it copies those Isometries * which extend to Isometries of the associated links (i.e. those * which take meridians to plus-or-minus meridians) onto a separate * list, and stores its address in *isometry_list_of_links. * * compute_cusped_isometries() returns func_OK if it can get a canonical * decomposition for both manifold0 and manifold1, and func_failed if it * can't (because one or both lacks a hyperbolic structure). * * compute_cusped_isometries() allocates the IsometryList data structures. * You should declare isometry_list as IsometryList *isometry_list, and pass * &isometry_list to compute_cusped_isometries(), and similarly for * isometry_list_of_links. */ #include "kernel.h" static FuncResult attempt_isometry(Triangulation *manifold0, Tetrahedron *tet0, Tetrahedron *tet1, Permutation map0); static Boolean is_isometry_plausible(Tetrahedron *initial_tet0, Tetrahedron *initial_tet1, Permutation initial_map); static void copy_isometry(Triangulation *manifold0, Triangulation *manifold1, Isometry **new_isometry); static void compute_cusp_action(Triangulation *manifold0, Triangulation *manifold1, Isometry *isometry); static void compute_cusp_image(Triangulation *manifold0, Isometry *isometry); static void compute_cusp_map(Triangulation *manifold0, Triangulation *manifold1, Isometry *isometry); static void copy_images_to_scratch(Triangulation *manifold, int which_set, Boolean double_copy_on_tori); static Boolean does_isometry_extend_to_link(Isometry *isometry); static void make_isometry_array(IsometryList *isometry_list, Isometry *the_linked_list); static void find_isometries_which_extend(IsometryList *isometry_list, IsometryList **isometry_list_of_links); FuncResult compute_cusped_isometries( Triangulation *manifold0, Triangulation *manifold1, IsometryList **isometry_list, IsometryList **isometry_list_of_links) { Triangulation *copy_of_manifold0, *copy_of_manifold1; Isometry *partial_isometry_list, *new_isometry; Tetrahedron *tet0, *tet1; int i; /* * If manifold0 != manifold1 we are computing the isometries from one * manifold to another. We begin by making a copy of each manifold * and converting it to the canonical retriangulation of the * canonical cell decomposition. */ if (manifold0 != manifold1) { /* * Make copies of the manifolds. */ copy_triangulation(manifold0, ©_of_manifold0); copy_triangulation(manifold1, ©_of_manifold1); /* * Find the canonical triangulations of the copies. */ if (canonize(copy_of_manifold0) == func_failed || canonize(copy_of_manifold1) == func_failed) { free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); *isometry_list = NULL; *isometry_list_of_links = NULL; return func_failed; } } /* * If manifold0 == manifold1 we are computing the symmetries from * a manifold to itself. In this case we find the canonical * retriangulation of the canonical cell decomposition before making * the second copy. This serves two purposes: * * (1) It reduces the run time, because we avoid a redundant call * to canonize(). * * (2) It insures that the Tetrahedra will be listed in the same * order in the two manifolds. This makes it easy for the * symmetry group program to recognize the identity symmetry. * (Perhaps you are curious as to how the Tetrahedra could * possibly fail to be listed in the same order. In rare * cases canonize() must do some random retriangulation. * If this is done to two identical copies of a Triangulation * the final canonical retriangulations will of course be * combinatorially the same, but the Tetrahedra might be listed * in different orders and numbered differently.) */ else { /* manifold0 == manifold1 */ /* * Make one copy of the manifold. */ copy_triangulation(manifold0, ©_of_manifold0); /* * Find the canonical retriangulation. */ if (canonize(copy_of_manifold0) == func_failed) { free_triangulation(copy_of_manifold0); *isometry_list = NULL; *isometry_list_of_links = NULL; return func_failed; } /* * Make a second copy of the canonical retriangulation. */ copy_triangulation(copy_of_manifold0, ©_of_manifold1); } /* * Allocate space for the IsometryList and initialize it. */ *isometry_list = NEW_STRUCT(IsometryList); (*isometry_list)->num_isometries = 0; (*isometry_list)->isometry = NULL; /* * If isometry_list_of_links is not NULL, allocate and * initialize *isometry_list_of_links as well. */ if (isometry_list_of_links != NULL) { *isometry_list_of_links = NEW_STRUCT(IsometryList); (*isometry_list_of_links)->num_isometries = 0; (*isometry_list_of_links)->isometry = NULL; } /* * If the two manifolds don't have the same number of * Tetrahedra, then there can't possibly be any isometries * between the two. We just initialized the IsometryList(s) * to be empty, so if there aren't any isometries, we're * ready free the copies of the manifolds and return. */ if (copy_of_manifold0->num_tetrahedra != copy_of_manifold1->num_tetrahedra) { free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); return func_OK; } /* * partial_isometry_list will keep a linked list of the * Isometries we discover. When we're done we'll allocate * the array (*isometry_list)->isometry and copy the * addresses of the Isometries into it. For now, we * initialize partial_isometry_list to NULL to show that * the linked list is empty. */ partial_isometry_list = NULL; /* * Assign indices to the Tetrahedra in each manifold. */ number_the_tetrahedra(copy_of_manifold0); number_the_tetrahedra(copy_of_manifold1); /* * Try mapping an arbitrary but fixed Tetrahedron of * manifold0 ("tet0") to each Tetrahedron of manifold1 * in turn, with each possible Permutation. See which * of these maps extends to a global isometry. We're * guaranteed to find all possible isometries this way * (and typically quite a lot of other garbage as well). */ /* * Let tet0 be the first Tetrahedron on manifold0's list. */ tet0 = copy_of_manifold0->tet_list_begin.next; /* * Consider each Tetrahedron in manifold1. */ for (tet1 = copy_of_manifold1->tet_list_begin.next; tet1 != ©_of_manifold1->tet_list_end; tet1 = tet1->next) /* * Consider each of the 24 possible ways to map tet0 to tet1. */ for (i = 0; i < 24; i++) /* * Does mapping tet0 to tet1 via permutation_by_index[i] * define an isometry? */ if (attempt_isometry(copy_of_manifold0, tet0, tet1, permutation_by_index[i]) == func_OK) { /* * Copy the isometry to an Isometry data structure . . . */ copy_isometry(copy_of_manifold0, copy_of_manifold1, &new_isometry); /* * . . . add it to the partial_isometry_list . . . */ new_isometry->next = partial_isometry_list; partial_isometry_list = new_isometry; /* * . . . and increment the count. */ (*isometry_list)->num_isometries++; } /* * If some Isometries were found, allocate space for the * array (*isometry_list)->isometry and write the addresses * of the Isometries into it. */ make_isometry_array(*isometry_list, partial_isometry_list); /* * If isometry_list_of_links is not NULL, make a copy of * those Isometries which extend to the associated link * (i.e. those which take meridians to meridians). */ find_isometries_which_extend(*isometry_list, isometry_list_of_links); /* * Discard the copies of the manifolds. */ free_triangulation(copy_of_manifold0); free_triangulation(copy_of_manifold1); return func_OK; } /* * attempt_isometry() checks whether sending tet0 (of manifold0) * to tet1 (of manifold1, which may or may equal manifold0) * defines an isometry between the two manifolds. If it does, * it leaves the isometry in the "image" and "map" fields of * the Tetrahedra of manifold0 (see the documentation at the top * of isometry.h for details), and returns func_OK. If it doesn't * define an isometry, it leaves garbage in the "image" and "map" * fields and returns func_failed. * * Technical note: attempt_isometry() could be written using a simple * recursive function to set the image and map fields, but I'm trying * to avoid recursions, because on some machines (e.g. older Macs) * a stack-heap collision is a possibility if one has a deep recursion * with a lot of nonstatic local variables, whereas in the future static * local variables are likely to cause problems in a multithreaded * environment. So . . . better to avoid the recursion. The actual * algorithm is documented in the following code. */ static FuncResult attempt_isometry( Triangulation *manifold0, Tetrahedron *initial_tet0, Tetrahedron *initial_tet1, Permutation initial_map) { Tetrahedron *tet0, *tet1, *nbr0, *nbr1, **queue; int first, last; FaceIndex face0, face1; Permutation gluing0, gluing1, nbr0_map; /* * initial_tet1 and initial_map are arbitrary, so * the vast majority of calls to attempt_isometry() * will fail. Therefore it's worth including a quick * plausibility check at the beginning, to make the * algorithm run faster. */ if (is_isometry_plausible(initial_tet0, initial_tet1, initial_map) == FALSE) return func_failed; /* * Initialize all the image fields of manifold0 * to NULL to show they haven't been set. */ for ( tet0 = manifold0->tet_list_begin.next; tet0 != &manifold0->tet_list_end; tet0 = tet0->next) tet0->image = NULL; /* * Allocate space for a queue which is large enough * to hold pointers to all the Tetrahedra in manifold0. */ queue = NEW_ARRAY(manifold0->num_tetrahedra, Tetrahedron *); /* * At all times, the Tetrahedra on the queue will be those which * * (1) have set their image and map fields, but * * (2) have not checked their neighbors. */ /* * Set the image and map fields for initial_tet0. */ initial_tet0->image = initial_tet1; initial_tet0->map = initial_map; /* * Put initial_tet0 on the queue. */ first = 0; last = 0; queue[first] = initial_tet0; /* * While there are Tetrahedra on the queue . . . */ while (last >= first) { /* * Pull the first Tetrahedron off the queue and call it tet0. */ tet0 = queue[first++]; /* * tet0 maps to some Tetrahedron tet1 in manifold1. */ tet1 = tet0->image; /* * For each face of tet0 . . . */ for (face0 = 0; face0 < 4; face0++) { /* * Let nbr0 be the Tetrahedron which meets tet0 at face0. */ nbr0 = tet0->neighbor[face0]; /* * tet0->map takes face0 of tet0 to face1 of tet1. */ face1 = EVALUATE(tet0->map, face0); /* * Let nbr1 be the Tetrahedron which meets tet1 at face1. */ nbr1 = tet1->neighbor[face1]; /* * Let gluing0 be the gluing which identifies face0 of * tet0 to nbr0, and similarly for gluing1. */ gluing0 = tet0->gluing[face0]; gluing1 = tet1->gluing[face1]; /* * gluing0 * tet0 ------> nbr0 * | | * tet0->map | | nbr0->map * | | * V gluing1 V * tet1 ------> nbr1 * * We want to ensure that tet1 and nbr1 enjoy the same * relationship to each other in manifold1 that tet0 and * nbr0 do in manifold0. The conditions * * nbr0->image == nbr1 * and * nbr0->map == gluing1 o tet0->map o gluing0^-1 * * are necessary and sufficient to insure that we have a * combinatorial equivalence between the Triangulations. * (The proof relies on the fact that we've already checked * (near the beginning of compute_cusped_isometries() above) * that the Triangulations have the same number of Tetrahedra; * otherwise one Triangulation could be a (possibly branched) * covering of the other.) */ /* * Compute the required value for nbr0->map. */ nbr0_map = compose_permutations( compose_permutations( gluing1, tet0->map ), inverse_permutation[gluing0] ); /* * If nbr0->image and nbr0->map have already been set, * check that they satisfy the above conditions. */ if (nbr0->image != NULL) { if (nbr0->image != nbr1 || nbr0->map != nbr0_map) { /* * This isn't an isometry. */ my_free(queue); return func_failed; } } /* * else . . . * nbr0->image and nbr0->map haven't been set. * Set them, and put nbr0 on the queue. */ else { nbr0->image = nbr1; nbr0->map = nbr0_map; queue[++last] = nbr0; } } } /* * A quick error check. * Is it plausible that each Tetrahedron * has been on the queue exactly once? */ if (first != manifold0->num_tetrahedra || last != manifold0->num_tetrahedra - 1) uFatalError("attempt_isometry", "isometry"); /* * Free the queue, and report success. */ my_free(queue); return func_OK; } static Boolean is_isometry_plausible( Tetrahedron *initial_tet0, Tetrahedron *initial_tet1, Permutation initial_map) { /* * To check whether an Isometry taking initial_tet0 to * initial_tet1 via initial_map is even plausible, let's * check whether their EdgeClass orders match up. */ int i, j; for (i = 0; i < 4; i++) for (j = i + 1; j < 4; j++) if (initial_tet0->edge_class[edge_between_vertices[i][j]]->order != initial_tet1->edge_class[edge_between_vertices[EVALUATE(initial_map, i)][EVALUATE(initial_map, j)]]->order) return FALSE; return TRUE; } /* * copy_isometry() assumes the "image" and "map" fields of * manifold0 describe an isometry to a (not necessarily * distinct) manifold1. It also assumes that the Tetrahedra * in both manifolds have been numbered as described in * symmetry.h. It allocates an Isometry data structure, * writes the isometry into it, and sets *new_isometry to * point to it. */ static void copy_isometry( Triangulation *manifold0, Triangulation *manifold1, Isometry **new_isometry) { Tetrahedron *tet0; int i; /* * Allocate the Isometry. */ *new_isometry = NEW_STRUCT(Isometry); (*new_isometry)->tet_image = NEW_ARRAY(manifold0->num_tetrahedra, int); (*new_isometry)->tet_map = NEW_ARRAY(manifold0->num_tetrahedra, Permutation); (*new_isometry)->cusp_image = NEW_ARRAY(manifold0->num_cusps, int); (*new_isometry)->cusp_map = NEW_ARRAY(manifold0->num_cusps, MatrixInt22); /* * Set the num_tetrahedra and num_cusps fields. */ (*new_isometry)->num_tetrahedra = manifold0->num_tetrahedra; (*new_isometry)->num_cusps = manifold0->num_cusps; /* * Copy the isometry from the Triangulation * to the Isometry data structure. */ for (tet0 = manifold0->tet_list_begin.next, i = 0; tet0 != &manifold0->tet_list_end; tet0 = tet0->next, i++) { (*new_isometry)->tet_image[i] = tet0->image->index; (*new_isometry)->tet_map[i] = tet0->map; } /* * How does this Isometry act on the Cusps? */ compute_cusp_action(manifold0, manifold1, *new_isometry); /* * We don't use the "next" field. */ (*new_isometry)->next = NULL; } /* * Given a Triangulation *manifold0 whose "image" and "map" fields * describe an isometry to some (not necessarily distinct) manifold, * compute_cusp_action() computes the action on the Cusps. Only real * cusps are considered -- finite vertices are ignored. */ static void compute_cusp_action( Triangulation *manifold0, Triangulation *manifold1, Isometry *isometry) { compute_cusp_image(manifold0, isometry); compute_cusp_map(manifold0, manifold1, isometry); isometry->extends_to_link = does_isometry_extend_to_link(isometry); } static void compute_cusp_image( Triangulation *manifold0, Isometry *isometry) { Tetrahedron *tet; VertexIndex v; /* * Examine each vertex of each Tetrahedron to see which * cusp is being taken to which. * * There's a tremendous amount of redundancy here -- each * cusp image is set over and over -- but it hardly matters. * * Ignore negatively indexed Cusps -- they're finite vertices. */ for (tet = manifold0->tet_list_begin.next; tet != &manifold0->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v]->index >= 0) isometry->cusp_image[tet->cusp[v]->index] = tet->image->cusp[EVALUATE(tet->map, v)]->index; } static void compute_cusp_map( Triangulation *manifold0, Triangulation *manifold1, Isometry *isometry) { Tetrahedron *tet; VertexIndex v; int i; /* * Copy the manifold1's peripheral curves into * scratch_curves[0], and copy the images of manifold0's * peripheral curves into scratch_curves[1]. * * When the manifold is orientable and the Isometry is * orientation-reversing, and sometimes when the manifold * is nonorientable, the images of the peripheral curves * of a torus will lie on the "wrong" sheet of the Cusp's * orientation double cover. (See peripheral_curves.c for * background material.) Therefore we copy the images of * the peripheral curves of torus cusps to both sheets of * the orientation double cover, to guarantee that the * intersection numbers come out right. */ copy_curves_to_scratch(manifold1, 0, FALSE); copy_images_to_scratch(manifold0, 1, TRUE); /* * Compute the intersection numbers of the images of manifold0's * peripheral curves with manifold1's peripheral curves.. */ compute_intersection_numbers(manifold1); /* * Now extract the cusp_maps from the linking numbers. * * There's a lot of redundancy in this loop, but a trivial * computation so the redundancy hardly matters. * * Ignore negatively indexed Cusps -- they're finite vertices. */ for (tet = manifold0->tet_list_begin.next; tet != &manifold0->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v]->index >= 0) for (i = 0; i < 2; i++) /* i = M, L */ { isometry->cusp_map[tet->cusp[v]->index][M][i] = + tet->image->cusp[EVALUATE(tet->map, v)]->intersection_number[L][i]; isometry->cusp_map[tet->cusp[v]->index][L][i] = - tet->image->cusp[EVALUATE(tet->map, v)]->intersection_number[M][i]; } } static void copy_images_to_scratch( Triangulation *manifold0, int which_set, Boolean double_copy_on_tori) { Tetrahedron *tet; int i, j, jj, k, kk, l, ll; /* * This function is modelled on copy_curves_to_scratch() * in intersection_numbers.c. */ /* * Note that even though we are passed manifold0 as an * explicit function parameter, we are ultimately writing * the image curves in manifold1. */ for (tet = manifold0->tet_list_begin.next; tet != &manifold0->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (k = 0; k < 4; k++) { kk = EVALUATE(tet->map, k); for (l = 0; l < 4; l++) { ll = EVALUATE(tet->map, l); if (tet->cusp[k]->topology == torus_cusp && double_copy_on_tori == TRUE) tet->image->scratch_curve[which_set][i][right_handed][kk][ll] = tet->image->scratch_curve[which_set][i][ left_handed][kk][ll] = tet->curve[i][right_handed][k][l] + tet->curve[i][ left_handed][k][l]; else /* * tet->cusp[k]->topology == Klein_cusp * || double_copy_on_tori == FALSE */ for (j = 0; j < 2; j++) { /* * parities can be tricky. * * When discussing gluings from a face of one Tetrahedron * to a face of another, an even parity corresponds to an * orientation-reversing gluing. * * When discussing a map from one Tetrahedron onto * another, an even parity is an orientation-preserving * map. */ /* * If tet->map has even parity (i.e. if it's an * orientation-preserving map) it will send the * right-handed vertex cross section to a right- * handed image. * * If tet->map has odd parity (i.e. if it's an * orientation-reversing map) it will send the * right-handed vertex cross section to a left- * handed image. */ jj = (parity[tet->map] == 0) ? j : !j; tet->image->scratch_curve[which_set][i][jj][kk][ll] = tet->curve[i][j][k][l]; } } } } static Boolean does_isometry_extend_to_link( Isometry *isometry) { /* * This function assumes the cusp_maps have been * computed, and checks whether they all take * meridians to meridians. */ int i; for (i = 0; i < isometry->num_cusps; i++) if (isometry->cusp_map[i][L][M] != 0) return FALSE; return TRUE; } static void make_isometry_array( IsometryList *isometry_list, Isometry *the_linked_list) { int i; Isometry *an_isometry; if (isometry_list->num_isometries == 0) isometry_list->isometry = NULL; else { isometry_list->isometry = NEW_ARRAY(isometry_list->num_isometries, Isometry *); for ( an_isometry = the_linked_list, i = 0; an_isometry != NULL && i < isometry_list->num_isometries; an_isometry = an_isometry->next, i++) isometry_list->isometry[i] = an_isometry; /* * A quick error check. */ if (an_isometry != NULL || i != isometry_list->num_isometries) uFatalError("make_isometry_array", "isometry"); } } static void find_isometries_which_extend( IsometryList *isometry_list, IsometryList **isometry_list_of_links) { Isometry *original, *copy; int i, j, k, l, count; /* * If the isometry_list_of_links isn't needed, don't compute it. */ if (isometry_list_of_links == NULL) return; /* * The function compute_cusped_isometries() (which calls this function) * has already allocated space for the new IsometryList. * (It has also intialized the num_isometries and isometry fields, * but we reinitialize them here just for good form.) */ /* * How many Isometries extend to the link? */ (*isometry_list_of_links)->num_isometries = 0; /* intentionally redundant */ for (i = 0; i < isometry_list->num_isometries; i++) if (isometry_list->isometry[i]->extends_to_link == TRUE) (*isometry_list_of_links)->num_isometries++; /* * If there are no Isometries which extend, set * (*isometry_list_of_links)->isometry to NULL and return. */ if ((*isometry_list_of_links)->num_isometries == 0) { (*isometry_list_of_links)->isometry = NULL; /* intentionally redundant */ return; } /* * Allocate space for the Isometries which extend to * Isometries of the associated links, and copy them in. */ (*isometry_list_of_links)->isometry = NEW_ARRAY((*isometry_list_of_links)->num_isometries, Isometry *); for (i = 0, count = 0; i < isometry_list->num_isometries; i++) if (isometry_list->isometry[i]->extends_to_link == TRUE) { (*isometry_list_of_links)->isometry[count] = NEW_STRUCT(Isometry); original = isometry_list->isometry[i]; copy = (*isometry_list_of_links)->isometry[count]; copy->num_tetrahedra = original->num_tetrahedra; copy->num_cusps = original->num_cusps; copy->tet_image = NEW_ARRAY(copy->num_tetrahedra, int); copy->tet_map = NEW_ARRAY(copy->num_tetrahedra, Permutation); for (j = 0; j < copy->num_tetrahedra; j++) { copy->tet_image[j] = original->tet_image[j]; copy->tet_map [j] = original->tet_map [j]; } copy->cusp_image = NEW_ARRAY(copy->num_cusps, int); copy->cusp_map = NEW_ARRAY(copy->num_cusps, MatrixInt22); for (j = 0; j < copy->num_cusps; j++) { copy->cusp_image[j] = original->cusp_image[j]; for (k = 0; k < 2; k++) for (l = 0; l < 2; l++) copy->cusp_map[j][k][l] = original->cusp_map[j][k][l]; } copy->extends_to_link = original->extends_to_link; count++; } } Boolean same_triangulation( Triangulation *manifold0, Triangulation *manifold1) { /* * Check whether manifold0 and manifold1 have combinatorially * equivalent triangulations (ignoring Dehn fillings). * * This function is most useful when manifold0 and manifold1 are * canonical retriangulations of the canonical cell decomposition, * but it can be applied to any Triangulations. For normal use, * compute_isometries(manifold0, manifold1, &are_isometric, NULL, NULL) * is more flexible and easier to use. The present function is * intended for batch use (in particular for processing Jim Hoste's * knot tabulation), when it's important to avoid the overhead of * recomputing canonical triangulations. */ /* * Imitate the algorithm in compute_cusped_isometries(). */ Tetrahedron *tet0, *tet1; int i; /* * If the triangulations contain different numbers of tetrahedra, * they can't possibly be equivalent. */ if (manifold0->num_tetrahedra != manifold1->num_tetrahedra) return FALSE; /* * Try mapping an arbitrary but fixed Tetrahedron of manifold0 * ("tet0") to each Tetrahedron of manifold1 in turn, with each * possible Permutation. See whether any of these maps extends * to a global isometry. */ /* * Let tet0 be the first Tetrahedron on manifold0's list. */ tet0 = manifold0->tet_list_begin.next; /* * Consider each Tetrahedron in manifold1. */ for (tet1 = manifold1->tet_list_begin.next; tet1 != &manifold1->tet_list_end; tet1 = tet1->next) /* * Consider each of the 24 possible ways to map tet0 to tet1. */ for (i = 0; i < 24; i++) /* * Does mapping tet0 to tet1 via permutation_by_index[i] * define an isometry? */ if (attempt_isometry(manifold0, tet0, tet1, permutation_by_index[i]) == func_OK) /* * The triangulations are equivalent. */ return TRUE; /* * The triangulations are different. */ return FALSE; } snappea-3.0d3/SnapPeaKernel/code/length_spectrum.c0100444000175000017500000015614307053771742020262 0ustar babbab/* * length_spectrum.c * * This file provides the functions * * void length_spectrum( WEPolyhedron *polyhedron, * double cutoff_length, * Boolean full_rigor, * Boolean multiplicities, * double user_radius, * MultiLength **spectrum, * int *num_lengths); * * void free_length_spectrum(MultiLength *spectrum); * * length_spectrum() takes the following inputs: * * *polyhedron The manifold whose length spectrum we're seeking is * given as a Dirichlet domain. The Dirichlet domain * may be computed either from a Triangulation, or * directly from a set of generating matrices. * * cutoff_length length_spectrum() reports geodesics of length * at most cutoff_length. * * full_rigor If full_rigor is TRUE, length_spectrum() guarantees * that it will find all geodesics of length at most * cutoff_length, with correct multiplicities if * the multiplicities parameter is also TRUE. * * multiplicities If both full_rigor and multiplicities are TRUE, * length_spectrum() reports complex lengths with * correct multiplicities. If multiplicities is TRUE * and full_rigor is FALSE, length_spectrum() reports * the multiplicities as best it can, but doesn't * promise they will be correct. If multiplicities is * FALSE, length_spectrum() reports all multiplicities * as zero. * * Note: The geodesics' topologies are computed iff * multiplicities is TRUE. * * user_radius When full_rigor is FALSE, length_spectrum() tiles * out to the user_radius instead of tiling out to * the rigorous tiling_radius it would otherwise use. * It may or may not find all geodesics of length up * to cutoff_length. When full_rigor is TRUE, the * user_radius parameter is ignored. * * length_spectrum provides the best results when both full_rigor and * multiplicities are TRUE. However, both these options slow the program * down, so the UI should offer the user the opportunity of doing * quick & dirty computations with one or both options off. * * length_spectrum() provides the following outputs: * * spectrum *spectrum is set to point to an array * of MultiLengths. * * num_lengths *num_lengths is set to the number of * elements in the array. * * length_spectrum allocates the array *spectrum. When you are done with * it, please call free_length_spectrum() to deallocate the memory it * occupies. * * * Theory. * ******************************************************************** * 95/10/31 * * The results in this Theory section have appeared in print as * * * * C. Hodgson and J. Weeks, Symmetries, isometries and * * length spectra of closed hyperbolic 3-manifolds, * * Experimental Mathematics 3 (1994) 261-274. * * * ******************************************************************** * * The remainder of this top-of-file documentation proves some lemmas * which will be needed in the code below. I offer my deepest thanks to * Craig Hodgson for his long-term collaboration on this work -- in * particular for providing the key ideas in some of the following * lemmas -- and also for his long-term friendship. * * Terminology. Throughout this file D will be a Dirichlet domain * with basepoint x. A typical translate of D will be denoted gD, * where g is an isometry in the group of covering transformations. * * We will be tiling hyperbolic space with copies of the Dirichlet domain D. * In particular, we'll need to find all translates gD which move the * basepoint x a distance less than some given distance s, i.e. we'll want * to find all gD such that d(x,gx) < s. The simplest algorithm is to * start with D and recursively attach its neighbors, stopping the * recursion when we reach translates gD whose basepoints are a distance * greater than s from the origin, i.e. when d(x,gx) > s. For an * arbitrary fundamental domain (not necessarily a Dirichlet domain) * with an arbitrary basepoint, this algorithm might fail: there could * be a translate with basepoint a distance less than s from the origin, * all of whose neighbors have basepoints a distance greater than s * from the origin. The simple recursive algorithm would not find such * a translate. Fortunately this cannot occur for a Dirichlet domain. * * Lemma 1 (Craig Hodgson). Let D be a Dirichlet domain with basepoint x, * and let gD be a translate of D such that for all neighbors hD of gD, * d(x,hx) >= d(x,gx). Then g = identity. * * Proof. For each neighbor hD of gD, the inequality d(x,hx) >= d(x,gx) * implies that x lies in the halfspace H_h consisting of points closer * to gx than hx. But gD is the intersection of all such H_h, so x must * lie in gD. Therefore gD = D. Q.E.D. * * Each translate of the Dirichlet domain corresponds to an isometry * H^3 -> H^3 in the group of covering transformations, and each such * isometry defines a complex length. Please see complex_length.c for * details on how the isometry determines the length. We consider * only hyperbolic and loxodromic isometries. Elliptics and parabolics * are not reported. * * Lemma 2. To find all closed geodesics of length <= L, it suffices * to find all translates gD satisfying d(x,gx) < L + 2R, where R is the * spine_radius defined in winged_edge.h and computed in * compute_spine_radius() in Dirichlet_extras.c. * * Proof. A closed geodesic must intersect a spine of D at some point P, * because otherwise it would be a parabolic or a trivial curve. Let g be * the covering transformation corresponding to given geodesic. Then * d(P,gP) = L, and d(x,gx) <= d(x,P) + d(P,gP) + d(gP,gx) <= R + L + R. * * (Yes, I realize that the spine_radius is an infimum which may not be * realized. If you want to fill in the missing the details, you may * replace R with R + epsilon, and then let epsilon go to zero.) * * Q.E.D. * * We can improve a bit on the estimate of L + 2R. * * Lemma 2'. To find all closed geodesics of length <= L, it suffices * to find all translates gD satisfying d(x,gx) < 2 acosh(cosh R cosh L/2), * where R is the spine_radius defined in winged_edge.h and computed in * compute_spine_radius() in Dirichlet_extras.c. * * Proof. Consider the point Q where the geodesic passes closest to * the basepoint x. Because the geodesic is known to intersect the * spine, d(x,Q) <= d(x,P) <= R (P is as in the proof of Lemma 2 above). * The advantage of working with Q instead of P is that the segment from * x to Q is orthogonal to the geodesic. (As a special case Q could equal * x, but our formula still holds.) Let the point M be the midpoint * of the segment from Q to gQ. * * __gx * __/ | * __/ | * __/ | R * __/ | * L/2 / | * Q---------------M---------------gQ * | __/ L/2 * | __/ * R | __/ * | __/ * |__/ * x * * The hyperbolic law of cosines bounds the distance from x to M * as acosh(cosh R cosh L/2). The distance from M to gx is the same, * so the distance from x to gx is bounded by 2 acosh(cosh R cosh L/2). * Q.E.D. * * Comment. Lemma 2' offers a 16-fold improvement over Lemma 2 when * R and L are large. * * Proof of comment. (Note: I haven't checked this proof as carefully * as I checked the proofs of the official lemmas, but I think it's * basically correct.) For sufficiently large arguments, * cosh(a) ~ exp(a)/2 and acosh(b) ~ log(2 b). So 2 acosh(cosh R cosh L/2) * ~ 2 log( 2 exp(R)/2 exp(L/2)/2) = 2 (R + L/2 - log(2)) = L + 2R - 2log(2). * In other words, as L and R go to infinity, the bound offered by Lemma 2' * is 2log(2) less than the bound offered by Lemma 2. How much is this * improvement worth? The number of images we compute within a ball of * radius r is roughly proportional to the ball's volume. The area of * a ball of radius r in H^3 is A = 4 pi (sinh r)^2, which for large r * is about 4 pi (exp(r)/2)^2 = pi exp(2r). So the ball's volume is * about (pi/2) exp(2r). The ratio of the volumes of balls of radius * r and r' is exp(2r')/exp(2r) = exp(2(r' - r)), which in the present * case is exp(2(2log2)) = 2^4 = 16. Q.E.D. * * * The above lemmas assure us that we've found all group elements * corresponding to geodesics of length at most L. Now we consider * how best to remove the vast numbers of duplicates on our list, i.e. * the vast numbers of distinct group elements which are conjugate to * one another and therefore represent the same geodesic in the manifold. * We take a two-part strategy: * * (1) We remove all group elements whose axes don't pass within * a distance R of the basepoint, where R is the spine radius * as above. (Every geodesic must intersect the spine, so we * are sure to retain at least one element in every conjugacy * class.) The documentation in distance_to_origin() below says * how the distance from an axis to the basepoint is computed. * * (2) We check the remaining group elements for conjugacy, using * Lemma 3' below. * * As with Lemmas 2 and 2', we first prove a simpler version of Lemma 3, * and then refine it to Lemma 3'. * * Lemma 3. If g and g' are conjugate group elements each of whose axes * passes within a distance R of the basepoint, then there is a group * element h such that (1) g = h(g')(h^-1) and (2) h moves the basepoint * a distance at most L/2 + 2R. * * Proof. Let A (resp. A') be the axis of g (resp. g'), and let Q * (resp. Q') be the point on A (resp. A') closest to the basepoint x. * There are infinitely many covering transformations taking A to A'; * let h be one which minimizes the distance from hQ to Q'. Because * the length of the underlying geodesic is at most L, the distance * from hQ to Q' is at most L/2 (it's L/2 and not L because if * L/2 < d(hQ,Q') < L then you've got the wrong h -- you need to * consider an h which takes Q to a point on the other side of Q'). * It's easy to get a bound on the distance h moves the basepoint x: * d(x,hx) <= d(x,Q') + d(Q',hQ) + d(hQ,hx) <= R + L/2 + R. * Q.E.D. * * Lemma 3'. If g and g' are conjugate group elements each of whose axes * passes within a distance R of the basepoint, then there is a group * element h such that (1) g = h(g')(h^-1) and (2) h moves the basepoint * a distance at most 2 acosh(cosh R cosh L/4). * * Proof. Use the same idea we used to upgrade Lemma 2 to Lemma 2': * * __hx * __/ | * __/ | * __/ | R * __/ | * L/4 / | * Q'--------------M---------------hQ * | __/ L/4 * | __/ * R | __/ * | __/ * |__/ * x * * Q.E.D. */ #include "kernel.h" #include /* needed for qsort() */ /* * Rather than just tiling out to the computed/requested tiling_radius, * we tile to tiling_radius + TILING_EPSILON to allow for roundoff error. */ #define TILING_EPSILON 1e-2 /* * already_on_tree() searches all nodes whose key values are within * TREE_EPSILON of the given key value. If TREE_EPSILON is too large, * the algorithm will waste time sifting through large numbers of * irrelevant matrices, but if it's too small you might end up adding * the same matrix over and over and over and . . . */ #define TREE_EPSILON 1e-5 /* * Two matrices will be considered equal (differing only by roundoff error) * iff corresponding entries differ by at most ISOMETRY_EPSILON. */ #define ISOMETRY_EPSILON 1e-3 /* * The lengths of two geodesics are considered potenially equal if they * differ by at most LENGTH_EPSILON. */ #define LENGTH_EPSILON 1e-3 /* * We allow for an error of up to SPINE_EPSILON in testing whether an * axis comes within a distance spine_radius of the origin. * When considering possible conjugates, however, we allow an even * greater margin for error, since we want to err on the side of * considering too many conjugates rather than too few. */ #define SPINE_EPSILON 1e-3 #define CONJUGATE_SPINE_EPSILON 1e-2 /* * distance_to_origin() uses COSH_EPSILON to decide when to report an error. */ #define COSH_EPSILON 1e-3 /* * When counting multiplicities, two complex lengths are considered * equal iff their real and imaginary parts agree to within * DUPLICATE_LENGTH_EPSILON. We use a fairly small value for * DUPLICATE_LENGTH_EPSILON so that we can resolve geodesics whose lengths * are equal in a cusped manifold but slightly different after a high-order * Dehn filling. This entails a risk that in low-accuracy situations we * might show equal lengths as different, but the user should be able to * figure out what's going on. */ #define DUPLICATE_LENGTH_EPSILON 1e-8 /* * CONJUGATOR_EPSILON provides an extra margin of safety to insure that * we find all relevant conjugators in eliminate_its_conjugates(). * (There is also an epsilon added to the spine_radius used there.) */ #define CONJUGATOR_EPSILON 1e-3 /* * Two geodesics are checked for conjugacy iff their lengths differ by * at most POSSIBLE_CONJUGATE_EPSILON. We choose a fairly large value for * POSSIBLE_CONJUGATE_EPSILON to insure that no conjugacies are missed. * The only possible harm a large value can do is slow down the algorithm * a bit as it does some "unnecessary" checks when geodesics of slightly * different lengths are present (as in a high-order Dehn filling). */ #define POSSIBLE_CONJUGATE_EPSILON 1e-3 /* * The tiling of hyperbolic space by translates gD of the Dirichlet domain * is stored on a binary tree. Each node in the tree is a Tile structure * containing the group element g associated with the given translate. */ typedef struct Tile { /* * A translate gD of the Dirichlet domain is determined * by the group element g. */ O31Matrix g; /* * Please see complex_length.c for details on how the * complex length is defined and computed. */ Complex length; /* * Is the group element g an orientation_preserving * or orientation_reversing isometry? */ MatrixParity parity; /* * Is the geodesic topologically a circle or a mirrored interval? */ Orbifold1 topology; /* * The to_be_eliminated flag is used locally in eliminate_powers() * and eliminate_conjugates(). */ Boolean to_be_eliminated; /* * The tiles are organized in two different way. */ /* * Organization #1. * * To make checking for duplicates easy, the Tiles are kept on * a binary tree. The sort and search key is a more or less * arbitrary function defined in the code. The next_subtree field * is used locally within already_on_tree() and free_tiling() * to avoid doing recursions on the system stack; the latter run * the risk of stack/heap collisions. */ struct Tile *left_child, *right_child; double key; struct Tile *next_subtree; /* * Organization #2. * * The function tile() needs to keep track of which Tiles have * not yet had their neighbors checked. Its keeps pending Tiles * on a doubly-linked list. */ struct Tile *prev, *next; } Tile; static void tile(WEPolyhedron *polyhedron, double tiling_radius, Tile **tiling); static double key_value(O31Matrix m); static Boolean already_on_tree(Tile *root, Tile *tile); static void add_to_tree(Tile *root, Tile *tile); static int count_translates(Tile *root); static void find_good_geodesics(Tile *tiling, int num_translates, Tile ***geodesic_list, int *num_good_geodesics, double cutoff_length, double spine_radius); static Boolean tile_is_good(Tile *tile, double cutoff_length, double spine_radius); static double distance_to_origin(Tile *tile); static void sort_by_length(Tile **geodesic_list, int num_good_geodesics); static int CDECL compare_lengths(const void *tile0, const void *tile1); static void eliminate_powers(Tile **geodesic_list, int *num_good_geodesics, double cutoff_length); static void eliminate_its_powers(Tile **geodesic_list, int num_good_geodesics, int i0, double cutoff_length); static void eliminate_conjugates(Tile **geodesic_list, int *num_good_geodesics, Tile *tiling, int num_translates, double spine_radius); static void make_conjugator_list(Tile ***conjugator_list, int *num_conjugators, Tile *tiling, int num_translates); static void add_conjugators_to_list(Tile *root, Tile **conjugator_list, int *num_conjugators); static int CDECL compare_translation_distances(const void *tile0, const void *tile1); static void initialize_elimination_flags(Tile **geodesic_list, int num_good_geodesics); static void eliminate_its_conjugates(Tile **geodesic_list, int num_good_geodesics, int i0, Tile **conjugator_list, int num_conjugators, double spine_radius); static void compress_geodesic_list(Tile **geodesic_list, int *num_good_geodesics); static Boolean is_manifold_orientable(WEPolyhedron *polyhedron); static void copy_lengths(Tile **geodesic_list, int num_good_geodesics, MultiLength **spectrum, int *num_lengths, Boolean multiplicities, Boolean manifold_is_orientable); static void free_tiling(Tile *root); void length_spectrum( WEPolyhedron *polyhedron, double cutoff_length, Boolean full_rigor, Boolean multiplicities, double user_radius, MultiLength **spectrum, int *num_lengths) { Tile *tiling, **geodesic_list; int num_translates, num_good_geodesics; /* * If full_rigor is TRUE, * we want to find all closed geodesics of length at most * cutoff_length. By Lemma 2' above, it suffices to find all * translates gD satisfying d(x,gx) <= 2 acosh( cosh(R) * cosh(L/2) ). * If full_rigor is FALSE, * tile out to the user_radius and hope for the best. */ tile( polyhedron, full_rigor ? 2 * arccosh( cosh(polyhedron->spine_radius) * cosh(cutoff_length/2) ) : user_radius, &tiling); /* * How many translates did we find? */ num_translates = count_translates(tiling); /* * Make a list of all group elements satisfying the following * three conditions: * * (1) The real part of the complex length is greater than zero. * In the orientation-preserving case this means the isometry * is hyperbolic or loxodromic. In the orientation-reversing * case, it's a glide reflection. Either way, the isometry * factors as a translation along an axis, followed by a * (possibly trivial) rotation or a reflection. * * (2) The translation distance along the axis is at most * cutoff_length (plus epsilon). * * (3) The distance from the axis to the basepoint is at most * polyhedron->spine_radius. Every geodesic intersects the * spine, so we can't lose any geodesics this way, although * we will happily lose some unnecessary conjugates. In any * case, this condition is necessary for checking for conjugacy * later on. * * find_good_geodesics() will allocate an array of pointers of * type (Tile *), and write in the addresses of all group elements * satisfying the above three conditions. It will report the number * of such pointers in num_good_geodesics. */ find_good_geodesics( tiling, num_translates, &geodesic_list, &num_good_geodesics, cutoff_length + LENGTH_EPSILON, polyhedron->spine_radius + SPINE_EPSILON); /* * Sort the geodesic_list by order of increasing lengths. */ sort_by_length(geodesic_list, num_good_geodesics); /* * We want only primitive group elements. Discard elements which are * squares or higher powers of the primitives. (We'll still have two * group elements for each lift of a geodesic -- they'll be inverses * of one another -- but we'll deal with them later after we've checked * for conjugacy.) */ eliminate_powers(geodesic_list, &num_good_geodesics, cutoff_length + 2*LENGTH_EPSILON); /* * If multiplicities is TRUE, we want to retain precisely one * element on the geodesic_list corresponding to each geodesic * in the manifold. * We cull the geodesic_list in place, moving good pointers * toward the beginning of the list to take the place of pointers * which have been removed. */ if (multiplicities == TRUE) eliminate_conjugates( geodesic_list, &num_good_geodesics, tiling, num_translates, polyhedron->spine_radius + CONJUGATE_SPINE_EPSILON); /* * Allocate space for the spectrum, copy in the lengths, parities, * topologies and multiplicities, and report its size. * * If multiplicities == FALSE, set all multiplicities to zero. * * If no lengths are present, set *spectrum = NULL. */ copy_lengths( geodesic_list, num_good_geodesics, spectrum, num_lengths, multiplicities, is_manifold_orientable(polyhedron)); /* * Free local storage. */ my_free(geodesic_list); free_tiling(tiling); } void free_length_spectrum( MultiLength *spectrum) { if (spectrum != NULL) my_free(spectrum); } static void tile( WEPolyhedron *polyhedron, double tiling_radius, Tile **tiling) { Tile queue_begin, queue_end, *identity, *tile, *nbr; double cosh_tiling_radius; WEFace *face; /* * Assorted methodological comments: * * When tiling one needs a way to decide whether the neighbors * of a given lift have already been found. One could work out * a fancy constant-time algorithm, but it's best just to keep all * the lifts we've found so far on a binary tree, and check the tree * whenever we need to know whether a given lift has already been * found. The search runs in log n time, where n ~ e^r and r is the * radius we're tiling to, so each query will require about * log(e^r) ~ r comparisions. This is a reasonable price to pay. * The constant time algorithm would be messy to write. The tree * algorithm is easy to write, robust, and fairly fast, since each of * the log n operations is completely trivial (it might even beat the * constant time algorithm for all relevant cases). Even when n ~ 1e6 * the tree depth will be only 1.4 * log_2(1e6) ~ 1.4 * 20 ~ 30. * * We also "waste" a factor of two by letting a lift A discover that * one of its neighbors is some other lift B, and then later on letting * lift B discover that one of its neighbors is lift A. A fancier * algorithm would have lift B remember that lift A is its neighbor * on a certain face, and avoid the duplication of effort. But I felt * that such an algorithm would be harder to write and maintain, so I * chose the simpler algorithm instead. * * If users tend to send the algorithm off on long, long computations, * we could include a parameter which puts a maximum on the number * of lifts it's willing to compute. But I haven't done this. * Eventually, though, the computation will use the services of * a long-computation-in-progress facility, which will allow aborts. */ /* * Initialize the doubly-linked list. * * Tiles will be added to the list as soon as they are computed, and * removed from the list once all their neighbors have been computed. */ queue_begin.prev = NULL; queue_begin.next = &queue_end; queue_end.prev = &queue_begin; queue_end.next = NULL; /* * Create a Tile for the identity element. */ identity = NEW_STRUCT(Tile); o31_copy(identity->g, O31_identity); identity->length = Zero; identity->parity = orientation_preserving; identity->topology = orbifold1_unknown; /* * Put the identity on the binary tree. */ identity->left_child = NULL; identity->right_child = NULL; identity->key = key_value(O31_identity); identity->next_subtree = NULL; *tiling = identity; /* * Put the identity on the double-linked list. */ INSERT_BEFORE(identity, &queue_end); /* * Compute cosh(tiling_radius + TILING_EPSILON) for later convenience. */ cosh_tiling_radius = cosh(tiling_radius + TILING_EPSILON); /* * We're ready to roll. Our algorithm is * * while (the queue is not empty) * pull a Tile off the beginning of the queue * for each of its neighbors * if (the neighbor is not beyond the tiling radius * && the neighbor is not already on the tree) * add the neighbor to the tree * add the neighbor to the end of the queue */ /* * While the queue is not empty . . . */ while (queue_begin.next != &queue_end) { /* * Pull a Tile off the beginning of the queue. */ tile = queue_begin.next; REMOVE_NODE(tile); /* * For each of its neighbors . . . */ for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { /* * Tentatively allocate a Tile. */ nbr = NEW_STRUCT(Tile); /* * Compute the neighbor's group element and key value. */ o31_product(tile->g, *face->group_element, nbr->g); nbr->key = key_value(nbr->g); nbr->next_subtree = NULL; /* * If nbr->g is not too far away and not already on the tree, * we compute its length and parity, initialize its topology, * and add it to both the tree and the queue. * Otherwise we discard it. */ if (nbr->g[0][0] < cosh_tiling_radius && already_on_tree(*tiling, nbr) == FALSE) { nbr->length = complex_length_o31(nbr->g); nbr->parity = gl4R_determinant(nbr->g) > 0.0 ? orientation_preserving : orientation_reversing; nbr->topology = orbifold1_unknown; add_to_tree(*tiling, nbr); INSERT_BEFORE(nbr, &queue_end); } else /* * Either the neighbor was beyond the tiling_radius or * already on the binary tree. Either way we discard it. */ my_free(nbr); } } } static double key_value( O31Matrix m) { /* * Define a sort key for storing Tiles on the binary tree. * Ideally we'd like a key with the property that nearby group * elements (differing only by roundoff error) will have close * key values, and distant group elements will have distant key * values. But homeomorphisms from R^6 to R are impossible. * So we try for the next best thing, a key that usually maps * distant group elements to distant key values. First note that * since the origin lies in the interior of the Dirichlet domain * and away from the singular set, the group elements are uniquely * determined by where they map the origin. Furthermore, the x[0] * coordinate of any point in hyperbolic space (in the Minkowski * space model) is completely determined by the x[1], x[2] and x[3] * coordinates. So for our sort key we use a random looking linear * combination of the x[1], x[2] and x[3] coordinates. The reason * for making it random looking is to minimize the chances that * distinct images of the basepoint will lie on the same level surface * of the sort key function. * * There could conceivably be problems in guessing the roundoff * error in the key values, because the possible values of m[][] * span several orders of magnitude. I'll have to think some more * about that. * * Recall that the first column of an O31Matrix gives the image * of the origin. */ return( m[1][0] * 0.47865745183883625637 + m[2][0] * 0.14087522034920476458 + m[3][0] * 0.72230196622481940253); } static Boolean already_on_tree( Tile *root, Tile *tile) { Tile *subtree_stack, *subtree; double delta; Boolean left_flag, right_flag; /* * Reliability is our first priority. Speed is second. * So if tile->key and root->key are close, we want to search both * the left and right subtrees. Otherwise we search only one or the * other. We implement the recursion using our own stack, rather than * the system stack, to avoid the possibility of a stack/heap collision * during deep recursions. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = root; if (root != NULL) root->next_subtree = NULL; /* * Process the subtrees on the stack, * adding additional subtrees as needed. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * Compare the key values of the tile and the subtree's root. */ delta = tile->key - subtree->key; /* * Which side(s) should we search? */ left_flag = (delta < +TREE_EPSILON); right_flag = (delta > -TREE_EPSILON); /* * Put the subtrees we need to search onto the stack. */ if (left_flag && subtree->left_child) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (right_flag && subtree->right_child) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Check this Tile if the key values match. */ if (left_flag && right_flag) if (o31_equal(subtree->g, tile->g, ISOMETRY_EPSILON)) return TRUE; } return FALSE; } static void add_to_tree( Tile *root, Tile *tile) { /* * already_on_tree() has already checked that tile->g does not * appear on the tree. So here we just add it in the appropriate * spot, based on the key value. */ Tile **location; location = &root; while (*location != NULL) { if (tile->key <= (*location)->key) location = &(*location)->left_child; else location = &(*location)->right_child; } *location = tile; tile->left_child = NULL; tile->right_child = NULL; } static int count_translates( Tile *root) { Tile *subtree_stack, *subtree; int num_translates; /* * Implement the recursive freeing algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = root; if (root != NULL) root->next_subtree = NULL; /* * Initialize the count to zero. */ num_translates = 0; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Count the subtree's root node. */ num_translates++; } return num_translates; } static void find_good_geodesics( Tile *tiling, int num_translates, Tile ***geodesic_list, int *num_good_geodesics, double cutoff_length, double spine_radius) { Tile *subtree_stack, *subtree; /* * The most good geodesics we could have would be num_translates, * so we'll allocate a geodesic_list of this length, even though * we probably won't use all its entries. */ *geodesic_list = NEW_ARRAY(num_translates, Tile *); /* * *num_good_geodesics will keep track of how many pointers have * been put on the geodesic_list. Initialize it to zero. */ *num_good_geodesics = 0; /* * Implement the recursive counting algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tiling tree. */ subtree_stack = tiling; if (tiling != NULL) tiling->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * If the subtree's root is a good tile, add it * to the geodesic_list. */ if (tile_is_good(subtree, cutoff_length, spine_radius)) (*geodesic_list)[(*num_good_geodesics)++] = subtree; } } static Boolean tile_is_good( Tile *tile, double cutoff_length, double spine_radius) { /* * tile_is_good() tests the three conditions given in * length_spectrum() above. It's essential that we test Condition #3 * after Condition #1, because the distance to the axis is undefined * for parabolics. We test Condition #2 before Condition #3 because * it's a little faster computationally. */ /* * Condition #1. Is tile->length.real > 0? */ if (tile->length.real < LENGTH_EPSILON) return FALSE; /* * Condition #2. Does the isometry translate its axis a distance * less than cutoff_length? * * length_spectrum() has already added LENGTH_EPSILON to cutoff_length. */ if (tile->length.real > cutoff_length) return FALSE; /* * Condition #3. Does the axis pass within a distance * spine_radius of the origin? * * length_spectrum() has already added SPINE_EPSILON to spine_radius. */ if (distance_to_origin(tile) > spine_radius) return FALSE; /* * All three conditions are satisfied, so return TRUE. */ return TRUE; } static double distance_to_origin( Tile *tile) { Tile square; double cosh_d, cosh_s, cos_t; /* * tile_is_good() has already checked Condition #1, so we know * that we're looking at a translation along a geodesic, perhaps * followed by a rotation or reflection about that geodesic. * (See complex_length.c for the full classification of isometries.) * In the orientation-preserving case the isometry tile->g is * conjugate to * * cosh s sinh s 0 0 * sinh s cosh s 0 0 * 0 0 cos t -sin t * 0 0 sin t cos t * * while in the orientation-reversing case it's conjugate to * * cosh s sinh s 0 0 * sinh s cosh s 0 0 * 0 0 -1 0 * 0 0 0 1 * * The latter case is hard to handle here, so if tile->g is * orientation-reversing, we compute its square. The square will * of course be orientation-preserving and have the same axis as * tile->g, so we may use it instead of tile->g itself. */ if (tile->parity == orientation_reversing) { o31_product(tile->g, tile->g, square.g); square.length.real = 2 * tile->length.real; square.length.imag = 0.0; square.parity = orientation_preserving; return distance_to_origin(&square); } /* * We may now assume the isometry is orientation-preserving, * so in some coordinate system it takes the form * * cosh s sinh s 0 0 * sinh s cosh s 0 0 * 0 0 cos t -sin t * 0 0 sin t cos t * * In this same coordinate system we may, without loss of generality, * assume that the basepoint lies at (cosh r, 0, sinh r, 0), where * r is the distance from the basepoint to the axis of the isometry. * The image of the basepoint under the isometry is then * * ( cosh s sinh s 0 0 ) (cosh r) (cosh r cosh s) * ( sinh s cosh s 0 0 ) ( 0 ) = (cosh r sinh s) * ( 0 0 cos t -sin t ) (sinh r) (sinh r cos t ) * ( 0 0 sin t cos t ) ( 0 ) (sinh r sin t ) * * The distance d which the isometry moves the basepoint may be * computed using the formula -cosh d = . * * - cosh d = * < (cosh r, 0, sinh r, 0), * (cosh r cosh s, cosh r sinh s, sinh r cos t, sinh r sin t) > * = - cosh^2 r cosh s + sinh^2 r cos t * * Fortunately we already know * * cosh d = tile->g[0][0] * s = tile->length.real * t = tile->length.imag * * so we may solve for r. * * r = acosh(sqrt( (cosh d - cos t) / (cosh s - cos t) )) * * Note that the argument of the sqrt() function is, in theory, * always at least one. */ cosh_d = tile->g[0][0]; cosh_s = cosh(tile->length.real); cos_t = cos(tile->length.imag); /* * Make sure cosh d really is greater than cosh s, even accounting * for roundoff error. */ if (cosh_d < cosh_s) { if (cosh_d > cosh_s - COSH_EPSILON) /* * The error is small, so we assume cosh d should equal * cosh s, and we return r = 0.0. */ return 0.0; else /* * The error is large. Something went wrong. */ uFatalError("distance_to_origin", "length_spectrum"); } /* * Use the above formula to solve for r, and return the answer. */ return arccosh(safe_sqrt( (cosh_d - cos_t) / (cosh_s - cos_t) )); } static void sort_by_length( Tile **geodesic_list, int num_good_geodesics) { /* * Sort the elements on the geodesic_list by order of increasing length. * * Probably all implementations of qsort() would handle the case * num_good_geodesics == 0 correctly, but why take chances? */ if (num_good_geodesics > 0) qsort( geodesic_list, num_good_geodesics, sizeof(Tile *), compare_lengths); } static int CDECL compare_lengths( const void *tile0, const void *tile1) { /* * This comparison function does not put a well-defined linearing * ordering on the set of all complex lengths, nor could it possibly * do so in any reasonable way. (Exercise for the reader: Find three * complex lengths a, b and c such that this function reports a < b, * b < c and c < a.) But as long as roundoff errors are less than * DUPLICATE_LENGTH_EPSILON and the differences between the true * lengths of geodesics are greater than DUPLICATE_LENGTH_EPSILON, * it should work fine. */ Complex diff; diff = complex_minus((*(Tile **)tile0)->length, (*(Tile **)tile1)->length); if (diff.real < -DUPLICATE_LENGTH_EPSILON) return -1; if (diff.real > DUPLICATE_LENGTH_EPSILON) return +1; if (diff.imag < 0.0) return -1; if (diff.imag > 0.0) return +1; return 0; } static void eliminate_powers( Tile **geodesic_list, int *num_good_geodesics, double cutoff_length) { int i; /* * Initialize the tile->to_be_eliminated flags to FALSE. */ initialize_elimination_flags(geodesic_list, *num_good_geodesics); /* * Look at each element on the geodesic list in turn. * * If it's to_be_eliminated, skip it. * * Otherwise, if any of its powers appear on the geodesic_list, * mark them to_be_eliminated. */ for (i = 0; i < *num_good_geodesics; i++) if (geodesic_list[i]->to_be_eliminated == FALSE) eliminate_its_powers( geodesic_list, *num_good_geodesics, i, cutoff_length); /* * Compress the geodesic_list by eliminating pointers to tiles which * are to_be_eliminated. The remaining good pointers move towards * the beginning of the list, overwriting the eliminated pointers. */ compress_geodesic_list(geodesic_list, num_good_geodesics); } static void eliminate_its_powers( Tile **geodesic_list, int num_good_geodesics, int i0, /* index of geodesic under consideration */ double cutoff_length) /* 2*LENGTH_EPSILON has already been added in */ { Tile the_power; int i; /* * Look at each power n > 1 of geodesic_list[i0]->g whose length * is less than the cutoff_length. */ for ( o31_product(geodesic_list[i0]->g, geodesic_list[i0]->g, the_power.g), the_power.length.real = 2 * geodesic_list[i0]->length.real; the_power.length.real < cutoff_length; o31_product(the_power.g, geodesic_list[i0]->g, the_power.g), the_power.length.real += geodesic_list[i0]->length.real ) /* * Does any element of the geodesic_list correspond to the_power? */ for ( i = i0 + 1; i < num_good_geodesics && geodesic_list[i]->length.real < the_power.length.real + LENGTH_EPSILON; i++ ) if (geodesic_list[i]->length.real > the_power.length.real - LENGTH_EPSILON) { if (o31_equal(geodesic_list[i]->g, the_power.g, ISOMETRY_EPSILON) == TRUE) { geodesic_list[i]->to_be_eliminated = TRUE; break; } } } static void eliminate_conjugates( Tile **geodesic_list, int *num_good_geodesics, Tile *tiling, int num_translates, double spine_radius) { int i; /* * Our task is to recognize which elements of the geodesic_list are * conjugate to one another, and eliminate the duplicates, keeping * exactly one element of each conjugacy class. We know that * each element on the geodesic_list corresponds to a geodesic with * 0 < length <= L, and its axis passes within a distance R of the * basepoint. So Lemma 3' at the top of this file says that * any conjugating elements we need will move the basepoint a distance * at most 2 acosh(cosh R cosh L/4). Fortunately the tiling contains * all group elements moving the basepoint a distance at most * 2 acosh(cosh R cosh L/2), so we have all the conjugators we need, * and then some. * * Actually, we take the culling a step further, and make sure we * have exactly one Tile for each geodesic. That is, whenever two * distinct conjugacy classes correspond to the same geodesic * (with opposite directions), we keep only one. */ Tile **conjugator_list; int num_conjugators; /* * If the geodesic_list is empty, there is no work to be done. * The subsequent code would actually run fine even with an empty * geodesic_list, but better not to take chances, just in case * someday I make modifications. */ if (*num_good_geodesics == 0) return; /* * Organize the possible conjugators on a list. * That is, make a list of (Tile *) pointers, with one pointer to * each element in the tiling tree. Sort the list by order of * increasing basepoint translation distance d(x,g(x0)). */ make_conjugator_list(&conjugator_list, &num_conjugators, tiling, num_translates); /* * Initialize the tile->to_be_eliminated flags to FALSE. */ initialize_elimination_flags(geodesic_list, *num_good_geodesics); /* * Look at each element on the geodesic list in turn. * * If it's to_be_eliminated, skip it. * * Otherwise, compute all its conjugates which pass within a distance * R of the basepoint, where R is the spine_radius (cf. top-of-file * documentation). Compare each conjugate to all other elements on * the geodesic_list which have the same complex length (recall that * the geodesic_list is sorted by complex length, so the potential * conjugates will all be nearby), and if any matches are found, mark * them to_be_eliminated. Do the same to eliminate the element's * inverse and all its conjugates. * * While we're at it, we'll also check whether each element is * conjugate to its own inverse, and thereby determine its topology. */ for (i = 0; i < *num_good_geodesics; i++) if (geodesic_list[i]->to_be_eliminated == FALSE) eliminate_its_conjugates( geodesic_list, *num_good_geodesics, i, conjugator_list, num_conjugators, spine_radius); /* * Compress the geodesic_list by eliminating pointers to tiles which * are to_be_eliminated. The remaining good pointers move towards * the beginning of the list, overwriting the eliminated pointers. */ compress_geodesic_list(geodesic_list, num_good_geodesics); /* * Free the conjugator_list. */ my_free(conjugator_list); } static void make_conjugator_list( Tile ***conjugator_list, int *num_conjugators, Tile *tiling, int num_translates) { /* * Allocate space for the conjugator_list. */ *conjugator_list = NEW_ARRAY(num_translates, Tile *); /* * Initialize the count to zero. */ *num_conjugators = 0; /* * Recursively add pointers to all tiling elements to the list. */ add_conjugators_to_list(tiling, *conjugator_list, num_conjugators); /* * Do a quick error check. */ if (*num_conjugators != num_translates) uFatalError("make_conjugator_list", "length_spectrum"); /* * Sort the list by order of increasing basepoint translation * distance. The basepoint translation distance is acosh(tile->g[0][0]) * and acosh() is monotonic, so we may sort directly on tile->g[0][0]. */ qsort(*conjugator_list, *num_conjugators, sizeof(Tile *), compare_translation_distances); } static void add_conjugators_to_list( Tile *root, Tile **conjugator_list, int *num_conjugators) { Tile *subtree_stack, *subtree; /* * Implement the recursive counting algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = root; if (root != NULL) root->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Add the subtree's root node. */ conjugator_list[(*num_conjugators)++] = subtree; } } static int CDECL compare_translation_distances( const void *tile0, const void *tile1) { double diff; diff = (*(Tile **)tile0)->g[0][0] - (*(Tile **)tile1)->g[0][0]; if (diff < 0.0) return -1; if (diff > 0.0) return +1; return 0; } static void initialize_elimination_flags( Tile **geodesic_list, int num_good_geodesics) { int i; for (i = 0; i < num_good_geodesics; i++) geodesic_list[i]->to_be_eliminated = FALSE; } static void eliminate_its_conjugates( Tile **geodesic_list, int num_good_geodesics, int i0, /* index of geodesic under consideration */ Tile **conjugator_list, int num_conjugators, double spine_radius) { double conjugator_cutoff; Tile the_conjugate, the_inverse, the_inverse_conjugate; int i, j; /* * We want to find all conjugates of geodesic_list[i0] or its inverse * which pass within a distance R of the basepoint. According to * Lemma 3' at the top of this file, it suffices to consider conjugators * which move the basepoint a distance at most 2 acosh(cosh R cosh L/4), * where R is the spine_radius and L is the length of geodesic_list[i0]. * * While we're at it, we'll also check whether geodesic_list[i0] is * conjugate to its own inverse, and thereby determine its topology. */ /* * The identity appears on the conjugator_list, so the inverse * of geodesic_list[i0] will be marked to_be_eliminated, along * with all its conjugates. */ /* * Set up the inverse. */ o31_invert(geodesic_list[i0]->g, the_inverse.g); the_inverse.length = geodesic_list[i0]->length; the_inverse.parity = geodesic_list[i0]->parity; /* * We'll consider conjugators whose [0][0] entry is at most * cosh( 2 acosh(cosh R cosh L/4) ). */ conjugator_cutoff = cosh( 2 * arccosh( cosh(spine_radius) * cosh(geodesic_list[i0]->length.real/4)) ) + CONJUGATOR_EPSILON; /* * While we're at it, we might as well check whether geodesic_list[i0] * is conjugate to its own inverse. If so, it will be topologically * a mirrored interval. If not, it will be topologically a circle. * We assume it's a circle until we discover otherwise. */ geodesic_list[i0]->topology = orbifold_s1; /* * Fortunately the conjugator_list is sorted by basepoint translation * distance (i.e. by tile->g[0][0]), so we can start at the beginning * of the list and go until tile->g[0][0] exceeds conjugator_cutoff. */ for ( j = 0; j < num_conjugators && conjugator_list[j]->g[0][0] <= conjugator_cutoff; j++) { /* * Conjugate geodesic_list[i0] by conjugator_list[j] * to obtain the_conjugate. */ o31_conjugate( geodesic_list[i0]->g, conjugator_list[j]->g, the_conjugate.g); the_conjugate.length = geodesic_list[i0]->length; the_conjugate.parity = geodesic_list[i0]->parity; /* * Conjugate the_inverse by conjugator_list[j] * to obtain the_inverse_conjugate. */ o31_conjugate( the_inverse.g, conjugator_list[j]->g, the_inverse_conjugate.g); the_inverse_conjugate.length = the_inverse.length; the_inverse_conjugate.parity = the_inverse.parity; /* * Does the_conjugate equal the_inverse? */ if (o31_equal(the_conjugate.g, the_inverse.g, ISOMETRY_EPSILON) == TRUE) geodesic_list[i0]->topology = orbifold_mI; /* * If the_conjugate's axis doesn't come within a distance R of * the basepoint, then it can't possibly be on the geodesic_list. * * length_spectrum() has already added CONJUGATE_SPINE_EPSILON * to spine_radius. We want to err on the side of considering * too many possible conjugates rather than too few. */ if (distance_to_origin(&the_conjugate) > spine_radius) continue; /* * Compare the_conjugate and the_inverse_conjugate to each * geodesic_list[i] which has the same real length as * geodesic_list[i0], up to roundoff error. (For an * orientation-preserving geodesic in a nonorientable * manifold, the_conjugate and geodesic_list[i0] might have * opposite torsions.) */ for (i = i0 + 1; i < num_good_geodesics; i++) { /* * If geodesic_list[i] is already marked for elimination * we can skip it. */ if (geodesic_list[i]->to_be_eliminated == TRUE) continue; /* * As soon as geodesic_list[i] has a length which differs from * the length of geodesic_list[i0] by more than roundoff error * we can break from the i loop (recall that the geodesic_list * is sorted by complex length). */ if ( geodesic_list[i] ->length.real - geodesic_list[i0]->length.real > POSSIBLE_CONJUGATE_EPSILON ) break; /* * Does geodesic_list[i]->g equal the_conjugate.g or * the_inverse_conjugate.g ? */ if (o31_equal(geodesic_list[i]->g, the_conjugate.g, ISOMETRY_EPSILON) || o31_equal(geodesic_list[i]->g, the_inverse_conjugate.g, ISOMETRY_EPSILON)) /* * Set geodesic_list[i]->to_be_eliminated to TRUE, because * geodesic_list[i]->g is conjugate to geodesic_list[i0]->g. */ geodesic_list[i]->to_be_eliminated = TRUE; } } } static void compress_geodesic_list( Tile **geodesic_list, int *num_good_geodesics) { int n, i; /* * The variable 'n' keeps track of how many (Tile *) pointers * have been kept. */ n = 0; /* * Copy pointers we want to keep into a contiguous block at the * beginning of the geodesic_list. */ for (i = 0; i < *num_good_geodesics; i++) if (geodesic_list[i]->to_be_eliminated == FALSE) geodesic_list[n++] = geodesic_list[i]; /* * Update *num_good_geodesics. */ *num_good_geodesics = n; } static Boolean is_manifold_orientable( WEPolyhedron *polyhedron) { WEFace *face; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) if (gl4R_determinant(*face->group_element) < 0.0) return FALSE; return TRUE; } static void copy_lengths( Tile **geodesic_list, int num_good_geodesics, MultiLength **spectrum, int *num_lengths, Boolean multiplicities, Boolean manifold_is_orientable) { int i, j; MultiLength *multilength_array; /* * The case num_good_geodesics == 0 is handled separately because * we don't want to allocate an array of zero length. */ if (num_good_geodesics == 0) { *spectrum = NULL; *num_lengths = 0; return; } /* * First allocate an array that's sure to be long enough. * Once we've found all the MultiLengths we'll copy them into an * array of precisely the right size. */ multilength_array = NEW_ARRAY(num_good_geodesics, MultiLength); /* * Initialize *num_lengths to zero. */ *num_lengths = 0; /* * By the way, if multiplicities == TRUE, then the topologies will * be set to orbifold_s1 or orbifold_mI. Otherwise they'll all be * set to orbifold1_unknown. Either way, the following code does * what it should. */ /* * Each Tile on the geodesic_list either defines a new MultiLength * or increases the multiplicity of an old one. */ for (i = 0; i < num_good_geodesics; i++) { /* * Compare geodesic_list[i] to all multilengths of the same * real length (recall that the geodesic_list is sorted by * length, so the list of MultiLengths will be too). */ for (j = *num_lengths - 1; TRUE; --j) { /* * If we either exhaust the multilength_array or reach * an element whose real length is less than that of * geodesic_list[i], then we know that geodesic_list[i] * defines a new MultiLength. */ if ( j < 0 || geodesic_list[i]->length.real - multilength_array[j].length.real > DUPLICATE_LENGTH_EPSILON ) { multilength_array[*num_lengths].length = geodesic_list[i]->length; multilength_array[*num_lengths].parity = geodesic_list[i]->parity; multilength_array[*num_lengths].topology = geodesic_list[i]->topology; multilength_array[*num_lengths].multiplicity = 1; /* * If the manifold or orbifold is nonorientable, the sign * of the torsion is arbitrary, so report it as positive. */ if (manifold_is_orientable == FALSE) multilength_array[*num_lengths].length.imag = fabs(multilength_array[*num_lengths].length.imag); (*num_lengths)++; break; } /* * If geodesic_list[i] has the same torsion, parity and * topology as multilength_array[j], then we increment the * multiplicity of multilength_array[j]. (The above test * insures that the lengths are equal up to roundoff error.) */ if ( geodesic_list[i]->parity == multilength_array[j].parity && geodesic_list[i]->topology == multilength_array[j].topology && fabs( ( manifold_is_orientable ? geodesic_list[i]->length.imag : fabs(geodesic_list[i]->length.imag) ) - multilength_array[j].length.imag ) < DUPLICATE_LENGTH_EPSILON ) { multilength_array[j].multiplicity++; break; } } } /* * If multiplicities is FALSE, report all multiplicities as zero. */ if (multiplicities == FALSE) for (j = 0; j < *num_lengths; j++) multilength_array[j].multiplicity = 0; /* * Allocate the array of MultiLengths which we'll pass to the UI. */ *spectrum = NEW_ARRAY(*num_lengths, MultiLength); /* * Copy in the data. */ for (j = 0; j < *num_lengths; j++) (*spectrum)[j] = multilength_array[j]; /* * Free the temporary array. */ my_free(multilength_array); } static void free_tiling( Tile *root) { Tile *subtree_stack, *subtree; /* * Implement the recursive freeing algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = root; if (root != NULL) root->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left_child != NULL) { subtree->left_child->next_subtree = subtree_stack; subtree_stack = subtree->left_child; } if (subtree->right_child != NULL) { subtree->right_child->next_subtree = subtree_stack; subtree_stack = subtree->right_child; } /* * Free the subtree's root node. */ my_free(subtree); } } snappea-3.0d3/SnapPeaKernel/code/link_complement.c0100444000175000017500000013553406742675502020241 0ustar babbab/* * link_complement.c * * This file provides the function * * Triangulation *triangulate_link_complement( * KLPProjection *aLinkProjection); * * which triangulates the complement of aLinkProjection. * * If aLinkProjection is empty returns NULL; otherwise returns * a pointer to the resulting Triangulation. */ /* * The Triangulation Algorithm * * * Introduction * * The triangulation algorithm is quite simple once you see the picture. * Unfortunately, there is no easy way to include a sketch in an ASCII * file, so you will need to sketch your own picture as I describe it. * First thicken the link so that each component becomes a solid torus. * Position it so that it lies near the equatorial 2-sphere in S^3. * As I describe the vertices, edges, faces and 3-cells in the * triangulation, try to draw them in your own picture. * * * Vertices * * Mark the solid tori (i.e. the link components) with * * (1) a longitude which runs along the top of each component, * (2) a longitude which runs along the bottom of each component, * (3) a meridian which encircles the understrand at each crossing, * (4) a meridian which encircles the overstrand at each crossing, and * (5) a meridian located halfway between each pair of adjacent crossings. * * These curves divide the surfaces of the solid tori into topological * squares which will serve as the ideal vertices of the triangulation. * * In addition, the initial triangulation will include two finite * (i.e. non-ideal) vertices, one at the south pole of S^3 and one at * the north pole. (Don't worry -- we'll get rid of them at the end.) * You should thicken these two finite vertices to become solid balls, * for consistency with our picture of the link as a union of solid tori. * * * Edges * * The triangulation's edges are as follows. * * (1) At each crossing of the link projection, there are edges running * * (A) from the south pole of S^3 to the bottom of the understrand, * (B) from the top of the understrand to the bottom of the overstrand, * (C) from the top of the overstrand to the north pole of S^3. * * (2) Midway between each pair of adjacent crossings, there are edges * * (A) from the south pole of S^3 to the bottom of the link component, * (B) from the top of the link component to the north pole of S^3. * * (3) In the center of each region of the link projection there is an edge * * (A) from the south pole to the north pole. * * * Faces * * There are three types of faces (2-cells). * * (1) Triangular faces with edges of type (2A), (2B) and (3A). * In your picture each such face will look like a topological hexagon, * but only three of the edges are true edges of the triangulation. * The other three "edges" are arcs on the boundary components: * one arc is a half-meridian on a solid torus, another is an arc * along the solid 3-ball at the north pole, and the last is an arc * along the solid 3-ball at the south pole. * * (2) Triangular faces with edges of type (1A), (1B) and (2A). * As before, each face will look like a topological hexagon. * In addition to the three real edges, there are three arcs on * the boundary: one runs along the bottom of the overstrand, * a second is half a meridian encircling the understrand, and * the last is an arc on the 3-ball at the south pole. * * (3) Triangular faces with edges of type (1B), (1C) and (2B). * These are just like the faces described in the previous paragraph, * except that they are in the northern hemisphere instead of the * southern hemisphere. * * (4) Bigons with edges of type (1A) and (2A). Each looks like a * topological square, because it includes an arc running along * the bottom of an understrand, and an arc on the 3-ball at the * south pole. * * (5) Bigons with edges of type (1C) and (2B). These are just like * the bigons in the previous paragraph, only they connect the * overstrand to the north pole, rather than connecting the * understrand to the south pole. * * So . . . you should draw all the 2-cells of the above types whereever * they make sense. (I'm hoping your understanding of what "makes sense" * is clearer than any longwinded explanation I could provide.) * * * 3-cells * * The 2-cells described above divide the link complement into 3-cells. * The 3-cells are all identical to one another, and four of them surround * each crossing. (So in particular the number of 3-cells is exactly * four times the number of crossings.) Each 3-cell has six real faces * (two of type (1) and one each of types (2), (3), (4) and (5) described * in the preceding section), as well as four square pieces of the * manifold's boundary (one on the understrand, one on the overstrand, * one on the 3-ball at the south pole and one on the 3-ball at the north * pole). * * At this point, our cell decomposition fails to be an ideal triangulation * for two reasons: * * (1) The 3-cells aren't tetrahedra. * * (2) The vertices at the north and south pole are finite, not ideal. * * The following section on Shrinking Away Bigons resolves problem (1), * and then the section on Removing Finite Vertices resolves problem (2). * * * Shrinking Away Bigons * * We can get rid of the bigons simply by shrinking them away! * Each is a rectangle with two real edges as well as two arcs on the * boundary. Shrink the arcs on the boundary to zero length. This * merges the two real edges into a single edge, and the bigon disappears. * * Essential Assumption: Each link component has at least one * overcrossing and at least one undercrossing. * * The Essential Assumption guarantees that the two real edges being * merged are always distinct. If the Essential Assumption were false, * you'd have a circular chain of bigons. You could shrink all but one * of the bigons, but when you went to shrink the last one you'd find * its two real edges were actually the same edge, so you couldn't shrink * away the bigon without changing the topology of the manifold. * * make_all_components_have_crossings() adds a nugatory crossing to each * link component which doesn't already have both under- and overcrossings, * so the Essential Assumption will always be satisfied. * * Once we've shrunk away all bigons, each 3-cell (which originally * had six faces -- two bigons and four triangles) is left with only * the four triangular faces. Furthermore, shrinking the bigons * collapses each square region on the manifold's boundary (i.e. on * the tubular neighborhood of the link and on the 3-balls at the * north and south poles) to a trianglar region. In other words, * each 3-cell has become an ideal tetrahedron. * * Computational note: The computer code never explicitly deals with * bigons. It simply sets up the correct gluings on the triangular faces, * ignores the bigons, and everything comes out right! * * * Removing Finite Vertices * * This is easy. We just call SnapPea's remove_finite_vertices() function. * Please see the file finite_vertices.c for extensive documentation * of the finite vertex removal algorithm. * * * March 1997. If the link projection is obviously disconnected, * triangulate_link_complement() now does a trivial type II Reidemeister * move to make it connected. Similarly, if there are obvious unknotted * components, it adds nugatory crossings. As a result, it can now * triangulate the complements of ALL link projections. * (The original design of storing the LCCrossings on a fixed array * is no longer so nice now that we sometimes have to add crossings. * Fortunately we add crossings only in rare circumstances.) */ #include "kernel.h" /* * The permutation 2310 is 10110100 = 0xB4. */ #define PERMUTATION2310 0xB4 /* * We'll transfer the link projection to our own internal format, so we * can tack on various fields for internal use without affecting the UI. */ typedef struct LCProjection LCProjection; typedef struct LCCrossing LCCrossing; struct LCProjection { /* * These fields correspond to those in KLPProjection. */ int num_crossings; int num_free_loops; int num_components; LCCrossing *crossings; }; struct LCCrossing { /* * The first four fields correspond to those in KLPProjection. */ LCCrossing *neighbor[2][2]; KLPStrandType strand[2][2]; KLPCrossingType handedness; int component[2]; /* * make_projection_connected() keeps track of which LCCrossings * it has placed on its queue. */ Boolean visited; /* * The four Tetrahedra incident to a crossing are numbered * according to the standard numbering of the quadrants, * except that we start with 0 instead of 1. * * KLPStrandY * ^ * 1 | 0 * ----+---> KLPStrandX * 2 | 3 * | */ Tetrahedron *tet[4]; }; static LCProjection *external_to_internal(KLPProjection *external_link_projection); static void free_internal_projection(LCProjection *internal_link_projection); static void add_nugatory_crossings_to_free_loops(LCProjection *internal_link_projection); static void resize_crossing_array(LCProjection *internal_link_projection, int new_array_size); static void make_projection_connected(LCProjection *internal_link_projection); static Boolean projection_is_connected(LCProjection *internal_link_projection, LCCrossing **crossing0, LCCrossing **crossing1); static void do_Reidemeister_II(LCProjection *internal_link_projection, LCCrossing *crossing0, LCCrossing *crossing1); static void make_all_components_have_crossings(LCProjection *internal_link_projection); static void add_nugatory_crossing(LCProjection *internal_link_projection, int component_index); static Triangulation *create_basic_triangulation(LCProjection *internal_link_projection); static void create_real_cusps(LCProjection *internal_link_projection, Triangulation *manifold); static void create_finite_vertices(LCProjection *internal_link_projection, Triangulation *manifold); static void add_peripheral_curves(LCProjection *internal_link_projection); static void clear_peripheral_curves(LCProjection *internal_link_projection); static void add_longitudes(LCProjection *internal_link_projection); static void add_meridians(LCProjection *internal_link_projection); static void adjust_longitudes(LCProjection *internal_link_projection); Triangulation *triangulate_link_complement( KLPProjection *aLinkProjection) { LCProjection *internal_link_projection; Triangulation *manifold; /* * We shouldn't be called with aLinkProjection == NULL, * but let's check just to be safe. */ if (aLinkProjection == NULL) return NULL; /* * Ingore empty projections. */ if (aLinkProjection->num_components == 0) return NULL; /* * Transfer aLinkProjection to our internal data structure. */ internal_link_projection = external_to_internal(aLinkProjection); /* * Are there any free loops? */ if (internal_link_projection->num_free_loops != 0) add_nugatory_crossings_to_free_loops(internal_link_projection); /* * If the link projection isn't already connected, * add a few "unnecessary" crossings to make it so. */ make_projection_connected(internal_link_projection); /* * Make sure each link component has at least one overcrossing * and at least one undercrossing, adding nugatory crossings if needed. */ make_all_components_have_crossings(internal_link_projection); /* * Set up the basic Triangulation data structure. Allocate the * Tetrahedra and set their neighbor and gluing fields. * Don't worry about the Cusps or EdgeClasses just yet. */ manifold = create_basic_triangulation(internal_link_projection); /* * The triangulation we just created is already oriented. */ manifold->orientability = oriented_manifold; /* * Set up the cusps. */ create_real_cusps(internal_link_projection, manifold); create_finite_vertices(internal_link_projection, manifold); /* * Set up the peripheral curves. */ add_peripheral_curves(internal_link_projection); /* * We're done with the internal_link_projection. * The external projection (aLinkProjection) which got passed in * remains untouched. */ free_internal_projection(internal_link_projection); /* * Add a generic set of EdgeClasses. */ create_edge_classes(manifold); orient_edge_classes(manifold); /* * Absorb the finite vertices at the north and south poles * into the cusps. */ remove_finite_vertices(manifold); /* * Try to find the complete hyperbolic structure. */ find_complete_hyperbolic_structure(manifold); return manifold; } static LCProjection *external_to_internal( KLPProjection *external_link_projection) { LCProjection *internal_link_projection; int i, j, k; internal_link_projection = NEW_STRUCT(LCProjection); internal_link_projection->num_crossings = external_link_projection->num_crossings; internal_link_projection->num_free_loops = external_link_projection->num_free_loops; internal_link_projection->num_components = external_link_projection->num_components; internal_link_projection->crossings = NEW_ARRAY(internal_link_projection->num_crossings, LCCrossing); for (i = 0; i < internal_link_projection->num_crossings; i++) { for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) internal_link_projection->crossings[i].neighbor[j][k] = internal_link_projection->crossings + (external_link_projection->crossings[i].neighbor[j][k] - external_link_projection->crossings); for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) internal_link_projection->crossings[i].strand[j][k] = external_link_projection->crossings[i].strand[j][k]; internal_link_projection->crossings[i].handedness = external_link_projection->crossings[i].handedness; for (j = 0; j < 2; j++) internal_link_projection->crossings[i].component[j] = external_link_projection->crossings[i].component[j]; internal_link_projection->crossings[i].visited = FALSE; for (j = 0; j < 4; j++) internal_link_projection->crossings[i].tet[j] = NULL; } return internal_link_projection; } static void free_internal_projection( LCProjection *internal_link_projection) { if (internal_link_projection != NULL) { if (internal_link_projection->crossings != NULL) my_free(internal_link_projection->crossings); my_free(internal_link_projection); } } static void add_nugatory_crossings_to_free_loops( LCProjection *internal_link_projection) { LCCrossing *new_crossing; Boolean *component_has_crossings; int next_component, i, j; if (internal_link_projection->num_free_loops <= 0) uFatalError("add_nugatory_crossings_to_free_loops", "link_complement"); /* * Transfer the existing crossings to a bigger array. */ resize_crossing_array( internal_link_projection, internal_link_projection->num_crossings + internal_link_projection->num_free_loops); /* * Note which components already have crossings. */ component_has_crossings = NEW_ARRAY(internal_link_projection->num_components, Boolean); for (i = 0; i < internal_link_projection->num_components; i++) component_has_crossings[i] = FALSE; for (i = 0; i < internal_link_projection->num_crossings; i++) { component_has_crossings[internal_link_projection->crossings[i].component[KLPStrandX]] = TRUE; component_has_crossings[internal_link_projection->crossings[i].component[KLPStrandY]] = TRUE; } next_component = 0; /* * Add the new nugatory crossings, one for each free loop. */ while (internal_link_projection->num_free_loops > 0) { new_crossing = &internal_link_projection->crossings[internal_link_projection->num_crossings]; for (i = 0; i < 2; i++) /* i = KLPStrandX, KLPStrandY */ for (j = 0; j < 2; j++) /* j = KLPBackward, KLPForward */ { new_crossing->neighbor[i][j] = new_crossing; new_crossing->strand [i][j] = !i; } new_crossing->handedness = KLPHalfTwistCL; /* * Move next_component to the first component which has no crossings. */ while (component_has_crossings[next_component] == TRUE) { next_component++; if (next_component == internal_link_projection->num_components) uFatalError("add_nugatory_crossings_to_free_loops", "link_complement"); } new_crossing->component[KLPStrandX] = next_component; new_crossing->component[KLPStrandY] = next_component; component_has_crossings[next_component] = TRUE; internal_link_projection->num_crossings++; internal_link_projection->num_free_loops--; } /* * Free local memory. */ my_free(component_has_crossings); } static void resize_crossing_array( LCProjection *internal_link_projection, int new_array_size) { /* * Resize the crossing array. * Do NOT change num_crossings. */ LCCrossing *old_array, *new_array; int i, j, k; if (new_array_size < internal_link_projection->num_crossings) uFatalError("resize_crossing_array", "link_complement"); old_array = internal_link_projection->crossings; new_array = NEW_ARRAY(new_array_size, LCCrossing); for (i = 0; i < internal_link_projection->num_crossings; i++) { /* * Copy everything . . . */ new_array[i] = old_array[i]; /* * . . . then revise the pointers. */ for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) new_array[i].neighbor[j][k] = &new_array[old_array[i].neighbor[j][k] - old_array]; } /* * Just to be safe, set uninitialized pointers to NULL. */ for (i = internal_link_projection->num_crossings; i < new_array_size; i++) { for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) new_array[i].neighbor[j][k] = NULL; for (j = 0; j < 4; j++) new_array[i].tet[j] = NULL; } my_free(old_array); internal_link_projection->crossings = new_array; } static void make_projection_connected( LCProjection *internal_link_projection) { /* * Check whether the projection is disconnected, and if it is, * do a Reidemeister type II move to create an "unnecessary" overlap * which merges two of the connected components. Repeat as necessary. * * * \ / \ / * \ / \ * | | | | * | | becomes | | * | | | | * / \ / * / \ / \ */ LCCrossing *crossing0, *crossing1; while (projection_is_connected( internal_link_projection, &crossing0, &crossing1) == FALSE) do_Reidemeister_II(internal_link_projection, crossing0, crossing1); } static Boolean projection_is_connected( LCProjection *internal_link_projection, LCCrossing **crossing0, LCCrossing **crossing1) { int i, j, queue_begin, queue_end; LCCrossing **queue, *crossing; Boolean is_connected; /* * Check whether the projection is connected. * If it's connected, * set *crossing0 and *crossing1 to NULL, and return TRUE. * If it isn't connected, * set *crossing0 and *crossing1 to be pointers to LCCrossings * in different connected components, and return FALSE. */ /* * This routine assumes (1) the link is nonempty, * and (2) there are no free loops. */ if (internal_link_projection->num_components == 0 || internal_link_projection->num_free_loops > 0) uFatalError("projection_is_connected", "link_complement"); /* * Mark all crossings as unvisited. */ for (i = 0; i < internal_link_projection->num_crossings; i++) internal_link_projection->crossings[i].visited = FALSE; /* * Start a queue to keep track of crossings which have been * visited, but whose neighbors have not yet been examined. */ queue = NEW_ARRAY(internal_link_projection->num_crossings, LCCrossing *); /* * Put the zeroth crossing onto the queue. */ queue[0] = &internal_link_projection->crossings[0]; queue[0]->visited = TRUE; queue_begin = 0; queue_end = 0; /* * Process the queue. */ while (queue_begin <= queue_end) { /* * Pull a pointer off the front of the queue. */ crossing = queue[queue_begin++]; /* * Check its four neighbors. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) /* * If a neighbor hasn't yet been visited . . . */ if (crossing->neighbor[i][j]->visited == FALSE) { /* * . . . add it to the queue. */ crossing->neighbor[i][j]->visited = TRUE; queue[++queue_end] = crossing->neighbor[i][j]; } } /* * Do a quick "unnecessary" error check. */ if (queue_end > internal_link_projection->num_crossings - 1) uFatalError("projection_is_connected", "link_complement"); /* * Free the queue. */ my_free(queue); /* * The link projection is connected iff we have visited all crossings. */ *crossing0 = NULL; *crossing1 = NULL; if (queue_end == internal_link_projection->num_crossings - 1) is_connected = TRUE; else { is_connected = FALSE; for (i = 0; i < internal_link_projection->num_crossings; i++) { if (internal_link_projection->crossings[i].visited == TRUE) *crossing0 = &internal_link_projection->crossings[i]; else *crossing1 = &internal_link_projection->crossings[i]; } } return is_connected; } static void do_Reidemeister_II( LCProjection *internal_link_projection, LCCrossing *crossing0, LCCrossing *crossing1) { /* * Crossing0 and crossing1 are assumed to lie in different * connected components of the link projection. Do a type II * Reidemeister move so that their forward X-strands pass over * one another. Note that the two connected components may always * be brought into position to do this (without creating any * additional crossings) if one considers them as link projections * on the 2-sphere, not just on the plane. * * (crossing2) (crossing3) * \ / \ / * \ / \ crossingB * | | | | * | | becomes | | * | | | | * / \ / crossingA * / \ / \ * crossing0 crossing1 crossing0 crossing1 */ int crossing_index0, crossing_index1; LCCrossing *crossingA, *crossingB, *crossing2, *crossing3; KLPStrandType strand2, strand3; /* * Make room for the two new crossings, and give them names. * Also revise the pointers to crossing0 and crossing1. */ crossing_index0 = crossing0 - internal_link_projection->crossings; crossing_index1 = crossing1 - internal_link_projection->crossings; resize_crossing_array( internal_link_projection, internal_link_projection->num_crossings + 2); internal_link_projection->num_crossings += 2; crossing0 = &internal_link_projection->crossings[crossing_index0]; crossing1 = &internal_link_projection->crossings[crossing_index1]; crossingA = &internal_link_projection->crossings[internal_link_projection->num_crossings - 2]; crossingB = &internal_link_projection->crossings[internal_link_projection->num_crossings - 1]; /* * Give names to the crossing which originally follow * crossings 0 and 1, and note which of their strands we're using. */ crossing2 = crossing0->neighbor[KLPStrandX][KLPForward]; strand2 = crossing0->strand [KLPStrandX][KLPForward]; crossing3 = crossing1->neighbor[KLPStrandX][KLPForward]; strand3 = crossing1->strand [KLPStrandX][KLPForward]; /* * Work the two new crossings into the link projection, * as illustrated above. */ crossingA->neighbor[KLPStrandX][KLPBackward] = crossing0; crossingA->neighbor[KLPStrandX][KLPForward ] = crossingB; crossingA->neighbor[KLPStrandY][KLPBackward] = crossing1; crossingA->neighbor[KLPStrandY][KLPForward ] = crossingB; crossingA->strand[KLPStrandX][KLPBackward] = KLPStrandX; crossingA->strand[KLPStrandX][KLPForward ] = KLPStrandY; crossingA->strand[KLPStrandY][KLPBackward] = KLPStrandX; crossingA->strand[KLPStrandY][KLPForward ] = KLPStrandX; crossingA->handedness = KLPHalfTwistCL; crossingA->component[KLPStrandX] = crossing0->component[KLPStrandX]; crossingA->component[KLPStrandY] = crossing1->component[KLPStrandX]; crossingB->neighbor[KLPStrandX][KLPBackward] = crossingA; crossingB->neighbor[KLPStrandX][KLPForward ] = crossing3; crossingB->neighbor[KLPStrandY][KLPBackward] = crossingA; crossingB->neighbor[KLPStrandY][KLPForward ] = crossing2; crossingB->strand[KLPStrandX][KLPBackward] = KLPStrandY; crossingB->strand[KLPStrandX][KLPForward ] = strand3; crossingB->strand[KLPStrandY][KLPBackward] = KLPStrandX; crossingB->strand[KLPStrandY][KLPForward ] = strand2; crossingB->handedness = KLPHalfTwistCCL; crossingB->component[KLPStrandX] = crossing1->component[KLPStrandX]; crossingB->component[KLPStrandY] = crossing0->component[KLPStrandX]; crossing0->neighbor[KLPStrandX][KLPForward] = crossingA; crossing0->strand [KLPStrandX][KLPForward] = KLPStrandX; crossing1->neighbor[KLPStrandX][KLPForward] = crossingA; crossing1->strand [KLPStrandX][KLPForward] = KLPStrandY; crossing2->neighbor[strand2][KLPBackward] = crossingB; crossing2->strand [strand2][KLPBackward] = KLPStrandY; crossing3->neighbor[strand3][KLPBackward] = crossingB; crossing3->strand [strand3][KLPBackward] = KLPStrandX; } static void make_all_components_have_crossings( LCProjection *internal_link_projection) { /* * Add nugatory crossings if necessary to ensure that each * link component has both overcrossings and undercrossings. */ Boolean *undercrossing_flags, *overcrossing_flags; int i; /* * The caller should have already checked that there are no free loops. */ if (internal_link_projection->num_free_loops != 0) uFatalError("make_all_components_have_crossings", "link_complement"); undercrossing_flags = NEW_ARRAY(internal_link_projection->num_components, Boolean); overcrossing_flags = NEW_ARRAY(internal_link_projection->num_components, Boolean); for (i = 0; i < internal_link_projection->num_components; i++) { undercrossing_flags[i] = FALSE; overcrossing_flags[i] = FALSE; } for (i = 0; i < internal_link_projection->num_crossings; i++) switch (internal_link_projection->crossings[i].handedness) { case KLPHalfTwistCL: undercrossing_flags[internal_link_projection->crossings[i].component[KLPStrandY]] = TRUE; overcrossing_flags[internal_link_projection->crossings[i].component[KLPStrandX]] = TRUE; break; case KLPHalfTwistCCL: undercrossing_flags[internal_link_projection->crossings[i].component[KLPStrandX]] = TRUE; overcrossing_flags[internal_link_projection->crossings[i].component[KLPStrandY]] = TRUE; break; default: uFatalError("make_all_components_have_crossings", "link_complement"); } for (i = 0; i < internal_link_projection->num_components; i++) if (undercrossing_flags[i] == FALSE || overcrossing_flags[i] == FALSE) add_nugatory_crossing(internal_link_projection, i); my_free(undercrossing_flags); my_free( overcrossing_flags); } static void add_nugatory_crossing( LCProjection *internal_link_projection, int component_index) { /* * The component of the given component_index has only overcrossings * or only undercrossings. Add a nugatory crossing so that the * component will have both. */ LCCrossing *crossingA, *crossingB, *crossingC; KLPStrandType strandC; int i; /* * The caller should have already checked that there are no free loops. */ if (internal_link_projection->num_free_loops != 0) uFatalError("add_nugatory_crossing", "link_complement"); /* * Make room for the new crossing. * (Note that we must resize the array before find the pointer * to crossingA.) */ resize_crossing_array( internal_link_projection, internal_link_projection->num_crossings + 1); /* * Lemma. If a component has at least one crossing, then it must * appear at least once as an X strand and once as a Y strand. * * Proof. If the component crosses itself, the result is obvious. * If it doesn't cross itself, then it's unknotted and bounds * a disk in the plane of the link projection. If some other * link component enters the disk as, say, an X strand, it must * eventually leave the disk as a Y strand. Q.E.D. * * Find a crossing where the link component occurs as an X strand. */ crossingA = NULL; for (i = 0; i < internal_link_projection->num_crossings; i++) if (internal_link_projection->crossings[i].component[KLPStrandX] == component_index) crossingA = &internal_link_projection->crossings[i]; if (crossingA == NULL) uFatalError("add_nugatory_crossing", "link_complement"); /* * Let crossingC be the crossing which follows crossingA * in the forward X direction. */ crossingC = crossingA->neighbor[KLPStrandX][KLPForward]; strandC = crossingA->strand [KLPStrandX][KLPForward]; /* * Create crossingB, which will be a nugatory crossing lying * between crossingA and crossingC. * * _ * / \ * \ / * / crossingB * ____/ \____ * crossingA crossingC */ internal_link_projection->num_crossings++; crossingB = &internal_link_projection->crossings[internal_link_projection->num_crossings - 1]; crossingA->neighbor[KLPStrandX][KLPForward] = crossingB; crossingA->strand [KLPStrandX][KLPForward] = KLPStrandY; crossingB->neighbor[KLPStrandX][KLPBackward] = crossingB; crossingB->neighbor[KLPStrandX][KLPForward ] = crossingC; crossingB->neighbor[KLPStrandY][KLPBackward] = crossingA; crossingB->neighbor[KLPStrandY][KLPForward ] = crossingB; crossingB->strand[KLPStrandX][KLPBackward] = KLPStrandY; crossingB->strand[KLPStrandX][KLPForward ] = strandC; crossingB->strand[KLPStrandY][KLPBackward] = KLPStrandX; crossingB->strand[KLPStrandY][KLPForward ] = KLPStrandX; crossingB->handedness = KLPHalfTwistCCL; crossingB->component[KLPStrandX] = component_index; crossingB->component[KLPStrandY] = component_index; crossingC->neighbor[strandC][KLPBackward] = crossingB; crossingC->strand [strandC][KLPBackward] = KLPStrandX; } static Triangulation *create_basic_triangulation( LCProjection *internal_link_projection) { Triangulation *manifold; LCCrossing *theCrossing, *theNbrCrossing; KLPStrandType theNbrStrand; int i, j, k; /* * Allocate and initialize the Triangulation structure itself. */ manifold = NEW_STRUCT(Triangulation); initialize_triangulation(manifold); /* * Allocate and initialize the four Tetrahedra incident to each crossing. */ for (i = 0; i < internal_link_projection->num_crossings; i++) for (j = 0; j < 4; j++) { internal_link_projection->crossings[i].tet[j] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(internal_link_projection->crossings[i].tet[j]); INSERT_BEFORE(internal_link_projection->crossings[i].tet[j], &manifold->tet_list_end); manifold->num_tetrahedra++; } /* * Interpret the vertex indices of each Tetrahedron as follows: * * Vertex 0 is at the south pole. * Vertex 1 is at the north pole. * Vertices 2 and 3 are chosen so that the Tetrahedron is right_handed * according to the definition of Orientation in kernel_typedefs.h. */ /* * Set the neighbor fields. * * To make sense of this, please refer to the sketch you made while * reading the documentation at the top of this file. */ for (i = 0; i < internal_link_projection->num_crossings; i++) { /* * Consider the group of four Tetrahedra surrounding crossing i. */ theCrossing = &internal_link_projection->crossings[i]; /* * Set the neighbor fields within the group. */ if (theCrossing->handedness == KLPHalfTwistCL) { theCrossing->tet[0]->neighbor[0] = theCrossing->tet[1]; theCrossing->tet[0]->neighbor[1] = theCrossing->tet[3]; theCrossing->tet[1]->neighbor[0] = theCrossing->tet[0]; theCrossing->tet[1]->neighbor[1] = theCrossing->tet[2]; theCrossing->tet[2]->neighbor[0] = theCrossing->tet[3]; theCrossing->tet[2]->neighbor[1] = theCrossing->tet[1]; theCrossing->tet[3]->neighbor[0] = theCrossing->tet[2]; theCrossing->tet[3]->neighbor[1] = theCrossing->tet[0]; } else { theCrossing->tet[0]->neighbor[0] = theCrossing->tet[3]; theCrossing->tet[0]->neighbor[1] = theCrossing->tet[1]; theCrossing->tet[1]->neighbor[0] = theCrossing->tet[2]; theCrossing->tet[1]->neighbor[1] = theCrossing->tet[0]; theCrossing->tet[2]->neighbor[0] = theCrossing->tet[1]; theCrossing->tet[2]->neighbor[1] = theCrossing->tet[3]; theCrossing->tet[3]->neighbor[0] = theCrossing->tet[0]; theCrossing->tet[3]->neighbor[1] = theCrossing->tet[2]; } /* * Set the neighbor fields connecting this group to other groups. */ /* * backward x-direction */ theNbrCrossing = theCrossing->neighbor[KLPStrandX][KLPBackward]; theNbrStrand = theCrossing->strand [KLPStrandX][KLPBackward]; if (theNbrStrand == KLPStrandX) { theCrossing->tet[2]->neighbor[3] = theNbrCrossing->tet[3]; theCrossing->tet[1]->neighbor[2] = theNbrCrossing->tet[0]; } else { theCrossing->tet[2]->neighbor[3] = theNbrCrossing->tet[0]; theCrossing->tet[1]->neighbor[2] = theNbrCrossing->tet[1]; } /* * forward x-direction */ theNbrCrossing = theCrossing->neighbor[KLPStrandX][KLPForward]; theNbrStrand = theCrossing->strand [KLPStrandX][KLPForward]; if (theNbrStrand == KLPStrandX) { theCrossing->tet[0]->neighbor[3] = theNbrCrossing->tet[1]; theCrossing->tet[3]->neighbor[2] = theNbrCrossing->tet[2]; } else { theCrossing->tet[0]->neighbor[3] = theNbrCrossing->tet[2]; theCrossing->tet[3]->neighbor[2] = theNbrCrossing->tet[3]; } /* * backward y-direction */ theNbrCrossing = theCrossing->neighbor[KLPStrandY][KLPBackward]; theNbrStrand = theCrossing->strand [KLPStrandY][KLPBackward]; if (theNbrStrand == KLPStrandX) { theCrossing->tet[3]->neighbor[3] = theNbrCrossing->tet[3]; theCrossing->tet[2]->neighbor[2] = theNbrCrossing->tet[0]; } else { theCrossing->tet[3]->neighbor[3] = theNbrCrossing->tet[0]; theCrossing->tet[2]->neighbor[2] = theNbrCrossing->tet[1]; } /* * forward y-direction */ theNbrCrossing = theCrossing->neighbor[KLPStrandY][KLPForward]; theNbrStrand = theCrossing->strand [KLPStrandY][KLPForward]; if (theNbrStrand == KLPStrandX) { theCrossing->tet[1]->neighbor[3] = theNbrCrossing->tet[1]; theCrossing->tet[0]->neighbor[2] = theNbrCrossing->tet[2]; } else { theCrossing->tet[1]->neighbor[3] = theNbrCrossing->tet[2]; theCrossing->tet[0]->neighbor[2] = theNbrCrossing->tet[3]; } } /* * Set the gluing fields. * * A very pleasant consequence of our indexing scheme is that * every gluing in the whole triangulation is 2310! */ for (i = 0; i < internal_link_projection->num_crossings; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) internal_link_projection->crossings[i].tet[j]->gluing[k] = PERMUTATION2310; return manifold; } static void create_real_cusps( LCProjection *internal_link_projection, Triangulation *manifold) { Cusp **theCusps, *theXCusp, *theYCusp; LCCrossing *theCrossing; int i; /* * Use a temporary array to keep the freshly created Cusps organized. */ theCusps = NEW_ARRAY(internal_link_projection->num_components, Cusp *); /* * Create and initialize the Cusps. */ manifold->num_cusps = 0; manifold->num_or_cusps = 0; manifold->num_nonor_cusps = 0; for (i = 0; i < internal_link_projection->num_components; i++) { theCusps[i] = NEW_STRUCT(Cusp); initialize_cusp(theCusps[i]); theCusps[i]->topology = torus_cusp; theCusps[i]->index = i; theCusps[i]->is_finite = FALSE; INSERT_BEFORE(theCusps[i], &manifold->cusp_list_end); manifold->num_cusps++; manifold->num_or_cusps++; } /* * Assign the Cusps to the ideal vertices of the Tetrahedra. * Don't worry about the finite vertices for now. */ for (i = 0; i < internal_link_projection->num_crossings; i++) { theCrossing = &internal_link_projection->crossings[i]; theXCusp = theCusps[theCrossing->component[KLPStrandX]]; theYCusp = theCusps[theCrossing->component[KLPStrandY]]; theCrossing->tet[0]->cusp[2] = theXCusp; theCrossing->tet[0]->cusp[3] = theYCusp; theCrossing->tet[1]->cusp[2] = theYCusp; theCrossing->tet[1]->cusp[3] = theXCusp; theCrossing->tet[2]->cusp[2] = theXCusp; theCrossing->tet[2]->cusp[3] = theYCusp; theCrossing->tet[3]->cusp[2] = theYCusp; theCrossing->tet[3]->cusp[3] = theXCusp; } /* * Free the temporary array. */ my_free(theCusps); } static void create_finite_vertices( LCProjection *internal_link_projection, Triangulation *manifold) { Cusp *thePoles[2]; int i, j, k; /* * Create finite vertices for the north and south poles. */ for (i = 0; i < 2; i++) { thePoles[i] = NEW_STRUCT(Cusp); initialize_cusp(thePoles[i]); thePoles[i]->index = i - 2; /* indices are -1 and -2 */ thePoles[i]->is_finite = TRUE; INSERT_BEFORE(thePoles[i], &manifold->cusp_list_end); } /* * Assign the finite vertices to the Tetrahedra. */ for (i = 0; i < internal_link_projection->num_crossings; i++) for (j = 0; j < 4; j++) for (k = 0; k < 2; k++) internal_link_projection->crossings[i].tet[j]->cusp[k] = thePoles[k]; } static void add_peripheral_curves( LCProjection *internal_link_projection) { clear_peripheral_curves (internal_link_projection); add_longitudes (internal_link_projection); add_meridians (internal_link_projection); adjust_longitudes (internal_link_projection); } static void clear_peripheral_curves( LCProjection *internal_link_projection) { int i, j, c, h, v, f; /* * initialize_tetrahedron() has already initialized the peripheral * curves to zero, but we reinitialize them just to be safe. */ for (i = 0; i < internal_link_projection->num_crossings; i++) for (j = 0; j < 4; j++) for (c = 0; c < 2; c++) for (h = 0; h < 2; h++) for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) internal_link_projection->crossings[i].tet[j]->curve[c][h][v][f] = 0; } static void add_longitudes( LCProjection *internal_link_projection) { /* * Construct longitudes which run along the right side of each link * component in the forward direction. Eventually add_peripheral_curves() * will call adjust_longitudes() to add in some number of meridians * to obtain the homologically trivial longitudes. */ int i; LCCrossing *theCrossing; for (i = 0; i < internal_link_projection->num_crossings; i++) { theCrossing = &internal_link_projection->crossings[i]; /* * x strand */ theCrossing->tet[2]->curve[L][right_handed][2][3] = +1; theCrossing->tet[3]->curve[L][right_handed][3][2] = -1; if (theCrossing->handedness == KLPHalfTwistCL) { theCrossing->tet[2]->curve[L][right_handed][2][0] = -1; theCrossing->tet[3]->curve[L][right_handed][3][0] = +1; } else { theCrossing->tet[2]->curve[L][right_handed][2][1] = -1; theCrossing->tet[3]->curve[L][right_handed][3][1] = +1; } /* * y strand */ theCrossing->tet[3]->curve[L][right_handed][2][3] = +1; theCrossing->tet[0]->curve[L][right_handed][3][2] = -1; if (theCrossing->handedness == KLPHalfTwistCL) { theCrossing->tet[3]->curve[L][right_handed][2][1] = -1; theCrossing->tet[0]->curve[L][right_handed][3][1] = +1; } else { theCrossing->tet[3]->curve[L][right_handed][2][0] = -1; theCrossing->tet[0]->curve[L][right_handed][3][0] = +1; } } } static void add_meridians( LCProjection *internal_link_projection) { Boolean *theArray; int i, theComponent; LCCrossing *theCrossing, *theNextCrossing; KLPStrandType theStrand, theNextStrand; Boolean theStrandGoesOver, theNextStrandGoesOver; /* * Construct one meridian for each link component. * Define it using the right hand rule (grasp the link component * with your right hand -- if your thumb points in the direction * of the positive longitude, your fingers will wrap around in the * direction of the positive meridian). */ /* * The following proposition allows us to examine only x-strands. * * Proposition. Each link component appears as the x-strand * at some crossing. * * Proof. If a component C crosses itself, it's both the x- and * y-strand at that crossing. So assume it doesn't cross itself, * in which case it bounds a disk D in the link projection. By the * Essential Assumption in the documentation at the top of this file, * the component C has crossings. Trace some other component C' which * crosses C. If C is an x-strand where C' enters the disk D, it's * a y-strand where C' leaves D, and vice versa. Q.E.D. */ /* * Use a temporary array to record which meridians have been constructed. */ theArray = NEW_ARRAY(internal_link_projection->num_components, Boolean); for (i = 0; i < internal_link_projection->num_components; i++) theArray[i] = FALSE; for (i = 0; i < internal_link_projection->num_crossings; i++) { theComponent = internal_link_projection->crossings[i].component[KLPStrandX]; if (theArray[theComponent] == FALSE) { /* * We've found the desired component, but drawing the * meridian isn't as simple as one would hope, because * either the top or the bottom of the x-strand is * incident to "shrinking bigons", as described in the * documentation at the top of this file. * * To simplify matters, we move along theComponent until * we reach an overcrossing immediately followed by an * undercrossing. (According to the Essential Assumption * each link component has both undercrossings and * overcrossings, so we are sure to find such a point.) * * Note: An "overcrossing" (resp. an "undercrossing") is * a crossing at which theComponent passes over (resp. under) * another strand. */ theCrossing = &internal_link_projection->crossings[i]; theStrand = KLPStrandX; theStrandGoesOver = (theCrossing->handedness == KLPHalfTwistCL) ? (theStrand == KLPStrandX) : (theStrand == KLPStrandY); theNextCrossing = theCrossing->neighbor[theStrand][KLPForward]; theNextStrand = theCrossing->strand [theStrand][KLPForward]; theNextStrandGoesOver = (theNextCrossing->handedness == KLPHalfTwistCL) ? (theNextStrand == KLPStrandX) : (theNextStrand == KLPStrandY); while (theStrandGoesOver == FALSE || theNextStrandGoesOver == TRUE) { theCrossing = theNextCrossing; theStrand = theNextStrand; theStrandGoesOver = theNextStrandGoesOver; theNextCrossing = theCrossing->neighbor[theStrand][KLPForward]; theNextStrand = theCrossing->strand [theStrand][KLPForward]; theNextStrandGoesOver = (theNextCrossing->handedness == KLPHalfTwistCL) ? (theNextStrand == KLPStrandX) : (theNextStrand == KLPStrandY); } /* * We've reach a point where theStrandGoesOver == TRUE and * theNextStrandGoesOver == FALSE, so we can draw the meridian. */ if (theStrand == KLPStrandX) /* => KLPHalfTwistCL */ { theCrossing->tet[3]->curve[M][right_handed][3][1] = -1; theCrossing->tet[3]->curve[M][right_handed][3][2] = +1; theCrossing->tet[0]->curve[M][right_handed][2][1] = +1; theCrossing->tet[0]->curve[M][right_handed][2][3] = -1; } else /* theStrand == KLPStrandY => KLPHalfTwistCCL */ { theCrossing->tet[0]->curve[M][right_handed][3][1] = -1; theCrossing->tet[0]->curve[M][right_handed][3][2] = +1; theCrossing->tet[1]->curve[M][right_handed][2][1] = +1; theCrossing->tet[1]->curve[M][right_handed][2][3] = -1; } if (theNextStrand == KLPStrandX) /* => KLPHalfTwistCCL */ { theNextCrossing->tet[1]->curve[M][right_handed][3][0] = -1; theNextCrossing->tet[1]->curve[M][right_handed][3][2] = +1; theNextCrossing->tet[2]->curve[M][right_handed][2][0] = +1; theNextCrossing->tet[2]->curve[M][right_handed][2][3] = -1; } else /* theNextStrand == KLPStrandY => KLPHalfTwistCL */ { theNextCrossing->tet[2]->curve[M][right_handed][3][0] = -1; theNextCrossing->tet[2]->curve[M][right_handed][3][2] = +1; theNextCrossing->tet[3]->curve[M][right_handed][2][0] = +1; theNextCrossing->tet[3]->curve[M][right_handed][2][3] = -1; } /* * Note that we've added the peripheral curves to this component. */ theArray[theComponent] = TRUE; } } /* * Free the temporary array. */ my_free(theArray); } static void adjust_longitudes( LCProjection *internal_link_projection) { /* * To define the canonical longitude on a link component, consider * the link component alone, ignoring all other link components, * and let the canonical longitude be the one which is homologically * trivial in the knot complement. (The direction of the longitude * is of course the direction of the link component.) The canonical * longitude is well defined up to isotopy. * * If a link component never crosses itself, then the longitude * which runs along the right side of the (thickened) link component * is a canonical longitude. Proof: cone to the north pole of S^3 * to see that the longitude bounds a disk. * * If the link component does cross itself, then when you try coning * to the north pole the "disk" will intersect the (thickened) link * component in some number of meridians. To obtain the canonical * longitude, we must subtract off that number of meridians. * * Each counterclockwise crossing generates a negative meridian, * and each clockwise crossing generates a positive meridian, so * we must compute the signed sum of the crossings, and subtract * that number of meridians from the longitude. */ int *theSignedSum, i, j, v, f, theXComponent, theYComponent, theXSignedSum, theYSignedSum; Tetrahedron *theTet; /* * Allocate a temporary array to hold the signed sum for each component. */ theSignedSum = NEW_ARRAY(internal_link_projection->num_components, int); /* * Initialize the signed sums to zero. */ for (i = 0; i < internal_link_projection->num_components; i++) theSignedSum[i] = 0; /* * Add in -1 (resp. +1) for each counterclockwise (resp. clockwise) * crossing where a component crosses itself. */ for (i = 0; i < internal_link_projection->num_crossings; i++) { theXComponent = internal_link_projection->crossings[i].component[KLPStrandX]; theYComponent = internal_link_projection->crossings[i].component[KLPStrandY]; if (theXComponent == theYComponent) { if (internal_link_projection->crossings[i].handedness == KLPHalfTwistCL) theSignedSum[theXComponent]++; else theSignedSum[theXComponent]--; } } /* * Subtract the appropriate multiples of the meridians from the * longitudes. Note that only vertices 2 and 3 carry peripheral curves. */ for (i = 0; i < internal_link_projection->num_crossings; i++) { theXSignedSum = theSignedSum[internal_link_projection->crossings[i].component[KLPStrandX]]; theYSignedSum = theSignedSum[internal_link_projection->crossings[i].component[KLPStrandY]]; for (j = 0; j < 4; j++) { theTet = internal_link_projection->crossings[i].tet[j]; for (v = 2; v < 4; v++) for (f = 0; f < 4; f++) theTet->curve[L][right_handed][v][f] -= (((j & 0x01) == (v & 0x01)) ? theXSignedSum : theYSignedSum) * theTet->curve[M][right_handed][v][f]; } } /* * Free the temporary array. */ my_free(theSignedSum); } snappea-3.0d3/SnapPeaKernel/code/matrix_conversion.c0100444000175000017500000004052307001153027020601 0ustar babbab/* * matrix_conversion.c * * This file provides the two functions * * void Moebius_to_O31(MoebiusTransformation *A, O31Matrix B); * void O31_to_Moebius(O31Matrix B, MoebiusTransformation *A); * * which convert matrices back and forth between SL(2,C) and O(3,1), * as well as the functions * * void Moebius_array_to_O31_array(MoebiusTransformation arrayA[], * O31Matrix arrayB[], * int num_matrices); * void O31_array_to_Moebius_array(O31Matrix arrayB[], * MoebiusTransformation arrayA[], * int num_matrices); * * which do the same for arrays of matrices. * * As an add-on, this file also provides * * Boolean O31_determinants_OK( O31Matrix arrayB[], * int num_matrices, * double epsilon); * * which returns TRUE if all the O31Matrices in the array have determinants * within epsilon of plus or minus one, and FALSE otherwise. * * The algorithm in Moebius_to_O31() is based on an explanation provided * by Craig Hodgson of a program written by Diane Hoffoss which in turn * was based on an algorithm explained to her by Bill Thurston. * One would expect a more straightforward algorithm (i.e. something * which makes a direct correspondence between the Minkowski space * model and the upper half space model), but two problems arise: * (1) Whenever you work with points on the sphere at infinity, you * encounter the problem that there is no good way to normalize * the lengths of vectors on the light cone. The most promising * approach to solving this problem is to work with the basis * which is dual to a basis of lightlike vectors, but this is * at best a nuisance. * (2) The computations required to pass from one model to the * other appear, superficially, more complicated than the simple * calculations used in Thurston's algorithm, but obviously they * have to simplify down to the same thing eventually. * So . . . for now I'll just stick with Thurston's algorithm. */ #include "kernel.h" void Moebius_array_to_O31_array( MoebiusTransformation arrayA[], O31Matrix arrayB[], int num_matrices) { int i; for (i = 0; i < num_matrices; i++) Moebius_to_O31(&arrayA[i], arrayB[i]); } void O31_array_to_Moebius_array( O31Matrix arrayB[], MoebiusTransformation arrayA[], int num_matrices) { int i; for (i = 0; i < num_matrices; i++) O31_to_Moebius(arrayB[i], &arrayA[i]); } void Moebius_to_O31( MoebiusTransformation *A, O31Matrix B) { /* * The trick here is to consider the Minkowski space E(3,1) * not just as an abstract space, but as the space of all * 2 x 2 Hermitian matrices. Roughly speaking, "Hermitian" * is the complex version of "symmetric". * * Definition. The adjoint M* of a complex matrix is * the transpose of the complex conjugate of M. * * Definition. A complex matrix is Hermitian iff M* = M. * * A 2 x 2 complex matrix is Hermitian matrix iff it has the form * * a + 0i c + di * c - di b + 0i * * Such matrices form a 4-dimensional real vector space V. * We choose the following basis for V: * * M0 = 1 0 M1 = 1 0 M2 = 0 1 M3 = 0 i * 0 1 0 -1 1 0 -i 0 * * The determinant -det() defines a (squared) norm on V. * (We use -det() instead of det() so that we end up with * a metric of signature (-+++) instead of (+---).) * This norm leads us to an inner product <,> on V. * We want the inner product to satisfy * * = + 2 + * * which is equivalent to * * -det(M+N) = -det(M) + 2 - det(N), * * so we define the inner product to be * * = (1/2)(det(M) + det(N) - det(M+N)). * * Relative to this inner product, the vectors {M0, M1, M2, M3} * are mutually orthogonal and have squared norms {-1, +1, +1, +1}. * I.e. this is the usual metric for the Minkowski space E(3,1). * * Assume for the moment that the MoebiusTransformation we want * to convert to O(3,1) represents an orientation_preserving isometry. * * We will let a matrix A in SL(2,C) act on the Minkowski * space V, and compute the matrix B in O(3,1) which describes * the action. The action of the matrix A will be denoted f(A). * Thus, f(A) is itself a function which acts on elements of V. * To define f(A), we must say how it acts on each element M of V: * * [f(A)](M) = A M A* * * It's trivial to see that f(A) is a linear function. * f(A) preserves norms [det(AMA*) = det(A)det(M)det(A*) = det(M), * because det(A) = 1], hence it also preserves the inner product; * therefore f(A) is an isometry of V. * * To show that f() is a homomorphism from SL(2,C) to Isom(V), * we must show that the isometries f(A) o f(A') and f(AA') * are equal (where "o" denotes composition of functions). * That is, we must show that [f(A) o f(A')](M) = f(AA')(M) * for all M in V. * * [f(A) o f(A')](M) = f(A)( f(A')(M) ) * = f(A)( A' M A'* ) * = A A' M A'* A* * = (AA') M (AA')* * = f(AA')(M) * * It's easy to check that f(A) fixes the basis {M0, M1, M2, M3} * iff A is plus or minus the identity. That is, the kernel * of f() is {+-I}, and we may think of f() as an injective * map from PSL(2,C) into Isom(V). * * Exercise for the reader: prove that f() maps PSL(2,C) * onto the set of isometries of V which preserve the "time * direction". I.e. the set of all isometries which don't * interchange the past and future light cones. * * So far we have only established the correspondence between * isometries in the upper half space model (as represented * by SL(2,C) matrices) and isometries in the Minkowski space * model (as represented by O(3,1) matrices). We haven't made * a direct correspondence between the points of the upper half * space model and the points of the Minkowski space model. * For some purposes (e.g. dealing with orientation_reversing * isometries) we need to know the precise correspondence between * the spheres at infinity in the two models. We deduce the * correspondence by converting several carefully chosen matrices * from one model to the other, and comparing their fixed points * on the sphere at infinity in each model. Each isometry is * a translation along a geodesic (without rotation). * * Upper Half Space Minkowski Space * * matrix axis matrix axis * * 17/8 15/8 0 0 from * 2 0 from 0 15/8 17/8 0 0 (1,-1, 0, 0) * 0 1/2 towards 0 0 1 0 towards * infinity 0 0 0 1 (1, 1, 0, 0) * * 17/8 0 15/8 0 from * 5/4 3/4 from -1 0 1 0 0 (1, 0,-1, 0) * 3/4 5/4 towards 1 15/8 0 17/8 0 towards * 0 0 0 1 (1, 0, 1, 0) * * 17/8 0 0 15/8 from * 5/4 3i/4 from -i 0 1 0 0 (1, 0, 0,-1) * -3i/4 5/4 towards i 0 0 1 0 towards * 15/8 0 0 17/8 (1, 0, 0, 1) * * Comparison of the above fixed points reveals the correspondence * * 0 <-> (1,-1, 0, 0) * infinity <-> (1, 1, 0, 0) * -1 <-> (1, 0,-1, 0) * 1 <-> (1, 0, 1, 0) * -i <-> (1, 0, 0,-1) * i <-> (1, 0, 0, 1) * * As a corollary, we may deduce the matrix in Minkowski space * which corresponds to complex conjugation in the sphere at * infinity in the upper half space model. In the upper half * space model, complex conjugation fixes 0, infinity, -1 and 1, * and interchanges -i and i. Therefore in the Minkowski space * model it must fix (1,-1, 0, 0), (1, 1, 0, 0), (1, 0,-1, 0) * and (1, 0, 1, 0), and interchange (1, 0, 0,-1) and (1, 0, 0, 1). * The matrix which does this is * * 1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 -1 * * Therefore, to convert an orientation_reversing MoebiusTransformation * to SO(3,1), we first convert the SL(2,C) matrix (as if the isometry * were orientation_preserving), and then multiply the resulting O(3,1) * matrix on the right by the matrix shown above, to account for the * complex conjugation. * * After that long-winded documentation, the code itself is very * simple. The (i,j)-th entry of the O(3,1) matrix B is the * i-th component (relative to the basis {m[0], m[1], m[2], m[3]}) * of A m[j] A*. */ SL2CMatrix ad_A, /* A* = adjoint of A */ fAmj, /* f(A)(m[j]) = A m[j] A* */ temp; int i, /* which row of B */ j; /* which column of B */ CONST static SL2CMatrix m[4] = { {{{ 1.0, 0.0},{ 0.0, 0.0}}, {{ 0.0, 0.0},{ 1.0, 0.0}}}, {{{ 1.0, 0.0},{ 0.0, 0.0}}, {{ 0.0, 0.0},{-1.0, 0.0}}}, {{{ 0.0, 0.0},{ 1.0, 0.0}}, {{ 1.0, 0.0},{ 0.0, 0.0}}}, {{{ 0.0, 0.0},{ 0.0, 1.0}}, {{ 0.0,-1.0},{ 0.0, 0.0}}} }; /* * First convert A->matrix to SO(3,1), without * worrying about A->parity. */ /* * For each basis vector m[j] . . . */ for (j = 0; j < 4; j++) { /* * . . . compute f(A)(m[j]) = A m[j] A* . . . */ sl2c_adjoint(A->matrix, ad_A); sl2c_product(A->matrix, m[j], temp); sl2c_product(temp, ad_A, fAmj); /* * . . . and find its components relative to the basis m[]. */ B[0][j] = 0.5 * (fAmj[0][0].real + fAmj[1][1].real); B[1][j] = 0.5 * (fAmj[0][0].real - fAmj[1][1].real); B[2][j] = fAmj[0][1].real; B[3][j] = fAmj[0][1].imag; } /* * If A->parity is orientation_reversing, multiply on the * right by * 1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 -1 */ if (A->parity == orientation_reversing) for (i = 0; i < 4; i++) B[i][3] = - B[i][3]; } void O31_to_Moebius( O31Matrix B, MoebiusTransformation *A) { /* * We want to invert the transformation described in Moebius_to_O31(). * * If the isometry is orientation_reversing (i.e. if det(B) == -1), * we first factor it as * * ( original ) ( new ) (1 0 0 0) * ( matrix ) = ( matrix ) (0 1 0 0) * ( B ) ( B ) (0 0 1 0) * ( ) ( ) (0 0 0 -1) * * We then convert the "new matrix B" to an SL(2,C) matrix to get * A->matrix, and we set A->parity to orientation_reversing. * The matrix on the far right (= diag(1, 1, 1, -1)) corresponds * to complex conjugation, as explained in the documentation in * Moebius_to_O31() above. * * First write out what Moebius_to_O31() does to a typical Moebius * transformation A. Assume for the moment that the Moebius * transformation is orientation_preserving, and denote the matrix * by A (rather than A->matrix) to save space. As usual, the entries * of A are * * a b * c d * * and the complex conjugate of a number z is written z' (read "z-bar"). * You should also imagine a single set of parentheses around each * 2 x 2 matrix, in spite of the limitations of this text-only file. * Each of the following lines computes A M A* for one of the four * basis vectors {M0, M1, M2, M3}. * * (a b) ( 1 0) (a' c') = ( aa' + bb' ac' + bd') * (c d) ( 0 1) (b' d') ( a'c + b'd cc' + dd') * * (a b) ( 1 0) (a' c') = ( aa' - bb' ac' - bd') * (c d) ( 0 -1) (b' d') ( a'c - b'd cc' - dd') * * (a b) ( 0 1) (a' c') = ( ab' + a'b ad' + bc') * (c d) ( 1 0) (b' d') ( a'd + b'c cd' + c'd) * * (a b) ( 0 i) (a' c') = i ( ab' - a'b ad' - bc') * (c d) (-i 0) (b' d') (-a'd + b'c cd' - c'd) * * The right side of each of the above equations is a * linear combination of the {M0, M1, M2, M3}. The coefficients * are the entries of the O(3,1) matrix B. For the j-th line above, * * A M[j] A* * * = B[0][j] M0 + B[1][j] M1 + B[2][j] M2 + B[3][j] M3 * * = B[0][j] (1 0) + B[1][j] (1 0) + B[2][j] (0 1) + B[3][j] ( 0 i) * (0 1) (0 -1) (1 0) (-i 0) * * = ( B[0][j] + B[1][j] B[2][j] + i B[3][j] ) * ( B[2][j] - i B[3][j] B[0][j] + B[1][j] ) * * Comparing matrix entries in the two computations of A M[j] A* gives * relations like aa' + bb' = B[0][0] + B[1][0], etc. * There is no need to write out all 16 relations -- we'll get the * ones we need later on, as we need them. * * It's not so easy to compute the matrix * * a b * c d * * from the above relations, but it is easy to compute * * 2a'a 2a'b or 2b'a 2b'b * 2a'c 2a'd 2b'c 2b'd * * (The details are in the code below.) * Each of the latter two matrices, if nonzero, can be normalized * to give the former. At least one of them will be nonzero, * because if a and b were both zero, the determinant would * be zero. * * So . . . the algorithm is to decided whether a or b is * nonzero, then compute one of the above two matrices, and * normalize it to give the matrix A. */ int i; double AM0A_00, /* The (0, 0) entry of A M0 A* */ AM1A_00, /* The (0, 0) entry of A M1 A* */ aa, /* 2 * |a|^2 */ bb; /* 2 * |b|^2 */ /* * Now deal with the orientation, as explained at the beginning * of this function's documentation. gl4R_determinant(B) will be * * +1 if the isometry is orientation_preserving * * -1 if the isometry is orientation_reversing */ if (gl4R_determinant(B) > 0.0) A->parity = orientation_preserving; else { A->parity = orientation_reversing; /* * Factor out diag(1, 1, 1, -1), as explained above. * At the end of the function we'll restore B to its * original condition. */ for (i = 0; i < 4; i++) B[i][3] = - B[i][3]; } /* * From above, * * (A M0 A*)[0][0] = aa' + bb' = B[0][0] + B[1][0] * (A M1 A*)[0][0] = aa' - bb' = B[0][1] + B[1][1] * => * 2aa' = (A M0 A*)[0][0] + (A M1 A*)[0][0] * = (B[0][0] + B[1][0]) + (B[0][1] + B[1][1]) * * 2bb' = (A M0 A*)[0][0] - (A M1 A*)[0][0] * = (B[0][0] + B[1][0]) - (B[0][1] + B[1][1]) */ AM0A_00 = B[0][0] + B[1][0]; AM1A_00 = B[0][1] + B[1][1]; aa = AM0A_00 + AM1A_00; bb = AM0A_00 - AM1A_00; if (aa > bb) /* |a| > |b| */ { A->matrix[0][0].real = aa; /* 2a'a */ A->matrix[0][0].imag = 0; /* * (A M2 A*)[0][0] = ab' + a'b * = 2 Re(a'b) * = B[0][2] + B[1][2] * * (A M3 A*)[0][0] = i (ab' - a'b) * = i ( -2i Im(a'b) ) * = 2 Im(a'b) * = B[0][3] + B[1][3] */ A->matrix[0][1].real = B[0][2] + B[1][2]; /* 2a'b */ A->matrix[0][1].imag = B[0][3] + B[1][3]; /* * a'c + b'd = (A M0 A*)[1][0] = B[2][0] - i B[3][0] * a'c - b'd = (A M1 A*)[1][0] = B[2][1] - i B[3][1] * * => 2a'c = (B[2][0] + B[2][1]) - i (B[3][0] + B[3][1]) */ A->matrix[1][0].real = B[2][0] + B[2][1]; /* 2a'c */ A->matrix[1][0].imag = - B[3][0] - B[3][1]; /* * a'd + b'c = (A M2 A*)[1][0] = B[2][2] - i B[3][2] * -i (a'd - b'c) = (A M3 A*)[1][0] = B[2][3] - i B[3][3] * * => 2a'd = (B[2][2] + B[3][3]) + i (B[2][3] - B[3][2]) */ A->matrix[1][1].real = B[2][2] + B[3][3]; /* 2a'd */ A->matrix[1][1].imag = B[2][3] - B[3][2]; } else /* |b| >= |a| */ { /* * (A M2 A*)[0][0] = b'a + ba' * = 2 Re(b'a) * = B[0][2] + B[1][2] * * (A M3 A*)[0][0] = i (b'a - ba') * = i ( 2i Im(b'a) ) * = -2 Im(b'a) * = B[0][3] + B[1][3] */ A->matrix[0][0].real = B[0][2] + B[1][2]; /* 2b'a */ A->matrix[0][0].imag = - B[0][3] - B[1][3]; A->matrix[0][1].real = bb; /* 2b'b */ A->matrix[0][1].imag = 0; /* * b'c + a'd = (A M2 A*)[1][0] = B[2][2] - i B[3][2] * i (b'c - a'd) = (A M3 A*)[1][0] = B[2][3] - i B[3][3] * * => 2b'c = (B[2][2] - B[3][3]) - i (B[2][3] + B[3][2]) */ A->matrix[1][0].real = B[2][2] - B[3][3]; /* 2b'c */ A->matrix[1][0].imag = - B[2][3] - B[3][2]; /* * b'd + a'c = (A M0 A*)[1][0] = B[2][0] - i B[3][0] * - (b'd - a'c) = (A M1 A*)[1][0] = B[2][1] - i B[3][1] * * => 2b'd = (B[2][0] - B[2][1]) + i (B[3][1] - B[3][0]) */ A->matrix[1][1].real = B[2][0] - B[2][1]; /* 2b'd */ A->matrix[1][1].imag = B[3][1] - B[3][0]; } /* * Normalize A to have determinant one. */ sl2c_normalize(A->matrix); /* * If the isometry is orientation_reversing, multiply back in * the diag(1, 1, 1, -1) which we factored out at the beginning. */ if (A->parity == orientation_reversing) for (i = 0; i < 4; i++) B[i][3] = - B[i][3]; } Boolean O31_determinants_OK( O31Matrix arrayB[], int num_matrices, double epsilon) { int i; for (i = 0; i < num_matrices; i++) if (fabs(fabs(gl4R_determinant(arrayB[i])) - 1.0) > epsilon) return FALSE; return TRUE; } snappea-3.0d3/SnapPeaKernel/code/matrix_generators.c0100444000175000017500000001300107041127733020565 0ustar babbab/* * matrix_generators.c * * This file provides the function * * void matrix_generators( Triangulation *manifold, * MoebiusTransformation generators[], * Boolean centroid_at_origin); * * which computes the MoebiusTransformations representing the action * of the generators of a manifold's fundamental group on the sphere at * infinity. matrix_generators() writes the MoebiusTransformations * to the array generators[], which it assumes has already been allocated. */ #include "kernel.h" static void compute_one_generator(Tetrahedron *tet, FaceIndex f, MoebiusTransformation *mt); void matrix_generators( Triangulation *manifold, MoebiusTransformation generators[], Boolean centroid_at_origin) { Boolean *already_computed; int i; FaceIndex f; Tetrahedron *tet; /* * Compute the locations of the ideal vertices of the Tetrahedra * on the sphere at infinity. */ choose_generators(manifold, TRUE, centroid_at_origin); /* * Keep track of which generators we've already computed, * to avoid unnecessary duplication of effort. */ already_computed = NEW_ARRAY(manifold->num_generators, Boolean); for (i = 0; i < manifold->num_generators; i++) already_computed[i] = FALSE; /* * Search through all the faces of all the Tetrahedra looking * for generators. Compute those not already computed. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (tet->generator_status[f] == outbound_generator && already_computed[tet->generator_index[f]] == FALSE) { compute_one_generator(tet, f, &generators[tet->generator_index[f]]); already_computed[tet->generator_index[f]] = TRUE; } /* * We're done. * Free the array and go home. */ my_free(already_computed); } static void compute_one_generator( Tetrahedron *tet, FaceIndex f, MoebiusTransformation *mt) { int i, count; Complex a[3], b[3], k, b1k, normalization; /* * First read out the locations of the corners of this face, and * also its mate elsewhere on the boundary of the fundamental domain. * * There are two possible interpretations for the matrix corresponding * to a given generator. * * (1) Think of the matrix as defining a face pairing isometry on * a fundamental domain. The isometry takes the given face to * its mate. * * (2) Think of the matrix as giving a representation of the abstract * fundamental group into the group of covering transformations. * The isometry takes the mate to the given face. * * Here we adopt interpretation (2). If you preferred (1) you'd need * to switch the definitions of a[] and b[] to * * a[count] = tet->corner[i]; * b[count] = tet->neighbor[f]->corner[EVALUATE(tet->gluing[f], i)]; * * (But don't switch them here! You'll foul up the matrix representations * in fundamental_group.c.) */ count = 0; for (i = 0; i < 4; i++) { if (i == f) continue; a[count] = tet->neighbor[f]->corner[EVALUATE(tet->gluing[f], i)]; b[count] = tet->corner[i]; count++; } /* * Note the parity of MoebiusTransformation. */ mt->parity = tet->generator_parity[f]; /* * If the MoebiusTransformation is orientation_reversing, we want * to compute a function of z-bar, as explained in the documentation * accompanying the definition of a MoebiusTransformation in SnapPea.h. */ if (mt->parity == orientation_reversing) for (i = 0; i < 3; i++) a[i] = complex_conjugate(a[i]); /* * The formula for the Moebius transformation taking the a[] to the b[] * is simple enough: * * f(z) = [ (b1*k - b0) * z + (b0*a1 - b1*a0*k)] / * [ (k - 1) * z + (a1 - k*a0) ] * * where * * k = [(b2-b0)/(b2-b1)] * [(a2-a1)/(a2-a0)] * * Even though one of the a[] and/or one of the b[] could be infinite, * we will bravely push forward with the computation. The justification * for such boldness is that the Complex constant Infinity is not actually * infinite, but is just a large number (1e34). Its square is less than * the assumed value of DBL_MAX, so we can safely compute squares * of "infinite" numbers and expect them to cancel properly with other * infinite numbers. In the normalization step we also assume the fourth * power of "infinity" is less than DBL_MAX. (DBL_MAX is 1.2e+4932 * on a Mac and 1.8e+308 on a Sun or NeXT, so we shouldn't run into * trouble (knock on wood).) * * It would be more rigorous to consider separately the cases with * a0, a1 or a2; and/or b0, b1 or b2 are infinite, and take the limit of * the above formula by hand, but I didn't feel up to it. */ k = complex_div( complex_mult(complex_minus(b[2],b[0]), complex_minus(a[2],a[1])), complex_mult(complex_minus(b[2],b[1]), complex_minus(a[2],a[0])) ); b1k = complex_mult(b[1], k); normalization = complex_sqrt( complex_div( One, complex_mult(k, complex_mult( complex_minus(a[1],a[0]), complex_minus(b[1],b[0]) ) ) ) ); mt->matrix[0][0] = complex_mult( normalization, complex_minus(b1k, b[0]) ); mt->matrix[0][1] = complex_mult( normalization, complex_minus( complex_mult(b[0], a[1]), complex_mult(b1k, a[0]) ) ); mt->matrix[1][0] = complex_mult( normalization, complex_minus(k, One) ); mt->matrix[1][1] = complex_mult( normalization, complex_minus( a[1], complex_mult(k,a[0]) ) ); } snappea-3.0d3/SnapPeaKernel/code/Moebius_transformations.c0100444000175000017500000000217007001144215021737 0ustar babbab/* * Moebius_transformations.c */ #include "kernel.h" CONST MoebiusTransformation Moebius_identity = { { {{1.0, 0.0}, {0.0, 0.0}}, {{0.0, 0.0}, {1.0, 0.0}} }, orientation_preserving }; void Moebius_copy( MoebiusTransformation *dest, MoebiusTransformation *source) { sl2c_copy(dest->matrix, source->matrix); dest->parity = source->parity; } void Moebius_invert( MoebiusTransformation *mt, MoebiusTransformation *mt_inverse) { sl2c_invert(mt->matrix, mt_inverse->matrix); if (mt->parity == orientation_reversing) sl2c_complex_conjugate(mt_inverse->matrix, mt_inverse->matrix); mt_inverse->parity = mt->parity; } void Moebius_product( MoebiusTransformation *a, MoebiusTransformation *b, MoebiusTransformation *product) { SL2CMatrix factor1, factor2; sl2c_copy(factor1, a->matrix); sl2c_copy(factor2, b->matrix); if (a->parity == orientation_reversing) sl2c_complex_conjugate(factor2, factor2); sl2c_product(factor1, factor2, product->matrix); product->parity = (a->parity == b->parity) ? orientation_preserving: orientation_reversing; } snappea-3.0d3/SnapPeaKernel/code/my_malloc.c0100444000175000017500000001220007067435204017007 0ustar babbab/* * my_malloc.c * * This file provides an interface to malloc() and free() which * * (1) Keeps track of the number of calls to malloc() minus the number * of calls to free(). This allows easy detection of programming * errors which allocate more memory than they free (or vice versa). * * (2) Aborts the program if malloc() returns NULL. This saves having * to check whether malloc() == NULL at each point where malloc() * is called. * * All kernel routines use my_malloc() and my_free(); * no UI routines do so. * * The UI should call verify_my_malloc_usage() upon exit, to verify * that the number of calls to my_malloc() was exactly balanced by * the number of calls to my_free(). * * The remainder of this comment deals with a debugging feature, and * may be safely ignored until such time as you start freeing memory * you haven't allocated, or writing off the ends of arrays. * * If the constant DEBUG_MALLOC is #defined to be 1 (see below) then * my_malloc() and my_free() maintain a linked list * of the addresses of the blocks of memory which have been allocated. * If some other part of the kernel attempts to free memory which has * not been allocated (or free the same memory twice), free() generates * and error message and exits. Because this feature is for debugging * purposes only, no attempt is made to be efficient. (Obviously a * binary tree would be more efficient than a linked list, but you'd have * to account for the fact that the memory is most likely allocated * in linear order. Reversing the order of the bytes would be simpler * than implementing a balanced tree.) * * Note that the DEBUG_MALLOC feature itself uses memory, but does not * record its own usage in the linked list. This is OK. Its purpose * is to help debug SnapPea, not malloc(). * * If DEBUG_MALLOC is turned on, my_malloc() tacks four extra bytes on * the end of every requested block of memory, and writes in an * arbitrary (but well defined) sequence of four characters. When the * memory is freed, my_free() checks those four characters to see whether * they've been overwritten. This is not a perfect guarantee against * writing past the ends of array, but it should detect at least some * errors. */ #include "kernel.h" #include /* needed for malloc() */ #include /* needed for sprintf() */ static int net_malloc_calls = 0; /* * The debugging feature is normally off. */ #define DEBUG_MALLOC 1 #if DEBUG_MALLOC #define MAX_BYTES 50000 typedef struct memnode { void *address; size_t bytes; struct memnode *next; } MemNode; static MemNode mem_list = {NULL, 0, NULL}; static const char secret_code[5] = "Adam"; static Boolean message_given = FALSE; #endif void *my_malloc( size_t bytes) { void *ptr; #if DEBUG_MALLOC MemNode *new_mem_node; char *error_bytes; int i; #endif #if DEBUG_MALLOC if (message_given == FALSE) { uAcknowledge("The my_malloc() memory allocator is in debugging mode."); message_given = TRUE; } #endif #if DEBUG_MALLOC if (bytes < 0) { uAcknowledge("A negative number of bytes were requested in my_malloc()."); exit(3); } if (bytes > MAX_BYTES) uAcknowledge("Too many bytes were requested in my_malloc()."); #endif /* * Most likely malloc() and free() would correctly handle * a request for zero bytes, but why take chances? */ if (bytes == 0) bytes = 1; #if DEBUG_MALLOC ptr = malloc(bytes + 4); #else ptr = malloc(bytes); #endif if (ptr == NULL) uAbortMemoryFull(); net_malloc_calls++; #if DEBUG_MALLOC error_bytes = (char *) ptr + bytes; for (i = 0; i < 4; i++) error_bytes[i] = secret_code[i]; new_mem_node = (MemNode *) malloc((size_t) sizeof(MemNode)); if (new_mem_node == NULL) { uAcknowledge("out of memory"); exit(4); } new_mem_node->address = ptr; new_mem_node->bytes = bytes; new_mem_node->next = mem_list.next; mem_list.next = new_mem_node; #endif return ptr; } void my_free( void *ptr) { #if DEBUG_MALLOC Boolean old_node_found; MemNode *old_mem_node, *prev_mem_node; size_t bytes; char *error_bytes; int i; #endif #if DEBUG_MALLOC old_node_found = FALSE; for ( prev_mem_node = &mem_list, old_mem_node = mem_list.next; old_mem_node != NULL; old_mem_node = old_mem_node->next, prev_mem_node = prev_mem_node->next) if (old_mem_node->address == ptr) { old_node_found = TRUE; bytes = old_mem_node->bytes; prev_mem_node->next = old_mem_node->next; free(old_mem_node); break; } if (old_node_found == FALSE) { uAcknowledge("A bad address was passed to my_free()."); exit(5); } error_bytes = (char *) ptr + bytes; for (i = 0; i < 4; i++) if (error_bytes[i] != secret_code[i]) { uAcknowledge("my_free() received a corrupted array."); exit(6); } #endif free(ptr); net_malloc_calls--; } int malloc_calls() { return net_malloc_calls; } void verify_my_malloc_usage() { char the_message[256]; if (net_malloc_calls != 0) { sprintf(the_message, "Memory allocation error:\rThere were %d %s calls to my_malloc() than to my_free().", net_malloc_calls > 0 ? net_malloc_calls : - net_malloc_calls, net_malloc_calls > 0 ? "more" : "fewer"); uAcknowledge(the_message); } } snappea-3.0d3/SnapPeaKernel/code/normal_surface_construction.c0100444000175000017500000010746106742675502022671 0ustar babbab/* * normal_surface_construction.c * * FuncResult find_normal_surfaces( Triangulation *manifold, * NormalSurfaceList **surface_list); * * tries to find connected, embedded normal surfaces of nonnegative * Euler characteristic. If spheres or projective planes are found, * then tori and Klein bottles aren't reported, because from the point * of view of the Geometrization Conjecture, one wants to cut along * spheres and projective planes first. Surfaces are guaranteed * to be connected. They aren't guaranteed to be incompressible, * although typically they are. There is no guarantee that all such * normal surfaces will be found. Returns its result as a * NormalSurfaceList. The present implementation works only for * cusped manifolds. Returns func_bad_input for closed manifolds, * or non-manifolds (e.g. for orbifolds or noninteger Dehn fillings). * * int number_of_normal_surfaces_on_list(NormalSurfaceList *surface_list); * * returns the number of normal surfaces contained in the list. * * Boolean normal_surface_is_orientable( NormalSurfaceList *surface_list, * int index); * Boolean normal_surface_is_two_sided( NormalSurfaceList *surface_list, * int index); * int normal_surface_Euler_characteristic(NormalSurfaceList *surface_list, * int index); * * return information about a given normal surface on the list. * * void free_normal_surfaces(NormalSurfaceList *surface_list); * * frees an array of NormalSurfaceLists. */ /* * The Algorithm * * Normal surfaces consist of a collection of squares and triangles, * as described in normal_surfaces.h. At present we assume the * manifold has no filled cusps, although a later version of SnapPea * may include an algoritm for the filled case as well. * * When SnapPea tries to find a hyperbolic structure for a manifold * containing an incompressible sphere, projective plane, torus or * Klein bottle, the tetrahedra containing the squares of the normal * surface description tend to "pinch off" at the square and become * degenerate. That is, the complex edge parameters of the edges * parallel to the square tend to one, while the parameters of the * remaining edges tend to zero and infinity. This tells us where to * find the squares for the normal surface, and saves us from blindly * trying all 3^(num tetrahedra) possibilities. I don't know how to * prove that the tetrahedra will always degenerate in this way, but * empirically this is what happens. * * Even though we know the position of the squares, we must still decide * how many parallel copies belong in each tetrahedron. Fortunately * we can set up a system of linear equations to do this. To understand * the meaning of the equations, it's helpful to (temporarily!) install * an infinite stack of triangles at each ideal vertex, beginning near * the fat part of the ideal tetrahedron, and marching off towards * the cusp. With the infinite stack of triangles in place, we * can assign any number of squares we want to each tetrahedron without * affecting how the surface (squares and triangles together) intersects * the faces of the tetrahedra. [Oh how I wish I could draw pictures * to illustrate this. It is so simple and clear. But let's push on * in ASCII...] That is, each face of each ideal tetrahedron intersects * the surface in three infinite stacks of line segments, one stack * going towards each ideal vertex. So no matter how we assign squares * to tetrahedra, the surface will match up across the faces. * * The question, then, is how does the surface look in the neighborhood * of an edge? By "edge" I mean an edge in the ideal triangulation, * where the edges of several tetrahedra come together. Consider a * regular neighborhood of the edge; its boundary is an infinite * cylinder. Look at the paths the normal surface traces out on the * cylinder. Where the cylinder intersects a 2-cell of the ideal * triangulation, the paths may naturally be divided into two sets, * according to which stack of line segments they pass through (cf. above); * in other words, according to which ideal vertex they are near. * Triangles (of the normal surface) define arcs (of paths) which stay * near the same ideal vertex. Squares define arcs which go from being * near one ideal vertex to being near the other. If, in going once * around the cylinder, the total number of square-defined arcs going * from the "lower" ideal vertex to the "upper" ideal vertex (I'm * imagining the edge to be vertical) equals the total number going * from the "upper" to the "lower", then the paths will all be circles; * otherwise they will be a finite set of helices. The normal surface * extends nicely across the edge iff the paths are circles. * * We also want to make sure that the surface is well behaved in the * neighborhood of each cusp. That is, we want the infinite stacks * for triangles to piece together to form infinite stacks of * boundary-parallel tori and Klein bottles, not infinite surfaces * wrappping around the cusp. The picture to keep in mind is similar * to the picture for the edge neighborhoods. As you trace a meridian * or longitude around the cusp, squares will "come up from below" or * "drop down out of sight". If the total number coming up equals the * total number going down, then (all but a finite number of) the * triangles will piece together to form boundary parallel tori and * Klein bottles. To obtain the final surface, discard all the * boundary parallel tori and Klein bottles, and keep the * non-boundary-parallel piece(s) which remain(s). In practice, * of course, we don't construct infinite stacks of surfaces. * We construct the squares, and then add a minimal number of triangles * to extend the squares to the closed surface. * * The algorithm is as follows. First we use the degenerate hyperbolic * structure to decide which way to position the squares in each * tetrahedron. Then we set up and solve a system of linear, integer * equations to determine how many squares belong in each tetrahedron. * There is one variable for each tetrahedron, saying how many (parallel) * squares it contains. There is one equation for each edge, saying * that the surface passes nicely through the edge without spiraling * (cf. two paragraph back). There are two equations for each cusp * (for the meridian and longitude), saying that the surface doesn't * sprial around the cusp (cf. the preceding paragraph). For example, * for the square knot these equations are as follows. (A vector * c0 c1 c2 c3 denotes the equation c0*x0 + c1*x1 + c2*x2 + c3*x3 = 0.) * * edge equations * 0 1 -1 0 * 0 -1 1 0 * 0 -1 1 0 * 0 1 -1 0 * * cusp equations * 0 0 0 0 * 0 1 1 0 * * These equations may be simplified over the integers to * * 0 1 0 0 * 0 0 1 0 * * They say that tetrahedron #0 and tetrahedron #3 may have any * nonnegative number of squares (independently of one another), * while tetrahedron #1 and tetrahedron #2 may have no squares at all. * In other words, the equations admit two independent solutions * * (x0 x1 x2 x3) = (1 0 0 0) * (x0 x1 x2 x3) = (0 0 0 1) * * Each solution defines a surface (one is a torus following the first * trefoil summand of the square knot, and the other is a torus following * the second trefoil summand). * * m051()(1) provides a more interesting example. It's equations * simplify to * -2 0 0 1 0 * 0 1 0 -1 0 * 0 0 1 0 0 * * The first three tetrahedra define "dependent variables", whose * value are completely determined by the values of the variables * which follow it. In this case * * x0 = x3 / 2 * x1 = x3 * x2 = 0 * * The last two tetrahedra define "independent variables", whose * values may be chosen freely, subject only to the constraint that * the values which depend on them be integers. So in this case, * x3 must be even. By the way, it was accidental that in this example * the two independent variables came last. The equations could just * as well have been * -2 0 1 0 0 * 0 1 -1 0 0 * 0 0 0 1 0 * * The code in simplify_equations() shows that any set of equations * may be brought into this from. * * Definition. A "nonnegative solution" is one for which all variables * have nonnegative values. * * Definition. The "surface defined by a nonnegative solution" is * the surface obtained by (1) constructing the specified number of * squares, (2) constructing an infinite stack of triangles at each * ideal vertex of each tetrahedron, and (3) removing all boundary * parallel components of the resulting surface. * * Proposition. A surface defined by a nonnegative solution is finite. * * Proof. The edge and cusp equations guarantee the existence of * infinitely many boundary parallel tori and Klein bottles. Q.E.D. * * Comment. A surface defined by a nonnegative solution may or may * not be connected. The code below checks explicitly, and rejects * nonconnected surfaces. */ /* * Simpler proof??? (This is mainly a note to myself. * Feel free to ignore it.) * * There may be a simpler justification of the edge and cusp equations. * The basic idea is that around each edge (of the manifold's trianglation), * the number of "upward sloping square" must equal the number of * "downward sloping squares" if the number of upper and lower edges * (of squares and triangles) is to balance out. Similar considerations * apply to cusps. The details appear in the notes for my CAM3DT talk. * Richard Rannard says this approach is well-known, and called * "Q normal surface theory" ('Q' stands for "quadrilateral"). * I haven't revised the above documentation, because I still need * to think through whether it's truly obvious that after choosing * the number of squares, one can extend to a closed surface with a * finite number of triangles. (I.e. whether one can justify that * without falling back on the image of infinite stacks of triangles * at the ideal vertices.) */ /* * Closed Manifolds * * The present algorithm works only for cusped manifolds. Eventually * it may be possible to extend the algorithm to closed manifolds. * Detecting the normal surface is no problem: instead of insisting that * the equations for the meridian and longitude both be satisfied, insist * only that the equation for the Dehn filling curve be satisfied, and use * the equation for a transverse curve to count how many times the normal * surface intersects the core geodesic. The messy part is checking the * Euler characteristic, and, worse still, splitting along the surface * once we've found it. */ #include "kernel.h" #include "normal_surfaces.h" #define NO_DEFINING_ROW -1 /* * Due to the quirks of C syntax, we can't say NEW_ARRAY(n, int [4]) * directly, but we can make the following typedef and then say * NEW_ARRAY(n, ArrayInt4). */ typedef int ArrayInt4[4]; static void create_equations(Triangulation *manifold, int ***equations, int *num_equations, int *num_variables); static void simplify_equations(int **equations, int num_equations, int num_variables); static void find_defining_rows(int **equations, int num_equations, int num_variables, int **defining_row); static int count_independent_variables(int *defining_row, int num_variables); static void solve_equations(int **equations, int num_equations, int num_variables, int *defining_row, int index, int *solution); static Boolean solution_is_nonnegative(int num_variables, int *solution); static void create_squares(Triangulation *manifold, int *solution); static void create_triangles(Triangulation *manifold, int *solution); static int count_surface_edges(Tetrahedron *tet, FaceIndex f, VertexIndex v); static void copy_normal_surface(Triangulation *manifold, NormalSurface *surface); static Boolean contains_positive_Euler_characteristic(NormalSurface *normal_surface_list); static void remove_zero_Euler_characteristic(NormalSurface **normal_surface_list, int *num_surfaces); static void transfer_list_to_array(NormalSurface **temporary_linked_list, NormalSurfaceList *permanent_surface_list); static void free_equations(int **equations, int num_equations); FuncResult find_normal_surfaces( Triangulation *manifold, NormalSurfaceList **surface_list) { int **equations, num_equations, num_variables, *defining_row, num_independent_variables, loop_stopper, index, *solution; NormalSurface *normal_surface_list, *new_entry; Boolean connected, orientable, two_sided; int Euler_characteristic; /* * Allocate and initialize the NormalSurfaceList. */ *surface_list = NEW_STRUCT(NormalSurfaceList); (*surface_list)->triangulation = NULL; (*surface_list)->num_normal_surfaces = 0; (*surface_list)->list = NULL; /* * If the space isn't a manifold, or is a manifold with no cusps, * return func_bad_input. (Eventually it may be possible to * extend the algorithm to closed manifolds -- see above.) */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE || all_cusps_are_filled(manifold) == TRUE) return func_bad_input; /* * Retriangulate the manifold to removed the filled cusps, if any. */ (*surface_list)->triangulation = fill_reasonable_cusps(manifold); if ((*surface_list)->triangulation == NULL) return func_failed; /* * Number the Triangulation's Tetrahedra and EdgeClasses, * so they indices may be used to index the rows and columns * in the equation matrix. */ number_the_tetrahedra((*surface_list)->triangulation); number_the_edge_classes((*surface_list)->triangulation); /* * Carry out the algorithm described at the top of this file. * Create the equations, simplify them, and decide which variables * are defined in terms of the others. (If c is the index of a * dependent variable, then defining_row[c] is the index of the * equation which defines it in terms of the independent variables. * If c is the index of an independent variable, then defining_row[c] * is set to NO_DEFINING_ROW.) */ create_equations((*surface_list)->triangulation, &equations, &num_equations, &num_variables); simplify_equations(equations, num_equations, num_variables); find_defining_rows(equations, num_equations, num_variables, &defining_row); /* * As we find NormalSurfaces, add them to the NULL-terminated * singly linked normal_surface_list. Once we know how many * there are, we'll transfer them to an array. */ normal_surface_list = NULL; /* * How many independent variables are there? */ num_independent_variables = count_independent_variables(defining_row, num_variables); /* * We'll examine all solutions (excluding the trivial one) in which * each independent variable takes the value 0 or 1. For example, * if there are two independent variables, the potential solutions * will be parameterized as * * 0 0 <- exclude as trivial * 0 1 * 1 0 * 1 1 * * An unsigned int serves well to parameterize such solutions. * * Eventually, of course, the solutions may have to be scaled * to insure that the dependent variables take integer values. */ /* * It's almost inconceivable we'd have 32 independent variables, * but we should check just to be safe. */ if (num_independent_variables >= 8 * sizeof(int)) uFatalError("find_normal_surfaces", "normal_surface_construction"); /* * Allocate space for a solution. */ solution = NEW_ARRAY(num_variables, int); /* * Loop through the solutions, as explained above. */ loop_stopper = 1 << num_independent_variables; for (index = 1; index < loop_stopper; index++) { /* * Solve the equations to find the number of squares * assigned to each Tetrahedron. Find the smallest * solution such that independent variable c is positive, * and all other independent variables are zero. */ solve_equations(equations, num_equations, num_variables, defining_row, index, solution); /* * Ignore solutions in which one or more dependent variables * are negative. */ if (solution_is_nonnegative(num_variables, solution) == TRUE) { /* * Construct (in the Tetrahedron data structure itself) * the number of squares specified by the solution. */ create_squares((*surface_list)->triangulation, solution); /* * Construct (in the Tetrahedron data structure itself) * the minimal set of triangles required to extend * the aforementioned squares to a closed surface. * (The fact that the squares satisfy the equations * implies that such a set of triangles exists.) */ create_triangles((*surface_list)->triangulation, solution); /* * What have we got? */ recognize_embedded_surface((*surface_list)->triangulation, &connected, &orientable, &two_sided, &Euler_characteristic); /* * Keep only connected surfaces of nonnegative Euler * characteristic, because these are the only ones we * need to split along. */ if (connected == TRUE && Euler_characteristic >= 0) { new_entry = NEW_STRUCT(NormalSurface); (*surface_list)->num_normal_surfaces++; new_entry->is_connected = connected; new_entry->is_orientable = orientable; new_entry->is_two_sided = two_sided; new_entry->Euler_characteristic = Euler_characteristic; copy_normal_surface((*surface_list)->triangulation, new_entry); new_entry->next = normal_surface_list; normal_surface_list = new_entry; } } } /* * If spheres and/or projective planes were found, don't report * tori or Klein bottles, since according to the Geometrization * Conjecture we should cut along spheres and projective planes first. */ if (contains_positive_Euler_characteristic(normal_surface_list) == TRUE) remove_zero_Euler_characteristic(&normal_surface_list, &(*surface_list)->num_normal_surfaces); /* * Transfer the NormalSurfaces from the linked list to an array. */ transfer_list_to_array(&normal_surface_list, *surface_list); /* * Free local storage. */ free_equations(equations, num_equations); my_free(defining_row); my_free(solution); /* * All done! */ return func_OK; } static void create_equations( Triangulation *manifold, int ***equations, int *num_equations, int *num_variables) { int i, j; Tetrahedron *tet; ComplexWithLog *z; double min_modulus; EdgeIndex min_modulus_index; int edge_value[6], value; VertexIndex v; FaceIndex initial_side, terminal_side; PeripheralCurve c; Orientation h; /* * Set up the equations as explained in the documentation at * the top of this file. */ /* * For now let's allow a square to (potentially) intersect each ideal * tetrahedron. Eventually we may want to restrict to degenerate * tetrahedra only, to speed up the algorithm. (Actually, it seems * plenty fast as it is, and treating all tetrahedra equally keeps * the code simple.) */ *num_equations = manifold->num_tetrahedra + 2*manifold->num_cusps; *num_variables = manifold->num_tetrahedra; *equations = NEW_ARRAY(*num_equations, int *); for (i = 0; i < *num_equations; i++) (*equations)[i] = NEW_ARRAY(*num_variables, int); for (i = 0; i < *num_equations; i++) for (j = 0; j < *num_variables; j++) (*equations)[i][j] = 0; /* * If a tetrahedron is degenerate, the complex edge angles will be * approaching 0, 1 and infinity. Note which angle is approaching 0. * (If a tetrahedron is nondegenerate, then it shouldn't matter which * angle is selected, because the corresponding square cross section * will be found to have multiplicity zero in the desired surface.) */ for (tet = manifold->tet_list_begin.next, i = 0; tet != &manifold->tet_list_end; tet = tet->next, i++) { /* * The tetrahedra have already been numbered. */ if (tet->index != i) uFatalError("create_equations", "normal_surface_construction"); z = tet->shape[filled]->cwl[ultimate]; /* * min_modulus_index is the index of the edge whose complex * edge parameter is closest to zero. tet->parallel_edge * is the index of the edge whose complex edge parameter is * closest to one. It's called the "parallel edge" because * it's parallel to the square cross section. */ min_modulus_index = 0; min_modulus = z[0].log.real; for (j = 1; j < 3; j++) if (z[j].log.real < min_modulus) { min_modulus_index = j; min_modulus = z[j].log.real; } tet->parallel_edge = (min_modulus_index + 1) % 3; /* * The squares may sit in the tetrahedron in one of three positions, * according to the value of tet->parallel_edge. * * parallel_edge = 0 parallel_edge = 1 parallel_edge = 2 * * 0 0 0 * /|\ /|\ /|\ * / | \ / | \ / | \ * 3/ 5 \4 5/ 4 \3 4/ 3 \5 * /###|###\ /###|###\ /###|###\ * / ###|### \ / ###|### \ / ###|### \ * 3--###|###--2 1--###|###--3 2--###|###--1 * \ ###|### / \ ###|### / \ ###|### / * \###|###/ \###|###/ \###|###/ * 1\ | /2 2\ | /0 0\ | /1 * \ | / \ | / \ | / * \|/ \|/ \|/ * 1 2 3 * * For each position, it's easy to look at the diagram and * see for which edges the square "passes from the lower vertex * to the upper vertex", for which edges it does the opposite, * and which edges it doesn't intersect at all. For full details, * please see the documentation at the top of this file, in * particular the discussion of the paths on the cylinder. */ switch (tet->parallel_edge) { case 0: edge_value[0] = edge_value[5] = 0; edge_value[1] = edge_value[4] = -1; edge_value[2] = edge_value[3] = +1; break; case 1: edge_value[0] = edge_value[5] = +1; edge_value[1] = edge_value[4] = 0; edge_value[2] = edge_value[3] = -1; break; case 2: edge_value[0] = edge_value[5] = -1; edge_value[1] = edge_value[4] = +1; edge_value[2] = edge_value[3] = 0; break; default: uFatalError("create_equations", "normal_surface_construction"); } /* * Add this tetrahedron's contributions to the edge equations. * Note that in a nonorientable manifold, the edge class may * see some tetrahedra with reversed orientations. */ for (j = 0; j < 6; j++) (*equations)[tet->edge_class[j]->index][i] += tet->edge_orientation[j] == right_handed ? +edge_value[j] : -edge_value[j]; /* * Add this tetrahedron's contributions to the cusp equations. */ for (v = 0; v < 4; v++) for (initial_side = 0; initial_side < 4; initial_side++) { if (initial_side == v) continue; terminal_side = remaining_face[v][initial_side]; value = edge_value[edge_between_faces[initial_side][terminal_side]]; for (c = 0; c < 2; c++) /* c = M, L */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ (*equations)[manifold->num_tetrahedra + 2*tet->cusp[v]->index + c][i] += value * FLOW(tet->curve[c][h][v][initial_side], tet->curve[c][h][v][terminal_side]); } } } static void simplify_equations( int **equations, int num_equations, int num_variables) { int r, c, rr, cc, mult, *temp, g; r = 0; /* row */ c = 0; /* column */ while (r < num_equations && c < num_variables) { /* * Look for a nonzero entry at or below position (r,c). */ for (rr = r; rr < num_equations; rr++) if (equations[rr][c] != 0) break; /* * If no nonzero entry is found, move one space to the right * and continue. */ if (rr == num_equations) { c++; continue; } /* * Swap rows r and rr, so that the new entry (r,c) is nonzero. */ temp = equations[r]; equations[r] = equations[rr]; equations[rr] = temp; /* * Do row operations so that * (1) entry (r,c) remains nonzero, and * (2) all entries below it (i.e. (rr,c) for rr > r) are zero. */ rr = r + 1; while (rr < num_equations) { if (equations[rr][c] != 0) { mult = equations[rr][c] / equations[r][c]; for (cc = c; cc < num_variables; cc++) equations[rr][cc] -= mult * equations[r][cc]; if (equations[rr][c] != 0) { temp = equations[r]; equations[r] = equations[rr]; equations[rr] = temp; } else rr++; } else rr++; } /* * Move one space down and one space to the right, and continue. */ r++; c++; } /* * Examine each row, starting at the bottom and working * our way up. */ for (r = num_equations; --r >= 0; ) { /* * Find the first nonzero entry in row r, if any. */ for (c = 0; c < num_variables; c++) if (equations[r][c] != 0) break; /* * If no nonzero entry was found, ignore this row. */ if (c == num_variables) continue; /* * Divide this row by the gcd of its entries. */ g = ABS(equations[r][c]); for (cc = c + 1; cc < num_variables; cc++) g = gcd(g, equations[r][cc]); for (cc = c; cc < num_variables; cc++) equations[r][cc] /= g; /* * Clear out all entries in column c, above row r. * (The entries below row r are already zero.) */ for (rr = r; --rr >= 0; ) { /* * If equations[rr][c] is already zero, * there is no work to be done. */ if (equations[rr][c] == 0) continue; /* * Multiply row rr through by a constant, if necessary, * to ensure that equations[r][c] divides equations[rr][c]. */ mult = equations[r][c] / gcd(equations[r][c], equations[rr][c]); if (mult != 1 && mult != -1) for (cc = 0; cc < num_variables; cc++) equations[rr][cc] *= mult; /* * Add a multiple of row r to row rr to create a zero * in position (rr,c). */ mult = equations[rr][c] / equations[r][c]; for (cc = c; cc < num_variables; cc++) equations[rr][cc] -= mult * equations[r][cc]; } } } static void find_defining_rows( int **equations, int num_equations, int num_variables, int **defining_row) { int r, c; *defining_row = NEW_ARRAY(num_variables, int); for (c = 0; c < num_variables; c++) (*defining_row)[c] = NO_DEFINING_ROW; for (r = 0; r < num_equations; r++) for (c = 0; c < num_variables; c++) if (equations[r][c] != 0) { (*defining_row)[c] = r; break; } } static int count_independent_variables( int *defining_row, int num_variables) { int c, count; count = 0; for (c = 0; c < num_variables; c++) if (defining_row[c] == NO_DEFINING_ROW) count++; return count; } static void solve_equations( int **equations, int num_equations, int num_variables, int *defining_row, int index, int *solution) /* space should already be allocated */ { int r, c, cc, numerator, denominator, mult; /* * Find a solution in which the independent variables are * or are not zero, as specified by the index (please see * find_normal_surfaces() for an explanation of the index). * If possible, each nonzero independent variable will be * assigned the value 1, but sometimes larger values will * be assigned so that the dependent variables are integers. * * Before trying to understand how the equations are being solved, * you might want to review simplify_equations() above to see the * form the equations have been put in. A typical set of equations * might look like * -2 0 1 0 1 * 0 1 -1 0 0 * 0 0 0 1 -2 * (but with many more rows and columns, of course). In this example * columns 0, 1 and 3 belong to the dependent variables, while * columns 2 and 4 belong to the independent variables. */ /* * Assign a value to each variable, starting with the last * one and working our way back. */ for (c = num_variables; --c >= 0; ) { /* * Is the variable c dependent or independent? */ if (defining_row[c] == NO_DEFINING_ROW) { /* * The variable c is independent. * Assign a 1 or a 0, as specified by the index. */ solution[c] = (index & 1); index >>= 1; } else { /* * The variable c is dependent. * * Use the defining row to deduce the value of the variable c * in terms of variables which have already been assigned. * If equations[r][c] has absolute value greater than one, * it may be necessary to multiply the existing partial * solution by some integer > 1 so that the value of the * new variable is an integer. The value of the new variable * could be negative; but we'll let the calling routine * worry about that. */ r = defining_row[c]; numerator = 0; for (cc = c + 1; cc < num_variables; cc++) numerator -= equations[r][cc] * solution[cc]; denominator = equations[r][c]; if (numerator % denominator != 0) { mult = ABS(denominator) / gcd(numerator, denominator); for (cc = c + 1; cc < num_variables; cc++) solution[cc] *= mult; numerator *= mult; } solution[c] = numerator / denominator; } } } static Boolean solution_is_nonnegative( int num_variables, int *solution) { int c; for (c = 0; c < num_variables; c++) if (solution[c] < 0) return FALSE; return TRUE; } static void create_squares( Triangulation *manifold, int *solution) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->num_squares = solution[tet->index]; } static void create_triangles( Triangulation *manifold, int *solution) { /* * The documentation at the top of this file proves that once * we have a set of squares satisfying the equations, we may * add a finite set of triangles to extend the squares to a * closed surface. * * If we wanted, we could write a mathematically sophisticated * algorithm which started at one ideal vertex of one ideal * tetrahedron, assumed that vertex had 'n' triangles, and * recursively examined neighboring ideal vertices deducing how * many triangles they must have (e.g. n+1, n-2, etc.) until it * examined all ideal vertices incident to a given cusp, at which * point it would choose the smallest value of n which makes the * number of triangles nonnegative at all ideal vertices incident * to that cusp. It would then repeat the whole procedure for * each remaining cusp. * * Such an algorithm would be a nuisance to code up. Instead we'll * use a more simple-minded algorithm. Just keep scanning down * the list of tetrahedra, and whenever the number of edges * (of squares and triangles combined) on a given face of a given * ideal tetrahedron near a given ideal vertex exceeds the number * on the face it's glued to, add triangles to make up the difference. * (Not only is this simple-minded algorithm easier to code, * but for simple manifolds is might be quicker at run time as well.) */ Boolean progress; Tetrahedron *tet, *nbr; FaceIndex f, ff; VertexIndex v, vv; Permutation gluing; int our_edges, nbr_edges; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->num_triangles[v] = 0; do { progress = FALSE; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { nbr = tet->neighbor[f]; gluing = tet->gluing[f]; ff = EVALUATE(tet->gluing[f], f); for (v = 0; v < 4; v++) { if (f == v) continue; vv = EVALUATE(gluing, v); our_edges = count_surface_edges(tet, f, v ); nbr_edges = count_surface_edges(nbr, ff, vv); if (our_edges > nbr_edges) { nbr->num_triangles[vv] += our_edges - nbr_edges; progress = TRUE; } if (nbr_edges > our_edges) { tet->num_triangles[v ] += nbr_edges - our_edges; progress = TRUE; } } } } while (progress == TRUE); } static int count_surface_edges( Tetrahedron *tet, FaceIndex f, VertexIndex v) { int num_edge_segments; num_edge_segments = 0; if (edge3_between_faces[f][v] == tet->parallel_edge) num_edge_segments += tet->num_squares; num_edge_segments += tet->num_triangles[v]; return num_edge_segments; } static void copy_normal_surface( Triangulation *manifold, NormalSurface *surface) { Tetrahedron *tet; VertexIndex v; surface->parallel_edge = NEW_ARRAY(manifold->num_tetrahedra, EdgeIndex); surface->num_squares = NEW_ARRAY(manifold->num_tetrahedra, int); surface->num_triangles = NEW_ARRAY(manifold->num_tetrahedra, ArrayInt4); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { surface->parallel_edge[tet->index] = tet->parallel_edge; surface->num_squares[tet->index] = tet->num_squares; for (v = 0; v < 4; v++) surface->num_triangles[tet->index][v] = tet->num_triangles[v]; } } static Boolean contains_positive_Euler_characteristic( NormalSurface *normal_surface_list) { Boolean positive_value_found; NormalSurface *surface; positive_value_found = FALSE; for (surface = normal_surface_list; surface != NULL; surface = surface->next) if (surface->Euler_characteristic > 0) positive_value_found = TRUE; return positive_value_found; } static void remove_zero_Euler_characteristic( NormalSurface **normal_surface_list, int *num_surfaces) { NormalSurface **surface_ptr, *dead_surface; surface_ptr = normal_surface_list; while (*surface_ptr != NULL) { if ((*surface_ptr)->Euler_characteristic != 0) surface_ptr = &(*surface_ptr)->next; else { dead_surface = *surface_ptr; *surface_ptr = (*surface_ptr)->next; my_free(dead_surface->parallel_edge); my_free(dead_surface->num_squares); my_free(dead_surface->num_triangles); my_free(dead_surface); (*num_surfaces)--; } } } static void transfer_list_to_array( NormalSurface **temporary_linked_list, NormalSurfaceList *permanent_surface_list) { int count; NormalSurface *the_surface; permanent_surface_list->list = NEW_ARRAY(permanent_surface_list->num_normal_surfaces, NormalSurface); count = 0; while (*temporary_linked_list != NULL) { the_surface = *temporary_linked_list; *temporary_linked_list = (*temporary_linked_list)->next; permanent_surface_list->list[count] = *the_surface; permanent_surface_list->list[count].next = NULL; count++; my_free(the_surface); } if (count != permanent_surface_list->num_normal_surfaces) uFatalError("transfer_list_to_array", "normal_surface_construction"); } static void free_equations( int **equations, int num_equations) { int i; for (i = 0; i < num_equations; i++) my_free(equations[i]); my_free(equations); } int number_of_normal_surfaces_on_list( NormalSurfaceList *surface_list) { return surface_list->num_normal_surfaces; } Boolean normal_surface_is_orientable( NormalSurfaceList *surface_list, int index) { return surface_list->list[index].is_orientable; } Boolean normal_surface_is_two_sided( NormalSurfaceList *surface_list, int index) { return surface_list->list[index].is_two_sided; } int normal_surface_Euler_characteristic( NormalSurfaceList *surface_list, int index) { return surface_list->list[index].Euler_characteristic; } void free_normal_surfaces( NormalSurfaceList *surface_list) { int i; if (surface_list != NULL) { if (surface_list->triangulation != NULL) free_triangulation(surface_list->triangulation); for (i = 0; i < surface_list->num_normal_surfaces; i++) { my_free(surface_list->list[i].parallel_edge); my_free(surface_list->list[i].num_squares); my_free(surface_list->list[i].num_triangles); } if (surface_list->list != NULL) my_free(surface_list->list); my_free(surface_list); } } snappea-3.0d3/SnapPeaKernel/code/normal_surface_recognition.c0100444000175000017500000003342007204003021022417 0ustar babbab/* * normal_surface_recognition.c * * The function * * void recognize_embedded_surface( * Triangulation *manifold, * Boolean *connected, * Boolean *orientable, * Boolean *two_sided, * int *Euler_characteristic); * * reports the connectedness, orientability, two-sidedness and Euler * characteristic of the normal surface described in the parallel_edge, * num_squares and num_triangles fields of the manifold's Tetrahedra. * The present implementation assumes the manifold has no filled cusps. */ #include "kernel.h" #include "normal_surfaces.h" typedef struct { /* * The "positive" normal vector to a square points in the direction of * tet->parallel_edge (which has EdgeIndex 0, 1 or 2) and away from * the opposite edge (which has EdgeIndex 5, 4 or 3, respectively). * The "positive" normal vector to a triangle points in the direction * of the associated ideal vertex. * * The algorithm for testing two-sidedness attempts to make a globally * consistent choice of normal vectors across the whole surface. */ Boolean positive_normal; /* * A Tetrahedron's right_handed Orientation lets us extend the above * definition of a positive normal vector to a definition of a positive * orientation on each square and triangle. It doesn't matter whether * you imagine using a right-hand rule or a left-hand rule, just so * you're consistent. * * The algorithm for testing orientability attempts to make a globally * consistent choice of orientation across the whole surface. */ Boolean positive_orientation; /* * Has the recursive algorithm visited this EmbeddedPolygon? */ Boolean visited; } EmbeddedPolygon; /* * Each Tetrahedron will need one array of squares, and four arrays * of triangles, one for each cusp. */ typedef struct { EmbeddedPolygon *squares, *triangles[4]; } PolygonsInTetrahedron; /* * The algorithm for determining connectedness, orientability and * two-sidedness keeps references to squares and triangles on a * NULL-terminated, singly linked list. */ typedef int EmbeddedPolygonType; enum { embedded_square, embedded_triangle }; typedef struct ListNode { /* * Which Tetrahedron is the polygon in? */ Tetrahedron *tet; /* * Is the polygon an embedded_square or an embedded_triangle? */ EmbeddedPolygonType type; /* * If the polygon is an embedded_triangle, which ideal vertex is it at? */ VertexIndex v; /* * Parallel copies of a square or triangle are indexed in the * direction opposite the normal vector defined above. For example, * the triangle closest to the ideal vertex has index 0, the next * closest one has index 1, etc. Similarly, the square closest * tet->parallel_edge has index 0, the next closest one has index 1, etc. */ int index; /* * The next ListNode on the NULL-terminated, singly linked list. */ struct ListNode *next; } ListNode; static void connected_orientable_twosided(Triangulation *manifold, Boolean *connected, Boolean *orientable, Boolean *two_sided); static int Euler_characteristic_of_embedded_surface(Triangulation *manifold); void recognize_embedded_surface( Triangulation *manifold, Boolean *connected, Boolean *orientable, Boolean *two_sided, int *Euler_characteristic) { /* * The present version of the software assumes all cusps are complete. */ if (all_cusps_are_complete(manifold) == FALSE) uFatalError("recognize_embedded_surface", "normal_surface_recognition"); /* * Compute the connectedness, orientability, two-sidedness and * Euler characteristic. */ connected_orientable_twosided(manifold, connected, orientable, two_sided); *Euler_characteristic = Euler_characteristic_of_embedded_surface(manifold); /* * In an orientable 3-manifold, a surface is orientable iff it's 2-sided. */ if (manifold->orientability == oriented_manifold && *orientable != *two_sided) uFatalError("recognize_embedded_surface", "normal_surface_recognition"); /* * An embedded sphere must be 2-sided. */ if (*connected == TRUE && *Euler_characteristic == 2 && *two_sided == FALSE) uFatalError("recognize_embedded_surface", "normal_surface_recognition"); /* * Orientable surfaces have even Euler characteristic. */ if (*orientable == TRUE && (*Euler_characteristic)%2 != 0) uFatalError("recognize_embedded_surface", "normal_surface_recognition"); } static void connected_orientable_twosided( Triangulation *manifold, Boolean *connected, Boolean *orientable, Boolean *two_sided) { PolygonsInTetrahedron *model; Tetrahedron *tet, *nbr; Permutation gluing; VertexIndex v, nbr_v; FaceIndex f, nbr_f; int i, index, nbr_index; ListNode *list, *node, *new_node; Boolean positive_normal, positive_orientation; EmbeddedPolygonType nbr_type; EmbeddedPolygon *data, *nbr_data; /* * Make an explicit model of the surface using EmbeddedPolygon structures. */ model = NEW_ARRAY(manifold->num_tetrahedra, PolygonsInTetrahedron); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * NEW_ARRAY uses my_malloc(), which gracefully handles requests * for zero bytes when num_squares or num_triangles is zero. */ model[tet->index].squares = NEW_ARRAY(tet->num_squares, EmbeddedPolygon); for (i = 0; i < tet->num_squares; i++) model[tet->index].squares[i].visited = FALSE; for (v = 0; v < 4; v++) { model[tet->index].triangles[v] = NEW_ARRAY(tet->num_triangles[v], EmbeddedPolygon); for (i = 0; i < tet->num_triangles[v]; i++) model[tet->index].triangles[v][i].visited = FALSE; } } /* * Initialize the linked list to be empty. */ list = NULL; /* * Find an arbitrary embedded square. The calling routine won't * create empty or boundary-parallel surfaces, so a square must exist. * "Visit" the square and set its normal vector and orientation * to be positive. Put a reference to the square onto the linked list. * The linked list will hold squares and triangles which have been * visited, but whose neighbors have not yet been visited. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->num_squares != 0) { model[tet->index].squares[0].positive_normal = TRUE; model[tet->index].squares[0].positive_orientation = TRUE; model[tet->index].squares[0].visited = TRUE; node = NEW_STRUCT(ListNode); node->tet = tet; node->type = embedded_square; node->v = -1; /* unused for a square */ node->index = 0; node->next = list; list = node; break; } if (list == NULL) uFatalError("connected_orientable_twosided", "normal_surface_recognition"); /* * Tentatively assume the surface is orientable and two-sided. * If the recursion below discovers that it cannot consistently * assign an orientation or normal vector, it will set the * corresponding variable to FALSE. */ *orientable = TRUE; *two_sided = TRUE; /* * As stated above, the linked list holds squares and triangles which * have been visited, but whose neighbors have not yet been visited. */ while (list != NULL) { /* * Pull the first node off the list. */ node = list; list = list->next; /* * The node defines a square or triangle in the embedded surface. * Look at each of its neighbors. */ tet = node->tet; for (f = 0; f < 4; f++) { /* * A square connects to all four of the tetrahedron's neighbors. * A triangle connects to only three of them. */ if (node->type == embedded_triangle && node->v == f) continue; /* * What vertex (of the tetrahedron) are we at, * and what's the index of the sheet we're on? */ switch (node->type) { case embedded_square: if (f == one_vertex_at_edge[tet->parallel_edge]) { v = other_vertex_at_edge[tet->parallel_edge]; index = node->index + tet->num_triangles[v]; } if (f == other_vertex_at_edge[tet->parallel_edge]) { v = one_vertex_at_edge[tet->parallel_edge]; index = node->index + tet->num_triangles[v]; } if (f == one_vertex_at_edge[5 - tet->parallel_edge]) { v = other_vertex_at_edge[5 - tet->parallel_edge]; index = ((tet->num_squares - 1) - node->index) + tet->num_triangles[v]; } if (f == other_vertex_at_edge[5 - tet->parallel_edge]) { v = one_vertex_at_edge[5 - tet->parallel_edge]; index = ((tet->num_squares - 1) - node->index) + tet->num_triangles[v]; } break; case embedded_triangle: v = node->v; index = node->index; break; default: uFatalError("connected_orientable_twosided", "normal_surface_recognition"); } /* * What normal vector and orientation are we passing * to the neighbor? Usually it will just be our own * normal vector and orientation, but in the case of * an "upside down" square we have to reverse them. */ if (node->type == embedded_square) data = &model[node->tet->index].squares[node->index]; else data = &model[node->tet->index].triangles[node->v][node->index]; positive_normal = data->positive_normal; positive_orientation = data->positive_orientation; if (data->visited != TRUE) uFatalError("connected_orientable_twosided", "normal_surface_recognition"); if (node->type == embedded_square && edge_between_vertices[v][f] != tet->parallel_edge) { positive_normal = ! positive_normal; positive_orientation = ! positive_orientation; } /* * Find our neighbor. */ nbr = tet->neighbor[f]; gluing = tet->gluing[f]; nbr_f = EVALUATE(gluing, f); nbr_v = EVALUATE(gluing, v); /* * If the gluing is orientation_reversing, then * what the old tetrahedron saw as right-handed, * the neighbor will see as left-handed, and vice-versa. * They'll agree on normal vectors, though. */ if (parity[gluing] == orientation_reversing) positive_orientation = ! positive_orientation; /* * Find the square or triangle we're connecting to. */ if (index < nbr->num_triangles[nbr_v]) { nbr_type = embedded_triangle; nbr_index = index; nbr_data = &model[nbr->index].triangles[nbr_v][nbr_index]; } else { if (edge3_between_vertices[nbr_f][nbr_v] != nbr->parallel_edge || index >= nbr->num_triangles[nbr_v] + nbr->num_squares) uFatalError("connected_orientable_twosided", "normal_surface_recognition"); nbr_type = embedded_square; nbr_index = index - nbr->num_triangles[nbr_v]; /* * If the square is "upside down", adjust the index, * orientation and normal vector. */ if (edge_between_vertices[nbr_f][nbr_v] != nbr->parallel_edge) { nbr_index = (nbr->num_squares - 1) - nbr_index; positive_normal = ! positive_normal; positive_orientation = ! positive_orientation; } nbr_data = &model[nbr->index].squares[nbr_index]; } /* * Has the newly found square or triangle already been visited? * If it has, check whether its orientation and normal vector * agree with the ones we're passing. If not, assign the * orientation and normal vectors, and add it to the linked list. */ if (nbr_data->visited == TRUE) { if (nbr_data->positive_normal != positive_normal) *two_sided = FALSE; if (nbr_data->positive_orientation != positive_orientation) *orientable = FALSE; } else { nbr_data->positive_normal = positive_normal; nbr_data->positive_orientation = positive_orientation; nbr_data->visited = TRUE; new_node = NEW_STRUCT(ListNode); new_node->tet = nbr; new_node->type = nbr_type; new_node->v = (nbr_type == embedded_triangle) ? nbr_v : -1; new_node->index = nbr_index; new_node->next = list; list = new_node; } } /* * Free the node, and continue with the loop. */ my_free(node); } /* * The embedded surface is connected iff we visited all its polygons. */ *connected = TRUE; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { for (i = 0; i < tet->num_squares; i++) if (model[tet->index].squares[i].visited == FALSE) *connected = FALSE; for (v = 0; v < 4; v++) for (i = 0; i < tet->num_triangles[v]; i++) if (model[tet->index].triangles[v][i].visited == FALSE) *connected = FALSE; } /* * Free the memory used to hold the EmbeddedPolygons. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { my_free(model[tet->index].squares); for (v = 0; v < 4; v++) my_free(model[tet->index].triangles[v]); } my_free(model); } static int Euler_characteristic_of_embedded_surface( Triangulation *manifold) { int num_vertices, num_edges, num_faces; EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; VertexIndex v; int total_squares, total_triangles; /* * Count the vertices. */ num_vertices = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { tet = edge->incident_tet; e = edge->incident_edge_index; if (edge3[e] != tet->parallel_edge) num_vertices += tet->num_squares; num_vertices += tet->num_triangles[one_vertex_at_edge[e]]; num_vertices += tet->num_triangles[other_vertex_at_edge[e]]; } /* * Count the edges and faces. */ total_squares = 0; total_triangles = 0; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { total_squares += tet->num_squares; for (v = 0; v < 4; v++) total_triangles += tet->num_triangles[v]; } num_edges = (4*total_squares + 3*total_triangles) / 2; num_faces = total_squares + total_triangles; /* * Return the Euler characteristic. */ return num_vertices - num_edges + num_faces; } snappea-3.0d3/SnapPeaKernel/code/normal_surface_splitting.c0100444000175000017500000015342506742675502022155 0ustar babbab/* * normal_surface_splitting.c * * This file contains the function * * FuncResult split_along_normal_surface( * NormalSurfaceList *surface_list, * int index, * Triangulation *pieces[2]); * * which splits the surface_list->triangulation along the normal surface * of the given index. All of the surface_list->triangulation's cusps * must be complete (no Dehn fillings) and the normal surface must be a * connected surface of nonnegative Euler characteristic. If the normal * surface is a 2-sided projective plane, split_along_normal_surface() * returns func_bad_input; otherwise it returns func_OK. If the normal * surface is a sphere or 1-sided projective plane, the resulting * spherical boundary component(s) are capped off with 3-ball(s); * otherwise the new torus or Klein bottle boundary component(s) become * cusp(s). If the normal surface is nonseparating, the result is * returned in pieces[0], and pieces[1] is set to NULL. If the normal * surface is separating, the two pieces are returned in pieces[0] and * pieces[1]. The original surface_list->triangulation is left unchanged, * except for the fact that the normal surface data is copied into the * parallel_edge, num_squares and num_triangles fields of its Tetrahedra. */ /* * The Algorithm * * To understand this documention, you should first read normal_surfaces.h * to learn what a normal surface is, and how its squares and triangles * sit in a tetrahedron. Then you should make yourself a drawing of an * ideal tetrahedron with a couple squares across its middle and a * triangle or two at each ideal vertex. Draw the squares in red and * the triangles in blue, to highlight the differences between the pieces * they bound. * * The red squares and blue triangles cut the tetrahedron into pieces. * Keeping in mind that the red squares and blue triangles will be * "pulled to infinity" to become ideal vertices, the pieces may be * classified as follows. * * tetrahedra * If there are no red squares cutting across the ideal tetrahedron, * then the central piece will itself be an ideal tetrahedron. * * pillows * A piece incident to a red square and two blue triangles has * four faces: two ideal triangles and two "bigons". We call * this piece a pillow. (It would be a true triangular pillow * if each "bigonal" face were collapsed to a single edge.) * * square prisms * A piece incident to two red squares is a square prism. * (When the two red squares are pulled to infinity this piece * will become long and skinny, but don't let that distract you.) * * triangular prisms * A piece incident to two blue triangles is a triangular prism. * (When the two blue triangles are pulled to infinity this piece * will become long and skinny, but don't let that distract you.) * * In the remainder of this explanation, "manifold" will refer to the * manifold obtained by splitting the original manifold along the given * normal surface. The manifold will have either one or two connected * components, according to whether the normal surface was separating. * * Initially the manifold has the cell division consisting of the four * types of pieces described above. We will subdivide it into tetrahedra. * The subdivision will introduce finite (non-ideal) vertices, so the * tetrahedra will be "hybrids", with some ideal vertices and some finite * vertices. * * subdividing the 1-skeleton * Introduce a finite vertex in the interior of each line in * the 1-skeleton. You should draw the finite vertex at the * midpoint of each line in your drawing, even though * the concept of "midpoint" has no intrinsic meaning in an * infinite line which runs from one ideal vertex to another. * * subdividing the 2-skeleton * Subdivide each bigon in the 2-skeleton by introducing a line * segment connecting the finite vertices at the "midpoints" * of its two edges. Subdivide each triangle in the 2-skeleton * by introducing three line segements connecting the finite * vertices at the "midpoints" of the triangle's edges. * * subdividing the 3-skeleton * Each type of piece (triangular prism, square prism, pillow * and tetrahedron) is subdivided differently. The details are * explained in the code itself. The important points are that * each piece is subdivided into tetrahedra, and the subdivision * is consistent with the subdivision of the 2-skeleton described * above. At least one tetrahedron in each piece is guaranteed * to have the correct orientation; split_along_normal_surface() * eventually extends that orientation to the whole connected * component of the triangulation. (In case you're wondering why * all tetrahedra aren't given the correct orientation right way, * the reason is that it would require a more complicated and less * natural indexing system.) * * We'll define the indexing system (i.e. the assignment of VertexIndices * to the vertices in the subdivision) only on the boundary of each * (original) tetrahedron. The SubdivisionData structure will assign to * each triangle (in the subdivision of each face of each original * tetrahedron) a pointer to the tetrahedron (in the subdivision of the * original Tetrahedron's interior) which is borders, as well as a * permutation mapping the boundary triangle's VertexIndices (defined * in a canonical way in the following paragraph) to the tetrahedron's * more or less arbitrary VertexIndices. * * You'll want to make yourself a drawing as we go along. First draw * an ideal triangle representing a face of one of the original * tetrahedra. Label its vertices 'a', 'b' and 'c'. (These are the * VertexIndices which come with the original Tetrahedron; they take * values in {0,1,2,3}.) Draw a couple red lines showing where the * red squares meet this face, and draw blue lines showing where the * blue triangles meet it. Then, in black, draw the subdivision of * the 2-skeleton as defined above in the section "subdividing the * 2-skeleton". Each ideal vertex in the subdivision (remember, ideal * vertices include the red and blue line segments, which will eventually * be "pulled to infinity") gets the VertexIndex of the nearest ideal * vertex of the large triangle. Each finite vertex (each black dot * along an edge of the large triangle) gets the VertexIndex of the * vertex of the large triangle opposite the edge the black dot's on. * Note that each finite vertex is incident to two faces of the original * tetrahedron, and it gets assigned a different VertexIndex on each; * this inconsistency is harmless. */ /* * Note: In a previous attempt at a splitting algorithm, I had hoped * to let the square and triangular prisms become lines, let the pillows * become triangles, and use the tetrahedra to triangulate the resulting * manifold(s). For an explanation of why this works for complicated * manifolds but fails for simple ones, please see * normal_surface_splitting.old.c. */ #include "kernel.h" #include "normal_surfaces.h" /* * A TetReference says which (new, small) Tetrahedron borders each triangle * in the subdivision of a boundary face of the (original, large) Tetrahedron. */ typedef struct { /* * Which Tetrahedron do we see? */ Tetrahedron *tet; /* * How do the canonical VertexIndices of the subdivision (defined above) * map to the actual vertices of the tet? */ Permutation gluing; } TetReference; typedef struct { /* * A bigon (on a face of an original large tetrahedron) get subdivided * into a pair of semi-ideal triangles. The triangles closer to the * cusp is called the "outer" triangle, and the other one is called * the "inner" triangles. */ TetReference outer, inner; } TetReferencePair; /* * Each (original, large) Tetrahedron has lots of (new, small) triangles * on its boundary. The SubdivisionData structure organizes the * TetReferences assigned to them. */ typedef struct { /* * central[f][f] holds the TetReference for the central triangle * on face f. central[f][v] holds the TetReference for the the * triangle bordering the central triangle on the side closest * to vertex v of the original large triangle. */ TetReference central[4][4]; /* * At each ideal vertex of each original large face, there may be * any number of bigons (each divided into two semi-ideal triangles). * side_array_length[f][v] tells how many such bigons there are at * ideal vertex v of face f, and side_array[f][v] is an array of * TetReferencePairs for the new, small Tetrahedra they see. * (side_array_length[f][f] and side_array[f][f] are unused.) */ int side_array_length[4][4]; TetReferencePair *side_array[4][4]; } SubdivisionData; /* * The IdealVertexReference structure is used only in distinguish_cusps(). */ typedef struct { Tetrahedron *tet; VertexIndex v; } IdealVertexReference; static Boolean is_two_sided_projective_plane(NormalSurfaceList *surface_list, int index); static void install_normal_surface(NormalSurfaceList *surface_list, int index); static Triangulation *subdivide_manifold(Triangulation *manifold, Boolean is_two_sided, int Euler_characteristic); static SubdivisionData *allocate_subdivision_data(Triangulation *manifold); static void free_subdivision_data(SubdivisionData *data, int num_old_tetrahedra); static void copy_cusps(Triangulation *manifold, Triangulation *subdivision); static void subdivide_old_tetrahedron(Tetrahedron *old_tet, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split); static void subdivide_triangular_prism(Tetrahedron *old_tet, VertexIndex old_v, int index, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split); static void subdivide_central_tetrahedron(Tetrahedron *old_tet, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split); static void subdivide_pillow(Tetrahedron *old_tet, EdgeIndex defining_edge, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split); static void subdivide_square_prism(Tetrahedron *old_tet, int index, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split); static void glue_external_faces(Triangulation *manifold, SubdivisionData *data); static Permutation compute_external_gluing(Permutation perm0_inverse, Permutation perm1, Permutation perm2); static void distinguish_cusps(Triangulation *subdivision, Cusp *new_cusps[2]); static void separate_connected_components(Triangulation *subdivision, Triangulation *pieces[2]); static Tetrahedron *find_correctly_oriented_tet(Triangulation *manifold); FuncResult split_along_normal_surface( NormalSurfaceList *surface_list, int index, Triangulation *pieces[2]) { Triangulation *subdivision; int i; /* * Dispose of a rare special case, before getting on to the * main algorithm. * * The correct way to handle a 2-sided projective plane would be * to split along it and cap off each boundary surface by coning * to a point, thereby producing an orbifold with two singular points. * SnapPea isn't prepared to do this. */ if (is_two_sided_projective_plane(surface_list, index) == TRUE) { uAcknowledge("Can't cut along 2-sided projective planes."); pieces[0] = NULL; pieces[1] = NULL; return func_bad_input; } /* * Copy the requested normal surface into surface_list->triangulation. */ install_normal_surface(surface_list, index); /* * The present version of the software assumes all cusps are complete. */ if (all_cusps_are_complete(surface_list->triangulation) == FALSE) uFatalError("split_along_normal_surface", "normal_surface_splitting"); /* * Subdivide the manifold. The result may or may not be connected. * subdivide_manifold() creates Tetrahedra and real Cusps, * but not EdgeClasses or fake Cusps ("fake Cusps" are Cusp structures * for finite vertices). */ subdivision = subdivide_manifold( surface_list->triangulation, surface_list->list[index].is_two_sided, surface_list->list[index].Euler_characteristic); /* * Separate the subdivision into its connected components. * If the subdivision is connected, pieces[1] will be set to NULL. */ separate_connected_components(subdivision, pieces); /* * Spruce up the two pieces. */ for (i = 0; i < 2; i++) if (pieces[i] != NULL) { /* * The subdivision algorithm promises to provide the correct * orientation for at least one tetrahedron in each piece. * Extend this orientation to all of pieces[i]. */ extend_orientation(pieces[i], find_correctly_oriented_tet(pieces[i])); /* * Install "fake cusps" for the finite vertices. */ create_fake_cusps(pieces[i]); /* * Install and orient the EdgeClasses. */ create_edge_classes(pieces[i]); orient_edge_classes(pieces[i]); /* * Retriangulate with no finite vertices. * * Note: We haven't set the cusp topologies or num_or_cusps * and num_nonor_cusps, but remove_finite_vertices() * doesn't care. * * Note: If pieces[i] is a closed manifold, * remove_finite_vertices() will drill out an arbitrary cusp. */ remove_finite_vertices(pieces[i]); /* * Install peripheral curves only for those cusps which * don't already have them. For cusps which have them, * keep the originals. */ peripheral_curves_as_needed(pieces[i]); count_cusps(pieces[i]); /* * The splitting may have turned a nonorientable manifold * into one or more orientable pieces, in which case * some of the original {meridian, longitude} pairs might * fail to obey the right-hand rule. */ if (pieces[i]->orientability == oriented_manifold) fix_peripheral_orientations(pieces[i]); /* * Find the hyperbolic structure. */ find_complete_hyperbolic_structure(pieces[i]); } /* * Free the subdivision, which has no Tetrahedra or Cusps left anyhow. */ free_triangulation(subdivision); /* * All done! */ return func_OK; } static Boolean is_two_sided_projective_plane( NormalSurfaceList *surface_list, int index) { return surface_list->list[index].is_connected == TRUE && surface_list->list[index].is_two_sided == TRUE && surface_list->list[index].Euler_characteristic == 1; } static void install_normal_surface( NormalSurfaceList *surface_list, int index) { Tetrahedron *old_tet; VertexIndex v; if (index < 0 || index >= surface_list->num_normal_surfaces) uFatalError("install_normal_surface", "normal_surface_splitting"); for (old_tet = surface_list->triangulation->tet_list_begin.next; old_tet != &surface_list->triangulation->tet_list_end; old_tet = old_tet->next) { old_tet->parallel_edge = surface_list->list[index].parallel_edge[old_tet->index]; old_tet->num_squares = surface_list->list[index].num_squares [old_tet->index]; for (v = 0; v < 4; v++) old_tet->num_triangles[v] = surface_list->list[index].num_triangles[old_tet->index][v]; } } static Triangulation *subdivide_manifold( Triangulation *manifold, Boolean is_two_sided, int Euler_characteristic) { Triangulation *subdivision; Cusp *new_cusps[2]; SubdivisionData *data; Tetrahedron *old_tet; /* * Create a Triangulation structure to hold the new Tetrahedra. */ subdivision = NEW_STRUCT(Triangulation); initialize_triangulation(subdivision); /* * Create copies of the old Cusps, for use in the subdivision. * Each old Cusp's matching_cusp field is set to point to its * corresponding new Cusp in the subdivision. */ copy_cusps(manifold, subdivision); /* * Allocate new Cusps as necessary. */ switch (Euler_characteristic) { case 2: /* * We're cutting along a sphere, so treat the boundary * as a finite vertex (to automatically fill it in). */ new_cusps[0] = NULL; new_cusps[1] = NULL; break; case 1: /* * We're cutting along a projective plane. If it's 1-sided * we'll get a spherical boundary component which should * be filled as in the spherical case immediately above. * If it's 2-sided, we're not prepared to handle it. */ if (is_two_sided == FALSE) { new_cusps[0] = NULL; new_cusps[1] = NULL; } else uFatalError("subdivide_manifold", "normal_surface_splitting"); break; case 0: /* * We're cutting along a torus or Klein bottle. * Allocate one or two cusps as necessary. */ new_cusps[0] = NEW_STRUCT(Cusp); initialize_cusp(new_cusps[0]); INSERT_BEFORE(new_cusps[0], &subdivision->cusp_list_end); new_cusps[0]->index = subdivision->num_cusps++; if (is_two_sided == TRUE) { new_cusps[1] = NEW_STRUCT(Cusp); initialize_cusp(new_cusps[1]); INSERT_BEFORE(new_cusps[1], &subdivision->cusp_list_end); new_cusps[1]->index = subdivision->num_cusps++; } else new_cusps[1] = NULL; break; default: uFatalError("subdivide_manifold", "normal_surface_splitting"); } /* * Allocate an array of SubdivisionData structures, one structure * for each old Tetrahedron in the original unsplit manifold. */ data = allocate_subdivision_data(manifold); /* * Create the new Tetrahedra which subdivide each old Tetrahedron. * Set their internal neighbor and gluing fields, which specify * how they glue to each other. Set tet->cusp fields to NULL for * finite vertices, to the correct copy of an old cusp for ideal * vertices which are incident to an old cusp, and to new_cusps[0] * for ideal vertices which are incident to the normal surface. * (In the case of a 2-sided torus or Klein bottle, some references * to new_cusps[0] will be corrected to new_cusps[1] below.) */ for (old_tet = manifold->tet_list_begin.next; old_tet != &manifold->tet_list_end; old_tet = old_tet->next) subdivide_old_tetrahedron(old_tet, subdivision, &data[old_tet->index], new_cusps[0]); /* * Set the external neighbor and gluing fields, which connect * the (small, new) tetrahedra within one (large, old) tetrahedron * to those within another. */ glue_external_faces(manifold, data); /* * For a 2-sided torus or Klein bottle, we have to change some * references from new_cusps[0] to new_cusps[1]. * Change an arbitrary tet->cusp[v] from new_cusps[0] to new_cusps[1], * and then recursively change its neighbors. */ if (Euler_characteristic == 0 && is_two_sided == TRUE) distinguish_cusps(subdivision, new_cusps); /* * Free the SubdivisionData array and its attached arrays, * but not the new Tetrahedra themselves, of course. */ free_subdivision_data(data, manifold->num_tetrahedra); /* * Return the subdivision, which may contain one or two * connected components. */ return subdivision; } static SubdivisionData *allocate_subdivision_data( Triangulation *manifold) { SubdivisionData *data; Tetrahedron *old_tet; FaceIndex f; VertexIndex v; int length; TetReferencePair *array; int i; data = NEW_ARRAY(manifold->num_tetrahedra, SubdivisionData); for (old_tet = manifold->tet_list_begin.next; old_tet != &manifold->tet_list_end; old_tet = old_tet->next) { /* * Set the central references to NULL. */ for (f = 0; f < 4; f++) for (v = 0; v < 4; v++) { data[old_tet->index].central[f][v].tet = NULL; data[old_tet->index].central[f][v].gluing = 0; } /* * Initialize the number of bigons at each vertex of each face * to be the number of blue triangles. */ for (f = 0; f < 4; f++) for (v = 0; v < 4; v++) if (v != f) data[old_tet->index].side_array_length[f][v] = old_tet->num_triangles[v]; else data[old_tet->index].side_array_length[f][v] = 0; /* * If there are any red squares, add in their contribution * to the number of bigons. (As usual, a picture makes * all this clear.) */ if (old_tet->num_squares != 0) { data[old_tet->index].side_array_length [one_vertex_at_edge [ old_tet->parallel_edge ]] [other_vertex_at_edge[ old_tet->parallel_edge ]] += old_tet->num_squares; data[old_tet->index].side_array_length [other_vertex_at_edge[ old_tet->parallel_edge ]] [one_vertex_at_edge [ old_tet->parallel_edge ]] += old_tet->num_squares; data[old_tet->index].side_array_length [one_vertex_at_edge [5 - old_tet->parallel_edge]] [other_vertex_at_edge[5 - old_tet->parallel_edge]] += old_tet->num_squares; data[old_tet->index].side_array_length [other_vertex_at_edge[5 - old_tet->parallel_edge]] [one_vertex_at_edge [5 - old_tet->parallel_edge]] += old_tet->num_squares; } /* * Allocate the arrays of TetReferencePairs, and set them to NULL. */ for (f = 0; f < 4; f++) for (v = 0; v < 4; v++) { length = data[old_tet->index].side_array_length[f][v]; array = NEW_ARRAY(length, TetReferencePair); data[old_tet->index].side_array[f][v] = array; for (i = 0; i < length; i++) { array[i].outer.tet = NULL; array[i].outer.gluing = 0; array[i].inner.tet = NULL; array[i].inner.gluing = 0; } } } return data; } static void free_subdivision_data( SubdivisionData *data, int num_old_tetrahedra) { int i; FaceIndex f; VertexIndex v; for (i = 0; i < num_old_tetrahedra; i++) for (f = 0; f < 4; f++) for (v = 0; v < 4; v++) my_free(data[i].side_array[f][v]); my_free(data); } static void copy_cusps( Triangulation *manifold, Triangulation *subdivision) { Cusp *cusp; if (subdivision->num_cusps != 0 || subdivision->cusp_list_begin.next != &subdivision->cusp_list_end) uFatalError("copy_cusps", "normal_surface_splitting"); for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->matching_cusp = NEW_STRUCT(Cusp); initialize_cusp(cusp->matching_cusp); if (cusp->is_complete != TRUE) uFatalError("copy_cusps", "normal_surface_splitting"); cusp->matching_cusp->topology = cusp->topology; cusp->matching_cusp->is_complete = TRUE; cusp->matching_cusp->m = 0; cusp->matching_cusp->l = 0; cusp->matching_cusp->index = cusp->index; cusp->matching_cusp->is_finite = FALSE; INSERT_BEFORE(cusp->matching_cusp, &subdivision->cusp_list_end); subdivision->num_cusps++; } if (subdivision->num_cusps != manifold->num_cusps) uFatalError("copy_cusps", "normal_surface_splitting"); } static void subdivide_old_tetrahedron( Tetrahedron *old_tet, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split) { VertexIndex v; int i; /* * Subdivide the triangular prisms, if any. */ for (v = 0; v < 4; v++) for (i = 0; i < old_tet->num_triangles[v]; i++) subdivide_triangular_prism(old_tet, v, i, subdivision, tet_data, cusp_at_split); /* * Does this old_tet contain squares? */ if (old_tet->num_squares == 0) { /* * There are no squares. * Subdivide the central tetrahedron. */ subdivide_central_tetrahedron(old_tet, subdivision, tet_data, cusp_at_split); } else { /* * There are squares. * Subdivide the two pillows, as well as any square prisms. */ subdivide_pillow(old_tet, old_tet->parallel_edge, subdivision, tet_data, cusp_at_split); subdivide_pillow(old_tet, 5 - old_tet->parallel_edge, subdivision, tet_data, cusp_at_split); for (i = 0; i < old_tet->num_squares - 1; i++) subdivide_square_prism(old_tet, i, subdivision, tet_data, cusp_at_split); } } static void subdivide_triangular_prism( Tetrahedron *old_tet, VertexIndex old_v, int index, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split) { Tetrahedron *tet[2]; int i; PeripheralCurve c; Orientation h; FaceIndex f; VertexIndex v; /* * Subdivide the triangular prism into two Tetrahedra, and add * the Tetrahedra to the subdivision. * tet[0] is closer to the cusp, while tet[1] is farther from it. * * Important note: tet[0] and tet[1] inherit VertexIndices from * old_tet in the natural way. tet[0] inherits the same orientation * as the old_tet, which, together with a similar convention in * subdivide_central_tetrahedron() and subdivide_pillow(), * ensures that the peripheral curves from the original cusps will * match correctly across the right_ and left_handed sheets. */ for (i = 0; i < 2; i++) { tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet[i]); /* * Is vertex old_v at a cusp of the original manifold? */ if (index == 0 && i == 0) { tet[i]->cusp[old_v] = old_tet->cusp[old_v]->matching_cusp; for (c = 0; c < 2; c++) /* M or L */ for (h = 0; h < 2; h++) /* right_handed or left_handed */ for (f = 0; f < 4; f++) /* which side of the triangle */ tet[i]->curve[c][h][old_v][f] = old_tet->curve[c][h][old_v][f]; } else tet[i]->cusp[old_v] = cusp_at_split; for (v = 0; v < 4; v++) if (v != old_v) tet[i]->cusp[v] = NULL; INSERT_BEFORE(tet[i], &subdivision->tet_list_end); subdivision->num_tetrahedra++; } /* * Glue tet[0] and tet[1] to each other. */ for (i = 0; i < 2; i++) { tet[i]->neighbor[old_v] = tet[!i]; tet[i]->gluing[old_v] = IDENTITY_PERMUTATION; } /* * Fill in the appropriate fields of the SubdivisionData. */ for (f = 0; f < 4; f++) if (f != old_v) { tet_data->side_array[f][old_v][index].outer.tet = tet[0]; tet_data->side_array[f][old_v][index].inner.tet = tet[1]; tet_data->side_array[f][old_v][index].outer.gluing = tet_data->side_array[f][old_v][index].inner.gluing = CREATE_PERMUTATION( old_v, old_v, f, f, remaining_face[old_v][f], remaining_face[f][old_v], remaining_face[f][old_v], remaining_face[old_v][f]); } /* * tet[0] has the correct orientation, but tet[1] does not. */ tet[0]->has_correct_orientation = TRUE; tet[1]->has_correct_orientation = FALSE; } static void subdivide_central_tetrahedron( Tetrahedron *old_tet, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split) { Tetrahedron *vertex_tet[4], *middle_tet[4], *face_tet[4]; int i; PeripheralCurve c; Orientation h; FaceIndex f; VertexIndex v; /* * Think of the central tetrahedron as the union of an octahedron * (whose vertices are the finite vertices at the "midpoints" of * the central tetrahedron's sides) plus four tetrahedra, one at * each of the central tetrahedron's four ideal vertices. */ /* * There are two obvious ways to subdivide the octahedron into * tetrahedra. One could divide it into four tetrahedra meeting * along an axis, or one could divide it into eight tetrahedra by * coning to its center. The first approach uses less memory, but * the second approach is simpler to program. For now I have * adopted the second approach. If memory usage gets to be a problem, * this function could be rewritten using the first approach. * The required changes would be local to this function; no other * functions would be affected. */ /* * The 12 tetrahedra in the subdivision are grouped as follows: * * vertex_tet[4] records the four Tetrahedra incident to the * ideal vertices, * middle_tet[4] records the four Tetrahedra which share * faces with the vertex_tet[], and * face_tet[4] records the remaining four Tetrahedra. * * All Tetrahedra are numbered and indexed in the obvious natural way. * * Important note: Each of the vertex_tets inherits VertexIndices * from old_tet in the natural way. In particular, they inherit * old_tet's orientation. Together with a similar convention * in subdivide_triangular_prism() and subdivide_pillow(), this * ensures that the peripheral curves of the original cusps will * match correctly across the right_ and left_handed sheets. */ for (i = 0; i < 4; i++) { vertex_tet[i] = NEW_STRUCT(Tetrahedron); middle_tet[i] = NEW_STRUCT(Tetrahedron); face_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(vertex_tet[i]); initialize_tetrahedron(middle_tet[i]); initialize_tetrahedron(face_tet[i]); for (v = 0; v < 4; v++) { if (v != i) vertex_tet[i]->cusp[v] = NULL; middle_tet[i]->cusp[v] = NULL; face_tet[i]->cusp[v] = NULL; } /* * Is vertex i of vertex_tet[i] at a cusp of the original manifold? */ if (old_tet->num_triangles[i] == 0) { vertex_tet[i]->cusp[i] = old_tet->cusp[i]->matching_cusp; for (c = 0; c < 2; c++) /* M or L */ for (h = 0; h < 2; h++) /* right_handed or left_handed */ for (f = 0; f < 4; f++) /* which side of the triangle */ vertex_tet[i]->curve[c][h][i][f] = old_tet->curve[c][h][i][f]; } else vertex_tet[i]->cusp[i] = cusp_at_split; INSERT_BEFORE(vertex_tet[i], &subdivision->tet_list_end); INSERT_BEFORE(middle_tet[i], &subdivision->tet_list_end); INSERT_BEFORE(face_tet[i], &subdivision->tet_list_end); subdivision->num_tetrahedra += 3; } /* * Glue the vertex_tets to the middle_tets. */ for (i = 0; i < 4; i++) { vertex_tet[i]->neighbor[i] = middle_tet[i]; middle_tet[i]->neighbor[i] = vertex_tet[i]; vertex_tet[i]->gluing[i] = IDENTITY_PERMUTATION; middle_tet[i]->gluing[i] = IDENTITY_PERMUTATION; } /* * Glue the middle_tets to the face_tets. */ for (i = 0; i < 4; i++) for (f = 0; f < 4; f++) if (f != i) { middle_tet[i]->neighbor[f] = face_tet[f]; face_tet [f]->neighbor[i] = middle_tet[i]; middle_tet[i]->gluing[f] = face_tet [f]->gluing[i] = CREATE_PERMUTATION( i,f, f,i, remaining_face[i][f], remaining_face[f][i], remaining_face[f][i], remaining_face[i][f]); } /* * Fill in the appropriate fields of the SubdivisionData. */ for (f = 0; f < 4; f++) { tet_data->central[f][f].tet = face_tet[f]; tet_data->central[f][f].gluing = IDENTITY_PERMUTATION; for (v = 0; v < 4; v++) if (v != f) { tet_data->central[f][v].tet = vertex_tet[v]; tet_data->central[f][v].gluing = CREATE_PERMUTATION( v,v, f,f, remaining_face[v][f], remaining_face[f][v], remaining_face[f][v], remaining_face[v][f]); } } /* * The vertex_tets and face_tets inherit the correct orientation, * but the middle_tets do not. */ for (i = 0; i < 4; i++) { vertex_tet[i]->has_correct_orientation = TRUE; middle_tet[i]->has_correct_orientation = FALSE; face_tet[i] ->has_correct_orientation = TRUE; } } static void subdivide_pillow( Tetrahedron *old_tet, EdgeIndex defining_edge, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split) { Tetrahedron *vertex_tet[2], *octa_tet[4]; VertexIndex v[4]; int i; PeripheralCurve c; Orientation h; FaceIndex f; VertexIndex vv; int ind[2]; /* * Think of the pillow as the union of an octahedron (whose vertices * are the five finite vertices at the "midpoints" of the pillow's * sides) plus two tetrahedra, one at each of the pillow's triangular * ideal vertices. The octahedron gets further subdivided into * four tetrahedra, meeting along the obvious axis of symmetry. * * The six tetrahedra in the subdivision are grouped as follows: * * vertex_tet[2] records the two Tetrahedra incident to the * ideal vertices, * octa_tet[2] records the four Tetrahedra which comprise * the octahedron. * * It will be helpful to draw yourself a picture as you read through * this documentation. Draw the triangular pillow, representing the * ideal vertices in the usual way (cf. the top of this file) as two * blue triangles and one red square. Then, with dotted lines, draw * the remaining portion of the original large tetrahedra, i.e. the * part that lies beyond the red square. Having the rest of the * large tetrahedron visible will make it easier to keep track of * the VertexIndices. The defining_edge is the edge of the pillow * parallel to the red square. Call its two endpoints (the blue * triangles) v[0] and v[1]. The faces opposite v[0] and v[1] are * f[0] and f[1], respectively. They are the bigonal faces of the * pillow, although when extended to the original large tetrahedron * they are triangles. Relative to the large tetrahedron's right_handed * Orientation, we may apply remaining_face[][] to locate faces * f[2] and f[3]. The ideal vertices opposite faces f[2] and f[3] * are v[2] and v[3], respectively. They lie at the far side of the * original large tetrahedron, and are NOT contained within the pillow * itself. (Error check: the vertices v[0],v[1],v[3] should go * counterclockwise around face f[2] in your drawing, and the vertices * v[1],v[0],v[2] should go counterclockwise around face f[3].) * * vertex_tet[0] is the one incident to vertex v[0], and similarly * vertex_tet[1] is the one incident to vertex v[1]. */ v[0] = one_vertex_at_edge[defining_edge]; v[1] = other_vertex_at_edge[defining_edge]; v[2] = remaining_face[v[0]][v[1]]; v[3] = remaining_face[v[1]][v[0]]; /* * Important note: Each of the vertex_tets inherits VertexIndices * from old_tet in the natural way. Together with a similar convention * in subdivide_triangular_prism() and subdivide_central_tetrahedron(), * this ensures that the peripheral curves of the original cusps will * match correctly across the right_ and left_handed sheets. */ for (i = 0; i < 2; i++) { vertex_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(vertex_tet[i]); for (vv = 0; vv < 4; vv++) if (vv != v[i]) vertex_tet[i]->cusp[vv] = NULL; /* * Is vertex v[i] of vertex_tet[i] at a cusp * of the original manifold? */ if (old_tet->num_triangles[v[i]] == 0) { vertex_tet[i]->cusp[v[i]] = old_tet->cusp[v[i]]->matching_cusp; for (c = 0; c < 2; c++) /* M or L */ for (h = 0; h < 2; h++) /* right_handed or left_handed */ for (f = 0; f < 4; f++) /* which side of the triangle */ vertex_tet[i]->curve[c][h][v[i]][f] = old_tet->curve[c][h][v[i]][f]; } else vertex_tet[i]->cusp[v[i]] = cusp_at_split; INSERT_BEFORE(vertex_tet[i], &subdivision->tet_list_end); subdivision->num_tetrahedra++; } for (i = 0; i < 4; i++) { octa_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(octa_tet[i]); /* * As explained in the paragraph immediately below, * the ideal vertex at the red square has VertexIndex 1. * The other vertices of the octa_tets are finite vertices. */ for (vv = 0; vv < 4; vv++) { if (vv == 1) octa_tet[i]->cusp[vv] = cusp_at_split; else octa_tet[i]->cusp[vv] = NULL; } INSERT_BEFORE(octa_tet[i], &subdivision->tet_list_end); subdivision->num_tetrahedra++; } /* * Glue the four octa_tets to each other. They are numbered * in "west-to-east" order, with * * octa_tet[0] at face f[1] of the original large tetrahedron, * octa_tet[1] at face f[3] of the original large tetrahedron, * octa_tet[2] at face f[0] of the original large tetrahedron, * octa_tet[3] at face f[2] of the original large tetrahedron. * * Error check: Your drawing should show that vertex_tet[0] borders * octa_tet[0], and vertex_tet[1] borders octa_tet[3]. * * Assign VertexIndices to each octa_tet so that vertex 0 is at the * "north pole" (i.e. on the defining_edge), vertex 1 is at the * "south pole" (i.e. at the red square), vertex 2 is to the "west" * and vertex 3 is to the "east". */ for (i = 0; i < 4; i++) { octa_tet[i]->neighbor[2] = octa_tet[(i+1)%4]; octa_tet[i]->gluing[2] = CREATE_PERMUTATION( 0,0, 1,1, 2,3, 3,2 ); octa_tet[i]->neighbor[3] = octa_tet[(i+3)%4]; octa_tet[i]->gluing[3] = CREATE_PERMUTATION( 0,0, 1,1, 2,3, 3,2 ); } /* * Glue the vertex_tets to the octa_tets. */ vertex_tet[0]->neighbor[v[0]] = octa_tet[0]; vertex_tet[0]->gluing[v[0]] = CREATE_PERMUTATION( v[0],1, v[1],0, v[2],3, v[3],2 ); octa_tet[0]->neighbor[1] = vertex_tet[0]; octa_tet[0]->gluing[1] = CREATE_PERMUTATION( 0,v[1], 1,v[0], 2,v[3], 3,v[2] ); vertex_tet[1]->neighbor[v[1]] = octa_tet[2]; vertex_tet[1]->gluing[v[1]] = CREATE_PERMUTATION( v[1],1, v[0],0, v[3],3, v[2],2 ); octa_tet[2]->neighbor[1] = vertex_tet[1]; octa_tet[2]->gluing[1] = CREATE_PERMUTATION( 0,v[0], 1,v[1], 2,v[2], 3,v[3] ); /* * Fill in the appropriate fields of the SubdivisionData. */ tet_data->central[v[2]][v[2]].tet = octa_tet[3]; tet_data->central[v[2]][v[2]].gluing = CREATE_PERMUTATION( v[3],0, v[0],2, v[1],3, v[2],1 ); tet_data->central[v[2]][v[0]].tet = vertex_tet[0]; tet_data->central[v[2]][v[0]].gluing = CREATE_PERMUTATION( v[0],v[0], v[1],v[3], v[2],v[2], v[3],v[1] ); tet_data->central[v[2]][v[1]].tet = vertex_tet[1]; tet_data->central[v[2]][v[1]].gluing = CREATE_PERMUTATION( v[0],v[3], v[1],v[1], v[2],v[2], v[3],v[0] ); tet_data->central[v[2]][v[3]].tet = octa_tet[3]; tet_data->central[v[2]][v[3]].gluing = CREATE_PERMUTATION( v[0],2, v[1],3, v[2],0, v[3],1 ); tet_data->central[v[3]][v[3]].tet = octa_tet[1]; tet_data->central[v[3]][v[3]].gluing = CREATE_PERMUTATION( v[2],0, v[1],2, v[0],3, v[3],1 ); tet_data->central[v[3]][v[1]].tet = vertex_tet[1]; tet_data->central[v[3]][v[1]].gluing = CREATE_PERMUTATION( v[1],v[1], v[0],v[2], v[3],v[3], v[2],v[0] ); tet_data->central[v[3]][v[0]].tet = vertex_tet[0]; tet_data->central[v[3]][v[0]].gluing = CREATE_PERMUTATION( v[1],v[2], v[0],v[0], v[3],v[3], v[2],v[1] ); tet_data->central[v[3]][v[2]].tet = octa_tet[1]; tet_data->central[v[3]][v[2]].gluing = CREATE_PERMUTATION( v[1],2, v[0],3, v[3],0, v[2],1 ); ind[0] = old_tet->num_triangles[v[0]]; ind[1] = old_tet->num_triangles[v[1]]; tet_data->side_array[v[1]][v[0]][ind[0]].outer.tet = vertex_tet[0]; tet_data->side_array[v[1]][v[0]][ind[0]].outer.gluing = CREATE_PERMUTATION( v[0],v[0], v[2],v[3], v[3],v[2], v[1],v[1] ); tet_data->side_array[v[1]][v[0]][ind[0]].inner.tet = octa_tet[0]; tet_data->side_array[v[1]][v[0]][ind[0]].inner.gluing = CREATE_PERMUTATION( v[0],1, v[2],2, v[3],3, v[1],0 ); tet_data->side_array[v[0]][v[1]][ind[1]].outer.tet = vertex_tet[1]; tet_data->side_array[v[0]][v[1]][ind[1]].outer.gluing = CREATE_PERMUTATION( v[1],v[1], v[3],v[2], v[2],v[3], v[0],v[0] ); tet_data->side_array[v[0]][v[1]][ind[1]].inner.tet = octa_tet[2]; tet_data->side_array[v[0]][v[1]][ind[1]].inner.gluing = CREATE_PERMUTATION( v[1],1, v[3],2, v[2],3, v[0],0 ); /* * All tetrahedra inherit the correct orientation. */ for (i = 0; i < 2; i++) vertex_tet[i]->has_correct_orientation = TRUE; for (i = 0; i < 4; i++) octa_tet[i] ->has_correct_orientation = TRUE; } static void subdivide_square_prism( Tetrahedron *old_tet, int index, Triangulation *subdivision, SubdivisionData *tet_data, Cusp *cusp_at_split) { Tetrahedron *tet[4]; int i; FaceIndex f[4]; VertexIndex v[4]; int ind[4]; /* * Subdivide the square prism (which is combinatorially an octahedron, * after taking into account the subdivision of its boundary as defined * at the top of this file) into four Tetrahedra arranged symmetrically * about a common "vertical" axis. Assign VertexIndices to each * Tetrahedron so that the ideal vertex at the octahedron's * "north pole" has index 0, the ideal vertex at its "south pole" * has index 1, the "western" finite vertex on the "equator" has * index 2, and the "eastern" finite vertex on the "equator" has * index 3. */ for (i = 0; i < 4; i++) { tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet[i]); tet[i]->cusp[0] = cusp_at_split; tet[i]->cusp[1] = cusp_at_split; tet[i]->cusp[2] = NULL; tet[i]->cusp[3] = NULL; INSERT_BEFORE(tet[i], &subdivision->tet_list_end); subdivision->num_tetrahedra++; } /* * Glue the four Tetrahedra to each other. * The array tet[] lists the Tetrahedra in west-to-east order. */ for (i = 0; i < 4; i++) { tet[i]->neighbor[2] = tet[(i+1)%4]; tet[i]->gluing[2] = CREATE_PERMUTATION( 0,0, 1,1, 2,3, 3,2); tet[i]->neighbor[3] = tet[(i+3)%4]; tet[i]->gluing[3] = CREATE_PERMUTATION( 0,0, 1,1, 2,3, 3,2); } /* * Fill in the appropriate fields of the SubdivisionData. * * Before reading this code, make yourself a sketch of a tetrahedron * containing two red squares at its center. Label the "parallel edge", * and then label the various faces and vertices as you read the code. */ f[0] = one_face_at_edge[old_tet->parallel_edge]; f[1] = other_face_at_edge[old_tet->parallel_edge]; f[2] = remaining_face[f[0]][f[1]]; f[3] = remaining_face[f[1]][f[0]]; v[0] = f[1]; v[1] = f[0]; v[2] = f[3]; v[3] = f[2]; ind[0] = (old_tet->num_squares - 1) - index + old_tet->num_triangles[v[0]]; ind[1] = (old_tet->num_squares - 1) - index + old_tet->num_triangles[v[1]]; ind[2] = 1 + index + old_tet->num_triangles[v[2]]; ind[3] = 1 + index + old_tet->num_triangles[v[3]]; tet_data->side_array[f[0]][v[0]][ind[0]].outer.tet = tet_data->side_array[f[0]][v[0]][ind[0]].inner.tet = tet[0]; tet_data->side_array[f[3]][v[3]][ind[3]].outer.tet = tet_data->side_array[f[3]][v[3]][ind[3]].inner.tet = tet[1]; tet_data->side_array[f[1]][v[1]][ind[1]].outer.tet = tet_data->side_array[f[1]][v[1]][ind[1]].inner.tet = tet[2]; tet_data->side_array[f[2]][v[2]][ind[2]].outer.tet = tet_data->side_array[f[2]][v[2]][ind[2]].inner.tet = tet[3]; tet_data->side_array[f[0]][v[0]][ind[0]].outer.gluing = CREATE_PERMUTATION( v[0],1, v[3],2, v[2],3, v[1],0 ); tet_data->side_array[f[0]][v[0]][ind[0]].inner.gluing = CREATE_PERMUTATION( v[0],0, v[3],2, v[2],3, v[1],1 ); tet_data->side_array[f[3]][v[3]][ind[3]].outer.gluing = CREATE_PERMUTATION( v[3],0, v[1],2, v[0],3, v[2],1 ); tet_data->side_array[f[3]][v[3]][ind[3]].inner.gluing = CREATE_PERMUTATION( v[3],1, v[1],2, v[0],3, v[2],0 ); tet_data->side_array[f[1]][v[1]][ind[1]].outer.gluing = CREATE_PERMUTATION( v[1],1, v[2],2, v[3],3, v[0],0 ); tet_data->side_array[f[1]][v[1]][ind[1]].inner.gluing = CREATE_PERMUTATION( v[1],0, v[2],2, v[3],3, v[0],1 ); tet_data->side_array[f[2]][v[2]][ind[2]].outer.gluing = CREATE_PERMUTATION( v[2],0, v[0],2, v[1],3, v[3],1 ); tet_data->side_array[f[2]][v[2]][ind[2]].inner.gluing = CREATE_PERMUTATION( v[2],1, v[0],2, v[1],3, v[3],0 ); /* * The tetrahedra inherit the correct orientation. */ for (i = 0; i < 4; i++) tet[i]->has_correct_orientation = TRUE; } static void glue_external_faces( Triangulation *manifold, SubdivisionData *data) { Tetrahedron *old_tet, *old_nbr, *tet, *nbr; Permutation gluing, tet_perm, nbr_perm; FaceIndex f, ff; VertexIndex v, vv; int i; /* * For each original large tetrahedron... */ for (old_tet = manifold->tet_list_begin.next; old_tet != &manifold->tet_list_end; old_tet = old_tet->next) /* * ...consider the subdivision of each face. */ for (f = 0; f < 4; f++) { /* * Find our neighboring original large tetrahedron. */ old_nbr = old_tet->neighbor[f]; gluing = old_tet->gluing[f]; ff = EVALUATE(gluing,f); /* * The gluing mapping each small tetrahedron at this * face to its mate at the neighboring face will be the * composition of * * (1) the mapping taking the actual VertexIndices of the * small tetrahedron to the standard VertexIndices on * the face (cf. the top of this file), * (2) old_tet->gluing[f], and * (3) the mapping taking the standard VertexIndices on the * neighboring face to actual VertexIndices of the * neighboring small tetrahedron. * * The function compute_external_gluing() computes * this composition. */ /* * There will always be a central piece. */ for (v = 0; v < 4; v++) { vv = EVALUATE(gluing,v); tet = data[old_tet->index].central[f] [v] .tet; tet_perm = data[old_tet->index].central[f] [v] .gluing; nbr = data[old_nbr->index].central[ff][vv].tet; nbr_perm = data[old_nbr->index].central[ff][vv].gluing; tet->neighbor[EVALUATE(tet_perm,f)] = nbr; tet->gluing [EVALUATE(tet_perm,f)] = compute_external_gluing(tet_perm, gluing, nbr_perm); } /* * There may also be (subdivided) bigons at the incident vertices. */ for (v = 0; v < 4; v++) if (v != f) for (i = 0; i < data[old_tet->index].side_array_length[f][v]; i++) { vv = EVALUATE(gluing,v); tet = data[old_tet->index].side_array[f] [v] [i].outer.tet; tet_perm = data[old_tet->index].side_array[f] [v] [i].outer.gluing; nbr = data[old_nbr->index].side_array[ff][vv][i].outer.tet; nbr_perm = data[old_nbr->index].side_array[ff][vv][i].outer.gluing; tet->neighbor[EVALUATE(tet_perm,f)] = nbr; tet->gluing [EVALUATE(tet_perm,f)] = compute_external_gluing(tet_perm, gluing, nbr_perm); tet = data[old_tet->index].side_array[f] [v] [i].inner.tet; tet_perm = data[old_tet->index].side_array[f] [v] [i].inner.gluing; nbr = data[old_nbr->index].side_array[ff][vv][i].inner.tet; nbr_perm = data[old_nbr->index].side_array[ff][vv][i].inner.gluing; tet->neighbor[EVALUATE(tet_perm,f)] = nbr; tet->gluing [EVALUATE(tet_perm,f)] = compute_external_gluing(tet_perm, gluing, nbr_perm); } } } static Permutation compute_external_gluing( Permutation perm0_inverse, Permutation perm1, Permutation perm2) { Permutation result; result = inverse_permutation[perm0_inverse]; result = compose_permutations(perm1, result); /* right-to-left evaluation */ result = compose_permutations(perm2, result); /* right-to-left evaluation */ return result; } static void distinguish_cusps( Triangulation *subdivision, Cusp *new_cusps[2]) { IdealVertexReference *ivr_stack; int ivr_stack_size; Tetrahedron *tet, *nbr; VertexIndex v, nbr_v; FaceIndex f; /* * The calling program has split along a 2-sided torus or Klein bottle. * By default it has set all the pointers tet->cusp[v] to point to * new_cusps[0]. The purpose of the present function is to leave * those belonging to one cusp set to new_cusps[0], while changing * those belong to the other cusp to new_cusps[1]. */ /* * Allocate space for a stack of IdealVertexReferences for which * tet->cusp[v] has been changed from new_cusps[0] to new_cusps[1], * but whose neighbors have not yet been examined. */ ivr_stack = NEW_ARRAY(4 * subdivision->num_tetrahedra, IdealVertexReference); ivr_stack_size = 0; /* * Find a reference to new_cusps[0], change it to new_cusps[1], * and put it on the ivr_stack. */ for (tet = subdivision->tet_list_begin.next; tet != &subdivision->tet_list_end && ivr_stack_size == 0; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == new_cusps[0]) { tet->cusp[v] = new_cusps[1]; ivr_stack[0].tet = tet; ivr_stack[0].v = v; ivr_stack_size = 1; break; } if (ivr_stack_size == 0) uFatalError("distinguish_cusps", "normal_surface_splitting"); /* * While the stack isn't empty... */ while (ivr_stack_size > 0) { /* * Pull an IdealVertexReference off the top of the stack. */ --ivr_stack_size; tet = ivr_stack[ivr_stack_size].tet; v = ivr_stack[ivr_stack_size].v; /* * Look at each of its neighbors. */ for (f = 0; f < 4; f++) if (f != v) { nbr = tet->neighbor[f]; nbr_v = EVALUATE(tet->gluing[f], v); /* * If the neighbor hasn't already been switched * to new_cusps[1], switch it and put it on the ivr_stack. */ if (nbr->cusp[nbr_v] != new_cusps[1]) { nbr->cusp[nbr_v] = new_cusps[1]; ivr_stack[ivr_stack_size].tet = nbr; ivr_stack[ivr_stack_size].v = nbr_v; ivr_stack_size++; } } } /* * Free the stack. */ my_free(ivr_stack); } static void separate_connected_components( Triangulation *subdivision, Triangulation *pieces[2]) { Tetrahedron *tet, **tet_stack, *nbr; int tet_stack_size, *which_piece, cusp_index, *old_to_new_index, i; FaceIndex f; VertexIndex v; Cusp *cusp; /* * Initialize pieces[0]. */ pieces[0] = NEW_STRUCT(Triangulation); initialize_triangulation(pieces[0]); /* * Move an arbitrary Tetrahedron from the subdivision to pieces[0], * and then recursively move its neighbors. */ /* * Use the flag to record which Tetrahedra have been moved. */ for (tet = subdivision->tet_list_begin.next; tet != &subdivision->tet_list_end; tet = tet->next) tet->flag = FALSE; /* * Initialize a stack. The stack will contain pointers to * Tetrahedra which have been moved, but whose neighbors have * not yet been examined. */ tet_stack = NEW_ARRAY(subdivision->num_tetrahedra, Tetrahedron *); tet_stack_size = 0; /* * Transfer an arbitrary Tetrahedron, and * put a pointer to it on the stack. */ if (subdivision->tet_list_begin.next == &subdivision->tet_list_end) uFatalError("separate_connected_components", "normal_surface_splitting"); tet_stack[0] = subdivision->tet_list_begin.next; tet_stack_size = 1; tet_stack[0]->flag = TRUE; REMOVE_NODE(tet_stack[0]); INSERT_BEFORE(tet_stack[0], &pieces[0]->tet_list_end); subdivision->num_tetrahedra--; pieces[0]->num_tetrahedra++; /* * While the stack is nonempty... */ while (tet_stack_size > 0) { /* * Pull a Tetrahedron pointer off the stack. */ tet = tet_stack[--tet_stack_size]; /* * Examine its neighbors. */ for (f = 0; f < 4; f++) { nbr = tet->neighbor[f]; /* * If the neighbor hasn't been transferred, * transfer it and put a pointer to it on the stack. */ if (nbr->flag == FALSE) { tet_stack[tet_stack_size++] = nbr; nbr->flag = TRUE; REMOVE_NODE(nbr); INSERT_BEFORE(nbr, &pieces[0]->tet_list_end); subdivision->num_tetrahedra--; pieces[0]->num_tetrahedra++; } } } /* * Free the stack. */ my_free(tet_stack); /* * A quick error check. */ if ((subdivision->num_tetrahedra == 0) != (subdivision->tet_list_begin.next == &subdivision->tet_list_end)) uFatalError("separate_connected_components", "normal_surface_splitting"); /* * If any Tetrahedra remain in subdivision, transfer them to pieces[1]. */ if (subdivision->num_tetrahedra > 0) { pieces[1] = NEW_STRUCT(Triangulation); initialize_triangulation(pieces[1]); pieces[1]->tet_list_begin.next = subdivision->tet_list_begin.next; pieces[1]->tet_list_begin.next->prev = &pieces[1]->tet_list_begin; pieces[1]->tet_list_end.prev = subdivision->tet_list_end.prev; pieces[1]->tet_list_end.prev->next = &pieces[1]->tet_list_end; subdivision->tet_list_begin.next = &subdivision->tet_list_end; subdivision->tet_list_end.prev = &subdivision->tet_list_begin; pieces[1]->num_tetrahedra = subdivision->num_tetrahedra; subdivision->num_tetrahedra = 0; } else pieces[1] = NULL; /* * Another quick error check. */ if (subdivision->cusp_list_begin.next == &subdivision->cusp_list_end) uFatalError("separate_connected_components", "normal_surface_splitting"); /* * If there's only one piece, transfer all the Cusps to it. * If there are two pieces we have to be a little more careful, * and transfer each Cusp to the correct piece. */ if (pieces[1] == NULL) { pieces[0]->cusp_list_begin.next = subdivision->cusp_list_begin.next; pieces[0]->cusp_list_begin.next->prev = &pieces[0]->cusp_list_begin; pieces[0]->cusp_list_end.prev = subdivision->cusp_list_end.prev; pieces[0]->cusp_list_end.prev->next = &pieces[0]->cusp_list_end; subdivision->cusp_list_begin.next = &subdivision->cusp_list_end; subdivision->cusp_list_end.prev = &subdivision->cusp_list_begin; pieces[0]->num_cusps = subdivision->num_cusps; subdivision->num_cusps = 0; } else { /* * We need to figure out which Cusp goes with which piece. * Set up an array to record this information: which_piece[i] * will equal 0 (resp. 1) when the Cusp of index i belongs to * pieces[0] (resp. pieces[1]). */ which_piece = NEW_ARRAY(subdivision->num_cusps, int); /* * We don't really need to initialize which_piece[], * but it allows error checking. */ for (i = 0; i < subdivision->num_cusps; i++) which_piece[i] = -1; /* * Use the tet->cusp[] fields to deduce which Cusp goes where. */ for (i = 0; i < 2; i++) for (tet = pieces[i]->tet_list_begin.next; tet != &pieces[i]->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) { if (tet->cusp[v] == NULL) /* skip finite vertices */ continue; cusp_index = tet->cusp[v]->index; if (cusp_index < 0 || cusp_index >= subdivision->num_cusps) uFatalError("separate_connected_components", "normal_surface_splitting"); switch (which_piece[cusp_index]) { case -1: which_piece[cusp_index] = i; break; case 0: if (i != 0) uFatalError("separate_connected_components", "normal_surface_splitting"); break; case 1: if (i != 1) uFatalError("separate_connected_components", "normal_surface_splitting"); break; } } for (i = 0; i < subdivision->num_cusps; i++) if (which_piece[i] == -1) uFatalError("separate_connected_components", "normal_surface_splitting"); /* * The Cusps should be numbered consecutively within each piece, * yet retain the same relative order as they had in subdivision. * The array old_to_new_index[] translates an old Cusp index * (in subdivision) to a new Cusp index (in pieces[which_piece[i]]). * As a side effect, this code sets pieces[]->num_cusps. */ old_to_new_index = NEW_ARRAY(subdivision->num_cusps, int); pieces[0]->num_cusps = 0; pieces[1]->num_cusps = 0; for (i = 0; i < subdivision->num_cusps; i++) old_to_new_index[i] = pieces[which_piece[i]]->num_cusps++; /* * Transfer the Cusps from subdivision to pieces[0] and pieces[1]. */ while (subdivision->cusp_list_begin.next != &subdivision->cusp_list_end) { cusp = subdivision->cusp_list_begin.next; REMOVE_NODE(cusp); INSERT_BEFORE(cusp, &pieces[which_piece[cusp->index]]->cusp_list_end); cusp->index = old_to_new_index[cusp->index]; } subdivision->num_cusps = 0; my_free(which_piece); my_free(old_to_new_index); } } static Tetrahedron *find_correctly_oriented_tet( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->has_correct_orientation == TRUE) return tet; uFatalError("find_correctly_oriented_tet", "normal_surface_splitting"); return NULL; /* provide a return value to keep the compiler happy */ } snappea-3.0d3/SnapPeaKernel/code/o31_matrices.c0100444000175000017500000002771106742675502017347 0ustar babbab/* * o31_matrices.c * * This file provides the following functions for working with O31Matrices: * * void o31_copy(O31Matrix dest, O31Matrix source); * void o31_invert(O31Matrix m, O31Matrix m_inverse); * FuncResult gl4R_invert(GL4RMatrix m, GL4RMatrix m_inverse); * double gl4R_determinant(GL4RMatrix m); * void o31_product(O31Matrix a, O31Matrix b, O31Matrix product); * Boolean o31_equal(O31Matrix a, O31Matrix b, double epsilon); * double o31_trace(O31Matrix m); * double o31_deviation(O31Matrix m); * void o31_GramSchmidt(O31Matrix m); * void o31_conjugate(O31Matrix m, O31Matrix t, O31Matrix Tmt); * double o31_inner_product(O31Vector u, O31Vector v); * void o31_matrix_times_vector(O31Matrix m, O31Vector v, O31Vector product); * void o31_constant_times_vector(double r, O31Vector v, O31Vector product); * void o31_copy_vector(O31Vector dest, O31Vector source); * void o31_vector_sum(O31Vector a, O31Vector b, O31Vector sum); * void o31_vector_diff(O31Vector a, O31Vector b, O31Vector diff); */ #include "kernel.h" /* * gl4R_invert will consider a matrix to be singular iff one of the * absolute value of one of the pivots is less than SINGULAR_MATRIX_EPSILON. */ #define SINGULAR_MATRIX_EPSILON 1e-2 #define COLUMN_PRODUCT(m, i, j) \ (-m[0][i]*m[0][j] + m[1][i]*m[1][j] + m[2][i]*m[2][j] + m[3][i]*m[3][j]) O31Matrix O31_identity = { {1.0, 0.0, 0.0, 0.0}, {0.0, 1.0, 0.0, 0.0}, {0.0, 0.0, 1.0, 0.0}, {0.0, 0.0, 0.0, 1.0} }; void o31_copy( O31Matrix dest, O31Matrix source) { int i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) dest[i][j] = source[i][j]; } void o31_invert( O31Matrix m, O31Matrix m_inverse) { /* * The inverse of an O(3,1) matrix may be computed by taking * the transpose and then negating both the zeroth row and the * zeroth column. The proof follows easily from the fact that * multiplying an O(3,1) matrix by its transpose is almost the * same thing as computing the inner product of each pair of * columns. (For O(4) matrices, the transpose is precisely * the inverse, because there are no minus sign in the metric * to fuss over.) * * We first write the inverse into the O31Matrix temp, so that if * the parameters m and m_inverse are the same O31Matrix, we don't * overwrite m[j][i] before computing m[i][j]. */ int i, j; O31Matrix temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) temp[i][j] = ((i == 0) == (j == 0)) ? m[j][i] : -m[j][i]; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) m_inverse[i][j] = temp[i][j]; } FuncResult gl4R_invert( GL4RMatrix m, GL4RMatrix m_inverse) { double row[4][8]; double *mm[4], *temp_row, multiple; int i, j, k; for (i = 0; i < 4; i++) mm[i] = row[i]; /* * Copy m -- don't alter the original. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) mm[i][j] = m[i][j]; /* * Initialize the four right hand columns to the identity. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) mm[i][4 + j] = (i == j) ? 1.0 : 0.0; /* * Forward elimination. */ for (j = 0; j < 4; j++) { /* * Partial pivoting. */ for (i = j+1; i < 4; i++) if (fabs(mm[j][j]) < fabs(mm[i][j])) { temp_row = mm[j]; mm[j] = mm[i]; mm[i] = temp_row; } /* * Is the matrix singular? */ if (fabs(mm[j][j]) < SINGULAR_MATRIX_EPSILON) return func_bad_input; /* * Divide through to get a 1.0 on the diagonal. */ multiple = 1.0 / mm[j][j]; for (k = j; k < 8; k++) mm[j][k] *= multiple; /* * Clear out that column. */ for (i = j+1; i < 4; i++) { multiple = mm[i][j]; for (k = j; k < 8; k++) mm[i][k] -= multiple * mm[j][k]; } } /* * Back substitution. */ for (j = 4; --j >= 0; ) for (i = j; --i >= 0; ) for (k = 4; k < 8; k++) mm[i][k] -= mm[i][j] * mm[j][k]; /* * Copy out the solution. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) m_inverse[i][j] = mm[i][4 + j]; return func_OK; } double gl4R_determinant( GL4RMatrix m) { /* * Two approaches come to mind for computing a 4 x 4 determinant. * * (1) Work out the 4! = 24 terms -- each a product of four * matrix entries -- and form their alternating sum. * * (2) Use Gaussian elimination. * * I doubt there is much difference in efficiency between the two * methods, so I've chosen method (2) on the assumption (which I * haven't thoroughly thought out) that it will be numerically more * robust. Numerical effects could be noticeable, because O(3,1) * matrices tend to have large entires. On the other hand, the * determinant will always be plus or minus one, so it's not worth * getting too concerned about the precision. */ /* * gl4R_determinant() no longer calls uFatalError() when the determinant * is something other than +1 or -1. This lets compute_approx_volume() * in Dirichlet_extras.c compute the determinant of matrices which * are in gl(2,C) but not O(3,1). Note that matrix_io.c already calls * O31_determinants_OK() to validate matrices read from files, and * that SnapPea has had no trouble with determinants of internally * computed O(3,1) matrices. * * JRW 94/11/30 */ int r, c, cc, pivot_row, row_swaps; double max_abs, this_abs, temp, factor, det; O31Matrix mm; /* * First copy the matrix, to avoid destroying it as * we compute its determinant. */ o31_copy(mm, m); /* * Put the matrix in upper triangular form. * * Count the number of row swaps, so we can get the * correct sign at the end. * * Technical comment: We don't actually write zeros into the * lower part of the matrix; we just pretend. */ row_swaps = 0; for (c = 0; c < 4; c++) { /* * Find the pivot row. */ max_abs = -1.0; for (r = c; r < 4; r++) { this_abs = fabs(mm[r][c]); if (this_abs > max_abs) { max_abs = this_abs; pivot_row = r; } } if (max_abs == 0.0) /* * The determinant of an O(3,1) matrix should always * be plus or minus one, never zero. */ /* uFatalError("gl4R_determinant", "o31_matrices"); */ return 0.0; /* JRW 94/11/30 (see explanation above) */ /* * Swap the pivot row into position. */ if (pivot_row != c) { for (cc = c; cc < 4; cc++) { temp = mm[c][cc]; mm[c][cc] = mm[pivot_row][cc]; mm[pivot_row][cc] = temp; } row_swaps++; } /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < 4; r++) { factor = - mm[r][c] / mm[c][c]; for (cc = c + 1; cc < 4; cc++) mm[r][cc] += factor * mm[c][cc]; } } /* * The determinant is now the product of the diagonal entries. */ det = 1.0; for (c = 0; c < 4; c++) det *= mm[c][c]; if (row_swaps % 2) det = - det; /* * Do a quick error check, just to be safe. * The determinant of an O31_matrix should be +1 or -1. */ /* commented out by JRW 94/11/30 (see explanation above) if (fabs(fabs(det) - 1.0) > 0.01) uFatalError("gl4R_determinant", "o31_matrices"); */ return det; } void o31_product( O31Matrix a, O31Matrix b, O31Matrix product) { register int i, j, k; register double sum; O31Matrix temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { sum = 0.0; for (k = 0; k < 4; k++) sum += a[i][k] * b[k][j]; temp[i][j] = sum; } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) product[i][j] = temp[i][j]; } Boolean o31_equal( O31Matrix a, O31Matrix b, double epsilon) { /* * There are a number of different ways one could decide whether two * O(3,1) matrices are the same or not. The fancier ways, such as * computing the sum of the squares of the differences of corresponding * entries, are numerically more time consuming. For now let's just * check that all entries are equal to within epsilon. This offers the * advantage that when scanning down lists, the vast majority of * matrices are diagnosed as different after the comparision of a * single pair of numbers. The epsilon can be fairly large, since to * qualify as equal, two matrices must have ALL their entries equal to * within that precision. */ int i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) if (fabs(a[i][j] - b[i][j]) > epsilon) return FALSE; return TRUE; } double o31_trace( O31Matrix m) { int i; double trace; trace = 0.0; for (i = 0; i < 4; i++) trace += m[i][i]; return trace; } double o31_deviation( O31Matrix m) { /* * The matrix m is, in theory, an element of SO(3,1), * so the inner product of column i with column j should be * * -1 if i = j = 0, * +1 if i = j != 0, or * 0 if i != j. * * Return the greatest deviation from these values, so the * calling function has some idea how precise the matrix is. * * The simplest way to code this is to multiply the matrix times its * inverse. Note that this approach relies on the fact that * o31_inverse() transposes the matrix and negates the appropriate * entries. If o31_inverse() did Gaussian elimination to numerically * invert the matrix, we'd have to rewrite the following code. */ O31Matrix the_inverse, the_product; double error, max_error; int i, j; o31_invert(m, the_inverse); o31_product(m, the_inverse, the_product); max_error = 0.0; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { error = fabs(the_product[i][j] - (i == j ? 1.0 : 0.0)); if (error > max_error) max_error = error; } return max_error; } void o31_GramSchmidt( O31Matrix m) { /* * Given a matrix m whose columns are almost orthonormal (in the sense * of O(3,1), not O(4)), use the Gram-Schmidt process to make small * changes to the matrix entries so that the columns become orthonormal * to the highest precision possible. */ int r, c, cc; double length, length_of_projection; for (c = 0; c < 4; c++) { /* * Adjust column c to have length -1 (if c == 0) or +1 (if c > 0). * We are assuming m is already close to being in O(3,1), so * it suffices to divide column c by sqrt(fabs(length)). */ length = sqrt(fabs(COLUMN_PRODUCT(m, c, c))); /* no need for safe_sqrt() */ for (r = 0; r < 4; r++) m[r][c] /= length; /* * We want to make all subsequent columns be orthogonal to column c, * so subtract off their components in the direction of column c. * Because column c is now a unit vector, the inner product * gives plus or minus the length of the * projection of column cc onto column c, according to whether or * not c == 0. */ for (cc = c + 1; cc < 4; cc++) { length_of_projection = COLUMN_PRODUCT(m, c, cc); if (c == 0) length_of_projection = - length_of_projection; for (r = 0; r < 4; r++) m[r][cc] -= length_of_projection * m[r][c]; } } } void o31_conjugate( O31Matrix m, O31Matrix t, O31Matrix Tmt) { /* * Replace m with (t^-1) m t. */ O31Matrix t_inverse, temp; o31_invert(t, t_inverse); o31_product(t_inverse, m, temp); o31_product(temp, t, Tmt); } double o31_inner_product( O31Vector u, O31Vector v) { int i; double sum; sum = - u[0]*v[0]; for (i = 1; i < 4; i++) sum += u[i]*v[i]; return sum; } void o31_matrix_times_vector( O31Matrix m, O31Vector v, O31Vector product) { register int i, j; register double sum; O31Vector temp; for (i = 0; i < 4; i++) { sum = 0.0; for (j = 0; j < 4; j++) sum += m[i][j] * v[j]; temp[i] = sum; } for (i = 0; i < 4; i++) product[i] = temp[i]; } void o31_constant_times_vector( double r, O31Vector v, O31Vector product) { int i; for (i = 0; i < 4; i++) product[i] = r * v[i]; } void o31_copy_vector( O31Vector dest, O31Vector source) { int i; for (i = 0; i < 4; i++) dest[i] = source[i]; } void o31_vector_sum( O31Vector a, O31Vector b, O31Vector sum) { int i; for (i = 0; i < 4; i++) sum[i] = a[i] + b[i]; } void o31_vector_diff( O31Vector a, O31Vector b, O31Vector diff) { int i; for (i = 0; i < 4; i++) diff[i] = a[i] - b[i]; } snappea-3.0d3/SnapPeaKernel/code/peripheral_curves.c0100444000175000017500000011543007041101116020546 0ustar babbab/* * peripheral_curves.c * * * This file provides the function * * void peripheral_curves(Triangulation *manifold); * * which puts a meridian and longitude on each cusp. If the * manifold is oriented, the meridian and longitude adhere to * the usual orientation convention; this is, if you place your * right hand on the torus with your fingers pointing in the * direction of the meridian and your thumb pointing in the * direction of the longitude, then your palm will face the * cusp while the back of your hand faces the fat part of the * manifold. Note that this corresponds to the usual convention * for orienting meridians and longitudes on link complements. * * Even if the manifold isn't oriented, the peripheral curves * adhere to the standard orientation convention relative to * the orientation of each Cusp's orientation double cover (more * on this below). * * peripheral_curves() does not need to know the CuspTopology * ahead of time. It figures it out for itself and records the * result in the field cusp->topology. * * The remainder of this documentation will * * (1) Define the meridian and the longitude. * * (2) Describe the data structure used to store * meridians and longitudes. * * (3) Explain the algorithm which peripheral_curves() * uses to find meridians and longitudes. * * * (1) Definition of the meridian and the longitude. * * The meridian and the longitude of an orientable cusp are * any pair of simple closed curves which intersect exactly * once. If the manifold is orientable, they will adhere to * the orientation convention described above. peripheral_curves() * finds meridians and longitudes which are reasonably short * in the sense that they pass through a small number of * triangles in the triangulation of the boundary torus. * * The meridian and the longitude of a nonorientable cusp are * defined more precisely. There are exactly four nontrivial * simple closed curves on a Klein bottle, up to isotopy. I'd * like to provide a picture of them, but, alas, there's no way * to include a picture in an ASCII file, so I must ask you to * draw your own picture as you read along. Define the * Klein bottle as a cylinder with ends glued. Parameterize * the cylinder by (x, theta), where -1 <= x <= +1 and theta * is defined mod 2pi as for a circle. To make the Klein * bottle, identify the cylinder's ends via the mapping * (-1, theta) -> (+1, -theta). With this notation, the four * simple closed curves on the Klein bottle are * * A: theta = 0 * B: theta = pi * C: (theta = pi/2) U (theta = - pi/2) * D: x = 0 * * The longitude will be either curve A or curve B. Thus, * counting orientation, there are four possible longitudes. * (Note: as explained in section (2) immediately following, * the longitude of a Klein bottle is actually stored as its * preimage in the Klein bottle's double cover.) * The meridian will be curve D. Curve D cannot be oriented, * because it's isotopic to its inverse. * * The holonomies of the longitude and meridian of a Klein * bottle have some very special properties, which are * discussed in the comment at the top of holonomy.c. * * * (2) How meridians and longitudes are stored. * * The meridian and longitude are stored not on the boundary * component itself, but on its orientation double cover. * The double cover of a Klein bottle is a torus. The double * cover of a torus is the union of two tori, only one of * which is actually used (the right_handed one, if the * manifold is oriented). The reason we want to always * store curves on tori and never on Klein bottles directly * is that the fundamental group of the torus is abelian: * isotopy classes of curves can be recovered from homological * information. The reason only the right_handed sheet * of the cover is used when the manifold is oriented is * that the holonomy of a Dehn filling curve on the left_handed * sheet is not a complex analytic function of the tetrahedron * shapes, but rather the complex conjugate of such a function; * we need complex analytic functions to compute the * derivative matrix in the complex version of Newton's * method used to find hyperbolic structures for oriented * manifolds. * * Each torus is the union of triangular cross sections of * ideal vertices. Each ideal vertex of each Tetrahedron * contributes two triangles, one with the right_handed * orientation and one with the left_handed orientation. * Visualize the right-handed triangle as containing a * counter-clockwise oriented circle, and the left-handed * triangle as containing a clockwise-oriented circle, as viewed * from infinity relative to the right_handed orientation of the * Tetrahedron. The triangles piece together so that orientations * of neighboring circles agree: if two neighboring Tetrahedra * have opposite orientations, then the right_handed triangles * of one connect with the left_handed triangles of the other; * if the two tetrahedra have the same orientation, then the * right_handed triangles of one match to the right_handed triangles * of the other, and left_handed to left_handed. Note that * the components of the orientation double cover of the Cusps * are all oriented, not just orientable (use the rule which * says to view the circles so that all appear counterclockwise * as seen from infinity). * * The meridian and longitude are specified by their * intersection numbers with the triangular cross sections. * tet->curve[M][right_handed][v][f] is the net number of * times the meridian crosses side f of the right_handed * triangle at vertex v, and similarly for the * longitude and the left_handed triangle. * A positive intersection number means the curve is * entering the triangle. The sides of the triangle are * numbered according to the faces of the tetrahedron * containing them. E.g., the sides of the triangle at * vertex 2 will be numbered 0, 1 and 3. The array vt_side[][] * in tables.c lets you refer to the sides of a triangle by * the integers 0, 1, 2, if this suits your purposes. * Note that these intersection numbers are sufficient to * reconstruct a simple closed curve up to isotopy. * * The longitude of the Klein bottle is a special case in * that its preimage in the double cover is connected. It * is stored as the complete preimage. All other curves are * stored as one component of their two-component preimages. * By the way, the two candidates for the longitude (curves * A and B in the discussion above) lift to the same curve in * the double cover. * * * (3) How peripheral_curves() finds the meridian and the * longitude. * * There are three main steps: * * Compute a fundamental domain for the boundary component. * * Identify it as a torus or Klein bottle. * * Find the longitude and meridian. * * The plan for finding a fundamental domain is * conceptually simple: initialize the domain as a single * triangular vertex cross section, and then expand it outward * by adding neighboring triangles in a breadth-first * fashion, until the domain fills the entire cusp. * * This plan is implemented with the PerimeterPiece data * structure. At each step, a circular linked list of * PerimeterPieces defines the boundary of the domain. * (Each PerimeterPiece corresponds to one edge of one * triangle on the perimeter of the domain.) The algorithm * is to keep going around the perimeter, adding new * PerimeterPieces as the domain expands across triangles * which were not previously included. * * While this process goes on, a second data structure is * being created. An array of 4 Extra fields attached to * each tetrahedron (one Extra field for each vertex) records * * (a) whether the vertex has been included in the domain, * * and, if so, * * (b) which other vertex (the "parent vertex") was * responsible for including it. * * The result is a tree structure, with the root at the * original triangle and the leaves at the perimeter. In * a moment we'll see how this tree is used to create the * longitudes and meridians. * * Once the domain fills the entire cusp, we check whether * there are any vertices of order one on the perimeter, and * if so we remove them by cancelling the adjacent * PerimeterPieces. * * Conceptually (but NOT in the code) we imagine removing * vertices of order two, thereby fusing the adjacent edges * into one. This must yield a fundamental domain which is * either a square or a hexagon. Here's the proof. Assume * a 2n-gon has pairs of sides glued so as to form a torus * or a Klein bottle, and assume all vertices have order * three or greater. Since the 2n-gon itself has 2n vertices, * there can be at most 2n/3 vertices in the resulting * cell-decomposition of the torus or Klein bottle. * Compute the Euler characteristic: * * 0 = Euler characteristic * = vertices - edges + faces * <= 2n/3 - n + 1 * = 1 - n/3 * * => n <= 3 * => the 2n-gon is a bigon, a square or a hexagon * * A case-by-case analysis reveals that the only possible * gluings are * * square hexagon * ______________________ * torus | abAB | abcABC | * |---------+------------| * Klein bottle | abAb | abcAcb | * | aabb | aabccB | * ---------------------- * * where the notation is what you would expect. I wish I could * provide an illustration of each gluing, but in platform- * independent text file this just isn't possible. So I ask * that you make your own illustration of each gluing. * * It is now straightforward to apply the definitions of the * longitude and meridian to each gluing. The details are * spelled out in the documentation contained in the function * find_meridian_and_longitude() and, especially, the functions * it calls. */ #include "kernel.h" typedef struct PerimeterPiece PerimeterPiece; struct extra { /* * Has this vertex been included in the fundamental domain? */ Boolean visited; /* * Which vertex of which tetrahedron is its parent in the * tree structure? * (parent_tet == NULL at the root.) */ Tetrahedron *parent_tet; VertexIndex parent_vertex; /* * Which side of this vertex faces the parent vertex? * Which side of the parent vertex faces this vertex? */ FaceIndex this_faces_parent, parent_faces_this; /* * What is the orientation of this vertex in the * fundamental domain? */ Orientation orientation; /* * Which PerimeterPiece, if any, is associated with * a given edge of the triangle at this vertex? * (As you might expect, its_perimeter_piece[i] refers * to the edge of the triangle contained in face i of * the Tetrahedron.) */ PerimeterPiece *its_perimeter_piece[4]; /* * When computing intersection numbers in * adjust_Klein_cusp_orientations() we want to allow for * the possibility that the Triangulation's scratch_curves * are already in use, so we copy them to scratch_curve_backup, * and restore them when we're done. */ int scratch_curve_backup[2][2][2][4][4]; }; struct PerimeterPiece { Tetrahedron *tet; VertexIndex vertex; FaceIndex face; Orientation orientation; /* How the PerimeterPiece sees the tetrahedron */ Boolean checked; PerimeterPiece *mate; /* the PerimeterPiece this one is glued to . . . */ GluingParity gluing_parity; /* . . . and how they match up */ PerimeterPiece *next; /* the neighbor in the counterclockwise direction */ PerimeterPiece *prev; /* the neighbor in the clockwise direction */ }; /* * The following enum lists the six possible gluing * patterns for a torus or Klein bottle. */ typedef int GluingPattern; enum { abAB, /* square torus */ abcABC, /* hexagonal torus */ abAb, /* standard square Klein bottle */ aabb, /* P^2 # P^2 square Klein bottle */ abcAcb, /* standard hexagonal Klein bottle */ aabccB /* P^2 # P^2 hexagonal Klein bottle */ }; static void zero_peripheral_curves(Triangulation *manifold); static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void initialize_flags(Triangulation *manifold); static Boolean cusp_has_curves(Triangulation *manifold, Cusp *cusp); static void do_one_cusp(Triangulation *manifold, Cusp *cusp); static void pick_base_tet(Triangulation *manifold, Cusp *cusp, Tetrahedron **base_tet, VertexIndex *base_vertex); static void set_up_perimeter(Tetrahedron *base_tet, VertexIndex base_vertex, PerimeterPiece **perimeter_anchor); static void expand_perimeter(PerimeterPiece *perimeter_anchor); static void find_mates(PerimeterPiece *perimeter_anchor); static void simplify_perimeter(PerimeterPiece **perimeter_anchor); static void find_meridian_and_longitude(PerimeterPiece *perimeter_anchor, CuspTopology *cusp_topology); static void advance_to_next_side(PerimeterPiece **pp); static GluingPattern determine_gluing_pattern(PerimeterPiece *side[6], int num_sides); static void do_torus(PerimeterPiece *side[6], int num_sides); static void do_standard_Klein_bottle(PerimeterPiece *side[6], int num_sides); static void do_P2P2_Klein_bottle(PerimeterPiece *side[6], int num_sides); static void trace_curve(PerimeterPiece *start, PeripheralCurve trace_which_curve, TraceDirection trace_direction, Boolean use_opposite_orientation); static void free_perimeter(PerimeterPiece *perimeter_anchor); static void adjust_Klein_cusp_orientations(Triangulation *manifold); static void reverse_meridians_where_necessary(Triangulation *manifold); static void backup_scratch_curves(Triangulation *manifold); static void restore_scratch_curves(Triangulation *manifold); void peripheral_curves( Triangulation *manifold) { Cusp *cusp; zero_peripheral_curves(manifold); attach_extra(manifold); initialize_flags(manifold); for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE) /* 97/2/4 Added to accomodate finite vertices. */ do_one_cusp(manifold, cusp); adjust_Klein_cusp_orientations(manifold); free_extra(manifold); } void peripheral_curves_as_needed( Triangulation *manifold) { /* * Add peripheral curves only to cusps for which all the * tet->curve[][][][] fields are zero. */ Cusp *cusp; attach_extra(manifold); initialize_flags(manifold); for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE && cusp_has_curves(manifold, cusp) == FALSE) do_one_cusp(manifold, cusp); adjust_Klein_cusp_orientations(manifold); free_extra(manifold); } static void zero_peripheral_curves( Triangulation *manifold) { Tetrahedron *tet; int i, j, k, l; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) tet->curve[i][j][k][l] = 0; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "peripheral_curves"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_ARRAY(4, Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->extra[v].visited = FALSE; } static Boolean cusp_has_curves( Triangulation *manifold, Cusp *cusp) { Tetrahedron *tet; VertexIndex v; FaceIndex f; PeripheralCurve c; Orientation h; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) for (f = 0; f < 4; f++) if (f != v) for (c = 0; c < 2; c++) /* c = M, L */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ if (tet->curve[c][h][v][f] != 0) return TRUE; return FALSE; } static void do_one_cusp( Triangulation *manifold, Cusp *cusp) { Tetrahedron *base_tet; VertexIndex base_vertex; PerimeterPiece *perimeter_anchor; pick_base_tet(manifold, cusp, &base_tet, &base_vertex); set_up_perimeter(base_tet, base_vertex, &perimeter_anchor); expand_perimeter(perimeter_anchor); find_mates(perimeter_anchor); simplify_perimeter(&perimeter_anchor); find_meridian_and_longitude(perimeter_anchor, &cusp->topology); free_perimeter(perimeter_anchor); } static void pick_base_tet( Triangulation *manifold, Cusp *cusp, Tetrahedron **base_tet, VertexIndex *base_vertex) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) { *base_tet = tet; *base_vertex = v; return; } /* * If pick_base_tet() didn't find any vertex belonging * to the specified cusp, we're in big trouble. */ uFatalError("pick_base_tet", "peripheral_curves"); } static void set_up_perimeter( Tetrahedron *base_tet, VertexIndex base_vertex, PerimeterPiece **perimeter_anchor) { int i; PerimeterPiece *pp[3]; base_tet->extra[base_vertex].visited = TRUE; base_tet->extra[base_vertex].parent_tet = NULL; base_tet->extra[base_vertex].orientation = right_handed; for (i = 0; i < 3; i++) pp[i] = NEW_STRUCT(PerimeterPiece); for (i = 0; i < 3; i++) { pp[i]->tet = base_tet; pp[i]->vertex = base_vertex; pp[i]->face = vt_side[base_vertex][i]; pp[i]->orientation = right_handed; pp[i]->checked = FALSE; pp[i]->next = pp[(i+1)%3]; pp[i]->prev = pp[(i+2)%3]; } *perimeter_anchor = pp[0]; } /* * expand_perimeter() starts with the initial triangular * perimeter found by set_up_perimeter() and expands it in * breadth-first fashion. It keeps going around and around * the perimeter, pushing it outwards wherever possible. * To know when it's done, it keeps track of the number * of PerimeterPieces which have not yet been been checked. * When this number is zero, it's done. */ static void expand_perimeter( PerimeterPiece *perimeter_anchor) { int num_unchecked_pieces; PerimeterPiece *pp, *new_piece; Permutation gluing; Tetrahedron *nbr_tet; VertexIndex nbr_vertex; FaceIndex nbr_back_face, nbr_left_face, nbr_right_face; Orientation nbr_orientation; for (num_unchecked_pieces = 3, pp = perimeter_anchor; num_unchecked_pieces; pp = pp->next) if (pp->checked == FALSE) { gluing = pp->tet->gluing[pp->face]; nbr_tet = pp->tet->neighbor[pp->face]; nbr_vertex = EVALUATE(gluing, pp->vertex); if (nbr_tet->extra[nbr_vertex].visited) { pp->checked = TRUE; num_unchecked_pieces--; } else { /* * Extend the tree to the neighboring vertex. */ nbr_back_face = EVALUATE(gluing, pp->face); if (parity[gluing] == orientation_preserving) nbr_orientation = pp->orientation; else nbr_orientation = ! pp->orientation; if (nbr_orientation == right_handed) { nbr_left_face = remaining_face[nbr_vertex][nbr_back_face]; nbr_right_face = remaining_face[nbr_back_face][nbr_vertex]; } else { nbr_left_face = remaining_face[nbr_back_face][nbr_vertex]; nbr_right_face = remaining_face[nbr_vertex][nbr_back_face]; } nbr_tet->extra[nbr_vertex].visited = TRUE; nbr_tet->extra[nbr_vertex].parent_tet = pp->tet; nbr_tet->extra[nbr_vertex].parent_vertex = pp->vertex; nbr_tet->extra[nbr_vertex].this_faces_parent = nbr_back_face; nbr_tet->extra[nbr_vertex].parent_faces_this = pp->face; nbr_tet->extra[nbr_vertex].orientation = nbr_orientation; /* * Extend the perimeter across the neighboring * vertex. The new PerimeterPiece is added on * the right side of the old one, so that the * pp = pp->next step in the loop moves us past * both the old and new perimeter pieces. This * causes the perimeter to expand uniformly in * all directions. */ new_piece = NEW_STRUCT(PerimeterPiece); new_piece->tet = nbr_tet; new_piece->vertex = nbr_vertex; new_piece->face = nbr_right_face; new_piece->orientation = nbr_orientation; new_piece->checked = FALSE; new_piece->next = pp; new_piece->prev = pp->prev; pp->prev->next = new_piece; pp->tet = nbr_tet; pp->vertex = nbr_vertex; pp->face = nbr_left_face; pp->orientation = nbr_orientation; pp->checked = FALSE; /* unchanged */ pp->next = pp->next; /* unchanged */ pp->prev = new_piece; /* * Increment the count of unchecked pieces. */ num_unchecked_pieces++; } } } static void find_mates( PerimeterPiece *perimeter_anchor) { PerimeterPiece *pp; Tetrahedron *nbr_tet; Permutation gluing; VertexIndex nbr_vertex; FaceIndex nbr_face; /* * First tell the tetrahedra about the PerimeterPieces. */ pp = perimeter_anchor; do { pp->tet->extra[pp->vertex].its_perimeter_piece[pp->face] = pp; pp = pp->next; } while (pp != perimeter_anchor); /* * Now let each PerimeterPiece figure out who its mate is. */ pp = perimeter_anchor; do { nbr_tet = pp->tet->neighbor[pp->face]; gluing = pp->tet->gluing[pp->face]; nbr_vertex = EVALUATE(gluing, pp->vertex); nbr_face = EVALUATE(gluing, pp->face); pp->mate = nbr_tet->extra[nbr_vertex].its_perimeter_piece[nbr_face]; pp->gluing_parity = (pp->orientation == pp->mate->orientation) == (parity[gluing] == orientation_preserving) ? orientation_preserving : orientation_reversing; pp = pp->next; } while (pp != perimeter_anchor); } static void simplify_perimeter( PerimeterPiece **perimeter_anchor) { PerimeterPiece *pp, *stop, *dead0, *dead1; /* * The plan here is to cancel adjacent edges of the form * * --o--->---o---<---o-- * * Travelling around the perimeter looking for such * edges is straightforward. For each PerimeterPiece (pp), * we check whether it will cancel with its lefthand * neighbor (pp->next). If it doesn't cancel, we advance * one step to the left (pp = pp->next). If it does cancel, * we move back one step to the right, to allow further * cancellation in case the previously cancelled edges were * part of a sequence * * . . . --o--->>---o--->---o---<---o---<<---o-- . . . * * One could no doubt devise a clever and efficient * way of deciding when to stop (readers are invited * to submit solutions), but to save wear and tear on * the programmer's brain, the present algorithm simply * keeps going until it has made a complete trip around * the perimeter without doing any cancellation. The * variable "stop" records the first noncancelling * PerimeterPiece which was encountered after the most * recent cancellation. Here's the loop in skeleton form: * * pp = perimeter_anchor; * stop = NULL; * * while (pp != stop) * { * if (pp cancels with its neighbor) * { * pp = pp->prev; * stop = NULL; * } * else (pp doesn't cancel with its neighbor) * { * if (stop == NULL) * stop = pp; * pp = pp->next; * } * } */ pp = *perimeter_anchor; stop = NULL; while (pp != stop) { /* * Check whether pp and the PerimeterPiece to * its left will cancel each other. */ if (pp->next == pp->mate && pp->gluing_parity == orientation_preserving) { /* * Note the addresses of the PerimeterPieces * which cancel . . . */ dead0 = pp; dead1 = pp->next; /* * . . . then remove them from the perimeter. */ dead0->prev->next = dead1->next; dead1->next->prev = dead0->prev; /* * Move pp back to the previous PerimeterPiece to * allow further cancellation. */ pp = dead0->prev; /* * Deallocate the cancelled PerimeterPieces. */ my_free(dead0); my_free(dead1); /* * We don't want to leave *perimeter_anchor * pointing to a dead PerimeterPiece, so set * it equal to a piece we know is still alive. */ *perimeter_anchor = pp; /* * We just did a cancellation, so set the * variable stop to NULL. */ stop = NULL; } else { /* * If this is the first noncancelling PerimeterPiece * after a sequence of one or more cancellations, * record its address in the variable stop. */ if (stop == NULL) stop = pp; /* * Advance to the next PerimeterPiece. */ pp = pp->next; } } } static void find_meridian_and_longitude( PerimeterPiece *perimeter_anchor, CuspTopology *cusp_topology) { PerimeterPiece *pp, *side[6]; int i, num_sides; /* * As explained in the documentation at the top of * this file, the fundamental domain for the cusp * will be either a square or a hexagon. Go around * the perimeter, recording the first PerimeterPiece * on each side of the square or hexagon. */ pp = perimeter_anchor; for (i = 0; i < 6; i++) { advance_to_next_side(&pp); side[i] = pp; } /* * Is it a square or a hexagon? */ if (side[0] == side[4]) num_sides = 4; else num_sides = 6; /* * Split into cases, according to how the square or * hexagon's edges are glued. The six types of gluings * are explained in the documentation at the top of * this file. */ switch (determine_gluing_pattern(side, num_sides)) { case abAB: case abcABC: do_torus(side, num_sides); *cusp_topology = torus_cusp; break; case abAb: case abcAcb: do_standard_Klein_bottle(side, num_sides); *cusp_topology = Klein_cusp; break; case aabb: case aabccB: do_P2P2_Klein_bottle(side, num_sides); *cusp_topology = Klein_cusp; break; } } /* * advance_to_next_side() advances the pointer *pp to * point to the first PerimeterPiece on the next side * of the square or hexagon, travelling counterclockwise. */ static void advance_to_next_side( PerimeterPiece **pp) { PerimeterPiece *p0, *p1; /* * Let p0 and p1 point to the given PerimeterPiece * and its mate. */ p0 = *pp; p1 = (*pp)->mate; /* * Move along the perimeter until p0 and p1 part company, * or until their relative orientation changes. */ if (p0->gluing_parity == orientation_preserving) do { p0 = p0->next; p1 = p1->prev; } while ( p0->mate == p1 && p0->gluing_parity == orientation_preserving); else /* (*pp)->gluing_parity == orientation_reversing */ do { p0 = p0->next; p1 = p1->next; } while ( p0->mate == p1 && p0->gluing_parity == orientation_reversing); /* * p0 now points to the first PerimeterPiece in the next * edge of the square or hexagon. Write its value into *pp. */ *pp = p0; } static GluingPattern determine_gluing_pattern( PerimeterPiece *side[6], int num_sides) { int i; /* * Please draw pictures of the six possible gluings * shown in the table in the documentation at the * top of this file. They will show that the logic * of this function, as summarized in the following * skeleton code, is correct. * * if (there is an orientation reversing side) * if (there are two adjacent, matching, orientation reversing sides) * if (num_sides == 4) * return(P^2 # P^2 square Klein bottle) * else (num_sides == 6) * return(P^2 # P^2 hexagonal Klein bottle) * else * if (num_sides == 4) * return(standard square Klein bottle) * else (num_sides == 6) * return(standard hexagonal Klein bottle) * else (all sides are orientation preserving) * if (num_sides == 4) * return(square torus) * else (num_sides == 6) * return(hexagonal torus) */ /* * Look for an orientation reversing side. * If one is found, check whether it's adjacent to its mate. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { if (side[i]->mate == side[(i+1)%num_sides] || side[i]->mate == side[(i-1+num_sides)%num_sides]) { if (num_sides == 4) return aabb; /* P^2 # P^2 square Klein bottle */ else return aabccB; /* P^2 # P^2 hexagonal Klein bottle */ } else { if (num_sides == 4) return abAb; /* standard square Klein bottle */ else return abcAcb; /* standard hexagonal Klein bottle */ } } /* * No orientation reversing side was found. * The surface is a torus. */ if (num_sides == 4) return abAB; /* square torus */ else return abcABC; /* hexagonal torus */ } static void do_torus( PerimeterPiece *side[6], int num_sides) { /* * The following calls to trace_curve() will always produce * a meridian and longitude which intersect exactly once. * If the manifold is orientable, they will adhere to the * orientation convention described at the top of this file. * (The proof of this relies on the fact that * set_up_perimeter() views the base vertex with the * right_handed orientation. Thus in an oriented manifold * all vertices are viewed with the right_handed orientation.) */ trace_curve(side[0], L, trace_backwards, FALSE); trace_curve(side[0]->mate, L, trace_forwards, FALSE); trace_curve(side[1], M, trace_backwards, FALSE); trace_curve(side[1]->mate, M, trace_forwards, FALSE); } static void do_standard_Klein_bottle( PerimeterPiece *side[6], int num_sides) { int i; /* * Let the meridian connect the unique pair of * orientation_preserving sides. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_preserving) { trace_curve(side[i], M, trace_backwards, FALSE); trace_curve(side[i]->mate, M, trace_forwards, FALSE); break; } /* * Let the longitude connect a pair of orientation_reversing * sides. Store it as its complete preimage in the double * cover, as explained in the documentation at the top of * this file. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { trace_curve(side[i], L, trace_backwards, FALSE); trace_curve(side[i]->mate, L, trace_forwards, FALSE); trace_curve(side[i], L, trace_backwards, TRUE); trace_curve(side[i]->mate, L, trace_forwards, TRUE); break; } } static void do_P2P2_Klein_bottle( PerimeterPiece *side[6], int num_sides) { int i; PerimeterPiece *side0a, *side0b, *side1a, *side1b; /* * Let the longitude connect either pair of * orientation_reversing sides, and store it as its * complete preimage in the double cover, as in * do_standard_Klein_bottle() above. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { trace_curve(side[i], L, trace_backwards, FALSE); trace_curve(side[i]->mate, L, trace_forwards, FALSE); trace_curve(side[i], L, trace_backwards, TRUE); trace_curve(side[i]->mate, L, trace_forwards, TRUE); break; } /* * The meridian is trickier. If you refer to pictures of * the aabb and aabccB Klein bottles and the definition * of the meridian, you will see the meridian is obtained * by gluing each orientation reversing side to the mate * of the opposite side of the square or hexagon. For * global consistency, the two pieces of the meridian * must be drawn on different preimages of the fundamental * domain (in the torus double cover). */ /* * Look for a side followed by its mate. */ for (i = 0; i < num_sides; i++) if (side[i]->mate == side[(i+1)%num_sides]) { /* * Name the four relevant sides. */ side0a = side[ i ]; side0b = side[(i + 1) %num_sides]; side1a = side[(i + num_sides/2)%num_sides]; side1b = side[(i + 1 + num_sides/2)%num_sides]; /* * Trace out the meridian. */ trace_curve(side0a, M, trace_backwards, FALSE); trace_curve(side1b, M, trace_forwards, FALSE); trace_curve(side1a, M, trace_backwards, TRUE); trace_curve(side0b, M, trace_forwards, TRUE); break; } } /* * trace_curve() traces out a curve on a cusp, beginning * at start and following the tree structure in Extra * back to the base vertex. The result is written directly * into the Tetrahedra's meridian or longitude fields, * according to whether trace_which_curve is M * or L. The curve is directed toward the * perimeter if trace_direction is trace_backwards, and * toward the base vertex if trace_direction is trace_forwards. * The orientation specified by start is used iff * use_opposite_orientation is FALSE. * * To trace a curve from one point on the perimeter to another, * you make two calls to trace_curve(), each of which traces * from the perimeter to the center. Note that some cancellation * is possible, so the final curve need not pass through the * center. The final curve will be the unique shortest path * in the tree structure. */ static void trace_curve( PerimeterPiece *start, PeripheralCurve trace_which_curve, TraceDirection trace_direction, Boolean use_opposite_orientation) { int out_sign, in_sign, (*curve)[4][4]; Tetrahedron *tet, *next_tet; VertexIndex vertex, next_vertex; Extra *tet_extra, *next_extra; /* * Based on the direction of the curve, decide which * sign (+1 or -1) is required where the curve leaves * a triangle going towards the perimeter, and which * is required where it is going towards the center. */ if (trace_direction == trace_backwards) { out_sign = -1; in_sign = +1; } else { out_sign = +1; in_sign = -1; } /* * Record where the curve hits the perimeter. */ curve = start->tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ start->orientation] [start->vertex] [start->face] += out_sign; /* * Now trace back to the root. */ tet = start->tet; vertex = start->vertex; tet_extra = &tet->extra[vertex]; while (tet_extra->parent_tet != NULL) { /* * Note where the curve leaves the present vertex . . . */ curve = tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ tet_extra->orientation] [vertex] [tet_extra->this_faces_parent] += in_sign; /* * . . . and where it enters the parent vertex. */ next_tet = tet_extra->parent_tet; next_vertex = tet_extra->parent_vertex; next_extra = &next_tet->extra[next_vertex]; curve = next_tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ next_extra->orientation] [next_vertex] [tet_extra->parent_faces_this] += out_sign; /* * Move on to the parent vertex. */ tet = next_tet; vertex = next_vertex; tet_extra = next_extra; } } static void free_perimeter( PerimeterPiece *perimeter_anchor) { PerimeterPiece *pp, *dead; pp = perimeter_anchor; do { dead = pp; pp = pp->next; my_free(dead); } while (pp != perimeter_anchor); } static void adjust_Klein_cusp_orientations( Triangulation *manifold) { /* * As explained at the top of this file, a Cusp's peripheral curves * live in the its orientation double cover. When I first wrote this * file, I didn't worry about the orientation of peripheral curves * in nonorientable manifolds. Subsequently it became clear that * they should have the standard orientation relative to the Cusp's * (oriented, not just orientable) orientation double cover. In * the case of a torus Cusp, the peripheral curves live in one * (arbitrarily chosen) component of the orientation double cover; * in the case of a Klein bottle Cusp, the orientation double cover * is connected. * * Fortunately, it's very easy to check whether the peripheral curves * have the standard orientation, and to correct them if necessary. * The definition of the standard orientation for peripheral curves on * a torus is that when the fingers of your right hand point in the * direction of the meridian and your thumb points in the direction * of the longitude, the palm of your hand should face the cusp and * the back of your hand should face the fat part of the manifold. * Combining this with the definition of the intersection number * found at the top of intersection_numbers.c reveals that the * intersection number of the longitude and the meridian (in that * order) should be +1. If it happens to be -1, we must reverse * the meridian. */ /* * If the manifold is oriented, then the peripheral curves will * already have the correct orientation. In fact, they will lie * on the right handed sheet of the Cusp's orientation double * cover, relative to the orientation of the manifold. */ if (manifold->orientability == oriented_manifold) return; /* * The scratch curves might already be in use, so let's make * a copy of whatever's there. */ backup_scratch_curves(manifold); /* * Copy the peripheral curves to both sets of scratch_curve fields. */ copy_curves_to_scratch(manifold, 0, FALSE); copy_curves_to_scratch(manifold, 1, FALSE); /* * Compute their intersection numbers. */ compute_intersection_numbers(manifold); /* * Restore whatever used to be in the scratch_curves. */ restore_scratch_curves(manifold); /* * On Cusps where the intersection number of the longitude and * meridian is -1, reverse the meridian. */ reverse_meridians_where_necessary(manifold); } static void reverse_meridians_where_necessary( Triangulation *manifold) { Tetrahedron *tet; int i, j, k; /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) if (tet->cusp[i]->intersection_number[L][M] == -1) /* which side of the vertex */ for (j = 0; j < 4; j++) if (i != j) /* which sheet (right_handed or left_handed) */ for (k = 0; k < 2; k++) tet->curve[M][k][i][j] = - tet->curve[M][k][i][j]; } static void backup_scratch_curves( Triangulation *manifold) { Tetrahedron *tet; int g, h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (g = 0; g < 2; g++) for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->extra->scratch_curve_backup[g][h][i][j][k] = tet->scratch_curve[g][h][i][j][k]; } static void restore_scratch_curves( Triangulation *manifold) { Tetrahedron *tet; int g, h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (g = 0; g < 2; g++) for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->scratch_curve[g][h][i][j][k] = tet->extra->scratch_curve_backup[g][h][i][j][k]; } snappea-3.0d3/SnapPeaKernel/code/polyhedral_group.c0100444000175000017500000006745206742675502020443 0ustar babbab/* * polyhedral_group.c * * This file provides the function * * Boolean is_group_polyhedral(SymmetryGroup *the_group); * * which symmetry_group.c uses to recognize triangle groups. * is_group_polyhedral() fills in the the_group's is_polyhedral field, * and if it's TRUE it fills in the is_binary_group, p, q and r fields to * completely specify the triangle group. * * is_group_polyhedral() assumes that the_group's order, product[][], * order_of_element[] and inverse[] fields have been set. (Also the * is_dihedral field -- see below.) * * Note: is_group_polyhedral() assumes that the_group's is_dihedral * field has already been set. (It would be trivially easy to have * is_group_polyhedral() check for dihedral groups. To do so, just * call is_triangle_group(the_group, 2, 2, the_group->order/2, FALSE) * near the end of is_group_polyhedral(). I don't currently do this * because the special-purpose function is_group_dihedral() in * symmetry_group.c not only recognizes the group, but orders the elements * in a natural way as well.) * * * The remainder of this comment defines the "extended polyhedral * groups" and the "binary polyhedral groups", and explains why the * extended groups needn't be considered in the code. I've also included * the code (no longer used) for recognizing extended groups directly * from a presentation (at the time I wrote it I didn't realize the * extended groups aren't necessary.) * * Throughout the following discussion, "polyhedral" may refer to dihedral * as well as tetrahedral, octahedral and icosahedral groups. The polygon * corresponding to the dihedral group Dn is a regular n-sided pillow. * (It won't appear quite so degenerate if you think of polyhedra as * tilings of the 2-sphere, rather than the traditional things with * planar faces.) * * Definitions: * * A plain polyhedral group consists of all orientation-preserving * isometries of the polyhedron. * * An extended polyhedral group consists of all isometries of the * polyhedron, including the orientation-reversing ones. * * An orientation-preserving isometry is an element of SO(3), so a * plain polyhedral group is naturally a subgroup of SO(3). SO(3) is * double covered by S^3, and the preimage in S^3 of the plain * polyhedral group is called the binary polyhedral group. Just as * one may visualize a plain polyhedral group as the positions of * a plain polyhedron, one may visualize a binary polyhedral group * as the positions of a polyhedron with a "Dirac belt" attached. * Whenever two distinct elements of the binary polyhedral group * project down to the same element of the plain polyhedral group, * they will correspond to polyhedra in identical positions, but * with belts differing by an odd number of turns. * * Theorem A. An extended dihedral group is isomorphic to the * corresponding plain dihedral group cross Z/2. * * Theorem B. The extended octahedral and icosahedral groups are * isomorphic to the plain octahedral and isosahedral group cross Z/2. * * Theorem C. The extended tetrahedral group is isomorphic is the * plain octahedral group. * * Taken together, Theorems A, B and C imply that SnapPea's group * recognition algorithm need not bother with the extended group -- * the plain and binary groups suffice. * * Proof of Theorem A. Ordinarily we visualize the plain dihedral group * as acting on an n-sided pillow, but we may also visualize it as * acting on the pillow's equatorial cross section (which is an n-gon). * When acting on the pillow the plain dihedral group preserves orientation, * but when acting on the n-gon it does not. It's easy to see that the * extended dihedral group (acting on the pillow) is the direct product * of the plain dihedral group (acting on the n-gon) cross Z/2 (acting * on the pillows vertical axis). This follows from the fact that the * up-down reflections are completely independent of the group's action * on the equatorial n-gon. Algebraically, all elements in the * extended dihedral group have matrices of the form * * * * 0 * * * 0 * 0 0 +-1 * * The two factors consist of matrices of the form * * * * 0 1 0 0 * * * 0 and 0 1 0 * 0 0 1 0 0 +-1 * Q.E.D. * * Proof of Theorem B. Let G be either the extended octahedral group * or the extended icosahedral group. We'll define an isomorphism to * the plain octahedral or icosahedral group cross Z/2. Let R be inversion * through the origin. Note that both the octahedron and the icosahedron * are invariant under R (but the tetrahedron is not, which is why it's * not included here). Note too that R commutes with all elements of G * (proof: it's matrix is minus the identity). * * Define f(g) = ( g, 0) if g preserves orientation, * = (gR, 1) if g reverses orientation. * * To prove that f is an isomorphism, we must prove that it is a * homomorphism, it's one-to-one, and it's onto. * * To prove that f is a homomorphism, we must show that for all g and h * in G, f(gh) = f(g)f(h). We check the four possible cases: * * g preserves orientation and h preserves orientation. * f(gh) = (gh, 0) * f(g)f(h) = (g, 0)(h, 0) = (gh, 0) * * g preserves orientation and h reverses orientation. * f(gh) = (ghR, 1) * f(g)f(h) = (g, 0)(hR, ) = (ghR, 1) * * g reverses orientation and h preserves orientation. * f(gh) = (ghR, 0) * f(g)f(h) = (gR, 1)(h, 0) = (gRh, 1) = (ghR, 1) * * g reverses orientation and h reverses orientation. * f(gh) = (gh, 0) * f(g)f(h) = (gR, 1)(hR, 1) = (gRhR, 0) = (gh, 0) * * To prove that f is one-to-one, assume f(g) = f(h). If f(g) and f(h) * have second component 0, then (g, 0) = f(g) = f(h) = (h, 0) => g = h. * Otherwise (gR, 1) = f(g) = f(h) = (hR, 1) => gR = hR => g = h. * * To prove that f is onto, note that each element (g, 0) in the range * is f(g), while each element (g, 1) is f(gR). * * Q.E.D. * * Proof of Theorem C. We will define an isomorphism from the extended * tetrahedral group to the plain octahedral group. * * The proof relies on the following image which you should draw * or -- better still -- build with polydrons. Make a regular octahedron * with blue and red faces alternating checkerboard-style. Now attach * a blue tetrahedron to each blue face of the octahedron; the easiest * way to do this is to remove each existing blue face and replace it * three blue triangles which have been snapped together to form the * three new faces of the tetrahedron you're attaching. When you're * done you'll have a large tetrahedron (edge length two), each face * of which consists of three blue triangles surrounding a red one. * Throughout this proof you should imagine the original octahedron * sitting inside the large tetrahedron. * * We will define a map f from the extended group of isometries of the * large tetrahedon to the plain group of isometries of the original * octahedron. * * R will be the central inversion, as in the proof of Theorem B. * It commutes with all elements of SO(3). * * Let f(g) = g if g preserves orientation, * = gR if g reverses orientation. * * To prove that f is an isomorphism, we must prove that it is a * homomorphism, it's one-to-one, and it's onto. * * To prove that f is a homomorphism, we must show that for all g and h, * f(gh) = f(g)f(h). We check the four possible cases: * * g preserves orientation and h preserves orientation. * f(gh) = gh * f(g)f(h) = gh * * g preserves orientation and h reverses orientation. * f(gh) = (gh)R * f(g)f(h) = g(hR) * * g reverses orientation and h preserves orientation. * f(gh) = ghR * f(g)f(h) = (gR)h = ghR * * g reverses orientation and h reverses orientation. * f(gh) = gh * f(g)f(h) = (gR)(hR) = gh * * To prove that f is one-to-one, assume f(g) = f(h). If f(g) and f(h) * preserve the red-blue checkboard coloring of the octahedron, then * g and h are orientation-preserving and g = f(g) = f(h) = h. * If f(g) and f(h) reverse the coloring, then g and h are orientation * reversing and gR = f(g) = f(h) = hR => g = h. * * To prove that f is onto, note that each element g which preserves the * octahedron's coloring is f(g) for the orientation-preserving element g, * while each element g which reverses the coloring is f(gR) for the * orientation-reversing element gR. * * Q.E.D. * * * Here's the code which used to check for extended polyhedral groups. */ #if 0 /* beginning of unused code */ static Boolean is_binary_triangle_group( SymmetryGroup *the_group, int p, int q, int r) { /* * The binary (p,q,r) triangle group is like the plain one defined * in is_plain_triangle_group() above, except that all symmetries * of the labelled tiling are allowed, including the orientation- * reversing ones. * * Theorem. The binary (p,q,r) triangle group has presentation * * = {a,b,c | (ab)^p = (bc)^q = (ca)^r = a^2 = b^2 = c^2 = 1} * * Proof. The proof is similar to the proof for the plain case * in is_plain_triangle_group() above, so here I will just point * out the differences. * The first difference is that the generators a, b and c have * a different interpretation. Here a, b and c are reflections in * the three sides of the triangle. * As before, we pick a base triangle. But unlike the plain case, * there is no need to color the triangles blue and red. Because * orientation reversing symmetries are allowed, every triangle is a * potential image of the base triangle, so every triangle is colored * blue and has a dot in the middle to represent the corresponding * symmetry. * There will now be two arcs connecting the centers of each pair * of adjacent triangles. One arc points in each direction. The * directions are chosen so that the arcs form a clockwise-directed * bigon. That is, they pass each other like cars in Japan or * Australia, not cars in Italy or the U.S. * The arcs divide the surface (i.e. the sphere, Euclidean plane or * hyperbolic plane, depending on the group) into four types of regions: * * (1) 2p-gons labelled "abab...", oriented counterclockwise * (2) 2q-gons labelled "bcbc...", oriented counterclockwise * (3) 2r-gons labelled "caca...", oriented counterclockwise * (4) bigons labelled "aa", "bb" or "cc", oriented clockwise * * (Line (3) is neither offensive nor amusing to speakers of English.) * * The remainder of the proof is identical to that of the plain case. * Q.E.D. * * Note: As in the plain case, we have shown that in the abstract * group {a,b,c | (ab)^p = (bc)^q = (ca)^r = a^2 = b^2 = c^2 = 1}, * ab, ba and ca have order exactly p, q and r, respectively, and * a, b and c each have order exactly 2. */ int a, b, c, ab, bc, ca, i, possible_generators[3]; /* * Consider all possible images of the generator a in the_group. */ for (a = 0; a < the_group->order; a++) { /* * If a does not have order 2, ignore it and move on. */ if (the_group->order_of_element[a] != 2) continue; /* * Consider all possible images of the generator b in the_group. */ for (b = 0; b < the_group->order; b++) { /* * If b does not have order 2, ignore it and move on. */ if (the_group->order_of_element[b] != 2) continue; /* * If the product ab does not have order p, move on. */ ab = the_group->product[a][b]; if (the_group->order_of_element[ab] != p) continue; /* * Consider all possible images of the generator c in the_group. */ for (c = 0; c < the_group->order; c++) { /* * If c does not have order 2, ignore it and move on. */ if (the_group->order_of_element[c] != 2) continue; /* * If the product bc does not have order q, move on. */ bc = the_group->product[b][c]; if (the_group->order_of_element[bc] != q) continue; /* * If the product ca does not have order r, move on. */ ca = the_group->product[c][a]; if (the_group->order_of_element[ca] != r) continue; /* * At this point we know * (ab)^p = (bc)^q = (ca)^r = a^2 = b^2 = c^2 = 1. * We have a homomorphism from the binary (p,q,r) triangle * group to the_group. It will be an isomorphism iff * a, b and c generate the_group. */ /* * Write a, b and c into an array . . . */ possible_generators[0] = a; possible_generators[1] = b; possible_generators[2] = c; /* * . . . and pass the array to the function which checks * whether they generate the group. */ if (elements_generate_group(the_group, 3, possible_generators) == TRUE) return TRUE; /* * If a, b and c failed to generate the_group, we continue * on with the hope that some other choice of a, b and c * will work. */ } } } return FALSE; } #endif /* end of unused code */ #include "kernel.h" static Boolean is_triangle_group(SymmetryGroup *the_group, int p, int q, int r, Boolean check_binary_group); static Boolean is_plain_triangle_group(SymmetryGroup *the_group, int p, int q, int r); static Boolean is_binary_triangle_group(SymmetryGroup *the_group, int p, int q, int r); Boolean is_group_polyhedral( SymmetryGroup *the_group) { /* * By the time this function is called, we should have already checked * whether the group is dihedral. If it is, just fill in the * required information and return TRUE. */ if (the_group->is_dihedral == TRUE) { the_group->is_polyhedral = TRUE; the_group->is_binary_group = FALSE; the_group->p = 2; the_group->q = 2; the_group->r = the_group->order / 2; return TRUE; } /* * Is this a tetrahedral, octahedral or isocahedral group? */ switch (the_group->order) { case 12: /* * Is it the plain tetrahedral group? */ if (is_triangle_group(the_group, 2, 3, 3, FALSE)) return TRUE; break; case 24: /* * Is it the binary tetrahedral group? */ if (is_triangle_group(the_group, 2, 3, 3, TRUE)) return TRUE; /* * Is it the plain octahedral group? */ if (is_triangle_group(the_group, 2, 3, 4, FALSE)) return TRUE; break; case 48: /* * Is it the binary octahedral group? */ if (is_triangle_group(the_group, 2, 3, 4, TRUE)) return TRUE; break; case 60: /* * Is it the plain icosahedral group? */ if (is_triangle_group(the_group, 2, 3, 5, FALSE)) return TRUE; break; case 120: /* * Is it the binary icosahedral group? */ if (is_triangle_group(the_group, 2, 3, 5, TRUE)) return TRUE; break; } /* * Is this a binary dihedral group? */ if (the_group->order % 4 == 0) if (is_triangle_group(the_group, 2, 2, the_group->order/4, TRUE)) return TRUE; /* * None of the above. */ the_group->is_binary_group = FALSE; the_group->p = 0; the_group->q = 0; the_group->r = 0; return FALSE; } static Boolean is_triangle_group( SymmetryGroup *the_group, int p, int q, int r, Boolean check_binary_group) { the_group->is_polyhedral = (check_binary_group == TRUE) ? is_binary_triangle_group(the_group, p, q, r) : is_plain_triangle_group(the_group, p, q, r); if (the_group->is_polyhedral == TRUE) { the_group->is_binary_group = check_binary_group; the_group->p = p; the_group->q = q; the_group->r = r; } return the_group->is_polyhedral; } static Boolean is_plain_triangle_group( SymmetryGroup *the_group, int p, int q, int r) { /* * A (p,q,r) triangle is a triangle with angles pi/p, pi/q and pi/r. * The triangle will be spherical, Euclidean or hyperbolic according * to whether the sum of the angles ( = pi(1/p + 1/q + 1/r) ) is * greater than, equal to, or less than pi. Imagine tiling a sphere, * Euclidean plane, or hyperbolic plane (according to the geometry * of the triangle) with (p,q,r) triangles by starting with one such * triangle and recursively reflecting it across its own sides. * The (p,q,r) triangle group is the group of orientation-preserving * symmetries which preserve the tiling. If the triangles are colored * red and blue checkerboard-fashion, the orientation-preserving * symmetries will preserve the coloring. (N.B. The group preserves * labelled triangles. If the triangles have "extra" symmetries -- * such as in the (4,4,4) triangle group -- we exclude them from * consideration.) * * Theorem. The (p,q,r) triangle group has presentation * * (p,q,r) = {a,b,c | a^p = b^q = c^r = abc = 1} * * Proof. The generators a, b and c represent counterclockwise * rotations of 2pi/p, 2pi/q and 2pi/r, respectively, about the * vertices A, B and C of a (p,q,r) triangle. The vertices A, B and C * are arranged in counterclockwise order around the triangle. * To understand this proof, it will be very useful to make * yourself a picture, as follows. Draw a portion of a tiling by * (p,q,r) triangles, colored blue and red, checkerboard fashion. * We'll work mainly with the blue triangles, ignoring the red ones. * Pick one blue triangle to be the "base triangle", and label its * vertices A, B and C, going counterclockwise. The blue triangles * are in one-to-one correspondence with the elements of the (p,q,r) * triangle group: each symmetry is associated with the blue triangle * to which it maps the base triangle. Put a dot in the middle of * each blue triangle to represent the associated symmetry. * Each vertex which is equivalent (under the symmetry group) to * vertex A will be incident to exactly p blue triangles. * Connect the dots at the centers of these p blue triangles with * arcs, in a cyclic fashion; that is, an oriented arc runs from * the center of each incident blue triangle to the center of the * next incident blue triangle going counterclockwise. Label the * arcs with the letter "a", and include arrows to show the direction. * Do the same for all vertices equivalent vertices B and C. * When you are done, you will have divided the surface (i.e. the * sphere, Euclidean plane or hyperbolic plane, depending on the * group) into four types of regions: * * (1) p-gons labelled "a", oriented counterclockwise * (2) q-gons labelled "b", oriented counterclockwise * (3) r-gons labelled "c", oriented counterclockwise * (4) triangles labelled a-b-c, oriented clockwise * * (One a-b-c triangle sits over each red triangle in the tiling.) * We'll soon see that these four types of regions give us the * relations in the group. * * We are now prepared to define a map from the abstract group * * {a,b,c | a^p = b^q = c^r = abc = 1} * * into the (p,q,r) triangle group. * * (0) Definition of map. The generators a, b and c map to the * counterclockwise rotations of 2pi/p, 2pi/r and 2pi/q about * the vertices A, B and C of the base triangle. (And their * inverses map to clockwise rotations, of course). * * (1) The map is a homomorphism from the free group on {a,b,c} to * the (p,q,r) triangle group. This part of the proof relies on * the convention that symmetries are performed right-to-left. * For example, the word "abc" means "do symmetry c, then b, * then a". It's easy to see that the image of the base triangle * under the symmetry abc is the may be found by beginning at the * base triangle and following the (forward pointing) arcs labelled * "a", then "b", then "c". It then follows that the product of * two symmetries, e.g. abc * baac = abcbaac, is obtained by * concatenating the corresponding arc-paths. If inverses of * generators were involved, we'd go against the direction of * the arrows. * * (2) The kernel of the map from the free group on {a,b,c} to the * (p,q,r) triangle group includes the relations * a^p = b^q = c^r = abc = 1. This is trivial -- just trace out * the words a^p, b^q, c^r and abc in your picture, and note that * all four are closed loops. This tells us that the map defined * on the free group on {a,b,c} projects to a map which is well- * defined on the quotient group {a,b,c | a^p = b^q = c^r = abc = 1}. * (It also explains why the four regions listed above give us the * relations in the group presentation.) * * (3) The quotient map is onto. This follows immediately from the * fact that the graph (of dots connected by "a", "b" and "c" arcs) * is connected. * * (4) The quotient map is one-to-one. This follows from the fact * that the underlying space (the sphere, Euclidean plane or * hyperbolic plane) is simply connected. A word, e.g. * "a(b^-1)caccb(c^-1)", maps to the identity iff the corresponding * path in your picture is a closed loop. First consider the * special case that it's a simple closed loop. The closed loop * bounds some number of regions (cf. types (1)-(4) above). * The corresponding relations may be conjugated and multiplied * to show that the given word lies in the normal subgroup generated * by a^p = b^q = c^r = abc = 1. If the loop is not simple, we * use the preceding technique to remove subloops which are simple, * until the whole word has been shown to be trivial. (Yes, I * realize this explanation is light on details, but I hope the * idea is clear.) * * (5) The map is an isomorphism. This follows from (3) and (4). * * Q.E.D. * * Note: The above proof shows not only that the abstract group * {a,b,c | a^p = b^q = c^r = abc = 1} is isomorphic to the * (p,q,r) triangle group. It also shows that the generators * a, b and c have order exactly p, q and r. (This is not obvious * from the presentation alone). We'll use this fact in the code * below. * * * Getting back to the computational problem at hand . . . * * We seek an isomorphism from {a,b,c | a^p = b^q = c^r = abc = 1} to * the_group. An isomorphism is given by specifying the images of the * generators a, b and c. Naively one would try all possibilities * for the images of a, b and c, and in each case check whether * the relations are satisfied. This would be a cubic time algorithm, * as a function of the size of the group. We can reduce this to a * quadratic time algorithm by considering all possible images of * a and b, and in each case letting c = (ab)^-1. The algorithm is * further streamlined by mapping a and b only to elements of order * p and q, respectively. * * Once we find images for a, b and c satisfying the relations * a^p = b^q = c^r = abc = 1 we know we have a homomorphism from * {a,b,c | a^p = b^q = c^r = abc = 1} to the_group. Because the * function is_group_polyhedral() insures that the_group has the same * order as the (p,q,r) triangle group, to check whether the * homomorphism is an isomorphism it suffices to check that it is onto. * * In the following code, the variables "a", "b" and "c" represent * the images of a, b and c in the_group. */ int a, b, c, possible_generators[3]; /* * Consider all possible images of the generator a in the_group. */ for (a = 0; a < the_group->order; a++) { /* * If a does not have order p, ignore it and move on. */ if (the_group->order_of_element[a] != p) continue; /* * Consider all possible images of the generator b in the_group. */ for (b = 0; b < the_group->order; b++) { /* * If b does not have order q, ignore it and move on. */ if (the_group->order_of_element[b] != q) continue; /* * The relation abc = 1 will be satisfied iff c = (ab)^-1. */ c = the_group->inverse[the_group->product[a][b]]; /* * c should have order r. */ if (the_group->order_of_element[c] != r) continue; /* * At this point we know a^p = b^q = c^r = abc = 1. * We have a homomorphism from the (p,q,r) triangle group * to the_group. It will be an isomorphism iff a, b and c * generate the_group. */ /* * Write a, b and c into an array . . . */ possible_generators[0] = a; possible_generators[1] = b; possible_generators[2] = c; /* * . . . and pass the array to the function which checks * whether they generate the group. */ if (elements_generate_group(the_group, 3, possible_generators) == TRUE) return TRUE; /* * If a, b and c failed to generate the_group, we continue on * with the hope that some other choice of a and b will work. */ } } return FALSE; } static Boolean is_binary_triangle_group( SymmetryGroup *the_group, int p, int q, int r) { /* * I don't yet know how to prove that the following presentation * for the binary triangle group is correct, but I want to get * the code up and running now anyhow. (Pat Callahan found the * presentation in a book and e-mailed it to me, so I'm pretty * sure it's correct.) * * Theorem (relayed by Pat). The binary triangle group has * presentation * * = {a,b,c | a^p = b^q = c^r = abc} * * Theorem (modified by me). The binary triangle group has * presentation * * = {a,b,c,d | a^p = b^q = c^r = abc = d, d^2 = 1} * * The element d is the identity map from the polyhedron to itself, * but with a single full turn in "the belt". Thus d has order * exactly 2, while a, b and c have orders exactly 2p, 2r and 2q, * respectively. * * Proof: ??? * [In the spherical case one ought to be able to prove this by * mapping a, b and c to the appropriate quaternions. JRW 96/2/6] * * Also, I'm not sure to what extent this presentation -- and my * added commentary -- is meaningful in the Euclidean and hyperbolic * cases. Probably it works there too, but I don't really know. * * I need to find a copy of Coxeter & Moser's "Generators and Relations * for Discrete Groups". * * In the following code, the variables "a", "b" and "c" represent * the images of a, b and c in the_group. */ int a, b, c, ap, bq, cr, count, possible_generators[3]; /* * Consider all possible images of the generator a in the_group. */ for (a = 0; a < the_group->order; a++) { /* * If a does not have order 2p, ignore it and move on. */ if (the_group->order_of_element[a] != 2*p) continue; /* * Compute a^p. */ ap = 0; for (count = 0; count < p; count++) ap = the_group->product[ap][a]; /* * Consider all possible images of the generator b in the_group. */ for (b = 0; b < the_group->order; b++) { /* * If b does not have order 2q, ignore it and move on. */ if (the_group->order_of_element[b] != 2*q) continue; /* * Compute b^q. */ bq = 0; for (count = 0; count < q; count++) bq = the_group->product[bq][b]; /* * If a^p != b^q, move on. */ if (ap != bq) continue; /* * The relation abc = a^p will be satisfied * iff c = (ab)^-1 a^p. */ c = the_group->product [the_group->inverse[the_group->product[a][b]]] [ap]; /* * c should have order 2r. */ if (the_group->order_of_element[c] != 2*r) continue; /* * Compute c^r. */ cr = 0; for (count = 0; count < r; count++) cr = the_group->product[cr][c]; /* * If a^p != c^r, move on. */ if (ap != cr) continue; /* * At this point we know a^p = b^q = c^r = abc. * We have a homomorphism from the (p,q,r) triangle group * to the_group. It will be an isomorphism iff a, b and c * generate the_group. */ /* * Write a, b and c into an array . . . */ possible_generators[0] = a; possible_generators[1] = b; possible_generators[2] = c; /* * . . . and pass the array to the function which checks * whether they generate the group. */ if (elements_generate_group(the_group, 3, possible_generators) == TRUE) return TRUE; /* * If a, b and c failed to generate the_group, we continue on * with the hope that some other choice of a and b will work. */ } } return FALSE; } snappea-3.0d3/SnapPeaKernel/code/positioned_tet.c0100444000175000017500000000546006742675502020104 0ustar babbab/* * positioned_tet.c * * This file provides the following functions for working with PositionedTets: * * void veer_left(PositionedTet *ptet); * void veer_right(PositionedTet *ptet); * void veer_backwards(PositionedTet *ptet); * Boolean same_positioned_tet(PositionedTet *ptet0, PositionedTet *ptet1); * void set_left_edge(EdgeClass *edge, PositionedTet *ptet); * * Their use is described in kernel_prototypes.h. */ #include "kernel.h" void veer_left( PositionedTet *ptet) { Permutation left_gluing; FaceIndex temp; left_gluing = ptet->tet->gluing[ptet->left_face]; ptet->tet = ptet->tet->neighbor[ptet->left_face]; temp = ptet->near_face; ptet->near_face = EVALUATE(left_gluing, ptet->left_face); ptet->left_face = EVALUATE(left_gluing, temp); ptet->right_face = EVALUATE(left_gluing, ptet->right_face); ptet->bottom_face = EVALUATE(left_gluing, ptet->bottom_face); if (parity[left_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } void veer_right( PositionedTet *ptet) { Permutation right_gluing; FaceIndex temp; right_gluing = ptet->tet->gluing[ptet->right_face]; ptet->tet = ptet->tet->neighbor[ptet->right_face]; temp = ptet->near_face; ptet->near_face = EVALUATE(right_gluing, ptet->right_face); ptet->right_face = EVALUATE(right_gluing, temp); ptet->left_face = EVALUATE(right_gluing, ptet->left_face); ptet->bottom_face = EVALUATE(right_gluing, ptet->bottom_face); if (parity[right_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } void veer_backwards( PositionedTet *ptet) { Permutation near_gluing; FaceIndex temp; near_gluing = ptet->tet->gluing[ptet->near_face]; ptet->tet = ptet->tet->neighbor[ptet->near_face]; temp = ptet->left_face; ptet->left_face = EVALUATE(near_gluing, ptet->right_face); ptet->right_face = EVALUATE(near_gluing, temp); ptet->near_face = EVALUATE(near_gluing, ptet->near_face); ptet->bottom_face = EVALUATE(near_gluing, ptet->bottom_face); if (parity[near_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } Boolean same_positioned_tet( PositionedTet *ptet0, PositionedTet *ptet1) { return ( ptet0->tet == ptet1->tet && ptet0->near_face == ptet1->near_face && ptet0->left_face == ptet1->left_face && ptet0->right_face == ptet1->right_face); /* * If three faces match, so must the fourth, and so must the orientation. */ } void set_left_edge( EdgeClass *edge, PositionedTet *ptet) { ptet->tet = edge->incident_tet; ptet->near_face = one_face_at_edge[edge->incident_edge_index]; ptet->left_face = other_face_at_edge[edge->incident_edge_index]; ptet->right_face = remaining_face[ptet->left_face][ptet->near_face]; ptet->bottom_face = remaining_face[ptet->near_face][ptet->left_face]; ptet->orientation = right_handed; } snappea-3.0d3/SnapPeaKernel/code/precision.c0100444000175000017500000000317207043064453017034 0ustar babbab/* * precision.c * * This file contains the functions * * int decimal_places_of_accuracy(double x, double y); * int complex_decimal_places_of_accuracy(Complex x, Complex y); * * which are used within the kernel to determine how many decimal * places of accuracy the doubles (resp. Complexes) x and y have in common. * Typically x and y will be two estimates of the same computed quantity * (e.g. the volume of a manifold as computed at the last and the * next-to-the-last iterations of Newton's method in * hyperbolic_structures.c), and decimal_places_of_accuracy() * will be used to tell the user interface how many decimal places * should be printed. We compute the number of decimal places of * accuracy instead of the number of significant figures, because * this is what printf() requires in its formatting string. */ #include "kernel.h" int decimal_places_of_accuracy( double x, double y) { int digits; if (x == y) { if (x == 0.0) digits = DBL_DIG; else digits = DBL_DIG - (int) ceil(log10(fabs(x))); } else digits = - (int) ceil(log10(fabs(x - y))); /* * Typically the difference between the computed values * of a quantity at the penultimate and ultimate iterations * of Newton's method is a little less than the true error. * So we fudge a bit. */ digits -= 4; if (digits < 0) digits = 0; return digits; } int complex_decimal_places_of_accuracy( Complex x, Complex y) { int real_precision, imag_precision; real_precision = decimal_places_of_accuracy(x.real, y.real); imag_precision = decimal_places_of_accuracy(x.imag, y.imag); return MIN(real_precision, imag_precision); } snappea-3.0d3/SnapPeaKernel/code/punctured_torus_bundles.c0100444000175000017500000007236606742675502022045 0ustar babbab/* * punctured_torus_bundles.c * * Please see function descriptions in SnapPea.h. */ #include "kernel.h" #define MAX_NAME_LENGTH 31 #define BIG_BUNDLE_NAME "untitled bundle" void bundle_LR_to_monodromy( LRFactorization *anLRFactorization, MatrixInt22 aMonodromy) { int i, temp; /* * The factorization should be available. */ if (anLRFactorization->is_available == FALSE) uFatalError("bundle_LR_to_monodromy", "punctured_torus_bundles"); /* * Initialize aMonodromy to the identity. */ aMonodromy[0][0] = 1; aMonodromy[0][1] = 0; aMonodromy[1][0] = 0; aMonodromy[1][1] = 1; /* * Right multiply by the LR factors. */ for (i = 0; i < anLRFactorization->num_LR_factors; i++) switch (anLRFactorization->LR_factors[i]) { case 'L': case 'l': /* * ( a b ) ( 1 0 ) = ( a+b b ) * ( c d ) ( 1 1 ) ( c+d d ) */ aMonodromy[0][0] += aMonodromy[0][1]; aMonodromy[1][0] += aMonodromy[1][1]; break; case 'R': case 'r': /* * ( a b ) ( 1 1 ) = ( a a+b ) * ( c d ) ( 0 1 ) ( c c+d ) */ aMonodromy[0][1] += aMonodromy[0][0]; aMonodromy[1][1] += aMonodromy[1][0]; break; default: uFatalError("bundle_LR_to_monodromy", "punctured_torus_bundles"); } /* * If the determinant should be negative, then * * ( 0 1 ) ( a b ) = ( c d ) * ( 1 0 ) ( c d ) ( a b ) */ if (anLRFactorization->negative_determinant == TRUE) { temp = aMonodromy[0][0]; aMonodromy[0][0] = aMonodromy[1][0]; aMonodromy[1][0] = temp; temp = aMonodromy[0][1]; aMonodromy[0][1] = aMonodromy[1][1]; aMonodromy[1][1] = temp; } /* * If the trace should be negative, then * * (-1 0 ) ( a b ) = ( -a -b ) * ( 0 -1 ) ( c d ) ( -c -d ) */ if (anLRFactorization->negative_trace == TRUE) { aMonodromy[0][0] = -aMonodromy[0][0]; aMonodromy[0][1] = -aMonodromy[0][1]; aMonodromy[1][0] = -aMonodromy[1][0]; aMonodromy[1][1] = -aMonodromy[1][1]; } } void bundle_monodromy_to_LR( MatrixInt22 aMonodromy, LRFactorization **anLRFactorization) { int a, b, c, d, aa, bb, cc, dd, t, theNumFactors; Boolean theTraceWasNegative, theDeterminantWasNegative; /* * Copy the entries of aMonodromy into the variables a, b, c and d. * This makes the notation more concise, and also means we don't * have to worry about overwriting aMonodromy. */ a = aMonodromy[0][0]; b = aMonodromy[0][1]; c = aMonodromy[1][0]; d = aMonodromy[1][1]; /* * Is the matrix OK? * * The factorization is available iff * * det = +1 and |trace| >= 2 * or * det = -1 and |trace| > 0. * * I'm pretty sure the manifold cannot be hyperbolic when * (det = 1 and |trace| <= 2) or (det = -1 and trace = 0), but * I don't know the proof. Note that we factor the det = 1 * and |trace| = 2 case even though it's not hyperbolic. */ switch (a*d - b*c) { case +1: if (a + d < 2 && a + d > -2) { (*anLRFactorization) = alloc_LR_factorization(0); (*anLRFactorization)->is_available = FALSE; (*anLRFactorization)->negative_determinant = FALSE; (*anLRFactorization)->negative_trace = (a + d < 0); return; } break; case -1: if (a + d == 0) { (*anLRFactorization) = alloc_LR_factorization(0); (*anLRFactorization)->is_available = FALSE; (*anLRFactorization)->negative_determinant = TRUE; (*anLRFactorization)->negative_trace = FALSE; return; } break; default: (*anLRFactorization) = alloc_LR_factorization(0); (*anLRFactorization)->is_available = FALSE; (*anLRFactorization)->negative_determinant = (a*d - b*c < 0); (*anLRFactorization)->negative_trace = (a + d < 0); return; } /* * Step 1. Make the trace positive. * * If the trace is negative, factor out -I. * * ( a b ) = (-1 0 ) (-a -b ) * ( c d ) ( 0 -1 ) (-c -d ) * * Note that -I lies in the center of GL(2,Z); in particular, * it commutes with any matrices we may later conjugate by. */ if (a + d < 0) { a = -a; b = -b; c = -c; d = -d; theTraceWasNegative = TRUE; } else theTraceWasNegative = FALSE; /* * Step 2. Make a >= d. * * If a < d, conjugate to swap a and d. * * ( 0 1 ) ( a b ) ( 0 -1 ) = ( d -c ) * (-1 0 ) ( c d ) ( 1 0 ) (-b a ) */ if (a < d) { t = a; a = d; d = t; t = b; b = -c; c = -t; } /* * Step 3. Make d nonnegative as well. * * At this point we know * * trace = a + d > 0 * and * a >= d, * * which together imply that a > 0. * We'd like to conjugate so that d >= 0 as well. * * Lemma. If d < 0, then either 0 < |b| < a or 0 < |c| < a. * * Proof. If d < 0, then a + d > 0 implies a > -d > 0, hence a >= 2. * Neither b nor c can be zero, since then we'd have |det| = |ad - bc| * = |ad| = |a|*|d| >= 2. On the other hand, if both |b| >= a * and |c| >= a, then |det| = |ad - bc| >= |bc| - |ad| >= |aa| - |ad| * = a*(a + d) >= 2*1 = 2. QED * * The lemma implies that we can do one of the following conjugations * to increase the value of d without making a negative. Repeat * until both a and d are nonnegative. * * ( 1 0 ) ( a b ) ( 1 0 ) = ( a-b b ) * ( 1 1 ) ( c d ) (-1 1 ) (c+a-b-d d+b ) * * ( 1 0 ) ( a b ) ( 1 0 ) = ( a+b b ) * (-1 1 ) ( c d ) ( 1 1 ) (c-a-b+d d-b ) * * ( 1 -1 ) ( a b ) ( 1 1 ) = ( a-c b+a-c-d) * ( 0 1 ) ( c d ) ( 0 1 ) ( c d+c ) * * ( 1 1 ) ( a b ) ( 1 -1 ) = ( a+c b-a-c+d) * ( 0 1 ) ( c d ) ( 0 1 ) ( c d-c ) * * Note: It may no longer be true that a >= d, but that's OK. */ while (d < 0) { if (b > 0 && b < a) /* use +b */ { c += a - b - d; a -= b; d += b; } else if (b < 0 && b > -a) /* use -b */ { c += d - a - b; a += b; d -= b; } else if (c > 0 && c < a) /* use +c */ { b += a - c - d; a -= c; d += c; } else if (c < 0 && c > -a) /* use -c */ { b += d - a - c; a += c; d -= c; } else uFatalError("bundle_monodromy_to_LR", "punctured_torus_bundles"); } /* * Step 4. Make b and c nonnegative as well. */ if (b >= 0 && c >= 0) { /* nothing to do here! */ } else if (b <= 0 && c <= 0) { /* * Conjugate using * * ( 0 1 ) ( a b ) ( 0 -1 ) = ( d -c ) * (-1 0 ) ( c d ) ( 1 0 ) (-b a ) */ t = a; a = d; d = t; t = b; b = -c; c = -t; } else { /* * b and c have opposite signs. This implies det > 0. * Hence det = +1, {b,c} = {-1,+1}, and {a,d} = {n,0}. * Furthermore, when det = +1 we handle only trace >= 2, * so n >= 2. Use one of the conjugations from Step 3 * to make b and c nonnegative while maintaining the * nonnegativity of a and d. */ if (b == +1) /* && c == -1 */ { if (a >= 2) /* && d == 0 */ { c += a - b - d; a -= b; d += b; } else /* a == 0 && d >= 2 */ { c += d - a - b; a += b; d -= b; } } else /* b == -1 && c == +1 */ { if (a >= 2) /* && d == 0 */ { b += a - c - d; a -= c; d += c; } else /* a == 0 && d >= 2 */ { b += d - a - c; a += c; d -= c; } } } /* * Step 5. Make the determinant positive by factoring if necessary * * ( a b ) = ( 0 1 ) ( c d ) * ( c d ) = ( 1 0 ) ( a b ) * * Note that (0, 1; 1, 0) does not commute with most matrices * in GL(2,Z), so it's important that we factored it out *after* * doing all necessary conjugations. */ if (a*d - b*c < 0) { t = a; a = c; c = t; t = b; b = d; d = t; theDeterminantWasNegative = TRUE; } else theDeterminantWasNegative = FALSE; /* * Step 6. Now that the matrix has no negative entries, we may factor * it as a product of L's and R's * * L = ( 1 0 ) R = ( 1 1 ) * ( 1 1 ) ( 0 1 ) * * Note that factoring out an L (resp. R) corresponds to subtracting * the first row from the second (resp. the second from the first). * * ( a b ) = ( 1 0 ) ( a b ) * ( c d ) ( 1 1 ) (c-a d-b) * * ( a b ) = ( 1 1 ) (a-c b-d) * ( c d ) ( 0 1 ) ( c d ) * * Lemma. If a, b, c and d are all nonnegative and det = +1, * then either * * (1) a <= c and b <= d, * (2) a >= c and b >= d, or * (3) a = d = 1 and b = c = 0. * * Comment. In case (1) we will factor out an L, in case (2) we * will factor out an R, and in case (3) we've reached the identity * and we're done. The algorithm has the flavor of the Euclidean * algorithm for finding the greatest common divisor of two positive * integers. * * Proof. * If a = c then one of conditions (1) or (2) must be satisfied, * according to whether b <= d or b >= d. * If a < c, then det = ad - bc = +1 implies that b < d, and * condition (1) is satisfied. * If a > c, then either * b >= d, in which case condition (2) is satisfied, or * b < d, in which case det = ad - bc >= (c+1)(b+1) - bc * = b + c + 1, which implies b = c = 0, hence a = d = 1. * QED */ /* * First an error check. */ if (a < 0 || b < 0 || c < 0 || d < 0) uFatalError("bundle_monodromy_to_LR", "punctured_torus_bundles"); /* * Take a dry run through the algorithm to count * how many factors we'll need. */ aa = a; bb = b; cc = c; dd = d; theNumFactors = 0; while (aa != 1 || bb != 0 || cc != 0 || dd != 1) { if (aa <= cc && bb <= dd) { cc -= aa; dd -= bb; theNumFactors++; } if (aa >= cc && bb >= dd) { aa -= cc; bb -= dd; theNumFactors++; } } /* * Allocate the LRFactorization. */ *anLRFactorization = alloc_LR_factorization(theNumFactors); /* * Record the original trace and determinant. */ (*anLRFactorization)->is_available = TRUE; (*anLRFactorization)->negative_determinant = theDeterminantWasNegative; (*anLRFactorization)->negative_trace = theTraceWasNegative; /* * Repeat the factorization, recording the factors * in the LR_factors array. */ theNumFactors = 0; while (a != 1 || b != 0 || c != 0 || d != 1) { if (a <= c && b <= d) { c -= a; d -= b; (*anLRFactorization)->LR_factors[theNumFactors++] = 'L'; } if (a >= c && b >= d) { a -= c; b -= d; (*anLRFactorization)->LR_factors[theNumFactors++] = 'R'; } } /* * All done! */ } LRFactorization *alloc_LR_factorization( int aNumFactors) { LRFactorization *anLRFactorization; anLRFactorization = NEW_STRUCT(LRFactorization); anLRFactorization->num_LR_factors = aNumFactors; if (aNumFactors > 0) anLRFactorization->LR_factors = NEW_ARRAY(aNumFactors, char); else anLRFactorization->LR_factors = NULL; return anLRFactorization; } void free_LR_factorization( LRFactorization *anLRFactorization) { if (anLRFactorization != NULL) { if (anLRFactorization->LR_factors != NULL) my_free(anLRFactorization->LR_factors); my_free(anLRFactorization); } } Triangulation *triangulate_punctured_torus_bundle( LRFactorization *anLRFactorization) { Boolean theFactorizationContainsAnL, theFactorizationContainsAnR; int n, /* number of tetrahedra */ i, j, k, l, m, image[4][4], signed_intersections[2]; TriangulationData *data; Triangulation *manifold; Tetrahedron *tet; PeripheralCurve c; MatrixInt22 change_matrices[1]; long m10, m11; /* * If the LR factorization is not available [because * (det(monodromy) = +1 and |trace(monodromy)| < 2) or * (det(monodromy) = -1 and |trace(monodromy)| < 1)], * then return NULL. */ if (anLRFactorization->is_available == FALSE) return NULL; /* * If the manifold is nonhyperbolic (because det(monodromy) = +1 * and |trace(monodromy)| = 2, in which case the LR factors are all * L's or all R's and the manifold splits open along an embedded * torus to yield a thrice-punctured sphere cross a circle), then * return NULL. */ theFactorizationContainsAnL = FALSE; theFactorizationContainsAnR = FALSE; for (i = 0; i < anLRFactorization->num_LR_factors; i++) switch (anLRFactorization->LR_factors[i]) { case 'L': theFactorizationContainsAnL = TRUE; break; case 'R': theFactorizationContainsAnR = TRUE; break; default: uFatalError("triangulate_punctured_torus_bundle", "punctured_torus_bundles"); } if ( anLRFactorization->negative_determinant == TRUE ? ( theFactorizationContainsAnL == FALSE && theFactorizationContainsAnR == FALSE ) : ( theFactorizationContainsAnL == FALSE || theFactorizationContainsAnR == FALSE ) ) return NULL; /* * To triangulate the punctured torus bundle, imagine wrapping * an almost flattened ideal tetrahedron onto a punctured torus. * * u * o------------>o * ^ \__ __/^ * | \ __/ | * v | __/ | v * | __/ \__ | * |/ \ | * o------------>o * u * * The tetrahedron covers the punctured torus exactly once. * The tetrahedron's ideal vertices coincide with the puncture, * and the edges labelled u (resp. v) become identified. * The homology classes of u and v (directed as shown) define * the ideal tetrahedron's position on the punctured torus. * * Now imagine an infinite stack of such ideal tetrahedra. * The top surface of tetrahedron i glues to the bottom surface of * tetrahedron i+1. We want to position the ideal tetrahedra so that * triangular faces glue to triangular faces. More specifically, * * If LR_factors[i mod n] == 'L' * * u[i+1] = u[i] + v[i] * v[i+1] = v[i] * * If LR_factors[i mod n] == 'R' * * u[i+1] = u[i] * v[i+1] = u[i] + v[i] * * where n is the number of LR_factors. * * When we do these gluings, we get a triangulation for a punctured * torus cross a line. If we then mod out by the translation * which takes tetrahedron i to tetrahedron i+n in such a way * that u[i]->u[i+n] and v[i]->v[i+n], we get a triangulation * for a punctured torus bundle over the circle. Let's compute * its monodromy. * * If we write the u[i]'s and v[i]'s as the elements of row vectors * (u[i] v[i]), then we can express the above relations as matrix * equations. * * If LR_factors[i mod n] == 'L' * * ( 1 0 ) * ( u[i+1] v[i+1] ) = ( u[i] v[i] ) ( ) * ( 1 1 ) * * If LR_factors[i mod n] == 'R' * * ( 1 1 ) * ( u[i+1] v[i+1] ) = ( u[i] v[i] ) ( ) * ( 0 1 ) * * Composing n such relations, we obtain the position of * tetrahedron i+n as of the position of tetrahedron i times * the product of the LR_factors. * * ( 1 1 ) ( 1 0 ) * ( u[i+n] v[i+n] ) = ( u[i] v[i] ) ( ) ( )... * ( 0 1 ) ( 1 1 ) * * ( product ) * ( u[i+n] v[i+n] ) = ( u[i] v[i] ) ( of all ) * ( LR_factors ) * * In other words, the first (resp. second) column of the product * of the LR_factors expresses u[i+n] (resp. v[i+n]) as a linear * combination of u[i] and v[i]. In other words, the product of * the LR_factors is the monodromy of the bundle, expressed relative * to the basis (u[i], v[i]). * * The algorithm is easily modified to accomodate negative * determinant and/or trace. * * If the determinant is to be negative, then when i + 1 = 0 (mod n) * we interchange the usual formulas for u[i+1] and v[i+1]. * * If the trace is to be negative, then when i + 1 = 0 (mod n) * we negate the usual formulas for u[i+1] and v[i+1]. * * These changes still map triangles to triangles, and if both the * trace and the determinant are negative, we can do them in either * order. They have the desired effect on the monodromy matrix. * For example, if both the determinant and the trace are to be * negative, the forumla for (u[n] v[n]) becomes * * ( product ) ( 0 1 ) (-1 0 ) * ( u[n] v[n] ) = ( u[0] v[0] ) ( of all ) ( ) ( ) * ( LR_factors ) ( 1 0 ) ( 0 -1 ) * * Comment. The topology of the bundle depends only on the cyclic * word of LR_factors (including the possible factors for negative * determinant and trace), even though the monodromy matrix itself * (the matrix product on the right hand side above) depends on how * you break the cyclic word into a linear word. The different * monodromy matrices are of course conjugates of one another. */ /* * The plan is to describe the bundle as a TriangulationData structure, * and then let data_to_triangulation() create the Triangulation * itself. */ /* * Let n be the number of tetrahedra. */ n = anLRFactorization->num_LR_factors; /* * Set up the header. */ data = NEW_STRUCT(TriangulationData); data->name = NULL; data->num_tetrahedra = n; data->solution_type = not_attempted; data->volume = 0.0; data->orientability = (anLRFactorization->negative_determinant == TRUE) ? nonorientable_manifold : oriented_manifold; data->CS_value_is_known = FALSE; data->CS_value = -1.0; data->num_or_cusps = (anLRFactorization->negative_determinant == TRUE) ? 0 : 1; data->num_nonor_cusps = (anLRFactorization->negative_determinant == TRUE) ? 1 : 0; data->cusp_data = NULL; data->tetrahedron_data = NULL; /* * Set up the name. */ if (anLRFactorization->num_LR_factors <= MAX_NAME_LENGTH - 3) { data->name = NEW_ARRAY(3 + n + 1, char); data->name[0] = 'b'; data->name[1] = (anLRFactorization->negative_determinant == TRUE) ? '-' : '+'; data->name[2] = (anLRFactorization->negative_trace == TRUE) ? '-' : '+'; for (i = 0; i < n; i++) data->name[3 + i] = anLRFactorization->LR_factors[i]; data->name[3 + n] = 0; } else { data->name = NEW_ARRAY(strlen(BIG_BUNDLE_NAME) + 1, char); strcpy(data->name, BIG_BUNDLE_NAME); } /* * Set up the cusp. */ data->cusp_data = NEW_ARRAY(1, CuspData); data->cusp_data->topology = (anLRFactorization->negative_determinant == TRUE) ? Klein_cusp : torus_cusp; data->cusp_data->m = 0.0; data->cusp_data->l = 0.0; /* * Set up the tetrahedra. * * Label the vertices {0,1,2,3} as shown. * Each face gets the index of the opposite vertex. * This indexing scheme gives each tetrahedron * the right_handed Orientation. * * u * 3------------>2 * ^ \__ __/^ * | \ __/ | * v | __/ | v * | __/ \__ | * |/ \ | * 0------------>1 * u * * To understand the gluings, it's helpful to draw tetrahedron i * on a piece of paper using one color ink, and then draw * tetrahedron i+1 on top of it using another color ink. * You'll have to make two such pictures, one for an 'L' * gluing and one for an 'R' gluing. The following illustrations * show the general idea. The vertex indices for tetrahedron i+1 * are shown in parentheses -- in your own picture you can write * the indices in the second color ink and omit the parentheses. * * _(2) * _/ | * _/ /| * / / | (3) * (3) 3---/--2 (1) 3------2------(2) * | / _/| | _/ | ___/| * | /_/ | | _/ ___/ __/ * |// | |/___/ | _/ * (0) 0------1 (0) 0/-----1/ (1) * * 'L' gluing 'R' gluing */ data->tetrahedron_data = NEW_ARRAY(n, TetrahedronData); for (i = 0; i < n; i++) { data->tetrahedron_data[i].neighbor_index[0] = (i + (n-1)) % n; data->tetrahedron_data[i].neighbor_index[1] = (i + 1 ) % n; data->tetrahedron_data[i].neighbor_index[2] = (i + (n-1)) % n; data->tetrahedron_data[i].neighbor_index[3] = (i + 1 ) % n; /* * Set up the gluings assuming det = +1 and trace > 0, and then * if necessary factor in det = -1 and/or trace < 0 later. */ if (anLRFactorization->LR_factors[i] == 'L') { data->tetrahedron_data[i].gluing[1][0] = 0; data->tetrahedron_data[i].gluing[1][1] = 2; data->tetrahedron_data[i].gluing[1][2] = 1; data->tetrahedron_data[i].gluing[1][3] = 3; data->tetrahedron_data[(i+1)%n].gluing[2][0] = 0; data->tetrahedron_data[(i+1)%n].gluing[2][1] = 2; data->tetrahedron_data[(i+1)%n].gluing[2][2] = 1; data->tetrahedron_data[(i+1)%n].gluing[2][3] = 3; data->tetrahedron_data[i].gluing[3][0] = 3; data->tetrahedron_data[i].gluing[3][1] = 1; data->tetrahedron_data[i].gluing[3][2] = 2; data->tetrahedron_data[i].gluing[3][3] = 0; data->tetrahedron_data[(i+1)%n].gluing[0][0] = 3; data->tetrahedron_data[(i+1)%n].gluing[0][1] = 1; data->tetrahedron_data[(i+1)%n].gluing[0][2] = 2; data->tetrahedron_data[(i+1)%n].gluing[0][3] = 0; } else /* 'R' */ { data->tetrahedron_data[i].gluing[1][0] = 1; data->tetrahedron_data[i].gluing[1][1] = 0; data->tetrahedron_data[i].gluing[1][2] = 2; data->tetrahedron_data[i].gluing[1][3] = 3; data->tetrahedron_data[(i+1)%n].gluing[0][0] = 1; data->tetrahedron_data[(i+1)%n].gluing[0][1] = 0; data->tetrahedron_data[(i+1)%n].gluing[0][2] = 2; data->tetrahedron_data[(i+1)%n].gluing[0][3] = 3; data->tetrahedron_data[i].gluing[3][0] = 0; data->tetrahedron_data[i].gluing[3][1] = 1; data->tetrahedron_data[i].gluing[3][2] = 3; data->tetrahedron_data[i].gluing[3][3] = 2; data->tetrahedron_data[(i+1)%n].gluing[2][0] = 0; data->tetrahedron_data[(i+1)%n].gluing[2][1] = 1; data->tetrahedron_data[(i+1)%n].gluing[2][2] = 3; data->tetrahedron_data[(i+1)%n].gluing[2][3] = 2; } for (j = 0; j < 4; j++) data->tetrahedron_data[i].cusp_index[j] = 0; /* * Set the peripheral curves to all zeros. This will force * data_to_triangulation() to provide default curves. * We'll then find linear combinations of the default curves * which provide the desired meridian and longitude. */ for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) for (m = 0; m < 4; m++) data->tetrahedron_data[i].curve[j][k][l][m] = 0; /* * The shape will be ignored. */ data->tetrahedron_data[i].filled_shape = Zero; } /* * If the determinant is to be negative, interchange u[0] and v[0] * relative to u[n-1] and v[n-1]. */ if (anLRFactorization->negative_determinant == TRUE) { for (i = 0; i < 4; i++) { image[0][i] = data->tetrahedron_data[0].gluing[0][i]; image[2][i] = data->tetrahedron_data[0].gluing[2][i]; } data->tetrahedron_data[0].gluing[0][1] = image[0][3]; data->tetrahedron_data[0].gluing[0][3] = image[0][1]; data->tetrahedron_data[0].gluing[2][1] = image[2][3]; data->tetrahedron_data[0].gluing[2][3] = image[2][1]; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][1]] = 3; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][3]] = 1; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][1]] = 3; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][3]] = 1; } /* * If the trace is to be negative, negate u[0] and v[0] * relative to u[n-1] and v[n-1]. */ if (anLRFactorization->negative_trace == TRUE) { for (i = 0; i < 4; i++) { image[0][i] = data->tetrahedron_data[0].gluing[0][i]; image[2][i] = data->tetrahedron_data[0].gluing[2][i]; } data->tetrahedron_data[0].gluing[0][0] = image[2][2]; data->tetrahedron_data[0].gluing[0][1] = image[2][3]; data->tetrahedron_data[0].gluing[0][2] = image[2][0]; data->tetrahedron_data[0].gluing[0][3] = image[2][1]; data->tetrahedron_data[0].gluing[2][0] = image[0][2]; data->tetrahedron_data[0].gluing[2][1] = image[0][3]; data->tetrahedron_data[0].gluing[2][2] = image[0][0]; data->tetrahedron_data[0].gluing[2][3] = image[0][1]; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][0]] = 2; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][1]] = 3; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][2]] = 0; data->tetrahedron_data[n-1].gluing[image[0][0]][image[0][3]] = 1; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][0]] = 2; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][1]] = 3; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][2]] = 0; data->tetrahedron_data[n-1].gluing[image[2][2]][image[2][3]] = 1; } /* * Create the Triangulation. */ data_to_triangulation(data, &manifold); /* * We no longer need the data. */ free_triangulation_data(data); /* * We want the meridian to be homotopic to the boundary of the * punctured torus fiber, and the longitude to be any curve * transverse to it. (Yes, I know that the boundary of the fiber * is usually called the longitude, but that conflicts with SnapPea's * convention for curves on Klein bottles, which says that the * meridian is a nonseparating orientation preserving simple closed * curve, and the longitude is an orientation reversing simple * closed curve.) * * Consider any ideal tetrahedron in the triangulation we just * created. The triangular cross sections of its four ideal vertices * sit like this in the cusp. * * 0 __ 2 __ * ___/ \___ ___/ \___ * /__________\____________/__________\____________ * \___ ___/ \___ ___/ * 3 \__/ 1 \__/ * * The horizontal line running across the picture is the desired * meridian. We'll compute the intersection numbers of the desired * meridian with the default meridian and longitude which * data_to_triangulation() provided, and use the information to * express the desired peripheral curves as linear combinations * of the default ones. * * Note: The desired meridian is completely well defined, but * the desired longitude is defined only up the addition of some * multiple of the meridian. In other words, the longitude might * twist around the cusp. */ /* * Let tet be any Tetrahedron in the Triangulation. */ tet = manifold->tet_list_begin.next; /* * We expect the indexing of the Tetrahedra to be the same as * the indexing of the data we provided, i.e. * * 3------2 * | \ _/| * | _/ | * | / \| * 0------1 * * but we don't want to rely on this assumption. To verify it, * we check that the edge from vertex 0 to 1 is identified in the * manifold to the edge from vertex 2 to vertex 3, and similarly * that the edge from vertex 1 to vertex 2 is identified to the * edge from vertex 3 to vertex 0. We do, however, assume that * the Triangulation is combinatorially equivalent to the one * specified in the data. */ if ( tet->edge_class[edge_between_vertices[0][1]] != tet->edge_class[edge_between_vertices[2][3]] || tet->edge_class[edge_between_vertices[1][2]] != tet->edge_class[edge_between_vertices[3][0]] ) { /* * I don't think this situation will ever arise, * so I haven't written any code to handle it. * If necessary, it would be very easy to write code * to discover the indexing scheme on the tet. */ uFatalError("triangulate_punctured_torus_bundle", "punctured_torus_bundle"); } /* * Count the number of times the default meridian and longitude * pass upwards through the intended meridian. Use only * the right_handed sheet -- this will give correct results on * both a torus cusp and a Klein bottle cusp. */ for (c = 0; c < 2; c++) /* c = M, L */ signed_intersections[c] = tet->curve[c][right_handed][0][2] - tet->curve[c][right_handed][1][3] + tet->curve[c][right_handed][2][0] - tet->curve[c][right_handed][3][1]; /* * The desired meridian and longitude will be linear combinations * of the default meridian and longitude * * desired meridian = m00*(default meridian) + m01*(default longitude) * desired longitude = m10*(default meridian) + m11*(default longitude) * * The desired meridian has intersection number zero with itself, so * * m00*signed_intersections[M] + m01*signed_intersections[L] = 0 * * The desired longitude has intersection number +1 with the * desired meridian, so * * m10*signed_intersections[M] + m11*signed_intersections[L] = +1 */ /* * Try * m00 = signed_intersections[L] * m01 = - signed_intersections[M] * * Later we may have to negate these coefficients if the direction * is wrong. */ change_matrices[0][0][0] = signed_intersections[L]; change_matrices[0][0][1] = - signed_intersections[M]; /* * Use the Euclidean algorithm to solve for M10 and M11. */ if (euclidean_algorithm(signed_intersections[M], signed_intersections[L], &m10, &m11) != 1) uFatalError("triangulate_punctured_torus_bundle", "punctured_torus_bundle"); change_matrices[0][1][0] = m10; change_matrices[0][1][1] = m11; /* * If change_matrices[0] has determinant -1, reverse the meridian. */ switch (DET2(change_matrices[0])) { case +1: /* * The meridian is correct. */ break; case -1: /* * Reverse the meridian. */ change_matrices[0][0][0] = - change_matrices[0][0][0]; change_matrices[0][0][1] = - change_matrices[0][0][1]; break; default: uFatalError("triangulate_punctured_torus_bundle", "punctured_torus_bundle"); } /* * Change the peripheral curves. */ change_peripheral_curves(manifold, change_matrices); /* * Done! */ return manifold; } snappea-3.0d3/SnapPeaKernel/code/rehydrate_census.c0100444000175000017500000002505407001153230020375 0ustar babbab/* * rehydrate_census.c * * The function * * void rehydrate_census_manifold( * TersestTriangulation tersest, * int which_census, * int which_manifold, * Triangulation **manifold); * * calls tersest_to_tri() to rehydrate the manifold, then call * resolve_ambiguous_bases() [described below] to insure that the * peripheral curves are correct. * * For certain census manifolds, the "canonical" peripheral curves * installed by terse_to_tri() are not well defined. (The problem * is that the cusps do not have unique shortest geodesics, so the * geometry of the cusp alone does not provide any way to select * a preferred meridian.) The function * * void resolve_ambiguous_bases( Triangulation *theTriangulation, * int aCensus, * int anIndex); * * resolves this problem for the census manifolds by choosing a * set of peripheral curves based on the homology of the manifold as * a whole. * * Comments: * * (1) Full documentation appears in the files "Read Me" and * "ambiguous examples.symmetries" in the folder "cusped census 2.1". * * (2) The choices rely on a fixed orientation for the manifold. * * (3) For most of the 2-cusp manifolds, all (shortest) choices for * the meridian of a single cusp are equivalent, but once you've * chosen it the choices for the remaining cusp are *not* equivalent. * This observation lets us treat the 1-cusp and 2-cusp cases using * the same usual_algorithm() code, which rotates the coordinates * on cusp 0 until the homology is right, regardless of whether the * manifold has a second cusp or not. */ #include "kernel.h" static void resolve_ambiguous_bases(Triangulation *theTriangulation, int aCensus, int anIndex); static void usual_algorithm(Triangulation *aTriangulation, int anM, int anL, CONST MatrixInt22 *aChangeMatrixArray, int aNumCoefficients, int aFirstCoefficient, int aSecondCoefficient, int aThirdCoefficient); static void algorithm_s596(Triangulation *aTriangulation); static Boolean check_homology(Triangulation *aTriangulation, AbelianGroup *anAbelianGroup); /* * The documentation at the top of the file change_peripheral_curves.c * explains the interpretation of the following "change matrices". */ CONST static MatrixInt22 rotate6[2] = { { { 0, 1 }, { -1, 1 } }, { { 1, 0 }, { 0, 1 } } }, rotate6a[2] = { { { 1, 0 }, { 0, 1 } }, { { 0, 1 }, { -1, 1 } } }, rotate4[2] = { { { 0, 1 }, { -1, 0 } }, { { 1, 0 }, { 0, 1 } } }; void rehydrate_census_manifold( TersestTriangulation tersest, int which_census, int which_manifold, Triangulation **manifold) { /* * Rehydrate the manifold. */ tersest_to_tri(tersest, manifold); /* * If the manifold happens to be one of the census manifolds * with square or hexagonal cusps, make sure the peripheral * curves are the standard ones. */ resolve_ambiguous_bases(*manifold, which_census, which_manifold); } static void resolve_ambiguous_bases( Triangulation *theTriangulation, int aCensus, int anIndex) { switch (aCensus) { case 5: switch (anIndex) { case 003: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 10, -1, -1); break; case 125: usual_algorithm(theTriangulation, 1, 0, rotate4, 1, 3, -1, -1); break; case 130: usual_algorithm(theTriangulation, 1, 1, rotate4, 2, 2, 16, -1); break; case 135: usual_algorithm(theTriangulation, 1, 1, rotate4, 3, 2, 2, 4); break; case 139: usual_algorithm(theTriangulation, 1, 0, rotate4, 1, 24, -1, -1); break; case 202: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 3, -1, -1); break; case 208: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 20, -1, -1); break; default: /* * Peripheral curves are already well defined by the * geometry of the cusp. Don't change them. */ break; } break; case 6: switch (anIndex) { case 594: usual_algorithm(theTriangulation, 1, 0, rotate6, 3, 2, 2, 0); break; case 596: algorithm_s596(theTriangulation); break; case 859: usual_algorithm(theTriangulation, 1, 0, rotate4, 1, 6, -1, -1); break; case 913: usual_algorithm(theTriangulation, 1, 0, rotate4, 1, 5, -1, -1); break; case 955: usual_algorithm(theTriangulation, 1, 0, rotate6, 2, 4, 20, -1); break; case 957: usual_algorithm(theTriangulation, 1, 0, rotate6, 2, 4, 4, -1); break; case 959: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 9, -1, -1); break; case 960: usual_algorithm(theTriangulation, 1, 0, rotate6, 3, 2, 10, 0); break; default: /* * Peripheral curves are already well defined by the * geometry of the cusp. Don't change them. */ break; } break; case 8: /* really the nonorientable 6-tetrahedron census */ /* * There are no square or hexagonal orientable cusps, * so there are no special cases to deal with. */ break; case 7: switch (anIndex) { case 1859: usual_algorithm(theTriangulation, 1, 0, rotate4, 3, 2, 2, 2); break; case 3318: usual_algorithm(theTriangulation, 1, 0, rotate4, 2, 2, 2, -1); break; case 3319: usual_algorithm(theTriangulation, 1, 0, rotate4, 1, 3, -1, -1); break; case 3461: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 5, -1, -1); break; case 3551: usual_algorithm(theTriangulation, 1, 0, rotate6, 1, 14, -1, -1); break; default: /* * Peripheral curves are already well defined by the * geometry of the cusp. Don't change them. */ break; } break; case 9: /* really the nonorientable 7-tetrahedron census */ /* * There are no square or hexagonal orientable cusps, * so there are no special cases to deal with. */ break; default: uFatalError("resolve_ambiguous_bases", "ambiguous_bases"); } } static void usual_algorithm( Triangulation *aTriangulation, int anM, int anL, CONST MatrixInt22 *aChangeMatrixArray, int aNumCoefficients, int aFirstCoefficient, int aSecondCoefficient, int aThirdCoefficient) { int i, theRotationCount; long theCoefficientArray[3]; AbelianGroup theAbelianGroup; /* * Set up theAbelianGroup. */ theCoefficientArray[0] = aFirstCoefficient; theCoefficientArray[1] = aSecondCoefficient; theCoefficientArray[2] = aThirdCoefficient; theAbelianGroup.num_torsion_coefficients = aNumCoefficients; theAbelianGroup.torsion_coefficients = theCoefficientArray; /* * Set up an (m,l) Dehn filling on each cusp, relative to the initial * (arbitrary) coordinate system. Don't actually compute the * hyperbolic structure -- the computation would be slow (compared * to what we're doing here) and we don't need the hyperbolic * structure to check the homology anyhow. */ for (i = 0; i < get_num_cusps(aTriangulation); i++) set_cusp_info(aTriangulation, i, FALSE, anM, anL); /* * We'll keep track of how many times we've been through the following * while() loop. If something goes wrong we should display an error * message instead of looping forever. */ theRotationCount = 0; /* * If the homology isn't what we want, rotate the coordinate system * a sixth or quarter turn, according to aChangeMatrixArray. * After at most two such rotations we should find the meridian * we're looking for. See the file "ambiguous examples.symmetries" * for an explanation of how the desired meridians were chosen. */ while (check_homology(aTriangulation, &theAbelianGroup) == FALSE) { /* * The call to change_peripheral_curves() will adjust the Dehn * filling coefficients to compensate for the changed coordinate * system, thereby preserving the original Dehn filling. * But we want to move on to a new Dehn filling, which is (m,l) * in the *new* coordinate system. */ change_peripheral_curves(aTriangulation, aChangeMatrixArray); set_cusp_info(aTriangulation, 0, FALSE, anM, anL); /* * We shouldn't have to rotate more than twice to find * the desired meridian. */ if (++theRotationCount > 2) uFatalError("usual_algorithm", "ambiguous_bases"); } /* * We've found the correct peripheral curves. Restore the * Dehn filling coefficients to their original, unfilled state. */ for (i = 0; i < get_num_cusps(aTriangulation); i++) set_cusp_info(aTriangulation, i, TRUE, 0.0, 0.0); } static void algorithm_s596( Triangulation *aTriangulation) { /* * Please see the file "ambiguous examples.symmetries" * for an explanation of why s596 needs special treatment. */ int theRotationCount; long theCoefficientArray[2]; AbelianGroup theAbelianGroup; theAbelianGroup.num_torsion_coefficients = 2; theAbelianGroup.torsion_coefficients = theCoefficientArray; theAbelianGroup.torsion_coefficients[0] = 2; theAbelianGroup.torsion_coefficients[1] = 2; set_cusp_info(aTriangulation, 0, FALSE, 1.0, 0.0); set_cusp_info(aTriangulation, 1, FALSE, 1.0, 0.0); theRotationCount = 0; while (check_homology(aTriangulation, &theAbelianGroup) == FALSE) { /* * Cycle through all possible combinations * of meridians for cusps 0 and 1. */ if (theRotationCount % 3 == 0) { change_peripheral_curves(aTriangulation, rotate6); set_cusp_info(aTriangulation, 0, FALSE, 1.0, 0.0); } else { change_peripheral_curves(aTriangulation, rotate6a); set_cusp_info(aTriangulation, 1, FALSE, 1.0, 0.0); } if (++theRotationCount > 8) uFatalError("algorithm_s596", "ambiguous_bases"); } set_cusp_info(aTriangulation, 0, TRUE, 0.0, 0.0); set_cusp_info(aTriangulation, 1, TRUE, 0.0, 0.0); } static Boolean check_homology( Triangulation *aTriangulation, AbelianGroup *anAbelianGroup) { AbelianGroup *theHomology; Boolean theGroupsAreIsomorphic; int i; theHomology = homology(aTriangulation); if (theHomology == NULL) uFatalError("check_homology", "rehydrate_census"); compress_abelian_group(theHomology); if (theHomology->num_torsion_coefficients != anAbelianGroup->num_torsion_coefficients) theGroupsAreIsomorphic = FALSE; else { theGroupsAreIsomorphic = TRUE; for (i = 0; i < theHomology->num_torsion_coefficients; i++) if (theHomology->torsion_coefficients[i] != anAbelianGroup->torsion_coefficients[i]) theGroupsAreIsomorphic = FALSE; } free_abelian_group(theHomology); return theGroupsAreIsomorphic; } snappea-3.0d3/SnapPeaKernel/code/representations.c0100444000175000017500000014260007041136724020265 0ustar babbab/* * representations.c * * find_representations() finds transitive representations of a manifold's * fundamental group into S(n), the symmetric group on n letters. * The representations may then be passed to construct_cover() to obtain * the corresponding covering spaces. Please see covers.h for details. * A representation is "transitive" iff the corresponding covering space * is connected. * * Pass range = permutation_subgroup_Sn to find all transitive * representations into S(n), up to conjugacy. This yields all n-fold * covering spaces, up to equivalence. * * Pass range = permutation_subgroup_Zn to find all transitive * representations into Z/n, up to automorphisms of Z/n. The next * section below proves that this yields all n-fold cyclic covers, * up to equivalence. (Note: S(n) may contain many Z/n subgroups. * We use the "obvious" one generated by i -> i+1.) * * For large values of n it's *much* faster to find cyclic representations * than all representations, because there are only n groups elements * to be considered for each generator instead of n!. * * At some point in the future it may be possible to pass * range == permutation_subgroup_Dn to find dihedral coverings, * but that's not implemented yet. * * The cusps may be unfilled or have integer Dehn filling coefficients * (non relatively prime integers are OK -- they define orbifolds). * However if noninteger coefficients are present the fundamental group * isn't well defined, and find_representations() returns an empty list. */ /* * Cyclic Coverings and Representations in Z/n * * (The documentation at the top of cover.h provides the background * necessary to understand this section.) * * Without loss of generality we may assume that the sheets of a cyclic * covering are numbered consecutively, so that some generator of the * covering's Z/n symmetry group takes sheet i to sheet i+1 (mod n). * It follows that the representation of the fundamental group in S(n) * is actually a representation into this Z/n subgroup. By finding * all representations into Z/n, we find all cyclic coverings. * The question is, how do we know which ones are equivalent? * * First recall the situation for arbitrary representations in S(n). * * Proposition. Two different representations yield equivalent covering * spaces iff the representations differ by conjugacy in S(n). * * Proof. Equivalent coverings may differ only in the way the labels * 0, 1, ..., n-1 are assigned to the sheets. Q.E.D. * * The special case of a cyclic covering is a little more delicate. * We could apply the above proposition to decide when two cyclic * coverings were equivalent, but it would be very slow -- we'd have * to check all n! possible conjugators, and our algorithm would slow * to a crawl. Fortunately we can do much better. In fact, our assumption * that the sheets of the cover be numbered consecutively implies that * we need to check fewer than n possible conjugators. * * Proposition. If two connected cyclic coverings (with consecutively * numbered sheets, as explained above) are equivalent, they are equivalent * by a map of the form f(i) = p*i, for some p relatively prime to n. * * Proof. Say we have two representations from the fundamental group * into Z/n which yield equivalent cyclic covers. That is, there is some * map f() from the sheets {0, 1, ..., n-1} of the first cover to the sheets * {0, 1, ..., n-1} of the second cover, and it respects the connections * between sheets. Because each cover has n-fold cyclic symmetry, we may * without loss of generality assume that f(0) = 0. Because the coverings * are connected, there is some path in the manifold which lifts to a path * from sheet 0 to sheet 1 in the first covering. In the second covering * that same path lifts to a path from sheet 0 to some other sheet p. * Because f() respects the connections between sheets, this implies that * f(1) = p. Because the sheets are numbered consecutively, if you * continue the lift of the path in the first manifold, you'll go from * sheet 1 to sheet 2. In the second covering you'll go from sheet p * to sheet 2p. Thus f(2) = 2p. And so on. Hence f(i) = p*i (mod n). * Furthermore p must be relatively prime to n, since otherwise f() * wouldn't be one-to-one. Q.E.D. */ /* * Conjugacy and Minimality * * To find all representations up to conjugacy we could, in principle, * find all representations and then check which ones are conjugate * to which others. But this would be very, very slow. Instead we will * define a canonical ordering on S(n), and use it to reject representations * which are not lexicographically minimal relative to this ordering. * * Indeed, if we (tentatively) assign a permutation to the first generator * of a group presentation, and notice that that permutation is not * a minimal one, we may ignore it and move on to the next possibility. * In particular, we needn't explicitly go through all the * (n!)^(num generators - 1) ways of assigning permutations to the * remaining generators. Most permutations aren't minimal, so this * speeds our algorithm up enormously. * * So how do we define the canonical ordering on S(n), and how do * we recognize the minimal element in an equivalence class? * We'll answer the second question before the first. * * An element of S(n) is defined up to conjugacy by the structure * of its cycles, for example * * (x)(x)(xx)(xxx)(xxx) * * Define the "minimal" element in the conjugacy class to be * the one obtained by filling in the symbols 0, 1, ..., n-1 * in order, e.g. * * (0)(1)(23)(456)(789) * * Proposition. When written in the from * * 0 1 2 3 4 5 6 7 8 9 * -> 0 1 3 2 5 6 4 8 9 7 * * this minimal form is lexicographically least among all its conjugates. * * Proof. To minimize the first entry, we'd like to map 0 to 0. * This is possible iff a cyclic of length one is present. Similarly, * to minimize the second element we'd like to map 1 to 1, which is * possible iff a second cycle of length one is present. Eventually * we run out of cycles of length one, and the best we can do is to * map the symbol i to i+1. For minimality we'd like to map something * to i as soon as possible, which means being part of a minimal length * cycle. And so on until we've filled in the whole permutation. Q.E.D. * * So we let the canonical ordering on S(n) be the lexicographic ordering. * The above proposition implies that a minimal element relative to * this order is of the form, e.g., (0)(1)(23)(456)(789). * * The above definitions do not carry over to representations in Z/n. * For example, the permutation (02)(13) is an element of the standard * Z/n subgroup of S(n), but its minimal form (01)(23) is not. * Instead we'll use the natural ordering Z/n already has, namely * 0, 1, ..., n-1. * * Proposition. For each nonzero element k of Z/n, there is an * automorphism of Z/n taking k to gcd(k,n). * * Proof. Let * g = gcd(k,n) * k' = k/g * n' = n/g * * An automorphism of Z/n is defined by a map f(i) = p*i, for some p * relatively prime to n. For convenience, we'll find an automorphism * taking g to k; that is, we'll find the inverse of the automorphism * advertised in the statement of the proposition. To do this, we need * to find a p such that * * (1) p*g == k (mod n) * and * (2) p and n are relatively prime. * * I claim that p = k' + n/g' does the job, where g' is the product * of all factors of g (with original multiplicities) which also occur * (at least once) in k'. [Note that g' is not quite the same thing as * the greatest common divisor of g and k'. For example, if k' = 2*3 = 6 * and g = 3*3*5 = 45, then g' = 3*3 = 9 even though gcd(g,k') = 3.] * * Clearly p satisfies condition (1): * * p*g = g*k' + (g/g')*n == k (mod n) * * To check condition (2), we must check whether p = k' + n/g' can have * any prime factors in common with n. There are three kinds of prime * factors of n to consider: * * (A) prime factors which occur in n but not in g, * (B) prime factors which occur in g but not in g', * (C) prime factors which occur in g'. * * Type (A) factors divide n/g'. But they cannot divide k', because * if they did they'd be part of g. Therefore type (A) factors do not * divide p. * * Type (B) factors also divide n/g' but not k', so they don't divide p. * * Type (C) factors divide k' (this is obvious from the definition of g') * but do not divide n/g' (additional copies of the factor can't * lie in n/g because that would make them common to both k' and n' and * therefore part of g, and can't lie in g/g' because the definition * of g' already includes all copies of each included factor). * * Q.E.D. * * Corollary. A nonzero element k of Z/n is minimal under automorphisms * of Z/n iff k divides n. * * Proof. * (<==) If k divides n, then k is an element of order n/k. Lesser * elements must have higher order, and therefore cannot be equivalent * to k under any automorphism. * (==>) If k does not divide n, then the automorphism provided by the * preceding proposition takes k to gcd(k,n), which is less than k. * Q.E.D. */ /* * Minor technical comment (which you may ingore). * * The algorithm for finding representations into Z/n does not * make use of the fact that Z/n is abelian, even though doing so * might speed it up a tiny bit. I felt it was more important * to keep the overall structure of the algorithm the same for * the Z/n and S(n) cases (and perhaps eventually for Dn if it * gets added). And in any case, finding representations into * Z/n isn't a bottleneck: creating the final Triangulations * is much slower than finding the representations to begin with. */ #include "kernel.h" static int **compute_Sn(int n); static void free_Sn(int **Sn, int n_factorial); static int factorial(int n); static Boolean first_indices_all_zero(int *representation_by_index, int num_generators); static Boolean group_element_is_Sn_minimal(int *permutation, int num_sheets); static Boolean group_element_is_Zn_minimal(int index, int num_sheets); static Boolean candidateSn_is_valid(int **candidateSn, int n, int **group_relations, int num_relations); static Boolean candidateZn_is_valid(int *candidateZn, int n, int **group_relations, int num_relations); static Boolean candidateSn_is_transitive(int **candidateSn, int num_generators, int n); static Boolean candidateZn_is_transitive(int *candidateZn, int num_generators, int n); static Boolean candidateSn_is_conjugacy_minimal(int **candidateSn, int num_generators, int n, int **Sn, int n_factorial); static Boolean candidateZn_is_conjugacy_minimal(int *candidateZn, int num_generators, int n); static RepresentationIntoSn *convert_candidateSn_to_original_generators(int **candidateSn, int n, int num_original_generators, int **original_generators, Triangulation *manifold, int **meridians, int **longitudes); static RepresentationIntoSn *convert_candidateZn_to_original_generators(int *candidateZn, int n, int num_original_generators, int **original_generators, Triangulation *manifold, int **meridians, int **longitudes); static RepresentationIntoSn *initialize_new_representation(int num_original_generators, int n, int num_cusps); static void word_to_Sn(int **candidateSn, int *word, int *permutation, int n); static int word_to_Zn(int *candidateZn, int *word, int n); static void compute_primitive_Dehn_coefficients(Cusp *cusp, int *primitive_m, int *primitive_l); static void compose_with_power(int *product, int *factor, int power, int n); static void Zn_to_Sn(int element_of_Zn, int *element_of_Sn, int n); static void compute_covering_type(RepresentationIntoSn *representation, int num_generators, int n); static void free_representation(RepresentationIntoSn *representation, int num_generators, int num_cusps); RepresentationList *find_representations( Triangulation *manifold, int n, PermutationSubgroup range) { RepresentationList *representation_list; int i, n_factorial, range_size, num_simplified_generators, num_simplified_relations, num_original_generators, **simplified_group_relations, **original_generators, **meridians, **longitudes, digit, **Sn, *representation_by_index, **candidateSn, *candidateZn; GroupPresentation *simplified_group; RepresentationIntoSn *new_representation; /* * Begin with a quick error check. */ if (manifold == NULL) uFatalError("find_representations", "representations"); /* * Make sure the manifold has a set of generators. */ choose_generators(manifold, FALSE, FALSE); /* * Allocate the RepresentationList. */ representation_list = NEW_STRUCT(RepresentationList); representation_list->num_generators = manifold->num_generators; representation_list->num_sheets = n; representation_list->num_cusps = manifold->num_cusps; representation_list->list = NULL; /* * The cusps must be unfilled or have integer Dehn filling * coefficients (non relatively prime integers are OK -- they * define orbifolds). Otherwise we can't define a representation * into S(n) in any meaningful way, so we return an empty list. */ if (all_Dehn_coefficients_are_integers(manifold) == FALSE) return representation_list; /* * If n is less than 1, return an empty list. */ if (n < 1) return representation_list; /* * Get a simplified presentation for the fundamental group. * Use minimize_number_of_generators == TRUE, because the number * of (potential) representations is an exponential function * of the number of generators. At the end we'll convert * the final representations to the standard generators defined in * choose_generators(). * * 97/4/7 If the group is trivial, return an empty list. */ simplified_group = fundamental_group(manifold, TRUE, TRUE, TRUE); num_simplified_generators = fg_get_num_generators(simplified_group); if (num_simplified_generators == 0) { free_group_presentation(simplified_group); return representation_list; } num_simplified_relations = fg_get_num_relations(simplified_group); simplified_group_relations = NEW_ARRAY(num_simplified_relations, int *); for (i = 0; i < num_simplified_relations; i++) simplified_group_relations[i] = fg_get_relation(simplified_group, i); num_original_generators = manifold->num_generators; original_generators = NEW_ARRAY(num_original_generators, int *); for (i = 0; i < num_original_generators; i++) original_generators[i] = fg_get_original_generator(simplified_group, i); meridians = NEW_ARRAY(manifold->num_cusps, int *); longitudes = NEW_ARRAY(manifold->num_cusps, int *); for (i = 0; i < manifold->num_cusps; i++) { meridians [i] = fg_get_meridian (simplified_group, i); longitudes[i] = fg_get_longitude(simplified_group, i); } free_group_presentation(simplified_group); /* * If the range is all of S(n), it will be convenient to precompute * an array containing all its elements. */ if (range == permutation_subgroup_Sn) { n_factorial = factorial(n); Sn = compute_Sn(n); } else { n_factorial = 0; Sn = NULL; } /* * If the range is all of S(n), then each "candidate" representation * in the loop below will be expressed as an array of pointers * to rows of the array Sn, one pointer for each generator. */ if (range == permutation_subgroup_Sn) candidateSn = NEW_ARRAY(num_simplified_generators, int *); else candidateSn = NULL; /* * The representation_by_index array keeps track of the index * of the group element assigned to each generator. If the range * is all of S(n), the indices 0, 1, ..., (n_factorial - 1) refer * to the permutations Sn[0], Sn[1], ..., Sn[n_factorial - 1]. * If the range is Z/n, the indices 0, 1, ..., n-1 refer naturally * to the corresponding elements of Z/n. * * N.B. The extra entry at the end of the representation_by_index * array is used to detect when the enumeration is complete. */ representation_by_index = NEW_ARRAY(num_simplified_generators + 1, int); if (range == permutation_subgroup_Sn) range_size = n_factorial; else range_size = n; /* * Initialize all representation indices to 0. */ for (i = 0; i < num_simplified_generators + 1; i++) representation_by_index[i] = 0; /* * Loop through all possible assignments of range elements to generators. * Roughly speaking, this is just like counting base range_size, with * the least significant digits at the left. For example, if n = 3, * range_size = 3! = 6, and num_simplified_generators = 4, the * enumeration would be * * 00000 * 10000 * 20000 * 30000 * 40000 * 50000 * 01000 * 11000 * 21000 * ... * 35550 * 45550 * 55550 * 00001 <-- the trailing 1 means were done */ while (representation_by_index[num_simplified_generators] == 0) /* Loop until we reach 00001 */ { /* * If the range is all of S(n), convert the representation_by_index[] * to an array of pointers to rows of Sn. Otherwise define * candidateZn to be a synomym for the representation_by_index[] * array itself. */ if (range == permutation_subgroup_Sn) for (i = 0; i < num_simplified_generators; i++) candidateSn[i] = Sn[representation_by_index[i]]; else candidateZn = representation_by_index; /* * Every representation is conjugate to one in which the high-order * permutation, i.e. * * representation_by_index[num_simplified_generators - 1]) * * is minimal (as defined in the section Conjugacy and Minimality * at the top of this file), so we may safely ignore representations * in which the high-order permutation isn't minimal. In practice, * this means that whenever the first (num_simplified_generators - 1) * permutation indices are zero, we check the last index, and if * it's not a minimal permutation we increment the index immediately. */ if ( first_indices_all_zero(representation_by_index, num_simplified_generators) == TRUE && ( range == permutation_subgroup_Sn ? group_element_is_Sn_minimal(candidateSn[num_simplified_generators - 1], n) == FALSE : group_element_is_Zn_minimal(candidateZn[num_simplified_generators - 1], n) == FALSE ) ) { /* * Increment the high order index, and check for a carry. */ representation_by_index[num_simplified_generators - 1]++; if (representation_by_index[num_simplified_generators - 1] == range_size) { representation_by_index[num_simplified_generators - 1] = 0; representation_by_index[num_simplified_generators] = 1; } /* * Continue with the while() loop. */ continue; } /* * Add the candidate to the representation_list iff it * * (1) respects the group relations, * (2) is transitive, and * (3) is minimal under conjugacy. * * The precise meaning of condition 3 is explained in * candidateSn_is_conjugacy_minimal(). * * Note: A quick experiment showed that the order of the calls * to candidateSn_is_valid() and candidateSn_is_transitive() has * no consistent effect on the run time, but the call to * candidateSn_is_conjugacy_minimal() should definitely be last. */ if (range == permutation_subgroup_Sn ? ( candidateSn_is_valid(candidateSn, n, simplified_group_relations, num_simplified_relations) && candidateSn_is_transitive(candidateSn, num_simplified_generators, n) && candidateSn_is_conjugacy_minimal(candidateSn, num_simplified_generators, n, Sn, n_factorial) ) : ( candidateZn_is_valid(candidateZn, n, simplified_group_relations, num_simplified_relations) && candidateZn_is_transitive(candidateZn, num_simplified_generators, n) && candidateZn_is_conjugacy_minimal(candidateZn, num_simplified_generators, n) ) ) { new_representation = range == permutation_subgroup_Sn ? convert_candidateSn_to_original_generators(candidateSn, n, num_original_generators, original_generators, manifold, meridians, longitudes) : convert_candidateZn_to_original_generators(candidateZn, n, num_original_generators, original_generators, manifold, meridians, longitudes); new_representation->next = representation_list->list; representation_list->list = new_representation; } /* * Increment the representation_by_index base range_size * as described above. * * (As a special case, if n == 1 just break, since the base 1 * arithmetic won't work.) */ if (n == 1) break; digit = 0; /* start at the low-order digit */ while (TRUE) /* loop until we break */ { representation_by_index[digit]++; /* increment current digit */ if (representation_by_index[digit] == range_size) /* is there a carry? */ { representation_by_index[digit] = 0; /* set this digit to 0 */ digit++; /* prepare to increment next digit */ } else break; /* no carry, so we're done */ } } /* * Free local arrays. */ if (range == permutation_subgroup_Sn) { free_Sn(Sn, n_factorial); my_free(candidateSn); } my_free(representation_by_index); for (i = 0; i < num_simplified_relations; i++) fg_free_relation(simplified_group_relations[i]); my_free(simplified_group_relations); for (i = 0; i < num_original_generators; i++) fg_free_relation(original_generators[i]); my_free(original_generators); for (i = 0; i < manifold->num_cusps; i++) { fg_free_relation(meridians[i]); fg_free_relation(longitudes[i]); } my_free(meridians); my_free(longitudes); /* * Done! */ return representation_list; } static int **compute_Sn( int n) { /* * We want to enumerate the elements of S(n) in a systematic way. * For concreteness, consider the problem of enumerating the * elements of S(4). To ensure that each symbol {0,1,2,3} * appears in the last position, we cyclically advance the * symbols four times: * * 0123 * 3012 * 2301 * 1230 * * For each fixed choice for the last position, we repeat * the process for the "subword" consisting of the first * three positions: * * 0123 * 0123 * 2013 * 1203 * 3012 * 3012 * 1302 * 0132 * 2301 * 2301 * 0231 * 3021 * 1230 * 1230 * 3120 * 2130 * * With the last two positions fixed, permute the remaining * subword, etc. * * 0123 * 0123 * 0123 * 1023 * 2013 * 2013 * 0213 * etc. * etc. * * The rule for obtaining the final list * * 0123 * 1023 * 2013 * 0213 * 1203 * 2103 * 3012 * 0312 * 1302 * etc. * 0123 * 1023 * 2013 * 0213 * 1203 * 2103 * 3012 * 0312 * 1302 * 3102 * 0132 * 1032 * 2301 * 3201 * 0231 * 2031 * 3021 * 0321 * 1230 * 2130 * 3120 * 1320 * 2310 * 3210 * * is that permutation 0 is the identity, and thereafter permutation i * is obtained from permutation i-1 by * * cycling the first 2 entries iff i is divisible by 1!, and * cycling the first 3 entries iff i is divisible by 2!, and * cycling the first 4 entries iff i is divisible by 3!. * * Note that the above clauses are joined by "and", so that, e.g. * for i == 12 you'd first cycle the first 2 entries, then the * first 3, and then all 4 to obtain the correct final permutation. */ int **Sn, n_factorial, i, j, k, subword_length, required_divisor, temp; /* * Compute n_factorial. */ n_factorial = factorial(n); /* * Allocate space for the array of permutations. */ Sn = NEW_ARRAY(n_factorial, int *); for (i = 0; i < n_factorial; i++) Sn[i] = NEW_ARRAY(n, int); /* * Sn[0] will be the identity permutation. */ for (j = 0; j < n; j++) Sn[0][j] = j; /* * All subsequent permutations are defined recursively, * by cycling subwords as explained above. */ for (i = 1; i < n_factorial; i++) { /* * Copy Sn[i-1] into Sn[i]. */ for (j = 0; j < n; j++) Sn[i][j] = Sn[i-1][j]; /* * Cycle an initial subword iff (subword_length - 1)! divides i. * (Note that shorter subwords cycle before longer ones, to * return the longer subword to its previous condition.) */ for ( subword_length = 2, required_divisor = 1; subword_length <= n; required_divisor *= subword_length, subword_length++) { if (i % required_divisor == 0) { temp = Sn[i][subword_length - 1]; for (k = subword_length - 1; k > 0; --k) Sn[i][k] = Sn[i][k-1]; Sn[i][0] = temp; } } } return Sn; } static void free_Sn( int **Sn, int n_factorial) { int i; for (i = 0; i < n_factorial; i++) my_free(Sn[i]); my_free(Sn); } static int factorial( int n) { int n_factorial; n_factorial = 1; while (n > 0) { n_factorial *= n; --n; } return n_factorial; } static Boolean first_indices_all_zero( int *representation_by_index, int num_generators) { int i; for (i = 0; i < num_generators - 1; i++) if (representation_by_index[i] != 0) return FALSE; return TRUE; } static Boolean group_element_is_Sn_minimal( int *permutation, int num_sheets) { /* * Is the permutation minimal, as defined in find_representations()? * Recall that an example of a minimal permutation is * * (0)(1)(23)(456)(789) * * or, in another from, * * 0 1 2 3 4 5 6 7 8 9 * -> 0 1 3 2 5 6 4 8 9 7 * * To test whether a permutation is minimal, we must check that * (1) cycles consist of consecutive integers, and * (2) the cycles lengths occur in ascending order. */ int position, cycle_start, old_cycle_length, new_cycle_length; position = 0; cycle_start = 0; old_cycle_length = 0; while (position < num_sheets) { if (permutation[position] == position + 1) /* cycle continues */ { position++; } else if (permutation[position] == cycle_start) /* cycle completes */ { new_cycle_length = (position - cycle_start) + 1; if (new_cycle_length < old_cycle_length) return FALSE; old_cycle_length = new_cycle_length; position++; cycle_start = position; } else /* bad cycle */ return FALSE; } return TRUE; } static Boolean group_element_is_Zn_minimal( int index, int num_sheets) { if (index == 0) /* * The identity element of Z/n is always minimal, * because it's fixed by all automorphisms. */ return TRUE; else /* By a corollary in the documentation at the top of this file, * a nonzero element k of Z/n is minimal under automorphisms * of Z/n iff k divides n. */ return (num_sheets % index == 0); } static Boolean candidateSn_is_valid( int **candidateSn, int n, int **group_relations, int num_relations) { Boolean satisfies_all_relations; int *permutation, i, j; /* * Initialize satisfies_all_relations to TRUE. */ satisfies_all_relations = TRUE; /* * Allocate scratch space to hold permutation which the * candidate assigns to a given group relation. */ permutation = NEW_ARRAY(n, int); /* * Check each relation. */ for (i = 0; i < num_relations; i++) { /* * Compute the permutation which the candidate assigns * to this group relation. */ word_to_Sn(candidateSn, group_relations[i], permutation, n); /* * The relation is satisfied iff the permutation is the identity. */ for (j = 0; j < n; j++) if (permutation[j] != j) satisfies_all_relations = FALSE; /* * If this relation isn't satisfied, there is no point * in looking at the remaining ones. */ if (satisfies_all_relations == FALSE) break; } /* * Free the scratch space. */ my_free(permutation); /* * All done. */ return satisfies_all_relations; } static Boolean candidateZn_is_valid( int *candidateZn, int n, int **group_relations, int num_relations) { Boolean satisfies_all_relations; int group_element, i; /* * Initialize satisfies_all_relations to TRUE. */ satisfies_all_relations = TRUE; /* * Check each relation. */ for (i = 0; i < num_relations; i++) { /* * Compute the permutation which the candidate assigns * to this group relation. */ group_element = word_to_Zn(candidateZn, group_relations[i], n); /* * The relation is satisfied iff the group_element * is the identity element of Z/n. */ if (group_element != 0) satisfies_all_relations = FALSE; /* * If this relation isn't satisfied, there is no point * in looking at the remaining ones. */ if (satisfies_all_relations == FALSE) break; } /* * All done. */ return satisfies_all_relations; } static Boolean candidateSn_is_transitive( int **candidateSn, int num_generators, int n) { Boolean *visited, progress; int count, i, j; /* * Is the candidate transitive? * In other words, is the corresponding covering space connected? * * Start on sheet 0 of the cover and see which other sheets we can * get to using the permutations assigned to the generators. * A "visited" array will keep track of which sheets we can reach. */ visited = NEW_ARRAY(n, Boolean); for (i = 0; i < n; i++) visited[i] = FALSE; /* * Begin by visiting sheet 0. */ visited[0] = TRUE; count = 1; /* * See which other sheets we can visit by following generators of the * fundamental group. Keep going as long as we're making progress. */ do { progress = FALSE; for (i = 0; i < num_generators; i++) for (j = 0; j < n; j++) if (visited[j]) if ( ! visited[candidateSn[i][j]]) { visited[candidateSn[i][j]] = TRUE; count++; progress = TRUE; } } while (progress == TRUE); /* * Free the visited array. */ my_free(visited); /* * Return TRUE iff we visited all the sheets. */ return (count == n); } static Boolean candidateZn_is_transitive( int *candidateZn, int num_generators, int n) { /* * The candidate will be transitive iff the greatest common divisor * of the group elements is 1. */ int the_gcd, i; the_gcd = n; for (i = 0; i < num_generators; i++) the_gcd = gcd(the_gcd, candidateZn[i]); return (the_gcd == 1); } static Boolean candidateSn_is_conjugacy_minimal( int **candidateSn, int num_generators, int n, int **Sn, int n_factorial) { /* * Define the lexicographic ordering on S(n) to be, e.g., * * 0123 * 0132 * 0213 * 0231 * 0312 * 0321 * 1023 * 1032 * etc. * * That is, the symbol in the first is most important, and the * symbol in the last position is least important. * * Warnings: * * (1) Don't confuse this with the lexicographic ordering on the * representations, in which the last generator is most important, * and the first generator is least important! * * (2) This lexicographic ordering on S(n) is NOT the order * in which the elements of the array Sn appear! * * We want to check whether the given candidate is minimal among all * its conjugates. To compare the candidate to one of its conjugates, * we will need to use BOTH the right-to-left ordering on the set * of all representations, and the left-to-right ordering on S(n). * Clear?!? */ int i, j, k, *scratch, comparison; /* * Allocate scratch space to compute the conjugate. */ scratch = NEW_ARRAY(n, int); /* * Consider each of the possible n! conjugating permutations. */ for (i = 0; i < n_factorial; i++) { /* * Consider the permutation assigned to each generator, * starting with the last one and working backwards. */ for (j = num_generators; --j >= 0; ) { /* * Copy the inverse of Sn[i] into scratch. */ for (k = 0; k < n; k++) scratch[Sn[i][k]] = k; /* * Multiply by the permutation assigned to generator j. */ for (k = 0; k < n; k++) scratch[k] = candidateSn[j][scratch[k]]; /* * Multiply by Sn[i]. */ for (k = 0; k < n; k++) scratch[k] = Sn[i][scratch[k]]; /* * Scratch now holds * * Sn[i]^-1 * candidateSn[j] * Sn[i] * * Is it less than, equal to, or greater than * candidateSn[j] itself? */ for (k = 0; k < n; k++) { if (scratch[k] < candidateSn[j][k]) { comparison = -1; break; } else if (scratch[k] > candidateSn[j][k]) { comparison = +1; break; } else { comparison = 0; continue; } } /* * If comparison == -1, we've found a conjugate which * is less than the original representation, so the * original representation can't be minimal. */ if (comparison == -1) { my_free(scratch); return FALSE; } /* * If comparison == +1, then this conjugate is greater * than the original representation. We should break * out of the j-loop and move on to the next conjugator. */ if (comparison == +1) break; /* * If comparison == 0, then we must continue with the * j-loop to check the effect of the current conjugator * on the next generator. */ } } /* * The representation has no conjugates lexicographically less * than itself, so return TRUE. */ my_free(scratch); return TRUE; } static Boolean candidateZn_is_conjugacy_minimal( int *candidateZn, int num_generators, int n) { /* * The representations are ordered lexicographically, but in * a backwards sort of way: the last generator is most important, * and the first generator is least important! * * Relative to this ordering, we want to check whether the given * candidate is minimal among all its conjugates. */ int p, i, image; /* * According to the section Cyclic Coverings and Representations in Z/n * at the top of this file, if two connected cyclic coverings are * equivalent, they are equivalent by a map of the form f(i) = p*i, * for some p relatively prime to n. Consider each of the possible * values for p, and test whether any produce an equivalent covering * which is lexicographically less than this one. */ for (p = 2; p < n; p++) { /* * p must be relative prime to n, or we don't have an automorphism. */ if (gcd(p,n) != 1) continue; /* * Consider the group element assigned to each generator, * starting with the last one and working backwards. */ for (i = num_generators; --i >= 0; ) { /* * Where does the automorphism take this generator? */ image = (p * candidateZn[i]) % n; /* * If the image is less than candidateZn[i], we've found * a conjugate which is less than the original representation, * so the original representation can't be minimal. */ if (image < candidateZn[i]) return FALSE; /* * If the image is greater than candidateZn[i], then this * conjugate is greater than the original representation. * We should break out of the i-loop and move on to the * next value of p. */ if (image > candidateZn[i]) break; /* * If the image equals candidateZn[i], then we must continue * with the i-loop to check the effect of the current * automorphism on the next generator. */ } } /* * The representation has no conjugates lexicographically less * than itself, so return TRUE. */ return TRUE; } static RepresentationIntoSn *convert_candidateSn_to_original_generators( int **candidateSn, int n, int num_original_generators, int **original_generators, Triangulation *manifold, int **meridians, int **longitudes) { /* * Convert the candidate from the simplified generators to the * original generators, and return it as a RepresentationIntoSn. */ RepresentationIntoSn *representation; int i, j, *meridional_permutation, *longitudinal_permutation, primitive_m, primitive_l; /* * Allocate and initialize the RepresentationIntoSn data structure. */ representation = initialize_new_representation(num_original_generators, n, manifold->num_cusps); /* * Allocate scratch space. */ meridional_permutation = NEW_ARRAY(n, int); longitudinal_permutation = NEW_ARRAY(n, int); /* * Compute the permutation assigned to each original generator. */ for (i = 0; i < num_original_generators; i++) word_to_Sn(candidateSn, original_generators[i], representation->image[i], n); /* * Compute the permutation assigned to each Dehn filling curve. */ for (i = 0; i < manifold->num_cusps; i++) { /* * Initialize representation->primitive_Dehn_image[i] to the identity. */ for (j = 0; j < n; j++) representation->primitive_Dehn_image[i][j] = j; /* * If the cusp is unfilled, its primitive_Dehn_image should * remain the identity. */ if (find_cusp(manifold, i)->is_complete == TRUE) continue; /* * Compute the permutations assigned to the meridian and longitude. */ word_to_Sn(candidateSn, meridians[i], meridional_permutation, n); word_to_Sn(candidateSn, longitudes[i], longitudinal_permutation, n); /* * Figure out the coefficients of the primitive Dehn filling curve. */ compute_primitive_Dehn_coefficients(find_cusp(manifold, i), &primitive_m, &primitive_l); /* * Compute * meridional_permutation^primitive_m * * longitudinal_permutation^primitive_l */ compose_with_power(representation->primitive_Dehn_image[i], meridional_permutation, primitive_m, n); compose_with_power(representation->primitive_Dehn_image[i], longitudinal_permutation, primitive_l, n); } /* * Free scratch space. */ my_free(meridional_permutation); my_free(longitudinal_permutation); /* * Does this representation define an irregular, regular or cyclic cover? */ compute_covering_type(representation, num_original_generators, n); /* * All done. */ return representation; } static RepresentationIntoSn *convert_candidateZn_to_original_generators( int *candidateZn, int n, int num_original_generators, int **original_generators, Triangulation *manifold, int **meridians, int **longitudes) { /* * Convert the candidate from the simplified generators to the * original generators, and return it as a RepresentationIntoSn. * * This is also the point where we convert from abstract elements * {0, 1, ..., n-1} of Z/n to the corresponding permutations in S(n). */ RepresentationIntoSn *representation; int i, meridional_transformation, longitudinal_transformation, primitive_m, primitive_l; /* * Allocate and initialize the RepresentationIntoSn data structure. */ representation = initialize_new_representation(num_original_generators, n, manifold->num_cusps); /* * Compute the element of Z/n assigned to each original generator, * and convert it to a full-fledged permutation. */ for (i = 0; i < num_original_generators; i++) Zn_to_Sn( word_to_Zn(candidateZn, original_generators[i], n), representation->image[i], n); /* * Compute the permutation assigned to each Dehn filling curve. */ for (i = 0; i < manifold->num_cusps; i++) { /* * If the cusp is unfilled, its primitive_Dehn_image should * be the identity. */ if (find_cusp(manifold, i)->is_complete == TRUE) { Zn_to_Sn(0, representation->primitive_Dehn_image[i], n); continue; } /* * Compute the group elements assigned to the meridian and longitude. */ meridional_transformation = word_to_Zn(candidateZn, meridians[i], n); longitudinal_transformation = word_to_Zn(candidateZn, longitudes[i], n); /* * Figure out the coefficients of the primitive Dehn filling curve. */ compute_primitive_Dehn_coefficients(find_cusp(manifold, i), &primitive_m, &primitive_l); /* * Compute * * primitive_m * meridional_transformation * + primitive_l * longitudinal_transformation * * and convert it to a full-fledged permutation. */ Zn_to_Sn( ( primitive_m * meridional_transformation + primitive_l * longitudinal_transformation), representation->primitive_Dehn_image[i], n); } /* * A representation into Z/n defines a cyclic covering. */ representation->covering_type = cyclic_cover; /* * All done. */ return representation; } static RepresentationIntoSn *initialize_new_representation( int num_original_generators, int n, int num_cusps) { RepresentationIntoSn *representation; int i; representation = NEW_STRUCT(RepresentationIntoSn); representation->image = NEW_ARRAY(num_original_generators, int *); for (i = 0; i < num_original_generators; i++) representation->image[i] = NEW_ARRAY(n, int); representation->primitive_Dehn_image = NEW_ARRAY(num_cusps, int *); for (i = 0; i < num_cusps; i++) representation->primitive_Dehn_image[i] = NEW_ARRAY(n, int); representation->covering_type = unknown_cover; representation->next = NULL; return representation; } static void word_to_Sn( int **candidateSn, int *word, int *permutation, int n) { /* * Compute the permutation which candidate assigns to the given word. */ int i, j, gen, *factor; /* * Allocate scratch space. */ factor = NEW_ARRAY(n, int); /* * Initialize the permutation to the identity. */ for (i = 0; i < n; i++) permutation[i] = i; /* * The word consists of a product of simplified generators. * Compose the corresponding permutations. */ for (i = 0; word[i] != 0; i++) { /* * Let the array "factor" hold the permutation which the * candidate assigns to the letter word[i]. Recall that * word[i] takes on the positive values +1, +2, ... for * positive generators and the negative values -1, -2, ... * for inverses of generators. */ if (word[i] > 0) /* positive generator */ { /* * Copy the permutation directly. */ gen = word[i] - 1; for (j = 0; j < n; j++) factor[j] = candidateSn[gen][j]; } else /* negative generator */ { /* * Compute the inverse permutation. */ gen = (-word[i]) - 1; for (j = 0; j < n; j++) factor[candidateSn[gen][j]] = j; } /* * Apply the factor to the permutation. */ for (j = 0; j < n; j++) permutation[j] = factor[permutation[j]]; } /* * Free scratch space. */ my_free(factor); } static int word_to_Zn( int *candidateZn, int *word, int n) { /* * Compute the element of Z/n which candidate assigns to the given word. */ int i, gen, sum; /* * Initialize the sum to the identity. */ sum = 0; /* * The word consists of a product of simplified generators. * Sum the corresponding group elements. */ for (i = 0; word[i] != 0; i++) { /* * Add in the group element which the candidate assigns to the * letter word[i]. Recall that word[i] takes on the positive * values +1, +2, ... for positive generators and the negative * values -1, -2, ... for inverses of generators. */ if (word[i] > 0) /* positive generator */ { gen = word[i] - 1; sum += candidateZn[gen]; } else /* negative generator */ { gen = (-word[i]) - 1; sum -= candidateZn[gen]; } } /* * Normalize the result to the range [0, n-1]. */ sum = sum % n; if (sum < 0) /* The ANSI operator % is not well defined. */ sum += n; /* If it's negative, add in another +n. */ return sum; } static void compute_primitive_Dehn_coefficients( Cusp *cusp, int *primitive_m, int *primitive_l) { int the_gcd; /* find_representations() has already checked that */ /* the Dehn filling coefficients are integers. */ the_gcd = gcd((long int)cusp->m, (long int)cusp->l); *primitive_m = (int)cusp->m / the_gcd; *primitive_l = (int)cusp->l / the_gcd; } static void compose_with_power( int *product, int *factor, int power, int n) { int positive_power, *positive_factor, i; /* * Allocate scratch space. */ positive_factor = NEW_ARRAY(n, int); /* * If power > 0, use the factor as given. * If power < 0, use its inverse. */ if (power > 0) { for (i = 0; i < n; i++) positive_factor[i] = factor[i]; positive_power = power; } else { for (i = 0; i < n; i++) positive_factor[factor[i]] = i; positive_power = - power; } /* * Compose product[i] with positive_factor to the positive_power. */ while (--positive_power >= 0) for (i = 0; i < n; i++) product[i] = positive_factor[product[i]]; /* * Free scratch space. */ my_free(positive_factor); } static void Zn_to_Sn( int element_of_Zn, int *element_of_Sn, int n) { /* * Convert an abstract element of Z/n to the corresponding * permutation in S(n). Use the natural correspondence which maps * each element k of Z/n to the permutation i->i+k (mod n) in S(n). */ int i; for (i = 0; i < n; i++) element_of_Sn[i] = (i + element_of_Zn) % n; return; } static void compute_covering_type( RepresentationIntoSn *representation, int num_generators, int n) { /* * Please see cover.h for the definition of a regular cover. * To check whether the given representation defines a regular cover, * it's sufficient to check whether for each sheet k, there is * a covering transformation taking sheet 0 to sheet k. * * While we're at it, we'll check whether the group of covering * transformations is cyclic of order n. A generator for a cyclic * group of covering transformations is a transformation whose * powers take sheet 0 to each of the other sheets. */ Boolean *transformation_found, progress; int k, *f, i, count, sheet, gen, nbr, nbr_must_map_to, image_of_zero, subgroup_order; /* * Keep track of the values of k for which we have found a * covering transformation taking sheet 0 to sheet k. */ transformation_found = NEW_ARRAY(n, Boolean); for (k = 0; k < n; k++) transformation_found[k] = FALSE; /* * Allocate space for the permutation f() which a covering * transformation induces on the sheets. */ f = NEW_ARRAY(n, int); /* * Attempt to find a covering transformation taking sheet 0 * to each sheet k. */ for (k = 0; k < n; k++) { /* * Skip values of k for which we've already found a covering * transformation (as a power of some previously computed * transformation). */ if (transformation_found[k] == TRUE) continue; /* * Let f() be a permutation of the sheets {0, 1, ..., n-1} * which, we hope, will define a covering transformation. * Initialize f() to all -1's, to show that no values have * been decided yet. */ for (i = 0; i < n; i++) f[i] = -1; /* * As an "unnecessary" error check, count how many values * of f() get assigned. */ count = 0; /* * Set f(0) = k. */ f[0] = k; count++; /* * Use the assignment of permutations to generators to deduce * the remaining values of f(). If we obtain contradictory * values for some f(sheet), then we know that there is no * covering transformation taking sheet 0 to sheet k. */ do { progress = FALSE; for (sheet = 0; sheet < n; sheet++) { /* * If we don't yet know where the covering transformation * takes this sheet, then we can't deduce where it takes * its neighbors. */ if (f[sheet] == -1) continue; /* * If we do know where the covering transformation takes * takes this sheet, then we can deduce where it must * take the neighbors. Consider each neighbor. */ for (gen = 0; gen < num_generators; gen++) { nbr = representation->image[gen][sheet]; nbr_must_map_to = representation->image[gen][f[sheet]]; if (f[nbr] == -1) { f[nbr] = nbr_must_map_to; count++; progress = TRUE; } else { if (f[nbr] != nbr_must_map_to) { representation->covering_type = irregular_cover; my_free(transformation_found); my_free(f); return; } } } } } while (progress == TRUE); /* * The covering is already known to be connected, * so we should have assigned exactly n value to f(). */ if (count != n) uFatalError("compute_covering_type", "representations"); /* * The last time through the above loop we assigned no new * values of f(), but it did serve to check that f() is * consistent with all the gluings. In other words, f() * is now known to be a valid covering transformation. */ /* * We can save a little computation time by looking at the * powers of f(), and seeing where they take sheet 0. * While we're at it, compute the order of the subgroup * of the group of covering transformations generated by f(). */ image_of_zero = 0; subgroup_order = 0; do { transformation_found[image_of_zero] = TRUE; subgroup_order++; image_of_zero = f[image_of_zero]; } while (image_of_zero != 0); /* * If f() generates the whole group of covering transformations, * then the cover is cyclic. */ if (subgroup_order == n) { representation->covering_type = cyclic_cover; my_free(transformation_found); my_free(f); return; } } /* * If the covering were either irregular or cyclic, we would have * returned from within the above loop. So it must be regular. */ representation->covering_type = regular_cover; /* * Free the local storage. */ my_free(transformation_found); my_free(f); } void free_representation_list( RepresentationList *representation_list) { RepresentationIntoSn *dead_representation; while (representation_list->list != NULL) { dead_representation = representation_list->list; representation_list->list = representation_list->list->next; free_representation(dead_representation, representation_list->num_generators, representation_list->num_cusps); } my_free(representation_list); } static void free_representation( RepresentationIntoSn *representation, int num_generators, int num_cusps) { int i; for (i = 0; i < num_generators; i++) my_free(representation->image[i]); my_free(representation->image); for (i = 0; i < num_cusps; i++) my_free(representation->primitive_Dehn_image[i]); my_free(representation->primitive_Dehn_image); my_free(representation); } snappea-3.0d3/SnapPeaKernel/code/shingling.c0100444000175000017500000006073006742675502017036 0ustar babbab/* * shingling.c * * This feature is being written for Bill Floyd and Jim Cannon. * Here is Bill's statement of the problem. * * Let D be a Dirichlet domain for the group action, and let S be * the generating set of non-identity group elements such that g(D) * intersects D. Given a nonnegative integer n, let B(n) be the * union of domains g(D) with word norm |g|_S <= n. * * Given a (probably small) nonnegative integer n, for output I * would like the circles at infinity corresponding to faces of * B(m) for m <= n. I would like to be able to do this for the * hyperbolic dodecahedral reflection group, and for other examples * (presumably found from SnapPea) where the dihedral angles are * submultiples of pi. */ #include "kernel.h" #define SAME_MATRIX_EPSILON 1e-4 #define TOO_FAR_EPSILON 1e-3 /* * matrix_on_tree() searches all nodes whose key values are within * TREE_EPSILON of the given key value. If TREE_EPSILON is too large, * the algorithm will waste time sifting through large numbers of * irrelevant matrices, but if it's too small you might end up adding * the same matrix over and over. */ #define TREE_EPSILON 1e-5 /* * Isometries are sometimes stored locally on NULL-terminated * singly linked lists... */ typedef struct IsometryListNode { O31Matrix m; struct IsometryListNode *next; } IsometryListNode; /* * ...and sometimes on binary trees. */ typedef struct IsometryTreeNode { O31Matrix m; /* the isometry */ struct IsometryTreeNode *left, /* the tree structure */ *right; double key; /* the sort key value */ struct IsometryTreeNode *next_on_stack, /* for tree traversals */ *next_on_stack1; } IsometryTreeNode; /* * Shingles are stored locally as NULL-terminated * singly linked lists of ShingleNodes. */ typedef struct ShingleNode { Shingle shingle; struct ShingleNode *next; } ShingleNode; static IsometryListNode *make_list_of_face_pairings(WEPolyhedron *polyhedron); static IsometryListNode *make_list_of_other_isometries(WEPolyhedron *polyhedron, IsometryListNode *s0); static void add_neighbors_to_s1(O31Matrix m, WEPolyhedron *polyhedron, IsometryListNode *s0, IsometryListNode *s1_begin, IsometryListNode ***s1_end); static Boolean intersection_is_nontrivial(WEPolyhedron *polyhedron, O31Matrix m); static Boolean vertex_in_polyhedron(O31Vector v, WEPolyhedron *polyhedron); static void add_isometry_list_node(IsometryListNode **isometry_list, O31Matrix m); static void add_isometry_tree_node(IsometryTreeNode **isometry_tree, O31Matrix m); static Boolean matrix_on_list(O31Matrix m, IsometryListNode *isometry_list); static Boolean matrix_on_tree(O31Matrix m, IsometryTreeNode *isometry_tree); static double key_value(O31Matrix m); static void add_shingle_node(ShingleNode **shingle_list, O31Matrix m0, O31Matrix m1, int index); static void free_isometry_node_list(IsometryListNode **isometry_list); static void free_isometry_node_tree(IsometryTreeNode **isometry_tree); static Shingling *convert_shingle_list_to_shingling(ShingleNode **shingle_list); Shingling *make_shingling( WEPolyhedron *polyhedron, int num_layers) { /* * Consider the tiling of H^3 by translates g(D) of the Dirichlet * domain D. Decompose the tiling into layers, defined inductively * as follows. L[-1] is the empty set. L[0] is the set containing * only the identity element. For n > 0, L[n] is the set obtained * by multiplying each element of of L[n-1] by each element of S * (defined above) and keeping only those elements which aren't * already in L[n-1] or L[n-2]. * * Abuse terminology slightly and think of L[n] sometimes as * a set of group elements g and sometimes as the union of the * corresponding translates g(D) of the Dirichlet domain. * * According to Bill Floyd's statement of the problem, the shingles * correspond to the faces of the boundary between each L[n-1] and * the next L[n]. It's easy to compute the shingles as we * compute the set L[n]: we get a shingle whenever an element * of L[n-1] shares a face (not just an edge or a vertex) with * an element of L[n]. This occurs when the element g of S which * takes an element of L[n-1] to an element of L[n] is a face * pairing isometry of the Dirichlet domain. So divide S into * two subsets * * S0 = elements g of S for which g(D) intersects D in a face * S1 = elements g of S for which g(D) intersects D in an edge or vertex */ IsometryListNode *s0, *s1, *g; IsometryTreeNode *prev_layer, *this_layer, *next_layer, *subtree_stack, *subtree_root; ShingleNode *shingle_list; O31Matrix new_matrix; Shingling *shingling; int i; /* * Shinglings of ideal polyhedra are infinite. * Don't try to compute them. * * Just to be robust, watch out for NULL polyhedra too. */ if (polyhedron == NULL || polyhedron->outradius == INFINITE_RADIUS) { shingling = NEW_STRUCT(Shingling); shingling->num_shingles = 0; shingling->shingles = NULL; return shingling; } /* * Set up S0 and S1 (defined above) as linked lists. */ s0 = make_list_of_face_pairings(polyhedron); s1 = make_list_of_other_isometries(polyhedron, s0); /* * Initialize prev_layer to L[-1] = {} and this_layer to L[0] = {id}. * The next_layer is initially empty. All three layers are stored * as binary trees. */ prev_layer = NULL; this_layer = NULL; next_layer = NULL; add_isometry_tree_node(&this_layer, O31_identity); /* * As we go along we'll accumulate Shingles onto a linked list. * At the end we'll copy them onto an array. */ shingle_list = NULL; /* * The parameter num_layers specifies how many layers to compute. * Compute the shingles corresponding to the faces of L[n] for * n <= num_layers. */ for (i = 0; i <= num_layers; i++) { /* * Multiply each tile in this_layer by each element of S0. * * Note: We read matrix products right-to-left, so * "premultiplication by an isometry" is the same as * "right multiplication by a matrix". */ /* * To avoid possible stack-heap collisions, we want to traverse * the tree without resorting to recursive function calls. * Put this_layer's root (if any) onto the subtree_stack. */ subtree_stack = this_layer; if (subtree_stack != NULL) subtree_stack->next_on_stack = NULL; /* * Pull a subtree off the subtree_stack, put its children * back onto the stack, and examine the matrix at the root. * Keep going until the subtree_stack is empty. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree_root = subtree_stack; subtree_stack = subtree_stack->next_on_stack; subtree_root->next_on_stack = NULL; /* * Put its left and right children back onto the subtree_stack. */ if (subtree_root->left != NULL) { subtree_root->left->next_on_stack = subtree_stack; subtree_stack = subtree_root->left; } if (subtree_root->right != NULL) { subtree_root->right->next_on_stack = subtree_stack; subtree_stack = subtree_root->right; } /* * Multiply subtree_root->m by each element g of S0. */ for (g = s0; g != NULL; g = g->next) { /* * new_matrix = subtree_root->m * g */ o31_product(subtree_root->m, g->m, new_matrix); /* * If the new_matrix isn't an element of prev_layer * or this_layer, then the face between the old_tile * and the new tile defines a shingle. */ if (matrix_on_tree(new_matrix, prev_layer) == FALSE && matrix_on_tree(new_matrix, this_layer) == FALSE) { add_shingle_node(&shingle_list, subtree_root->m, new_matrix, i); /* * Add the new_matrix to next_layer iff * * (1) it's not already there, and * (2) this isn't the last time through the loop * (in that case we won't need next_layer). */ if (i != num_layers && matrix_on_tree(new_matrix, next_layer) == FALSE) add_isometry_tree_node(&next_layer, new_matrix); } } /* * If this isn't the last time through the loop, * multiply subtree_root->m by each element of S1 * so we can be sure to compute all of next_layer. */ if (i != num_layers) for (g = s1; g != NULL; g = g->next) { o31_product(subtree_root->m, g->m, new_matrix); if (matrix_on_tree(new_matrix, prev_layer) == FALSE && matrix_on_tree(new_matrix, this_layer) == FALSE && matrix_on_tree(new_matrix, next_layer) == FALSE) add_isometry_tree_node(&next_layer, new_matrix); } } /* * Get ready to move on to the next level. */ free_isometry_node_tree(&prev_layer); prev_layer = this_layer; this_layer = next_layer; next_layer = NULL; } /* * Release the lists of isometries. */ free_isometry_node_list(&s0); free_isometry_node_list(&s1); free_isometry_node_tree(&prev_layer); free_isometry_node_tree(&this_layer); free_isometry_node_tree(&next_layer); /* * Repackage the results as a Shingling. * (This frees the ShingleNodes.) */ shingling = convert_shingle_list_to_shingling(&shingle_list); return shingling; } void free_shingling( Shingling *shingling) { if (shingling != NULL) { if (shingling->shingles != NULL) my_free(shingling->shingles); my_free(shingling); } } void compute_center_and_radials( Shingle *shingle, O31Matrix position, double scale) { O31Vector n, nn, u; double factor, radius; O31Vector e1 = {0.0, 1.0, 0.0, 0.0}, e2 = {0.0, 0.0, 1.0, 0.0}; /* * Let n be the current position of the shingle's normal vector. */ o31_matrix_times_vector(position, shingle->normal, n); /* * Let nn be a unit vector parallel to the projection of n onto * the xyz hyperplane. (shingle->normal has squared length 1.0, * so the argument of the sqrt() is at least 1.0.) */ factor = 1.0 / sqrt(n[1]*n[1] + n[2]*n[2] + n[3]*n[3]); nn[0] = 0.0; nn[1] = factor * n[1]; nn[2] = factor * n[2]; nn[3] = factor * n[3]; /* * The face plane is defined by the equation * * < (1, x, y, z), (n[0], n[1], n[2], n[3]) > == 0 * or * n[1]*x + n[2]*y + n[3]*z = n[0] */ /* * The center of the desired circle at infinity lies in the * direction of the vector nn (but with 0-th coordinate 1.0). * Scale it so that it satisfies the above equation. */ shingle->center[0] = 1.0; shingle->center[1] = nn[1] * factor * n[0]; shingle->center[2] = nn[2] * factor * n[0]; shingle->center[3] = nn[3] * factor * n[0]; /* * For the remainder of this function we work in the xyz slice * of O(3,1). That is, all 0-th coordinates are 0.0, and are * not mentioned in the comments. */ /* * To draw a circle we must extend nn to a basis for R^3. */ /* * Let u be an arbitrary unit vector not parallel to nn. */ o31_copy_vector(u, (fabs(nn[1]) < fabs(nn[2])) ? e1 : e2); /* * Let shingle->radialA = u x nn, normalized to have length one. */ shingle->radialA[0] = 0.0; shingle->radialA[1] = u[2] * nn[3] - u[3] * nn[2]; shingle->radialA[2] = u[3] * nn[1] - u[1] * nn[3]; shingle->radialA[3] = u[1] * nn[2] - u[2] * nn[1]; o31_constant_times_vector( 1.0 / sqrt(o31_inner_product(shingle->radialA, shingle->radialA)), shingle->radialA, shingle->radialA); /* * Let shingle->radialB = shingle->radialA x nn. * Its length will automatically be one. */ shingle->radialB[0] = 0.0; shingle->radialB[1] = shingle->radialA[2] * nn[3] - shingle->radialA[3] * nn[2]; shingle->radialB[2] = shingle->radialA[3] * nn[1] - shingle->radialA[1] * nn[3]; shingle->radialB[3] = shingle->radialA[1] * nn[2] - shingle->radialA[2] * nn[1]; /* * Rescale the radials so that shingle->center + shingle->radial * lies on the sphere at infinity. Recall (or deduce) from above * that the length of shingle->center's xyz component is * factor * n[0]. */ radius = sqrt(1.0 - (factor * n[0]) * (factor * n[0])); o31_constant_times_vector(radius, shingle->radialA, shingle->radialA); o31_constant_times_vector(radius, shingle->radialB, shingle->radialB); /* * Multiply our results by the pixel size of the window. */ o31_constant_times_vector(scale, shingle->center, shingle->center ); o31_constant_times_vector(scale, shingle->radialA, shingle->radialA); o31_constant_times_vector(scale, shingle->radialB, shingle->radialB); } static IsometryListNode *make_list_of_face_pairings( WEPolyhedron *polyhedron) { IsometryListNode *s0; WEFace *face; s0 = NULL; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) add_isometry_list_node(&s0, *face->group_element); return s0; } static IsometryListNode *make_list_of_other_isometries( WEPolyhedron *polyhedron, IsometryListNode *s0) { /* * The plan is to look at the neighbors of the elements of S0 * (i.e. elements g such that g(D) and s(D) share a face, for * some s in S0), then at the neighbors of the neighbors, and so on. * At each stage we keep only those elements g such that * g(D) intersects D in a vertex or edge (in other words we keep * elements of S which aren't already in S0). The connectedness * of S implies that we'll eventually get all of S1 this way. */ IsometryListNode *s1_begin, **s1_end, *s1_mark, *g; /* * s1_begin points to the beginning of the NULL-terminated * singly linked list. s1_end points to the field containing * the terminal NULL (initially this is s1_begin itself, but * typically it's the last node's "next" field). */ s1_begin = NULL; s1_end = &s1_begin; /* * Initialize s1 to contain all immediate neighbors of s0 * which intersect D and aren't already in (s0 union {id}). */ for (g = s0; g != NULL; g = g->next) add_neighbors_to_s1(g->m, polyhedron, s0, s1_begin, &s1_end); /* * Think of the linked list S1 as a queue. Go down the queue, * starting at the beginning, and compute the neighbors of * each element (i.e. those elements which share a face with * the given element). Those neighbors which aren't already * in S0 or S1 and which have a nonempty intersection with D * get appended to the end of the queue. The pointer s1_mark * points to the next element to be processed. When s1_mark * reaches the end, we will have computed the whole set S1, * and we'll be done. */ for (s1_mark = s1_begin; s1_mark != NULL; s1_mark = s1_mark->next) add_neighbors_to_s1(s1_mark->m, polyhedron, s0, s1_begin, &s1_end); return s1_begin; } static void add_neighbors_to_s1( O31Matrix m, WEPolyhedron *polyhedron, IsometryListNode *s0, IsometryListNode *s1_begin, IsometryListNode ***s1_end) { IsometryListNode *h, *new_node; O31Matrix mh; for (h = s0; h != NULL; h = h->next) { o31_product(m, h->m, mh); if (o31_equal(mh, O31_identity, SAME_MATRIX_EPSILON) == FALSE && matrix_on_list(mh, s0) == FALSE && matrix_on_list(mh, s1_begin) == FALSE && intersection_is_nontrivial(polyhedron, mh) == TRUE) { new_node = NEW_STRUCT(IsometryListNode); o31_copy(new_node->m, mh); new_node->next = NULL; **s1_end = new_node; *s1_end = &new_node->next; } } } static Boolean intersection_is_nontrivial( WEPolyhedron *polyhedron, O31Matrix m) { WEVertex *vertex; O31Vector gv; double temp, max_length_squared; /* * If the matrix m translates the origin a distance greater than * twice the polyhedron's outradius, then the corresponding * image g(D) cannot possibly intersection the Dirichlet domain D. */ if (m[0][0] > cosh(2.0 * polyhedron->outradius) + TOO_FAR_EPSILON) return FALSE; /* * Does the image g(v) of some vertex v lie within the original * Dirichlet domain D? */ /* * We may safely ignore those g(v) whose lengths squared place * them beyond the polyhedron's circumradius. Please see the * documentation in compute_vertex_distance() in Dirichlet_extras.c * for an explanation of what's going on here. */ temp = cosh(polyhedron->outradius) + TOO_FAR_EPSILON; max_length_squared = -1.0 / (temp * temp); for (vertex = polyhedron->vertex_list_begin.next; vertex != &polyhedron->vertex_list_end; vertex = vertex->next) { /* * Compute g(v). */ o31_matrix_times_vector(m, vertex->x, gv); /* * Normalize gv to have gv[0] == 1.0. As well as allowing * an easy comparison against max_length_squared, this will * make vertex_in_polyhedron()'s usage of vertex_epsilon * consistent with the usage in Dirichlet_construction.c. */ o31_constant_times_vector(1.0/gv[0], gv, gv); /* * Does gv lie outside the polyhedron's circumsphere? */ if (o31_inner_product(gv, gv) > max_length_squared) continue; /* * Test whether gv lies (approximately) on the polyhedron. */ if (vertex_in_polyhedron(gv, polyhedron) == TRUE) return TRUE; } return FALSE; } static Boolean vertex_in_polyhedron( O31Vector v, WEPolyhedron *polyhedron) { WEFace *face; O31Vector normal; int i; for (face = polyhedron->face_list_begin.next; face != &polyhedron->face_list_end; face = face->next) { /* * Compute the face's normal vector, and normalize it to * have length one. */ for (i = 0; i < 4; i++) normal[i] = (*face->group_element)[i][0] - (i ? 0.0 : 1.0); o31_constant_times_vector( 1.0 / sqrt(o31_inner_product(normal, normal)), normal, normal); /* * If the vertex v clearly lies beyond the face, return FALSE. */ if (o31_inner_product(v, normal) > polyhedron->vertex_epsilon) return FALSE; } return TRUE; } static void add_isometry_list_node( IsometryListNode **isometry_list, O31Matrix m) { IsometryListNode *new_node; new_node = NEW_STRUCT(IsometryListNode); o31_copy(new_node->m, m); new_node->next = *isometry_list; *isometry_list = new_node; } static void add_isometry_tree_node( IsometryTreeNode **isometry_tree, O31Matrix m) { IsometryTreeNode *new_node, **location; new_node = NEW_STRUCT(IsometryTreeNode); o31_copy(new_node->m, m); new_node->left = NULL; new_node->right = NULL; new_node->key = key_value(m); new_node->next_on_stack = NULL; location = isometry_tree; while (*location != NULL) { if (new_node->key <= (*location)->key) location = &(*location)->left; else location = &(*location)->right; } *location = new_node; } static Boolean matrix_on_list( O31Matrix m, IsometryListNode *isometry_list) { IsometryListNode *node; for (node = isometry_list; node != NULL; node = node->next) if (o31_equal(m, node->m, SAME_MATRIX_EPSILON) == TRUE) return TRUE; return FALSE; } static Boolean matrix_on_tree( O31Matrix m, IsometryTreeNode *isometry_tree) { double m_key, delta; IsometryTreeNode *subtree_stack, *subtree; Boolean left_flag, right_flag; /* * Compute a key value for the matrix m. */ m_key = key_value(m); /* * Reliability is our first priority. Speed is second. * So if m_key is close to a node's key value, we want to search both * the left and right subtrees. Otherwise we search only one or the * other. We implement the recursion using our own stack, rather than * the system stack, to avoid the possibility of a stack/heap collision * during deep recursions. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = isometry_tree; if (isometry_tree != NULL) isometry_tree->next_on_stack1 = NULL; /* * Process the subtrees on the stack, * adding additional subtrees as needed. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_on_stack1; subtree->next_on_stack1 = NULL; /* * Compare the key values of the tile and the subtree's root. */ delta = m_key - subtree->key; /* * Which side(s) should we search? */ left_flag = (delta < +TREE_EPSILON); right_flag = (delta > -TREE_EPSILON); /* * Put the subtrees we need to search onto the stack. */ if (left_flag && subtree->left) { subtree->left->next_on_stack1 = subtree_stack; subtree_stack = subtree->left; } if (right_flag && subtree->right) { subtree->right->next_on_stack1 = subtree_stack; subtree_stack = subtree->right; } /* * Check this matrix if the key values match. * (We're leaving non-NULL values in some next_on_stack1 fields, * but that's OK.) */ if (left_flag && right_flag) if (o31_equal(m, subtree->m, SAME_MATRIX_EPSILON) == TRUE) return TRUE; } return FALSE; } static double key_value( O31Matrix m) { /* * Please see key_value() in length_spectrum.c * for a full explanation of what's going on here. */ return( m[1][0] * 0.47865745183883625637 + m[2][0] * 0.14087522034920476458 + m[3][0] * 0.72230196622481940253); } static void add_shingle_node( ShingleNode **shingle_list, O31Matrix m0, O31Matrix m1, int index) { /* * Let b = (1,0,0,0) be the Dirichlet domain's basepoint. * Let g0 and g1 be the isometries given by m0 and m1, respectively. * The caller has checked that g0(D) and g1(D) are adjacent * translates of the Dirichlet domain D. The points g0(b) and g1(b) * are the corresponding translates of the basepoint, and by * the definition of the Dirichlet domain the vector g1(b) - g0(b) * is orthogonal to g0(D) and g1(D)'s common face. */ ShingleNode *new_node; int i; O31Vector zero_vector = {0.0, 0.0, 0.0, 0.0}; /* * Allocate the new_node. */ new_node = NEW_STRUCT(ShingleNode); /* * Compute the (spacelike) vector g1(b) - g0(b) and normalize * its length to 1.0. */ for (i = 0; i < 4; i++) new_node->shingle.normal[i] = m1[i][0] - m0[i][0]; o31_constant_times_vector( 1.0 / sqrt(o31_inner_product(new_node->shingle.normal, new_node->shingle.normal)), new_node->shingle.normal, new_node->shingle.normal); /* * The UI will set the center and radii before displaying * the shingling. */ o31_copy_vector(new_node->shingle.center, zero_vector); o31_copy_vector(new_node->shingle.radialA, zero_vector); o31_copy_vector(new_node->shingle.radialB, zero_vector); /* * Set the index. */ new_node->shingle.index = index; /* * Put the new_node on the shingle_list. */ new_node->next = *shingle_list; *shingle_list = new_node; } static void free_isometry_node_list( IsometryListNode **isometry_list) { IsometryListNode *dead_isometry_node; while (*isometry_list != NULL) { dead_isometry_node = *isometry_list; *isometry_list = (*isometry_list)->next; my_free(dead_isometry_node); } } static void free_isometry_node_tree( IsometryTreeNode **isometry_tree) { IsometryTreeNode *subtree_stack, *subtree; /* * Implement the recursive freeing algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = *isometry_tree; if (subtree_stack != NULL) subtree_stack->next_on_stack1 = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_on_stack1; subtree->next_on_stack1 = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left != NULL) { subtree->left->next_on_stack1 = subtree_stack; subtree_stack = subtree->left; } if (subtree->right != NULL) { subtree->right->next_on_stack1 = subtree_stack; subtree_stack = subtree->right; } /* * Free the subtree's root node. */ my_free(subtree); } /* * Clear the caller's reference to the defunct tree. */ *isometry_tree = NULL; } static Shingling *convert_shingle_list_to_shingling( ShingleNode **shingle_list) { Shingling *shingling; ShingleNode *shingle, *dead_shingle_node; int i; /* * Allocate the Shingling. */ shingling = NEW_STRUCT(Shingling); /* * Count the Shingles. */ shingling->num_shingles = 0; for ( shingle = *shingle_list; shingle != NULL; shingle = shingle->next) shingling->num_shingles++; /* * Allocate the array. */ shingling->shingles = NEW_ARRAY(shingling->num_shingles, Shingle); /* * Copy the Shingles into the array, freeing the ShingleNodes as we go. */ for (i = 0; i < shingling->num_shingles; i++) { if (*shingle_list == NULL) uFatalError("convert_shingle_list_to_shingling", "shingling"); shingling->shingles[i] = (*shingle_list)->shingle; dead_shingle_node = *shingle_list; *shingle_list = (*shingle_list)->next; my_free(dead_shingle_node); } if (*shingle_list != NULL) uFatalError("convert_shingle_list_to_shingling", "shingling"); return shingling; } snappea-3.0d3/SnapPeaKernel/code/shortest_cusp_basis.c0100444000175000017500000002043607001147021021114 0ustar babbab/* * shortest_cusp_basis.c * * This file provides the functions * * Complex cusp_modulus( Complex cusp_shape); * * void shortest_cusp_basis( Complex cusp_shape, * MatrixInt22 basis_change); * * Complex transformed_cusp_shape( Complex cusp_shape, * CONST MatrixInt22 basis_change); * * void install_shortest_bases( Triangulation *manifold); * * cusp_modulus() accepts a cusp_shape (longitude/meridian) and returns * the cusp modulus. Loosely speaking, the cusp modulus is defined as * (second shortest translation)/(shortest translation); it is a * complex number z lying in the region |Re(z)| <= 1/2 && |z| >= 1. * If z lies on the boundary of this region, we choose it so that * Re(z) >= 0. * * shortest_cusp_basis() accepts a cusp_shape (longitude/meridian) and * computes the 2 x 2 integer matrix which transforms the old basis * (u, v ) = (meridian, longitude) to the new basis * (u', v') = (shortest, second shortest). That is, * * | u' | | | | u | * | | = | basis_change | | | * | v' | | | | v | * * 2 x 1 2 x 2 2 x 1 * complex integer complex * vector matrix vector * * (u', v') is such that v'/u' is the cusp modulus defined above. * * Occasionally the shortest or second shortest curve won't be * unique. In most cases the conventions for the cusp modulus stated * above (in particular the convention that Re(z) >= 0 when z is on * the boundary of the fundamental domain) serve to uniquely specify * (u', v') in spite of the nonuniqueness of lengths. The only * exceptions are the hexagonal lattice, where three different curves * all have minimal length, and the square lattice, where two different * curves have minimal length. In these cases the ambiguity is not * resolved, and the choice of (u', v') may be machine dependent. * * transformed_cusp_shape() accepts a cusp_shape and a basis_change, * and computes the shape of the cusp relative to the basis (u', v') * defined by * * | u' | | | | u | * | | = | basis_change | | | * | v' | | | | v | * * (u', v') need not be the (shortest, second shortest) basis. * * install_shortest_bases() installs the (shortest, second shortest) * basis on each torus Cusp of manifold. It does not change the bases * on Klein bottle cusps. As explained for shortest_cusp_basis() * above, the (shortest, second shortest) is not well defined for a * hexagonal lattice, and the results may be machine dependent. * * * shortest_cusp_basis() uses the following algorithm. In principle * u and v could be any two translations which generate the fundamental * group of a torus, although in shortest_cusp_basis(), u is initially 1 * and v is initially the cusp_shape. * * do * { * * if (|u + v| < |u|) * u += v; * * if (|u - v| < |u|) * u -= v; * * if (|v + u| < |v|) * v += u; * * if (|v - u| < |v|) * v -= u; * * } while (still making progress); * * if (|u| > |v|) * replace (u, v) with (v, -u) * * if (Im(v/u) < 0) * flag an error -- the original orientation was wrong * * if (v/u is on the boundary of the fundamental domain described above) * make sure Re(v/u) >= 0 * * Theorem. The above algorithm computes the * (shortest, second shortest) basis. * * Proof. The angle between u and v must be between pi/3 and 2 pi/3; * otherwise the length of the projection of v onto u would be * |v| cos(theta) > |u| cos(pi/3) = |u|/2, and we would have added * +-u to v, thereby shortening v. This shows that |Re(v/u)| <= 1/2. * Because we chose |u| <= |v|, |v/u| >= 1. Therefore v/u lies within * the fundamental domain described above. It is also easy to see that * v is the shortest translation which is linearly independent of u. * The reason is that the row of lattice points 2v + nu is a distance * at least 2 |v| sin(theta) >= 2 |u| sin(pi/3) = sqrt(3) |u| away from * the row 0v + nu. */ #include "kernel.h" #define EPSILON (1e5 * DBL_EPSILON) Complex cusp_modulus( Complex cusp_shape) { MatrixInt22 basis_change; shortest_cusp_basis(cusp_shape, basis_change); return transformed_cusp_shape(cusp_shape, basis_change); } void shortest_cusp_basis( Complex cusp_shape, MatrixInt22 basis_change) /* basis_change is an output variable here */ { Complex u, v, u_plus_v, u_minus_v, temp, cusp_modulus; double mod_u, /* These are the complex moduli */ mod_v, /* of the preceding variables. */ mod_u_plus_v, mod_u_minus_v; int i, j, temp_int; Boolean progress; /* * For an explanation of this algorithm, see the documentation above. */ /* * Make sure cusp_shape is nondegenerate. */ if (fabs(cusp_shape.imag) < EPSILON) { for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = 0; return; } u = One; v = cusp_shape; mod_u = complex_modulus(u); mod_v = complex_modulus(v); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = (i == j); do { progress = FALSE; u_plus_v = complex_plus(u,v); mod_u_plus_v = complex_modulus(u_plus_v); if (mod_u - mod_u_plus_v > EPSILON) { u = u_plus_v; mod_u = mod_u_plus_v; for (j = 0; j < 2; j++) basis_change[0][j] += basis_change[1][j]; progress = TRUE; } else if (mod_v - mod_u_plus_v > EPSILON) { v = u_plus_v; mod_v = mod_u_plus_v; for (j = 0; j < 2; j++) basis_change[1][j] += basis_change[0][j]; progress = TRUE; } u_minus_v = complex_minus(u,v); mod_u_minus_v = complex_modulus(u_minus_v); if (mod_u - mod_u_minus_v > EPSILON) { u = u_minus_v; mod_u = mod_u_minus_v; for (j = 0; j < 2; j++) basis_change[0][j] -= basis_change[1][j]; progress = TRUE; } else if (mod_v - mod_u_minus_v > EPSILON) { v = complex_negate(u_minus_v); mod_v = mod_u_minus_v; for (j = 0; j < 2; j++) basis_change[1][j] -= basis_change[0][j]; progress = TRUE; } } while (progress); if (mod_u > mod_v + EPSILON) { temp = u; u = v; v = complex_negate(temp); for (j = 0; j < 2; j++) { temp_int = basis_change[0][j]; basis_change[0][j] = basis_change[1][j]; basis_change[1][j] = - temp_int; } } cusp_modulus = complex_div(v,u); if (cusp_modulus.imag < 0) uFatalError("cusp_modulus", "cusp_modulus"); if (cusp_modulus.real < -0.5 + EPSILON) { /* * Do an extra v += u. */ cusp_modulus.real = 0.5; for (j = 0; j < 2; j++) basis_change[1][j] += basis_change[0][j]; } if (complex_modulus(cusp_modulus) < 1.0 + EPSILON) { if (cusp_modulus.real < -EPSILON) { /* * Replace (u,v) with (v,-u). */ cusp_modulus.real = - cusp_modulus.real; for (j = 0; j < 2; j++) { temp_int = basis_change[0][j]; basis_change[0][j] = basis_change[1][j]; basis_change[1][j] = - temp_int; } } } } Complex transformed_cusp_shape( Complex cusp_shape, CONST MatrixInt22 basis_change) /* basis_change is an input variable here */ { Complex u, v; u = complex_plus( complex_real_mult( basis_change[0][0], One ), complex_real_mult( basis_change[0][1], cusp_shape ) ); v = complex_plus( complex_real_mult( basis_change[1][0], One ), complex_real_mult( basis_change[1][1], cusp_shape ) ); if (complex_modulus(u) < EPSILON) return Infinity; else return complex_div(v,u); } void install_shortest_bases( Triangulation *manifold) { Cusp *cusp; MatrixInt22 *change_matrices; int i, j; /* * Allocate an array to store the change of basis matrices. */ change_matrices = NEW_ARRAY(manifold->num_cusps, MatrixInt22); /* * Compute the change of basis matrices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->topology == torus_cusp) shortest_cusp_basis( cusp->cusp_shape[initial], change_matrices[cusp->index]); else for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) change_matrices[cusp->index][i][j] = (i == j); /* * Install the change of basis matrices. */ if (change_peripheral_curves(manifold, change_matrices) != func_OK) uFatalError("install_shortest_bases", "shortest_cusp_basis"); /* * Free the array used to store the change of basis matrices. */ my_free(change_matrices); } snappea-3.0d3/SnapPeaKernel/code/simplify_triangulation.c0100444000175000017500000023433607063526231021644 0ustar babbab/* * simplify_triangulation.c * * This file contains the following low-level routines for locally * modifying a triangulation * * FuncResult cancel_tetrahedra(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); * FuncResult three_to_two(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); * FuncResult two_to_three(Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr); * * as well as the following high-level routines which call them: * * void basic_simplification(Triangulation *manifold); * void randomize_triangulation(Triangulation *manifold); * * It also includes the low-level routine * * void one_to_four(Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index); * * which is not used by basic_simplification() or randomize_triangulation(), * but is called from canonize_part_2.c. * * The low-level routines are as follows * * cancel_tetrahedra() cancels two Tetrahedra which share * a common edge of order 2. * * three_to_two() replaces three Tetrahedra surrounding a common * edge with two Tetrahedra sharing a common face. * * two_to_three() replaces two Tetrahedra sharing a common face * with three Tetrahedra surrounding a common edge. * * one_to_four() replaces one Tetrahedron with four Tetrahedra * meeting at a point. * * If an operation cannot be performed because of a topological or * geometric obstruction, the function does nothing and returns func_failed. * Otherwise, it performs the operation and returns func_OK. * * The function one_to_four() will always succeed, and therefore returns * void. It introduces a finite vertex at the center of the Tetrahedron, * and therefore cannot be used when a hyperbolic structure is present. * * The three_to_two(), two_to_three() and one_to_four() operations each * correspond to a projection of a 4-simplex. * * For further details, please see the comments preceding each low-level * function. * * * In practice, other SnapPea routines will most likely call the * high-level functions basic_simplification() and randomize_triangulation(). * * basic_simplification() first does easy simplifications * (namely retriangulating neighborhoods of EdgeClasses of * order 1, 2 and 3 to reduce the number of Tetrahedra whenever * possible, and retriangulating suspended pentagons using five * Tetrahedra instead of six), and then retriangulates octahedra * (choosing a different one of the three possible axes for the * subdivision into four Tetrahedra) in hopes of making further * easy simplifications possible. * * randomize_triangulation() randomizes the Triangulation, and then * resimplifies it. * * basic_simplification() and randomize_triangulation() may be called * for manifolds with or without a hyperbolic structure present. * The final Triangulation may depend on whether or not the hyperbolic * structure is present, because when a hyperbolic structure is present * the low-level routines will refuse to create degenerate Tetrahedra. * * Most routines in SnapPea keep track of edge angles "mod 0" rather * than just "mod 2 pi", so that, e.g., a ComplexWithLog with * log.imag equal to (3/2) pi is different than one with log.imag * equal to (-1/2) pi. Unfortunately, the mod 0 angles for a given * Triangulation are somewhat arbitrary, in the sense that the following * procedure converts one mod 0 solution to a different mod 0 solution. * * Pick an EdgeClass ec in the Triangulation, and consider all the * Tetrahedra incident to it. If the incident edges don't all belong * to distinct Tetrahedra, work in the universal cover, so that the * Tetrahedra will at least appear distinct. For each Tetrahedron, * call the angle incident to the EdgeClass ec gamma, and call the * opposite angle gamma as well (they will of course be equal, due to * the symmetry of the ideal tetrahedron). Call one remaining pair * of opposite edges alpha, and the other pair beta. Make the choice * of alphas and betas consistent for all the Tetrahedra incident to * the EdgeClass ec; that is, each alpha of one Tetrahedron should * be incident to a beta of an adjacent Tetrahedron. Now add 2 pi i * to the log of each alpha edge angle, and subtract 2 pi i from the * log of each beta edge angle. Note that * * (1) The sum of the logs of the edge angles remains pi i for * each Tetrahedron. * * (2) The sum of the logs of the angles surrounding each EdgeClass * remains 2 pi i. * * (3) The holonomies of the cusps are unaltered. (At least in the * generic case -- I haven't thought through what happens when * the Tetrahedra incident to the EdgeClass ec are not all distinct.) * * The point of all this is that the mod 0 edge angles in a Triangulation * are not uniquely defined. If all the Tetrahedra are positively * oriented, then one typically expects to find a solution with mod 0 * edge angles in the range [0, pi], but if some of the Tetrahedra * are negatively oriented, then the choice of edge angles becomes * murkier. * * When I first started writing the low-level routines in this file * (i.e. two_to_three(), three_to_two() and cancel_tetrahedra()) * I naively expected to keep track of the mod 0 edge angles. This * was no problem in the three_to_two() move. It was a little more * difficult in the two_to_three() move because some arbitrary choices * were involved, and I couldn't see how to prove that the angles sums * would be preserved both at EdgeClasses and in each Tetrahedron. * The scheme broke down entirely in cancel_tetrahedra(), because * a pair of allegedly cancelling angles could differ by 2 pi i. * One could correct the problem locally, but only at the risk of * creating a solution whose edge angles differed from the "preferred" * ones by multiples of 2 pi i, as described above. Given that * * (1) I couldn't see how to simplify the mod 0 angles in any * reasonable and canonical way, and * * (2) We are mainly interested in solutions with positively * oriented Tetrahedra, or at worst with angles in the * range [(-1/2) pi, (3/2) pi], * * I decided that the low-level routines in this file should only * keep track of the mod 2 pi angles, choosing values in the * range [(-1/2) pi, (3/2) pi]. Just before returning, * basic_simplification() calls polish_solution() (that's polish, * not Polish). In the generic case (when the mod 0 angles are * valid, but the TetShapes have lost some accuracy) the effect of * polish_solution() is to recover the lost accuracy, without * substantially changing the solution. In the exceptional case * that the edge angles don't add up correctly around a Tetrahedron * or EdgeClass, polish_solution() will find an entirely new * solution to the gluing equations. * * randomize_triangulation() calls basic_simplification(), so its * solutions also get polished. * * 97/2/3 Modified to strip off the geometric structure (if any) * at the start of basic_simplification() and randomize_triangulation(), * and (if there was a geometric structure) recompute it at the end. * The old system was working fine for hyperbolic manifolds, but now that * SnapPea is working with degenerate solutions (to split along normal * surfaces) one wants to be able to randomize and simplify them too. * 98/5/20 Modified *not* to strip off the geometric structure * when the cusp_nbhd_position is present. The low-level routines * need the hyperbolic structure to maintain the cusp_nbhd_position. * * 97/2/4 Modified to handle Triangulations containing finite vertices. * The 4-1 move, in which four tetrahedra surrounding a common finite * vertex are replaced by a single tetrahedron, is handled implicitly * as a 3-2 move (on one of the edge classes incident to the finite * vertex) followed by a 2-0 move on a pair of tetrahedra having three * faces and a finite vertex in common. The code in cancel_tetrahedra() * was modified to accomodate this. When the finite vertex is removed, * a gap remains in the (negative) numbering of the Cusps structures * for finite vertices, but this isn't a problem. */ #include "kernel.h" #include /* needed for rand() */ /* * ORDER_FOUR_ITERATIONS_IN_SIMPLIFY tells how many times * basic_simplification() should pass unsuccessfully down * the list of EdgeClasses before giving up. */ #define ORDER_FOUR_ITERATIONS_IN_SIMPLIFY 6 /* * RANDOMIZATION_MULTIPLE tells how long randomize_triangulation() * should keep randomizing before it resimplifies the manifold. * It will attempt RANDOMIZATION_MULTIPLE * manifold->num_tetrahedra * two-to-three moves, each followed by some rudimentary resimplification * to avoid wasting time in degenerate situations. */ #define RANDOMIZATION_MULTIPLE 4 static Tetrahedron *get_tet(Triangulation *manifold, int desired_index); static void check_for_cancellation(Triangulation *manifold); static Boolean easy_simplification(Triangulation *manifold); static FuncResult remove_edge_of_order_one(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static Boolean this_way_works(Tetrahedron *tet, FaceIndex left_face, FaceIndex right_face, FaceIndex bottom_face); static FuncResult cancel_tetrahedra_with_finite_vertex(Tetrahedron *tet, VertexIndex finite_vertex, EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult edges_of_order_four(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult try_adjacent_fours(Tetrahedron *tet0, FaceIndex f0, FaceIndex f1, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult create_new_order_four(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static Boolean four_tetrahedra_are_distinct(PositionedTet ptet); static void set_inverse_neighbor_and_gluing(Tetrahedron *tet, FaceIndex f); void basic_simplification( Triangulation *manifold) { SolutionType original_solution_type[2]; int iter; EdgeClass *edge, *where_to_resume; Boolean hyperbolic_structure_was_removed; /* * 97/2/3 Strip off the geometric structure if there is one. * * 98/5/20 Oops. We don't want to strip off the hyperbolic * structure if the cusp_nbhd_position is present, because the * low-level routines need the hyperbolic structure to maintain * cusp_nbhd_position. */ if (manifold->tet_list_begin.next->cusp_nbhd_position == NULL) { original_solution_type[complete] = manifold->solution_type[complete]; original_solution_type[filled] = manifold->solution_type[filled]; remove_hyperbolic_structures(manifold); hyperbolic_structure_was_removed = TRUE; } else hyperbolic_structure_was_removed = FALSE; /* * First do all the easy simplifications, namely removing * EdgeClasses of order 1, 2 and 3 when possible, and * retriangulating suspended pentagons with five Tetrahedra * instead of six. */ easy_simplification(manifold); /* * Go down the list retriangulating the octahedra surrounding * EdgeClasses of order 4, in the hope of creating new, more * useful EdgeClasses of order 4. Keep doing this until we've * gone through the list ORDER_FOUR_ITERATIONS_IN_SIMPLIFY times * with no further progress. * * The operation of the inner loop is complicated by the * appearance and disappearance of EdgeClasses as the * algorithm proceeds. To avoid possible infinite loops, * and also to avoid possible "resonance" phenomena, we * pseudorandomly decide whether or not to perform each * potential retriangulation we encounter. */ for (iter = 0; iter < ORDER_FOUR_ITERATIONS_IN_SIMPLIFY; iter++) for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if ((rand() & 3) > 0 /* proceed with probability 3/4 */ && create_new_order_four(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { if (easy_simplification(manifold) == TRUE) { iter = -1; break; } else edge = where_to_resume; } /* * Clean up. * * 97/2/3 If we trashed the tet shapes, reinitialize * them and then call polish_hyperbolic_structure(). Obviously * polish_hyperbolic_structure() will be recomputing the geometric * structure from scratch, not just "polishing" it. */ tidy_peripheral_curves(manifold); if (hyperbolic_structure_was_removed && original_solution_type[complete] != not_attempted) { manifold->solution_type[complete] = original_solution_type[complete]; manifold->solution_type[filled] = original_solution_type[filled]; initialize_tet_shapes(manifold); polish_hyperbolic_structures(manifold); } /* * The Chern-Simons invariant of the manifold is still the * same, but the fudge factor may have changed. */ compute_CS_fudge_from_value(manifold); } void randomize_triangulation( Triangulation *manifold) { SolutionType original_solution_type[2]; int count; Boolean hyperbolic_structure_was_removed; /* * 97/2/3 Strip off the geometric structure if there is one. * * 98/5/20 Oops. We don't want to strip off the hyperbolic * structure if the cusp_nbhd_position is present, because the * low-level routines need the hyperbolic structure to maintain * cusp_nbhd_position. */ if (manifold->tet_list_begin.next->cusp_nbhd_position == NULL) { original_solution_type[complete] = manifold->solution_type[complete]; original_solution_type[filled] = manifold->solution_type[filled]; remove_hyperbolic_structures(manifold); hyperbolic_structure_was_removed = TRUE; } else hyperbolic_structure_was_removed = FALSE; /* * Randomize the triangulation, doing only minimal * simplifications along the way. The minimal simplifications * are crucial -- otherwise the algorithm would create, * say, a pair of potentially cancelling Tetrahedra, and * then waste all it's remaining efforts making the union * of those two Tetrahedra more and more complex. * * By the way, not all the calls to two_to_three() will * succeed (e.g. because some Tetrahedra may be glued to * themselves), but that's OK. */ for (count = RANDOMIZATION_MULTIPLE * manifold->num_tetrahedra; --count >= 0; ) if (two_to_three( get_tet(manifold, rand() % manifold->num_tetrahedra), rand() % 4, &manifold->num_tetrahedra) == func_OK) check_for_cancellation(manifold); /* * Resimplify the manifold. * basic_simplification() will tidy up the peripheral curves, * recompute the hyperbolic structure (if one is present), * and recompute the CS_fudge. */ if (hyperbolic_structure_was_removed && original_solution_type[complete] != not_attempted) { manifold->solution_type[complete] = original_solution_type[complete]; manifold->solution_type[filled] = original_solution_type[filled]; initialize_tet_shapes(manifold); /* unnecessary, but robust */ } basic_simplification(manifold); } static Tetrahedron *get_tet( Triangulation *manifold, int desired_index) { int i; Tetrahedron *tet; /* * Return a pointer to the i-th Tetrahedron on the list, * with implicit numbering 0 through (num_tetrahedra - 1). */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) if (i == desired_index) return tet; /* * If we get to here, something went wrong. */ uFatalError("get_tet", "simplify_triangulation"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return NULL; } static void check_for_cancellation( Triangulation *manifold) { Boolean progress; EdgeClass *edge, *where_to_resume; /* * This function is similar to easy_simplification() (see below), * except that it checks only for EdgeClasses of order 1 or 2. */ do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) switch (edge->order) { case 1: if (remove_edge_of_order_one(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; edge = where_to_resume; } break; case 2: if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; edge = where_to_resume; } break; default: break; } } while (progress == TRUE); } /* * easy_simplification() removes edges of order 1, 2 and 3 * whenever possible, and simplifies the neighborhoods of adjacent * edges of order 4 when the six incident Tetrahedra are distinct. * * easy_simplification() returns TRUE if it simplifies the * Triangulation, FALSE otherwise. * * create_new_order_four() undertakes more daring operations * with EdgeClasses of order 4. */ static Boolean easy_simplification( Triangulation *manifold) { Boolean progress, triangulation_was_simplified; EdgeClass *edge, *where_to_resume; /* * Our plan is to keep going down the list of EdgeClasses, * removing EdgeClasses of order 1, 2 or 3 whenever possible, * and retriangulating suspended pentagons with five Tetrahedra * instead of six. When no further progress can be made, we're done. * * The low-level routines set the variable where_to_resume to point * to some valid EdgeClass. This allows the for(;;) loop to continue * down the list, rather than restarting at the beginning each time * a simplification occurs. (If only one EdgeClass is deleted, * where_to_resume points to its predecessor.) * * Technical comment: This function would run a tiny bit faster * if the EdgeClasses were shuffled about on various queues, * as in the old snappea, but the present system is simpler. */ triangulation_was_simplified = FALSE; do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) switch (edge->order) { case 1: if (remove_edge_of_order_one(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 2: if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 3: if (three_to_two(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 4: if (edges_of_order_four(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; default: break; } } while (progress == TRUE); return triangulation_was_simplified; } static FuncResult remove_edge_of_order_one( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet; FaceIndex left_face, right_face, bottom_face; /* * remove_edge_of_order_one() contains no explicit low-level * retriangulation. Instead, each call to remove_edge_of_order_one() * calls two_to_three() to increase the order of EdgeClass *edge from * one to two, and then calls cancel_tetrahedra() to remove *edge. * Because two_to_three() increases the number of Tetrahedra by * one and cancel_tetrahedra() decreases it by two, there is a net * loss of one Tetrahedron. * * remove_edge_of_order_one() checks ahead of time whether the * calls to two_to_three() and cancel_tetrahedra() will be able * to succeed; if not (e.g. because of an embedded annulus), * remove_edge_of_order_one() does nothing and returns func_failed. * * The new EdgeClass created in the call to two_to_three() has * its order reduced to one in the call to cancel_tetrahedra(). * Thus, remove_edge_of_order_one() always leave a new EdgeClass * of order one. Eventually remove_edge_of_order_one() will be * blocked by an annulus. Typically this annulus is trivial, * and opposite the EdgeClass of order 1 there is an EdgeClass of * order two, whose removal (by an independent call to * cancel_tetrahedra()) also destroys the EdgeClass of order 1. * * I'd like to draw some illustrations, but it just isn't possible * in a text-only file. So I'll leave it as an exercise for the * reader to illustrate what happens in the successive calls * to remove_edge_of_order_one(). */ /* * Label the Tetrahedron and the two faces incident to the * EdgeClass of order one. */ tet = edge->incident_tet; left_face = one_face_at_edge[edge->incident_edge_index]; right_face = other_face_at_edge[edge->incident_edge_index]; /* * EdgeClasses of order 1 should never occur when a hyperbolic * structure is present. */ if (tet->shape[complete] != NULL) uFatalError("remove_edge_of_order_one", "simplify_triangulation"); /* * Let bottom_face be a candidate face for performing the * two-to-three move. Check ahead of time whether the calls * to two-to-three() and cancel_tetrahedra() will succeed. */ if (this_way_works(tet, left_face, right_face, remaining_face[left_face][right_face]) == TRUE) bottom_face = remaining_face[left_face][right_face]; else if (this_way_works(tet, left_face, right_face, remaining_face[right_face][left_face]) == TRUE) bottom_face = remaining_face[right_face][left_face]; else return func_failed; /* * Call two_to_three() and cancel_tetrahedra(). */ if ( two_to_three(tet, bottom_face, num_tetrahedra_ptr) == func_failed || edge->order != 2 || cancel_tetrahedra(edge, where_to_resume, num_tetrahedra_ptr) == func_failed ) uFatalError("remove_edge_of_order_one", "simplify_triangulation"); return func_OK; } static Boolean this_way_works( Tetrahedron *tet, FaceIndex left_face, FaceIndex right_face, FaceIndex bottom_face) { Tetrahedron *tet1; FaceIndex left1, right1, bottom1; EdgeClass *edgeA, *edgeB; /* * The left_ and right_faces fold together to form the EdgeClass * of order one. * The bottom_face cannot be glued to the remaining face of tet, * because if it were we'd have a manifold with only one Tetrahedron * but at least two EdgeClasses, which violates the proposition * that in a manifold with cusp cross sections of Euler characteristic * zero, the number of EdgeClasses must equal the number of * Tetrahedra. */ if (tet->neighbor[bottom_face] == tet) uFatalError("this_way_works", "simplify_triangulation"); /* * We want to locate the two EdgeClasses which would be combined * when remove_edge_of_order_one() calls cancel_tetrahedra(). */ tet1 = tet->neighbor[bottom_face]; left1 = EVALUATE(tet->gluing[bottom_face], left_face); right1 = EVALUATE(tet->gluing[bottom_face], right_face); bottom1 = EVALUATE(tet->gluing[bottom_face], bottom_face); edgeA = tet1->edge_class[edge_between_vertices[bottom1][ left1]]; edgeB = tet1->edge_class[edge_between_vertices[bottom1][right1]]; return (edgeA != edgeB); } /* * cancel_tetrahedra() checks whether the two Tetrahedra * incident to the EdgeClass edge contain an annulus or * Moebius strip, and if they do not, it cancels them and * returns func_OK. If they do contain an annulus or Moebius * strip, cancel_tetrahedra() does nothing and returns func_failed. * * Comments in the code below explain how the cancellation * occurs, and why no other degenerate situations can occur. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. * * 97/2/4 Modified to allow for the possibility of two Tetrahedra * sharing three faces and the enclosed finite vertex. In this * case the annulus referred to above encloses a solid cylinder. * The tetrahedra are cancelled and the finite vertex is removed. */ FuncResult cancel_tetrahedra( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet[2], *nbr[2], *t; VertexIndex v[2][4], w[2][4]; Orientation orientation[2]; EdgeClass *outer_edge[2]; Boolean are_whole_manifold; int c, i, ii, j, k; int delta[2][2][2]; VertexIndex active_vertex; Boolean tet_orientations_agree, edge_orientations_agree, edge_class_orientations_agree; PositionedTet ptet, ptet0; EdgeIndex left_edge; Permutation gluing[2]; /* * Just to be safe . . . */ if (edge->order != 2) uFatalError("cancel_tetrahedra", "simplify_triangulation"); /* * Let tet[0] and tet[1] be the two Tetrahedra incident * to EdgeClass *edge, and v[i][j] be their vertices. * Vertex v[0][i] is glued to vertex v[1][i]. * Vertices v[i][0] and v[i][1] are incident to the * EdgeClass *edge. */ tet[0] = edge->incident_tet; v[0][0] = one_vertex_at_edge[edge->incident_edge_index]; v[0][1] = other_vertex_at_edge[edge->incident_edge_index]; v[0][2] = remaining_face[v[0][1]][v[0][0]]; v[0][3] = remaining_face[v[0][0]][v[0][1]]; orientation[0] = right_handed; if (tet[0]->neighbor[v[0][2]] != tet[0]->neighbor[v[0][3]] || tet[0]->gluing [v[0][2]] != tet[0]->gluing [v[0][3]]) uFatalError("cancel_tetrahedra", "simplify_triangulation"); tet[1] = tet[0]->neighbor[v[0][2]]; for (i = 0; i < 4; i++) v[1][i] = EVALUATE(tet[0]->gluing[v[0][2]], v[0][i]); orientation[1] = (parity[tet[0]->gluing[v[0][2]]] == orientation_preserving) ? orientation[0] : ! orientation[0]; /* * It's easy to prove that if the manifold has only torus and Klein * bottle cusp cross sections, then tet[0] and tet[1] are distinct. * * 97/2/4 I assume that the presence of at least one torus or * Klein bottle cusp is enough to guarantee that tet[0] != tet[1], * but I haven't thought through the details. Even if we eventually * wanted to use this code to simplify non-ideal triangulations * of closed manifolds, we could simply replace the uFatalError() * call with func_failed. * * 99/06/04 Indeed we do want this code to simplify non-ideal * triangulations of closed manifolds, so I replaced * uFatalError("cancel_tetrahedra", "simplify_triangulation"); * with * return func_failed; */ if (tet[0] == tet[1]) return func_failed; /* * If the edge connecting v[0][2] to v[0][3] belongs to the same * EdgeClass as the edge connecting v[1][2] to v[1][3], then the * union of tet[0] and tet[1] contains an embedded annulus or * Moebius strip, and we should return func_failed. * * 97/2/4 Check whether tet[0] and tet[1] share three faces, * and enclose a finite vertex. If so, we may cancel the Tetrahedra, * and also remove the finite vertex. Obviously these changes will * never be invoked for ideal triangulations (i.e. with no finite * vertices). * * 2000/03/14 Oops! In the 97/2/4 change I overlooked the possibility * that tet[0] and tet[1] comprise the entire manifold (in which * case the manifold is a 3-sphere or L(3,1)). The code now tests * for this possibility, and returns func_failed when it occurs. */ for (i = 0; i < 2; i++) outer_edge[i] = tet[i]->edge_class[edge_between_vertices[v[i][2]][v[i][3]]]; if (outer_edge[0] == outer_edge[1]) { for (i = 0; i < 2; i++) if (tet[0]->cusp[v[0][i]]->is_finite == TRUE && tet[0]->neighbor[v[0][!i]] == tet[1] && tet[0]->neighbor[v[0][ i]] != tet[1] && tet[0]->gluing[v[0][!i]] == tet[0]->gluing[v[0][2]]) return cancel_tetrahedra_with_finite_vertex(tet[0], v[0][i], edge, where_to_resume, num_tetrahedra_ptr); return func_failed; } /* * The plan is to flatten the two Tetrahedra. To prove rigorously * that this does not change the topology of the manifold, first * imagine compressing the strip lying between the edge from * v[0][2] to v[0][3] and the edge from v[1][2] to v[1][3]. * This is valid iff the two edges are in distinct EdgeClasses, * and we just checked that they are. Then imagine flattening * the two triangular pillows. This is OK iff the two triangular * pillows don't make up the whole manifold, which they don't * because otherwise the boundary would contain a sphere. * Q.E.D. * * 2000/03/14 Test explicitly whether the two triangular pillows * make up the whole manifold. */ are_whole_manifold = TRUE; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (tet[i]->neighbor[v[i][j]] != tet[0] && tet[i]->neighbor[v[i][j]] != tet[1]) are_whole_manifold = FALSE; if (are_whole_manifold == TRUE) return func_failed; /* * Before compressing the aforementioned strip, we need to clear * the peripheral curves away from the strip we're going to * collapse. While we're at it, we'll relabel all edges in * EdgeClass outer_edge[1] as EdgeClass outer_edge[0], and adjust * their edge_orientation if necessary, in preparation for merging * the two classes. */ for (c = 0; c < 2; c++) /* M or L */ for (j = 0; j < 2; j++) /* top (= 0) or bottom (= 1) */ for (i = 0; i < 2; i++) /* right_handed or left_handed */ { ii = (orientation[0] == orientation[1]) ? i : !i; delta[c][j][i] = tet[1]->curve[c][ i][v[1][j+2]][v[1][0]] + tet[0]->curve[c][ii][v[0][j+2]][v[0][0]]; } tet_orientations_agree = (orientation[0] == orientation[1]); edge_orientations_agree = (tet[0]->edge_orientation[edge_between_faces[v[0][0]][v[0][1]]] == tet[1]->edge_orientation[edge_between_faces[v[1][0]][v[1][1]]]); edge_class_orientations_agree = (tet_orientations_agree == edge_orientations_agree); ptet0.tet = tet[1]; ptet0.near_face = v[1][1]; ptet0.left_face = v[1][0]; ptet0.right_face = v[1][3]; ptet0.bottom_face = v[1][2]; ptet0.orientation = orientation[1]; ptet = ptet0; do { /* * Adjust the peripheral curves. */ for (c = 0; c < 2; c++) for (j = 0; j < 2; j++) { active_vertex = (j == 0) ? ptet.bottom_face : ptet.right_face; for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; ptet.tet->curve[c][i][active_vertex][ptet.left_face] -= delta[c][j][ii]; ptet.tet->curve[c][i][active_vertex][ptet.near_face] += delta[c][j][ii]; } } /* * For convenience, note the EdgeIndex of the left edge. */ left_edge = edge_between_faces[ptet.near_face][ptet.left_face]; /* * Adjust the EdgeClass. */ ptet.tet->edge_class[left_edge] = outer_edge[0]; /* * Adjust the edge_orientation. */ if ( ! edge_class_orientations_agree) ptet.tet->edge_orientation[left_edge] = ! ptet.tet->edge_orientation[left_edge]; /* * Move on. */ veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); /* * Adjust the EdgeClass sizes. */ outer_edge[0]->order += outer_edge[1]->order; for (i = 0; i < 2; i++) for (j = 0; j < 6; j++) tet[i]->edge_class[j]->order--; /* * We are about to delete EdgeClasses edge and outer_edge[1]. * Set *where_to_resume to point to the EdgeClass just * just before the spot where edge was. */ if (edge->prev != outer_edge[1]) *where_to_resume = edge->prev; else *where_to_resume = outer_edge[1]->prev; /* * Free the unused EdgeClasses. */ REMOVE_NODE(edge); REMOVE_NODE(outer_edge[1]); my_free(edge); my_free(outer_edge[1]); /* * Set the incident_tet and incident_edge_index fields * for all EdgeClasses which lost members. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { t = tet[i]->neighbor[v[i][j]]; if (t != tet[0] && t != tet[1]) for (k = 0; k < 6; k++) { t->edge_class[k]->incident_tet = t; t->edge_class[k]->incident_edge_index = k; } } /* * Set neighbors and gluings. */ for (i = 0; i < 2; i++) { /* which face */ for (j = 0; j < 2; j++) /* which Tetrahedron */ { nbr[j] = tet[j]->neighbor[v[j][i]]; gluing[j] = tet[j]->gluing [v[j][i]]; for (k = 0; k < 4; k++) /* which vertex */ w[j][k] = EVALUATE(gluing[j], v[j][k]); } for (j = 0; j < 2; j++) /* which Tetrahedron */ { nbr[j]->neighbor[w[j][i]] = nbr[!j]; nbr[j]->gluing [w[j][i]] = CREATE_PERMUTATION( w[j][0], w[!j][0], w[j][1], w[!j][1], w[j][2], w[!j][2], w[j][3], w[!j][3]); } } /* * Free the collapsed Tetrahedra. */ for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr -= 2; return func_OK; } static FuncResult cancel_tetrahedra_with_finite_vertex( Tetrahedron *tet, VertexIndex finite_vertex, EdgeClass *edge, /* needed only for setting *where_to_resume */ EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *nbr, *tet_outer, *nbr_outer; Permutation gluing; FaceIndex f, ff, tet_outer_f, nbr_outer_f; VertexIndex v, nbr_finite; EdgeIndex e; Cusp *dead_cusp; EdgeClass *dead_edge; /* * The three faces of the tet surrounding the finite_vertex * are glued to the neighboring Tetrahedron in the obvious way, * forming a triangular pillow with a finite vertex and three * EdgeClasses in its interior. */ nbr = tet->neighbor[!finite_vertex]; gluing = tet->gluing [!finite_vertex]; if (tet->cusp[finite_vertex]->is_finite != TRUE) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); for (f = 0; f < 4; f++) if (f != finite_vertex) if (tet->neighbor[f] != nbr || tet->gluing [f] != gluing) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * If tet and nbr had four faces in common, then the manifold * couldn't have any torus or Klein bottle boundary components. * We assume this isn't the case. */ if (tet->neighbor[finite_vertex] == nbr) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * The peripheral curves will match up correctly after the cancellation. * No explicit preparation is required. */ /* * Remove the Cusp structure representing the finite vertex * in the triangular pillow's interior. Finite vertices aren't * counted in a Triangulation's num_cusps field. By removing * this Cusp we may leave a gap in the (negative) indexing of * finite vertex Cusps, but that's OK. */ dead_cusp = tet->cusp[finite_vertex]; REMOVE_NODE(dead_cusp); my_free(dead_cusp); /* * Remove the three EdgeClasses from the pillow's interior. * * Make sure the calling program is left with a valid pointer * to an EdgeClass, to continue its loop where it left off. */ *where_to_resume = edge->prev; for (v = 0; v < 4; v++) if (v != finite_vertex) { dead_edge = tet->edge_class[edge_between_vertices[v][finite_vertex]]; if (dead_edge == *where_to_resume) *where_to_resume = dead_edge->prev; REMOVE_NODE(dead_edge); my_free(dead_edge); } /* * Note which Tetrahedra border the triangular pillow's outer faces. */ tet_outer = tet->neighbor[finite_vertex]; tet_outer_f = EVALUATE(tet->gluing[finite_vertex], finite_vertex); nbr_finite = EVALUATE(gluing, finite_vertex); nbr_outer = nbr->neighbor[nbr_finite]; nbr_outer_f = EVALUATE(nbr->gluing[nbr_finite], nbr_finite); /* * Make sure the three EdgeClasses around the pillow's boundary * "see" Tetrahedra other than the ones we are about to cancel. * Reduce the order of each such EdgeClass by two. */ for (f = 0; f < 4; f++) if (f != finite_vertex) { ff = EVALUATE(tet->gluing[finite_vertex], f); e = edge_between_faces[tet_outer_f][ff]; tet_outer->edge_class[e]->incident_tet = tet_outer; tet_outer->edge_class[e]->incident_edge_index = e; tet_outer->edge_class[e]->order -= 2; } /* * Glue tet_outer and nbr_outer to one another. * (Note: compose_permutations() composes right-to-left.) */ tet_outer->neighbor[tet_outer_f] = nbr_outer; tet_outer->gluing[tet_outer_f] = compose_permutations(gluing, tet_outer->gluing[tet_outer_f]); tet_outer->gluing[tet_outer_f] = compose_permutations(nbr->gluing[nbr_finite], tet_outer->gluing[tet_outer_f]); nbr_outer->neighbor[nbr_outer_f] = tet_outer; nbr_outer->gluing[nbr_outer_f] = compose_permutations(inverse_permutation[gluing], nbr_outer->gluing[nbr_outer_f]); nbr_outer->gluing[nbr_outer_f] = compose_permutations(tet->gluing[finite_vertex], nbr_outer->gluing[nbr_outer_f]); if (nbr_outer->gluing[nbr_outer_f] != inverse_permutation[tet_outer->gluing[tet_outer_f]] || EVALUATE(tet_outer->gluing[tet_outer_f], tet_outer_f) != nbr_outer_f) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * Remove tet and nbr. */ REMOVE_NODE(tet); REMOVE_NODE(nbr); free_tetrahedron(tet); free_tetrahedron(nbr); *num_tetrahedra_ptr -= 2; return func_OK; } /* * If the three Tetrahedra surrounding the EdgeClass *edge are distinct, * three_to_two() replaces them with two Tetrahedra sharing a common * face, and returns func_OK. Otherwise it does nothing and returns * func_failed. * * The Orientations of the two new Tetrahedra are set to match the * Orientation of one of the three old ones, so that the Orientability * of the Triangulation (if there is one) will be preserved. * * The two new Tetrahedra created by three_to_two() take the place * of one of the doomed ones in the list of Tetrahedra. The doomed * Tetrahedron are removed from the list before being destroyed. * Similarly, the EdgeClass edge is removed from its list before * being destroyed. * * If the three original Tetrahedra are nondegenerate, the two * two new ones must perforce be nondegenerate as well. Proof: * if a pair of ideal vertices coincides in a new Tetrahedra, * that pair must have coincided in one of the three original * Tetrahedra as well. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. */ FuncResult three_to_two( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { int c, h, hh, i, j, j1, j2; Tetrahedron *tet[3], *new_tet[2]; VertexIndex v[3][4], w[2][4]; Orientation old_orientation[3]; Permutation gluing; EdgeIndex old_h_edge_index, old_v_edge_index, new_h_edge_index, new_v_edge_index; /* * Just to be safe . . . */ if (edge->order != 3) uFatalError("three_to_two", "simplify_triangulation"); /* * The three Tetrahedra incident to the EdgeClass *edge will be * called tet[0], tet[1] and tet[2]. The vertices of tet[i] will * be v[i][0-3]. * * I recommend making a sketch of tet[0-2] to consult as you * read through the following code. The EdgeClass *edge is * vertical. Vertex v[i][0] of each tet[i] is at the bottom * of the edge, and vertex v[i][1] is at the top. Vertices * v[i][2] and v[i][3] are on the "equator", with v[i][3] * being counterclockwise from v[i][2] as viewed from above. */ /* * Locate one Tetrahedron incident to EdgeClass *edge. * Choose the v[0][j] so that tet[0] is viewed with * the right_handed Orientation. */ tet[0] = edge->incident_tet; v[0][0] = one_vertex_at_edge[edge->incident_edge_index]; v[0][1] = other_vertex_at_edge[edge->incident_edge_index]; v[0][2] = remaining_face[v[0][0]][v[0][1]]; v[0][3] = remaining_face[v[0][1]][v[0][0]]; old_orientation[0] = right_handed; /* * Locate the two remaining Tetrahedra. * If the Triangulation is oriented, they will also be positioned * with the right_handed Orientation. */ for (i = 0; i < 2; i++) { tet[i+1] = tet[i]->neighbor[v[i][2]]; gluing = tet[i]->gluing[v[i][2]]; v[i+1][0] = EVALUATE(gluing, v[i][0]); v[i+1][1] = EVALUATE(gluing, v[i][1]); v[i+1][2] = EVALUATE(gluing, v[i][3]); v[i+1][3] = EVALUATE(gluing, v[i][2]); old_orientation[i+1] = (parity[gluing] == orientation_preserving) ? old_orientation[i] : ! old_orientation[i]; } /* * If the three Tetrahedra are not distinct, we can't do any * simplification, so return func_failed. */ for (i = 0; i < 3; i++) if (tet[i] == tet[(i+1)%3]) return func_failed; /* * This function should never be invoked when canonize_info is present. */ if (tet[0]->canonize_info != NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Create the new Tetrahedra. * * new_tet[0] occupies the northern half of the picture as described * above, and new_tet[1] occupies the southern half. Vertex w[0][3] of * new_tet[0] is at the north pole, and vertex w[1][3] of new_tet[1] is at * the south pole. Face w[i][j] (j = 0,1,2) of new_tet[i] coincides with * face v[j][i] of tet[j]. The actual values of w[][] give both * new_tet[0] and new_tet[1] the right_handed Orientation. */ for (i = 0; i < 2; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } w[0][0] = 0; w[0][1] = 1; w[0][2] = 3; w[0][3] = 2; w[1][0] = 0; w[1][1] = 1; w[1][2] = 2; w[1][3] = 3; /* * Set the gluing and neighbor fields. * * Note that this code works correctly even if some of the faces * of the tet[i] were glued to each other. */ for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { new_tet[i]->neighbor[w[i][j]] = tet[j]->neighbor[v[j][i]]; new_tet[i]->gluing [w[i][j]] = CREATE_PERMUTATION( w[i][j], EVALUATE(tet[j]->gluing[v[j][i]], v[j][i]), w[i][(j+1)%3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][2]), w[i][(j+2)%3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][3]), w[i][3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][!i]) ); set_inverse_neighbor_and_gluing(new_tet[i], w[i][j]); } new_tet[i]->neighbor[w[i][3]] = new_tet[!i]; new_tet[i]->gluing [w[i][3]] = CREATE_PERMUTATION( w[i][0], w[!i][0], w[i][1], w[!i][1], w[i][2], w[!i][2], w[i][3], w[!i][3]); } /* * Set the cusp fields. */ for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) new_tet[i]->cusp[w[i][j]] = tet[(j+1)%3]->cusp[v[(j+1)%3][3]]; new_tet[i]->cusp[w[i][3]] = tet[0]->cusp[v[0][!i]]; } /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) /* which sheet */ for (i = 0; i < 2; i++) { /* which tetrahedron */ /* * Set the equatorial vertices. */ for (j = 0; j < 3; j++) /* which vertex */ { j1 = (j+1) % 3; j2 = (j+2) % 3; hh = (old_orientation[j1] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][j]][w[i][j1]] = tet[j1]->curve[c][hh][v[j1][3]][v[j1][i]]; hh = (old_orientation[j2] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][j]][w[i][j2]] = tet[j2]->curve[c][hh][v[j2][2]][v[j2][i]]; new_tet[i]->curve[c][h][w[i][j]][w[i][3]] = - (new_tet[i]->curve[c][h][w[i][j]][w[i][j1]] + new_tet[i]->curve[c][h][w[i][j]][w[i][j2]]); } /* * Set the polar vertices. */ for (j = 0; j < 3; j++) { /* which side of vertex 3 */ hh = (old_orientation[j] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][3]][w[i][j]] = tet[j]->curve[c][hh][v[j][!i]][v[j][i]]; } } /* * Set where_to_resume to the predecessor of the EdgeClass about * to be killed, so that the loop in the calling function can * continue at the correct spot in the list. */ *where_to_resume = edge->prev; /* * Kill the EdgeClass at the center of the three old Tetrahedra. */ REMOVE_NODE(edge); my_free(edge); /* * Update the surviving EdgeClasses. */ for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) { j1 = (j+1) % 3; j2 = (j+2) % 3; old_h_edge_index = edge_between_vertices[v[j2][2]][v[j2][ 3]]; old_v_edge_index = edge_between_vertices[v[j2][2]][v[j2][!i]]; new_h_edge_index = edge_between_vertices[w[ i][j]][w[ i][j1]]; new_v_edge_index = edge_between_vertices[w[ i][j]][w[ i][ 3]]; new_tet[i]->edge_class[new_h_edge_index] = tet[j2]->edge_class[old_h_edge_index]; new_tet[i]->edge_class[new_v_edge_index] = tet[j2]->edge_class[old_v_edge_index]; if (old_orientation[j2] == right_handed) { new_tet[i]->edge_orientation[new_h_edge_index] = tet[j2]->edge_orientation[old_h_edge_index]; new_tet[i]->edge_orientation[new_v_edge_index] = tet[j2]->edge_orientation[old_v_edge_index]; } else { new_tet[i]->edge_orientation[new_h_edge_index] = ! tet[j2]->edge_orientation[old_h_edge_index]; new_tet[i]->edge_orientation[new_v_edge_index] = ! tet[j2]->edge_orientation[old_v_edge_index]; } new_tet[i]->edge_class[new_v_edge_index]->order--; if (i == 0) new_tet[i]->edge_class[new_h_edge_index]->order++; new_tet[i]->edge_class[new_h_edge_index]->incident_tet = new_tet[i]; new_tet[i]->edge_class[new_v_edge_index]->incident_tet = new_tet[i]; new_tet[i]->edge_class[new_h_edge_index]->incident_edge_index = new_h_edge_index; new_tet[i]->edge_class[new_v_edge_index]->incident_edge_index = new_v_edge_index; } /* * Compute the shapes of the new Tetrahedra iff * the old tetrahedra had shapes. */ if (tet[0]->shape[complete] != NULL) { /* * Allocate space for the TetShapes of the new Tetrahedra. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) new_tet[i]->shape[j] = NEW_STRUCT(TetShape); /* * Add the complex edge angles of the old Tetrahedra * to get those of the new Tetrahedra. Use the * edge_orientation[] to get the orientations correct. * Note that add_edge_angles chooses angles in the range * [(-1/2) pi, (3/2) pi], regardless of the angles of * summands. */ for (i = 0; i < 2; i++) /* which new Tetrahedron */ for (j = 0; j < 3; j++) /* which EdgeClass */ add_edge_angles( tet[(j+1)%3], edge_between_vertices[v[(j+1)%3][3]][v[(j+1)%3][!i]], tet[(j+2)%3], edge_between_vertices[v[(j+2)%3][2]][v[(j+2)%3][!i]], new_tet[i], edge_between_vertices[w[i][j]][w[i][3]] ); } /* * Compute VertexCrossSections for the new Tetrahedra * iff the old Tetrahedra had VertexCrossSections. */ if (tet[0]->cross_section != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Allocate space for the VertexCrossSections of the new Tetrahedra. */ for (i = 0; i < 2; i++) new_tet[i]->cross_section = NEW_STRUCT(VertexCrossSections); /* * Compute the VertexCrossSections for each of the two new Tetrahedra. */ for (i = 0; i < 2; i++) { /* * Compute the polar VertexCrossSections. */ for (j = 0; j < 3; j++) new_tet[i]->cross_section->edge_length[w[i][3]][w[i][j]] = tet[j]->cross_section->edge_length[v[j][!i]][v[j][i]]; new_tet[i]->cross_section->has_been_set[w[i][3]] = TRUE; /* * Compute the equatorial VertexCrossSections. */ for (j = 0; j < 3; j++) compute_three_edge_lengths(new_tet[i], w[i][(j+1)%3], w[i][j], tet[j]->cross_section->edge_length[v[j][2]][v[j][i]]); } /* * Update the tilts. */ for (i = 0; i < 2; i++) compute_tilts_for_one_tet(new_tet[i]); } /* * Compute CuspNbhdPositions for the new Tetrahedra iff * the old Tetrahedra had CuspNbhdPositions. */ if (tet[0]->cusp_nbhd_position != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Allocate space for the CuspNbhdPositions of the new Tetrahedra. */ for (i = 0; i < 2; i++) new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); /* * Compute the CuspNbhdPositions for each of the two new Tetrahedra. */ for (i = 0; i < 2; i++) /* * Consider both the right_handed and left_handed sheets. */ for (h = 0; h < 2; h++) { /* * Compute the polar CuspNbhdPositions. * * The first approach which comes to mind is simply to copy * the relevant coordinates from the old Tetrahedra to the * new ones. Unfortunately the CuspNbhdPositions of the * three old tetrahedra may differ by covering translations. * * This problem is not insurmountable, but the code will be * cleaner if we simply copy the coordinates of two corners, * and then call cn_find_third_corner() to compute the * remaining corner. Recall that both new Tetrahedra are * seen in the right_handed Orientation, as is old tet[0]. */ if (tet[0]->cusp_nbhd_position->in_use[h][v[0][!i]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][1]] = tet[0]->cusp_nbhd_position->x[h][v[0][!i]][v[0][2]]; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][2]] = tet[0]->cusp_nbhd_position->x[h][v[0][!i]][v[0][3]]; cn_find_third_corner(new_tet[i], h, w[i][3], w[i][1], w[i][2], w[i][0]); new_tet[i]->cusp_nbhd_position->in_use[h][w[i][3]] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][1]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][2]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][0]] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][w[i][3]] = FALSE; } /* * Compute the equatorial CuspNbhdPositions. * * Technical note: The new_tets are both seen with the * right_handed Orientation. So when old_orientation[] * is also right_handed, we want to read the new sheet h * from the old sheet h. But when old_orientation[] is * left_handed, we want to read the new sheet h from the * old sheet !h. Because left_handed == 1, the expression * (old_orientation[] ^ h) gives the correct old sheet * to read from. */ for (j = 0; j < 3; j++) { if (tet[j]->cusp_nbhd_position->in_use[old_orientation[j]^h][v[j][2]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][(j+2)%3]] = tet[j]->cusp_nbhd_position->x[old_orientation[j]^h][v[j][2]][v[j][3]]; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][3]] = tet[j]->cusp_nbhd_position->x[old_orientation[j]^h][v[j][2]][v[j][!i]]; cn_find_third_corner(new_tet[i], h, w[i][(j+1)%3], w[i][(j+2)%3], w[i][3], w[i][j]); new_tet[i]->cusp_nbhd_position->in_use[h][w[i][(j+1)%3]] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][(j+2)%3]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i] [3] ] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i] [j] ] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][w[i][(j+1)%3]] = FALSE; } } } } /* * Put the new Tetrahedra on the list, and remove and free * the old ones. */ for (i = 0; i < 2; i++) INSERT_BEFORE(new_tet[i], tet[0]); for (i = 0; i < 3; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr -= 1; return func_OK; } /* * The three new Tetrahedra created by two_to_three() take tet0's place * in the list of Tetrahedra. Tet0 and the other doomed Tetrahedron are * removed from the list before being destroyed. Similarly, the new * EdgeClass is added to the list of EdgeClasses just in front of one * of the existing EdgeClasses. * * The Orientations of the three new Tetrahedra are set to match the * Orientation of one of the two old ones, so that the Orientability * of the Triangulation (if there is one) will be preserved. * * two_to_three() returns func_failed if either * * (1) the two initial Tetrahedra are not not distinct (i.e. tet0 * is glued to itself at face f), or * * (2) a hyperbolic structure is present, and even though the two * initial Tetrahedra are combinatorially distinct, they are * superimposed in hyperbolic space (i.e. the vertices opposite * their common face, though combinatorially distinct, lie at the * same point on the sphere at infinity). In this case, performing * the two_to_three() move would create degenerate Tetrahedra. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. */ FuncResult two_to_three( Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr) { Tetrahedron *tet[2], *new_tet[3]; VertexIndex v[2][4]; Orientation old_orientation[2]; int c, h, hh, i, i1, i2, j, k; EdgeClass *new_class; /* * two_to_three() is the inverse of three_to_two(), and * is implemented similarly. In particular, the picture to * imagine (or, better yet, draw on a scrap of paper before * diving into this code) is virtually identical to that from * three_to_two(), only what was tet[] there is new_tet[] here, * and vice versa. */ /* * Label tet[0] and tet[1]. */ tet[0] = tet0; v[0][3] = f; v[0][0] = !f; /* v[0][0] is some vertex other than v[0][3] */ v[0][1] = remaining_face[v[0][3]][v[0][0]]; /* tet[0] will be seen */ v[0][2] = remaining_face[v[0][0]][v[0][3]]; /* as left_handed */ old_orientation[0] = left_handed; tet[1] = tet[0]->neighbor[f]; for (i = 0; i < 4; i++) v[1][i] = EVALUATE(tet[0]->gluing[f], v[0][i]); old_orientation[1] = (parity[tet[0]->gluing[f]] == orientation_preserving) ? old_orientation[0] : ! old_orientation[0]; /* * If tet[0] and tet[1] are not distinct, we cannot proceed. */ if (tet[0] == tet[1]) return func_failed; /* * If a hyperbolic structure is present and the 2-3 move would create * degenerate Tetrahedra, we do not want to proceed. Degenerate * Tetrahedra will be created iff vertices v[0][3] and v[1][3] coincide. * (People usually think of degeneracy in terms of the dihedral angles * approaching {0, 1, infinity}, but in this context we also think in * terms of the equivalent definition that a Tetrahedron is degenerate * iff two or more vertices coincide.) * * angles_sum_to_zero() check whether the angles sum to zero mod 2 pi. */ if (tet[0]->shape[complete] != NULL) if (angles_sum_to_zero( tet[0], edge_between_vertices[v[0][0]][v[0][1]], tet[1], edge_between_vertices[v[1][0]][v[1][1]])) return func_failed; /* * Allocate the three new Tetrahedra. */ for (i = 0; i < 3; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } /* * Note that here we can refer to the VertexIndices of the new_tet[i] * directly, because a symmetrical indexing scheme is consistent * with a fixed orientation. In three_to_two(), the symmetrical * indexing scheme was not consistent with a fixed orientation, * so we had to use the w[][] to store the true indices of the * new Tetrahedra. */ /* * Set "internal" neighbors and gluings. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->neighbor[2] = new_tet[i1]; new_tet[i]->neighbor[3] = new_tet[i2]; new_tet[i]->gluing[2] = CREATE_PERMUTATION(0, 0, 1, 1, 2, 3, 3, 2); new_tet[i]->gluing[3] = CREATE_PERMUTATION(0, 0, 1, 1, 2, 3, 3, 2); } /* * Set "external" neighbors and gluings. * This code works even if some of the external faces of tet[0] * and tet[1] are glued to each other. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ for (j = 0; j < 2; j++) /* which face */ { new_tet[i]->neighbor[j] = tet[j]->neighbor[v[j][i]]; new_tet[i]->gluing[j] = CREATE_PERMUTATION( j, EVALUATE(tet[j]->gluing[v[j][i]], v[j][i]), !j, EVALUATE(tet[j]->gluing[v[j][i]], v[j][3]), 2, EVALUATE(tet[j]->gluing[v[j][i]], v[j][(i+1)%3]), 3, EVALUATE(tet[j]->gluing[v[j][i]], v[j][(i+2)%3]) ); set_inverse_neighbor_and_gluing(new_tet[i], j); } /* * Set the cusp fields. */ for (i = 0; i < 3; i++) { new_tet[i]->cusp[0] = tet[1]->cusp[v[1][3]]; new_tet[i]->cusp[1] = tet[0]->cusp[v[0][3]]; new_tet[i]->cusp[2] = tet[0]->cusp[v[0][(i+1)%3]]; new_tet[i]->cusp[3] = tet[0]->cusp[v[0][(i+2)%3]]; } /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) { /* which sheet */ /* * Set the exterior sides of the polar vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 0; j < 2; j++) { /* which vertex */ hh = (old_orientation[!j] == left_handed) ? h : !h; new_tet[i]->curve[c][h][j][!j] = tet[!j]->curve[c][hh][v[!j][3]][v[!j][i]]; } /* * Set the interior sides of the polar vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 0; j < 2; j++) /* which vertex */ for (k = 2; k < 4; k++) /* which side */ new_tet[i]->curve[c][h][j][k] = - FLOW( new_tet[ i ]->curve[c][h][j][!j], new_tet[(i+k-1)%3]->curve[c][h][j][!j]); /* * Set the equatorial vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 2; j < 4; j++) { /* which vertex */ for (k = 0; k < 2; k++) /* which side */ { hh = (old_orientation[k] == left_handed) ? h : !h; new_tet[i]->curve[c][h][j][k] = tet[k]->curve[c][hh][v[k][(i+j-1)%3]][v[k][i]]; } new_tet[i]->curve[c][h][j][5-j] = - (new_tet[i]->curve[c][h][j][0] + new_tet[i]->curve[c][h][j][1]); } } /* * Create the new EdgeClass. */ new_class = NEW_STRUCT(EdgeClass); initialize_edge_class(new_class); new_class->order = 3; new_class->incident_tet = new_tet[0]; new_class->incident_edge_index = edge_between_vertices[0][1]; /* * Insert the new EdgeClass at an arbitrary spot in the linked list. */ INSERT_BEFORE(new_class, tet[0]->edge_class[0]); /* * Set the EdgeClasses. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->edge_class[edge_between_vertices[0][1]] = new_class; new_tet[i]->edge_class[edge_between_vertices[0][2]] = tet[1]->edge_class[edge_between_vertices[v[1][3]][v[1][i1]]]; new_tet[i]->edge_class[edge_between_vertices[0][3]] = tet[1]->edge_class[edge_between_vertices[v[1][3]][v[1][i2]]]; new_tet[i]->edge_class[edge_between_vertices[1][2]] = tet[0]->edge_class[edge_between_vertices[v[0][3]][v[0][i1]]]; new_tet[i]->edge_class[edge_between_vertices[1][3]] = tet[0]->edge_class[edge_between_vertices[v[0][3]][v[0][i2]]]; new_tet[i]->edge_class[edge_between_vertices[2][3]] = tet[0]->edge_class[edge_between_vertices[v[0][i1]][v[0][i2]]]; } /* * Set the edge_orientations. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->edge_orientation[edge_between_vertices[0][1]] = right_handed; new_tet[i]->edge_orientation[edge_between_vertices[0][2]] = (old_orientation[1] == left_handed) ? tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i1]]] : ! tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i1]]]; new_tet[i]->edge_orientation[edge_between_vertices[0][3]] = (old_orientation[1] == left_handed) ? tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i2]]] : ! tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i2]]]; new_tet[i]->edge_orientation[edge_between_vertices[1][2]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i1]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i1]]]; new_tet[i]->edge_orientation[edge_between_vertices[1][3]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i2]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i2]]]; new_tet[i]->edge_orientation[edge_between_vertices[2][3]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][i1]][v[0][i2]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][i1]][v[0][i2]]]; } /* * Adjust the EdgeClass orders. */ for (i = 0; i < 3; i++) { new_tet[i]->edge_class[edge_between_vertices[0][2]]->order++; new_tet[i]->edge_class[edge_between_vertices[1][2]]->order++; new_tet[i]->edge_class[edge_between_vertices[2][3]]->order--; } /* * Set incident_tets and incident_edge_indices. */ for (i = 0; i < 3; i++) for (j = 0; j < 6; j++) { new_tet[i]->edge_class[j]->incident_tet = new_tet[i]; new_tet[i]->edge_class[j]->incident_edge_index = j; } /* * Compute the shapes of the new Tetrahedra iff * the old tetrahedra had shapes. */ if (tet[0]->shape[complete] != NULL) { /* * Allocate space for the TetShapes of the new Tetrahedra. */ for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) new_tet[i]->shape[j] = NEW_STRUCT(TetShape); /* * First compute the TetShapes for the equatorial angles. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ add_edge_angles( tet[0], edge_between_vertices[v[0][(i+1)%3]][v[0][(i+2)%3]], tet[1], edge_between_vertices[v[1][(i+1)%3]][v[1][(i+2)%3]], new_tet[i], edge_between_vertices[2][3] ); /* * Now compute the remaining complex angles of each Tetrahedron, * with log.imag in the range [(-1/2) pi, (3/2) pi]. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ compute_remaining_angles(new_tet[i], edge3_between_vertices[2][3]); } /* * Compute VertexCrossSections for the new Tetrahedra * iff the old Tetrahedra had VertexCrossSections. */ if (tet[0]->cross_section != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("two_to_three", "simplify_triangulation"); /* * Allocate space for the VertexCrossSections of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->cross_section = NEW_STRUCT(VertexCrossSections); /* * Compute the VertexCrossSections for each of * the three new Tetrahedra. */ for (i = 0; i < 3; i++) { /* * Compute the polar vertices. */ for (j = 0; j < 2; j++) compute_three_edge_lengths(new_tet[i], !j, j, tet[j]->cross_section->edge_length[v[j][3]][v[j][i]]); /* * Compute the equatorial vertices. */ for (j = 2; j < 4; j++) compute_three_edge_lengths(new_tet[i], j, 0, tet[0]->cross_section->edge_length[v[0][(i+j+2)%3]][v[0][i]]); } /* * Update the tilts. */ for (i = 0; i < 3; i++) compute_tilts_for_one_tet(new_tet[i]); } /* * Provide CanonizeInfo for the new Tetrahedra * iff the old Tetrahedra had CanonizeInfo. */ if (tet[0]->canonize_info != NULL) { /* * Allocate space for the CanonizeInfo of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->canonize_info = NEW_STRUCT(CanonizeInfo); /* * Set part_of_coned_cell to TRUE for each new Tetrahedron. */ for (i = 0; i < 3; i++) new_tet[i]->canonize_info->part_of_coned_cell = TRUE; /* * Set each new "exterior" face to have the same face_status * as the corresponding old face. */ for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) new_tet[i]->canonize_info->face_status[j] = tet[j]->canonize_info->face_status[v[j][i]]; /* * Set each new "interior" face to have face_status inside_cone_face. */ for (i = 0; i < 3; i++) for (j = 2; j < 4; j++) new_tet[i]->canonize_info->face_status[j] = inside_cone_face; } /* * Compute CuspNbhdPositions for the new Tetrahedra iff * the old Tetrahedra had CuspNbhdPositions. */ if (tet[0]->cusp_nbhd_position != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("two_to_three", "simplify_triangulation"); /* * Allocate space for the CuspNbhdPositions of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); /* * Compute the CuspNbhdPositions for each of the three new Tetrahedra. */ for (i = 0; i < 3; i++) /* * Consider both the right_handed and left_handed sheets. */ for (h = 0; h < 2; h++) { /* * Compute the polar CuspNbhdPositions. * * Technical note: The new_tets are all seen with the * left_handed Orientation. So when old_orientation[] * is also left_handed, we want to read the new sheet h * from the old sheet h. But when old_orientation[] is * right_handed, we want to read the new sheet h from the * old sheet !h. Because left_handed == 1, the expression * (old_orientation[] == h) gives the correct old sheet * to read from. */ for (j = 0; j < 2; j++) { if (tet[j]->cusp_nbhd_position->in_use[old_orientation[j]==h][v[j][3]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][!j][2] = tet[j]->cusp_nbhd_position->x[old_orientation[j]==h][v[j][3]][v[j][(i+1)%3]]; new_tet[i]->cusp_nbhd_position->x[h][!j][3] = tet[j]->cusp_nbhd_position->x[old_orientation[j]==h][v[j][3]][v[j][(i+2)%3]]; cn_find_third_corner(new_tet[i], h, !j, 2, 3, j); new_tet[i]->cusp_nbhd_position->in_use[h][!j] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][!j][2] = Zero; new_tet[i]->cusp_nbhd_position->x[h][!j][3] = Zero; new_tet[i]->cusp_nbhd_position->x[h][!j][j] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][!j] = FALSE; } } /* * Compute the equatorial CuspNbhdPositions. * * The three new Tetrahedra are seen in the left_handed * Orientation, as is the old tet[0]. So if we coordinates * coordinates from tet[0] we know the sheets will match up. */ for (j = 2; j < 4; j++) { if (tet[0]->cusp_nbhd_position->in_use[h][v[0][(i+j+2)%3]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][j][5-j] = tet[0]->cusp_nbhd_position->x[h][v[0][(i+j+2)%3]][v[0][(4+i-j)%3]]; new_tet[i]->cusp_nbhd_position->x[h][j][1] = tet[0]->cusp_nbhd_position->x[h][v[0][(i+j+2)%3]][v[0][3]]; cn_find_third_corner(new_tet[i], h, j, 5-j, 1, 0); new_tet[i]->cusp_nbhd_position->in_use[h][j] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][j][5-j] = Zero; new_tet[i]->cusp_nbhd_position->x[h][j][ 1 ] = Zero; new_tet[i]->cusp_nbhd_position->x[h][j][ 0 ] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][j] = FALSE; } } } } /* * Put the new Tetrahedra on the list, and remove and free * the old ones. */ for (i = 0; i < 3; i++) INSERT_BEFORE(new_tet[i], tet[0]); for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr += 1; return func_OK; } /* * one_to_four() performs a one-to-four move, replacing a Tetrahedron * with four new Tetrahedra meeting at a finite vertex. It adds * the four new Tetrahedra and the four new EdgeClasses to the * appropriate lists. */ void one_to_four( Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index) { int c, h, i, j, k; Tetrahedron *new_tet[4]; Cusp *new_cusp; EdgeClass *new_class[4]; /* * It doesn't make sense to call this function when a hyperbolic * structure, VertexCrossSections or a CuspNbhdPosition are present. */ if (tet->shape[complete] != NULL || tet->cross_section != NULL || tet->cusp_nbhd_position != NULL) uFatalError("one_to_four", "simplify_triangulation"); /* * To understand this code, I recommend you first make a drawing * of a truncated ideal tetrahedron, and draw its subdivision into * four tetrahedra meeting at the center. The four new Tetrahedra * are indexed in the natural way: each vertex which coincides with * a vertex of the old Tetrahedron inherits the latter's VertexIndex, * while the vertex at the center gets the VertexIndex from the * "opposite" vertex of the old Tetrahedron. * * Note that if the manifold is oriented, this scheme preserves * the orientation. */ /* * Allocate space for the new Tetrahedra. * * new_tet[i] will be the new Tetrahedron incident to face i * of the old Tetrahedron. */ for (i = 0; i < 4; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } /* * Set neighbors and gluings. * * This code works even if some of tet's external faces * are glued to each other. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) if (j == i) /* "external" neighbor */ { new_tet[i]->neighbor[j] = tet->neighbor[i]; new_tet[i]->gluing[j] = tet->gluing[i]; set_inverse_neighbor_and_gluing(new_tet[i], j); } else { /* "internal" neighbor */ new_tet[i]->neighbor[j] = new_tet[j]; new_tet[i]->gluing[j] = CREATE_PERMUTATION( remaining_face[i][j], remaining_face[i][j], remaining_face[j][i], remaining_face[j][i], i, j, j, i); } /* * Create a Cusp structure for the finite vertex. */ new_cusp = NEW_STRUCT(Cusp); initialize_cusp(new_cusp); new_cusp->is_finite = TRUE; new_cusp->index = new_cusp_index; INSERT_BEFORE(new_cusp, tet->cusp[0]); /* * Set the new Tetrahedra's cusp fields. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) new_tet[i]->cusp[j] = j == i ? new_cusp : /* finite vertex */ tet->cusp[j]; /* ideal vertex */ /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) /* which sheet */ for (i = 0; i < 4; i++) /* which tetrahedron */ for (j = 0; j < 4; j++) /* which vertex */ if (j == i) /* * Set the peripheral curves on the finite vertex * to zero. */ for (k = 0; k < 4; k++) /* which side */ new_tet[i]->curve[c][h][i][k] = 0; else /* * Set the curves on the new ideal vertices in * terms of those on the old. */ for (k = 0; k < 4; k++) /* which side */ if (k == j) /* * The j-th vertex has no j-th side. * Do nothing. */ ; else if (k == i) /* * The i-th side of vertex j on new * Tetrahedron i coinicdes with the * i-th side of vertex j on the old * Tetrahedron. */ new_tet[i]->curve[c][h][j][i] = tet->curve[c][h][j][i]; else /* * Compute the new side as a FLOW * between two of the old sides. */ new_tet[i]->curve[c][h][j][k] = FLOW( tet->curve[c][h][j][k], tet->curve[c][h][j][i]); /* * Create the new EdgeClasses. */ for (i = 0; i < 4; i++) { new_class[i] = NEW_STRUCT(EdgeClass); initialize_edge_class(new_class[i]); new_class[i]->order = 3; new_class[i]->incident_tet = new_tet[!i]; new_class[i]->incident_edge_index = edge_between_vertices[i][!i]; } /* * Insert the new EdgeClasses at an arbitrary spot in the linked list. */ for (i = 0; i < 4; i++) INSERT_BEFORE(new_class[i], tet->edge_class[0]); /* * Set the edge_class and edge_orientation fields of the new Tetrahedra. */ for (i = 0; i < 4; i++) /* which Tetrahedron */ for (j = 0; j < 4; j++) /* VertexIndex at one end of edge */ for (k = j + 1; k < 4; k++) /* VertexIndex at other end of edge */ { new_tet[i]->edge_class[edge_between_vertices[j][k]] = (j == i || k == i) ? (j == i ? new_class[k] : new_class[j]) : tet->edge_class[edge_between_vertices[j][k]]; new_tet[i]->edge_orientation[edge_between_vertices[j][k]] = (j == i || k == i) ? right_handed : tet->edge_orientation[edge_between_vertices[j][k]]; } /* * Adjust the EdgeClass orders of the preexisting EdgeClasses. * (The orders of the new EdgeClasses were set above.) */ for (i = 0; i < 6; i++) tet->edge_class[i]->order++; /* * Set incident_tets and incident_edge_indices for the preexisting * EdgeClasses. (Those for the new EdgeClasses were set above.) */ for (i = 0; i < 6; i++) { tet->edge_class[i]->incident_tet = new_tet[one_face_at_edge[i]]; tet->edge_class[i]->incident_edge_index = i; } /* * Provide CanonizeInfo for the new Tetrahedra iff the old Tetrahedron * had CanonizeInfo. */ if (tet->canonize_info != NULL) { /* * For each new Tetrahedron . . . */ for (i = 0; i < 4; i++) { /* * Allocate space for the CanonizeInfo. */ new_tet[i]->canonize_info = NEW_STRUCT(CanonizeInfo); /* * Set part_of_coned_cell to TRUE. */ new_tet[i]->canonize_info->part_of_coned_cell = TRUE; /* * Set face_status to inside_cone_face for each "interior" face, * and have it match the old values for the "exterior" faces. */ for (j = 0; j < 4; j++) /* which face */ new_tet[i]->canonize_info->face_status[j] = j == i ? tet->canonize_info->face_status[j] : /* exterior face */ inside_cone_face; /* interior face */ } } /* * Put the new Tetrahedra on the list, and remove and free the old one. */ for (i = 0; i < 4; i++) INSERT_BEFORE(new_tet[i], tet); REMOVE_NODE(tet); free_tetrahedron(tet); *num_tetrahedra_ptr += 3; } static FuncResult edges_of_order_four( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { PositionedTet ptet0, ptet; /* * *edge is an EdgeClass of order 4. Look for another EdgeClass * of order 4 which shares a triangle with *edge. If the six * Tetrahedra incident to the two EdgeClasses are all distinct, * then their union is a suspended pentagon which will be * retriangulated with only five Tetrahedra. */ ptet0.tet = edge->incident_tet; ptet0.bottom_face = one_vertex_at_edge[edge->incident_edge_index]; ptet0.right_face = other_vertex_at_edge[edge->incident_edge_index]; ptet0.near_face = remaining_face[ptet0.bottom_face][ptet0.right_face]; ptet0.left_face = remaining_face[ptet0.right_face][ptet0.bottom_face]; ptet0.orientation = right_handed; ptet = ptet0; do { if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face]]->order == 4) if (try_adjacent_fours(ptet.tet, ptet.near_face, ptet.bottom_face, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->order == 4) if (try_adjacent_fours(ptet.tet, ptet.near_face, ptet.right_face, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); return func_failed; } static FuncResult try_adjacent_fours( Tetrahedron *tet0, FaceIndex f0, FaceIndex f1, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet[6]; FaceIndex f2, f3, g2, g3; int i, j; EdgeClass *class0, *class1; /* * Two nonantipodal EdgeClasses of order 4 lies on tet. The * face between them has index f0. The face not incident to * either has index f1. */ /* * Find the six Tetrahedra adjacent to the EdgeClasses of order 4. */ tet[0] = tet0; f2 = remaining_face[f0][f1]; f3 = remaining_face[f1][f0]; tet[1] = tet0->neighbor[f0]; g2 = EVALUATE(tet0->gluing[f0], f2); g3 = EVALUATE(tet0->gluing[f0], f3); tet[2] = tet[0]->neighbor[f2]; tet[3] = tet[0]->neighbor[f3]; tet[4] = tet[1]->neighbor[g2]; tet[5] = tet[1]->neighbor[g3]; /* * If the six Tetrahedra aren't all distinct, return func_failed. * (Thought question: Might simplification sometimes be possible * even if all six Tetrahedra aren't distinct? Hmmm . . . seems * unlikely.) */ for (i = 0; i < 6; i++) for (j = i + 1; j < 6; j++) if (tet[i] == tet[j]) return func_failed; /* * Note the two EdgeClasses which now have order four. */ class0 = tet0->edge_class[edge_between_faces[f0][f2]]; class1 = tet0->edge_class[edge_between_faces[f0][f3]]; /* * The following two-to-three move increases the number of * Tetrahedra by one, but it creates two EdgeClasses of * order three . . . */ if (two_to_three(tet0, f0, num_tetrahedra_ptr) == func_failed) { /* * (There can't be any topological obstruction to the * retriangulation, but there might be a geometric obstruction, * namely that the two_to_three() move might require creation * of degenerate Tetrahedra. So if two_to_three() fails when * a hyperbolic structure is present, we assume (potential) * degenerate Tetrahedra are the cause, and we return func_failed. * Otherwise we call uFatalError().) */ if (tet0->shape[complete] != NULL) return func_failed; else uFatalError("try_adjacent_fours", "simplify_triangulation"); } /* * . . . each of which can be used to reduce the number of * Tetrahedra by one. */ if (three_to_two(class0, where_to_resume, num_tetrahedra_ptr) == func_failed || three_to_two(class1, where_to_resume, num_tetrahedra_ptr) == func_failed) uFatalError("try_adjacent_fours", "simplify_triangulation"); /* * Note that where_to_resume will come out pointing to some * valid EdgeClass. We won't worry too much about just which * one it points at. */ return func_OK; } static FuncResult create_new_order_four( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { PositionedTet ptet0, ptet; if (edge->order != 4) return func_failed; /* * create_new_order_four() is similar to edges_of_order_four(). * * *edge is an EdgeClass of order 4. Look for another EdgeClass * of order 5 or less which shares a triangle with *edge. * If the four Tetrahedra incident to *edge are all distinct, * then their union is an octagon which will be retriangulated * so as to create a new EdgeClass of order 4 or less. */ ptet0.tet = edge->incident_tet; ptet0.bottom_face = one_vertex_at_edge[edge->incident_edge_index]; ptet0.right_face = other_vertex_at_edge[edge->incident_edge_index]; ptet0.near_face = remaining_face[ptet0.bottom_face][ptet0.right_face]; ptet0.left_face = remaining_face[ptet0.right_face][ptet0.bottom_face]; ptet0.orientation = right_handed; if (four_tetrahedra_are_distinct(ptet0) == FALSE) return func_failed; ptet = ptet0; do { if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face ]]->order <= 5 || ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->order <= 5) { if (two_to_three(ptet.tet, ptet.near_face, num_tetrahedra_ptr) == func_OK) { if (three_to_two(edge, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; else uFatalError("create_new_order_four", "simplify_triangulation"); } else { /* * The call to two_to_three() failed. It can't fail for * topological reasons (we checked that the four Tetrahedra * surrounding the EdgeClass of order 4 are distinct), but * if a hyperbolic structure is present it might fail * because two antipodal vertices of the octahedron * coincide. In the latter case, we simply move on in * the hope that a different retriangulation will work. */ if (ptet.tet->shape[complete] == NULL) uFatalError("create_new_order_four", "simplify_triangulation"); /* * else continue with do loop */ } } veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); return func_failed; } static Boolean four_tetrahedra_are_distinct( PositionedTet ptet) { int i, j; Tetrahedron *tet[4]; for (i = 0; i < 4; i++) { tet[i] = ptet.tet; veer_left(&ptet); } for (i = 0; i < 4; i++) for (j = i + 1; j < 4; j++) if (tet[i] == tet[j]) return FALSE; return TRUE; } static void set_inverse_neighbor_and_gluing( Tetrahedron *tet, FaceIndex f) { tet->neighbor[f]->neighbor[EVALUATE(tet->gluing[f], f)] = tet; tet->neighbor[f]->gluing [EVALUATE(tet->gluing[f], f)] = inverse_permutation[tet->gluing[f]]; } snappea-3.0d3/SnapPeaKernel/code/sl2c_matrices.c0100444000175000017500000000547607001146376017603 0ustar babbab/* * sl2c_matrices.c * * This file provides the following functions for working with SL2CMatrices: * * void sl2c_copy(SL2CMatrix dest, CONST SL2CMatrix source); * void sl2c_invert(CONST SL2CMatrix a, SL2CMatrix inverse); * void sl2c_complex_conjugate(CONST SL2CMatrix a, SL2CMatrix conjugate); * void sl2c_product(CONST SL2CMatrix a, CONST SL2CMatrix b, SL2CMatrix product); * void sl2c_adjoint(CONST SL2CMatrix a, SL2CMatrix adjoint); * Complex sl2c_determinant(CONST SL2CMatrix a); * void sl2c_normalize(SL2CMatrix a); * Boolean sl2c_matrix_is_real(CONST SL2CMatrix a); */ #include "kernel.h" void sl2c_copy( SL2CMatrix dest, CONST SL2CMatrix source) { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) dest[i][j] = source[i][j]; } void sl2c_invert( CONST SL2CMatrix a, SL2CMatrix inverse) { Complex temp; temp = a[0][0]; inverse[0][0] = a[1][1]; inverse[1][1] = temp; inverse[0][1] = complex_negate(a[0][1]); inverse[1][0] = complex_negate(a[1][0]); } void sl2c_complex_conjugate( CONST SL2CMatrix a, SL2CMatrix conjugate) { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) conjugate[i][j] = complex_conjugate(a[i][j]); } void sl2c_product( CONST SL2CMatrix a, CONST SL2CMatrix b, SL2CMatrix product) { int i, j; SL2CMatrix temp; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) temp[i][j] = complex_plus ( complex_mult(a[i][0], b[0][j]), complex_mult(a[i][1], b[1][j]) ); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) product[i][j] = temp[i][j]; } void sl2c_adjoint( CONST SL2CMatrix a, SL2CMatrix adjoint) { int i, j; SL2CMatrix temp; /* * We initially write the result into a temporary matrix, * so that if the matrices a and adjoint are the same the * [1][0] entry won't overwrite the [0][1] entry. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) temp[i][j] = complex_conjugate(a[j][i]); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) adjoint[i][j] = temp[i][j]; } Complex sl2c_determinant( CONST SL2CMatrix a) { return( complex_minus ( complex_mult(a[0][0], a[1][1]), complex_mult(a[0][1], a[1][0]) ) ); } void sl2c_normalize( SL2CMatrix a) { /* * If the matrix a is nonsingular, normalize it to have determinant one. * Otherwise, generate an error message and quit. */ int i, j; Complex det, factor; det = sl2c_determinant(a); if (complex_nonzero(det) == FALSE) uFatalError("sl2c_normalize", "sl2c_matrices"); factor = complex_sqrt(det); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) a[i][j] = complex_div(a[i][j], factor); } Boolean sl2c_matrix_is_real( CONST SL2CMatrix a) { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (a[i][j].imag != 0.0) return FALSE; return TRUE; } snappea-3.0d3/SnapPeaKernel/code/solve_equations.c0100444000175000017500000002245306742675502020274 0ustar babbab/* * solve_equations.c * * This file provides the functions * * FuncResult solve_complex_equations(Complex **complex_equations, * int num_rows, int num_columns, Complex *solution); * FuncResult solve_real_equations(double **real_equations, * int num_rows, int num_columns, double *solution); * * which do_Dehn_filling() in hyperbolic_structure.c calls to * solve num_rows linear equations in num_columns variables. Gaussian * elimination with partial pivoting is used. * * The equations are stored as an array of num_rows pointers, each * of which points to an array of (num_columns + 1) entries. The * last entry in each row is the constant on the right hand side of * the equation. * * num_rows is assumed to be greater than or equal to num_columns. * The equations are assumed to have rank exactly equal to num_columns. * Thus, even though there may be more equations than variables, the * equations are assumed to be consistent. * * Even though these routines make no assumption about the origin or * purpose of the equations, their main use is, of course, to find * hyperbolic structures. My hope in including all the equations * (rather than just a linearly independent subset) is that we will * get more accurate solutions, particularly in degenerate or * nearly degenerate situations. * * These functions assume an array of num_columns elements has already * been allocated for the solution. * * Technical detail: While doing the Gaussian elimination, these functions * do not actually compute or write values which are guaranteed to be one * or zero, but they knows where they are. So don't worry that in the end * the matrix does not contain all ones and zeros; the matrix will be a * mess, but the solution will be correct. */ #include "kernel.h" FuncResult solve_complex_equations( Complex **complex_equations, int num_rows, int num_columns, Complex *solution) { /* * The following register variables are used in the n^3 bottleneck. * (See below.) */ register double factor_real, factor_imag; register Complex *row_r, *row_c; register int count; /* * The remaining variables are used in less critical places. */ int r, c, cc, pivot_row = -1; double max_modulus, this_modulus, max_error, error; Complex *temp, factor; /* * Forward elimination. */ for (c = 0; c < num_columns; c++) { /* * Find the pivot row. */ max_modulus = 0.0; for (r = c; r < num_rows; r++) { this_modulus = complex_modulus(complex_equations[r][c]); if (this_modulus > max_modulus) { max_modulus = this_modulus; pivot_row = r; } } if (max_modulus == 0.0) /* In the old snappea, max_modulus */ return func_failed; /* was was never below 1e-100, even */ /* in degenerate cases. */ /* * Swap the pivot row into position. */ temp = complex_equations[c]; complex_equations[c] = complex_equations[pivot_row]; complex_equations[pivot_row] = temp; /* * Multiply the pivot row through by 1.0/(pivot value). */ factor = complex_div(One, complex_equations[c][c]); for (cc = c + 1; cc <= num_columns; cc++) complex_equations[c][cc] = complex_mult( factor, complex_equations[c][cc] ); /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < num_rows; r++) { /* * The following loop is the bottleneck for computing * hyperbolic structures. It is executed n^3 times to solve * an n x n system of equations, and no other n^3 algorithms * are used. For this reason, I've written the loop to * maximize speed at the expense of readability. * * Here's the loop in pseudocode: * for (cc = c + 1; cc <= num_columns; cc++) complex_equations[r][cc] -= complex_equations[r][c] * complex_equations[c][cc] * * Here's a version that will actually run: * for (cc = c + 1; cc <= num_columns; cc++) complex_equations[r][cc] = complex_minus( complex_equations[r][cc], complex_mult( complex_equations[r][c], complex_equations[c][cc] ) ); * * And here's the fancy, built-for-speed version: */ factor_real = - complex_equations[r][c].real; factor_imag = - complex_equations[r][c].imag; if (factor_real || factor_imag) { row_r = complex_equations[r] + c + 1; row_c = complex_equations[c] + c + 1; for (count = num_columns - c; --count >= 0; ) { if (row_c->real || row_c->imag) { row_r->real += factor_real * row_c->real - factor_imag * row_c->imag; row_r->imag += factor_real * row_c->imag + factor_imag * row_c->real; } row_r++; row_c++; } } /* * With all the THINK C compiler's optimization options on, * the fancy version runs 8 times faster than the plain * version. With all THINK C optimization options off, * it runs 9 times faster. The THINK C optimizer increases * the speed of the fancy code by only 6%. */ /* * Yield some time to the window system, and check * whether the user has cancelled this computation. */ if (uLongComputationContinues() == func_cancelled) return func_cancelled; } } /* * Back substitution. */ for (c = num_columns; --c > 0; ) /* Do columns (num_columns - 1) to 1, */ /* but skip column 0. */ for (r = c; --r >= 0; ) /* Do rows (c - 1) to 0. */ complex_equations[r][num_columns] = complex_minus( complex_equations[r][num_columns], complex_mult( complex_equations[r][c], complex_equations[c][num_columns] ) ); /* * Check "extra" rows for consistency. * That is, in each of the last (num_rows - num_columns) rows, * check that the constant on the right hand side is zero. * This will give us a measure of the accuracy of the solution. * I still haven't decided what to do with this number. */ max_error = 0.0; for (r = num_columns; r < num_rows; r++) { error = complex_modulus(complex_equations[r][num_columns]); if (error > max_error) max_error = error; } /* * Record the solution. */ for (r = 0; r < num_columns; r++) solution[r] = complex_equations[r][num_columns]; return func_OK; } FuncResult solve_real_equations( double **real_equations, int num_rows, int num_columns, double *solution) { /* * The following register variables are used in the n^3 bottleneck. * (See below.) */ register double factor, *row_r, *row_c; register int count; /* * The remaining variables are used in less critical places. */ int r, c, cc, pivot_row = -1; double max_abs, this_abs, max_error, error, *temp; /* * Forward elimination. */ for (c = 0; c < num_columns; c++) { /* * Find the pivot row. */ max_abs = 0.0; for (r = c; r < num_rows; r++) { this_abs = fabs(real_equations[r][c]); if (this_abs > max_abs) { max_abs = this_abs; pivot_row = r; } } if (max_abs == 0.0) return func_failed; /* * Swap the pivot row into position. */ temp = real_equations[c]; real_equations[c] = real_equations[pivot_row]; real_equations[pivot_row] = temp; /* * Multiply the pivot row through by 1.0/(pivot value). */ factor = 1.0 / real_equations[c][c]; for (cc = c + 1; cc <= num_columns; cc++) real_equations[c][cc] *= factor; /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < num_rows; r++) { factor = - real_equations[r][c]; /* * The following loop is the bottleneck for computing * hyperbolic structures. It is executed n^3 times to solve * an n x n system of equations, and no other n^3 algorithms * are used. For this reason, I've written the loop to * maximize speed at the expense of readability. * * Here's the loop in its humanly comprehensible form: * if (factor) for (cc = c + 1; cc <= num_columns; cc++) real_equations[r][cc] += factor * real_equations[c][cc]; * * Here's the optimized version of the same thing: */ if (factor) { row_r = real_equations[r] + c + 1; row_c = real_equations[c] + c + 1; for (count = num_columns - c; --count>=0; ) *row_r++ += factor * *row_c++; } /* * Yield some time to the window system, and check * whether the user has cancelled this computation. */ if (uLongComputationContinues() == func_cancelled) return func_cancelled; } } /* * Back substitution. */ for (c = num_columns; --c > 0; ) /* Do columns (num_columns - 1) to 1, */ /* but skip column 0. */ for (r = c; --r >= 0; ) /* Do rows (c - 1) to 0. */ real_equations[r][num_columns] -= real_equations[r][c] * real_equations[c][num_columns]; /* * Check "extra" rows for consistency. * That is, in each of the last (num_rows - num_columns) rows, * check that the constant on the right hand side is zero. * This will give us a measure of the accuracy of the solution. * I still haven't decided what to do with this number. */ max_error = 0.0; for (r = num_columns; r < num_rows; r++) { error = fabs(real_equations[r][num_columns]); if (error > max_error) max_error = error; } /* * Record the solution. */ for (r = 0; r < num_columns; r++) solution[r] = real_equations[r][num_columns]; return func_OK; } snappea-3.0d3/SnapPeaKernel/code/subdivide.c0100444000175000017500000004542507040650170017020 0ustar babbab/* * subdivide.c * * This file contains the function * * Triangulation *subdivide(Triangulation *manifold, char *new_name); * * which accepts a Triangulation *manifold, copies it, and * subdivides the copy as described below into a Triangulation * with finite as well as ideal vertices. The original * triangulation is not changed. * * Triangulations produced by subdivide() differ from * ordinary triangulations in that they have finite vertices * as well as ideal vertices. * * At present, only the function fill_cusps() calls subdivide(), * but other kernel functions could use it too, if the need arises. * * Note: subdivide() has a subtle dependence on the implementation * of orient(). The first Tetrahedron on the new Triangulation's * tet list has the correct orientation, and orient() must propogate * that orientation to all the other Tetrahedra. * * The function subdivide() subdivides each Tetrahedron of a * Triangulation as follows. First, a regular neighborhood of * each ideal vertex is sliced off, to form its own new * Tetrahedron. Each such new Tetrahedron will have three finite * vertices as well as the original ideal vertex. The ideal * vertex keeps the same VertexIndex as the original, while * each of the three finite vertices inherits the VertexIndex * of the nearest (other) ideal vertex of the original Tetrahedron. * I wish I could provide a good illustration, but it's hard to * embed 3-D graphics in ASCII files; here's a 2-D illustration * which shows the general idea: * * original * vertex * #0 * * 0 /\ * / \ * / \ * / \ * / \ * 1 /__________\ 2 * / \ * / \ * / \ * / \ * / \ * / \ * 0 /\ /\ 0 * / \ / \ * / \ / \ * / \ / \ * / \ / \ * original /__________\____________/__________\ original * vertex #1 1 2 1 2 vertex #2 * * After removing the four Tetrahedra just described, you are * left with a solid which has four hexagonal faces and four * triangular faces. Please make yourself a sketch of a * truncated tetrahedron to illustrate this. Each hexagonal * face is subdivided into six triangles by coning to the * center: * * original * vertex * #0 * * .. * . . * . . * . . * . . * 1 ____________ 2 * /\ /\ * / \ / \ * / \ / \ * / \ / \ * / \ / \ * 0 /_________3\/__________\ 0 * .\ /\ /. * . \ / \ / . * . \ / \ / . * . \ / \ / . * . \ / \ / . * original . . . . . .\/__________\/. . . . . . original * vertex #1 2 1 vertex #2 * * The center vertex on each face gets the VertexIndex of the * opposite vertex of the original Tetrahedron (which equals the * FaceIndex of the face being subdivided). * Finally, the truncated tetrahedron is subdivided by coning * to the center. This creates 28 Tetrahedron (6 for each of * the four hexagonal faces, plus 1 for each of the four triangular * faces). The vertex at the center of the truncated tetrahedron * cannot be given a canonical VertexIndex common to all incident * Tetrahedra. Instead, each incident Tetrahedron assigns it * the unique index not yet used by that Tetrahedron. * * This canonical scheme for numbering the vertices makes the * calculation of the gluing Permutations very easy. "Internal" * gluings (i.e. between two new Tetrahedra belonging to same old * Tetrahedron) may be readily deduced from the above definitions. * Because the numbering scheme is canonical, all "external" gluings * (i.e. between two new Tetrahedra belonging to different old * Tetrahedra) will be the same as the corresponding gluing of the * original Tetrahedron. (Yes, I realize that the above definitions * of "internal" and "external" aren't quite right in the case of * an original Tetrahedron glued to itself, but I hope you understood * what I said anyhow.) */ #include "kernel.h" /* * If you are not familiar with SnapPea's "Extra" field in * the Tetrahedron data structure, please see the explanation * preceding the Extra typedef in kernel_typedefs.h. * * Subdivide() uses an Extra field in each Tetrahedron of the * old_triangulation to keep track of the 32 corresponding * Tetrahedra in the new_triangulation. Please see the * documentation above for an explanation of the subdivision * algorithm. If you drew a sketch illustrating the subdivision, * it will be helpful in understanding the fields of the Extra struct. */ struct extra { /* * The first four Tetrahedra lopped off in the above * algorithm will be called "outer vertex Tetrahedra". * outer_vertex_tet[i] is a pointer to the outer vertex * Tetrahedron at vertex i of the old Tetrahedron. */ Tetrahedron *outer_vertex_tet[4]; /* * The "inner vertex Tetrahedra" are the remaining * Tetrahedra which are naturally associated to the * ideal vertices of the old Tetrahedron. * inner_vertex_tet[i] is a pointer to the new Tetrahedron * which meets outer_vertex_tet[i] along it's i-th face. */ Tetrahedron *inner_vertex_tet[4]; /* * The "edge Tetrahedra" are the 12 Tetrahedra which have * precisely one edge contained within an edge of the * old Tetrahedron. edge_tet[i][j] is the Tetrahedron * which has a face contained in face i of the old * Tetrahedron, on the side (of face i) opposite vertex j. * edge_tet[i][j] is defined iff i != j. */ Tetrahedron *edge_tet[4][4]; /* * The "face Tetrahedra" are the 12 remaining Tetrahedra. * face_tet[i][j] has a face contained in face i of the * old Tetrahedron, on the side near vertex j (of the old * Tetrahedron). face_tet[i][j] is defined iff i != j. */ Tetrahedron *face_tet[4][4]; }; static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void create_new_tetrahedra(Triangulation *new_triangulation, Triangulation *old_triangulation); static void allocate_new_tetrahedra(Triangulation *new_triangulation, Triangulation *old_triangulation); static void set_outer_vertex_tets(Tetrahedron *old_tet); static void set_inner_vertex_tets(Tetrahedron *old_tet); static void set_edge_tets(Tetrahedron *old_tet); static void set_face_tets(Tetrahedron *old_tet); static void create_new_cusps(Triangulation *new_triangulation, Triangulation *old_triangulation); static void create_real_cusps(Triangulation *new_triangulation, Triangulation *old_triangulation); Triangulation *subdivide( Triangulation *old_triangulation, char *new_name) { Triangulation *new_triangulation; /* * Allocate storage for the new_triangulation * and initialize it. * * (In spite of what I wrote at the top of this file, * we don't explicitly make a copy of *old_triangulation, * but instead we create *new_triangulation from scratch.) */ new_triangulation = NEW_STRUCT(Triangulation); initialize_triangulation(new_triangulation); new_triangulation->name = NEW_ARRAY(strlen(new_name) + 1, char); strcpy(new_triangulation->name, new_name); new_triangulation->num_tetrahedra = 32 * old_triangulation->num_tetrahedra; new_triangulation->num_cusps = old_triangulation->num_cusps; new_triangulation->num_or_cusps = old_triangulation->num_or_cusps; new_triangulation->num_nonor_cusps = old_triangulation->num_nonor_cusps; /* * Attach an Extra field to each Tetrahedron in the * old_triangulation, to keep track of the corresponding * 32 Tetrahedra in the new_triangulation. */ attach_extra(old_triangulation); /* * Create the new Tetrahedra. * Set fields such as tet->neighbor and tet->gluing, * which can be determined immediately. * Postpone determination of fields such as tet->cusp * and tet->edge_class. */ create_new_tetrahedra(new_triangulation, old_triangulation); /* * Copy the Cusps from the old_triangulation to * the new_triangulation. (Technical note: functions * called by create_new_cusps() rely on the fact that * create_new_tetrahedra() initializes all tet->cusp * fields to NULL.) */ create_new_cusps(new_triangulation, old_triangulation); /* * We're done with the Extra fields, so free them. */ free_extra(old_triangulation); /* * Add the bells and whistles. */ create_edge_classes(new_triangulation); orient_edge_classes(new_triangulation); orient(new_triangulation); /* * Return the new_triangulation. */ return new_triangulation; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "filling"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void create_new_tetrahedra( Triangulation *new_triangulation, Triangulation *old_triangulation) { Tetrahedron *old_tet; /* * Allocate the memory for all the new Tetrahedra. * We do this before setting any of the fields, so * that the tet->neighbor fields will all have something * to point to. */ allocate_new_tetrahedra(new_triangulation, old_triangulation); /* * For each Tetrahedron in the old_triangulation, we want * to set the various fields of the 32 corresponding Tetrahedra * in the new triangulation. The new Tetrahedra are accessed * through the Extra fields of the old Tetrahedra. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) { set_outer_vertex_tets(old_tet); set_inner_vertex_tets(old_tet); set_edge_tets(old_tet); set_face_tets(old_tet); } } static void allocate_new_tetrahedra( Triangulation *new_triangulation, Triangulation *old_triangulation) { int i, j; Tetrahedron *old_tet, *new_tet; /* * For each Tetrahedron in the old_triangulation, we want * to allocate and initialize the 32 corresponding Tetrahedra * in the new_triangulation. */ /* * IMPORTANT: It's crucial that one of the outer vertex tetrahedra * appears first on new_triangulation's tet list, so that orient() * will preserve the manifold's original orientation. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) { for (i = 0; i < 4; i++) { new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->outer_vertex_tet[i] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) { new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->inner_vertex_tet[i] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { if (i == j) { old_tet->extra->edge_tet[i][j] = NULL; continue; } new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->edge_tet[i][j] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { if (i == j) { old_tet->extra->face_tet[i][j] = NULL; continue; } new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->face_tet[i][j] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } } } static void set_outer_vertex_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the four outer_vertex_tets. */ for (i = 0; i < 4; i++) { /* * For notational clarity, let new_tet be a pointer * to the outer_vertex_tet under consideration. */ new_tet = old_tet->extra->outer_vertex_tet[i]; /* * Set the new_tet's four neighbor fields. */ for (j = 0; j < 4; j++) new_tet->neighbor[j] = (i == j) ? old_tet->extra->inner_vertex_tet[i] : old_tet->neighbor[j]->extra->outer_vertex_tet[EVALUATE(old_tet->gluing[j], i)]; /* * Set the new_tet's four gluing fields. */ for (j = 0; j < 4; j++) new_tet->gluing[j] = (i == j) ? IDENTITY_PERMUTATION : old_tet->gluing[j]; /* * Copy the peripheral curves from the old_tet to the ideal * vertex of the new_tet. The peripheral curves of * finite vertices have already been set to zero. */ for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) new_tet->curve[j][k][i][l] = old_tet->curve[j][k][i][l]; } } static void set_inner_vertex_tets( Tetrahedron *old_tet) { int i, j; Tetrahedron *new_tet; /* * Set the fields for each of the four inner_vertex_tets. */ for (i = 0; i < 4; i++) { /* * For notational clarity, let new_tet be a pointer * to the inner_vertex_tet under consideration. */ new_tet = old_tet->extra->inner_vertex_tet[i]; /* * Set the new_tet's four neighbor fields. */ for (j = 0; j < 4; j++) new_tet->neighbor[j] = (i == j) ? old_tet->extra->outer_vertex_tet[i] : old_tet->extra->face_tet[j][i]; /* * Set the new_tet's four gluing fields. */ for (j = 0; j < 4; j++) new_tet->gluing[j] = IDENTITY_PERMUTATION; } } static void set_edge_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the twelve edge_tets. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { /* * Only the case i != j is meaningful. */ if (i == j) continue; /* * For notational clarity, let new_tet be a pointer * to the edge_tet under consideration. */ new_tet = old_tet->extra->edge_tet[i][j]; /* * Let i, j, k and l be the VertexIndices of * new_tet. i and j are already defined as * loop variables. We define k and l here. */ k = remaining_face[i][j]; l = remaining_face[j][i]; /* * Set the new_tet's four neighbor fields. */ new_tet->neighbor[i] = old_tet->extra->edge_tet[j][i]; new_tet->neighbor[j] = old_tet->neighbor[i]->extra->edge_tet[EVALUATE(old_tet->gluing[i], i)][EVALUATE(old_tet->gluing[i], j)]; new_tet->neighbor[k] = old_tet->extra->face_tet[i][k]; new_tet->neighbor[l] = old_tet->extra->face_tet[i][l]; /* * Set the new_tet's four gluing fields. */ new_tet->gluing[i] = CREATE_PERMUTATION(i,j,j,i,k,k,l,l); new_tet->gluing[j] = old_tet->gluing[i]; new_tet->gluing[k] = CREATE_PERMUTATION(i,i,j,k,k,j,l,l); new_tet->gluing[l] = CREATE_PERMUTATION(i,i,j,l,k,k,l,j); } } static void set_face_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the twelve face_tets. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { /* * Only the case i != j is meaningful. */ if (i == j) continue; /* * For notational clarity, let new_tet be a pointer * to the face_tet under consideration. */ new_tet = old_tet->extra->face_tet[i][j]; /* * Let i, j, k and l be the VertexIndices of * new_tet. i and j are already defined as * loop variables. We define k and l here. */ k = remaining_face[i][j]; l = remaining_face[j][i]; /* * Set the new_tet's four neighbor fields. */ new_tet->neighbor[i] = old_tet->extra->inner_vertex_tet[j]; new_tet->neighbor[j] = old_tet->neighbor[i]->extra->face_tet[EVALUATE(old_tet->gluing[i], i)][EVALUATE(old_tet->gluing[i], j)]; new_tet->neighbor[k] = old_tet->extra->edge_tet[i][k]; new_tet->neighbor[l] = old_tet->extra->edge_tet[i][l]; /* * Set the new_tet's four gluing fields. */ new_tet->gluing[i] = IDENTITY_PERMUTATION; new_tet->gluing[j] = old_tet->gluing[i]; new_tet->gluing[k] = CREATE_PERMUTATION(i,i,j,k,k,j,l,l); new_tet->gluing[l] = CREATE_PERMUTATION(i,i,j,l,k,k,l,j); } } static void create_new_cusps( Triangulation *new_triangulation, Triangulation *old_triangulation) { /* * The Cusp data structures for the real cusps are * handled separately from the Cusp data structures * for the finite vertices. */ create_real_cusps(new_triangulation, old_triangulation); create_fake_cusps(new_triangulation); } static void create_real_cusps( Triangulation *new_triangulation, Triangulation *old_triangulation) { Cusp *old_cusp, *new_cusp; Tetrahedron *old_tet; int i; /* * The Cusp data structures for the ideal vertices * in the new_triangulation are essentially just * copied from those in the old_triangulation. */ /* * Allocate memory for the new Cusps, copy in the * values from the old cusps, and put them on the * queue. */ for (old_cusp = old_triangulation->cusp_list_begin.next; old_cusp != &old_triangulation->cusp_list_end; old_cusp = old_cusp->next) { new_cusp = NEW_STRUCT(Cusp); *new_cusp = *old_cusp; new_cusp->is_finite = FALSE; INSERT_BEFORE(new_cusp, &new_triangulation->cusp_list_end); old_cusp->matching_cusp = new_cusp; } /* * Set the cusp field for ideal vertices in * the new_triangulation. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) for (i = 0; i < 4; i++) old_tet->extra->outer_vertex_tet[i]->cusp[i] = old_tet->cusp[i]->matching_cusp; } snappea-3.0d3/SnapPeaKernel/code/symmetric_group.c0100444000175000017500000000574006742675502020304 0ustar babbab/* * symmetric_group.c * * At the moment this file contains only the function * * Boolean is_group_S5(SymmetryGroup *the_group); * * which checks whether the_group is the symmetric group S5. * Eventually I will write a more general set of functions * to test for other symmetric and alternating groups. */ #include "kernel.h" Boolean is_group_S5( SymmetryGroup *the_group) { /* * Table 5 on page 137 of Coxeter & Moser's book "Generators and * relations for discrete groups" provides the following presentation * for the symmetric group S5, which they in turn credit to page 125 of * W. Burnside's article "Note on the symmetric group", Proc. London * Math. Soc. (1), vol. 28 (1897) 119-129. * * {A, B | A^2 = B^5 = (AB)^4 = (A(B^-1)AB)^3 = 1} * * where A = (1 2) and B = (1 2 3 4 5). * * Coxeter & Moser adopt the convention of reading products of * group elements left-to-right, whereas we read them right-to-left, * so we should translate the above presentation as * * {A, B | A^2 = B^5 = (BA)^4 = (BA(B^-1)A)^3 = 1} * * In this case, though, it doesn't matter, because the relations * in the latter presentation are conjugate to the corresponding * relations in the former. */ int a, b, ab, aB, aBab, possible_generators[2]; /* * If the order of the_group isn't 120, it can't possibly be S5. */ if (the_group->order != 120) return FALSE; /* * Consider all possible images of the generator a in the_group. */ for (a = 0; a < the_group->order; a++) { /* * If a does not have order 2, ignore it and move on. */ if (the_group->order_of_element[a] != 2) continue; /* * Consider all possible images of the generator b in the_group. */ for (b = 0; b < the_group->order; b++) { /* * If b does not have order 5, ignore it and move on. */ if (the_group->order_of_element[b] != 5) continue; /* * Compute ab and a(b^-1)ab. */ ab = the_group->product[a][b]; aB = the_group->product[a][the_group->inverse[b]]; aBab = the_group->product[aB][ab]; /* * If ab does not have order 4, give up and move on. */ if (the_group->order_of_element[ab] != 4) continue; /* * If a(b^-1)ab does not have order 3, give up and move on. */ if (the_group->order_of_element[aBab] != 3) continue; /* * At this point we know a^2 = b^5 = (ab)^4 = (a(b^-1)ab)^3 = 1. * We have a homomorphism from S5 to the_group. It will be an * isomorphism iff a and b generate the_group. */ /* * Write a and b into an array . . . */ possible_generators[0] = a; possible_generators[1] = b; /* * . . . and pass the array to the function which checks * whether they generate the group. */ if (elements_generate_group(the_group, 2, possible_generators) == TRUE) return TRUE; /* * If a and b failed to generate the_group, we continue on * with the hope that some other choice of a and b will work. */ } } return FALSE; } snappea-3.0d3/SnapPeaKernel/code/symmetry_group.c0100444000175000017500000001257706742675502020167 0ustar babbab/* * symmetry_group.c * * This file provides the following two functions * * FuncResult compute_symmetry_group( * Triangulation *manifold, * SymmetryGroup **symmetry_group_of_manifold, * SymmetryGroup **symmetry_group_of_link, * Triangulation **symmetric_triangulation, * Boolean *is_full_group); * * void free_symmetry_group(SymmetryGroup *symmetry_group); * * If *manifold is not a manifold (i.e. if any cusp is filled with * coefficients other than relatively prime integers), then * compute_symmetry_group() returns func_bad_input. * * If *manifold is a closed manifold (i.e. all cusps are * filled with relatively prime Dehn filling coefficients), then * compute_symmetry_group() attempts to compute its symmetry group. * If it succeeds, it sets * * *symmetry_group_of_manifold points to the symmetry group * *symmetry_group_of_link left as NULL * *symmetric_triangulation points to a Dehn filling * description of the manifold * realizing the symmetry group * *is_full_group TRUE * returns func_OK. * * Sometimes it will compute a group which is known to be a * subgroup of the true symmetry group, but may or may not be * the full group. In this case it sets * * *symmetry_group_of_manifold points to the known subgroup * *symmetry_group_of_link left as NULL * *symmetric_triangulation points to a Dehn filling * description of the manifold * realizing the subgroup * *is_full_group FALSE * returns func_OK. * * If the algorithm fails entirely (this should be rare) it sets * * *symmetry_group_of_manifold left as NULL * *symmetry_group_of_link left as NULL * *symmetric_triangulation left as NULL * *is_full_group undefined * returns func_failed. * * For details on the algorithm for closed manifolds, * see symmetry_group_closed.c. * * If *manifold is a cupsed manifold (i.e. at least one cusp is unfilled, * and all filled cusps have relatively prime Dehn filling * coefficients), then compute_symmetry_group() attempts to compute * its symmetry group, and also the symmetry group of the * corresponding link (defined in symmetry_group_cusped.c). * If it succeeds, it sets * * *symmetry_group_of_manifold points to the symmetry group * *symmetry_group_of_link points to link symmetry group * *symmetric_triangulation left as NULL * *is_full_group TRUE * returns func_OK. * * If it fails (this should be rare), its sets * * *symmetry_group_of_manifold left as NULL * *symmetry_group_of_link left as NULL * *symmetric_triangulation left as NULL * *is_full_group undefined * returns func_failed. * * The case where only a subgroup is known does not occur for * cusped manifolds. For details on the algorithm for cusped * manifolds, see symmetry_group_cusped.c. * * * compute_symmetry_group() assumes that initially * * *symmetry_group_of_manifold == NULL * *symmetry_group_of_link == NULL * *symmetric_triangulation == NULL. * * In other words, there are no leftover SymmetryGroups or * Triangulations to be freed. * * free_symmetry_group() frees the SymmetryGroup. */ #include "kernel.h" FuncResult compute_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link, Triangulation **symmetric_triangulation, Boolean *is_full_group) { Triangulation *simplified_manifold; FuncResult result; /* * Make sure the variables used to pass back our results * are all initially empty. */ if (*symmetry_group_of_manifold != NULL || *symmetry_group_of_link != NULL || *symmetric_triangulation != NULL) uFatalError("compute_symmetry_group", "symmetry_group"); /* * If the space isn't a manifold, return func_bad_input. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) return func_bad_input; /* * Whether the manifold is cusped or not, we want to begin * by getting rid of "unnecessary" cusps. */ simplified_manifold = fill_reasonable_cusps(manifold); if (simplified_manifold == NULL) return func_failed; /* * Split into cases according to whether the manifold is * closed or cusped (i.e. whether all cusps are filled or not). */ if (all_cusps_are_filled(simplified_manifold) == TRUE) result = compute_closed_symmetry_group( simplified_manifold, symmetry_group_of_manifold, symmetric_triangulation, is_full_group); else { result = compute_cusped_symmetry_group( simplified_manifold, symmetry_group_of_manifold, symmetry_group_of_link); *is_full_group = TRUE; } free_triangulation(simplified_manifold); return result; } void free_symmetry_group( SymmetryGroup *symmetry_group) { if (symmetry_group != NULL) { int i; free_isometry_list(symmetry_group->symmetry_list); for (i = 0; i < symmetry_group->order; i++) my_free(symmetry_group->product[i]); my_free(symmetry_group->product); my_free(symmetry_group->order_of_element); my_free(symmetry_group->inverse); if (symmetry_group->abelian_description != NULL) free_abelian_group(symmetry_group->abelian_description); if (symmetry_group->is_direct_product == TRUE) for (i = 0; i < 2; i++) free_symmetry_group(symmetry_group->factor[i]); my_free(symmetry_group); } } snappea-3.0d3/SnapPeaKernel/code/symmetry_group_closed.c0100444000175000017500000010160707053772000021474 0ustar babbab/* * symmetry_group_closed.c * * This file provides the function * * FuncResult compute_closed_symmetry_group( * Triangulation *manifold, * SymmetryGroup **symmetry_group, * Triangulation **symmetric_triangulation, * Boolean *is_full_group); * * See symmetry_group.c for an explanation of the arguments * and return values. * * The theory behind this algorithm is explained in the preprint * * C. Hodgson & J. Weeks, "Symmetries, isometries and length * spectra of closed hyperbolic 3-manifolds", to appear in * Experimental Mathematics. * * Please note that the current version of this preprint is * substantially different than the version which appeared * in the Geometry Center's preprint series (although I plan * to install the new version in the on-line preprint library). */ #include "kernel.h" #include #define LENGTH_EPSILON 1e-8 #define TORSION_EPSILON 1e-8 #define ZERO_TORSION_EPSILON 1e-8 #define PI_TORSION_EPSILON 1e-8 #define CRUDE_EPSILON 1e-3 #define VOLUME_ERROR_EPSILON 1e-8 #define INFINITE_ORDER INT_MAX #define INFINITE_MULTIPLICITY INT_MAX #define MAX_DUAL_CURVE_LENGTH 8 #define MAX_RANDOMIZATIONS 16 /* for geometric complete structure */ #define MAX_RETRIANGULATIONS 8 /* for geometric filled structure */ typedef struct { double length, torsion; /* absolute value of torsion */ int pos_multiplicity, neg_multiplicity, zero_multiplicity, /* torsion within epsilon of zero */ total_multiplicity; /* sum of previous three fields */ /* torsions within epsilon of -pi should not occur */ } MergedMultiLength; static WEPolyhedron *compute_polyhedron(Triangulation *manifold); static FuncResult compute_symmetry_group_using_polyhedron(Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group, WEPolyhedron *polyhedron); static void compute_length_spectrum(WEPolyhedron *polyhedron, MultiLength **spectrum, int *num_lengths); static double rigor_radius(double spine_radius, double cutoff_length); static FuncResult merge_length_spectrum(int num_lengths, MultiLength *spectrum, int *num_merged_lengths, MergedMultiLength **merged_spectrum); static void try_to_drill_curves(Triangulation *original_manifold, MergedMultiLength desired_curves, int *lower_bound, int *upper_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation); static FuncResult drill_one_curve(Triangulation **manifold, MergedMultiLength *remaining_curves); static FuncResult fill_first_cusp(Triangulation **manifold); static FuncResult find_geometric_solution(Triangulation **manifold); static FuncResult compute_symmetry_group_without_polyhedron(Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group); static void try_to_drill_unknown_curves(Triangulation **manifold, Complex desired_length, int *lower_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation); FuncResult compute_closed_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group) { FuncResult result; /* * Make sure the variables used to pass back our results * are all initially empty. */ if (*symmetry_group != NULL || *symmetric_triangulation != NULL) uFatalError("compute_closed_symmetry_group", "symmetry_group"); /* * compute_symmetry_group() should have passed us a 1-cusp * manifold with a Dehn filling on its cusp. */ if (get_num_cusps(manifold) != 1 || all_cusps_are_filled(manifold) == FALSE || all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) { uFatalError("compute_closed_symmetry_group", "symmetry_group_closed"); } /* * For later convenience, change the basis on the cusp * so that the Dehn filling curve becomes a meridian. */ { MatrixInt22 basis_change[1]; current_curve_basis(manifold, 0, basis_change[0]); change_peripheral_curves(manifold, basis_change); } /* * At the very least, we can try to establish a (possibly trivial) * lower bound on the symmetry group by computing the group * which preserves the given core geodesic. */ { SymmetryGroup *dummy = NULL; if (compute_cusped_symmetry_group(manifold, &dummy, symmetry_group) == func_OK) { copy_triangulation(manifold, symmetric_triangulation); free_symmetry_group(dummy); /* we don't need dummy */ } else { /* * The only way compute_cusped_symmetry_group() may fail * is if a canonical cell decomposition cannot be found, * e.g. because the manifold is not hyperbolic. */ return func_failed; } } /* * For small to medium sized manifolds we should have * no trouble getting a Dirichlet domain. But if we can't, * then we want to muddle along as best we can without one. */ { WEPolyhedron *polyhedron; polyhedron = compute_polyhedron(manifold); if (polyhedron != NULL) { result = compute_symmetry_group_using_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group, polyhedron); free_Dirichlet_domain(polyhedron); } else result = compute_symmetry_group_without_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group); } return result; } static WEPolyhedron *compute_polyhedron( Triangulation *manifold) { int i; WEPolyhedron *polyhedron; const static int num_precisions = 5; const static double precision[5] = {1e-8, 1e-6, 1e-10, 1e-4, 1e-12}; for (i = 0; i < num_precisions; i++) { polyhedron = Dirichlet( manifold, precision[i], TRUE, Dirichlet_stop_here, TRUE); if (polyhedron != NULL) return polyhedron; } /* * Even after trying five precisions we still couldn't * get a Dirichlet domain. */ return NULL; } static FuncResult compute_symmetry_group_using_polyhedron( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group, WEPolyhedron *polyhedron) { MultiLength *spectrum; int num_lengths; MergedMultiLength *merged_spectrum; int i, num_merged_lengths, lower_bound, upper_bound; /* * Use the polyhedron to compute a length spectrum, * if a nontrivial length spectrum can be computed in * a reasonable amount of time. */ compute_length_spectrum(polyhedron, &spectrum, &num_lengths); /* * If we couldn't get a nonempty length spectrum in * a reasonable amount of time, so we must fall back to * compute_symmetry_group_without_polyhedron(). */ if (num_lengths == 0) return(compute_symmetry_group_without_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group)); /* * Merge complex lengths with opposite torsions, because * typically they will need to be drilled together. * Usually merge_length_spectrum() will succeed, but it will fail * if the numerical accuracy of the length spectrum is too poor * to clearly resolve equal lengths. In the latter case we must * fall back to compute_symmetry_group_without_polyhedron(). */ if (merge_length_spectrum( num_lengths, spectrum, &num_merged_lengths, &merged_spectrum) == func_failed) { free_length_spectrum(spectrum); return(compute_symmetry_group_without_polyhedron( manifold, symmetry_group, symmetric_triangulation, is_full_group)); } /* * We no longer need the original, unmerged spectrum. */ free_length_spectrum(spectrum); spectrum = NULL; num_lengths = 0; /* * We maintain lower and upper bounds on the order of the symmetry * group. Please see the preprint "Symmetries, isometries and length * spectra of closed hyperbolic 3-manifolds" for details (cf. the top * of this file). Most likely, compute_closed_symmetry_group() has * already provided us with a crude lower bound on the true group. */ if (*symmetry_group != NULL) { lower_bound = symmetry_group_order(*symmetry_group); upper_bound = INFINITE_ORDER; } else { lower_bound = 0; upper_bound = INFINITE_ORDER; } /* * Try to drill each MergedMultiLength in turn, to obtain various * Dehn filling descriptions of the manifold. A given description * may improve the lower bound and/or the upper bound. */ for (i = 0; i < num_merged_lengths; i++) { try_to_drill_curves( manifold, merged_spectrum[i], &lower_bound, &upper_bound, symmetry_group, symmetric_triangulation); if (lower_bound == upper_bound) break; } /* * Free merged_spectrum. */ my_free(merged_spectrum); /* * We know the symmetry group with complete certainty iff * lower_bound == upper_bound. Otherwise we have only a * lower bound on the true symmetry group. */ *is_full_group = (lower_bound == upper_bound); /* * If we've found any symmetry group at all, return func_OK. */ if (lower_bound > 0) return func_OK; else return func_failed; } static void compute_length_spectrum( WEPolyhedron *polyhedron, MultiLength **spectrum, int *num_lengths) { /* * We have two concerns: * * (1) We want to get a nontrivial length spectrum * (preferably one containing several different lengths, * but certainly not an empty one). * * (2) We don't want the computation to take too long. * * We address the second concern by refusing to tile * beyond a max_tiling_radius of 4.0 (this could be increased, * perhaps to 5.0, on faster machines). * * Our plan, then, is to try successively larger values * for cutoff_length until either we get enough_lengths, or we * exceed the max_tiling_radius, whichever comes first. * At that point we declare success if the length spectrum * is nonempty (even if it contains fewer than enough_lengths), * or failure otherwise. */ double cutoff_length; const static double max_tiling_radius = 5.0; /* changed from 4.0 to 5.0 JRW 98/4/30 */ const static int enough_lengths = 3; /* * Initially we have no length spectrum. */ *spectrum = NULL; *num_lengths = 0; /* * Start with cutoff_length = 1.0, and keep incrementing it * until either (1) we get enough_lengths, or (2) the tiling_radius * becomes unacceptably large. */ for ( cutoff_length = 1.0; *num_lengths < enough_lengths && rigor_radius(polyhedron->spine_radius, cutoff_length) < max_tiling_radius; cutoff_length += 1.0 ) { /* * If a spectrum is left over from the previous iteration * of the for(;;) loop, free it. */ if (*spectrum != NULL) { free_length_spectrum(*spectrum); *spectrum = NULL; *num_lengths = 0; } length_spectrum(polyhedron, cutoff_length, TRUE, TRUE, 0.0, spectrum, num_lengths); } /* * We've done our best, so return whether or not *num_lengths > 0. */ } static double rigor_radius( double spine_radius, double cutoff_length) { return 2*arccosh(cosh(spine_radius)*cosh(cutoff_length/2)); } static FuncResult merge_length_spectrum( int num_lengths, MultiLength *spectrum, int *num_merged_lengths, MergedMultiLength **merged_spectrum) { int i, j; Boolean already_on_list; /* * compute_symmetry_group_using_polyhedron() has already checked * that num_lengths is nonzero. */ if (num_lengths <= 0) uFatalError("merge_length_spectrum", "symmetry_group_closed"); /* * The merged_spectrum will require at most as many entries * as the original spectrum, so we allocate an array of that * length. This could waste space by up to a factor of two, * but this is no big deal. */ *merged_spectrum = NEW_ARRAY(num_lengths, MergedMultiLength); *num_merged_lengths = 0; /* * Look at each MultiLength in turn, and decide how it should * be incorporated into the merged_spectrum. */ for (i = 0; i < num_lengths; i++) { /* * Handle torsion zero as a special case. */ if (fabs(spectrum[i].length.imag) < ZERO_TORSION_EPSILON) { (*merged_spectrum)[*num_merged_lengths].length = spectrum[i].length.real; (*merged_spectrum)[*num_merged_lengths].torsion = 0.0; (*merged_spectrum)[*num_merged_lengths].pos_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].neg_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].zero_multiplicity = spectrum[i].multiplicity; (*merged_spectrum)[*num_merged_lengths].total_multiplicity = spectrum[i].multiplicity; (*num_merged_lengths)++; continue; } /* * The complex length program should never report a torsion * of -pi. (It always converts to +pi.) */ if (spectrum[i].length.imag < -PI + PI_TORSION_EPSILON) uFatalError("merge_length_spectrum", "symmetry_group_closed"); /* * Treat torsion pi as a special case. */ if (spectrum[i].length.imag > PI - PI_TORSION_EPSILON) { (*merged_spectrum)[*num_merged_lengths].length = spectrum[i].length.real; (*merged_spectrum)[*num_merged_lengths].torsion = PI; (*merged_spectrum)[*num_merged_lengths].pos_multiplicity = spectrum[i].multiplicity; (*merged_spectrum)[*num_merged_lengths].neg_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].zero_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].total_multiplicity = spectrum[i].multiplicity; (*num_merged_lengths)++; continue; } /* * Is the given (length, abs(torsion)) already on the merged list? * If so, fold in the new values. */ already_on_list = FALSE; for (j = 0; j < *num_merged_lengths; j++) { if (fabs(spectrum[i].length.real - (*merged_spectrum)[j].length) < LENGTH_EPSILON && fabs(fabs(spectrum[i].length.imag) - (*merged_spectrum)[j].torsion) < TORSION_EPSILON) { if (spectrum[i].length.imag > 0.0) (*merged_spectrum)[j].pos_multiplicity += spectrum[i].multiplicity; else (*merged_spectrum)[j].neg_multiplicity += spectrum[i].multiplicity; (*merged_spectrum)[j].total_multiplicity += spectrum[i].multiplicity; already_on_list = TRUE; break; } } if (already_on_list == TRUE) continue; /* * As a guard against errors, check that the current complex length * is not within some very crude epsilon of an existing length. */ for (j = 0; j < *num_merged_lengths; j++) { if (fabs(spectrum[i].length.real - (*merged_spectrum)[j].length) < CRUDE_EPSILON && fabs(fabs(spectrum[i].length.imag) - (*merged_spectrum)[j].torsion) < CRUDE_EPSILON) { my_free(*merged_spectrum); *merged_spectrum = NULL; *num_merged_lengths = 0; return func_failed; } } /* * Create a new entry on the merged list. */ (*merged_spectrum)[*num_merged_lengths].length = spectrum[i].length.real; (*merged_spectrum)[*num_merged_lengths].torsion = fabs(spectrum[i].length.imag); if (spectrum[i].length.imag > 0.0) { (*merged_spectrum)[*num_merged_lengths].pos_multiplicity = spectrum[i].multiplicity; (*merged_spectrum)[*num_merged_lengths].neg_multiplicity = 0; } else { (*merged_spectrum)[*num_merged_lengths].pos_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].neg_multiplicity = spectrum[i].multiplicity; } (*merged_spectrum)[*num_merged_lengths].zero_multiplicity = 0; (*merged_spectrum)[*num_merged_lengths].total_multiplicity = spectrum[i].multiplicity; (*num_merged_lengths)++; } if (*num_merged_lengths > num_lengths) uFatalError("merge_length_spectrum", "symmetry_group_closed"); return func_OK; } static void try_to_drill_curves( Triangulation *original_manifold, MergedMultiLength desired_curves, int *lower_bound, int *upper_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation) { Triangulation *manifold; double old_volume, new_volume; int singularity_index; Complex core_length; SymmetryGroup *manifold_sym_grp = NULL, *link_sym_grp = NULL; int new_upper_bound; MergedMultiLength remaining_curves; int num_possible_images; /* * Let's work on a copy, and leave the original_manifold untouched. */ copy_triangulation(original_manifold, &manifold); /* * As a guard against creating weird triangulations, * do an "unnecessary" error check. */ old_volume = volume(manifold, NULL); /* * To assist in the bookkeeping, we make a copy of the MergedMultiLength * and use it to keep track of how many curves remain. */ remaining_curves = desired_curves; /* * In cases where the number of geodesics with positive torsion * does not equal the number with negative torsion (and both numbers * are nonzero), the manifold is clearly chiral, and we want to * drill only the curves with the lesser multiplicity. */ if (remaining_curves.pos_multiplicity > 0 && remaining_curves.neg_multiplicity > 0 && remaining_curves.pos_multiplicity != remaining_curves.neg_multiplicity) { /* * Supress the curves with the greater multiplicity. */ if (remaining_curves.pos_multiplicity > remaining_curves.neg_multiplicity) { remaining_curves.total_multiplicity -= remaining_curves.pos_multiplicity; remaining_curves.pos_multiplicity = 0; } else { remaining_curves.total_multiplicity -= remaining_curves.neg_multiplicity; remaining_curves.neg_multiplicity = 0; } } /* * Note the original number of curves in the set we'll be drilling. * It's the number of possible images of a given curve under the * action of the symmetry group. (In the case of differing positive * and negative multiplicity, the curves of lesser multiplicity * must be taken to themselves. Otherwise curves of positive * torsion could be taken to curves of negative torsion.) */ num_possible_images = remaining_curves.total_multiplicity; /* * The current core geodesic may or may not have the desired length. * If it doesn't, replace it with a new one which does. * Note that the absolute value of the torsion might be correct, * but the sign of the torsion might have been "suppressed" above. */ core_geodesic(manifold, 0, &singularity_index, &core_length, NULL); if ( /* * length is wrong */ fabs(desired_curves.length - core_length.real) > LENGTH_EPSILON || /* * torsion is wrong */ ( /* * Doesn't match desired positive torsion. */ ( /* * We're not looking for positive torsion. */ remaining_curves.pos_multiplicity == 0 || /* * It doesn't have the correct positive torsion. */ fabs(desired_curves.torsion - core_length.imag) > TORSION_EPSILON ) && /* * Doesn't match desired negative torsion. */ ( /* * We're not looking for negative torsion. */ remaining_curves.neg_multiplicity == 0 || /* * It doesn't have the correct negative torsion. */ fabs((-desired_curves.torsion) - core_length.imag) > TORSION_EPSILON ) && /* * Doesn't match desired zero torsion. */ ( /* * We're not looking for zero torsion. */ remaining_curves.zero_multiplicity == 0 || /* * It doesn't have zero torsion. */ fabs(core_length.imag) > ZERO_TORSION_EPSILON ) ) ) { if (drill_one_curve(&manifold, &remaining_curves) == func_failed || fill_first_cusp(&manifold) == func_failed) { free_triangulation(manifold); return; } } else /* core geodesic is already a desired curve */ { /* * The complex length program should never report a torsion * of -pi. (It always converts to +pi.) */ if (core_length.imag < -PI + PI_TORSION_EPSILON) uFatalError("try_to_drill_curves", "symmetry_group_closed"); if (core_length.imag > ZERO_TORSION_EPSILON) remaining_curves.pos_multiplicity--; else if (core_length.imag < -ZERO_TORSION_EPSILON) remaining_curves.neg_multiplicity--; else remaining_curves.zero_multiplicity--; remaining_curves.total_multiplicity--; } /* * At this point we have drilled out one curve of the correct length. * If the description is positively oriented, we may obtain an upper bound * on the order of the symmetry group. */ if (find_geometric_solution(&manifold) == func_OK) { if (compute_cusped_symmetry_group(manifold, &manifold_sym_grp, &link_sym_grp) != func_OK) { free_triangulation(manifold); return; } new_upper_bound = num_possible_images * symmetry_group_order(link_sym_grp); if (new_upper_bound < *upper_bound) *upper_bound = new_upper_bound; free_symmetry_group(manifold_sym_grp); free_symmetry_group(link_sym_grp); manifold_sym_grp = NULL; link_sym_grp = NULL; } /* * We have drilled out one curve of the correct length. * Keep drilling curves until we've got them all. */ while (remaining_curves.total_multiplicity > 0) if (drill_one_curve(&manifold, &remaining_curves) == func_failed) { free_triangulation(manifold); return; } /* * We could have a degenerate_solution if we drilled out * curves in the wrong isotopy classes. */ if (get_filled_solution_type(manifold) == degenerate_solution) { free_triangulation(manifold); return; } /* * Try to get a geometric_solution. */ (void) find_geometric_solution(&manifold); /* * Finish our "unnecessary" error check. */ new_volume = volume(manifold, NULL); if (fabs(new_volume - old_volume) > VOLUME_ERROR_EPSILON) { /* * If ever we're looking for pathological triangulations, * this would be a good place to set a breakpoint. */ free_triangulation(manifold); return; } /* * Check the symmetry group of what we have, and see whether it provides * a lower bound. If a geometric_solution was found, then we'll have * an upper bound as well, i.e. we'll know the symmetry group exactly. */ if (compute_cusped_symmetry_group(manifold, &manifold_sym_grp, &link_sym_grp) == func_failed) { free_triangulation(manifold); return; } if (symmetry_group_order(link_sym_grp) > *lower_bound) { *lower_bound = symmetry_group_order(link_sym_grp); free_symmetry_group(*symmetry_group); /* NULL is OK */ *symmetry_group = link_sym_grp; free_triangulation(*symmetric_triangulation); /* NULL is OK */ copy_triangulation(manifold, symmetric_triangulation); } if (get_filled_solution_type(manifold) == geometric_solution) { /* * Include an error check. */ new_upper_bound = symmetry_group_order(link_sym_grp); if (new_upper_bound < *upper_bound) *upper_bound = new_upper_bound; if (*upper_bound != *lower_bound) uFatalError("try_to_drill_curves", "symmetry_group_closed"); } free_symmetry_group(manifold_sym_grp); if (link_sym_grp != *symmetry_group) free_symmetry_group(link_sym_grp); free_triangulation(manifold); } static FuncResult drill_one_curve( Triangulation **manifold, MergedMultiLength *remaining_curves) { int i; int num_curves; DualOneSkeletonCurve **the_curves; int desired_index; Complex filled_length; Triangulation *new_manifold; int count; /* * See what curves are drillable. */ dual_curves(*manifold, MAX_DUAL_CURVE_LENGTH, &num_curves, &the_curves); if (num_curves == 0) return func_failed; desired_index = -1; for (i = 0; i < num_curves; i++) { get_dual_curve_info(the_curves[i], NULL, &filled_length, NULL); if ( fabs(remaining_curves->length - filled_length.real) < LENGTH_EPSILON && fabs(remaining_curves->torsion - fabs(filled_length.imag)) < TORSION_EPSILON && ( ( remaining_curves->pos_multiplicity > 0 && filled_length.imag > ZERO_TORSION_EPSILON ) || ( remaining_curves->neg_multiplicity > 0 && filled_length.imag < -ZERO_TORSION_EPSILON ) || ( remaining_curves->zero_multiplicity > 0 && fabs(filled_length.imag) < ZERO_TORSION_EPSILON ) ) ) { desired_index = i; break; } } if (desired_index == -1) { free_dual_curves(num_curves, the_curves); return func_failed; } new_manifold = drill_cusp(*manifold, the_curves[desired_index], get_triangulation_name(*manifold)); if (new_manifold == NULL) { free_dual_curves(num_curves, the_curves); return func_failed; } /* * Usually the complete solution will be geometric, even if * the filled solution is not. But occasionally we'll get * a new_manifold which didn't simplify sufficiently, and * we'll need to rattle it around to get a decent triangulation. */ count = MAX_RANDOMIZATIONS; while (--count >= 0 && get_complete_solution_type(new_manifold) != geometric_solution) randomize_triangulation(new_manifold); /* * Set the new Dehn filling coefficient to (1, 0) * to recover the closed manifold. */ set_cusp_info(new_manifold, get_num_cusps(new_manifold) - 1, FALSE, 1.0, 0.0); do_Dehn_filling(new_manifold); free_dual_curves(num_curves, the_curves); free_triangulation(*manifold); *manifold = new_manifold; new_manifold = NULL; if (filled_length.imag > ZERO_TORSION_EPSILON) remaining_curves->pos_multiplicity--; else if (filled_length.imag < -ZERO_TORSION_EPSILON) remaining_curves->neg_multiplicity--; else remaining_curves->zero_multiplicity--; remaining_curves->total_multiplicity--; return func_OK; } static FuncResult fill_first_cusp( Triangulation **manifold) { Triangulation *new_manifold; int count; Boolean fill_cusp[2] = {TRUE, FALSE}; if (get_num_cusps(*manifold) != 2) uFatalError("fill_first_cusp", "symmetry_group_closed"); new_manifold = fill_cusps(*manifold, fill_cusp, get_triangulation_name(*manifold), FALSE); if (new_manifold == NULL) return func_failed; /* this seems unlikely */ /* * Usually the complete solution will be geometric, even if * the filled solution is not. But occasionally we'll get * a new_manifold which didn't simplify sufficiently, and * we'll need to rattle it around to get a decent triangulation. */ count = MAX_RANDOMIZATIONS; while (--count >= 0 && get_complete_solution_type(new_manifold) != geometric_solution) randomize_triangulation(new_manifold); free_triangulation(*manifold); *manifold = new_manifold; new_manifold = NULL; return func_OK; } static FuncResult find_geometric_solution( Triangulation **manifold) { int i; Triangulation *copy; /* * If it ain't broke, don't fix it. */ if (get_filled_solution_type(*manifold) == geometric_solution) return func_OK; /* * Save a copy in case we make the triangulation even worse, * e.g. by converting a nongeometric_solution to a degenerate_solution. */ copy_triangulation(*manifold, ©); /* * Attempt to find a geometric_solution. */ for (i = 0; i < MAX_RETRIANGULATIONS; i++) { randomize_triangulation(*manifold); if (get_filled_solution_type(*manifold) == geometric_solution) { free_triangulation(copy); return func_OK; } /* * Every so often try canonizing as a further * stimulus to randomization. */ if ((i%4) == 3) { proto_canonize(*manifold); if (get_filled_solution_type(*manifold) == geometric_solution) { free_triangulation(copy); return func_OK; } } } /* * What have we got left? */ switch (get_filled_solution_type(*manifold)) { case geometric_solution: free_triangulation(copy); return func_OK; case nongeometric_solution: free_triangulation(copy); return func_failed; default: /* * If we don't have at least a nongeometric_solution, * restore the original triangulation. */ free_triangulation(*manifold); *manifold = copy; return func_failed; } } static FuncResult compute_symmetry_group_without_polyhedron( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group) { int lower_bound, num_curves, i; DualOneSkeletonCurve **the_curves; Complex prev_length, filled_length; Triangulation *copy; /* * Without a polyhedron we can't get a length spectrum, * and without a length spectrum we can't know for sure * when we've found the whole symmetry group. */ *is_full_group = FALSE; /* * compute_closed_symmetry_group() will have already computed * the symmetry subgroup preserving the current core geodesic. * (compute_closed_symmetry_group() also checks that we have * precisely one cusp.) */ if (*symmetry_group == NULL) uFatalError("compute_symmetry_group_without_polyhedron", "symmetry_group_closed"); /* * Initialize a (possible trivial) lower_bound on the order * of the full symmetry group. */ lower_bound = symmetry_group_order(*symmetry_group); /* * What curves are available in the 1-skeleton? */ dual_curves(manifold, MAX_DUAL_CURVE_LENGTH, &num_curves, &the_curves); prev_length = Zero; for (i = 0; i < num_curves; i++) { /* * Get the filled_length of the_curves[i]. */ get_dual_curve_info(the_curves[i], NULL, &filled_length, NULL); /* * Work with the absolute value of the torsion. */ filled_length.imag = fabs(filled_length.imag); /* * Skip lengths which appear equal a previous length. * (Note: dual_curves() first sorts by filled_length, * then complete_length.) */ if (fabs(filled_length.real - prev_length.real) < LENGTH_EPSILON && fabs(filled_length.imag - prev_length.imag) < TORSION_EPSILON) continue; else prev_length = filled_length; /* * Let's work on a copy, and leave the original_manifold untouched. */ copy_triangulation(manifold, ©); /* * Proceed as in try_to_drill_curves(), but with the realization * that we don't know how many curves we have of each given length. */ try_to_drill_unknown_curves( ©, filled_length, &lower_bound, symmetry_group, symmetric_triangulation); /* * Free the copy. */ free_triangulation(copy); } free_dual_curves(num_curves, the_curves); return func_OK; } static void try_to_drill_unknown_curves( Triangulation **manifold, Complex desired_length, int *lower_bound, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation) { double old_volume; MergedMultiLength the_desired_curves; int singularity_index; Complex core_length; SymmetryGroup *manifold_sym_grp = NULL, *link_sym_grp = NULL; /* * We want to drill as many curves as possible whose length * is desired_length.real and whose torsion has absolute value * desired_length.imag. We don't know how many curves of this * complex length to expect, so we won't know when to stop. * (We stop when we can't find another such curve to drill, or * a drilling fails.) We compute the symmetry group at each * step, since drilling additional curves may sometimes decrease * the size of the symmetry subgroup. */ /* * As a guard against creating weird triangulations, * do an "unnecessary" error check. */ old_volume = volume(*manifold, NULL); /* * Mock up a MergedMultiLength to represent the desired_length * we want to drill. (desired_length.imag will always be nonnegative.) */ the_desired_curves.length = desired_length.real; the_desired_curves.torsion = desired_length.imag; the_desired_curves.pos_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.neg_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.zero_multiplicity = INFINITE_MULTIPLICITY; the_desired_curves.total_multiplicity = INFINITE_MULTIPLICITY; /* * The current core geodesic may or may not have the desired length. * If it doesn't, replace it with a new one which does. * Note that the absolute value of the torsion might be correct, * but the sign of the torsion might have been "suppressed" above. */ core_geodesic(*manifold, 0, &singularity_index, &core_length, NULL); if (fabs(desired_length.real - core_length.real ) > LENGTH_EPSILON || fabs(desired_length.imag - fabs(core_length.imag)) > TORSION_EPSILON) { /* * Try to drill a curve of the desired_length. */ if (drill_one_curve(manifold, &the_desired_curves) == func_failed || fill_first_cusp(manifold) == func_failed) return; } /* * At this point we've got one curve of the desired_length drilled. * Compute its symmetry subgroup, try to drill another curve, . . . * until we can no longer drill. */ do { /* * Don't mess with anything worse than a nongeometric_solution. */ if (get_filled_solution_type(*manifold) != geometric_solution && get_filled_solution_type(*manifold) != nongeometric_solution) break; /* * Make sure the volume is plausible, as a further guard against * weird solutions. (The symmetry subgroup would still be valid, * but we certainly don't want to proceed further.) */ if (fabs(volume(*manifold, NULL) - old_volume) > VOLUME_ERROR_EPSILON) break; /* * Compute the symmetry subgroup. */ if (compute_cusped_symmetry_group(*manifold, &manifold_sym_grp, &link_sym_grp) == func_failed) break; /* * Have we improved the lower bound? */ if (symmetry_group_order(link_sym_grp) > *lower_bound) { *lower_bound = symmetry_group_order(link_sym_grp); free_symmetry_group(*symmetry_group); /* NULL is OK */ *symmetry_group = link_sym_grp; free_triangulation(*symmetric_triangulation); /* NULL is OK */ copy_triangulation(*manifold, symmetric_triangulation); } free_symmetry_group(manifold_sym_grp); if (link_sym_grp != *symmetry_group) free_symmetry_group(link_sym_grp); manifold_sym_grp = NULL; link_sym_grp = NULL; } while (drill_one_curve(manifold, &the_desired_curves) == func_OK); } snappea-3.0d3/SnapPeaKernel/code/symmetry_group_cusped.c0100444000175000017500000012062207037711254021512 0ustar babbab/* * symmetry_group_cusped.c * * This file provides the following functions: * * FuncResult compute_cusped_symmetry_group( * Triangulation *manifold, * SymmetryGroup **symmetry_group_of_manifold, * SymmetryGroup **symmetry_group_of_link); * * Computes the symmetry group of the manifold and also the symmetry * group of the corresponding link (defined below). * * Assumes *manifold is a cusped manifold, with all cusps complete. * To compute the symmetry group of a more general manifold, see symmetry_group.c. * * compute_cusped_symmetry_group() returns func_OK if its call to * compute_cusped_isometries() can get a canonical decomposition * for the manifold, and func_failed if it can't (e.g. because the manifold * lacks a hyperbolic structure). * * The "correspdoning link" lies in the space obtained by doing * meridional Dehn fillings on all cusps. The link itself consists * of the core curves of the filled-in solid tori and/or Klein bottles. * In the special case of a link complement in the 3-sphere, this * defintion leads to the usual notion of the symmetry group of a link. * * void recognize_group(SymmetryGroup *the_group); * * compute_cusped_symmetry_group() calls recognize_group() to attempt * to identify the SymmetryGroup. recognize_group() is, * in spirit, a local function, but it's externally visible * to allow communication with the recursive function * is_group_direct_product() in direct_product.c. * * I'd like to thank Pat Callahan for the many useful ideas * he contributed to SnapPea's symmetry group module. */ #include "kernel.h" /* * The PrimaryPart data structure represents the * p-primary part of an abelian group G. */ typedef struct { /* * What is the prime p? */ int p; /* * How many elements are in the p-primary part of G? */ int num_elements; /* * Here's an array giving the elements in the p-primary * part of G. */ int *element; /* * How many generators does this p-primary part have? */ int num_generators; /* * List the generators in order of decreasing order, * e.g. first a generator for Z/8, then Z/4, then another * Z/4, then Z/2. */ int *generator; } PrimaryPart; static void symmetry_list_to_group(SymmetryList **symmetry_list, SymmetryGroup **symmetry_group); static void put_identity_first(SymmetryGroup *the_group); static int find_index_of_identity(SymmetryList *the_symmetry_list); static Boolean is_identity(Symmetry *the_symmetry); static void compute_multiplication_table(SymmetryGroup *the_group); static void compose_symmetries(Symmetry *symmetry1, Symmetry *symmetry0, Symmetry *product); static int find_index(SymmetryList *the_symmetry_list, Symmetry *the_symmetry); static Boolean same_symmetry(Symmetry *symmetry0, Symmetry *symmetry1); static Boolean is_group_abelian(SymmetryGroup *the_group); static Boolean is_group_cyclic(SymmetryGroup *the_group); static Boolean is_group_dihedral(SymmetryGroup *the_group); static Boolean f_is_a_power_of_r(SymmetryGroup *the_group, int f, int r); static void set_cyclic_ordering(SymmetryGroup *the_group, int a_generator); static void attach_cyclic_description(SymmetryGroup *the_group); static void set_dihedral_ordering(SymmetryGroup *the_group, int f, int r); static void reorder_elements(SymmetryGroup *the_group, int *old_from_new); static void reorder_symmetries(SymmetryList *the_symmetry_list, int *old_from_new); static void reorder_product(SymmetryGroup *the_group, int *old_from_new, int *new_from_old); static void reorder_orders(SymmetryGroup *the_group, int *old_from_new); static void reorder_inverses(SymmetryGroup *the_group, int *old_from_new, int *new_from_old); static void describe_abelian_group(SymmetryGroup *the_group); static void find_basis(SymmetryGroup *the_group, int *num_generators, int **the_generators); static Boolean prime_power(int p, int n); static void primary_part_generators(SymmetryGroup *the_group, PrimaryPart *primary_part); static void find_lexicographic_ordering(SymmetryGroup *the_group, int num_generators, int the_generators[], int **desired_ordering); static void attach_abelian_description(SymmetryGroup *the_group, int num_generators, int the_generators[]); FuncResult compute_cusped_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link) { SymmetryList *symmetry_list_of_manifold, *symmetry_list_of_link; /* * There are two ways this function may be called: * * (1) compute_symmetry_group() may call it to compute the * symmetry group of a cusped manifold. * * (2) compute_closed_symmetry_group() may call it as part * of the algorithm to compute a symmetry group of a * closed manifold. * * Either way, we ignore the Dehn filling coefficients, * and use only the complete hyperbolic structure. */ /* * Make sure the variables used to pass back our results * are all initially empty. */ if (*symmetry_group_of_manifold != NULL || *symmetry_group_of_link != NULL) uFatalError("compute_cusped_symmetry_group", "symmetry_group"); /* * Symmetries are just Isometries from a manifold to itself. */ if (compute_cusped_isometries( manifold, manifold, &symmetry_list_of_manifold, &symmetry_list_of_link) == func_failed) { *symmetry_group_of_manifold = NULL; *symmetry_group_of_link = NULL; return func_failed; } /* * Convert the SymmetryLists to SymmetryGroups. */ symmetry_list_to_group(&symmetry_list_of_manifold, symmetry_group_of_manifold); symmetry_list_to_group(&symmetry_list_of_link, symmetry_group_of_link); return func_OK; } /* * symmetry_list_to_group() converts a SymmetryList to a SymmetryGroup. * The SymmetryGroup subsumes the SymmetryList, and adds additional * information, namely a mulitplication table for the group, the orders * and inverses of the elements, and whether the group is abelian, cyclic, * dihedral and/or a spherical triangle group. * * IMPORTANT: Because the SymmetryGroup subsumes the SymmetryList, * symmetry_list_to_group() sets the pointer symmetry_list to NULL. * In particular, you don't need to free the SymmetryList, just the * SymmetryGroup. */ static void symmetry_list_to_group( SymmetryList **symmetry_list, SymmetryGroup **symmetry_group) { SymmetryGroup *the_group; /* * Allocate the SymmetryGroup. */ (*symmetry_group) = NEW_STRUCT(SymmetryGroup); /* * Give it a local name to avoid the double indirection. */ the_group = *symmetry_group; /* * Copy the pointer to the SymmetryList. */ the_group->symmetry_list = *symmetry_list; /* * Clear the original pointer, since the_group has now * taken responsibility for the SymmetryList. */ *symmetry_list = NULL; /* * Set the order of the group. */ the_group->order = the_group->symmetry_list->num_isometries; /* * Make sure the group isn't empty. */ if (the_group->order == 0) uFatalError("symmetry_list_to_group", "symmetry_group"); /* * Make sure the identity is element 0. */ put_identity_first(the_group); /* * Compute the multiplication table for the group. */ compute_multiplication_table(the_group); /* * Use the multiplication table to compute the order of each element. */ compute_orders_of_elements(the_group); /* * Use the multiplication table to compute the inverse of each element. */ compute_inverses(the_group); /* * Attempt to recognize the_group. */ recognize_group(the_group); } void recognize_group( SymmetryGroup *the_group) { /* * We assume the_group's order, symmetry_list, product[][], * order_of_element[] and inverse[] fields have been set. * * 96/11/30 It's OK to have symmetry_list == NULL. */ /* * Set the abelian_description to NULL. * This will be overridden if the group is abelian. */ the_group->abelian_description = NULL; /* * Check whether the group is abelian, cyclic, dihedral, * a spherical triangle group, or [eventually] a symmetric * or alternating group. * * If the group is cyclic or dihedral, the elements will be * reordered in a natural way. */ the_group->is_abelian = is_group_abelian(the_group); the_group->is_cyclic = is_group_cyclic(the_group); the_group->is_dihedral = is_group_dihedral(the_group); the_group->is_polyhedral = is_group_polyhedral(the_group); the_group->is_S5 = is_group_S5(the_group); /* * If the group is abelian but not cyclic, we want to * * (1) figure out which group it is, and * * (2) order the elements in a natural way. * * (If the group is cyclic, the function is_group_cyclic() will have * already attached an abelian_description and reordered the elements.) */ if (the_group->is_abelian == TRUE && the_group->is_cyclic == FALSE) describe_abelian_group(the_group); /* * If the_group hasn't yet been identified, check whether it's a * nontrivial direct product. is_group_direct_product() will set * the is_direct_product and factor[] field correctly, whether or * not the_group is a nontrivial direct product. */ if (the_group->is_cyclic == FALSE && the_group->is_dihedral == FALSE && the_group->is_polyhedral == FALSE && the_group->is_S5 == FALSE && the_group->is_abelian == FALSE) the_group->is_direct_product = is_group_direct_product(the_group); else { the_group->is_direct_product = FALSE; the_group->factor[0] = NULL; the_group->factor[1] = NULL; } } static void put_identity_first( SymmetryGroup *the_group) { int index_of_identity; Symmetry **the_symmetries, *temp; index_of_identity = find_index_of_identity(the_group->symmetry_list); if (index_of_identity != 0) { the_symmetries = the_group->symmetry_list->isometry; temp = the_symmetries[0]; the_symmetries[0] = the_symmetries[index_of_identity]; the_symmetries[index_of_identity] = temp; } } static int find_index_of_identity( SymmetryList *the_symmetry_list) { int i; for (i = 0; i < the_symmetry_list->num_isometries; i++) if (is_identity(the_symmetry_list->isometry[i])) return i; /* * The identity was not found on the list. Uh oh. */ uFatalError("find_index_of_identity", "symmetry_group"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return 0; } static Boolean is_identity( Symmetry *the_symmetry) { int i; for (i = 0; i < the_symmetry->num_tetrahedra; i++) if (the_symmetry->tet_image[i] != i || the_symmetry->tet_map[i] != IDENTITY_PERMUTATION) return FALSE; return TRUE; } static void compute_multiplication_table( SymmetryGroup *the_group) { int i, j; Symmetry *the_product; int num_tetrahedra; /* * Allocate space for the multiplication table. */ the_group->product = NEW_ARRAY(the_group->order, int *); for (i = 0; i < the_group->order; i++) the_group->product[i] = NEW_ARRAY(the_group->order, int); /* * Note how many Tetrahedra underlie each Symmetry. */ num_tetrahedra = the_group->symmetry_list->isometry[0]->num_tetrahedra; /* * Allocate space to temporarily hold the product of two * symmetries. */ the_product = NEW_STRUCT(Symmetry); the_product->tet_image = NEW_ARRAY(num_tetrahedra, int); the_product->tet_map = NEW_ARRAY(num_tetrahedra, Permutation); /* * For each pair of elements . . . */ for (i = 0; i < the_group->order; i++) for (j = 0; j < the_group->order; j++) { /* * . . . compute their product . . . */ compose_symmetries( the_group->symmetry_list->isometry[i], the_group->symmetry_list->isometry[j], the_product); /* * . . . and write its index into the multiplication table. */ the_group->product[i][j] = find_index(the_group->symmetry_list, the_product); } /* * Free the temporary storage. */ my_free(the_product->tet_image); my_free(the_product->tet_map); my_free(the_product); } static void compose_symmetries( Symmetry *symmetry1, Symmetry *symmetry0, Symmetry *product) { int i; product->num_tetrahedra = symmetry0->num_tetrahedra; for (i = 0; i < product->num_tetrahedra; i++) { product->tet_image[i] = symmetry1->tet_image[symmetry0->tet_image[i]]; product->tet_map[i] = compose_permutations( symmetry1->tet_map[symmetry0->tet_image[i]], symmetry0->tet_map[i]); } } /* * find_index() finds the position of the_symmetry on the_symmetry_list. * If the_symmetry does not occur on the_symmetry_list, it calls * uFatalError() to exit. */ static int find_index( SymmetryList *the_symmetry_list, Symmetry *the_symmetry) { int i; for (i = 0; i < the_symmetry_list->num_isometries; i++) if (same_symmetry(the_symmetry, the_symmetry_list->isometry[i])) return i; /* * the_symmetry was not found on the_symmetry_list. */ uFatalError("find_index", "symmetry_group"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return 0; } /* * same_symmetry() returns TRUE if symmetry0 and symmetry1 are the same, * FALSE otherwise. */ static Boolean same_symmetry( Symmetry *symmetry0, Symmetry *symmetry1) { int i; for (i = 0; i < symmetry0->num_tetrahedra; i++) if (symmetry0->tet_image[i] != symmetry1->tet_image[i] || symmetry0->tet_map[i] != symmetry1->tet_map[i]) return FALSE; return TRUE; } void compute_orders_of_elements( SymmetryGroup *the_group) { int i; int running_product; /* * Allocate the array which will hold the orders of the elements. */ the_group->order_of_element = NEW_ARRAY(the_group->order, int); /* * We'll use the fact that put_identity_first() has put the * identity in position 0. */ /* * Compute the order of each element. */ for (i = 0; i < the_group->order; i++) { the_group->order_of_element[i] = 0; running_product = 0; do { running_product = the_group->product[i][running_product]; the_group->order_of_element[i]++; } while (running_product != 0); } } void compute_inverses( SymmetryGroup *the_group) { int i, j; /* * Allocate the array which will hold the inverses of the elements. */ the_group->inverse = NEW_ARRAY(the_group->order, int); /* * Compute the inverse of each element. */ for (i = 0; i < the_group->order; i++) { for (j = 0; j < the_group->order; j++) if (the_group->product[i][j] == 0) { the_group->inverse[i] = j; break; } if (j == the_group->order) /* no inverse was found */ uFatalError("compute_inverses", "symmetry_group"); } /* * Just for good measure, let's make sure the inverses are consistent. */ for (i = 0; i < the_group->order; i++) if (the_group->inverse[the_group->inverse[i]] != i) uFatalError("compute_inverses", "symmetry_group"); } static Boolean is_group_abelian( SymmetryGroup *the_group) { int i, j; for (i = 0; i < the_group->order; i++) for (j = i + 1; j < the_group->order; j++) if (the_group->product[i][j] != the_group->product[j][i]) return FALSE; return TRUE; } static Boolean is_group_cyclic( SymmetryGroup *the_group) { int i; for (i = 0; i < the_group->order; i++) if (the_group->order_of_element[i] == the_group->order) { set_cyclic_ordering(the_group, i); attach_cyclic_description(the_group); return TRUE; } return FALSE; } static Boolean is_group_dihedral( SymmetryGroup *the_group) { /* * Definition. The dihedral group of order 2n, denoted Dn, * is the group of symmetries of a regular n-gon. * * Proposition 1. The dihedral group Dn has the presentation * * { F, R | F^2 = 1, R^n = 1, RF = FR^-1} * * Notes: * * (1) Intuitively, F is a flip about some diagonal, * and R is a 2pi/n rotation. The relation F^2 = 1 * says that two consecutive flips take you back to * where you started. The relation R^n = 1 says that * n rotations of 2pi/n take you back to where you * started. The relation RF = FR^-1 says that a * flip followed by a counterclockwise rotation * equals a clockwise rotation followed by a flip. * * (2) RF = FR^-1 iff F(RF)F = F(FR^-1)F iff FR = R^-1F, * so it doesn't matter which 2pi/n rotation we call * R, and which we call R^-1. * * Proof of Proposition 1. Consider the map from the free * group on {F, R} to Dn defined by sending F to a flip * about some arbitrary but fixed diagonal, and R to a * 2pi/n rotation. (If n is odd the diagonal will run from * the midpoint of an edge to the opposite vertex. If n is * even it may run from vertex to vertex or edge to edge.) * Clearly this map is onto; we must show that the words * F^2, R^n, and RFRF generate the kernel. It's trivial to * check that the three words lie in the kernel; we need * to prove that they generate it. Imagine some arbitrary * word FRRFRFR^-1FRR....R^-3F in the kernel. Use the relation * RF = FR^-1 to push all the F's to the left and all the R's * to the right, so the word ends up in the form (F^a)(R^b). * Use the other two relations to insure that 0 <= a < 2 and * 0 <= b < n. The only way this can map to the trivial symmetry * of an n-gon is to have a = b = 0, which proves that the * relations F^2 = 1, R^n = 1, and RF = FR^-1 generate the * kernel. Q.E.D. * * Notation: Let G denote the_group. That is, what's called * the_group in the is_group_dihedral() function definition * will be called G in this documentation. * * We want an algorithm to check whether G is a dihedral group. * * Proposition 2. A group G of order 2n is isomorphic to the * dihedral group Dn iff G contains elements F and R such that * * (1) F has order 2, * * (2) R has order n, * * (3) RF = FR^-1, and * * (4) F is not a power of R. * * Proof. * ( => ) Trivial. * ( <= ) Assume G contains elements F and R satisfying the * above conditions. We must construct an isomorphism from * Dn = { f, r | f^2 = 1, r^n = 1, rf = fr^-1} to G. * Let phi be the map from Dn to G which sends f to F and r to R. * By conditions (1)-(3), phi is a well defined homomorphism. * Because Dn and G have the same order, phi will be bijective * iff it is surjective. To see that it is surjective, note * that the subgroup of G generated by R divides G into two * cosets, and condition (4) implies that F does not lie in the * coset containing the identity. It follows that both cosets * are contained in the image of phi, hence phi is surjective. * The above reasoning now implies that phi is an isomorphism. * Q.E.D. */ /* * Our algorithm is to check the conditions of Proposition 2. */ int n, f, r; /* * Does the_group have even order? * If not, it can't possible be dihedral. */ if (the_group->order % 2 == 1) return FALSE; /* * Let the order of the group be 2n. */ n = the_group->order / 2; /* * Consider all candidates for r. */ for (r = 0; r < the_group->order; r++) /* * Proceed iff r has order n. */ if (the_group->order_of_element[r] == n) /* * Consider all candidates for f. */ for (f = 0; f < the_group->order; f++) /* * Proceed iff f has order 2. */ if (the_group->order_of_element[f] == 2) /* * If * rf == fr^-1 * and * f is not a power of r * then * we've satisfied the conditions * of Proposition 2. */ if ( the_group->product[r][f] == the_group->product[f][the_group->inverse[r]] && f_is_a_power_of_r(the_group, f, r) == FALSE ) { set_dihedral_ordering(the_group, f, r); return TRUE; } /* * There are no elements r and f satisfying the conditions * of Proposition 2. Therefore the_group is not dihedral. */ return FALSE; } static Boolean f_is_a_power_of_r( SymmetryGroup *the_group, int f, int r) { int n, exponent, running_product; /* * Let the order of the group be 2n. */ n = the_group->order / 2; /* * We use the fact that the identity Symmetry is element 0. */ for ( exponent = 0, running_product = 0; exponent < n; exponent++, running_product = the_group->product[running_product][r]) if (running_product == f) return TRUE; return FALSE; } /* * set_cyclic_ordering() reorders the elements of the_group * as consecutive powers of a_generator. */ static void set_cyclic_ordering( SymmetryGroup *the_group, int a_generator) { int *desired_ordering, i, running_product; /* * Allocate space for an array which will temporarily * hold the desired ordering. */ desired_ordering = NEW_ARRAY(the_group->order, int); /* * Compute a running_product, which will equal the * zeroth, first, second, etc. power of a_generator. * Note that the identity symmetry is number 0. */ for ( i = 0, running_product = 0; i < the_group->order; i++, running_product = the_group->product[running_product][a_generator]) /* * Set desired_ordering[i] equal to the element which * is the i-th power of the generator. */ desired_ordering[i] = running_product; /* * Reorder the elements of the_group according to the desired_ordering. */ reorder_elements(the_group, desired_ordering); /* * Free the temporary storage. */ my_free(desired_ordering); } static void attach_cyclic_description( SymmetryGroup *the_group) { /* * The function which called attach_cyclic_description() just * discovered that the_group is cyclic. We must attach an * AbelianGroup structure to record this fact. */ the_group->abelian_description = NEW_STRUCT(AbelianGroup); /* * Handle the trivial group separately. * JRW 2000/1/14 */ if (the_group->order > 1) { the_group->abelian_description->num_torsion_coefficients = 1; the_group->abelian_description->torsion_coefficients = NEW_ARRAY(1, long int); the_group->abelian_description->torsion_coefficients[0] = the_group->order; } else { the_group->abelian_description->num_torsion_coefficients = 0; the_group->abelian_description->torsion_coefficients = NULL; } } /* * set_dihedral_ordering() reorders the elements of the_group * as I, R, R^2, . . . , R^(n-1), F, FR, FR^2, . . . , FR^(n-1). */ static void set_dihedral_ordering( SymmetryGroup *the_group, int f, int r) { int *desired_ordering, n, i, running_product; /* * Let the order of the group be 2n. */ n = the_group->order / 2; /* * Allocate space for an array which will temporarily * hold the desired ordering. */ desired_ordering = NEW_ARRAY(the_group->order, int); /* * Compute successive powers of r. */ for ( i = 0, running_product = 0; i < n; i++, running_product = the_group->product[running_product][r]) { desired_ordering[i] = running_product; /* R^i */ desired_ordering[i + n] = the_group->product[f][running_product]; /* FR^i */ } /* * Reorder the elements of the_group according to the desired_ordering. */ reorder_elements(the_group, desired_ordering); /* * Free the temporary storage. */ my_free(desired_ordering); } /* * reorder_elements() reorders the elements of the_group * according the prescribed ordering. */ static void reorder_elements( SymmetryGroup *the_group, int *old_from_new) { int *new_from_old, i; /* * The array old_from_new[] (which the functions set_cyclic_ordering() * and set_dihedral_ordering() refer to as desired_ordering[]) * gives a group element's old index in terms of its new (desired) * index. The array new_from_old[] does the opposite. */ new_from_old = NEW_ARRAY(the_group->order, int); for (i = 0; i < the_group->order; i++) new_from_old[old_from_new[i]] = i; reorder_symmetries (the_group->symmetry_list, old_from_new); reorder_product (the_group, old_from_new, new_from_old); reorder_orders (the_group, old_from_new); reorder_inverses (the_group, old_from_new, new_from_old); my_free(new_from_old); } static void reorder_symmetries( SymmetryList *the_symmetry_list, int *old_from_new) { Symmetry **old_symmetry_list; int i; /* * 96/11/30 If the group doesn't have a SymmetryList, do nothing. */ if (the_symmetry_list == NULL) return; /* * Allocate space for a copy of the array of pointers to * the Symmetries . . . */ old_symmetry_list = NEW_ARRAY(the_symmetry_list->num_isometries, Symmetry *); /* * . . . and copy in the pointers to the Symmetries * in their original order. */ for (i = 0; i < the_symmetry_list->num_isometries; i++) old_symmetry_list[i] = the_symmetry_list->isometry[i]; /* * Rewrite the_symmetry_list->isometry[] relative to the new * indexing system. */ for (i = 0; i < the_symmetry_list->num_isometries; i++) the_symmetry_list->isometry[i] = old_symmetry_list[old_from_new[i]]; /* * Free the old_symmetry_list[]. */ my_free(old_symmetry_list); } static void reorder_product( SymmetryGroup *the_group, int *old_from_new, int *new_from_old) { int **old_product, i, j; /* * Allocate space for a copy of the group multiplication table . . . */ old_product = NEW_ARRAY(the_group->order, int *); for (i = 0; i < the_group->order; i++) old_product[i] = NEW_ARRAY(the_group->order, int); /* * . . . and fill it in relative to the old numbering system. */ for (i = 0; i < the_group->order; i++) for (j = 0; j < the_group->order; j++) old_product[i][j] = the_group->product[i][j]; /* * Rewrite the_group->product[][] relative to the new numbering system. */ for (i = 0; i < the_group->order; i++) for (j = 0; j < the_group->order; j++) the_group->product[i][j] = new_from_old[old_product[old_from_new[i]][old_from_new[j]]]; /* * Free the copy of the multiplication table. */ for (i = 0; i < the_group->order; i++) my_free(old_product[i]); my_free(old_product); } static void reorder_orders( SymmetryGroup *the_group, int *old_from_new) { int *order_of_old_element, i; /* * Allocate the array order_of_old_element[] . . . */ order_of_old_element = NEW_ARRAY(the_group->order, int); /* * . . . and copy in the orders of the elements indexed relative * to the old numbering system. */ for (i = 0; i < the_group->order; i++) order_of_old_element[i] = the_group->order_of_element[i]; /* * Rewrite the_group->order_of_element[] indexed relative to * the new numbering system. */ for (i = 0; i < the_group->order; i++) the_group->order_of_element[i] = order_of_old_element[old_from_new[i]]; /* * Free the order_of_old_element[] array. */ my_free(order_of_old_element); } static void reorder_inverses( SymmetryGroup *the_group, int *old_from_new, int *new_from_old) { int *old_inverse, i; /* * Allocate the array old_inverse . . . */ old_inverse = NEW_ARRAY(the_group->order, int); /* * . . . and copy in the inverses as expressed * relative to the old numbering system. */ for (i = 0; i < the_group->order; i++) old_inverse[i] = the_group->inverse[i]; /* * Rewrite the array the_group->inverse[] * relative to the new numbering system. */ for (i = 0; i < the_group->order; i++) the_group->inverse[i] = new_from_old[old_inverse[old_from_new[i]]]; /* * Free the old_inverse[] array. */ my_free(old_inverse); } static void describe_abelian_group( SymmetryGroup *the_group) { int num_generators, *the_generators, *desired_ordering; /* * Throughout this documentation, we refer to the_group as G. * * By a standard theorem in finite group theory, G has the form * * Z/n0 + Z/n1 + ... + Z/nk where n0 | n1 | ... | nk. * * We first call a function which finds generators * * a0 = (1, 0, 0, ... , 0, 0) * a1 = (0, 1, 0, ... , 0, 0) * ... * ak = (0, 0, 0, ... , 0, 1) * * The generators will be reported in descending order * ak, ... , a0. */ find_basis(the_group, &num_generators, &the_generators); /* * Attach an abelian_description. */ attach_abelian_description(the_group, num_generators, the_generators); /* * We now call a function which computes a lexicographic * ordering for the elements. If, for example, n0 = 2, * n1 = 2 and n2 = 6, the lexicographic ordering will be * * (0, 0, 0) * (0, 0, 1) * (0, 0, 2) * (0, 0, 3) * (0, 0, 4) * (0, 0, 5) * (0, 1, 0) * (0, 1, 1) * (0, 1, 2) * (0, 1, 3) * (0, 1, 4) * (0, 1, 5) * (1, 0, 0) * (1, 0, 1) * ... * (1, 1, 5) */ find_lexicographic_ordering(the_group, num_generators, the_generators, &desired_ordering); /* * Reorder the group elements according to the desired_ordering. */ reorder_elements(the_group, desired_ordering); /* * Free the local arrays. */ my_free(the_generators); my_free(desired_ordering); } static void find_basis( SymmetryGroup *the_group, int *num_generators, int **the_generators) { int num_primary_parts; PrimaryPart *primary_part; int p, n; int i, j; /* * Think of the_group, which we'll call G, in its most factored form. * For example, G might be * * Z/2 + Z/2 + Z/4 + Z/8 + Z/3 + Z/9 + Z/25 * * Definition. * Z/2 + Z/2 + Z/4 + Z/8 is called the 2-primary part of G, * Z/3 + Z/9 is called the 3-primary part of G, etc. * * Our plan is to find generators for each primary part separately, * and then combine them. */ /* * We don't know how many primary parts there'll be, but for * sure it'll be fewer than the number of element in the group. * So rather than fussing around, we'll allocate an array which * is much too big. */ primary_part = NEW_ARRAY(the_group->order, PrimaryPart); /* * Find the prime divisors of |G|. */ num_primary_parts = 0; for ( p = 2, n = the_group->order; n > 1; p++) if (n%p == 0) { /* * Allocate a primary part. */ primary_part[num_primary_parts++].p = p; /* * Divide all powers of p out of n. */ while (n%p == 0) n /= p; } /* * Find each primary part. */ for (i = 0; i < num_primary_parts; i++) { /* * Get set up. * We don't know how many elements we'll have, but * for sure not more than the order of the group. */ primary_part[i].num_elements = 0; primary_part[i].element = NEW_ARRAY(the_group->order, int); primary_part[i].generator = NEW_ARRAY(the_group->order, int); /* * Find which group elements have order a power of p. */ for (j = 0; j < the_group->order; j++) if (prime_power(primary_part[i].p, the_group->order_of_element[j])) primary_part[i].element[primary_part[i].num_elements++] = j; /* * Find the generators for this primary part. */ primary_part_generators(the_group, &primary_part[i]); } /* * Combine the generators for the primary parts into * generators for the complete group. For example, if * the primary parts are * * Z/8 + Z/4 + Z/2 * and * Z/3 + Z/3 * * then the first generator will be the product (sum) of * the generators for the Z/8 and the first Z/3, the second * generator will be the product of the generators of the Z/4 * and the second Z/3, and the third generator will be the * generator of the Z/2. */ /* * The number of generators for the group will be the maximum * number of generators for any primary part. */ *num_generators = 0; for (i = 0; i < num_primary_parts; i++) if (primary_part[i].num_generators > *num_generators) *num_generators = primary_part[i].num_generators; /* * Allocate memory for the generators for the group. */ *the_generators = NEW_ARRAY(*num_generators, int); /* * The i-th generator will be the product (sum) of all * i-th generators of the primary parts. */ for (i = 0; i < *num_generators; i++) { /* * Initialize the i-th generator to the identity. */ (*the_generators)[i] = 0; /* * Add in the i-th generator of each primary part which * has an i-th generator. */ for (j = 0; j < num_primary_parts; j++) if (i < primary_part[j].num_generators) (*the_generators)[i] = the_group->product [(*the_generators)[i]] [primary_part[j].generator[i]]; } /* * Free local memory. */ for (i = 0; i < num_primary_parts; i++) { my_free(primary_part[i].element); my_free(primary_part[i].generator); } my_free(primary_part); } /* * prime_power() returns TRUE if n is a power of p, * FALSE otherwise. */ static Boolean prime_power( int p, int n) { while (TRUE) { if (n == 1) return TRUE; if (n%p) return FALSE; n /= p; } } /* * primary_part_generators() takes a PrimaryPart whose fields p, * num_elements, and element[] have been filled in (and whose generator[] * array has been allocated) and computes a set of standard generators. */ static void primary_part_generators( SymmetryGroup *the_group, PrimaryPart *primary_part) { /* * The algorithm is more clearly explained in terms of an example, * without the burden of the general notation. * * We are working with a primary part which looks something like * * Z/8 + Z/4 + Z/4 + Z/2 * * and we want to find generators * * a3 = (1, 0, 0, 0) * a2 = (0, 1, 0, 0) * a1 = (0, 0, 1, 0) * a0 = (0, 0, 0, 1) * * We begin by finding an element of maximal order (order 8 in this * example). We may take the element of maximal order to be the * generator a3. This follows from the following two lemmas. * * Lemma 1. An element of maximal order must have a first component * which generates the Z/8 factor. That is, it must look like * (1, *, *, *), relative to some choice of generator for the Z/8 factor. * * Proof. If it looked like (0, *, *, *) or (2, *, *, *) it would have * order 4, not order 8. If the group had two Z/8 factors, then the * element of maximal order would have to have a component which * generates at least one of the factors, and we'd list that factor first. * * Lemma 2. The map * * (1, 0, 0, 0) -> (1, *, *, *) * (0, 1, 0, 0) -> (0, 1, 0, 0) * (0, 0, 1, 0) -> (0, 0, 1, 0) * ... * (0, 0, 0, 1) -> (0, 0, 0, 1) * * is an automorphism of the primary part. * * Proof. Each generator of order n goes to an element of order n, * so the map is a homomorphism. It's onto, so it's an isomorphism. * * Lemmas 1 and 2 together imply that an arbitrary element * of maximal order may be taken to be (1, 0, 0, 0). * * * We'll extend this approach to find the remaining generators. * * Let P be the primary part we are working with, and * let H be the subgroup of P generated by the generators we've * found so far. (Initially H = {0}.) * * Define the "coset order" of an element to be the order of its * coset in the quotient group P/H. * * Continuing with the above example, say we've found a3 and * are looking for a2. We choose a highest order element * whose coset order equals its regular order. The following * two lemmas show that this element may be taken to be a2. * * Lemma 1'. A highest order element whose coset order equals * its regular order is of the form (*, 1, *, *), relative to some * choice of generator for the Z/4 factor. * * Proof. The element (0, 1, 0, 0) has coset order = regular order = 4, * and no element has coset order 8, so clearly the given element * must have coset order = regular order = 4. If it were of the * form (*, 0or2, 0or2, *) it would have coset order 2, not four, * so either the second or third component must be a 1 or a 3. * After possibly interchanging the two Z/4 factors and/or choosing * a new generator for a Z/4 factor, we may assume our element is * of the form (*, 1, *, *). * * Lemma 2'. If the coset order of (*, 1, *, *) equals its * regular order, then the map * * (1, 0, 0, 0) -> (1, 0, 0, 0) * (0, 1, 0, 0) -> (*, 1, *, *) * (0, 0, 1, 0) -> (0, 0, 1, 0) * ... * (0, 0, 0, 1) -> (0, 0, 0, 1) * * is an automorphism of the primary part. * * Proof. Each generator of order n goes to an element of order n * (this is where we use coset order == regular order), * so the map is a homomorphism. It's onto, so it's an isomorphism. * Note that this automorphism doesn't affect our previously * chosen a3 = (1, 0, 0, 0). * * We continue in this fashion until all generators have been found. */ Boolean *belongs_to_H, *old_belongs_to_H; int i, size_of_H, *coset_order, *regular_order, running_product, max_order, new_generator, power_of_new_generator; /* * Initially we have no generators. */ primary_part->num_generators = 0; /* * The array belongs_to_H keeps track of which elements * are in the subgroup H. That is, belongs_to_H[i] is * true iff the element i belongs to H (the index i refers * to the elements index in the full group G, NOT its * array index in the PrimaryPart). */ belongs_to_H = NEW_ARRAY(the_group->order, Boolean); old_belongs_to_H = NEW_ARRAY(the_group->order, Boolean); /* * Initially only the identity belongs to H. */ belongs_to_H[0] = TRUE; for (i = 1; i < the_group->order; i++) belongs_to_H[i] = FALSE; /* * size_of_H keeps track of how many elements are in H. * Initially we've got only the identity. */ size_of_H = 1; /* * We'll need to keep track of both the coset and * regular orders of each element of the primary part. * Unlike belongs_to_H[], coset_order[] and regular_order[] * are indexed relative to the primary part. */ coset_order = NEW_ARRAY(primary_part->num_elements, int); regular_order = NEW_ARRAY(primary_part->num_elements, int); /* * Record the regular_orders. */ for (i = 0; i < primary_part->num_elements; i++) regular_order[i] = the_group->order_of_element[primary_part->element[i]]; /* * We'll keep looking for new generators until H fills * up the whole primary part. */ while (size_of_H < primary_part->num_elements) { /* * Compute the coset_orders. */ for (i = 0; i < primary_part->num_elements; i++) { coset_order[i] = 1; running_product = primary_part->element[i]; while (belongs_to_H[running_product] == FALSE) { running_product = the_group->product [running_product] [primary_part->element[i]]; coset_order[i]++; } } /* * Find the highest order element whose order * equals its coset order. */ max_order = 0; for (i = 0; i < primary_part->num_elements; i++) { if (coset_order[i] == regular_order[i] && coset_order[i] > max_order) { max_order = coset_order[i]; new_generator = primary_part->element[i]; } } /* * Just for an unnecessary error check . . . */ if (max_order < 2) uFatalError("primary_part_generators", "symmetry_group"); /* * Record the new generator. */ primary_part->generator[primary_part->num_generators++] = new_generator; /* * Update H. */ for (i = 0; i < the_group->order; i++) old_belongs_to_H[i] = belongs_to_H[i]; power_of_new_generator = 0; do { for (i = 0; i < the_group->order; i++) if (old_belongs_to_H[i] == TRUE) belongs_to_H[the_group->product[i][power_of_new_generator]] = TRUE; power_of_new_generator = the_group->product[power_of_new_generator][new_generator]; } while (power_of_new_generator != 0); size_of_H *= max_order; } my_free(belongs_to_H); my_free(old_belongs_to_H); my_free(coset_order); my_free(regular_order); } static void find_lexicographic_ordering( SymmetryGroup *the_group, int num_generators, int the_generators[], int **desired_ordering) { int i, j, count, old_count, power_of_generator; /* * Allocate space for the desired_ordering. */ *desired_ordering = NEW_ARRAY(the_group->order, int); /* * The identity goes first. */ (*desired_ordering)[0] = 0; /* * We've got one element in the array. */ count = 1; /* * For each generator . . . */ for (i = 0; i < num_generators; i++) { old_count = count; /* * . . . consider each of its nontrivial powers . . . */ for ( power_of_generator = the_generators[i]; power_of_generator != 0; power_of_generator = the_group->product [power_of_generator] [the_generators[i]] ) /* * . . . and multiply all the old elements on the * desired_ordering array by each power_of_generator. */ for (j = 0; j < old_count; j++) (*desired_ordering)[count++] = the_group->product [(*desired_ordering)[j]][power_of_generator]; } } static void attach_abelian_description( SymmetryGroup *the_group, int num_generators, int the_generators[]) { int i; the_group->abelian_description = NEW_STRUCT(AbelianGroup); the_group->abelian_description->num_torsion_coefficients = num_generators; the_group->abelian_description->torsion_coefficients = NEW_ARRAY(num_generators, long int); /* * the_generators[] lists the generators in descending order, * so reverse that order here. */ for (i = 0; i < num_generators; i++) the_group->abelian_description->torsion_coefficients[(num_generators - 1) - i] = the_group->order_of_element[the_generators[i]]; } snappea-3.0d3/SnapPeaKernel/code/symmetry_group_info.c0100444000175000017500000014420607203634451021164 0ustar babbab/* * symmetry_group_info.c * * The SymmetryGroup data structure is private to the kernel. * The UI accesses its fields via the following function calls. * * Boolean symmetry_group_is_abelian( SymmetryGroup *symmetry_group, * AbelianGroup **abelian_description); * * Says whether the SymmetryGroup is abelian. If it is, it sets * abelian_description to point to the SymmetryGroup's abelian_description. * It points to the original, not a copy, so please don't modify it. * * Boolean symmetry_group_is_dihedral(SymmetryGroup *symmetry_group); * * Says whether the SymmetryGroup is dihedral. * * Boolean symmetry_group_is_polyhedral( SymmetryGroup *symmetry_group * Boolean *is_binary_group, * int *p, * int *q, * int *r); * * Says whether the SymmetryGroup is polyhedral. If it is, reports * whether it's the binary group, and reports the values for (p,q,r). * The pointers for is_binary_group, p, q and r may be NULL if this * information is not desired. * * Boolean symmetry_group_is_direct_product(SymmetryGroup *symmetry_group); * * Says whether the SymmetryGroup is a nontrivial, nonabelian * direct product. * * SymmetryGroup *get_symmetry_group_factor( SymmetryGroup *symmetry_group, * int factor_number); * * If the SymmetryGroup is a nontrivial, nonabelian direct product, * returns a pointer to factor "factor_number" (factor_number = 0 or 1). * Otherwise returns NULL. * * Boolean symmetry_group_is_amphicheiral(SymmetryGroup *symmetry_group); * * Says whether the SymmetryGroup contains orientation-reversing * elements. Assumes the underlying manifold is oriented. * * Boolean symmetry_group_invertible_knot(SymmetryGroup *symmetry_group); * * Assumes the underlying manifold is oriented and has exactly * one Cusp. Returns TRUE if some Symmetry acts on the Cusp * via the matrix (-1, 0; 0, -1); returns FALSE otherwise. * * int symmetry_group_order(SymmetryGroup *symmetry_group); * * Returns the order of the SymmetryGroup. * * int symmetry_group_product(SymmetryGroup *symmetry_group, int i, int j); * * Returns the product of group elements i and j. We use the * convention that products of symmetries read right to left. * That is, the composition symmetry[i] o symmetry[j] acts by * first doing symmetry[j], then symmetry[i]. * * IsometryList *get_symmetry_list(SymmetryGroup *symmetry_group); * * Returns the list of "raw" Isometries comprising a SymmetryGroup. * * SymmetryGroup *get_commutator_subgroup(SymmetryGroup *symmetry_group); * SymmetryGroup *get_abelianization (SymmetryGroup *symmetry_group); * * Compute the commutator subgroup [G,G] and the abelianization * G/[G,G]. The UI should eventually use free_symmetry_group() * to free them. * * SymmetryGroup *get_center(SymmetryGroup *symmetry_group); * * Computes the center of G, which is the subgroup consisting of * elements which commute with all elements in G. The UI should * eventually use free_symmetry_group() to free it. * * SymmetryGroupPresentation *get_symmetry_group_presentation( * SymmetryGroup *symmetry_group); * int sg_get_num_generators( SymmetryGroupPresentation *group); * int sg_get_num_relations( SymmetryGroupPresentation *group); * int sg_get_num_factors( SymmetryGroupPresentation *group, * int which_relation); * void sg_get_factor( SymmetryGroupPresentation *group, * int which_relation, * int which_factor, * int *generator, * int *power); * void free_symmetry_group_presentation(SymmetryGroupPresentation *group); * * get_symmetry_group_presentation() computes a presentation for * the SymmetryGroup, the various sg_ functions let you get * about it, and free_symmetry_group_presentation() frees it when * you're done. All these functions are documented in SnapPea.h, * so I won't repeat the details here. */ /* * Symmetry Group Presentations * * This comment provides the theoretical underpinnings for computing * presentations of symmetry groups. The main proposition shows how * to compute an inefficient -- but correct! -- presentation. Various * improvements follow. [The writing in this comment isn't very polished, * but I wanted to get my thoughts down so if I come back to this * in the future I'll have some record of what I was thinking. Also I * wanted to make sure that my rough intuitions about presentations * are rigorously correct.] * * The idea of a presentation for a group G is formalized as a map from * a free group F onto G. The "generators" of the presentation are a * set of generators for the free group F, and the "relations" are a set * of words whose normal closure in F is the kernel of the map F -> G. * * The inefficient method is, roughly speaking, to make every element * a generator, and every entry in the multiplication table a relation. * More formally, let G be a finite group with elements {0, 1, 2, ...}, * and let F be the free group generated by {a, b, c, ...}, where the * number of generators of F equals the number of elements of G. * The map F -> G is the obvious one: a->0, b->1, ... . * (Warning: Somehow I ended up using right-to-left composition * in the multiplication table, even though I use left-to-right * composition in the free group. So the map F -> G is an antihomeomorphsim. * To further comfuse matters, the Mac UI now displays the transposed * multiplication table, so the user sees left-to-right composition * even though right-to-left composition is still used internally. * Sorry about that.) The relations correspond to the entries in G's * multiplication table; for example, if 1*3 = 4 in G, then db(e^-1) * is a relation in the kernel. Clearly each relation is in the kernel; * it remains to prove that they generate the kernel. The proof is easy. * Imagine some word e(k^-1)cb(a^-2)d which maps to 0. We want to show * that we can build it up by multiplying and conjugating relations. * First note that our set contains the relation aa(a^-1) = a = 1. * So if the sample word contains a power of "a", we may conjugate it * to put the power of "a" on the outside, de(k^-1)cb(a^-2), and we've * reduce the problem to showing that our relations generate de(k^-1)cb * (if there were more factors of "a", we'd eliminate them, too). * Now let's get rid of the inverses. Our sample word de(k^-1)cb contains * one negative power, namely k^-1. The generator k maps to the * element 10 in G, and 10 has some inverse, say 7, in G. This means * there's a relation hk(a^-1) in our set, hence our relation set also * produces kh = 1. The sample word is conjugate to cbde(k^-1), so if * our set produces cbdeh, it must produce cbde(k^-1) as well. * If there were more negative powers we eliminate them too, until we * arrive at the case of all positive factors. Look at the first two * factors in cbdeh, namely cb. They map to the group elements * 2 and 1, respectively. In G, 1*2 has some value, say 5, so there's * a relation cb(f^-1) in our set. Rewrite the above word as * (cb(f^-1))fdeh. It's clear that if our set of relations produces * fdeh, then it produces (cb(f^-1))fdeh and hence cbdeh as well. * Continue in this fashion until we're down to a single element. * By assumption the sample word is in the kernel, and "a" is the only * generator which maps to 0, therefore that last letter must be "a", * which we already know is produced by our set. Therefore our set * generates the entire kernel, and our presentation really is a * presentation for G. Q.E.D. * * Improvement #1. Reduce the number of generators. * * Any time a presentation has a relation expressing one of the * generators in terms of the others, say c = abbdbab, that generator * may be eliminated. Here's how to do it. First eliminate the * redundant generator (in this case "c") from all other relations * by substituting the equivalent expression (in this case "abbdbab"). * (To be completely rigorous, one takes the other relation, say * bcddca, conjugates it to bring "c" to the outside, cddcab, and * then multiplies to get (abbdbab(c^-1))(cddcab) = abbdbabddcab. * One continues in this way until all occurences of "c" have been * eliminated from all relations except the original c = abbdbab.) * Then define a new presentation which is just like the old one * except that (1) the generator "c" has been eliminated, and * (2) the relation c = abbdbab has been elminated. To prove the * old and new presentations are equivalent, define a map from * one to the other by a->a, b->b, c->abbdbab, d->d, ... . * Clearly this map is onto, and clearly it maps the normal closure * of the old relation set to the normal closure of the new one. * So the quotients are the same. Q.E.D. * * In practice one doesn't want to explicitly create n generators, * and then eliminate all but a handful of them. Instead, one * explicitly creates a first generator, preferably one which maps * to an element of high order in G. Then one eliminates all the * generators which correspond to powers of the first one (one * eliminates them mentally in this proof, not explicitly in the * computer program, because the computer program never created them * to begin with). One then explicitly creates another generator, * and eliminates other generators (not explicitly created) which * can be expressed in terms of it and first explicitly created * generator. One continues in this fashion until all generators * have either been explicitly created or eliminated. * * Improvement #2. Reduce the number of relations. * * After eliminating most of the generators, we'll find that many * of the relations have become trivial (e.g. ba = ba) or equivalent * to other relations. So we should reduce the set of relations to * the smallest set which generates the same normal closure. */ #include "kernel.h" /* * I chose to define a SymmetryGroupPresentation data structure * independent of fundamental_groups.c's GroupPresentation structure, * for the following two reasons. * * (1) The GroupPresentation structure carries a lot of information * about peripheral curves, representations into Isom(H^3), etc. * These fields obviously don't apply to symmetry groups, * so fundamental_group.c would require a lot of modifications * to make them optional. * * (2) The algorithms for simplifying group presentations are * different for symmetry groups than for fundamental groups. * For fundamental groups, a major goal is reducing the number * of generators. For symmetry groups we have a reasonable * set of generators at the beginning, and our main goal is * reducing the vast number of relations in some efficient way. */ typedef struct Factor { /* * A Factor is a generator to a power. * For example, a^3 would be {0,3}, b^-2 would be {1,-2} etc. */ int generator, power; /* * The linear words assigned to group elements are stored as * NULL-terminated singly-linked list of Factors. * Relations, on the other hand, are stored as CyclicWords. */ struct Factor *next; } Factor; typedef struct CyclicWord { /* * itsFactors points to a circular singly-linked list of Factors. * The UI may decide whether to print a CyclicWord left-to-right or * right-to-left. Either way, the order of composition is in the * sense of the linked list: the symmetry corresponding to itsFactors * is done first, then the symmetry corresponding to itsFactors->next, * and so on. */ Factor *itsFactors; /* * The size of a CyclicWord is the sum of the absolute values of the * powers in its Factors. It serves two purposes in create_relations(). * First, it makes checking for duplicates quicker. Second, and * more importantly, it serves as the criterion of "simplicity" used * in simplifying the relations. That is, a "helper" relation is * inserted into a "target" relation iff the target's size decreases. */ int size; /* * The (signed) sum of the powers and the number of Factors * are also used to speed up duplicate checking. */ int sum, num_factors; /* * A SymmetryGroupPresentation keeps its relations on * a NULL-terminated, singly-linked list of CyclicWords. */ struct CyclicWord *next; } CyclicWord; struct SymmetryGroupPresentation { int itsNumGenerators, itsNumRelations; CyclicWord *itsRelations; }; #define NOT_ASSIGNED ((Factor *) -1) static Boolean is_inverting_matrix(MatrixInt22 a_matrix); static Boolean *compute_commutator_subset(SymmetryGroup *symmetry_group); static SymmetryGroup *create_subgroup(SymmetryGroup *symmetry_group, Boolean *subset); static SymmetryGroup *create_quotient(SymmetryGroup *symmetry_group, Boolean *subset); static Boolean *compute_center(SymmetryGroup *symmetry_group); static void assign_generators(SymmetryGroup *symmetry_group, Factor ***elements, int *num_generators); static Factor *compose_right_to_left(Factor *word1, Factor *word0); static Factor *invert_word(Factor *word); static void simplify_linearly(Factor **word); static void free_elements_array(Factor **elements, int order); static void free_factor_list(Factor *factor_list); static void create_relations(SymmetryGroup *symmetry_group, Factor **elements, SymmetryGroupPresentation *group); static Boolean same_word(Factor *word0, Factor *word1); static void combine_like_factors(CyclicWord *word); static void normalize_powers(CyclicWord *word, int *powers); static void normalize_power(int *power, int modulus); static Boolean remove_zero_factors(CyclicWord *word); static CyclicWord *invert_cyclic_word(CyclicWord *word); static Boolean cyclic_word_is_on_list(CyclicWord *word, CyclicWord *list); static Boolean same_cyclic_word(CyclicWord *word0, CyclicWord *word1); static Boolean same_based_cyclic_word(Factor *word0, Factor *word1); static void compute_word_info(CyclicWord *word); static Boolean substitute_to_simplify(CyclicWord *helper, CyclicWord *target, int *powers); static Boolean substitute_word_to_simplify(CyclicWord *helper, CyclicWord *target, int *powers); static int cancellation_size(CyclicWord *word0, CyclicWord *word1, int *powers); static void insert_word(CyclicWord *helper, CyclicWord *target, int *powers); static void invert_relations_as_necessary(CyclicWord **relation_list); static void free_cyclic_word(CyclicWord *word); Boolean symmetry_group_is_abelian( SymmetryGroup *symmetry_group, AbelianGroup **abelian_description) { if (abelian_description != NULL) *abelian_description = symmetry_group->abelian_description; return symmetry_group->is_abelian; } Boolean symmetry_group_is_dihedral( SymmetryGroup *symmetry_group) { return symmetry_group->is_dihedral; } Boolean symmetry_group_is_polyhedral( SymmetryGroup *symmetry_group, Boolean *is_binary_group, int *p, int *q, int *r) { if (symmetry_group->is_polyhedral == TRUE) { if (is_binary_group != NULL) *is_binary_group = symmetry_group->is_binary_group; if (p != NULL) *p = symmetry_group->p; if (q != NULL) *q = symmetry_group->q; if (r != NULL) *r = symmetry_group->r; return TRUE; } else { if (is_binary_group != NULL) *is_binary_group = FALSE; if (p != NULL) *p = 0; if (q != NULL) *q = 0; if (r != NULL) *r = 0; return FALSE; } } Boolean symmetry_group_is_S5( SymmetryGroup *symmetry_group) { return symmetry_group->is_S5; } Boolean symmetry_group_is_direct_product( SymmetryGroup *symmetry_group) { return symmetry_group->is_direct_product; } SymmetryGroup *get_symmetry_group_factor( SymmetryGroup *symmetry_group, int factor_number) { if (factor_number != 0 && factor_number != 1) uFatalError("get_symmetry_group_factor", "symmetry_group"); if (symmetry_group->is_direct_product == TRUE) return symmetry_group->factor[factor_number]; else return NULL; } Boolean symmetry_group_is_amphicheiral( SymmetryGroup *symmetry_group) { /* * We assume the underlying manifold is oriented. */ int i; for (i = 0; i < symmetry_group->order; i++) if (parity[symmetry_group->symmetry_list->isometry[i]->tet_map[0]] == 1) return TRUE; return FALSE; } Boolean symmetry_group_invertible_knot( SymmetryGroup *symmetry_group) { /* * We assume the underlying manifold is oriented and has * exactly one Cusp. */ int i; for (i = 0; i < symmetry_group->order; i++) if (is_inverting_matrix(symmetry_group->symmetry_list->isometry[i]->cusp_map[0])) return TRUE; return FALSE; } static Boolean is_inverting_matrix( MatrixInt22 a_matrix) { int i, j; const static MatrixInt22 inverting_matrix = {{-1, 0}, {0, -1}}; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (a_matrix[i][j] != inverting_matrix[i][j]) return FALSE; return TRUE; } int symmetry_group_order( SymmetryGroup *symmetry_group) { return symmetry_group->order; } int symmetry_group_product( SymmetryGroup *symmetry_group, int i, int j) { return symmetry_group->product[i][j]; } int symmetry_group_order_of_element( SymmetryGroup *symmetry_group, int i) { return symmetry_group->order_of_element[i]; } IsometryList *get_symmetry_list( SymmetryGroup *symmetry_group) { return symmetry_group->symmetry_list; } SymmetryGroup *get_commutator_subgroup( SymmetryGroup *symmetry_group) { Boolean *subset; SymmetryGroup *subgroup; if (symmetry_group != NULL) { subset = compute_commutator_subset(symmetry_group); subgroup = create_subgroup(symmetry_group, subset); my_free(subset); return subgroup; } else return NULL; } SymmetryGroup *get_abelianization( SymmetryGroup *symmetry_group) { Boolean *subset; SymmetryGroup *quotient; if (symmetry_group != NULL) { subset = compute_commutator_subset(symmetry_group); quotient = create_quotient(symmetry_group, subset); my_free(subset); return quotient; } else return NULL; } static Boolean *compute_commutator_subset( SymmetryGroup *symmetry_group) { Boolean *subset, progress; int i, j; /* * Allocate an array of Booleans, to keep track of which * elements are in the commutator subgroup. */ subset = NEW_ARRAY(symmetry_group->order, Boolean); /* * Initialize the subset to be empty. */ for (i = 0; i < symmetry_group->order; i++) subset[i] = FALSE; /* * For each pair of elements i and j, add the commutator ijIJ * to the subset. */ for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) subset [symmetry_group->product [symmetry_group->product[i][j]] [symmetry_group->inverse[symmetry_group->product[j][i]]] ] = TRUE; /* * At this point the subset is closed under inverses, and * contains the identity, but may or may not be closed under * multiplication. So keep adding products of elements until * it is closed under multiplication. */ do { progress = FALSE; for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) if (subset[i] && subset[j]) { if ( ! subset[symmetry_group->product[i][j]]) { subset[symmetry_group->product[i][j]] = TRUE; progress = TRUE; } } } while (progress == TRUE); /* * All done! */ return subset; } static SymmetryGroup *create_subgroup( SymmetryGroup *symmetry_group, Boolean *subset) { SymmetryGroup *subgroup; int *subgroup_element, i, j; /* * Allocate the SymmetryGroup data structure. */ subgroup = NEW_STRUCT(SymmetryGroup); /* * The array subgroup_element[] will translate indices in the * full symmetry_group to indices in the subgroup. As we set * it up, we count how many elements belong to the subgroup. */ subgroup_element = NEW_ARRAY(symmetry_group->order, int); subgroup->order = 0; for (i = 0; i < symmetry_group->order; i++) if (subset[i]) subgroup_element[i] = subgroup->order++; else subgroup_element[i] = -1; /* * The subgroup won't have a SymmetryList, * so we'd better not ever pass it to a function which requires one! * (If desired we could write code to copy the symmetries. * In fact direct_product.c contains such code.) */ subgroup->symmetry_list = NULL; /* * The subgroup's multiplication table is essentially * a subset of the full SymmetryGroup's multiplication table. */ subgroup->product = NEW_ARRAY(subgroup->order, int *); for (i = 0; i < subgroup->order; i++) subgroup->product[i] = NEW_ARRAY(subgroup->order, int); for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) if (subset[i] && subset[j]) subgroup->product[subgroup_element[i]][subgroup_element[j]] = subgroup_element[symmetry_group->product[i][j]]; /* * Copy the orders of the elements. */ subgroup->order_of_element = NEW_ARRAY(subgroup->order, int); for (i = 0; i < symmetry_group->order; i++) if (subset[i]) subgroup->order_of_element[subgroup_element[i]] = symmetry_group->order_of_element[i]; /* * Copy the inverses. */ subgroup->inverse = NEW_ARRAY(subgroup->order, int); for (i = 0; i < symmetry_group->order; i++) if (subset[i]) subgroup->inverse[subgroup_element[i]] = subgroup_element[symmetry_group->inverse[i]]; /* * Free the temporary array. */ my_free(subgroup_element); /* * Try to find a humanly comprehensible description of the subgroup. */ recognize_group(subgroup); return subgroup; } static SymmetryGroup *create_quotient( SymmetryGroup *symmetry_group, Boolean *subset) { SymmetryGroup *quotient; int *coset, i, j; /* * Allocate the SymmetryGroup data structure. */ quotient = NEW_STRUCT(SymmetryGroup); /* * We'll assign each element of symmetry_group to a coset * of the given subset. We assume the subset is a normal subgroup, * which is certainly the case when it's the commutator subgroup. */ coset = NEW_ARRAY(symmetry_group->order, int); /* * First assign the elements of the subset to the identity coset. * Temporarily assign all other elements to the dummy coset -1. */ for (i = 0; i < symmetry_group->order; i++) if (subset[i]) coset[i] = 0; else coset[i] = -1; /* * We now go down the list of group elements, and whenever we * encounter an element not assigned to a coset, we create a * new coset, and locate all elements which belong to it. * We count the cosets as we go along. */ quotient->order = 1; for (i = 0; i < symmetry_group->order; i++) if (coset[i] == -1) { for (j = 0; j < symmetry_group->order; j++) if (subset[j]) coset[symmetry_group->product[i][j]] = quotient->order; quotient->order++; } /* * The quotient won't have a SymmetryList, * so we'd better not ever pass it to a function which requires one! */ quotient->symmetry_list = NULL; /* * Compute a multiplication table for the quotient. * (This isn't the most efficient way to do it, but * I don't think it really matters.) */ quotient->product = NEW_ARRAY(quotient->order, int *); for (i = 0; i < quotient->order; i++) quotient->product[i] = NEW_ARRAY(quotient->order, int); for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) quotient->product[coset[i]][coset[j]] = coset[symmetry_group->product[i][j]]; /* * Free the temporary array. */ my_free(coset); /* * Use existing code to compute orders of elements. */ compute_orders_of_elements(quotient); /* * Use existing code to compute inverses. */ compute_inverses(quotient); /* * Try to find a humanly comprehensible description of the quotient. */ recognize_group(quotient); return quotient; } SymmetryGroup *get_center( SymmetryGroup *symmetry_group) { Boolean *subset; SymmetryGroup *center; if (symmetry_group != NULL) { subset = compute_center(symmetry_group); center = create_subgroup(symmetry_group, subset); my_free(subset); } else center = NULL; return center; } static Boolean *compute_center( SymmetryGroup *symmetry_group) { Boolean *subset; int i, j; /* * Allocate an array of Booleans to keep track of which * elements are in the center. */ subset = NEW_ARRAY(symmetry_group->order, Boolean); /* * An element is in the center iff it commutes with all group elements. */ for (i = 0; i < symmetry_group->order; i++) { subset[i] = TRUE; for (j = 0; j < symmetry_group->order; j++) if (symmetry_group->product[i][j] != symmetry_group->product[j][i]) { subset[i] = FALSE; break; } } /* * All done! */ return subset; } SymmetryGroupPresentation *get_symmetry_group_presentation( SymmetryGroup *symmetry_group) { SymmetryGroupPresentation *group; Factor **elements; group = NEW_STRUCT(SymmetryGroupPresentation); /* * Choose a set of generators. The "elements" array reports the * word assigned to each element in the group. (In terms of the * theoretical discussion at the top of this file, it reports * the name of each explicitly constructed generator, or the word * in the free group which has replaced each eliminated generator.) */ assign_generators(symmetry_group, &elements, &group->itsNumGenerators); /* * Assemble a set of relations, eliminating the redundancies * as much as possible. */ create_relations(symmetry_group, elements, group); free_elements_array(elements, symmetry_group->order); return group; } static void assign_generators( SymmetryGroup *symmetry_group, Factor ***elements, int *num_generators) { int i, j, elements_remaining, max_element, max_power, product, power; Boolean progress; /* * Try to choose a fairly small set of generators. * Let the first generator "a" be an element of maximal order; * that is, let it be an element which maximizes the size of the * subgroup [a] which it generates. Let the second generator "b" * maximize the size of the subgroup [a,b], etc. I haven't implemented * this in a rigorous way -- all we really need is a heuristic to * get a fairly small set of generators. */ /* * Initialize the number of generators to zero. */ *num_generators = 0; /* * Allocate the array which will keep track of the linked list * of Factors assigned to each group element. */ *elements = NEW_ARRAY(symmetry_group->order, Factor *); /* * Initialize *elements to indicate that no words * have yet been assigned. */ for (i = 0; i < symmetry_group->order; i++) (*elements)[i] = NOT_ASSIGNED; /* * Keep track of how many elements still require words. */ elements_remaining = symmetry_group->order; /* * Assign the identity the empty word. */ (*elements)[0] = NULL; elements_remaining--; /* * Add generators until all elements have been assigned one. */ while (elements_remaining > 0) { /* * Choose each new generator so as to maximize the power of it * required to get an element which has already been assigned * a generator. The hope is that this will more or less * maximize the number of new assignments provided by this * generator. (It will certainly be quicker than explicitly * computing the number of new assignments provided by each * potential new generator). */ max_element = -1; max_power = 0; for (i = 0; i < symmetry_group->order; i++) { product = i; power = 1; while ((*elements)[product] == NOT_ASSIGNED) { product = symmetry_group->product[i][product]; power++; } if (power > max_power) { max_power = power; max_element = i; } } if (max_power < 2) uFatalError("assign_generators", "symmetry_group_info"); /* * Assign a new generator to max_element. */ (*elements)[max_element] = NEW_STRUCT(Factor); (*elements)[max_element]->generator = (*num_generators)++; (*elements)[max_element]->power = 1; (*elements)[max_element]->next = NULL; elements_remaining--; /* * Use the symmetry group's mulitplication table to deduce the * assignments of words to as many other group elements as possible. * The symmetry group's mulitplication table composes symmetries * right-to-left: product[i][j] is obtained by doing symmetry j, * followed by symmetry i. */ do { progress = FALSE; for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) if ((*elements)[i] != NOT_ASSIGNED && (*elements)[j] != NOT_ASSIGNED && (*elements)[symmetry_group->product[i][j]] == NOT_ASSIGNED) { (*elements)[symmetry_group->product[i][j]] = compose_right_to_left((*elements)[i], (*elements)[j]); simplify_linearly(&(*elements)[symmetry_group->product[i][j]]); elements_remaining--; progress = TRUE; } } while (progress == TRUE); } } static Factor *compose_right_to_left( Factor *word1, Factor *word0) { Factor *product, **p, *factor; product = NULL; p = &product; for (factor = word0; factor != NULL; factor = factor->next) { *p = NEW_STRUCT(Factor); (*p)->generator = factor->generator; (*p)->power = factor->power; (*p)->next = NULL; p = &(*p)->next; } for (factor = word1; factor != NULL; factor = factor->next) { *p = NEW_STRUCT(Factor); (*p)->generator = factor->generator; (*p)->power = factor->power; (*p)->next = NULL; p = &(*p)->next; } return product; } static Factor *invert_word( Factor *word) { Factor *inverse, *factor, *new_factor; inverse = NULL; for (factor = word; factor != NULL; factor = factor->next) { new_factor = NEW_STRUCT(Factor); new_factor->generator = factor->generator; new_factor->power = - factor->power; new_factor->next = inverse; inverse = new_factor; } return inverse; } static void simplify_linearly( Factor **word) { Boolean progress; Factor **factor, *dead_factor; do { progress = FALSE; for (factor = word; *factor != NULL; factor = &(*factor)->next) if ((*factor)->next != NULL && (*factor)->generator == (*factor)->next->generator) { dead_factor = (*factor)->next; (*factor)->power += dead_factor->power; (*factor)->next = dead_factor->next; my_free(dead_factor); if ((*factor)->power == 0) { dead_factor = *factor; *factor = (*factor)->next; my_free(dead_factor); } progress = TRUE; break; } } while (progress == TRUE); } static void free_elements_array( Factor **elements, int order) { int i; for (i = 0; i < order; i++) free_factor_list(elements[i]); my_free(elements); } static void free_factor_list( Factor *factor_list) { Factor *dead_factor; while (factor_list != NULL) { dead_factor = factor_list; factor_list = factor_list->next; my_free(dead_factor); } } static void create_relations( SymmetryGroup *symmetry_group, Factor **elements, SymmetryGroupPresentation *group) { int *powers, i, j, k; Factor *temp1, *temp2, *relation, *last; CyclicWord **end_of_relation_list, **first_generic_relation, *word, *inverse_word, *helper, *helper_inverse, **target, *dead_relation; Boolean progress; /* * The list of relations is initially empty. */ group->itsNumRelations = 0; group->itsRelations = NULL; end_of_relation_list = &group->itsRelations; /* * Begin by creating the relations of the form a^n. * All other relations will be simplified modulo a^n, * it'll be useful to store the powers in an array. */ powers = NEW_ARRAY(group->itsNumGenerators, int); for (i = 0; i < symmetry_group->order; i++) if (elements[i] != NULL && elements[i]->power == 1 && elements[i]->next == NULL) { powers[elements[i]->generator] = symmetry_group->order_of_element[i]; word = NEW_STRUCT(CyclicWord); word->itsFactors = NEW_STRUCT(Factor); word->itsFactors->generator = elements[i]->generator; word->itsFactors->power = symmetry_group->order_of_element[i]; word->itsFactors->next = word->itsFactors; word->size = symmetry_group->order_of_element[i]; word->sum = symmetry_group->order_of_element[i]; word->num_factors = 1; word->next = NULL; *end_of_relation_list = word; end_of_relation_list = &word->next; group->itsNumRelations++; } /* * During the simplification phase (below) we'll need to know where * the basic a^n relations end, and where the other relations begin. */ first_generic_relation = end_of_relation_list; /* * Each entry in the multiplication table gives a relation * * elements[j] elements[i] = elements[i*j] * * (Recall that pesky right-to-left composition.) * * Eliminate the obvious duplications as we go along, to minimize * the size of the list which must then be simplified. */ for (i = 0; i < symmetry_group->order; i++) for (j = 0; j < symmetry_group->order; j++) { /* * The plan is to compute the left and right hand sides * of the relation (cf. above). Often they will be equal, * and we know right away the relation is trivial. * If they're not equal, go ahead and create the relation. */ k = symmetry_group->product[i][j]; temp1 = compose_right_to_left(elements[i], elements[j]); simplify_linearly(&temp1); if (same_word(temp1, elements[k])) relation = NULL; else { temp2 = invert_word(elements[k]); relation = compose_right_to_left(temp1, temp2); free_factor_list(temp2); } free_factor_list(temp1); /* * If the relation is nontrivial, consider adding * it to itsRelations list. */ if (relation != NULL) { /* * Make the relation circular. */ for (last = relation; last->next != NULL; last = last->next) ; last->next = relation; /* * Create a new CyclicWord, and install the * (now circular) relation. */ word = NEW_STRUCT(CyclicWord); word->itsFactors = relation; word->next = NULL; /* * Simplify the CyclicWord by combining adjacent Factors * with the same generator. While we're at it, normalize * all powers to make it easier to spot duplicates. */ do { combine_like_factors(word); normalize_powers(word, powers); } while (remove_zero_factors(word) == TRUE); /* * Compute the size of the CyclicWord, the sum of its * powers, and the number of factors. * This will speed up duplicate checking, since we know * two words of different sizes can't possibly be equal. */ compute_word_info(word); /* * Compute the inverse. */ inverse_word = invert_cyclic_word(word); normalize_powers(inverse_word, powers); /* * Install the new CyclicWord on itsRelations list * iff it's nontrivial and neither it nor its inverse * is already already there. */ if (word->itsFactors != NULL && cyclic_word_is_on_list(word, group->itsRelations) == FALSE && cyclic_word_is_on_list(inverse_word, group->itsRelations) == FALSE) { *end_of_relation_list = word; end_of_relation_list = &word->next; group->itsNumRelations++; } else free_cyclic_word(word); free_cyclic_word(inverse_word); } } /* * Simplify the generic relations by substituting other relations * into them so as to reduce the sum of the absolute values * of the powers. Simply modulo the a^n relations. Remove empty * relations as they occur. * * "target" is the relation being simplified. * "helper" is the relation which will be substituted into target, * if doing so reduces target's size. */ do { progress = FALSE; for (helper = group->itsRelations; helper != NULL; helper = helper->next) { helper_inverse = invert_cyclic_word(helper); target = first_generic_relation; while (*target != NULL) { if (*target == helper) { target = &(*target)->next; continue; } if (substitute_to_simplify(helper, *target, powers) == TRUE || substitute_to_simplify(helper_inverse, *target, powers) == TRUE) progress = TRUE; if ((*target)->size == 0) { dead_relation = *target; *target = dead_relation->next; free_cyclic_word(dead_relation); group->itsNumRelations--; } else target = &(*target)->next; } free_cyclic_word(helper_inverse); } } while (progress == TRUE); /* * If a relation has more negative than positive factors, * replace it with its inverse. */ invert_relations_as_necessary(&group->itsRelations); /* * Free the local array of powers. */ my_free(powers); } static Boolean same_word( Factor *word0, Factor *word1) { while (TRUE) { if (word0 == NULL && word1 == NULL) return TRUE; if (word0 == NULL || word1 == NULL) return FALSE; if (word0->generator != word1->generator || word0->power != word1->power ) return FALSE; word0 = word0->next; word1 = word1->next; } } static void combine_like_factors( CyclicWord *word) { Factor *factor, *dead_factor; /* * Combine adjacent factors with the same generator. */ if (word->itsFactors != NULL) { factor = word->itsFactors; do { while (factor != factor->next && factor->generator == factor->next->generator) { dead_factor = factor->next; if (dead_factor == word->itsFactors) word->itsFactors = dead_factor->next; factor->power += dead_factor->power; factor->next = dead_factor->next; my_free(dead_factor); } factor = factor->next; } while (factor != word->itsFactors); } } static void normalize_powers( CyclicWord *word, int *powers) { Factor *factor; if (word->itsFactors != NULL) { factor = word->itsFactors; do { normalize_power(&factor->power, powers[factor->generator]); factor = factor->next; } while (factor != word->itsFactors); } } static void normalize_power( int *power, int modulus) { while (*power <= -((modulus + 1)/2)) *power += modulus; while (*power > modulus/2) *power -= modulus; } static Boolean remove_zero_factors( CyclicWord *word) { Factor *factor, *dead_factor; Boolean zero_factors_were_removed; /* * Eliminate Factors with power zero. */ zero_factors_were_removed = FALSE; if (word->itsFactors != NULL) { factor = word->itsFactors; do { while (factor->next->power == 0) { /* * If this Factor is the only one on the ciruclar linked * list, eliminite it and leave the CyclicWord empty. */ if (factor->next == factor) { my_free(factor); word->itsFactors = NULL; return TRUE; } /* * Eliminate factor->next. */ dead_factor = factor->next; if (dead_factor == word->itsFactors) word->itsFactors = dead_factor->next; factor->next = dead_factor->next; my_free(dead_factor); zero_factors_were_removed = TRUE; } factor = factor->next; } while (factor != word->itsFactors); } return zero_factors_were_removed; } static CyclicWord *invert_cyclic_word( CyclicWord *word) { CyclicWord *inverse_word; Factor *factor, *inverse_factor; inverse_word = NEW_STRUCT(CyclicWord); inverse_word->itsFactors = NULL; inverse_word->size = word->size; inverse_word->sum = - word->sum; inverse_word->num_factors = word->num_factors; inverse_word->next = NULL; if (word->itsFactors != NULL) { factor = word->itsFactors; do { inverse_factor = NEW_STRUCT(Factor); inverse_factor->generator = factor->generator; inverse_factor->power = - factor->power; if (inverse_word->itsFactors == NULL) { inverse_word->itsFactors = inverse_factor; inverse_factor->next = inverse_factor; } else { inverse_factor->next = inverse_word->itsFactors->next; inverse_word->itsFactors->next = inverse_factor; } factor = factor->next; } while (factor != word->itsFactors); } return inverse_word; } static Boolean cyclic_word_is_on_list( CyclicWord *word, CyclicWord *list) { CyclicWord *word1; for (word1 = list; word1 != NULL; word1 = word1->next) if (word->size == word1->size /* compare size, sum and */ && word->sum == word1->sum /* num_factors first, */ && word->num_factors == word1->num_factors /* for greater speed */ && same_cyclic_word(word, word1)) return TRUE; return FALSE; } static Boolean same_cyclic_word( CyclicWord *word0, CyclicWord *word1) { Factor *start; if (word0->itsFactors == NULL && word1->itsFactors == NULL) return TRUE; if (word0->itsFactors == NULL || word1->itsFactors == NULL) return FALSE; start = word0->itsFactors; do { if (same_based_cyclic_word(start, word1->itsFactors)) return TRUE; start = start->next; } while (start != word0->itsFactors); return FALSE; } static Boolean same_based_cyclic_word( Factor *word0, Factor *word1) { Factor *factor0, *factor1; factor0 = word0; factor1 = word1; while (TRUE) { if (factor0->generator != factor1->generator || factor0->power != factor1->power ) return FALSE; factor0 = factor0->next; factor1 = factor1->next; if (factor0 == word0 && factor1 == word1) return TRUE; if (factor0 == word0 || factor1 == word1) return FALSE; } } static void compute_word_info( CyclicWord *word) { Factor *factor; word->size = 0; word->sum = 0; word->num_factors = 0; if (word->itsFactors != NULL) { factor = word->itsFactors; do { word->size += ABS(factor->power); word->sum += factor->power; word->num_factors++; factor = factor->next; } while (factor != word->itsFactors); } } static Boolean substitute_to_simplify( CyclicWord *helper, CyclicWord *target, int *powers) { Factor *original_helper_base, *original_target_base; /* * There shouldn't be any empty words on the relation list, * but in case of error let's not crash the whole system. */ if (helper->itsFactors == NULL || target->itsFactors == NULL) return FALSE; /* * If the target isn't more than half the size of the helper, * then the helper can't possibly simplify it. */ if (target->size <= helper->size/2) return FALSE; /* * Don't worry about substituting in helper's inverse. * create_relations() passes us helper's inverse in a separeate call. */ /* * A priori one might want to consider substituting helper into * target in the middle of a factor, e.g. * * aaabbbbcc -> aaab(BAAdB)bbbcc = adbbcc. * * But it's easy to prove that the same cancellations may be obtained * by substituting at "factor boundaries", e.g. * * aaa(AAdBB)bbbbcc -> adbbcc. */ /* * Consider all possible "basepoints" for both helper and target. */ original_helper_base = helper->itsFactors; original_target_base = target->itsFactors; do { do { if (substitute_word_to_simplify(helper, target, powers) == TRUE) { helper->itsFactors = original_helper_base; return TRUE; } target->itsFactors = target->itsFactors->next; } while (target->itsFactors != original_target_base); helper->itsFactors = helper->itsFactors->next; } while (helper->itsFactors != original_helper_base); return FALSE; } static Boolean substitute_word_to_simplify( CyclicWord *helper, CyclicWord *target, int *powers) { /* * The helper has factors * * h0 h1 h2 ... hm * * and the target has factors * * t0 t1 t2 ... tn. * * We want to replace the target with * * h0 h1 h2 ... hm t0 t1 t2 ... tn * * iff enough cancellations will occur to decrease target's size. * For this to happen, the size of the cancellations must exceed * half the size of the helper. * * We compute the "tail end" cancellations (hm with t0, * h(m-1) with t1, etc.) separately from the "head end" cancellations * (h0 with tn, h1 with t(n-1), etc.) and add their sizes. * It's possible that the head end and tail end cancellations * will overlap, and the sum of their sizes will be greater than * the true cancellation, but this doesn't matter. If they overlap * in either of the two words (helper or target), then they are * sure to overlap in the small of the two words. If the smaller * word is the helper, then the entire helper word is cancelling * with a substring of the target, and we are happy. If the smaller * of the two words is the target, then the entire target is * cancelling with a substring of the helper, and * substitute_to_simplify() has already checked that * target->size > helper->size/2, so again we are happy, because * the replacement has shortened the target. * (Hmmm . . . does the fact that, say, a^3 can "cancel" with * a^2 to give a^-2 when a^7 == 1 affect the validity of this * proof? I doubt it, but . . .) * * We "double count" cancellations. That is, if a^3 cancels with * a^(-3), we add 6 to the cancellation count. This makes it * easier to count cancellations like a^3 "cancelling" with * a^2 to give a^-2, which counts for a saving of 3 in a group * in which a^7 == 1. */ if (cancellation_size(helper, target, powers) + cancellation_size(target, helper, powers) > helper->size) { /* * Given the doubt in the above proof, * let's check explicitly that size has truly been reduced. */ int old_size; old_size = target->size; insert_word(helper, target, powers); if (target->size >= old_size) uFatalError("substitute_word_to_simplify", "symmetry_group_info"); return TRUE; } else return FALSE; } static int cancellation_size( CyclicWord *word0, CyclicWord *word1, int *powers) { Factor *h_first, *t_last, *h, *t, *old_t; int cancellation_size, sum; /* * Count the size of the potential cancellations * between the head of word0 and the tail or word1. */ /* * Let h_first be the first factor at the head of word0, * and t_last be the last factor at the tail of word1. * substitute_to_simplify() has checked that neither word is empty. */ h_first = word0->itsFactors; for ( t_last = word1->itsFactors; t_last->next != word1->itsFactors; t_last = t_last->next) ; /* * Count the size of the potential cancellation. */ cancellation_size = 0; h = h_first; t = t_last; do { if (h->generator == t->generator) { sum = h->power + t->power; normalize_power(&sum, powers[h->generator]); cancellation_size += ABS(h->power); cancellation_size += ABS(t->power); cancellation_size -= ABS(sum); if (sum != 0) break; } else break; /* * Move h to the next Factor in helper. * Move t to the previous Factor in target. */ h = h->next; old_t = t; while (t->next != old_t) t = t->next; } while (h != h_first && t != t_last); return cancellation_size; } static void insert_word( CyclicWord *helper, CyclicWord *target, int *powers) { /* * The helper has factors * * h0 h1 h2 ... hm * * and the target has factors * * t0 t1 t2 ... tn. * * Replace the target with * * h0 h1 h2 ... hm t0 t1 t2 ... tn * * and simplify. */ Factor **last, *h, *h_copy; /* * Find tn's next field. */ for ( last = &target->itsFactors->next; *last != target->itsFactors; last = &(*last)->next) ; /* * Insert copies of the h0...hm. */ h = helper->itsFactors; do { h_copy = NEW_STRUCT(Factor); h_copy->generator = h->generator; h_copy->power = h->power; h_copy->next = target->itsFactors; *last = h_copy; last = &h_copy->next; h = h->next; } while (h != helper->itsFactors); /* * Simplify the result. */ do { combine_like_factors(target); normalize_powers(target, powers); } while (remove_zero_factors(target) == TRUE); /* * Recompute the size, sum and num_factors. */ compute_word_info(target); } static void invert_relations_as_necessary( CyclicWord **relation_list) { /* * If a relation has more negative than positive factors, * replace it with its inverse. */ CyclicWord **word, *dead_word, *inverse_word; Factor *factor; int num_positive_factors, num_negative_factors; for (word = relation_list; *word != NULL; word = &(*word)->next) { num_positive_factors = 0; num_negative_factors = 0; if ((*word)->itsFactors != NULL) { factor = (*word)->itsFactors; do { if (factor->power > 0) num_positive_factors++; else num_negative_factors++; factor = factor->next; } while (factor != (*word)->itsFactors); } if (num_negative_factors > num_positive_factors) { dead_word = *word; inverse_word = invert_cyclic_word(dead_word); inverse_word->next = dead_word->next; *word = inverse_word; free_cyclic_word(dead_word); } } } int sg_get_num_generators( SymmetryGroupPresentation *group) { return group->itsNumGenerators; } int sg_get_num_relations( SymmetryGroupPresentation *group) { return group->itsNumRelations; } int sg_get_num_factors( SymmetryGroupPresentation *group, int which_relation) { CyclicWord *relation; Factor *factor; int num_factors; if (which_relation < 0 || which_relation >= group->itsNumRelations) uFatalError("sg_get_relation", "symmetry_group_info"); relation = group->itsRelations; while (--which_relation >= 0) relation = relation->next; num_factors = 0; if (relation->itsFactors != NULL) { factor = relation->itsFactors; do { num_factors++; factor = factor->next; } while (factor != relation->itsFactors); } return num_factors; } void sg_get_factor( SymmetryGroupPresentation *group, int which_relation, int which_factor, int *generator, int *power) { CyclicWord *relation; Factor *factor; if (which_relation < 0 || which_relation >= group->itsNumRelations) uFatalError("sg_get_relation", "symmetry_group_info"); relation = group->itsRelations; while (--which_relation >= 0) relation = relation->next; if (relation->itsFactors == NULL) uFatalError("sg_get_relation", "symmetry_group_info"); factor = relation->itsFactors; while (--which_factor >= 0) factor = factor->next; *generator = factor->generator; *power = factor->power; } void free_symmetry_group_presentation( SymmetryGroupPresentation *group) { CyclicWord *dead_word; if (group != NULL) { while (group->itsRelations != NULL) { dead_word = group->itsRelations; group->itsRelations = group->itsRelations->next; free_cyclic_word(dead_word); } my_free(group); } } static void free_cyclic_word( CyclicWord *word) { Factor *list; if (word->itsFactors != NULL) { /* * Convert the circular factor list to a NULL-terminated * linear list, and let free_factor_list() dispose of it. */ list = word->itsFactors->next; word->itsFactors->next = NULL; free_factor_list(list); } my_free(word); } snappea-3.0d3/SnapPeaKernel/code/tables.c0100444000175000017500000002154306742675502016325 0ustar babbab/* * tables.c * * This file provides tables used in working with triangulations. * They are based on the numbering conventions for tetrahedra * described in triangulation.h. * * The tables are * * EdgeIndex edge3[6]; * EdgeIndex edge_between_faces[4][4]; * EdgeIndex edge3_between_faces[4][4]; * EdgeIndex edge_between_vertices[4][4]; * EdgeIndex edge3_between_vertices[4][4]; * FaceIndex one_face_at_edge[6]; * FaceIndex other_face_at_edge[6]; * VertexIndex one_vertex_at_edge[6]; * VertexIndex other_vertex_at_edge[6]; * FaceIndex remaining_face[4][4]; * FaceIndex face_between_edges[6][6]; * Permutation inverse_permutation[256]; * signed char parity[256]; * FaceIndex vt_side[4][3]; * Permutation permutation_by_index[24]; * * Their use is described in detail below. * * The value 9 is used as a filler for undefined table entries. */ #include "kernel.h" /* * edge3[i] is the index of the complex edge parameter associated with * the edge of EdgeIndex i. Opposite edges have equal complex edge * parameters, which are stored only under the lesser EdgeIndex; thus * even though the numbering of the edges runs from 0 to 5, the * numbering of the edge parameters run only from 0 to 2. */ const EdgeIndex edge3[6] = {0, 1, 2, 2, 1, 0}; /* * edge_between_faces[i][j] is the index of the edge lying between * faces i and j. */ const EdgeIndex edge_between_faces[4][4] = {{9, 0, 1, 2}, {0, 9, 3, 4}, {1, 3, 9, 5}, {2, 4, 5, 9}}; /* * edge3_between_faces[i][j] = edge3[ edge_between_faces[i][j] ]. * This table is useful when looking up complex edge parameters. * Cf. edge3[] above. */ const EdgeIndex edge3_between_faces[4][4] = {{9, 0, 1, 2}, {0, 9, 2, 1}, {1, 2, 9, 0}, {2, 1, 0, 9}}; /* * edge_between_vertices[i][j] is the index of the edge lying between * vertices i and j. */ const EdgeIndex edge_between_vertices[4][4] = {{9, 5, 4, 3}, {5, 9, 2, 1}, {4, 2, 9, 0}, {3, 1, 0, 9}}; /* * edge3_between_vertices[i][j] = edge3[ edge_between_vertices[i][j] ]. * This table is useful when looking up complex edge parameters. * Cf. edge3[] above. * Note that edge3_between_vertices[][] and edge3_between_faces[][] * are identical. */ const EdgeIndex edge3_between_vertices[4][4] = {{9, 0, 1, 2}, {0, 9, 2, 1}, {1, 2, 9, 0}, {2, 1, 0, 9}}; /* * one_face_at_edge[i] and other_face_at_edge[i] gives the indices of * the two faces incident to edge i. */ const FaceIndex one_face_at_edge[6] = {0, 0, 0, 1, 1, 2}; const FaceIndex other_face_at_edge[6] = {1, 2, 3, 2, 3, 3}; /* * one_vertex_at_edge[i] and other_vertex_at_edge[i] give the indices of * the two vertices incident to edge i. */ const FaceIndex one_vertex_at_edge[6] = {2, 1, 1, 0, 0, 0}; const FaceIndex other_vertex_at_edge[6] = {3, 3, 2, 3, 2, 1}; /* * Given two faces i and j, remaining_face[i][j] tells you the index * of one of the remaining faces. For a right_handed tetrahedron * (see kernel_typedefs.h for the definition of right_handed) the * faces i, j, and remaining_face[i][j] are arranged in a counterclockwise * order around their common ideal vertex. Thus, for two faces i and j, * remaining_face[i][j] gives one of the remaining faces and * remaining_face[j][i] gives the other. */ const FaceIndex remaining_face[4][4] = {{9, 3, 1, 2}, {2, 9, 3, 0}, {3, 0, 9, 1}, {1, 2, 0, 9}}; /* * face_between_edges[i][j] is the index of the face lying between * (nonopposite) edges i and j. */ const FaceIndex face_between_edges[6][6] = {{9, 0, 0, 1, 1, 9}, {0, 9, 0, 2, 9, 2}, {0, 0, 9, 9, 3, 3}, {1, 2, 9, 9, 1, 2}, {1, 9, 3, 1, 9, 3}, {9, 2, 3, 2, 3, 9}}; /* * inverse_permutation[p] is the inverse of Permutation p. */ const Permutation inverse_permutation[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; /* * parity[p] is the parity of Permutation p. * * 0 signifies an even permutation * (corresponding to an orientation reversing gluing) * * 1 signifies an odd permutation * (corresponding to an orientation preserving gluing) * * 9 signifies an invalid permutation * * Notes: * (1) The typedef GluingParity relies on 0 and 1 meaning what they do. * (2) The 0 and 1 are reversed relative to the parity[] table in the * old version of snappea. * (3) Use the constants in GluingParity; don't use 0 and 1 directly. */ const signed char parity[256] = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; /* * vt_side[i][j] is the side of the cross sectional triangle at * vertex i which lies between edges j and (j+1)%3, where * the edge numbering is as in edge3_between_faces[][] above. * * An alternate interpretation is that vt_side[v][0], vt_side[v][1] * and vt_side[v][2] are the three faces surrounding vertex v, given * in counterclockwise order relative to the right_handed orientation * of the Tetrahedron. */ const FaceIndex vt_side[4][3] = {{3, 1, 2}, {2, 0, 3}, {1, 3, 0}, {0, 2, 1}}; /* * There are 24 possible Permutations of the set {3, 2, 1, 0}. The table * permutation_by_index[] list them all. E.g. permutation_by_index[2] = 0xD2 * = 3102, which is the permutation taking 3210 to 3102. */ const Permutation permutation_by_index[24] = { 0xE4, 0xE1, 0xD2, 0xD8, 0xC9, 0xC6, 0x93, 0x9C, 0x8D, 0x87, 0xB4, 0xB1, 0x4E, 0x4B, 0x78, 0x72, 0x63, 0x6C, 0x39, 0x36, 0x27, 0x2D, 0x1E, 0x1B}; /* * index_by_permutation[] is the inverse of permutation_by_index[]. * That is, for 0 <= i < 24, index_by_permutation[permutation_by_index[i]] = i. */ const char index_by_permutation[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 23, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, 19, -1, -1, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, -1, -1, -1, -1, 15, -1, -1, -1, -1, -1, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, -1, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; snappea-3.0d3/SnapPeaKernel/code/terse_triangulation.c0100444000175000017500000004654106742675502021142 0ustar babbab/* * terse_triangulation.c * * This file provides the functions * * TerseTriangulation *tri_to_terse(Triangulation *manifold); * Triangulation *terse_to_tri(TerseTriangulation *tt); * void free_terse_triangulation(TerseTriangulation *tt); * * tri_to_terse() accepts a pointer to a Triangulation, computes the * corresponding TerseTriangulation, and returns a pointer to it. * * terse_to_tri() accepts a pointer to a TerseTriangulation, expands it * to a full Triangulation, and returns a pointer to it. * * free_terse_triangulation() releases the memory used to store a * TerseTriangulation. */ #include "kernel.h" #define DAFAULT_NAME "unknown" /* * If you are not familiar with SnapPea's "Extra" field in * the Tetrahedron data structure, please see the explanation * preceding the Extra typedef in kernel_typedefs.h. * * tri_to_terse() attaches an Extra field to each old Tetrahedron * to keep track of the Tetrahedron's role in the TerseTriangulation. */ struct extra { /* * Has this Tetrahedron been incorporated in the TerseTriangulation? */ Boolean in_use; /* * The remaining fields will be defined iff in_use == TRUE. */ /* * What is this Tetrahedron's index in the TerseTriangulation? */ int index; /* * The Permutation convert_tri_to_terse_indices takes a VertexIndex * or FaceIndex in the Triangulation data structure and "returns" * the TerseTriangulation's index for that same vertex or face. * That is, * * (index in TerseTriangulation) * = EVALUATE(convert_tri_to_terse_indices, index in Triangulation); * * The Permutation convert_terse_to_tri_indices does just the opposite. */ Permutation convert_tri_to_terse_indices, convert_terse_to_tri_indices; /* * Which of the four faces have been glued in the TerseTriangulation? * The indices are those of the TerseTriangulation, not the * original Triangulation. */ Boolean face_is_glued[4]; }; static Boolean better_terse(TerseTriangulation *challenger, TerseTriangulation *defender); static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void initialize_extra(Triangulation *manifold); static Triangulation *bare_bones_triangulation(TerseTriangulation *tt); TerseTriangulation *tri_to_canonical_terse( Triangulation *manifold, Boolean respect_orientation) { /* * Compute all TerseTriangulations, ranging over all possible * choices of base_tetrahedron and base_permutation, and * return the one that is "lexicographically least". */ TerseTriangulation *defender, *challenger; Tetrahedron *tet; int i; Permutation p; defender = tri_to_terse(manifold); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 24; i++) { p = permutation_by_index[i]; if (manifold->orientability == oriented_manifold && respect_orientation == TRUE && parity[p] == 1 /* odd permutation */) continue; challenger = tri_to_terse_with_base(manifold, tet, p); if (better_terse(challenger, defender) == TRUE) { free_terse_triangulation(defender); defender = challenger; challenger = NULL; } else { free_terse_triangulation(challenger); challenger = NULL; } } return defender; } static Boolean better_terse( TerseTriangulation *challenger, TerseTriangulation *defender) { int i; if (challenger->num_tetrahedra != defender->num_tetrahedra) uFatalError("better_terse", "terse_triangulation"); for (i = 0; i < 2*challenger->num_tetrahedra; i++) { if (challenger->glues_to_old_tet[i] == FALSE && defender->glues_to_old_tet[i] == TRUE) return TRUE; if (challenger->glues_to_old_tet[i] == TRUE && defender->glues_to_old_tet[i] == FALSE) return FALSE; } for (i = 0; i < challenger->num_tetrahedra + 1; i++) { if (challenger->which_old_tet[i] < defender->which_old_tet[i]) return TRUE; if (challenger->which_old_tet[i] > defender->which_old_tet[i]) return FALSE; } for (i = 0; i < challenger->num_tetrahedra + 1; i++) { if (challenger->which_gluing[i] < defender->which_gluing[i]) return TRUE; if (challenger->which_gluing[i] > defender->which_gluing[i]) return FALSE; } return FALSE; /* challenger and defender are identical, except perhaps CS */ } TerseTriangulation *tri_to_terse( Triangulation *manifold) { /* * Pick an arbitrary base tetrahedron. * * Note: It is essential that the convert_tri_to_terse_indices and * convert_terse_to_tri_indices Permutations be orientation_preserving. * Together with the properties of terse_to_tri() and orient(), this * insures that when an oriented Triangulation is converted to a * TerseTriangulation and then back to a regular Triangulation, the * Orientation will be preserved (the proof is that all functions * preserve the Orientation of the base Tetrahedron). */ return tri_to_terse_with_base( manifold, manifold->tet_list_begin.next, IDENTITY_PERMUTATION); } TerseTriangulation *tri_to_terse_with_base( Triangulation *manifold, Tetrahedron *base_tetrahedron, Permutation base_permutation) { TerseTriangulation *tt; int count_glues_to_old_tet, count_which_old_tet, count_which_gluing, tet_count, tet_index; Tetrahedron **tet_list; FaceIndex terse_f, tri_f, nbr_terse_f, nbr_tri_f; Tetrahedron *tet, *nbr; /* * We assume the user wants to compress the complete manifold. * If Dehn fillings are present, something has gone wrong. * (This code could easily call fill_reasonable_cusps() * to handle partially filled manifolds, but at present * tri_to_terse() isn't used by the standard UI at all, * so I won't both making modifications until the need arises.) */ if (all_cusps_are_complete(manifold) == FALSE) uFatalError("tri_to_terse", "terse_triangulation"); /* * Attach an Extra field to each old Tetrahedron to keep * track of its role in the TerseTriangulation. */ attach_extra(manifold); /* * Initialize the Extra fields to show that initially no * Tetrahedra are in use. */ initialize_extra(manifold); /* * Allocate space for the TerseTriangulation. */ tt = alloc_terse(manifold->num_tetrahedra); /* * Set the number of Tetrahedra. */ tt->num_tetrahedra = manifold->num_tetrahedra; /* * Set the Chern-Simons invariant, if it's present. * (Note that this assumes the current value is that of the * complete structure. I.e. no Dehn fillings are present.) */ tt->CS_is_present = manifold->CS_value_is_known; tt->CS_value = manifold->CS_value[ultimate]; /* * Keep track of how many entries have been written into * each of the TerseTriangulation's arrays. */ count_glues_to_old_tet = 0; count_which_old_tet = 0; count_which_gluing = 0; /* * Keep track of how many Tetrahedra have been * incorporated into the TerseTriangulation. */ tet_count = 0; /* * Keep an array which tells us where to find the Tetrahedra which * have been incorporated into the TerseTriangulation. The array * is indexed by a Tetrahedron's index in the TerseTriangulation, * not the original Triangulation. tet_count tells the number of * elements presently on the array. */ tet_list = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); /* * Strictly speaking it shouldn't be necessary, but as a guard * against errors let's initialize the tet_list to all NULL's. */ for (tet_index = 0; tet_index < manifold->num_tetrahedra; tet_index++) tet_list[tet_index] = NULL; /* * Initialize the base Tetrahedron. */ base_tetrahedron->extra->in_use = TRUE; base_tetrahedron->extra->index = 0; base_tetrahedron->extra->convert_tri_to_terse_indices = base_permutation; base_tetrahedron->extra->convert_terse_to_tri_indices = inverse_permutation[base_permutation]; tet_list[tet_count++] = base_tetrahedron; /* * Go through the faces of the Tetrahedra on the tet_list, * noting where each is glued and creating the TerseTriangulation. * Don't worry that the tet_list initially contains only one element. * The connectedness of the manifold implies that the remaining * Tetrahedra will all arrive on time. */ for (tet_index = 0; tet_index < manifold->num_tetrahedra; tet_index++) { /* * Dereference the Tetrahedron under consideration, * and do a quick error check. */ tet = tet_list[tet_index]; if (tet == NULL || tet->extra->in_use == FALSE) uFatalError("tri_to_terse", "terse_triangulation"); /* * Consider each face, in order. */ for (terse_f = 0; terse_f < 4; terse_f++) { /* * If this face is already glued, do nothing. */ if (tet->extra->face_is_glued[terse_f] == TRUE) continue; /* * Otherwise, see what's it should be glued to. */ tri_f = EVALUATE(tet->extra->convert_terse_to_tri_indices, terse_f); nbr = tet->neighbor[tri_f]; /* * Is the neighbor already part of the TerseTriangulation? */ if (nbr->extra->in_use == TRUE) { /* * The neighbor is already part of the TerseTriangulation. */ /* * Make the appropriate entries in the TerseTriangulation. * (Note that compose_permutations() composes right to * left, so that first we convert from the TerseTriangulation * indices on tet to the standard Triangulation indices, * then we do the gluing to get the corresponding * standard Triangulation indices on nbr, then we convert * to the TerseTriangulation indices on nbr.) */ tt->glues_to_old_tet[count_glues_to_old_tet++] = TRUE; tt->which_old_tet[count_which_old_tet++] = nbr->extra->index; tt->which_gluing[count_which_gluing++] = compose_permutations( compose_permutations( nbr->extra->convert_tri_to_terse_indices, tet->gluing[tri_f] ), tet->extra->convert_terse_to_tri_indices ); /* * Make the appropriate entries in the Extra fields. */ nbr_tri_f = EVALUATE(tet->gluing[tri_f], tri_f); nbr_terse_f = EVALUATE(nbr->extra->convert_tri_to_terse_indices, nbr_tri_f); tet->extra->face_is_glued[terse_f] = TRUE; nbr->extra->face_is_glued[nbr_terse_f] = TRUE; } else { /* * The neighbor is not yet part of the TerseTriangulation. */ tt->glues_to_old_tet[count_glues_to_old_tet++] = FALSE; /* * Set up nbr's Extra fields. * * We must define nbr->extra->convert_terse_to_tri_indices * so that the following diagram commutes: * * tet nbr * tri .------gluing------>. * ^ ^ * | | * tet->extra-> nbr->extra-> * terse_to_tri terse_to_tri * | | * | | * terse .-----identity----->. */ nbr->extra->in_use = TRUE; nbr->extra->index = tet_count; nbr->extra->convert_terse_to_tri_indices = compose_permutations( tet->gluing[tri_f], tet->extra->convert_terse_to_tri_indices ); nbr->extra->convert_tri_to_terse_indices = inverse_permutation[ nbr->extra->convert_terse_to_tri_indices]; /* * initialize_extra() has already initialized the * nbr->extra->face_is_glued[] fields. */ /* * Enter nbr on the tet_list. * Note that tet_count is incremented after setting * nbr->extra->index above. */ tet_list[tet_count++] = nbr; /* * Record that these faces have been glued. * Because the gluing is the identity relative to * the TerseTriangulation, nbr_terse_f == terse_f. */ tet->extra->face_is_glued[terse_f] = TRUE; nbr->extra->face_is_glued[terse_f] = TRUE; } } } /* * Free the tet_list. */ my_free(tet_list); /* * Free the Extra fields. */ free_extra(manifold); /* * As a guard against errors, make sure * the array lengths came out right. */ if (count_glues_to_old_tet != 2 * manifold->num_tetrahedra || count_which_old_tet != manifold->num_tetrahedra + 1 || count_which_gluing != manifold->num_tetrahedra + 1 || tet_count != manifold->num_tetrahedra) uFatalError("tri_to_terse", "terse_triangulation"); return tt; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "terse_triangulation"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void initialize_extra( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Really only the in_use and face_is_glued[] fields need to be * initialized, but better to be extra safe and do 'em all. */ tet->extra->in_use = FALSE; tet->extra->index = -1; tet->extra->convert_tri_to_terse_indices = 0x00; tet->extra->convert_terse_to_tri_indices = 0x00; for (f = 0; f < 4; f++) tet->extra->face_is_glued[f] = FALSE; } } TerseTriangulation *alloc_terse( int num_tetrahedra) { /* * The global optimizer screws up here. * See the file "notes 2.1" for details. * The following pragma turns the optimizer off for this function only. */ #ifdef __SC__ #if ( __SC__ >= 0x800 ) #pragma options(!global_optimizer); #endif #endif TerseTriangulation *tt; tt = NEW_STRUCT(TerseTriangulation); tt->glues_to_old_tet = NEW_ARRAY(2 * num_tetrahedra, Boolean); tt->which_old_tet = NEW_ARRAY(num_tetrahedra + 1, int); tt->which_gluing = NEW_ARRAY(num_tetrahedra + 1, Permutation); return tt; } Triangulation *terse_to_tri( TerseTriangulation *tt) { Triangulation *manifold; /* * Begin by setting up the bare bones Triangulation, * with only the neighbor and gluing fields set. */ manifold = bare_bones_triangulation(tt); /* * Attempt to orient the manifold. Note that * * (1) orient() works fine when only the neighbor * and gluing fields are set. * * (2) tri_to_terse() followed by terse_to_tri() will yield the * manifold's original orientation. This is because * * (A) tri_to_terse() chooses the base tetrahedron to be * manifold->tet_list_begin.next, with its original * VertexIndices (any orientation_preserving Permutation * would do). (The VertexIndices on all other Tetrahedra * may change -- sometimes in orientation_reversing ways -- * in passing to the TerseTriangulation, but that's OK.) * * (B) bare_bones_triangulation preserves all VertexIndices * in passing from the TerseTriangulation to the * regular Triangulation. * * (B) orient() preserves the Vertex indices on * manifold->tet_list_begin.next. (The VertexIndices on * all other Tetrahedra may change.) * * Because manifold->tet_list_begin.next has its original * VertexIndices intact, we know the orientation has not changed. */ orient(manifold); /* * Create the Cusps. */ create_cusps(manifold); /* * Create and orient the EdgeClasses. */ create_edge_classes(manifold); orient_edge_classes(manifold); /* * Install an arbitrary set of peripheral curves. * * Notes: * * (1) After the hyperbolic structure is in place we'll * replace these arbitrary curves with a canonical set. * * (2) We call peripheral_curves() after orient(), so that * if the manifold is orientable the peripheral curves * will respect the standard orientation convention. * * (3) peripheral_curves() will determine the CuspTopology of * each Cusp, and write it into the cusp->topology field. */ peripheral_curves(manifold); /* * Count the total number of Cusps, and also the number * with torus and Klein bottle CuspTopology. */ count_cusps(manifold); /* * Attempt to compute a hyperbolic structure. */ find_complete_hyperbolic_structure(manifold); /* * Install the (almost) canonical set of generators. */ install_shortest_bases(manifold); /* * Install the Chern-Simons invariant, if one is present. */ if (tt->CS_is_present) set_CS_value(manifold, tt->CS_value); return manifold; } static Triangulation *bare_bones_triangulation( TerseTriangulation *tt) { Triangulation *manifold; int count_glues_to_old_tet, count_which_old_tet, count_which_gluing, tet_count, tet_index; Tetrahedron **tet_list; FaceIndex f, nbr_f; Tetrahedron *tet, *nbr; Permutation gluing; int i; /* * Set up the header structure. */ manifold = NEW_STRUCT(Triangulation); initialize_triangulation(manifold); /* * Set the manifold's name to DAFAULT_NAME. */ manifold->name = NEW_ARRAY(strlen(DAFAULT_NAME) + 1, char); strcpy(manifold->name, DAFAULT_NAME); /* * Record the number of Tetrahedra. */ manifold->num_tetrahedra = tt->num_tetrahedra; /* * Allocate and initialize the Tetrahedra, and temporarily * record their addresses in the tet_list. */ tet_list = NEW_ARRAY(tt->num_tetrahedra, Tetrahedron *); for (i = 0; i < tt->num_tetrahedra; i++) { tet_list[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet_list[i]); tet_list[i]->index = i; INSERT_BEFORE(tet_list[i], &manifold->tet_list_end); } /* * Keep track of how many entries have been read from * each of the TerseTriangulation's arrays. */ count_glues_to_old_tet = 0; count_which_old_tet = 0; count_which_gluing = 0; /* * Initially we imagine a single Tetrahedron (namely tet_list[0]) * to be "worked into the system". */ tet_count = 1; /* * Go down the list, setting the neighbors and gluings * as specified by the TerseTriangulation. * * This code is conceptually simpler than the corresponding * code in tri_to_terse, because here the regular Triangulation * indexing system coincides with the TerseTriangulation * indexing system. */ for (tet_index = 0; tet_index < manifold->num_tetrahedra; tet_index++) for (f = 0; f < 4; f++) if (tet_list[tet_index]->neighbor[f] == NULL) { tet = tet_list[tet_index]; if (tt->glues_to_old_tet[count_glues_to_old_tet++] == TRUE) { nbr = tet_list[tt->which_old_tet[count_which_old_tet++]]; gluing = tt->which_gluing[count_which_gluing++]; nbr_f = EVALUATE(gluing, f); } else { nbr = tet_list[tet_count++]; gluing = IDENTITY_PERMUTATION; nbr_f = f; } tet->neighbor[f] = nbr; tet->gluing[f] = gluing; nbr->neighbor[nbr_f] = tet; nbr->gluing[nbr_f] = inverse_permutation[gluing]; } /* * Free the Tetrahedron address list. */ my_free(tet_list); /* * As a guard against errors, make sure * the array lengths came out right. */ if (count_glues_to_old_tet != 2 * manifold->num_tetrahedra || count_which_old_tet != manifold->num_tetrahedra + 1 || count_which_gluing != manifold->num_tetrahedra + 1 || tet_count != manifold->num_tetrahedra) uFatalError("terse_to_tri", "terse_triangulation"); return manifold; } void free_terse_triangulation( TerseTriangulation *tt) { my_free(tt->glues_to_old_tet); my_free(tt->which_old_tet); my_free(tt->which_gluing); my_free(tt); } snappea-3.0d3/SnapPeaKernel/code/tersest_triangulation.c0100444000175000017500000001243106742675502021500 0ustar babbab/* * tersest_triangulation.c * * This file provides the functions * * void terse_to_tersest( TerseTriangulation *terse, * TersestTriangulation tersest); * * void tersest_to_terse( TersestTriangulation tersest, * TerseTriangulation **terse); * * void tri_to_tersest( Triangulation *manifold, * TersestTriangulation tersest); * * void tersest_to_tri( TersestTriangulation tersest, * Triangulation **manifold); * * terse_to_tersest() and tersest_to_terse() convert back and forth * between TerseTriangulations and TersestTriangulations. * tersest_to_terse() allocates space for its result, but * terse_to_tersest() does not. * * tri_to_tersest() is the composition of tri_to_terse() and terse_to_tersest(). * tersest_to_tri() is the composition of tersest_to_terse() and terse_to_tri(). */ #include "kernel.h" void terse_to_tersest( TerseTriangulation *terse, TersestTriangulation tersest) { int i, j; double cs, integer_part; /* * The TersestTriangulation is suitable only for Triangulations * with 7 or fewer Tetrahedra. */ if (terse->num_tetrahedra > 7) uFatalError("terse_to_tersest", "tersest_triangulation"); /* * Compress the glues_to_old_tet[] array. */ for (i = 0; i < 2; i++) { tersest[i] = 0; for (j = 8; --j >= 0; ) { tersest[i] <<= 1; if (8*i + j < 2 * terse->num_tetrahedra) tersest[i] |= terse->glues_to_old_tet[8*i + j]; } } /* * Compress which_old_tet[] and which_gluing[] */ for (i = 0; i < terse->num_tetrahedra + 1; i++) { tersest[i+2] = terse->which_old_tet[i]; tersest[i+2] <<= 5; tersest[i+2] |= index_by_permutation[terse->which_gluing[i]]; } /* * Set unused bytes to 0. */ for (i = terse->num_tetrahedra + 1; i < 8; i++) tersest[i+2] = 0; /* * Compress the Chern-Simons invariant, if present. */ if (terse->CS_is_present) { /* * Set the highest-order bit in byte 1 to TRUE. */ tersest[1] |= 0x80; /* * Copy the given value. */ cs = terse->CS_value; /* * Make sure it's in the range [-1/4, +1/4). */ while (cs < -0.25) cs += 0.5; while (cs >= 0.25) cs -= 0.5; /* * cs = 2*cs + 1/2 places cs in the range [0, 1) */ cs = 2*cs + 0.5; /* * Peel off the significant binary digits 8 at a time * and stash them in tersest[10] through tersest[17]. */ for (i = 0; i < 8; i++) { /* * Do a "floating point bitshift". */ cs *= (double) 0x100; /* * Extract the integer part, and replace cs with * the fractional part. */ cs = modf(cs, &integer_part); /* * The integer part should be in the range [0, 256). * Store it as a 1-byte unsigned integer in tersest[10 + i]. */ tersest[10 + i] = (unsigned char) integer_part; } } else { /* * Set the highest-order bit in byte 1 to FALSE. */ tersest[1] &= 0x7F; for (i = 0; i < 8; i++) tersest[10 + i] = 0; } } void tersest_to_terse( TersestTriangulation tersest, TerseTriangulation **terse) { int i, j, byte, glue_data[16], num_tetrahedra, num_free_faces, bits_read; double cs; /* * Extract the bits for the glues_to_old_tet[] array. We don't yet * know how many are meaningful, so get all 16 bits, and then use them * to deduce the number of Tetrahedra. (Yes, I know the last two can't * possibly be meaningful, but reading them keeps the code simple.) */ for (i = 0; i < 2; i++) { byte = tersest[i]; for (j = 0; j < 8; j++) { glue_data[8*i + j] = byte & 0x01; byte >>= 1; } } /* * Deduce the number of Tetrahedra. */ num_tetrahedra = 1; num_free_faces = 4; bits_read = 0; while (num_free_faces > 0) if (glue_data[bits_read++] == TRUE) { num_free_faces -= 2; } else { num_tetrahedra++; num_free_faces += 2; } if (bits_read != 2 * num_tetrahedra || num_tetrahedra > 7) uFatalError("tersest_to_terse", "tersest_triangulation"); /* * Allocate space for the TerseTriangulation. */ (*terse) = alloc_terse(num_tetrahedra); /* * Set num_tetrahedra. */ (*terse)->num_tetrahedra = num_tetrahedra; /* * Set glues_to_old_tet[]. */ for (i = 0; i < 2 * num_tetrahedra; i++) (*terse)->glues_to_old_tet[i] = glue_data[i]; /* * Set which_old_tet[] and which_gluing[]. */ for (i = 0; i < num_tetrahedra + 1; i++) { (*terse)->which_old_tet[i] = tersest[2 + i] >> 5; (*terse)->which_gluing[i] = permutation_by_index[tersest[2 + i] & 0x1F]; } /* * Set the Chern-Simons invariant, if present. * * These steps reverse the corresponding steps documented * in terse_to_tersest() above. */ if (tersest[1] & 0x80) { (*terse)->CS_is_present = TRUE; cs = 0.0; for (i = 8; --i >= 0; ) { cs += (double) tersest[10 + i]; cs /= (double) 0x100; } cs = (cs - 0.5) / 2.0; (*terse)->CS_value = cs; } else { (*terse)->CS_is_present = FALSE; (*terse)->CS_value = 0.0; } } void tri_to_tersest( Triangulation *manifold, TersestTriangulation tersest) { TerseTriangulation *terse; terse = tri_to_terse(manifold); terse_to_tersest(terse, tersest); free_terse_triangulation(terse); } void tersest_to_tri( TersestTriangulation tersest, Triangulation **manifold) { TerseTriangulation *terse; tersest_to_terse(tersest, &terse); *manifold = terse_to_tri(terse); free_terse_triangulation(terse); } snappea-3.0d3/SnapPeaKernel/code/tet_shapes.c0100444000175000017500000001256506742675502017216 0ustar babbab/* * tet_shapes.c * * This file contains the following low-level functions for * working with TetShapes. * * void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, * Tetrahedron *tet1, EdgeIndex e1, * Tetrahedron *tet2, EdgeIndex e2) * * Boolean angles_sum_to_zero( Tetrahedron *tet0, EdgeIndex e0, * Tetrahedron *tet1, EdgeIndex e1); * * void compute_remaining_angles(Tetrahedron *tet, EdgeIndex e); * * * add_edge_angles() adds the edge angles at edge e0 of tet0 * to the corresponding angles at edge e1 of tet1, and writes * the results to edge e2 of tet2. It pays careful attention * to the edge_orientations. Note that even though opposite * edges of a Tetrahedron have equal angles, they needn't have * the same edge_orientation, so you should pass an actual * EdgeIndex in the range 0-5, not merely a quasi-equivalent * index in the range 0-2. The edge angle of the sum will be * in the range [(-1/2) pi, (3/2) pi], regardless of the angles * of the summands. * * angles_sum_to_zero() returns TRUE iff one of the angles * (shape[complete]->cwl[ultimate] or shape[filled]->cwl[ultimate]) * at edge e0 of tet0 cancels the corresponding angle at edge e1 * of tet1 (mod 2 pi i). Accounts for edge_orientations. * * compute_remaining_angles() assumes the angle at edge e is * correct, and computes the remaining angles in terms of it. * The EdgeIndex may be given in either the range 0-5 or the * range 0-2. The arguments of the remaining angles will be * in the range [(-1/2) pi, (3/2) pi]. */ #include "kernel.h" /* * CANCELLATION_EPSILON says how close the logs of two complex * numbers must be to be considered equal. */ #define CANCELLATION_EPSILON 1e-4 static void add_tet_shapes( TetShape *ts0, EdgeIndex e30, Orientation eo0, TetShape *ts1, EdgeIndex e31, Orientation eo1, TetShape *ts2, EdgeIndex e32, Orientation eo2); static void add_complex_with_log( ComplexWithLog *cwl0, Orientation eo0, ComplexWithLog *cwl1, Orientation eo1, ComplexWithLog *cwl2, Orientation eo2); static Boolean logs_sum_to_zero( Complex summand0, Orientation eo0, Complex summand1, Orientation eo1); static void compute_cwl(ComplexWithLog cwl[3], EdgeIndex e); static void normalize_angle(double *angle); void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1, Tetrahedron *tet2, EdgeIndex e2) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ add_tet_shapes( tet0->shape[i], edge3[e0], tet0->edge_orientation[e0], tet1->shape[i], edge3[e1], tet1->edge_orientation[e1], tet2->shape[i], edge3[e2], tet2->edge_orientation[e2]); } static void add_tet_shapes( TetShape *ts0, EdgeIndex e30, Orientation eo0, TetShape *ts1, EdgeIndex e31, Orientation eo1, TetShape *ts2, EdgeIndex e32, Orientation eo2) { int i; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ add_complex_with_log( &ts0->cwl[i][e30], eo0, &ts1->cwl[i][e31], eo1, &ts2->cwl[i][e32], eo2); } static void add_complex_with_log( ComplexWithLog *cwl0, Orientation eo0, ComplexWithLog *cwl1, Orientation eo1, ComplexWithLog *cwl2, Orientation eo2) { /* * First compute the sum of the logs, then recover * the rectangular form. * * We do all computations relative to the Orientation * of the EdgeClass. So if a particular edge is seen * as left_handed by the EdgeClass, we must negate the * real part of the log of its complex angle. (Recall * that all all TetShapes are stored relative to the * right_handed Orientation of the Tetrahedron.) */ Complex summand0, summand1, sum; summand0 = cwl0->log; if (eo0 == left_handed) summand0.real = - summand0.real; summand1 = cwl1->log; if (eo1 == left_handed) summand1.real = - summand1.real; sum = complex_plus(summand0, summand1); if (eo2 == left_handed) sum.real = - sum.real; normalize_angle(&sum.imag); cwl2->log = sum; cwl2->rect = complex_exp(sum); } Boolean angles_sum_to_zero( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ if (logs_sum_to_zero( tet0->shape[i]->cwl[ultimate][edge3[e0]].log, tet0->edge_orientation[e0], tet1->shape[i]->cwl[ultimate][edge3[e1]].log, tet1->edge_orientation[e1])) return TRUE; return FALSE; } static Boolean logs_sum_to_zero( Complex summand0, Orientation eo0, Complex summand1, Orientation eo1) { Complex sum; if (eo0 != eo1) summand1.real = - summand1.real; sum = complex_plus(summand0, summand1); normalize_angle(&sum.imag); return (complex_modulus(sum) < CANCELLATION_EPSILON); } void compute_remaining_angles( Tetrahedron *tet, EdgeIndex e) { int i, j; for (i = 0; i < 2; i++) /* i = complete, filled */ for (j = 0; j < 2; j++) /* j = ultimate, penultimate */ compute_cwl(tet->shape[i]->cwl[j], edge3[e]); } static void compute_cwl( ComplexWithLog cwl[3], EdgeIndex e) { /* * Compute cwl[(e+1)%3] and cwl[(e+2)%3] in terms of cwl[e]. */ int i; for (i = 1; i < 3; i++) { cwl[(e+i)%3].rect = complex_div(One, complex_minus(One, cwl[(e+i-1)%3].rect)); cwl[(e+i)%3].log = complex_log(cwl[(e+i)%3].rect, PI_OVER_2); } } static void normalize_angle( double *angle) { /* * Normalize the angle to lie in the range [(-1/2) pi, (3/2) pi]. */ while (*angle > THREE_PI_OVER_2) *angle -= TWO_PI; while (*angle < - PI_OVER_2) *angle += TWO_PI; } snappea-3.0d3/SnapPeaKernel/code/tidy_peripheral_curves.c0100444000175000017500000000555206742675502021630 0ustar babbab/* * tidy_peripheral_curves.c * * * This file provides the function * * void tidy_peripheral_curves(Triangulation *manifold); * * which is used within the kernel to clean up a set of peripheral * curves. * * Functions which alter triangulations maintain a set of peripheral * curves which is technically correct, but suffers two faults. * A minor fault is that the peripheral curves wind around a lot, * and therefore create unnecessarily complicated cusp equations. * The more serious fault is that the curves may evolve trivial loops; * that is, a single meridian or longitude will have several components, * one of which is the correct curve while the others are homotopically * trivial loops. Such trivial loops introduce erroneous multiples * of 2pi into the computed holonomies, and thereby foul up the * computation of hyperbolic structures. * * The function tidy_peripheral_curves() * * (1) makes a copy of the existing peripheral curves, * (2) calls peripheral_curves() to create a nice set of * peripheral curves, and * (3) expresses the original curves as linear combinations * of the nice curves. * * The result is an "efficient" set of curves, with no trivial * loops. */ #include "kernel.h" /* * scratch_curves[0] will store the original peripheral curves. * scratch_curves[1] will store the nice peripheral curves. */ #define original_curves 0 #define nice_curves 1 static void compute_new_curves(Triangulation *manifold); void tidy_peripheral_curves( Triangulation *manifold) { /* * Copy the original peripheral curves to the * scratch_curve[original_curves] fields of the Tetrahedra. */ copy_curves_to_scratch(manifold, original_curves, TRUE); /* * Compute a nice set of peripheral curves. */ peripheral_curves(manifold); /* * Copy the nice peripheral curves to the * scratch_curve[nice_curves] fields of the Tetrahedra. */ copy_curves_to_scratch(manifold, nice_curves, FALSE); /* * Compute the intersection numbers of the original curves * with the nice curves. */ compute_intersection_numbers(manifold); /* * Compute the new curves as linear combinations of the * nice curves. */ compute_new_curves(manifold); } static void compute_new_curves( Triangulation *manifold) { Tetrahedron *tet; int h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (h = 0; h < 2; h++) /* which curve */ for (i = 0; i < 2; i++) /* which sheet */ for (j = 0; j < 4; j++) /* which vertex */ for (k = 0; k < 4; k++) /* which side of that vertex */ tet->curve[h][i][j][k] = (j == k) ? 0 : - tet->cusp[j]->intersection_number[h][L] * tet->scratch_curve[nice_curves][M][i][j][k] + tet->cusp[j]->intersection_number[h][M] * tet->scratch_curve[nice_curves][L][i][j][k]; } snappea-3.0d3/SnapPeaKernel/code/transcendentals.c0100444000175000017500000000321407053772047020234 0ustar babbab/* * transcendentals.c * * What is the value of acos(1.0000000001)? * On the Mac you get the intended answer (0.0), but unix returns NaN. * To guard against this sort of problem, the SnapPea kernel uses * * double safe_acos(double x); * double safe_asin(double x); * double safe_sqrt(double x); * * Incredibly enough, the standard ANSI libraries omit the * inverse hyperbolic trig functions entirely. * * double arcsinh(double x); * double arccosh(double x); * * Many (but not all) standard libraries now provide asinh() and acosh(). * I've changed the names of my homemade versions to arcsinh() and arccosh() * to avoid conflicts. 2000/02/20 JRW */ #include "kernel.h" #define ERROR_EPSILON 1e-3 double safe_acos(double x) { if (x > 1.0) { if (x > 1.0 + ERROR_EPSILON) uFatalError("safe_acos", "transcendentals"); x = 1.0; } if (x < -1.0) { if (x < -(1.0 + ERROR_EPSILON)) uFatalError("safe_acos", "transcendentals"); x = -1.0; } return acos(x); } double safe_asin(double x) { if (x > 1.0) { if (x > 1.0 + ERROR_EPSILON) uFatalError("safe_asin", "transcendentals"); x = 1.0; } if (x < -1.0) { if (x < -(1.0 + ERROR_EPSILON)) uFatalError("safe_asin", "transcendentals"); x = -1.0; } return asin(x); } double safe_sqrt(double x) { if (x < 0.0) { if (x < -ERROR_EPSILON) uFatalError("safe_sqrt", "transcendentals"); x = 0.0; } return sqrt(x); } double arcsinh( double x) { return log(x + sqrt(x*x + 1.0)); } double arccosh( double x) { if (x < 1.0) { if (x < 1.0 - ERROR_EPSILON) uFatalError("arccosh", "transcendentals"); x = 1.0; } return log(x + sqrt(x*x - 1.0)); } snappea-3.0d3/SnapPeaKernel/code/triangulations.c0100444000175000017500000006630707041647214020115 0ustar babbab/* * triangulation.c * * This file contains the following functions which the kernel * provides for the UI: * * void data_to_triangulation( TriangulationData *data, * Triangulation **manifold_ptr); * void triangulation_to_data( TriangulationData **data_ptr, * Triangulation *manifold); * void free_triangulation_data(TriangulationData *data); * void free_triangulation( Triangulation *manifold); * void copy_triangulation( Triangulation *source, * Triangulation **destination); * * Their use is described in SnapPea.h. * * This file also provides the functions * * void initialize_triangulation(Triangulation *manifold); * void initialize_tetrahedron(Tetrahedron *tet); * void initialize_cusp(Cusp *cusp); * void initialize_edge_class(EdgeClass *edge_class); * * which kernel functions use to do generic initializations. Much of * what they do is not really necessary, but it seems like a good idea * to at least write NULL into pointers which have not been set. * * This file also includes * * FuncResult check_Euler_characteristic_of_boundary(Triangulation *manifold); * * which returns func_OK if the Euler characteristic of the total * boundary of the manifold is zero. Otherwise it returns func_failed. * * Furthermore, this file includes * * void number_the_tetrahedra(Triangulation *manifold); * * which sets each Tetrahedron's index field equal to its position in * the linked list. Indices range from 0 to (num_tetrahedra - 1). * * In addition, we have * * void free_tetrahedron(Tetrahedron *tet); * * which frees a Tetrahedron and all attached data structures, but does NOT * remove the Tetrahedron from any doubly linked list it may be on, and * * void clear_shape_history(Tetrahedron *tet); * void copy_shape_history(ShapeInversion *source, ShapeInversion **dest); * void clear_one_shape_history(Tetrahedron *tet, FillingStatus which_history); * * which do what you'd expect (please see the code for details). */ #include "kernel.h" #include /* for sprintf() in check_neighbors_and_gluings() */ static void check_neighbors_and_gluings(Triangulation *manifold); static int count_the_edge_classes(Triangulation *manifold); void data_to_triangulation( TriangulationData *data, Triangulation **manifold_ptr) { /* * We assume the UI has done some basic error checking * on the data, so we don't repeat it here. */ Triangulation *manifold; Tetrahedron **tet_array; Cusp **cusp_array; Boolean cusps_are_given; int i, j, k, l, m; Boolean all_peripheral_curves_are_zero, finite_vertices_are_present; /* * Initialize *manifold_ptr to NULL. * We'll do all our work with manifold, and then copy * manifold to *manifold_ptr at the very end. */ *manifold_ptr = NULL; /* * Allocate and initialize the Triangulation structure. */ manifold = NEW_STRUCT(Triangulation); initialize_triangulation(manifold); /* * Allocate and copy the name. */ manifold->name = NEW_ARRAY(strlen(data->name) + 1, char); strcpy(manifold->name, data->name); /* * Set up the global information. * * The hyperbolic structure is included in the file only for * human readers; here we recompute it from scratch. */ manifold->num_tetrahedra = data->num_tetrahedra; manifold->solution_type[complete] = not_attempted; manifold->solution_type[ filled ] = not_attempted; manifold->orientability = data->orientability; manifold->num_or_cusps = data->num_or_cusps; manifold->num_nonor_cusps = data->num_nonor_cusps; manifold->num_cusps = manifold->num_or_cusps + manifold->num_nonor_cusps; /* * Allocate the Tetrahedra. * Keep pointers to them on a temporary array, so we can * find them by their indices. */ tet_array = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); for (i = 0; i < manifold->num_tetrahedra; i++) { tet_array[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet_array[i]); INSERT_BEFORE(tet_array[i], &manifold->tet_list_end); } /* * If num_or_cusps or num_nonor_cusps is nonzero, allocate the Cusps. * Keep pointers to them on temporary arrays, so we can find them * by their indices. * Otherwise we will create arbitrary Cusps later. */ cusps_are_given = (data->num_or_cusps != 0) || (data->num_nonor_cusps != 0); if (cusps_are_given == TRUE) { cusp_array = NEW_ARRAY(manifold->num_cusps, Cusp *); for (i = 0; i < manifold->num_cusps; i++) { cusp_array[i] = NEW_STRUCT(Cusp); initialize_cusp(cusp_array[i]); INSERT_BEFORE(cusp_array[i], &manifold->cusp_list_end); } } else cusp_array = NULL; /* * Set up the Tetrahedra. */ all_peripheral_curves_are_zero = TRUE; finite_vertices_are_present = FALSE; for (i = 0; i < manifold->num_tetrahedra; i++) { for (j = 0; j < 4; j++) tet_array[i]->neighbor[j] = tet_array[data->tetrahedron_data[i].neighbor_index[j]]; for (j = 0; j < 4; j++) tet_array[i]->gluing[j] = CREATE_PERMUTATION( 0, data->tetrahedron_data[i].gluing[j][0], 1, data->tetrahedron_data[i].gluing[j][1], 2, data->tetrahedron_data[i].gluing[j][2], 3, data->tetrahedron_data[i].gluing[j][3]); if (cusps_are_given == TRUE) { for (j = 0; j < 4; j++) { if (data->tetrahedron_data[i].cusp_index[j] >= 0) /* assign a real cusp */ tet_array[i]->cusp[j] = cusp_array[data->tetrahedron_data[i].cusp_index[j]]; else { /* mark a finite vertex with NULL */ tet_array[i]->cusp[j] = NULL; finite_vertices_are_present = TRUE; } } for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) for (m = 0; m < 4; m++) { tet_array[i]->curve[j][k][l][m] = data->tetrahedron_data[i].curve[j][k][l][m]; if (data->tetrahedron_data[i].curve[j][k][l][m] != 0) all_peripheral_curves_are_zero = FALSE; } } } /* * 97/12/8 Check that the neighbors and gluings are consistent. * For SnapPea's own files this isn't necessary, but it's a big * help for people who write files by hand. It catches the most * obvious errors and provides a useful diagnosis (as opposed to, * say, having the program hang when inconsistent neighbors and/or * gluings send create_edge_classes() into an infinite loop). * Even in the typical case of reading SnapPea's own files, * check_neighbors_and_gluings() is very quick. */ check_neighbors_and_gluings(manifold); /* * Set up the EdgeClasses. */ create_edge_classes(manifold); orient_edge_classes(manifold); /* * If the Cusps were specified explicitly, copy in the data. * Otherwise create arbitrary Cusps now. */ if (cusps_are_given == TRUE) { for (i = 0; i < manifold->num_cusps; i++) { cusp_array[i]->topology = data->cusp_data[i].topology; cusp_array[i]->m = data->cusp_data[i].m; cusp_array[i]->l = data->cusp_data[i].l; cusp_array[i]->is_complete = (data->cusp_data[i].m == 0.0 && data->cusp_data[i].l == 0.0); cusp_array[i]->index = i; } /* * If finite vertices are present they will be marked with NULL. * Assign Cusp structures. */ if (finite_vertices_are_present == TRUE) create_fake_cusps(manifold); } else { create_cusps(manifold); finite_vertices_are_present = mark_fake_cusps(manifold); } /* * Provide peripheral curves if necessary. * This automatically records the CuspTopologies. * (Note: all_peripheral_curves_are_zero is TRUE whenever * cusps_are_given is FALSE.) */ if (all_peripheral_curves_are_zero == TRUE) peripheral_curves(manifold); /* * If the given triangulation includes finite vertices, remove them. */ if (finite_vertices_are_present == TRUE) remove_finite_vertices(manifold); /* * Count the Cusps if necessary, noting how many have each topology. */ if (cusps_are_given == FALSE) count_cusps(manifold); /* * Free the temporary arrays. */ my_free(tet_array); if (cusp_array != NULL) my_free(cusp_array); /* * Typically the manifold's orientability will already be known, * but if it isn't, try to orient it now. */ if (manifold->orientability == unknown_orientability) { orient(manifold); if (manifold->orientability == oriented_manifold) { if (all_peripheral_curves_are_zero == FALSE) uAcknowledge("Meridians may be reversed to insure right-handed {M,L} pairs."); fix_peripheral_orientations(manifold); } } /* * Compute the complete and filled hyperbolic structures. * * (The Dehn fillings should be nontrivial only if the data * provided the peripheral curves.) */ find_complete_hyperbolic_structure(manifold); do_Dehn_filling(manifold); /* * If we provided the basis and the manifold is hyperbolic, * replace it with a shortest basis. */ if (all_peripheral_curves_are_zero == TRUE && ( manifold->solution_type[complete] == geometric_solution || manifold->solution_type[complete] == nongeometric_solution)) install_shortest_bases(manifold); /* * If the Chern-Simons invariant is present, compute the fudge factor. * Then recompute the value from the fudge factor, to restore the * uncertainty between the ultimate and penultimate values. */ manifold->CS_value_is_known = data->CS_value_is_known; manifold->CS_value[ultimate] = data->CS_value; manifold->CS_value[penultimate] = data->CS_value; compute_CS_fudge_from_value(manifold); compute_CS_value_from_fudge(manifold); /* * Done. */ *manifold_ptr = manifold; } static void check_neighbors_and_gluings( Triangulation *manifold) { Tetrahedron *tet, *nbr; FaceIndex f, nbr_f; Permutation this_gluing; char scratch[256]; number_the_tetrahedra(manifold); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { this_gluing = tet->gluing[f]; nbr = tet->neighbor[f]; nbr_f = EVALUATE(this_gluing, f); if (nbr->neighbor[nbr_f] != tet) { sprintf(scratch, "inconsistent neighbor data, tet %d face %d to tet %d face %d", tet->index, f, nbr->index, nbr_f); uAcknowledge(scratch); uFatalError("check_neighbors_and_gluings", "triangulations"); } if (nbr->gluing[nbr_f] != inverse_permutation[this_gluing]) { sprintf(scratch, "inconsistent gluing data, tet %d face %d to tet %d face %d", tet->index, f, nbr->index, nbr_f); uAcknowledge(scratch); uFatalError("check_neighbors_and_gluings", "triangulations"); } } } void triangulation_to_data( Triangulation *manifold, TriangulationData **data_ptr) { /* * Allocate the TriangulationData and write in the data describing * the manifold. Set *data_ptr to point to the result. The UI * should call free_triangulation_data() when it's done with the * TriangulationData. */ TriangulationData *data; Cusp *cusp; Tetrahedron *tet; int i, j, k, l, m; *data_ptr = NULL; data = NEW_STRUCT(TriangulationData); if (manifold->name != NULL) { data->name = NEW_ARRAY(strlen(manifold->name) + 1, char); strcpy(data->name, manifold->name); } else data->name = NULL; data->num_tetrahedra = manifold->num_tetrahedra; data->solution_type = manifold->solution_type[filled]; data->volume = volume(manifold, NULL); data->orientability = manifold->orientability; data->CS_value_is_known = manifold->CS_value_is_known; data->num_or_cusps = manifold->num_or_cusps; data->num_nonor_cusps = manifold->num_nonor_cusps; if (manifold->CS_value_is_known == TRUE) data->CS_value = manifold->CS_value[ultimate]; data->cusp_data = NEW_ARRAY(manifold->num_cusps, CuspData); for (i = 0; i < manifold->num_cusps; i++) { cusp = find_cusp(manifold, i); data->cusp_data[i].topology = cusp->topology; data->cusp_data[i].m = cusp->m; data->cusp_data[i].l = cusp->l; } number_the_tetrahedra(manifold); data->tetrahedron_data = NEW_ARRAY(manifold->num_tetrahedra, TetrahedronData); for (tet = manifold->tet_list_begin.next, i = 0; tet != &manifold->tet_list_end; tet = tet->next, i++) { for (j = 0; j < 4; j++) data->tetrahedron_data[i].neighbor_index[j] = tet->neighbor[j]->index; for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) data->tetrahedron_data[i].gluing[j][k] = EVALUATE(tet->gluing[j], k); /* * All negative cusp indices (for finite vertices) map to -1. * This could be changed if desired, but at the moment it's * consistent with TriagulationFileFormat. */ for (j = 0; j < 4; j++) data->tetrahedron_data[i].cusp_index[j] = ((tet->cusp[j]->index >= 0) ? tet->cusp[j]->index : -1); for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) for (m = 0; m < 4; m++) data->tetrahedron_data[i].curve[j][k][l][m] = tet->curve[j][k][l][m]; data->tetrahedron_data[i].filled_shape = (tet->shape[filled] != NULL) ? tet->shape[filled]->cwl[ultimate][0].rect : Zero; } *data_ptr = data; } void free_triangulation_data( TriangulationData *data) { /* * If the kernel allocates a TriangulationData structure, * the kernel must free it. */ if (data != NULL) { if (data->name != NULL) my_free(data->name); if (data->cusp_data != NULL) my_free(data->cusp_data); if (data->tetrahedron_data != NULL) my_free(data->tetrahedron_data); my_free(data); } } void free_triangulation( Triangulation *manifold) { Tetrahedron *dead_tet; EdgeClass *dead_edge; Cusp *dead_cusp; if (manifold != NULL) { if (manifold->name != NULL) my_free(manifold->name); while (manifold->tet_list_begin.next != &manifold->tet_list_end) { dead_tet = manifold->tet_list_begin.next; REMOVE_NODE(dead_tet); free_tetrahedron(dead_tet); } while (manifold->edge_list_begin.next != &manifold->edge_list_end) { dead_edge = manifold->edge_list_begin.next; REMOVE_NODE(dead_edge); my_free(dead_edge); } while (manifold->cusp_list_begin.next != &manifold->cusp_list_end) { dead_cusp = manifold->cusp_list_begin.next; REMOVE_NODE(dead_cusp); my_free(dead_cusp); } my_free(manifold); } } void free_tetrahedron( Tetrahedron *tet) { /* * This function does NOT remove the Tetrahedron from * any doubly linked list it may be on, but does remove * all data structures attached to the Tetrahedron. */ int i; for (i = 0; i < 2; i++) /* i = complete, filled */ if (tet->shape[i] != NULL) my_free(tet->shape[i]); clear_shape_history(tet); if (tet->cross_section != NULL) my_free(tet->cross_section); if (tet->canonize_info != NULL) my_free(tet->canonize_info); if (tet->cusp_nbhd_position != NULL) my_free(tet->cusp_nbhd_position); if (tet->extra != NULL) my_free(tet->extra); my_free(tet); } void clear_shape_history( Tetrahedron *tet) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ clear_one_shape_history(tet, i); } void clear_one_shape_history( Tetrahedron *tet, FillingStatus which_history) /* filled or complete */ { ShapeInversion *dead_shape_inversion; while (tet->shape_history[which_history] != NULL) { dead_shape_inversion = tet->shape_history[which_history]; tet->shape_history[which_history] = tet->shape_history[which_history]->next; my_free(dead_shape_inversion); } } void copy_triangulation( Triangulation *source, Triangulation **destination_ptr) { Triangulation *destination; Tetrahedron **new_tet; EdgeClass **new_edge; Cusp **new_cusp; Tetrahedron *tet; EdgeClass *edge; Cusp *cusp; int num_edge_classes, min_cusp_index, max_cusp_index, num_potential_cusps, i, j; /* * Allocate space for the new Triangulation. */ *destination_ptr = NEW_STRUCT(Triangulation); /* * Give it a local name. */ destination = *destination_ptr; /* * Copy the global information. * In a moment we'll overwrite the fields involving pointers. */ *destination = *source; /* * Allocate space for the name, and copy it it. */ destination->name = NEW_ARRAY(strlen(source->name) + 1, char); strcpy(destination->name, source->name); /* * Initialize the doubly linked lists. */ destination->tet_list_begin.prev = NULL; destination->tet_list_begin.next = &destination->tet_list_end; destination->tet_list_end.prev = &destination->tet_list_begin; destination->tet_list_end.next = NULL; destination->edge_list_begin.prev = NULL; destination->edge_list_begin.next = &destination->edge_list_end; destination->edge_list_end.prev = &destination->edge_list_begin; destination->edge_list_end.next = NULL; destination->cusp_list_begin.prev = NULL; destination->cusp_list_begin.next = &destination->cusp_list_end; destination->cusp_list_end.prev = &destination->cusp_list_begin; destination->cusp_list_end.next = NULL; /* * Assign consecutive indices to source's Tetrahedra and EdgeClasses. * The Cusps will already be numbered. * * While we're at it, count the EdgeClasses. * If no finite vertices are present, the number of EdgeClasses * will equal the number of Tetrahedra. */ number_the_tetrahedra(source); number_the_edge_classes(source); num_edge_classes = count_the_edge_classes(source); /* * Find the largest and smallest Cusp indices. */ min_cusp_index = source->cusp_list_begin.next->index; max_cusp_index = source->cusp_list_begin.next->index; for ( cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) { if (cusp->index < min_cusp_index) min_cusp_index = cusp->index; if (cusp->index > max_cusp_index) max_cusp_index = cusp->index; } num_potential_cusps = max_cusp_index - min_cusp_index + 1; /* * Allocate the new Tetrahedra, EdgeClasses and Cusps. * For the Cusps we want to allow for the possibility * that there'll be gaps in the indexing scheme. */ new_tet = NEW_ARRAY(source->num_tetrahedra, Tetrahedron *); for (i = 0; i < source->num_tetrahedra; i++) new_tet[i] = NEW_STRUCT(Tetrahedron); new_edge = NEW_ARRAY(num_edge_classes, EdgeClass *); for (i = 0; i < num_edge_classes; i++) new_edge[i] = NEW_STRUCT(EdgeClass); new_cusp = NEW_ARRAY(num_potential_cusps, Cusp *); for (i = 0; i < num_potential_cusps; i++) new_cusp[i] = NULL; for (cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) new_cusp[cusp->index - min_cusp_index] = NEW_STRUCT(Cusp); /* * Copy the fields of each Tetrahedron in source * to the corresponding fields in new_tet[i]. */ for (tet = source->tet_list_begin.next, i = 0; tet != &source->tet_list_end; tet = tet->next, i++) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_tet[i] = *tet; for (j = 0; j < 4; j++) { new_tet[i]->neighbor[j] = new_tet[tet->neighbor[j]->index]; new_tet[i]->gluing[j] = tet->gluing[j]; new_tet[i]->cusp[j] = new_cusp[tet->cusp[j]->index - min_cusp_index]; } for (j = 0; j < 6; j++) new_tet[i]->edge_class[j] = new_edge[tet->edge_class[j]->index]; for (j = 0; j < 2; j++) /* j = complete, filled */ if (tet->shape[j] != NULL) { new_tet[i]->shape[j] = NEW_STRUCT(TetShape); *new_tet[i]->shape[j] = *tet->shape[j]; } for (j = 0; j < 2; j++) /* j = complete, filled */ copy_shape_history(tet->shape_history[j], &new_tet[i]->shape_history[j]); if (tet->cusp_nbhd_position != NULL) { new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); *new_tet[i]->cusp_nbhd_position = *tet->cusp_nbhd_position; } /* * Just to be safe. */ new_tet[i]->cross_section = NULL; new_tet[i]->canonize_info = NULL; new_tet[i]->extra = NULL; INSERT_BEFORE(new_tet[i], &destination->tet_list_end); } /* * Copy the fields of each EdgeClass in source * to the corresponding fields in new_edge[i]. */ for (edge = source->edge_list_begin.next, i = 0; edge != &source->edge_list_end; edge = edge->next, i++) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_edge[i] = *edge; new_edge[i]->incident_tet = new_tet[edge->incident_tet->index]; INSERT_BEFORE(new_edge[i], &destination->edge_list_end); } /* * Copy the fields of each Cusp in source * to the corresponding fields in new_cusp[i]. */ for (cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_cusp[cusp->index - min_cusp_index] = *cusp; INSERT_BEFORE(new_cusp[cusp->index - min_cusp_index], &destination->cusp_list_end); } /* * Free the arrays of pointers. */ my_free(new_tet); my_free(new_edge); my_free(new_cusp); } void copy_shape_history( ShapeInversion *source, ShapeInversion **dest) { while (source != NULL) { *dest = NEW_STRUCT(ShapeInversion); (*dest)->wide_angle = source->wide_angle; source = source->next; dest = &(*dest)->next; } *dest = NULL; } void initialize_triangulation( Triangulation *manifold) { manifold->name = NULL; manifold->num_tetrahedra = 0; manifold->solution_type[complete] = not_attempted; manifold->solution_type[filled] = not_attempted; manifold->orientability = unknown_orientability; manifold->num_cusps = 0; manifold->num_or_cusps = 0; manifold->num_nonor_cusps = 0; manifold->num_generators = 0; manifold->CS_value_is_known = FALSE; manifold->CS_fudge_is_known = FALSE; manifold->CS_value[ultimate] = 0.0; manifold->CS_value[penultimate] = 0.0; manifold->CS_fudge[ultimate] = 0.0; manifold->CS_fudge[penultimate] = 0.0; initialize_tetrahedron(&manifold->tet_list_begin); initialize_tetrahedron(&manifold->tet_list_end); manifold->tet_list_begin.prev = NULL; manifold->tet_list_begin.next = &manifold->tet_list_end; manifold->tet_list_end.prev = &manifold->tet_list_begin; manifold->tet_list_end.next = NULL; initialize_edge_class(&manifold->edge_list_begin); initialize_edge_class(&manifold->edge_list_end); manifold->edge_list_begin.prev = NULL; manifold->edge_list_begin.next = &manifold->edge_list_end; manifold->edge_list_end.prev = &manifold->edge_list_begin; manifold->edge_list_end.next = NULL; initialize_cusp(&manifold->cusp_list_begin); initialize_cusp(&manifold->cusp_list_end); manifold->cusp_list_begin.prev = NULL; manifold->cusp_list_begin.next = &manifold->cusp_list_end; manifold->cusp_list_end.prev = &manifold->cusp_list_begin; manifold->cusp_list_end.next = NULL; } void initialize_tetrahedron( Tetrahedron *tet) { int h, i, j, k; for (i = 0; i < 4; i++) { tet->neighbor[i] = NULL; tet->gluing[i] = 0; tet->cusp[i] = NULL; tet->generator_status[i] = unassigned_generator; tet->generator_index[i] = -1; tet->generator_parity[i] = -1; tet->corner[i] = Zero; tet->tilt[i] = -1.0e17; } for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->curve[h][i][j][k] = 0; for (i = 0; i < 6; i++) { tet->edge_class[i] = NULL; tet->edge_orientation[i] = -1; } for (i = 0; i < 2; i++) { tet->shape[i] = NULL; tet->shape_history[i] = NULL; } tet->generator_path = -2; tet->cross_section = NULL; tet->canonize_info = NULL; tet->cusp_nbhd_position = NULL; tet->extra = NULL; tet->prev = NULL; tet->next = NULL; } void initialize_cusp( Cusp *cusp) { cusp->topology = unknown_topology; cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; cusp->holonomy[ ultimate][M] = Zero; cusp->holonomy[ ultimate][L] = Zero; cusp->holonomy[penultimate][M] = Zero; cusp->holonomy[penultimate][L] = Zero; cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; cusp->cusp_shape[initial] = Zero; cusp->cusp_shape[current] = Zero; cusp->shape_precision[initial] = 0; cusp->shape_precision[current] = 0; cusp->index = 255; cusp->displacement = 0.0; cusp->displacement_exp = 1.0; cusp->is_finite = FALSE; cusp->matching_cusp = NULL; cusp->prev = NULL; cusp->next = NULL; } void initialize_edge_class( EdgeClass *edge_class) { edge_class->order = 0; edge_class->incident_tet = NULL; edge_class->incident_edge_index = -1; edge_class->num_incident_generators = -1; edge_class->complex_edge_equation = NULL; edge_class->real_edge_equation_re = NULL; edge_class->real_edge_equation_im = NULL; edge_class->prev = NULL; edge_class->next = NULL; } FuncResult check_Euler_characteristic_of_boundary( Triangulation *manifold) { int num_edges; EdgeClass *edge; /* * check_Euler_characteristic_of_boundary() returns * func_OK if the Euler characteristic of the total * boundary is zero, and returns func_failed otherwise. * Note that (so far) all functions which call * check_Euler_characteristic_of_boundary() are testing * whether curves on the (intended) boundary components * have been pinched off. In these cases the Euler * characteristic of the affected boundary component * will increase, but never decrease. So knowing that * the Euler characteristic of the total boundary is * zero implies that the Euler characteristic of each * component is also zero. * * Checking the Euler characteristic of the boundary * is trivially easy -- you just check whether the * number of EdgeClasses in the manifold equals the * number of Tetrahedra. Here's the proof: * * Let v, e and f be the number of vertices, edges, and * faces in the triangulation of the boundary. * Let E, F and T be the number of edges, faces and * tetrahedra in the ideal triangulation of the * manifold. * * v = 2E * e = 3F = 6T * f = 4T * * v - e + f = 2E - 6T + 4T = 2(E - T) * * So the boundary topology will be correct iff E == T. */ /* * Count the number of edge classes. */ num_edges = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) num_edges++; /* * Compare the number of edges to the number of tetrahedra. */ if (num_edges != manifold->num_tetrahedra) return func_failed; else return func_OK; } /* * number_the_tetrahedra() fills in the index field of the Tetrahedra * according to their order in the manifold's doubly-linked list. * Indices range from 0 to (num_tetrahedra - 1). */ void number_the_tetrahedra( Triangulation *manifold) { Tetrahedron *tet; int count; count = 0; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->index = count++; } /* * number_the_edge_classes() fills in the index field of the EdgeClasses * according to their order in the manifold's doubly-linked list. * Indices range from 0 to (number of EdgeClasses) - 1. */ void number_the_edge_classes( Triangulation *manifold) { EdgeClass *edge; int count; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) edge->index = count++; } static int count_the_edge_classes( Triangulation *manifold) { EdgeClass *edge; int count; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) count++; return count; } /* * compose_permutations() returns the composition of two permutations. * Permutations are composed right-to-left: the composition p1 o p0 * is what you get by first doing p0, then p1. */ Permutation compose_permutations( Permutation p1, Permutation p0) { Permutation result; int i; result = 0; for (i = 4; --i >= 0; ) { result <<= 2; result += EVALUATE(p1, EVALUATE(p0, i)); } return result; } snappea-3.0d3/SnapPeaKernel/code/two_bridge.c0100444000175000017500000013270606742675502017204 0ustar babbab/* * two_bridge.c * * This file provides the function * * void two_bridge(Triangulation *manifold, * Boolean *is_two_bridge, * long int *p, * long int *q); * * which determines whether a Triangulation is the canonical * triangulation of a two-bridge knot or link complement. If * it is, it finds a rational number p/q (defined below) which * specifies which two-bridge knot or link complement it is. * * When the program sets *is_two_bridge to TRUE, the * Triangulation *manifold is definitely a 2-bridge knot * or link complement, and is described by the fraction * (*p)/(*q). *p and *q are stored in long int's because * their size grows exponentially with the number of * crossings. * * When the program sets *is_two_bridge to FALSE, the * Triangulation *manifold is probably not a 2-bridge knot * or link complement, but we don't know this for sure * until Makoto Sakuma and I finish proving our conjecture * (more on this below). * * The fraction p/q is essentially the normal form defined by * Schubert in "Knoten mit zwei Bru"cken", Math. Zeitschrift, * Bd. 65, S. 133-170 (1956). For the purposes of this * program, however, we take the definition of p/q to be * the slope of a line on a square pillowcase, as in * Hatcher and Thurston's "Incompressible surfaces in * 2-bridge knot complements". The following illustration, * which is a crude reproduction of Hatcher & Thurston's * Figure 1, illustrates the 2-bridge knot 3/5 (because the * slope of the line is 3/5, after you compensate for the * aspect ratio of the text editor). * * _____ _____ * | \ /\ /\ / | * | \ / \ / \ / | * | / / / | * | / \ / \ / \ | * | / / / \ | * | \ / / / | * | \ / \ / \ / | * | / / / | * | / \ / \ / \ | * | / / / \ | * | \ / / / | * | \ / \ / \ / | * | / / / | * | / \ / \ / \ | * | / / / \ | * | \ / / / | * | \ / \ / \ / | * | / / / | * | / \ / \ / \ | * | / / / \ | * | \ / / / | * | \ / \ / \ / | * | / / / | * | / \ / \ / \ | * |_____/ \/ \/ \_____| * * The fraction p/q describing a given 2-bridge knot or * link is not quite unique. * * Lemma. Let p and q be relatively prime integers * satisfying 0 < p < q. Then p has a unique inverse * p' in the ring Z/q. (Note that Z/q need not be a * field -- we don't require q to be prime.) * * Proof. Consider multiplying p by each element * in the ring Z/q, in turn. We must get q distinct * elements of Z/q, since pp' = pp" (mod q) implies * p(p - p") = 0 (mod q), and because p and q are relatively * prime, this implies p' - p" = 0 (mod q). Hence the * pidgeonhole principle implies that p has a unique * inverse. Q.E.D. * * Two fractions p/q and p'/q' represent equivalent * (oriented) knots iff q = q', and p and p', considered * as elements of the ring Z/q, are either equal or inverses * of each other. (See Hatcher and Thurston or further * references.) * * Example. The fractions 3/11 and 4/11 represent * equivalent knots because (3)(4) = 1 (mod 11). * * Example. The fractions 3/11 and -3/11 represent * mirror image knots. The latter can be expressed * as 8/11. * * Example. The knot 3/5 (the figure eight knot illustrated * above) is amphicheiral, because its mirror image is * -3/5 = 2/5, and 2 and 3 are inverses in Z/5 (i.e. * (2)(3) = 1 (mod 5)). * * All that follows is based on joint work with Makoto Sakuma * of Osaka University, to whom I offer my deepest thanks. * * The algorithm for recognizing two-bridge knot and link * complements is based on the conjecture/theorem (?) of * Sakuma and Weeks' "Recognizing two-bridge knot and * link complements..." (in preparation). That article * constructs a triangulation for a 2-bridge knot or link * complement, and conjectures/proves (?) that it is the * canonical one. SnapPea has verified the conjecture * for knots through 11 crossings and links through 10 crossings, * thanks to Joe Christy's tables. The best thing to do at this * point is to see that article, in particular, the illustration * of the (allegedly) canonical triangulation. However, * for those readers of this documentation who don't have * a copy of the article handy, I will show you how to * construct the key illustration for yourself. The given * 2-bridge knot or link is positioned on the surface of * a tall rectangular box (with slight deviations to * accomodate the crossings, just as ordinary planar * projections of knots allow slight deviations from * the plane to accomodate the crossings). To draw the * box in this text-only file, I have cut it open. * * column column column column * 1 2 3 4 * * ___ ___ * . \ / . * . / . * . / \ . * . \ / . * . / . * ................___/.\___........ * | | | | . level n - 4 * | | .\ /. . * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . * | | | | . * | | | | . level n - 5 * | .\ /. | . * | . \ / . | . * | . \ / . | . * | . \ . | . * | . / \ . | . * | . / \ . | . * | ./ \. | . * | | | | . * * . . . etc. . . . * * | | | | . level 1 * | .\ /. | . * | . \ / . | . * | . \ / . | . * | . \ . | . * | . / \ . | . * | . / \ . | . * | ./ \. | . * |.......|.......|__...__|........ level 0 * . \ / . * . / . * . / \ . * . \ / . * . / . * ___/.\___ * * To reconstruct the 3-dimensional illustration, imagine * folding the above figure into a tall, narrow box with * a square cross section. Better yet, pull out a piece * of scrap paper and draw it. The four tall, narrow * rectangles in the above diagram will be the four sides * of the tall, narrow box. The squares attached at the * top and bottom of the diagram will be the top and bottom * of the rectangular box. The dashes in the diagram * (i.e. the charaters '/', '\', '|' and '-') represent * the path of the knot or link. The dots (i.e. the * characters '.') indicate edges of the box where the * knot or link does not pass. * * The columns obey the rules * * Column 1 contains no crossings. * Column 2 contains only left handed crossings. * Column 3 contains only right handed crossings. * Column 4 contains no crossings. * * The top and the bottom of the box each contain * a double crossing. If a double crossing is left handed * (resp. right handed) it must occur in column 2 * (resp. column 3). The sense of the crossings at the * top and bottom are independent of one another. * * Because of the above rules, this projection is * automatically alternating, and therefore is a projection * with minimal crossing number. Every 2-bridge knot * or link with at least four crossings may be put in this form * [reference?]. Two-bridge knots and links with fewer * than four crossings are never hyperbolic, and will not * concern us. * * Label the "levels" of the box from 0 to n - 4, where * n is the number of crossings, as shown in the above * diagram. Note that levels occur between crossings, * not at crossings. The (allegedly) canonical triangulation * consists of two tetrahedra at each level. Imagine the * tetrahedra as being very nearly flat. One tetrahedron * at each level forms a cross section of the box: * * column 4 * ______________ * |. /| * | . / | * | . / | * | . / | * | . / | * column | . / | column * 1 | ./ | 3 * | /. | * | / . | * | / . | * | / . | * | / . | * | / . | * |/_____________| * column 2 * * This illustration shows the tetrahedron as viewed * from above, with the sides of the box as indicated. * The solid diagonal runs across the top face of the * tetrahedron, while the dotted diagonal runs across * the bottom face. * * The second tetrahedron at each level lies in the * region outside the rectangular box. That is, you * must imagine the box as sitting in the 3-sphere, * with a second rectangular box in its complement. * The corresponding (tall) sides of the boxes are * identified, but the tops and bottoms are not identified. * This decomposes the 3-sphere into four regions: two * rectangular boxes, and two square "pillows". Each * of the two pillows contains a double crossing, which * we now imagine to be lifted off the surface of * the rectangular box. The second tetrahedron at each * level forms a cross section of the second rectangular * box. The "diagonals" of the second tetrahedron * are positioned as shown (sorry I can't include the * point at infinity in this illustration): * * top \ . bottom * diagonal \ . diagonal * of second \ . of second * tetrahedron \______________. tetrahedron * |. /| * | . / | * | . / | * | . / | * | . / | * | . / | * | ./ tet | tet * | /. #1 | #2 * | / . | * | / . | * | / . | * | / . | * | / . | * |/_____________| * bottom . \ top * diagonal . \ diagonal * of second . \ of second * tetrahedron \tetrahedron * * Note that the top (resp. bottom) surface of the union * of the two tetrahedra is combinatorially a tetrahedron. * To avoid confusion, we'll refer to the two solid * tetrahedra at each level as "solid tetrahedra", and * to their upper and lower surfaces as "surface tetrahedra". * The former are 3-dimensional; the latter are 2-dimensional. * To specify the gluing, we must say how the upper surface * tetrahedron at level i glues to the bottom surface * tetrahedron at level i+1. The answer, basically, is to * follow your nose. Isotop the upper surface tetrahedron * at level i to the lower surface tetrahedron at level i+1, * keeping their vertices firmly attached to the knot. * The half twist in the knot will automatically guide * one surface onto the other. * * A similar phenomenon occurs with the double crossing at * the top of the box. The double crossing guides the * upper surface tetrahedron of the (n - 4)th level as it * collapses onto itself. This is harder to visualize * than the gluings between layers -- drawing an illustration * on a piece of scrap paper is essential (it's essential * for me, anyhow). Draw the tetrahedron at several * different stages, as its vertices push towards each * other along the knot. Try to imagine it as a movie. * The surface tetrahedron will, at the last moment, * collapse to a 2-complex which is best described as * two disks intersecting along a radius. I'd like to be * able to include a 3-dimensional picture in this * documentation, but as usual I'll have to ask you to * make your own. Once you've succeeded in visualizing * this "movie", you'll see that the double crossing * unambiguously shows how to identify the four triangles * of the upper surface tetrahedon so as to realize a * triangulation of that portion of the knot or link complement. * The double crossing at the bottom of the box is handled * similarly. * * The existence of this triangulation immediately shows that * 2-bridge knot and link complements have symmetry group * at least D2 (the dihedral group of order 4). The D2 * is generated by * * (1) a half turn about the central axis of * the rectangular box, and * * (2) a symmetry which interchanges the two tetrahedra * at each level. * * In addition (but less obvious), if the fraction p/q satisfies * satisfies p^2 = 1 (mod q), then the knot or link will be * amphicheiral, its continued fraction expansion will * be symmetrical, and there will be an additional symmetry * of the triangulation which turns the rectangular box * upside down. Furthermore, if I've finished the proof * that this triangulation is the canonical one, we see that * there can be no other symmetries of the knot or link, * because there are no other symmetries of the triangulation. * * We now turn to the main work of the function two_bridge(), * which is to accept a canonical triangulation of a knot * or link complement, check whether it has the form described * above, and, if so, calculate the fraction p/q which * describes the knot or link. Checking whether the triangulation * has the desired form is straightforward enough, so here * I'll describe how two_bridge() deduces the fraction p/q. * * First redraw the rectangular box picture, sliding the * double crossings from the top and bottom onto the sides: * * column column column column * 1 2 3 4 * * ......... * | | * | | * | | * | | * ................|.......|........ * | | | | . * | | .\ /. . * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . These first two * | | | | . crossings were * | | | | . previously on the * | | .\ /. . top of the box. * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . * | | | | . * | | | | . * | | .\ /. . * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . * | | | | . * | | | | . * | .\ /. | . * | . \ / . | . * | . \ / . | . * | . \ . | . * | . / \ . | . * | . / \ . | . * | ./ \. | . * | | | | . * * . . . etc. . . . * * | | | | . * | .\ /. | . * | . \ / . | . * | . \ / . | . * | . \ . | . * | . / \ . | . * | . / \ . | . * | ./ \. | . * | | | | . * | | | | . * | | .\ /. . * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . These last two * | | | | . crossings were * | | | | . previously on the * | | .\ /. . bottom of the box. * | | . \ / . . * | | . \ / . . * | | . / . . * | | . / \ . . * | | . / \ . . * | | ./ \. . * |.......|.......|.......|........ * | | * | | * | | * | | * |.......| * * * Now look at the square pillow whose two faces are * the bottom of the original rectangular box and the * bottom of the second rectangular box in the complementary * region. The knot or link passes along two sides of * this square pillow. Assign the following coordinate * system to the pillow. (Here it's viewed from within * the original rectangular box.) * * column 3 * (0,1)______________(1,1) * |\ .| * | \ . | * ^ | \ . | * | | \ . | * | | \ . | * positive column | \ . | column * y direction 4 | \. | 2 * | | .\ | * | | . \ | * | . \ | * | . \ | * | . \ | * | . \ | * |.____________\| * (0,0) column 1 (1,0) * * positive * ----- x direction -----> * * Relative to this coordinate system, the two strands * of the link which pass the pillow will have slope * either 0/1 or 1/0. In the example given above, the * slope is 1/0. If, instead of double right handed * twists in column 3, the bottom of the box had had * double left handed twists in column 2, then the * strands on the pillow would have slope 0/1. Let * this slope (0/1 or 1/0) be the initial value of a * fraction which we will call a/b. We begin to work * our way up the rectangular box, untwisting the crossings * as we encounter them. That is, we transfer the twist * from the "free" part of the knot or link to the square * pillow. As we do this, we keep track of the slope of * the two strands of the knot or link which pass across * the pillow. Each right handed twist in column 3 will * introduce a twist in the pillow which takes a line of * slope a/b to a line of slope a/(a+b). Each left handed * twist in column 2 will introduce a twist in the pillow * which takes a line of slope a/b to a line of slope * (a+b)/b. Continue in this fashion until we reach the * top of the rectangular box. If the two strands which * pass the pillow at the top of the box run parallel * to the y axis, then (according to the Figure 1 of * Hatcher-Thurston, which is reproduced near the top of * this file) the value of a/b will be precisely the * fraction p/q describing the 2-bridge knot or link. * If the two strands which pass the pillow at the top * of the box run parallel to the x axis, then we must * rotate the whole figure a quarter turn; in this * case fraction p/q equals -b/a. * * It's very easy to phrase the above analysis in terms * of continued fractions, but for the purposes of the * program we have no need to do so. (Just read down the * box picture, letting the number of consecutive crossings * in a given column be a term in the continued fraction.) */ #include "kernel.h" /* * The Level data structure describes the two Tetrahedra * which lie at a given level in the rectangular box picture. */ typedef struct { /* * tet[0] lies in the original rectangular box. * tet[1] lies in the complementary box. */ Tetrahedron *tet[2]; /* * vertex[i][j][k] is the VertexIndex of the vertex of * Tetrahedron i (i = 0 or 1, as in the preceding tet field) * which lies at (x, y) = (j, k), where x and y are defined * in an illustration above. The relationship between * the vertex numbering of the original and complementary * Tetrahedra is what you would expect: vertex[0][i][j] * of tet[0] is incident to vertex[1][i][j] of tet[1]. */ VertexIndex vertex[2][2][2]; /* * a/b is the current slope of the lines on the square * pillow, as described in the above documentation. * It accounts for all the crossings below this level, * and none of the crossings above it. */ long int a, b; } Level; static FuncResult find_level_zero(Triangulation *manifold, Level *level_zero); static FuncResult position_double_bonded_tetrahedra(Tetrahedron *tet, FaceIndex i, FaceIndex j, Level *level_zero); static Boolean at_top_of_box(Level *current_level, long int *p, long int *q); static Boolean left_handed_double_crossing(Level *current_level); static Boolean right_handed_double_crossing(Level *current_level); static FuncResult move_to_next_level(Level *current_level); static FuncResult find_new_level_tetrahedra(Level *old_level, Level *new_level); static Boolean left_handed_crossing(Level *old_level, Level *new_level); static Boolean right_handed_crossing(Level *old_level, Level *new_level); static void interchange_x_and_y(Level *level); static void normal_form(long int *p, long int *q); void two_bridge(Triangulation *manifold, Boolean *is_two_bridge, long int *p, long int *q) { Level current_level; /* * The overall plan is to assume the Triangulation * *manifold is of the form illustrated by the * rectangular box picture (cf. the lengthy top-of-file * documentation above). We'll start at the bottom * of the box and work our way up. If at some point * we find we can't fit the Triangulation into the * required form, we set *is_two_bridge to FALSE and * return. Otherwise, we build up the fraction p/q * as we going along, and at the end we set *is_two_bridge * to TRUE, set the correct values for *p and *q, and * return. */ /* * To get started, find the two Tetrahedra at level 0. * These Tetrahedra are easily recognized by their * "double bond"; that is, they share two pairs of glued * faces. It doesn't matter which two double-bonded * Tetrahedra we choose: if we swap the pair at the top * of the rectangular box for the pair at the bottom the * whole rectangular box picture gets turned upside down, * the continued fraction expansion gets reversed, and * instead of the fraction p/q we get the fraction +- p'/q, * where p' is the inverse of p in the ring Z/q, and the * +- depends on whether there are an even or odd number * of terms in the continued fraction expansion. * * If we can't find a pair of double bonded Tetrahedra, * then (modulo the conjecture described above) we know * this isn't a two-bridge knot or link complement, and * we set *is_two_bridge to FALSE and return. */ if (find_level_zero(manifold, ¤t_level) == func_failed) { *is_two_bridge = FALSE; return; } /* * Now we work our way up the rectangular box until * we reach the top. When we reach the top, we * account for the final double crossing, and compute * the fraction p/q. */ while (at_top_of_box(¤t_level, p, q) == FALSE) if (move_to_next_level(¤t_level) == func_failed) { *is_two_bridge = FALSE; return; } /* * For a given 2-bridge knot or link, the denominator q * in the fraction p/q is well-defined, but there are typically * four possibilities for p in the range -q < p < q. We report * the value of p whose absolute value is smallest * (if (p)(-p) == 1 (mod q) we report the positive value). * This convention makes it obvious when two knots or links * are equivalent, and also makes it obvious when they are * mirror-images of each other. */ normal_form(p, q); /* * Set *is_two_bridge to TRUE and return. * The function at_top_of_box() has already set *p and *q. */ *is_two_bridge = TRUE; } static FuncResult find_level_zero( Triangulation *manifold, Level *level_zero) { Tetrahedron *tet; FaceIndex i, j; /* * Look for a pair of double-bonded Tetrahedra. * If found, try to position them as described in the rectangular * box picture at the top of this file. If successful, * fill in the fields of *level_zero. If anything goes * wrong, return func_failed. * * In almost all cases, there will be only one candidate (i, j) * for the double bond. The exception is the figure eight knot * complement, where the bonds at the bottom of the box might * be confused with the bonds at the top (that is, in the following * loop, the index i might refer to a face at the top of the box, * while the index j refers to a face at the bottom). With this * case in mind, we are careful to return from within the loop * only when position_double_bonded_tetrahedra() is successful. * When it's unsuccessful we keep on going. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) for (j = i + 1; j < 4; j++) if (tet->neighbor[i] == tet->neighbor[j]) if (position_double_bonded_tetrahedra(tet, i, j, level_zero) == func_OK) return func_OK; return func_failed; } static FuncResult position_double_bonded_tetrahedra( Tetrahedron *tet, FaceIndex i, FaceIndex j, Level *level_zero) { EdgeClass *diagonal_class; Orientation twist; int i0, i1; /* * All we know to begin with is that two faces of tet are glued * to the same thing. If in fact tet is glued to itself, * return func_failed. */ if (tet->neighbor[i] == tet) return func_failed; /* * Set level_zero->tet[]. */ level_zero->tet[0] = tet; level_zero->tet[1] = tet->neighbor[i]; /* * It doesn't matter which is vertex[0][1][0] and which is * vertex[0][0][1]; swapping them would just perform a symmetry * of the manifold. */ level_zero->vertex[0][1][0] = i; level_zero->vertex[0][0][1] = j; /* * If the manifold is oriented, it DOES make a difference which * is vertex[0][0][0] and which is vertex[0][1][1]; swapping them * would change the orientation, and two_bridge() would compute * -p/q instead of p/q. */ level_zero->vertex[0][0][0] = remaining_face[j][i]; level_zero->vertex[0][1][1] = remaining_face[i][j]; /* * We expect the pattern of edge identifications on the bottom * of this level to be either * * (0,0) |------1->-----| (1,0) * |\ .| * | \ . | * | \ . | * | \ 3 | * | \ |/_ | * | \ . | * 3 \. 3 tet[1] * \|/ .\ \|/ * v . \ v * | . \ | * | . \ | * | . \ | * | . \ | * (0,1) |------2->----\| (1,1) * |\ .| * | \ . | * | \ _. | * | \ /| | * | \ 3 | * ^ \ . ^ * /|\ \. /|\ tet[0] * 3 .\ 3 * | . \ | * | . \ | * | . \ | * | . \ | * | . \ | * (0,0) |------1->----\| (1,0) * * or * * (0,1) (1,1) (0,1) * ------3->----- -----<-3------ * |\ .|\ .| * | \ . | \ . | * | \ _. | \ . | * | \ /| | \ . | * ^ \ 3 ^ \ . ^ * /|\ \ . /|\ \ . /|\ * 1 \. 2 \. 1 * | .\ | .\ | * | . \ | . \ | * | . \ | 3 \ | * | . \ | |/_ \ | * | . \ | . \ | * | . \ | . \ | * |------3->----\|-----<-3-----\| * (0,0) (1,0) (0,0) * tet[0] tet[1] * * depending on whether the bottom of the box has left handed * double crossing in column 2 or a right handed double crossing * in column 3, respectively (cf. top-of-file documentation). */ /* * Let diagonal_class be the EdgeClass of the diagonal, * i.e. the class marked 3 in the preceding illustrations. */ diagonal_class = tet->edge_class[edge_between_faces[i][j]]; /* * Decide whether we hope to have left handed crossings in * column 2 or right handed crossings in column 3. */ if (tet->edge_class [edge_between_vertices [level_zero->vertex[0][0][0]] [level_zero->vertex[0][0][1]] ] == diagonal_class) /* * We hope to have a left handed double crossing in column 2. */ twist = left_handed; else if (tet->edge_class [edge_between_vertices [level_zero->vertex[0][0][0]] [level_zero->vertex[0][1][0]] ] == diagonal_class) /* * We hope to have a right handed double crossing in column 3. */ twist = right_handed; else return func_failed; /* * Assign the vertices of tet[1]. */ level_zero->vertex[1][0][0] = EVALUATE( tet->gluing[twist == left_handed ? j : i], level_zero->vertex[0][0][0]); level_zero->vertex[1][1][0] = EVALUATE( tet->gluing[j], level_zero->vertex[0][1][0]); level_zero->vertex[1][0][1] = EVALUATE( tet->gluing[i], level_zero->vertex[0][0][1]); level_zero->vertex[1][1][1] = EVALUATE( tet->gluing[twist == left_handed ? i : j], level_zero->vertex[0][1][1]); /* * Now check to make sure the configuration is really * what we are hoping for. */ /* * Are the values of level_zero->vertex[1][][] distinct? * * If so, then we've defined a valid position for tet[1]. * (It's the only possible candidate making this Triangulation * look like the rectangular box picture from the top-of-file * documentation. In a moment we'll check whether it really works.) * * If not, then this Triangulation can't possibly be of the * desired form, and we return func_failed. */ for (i0 = 0; i0 < 4; i0++) for (i1 = i0 + 1; i1 < 4; i1++) if (level_zero->vertex[1][i0/2][i0%2] == level_zero->vertex[1][i1/2][i1%2]) return func_failed; /* * We know that tet->gluing[i] must map two of the three relevant * vertex[0][][]'s to the correct vertex[1][][]'s, and similarly * for tet->gluing[j], since that's how the vertex[1][][]'s were * defined. It remains to check the third vertex on each face. */ if ( twist == left_handed ? ( (EVALUATE(tet->gluing[i], level_zero->vertex[0][0][0]) != level_zero->vertex[1][1][0]) || (EVALUATE(tet->gluing[j], level_zero->vertex[0][1][1]) != level_zero->vertex[1][0][1]) ) : /* twist == right_handed */ ( (EVALUATE(tet->gluing[i], level_zero->vertex[0][1][1]) != level_zero->vertex[1][1][0]) || (EVALUATE(tet->gluing[j], level_zero->vertex[0][0][0]) != level_zero->vertex[1][0][1]) ) ) return func_failed; /* * We now know that tet[0] and tet[1] have been positioned * as in the rectangular box picture. All that remains is * to set the fraction a/b. */ if (twist == left_handed) { /* * The initial slope of the segments is 0/1, but after accounting * for the left handed double crossing it's 2/1. */ level_zero->a = 2; level_zero->b = 1; } else /* twist == right_handed */ { /* * The initial slope of the segments is 1/0, but after accounting * for the right handed double crossing it's 1/2. */ level_zero->a = 1; level_zero->b = 2; } return func_OK; } static Boolean at_top_of_box( Level *current_level, long int *p, long int *q) { /* * Check whether the top of the current_level glues * to itself in the manner corresponding to the * top of the rectangular box picture, as described * at the top of this file. If it does, set *p and *q, * and return TRUE. If it doesn't, return FALSE. * * In setting *p and *q, we must account for two things: * * (1) The double crossing at the top of the box, * which either addes twice the denominator to * the numerator, or vice versa. * * (2) The way the two arcs are attached to the * remainder of the knot or link. The usual * convention (see Figure 1 of Hatcher-Thurston, * reproduced at the top of this file) is that * the attached arcs have slope 1/0 relative to * the x-y coordinate system we're using. * This will be the case when we have a right * handed double crossing in column 3. When * we have a left handed double crossing in * column 2, we must rotate the whole box a * quarter turn about its vertical axis; this * replaces the slope a/b with -b/a. * * Note that at_top_of_box() needn't worry about any * error conditions -- that's the job of move_to_next_level(). */ if (left_handed_double_crossing(current_level)) { *p = - current_level->b; *q = current_level->a + 2 * current_level->b; return TRUE; } if (right_handed_double_crossing(current_level)) { *p = current_level->a; *q = current_level->b + 2 * current_level->a; return TRUE; } return FALSE; } static Boolean left_handed_double_crossing( Level *current_level) { Tetrahedron *tet0, *tet1; Permutation gluingA, gluingB; /* * If we have a left handed double crossing in column 2, the * top faces of current_level->tet[0] and current_level->tet[1] * will be glued as shown: * * (0,0) |------1->-----| (1,0) * |\ .| * | \ . | * | \ B' . | * | \ . | * | 3 . | * | _\| . | * 3 \. 3 tet[1] * \|/ .\ \|/ * v A' . \ v * | . \ | * | . \ | * | . \ | * | . \ | * (0,1) |------2->----\| (1,1) * |\ .| * | \ . | * | \ A . | * | \_ . | * | |\ . | * ^ 3 . ^ * /|\ \. /|\ tet[0] * 3 .\ 3 * | B . \ | * | . \ | * | . \ | * | . \ | * | . \ | * (0,0) |------1->----\| (1,0) * * The pattern of face identifications must be exactly this, * so it is very easy to check. */ tet0 = current_level->tet[0]; tet1 = current_level->tet[1]; if (tet0->neighbor[current_level->vertex[0][0][0]] != tet1 || tet0->neighbor[current_level->vertex[0][1][1]] != tet1) return FALSE; gluingA = tet0->gluing[current_level->vertex[0][0][0]]; gluingB = tet0->gluing[current_level->vertex[0][1][1]]; if (gluingA != CREATE_PERMUTATION( current_level->vertex[0][0][0], current_level->vertex[1][1][0], current_level->vertex[0][0][1], current_level->vertex[1][0][1], current_level->vertex[0][1][0], current_level->vertex[1][0][0], current_level->vertex[0][1][1], current_level->vertex[1][1][1]) || gluingB != CREATE_PERMUTATION( current_level->vertex[0][0][0], current_level->vertex[1][0][0], current_level->vertex[0][0][1], current_level->vertex[1][1][1], current_level->vertex[0][1][0], current_level->vertex[1][1][0], current_level->vertex[0][1][1], current_level->vertex[1][0][1])) return FALSE; return TRUE; } static Boolean right_handed_double_crossing( Level *current_level) { Boolean result; /* * right_handed_double_crossing() should be identical to * left_handed_double_crossing(), except that the roles of the * x and y coordinates are reversed. So in the interest of * concise, easily modifiable code, it makes sense to write * the former as a function call to the latter. * * For those who are interested, I have appended an illustration * of what the gluing looks like in the case of a right handed * double crossing. */ interchange_x_and_y(current_level); result = left_handed_double_crossing(current_level); interchange_x_and_y(current_level); return result; /* * If we have a right handed double crossing in column 3, * the top faces of current_level->tet[0] and current_level->tet[1] * will be glued as shown: * * (0,1) (1,1) (0,1) * ------3->----- -----<-3------ * |\ .|\ .| * | \ . | \ . | * | \ A . | \ B' . | * | \ . | \_ . | * ^ 3 . ^ |\ . ^ * /|\ _\| . /|\ 3 . /|\ * 1 \. 2 \. 1 * | .\ | .\ | * | . \ | . \ | * | . \ | . \ | * | . B \ | . A' \ | * | . \ | . \ | * | . \ | . \ | * |------3->----\|-----<-3-----\| * (0,0) (1,0) (0,0) * tet[0] tet[1] */ } static FuncResult move_to_next_level( Level *current_level) { Level *new_level, *old_level, the_new_level; /* * Let old_level be a pointer to the most recently * computed level, and new_level be a pointer to * the new level we are hoping to find. At the end * of the function we will, if successful, copy * the contents of *new_level into *current_level, * and return func_ok. Otherwise we'll leave *current_level * unmodified, and return func_failed. */ old_level = current_level; new_level = &the_new_level; /* * The rectangular box picture described in the * documentation at the top of this file shows * how each level glues to the next level. * Keep a copy of that picture handy as you read * this documentation. */ /* * First find the two tetrahedra at the new_level, * and set the fields new_level->tet[0] and new_level->tet[1]. */ if (find_new_level_tetrahedra(old_level, new_level) == func_failed) return func_failed; /* * Now see whether we can label the vertices of * new_level->tet[0] and new_level->tet[1] in such a way * as to realize either a left handed crossing in column 2 or * a right handed crossing in column 3. */ if (left_handed_crossing(old_level, new_level) == TRUE) { /* * The left handed crossing twists the pillow in such * a way that the segments of slope a/b at the old level * get taken to segments of slope (a+b)/b at the new level. */ new_level->a = old_level->a + old_level->b; new_level->b = old_level->b; /* * We're done! */ *current_level = *new_level; return func_OK; } if (right_handed_crossing(old_level, new_level) == TRUE) { /* * The right handed crossing twists the pillow in such * a way that the segments of slope a/b at the old level * get taken to segments of slope a/(a+b) at the new level. */ new_level->a = old_level->a; new_level->b = old_level->a + old_level->b; /* * We're done! */ *current_level = *new_level; return func_OK; } /* * Oh, well. This isn't a 2-bridge knot or link complement. */ return func_failed; } static FuncResult find_new_level_tetrahedra( Level *old_level, Level *new_level) { int i, j; /* * Regardless of whether we have a left handed crossing in * column 2 or a right handed crossing in column 3, the face of * old_level->tet[0] opposite vertex[0][1][1] will glue to * new_level->tet[0], and the face of old_level->tet[0] opposite * vertex[0][0][0] will glue to new_level->tet[1]. */ new_level->tet[0] = old_level->tet[0]->neighbor[old_level->vertex[0][1][1]]; new_level->tet[1] = old_level->tet[0]->neighbor[old_level->vertex[0][0][0]]; /* * Do we have two distinct new Tetrahedra? * (The new Tetrahedra couldn't possibly equal any that occur at * lower levels (because the latter have all their faces accounted * for), but, if this isn't a 2-bridge knot or link complement, * then the two new Tetrahedra might coincide with each other, * or with one of the Tetrahedra at the old_level. If they do, * return func_failed. */ if (new_level->tet[0] == new_level->tet[1]) return func_failed; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (new_level->tet[i] == old_level->tet[j]) return func_failed; return func_OK; } static Boolean left_handed_crossing( Level *old_level, Level *new_level) { int i, j, k; Permutation gluing; /* * If we do in fact have a left handed crossing in column 2, * the top surface of the old level will glue to the bottom * surface of the new level as illustrated below. * * For greater clarity, the illustration supresses the diagonals * on the top surface of the new level and and bottom surface of the * old level. They are irrelevant to the present gluing. * * Each double square in the illustration is really a tetrahedron * (thought of as a 2-complex, not a 3-complex). We are describing * a map from one tetrahedron to another. You may also think of * it as a map from one square pillowcase to another, if you prefer. * In any case, you don't have to study the illustration too * carefully, because it's clear that the net effect of the * map is to leave vertices (0, 0) and (0, 1) alone, and interchange * (1, 0) and (1, 1). * * (0,1) (1,1) (0,1) * ------5->----- -----<-5------ * | .| .| * | . | . | * | . | . | * | A' . | B' . | * ^ _ . 2 . ^ * /|\ /| \|/ 3 /|\ * 1 4 v |/_ 1 * | . | . | * | . | . | * | . | . | * | . C' | . D' | * | . | . | * | . | . | * |------6->-----|-----<-6------| * (0,0) (1,0) (0,0) * new_level new_level * ->tet[0] ->tet[1] * * (0,1) (1,1) (0,1) * ------3->----- -----<-3------ * |\ |\ | * | \ | \ | * | \ B | \ D | * | \ | \_ | * ^ 5 ^ |\ ^ * /|\ _\| /|\ 6 /|\ * 1 \ 2 \ 1 * | \ | \ | * | \ | \ | * | \ | \ | * | A \ | C \ | * | \ | \ | * | \ | \ | * |------4->----\|-----<-4-----\| * (0,0) (1,0) (0,0) * old_level old_level * ->tet[0] ->tet[1] */ /* * First make sure the tet->neighbor fields are what they * ought to be. (Two of them will be correct as a consequence * of the choice of new_level->tet[0] and new_level->tet[0] * in find_new_level_tetrahedra(), but we'll check 'em all * for the sake of robustness.) */ if (old_level->tet[0]->neighbor[old_level->vertex[0][1][1]] != new_level->tet[0] /* gluing A */ || old_level->tet[0]->neighbor[old_level->vertex[0][0][0]] != new_level->tet[1] /* gluing B */ || old_level->tet[1]->neighbor[old_level->vertex[1][0][1]] != new_level->tet[0] /* gluing C */ || old_level->tet[1]->neighbor[old_level->vertex[1][1][0]] != new_level->tet[1]) /* gluing D */ return FALSE; /* * Use gluings A and B to define the new_level->vertex_index[][][]. */ for (i = 0; i < 2; i++) { /* * "gluing" will be gluing A when i = 0 and gluing B when i = 1. * (There's nothing special about this choice -- other gluings * could have been used used instead.) */ gluing = old_level->tet[0]->gluing[old_level->vertex[0][!i][!i]]; /* * We want to leave (0, 0) and (0, 1) alone, * and swap (1, 0) and (1,1). */ for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) new_level->vertex[i][j][k] = EVALUATE(gluing, old_level->vertex[0][j][j^k]); } /* * Given the above definitions of the new_level->vertex_index[][][], * check whether gluings C and D are what they should be. */ for (i = 0; i < 2; i++) { /* * "gluing" will be gluing C when i = 0 and gluing D when i = 1. */ gluing = old_level->tet[1]->gluing[old_level->vertex[1][i][!i]]; for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) if (new_level->vertex[i][j][k] != EVALUATE(gluing, old_level->vertex[1][j][j^k])) return FALSE; } /* * We've checked that the tet->neighbor and tet->gluing fields * correspond to a left handed crossing in column 2, and we've * set the new_level->vertex[][][] fields, so return TRUE. */ return TRUE; } static Boolean right_handed_crossing( Level *old_level, Level *new_level) { Boolean result; /* * right_handed_crossing() should be identical to * left_handed_crossing(), except that the roles of the * x and y coordinates are reversed. So in the interest * of concise, easily modifiable code, it makes sense to * write the former as a function call to the latter. */ interchange_x_and_y(old_level); result = left_handed_crossing(old_level, new_level); interchange_x_and_y(old_level); interchange_x_and_y(new_level); return result; } static void interchange_x_and_y( Level *level) { int i; VertexIndex temp; for (i = 0; i < 2; i++) { temp = level->vertex[i][0][1]; level->vertex[i][0][1] = level->vertex[i][1][0]; level->vertex[i][1][0] = temp; } } static void normal_form( long int *p, long int *q) { long int pp[4]; int i; /* * normal_form() accepts a fraction p/q in lowest terms * satisfying -q < p < q. Typically the range (-q, q) * contains four values of p such that the corresponding * fractions p/q represent the same knot or link. * normal_form() replaces the original p with an equivalent * one of minimal absolute value. In case of ties, it * chooses the positive value. This convention makes it * obvious when two knots are equivalent, and when they are * mirror-images. */ /* * Let pp[0] be a candidate for p in the range (0, q). */ pp[0] = (*p > 0) ? *p : *p + *q; /* * Let pp[1] be the inverse of pp[0] in ring Z/q. * pp[1] will lie in the range (0, q). */ pp[1] = Zq_inverse(pp[0], *q); /* * Let pp[2] and pp[3] be candidates for p in the range (-q, 0). */ pp[2] = pp[0] - *q; pp[3] = pp[1] - *q; /* * Let *p be the value of pp[0-3] with the smallest absolute value. * In case of a tie, choose the positive value. */ for (i = 0; i < 4; i++) if (ABS(pp[i]) < ABS(*p) || (ABS(pp[i]) == ABS(*p) && pp[i] > 0)) *p = pp[i]; } snappea-3.0d3/SnapPeaKernel/code/update_shapes.c0100444000175000017500000002010406742675502017670 0ustar babbab/* * update_shapes.c * * This file provides the function * * void update_shapes(Triangulation *manifold, Complex *delta); * * which is called by do_Dehn_filling() in hyperbolic_structure.c. * update_shapes() updates the shapes of the tetrahedra in *manifold * by the amounts specified in the array delta. If necessary, delta * is first scaled so that no delta[i].real or delta[i].imag exceeds * the limit specified by the constant allowable_change (see below). * * The entries in delta are interpreted relative to the coordinate system * given by the coordinate_system field of each Tetrahedron, and the * indexing of delta is assumed to correspond to the index field of each * tetrahedron. */ /* * The allowable_change constant specifies the maximum amount * the log of the complex edge parameter may change. * * allowable_change.real is the maximum allowable change in * the log of its modulus, and * * allowable_change.imag is the maximum allowable change in * its argument. * * If necessary, all the delta[i] are scaled by a constant (between * zero and one) so that no delta[i] exceeds the allowable change. * * * Setting allowable_change. * * A small value for allowable_change makes Newton's method slow, * but reliable. A larger value speeds it up, but increases the risk * of winding up on some funny branch of the solution space. * The values of allowable_change.real and allowable_change.imag * must not exceed 0.5, for the following reasons. * * (1) Because choose_coordinate_system() is called at the start of * each iteration of Newton's method, we know that the current * value of the edge parameter (relative to the chosen coordinate * system) satisfies |z-1| >= 1 and Re(z) <= 0.5 (see the comment * preceding choose_coordinate_system() in hyperbolic_structure.c). * Therefore if allowable_change.imag is less than pi/6 = 0.52..., * the parameter z cannot go more than half way to the singularity * at 1. If allowable_change.real is less than log(2) = 0.69..., * then z cannot go more than half way to the singularity at 0, nor * can it go "more than half way to infinity", in the sense that * its modulus cannot increase by more than a factor of two. * * (2) The code which maintains the shape_history assumes that when a * Tetrahedron's shape changes, the edge parameter given by * coordinate_system is the one passing through pi (mod 2 pi), and * the other two edge parameters are passing through 0 (mod 2 pi). * This assumption relies on the fact that allowable_change.imag * is less than pi/6 = 0.52... . */ #include "kernel.h" /* * The entries in allowable_change must not exceed 0.5. * See explanation above. */ static const Complex allowable_change = {0.5, 0.5}; static void scale_delta(Triangulation *manifold, Complex *delta); static void recompute_shapes(Triangulation *manifold, Complex *delta); void update_shapes( Triangulation *manifold, Complex *delta) { scale_delta(manifold, delta); recompute_shapes(manifold, delta); } static void scale_delta( Triangulation *manifold, Complex *delta) { int i; Complex max; double scaled_max, factor; /* * Find the maximum values of delta[i].real and delta[i].imag. */ max = Zero; for (i = 0; i < manifold->num_tetrahedra; i++) { if ( fabs(delta[i].real) > max.real ) max.real = fabs(delta[i].real); if ( fabs(delta[i].imag) > max.imag ) max.imag = fabs(delta[i].imag); } /* * Scale the solution if necessary. */ scaled_max = MAX( max.real/allowable_change.real, max.imag/allowable_change.imag ); if (scaled_max > 1.0) { factor = 1.0 / scaled_max; for (i = 0; i < manifold->num_tetrahedra; i++) delta[i] = complex_real_mult(factor, delta[i]); } } static void recompute_shapes( Triangulation *manifold, Complex *delta) { Tetrahedron *tet; int i, c[3]; Complex log_z, z[3], old_z, new_z; ShapeInversion *dead_shape_inversion, *new_shape_inversion; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * The array c[] is used to index the coordinate systems. * For example, if tet->coordinate_system is 1, then * c[0] = 1 (the current coordinate system), c[1] = 2 (the next * one), and c[2] = 0 (the one after that, cyclically speaking). */ for (i = 0; i < 3; i++) c[i] = (tet->coordinate_system + i) % 3; /* * Find the new value of log(z) in the primary coordinate system. */ log_z = complex_plus( tet->shape[filled]->cwl[ultimate][c[0]].log, /* old log_z */ delta[tet->index] /* change in log_z */ ); /* * Compute the new edge parameters in rectangular form. * Use z1 = 1/(1 - z0), etc. */ z[c[0]] = complex_exp(log_z); z[c[1]] = complex_div( One, complex_minus(One, z[c[0]]) ); z[c[2]] = complex_div( One, complex_minus(One, z[c[1]]) ); /* * Note the old z[0] and the new z[0]. * * If the Tetrahedron has experienced a ShapeInversion, update * its shape_history. * * Note that this approach is completely robust with respect * to roundoff errors. A Tetrahedron is considered positively * oriented if its shape has z.imag >= 0.0, and negatively * oriented if its shape has z.imag < 0.0. (It doesn't matter * whether z.imag == 0.0 is considered positively or negatively * oriented, just so we make a convention and use it * consistently.) Also, whether a Tetrahedron is perceived * as positively or negatively oriented is independent of its * coordinate_system: the above computation of z[c[0]], z[c[1]], * and z[c[2]] insures that the imaginary parts of all three will * have the same sign (-, 0, +), regardless of roundoff errors. */ old_z = tet->shape[filled]->cwl[ultimate][0].rect; new_z = z[0]; if ((old_z.imag >= 0.0) != (new_z.imag >= 0.0)) { /* * The Tetrahedron has undergone a ShapeInversion. * Because old_z is in the region |z-1| >= 1 and Re(z) <= 0.5 * (see the comment preceding choose_coordinate_system() in * hyperbolic_structure.c) and allowable_change.imag <= 0.5 < pi/6, * it follows that the edge parameter coordinate_system * passed through pi (mod 2 pi), and the other two edge * parameters passed through 0 (mod 2 pi). That is, the * ShapeInversion we are adding to the stack will have * wide_angle = coordinate_system. * * If the last item on the shape_history stack also has its * wide_angle field equal to the present coordinate_system, * then we remove it, because it cancels with the present * ShapeInversion. Otherwise we add the new ShapeInversion * to the stack. */ /* * If there's a nonempty shape_history stack and the last * ShapeInversion has wide_angle == coordinate_system, then * remove it. It cancels with the ShapeInversion we were * about to put on the stack. */ if (tet->shape_history[filled] != NULL && tet->shape_history[filled]->wide_angle == tet->coordinate_system) { dead_shape_inversion = tet->shape_history[filled]; tet->shape_history[filled] = tet->shape_history[filled]->next; my_free(dead_shape_inversion); } /* * Otherwise add the new ShapeInversion to the stack. */ else { new_shape_inversion = NEW_STRUCT(ShapeInversion); new_shape_inversion->wide_angle = tet->coordinate_system; new_shape_inversion->next = tet->shape_history[filled]; tet->shape_history[filled] = new_shape_inversion; } } /* * For each of the three complex edge parameters . . . */ for (i = 0; i < 3; i++) { /* * Copy the ultimate shape to the penultimate. */ tet->shape[filled]->cwl[penultimate][i] = tet->shape[filled]->cwl[ultimate][i]; /* * Copy in the new ultimate shape in rectangular form. */ tet->shape[filled]->cwl[ultimate][i].rect = z[i]; /* * Compute the log, using the argument of the previous log * to choose the branch (for analytic continuation). */ tet->shape[filled]->cwl[ultimate][i].log = complex_log( tet->shape[filled]->cwl[ultimate][i].rect, tet->shape[filled]->cwl[penultimate][i].log.imag ); } } } snappea-3.0d3/SnapPeaKernel/code/volume.c0100444000175000017500000001631706742675502016365 0ustar babbab/* * volume.c * * This file contains the function * * double volume(Triangulation *manifold, int *precision); * * which the kernel provides for the UI. It computes and returns * the volume of the manifold. If the pointer "precision" is not NULL, * volume() estimates the number of decimal places of accuracy, and * places the result in the variable *precision. The error estimate is * the difference in the computed volumes at the last and next-to-the-last * iterations of Newton's method (cf. hyperbolic_structures.c). * * 94/11/30 JRW This file now contains the function * * double birectangular_tetrahedron_volume( O31Vector a, * O31Vector b, * O31Vector c, * O31Vector d); * * as well, for use within the kernel. */ #include "kernel.h" static double Lobachevsky(double theta); double volume( Triangulation *manifold, int *precision) { int i, j; double vol[2]; /* vol[ultimate/penultimate] */ Tetrahedron *tet; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ vol[i] = 0.0; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->shape[filled] != NULL) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ for (j = 0; j < 3; j++) vol[i] += Lobachevsky(tet->shape[filled]->cwl[i][j].log.imag); if (precision != NULL) *precision = decimal_places_of_accuracy(vol[ultimate], vol[penultimate]); return vol[ultimate]; } /* * The Lobachevsky() function is based on the formula in * Milnor's article "Hyperbolic geometry: The first 150 years", * Bulletin of the American Mathematical Society, volume 6, number 1, * January 1982, pp. 9-24. The actual formula appears about 2/3 of * the way down page 18. */ static double Lobachevsky(double theta) { double term, sum, product, theta_over_pi_squared; const double *lobcoefptr; const static double lobcoef[30] = { 5.4831135561607547882413838888201e-1, 1.0823232337111381915160036965412e-1, 4.8444907713545197129262758561472e-2, 2.7891037672165120538296812180796e-2, 1.8199901365960328824311744707278e-2, 1.2823667776324462157674846128817e-2, 9.5243928393815114745643670962394e-3, 7.3530535460250636167039159668208e-3, 5.8479755397266959054962366178048e-3, 4.7619093045811136799814912001842e-3, 3.9525701124525799515137944808229e-3, 3.3333335320272968375315987081340e-3, 2.8490028914574211634331659107080e-3, 2.4630541963678177950454607261557e-3, 2.1505376364114568439132649094016e-3, 1.8939393943803620897114593734851e-3, 1.6806722690053911275277764855335e-3, 1.5015015015233512340706336099638e-3, 1.3495276653220485553945730785293e-3, 1.2195121951230603594927151084491e-3, 1.1074197120711266596728487987281e-3, 1.0101010101010675186059356321779e-3, 9.2506938020352840967144128733280e-4, 8.5034013605442478972252664720549e-4, 7.8431372549019677504189889653457e-4, 7.2568940493468811469129541350086e-4, 6.7340067340067343805464730535677e-4, 6.2656641604010025932192218654462e-4, 5.8445353594389246257711686275038e-4, 5.4644808743169398954500641421420e-4}; /* * As long as DBL_EPSILON > 5e-22 there will be enough lobcoefs * for the series. If DBL_EPSILON is smaller than this you'll * need to add more coefficients to the list. */ #if (DBL_DIG > 19) You need to check DBL_EPSILON. If it is less than 5e-22 you will need to provide more lobcoefs. #endif /* * Milnor (Lemma 1, p. 17) shows that the Lobachevsky function is * periodic with period pi. Put theta in the range [-pi/2, +pi/2]. */ while (theta > PI_OVER_2) theta -= PI; while (theta < -PI_OVER_2) theta += PI; /* * Milnor (Lemma 1, p. 17) also shows that the Lobachevsky function * is an odd function, so we can further restrict theta to the * range [0, pi/2]. */ if (theta < 0.0) return -Lobachevsky(-theta); /* * Handle theta == 0.0 specially, to avoid encountering * log(0.0) later on. */ if (theta == 0.0) return 0.0; theta_over_pi_squared = (theta/PI)*(theta/PI); sum = 0.0; product = 1.0; lobcoefptr = lobcoef; do { product *= theta_over_pi_squared; term = *lobcoefptr++ * product; sum += term; } while (term > DBL_EPSILON); return theta*(1.0 - log(2*theta) + sum); } double birectangular_tetrahedron_volume( O31Vector a, O31Vector b, O31Vector c, O31Vector d) { /* * Compute the volume of the birectangular tetrahedron with vertices * a, b, c and d, using the method found in section 4.3 of * * E. B. Vinberg, Ob'emy neevklidovykh mnogogrannikov, * Uspekhi Matematicheskix Nauk, May(?) 1993, 17-46. * * Our a, b, c and d correspond to Vinberg's A, B, C and D, as shown * in his Figure 9. We need to compute the dual basis {aa, bb, cc, dd} * defined by = 1, = = = 0, etc. * Let m be the matrix whose rows are the vectors {a, b, c, d}, but * with the entries in the first column negated to account for the * indefinite inner product, and let mm be the matrix whose columns * are the vectors {aa, bb, cc, dd}. Then (m)(mm) = identity, by the * definition of the dual basis. */ GL4RMatrix m, mm; O31Vector aa, bb, cc, dd; double alpha, beta, gamma, delta, big_delta, tetrahedron_volume; int i; /* * Set up the matrix m. */ for (i = 0; i < 4; i++) { m[0][i] = a[i]; m[1][i] = b[i]; m[2][i] = c[i]; m[3][i] = d[i]; } for (i = 0; i < 4; i++) m[i][0] = - m[i][0]; /* * The matrix mm will be the inverse of m, as explained above. * When m is singular, the birectangular tetrahedron's volume is zero. */ if (gl4R_invert(m, mm) != func_OK) return 0.0; /* * Read the dual basis {aa, bb, cc, dd} from mm. */ for (i = 0; i < 4; i++) { aa[i] = mm[i][0]; bb[i] = mm[i][1]; cc[i] = mm[i][2]; dd[i] = mm[i][3]; } /* * Any pair of dual vectors lies in a positive definite 2-plane * in E^(3,1). Normalize them to have length one, so we can use * their dot products to compute the dihedral angles. */ o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(aa,aa) ), aa, aa); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(bb,bb) ), bb, bb); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(cc,cc) ), cc, cc); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(dd,dd) ), dd, dd); /* * Compute the angles alpha, beta and gamma, as shown in * Vinberg's Figure 9. */ alpha = PI - safe_acos(o31_inner_product(aa, bb)); beta = PI - safe_acos(o31_inner_product(bb, cc)); gamma = PI - safe_acos(o31_inner_product(cc, dd)); /* * Compute big_delta and delta, as in * Vinberg's Sections 4.2 and 4.3. */ big_delta = sin(alpha) * sin(alpha) * sin(gamma) * sin(gamma) - cos(beta) * cos(beta); if (big_delta >= 0.0) uFatalError("birectangular_tetrahedron_volume", "volume"); delta = atan( safe_sqrt( - big_delta) / (cos(alpha) * cos(gamma)) ); tetrahedron_volume = 0.25 * ( Lobachevsky(alpha + delta) - Lobachevsky(alpha - delta) + Lobachevsky(gamma + delta) - Lobachevsky(gamma - delta) - Lobachevsky(PI_OVER_2 - beta + delta) + Lobachevsky(PI_OVER_2 - beta - delta) + 2.0 * Lobachevsky(PI_OVER_2 - delta) ); return tetrahedron_volume; } snappea-3.0d3/SnapPeaPython/0040755000175000017500000000000007237315405014027 5ustar babbabsnappea-3.0d3/SnapPeaPython/SnapPea.py0100644000175000017500000004254607061473600015735 0ustar babbab# SnapPea.py # # The Python interface to the SnapPea kernel consists of two parts. # # SnapPea.py (this file) defines a set of objects (Triangulation, # AbelianGroup, etc.) purely in Python. # # SnapPeaC.c (in a different file) implements SnapPea.py's methods # as a set of wrappers to the standard SnapPea kernel functions, # which are written in C. import SnapPeaC False = 0 True = 1 # Keep track of when memory is released. def VerifyMyMallocUsage(): return SnapPeaC.verify_my_malloc_usage() class Triangulation: def __init__(self, spec, orientable = 1, index = 0): # Triangulation('file_name') reads a file from disk. # Triangulation(num_tet (=5,6,7), orientable (=0(false),1(true)), index) # reads a cusped census manifold. # Triangulation((long) (Trianguation *)) accepts a pointer # to an existing SnapPea Triangulation. if spec in range(5,8): self.triangulation = SnapPeaC.get_cusped_census_manifold(spec, orientable, index) elif type(spec) == type(''): self.triangulation = SnapPeaC.get_triangulation(spec) else: self.triangulation = spec def __del__(self): # Let the SnapPea kernel free its private representation # of a Triangulation. SnapPeaC.free_triangulation(self.triangulation) def __repr__(self): s = '\n' s = s + 'name: %s' % SnapPeaC.get_triangulation_name(self.triangulation) + '\n' s = s + 'volume: %f' % SnapPeaC.volume(self.triangulation) + '\n' s = s + 'homology: %s' % self.homology() + '\n' s = s + 'Dehn fillings:\n' for i in range(SnapPeaC.get_num_cusps(self.triangulation)): s = s + '%3i ' % i if SnapPeaC.get_cusp_is_complete(self.triangulation, i): s = s + ' complete' else: s = s + '%f ' % SnapPeaC.get_cusp_m(self.triangulation, i) if SnapPeaC.get_cusp_is_orientable(self.triangulation, i): s = s + '%f ' % SnapPeaC.get_cusp_l(self.triangulation, i) s = s + '\n' return s def save(self, file_name): if len(file_name) > 0: SnapPeaC.set_triangulation_name(self.triangulation, file_name) SnapPeaC.save_triangulation(self.triangulation, file_name) def clone(self): return Triangulation(SnapPeaC.copy_triangulation(self.triangulation)) def get_name(self): return SnapPeaC.get_triangulation_name(self.triangulation) def set_name(self, name): SnapPeaC.set_triangulation_name(self.triangulation, name) def get_triangulation_is_orientable(self): return SnapPeaC.get_triangulation_is_orientable(self.triangulation) def get_solution_type(self): return SnapPeaC.get_solution_type(self.triangulation) def get_num_tetrahedra(self): return SnapPeaC.get_num_tetrahedra(self.triangulation) def get_num_cusps(self): return SnapPeaC.get_num_cusps(self.triangulation) def get_cusp_is_orientable(self, i): return SnapPeaC.get_cusp_is_orientable(self.triangulation, i) def get_cusp_is_complete(self, i): return SnapPeaC.get_cusp_is_complete(self.triangulation, i) def get_cusp_m(self, i): return SnapPeaC.get_cusp_m(self.triangulation, i) def get_cusp_l(self, i): return SnapPeaC.get_cusp_l(self.triangulation, i) def set_cusp(self, index, m = 0, l = 0, recompute = 1): # set_cusp(i) # makes cusp i complete. # # set_cusp(i, m, l) # does (m,l) Dehn filling on cusp i. # (As a special case, set_cusp(i, 0, 0) is equivalent # to set_cusp(i), and makes the cusp complete.) # # set_cusp(i, m, l, 0) # sets the cusp coefficients, but doesn't attempt # to find the hyperbolic structure. # (set_cusp(i, m, l, 1) is equivalent to set_cusp(i, m, l).) if index in range(SnapPeaC.get_num_cusps(self.triangulation)): SnapPeaC.set_cusp_info(self.triangulation, index, m, l, recompute) else: return 'cusps are numbered 0 through %i\n' % SnapPeaC.get_num_cusps(self.triangulation) def cusp_is_fillable(self, index): return SnapPeaC.cusp_is_fillable(self.triangulation, index) def remove_Dehn_fillings(self): return SnapPeaC.remove_Dehn_fillings(self.triangulation) def tet_shapes(self, use_fixed_coordinates): return SnapPeaC.tet_shapes(self.triangulation, use_fixed_coordinates) def volume(self): return SnapPeaC.volume(self.triangulation) def homology(self): # If the homology couldn't be computed # (perhaps because some Dehn filling coefficients # aren't integers) return None. # Otherwise return the corresponding AbelianGroup. # Note: An empty coefficient list is not None, # and will generate an AbelianGroup with # no torsion coefficients. theTorsionCoefficients = SnapPeaC.homology(self.triangulation) if theTorsionCoefficients == None: return None else: return AbelianGroup(theTorsionCoefficients) def fundamental_group( self, simplify_flag = 1, fillings_affect_generators_flag = 1, minimize_num_generators_flag = 0): return FundamentalGroup(SnapPeaC.fundamental_group( self.triangulation, simplify_flag, fillings_affect_generators_flag, minimize_num_generators_flag)) def fill_cusp(self, index): if index not in range(self.get_num_cusps()): return 'cusps are numbered 0 through %i\n' % self.get_num_cusps() elif self.get_num_cusps() == 1: print 'The triangulation must retain at least one cusp.' elif not self.cusp_is_fillable(index): print 'To permanently fill a cusp, the Dehn filling coefficients must be relatively prime integers.' else: theFilledTriangulation = SnapPeaC.fill_cusp(self.triangulation, index) if (theFilledTriangulation != self.triangulation): SnapPeaC.free_triangulation(self.triangulation) self.triangulation = theFilledTriangulation def get_drillable_curves(self, max_segments = 6): return SnapPeaC.get_drillable_curves(self.triangulation, max_segments) def drill_curve(self, index, max_segments = 6): theDrilledTriangulation = SnapPeaC.drill_curve(self.triangulation, index, max_segments) if (theDrilledTriangulation != 0): SnapPeaC.free_triangulation(self.triangulation) self.triangulation = theDrilledTriangulation def get_normal_surfaces(self): return SnapPeaC.get_normal_surfaces(self.triangulation) def split_on_normal_surface(self, index): # SnapPeaC produces bare SnapPea Triangulations. # Convert them to Python Triangulation objects. theBareTriangulations = SnapPeaC.split_along_normal_surface(self.triangulation, index) theTriangulationObjects = [] for theBareTriangulation in theBareTriangulations: theTriangulationObjects.append(Triangulation(theBareTriangulation)) return theTriangulationObjects def core_geodesic(self, index): return SnapPeaC.core_geodesic(self.triangulation, index) def shortest_curves_become_meridians(self): SnapPeaC.shortest_curves_become_meridians(self.triangulation) def current_fillings_become_meridians(self): SnapPeaC.current_fillings_become_meridians(self.triangulation) def simplify(self): SnapPeaC.basic_simplification(self.triangulation) def randomize(self): SnapPeaC.randomize_triangulation(self.triangulation) def reflect(self): SnapPeaC.reorient(self.triangulation) def canonize(self): SnapPeaC.proto_canonize(self.triangulation) def is_canonical_triangulation(self): return SnapPeaC.is_canonical_triangulation(self.triangulation) def symmetry_group(self): theRawResults = SnapPeaC.symmetry_group(self.triangulation) thePackagedResults = {} if theRawResults[0] == 0: thePackagedResults['manifold'] = None else: thePackagedResults['manifold'] = SymmetryGroup(theRawResults[0], theRawResults[3]) if theRawResults[1] == 0: thePackagedResults['link'] = None else: thePackagedResults['link'] = SymmetryGroup(theRawResults[1], theRawResults[3]) if theRawResults[2] == 0: thePackagedResults['symmetric triangulation'] = None else: thePackagedResults['symmetric triangulation'] = Triangulation(theRawResults[2]) return thePackagedResults def Dirichlet(self, centroid_at_origin=1, maximize_inj_radius=0, displacement=(0,0,0)): return DirichletDomain(SnapPeaC.Dirichlet( self.triangulation, centroid_at_origin, maximize_inj_radius, displacement[0], displacement[1], displacement[2])) class CuspedCensus: def __init__(self, num_tetrahedra = 5, orientable = 'o'): self.num_tetrahedra = num_tetrahedra self.orientable = (orientable == 'o' or orientable == 'O') if num_tetrahedra == 5: self.num_manifolds = 415 elif num_tetrahedra == 6: if self.orientable: self.num_manifolds = 962 else: self.num_manifolds = 259 elif num_tetrahedra == 7: if self.orientable: self.num_manifolds = 3552 else: self.num_manifolds = 887 else: self.num_manifolds = 0 def __repr__(self): if self.num_tetrahedra == 5: return 'census of all cusped hyperbolic 3-manifolds triangulable with 5 or fewer ideal tetrahedra' elif self.num_tetrahedra == 6: if self.orientable: return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra' else: return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra' elif self.num_tetrahedra == 7: if self.orientable: return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra' else: return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra' else: return 'empty census' def __len__(self): return self.num_manifolds def __getitem__(self, i): if not 0 <= i < self.num_manifolds: raise IndexError else: return Triangulation(self.num_tetrahedra, self.orientable, i) class AbelianGroup: def __init__(self, coefficients): self.coefficients = coefficients def __repr__(self): coef = self.coefficients n = len(coef) if (n == 0): return 'trivial' s = '' for i in range(n): s = s + 'Z' if coef[i] != 0: s = s + ('/%i' % coef[i]) if i != n - 1: s = s + ' + ' return s def __len__(self): return len(self.coefficients) def __getitem__(self, i): return self.coefficients[i] def Betti_number(self): return self.coefficients.count(0) class FundamentalGroup: def __init__(self, fundamental_group): # fundamental_group is an integer that is really # a pointer to a SnapPea kernel FundamentalGroup. self.fundamental_group = fundamental_group def __del__(self): # Let the SnapPea kernel free its private representation # of a FundamentalGroup. SnapPeaC.free_group_presentation(self.fundamental_group) def __repr__(self): return ( 'generators: ' + self.generators_string() + '\n' + 'relations:\n' + self.relations_string() ) def num_generators(self): return SnapPeaC.fg_get_num_generators(self.fundamental_group) def relations(self): return SnapPeaC.fg_get_relations(self.fundamental_group) def representation(self, word): return SnapPeaC.fg_representation(self.fundamental_group, word) def peripheral_curves(self): return SnapPeaC.fg_peripheral_curves(self.fundamental_group) def generators_string(self): theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z' if self.num_generators() > 0: return theAlphabet[0 : 2*self.num_generators() - 1] else: return 'none' def relations_string(self): theString = '' for theWord in self.relations(): theString = theString + theWord + '\n' return theString class SymmetryGroup: def __init__(self, symmetry_group, is_full_group, owns_symmetry_group=True): # symmetry_group is an integer that is really # a pointer to a SnapPea kernel SymmetryGroup. self.symmetry_group = symmetry_group self.is_full_group = is_full_group self.owns_symmetry_group = owns_symmetry_group def __del__(self): # Let the SnapPea kernel free its private representation # of a SymmetryGroup. if self.owns_symmetry_group: SnapPeaC.free_symmetry_group(self.symmetry_group) def __repr__(self): if self.is_full_group: thePretext = '' else: thePretext = 'at least ' if self.is_abelian(): theText = self.abelian_description().__repr__() elif self.is_dihedral(): theText = 'D%d'%(self.order()/2) elif self.is_polyhedral(): theText = self.polyhedral_description()['name'] elif self.is_S5(): theText = 'S5' elif self.is_direct_product(): theText = self.get_factor(0).__repr__() \ + ' X ' \ + self.get_factor(1).__repr__() else: theText = 'nonabelian group of order %d'%self.order() return thePretext + theText def order(self): return SnapPeaC.symmetry_group_order(self.symmetry_group) def is_abelian(self): return SnapPeaC.symmetry_group_is_abelian(self.symmetry_group) def abelian_description(self): return AbelianGroup(SnapPeaC.symmetry_group_abelian_description(self.symmetry_group)) def is_dihedral(self): return SnapPeaC.symmetry_group_is_dihedral(self.symmetry_group) def is_polyhedral(self): return SnapPeaC.symmetry_group_is_polyhedral(self.symmetry_group) def polyhedral_description(self): return SnapPeaC.symmetry_group_polyhedral_description(self.symmetry_group) def is_S5(self): return SnapPeaC.symmetry_group_is_S5(self.symmetry_group) def is_direct_product(self): return SnapPeaC.symmetry_group_is_direct_product(self.symmetry_group) def get_factor(self, i): return SymmetryGroup( SnapPeaC.symmetry_group_factor(self.symmetry_group, i), True, False) def is_amphicheiral(self): return SnapPeaC.symmetry_group_is_amphicheiral(self.symmetry_group) def is_invertible_knot(self): return SnapPeaC.symmetry_group_invertible_knot(self.symmetry_group) # Please don't confuse abelian_description() (above) # with abelianization() (below). The former describes # a group which already happens to be abelian, while # the latter gives the quotient of the group by its # commutator subgroup. def commutator_subgroup(self): return SymmetryGroup( SnapPeaC.symmetry_group_commutator_subgroup(self.symmetry_group), self.is_full_group, True) def abelianization(self): if self.is_full_group: # Compute the abelianization as a pointer to a SnapPea kernel # internal SymmetryGroup data structure. theSnapPeaGroup = SnapPeaC.symmetry_group_abelianization(self.symmetry_group) # Create the corresponding Python AbelianGroup object. theAbelianization = AbelianGroup(SnapPeaC.symmetry_group_abelian_description(theSnapPeaGroup)) # Free the SnapPea kernel's data structure. SnapPeaC.free_symmetry_group(theSnapPeaGroup) # Return the Python object. return theAbelianization else: return None def center(self): if self.is_full_group: return SymmetryGroup( SnapPeaC.symmetry_group_center(self.symmetry_group), True, True) else: return None def presentation(self): if self.is_full_group: return SnapPeaC.symmetry_group_presentation(self.symmetry_group) else: return None def presentation_text(self): if self.is_full_group: theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z' thePresentation = self.presentation() theText = '{' theText = theText + theAlphabet[0 : 2*thePresentation['number of generators'] - 1] theText = theText + ' |' theLowerAlphabet = 'abcdefghijklmnopqrstuvwxyz' theUpperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' theFirstRelationFlag = 1 for theRelation in thePresentation['relations']: if theFirstRelationFlag == 0: theText = theText + ',' else: theFirstRelationFlag = 0 for theFactor in theRelation: if theFactor[1] < 0: theLetter = theUpperAlphabet[theFactor[0]] thePower = - theFactor[1] else: theLetter = theLowerAlphabet[theFactor[0]] thePower = theFactor[1] if thePower > 1: theText = theText + ' %c^%i'%(theLetter, thePower) elif thePower == 1: theText = theText + ' %c'%(theLetter) else: raise RuntimeError, 'zero exponent in symmetry group presentation' theText = theText + ' }' return theText else: return None class DirichletDomain: def __init__(self, Dirichlet_domain): # Dirichlet_domain is an integer that is really # a pointer to a SnapPea kernel WEPolyhedron. self.Dirichlet_domain = Dirichlet_domain def __del__(self): # Let the SnapPea kernel free its private representation # of a WEPolyhedron. SnapPeaC.free_Dirichlet_domain(self.Dirichlet_domain) def __repr__(self): return 'Dirichlet domain v=? e=? f=?' def off(self): theText = 'OFF %d %d 0\n\n'%(self.v(), self.f()) theVertices = self.vertices() for i in range(self.v()): theText = theText + '%11.8f %11.8f %11.8f\n'%theVertices[i] theText = theText + '\n' theFaces = self.faces() theFaceColors = self.face_colors() for i in range(self.f()): theText = theText + '%2d'%len(theFaces[i]) for j in range(len(theFaces[i])): theText = theText + ' %2d'%theFaces[i][j] theText = theText + ' %5.3f %5.3f %5.3f 1.000\n'%theFaceColors[i] return theText def v(self): return SnapPeaC.Dirichlet_num_vertices(self.Dirichlet_domain) def e(self): return SnapPeaC.Dirichlet_num_edges(self.Dirichlet_domain) def f(self): return SnapPeaC.Dirichlet_num_faces(self.Dirichlet_domain) def vertices(self): return SnapPeaC.Dirichlet_vertices(self.Dirichlet_domain) def faces(self): return SnapPeaC.Dirichlet_faces(self.Dirichlet_domain) def face_colors(self): return SnapPeaC.Dirichlet_face_colors(self.Dirichlet_domain) def face_pairings(self): return SnapPeaC.Dirichlet_face_pairings(self.Dirichlet_domain) snappea-3.0d3/SnapPeaPython/links.py0100644000175000017500000000136207067224762015526 0ustar babbab# links.py # Search for link complements in the 5-tetrahedron census. # Note: The pound sign (#) introduces comments. from SnapPea import * # Examine each Triangulation in the 5-tetrahedron census. for t in CuspedCensus(): # For each cusp, do a Dehn filling on the shortest # curve, which, by SnapPea's convention, is the # meridional filling (1,0). for i in range(t.get_num_cusps()): t.set_cusp(i, 1, 0) # We now have a closed manifold. # Look at its fundamental group, and if the # number of generators is zero (meaning the # group is trivial), print the name of # the original Triangulation, which we have shown # to be a link complement in a homotopy sphere. if t.fundamental_group().num_generators() == 0: print t.get_name() snappea-3.0d3/SnapPeaPython/open0100754000175000017500000000011307036433231014700 0ustar babbabnedit SnapPeaGUI.py SnapPea.py SnapPeaC.c ../../kernel/headers/SnapPea.h & snappea-3.0d3/SnapPeaPython/SnapPeaC.c0100644000175000017500000017250207063251165015630 0ustar babbab// SnapPeaC.c // // The Python interface to the SnapPea kernel consists of two parts. // // SnapPea.py (in a different file)(this file) defines a set // of objects (Triangulation, AbelianGroup, etc.) purely in Python. // // SnapPeaC.c (this file) implements SnapPea.py's methods as a set // of wrappers to the standard SnapPea kernel functions, // which are written in C. // // Technical comment: There's the awkward question of how the Python // interpreter is to keep copies of pointers to SnapPea data structures, // such as Triangulations, which SnapPea.h exports as "opaque typedefs". // The present solution is to pass them to the interpreter as long ints. // If your compiler can't find Python.h, please go to the makefile // and adjust the line // // PYTHON_DIRECTORY = /usr/include/python1.5 // // If you don't know where Python.h is, try "locate Python.h". #include #include #include "assert.h" #include "SnapPea.h" // from SnapPea's unix kit #include "unix_cusped_census.h" #include "unix_file_io.h" #define FALSE 0 #define TRUE 1 void initSnapPeaC(void); static char *RelationToString(int *aSnapPeaRelation); #define DECLARE_WRAPPER(name) \ static PyObject *name(PyObject *self, PyObject *args); DECLARE_WRAPPER(wrap_verify_my_malloc_usage); DECLARE_WRAPPER(wrap_get_triangulation); DECLARE_WRAPPER(wrap_save_triangulation); DECLARE_WRAPPER(wrap_copy_triangulation); DECLARE_WRAPPER(wrap_get_cusped_census_manifold); DECLARE_WRAPPER(wrap_free_triangulation); DECLARE_WRAPPER(wrap_get_triangulation_name); DECLARE_WRAPPER(wrap_set_triangulation_name); DECLARE_WRAPPER(wrap_get_triangulation_is_orientable); DECLARE_WRAPPER(wrap_get_solution_type); DECLARE_WRAPPER(wrap_get_num_tetrahedra); DECLARE_WRAPPER(wrap_get_num_cusps); DECLARE_WRAPPER(wrap_get_cusp_is_complete); DECLARE_WRAPPER(wrap_get_cusp_is_orientable); DECLARE_WRAPPER(wrap_get_cusp_m); DECLARE_WRAPPER(wrap_get_cusp_l); DECLARE_WRAPPER(wrap_set_cusp_info); DECLARE_WRAPPER(wrap_cusp_is_fillable); DECLARE_WRAPPER(wrap_remove_Dehn_fillings); DECLARE_WRAPPER(wrap_volume); DECLARE_WRAPPER(wrap_homology); DECLARE_WRAPPER(wrap_fill_cusp); DECLARE_WRAPPER(wrap_get_drillable_curves); DECLARE_WRAPPER(wrap_drill_curve); DECLARE_WRAPPER(wrap_get_normal_surfaces); DECLARE_WRAPPER(wrap_split_along_normal_surface); DECLARE_WRAPPER(wrap_fundamental_group); DECLARE_WRAPPER(wrap_free_group_presentation); DECLARE_WRAPPER(wrap_fg_get_num_generators); DECLARE_WRAPPER(wrap_fg_get_relations); DECLARE_WRAPPER(wrap_fg_representation); DECLARE_WRAPPER(wrap_fg_peripheral_curves); DECLARE_WRAPPER(wrap_core_geodesic); DECLARE_WRAPPER(wrap_shortest_curves_become_meridians); DECLARE_WRAPPER(wrap_current_fillings_become_meridians); DECLARE_WRAPPER(wrap_basic_simplification); DECLARE_WRAPPER(wrap_randomize_triangulation); DECLARE_WRAPPER(wrap_reorient); DECLARE_WRAPPER(wrap_proto_canonize); DECLARE_WRAPPER(wrap_is_canonical_triangulation); DECLARE_WRAPPER(wrap_symmetry_group); DECLARE_WRAPPER(wrap_free_symmetry_group); DECLARE_WRAPPER(wrap_symmetry_group_order); DECLARE_WRAPPER(wrap_symmetry_group_is_abelian); DECLARE_WRAPPER(wrap_symmetry_group_abelian_description); DECLARE_WRAPPER(wrap_symmetry_group_is_dihedral); DECLARE_WRAPPER(wrap_symmetry_group_is_polyhedral); DECLARE_WRAPPER(wrap_symmetry_group_polyhedral_description); DECLARE_WRAPPER(wrap_symmetry_group_is_S5); DECLARE_WRAPPER(wrap_symmetry_group_is_direct_product); DECLARE_WRAPPER(wrap_symmetry_group_factor); DECLARE_WRAPPER(wrap_symmetry_group_is_amphicheiral); DECLARE_WRAPPER(wrap_symmetry_group_invertible_knot); DECLARE_WRAPPER(wrap_symmetry_group_commutator_subgroup); DECLARE_WRAPPER(wrap_symmetry_group_abelianization); DECLARE_WRAPPER(wrap_symmetry_group_center); DECLARE_WRAPPER(wrap_symmetry_group_presentation); DECLARE_WRAPPER(wrap_tet_shapes); DECLARE_WRAPPER(wrap_Dirichlet); DECLARE_WRAPPER(wrap_free_Dirichlet_domain); DECLARE_WRAPPER(wrap_Dirichlet_num_vertices); DECLARE_WRAPPER(wrap_Dirichlet_num_edges); DECLARE_WRAPPER(wrap_Dirichlet_num_faces); DECLARE_WRAPPER(wrap_Dirichlet_vertices); DECLARE_WRAPPER(wrap_Dirichlet_faces); DECLARE_WRAPPER(wrap_Dirichlet_face_colors); DECLARE_WRAPPER(wrap_Dirichlet_face_pairings); static struct PyMethodDef SnapPeaCMethods[] = { {"verify_my_malloc_usage", wrap_verify_my_malloc_usage, METH_VARARGS}, {"get_triangulation", wrap_get_triangulation, METH_VARARGS}, {"save_triangulation", wrap_save_triangulation, METH_VARARGS}, {"copy_triangulation", wrap_copy_triangulation, METH_VARARGS}, {"get_cusped_census_manifold", wrap_get_cusped_census_manifold, METH_VARARGS}, {"free_triangulation", wrap_free_triangulation, METH_VARARGS}, {"get_triangulation_name", wrap_get_triangulation_name, METH_VARARGS}, {"set_triangulation_name", wrap_set_triangulation_name, METH_VARARGS}, {"get_triangulation_is_orientable", wrap_get_triangulation_is_orientable, METH_VARARGS}, {"get_solution_type", wrap_get_solution_type, METH_VARARGS}, {"get_num_tetrahedra", wrap_get_num_tetrahedra, METH_VARARGS}, {"get_num_cusps", wrap_get_num_cusps, METH_VARARGS}, {"get_cusp_is_complete", wrap_get_cusp_is_complete, METH_VARARGS}, {"get_cusp_is_orientable", wrap_get_cusp_is_orientable, METH_VARARGS}, {"get_cusp_m", wrap_get_cusp_m, METH_VARARGS}, {"get_cusp_l", wrap_get_cusp_l, METH_VARARGS}, {"set_cusp_info", wrap_set_cusp_info, METH_VARARGS}, {"cusp_is_fillable", wrap_cusp_is_fillable, METH_VARARGS}, {"remove_Dehn_fillings", wrap_remove_Dehn_fillings, METH_VARARGS}, {"volume", wrap_volume, METH_VARARGS}, {"homology", wrap_homology, METH_VARARGS}, {"fill_cusp", wrap_fill_cusp, METH_VARARGS}, {"get_drillable_curves", wrap_get_drillable_curves, METH_VARARGS}, {"drill_curve", wrap_drill_curve, METH_VARARGS}, {"get_normal_surfaces", wrap_get_normal_surfaces, METH_VARARGS}, {"split_along_normal_surface", wrap_split_along_normal_surface, METH_VARARGS}, {"fundamental_group", wrap_fundamental_group, METH_VARARGS}, {"free_group_presentation", wrap_free_group_presentation, METH_VARARGS}, {"fg_get_num_generators", wrap_fg_get_num_generators, METH_VARARGS}, {"fg_get_relations", wrap_fg_get_relations, METH_VARARGS}, {"fg_representation", wrap_fg_representation, METH_VARARGS}, {"fg_peripheral_curves", wrap_fg_peripheral_curves, METH_VARARGS}, {"core_geodesic", wrap_core_geodesic, METH_VARARGS}, {"shortest_curves_become_meridians", wrap_shortest_curves_become_meridians, METH_VARARGS}, {"current_fillings_become_meridians", wrap_current_fillings_become_meridians, METH_VARARGS}, {"basic_simplification", wrap_basic_simplification, METH_VARARGS}, {"randomize_triangulation", wrap_randomize_triangulation, METH_VARARGS}, {"reorient", wrap_reorient, METH_VARARGS}, {"proto_canonize", wrap_proto_canonize, METH_VARARGS}, {"is_canonical_triangulation", wrap_is_canonical_triangulation, METH_VARARGS}, {"symmetry_group", wrap_symmetry_group, METH_VARARGS}, {"free_symmetry_group", wrap_free_symmetry_group, METH_VARARGS}, {"symmetry_group_order", wrap_symmetry_group_order, METH_VARARGS}, {"symmetry_group_is_abelian", wrap_symmetry_group_is_abelian, METH_VARARGS}, {"symmetry_group_abelian_description", wrap_symmetry_group_abelian_description, METH_VARARGS}, {"symmetry_group_is_dihedral", wrap_symmetry_group_is_dihedral, METH_VARARGS}, {"symmetry_group_is_polyhedral", wrap_symmetry_group_is_polyhedral, METH_VARARGS}, {"symmetry_group_polyhedral_description", wrap_symmetry_group_polyhedral_description, METH_VARARGS}, {"symmetry_group_is_S5", wrap_symmetry_group_is_S5, METH_VARARGS}, {"symmetry_group_is_direct_product", wrap_symmetry_group_is_direct_product, METH_VARARGS}, {"symmetry_group_factor", wrap_symmetry_group_factor, METH_VARARGS}, {"symmetry_group_is_amphicheiral", wrap_symmetry_group_is_amphicheiral, METH_VARARGS}, {"symmetry_group_invertible_knot", wrap_symmetry_group_invertible_knot, METH_VARARGS}, {"symmetry_group_commutator_subgroup", wrap_symmetry_group_commutator_subgroup, METH_VARARGS}, {"symmetry_group_abelianization", wrap_symmetry_group_abelianization, METH_VARARGS}, {"symmetry_group_center", wrap_symmetry_group_center, METH_VARARGS}, {"symmetry_group_presentation", wrap_symmetry_group_presentation, METH_VARARGS}, {"tet_shapes", wrap_tet_shapes, METH_VARARGS}, {"Dirichlet", wrap_Dirichlet, METH_VARARGS}, {"free_Dirichlet_domain", wrap_free_Dirichlet_domain, METH_VARARGS}, {"Dirichlet_num_vertices", wrap_Dirichlet_num_vertices, METH_VARARGS}, {"Dirichlet_num_edges", wrap_Dirichlet_num_edges, METH_VARARGS}, {"Dirichlet_num_faces", wrap_Dirichlet_num_faces, METH_VARARGS}, {"Dirichlet_vertices", wrap_Dirichlet_vertices, METH_VARARGS}, {"Dirichlet_faces", wrap_Dirichlet_faces, METH_VARARGS}, {"Dirichlet_face_colors", wrap_Dirichlet_face_colors, METH_VARARGS}, {"Dirichlet_face_pairings", wrap_Dirichlet_face_pairings, METH_VARARGS}, {NULL, NULL } }; void initSnapPeaC(void) { (void) Py_InitModule("SnapPeaC", SnapPeaCMethods); } static PyObject *wrap_verify_my_malloc_usage(PyObject *self, PyObject *args) { verify_my_malloc_usage(); return Py_BuildValue(""); } static PyObject *wrap_get_triangulation(PyObject *self, PyObject *args) { char *theName; Triangulation *theTriangulation; if (!PyArg_ParseTuple(args, "s", &theName)) { PyErr_SetString(PyExc_TypeError, "wrap_get_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } theTriangulation = get_triangulation(theName); if (theTriangulation == NULL) { PyErr_SetString(PyExc_RuntimeError, "Could not read named triangulation."); return NULL; } return Py_BuildValue("l", (long int) theTriangulation); } static PyObject *wrap_save_triangulation(PyObject *self, PyObject *args) { long theTriangulation; char *theName; if (!PyArg_ParseTuple(args, "ls", &theTriangulation, &theName)) { PyErr_SetString(PyExc_TypeError, "wrap_save_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } save_triangulation((Triangulation *)theTriangulation, theName); return Py_BuildValue(""); } static PyObject *wrap_copy_triangulation(PyObject *self, PyObject *args) { long theTriangulation; Triangulation *theCopy; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_copy_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } copy_triangulation((Triangulation *)theTriangulation, &theCopy); if (theCopy == NULL) { PyErr_SetString(PyExc_RuntimeError, "Could not copy the triangulation."); return NULL; } return Py_BuildValue("l", (long int) theCopy); } static PyObject *wrap_get_cusped_census_manifold(PyObject *self, PyObject *args) { int theCensus; // 5, 6, or 7 int theOrientability; // 0 (false) or 1 (true) int theIndex; // 0 through #manifolds - 1 Triangulation* theTriangulation; if (!PyArg_ParseTuple(args, "iii", &theCensus, &theOrientability, &theIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_get_cusped_census_manifold() in SnapPeaC.c received data of the wrong type."); return NULL; } theTriangulation = GetCuspedCensusManifold( theCensus, theOrientability ? oriented_manifold : nonorientable_manifold, theIndex); if (theTriangulation == NULL) { PyErr_SetString(PyExc_RuntimeError, "Could not load requested census manifold."); return NULL; } return Py_BuildValue("l", (long int) theTriangulation); } static PyObject *wrap_free_triangulation(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_free_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } free_triangulation((Triangulation *) theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_get_triangulation_name(PyObject *self, PyObject *args) { long theTriangulation; char *theName; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_triangulation_name() in SnapPeaC.c received data of the wrong type."); return NULL; } // We get a pointer to the actual name string (not a copy), // so we won't need to free it. theName = get_triangulation_name((Triangulation *) theTriangulation); return Py_BuildValue("s", theName); } static PyObject *wrap_set_triangulation_name(PyObject *self, PyObject *args) { long theTriangulation; char *theName; if (!PyArg_ParseTuple(args, "ls", &theTriangulation, &theName)) { PyErr_SetString(PyExc_TypeError, "wrap_set_triangulation_name() in SnapPeaC.c received data of the wrong type."); return NULL; } set_triangulation_name((Triangulation *)theTriangulation, theName); return Py_BuildValue(""); } static PyObject *wrap_get_triangulation_is_orientable(PyObject *self, PyObject *args) { long theTriangulation; Orientability theOrientability; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_triangulation_is_orientable() in SnapPeaC.c received data of the wrong type."); return NULL; } theOrientability = get_orientability((Triangulation *) theTriangulation); if (theOrientability != oriented_manifold && theOrientability != nonorientable_manifold) { PyErr_SetString(PyExc_RuntimeError, "Triangulation's orientability is unknown."); return NULL; } return Py_BuildValue("i", theOrientability == oriented_manifold); } static PyObject *wrap_get_solution_type(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_solution_type() in SnapPeaC.c received data of the wrong type."); return NULL; } switch (get_filled_solution_type((Triangulation *) theTriangulation)) { case not_attempted: return Py_BuildValue("s", "not attempted"); case geometric_solution: return Py_BuildValue("s", "all tetrahedra positively oriented"); case nongeometric_solution: return Py_BuildValue("s", "contains negatively oriented tetrahedra"); case flat_solution: return Py_BuildValue("s", "contains flat tetrahedra"); case degenerate_solution: return Py_BuildValue("s", "contains degenerate tetrahedra"); case other_solution: return Py_BuildValue("s", "unrecognized solution type"); case no_solution: return Py_BuildValue("s", "no solution found"); default: PyErr_SetString(PyExc_RuntimeError, "Bad solution type."); return NULL; } } static PyObject *wrap_get_num_tetrahedra(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_num_tetrahedra() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", get_num_tetrahedra((Triangulation *) theTriangulation)); } static PyObject *wrap_get_num_cusps(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_num_cusps() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", get_num_cusps((Triangulation *) theTriangulation)); } static PyObject *wrap_get_cusp_is_complete(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; Boolean theCuspIsComplete; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_get_cusp_is_complete() in SnapPeaC.c received data of the wrong type."); return NULL; } get_cusp_info( (Triangulation *) theTriangulation, theCuspIndex, NULL, &theCuspIsComplete, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return Py_BuildValue("i", theCuspIsComplete); } static PyObject *wrap_get_cusp_is_orientable(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; CuspTopology theCuspTopology; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_get_cusp_is_orientable() in SnapPeaC.c received data of the wrong type."); return NULL; } get_cusp_info( (Triangulation *) theTriangulation, theCuspIndex, &theCuspTopology, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); // Python scripts should never encounter Cusps of unknown CuspTopology. if (theCuspTopology != torus_cusp && theCuspTopology != Klein_cusp) { PyErr_SetString(PyExc_RuntimeError, "Unknown cusp topology."); return NULL; } return Py_BuildValue("i", theCuspTopology == torus_cusp); } static PyObject *wrap_get_cusp_m(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; double m; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_get_cusp_m() in SnapPeaC.c received data of the wrong type."); return NULL; } get_cusp_info( (Triangulation *) theTriangulation, theCuspIndex, NULL, NULL, &m, NULL, NULL, NULL, NULL, NULL, NULL, NULL); return Py_BuildValue("d", m); } static PyObject *wrap_get_cusp_l(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; double l; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_get_cusp_l() in SnapPeaC.c received data of the wrong type."); return NULL; } get_cusp_info( (Triangulation *) theTriangulation, theCuspIndex, NULL, NULL, NULL, &l, NULL, NULL, NULL, NULL, NULL, NULL); return Py_BuildValue("d", l); } static PyObject *wrap_set_cusp_info(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; double m, l; int theRecomputeFlag; if (!PyArg_ParseTuple(args, "liddi", &theTriangulation, &theCuspIndex, &m, &l, &theRecomputeFlag)) { PyErr_SetString(PyExc_TypeError, "wrap_set_cusp_info() in SnapPeaC.c received data of the wrong type."); return NULL; } set_cusp_info( (Triangulation *) theTriangulation, theCuspIndex, (m == 0.0 && l == 0.0), m, l); if (theRecomputeFlag) (void) do_Dehn_filling((Triangulation *) theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_cusp_is_fillable(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_cusp_is_fillable() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", cusp_is_fillable((Triangulation *) theTriangulation, theCuspIndex)); } static PyObject *wrap_remove_Dehn_fillings(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_remove_Dehn_fillings() in SnapPeaC.c received data of the wrong type."); return NULL; } (void) remove_Dehn_fillings((Triangulation *) theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_volume(PyObject *self, PyObject *args) { long theTriangulation; double theVolume; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_volume() in SnapPeaC.c received data of the wrong type."); return NULL; } theVolume = volume((Triangulation *) theTriangulation, NULL); return Py_BuildValue("d", theVolume); } static PyObject *wrap_homology(PyObject *self, PyObject *args) { long theTriangulation; AbelianGroup *theHomology; PyObject *theList; int i; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_homology() in SnapPeaC.c received data of the wrong type."); return NULL; } theHomology = homology((Triangulation *) theTriangulation); if (theHomology == NULL) return Py_BuildValue(""); compress_abelian_group(theHomology); theList = PyList_New(theHomology->num_torsion_coefficients); for (i = 0; i < theHomology->num_torsion_coefficients; i++) PyList_SetItem(theList, i, Py_BuildValue("l", theHomology->torsion_coefficients[i])); free_abelian_group(theHomology); return theList; } static PyObject *wrap_fill_cusp(PyObject *self, PyObject *args) { long theTri; Triangulation *theTriangulation, *theResult; int theIndex, n, i; Boolean *theFillArray; if (!PyArg_ParseTuple(args, "li", &theTri, &theIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_fill_cusp() in SnapPeaC.c received data of the wrong type."); return NULL; } theTriangulation = (Triangulation *) theTri; if (theIndex < 0 || theIndex >= get_num_cusps(theTriangulation)) { PyErr_SetString(PyExc_RuntimeError, "Cusp index out of bounds."); return NULL; } // If the cusp isn't fillable, return the original Triangulation. if (cusp_is_fillable(theTriangulation, theIndex) == FALSE) return Py_BuildValue("l", theTri); // Otherwise do the filling, and return the result. n = get_num_cusps(theTriangulation); theFillArray = (Boolean *) malloc(n * sizeof(Boolean)); if (theFillArray == NULL) { PyErr_SetString(PyExc_RuntimeError, "Couldn't allocate theFillArray."); return NULL; } for (i = 0; i < n; i++) theFillArray[i] = FALSE; theFillArray[theIndex] = TRUE; theResult = fill_cusps( theTriangulation, theFillArray, get_triangulation_name(theTriangulation), FALSE); free(theFillArray); if (theResult == NULL) { PyErr_SetString(PyExc_RuntimeError, "Couldn't fill the requested cusps."); return NULL; } else return Py_BuildValue("l", (long int) theResult); } static PyObject *wrap_get_drillable_curves(PyObject *self, PyObject *args) { long theTriangulation; int theMaxSegments, theNumCurves, i; DualOneSkeletonCurve **theCurves; MatrixParity theParity; Complex theCompleteLength, theFilledLength; PyObject *theReturnData, *theCurveData; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theMaxSegments)) { PyErr_SetString(PyExc_TypeError, "wrap_get_drillable_curves() in SnapPeaC.c received data of the wrong type."); return NULL; } dual_curves((Triangulation *) theTriangulation, theMaxSegments, &theNumCurves, &theCurves); theReturnData = PyList_New(theNumCurves); for (i = 0; i < theNumCurves; i++) { get_dual_curve_info(theCurves[i], &theCompleteLength, &theFilledLength, &theParity); theCurveData = Py_BuildValue("[i,d,d,d,d]", theParity == orientation_reversing, theFilledLength.real, theFilledLength.imag, theCompleteLength.real, theCompleteLength.imag); PyList_SetItem(theReturnData, i, theCurveData); // PyList_SetItem() takes ownership of the reference to theCurveData, // so we do NOT decrement theCurveData's reference count. } free_dual_curves(theNumCurves, theCurves); return theReturnData; } static PyObject *wrap_drill_curve(PyObject *self, PyObject *args) { long theTriangulation; int theMaxSegments, theIndex, theNumCurves; Triangulation *theResult; DualOneSkeletonCurve **theCurves; if (!PyArg_ParseTuple(args, "lii", &theTriangulation, &theIndex, &theMaxSegments)) { PyErr_SetString(PyExc_TypeError, "wrap_drill_curve() in SnapPeaC.c received data of the wrong type."); return NULL; } dual_curves((Triangulation *) theTriangulation, theMaxSegments, &theNumCurves, &theCurves); if (theIndex < 0 || theIndex >= theNumCurves) { free_dual_curves(theNumCurves, theCurves); PyErr_SetString(PyExc_RuntimeError, "Curve index out of bounds."); return NULL; } theResult = drill_cusp( (Triangulation *) theTriangulation, theCurves[theIndex], get_triangulation_name((Triangulation *) theTriangulation) ); free_dual_curves(theNumCurves, theCurves); if (theResult == NULL) { // theResult will be NULL if the curve is boundary parallel. // At present such curves aren't offered to the user, // so NULL results shouldn't occur. If we want to allow // them in the future, replace "return NULL" with // // return Py_BuildValue("l", (long int) 0); // PyErr_SetString(PyExc_RuntimeError, "Requested curve cannot be drilled because it's parallel to the boundary."); return NULL; } else return Py_BuildValue("l", (long int) theResult); } static PyObject *wrap_get_normal_surfaces(PyObject *self, PyObject *args) { long theTriangulation; NormalSurfaceList *theSurfaceList; int theNumSurfaces, i; PyObject *theReturnData, *theSurfaceDescription; char theDescription[128]; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_get_normal_surfaces() in SnapPeaC.c received data of the wrong type."); return NULL; } switch (find_normal_surfaces((Triangulation *) theTriangulation, &theSurfaceList)) { case func_OK: theNumSurfaces = number_of_normal_surfaces_on_list(theSurfaceList); theReturnData = PyList_New(theNumSurfaces); for (i = 0; i < theNumSurfaces; i++) { if (normal_surface_is_two_sided(theSurfaceList, i)) strcpy(theDescription, "2-sided "); else strcpy(theDescription, "1-sided "); switch (normal_surface_Euler_characteristic(theSurfaceList, i)) { case 2: assert(normal_surface_is_orientable(theSurfaceList, i) == TRUE); strcat(theDescription, "sphere"); break; case 1: assert(normal_surface_is_orientable(theSurfaceList, i) == FALSE); strcat(theDescription, "projective plane"); break; case 0: if (normal_surface_is_orientable(theSurfaceList, i)) strcat(theDescription, "torus"); else strcat(theDescription, "Klein bottle"); break; default: // shouldn't ever happen Py_DECREF(theReturnData); free_normal_surfaces(theSurfaceList); PyErr_SetString(PyExc_RuntimeError, "'Impossible' situation: splitting surface as Euler characteristic not equal to 0, 1, or 2."); return NULL; } theSurfaceDescription = Py_BuildValue("s", theDescription); PyList_SetItem(theReturnData, i, theSurfaceDescription); // PyList_SetItem() takes ownership of the reference // to theSurfaceDescription, so we do NOT decrement // theSurfaceDescription's reference count. } free_normal_surfaces(theSurfaceList); return theReturnData; case func_bad_input: return Py_BuildValue("[s]", "splittings not yet available for closed manifolds"); default: return Py_BuildValue("[s]", "???"); // should never occur } } static PyObject *wrap_split_along_normal_surface(PyObject *self, PyObject *args) { long theTriangulation; int theIndex; NormalSurfaceList *theSurfaceList; int theNumSurfaces; Triangulation *thePieces[2]; PyObject *thePieceList; char *theOldName, *theNewName; int i; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_split_along_normal_surface() in SnapPeaC.c received data of the wrong type."); return NULL; } if (func_OK != find_normal_surfaces((Triangulation *) theTriangulation, &theSurfaceList)) { PyErr_SetString(PyExc_RuntimeError, "Couldn't split along normal surface. 2-sided projective plane?"); return NULL; } theNumSurfaces = number_of_normal_surfaces_on_list(theSurfaceList); assert(theIndex >= 0 && theIndex < theNumSurfaces); // Even if split_along_normal_surface() returns func_bad_input, // we can proceed normally. thePieces[] will both be NULL. (void) split_along_normal_surface(theSurfaceList, theIndex, thePieces); free_normal_surfaces(theSurfaceList); thePieceList = PyList_New(0); theOldName = get_triangulation_name((Triangulation *) theTriangulation); for (i = 0; i < 2; i++) if (thePieces[i] != NULL) { theNewName = (char *) malloc((strlen(theOldName) + 3) * sizeof(char)); strcpy(theNewName, theOldName); strcat(theNewName, i == 0 ? ".a" : ".b"); set_triangulation_name(thePieces[i], theNewName); free(theNewName); PyList_Append(thePieceList, Py_BuildValue("l", (long) thePieces[i])); } return thePieceList; } static char *RelationToString(int *aSnapPeaRelation) { int *theLetter, theRelationLength, i; char *theCString; // How long is aSnapPeaRelation? theRelationLength = 0; for ( theLetter = aSnapPeaRelation; *theLetter != 0; theLetter++) theRelationLength++; // Allocate memory for theCString. theCString = malloc(theRelationLength + 1); assert(theCString != NULL); // Write the relation. for ( theLetter = aSnapPeaRelation, i = 0; *theLetter != 0; theLetter++, i++) { if (*theLetter > 0) theCString[i] = 'a' - 1 + *theLetter; else theCString[i] = 'A' - 1 - *theLetter; } assert(i == theRelationLength); theCString[i] = 0; return theCString; } static PyObject *wrap_fundamental_group(PyObject *self, PyObject *args) { long theTriangulation; int theSimplifyFlag, theFillingsAffectGeneratorsFlag, theMinimizeNumGeneratorsFlag; GroupPresentation *theFundamentalGroup; if (!PyArg_ParseTuple( args, "liii", &theTriangulation, &theSimplifyFlag, &theFillingsAffectGeneratorsFlag, &theMinimizeNumGeneratorsFlag)) { PyErr_SetString(PyExc_TypeError, "wrap_fundamental_group() in SnapPeaC.c received data of the wrong type."); return NULL; } theFundamentalGroup = fundamental_group( (Triangulation *) theTriangulation, theSimplifyFlag, theFillingsAffectGeneratorsFlag, theMinimizeNumGeneratorsFlag); return Py_BuildValue("l", (long) theFundamentalGroup); } static PyObject *wrap_free_group_presentation(PyObject *self, PyObject *args) { long theFundamentalGroup; if (!PyArg_ParseTuple(args, "l", &theFundamentalGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_free_group_presentation() in SnapPeaC.c received data of the wrong type."); return NULL; } free_group_presentation((GroupPresentation *) theFundamentalGroup); return Py_BuildValue(""); } static PyObject *wrap_fg_get_num_generators(PyObject *self, PyObject *args) { long theFundamentalGroup; if (!PyArg_ParseTuple(args, "l", &theFundamentalGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_fg_get_num_generators() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (fg_integer_fillings((GroupPresentation *) theFundamentalGroup) == TRUE) ? fg_get_num_generators((GroupPresentation *) theFundamentalGroup) : 0); } static PyObject *wrap_fg_get_relations(PyObject *self, PyObject *args) { long theFundamentalGroup; int theNumRelations, *theRawRelation, i; char *theString; PyObject *theRelationList; if (!PyArg_ParseTuple(args, "l", &theFundamentalGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_fg_get_relations() in SnapPeaC.c received data of the wrong type."); return NULL; } if (fg_integer_fillings((GroupPresentation *) theFundamentalGroup) == TRUE) { theNumRelations = fg_get_num_relations((GroupPresentation *) theFundamentalGroup); theRelationList = PyList_New(theNumRelations); for (i = 0; i < theNumRelations; i++) { theRawRelation = fg_get_relation((GroupPresentation *) theFundamentalGroup, i); theString = RelationToString(theRawRelation); fg_free_relation(theRawRelation); PyList_SetItem(theRelationList, i, Py_BuildValue("s", theString)); free(theString); } return theRelationList; } else return Py_BuildValue("[]"); } static PyObject *wrap_fg_representation(PyObject *self, PyObject *args) { long theFundamentalGroup; int theWordLength; char *theWord; int *theSnapPeaWord; int i; FuncResult theError; O31Matrix theO31Matrix; MoebiusTransformation theMoebiusTransformation; double (*o)[4]; Complex (*s)[2]; if (!PyArg_ParseTuple( args, "ls", &theFundamentalGroup, &theWord)) { PyErr_SetString(PyExc_TypeError, "wrap_fg_representation() in SnapPeaC.c received data of the wrong type."); return NULL; } theWordLength = strlen(theWord); theSnapPeaWord = malloc((theWordLength + 1) * sizeof(int)); for (i = 0; i < theWordLength; i++) { if (islower(theWord[i])) theSnapPeaWord[i] = (theWord[i] - 'a' + 1); else if (isupper(theWord[i])) theSnapPeaWord[i] = -(theWord[i] - 'A' + 1); } theSnapPeaWord[theWordLength] = 0; theError = fg_word_to_matrix( (GroupPresentation *) theFundamentalGroup, theSnapPeaWord, theO31Matrix, &theMoebiusTransformation); free(theSnapPeaWord); if (theError == func_bad_input) { PyErr_SetString(PyExc_ValueError, "letter in word is not a valid generator"); return NULL; } o = theO31Matrix; s = theMoebiusTransformation.matrix; return Py_BuildValue ( "(((dddd)(dddd)(dddd)(dddd)) (i(((dd)(dd))((dd)(dd)))))", o[0][0], o[0][1], o[0][2], o[0][3], o[1][0], o[1][1], o[1][2], o[1][3], o[2][0], o[2][1], o[2][2], o[2][3], o[3][0], o[3][1], o[3][2], o[3][3], theMoebiusTransformation.parity == orientation_preserving ? 0 : 1, s[0][0].real, s[0][0].imag, s[0][1].real, s[0][1].imag, s[1][0].real, s[1][0].imag, s[1][1].real, s[1][1].imag ); } static PyObject *wrap_fg_peripheral_curves(PyObject *self, PyObject *args) { long theFundamentalGroup; int theNumCusps, i; int *theMeridian, *theLongitude; char *theMString, *theLString; PyObject *thePeripheralCurves; if (!PyArg_ParseTuple(args, "l", &theFundamentalGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_fg_peripheral_curves() in SnapPeaC.c received data of the wrong type."); return NULL; } theNumCusps = fg_get_num_cusps((GroupPresentation *) theFundamentalGroup); thePeripheralCurves = PyList_New(theNumCusps); for (i = 0; i < theNumCusps; i++) { theMeridian = fg_get_meridian ((GroupPresentation *) theFundamentalGroup, i); theLongitude = fg_get_longitude((GroupPresentation *) theFundamentalGroup, i); theMString = RelationToString(theMeridian ); theLString = RelationToString(theLongitude); fg_free_relation(theMeridian); fg_free_relation(theLongitude); PyList_SetItem(thePeripheralCurves, i, Py_BuildValue("[ss]", theMString, theLString)); free(theMString); free(theLString); } return thePeripheralCurves; } static PyObject *wrap_core_geodesic(PyObject *self, PyObject *args) { long theTriangulation; int theCuspIndex; int theSingularityIndex; Complex theComplexLength, theHolonomy, theTraceSquared, theTrace, theEigenvalue; int thePrecision; PyObject *theDictionary, *theObject; static Complex zero = { 0.0, 0.0}, one = { 1.0, 0.0}, two = { 2.0, 0.0}; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theCuspIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_core_geodesic() in SnapPeaC.c received data of the wrong type."); return NULL; } core_geodesic( (Triangulation *) theTriangulation, theCuspIndex, &theSingularityIndex, &theComplexLength, &thePrecision); if (theSingularityIndex > 0) { // complex_length.c explains the relationship between // holonomy, complex length and trace. It uses the // variable 'k' for theHolonomy. theHolonomy = complex_exp(theComplexLength); theTraceSquared = complex_plus ( complex_plus( theHolonomy, complex_div(one, theHolonomy)), two); theTrace = complex_sqrt(theTraceSquared); theEigenvalue = complex_exp(complex_real_mult(0.5, theComplexLength)); } else { theComplexLength = zero; theHolonomy = zero; theTraceSquared = zero; theTrace = zero; theEigenvalue = zero; } theDictionary = PyDict_New(); theObject = Py_BuildValue("i", theSingularityIndex); PyDict_SetItemString(theDictionary, "singularity index", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", thePrecision); PyDict_SetItemString(theDictionary, "precision", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theComplexLength.real, theComplexLength.imag ); PyDict_SetItemString(theDictionary, "complex length", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theHolonomy.real, theHolonomy.imag ); PyDict_SetItemString(theDictionary, "holonomy", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theTraceSquared.real, theTraceSquared.imag); PyDict_SetItemString(theDictionary, "trace squared", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theTrace.real, theTrace.imag ); PyDict_SetItemString(theDictionary, "trace", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theEigenvalue.real, theEigenvalue.imag ); PyDict_SetItemString(theDictionary, "eigenvalue", theObject); Py_DECREF(theObject); return theDictionary; } static PyObject *wrap_shortest_curves_become_meridians(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_shortest_curves_become_meridians() in SnapPeaC.c received data of the wrong type."); return NULL; } install_shortest_bases((Triangulation *)theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_current_fillings_become_meridians(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_current_fillings_become_meridians() in SnapPeaC.c received data of the wrong type."); return NULL; } install_current_curve_bases((Triangulation *)theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_basic_simplification(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_basic_simplification() in SnapPeaC.c received data of the wrong type."); return NULL; } basic_simplification((Triangulation *)theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_randomize_triangulation(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_randomize_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } randomize_triangulation((Triangulation *)theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_reorient(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_reorient() in SnapPeaC.c received data of the wrong type."); return NULL; } reorient((Triangulation *)theTriangulation); return Py_BuildValue(""); } static PyObject *wrap_proto_canonize(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_proto_canonize() in SnapPeaC.c received data of the wrong type."); return NULL; } switch (proto_canonize((Triangulation *)theTriangulation)) { case func_OK: if (is_canonical_triangulation((Triangulation *)theTriangulation) == FALSE) uAcknowledge("The canonical cell decomposition contains cells other than tetrahedra. Such cells have been arbitrarily subdivided into tetrahedra."); break; case func_failed: uAcknowledge("Only hyperbolic manifolds have canonical decompositions."); break; default: // Should not occur. PyErr_SetString(PyExc_RuntimeError, "Unknown error in proto_canonize()."); return NULL; } return Py_BuildValue(""); } static PyObject *wrap_is_canonical_triangulation(PyObject *self, PyObject *args) { long theTriangulation; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_is_canonical_triangulation() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", is_canonical_triangulation((Triangulation *)theTriangulation)); } static PyObject *wrap_symmetry_group(PyObject *self, PyObject *args) { long theTriangulation; SymmetryGroup *symmetry_group_of_manifold, *symmetry_group_of_link; Triangulation *symmetric_triangulation; Boolean is_full_group; if (!PyArg_ParseTuple(args, "l", &theTriangulation)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group() in SnapPeaC.c received data of the wrong type."); return NULL; } symmetry_group_of_manifold = NULL; symmetry_group_of_link = NULL; symmetric_triangulation = NULL; is_full_group = FALSE; // In the rare event that compute_symmetry_group() fails, // the various pointers will be left as NULL. (void) compute_symmetry_group( (Triangulation *)theTriangulation, &symmetry_group_of_manifold, &symmetry_group_of_link, &symmetric_triangulation, &is_full_group); return Py_BuildValue("[llli]", (long) symmetry_group_of_manifold, (long) symmetry_group_of_link, (long) symmetric_triangulation, is_full_group); } static PyObject *wrap_free_symmetry_group(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_free_symmetry_group() in SnapPeaC.c received data of the wrong type."); return NULL; } free_symmetry_group((SymmetryGroup *) theSymmetryGroup); return Py_BuildValue(""); } static PyObject *wrap_symmetry_group_order(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_order() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", symmetry_group_order((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_is_abelian(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_abelian() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (int) symmetry_group_is_abelian((SymmetryGroup *) theSymmetryGroup, NULL)); } static PyObject *wrap_symmetry_group_abelian_description(PyObject *self, PyObject *args) { long theSymmetryGroup; AbelianGroup *theAbelianDescription; PyObject *theList; int i; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_abelian_description() in SnapPeaC.c received data of the wrong type."); return NULL; } if (symmetry_group_is_abelian( (SymmetryGroup *) theSymmetryGroup, &theAbelianDescription) != TRUE) { PyErr_SetString(PyExc_TypeError, "Abelian desciptions aren't available for nonabelian groups. Use SymmetryGroup.is_abelian() to test."); return NULL; } theList = PyList_New(theAbelianDescription->num_torsion_coefficients); for (i = 0; i < theAbelianDescription->num_torsion_coefficients; i++) PyList_SetItem(theList, i, Py_BuildValue("l", theAbelianDescription->torsion_coefficients[i])); return theList; } static PyObject *wrap_symmetry_group_is_dihedral(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_dihedral() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (int) symmetry_group_is_dihedral((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_is_polyhedral(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_polyhedral() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (int) symmetry_group_is_polyhedral( (SymmetryGroup *) theSymmetryGroup, NULL, NULL, NULL, NULL)); } static PyObject *wrap_symmetry_group_polyhedral_description(PyObject *self, PyObject *args) { long theSymmetryGroup; Boolean is_binary_group; int p, q, r; PyObject *theDictionary, *theObject; char theName[64]; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_polyhedral_description() in SnapPeaC.c received data of the wrong type."); return NULL; } if (symmetry_group_is_polyhedral( (SymmetryGroup *) theSymmetryGroup, &is_binary_group, &p, &q, &r ) != TRUE) { PyErr_SetString(PyExc_ValueError, "Symmetry group is not a polyhedral group, and therefore does not have a polyhedral description. Use SymmetryGroup.is_polyhedral() to test."); return NULL; } // Use the fact that the SnapPea kernel generates the (p,q,r) // in ascending order. assert(p == 2); switch(q) { case 2: // Plain dihedral groups are best handled // in the specialized dihedral group code, // which puts the elements into a natural order. assert(is_binary_group == TRUE); sprintf(theName, "binary dihedral group <2,2,%d>", r); break; case 3: strcpy(theName, is_binary_group ? "binary " : ""); switch (r) { case 3: strcat(theName, "tetrahedral group"); break; case 4: strcat(theName, "octahedral group"); break; case 5: strcat(theName, "icosahedral group"); break; default: assert(FALSE); } break; default: assert(FALSE); } theDictionary = PyDict_New(); theObject = Py_BuildValue("i", is_binary_group ? 1 : 0); PyDict_SetItemString( theDictionary, "is binary", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("(iii)", p, q, r); PyDict_SetItemString( theDictionary, "pqr", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("s", theName); PyDict_SetItemString( theDictionary, "name", theObject); Py_DECREF(theObject); return theDictionary; } static PyObject *wrap_symmetry_group_is_S5(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_S5() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (int) symmetry_group_is_S5((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_is_direct_product(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_direct_product() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", (int) symmetry_group_is_direct_product((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_factor(PyObject *self, PyObject *args) { long theSymmetryGroup; int theFactorIndex; if (!PyArg_ParseTuple(args, "li", &theSymmetryGroup, &theFactorIndex)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_factor() in SnapPeaC.c received data of the wrong type."); return NULL; } if (theFactorIndex != 0 && theFactorIndex != 1) { PyErr_SetString(PyExc_ValueError, "wrap_symmetry_group_factor() provides only two factors, indexed 0 and 1, which may be factored recursively if necessary."); return NULL; } return Py_BuildValue("l", (long int) get_symmetry_group_factor((SymmetryGroup *) theSymmetryGroup, theFactorIndex)); } static PyObject *wrap_symmetry_group_is_amphicheiral(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_is_amphicheiral() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", symmetry_group_is_amphicheiral((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_invertible_knot(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_invertible_knot() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("i", symmetry_group_invertible_knot((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_commutator_subgroup(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_commutator_subgroup() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("l", (long int) get_commutator_subgroup((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_abelianization(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_abelianization() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("l", (long int) get_abelianization((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_center(PyObject *self, PyObject *args) { long theSymmetryGroup; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_center() in SnapPeaC.c received data of the wrong type."); return NULL; } return Py_BuildValue("l", (long int) get_center((SymmetryGroup *) theSymmetryGroup)); } static PyObject *wrap_symmetry_group_presentation(PyObject *self, PyObject *args) { long theSymmetryGroup; SymmetryGroupPresentation *thePresentation; PyObject *theRelations, *theRelation, *theDictionary, *theObject; int theNumGenerators, theNumRelations, theNumFactors, theGenerator, thePower, i, j; if (!PyArg_ParseTuple(args, "l", &theSymmetryGroup)) { PyErr_SetString(PyExc_TypeError, "wrap_symmetry_group_presentation() in SnapPeaC.c received data of the wrong type."); return NULL; } thePresentation = get_symmetry_group_presentation((SymmetryGroup *) theSymmetryGroup); theNumGenerators = sg_get_num_generators(thePresentation); theNumRelations = sg_get_num_relations(thePresentation); theRelations = PyList_New(theNumRelations); for (i = 0; i < theNumRelations; i++) { theNumFactors = sg_get_num_factors(thePresentation, i); theRelation = PyList_New(theNumFactors); for (j = 0; j < theNumFactors; j++) { sg_get_factor(thePresentation, i, j, &theGenerator, &thePower); PyList_SetItem(theRelation, j, Py_BuildValue("(ii)", theGenerator, thePower)); } PyList_SetItem(theRelations, i, theRelation); } free_symmetry_group_presentation(thePresentation); theDictionary = PyDict_New(); theObject = Py_BuildValue("i", theNumGenerators); PyDict_SetItemString(theDictionary, "number of generators", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", theNumRelations); PyDict_SetItemString(theDictionary, "number of relations", theObject); Py_DECREF(theObject); PyDict_SetItemString(theDictionary, "relations", theRelations); Py_DECREF(theRelations); return theDictionary; } static PyObject *wrap_tet_shapes(PyObject *self, PyObject *args) { long theTriangulation; int theFixedAlignment; int theNumTetrahedra, i; PyObject *theList, *theDictionary, *theObject; double theShapeRectReal, theShapeRectImag, theShapeLogReal, theShapeLogImag; int thePrecisionRectReal, thePrecisionRectImag, thePrecisionLogReal, thePrecisionLogImag; Boolean theGeometricFlag; if (!PyArg_ParseTuple(args, "li", &theTriangulation, &theFixedAlignment)) { PyErr_SetString(PyExc_TypeError, "wrap_tet_shapes() in SnapPeaC.c received data of the wrong type."); return NULL; } theNumTetrahedra = get_num_tetrahedra((Triangulation *)theTriangulation); theList = PyList_New(theNumTetrahedra); for (i = 0; i < theNumTetrahedra; i++) { get_tet_shape( (Triangulation *)theTriangulation, i, theFixedAlignment, &theShapeRectReal, &theShapeRectImag, &theShapeLogReal, &theShapeLogImag, &thePrecisionRectReal, &thePrecisionRectImag, &thePrecisionLogReal, &thePrecisionLogImag, &theGeometricFlag); theDictionary = PyDict_New(); theObject = PyComplex_FromDoubles(theShapeRectReal, theShapeRectImag); PyDict_SetItemString(theDictionary, "shape rect", theObject); Py_DECREF(theObject); theObject = PyComplex_FromDoubles(theShapeLogReal, theShapeLogImag ); PyDict_SetItemString(theDictionary, "shape log", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", thePrecisionRectReal); PyDict_SetItemString(theDictionary, "precision rect real", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", thePrecisionRectImag); PyDict_SetItemString(theDictionary, "precision rect imag", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", thePrecisionLogReal); PyDict_SetItemString(theDictionary, "precision log real", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", thePrecisionLogImag); PyDict_SetItemString(theDictionary, "precision log imag", theObject); Py_DECREF(theObject); theObject = Py_BuildValue("i", theGeometricFlag); PyDict_SetItemString(theDictionary, "is geometric", theObject); Py_DECREF(theObject); PyList_SetItem(theList, i, theDictionary); } return theList; } static PyObject *wrap_Dirichlet(PyObject *self, PyObject *args) { long theTriangulation; WEPolyhedron *theDirichletDomain; int theCentroidAtOriginFlag, theMaximizeInjRadiusFlag; double displacement[3]; if (!PyArg_ParseTuple(args, "liiddd", &theTriangulation, &theCentroidAtOriginFlag, &theMaximizeInjRadiusFlag, &displacement[0], &displacement[1], &displacement[2])) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet() in SnapPeaC.c received data of the wrong type."); return NULL; } theDirichletDomain = Dirichlet_with_displacement( (Triangulation *) theTriangulation, // manifold displacement, // displacement 1e-8, // vertex epsilon theCentroidAtOriginFlag, // centroid_at_origin Dirichlet_keep_going, // DirichletInteractivity theMaximizeInjRadiusFlag); // maximize_injectivity_radius return Py_BuildValue("l", (long) theDirichletDomain); } static PyObject *wrap_free_Dirichlet_domain(PyObject *self, PyObject *args) { long theDirichletDomain; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_free_Dirichlet_domain() in SnapPeaC.c received data of the wrong type."); return NULL; } if (theDirichletDomain != 0) free_Dirichlet_domain((WEPolyhedron *) theDirichletDomain); return Py_BuildValue(""); } static PyObject *wrap_Dirichlet_num_vertices(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_num_vertices() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD != NULL) return Py_BuildValue("i", theDD->num_vertices); else return Py_BuildValue("i", 0); } static PyObject *wrap_Dirichlet_num_edges(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_num_edges() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD != NULL) return Py_BuildValue("i", theDD->num_edges); else return Py_BuildValue("i", 0); } static PyObject *wrap_Dirichlet_num_faces(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_num_faces() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD != NULL) return Py_BuildValue("i", theDD->num_faces); else return Py_BuildValue("i", 0); } static PyObject *wrap_Dirichlet_vertices(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; PyObject *theList; WEVertex *theVertex; int theCount; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_vertices() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD == NULL) return Py_BuildValue("[]"); theList = PyList_New(theDD->num_vertices); for (theVertex = theDD->vertex_list_begin.next, theCount = 0; theVertex != &theDD->vertex_list_end; theVertex = theVertex->next, theCount++) { PyList_SetItem(theList, theCount, Py_BuildValue("(fff)", theVertex->x[1], theVertex->x[2], theVertex->x[3])); } assert(theCount == theDD->num_vertices); return theList; } static PyObject *wrap_Dirichlet_faces(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; WEVertex *theVertex, *theCandidateVertex; WEEdge *theEdge; WEFace *theFace; PyObject *theList, *theVertexList; int theFaceCount, theVertexCount, theVertexIndex; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_faces() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD == NULL) return Py_BuildValue("[]"); theList = PyList_New(theDD->num_faces); for (theFace = theDD->face_list_begin.next, theFaceCount = 0; theFace != &theDD->face_list_end; theFace = theFace->next, theFaceCount++) { theVertexList = PyList_New(theFace->num_sides); theVertexCount = 0; // Enumerate the indices of the vertices, travelling counterclockwise. theEdge = theFace->some_edge; do { // Find the more clockwise of theEdge's two endpoints. if (theEdge->f[left] == theFace) theVertex = theEdge->v[tip]; else theVertex = theEdge->v[tail]; // The vertices don't have explicit indices, so we have // to figure out on the fly where each vertex is on the // list. This isn't very efficient, but this isn't // a time-critical routine. theVertexIndex = 0; theCandidateVertex = theDD->vertex_list_begin.next; while (theCandidateVertex != theVertex) { theVertexIndex++; theCandidateVertex = theCandidateVertex->next; } PyList_SetItem( theVertexList, theVertexCount++, Py_BuildValue("i", theVertexIndex)); // Move on to the next edge. if (theEdge->f[left] == theFace) theEdge = theEdge->e[tip][left]; else theEdge = theEdge->e[tail][right]; } while (theEdge != theFace->some_edge); assert(theVertexCount == theFace->num_sides); PyList_SetItem(theList, theFaceCount, theVertexList); } assert(theFaceCount == theDD->num_faces); return theList; } static PyObject *wrap_Dirichlet_face_colors(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; PyObject *theList; WEFace *theFace; int theCount; double h, r, g, b; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_face_colors() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD == NULL) return Py_BuildValue("[]"); theList = PyList_New(theDD->num_faces); for (theFace = theDD->face_list_begin.next, theCount = 0; theFace != &theDD->face_list_end; theFace = theFace->next, theCount++) { // Determine the face color. // Uh-oh. I know I want // // hue = theFace->f_class->hue; // saturation = 1.0; // value = 0.75; // // but I have no idea how to convert HSV to RGB on unix. // For now, let's just fudge it. h = 3.0 * theFace->f_class->hue; if (h < 1.0) { r = 0.25 + 0.75*(1.0 - h); g = 0.25 + 0.75*(h - 0.0); b = 0.25; } else if (h < 2.0) { r = 0.25; g = 0.25 + 0.75*(2.0 - h); b = 0.25 + 0.75*(h - 1.0); } else { r = 0.25 + 0.75*(h - 2.0); g = 0.25; b = 0.25 + 0.75*(3.0 - h); } PyList_SetItem(theList, theCount, Py_BuildValue("(fff)", r, g, b)); } assert(theCount == theDD->num_faces); return theList; } static PyObject *wrap_Dirichlet_face_pairings(PyObject *self, PyObject *args) { long theDirichletDomain; WEPolyhedron *theDD; PyObject *theList; WEFace *theFace; int theCount; if (!PyArg_ParseTuple(args, "l", &theDirichletDomain)) { PyErr_SetString(PyExc_TypeError, "wrap_Dirichlet_face_pairings() in SnapPeaC.c received data of the wrong type."); return NULL; } theDD = (WEPolyhedron *) theDirichletDomain; if (theDD == NULL) return Py_BuildValue("[]"); theList = PyList_New(theDD->num_faces); for (theFace = theDD->face_list_begin.next, theCount = 0; theFace != &theDD->face_list_end; theFace = theFace->next, theCount++) { PyList_SetItem(theList, theCount, Py_BuildValue("(ffff)(ffff)(ffff)(ffff)", (*(theFace->group_element))[0][0], (*(theFace->group_element))[0][1], (*(theFace->group_element))[0][2], (*(theFace->group_element))[0][3], (*(theFace->group_element))[1][0], (*(theFace->group_element))[1][1], (*(theFace->group_element))[1][2], (*(theFace->group_element))[1][3], (*(theFace->group_element))[2][0], (*(theFace->group_element))[2][1], (*(theFace->group_element))[2][2], (*(theFace->group_element))[2][3], (*(theFace->group_element))[3][0], (*(theFace->group_element))[3][1], (*(theFace->group_element))[3][2], (*(theFace->group_element))[3][3] )); } assert(theCount == theDD->num_faces); return theList; } snappea-3.0d3/SnapPeaPython/makefile0100644000175000017500000000243107067223671015530 0ustar babbab# Where is the SnapPea kernel source code on your computer? SNAPPEA_KERNEL = /home/jeff/SnapPea/kernel # Where is Python.h on your computer? # If you don't know, try typing "locate Python.h" # (without the quotation marks) at the shell prompt. PYTHON_DIRECTORY = /usr/include/python1.5 # Compiler options. # -g debugging code # -O optimize CFLAGS = -g -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations # This tells the compiler where to look for the header files. HEADER_PATH = -I$(SNAPPEA_KERNEL)/headers/ \ -I$(SNAPPEA_KERNEL)/unix_kit/ \ -I$(PYTHON_DIRECTORY) SnapPeaC.so: SnapPeaC.o KernelObjects/BuildDate gcc -shared -o SnapPeaC.so $(CFLAGS) \ SnapPeaC.o \ KernelObjects/*.o \ -lm SnapPeaC.o: SnapPeaC.c gcc -c $(CFLAGS) $(HEADER_PATH) SnapPeaC.c KernelObjects/BuildDate: $(SNAPPEA_KERNEL)/code/*.c \ $(SNAPPEA_KERNEL)/headers/*.h \ $(SNAPPEA_KERNEL)/unix_kit/* mkdir -p KernelObjects cd KernelObjects; \ gcc -c $(CFLAGS) $(HEADER_PATH) \ $(SNAPPEA_KERNEL)/code/*.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_UI.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_cusped_census.c \ $(SNAPPEA_KERNEL)/unix_kit/unix_file_io.c date > KernelObjects/BuildDate clean: rm -f SnapPeaC.o rm -rf KernelObjects rm -f *.pyc snappea-3.0d3/SnapPeaPython/CuspedCensusData/0040755000175000017500000000000007011566574017232 5ustar babbabsnappea-3.0d3/SnapPeaPython/CuspedCensusData/terse5.bin0100444000175000017500000001645607011566574021142 0ustar babbab76!34%).+57)%:7WUN9RFHEoՑdX:7BAM:p*n:3OIE<7KMAuUUUUUUa<7KJVUUUUUUb<7"HOǒDuK)<6VTC:4!FF7Wwrq<3IQM1:S<'$HO䏩56:.$TM:S<4TUI6).AH4VZi6$#POQ6hdjK,lgs JMK,lpd JN7Ww<7KL@ր7'HlmX)o܀.+Vwr.e ,Pod,avFmI#VMkmlI#V Merb5Ehhf83)hQhfO83.Lkgdぽk/,avMmʄSFhfv&ـRFlrʄ.Mojvnt`,Lkf5{fFX&7Lk5{fFX&Vf/gc"Ohڀ,.Rlm"Bo(ڀ3'Awv5i;&UVrFrQ.MvMoQ \+'Vwct=QFlRGo\.+Vw95 1MIms+KF܀.+Mlr_T0fROmkp +Chom6q>_/7hHomq>_UjHOw<ژ@ ;r76rfB x7Wlot7WqrwI(QfrHEkXx\v0sFqgr76rf,DN'/sVfvڀVM`ob X"W+,fFvmݨVj7WAk X"W+t,Fm۵T(ƺV'5EkbIhhLlIDlF cxwOJFo^7haf<(,mvcu+hoe(W'Zƃ7ohTVN)aVVhW.7r'rRHf &xwOJF/2xwFOJJGoPpslK+kn|OԶBpp%fKdkڀVHwh9y?wFOv y$tToVQwhBi't7amjboړcCOrh boړ50ngl5Upp`@Puu$BbdbK,wibJ7Lkiu"oOv%+6t=<|ʃ,LniusaT҃76fiZtK(Mvڦ1-ZVMk'c$ZVMk) Ĥ¸u2co/qܪFr76afQr3mAHA,:R|dԃLRfr(5|: $oHh~o9C؃SHmUUUUUUaSOmUUUUUU=уwkl \؃'Rha iGXMfFuMt5fEO {AZ0,mtŃ +mP̷҃,2a7󄖦,ft톝USฃlFRmg[htLR2r u7Pm7rrc\ ~[tLR/r]RVάK,l )mwK+w)m.WmOF~?7Wwr76af@j4U>,tvJ_(yt5fRO JƵ5Ń,vrbDL O r76aos#)IQfrz;1b ZVHkόxzI&F7WwtUH/cQ5=jEabJm.2>Ft+MmMdҔq-pJrXÀ,/tr# zkxI2Vm|')lORm qjVQwkpnr7WwKMvv/ 5Obf +ehkK#}ԃPHhvL᚟t+MmFV/wIԃKrvVCY n*/uoh<gKfh-=6,Fff=5ZVHkN%ndpMfvKMvo,UtI(Qr ۛ,MwuuchRjԃPHhv(~pt0fRRتfvr,2af#\F3r.ThF2)/Kvw؃'RhajڲYMF` f*3*:r)mVV[#?|&Ń,fv qYZVHw?ZVHwG'5>y!ZVHkt5fROcC W6Vdr3mVHьfy؃bVmHSHfq VR`)f̃+KwVSsVZUcf근VʃVkf.$(Qqhh=,chV˷ 7Oh,=! 6`\7vm(KAa,=! 6`7r3mAR]=)L7OoXhLhl7Wt7Wtr3mVV$E=wRlkt5fRR#/Bt+Qfee-xp;x+vVv-xp`DAf,Fm!߄(<"fdOibRLMml(vMmyX)7Wh.QqcB. $ں,FF&H0z7WkpMr7bh/@`oH@hR2qvsr3:R/qv-Π[?!̃+Kwvc6<2rOeDGsn(Qrfji15hf $DNbm`L2h#haMvFh5ϥ/HmdjORh9؃.vmeZDku oaԃL-h8)%߸2cOo <{4R/rf vmb VHwv<6wRlkW@wWKlLmRf $mAaʃ50ln6=0NMvMv bϱ>UffvD%UR/qv~"9eBуOlr4]NsoKqR/qh۽%/Vvs5QMvMj50ngdd_9Vni7Rrjz/ZK,wd97Lktd97fr jz/YOrF5Fq$VfvFq$R2qv-#ͣ6AuԃUohZQ9iwRlkLmRfh׆OJlA"eeOmVh#fh1`qW`wR2rm#Drn4TtVH]VYbFj5JWjO]VYeOpt`)6Qrm >g7FʃVkmi|HlqʃVkm](Q u,soʃVwh-E\t5UuuUhvLmRfSk@V0ʃVwhg6,ʃVkmC9W LmRf6FO`UsK.Hnd7KlvKHvKoԃ0ef@UsIUrh@vRIUsFsnappea-3.0d3/SnapPeaPython/CuspedCensusData/terse6o.bin0100444000175000017500000004164407011566574021317 0ustar babbab7WwbD7WwDZ7W6wkz^W:Z7W6wAF0h`BQqvK>xcLK,d>xc̍+KFF{rl7W F{rl+Vmm cK\\+KOwcK\Z7W6w:r)mAH`p#ԍNortp#V+KwƗwSU,Kw@_V+Kw:0U,Kw2]TU7L , +W_ U,Kw3ò!U,KwU\+KkX4ݴUmOLYZ7W6w}=bwZ7W6w' "l7WDKp摮k"Rf1@~'rFrMl*QOw#I/57W6J<{Fe$hMv;aba>ЏfoYO'luV7WlMUV7WlrV!+KE)V U7Kk .Ε7K7)p _ +KSY,NV7LkIrvV7LkR!5LU,Kkn0".U,KkLpw٬+Kk[,rFh vu ,KG+YV+Kw2"UϯV+Kww%{U,Kwҁ_/>U,KwHo'ִ7e|alƍ7hoѾ5Wl=qOHw% hG+hjeUUUUUUUbWoRC̭nq7vm9b"*tqOHw9(O\&tRJwl:(e,mccNZx2+K2x 7WR 2r7]Ԏ+2h:vn-'Vm )XR^gkO/oZ׉ciAHl~izʨTLj5it+2veY}l=7W6rE+6r3Y$v +KJ &ō,frC]dQ" ,Ldv,mvn67K u a,e7oh2O[ |+Koq|YS 7W6tϱ1e,cfeCJLFԎKmVkO( G9b+hFr>ў.MvYh8L+KF}&e7ohQd%17W6_7[ Ƃ7W טMpLKCfqܐ8 T7Kb䴎7/vT[d7WjhKff]+th+tvl[qOHw&5ԕ,KJrT#ͤ7e] jJf7W6&i̸OOlq-,LӤ +K:4q5t72oeu!FTKrvS"k& +vve=bqAFl<g7K_y}qOHw1SN2,-rq=^WCy7Ke+K+sCR76j'7W6'%]PMrS^ƍ7ofpq<,KH)B CqOHw,XJ9+K-‰!80cE,ft%h7hJU} P? +WF򐣩qOHw,tJH`#We,rfVg?7vm^A6H7KHj7E7L_e k蕍7K72्7rhp>I,>4zЏHhvLo?GpKmFy=&~r76raq/ eō +hmUg&,PUl7WBayA̳qOFwn7K&K2ƍ7hon'ee,mcm 17Kw1;z7W`7vo$#7W6 ;ߖFL,/3?T7W"SU,K_7cb1)ƍ7ho=Sc54ە,K )!ixu2chDce7eh|I¦7vm? ]E+K/'-",7Wwע$Ԗ,L7K#N|iEOkqc~,*7W6CwRNzn~Ŗ+KA;o7KKkZ;K"HhnPT箖,L-lFHll5n ҍ,-ohR;HCȏbFoWobf7KsQQ̍7KF ? +K&] ܕ,K1'6IRpsan &7K#?Z+K-Vy,KA!ʖ+LO|'rRO[t76ak| |17W,Kp7r,=! 6`7e,rfV˷ *7Ww4H}*lFOfrj<Hl4q`ʍ7LFyN +KYAOlxeڎ,2aIuLa,K3yVyh+mtM:x.rEO]ܿ$oHvG&TPChv5c}{c3,K ᗛ~8XbjmBxI=_#D,KZxS2Ch(;h37a,̎L+Ov@ghSō7hoƍ7hmY{$J/qVTk%lsJ`7K ? ejQOwȕ,K\%o? ؍pfOer8b/8e,cf?.+KAm:LprVeR*C6Ҏ76f,ZZLK,VW1i߫qō,cvNPQxS2Co_}Ό̴5fEr[I*TK+6gi/&7v;&EFiRH`f]5'e7eh2)>S:qOFwPCe,rf^8PiRH`\-:@ջ7W6Pw䕍7KeygXSVar sgTЏRavO22NRIF7Wwm7Wwxd(vjɢW,mvB|7KBBxل,crM<ƍ7hosE9F +WೣSMv+LV^t$oHv1r#ō,fr9ac` Rō7ho[w#rō+mo^/Nhuco)Xʍ7Wi^Y`sioH .#6xe7vea Fl ̄_.MmϢt+KJō,frE?.,mr5y5,mvM eƍ7fhar3Bƍ ,ofyi,2HlL,li_k-,_e,cf#8 ԎPHc<t^0啍,KL2xqOFwܨ>i7hfgGqOFlҢ,Kia[,ō,fvAe7ehi}0dQ7K 0I mlB7W6U6:؎,2vL>Q7omgjd#,K4n/8l7K'ð;67K*$Qeh,L!|hChgQFa,K$HsD +K'ԋFH Tlr&H0zR76fB. $ں.VmB. $ں,K3z;,Lς:`ڏe,mcfD%Sdvj8_we7eht7K@3ޏt_qOFw s1,mv𫂺@wdЏVoOt4윿YAOluyTn7K lnYƍ7of: Ύt7/veP7Kciō,frCƩ˚a1,K֑9t"ƍ7hmdd$,KhSώ>؍+6fvEWqhR =GxKcfVa=,KJ Su̼e,cf*7J.,mvO֧g7K.+38,LZeaOo|ō,fr#A ,mvlq;ō7hm-VaǮP9bō +hmCy2z.,mrp9),K(̤KfOD%AjXƍ7ofR$TՌ7KA&o`ō,fvJx?",cHt cUlFrrcU7K,86qOFl4G,L*g4(h%lP'Thpzzhƍ7of\CBJ M]鞕,KFV,K*L!hHRaG7WaGƍ,vt" ЬVAmjMPM0T+2hMPM07߫_44؍WqoRj9hfR7/eoB1R ,mvI"7*7Ww6ZV +ȥ,cre T<ō,frP-cvQOwbt0BMvt0BVv~FFKewlkiō,fr?0{VFq$ʍ50lnFq$*7Wwfτ*7WwfτrHl.(ÁSjtSѻЏHhh\贱|71V \veQ9iLԎPHh>I[hChK_i*,Ll#\I.rE 5<1/nȏWoR]NUP WVAmj]NUP WShvtUB̝qэOlrk5^4+2oНr.MLwo+OJawV } '2Hlm":USjt5UUUUUUa*7WwDњI@p, /hChW삿Vo%+vWO1xk.MH2oMэOlrӒajGkwF HwvK4ۂE7 uRF%7eJP vЏHhh)1ҎMw>NZR,-o\ Qu -Vv !=eE,c)̥sE, !=d% ,h$gt/Fl.zэAlm|MhvФƔm΄Obr=OlKJ/r=OHlƁɀ#S@R,-opEZHlmClmN7sJwA>J`rw&3v 2Vakc +vj?D0ޗ4%,mdTlDd}qFr=V4@HFh ;HwgKHwvZuٯ\sHlfu ) U_эOlrO Mp/N<F +mFDPXKfO"P̎7Wlʒ]%Έ*,Lli[XBROkUfV D[7m%IT7P[>R,-ȯ%2$эOlr vtȏWa@ J_%,r}"|m*,LlʚU^̍+Kwv=fWUhz_TE,f;)ulwk3iT+HwTXlvx#2Vvu3PEE,5pbY%,c ŸaэOlr'tJ,L+Kl(._DHwPAJ̍P0|].tKcfV^zf/jd IMvMh`ߴIRcwR*,Ll E'1юOlU%,m"TE,fxpQŎ,cv8t\h ,r e ,raL][ht*7Ww *7WwFwܩv эO`r1ܩ!PMh"dE,fg {̍+KwvpBR+rFQyh>,r>2AFS,,dэO`rZ`^эO`r  )xHlm$в]QHrrM$̎7Wk%̎7W`J`8}Flwkryp[nsBsDn{UsR,-on4ƍ$"wg[[6g$m09ڽ3E7h9%7yDHwoYHlmmk,v1D;$p,9yHw ,YCӆ,r'lw\<[ҍHwrC.G7^Hwv-^1(ϤwRoŏ,rgZڂoO^юOwZ!,r@> CHwpX,rw\45Q`,v ?ʍ5Ppu[dEhIMt(KvFfGq7c1,d^K$LvAY+{K,2vAY+{',v0̆e)+ty]; 7m[˓nE,# #ABpMvhkS^$эO`rI'p R`f_P2,-r{cJwv|f,mf忮Q̍U0cjX7WwQ8{7WwQ8{HwvRÊ.Hwo*Ҿ%*ЏvhRxķOPb7Or]xķOuď+fu5xȏ"Ou5x8'ju5xďNrt= T7*ЏF= T7Pcfa=9C-,v$_x,퓑YjRJJT~# Hwv6 Zw?Z?7hK%,mJK7ra5GE7hGI{D#wRooz:k;vuԎKmrVpWshƎ7o \ȥ,r1~ )m0+CKfVa}Kz\p(m֞K8BэOlmzmНwЏhFf*7WwFD o%,mv?e`b%,cOMCJwv{7ZE,ck-up(l +mlHwvlVx*7Wwh!*7Wwh!эO`o>l)OFw%G<(ҎH`kU,r7ra9w((<,k.q%,m§ۅ,vSwЏoe/)+9юOw}_;Ffj}_;EэOlm# jRVPEhMvOB(эOlm4W'HE7oWE,f~ 2~* VkwN<E,sfwFw}i[pH/6`Ḵ)M`ḳE,f޼Hlf˫-Rƨ+Hr[5/"mFegsЏHaKe|@3qot$%,c)ՐxPMM ҍ,Qo <O<UE7h(<ٮ%,m%ٌ1iHj,S[:E,f k,r{ƞ쮌,vs$[@Å,vo#юOlASVlݗn"eOϤSVD юOl:f4fWvhzkCfTCazkC9эgiOum2,rުo%T'HvR'f%эO`oGhF%,m Whm5>SYKoL}ĭA%,cjJ٥57L$t$ho3$t7W0`3Q7mtb:QB!,/r7scЦʎQw"^ke%,mכ}ٺHwv*pэO`r0"TF.:sE,fjFw%oD%,cX6irrЏtv⣌+K缤t+эO`r =VE,f,d!eO1Vf[S/@cAlFS/@cAScv-Qy%,mHLh%`rOl(%,m%M]\S؍+tr]\E7hx0O2r7oOrnz5{ ]o؍DqF p;юO`B@-1E7h~L&Rj6 VюO`}0JЏ6VhS0ЏMVhS0冏7f v(FwC `bE,f.0˽юOl>SэO`rc3\ -,rfC[JwǟlYz2Rrj2rꪪTCUUUUUU=RFjTtUUUUUUЏ2MfK7ޮT#Fwv'=6%,c.͇,v+@ 7a袹(8uMo袹(dLf;袹'Ro Eݟ^4эipWuԅ,v E10Dvr)m%,czd%,cj_{ khGc7ruZ Vk*VE,f&^]mD%,c0 ,Q$"UPVMf5߇g 9E,f^ O驐`,vT6ȏ"O&J|,bOx6h̸KvMiSVSďlm^Pskm0k#ȏbFjAs:mmэO`oLVz]G# QA) Pz3r#Sďlm?R|UqW #g{OiюOlUb 3$hh'Fwfmvgꉋ0,vdW,v ]ďwvt\Eɍ MwЏJf,Uja2oD45rT",DэspT_bkwl*y^ďUhm.O\녏,rN\p%,cq],,vZXJ!#ohFh3f|j\*VnyW\j8WvQPkBďwvBuC ԎLmFwwy-!KmqDpwhfRJfDpwhLF0j9Yʍ7Wk`zpMImӌ+W3Imf4N(h2RhTCCccʍ7Wk$ȏBq[j+2b dbzB 7W6ϯϷ=Oh˩<,L:"Oh<,L<,L<,L<7W6Q;Ow<7W6=7K<7W6E=7h=,oȱQ;Ow=7W6ʷ=7W6ҷE>,fE=,f=7W6Է>,r>,r>,r<+K%>,m׭<+K<,KE>,fƷE>,fҦ<,Lϫ?7ֶƵ<,Lѫ<,Lʫ=7KϬ=7Kʬ=7KѬ<7Kѫ<7Kϫ ?7W<7Kʫ<+K<+K<+K<7WwϨ=Oh˦=,Kҷ=,Kʷ=,KԷ<7W<,K<,K<,K?7ή;Hw>+K=&r˲E>,f>7W6˷7oKe5wRlk:7v%>,m&=7v:7h=,rֵ%>7vӰ>rFrӣ?,̃ʵ;,r5wRlk<7W?,˃E>,f˫&;7v<7K<7WwԴ:7WwQ>Ow֢>rFrӲ:,f<,L:7h&;7v>7W6˷<,KE=,f͢>,rаE=,f=7K;,r:,m<7W6>7Kˬ>,LˬE;,f%>,m%;,m:,f<,LE>,f%;,m=,K>,Lˬ>,K˷>7KˬE;,f>7Kˬ>,Lˬ<7W6>,K˷>,K˷<7W6ϯ:7W6<,L:7Ww:7Ww?ʰs֠<,LȨ<,Lʪ=Oh:.Rh:.Rh=7W6=7W6<,L:,L;Iˢ<,L̬=7K˫<,L=7K;7K<7K̬>Mm׶:7K:7K<+K<,Lˠ=,K=,K=,K˫:,L:,L%>7vխ%>7vӭ=7K̠=7K;7K:+K:+K<,K:7K:7KR>,-o<7Kˠ:+K:+K:,K:,K=,Kנ=,K=,K<+K:7Ww:,K:,K<,KQ;OwQ;Ow*;7Ww*;7Ww;>w̶&=7vͭ&=7vͭ;>,rE=,fE=,fd>Hh̷*>,Ll>(Oϥ>OcщҮ=u=r?Cϥ`?KHԏī?o?o$?HѨ67r>,r>,rd;H`d;H`:Hl<,-o>ff;Rl`?ˑh ?K+ħħD?+,>+Kkɦ:H`d;H`=H6.ms7Kbp>fFm´`?Ohvͪ6.Ru?ʰs֠%>,mֶ%>,mֶ7SkR;,-o;wE>,fҲE>,fҲ:Kho%>,m;Iˢ:Hv>Hʤ;I̢$?HmШ<OwQ>OwE>,f>Wcˏ=H׳0?N͉Ϡ*;7Ww6'Hd<OwQ>Ow=Hׯ=Hׯ:Ol:7Ww*;7Ww6.Ru77h97vȳ6Ko;77Kid?mk:OwQ;OwQ;Ow:Hw;7o,>+KkӦE>,Q;OwT=Ooԭ<7o?OE;7h=7d>H`зL=+Kͤd>Ohġ<OwQ>Ow֮?hQ;OwQ;Ow=7l֮<,mƢ>,r=S=`̆>rr>7=hmhӡ=6m֪Q>Ow֮Q>Owȫ>,v67r?,˦=UO;.mf;,*>,Ll*77Ww=H67r<,fͤ>.uQ;Ow?I̧$?HѨ>,?Hҋ=UO?hh?ʆˆ=t;7m>tt%;,m>,Q;OwR>,-o?HaQ;Ow>IR7KbpE=,gͦ>Fwǵ³6Bbd6Ehh?=H>LE;,f:Fw<,-oרE;7h7+bh:O`?P?hƢ%;,c<Rl;H>"Oh=mַ7+bh?7˗>wsʡE>,gƩ>.Rũ;,r>,o=H7Fr$?HfШ=IRQ;Ow%;,m>&t*;7Ww7mV?,>ѩ?,˭P?dֳ?g˨:Ow?i˗ɳ;,v;7>UOE;7hE;7h?7חQ>OwǠ&>7v=H*;7WwQ7OwE;,f=l̷:,m:,mmp>w:7Ww:.Rh:.Rh%>,nͰE;7hE;7h:Ow>bͳ=/jlɰ=UO?hhQ>OwͬQ>OwҧQ;Ow7,L=IRš&>7vȩE>,f˦=,rֲ=w׷<Ow?ƭh<Ow=+Kjˤ?OE>7hǧp>Pp*;7Ww7Kid>`Ү>`Ղҩ?Hd?hρ=Ep:UOh>TwԷ>Qḻ6Oq5Oa<OwQ>Ow÷=UO?E̴=Mm>+K?Sɓ©=Ht7Kid=eTtԦ>+K:,f:,f;H=wjӥ>Kˬ=rђ?rŰ=tů?SȬ?t×?tå ?6V ?6V ?7WƳ:,m:,m?,˫<7h;H?,̫<OwQ;Ow=tŏ%>,mа?,:,f:,f>7=,rͩ>UOԱ;H>QlӢ>tt>wt>wt=r?vmɥ?IRʪ;,>,Lր=Mv֧*>7Ww®E>,f%>7v<,m=,oԣ,>/Hs>Bfƴ?7˗>/H>mpE=,fͦE>,fɩ?I?@kͱ<0ddԩ?Buψ?7ח>a֦Щ?`?Ieԡ:7Kl:Ow%>7vȩ>`Ү>`Ղҩ ?/Hsä<,f7wQ7Ow7w?lĭ?d;H`=2kצE;7hE;7h=7WaE=,f֮=UOť*;7WwH?lHE;7hE;7h`?mb:Ow?Vʠ6(pNP?@oԭ=l̷$?UO:,m:,m?Avրέ?Vȴ?H֎=SHĩh>Rff ?7W ?6V ?6V ?7Wˠ ?7W˴ë;t>gϠ=al<7Wh׷<3vv?PŶ=w׷7,L>7?Hϰh>'vi=`Ϩ>QEʬ=H̯>Hh:0Ff=/Hf:,m:,m?i>b>+K?>t<Ow?,˫>`ҭ>`Ɏҭ?,̫׶>pbDz???,̫>D?,̫ר:,f:,f<Ow7+k?TёХ=OvϮ;,4=UnOժp>g˪>cÈE;,fE;,fH?Ko>CH?*Ѫѩ=uҰQ;OwB?%˶Q;Ow ?7W ?7Wʪ ?7WϠ ?7Wʤ ?7W˨ ?7Wʫ ?7WӪ ?7W ?7WҴæ ?7WҴæ ?7Wմð ?7WҴæ ?7WҴæ ?7W˴ë>+K>Tiӯ;Tq=Hׯ(?Bw<.Ru<Owѷ:Pg=Lӈ>@`ח ?7WpÄ ?7Wu ?7W ?7Wĩ ?7WӴç>hmֶ>t԰>4ԠԴ?ľ=%@`D?Ol;Rrb??TUӫյ7Aff,>/HsҲԶ?Tַ;TfmwLQ;Ow?oсĶ?ӌ?Gʳ=sŠL7+Kqh;s3ò>7rĥQ;OwH?oK>+Kp=Is>bSԢ>T`Ƿ֦?Gˆі=/Hf:.Ov:.Ov(?1Uh<Owѷ>k̪=7r=fͨ0?QoêQ;Ow:7WlQ;OwB?2k>Tdķ=.Rī=.R·Q;Ow:oM>Udå<.Ru˫=WV"?)M"?)M`?Is?gВ ?/H>Tׄ?=`̒p>Wwè`?PpµH?0Ӯ ?/H<.Rb̠<.kRïQ;Ow?,oч?,oŇ(?1UՆh>HlŢ;O;lm=i“>sӤ??97Wh97Wcp;Od֥$?H=`̈>Ojפ:.Rh:.Rh>+K$?H$?H$?H$?HɃҧ$?HȄҨ$?HȂҨ$?HȩҨ$?HȫҨ?vѫP?qfխ"?)Mĵ"?)Mı"?)M"?)MϷ"?)M¶"?)M$?0FH?lHѬ ?/Hł>Vͯ>Ikij?Ʒ?qŵ?T``?T@ŷԲP?dֆϬh>'viנ$?HsΤ$?HҤ$?HҤ$?HՎҤ:7Wk;wv??>5Ԑ>,m̯Ѧ;+=oV=Ra>Aaa>oi?dL֡Ե:7Wo>,mƯd>Hoo=/Hf?tŰ>c@`?T@ŷԵ`?BǎB?4e״>Lɤ>wt`?Olũ?/hv׫<.Ruˠ?Nɮ=.Rө?/lh=/Hf>S>a?È̱9oH?Dg҅׭h>'viנ>wӨ?uvH?Hl;lf>cBɨ:Rf ?7Wԗ ?7W׷:UOd:UsO=Eh<.Ruˠ<7Wo<7Wch7KIo;wo;k=$l<Oo>LŌ?.O?.Oʧ?oсĭ>Wֵ?SƉϑ`?rS>eoͳ)a֤ǧ9Oo?t=gǩ<mw?oсĦ="u`?r`β;@q;ql`?RF̣>@rǡ=R=R=K=A=U>(qSȡ=)M?3ʠ?2ʣ?.ʧ?.?*ʪ:7Klsnappea-3.0d3/SnapPeaPython/CuspedCensusData/terse7o.bin0100444000175000017500000017470007011566574021320 0ustar babbab7Ww^BQe7WwBQfZ7W6wq\1V ,LlFʼ@l+KkMί5@fHKo̠ѯ@f;V+Kk֕soá,avmsoáZ7W6wCy%E+fr˲F)pK+ײkF)Z7W6wn9-,of̶i# Q< \K+(kӲ# Q<袾7oͨ% ,rpV7WwZ B7 \\L,/l͏Z B7 8J,WbehV˶hb,r|jAqÕ,LjAq¬7Ww[攬7Ww AzU ,Ll{ޮZU7Wwד{ޮZ7K2TJDRjmփTpZ7W6w;S; Z7W6we_"ŏofa _S6ش7Wϊ_S6صZ7W6wg)иZ7W6w=k xwU,Ll TޟxKQfRN Tޟ\+K2l0}U7Wl̆0XV+KwƇ7V+Kw7U7Kk%yeU7KkF:hRoV7Wl/+wV7WlYԂwFU,Llȱ?;>%Z7W(w1?;>7W(m1%27(rҲŨm1%7Ww"IJg蝬7WwT U,Kk:*XYtK-VvjJlL+kOsj&U,KkV ,Wwu&d,7Wlm&V7LkP.^V ,Lk\~4U7Wlˆ\~YV7Llc {NH?V +Ww {NH>V7WlZөV7Wl0K6.xhjVMkͅZ΍FlK,wOZ΍ו+Lvg˖\ ,W·xvg˖U+Kwև4i[V ,Lk4iU+Wl!Z,W-k̆!U7KkGLU$U7KkeV7Llu _hKobu _ Z7W6k36 |Z7W6k]rشfaҬw;3+K,w;V ,WwgDZ5NHwrgDaU,LlOVV7WwVV,Lkͪ}DL+̦*}D7WwO%l7WwORK50l+WkOu*zZ'rRh`u*zYV+Kw*UoY\7WAlҪUoZ:V+KwIĠV+KwEӬU,Kk |U,Kk9F| l7WwO=(U7KkJY!l7WwO$z;bKU7KkDJZ,W2wcc9wtlK,lVc9w,7Wlms ]JB&Z7L6l ]JBLV7Lk$-9!V7Lk֯y'sfj_R/f7LĬR/BU,KwLȚU,Kw1ȓ?.l+WlOf0^[U,Kw]f0^[U,KwͅS<^V7LkS<8,mv˶͢@n5\K,2w"@n5V7LkܵӍV7LkƲ L_͇U7Kk#eoU7KkI]>X_V+WlV uNnlK,wO׆V uNV+KwZfZV+Kw֯<Lx.rFMb$l5ovmbb$lU,Kk  %U,Kk6øq7x.rFM֪?$?L+צ*?$?V+Kw)Ly7W6͠kdb7W6YҜ"tRJwӸؚ7W60lL,lO˲n\qOHwƓ?Tt?`e,rc͟oӸsZ,L-l.:ZzqOHwApKqd7W6͂XÀ+K֣BC%O;UZ,L-lփ~r箕7Wȝo2aM7WĵG(4yL,̭B+KƘ{Y/l7WwOBE7hυCKήV7Ww˅ƌqVh,m΍&LmƳ}`-,\+K2k˯)O/7a $V7Wl}V+Kkkd6I`+K z0RdwQre5aWR8Rin8V +KҾo:J7K~P7K֋:C*"V7WlqӮG8~2U7Kk xl ,W³݆0WЕ,Lĵ=17w1b,Lҙ@̵7KF绌AH7Wƚf7KƉbwC77vH7CfV ,Lkni䅧tRJw73cu7K/x35iA ,W[zs>dU,KkLt0 j^V7Wlol+K2Ý|fj,WlV[뻧Z,L-lϗx,.x,KsY+V+WlҜ0[b7r׏@4M&j,LlFצ]c~NV7Wl9ʧl+KkMWMHf,K˳)ϦhV+KwT^H7W(=1n5!hKmo׶BNu7hčʏ\WԺKcOddfCvdKh `x̯\+L?b 8U7Kk]7l+KkMj˩V7LlҥU ,Lk׭V3dU7Kk\ȕDfl,W2pO;flZ,L-lnb̶L,㻹dWV+KwDK,/ό/1\L,/w׭q^rI4+h˒rsmU+WlXZHV7LkOnfRU7Kk׭ c>CU,LlȻ긗qOHwқ8ֳ<6U7Kk ߹u\o57hmgmSwJs Wt7̼L,Rĵԕtr;U,Kkr!6wR׭ϛ*}QV+WlVO"U,KwtU,KkP+VV,Lk!qy,KteWƵ7hoϟs1J\K+6k8L EiAHwka^&"37KÉ|y+t 1;6h7LȏM[T{j7LwF̨Lp)V7LkҝMIUl+KlMPV+Wl˲̜{=71VU,KkKXT$p-7KldqOHwu|8d,KV+Kwzi% V+Ll̶]N|x,K,wv˱מj[JU7Klȸ6U,KwE8,nZ7W6w?nV!V,LkF,Kv˳?$d\L,2w¥yCuU,KkJA(7Lw6gFU,KwFk]/dKr{cl+KlMצ1-j7LwFצ %`U7Kl ']W6?ԵLmAcrU3^5,K,lfmgjg̦7vm,.Z,W2k̶ U+Lw ny\5NZ7W6w~@ Q1W8Z7W6wҐN*CK6ow%YV,KwצG6 ݰU7Ww |Q]0fEmcSyغ,/r4K鯕̖7WE bF}U7WwҵvGږ7WcBT V7Wl5\YbeZ7W6weZ42s7Hh̶ʂgV7WlȆzXHU7Kk/ޠU7KkEhƷ_U+Kkv7Wiv¯(pfRˈȥ.Q7W6J;]+Hlˇqq%}El+KkMӓ<8(c/V7Wl3HNsbU,Kka-WV7Wl )e,rfҭ4*l7WwO"`U ,Lk Gv U7Kk!okͰ9b7rhׯ0\9lL+kOn$EU7KkU7Kkr0l+WkO/rfbqOFwtl7WwOY\V ,Lk\&U(XwVʲlAjV'Ma̭@ZuU7Kkp+nJV+Kwͬ`Bv%V+KwJ\V7Lkͳl+KlFBe:ex,mƅ lL,wOY8#EU7KkGRJZQHklqjC V7LkyzɅ4U,Kk7'`jf U ,LkȆ\0pe7eh֔B5AU,Kk6/V7LkȲh`V7Wl5F3 (BU,Kk4 ;ך7W60ATf2dKfvEuSg<,L$bbʓ?V+KwWІE`U7Kk23! $,7Wlmy02*U,Kw(R6U,Kw0L/j7LkMֱ|h? x7W6,MRKrغDRq̲p_U,Ll-v U,Kk vV+Kw)y=s/U,Kk.CXV+Wlw{HV7LkmujtZ,L-lj1Sŵ,frҸݷ5U7Ww0q&U,Kk!R2ʪDQrfƷ͑1KV7WwMFbؑ0V+Ll}`,K,lf'l4<%VAV,LkRoP'U +Wwx!V+Wl͈Y 0,L˲s}xyU +WwaJ8HݢU,Kw KGp,mv{"U,Kk`!NpKV+Kw5-clFHlcp/YU7KkGQV7Lk/y,,dKfv͇\ۢ#U,Kw\:0Q:y,rR4Kmc>pBQf8|CU,LkҭET\\7K/k_b݅nU +Wl cU,Kw1 >,LlۑLH7W6Ja%&U7KlҤ&{QU7Lkl ݺ7K$$6]7eo{@a~V,Lk!p4brV,LkͬGAĸ'Varֳ,iVZ\L+6l}r\+K-lɇ7W6LU ))U,KwΡ"%V +WwFXU,Kk5$7Չ_U+Kw)!?lHU +Wl௔VZ,L-lOX+K&j4k`ro¬g7vmnĥ.j+cE-rY떵,LzͪԺIRr˯W6 DhlHf׼4iRH`Ə(V,Lwr>7W6֯n47Wϧ9IR7vbLmOFq%C1V7WliZVHkŰfpDb,Lŵ7ho-pCm7WK 7Kz=V7Wlr5.rC2,2rՃn_>,+KkQCU\\U7WwF ]qOFwͦ𺒖,Lj!R7WfpD`ʵ7WH` d$^ Rlɱ"fK je,rfπLCW72eˌ(XhG%+m›O2flŵ ,ofLE$`ʺVb4\~TLcr˦"{7mS,KFo殖+LGw7K8X97K5:47W'JUU7Klf2[:0< +K}1bn ,LτyWDžVU7Kk_UQB,L"ts7vm:WqOFl϶ʊ~,K)L曕,KW_[S,5U,KkN}U7Kk``  ,WƂ_rS7oˍfgn}ŵ7ho֝^0 6 +Kq17WҢzQwӕ,KNUOhv3 i(ԾV7LkT(TZ,L-lҡaDf7ra.j6@7Wwg_BK:i7Ww_BK: V7Llj_K7W(ϼ*amV+KwϡK&M;U7Kk|V&7rvyB,L GJPCh˭WuU١ +W5}7vm]^]s PƵ7ho+U7KkE ,KTz+U,Lw][Dȶ-U7Kl 09>O+LnziU,Kkة,ۣȷdFvQl+KkM;qedV+KwҾjd^ V+Kw4yw~GXYYAOlƭ;V7Wlɶ3r7U,Kk4|K 4Lcfרf1fQƜ7W2ܒbil_`,+KkuV+Wlzՙau V7Wlsw5\,L)lϚ9wOR̯b L+6=D߃^/U,KwGxS:Hl2Þ*V7Lk!DD% +vռS7r2U,Kkt}0+o˖ֵ˂SV+Wl|ux3+,KrNgLtU,KkrdGe,K!*2o$nV+KwF";re7eh =d,KSG G̶K,V v$3U,Kkk&ٱL+6VN~LȾKoFIvϲ׹dvj̨6WU7Kkx,;& +v"ۜl+KkM*"Հ2U,KwgMǙrU,Kwlv7LIPsɖ7WzD%֓V,Lk3mN"5ҵ,-of7y)U7Kk0p3jJV+KwA}VD7KÃΔkV7LkҚbjLC>+Kʹ wU,KwJaS|O\VL+WO;E}N$,Lyj{O/.To̲[DjU7Kk_V@XbU7Kl͙:jdU,KwԖ`Æd7KҍG]0NU,KkM4Tn6"oTI`)V,LkҿlLd*wRגʊZ7BU,KwIDDpl Mkh'ټV+WlҙbV{U,KkN8%^,+Kky fE7W6רI?%.V7LkS3b0\V7WlQLn?dLfv̨ 5T'U,KkJ0RqOFwZ6|7ofjO%U7Kl n4 t,Kq ߰;V+KwȾ|7fԉƵ7hot:m,L2Dv-'Vvl5z5V,Lk S'P鎕,K49 ŵ +hmh5bqOFwyrbe ,raҐ!02p(MojPhVЧʎoAf14Lmc̦͝k) ,Lҙm˷nVU,KwiP)mA̯G,ЙtKvV  +K7V txU7Klȶ)!< A,mvI>Pa,Ƶ7hmj)*lK+wOϬدȢIܕ7KI\ŵ,frJL:} .Hhu#.L,/x+KȖ媋V sl;WnkU7Kk>5NOuE0eEuEZŵ ,af"hpre̲ nke;r,2ao;?#7Kl;6,L͸Bͮ 7W(tTpNO7K˓k$ҵ76of'Mp'rRCVe,cfPDCj1>vvL7WRώR斶 +K݄֘A>J7Wd6=0NqOFw`*u|7K 0b,L6c<²'eRsҝ5#,KFYGW7veֆ 6,+Kk[1SF+LғžKە7K`8%^2hprr˶r ,K"iqV7LkІM(ژU,Kk7e7ehҕmɸwOR˯$p}* , +K<-:JqOFw'#V"صWqmO4=eՃg,LȚB2wnϚ7W65Q]!n7Kω\.ZlK,lV-`1S7KE,AH 7K F V7WlQuJ/&%e7eh7JvV7WlOrͬdd_^hOoҬdd_^,K"k،7Wǹ- mlFO˲9- mt7/veɇ:ڬ7Ww̯JC&-,KUpVl+KkMۭ$Y +Kȿp)-#V7LkҼ0`a[f7raajМ7K/RTB]fhwe ,ra6^7Kmz[5V+Kw31\.rRרһ4Z+'r,KĦ%EF̵L+Ocw$蕵,KI7Kf,mrFAvrsŵ,fr|^wFO.[6PU,Lkߊ&Ď,mv!-,mvz<PU,Kk,uV3x +KIbhP/LL,RӗR V+KwUUeib,hf\_ I +WzU7Kk>Jڛc+ +KB}r,,KHDeU7Kk($ymɺHl˶oRd,+Klr:&ŵ,frͺ)Wd̵7KFbɤ\LprfרVRhmU,Kw(4!,KKAB,L2 $ރa,KxS|B@̵K,Vt!,K{ò$7K8ʓ~ ,W]eHb[,mvk!ŵ,fr9@]`X~ ,W3bv7KσˋW ,rhzΧf/(U,Kwt~7V+Wl+".HoR3ʶ7WVKE" ,rhou/,7W6J ٕy7K֌&   ,WDTYWqBQrׯ4v(E +GKXWhqר7-XӫZV+Kwt/>Ƶ7ofx2UgKn +WGd)+Kҟðٸ$hTPi@""e,cfVm1Cŵ,fvE@ŵ +moK~k,$b,ahϵ3ŵ,fr 2,2r!"\,mvP~ޕSVv)nThv즵+tr\s[$:KfvVFN+mr^$6صwvJaH7W{$lOJר-\?MľKF $&s_& ,rŲ79V7W49t?ŵ +mo6_!E,fͥȑ̆8,mvE0D,KmZ0ݺdLcv˨yLͧ}tlOto y(fRƍyMFl yMLK+l͍yŵ,frzn.R76ֵt`)% +vרҊ]VY7W+K⠖7L`5bߔ+KkKwԵKcrV| /HHl:t<$bR7W6ec {ȷWh֯$2W1 +eo;TԄصlFJf>^8eF'eF}պJ7WHL 27Lk4صlFJfu..,2rh [QbUŵ,fvn<2lORfDHƵ7hm3f7aoEa;yIVkW!rrF̷(B74Ƶ7ofZW: 7W6ˬ5P/.Va]qe7raāϵ,Kb?x+6tfMmeF:Ƶ,vrhn]7K^/Е,K&;t`mҭ6Oŵ+fvŹ3pTKmr̲|صlFHf*f)ڕ,K"U h,LeC)Q"TлhOo˭O*+KJt+tfD}e ,ra7+7Kf; f7aoqP% ,Wρ"h,Lҽ|殲)aV.p;фصWhHo ųPa ,LˬҊųP,Crc*!q`V˶*!q`Е7KR ؐ7K1 *1ŵ,fr9lx?´7/eobe+K۪͢07K<4ЛPҚ",f̷P0wlk,Z@MK(.Vm,"&+v̲WO1ydFŵ +mo̕1Ӵ7WlXﲣ'eF˯V`/صwFHv[v7afj,v'敲'eF}7Ww˫6,Kx,s(t +K7>x= +W4=qʭT,K$ǔX9,KN[+K-cvZ,aZ#ZC,Kco̍"6HWYHlm wT;K,/9 $pʵ,WV+7,UCh׭SqKC7Ww˯FҬ7Ww׷F7W6;m677WײG[y8G,Kee f7ao\FǔR,-oC~„HF,oȯ)JdP_7WV.~7ao`pPw,Kςށ7WbWHJ,WV3>_7KhR&ڕ +voH{,LˠmLմLK,V5ãͰ(hR>vLHԜ7K/g%S0.fE̲BB0G +vqkK.ԺUHc˶+pU +Wnrs_ +Kyܘp% +vҭ܌ϫ|VJh\ϫ зjjRlzXd +KCη9=4LcfרLQ hKmo˲PW4DU,K vTLcrצvHWz7W̷=GF,K7%A,,KD?R\$(E7oͨ;ԵKcTh֮2Gi&7W.2Gi&K,/׷'mn+Lv'mn7K̠3&1ĖغlFJ̨6[i+W27$~ox+6tf#M/ߕ,Kw]k,fE$ks%+Lw$ks%LL,RGzŵ +moZ ,Lv?dqx+6mfAg^(,rˬ,|5̼L,RƬ|5472v˲P[>ԵKmrVP[>H`,^@Km׫959D޼F ,oZA[,W2\ֿu(7K/-f&ap,Kנ"Jܽ>Lзma0|L E,fʹ%\:7K/+:8oOho`z?ԕ37L̫z?ԕX,rƶ8F Bk,Kּ¬S4,L̠H]!ʕ,K_CB j47o׍CB jXԵKmrVS7+LiפM}[OhvkHvKɾbAj!'ViضBRr̙ò)mV˲E}wpp+K̷dpu3.7Kˬa,mr jϏ T,KȨuD +Wײ}7Kˬ,4=IRl¶tC;.LZHl˯UUUUUUb,LˬM jkvV"7h̷OBު7WJT7\7Kˬ  hChy`̵+Kwvַ^R F7o7f2'fF֦Al%7o|].u<Oϲ/ E,fHw iLK,V}wΔ[7KˬȘOnHlrРn +WRp ʶ7LFb\,K̷ʋ^1ķ'R͎RhHw֪1sQ,Kˬ¢+K˷ҋsE;bmճļJu'PE7h֏QQJo+A2,-hϯxpv,/vτ8t\hѵOlrdA~$h,-fצֿt`ˑhMC푄M%+v˯`^LOB67W6˷c1ZF7W6ˬ8!d7W6רȗӔǰ +Ẃ̷gIҵMkm͞7Unx"CccnފVӶn ,Lֵ?r&d½.BvI MJ&@ZO`̑hԯ*1W|prFr̯R!כෑFhȴR61lwkƬT DLv׫Ⱦ]GFR7W6˫ғgSwnPhHЏ϶7DsѺOl̯"[Gw]+Kk׶fCѵOlrҦ,_vԖ7W[[6gȻWoRײ09ڽX7W09ڽXrROrIPq7W6˫QMF`GLmE,fh:F7oŰѵOlr֝ c ѵOlrַ8WHJL,/ౚT+2hWu6ȽwFˆbƌѵOlm͆i HlmN! AHwv^7K%֖hE7T?Hw\2 E,fRR7ra\/-Nb5vȆų>F̶7Wl$fHlm 8Ph҈ \Hlm;B܀h F7o2t^Զ.jt"܂%,m)#}R,-o(8$*,Ll/ K+(O|ht ,L.B6"7K$ѽJQ7KO|ht7W6̠MSŃB2RǩRiy(WBlwkBԜ8+K˫ơlI*xprFr>R!Yp,K n*n,K7dMAOo׷%h$:J`rֳoi̬7Www;A7Ww;AෑMheرhTM%y7K˫ς$޴E,Ϣg1HCo}j=HwvMc ,LҳDsC.'Gn`C4E,f Z??7K3>63)`kd)寐h,o˫ѡG;奖,L]wԖ ,WHZN.nnҝIG+KÂrٌ6̼L,R˫WX.As&7r 8k~JwvA>fveq.U \[҆n,Lˬvx}/Z+K̷*҄%,m˯ɔѺAw̲9=E,f/J ,L<+K\JtHlmC߾Cj7K̬l_@ķKha汬+Kk̭9?cg7 ;,7Kˬ f#ĵ7KˬKo_7W6˫A2΍U7W˨j!,K2jDpVhm*Ќ+KՒM*Hwv֦i@7KˬMn7Kˬ"L: eφ,r̨mۭNHlm& Hw_d~7W6˦MsYflHlm T%UMNHlm64qлvhRҶ5B},W2͍9MhKoϲRu,K2Me,KlHlmfyw`R76aKe|@3ѵOlrrD^LK,/ +W2ʥ[skF,LˬUY/0,Lˬk7Kˬ i%7r7fkb+Kײ7㫕,KwV7W2薼,L̬9Mnq7K˫.Wp+̼+WOŊdp 7K/ѵw*,Ll-׶kPN7KˬL[ H+KфnW0L+KMZ?ìk7̶ZBU Hw\7x>E,f0d/GHlfhdD6+K1WN7K̬҄#댸67o̦͵K[5/,K˫13,LˬaQa7Wנj΋=h,K˷6qGI,K̷ p:̵+Kwv^ {q@E7o8 Z+Kk̶ơ[=Ar(+rȢh^Ur1Mfv"h^Ur1mHlmҠF7o˲ƪڀ7L6֟ZT ,WX4̺7Wl˦OSVD/*,Ll֡:rwHlm҉P7Wײϔ_C%7v̲yߙPb7r̒PLa?+K_qmLhnVTQ@++KרȟqtSE,fׯ+KLR,K˷ A,Kˬ4..+왺̶FlQrȲȧ",f̬ )Y<+Kנb7ȑңjzҵHwoax&(47K˷>`TJlprFrlٮ|Ś7W6˦Ҝ L*,LlϨLj׊,Kר{~t7W6˫,(HlmvE(g7K7ݦཬFr̒ϥ|MÕ7K˫:J;7,L׶Ϸ]Ħ&,K̠t:+L׷?^i-,Lˠ, blR7L6ˠրc=+(v˶֮"6"+Hwײq7XXhCh u+KˬqT/Ohˍºp+L̲͆FnƄR,-o.B$wRoo,`k T72eͬ|9 ѵO`r{̀@XBR/1ʺQw˭] L K,/׷3lcMբ,K׫&L/*(,K`*,LlQ1 +W̲n݈zOp2rM+AddfEV̨͕-O$+xJ7LF׷'BדHHlmk7nHl˯,Km׫{-VvȄ@z,m0M̈K VF,K%`SfɃ,KˠbaNѵ F`rYqmM·dlϒ"nrRRh@,](E7hM2z꘻$ho'5gY_R̦Χ5gY_d+v˖ҞTDU{88d,K˦g\7,K#VE+K׷ѓ.P +W̯dD-`.HRy4ZQKcOytؐ#&+vz*TdA,LˬOE7h,  ,K˷Ѫ'Ɉ,L̠7Äc&HwT',L̠ȏ_7hR,-aWpyKѶOlҷƼ}7W?:l1wDQ ~ȷ63ѵO`r͛9PB E,f̯XL\TKov.zARPR,-o׭1aVP߆7mǫ~p]7ǫͦ6|*,Lll\J%xHwoƠǢ; +WׯNc( R76a]HJwvq&T$7K/̬Voctt+K˦ҙXiiJ`C\bhh·,fhhn,K˷+W8,Lײ֨vHwv9?O07K̫lJ7LFנjzD7,K׷#Y7Kˠϕxl7W6׫8 (D@W+K̠Q:w 7K̷ߪ:aΠ,L̬DIyOҶ^_q7Ww̶ꪪĬ7Wwj`ˑh ,TwRooٟͅqR,-o˶IS෬FrmzPTS27(hrwZp/ߕ7K˫ϊ?aZE7h҇?P@,׭UPu&~;s,Kref8+(m˲ٙ|( 7Kˬϙ<=FOhגe/fvH{w?rn’0qˬN+Zs?Hlf6-Vvצȸj7Kˠzqq4%7K̠%r`%,c˭ƭ(=@B7K˫Yej-PHwvχ|y7Kʘ:U',r̲P։dHwvƟWidž7FU,ԛhTM˭1~r3 % ,r̭HS7K˷ώw=Hwo7@u7K͜) E:膻7o̭gGa/ѶOwȄ"a`97路KfV.pvE,c + JwײҵۭeT+-h7GrAL7Wwdjg57Wwjg7K̬qXKTHl̯.,ȱ3-ZѵOlmš 27K㚮R,-ouM \,K׫ҁGM7Ưp}HO`v֝3QF% +vYkTޖ+Kצȋ>r(qP8R,-o˲aAImb7K r,KˠIrbάѵOlm򆖿Sp7K̬ҎFdjl+KKGԛ%۽,ҋq*Jwi$p<2}$7KˠȌ?"c,LˠBH2HwvͬU Ɇ,ri?ɼȷ"Fy/KN7K̠Hs̺+Kw˲+K/Vd7KˬL1*HKfͧo,LƗQWl:B,K׫v$DzwHweG,L̬󌑷lwӒKhFׯrn0++KˬƕHfq̺Ow˦ҮڱftE7hϖ8q>•,K˦%k+ چ,r/B-E,f.uae,Lˠ@~պP7W6ˠWts;ך7o>'s9=7K˫5w,״!ME.7K̫Ҟf$LW,L˫@iO(Q,֨J&Px*,LlͳD+&,mײ"0..%ҺHwצ%DP&.fFˢ /,K̠!Qn`Mv˖e(SȻlHfׯ҇e(T,L˫֖k[<,K˫vh?,L̲Ҹp!Hݕ,KM{7 `̑h^ >i-E7;袹',+Kkϊ5IR`̡̪ %Kl<,K˨Ҿ5Dl&,Kר~: ,LQCymIRl̲̯B ,ѶOw־ "ǚQq˒ͭQsZ'Y,L̠pKb~7K̬8%fѵO`oJ6uFwօ\Aې_,r̶ Co̒,-f˦O$"X ,K˷~4 f,rר֤+@`DF,Ku'.3RFw4lȫ=ŝ~%7e˯\o2`̑h}dGXAZ,KˠQJ,Kֺ@Ky,K׷sbfH?s,Kss",`ˑh,g4LvR̶|d46=ѵOlm' > *,Ll$ͽeE7h͘`xI],K׷%R7Ww˲tzQ7WwzѺOl̯ϙ,̌Ʃ 3y%&Hwv#B(A;7W6˷Ƃj!KËR7KˠD~fNv7շ3f&4%,m˶R CTbĬ$HwCWi,Lˬ߿[W? Vk֣dX+KժHݐi¾ƒƲ!V&oĽLEeƲ2!V&o#F`ƴRWʔKmVkMo&LpѶOw+1,HfE,f0kc C-Hlf֟!a127W!h+Kר 7D& +vƭ:N]p,r;my[/ѶFlDM\7Ww˶ү go뜕,K˲͆}QE7h:L}MFׯn:L}&,mײֱ4I;9UapOe̫ɨBj ,rרq)8<,rƣaQZa^nXHw˦,Kנ҄ǵ$Z+,vҏ :k,K˫ ϩH9ȻWoOײgo*,Ll(7K̠)K7K BnѺOl̯z̲'T,K׫ȸ4*"ҺCkׯ<`ԕ7WׯG"w7K̫%%9,qE7=ۜ$Kf׬4e`o,YdUhmL:ӸѵO`rϔ2i]l7KˬH4E,f̲5,9lE7h̭gbz`Hwvh*Yg3Fhv׬oxagRbu̒|x_ѺOlׯҘw{7`hmoNVѶOly+ ľ`oҴȢ8(J`JcφdtʺVw˨0DIn,L̠ F ,oׯ s̶L,wȉsOhv5l䖼+Kצ۔^?HlfX,K׶-Μ!b,r־ s[,KZ CV7m˭Fp*qRȘd6{YFw˲țq,r0jjERjprRrר/E:+ɶAlv iĻKvO׭)5"+K>SIE,Qz*7Wwׅ\8IQw̶AubNѡkwlUE7h)Rr,K˨W$h.·,ofKiQt*,Ll2\%,mң1l`Hwo`]瑤7X1f2ѶO`OT)sp,v3< O=P7ivDz8J,rU*,Ll  IصwFooGSy H`"FUE,cWI(} ,fצ_/|^u,Lm̦͠q L%,m֯@@E,c̲+t S\jOrY,LҞ<=047mr|ƖE7h˲0!Ԫ,K˲֜:Hwviva `ˑhQ:@zǖ,LұػfZ9b,K˷k3[+FE7h̲ő[E,f̶or}[ѵO`r]0s'}&J`׶؅qVŔ1 6/MηLsB%,mkn& ,r5?p,r˨'3L,Kר-JoHwv o$ ʺQ`̭MٷR,-oײg"F,+CQg2ѶOw͹S婾X"ttKr(|"QOwph,PԆ7m̭>$(Rl $¾)ɎѺO`׶2fws$E,ҁZkȏ&dwDTIRhϥҦdwE,fֻ`cpF5,KmDvMѶOw5)sGDptOf׭ \}P̑} \}놻+t̨͝K2@ѵO`r;V7K̓P: 7L6̬kpLO',LˬֵvbE7V-y&7vŴϤ/<͒R>,v JYy&7E,fҿJ{ٛ@,rp"2,Lˠ֠EW+K-˫&6KѵO`ŕAФ+hpcc7Kˠf hHw֯B7Kˬn/n('&+m{a>e!?Oh׏a>e!?7W-q;R,-o̶Ʀ]t5D%,c˲jJkJwֹKo![Jwv)/ɛ Rlffu)Ad҈fu)Ɔ7mר1В76h#j pf/f2rhI *,LlitCJO`˲<'F ,oȨƅ{BQw{ص,Qm{BQw{lѶO`ҊEAzdUJ,K=۴7KˬdqȆ7mצF$ѺOl̲D8Äp>,L9h)kuѵO`r7F` is+KצN;#-HJ,WV̬:5=δE,f҅~S%,c˶~R,-oϱƞ:tp{ѶOw=2wϕ7Kˬ"h9,Kp* 7oҔ@F&,Lˬ$"^7K̫t$>,Kˠ2|C6&+vͭEzF7oȨňEzE,f˯ҴgB%,m͈q<ȠrȜğX00rh5qi,1*7K̬TF7KˠגD<;%,m˨ȊYҪmѺOl˯}R#E,fd_X,7K̬1,V,͛~`i+tͤk&|Ⱦdvƭσƒ5TIRhԯƒ5E,fȨ"tX̫HwvڑyYlE,f͑tE7h$O@7W6˭Ki#MN 7K̬҅@;7K̫LS*,Ll1.$O*,LlcTUI,L̠ٷ.)4lO˭o" ԛ,ӷHw̦^Kl7KJh[x̕,KG ,v˭ҥm@d%7v̲ҟ! R S7KIC=A?7W6˫D{}/B,r׶'B-Hwv{y-,K˷uq #$7f˦4xOV<7KˬG1ԏлe ͵E7ȶJN@ 7K̠yU,K˫Q敺<7K̠$Jt+K׷!O),L˫8C-6 [,KeDcE,:w[k%,mׯ֢OͅRl׶^|UE,K˷W af=7Kˬ! V,L˫֞'@vѶOlT-{3Jwײe.ָ:,K%XlFH Ci,Kˠl9A[RL7W(˫Ҵ{E{2Զ0eeϫ:9H "DLr׬X=UtV,K˭nRv&Fw;׸d%,c$B bs:%,mײֹy̚[% 7K̠% M$[ ,rײֲ?%,m d[A7ƭ`˖m̀E7hYIV-E,f׶{'38HImѶє,hDrF,%,mҴå,L?~\E,c=hѶOwvVw2OHIqvgVnFwȅު .E,fҫw9%E,c̨zz̨'veŵaWP ྈfĈʞ3%,m̨A,LˬK$6+rաʥ>&4Gs=,K ɗ ,K l7l D+oY%,m! (w|,K˲^2L_6B oƯ fjS,K׶6}D kƒD~_7K5^DE7hרҘ3[]d,K׫ 0,E,fQ\5hR,2fZQ\5hb,vcu `˲z ID VkHYM{7זM<"ˤTz,K˨4qGה ѶOw)l}6jE,fרґ{зje.&L/!kN' 7""L,̪pҭr,E7hGTb%,m̯ϬOӒlN,Kנ2gDz,r/E7h%gN}%,m}eL,v׶3)H+rR#(dѵO`oSwE7hֶΣidNE7h̨SS$,K˷9ũN,r֩&z 7a;us3d˒3*dH`̨d˒3*E,f ,ϯ1q,rH?Hw˲ȿ*B1 mӆoJVff+"{vϒ pgʹʦToJ{ѺAw̯̳[c&7r^̳[c&+tƥX>x,Kנ2!/FWL ,hVE,v҂Ap+e셻,r̨ϫE[R,-o˲ָd_q#,K׫ gA8ཬVv̲Х>gA8%,m˶t"h7K̬5Bv,]>i7K2;>z,LˠM߃,kNdDоvh̠˧g^!bѺO`׭ҡ:Gl,̀ƺ 2k& +HY4%,m\4u,Fhdžh+O˶}C|7Kˠ;xnpclРlz%,m e i'лRmpFjBk-Ѽgw¶Ţh?,v̦Xq`aE,f~N,v׶ҊԊQ->+rK~ F +hϯ່h˰SدVcE,L˫#DVnMvij˷!ײɸDr̲϶*+O*ffϸs쐿hvG6(8+6mt2,2rctE7h>({"(vm̶\Q,2½.ו ֓ACh̷b+YR 7Wֶ)lˡ\,Kˠgm]KD,L˫w %,mKT] 2,2rƇUD?h,m; oBys +KfzNhrlFfײ)aꅷ,r҂ִ(^.Tr͡xw^E7h͏Ţ v>hѺO`˶ҘS$.%7e᲋* Ң,hf ~Au% +vU,ٮ%7vִ ]>Pg̺7Wl˭͝+ s̺7Wl+ s7Wl,DC7Wlb,DC,K˫$p G,v7ppY lRf;£U,r˲= զѶOlOn صDqFȻ\D&o»˶U̱|[,r"pȷ+frmnu,K# q)`hvqyȻBF-}Lv%,m׶hj!WxPKfVl@Hi,K׷"@,,rȵ|THD./,Kˠl <7Kˬss$,v׭ֲiE,fZҵupQNXȆ7c*Fr5X,vҌ-NfΨE7h˲%I 8,K׷N=pVv]|`aF +hϯݡ|`aȆ,rͮO؅$Fw˯0*L䅷,ɿmصDqF. [ۢÅ,r˶x4w$4E,f'eR|\ K+ͨHE7h˭2$ jѺOl׶GOPOE,f˨%ҰR8+6m˲Fr9E,fA4 1셷,۶df֪27f˦=2:v׈Ю@%%,m˲9OmѶO`+GTѺOl̯?-'DE +h85:h˧ƱDzI.7K̫wص+Mhc˚PYE7h̲pdG`W&O`˯_yl^efגLȮ-ˡRlա̥Ȯ-E,f8B)/P$JVw>!`Щf@>!,PΔ0oH̅NÑud$n7K_l*%,c>DR?Shv]"Y4 %,m˨1 `hf,*PVˡ\[gPJ,Ll\[gu Flܖ[gPRl׶@tE7hQTBE7h̭3rlh%]ejmH"̠Wݏ.MD ȷWohx<"E7oȨUx;%,m'ܴ.mN~BྈV&84wRhσ1=EJwׯҼ's;/ѺO`̶I-[O໫cOo׶^靘ѼOwѠr0dγvĨ r/*7Ww %,m̲1o䇢.7K̠vEyҬ>E7hW$ycE,fײ?ԃx*z! Rʪ'Ej4+-e4g~! Rʯ 3tFw˭Ⱦ e=PE7h˨>U.%,mn4i&E,f8V%,m̨f <,Lˬ)Yis&ㆻ7m̦͏#WE,flXRQg%,m̯E4%,meݱ,r)$/໖qv:_໖fv]:^,ryKLUѺO`רHĚ E&E7hsU2eT{ Rl+OAě%,m/,^5Fw̦ϑ Iٮ 4ѺO`׶ut~+eHdv;E)2`M̛QOwδ r'\·$hf[ r'[E,f̯":G%,m. z'h.RoͿ(3ތApmJ'"\cŸ<ѺOl̶ubI ىA8лcVo>@#% ,rzME͔TLmؾ-.ķKrVmh@e3,K^tAi7oCc _Hʵ7WkWQ ?E,f˶ 2Yu7hZ.FE,cҲVk,K׫\g#fdSVAhC)ķ+hm*i)Bh%,mׯrP,VE,f˯f@#Ⱦbv׫Ϣs %,mֽNLwE +m̶+5]%,mca %pMs@,Kv˵Bs@,,Kנ_BvE,f 8u7OCy"U* FoƨP؃8 E7h˭5C5"»,ofȹ4%,c~-b7f˭⼂ѺO`צʗoKhӈȿ/O <VɶH/O مbdɲrToW˪ĎoʞHʨOp5Flhhʯ-.3Rlר6*ҝ&+m_jJ"F,ˠe_4,v˶h#jJwײϾLTx+zE,f˨1&3F+.#@E'JлajRϸqr%dSVƷ,5 lr˶m;Y)PhJβ/P{-ȷ0fv֯P{䌾K+ȶ 5ޖ:%,ct A`,ݮ,v˨Ҿ::$<%B`oȿ`YѻʺVwײ!?Khֶ EptKcČѠiI dn>4|؃DHw˭8խ*AE,fG?+E,c17JZxȾIӉօ=7rχ3>O໬F9~VDE,f@^,r̶l* E,fI,Fw˦npX F7%^%,mRI\j񠿖Ֆh)tF>AͣͲ䲞7˗ҹЕ7}7mצnN[6}'̎<34}L,pӭ}-Ч7f˦3n󅊷50nϔ:F7oF)_t_fEOR͐6ǠЫBO7HE,f O0E,fר8ADމ%,c7(a>ϙ27(rȭt1uj,r̲/ 7y Fl˪ӝPL20f/PL1akͦ jisu20FOɖ5u|,vq@=A4ѵsp-<޺O3Nt +o!ݔ '8ྖF̆Ҳ b&|E7h.]aE,f˶u4:"O6hP@3G,rlab,rV^*eྈh׈DV^*eBKחDV^*eaF @r+2bH>6E,f˲8 YV)꠿hhnHO6B%,mײInHE,fv"޼E,fkBnѵnl>o2E7XPXJPco"X7$MFӤEr(ѺOl̲Ee̴*໑Fe˦Da5셻,r̶2G߿? \,rײ _|HKbV¢ѦL%E7hר@ZpԎлhפ?a}0o5& +vԴV=,r׶KunjZuhİ++J50l̲q]ʙ7˗q]ʙ·kmϴ"%"bѭֵh,c+(ˬCrhwR @HtѺOl˶S=]Ҹ0E,f˨l~P}b7rhѥ1*,-ҖiؓyE,f% 3vt9Ⱦ,mFE,f˲ś˴FE7h̨oe[񠿃hRASZ4POFǵ³OVķSTvfAශcjMʓP=QOw1E褧,vרGIf,v8XkQpDE,f׶#YfitлVJhjo0,vC Ģ΍Z\)'ȷWO-G q%,cko̳ڲ)tMי?7֭ޅ,r8}97%ܚe󤆷71֬ $%,m4fp*7Ww<ΗU%,cׯ5|9w \E,f:r5 ,-ҥ~N[M;|$Hңzӣb.o˴ͅ,\O\o +Kȣƥ~N[M;W 7WƍZR,vB\wi^ĔлVcf֦oذfQYa2rOɤRX*tmQ-rQqu&ѰVOW@0 лqFOjwrF +hѴ 6yE,fי "OvʲM$=D'׬8{aP-'<<,rٯIB rkƒ  Hf:^j({ˀE,f˶XU)b·)m͛%F0IȻwooײ֒jxu$"wׯ8B~7ח8BYȷ$OJIPFоVT̩ž`aZ )XOU' "7ח ^\"J%,m˲BY%,c:D㖐rϕF ුthR `,v̨t/7,vצt)"Ƽ7oҨnIOB#%,m׶LgMZkлcVo+ Ԓ» `m̲%Q-|| Mnnr~D|76fȲ seE,fT',dza^`׳̈H'LI,v̦%q¶B(+v̬<È_(,o¥6 b,=E,f̲rB%,m̶Q*{E,f`smkrIJn+Ssw6*7Wwj4sKH0v˵֟k%,m̲傿.eè1ȷ"OUPPPt®Ӎp?jU*7Ww/ @E5لKׇήͯf@@<оHo׬ˬ噿U2wRթ噿TzwRFãeTÈlΌӱf@@<Ҷ'fa3I>R,-opо-Mˠ@^HZR,2oDlE, 48'nH(lJɉƯ\% +vե&Ϳq亂,d!໑Fe˭RL/cXE,f[+C }Vr3\ɴ+F׋8;nc,r2Y_GCFw˭0) E,f̶S z Xо2Mˠ^1EU +ĕձ֍#{$cnsͨ,bʵ7Wk{Bnw%,m֭e&Kձswb5`XbE,fײk!a ѺO`ר Z|#joˢ_'"-qV׍"fi;bɭ1{u$¾'·N-B{ʵ7Wk z*ZR.At G0Thϲ:עE,f b (YPˤF47hH̲>la`f.'+н2Rɫ]UFȗ,ͨ+Bзqj430@wRm+8tQOwCE g QffwB&· kvUͦ(,rڵL !loM-c{E,f˶Ho$Fh%`X"E,f˲aD+½3ײ&9ilK%7πE7hϨц& tAWvĆ֬;iQFh QԺLrvúSc0~7/27U»,fhIWj/uM,vר֒U7˗q9:fQHͷ<.ȷ"O@k!*&FʦRkzϙl̏ɩxm簒$KqˋѲVX|PhoVX|QOwpVX|ĻDoo.MO.нFmBp/л(Vr6hmէ͜RoIl,Y腷,rQkwqE,f˲(Kt f,of̭70<𩂿,òŷ0<+2cdؑ,vצY_+ L,ԆȞE (`rZELCгfĤßIF̧е|38,],v˨yTb;{,˫ϟO:TtƗ1(A7גo + S$h Ⲷ,NĻlm˭ƹͰcNĽwזқ ),7ח 4yPPtCķɧХPA! Rִͥq@^ʵ7WkX06tĻlm˲nMrgH2heA`jFֶ_RHoHaײE/"Xiڊ7W|2˳MFӤ*~PҘ$h 5rfŪ Gqa`Jӆş Gqa+_Ո&{KRoylV8lFO[nLZfrM߃':Fr2r:ow_XbjҊB&7vȭ, U»wo̲+»kf’S"+!":\Ļlm˲ RZhF7oȯޤ}* +oeO%,m [*Bлm̮Mez)Vغ.vm܁E"½ bЭ@ ,rֲ4LcOƭn7Q[Ľ`׍KwaօĻlm˨ [r(ȇ ,fȯߤ%37xns֓6q- ,յϭ?}ȻKoϨxulP, u`'lfE?PŴy`hf:F ,aȲS쵘,rp"o]·5FmgJQAwײĪκ{F+KyCO9x9%XBRͣ\o=aзvo_>x7ג6: f>HIqvmL@QffyKз2MmME7h<럮 ྫrm̷f7G4*Vry0!Ļlmרҥl{*50g׭ZYuhUff/].ȻSc҅1ÔxtĽ`̒80;0f2cȶn@T{Ļlm˭j RWhOcl*,o.G'hlH̡îGҺ,2oȰĽ`ג\ϖĽw׈*0]nphRгO]Wv[cuRũn#,ͫG  LRfρssE,oKb`Mh5"K+uɥЈyv{ml׉VUUUUUUྃvUUUUUT 7WoPFӢt$N&q̠©$N'KfΏ% Pv`vKvE7h6;'FĻ`oײҦT]w{.E +hѲa6{ߴp6t>iP-ƷC eђ,2և]+ݦ06oש?\O,˫+dAXp/fՔ֨:SCPo?XB&bVm1 l.|Ľ`ג֨H% L(SjӲԎcD,҉Ğ0Rlx1Z,̫L p6 {YUȻwoo̶LwGܪ7Ww zy̪7Wwʊ zy^p6tǓ.{Xh̩ȍ锾+-mŲpC&S9Y+K7ϭh2L½3˲ͭ ؍7ҡ9oyMrKn 8wORסgV)Uevҭtj:ahaĵ֟uFǕqcR"w8Rv<?ߑFhstta m?jgM+ĕձ8*0nwB$rDr֥f8dOķKVv$1l̦̀ Lt XWvAWBź,fk!=jAgw09ft,roŨzвA7RM0?8(KmԯPfEsWnQOw˨Otf,ЪFr˦@Otf, +@ttHIrʒ7>LĻwv˲1>·o #Ɓ8Wvrn>F]p7˗if)+/Ľl̖#s.zdsHw/ _O,rͦr(jI,=f+,ƭ~jcXоMaFijf;dg{·)mȻG9PrΖյϔ)9Z»$aw!^4fjT!^ZPcŏȲ!^4`ELw?.mmΫw!^YԼIRt!^4ѺOwDI߄ V҈Ԩ-|з(OfѼOwοP,{ ȷ.Vǂ9Ǘv! RֲxHlppoVĠϗPI#moנ(?CxBĽwזy]לĽ`׈Z&ٰĽl̖{k%+Kҋ5Ѡ˫Ljn|qעѼOwӳ +$+2r7rw_}J50ḽƴ< ,3)nֺK$4rȾ+v˫ěX_`-NнfMr̢][4(,ƶᶇ[Ļwv׭:~@P(rK_|7חȣ4HmxrPĻwv˭}ӗwĻwvרҞ 6ĻwoײҒhh3-vhoנ̠[bctנ[b`о2Fˠ׋[bƪĻwo˲XWcYo G7ɕ +Ϯ>[A`ϨѿA:TDyѼOw Ļlm˲J%VMMnȁU?ܢ"jJjǮw ȷwoo4̳؆ĻwvרO*T9Ļwv˲֪Fɛrɭm~"`aR׵Fm _)Cr֯T P*нReş!gFo(pNhscAġT`-- Vh4p:,˫e fqbfROˮ̵шHb\bTRl˨M ׍,˫7)UU=b7˗4uĻlf˭j4.c,̫ȗэC,̫>Pُ+K;Dbׁƛ,̫FSU'ķwϯ8, Rk\w оFt̆p\wjƺ7oȼ$ik{ ,9u_BwȻwoo̶-Mq»7ra]6W#̺+KwY7,̫^3~O+MEKR̯UP 7חϦ%YxQOwǑL3n཈hhL3nt˰Ʃ1Ļlm˭0[ཱིqF/Ƌ~ ,̫D<>ʮĻlm˲hF5#E,͹e:,̫>!_Ľ`̒ƌ|NbR,rťئjHH7˗VY%,˫ַ3AT4Ľ`גTZR2,ˠ6 i!h RRՍq$ρ"aפz$^(pRƯT%a7ˀfp`_1]$ǠΦ/phmo8Fd.mrĸFH3orB$נˆfQ,0,̫Ҷ}-L\`TзA Ļwv˭Tk(~KȾbFҴgnJd,̫Zw,˫/' +r,ohWvタ,ov䨽Im? =H,tˡhXp}|གྷmՖ͜kuwĽ`ג<Ļlf˭"RGmo6jbD,̫HS9݉pMͦѲ= 3sOؐq̆2= 3sO່h2= 3sOPhoβ= 3sOTCנ^ȥTC̫^ȥ:RF̠נxmw7[ZоhF\? ,Q̇#L!dH= 3sOH׏ˠǰM 7Wʪϲ= 3sO 7Wʪ= 3sOȽUh˨2= 3sO 7Wʡ2= 3sOB5MǰMHWדMǰMD5ׇMǰM»$MǰM 7WҴæǰ 7WҴæǰN 7Wȴáǰr Vkצ״8cN3,̠D,kkKmq"3.X2TMzQh%?Ļwv˲ͱ6k荚»7ra(Y,̫ =b,̠Ȓ#F1ĻwoרaCJfҎ7$ĻwvײI{LkB)cˈEHWĻlmײJ')SѼOwӳgT8о-Mˠ VS;^SнMVנ̊ġȻw7Wkġ;7Wk ġ;$HôҴ VS;^RQOdĪĻlmײD`оFt̆ҹ"`iԢ3AbZ$,˫Ϣk2O@,˫ɉgW8Ľ`̒@xt&ĻlfײV[5o-XuOF{e-$UOդRtwpQ׶X>»7rad@ 7Wަ^B 7WȡYf| ѼOw*sBPOv׮}bk oĻwv˨wCfw2q˒қm]\dw}ȻWHoרDaG^Nо2MˠAb+Ļwo׭ dz ĻwoײcH%&Ļlf׭Ңp[ ཈hvdI9 FlI8K+, eFiATh1t%pOcK¾'t˓ȭcqSȾlJ̏U;VĻlf׭8`'TLغ.vmHv<=,̠o;XĽl̒bW$C»7ra,$ꔉ0F̡Ż@mA`Tз-cĻwv˨Ʌ٤YĽ`̈=@joEEr̰ng#Eomį􋋡 ?,G[uGU,@[iizHm©Lhj+K-糗{&XlvHm糗{K.ReܫguֺĻwv׭Pn@}Z+KJBǙķw(7qAhaO.bv+_ȷwooI*\Ļwo˭V΋,pVčǫͱz~,˫=H㰻hJ̲Ƃ)u󀔾IRr˨ͦ)uĻlfײҥXa7ˀf-h&HdF҄<_dOQĻwo˲Ͳ{eBFJI,̠C8:@n,M.Ļwo׭/9m{P?7fhűV+k8ķl&o,̫ҴkĻwo˭ƥ;ྃt gQ!̖~̏Rr˷DLcȾ+vҋpx6Ļwv˭ͰeDU,˫րffbSʼ7Wk9`WLmvmU#*CĻwoײ[%Ⱦ+«h_!f6.hoU3PuOԴEAKD,˫{pJ HP"Tp}A ,̫Ҩ20̰Ľ+h̲[@,˫udVqrȆH;50D ySvզ 3\ja.RÊ3\jྖmˬUJ&d'mvȡٯK+M<Ļlf˲w-HJȻWHǫgͨy2Ļwo˨?9^i4IRq+;S(Ľl̈˖I!AAȾbЋ2P~/̼+Kl9WH,m˨5>^hwRFܽjĽ`̭/VDȽpr:z#!?غKhoQ^%fΠҜUĻlf˲޴X+Ļwv׭56$q ȽBfMfD+Ļwvרϧ5S_ 7Wֶȍv^VRT^c@¾ Ғx6`Hjh,JpE\w ķlG%y},̠1>i)vоJfǁIU6jbĻwo׭5[Uk+vhֶaGЂ `f荪 }]wwhƦϏWv%{,̠.ԘKVmն;X>jĽw˲ĥgV»7raқ;6:Ļwo˭Wlj{hh@"KqMdjvϿ)6`rMD-`ah=XXm"/ؼlFHvTr8:wv`S,˫־ hwRF`f-,˫>aX%AȾpTTdL+vhͲľUfjϨOFe˲Ϩ4ELĽl̖J# 4y_jme³ҧP<Q½)tϕUUUUUU= 7Wj 7WUUUUUUaнRCr֬AH\%3EOp1vj,RoƲelW2*wȨ 3o>oAȒ]JjddHl̲Φ*FL+Kl̨ΦUB9𺉘KĽw˭U9 ̼7Wl׶a-z54s`fEf"̳RO¹VScvĒʶ `"i$HϯYd,RoҲcqaY&#$Hrd{ 7Wͽ^QOl˲+/Y\RBwoͫo;(WTӤʭ rϤPcoÒ6RuyCn$htԴҶRuyCԺIRo6RuyC7LĥǶRuyCn 7WʡŶRuyC 7WȯI[HB lr֨LD,0fΊΩg0JgN)Ƚ,mϢ-`M( 7WѾ29!B,roƲҴ9H+dPhv46OQ=vwvƦDwHSoĭ|q-LnА2/A{106Ǯه5'ѮڊZ%̼7Wlרtܽ Ӌ̼7Wlܽ Ӌ䬺7Wlܽ Ӌ,7Wltܽ Ӌu$HϴƯ#B ,tf 7WŲʥџh~6]¾7rĎiAEo7OiAid;rԼ5r $sl̄taojOo $s)(bOˋKwoͲL@h vk?Ⱦ,mRBhO|LJi-OȲԨ=U1HAQOl˨ɪC &L,RoƲ͵QƥྖVf̢pP>]$"ϴkyf ,RoҲyR'2,RoҲ5>,roƲ4VOאg ps¾ҭyS*StHaЮȫ©C܈u֨^$KVm(nMΤjꍠpφl 7K?ہ`mTH.K.[ 50ƥ͛\e" 50ŲUUUUUU<snappea-3.0d3/SnapPeaPython/CuspedCensusData/terse6n.bin0100444000175000017500000001106607011566574021311 0ustar babbab7Ww+KZ7W6wE7h,L7W6E,f,r7W6Z7W6w+KE,fl7WwOV7Wl,L7K7KV7WlU7Kk +Kl7WwOU,Kk,KV7Lk,KU7KkV+KwV7LkU,KkV+KwU,KwU,Kw/hh WuD7Wwqk7Ww*7Ww7Ww/hmhBff"OhHQOwOQOwQOwPp&7v&7v&7vQOwQOwQOwGirr,r,r,r,UO&rm*7Ww&o%,m%,m%,mE,fE,fE,fdHhh%,n wRlk7p7Ww*7WwSkHT5rrOwOw.Ru%7e&fhR,-o7oWbOw.QOw7Ww7Ww%,cOw.Rh.Rh7m,uHhR,-oRKK`SfVfE7hdH`dH`7v&7v,rh,v4UsOE7hE7h,m,m%,m.Ru,go7Wo7Wc,r,rPauE,fHlHHE7hX"ii+Ke,f,f.Lm.mgIRg%,m,m,mSk.ms,f,fQOwQOwa@T@TwwtDd7WtRRw67Wt&kwE,fSkOl+KHwVm'Hd0FIRkKK7W`7W`pTd7r7rOrFSk+K 'HdtT`+bh%i4UnOHb%r7Ww7Wl*sg IRsh1UhhwR7rViVqqpT`fibUnU7rk7rrhmIsL!A`O`Mub*snUffpPfflmddOo/Hfwv,SoAfftMM.OvrWOKidOL#CcO7W7Ww7Wl/rl(rkmhm!dlfOf Oj7rp7rk7r7r7r7rwoRtO0FrOf7rU-wl/rlKKo`KLowWwj@`&L@LKLl@`4Tt4Tt,snappea-3.0d3/SnapPeaPython/SnapPeaGUI.py0100644000175000017500000013577207063251433016306 0ustar babbab# SnapPeaGUI.py # # A graphical user interface for SnapPea, using the Python wrapper # for the SnapPea kernel found in SnapPea.py and SnapPeaC.c. # WARNING #1: This GUI uses Python MegaWidgets. # MegaWidgets are great -- but leak memory, in the sense that # a Toplevel containing a MegaWidget is never freed. # [The guy maintaining PMW plans to fix this.] # WARNING #2: Given that PMW leaks memory already, I've # succumbed to the temptation to write memory-leaking code # myself. A SnapPeaGUITriangulation keeps pointers to its # constituent SnapPeaGUITriangulationDataPanes, and the # SnapPeaGUITriangulationDataPanes keep pointers to the # SnapPeaGUITriangulation, so neither will ever be freed. # It's embarassing to intentionally write code like this, # but for a rapid prototyping tool I guess it's OK. # Serious work should take place in the command line interpreter. # [Perhaps I could eventually fix this by manually deleting # the references. Ugh. But until PMW is fixed, there's no point, # because I can't verify that everything gets deleted OK.] # [See Nathan's comments on this.] from Tkinter import * import Pmw from SnapPea import * # A simple dialog to request a file name. class SaveAsDialog(Toplevel): def __init__(self, name = 'untitled'): Toplevel.__init__(self) self.file_name = '' # by convention the empty name '' means "don't save" self.title('Save As') self.entry = Entry(self, relief=SUNKEN) self.entry.insert(0, name) self.entry.selection_range(0, END) self.entry.pack(fill='x', expand=1) self.entry.bind('', self.dismiss) self.grab_set() self.entry.focus_set() self.wait_window() def dismiss(self, event): self.file_name = self.entry.get() self.destroy() # The various SnapPeaGUITriangulationPanes display # information about a SnapPeaGUITriangulation. # SnapPeaGUITriangulationDataPane is their common base class. class SnapPeaGUITriangulationDataPane(Pmw.Group): def __init__(self, owner, label): Pmw.Group.__init__( self, owner.options_frame.interior(), tag_pyclass=Checkbutton, tag_text=label) self.component('tag').select() self.component('tag').configure(command = self.DeleteSelf, selectcolor = '#C00000') self.owner = owner def DeleteSelf(self): self.owner.DeleteDataPane(self) class SnapPeaGUITriangulationDehnFillingPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'Dehn filling coefficients') theNumCusps = owner.triangulation.get_num_cusps() for i in range(theNumCusps): theFillButton = Checkbutton(self.interior()) theFillButton.grid(row=i, column=0) theFillButton.select() theFillButton['command'] = lambda o=owner, ii=i: o.FillCusp(ii) theFillButton['selectcolor'] = '#C00000' m = Entry(self.interior(), textvariable=owner.coef[i][0], width=16) m.grid(row=i, column=1) m.bind('', owner.Recompute) if owner.triangulation.get_cusp_is_orientable(i): l = Entry(self.interior(), textvariable=owner.coef[i][1], width=16) l.grid(row=i, column=2) l.bind('', owner.Recompute) Button( self.interior(), text='Recompute', font='Helvetica 10', command=owner.Recompute).grid(row=theNumCusps, column=1, pady=2) Button( self.interior(), text='Reset', font='Helvetica 10', command=owner.Reset).grid(row=theNumCusps, column=2, pady=2) def Update(self): for i in range(self.owner.triangulation.get_num_cusps()): if self.owner.triangulation.get_cusp_is_complete(i): self.owner.coef[i][0].set('') self.owner.coef[i][1].set('') else: self.owner.coef[i][0].set(self.owner.triangulation.get_cusp_m(i)) self.owner.coef[i][1].set(self.owner.triangulation.get_cusp_l(i)) class SnapPeaGUITriangulationVolumePane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'volume') self.volume = DoubleVar() Entry( self.interior(), textvariable=self.volume, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.Update() def Update(self): self.volume.set(self.owner.triangulation.volume()) class SnapPeaGUITriangulationHomologyPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'homology') self.homology = StringVar() Entry( self.interior(), textvariable=self.homology, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.Update() def Update(self): self.homology.set(self.owner.triangulation.homology()) class SnapPeaGUITriangulationOrientabilityPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'orientability') self.orientability = StringVar() Entry( self.interior(), textvariable=self.orientability, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.Update() def Update(self): if self.owner.triangulation.get_triangulation_is_orientable(): self.orientability.set('oriented') else: self.orientability.set('nonorientable') class SnapPeaGUITriangulationSolutionTypePane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'solution type') self.solution_type = StringVar() Entry( self.interior(), textvariable=self.solution_type, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.Update() def Update(self): self.solution_type.set(self.owner.triangulation.get_solution_type()) class SnapPeaGUITriangulationDrillingPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'drilling') self.box = Pmw.ScrolledListBox( self.interior(), listbox_height = 6, dblclickcommand = self.DrillCurve) self.box.pack(fill='x', expand=1, pady=2) self.box.component('listbox')['font'] = 'Courier 12' self.max_segments = IntVar() self.max_segments.set(6) self.counter = Pmw.Counter( self.interior(), labelpos = 'w', label_text = 'max. segments', entry_width = 2, entryfield_value = 6, entryfield_validate = {'validator':'integer', 'min':1, 'max':12}) self.counter.pack() self.counter.component('label')['font'] = 'Helvetica 10' self.counter.component('entryfield').component('entry')['font'] = 'Helvetica 10' self.counter.component('entryfield').component('entry')['textvariable'] = self.max_segments self.counter.component('entryfield').configure(modifiedcommand = self.Update) self.Update() def Update(self): # The max_segments could temporarily be empty, # e.g. after the user has erased an old value, # but before he/she has entered a new one. if self.counter.component('entryfield').valid() != 1: self.box.setlist('') return theNumericalData = self.owner.triangulation.get_drillable_curves(self.max_segments.get()) theTextData = [] for theCurve in theNumericalData: if theCurve[0]: # orientation revsersing theLine = '%7.5lf (%7.5lf )'%( theCurve[1],theCurve[3]) if (theCurve[2] != 0.0 or theCurve[4] != 0.0): print theCurve[2], theCurve[4] raise RuntimeError else: # orientation preserving theLine = '%7.5lf %8.5lf (%7.5lf %8.5lf)'%( theCurve[1],theCurve[2],theCurve[3],theCurve[4]) theTextData.append(theLine) self.box.setlist(theTextData) def DrillCurve(self): theSelection = self.box.curselection() # Due to a bug in one version of Python, this list # could contain strings instead of integers. # Here's the workaround from the Tkinter Intro book. try: theSelection = map(int, theSelection) except ValueError: pass if len(theSelection) > 0: self.owner.DrillCurve(theSelection[0], self.max_segments.get()) class SnapPeaGUITriangulationSplittingsPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'splitting surfaces') self.box = Pmw.ScrolledListBox( self.interior(), listbox_height = 3, dblclickcommand = self.Split) self.box.pack(fill='x', expand=1, pady=2) # self.box.component('listbox')['font'] = 'Courier 12' self.Update() def Update(self): self.box.setlist(self.owner.triangulation.get_normal_surfaces()) def Split(self): theSelection = self.box.curselection() # Due to a bug in one version of Python, this list # could contain strings instead of integers. # Here's the workaround from the Tkinter Intro book. try: theSelection = map(int, theSelection) except ValueError: pass if len(theSelection) > 0: thePieces = self.owner.triangulation.split_on_normal_surface(theSelection[0]) for thePiece in thePieces: SnapPeaGUITriangulation(thePiece) class SnapPeaGUITriangulationFundamentalGroupPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'fundamental group') # Set up the main display for the group presentation. self.generators = Text(self.interior(), state=DISABLED, relief=GROOVE, width=1, height=1) self.generators.pack(fill='x', expand=1, padx=2, pady=2) self.relations = Pmw.ScrolledText(self.interior(), usehullsize=1, hull_width=16, hull_height=80, hscrollmode='none', vscrollmode='dynamic') self.relations.component('text').configure(state=DISABLED, relief=GROOVE) self.relations.pack(fill='x', expand=1, padx=2, pady=2) self.option_simplify = BooleanVar() self.option_simplify.set(1) self.option_fillings_affect_generators = BooleanVar() self.option_fillings_affect_generators.set(1) self.option_minimize_num_generators = BooleanVar() self.option_minimize_num_generators.set(0) # Set up the options sub-pane. self.show_options = BooleanVar() self.show_options.set(0) self.options = Pmw.Group( self.interior(), tag_pyclass=Checkbutton, tag_text='options') self.options.component('tag').configure( command = self.ToggleOptions, variable = self.show_options, selectcolor = '#F08000') self.options.pack(fill='x', padx=6, pady=2) # Set up the representation sub-pane. self.show_representation = BooleanVar() self.show_representation.set(0) self.word = StringVar() self.word.set('a') self.matrix_text = None self.use_O31 = BooleanVar() self.use_O31.set(1) self.representation = Pmw.Group(self.interior(), tag_pyclass=Checkbutton, tag_text='representation') self.representation.component('tag').configure( command = self.ToggleRepresentation, variable = self.show_representation, selectcolor = '#F08000') self.representation.pack(fill='x', padx=6, pady=2) # Set up the peripheral curves sub-pane. self.show_peripheral_curves = BooleanVar() self.show_peripheral_curves.set(0) self.peripheral_curves = Pmw.Group(self.interior(), tag_pyclass=Checkbutton, tag_text='peripheral curves') self.peripheral_curves.component('tag').configure( command = self.TogglePeripheralCurves, variable = self.show_peripheral_curves, selectcolor = '#F08000') self.peripheral_curves.pack(fill='x', padx=6, pady=2) # Update the display. self.Update() def destroy(self): # Given the memory management problems currently plagueing # Python Mega Widgets, let's be safe and explicitly cut # loose any structure that keeps a pointer to kernel memory, # just in case this pane isn't properly released. self.presentation = None # Call the default destroy(). SnapPeaGUITriangulationDataPane.destroy(self) def Update(self): self.UpdatePresentation() # The options display doesn't need explicit updates # because it changes only under direct user control. if self.show_representation.get() == 1: self.UpdateRepresentation() if self.show_peripheral_curves.get() == 1: self.UpdatePeripheralCurves() def UpdatePresentation(self): self.presentation = self.owner.triangulation.fundamental_group( self.option_simplify.get(), self.option_fillings_affect_generators.get(), self.option_minimize_num_generators.get()) self.generators.configure(state=NORMAL) self.generators.delete(1.0, END) self.generators.insert(END, self.presentation.generators_string()) self.generators.configure(state=DISABLED) self.relations.component('text').configure(state=NORMAL) self.relations.delete(1.0, END) self.relations.insert(END, self.presentation.relations_string()) self.relations.component('text').configure(state=DISABLED) def ToggleOptions(self): if self.show_options.get() == 1: self.CreateOptions() else: self.options_frame.pack_forget() self.options_frame.destroy() self.options_frame = None self.options.interior().configure(width = 1, height = 1) def CreateOptions(self): self.options_frame = Frame(self.options.interior()) self.options_frame.pack() thePair1 = Frame(self.options_frame) thePair1.pack(fill='x') Radiobutton(thePair1, text = 'simplified presentation', variable = self.option_simplify, value = 1, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) Radiobutton(thePair1, text = 'geometric presentation', variable = self.option_simplify, value = 0, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) thePair2 = Frame(self.options_frame) thePair2.pack(fill='x') Radiobutton(thePair2, text = 'generators may depend on fillings', variable = self.option_fillings_affect_generators, value = 1, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) Radiobutton(thePair2, text = 'same generators for all fillings', variable = self.option_fillings_affect_generators, value = 0, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) thePair3 = Frame(self.options_frame) thePair3.pack(fill='x') Radiobutton(thePair3, text = 'minimize number of generators', variable = self.option_minimize_num_generators, value = 1, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) Radiobutton(thePair3, text = 'minimize total length of relations', variable = self.option_minimize_num_generators, value = 0, selectcolor = 'LightBlue', command = self.Update).grid(sticky=W) def ToggleRepresentation(self): if self.show_representation.get() == 1: self.CreateRepresentation() else: self.matrix_text = None self.representation_frame.pack_forget() self.representation_frame.destroy() self.representation_frame = None self.representation.interior().configure(width = 1, height = 1) def CreateRepresentation(self): self.representation_frame = Frame(self.representation.interior()) self.representation_frame.pack(fill='x', expand=1) self.representation_frame.columnconfigure(0, weight=1) self.representation_frame.columnconfigure(1, weight=1) Pmw.EntryField( self.representation_frame, entry_textvariable = self.word, validate = {'validator':'alphabetic'}, modifiedcommand=self.UpdateRepresentation).grid( row=0, column=0, columnspan=2, sticky=W+E, padx=6, pady=6) self.matrix_text = Text(self.representation_frame, width=52, height=4, font='Courier 12', state=DISABLED, relief=GROOVE) self.matrix_text.grid(row=1, column=0, columnspan=2, sticky=W+E, padx=16, pady=6) Radiobutton(self.representation_frame, text = 'O(1,3)', variable = self.use_O31, value = 1, selectcolor = 'LightBlue', command = self.UpdateRepresentation).grid(row=2, column=0) Radiobutton(self.representation_frame, text = 'SL(2,C)', variable = self.use_O31, value = 0, selectcolor = 'LightBlue', command = self.UpdateRepresentation).grid(row=2, column=1) self.UpdateRepresentation() def UpdateRepresentation(self): theMatrices = self.presentation.representation(self.word.get()) theText = '' if (self.use_O31.get() == 1): for i in range(4): for j in range(4): theText = theText + '%12.8lf '%theMatrices[0][i][j] theText = theText + '\n' else: for i in range(2): for j in range(2): theText = theText + '%12.8lf %12.8lf i '%( theMatrices[1][1][i][j][0], theMatrices[1][1][i][j][1]) theText = theText + '\n' if (theMatrices[1][0] == 1): theText = theText + '*** orientation reversing ***\n(function of z-bar, not z)' self.matrix_text.config(state=NORMAL) self.matrix_text.delete(1.0, END) self.matrix_text.insert(END, theText) self.matrix_text.config(state=DISABLED) def TogglePeripheralCurves(self): if self.show_peripheral_curves.get() == 1: self.CreatePeripheralCurves() else: self.peripheral_curve_entries = None self.peripheral_curves_frame.pack_forget() self.peripheral_curves_frame.destroy() self.peripheral_curves_frame = None self.peripheral_curves.interior().configure(width = 1, height = 1) def CreatePeripheralCurves(self): self.peripheral_curves_frame = Frame(self.peripheral_curves.interior()) self.peripheral_curves_frame.pack(fill='x', expand=1) self.peripheral_curves_frame.columnconfigure(0, weight=1) self.peripheral_curves_frame.columnconfigure(1, weight=1) self.peripheral_curve_entries = [] for i in range(self.owner.triangulation.get_num_cusps()): theMeridian = Entry(self.peripheral_curves_frame, width=1, relief=GROOVE) theMeridian.grid (row = i, column = 0, sticky=W+E) theLongitude = Entry(self.peripheral_curves_frame, width=1, relief=GROOVE) theLongitude.grid(row = i, column = 1, sticky=W+E) self.peripheral_curve_entries.append([theMeridian, theLongitude]) self.UpdatePeripheralCurves() def UpdatePeripheralCurves(self): thePeripheralCurves = self.presentation.peripheral_curves() for i in range(self.owner.triangulation.get_num_cusps()): for j in range(2): # meridian, longitude self.peripheral_curve_entries[i][j].config(state=NORMAL) self.peripheral_curve_entries[i][j].delete(0, END) self.peripheral_curve_entries[i][j].insert(END, thePeripheralCurves[i][j]) self.peripheral_curve_entries[i][j].config(state=DISABLED) class SnapPeaGUITriangulationCoreGeodesicsPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'core geodesics') self.text = Pmw.ScrolledText(self.interior(), usehullsize=1, hull_width=16, hull_height=80, hscrollmode='none', vscrollmode='dynamic') self.text.component('text').configure( state=DISABLED, relief=GROOVE, font='Courier 12') self.text.pack(fill='x', expand=1, padx=2, pady=2) self.option_menu = Pmw.OptionMenu( self.interior(), items=[ 'complex length', 'holonomy', 'trace in PSL(2,C)', 'trace squared in PSL(2,C)', 'eigenvalue in PSL(2,C)'], initialitem='complex length', command=self.UpdateFormat) self.option_menu.component('menu' ).configure(font='Helvetica 10') self.option_menu.component('menubutton').configure(font='Helvetica 10') self.option_menu.pack() self.Update() def Update(self): self.UpdateFormat(self.option_menu.get()) def UpdateFormat(self, format): self.text.component('text').configure(state=NORMAL) self.text.delete(1.0, END) for i in range(self.owner.triangulation.get_num_cusps()): self.text.insert(END, '%2d '%i) theCore = self.owner.triangulation.core_geodesic(i) # singular index is zero => the Cusp is unfilled # or the Dehn filling coefficients are not integers if theCore['singularity index'] > 0: if theCore['singularity index'] > 1: self.text.insert(END, '%2d '%theCore['singularity index']) else: self.text.insert(END, ' ') if format == 'complex length': theValue = theCore['complex length'] if format == 'holonomy': theValue = theCore['holonomy'] if format == 'trace in PSL(2,C)': theValue = theCore['trace'] if format == 'trace squared in PSL(2,C)': theValue = theCore['trace squared'] if format == 'eigenvalue in PSL(2,C)': theValue = theCore['eigenvalue'] thePrecision = theCore['precision'] theSpacing = ' ' if self.owner.triangulation.get_cusp_is_orientable(i): self.text.insert(END, '%*.*f%s %*.*f%s i' %( thePrecision + 3, thePrecision, theValue.real, theSpacing[thePrecision:], thePrecision + 3, thePrecision, theValue.imag, theSpacing[thePrecision:])) else: self.text.insert(END, '%*.*f%s' %( thePrecision + 3, thePrecision, theValue.real, theSpacing[thePrecision:])) if theValue.imag != 0.0: raise RuntimeError, 'nonorientable geodesic has nonzero torison' self.text.insert(END, '\n') self.text.component('text').configure(state=DISABLED) class SnapPeaGUITriangulationChangePeripheralCurvesPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'change peripheral curves') Button( self.interior(), text='shortest curves become meridians', command=self.owner.ShortestCurvesBecomeMeridians).pack(padx=2, pady=2) Button( self.interior(), text='current Dehn fillings become meridians', command=self.owner.CurrentFillingsBecomeMeridians).pack(padx=2, pady=2) def Update(self): pass class SnapPeaGUITriangulationRetriangulationPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'retriangulation') Label( self.interior(), text='%d tetrahedra'%owner.triangulation.get_num_tetrahedra() ).pack(padx=2, pady=2) Button( self.interior(), text='simplify', command=self.owner.Simplify).pack(padx=2, pady=2) Button( self.interior(), text='randomize', command=self.owner.Randomize).pack(padx=2, pady=2) Button( self.interior(), text='reverse orientation', command=self.owner.Reflect).pack(padx=2, pady=2) Button( self.interior(), text='canonize', command=self.owner.Canonize).pack(padx=2, pady=2) def Update(self): pass class SnapPeaGUITriangulationSymmetryGroupPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'symmetry group') Label( self.interior(), text='symmetry group of manifold', font='Helvetica 12').pack() self.manifold_group = StringVar() Entry( self.interior(), textvariable=self.manifold_group, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.manifold_chirality = StringVar() Entry( self.interior(), textvariable=self.manifold_chirality, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) Label( self.interior(), text='symmetry group of associated link', font='Helvetica 12').pack() self.link_group = StringVar() Entry( self.interior(), textvariable=self.link_group, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.link_chirality = StringVar() Entry( self.interior(), textvariable=self.link_chirality, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.knot_invertibility = StringVar() Entry( self.interior(), textvariable=self.knot_invertibility, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) # Set up the details sub-pane. self.show_details = BooleanVar() self.show_details.set(0) self.details = Pmw.Group( self.interior(), tag_pyclass=Checkbutton, tag_text='group structure') self.details.component('tag').configure( command = self.ToggleDetails, variable = self.show_details, selectcolor = '#F08000') self.details.pack(fill='x', padx=6, pady=2) # Update. self.Update() def destroy(self): # Given the memory management problems currently plagueing # Python Mega Widgets, let's be safe and explicitly cut # loose any structure that keeps a pointer to kernel memory, # just in case this pane isn't properly released. self.symmetry_group = None # Call the default destroy(). SnapPeaGUITriangulationDataPane.destroy(self) def Update(self): self.symmetry_group = self.owner.triangulation.symmetry_group() self.UpdateMainDisplay() if self.show_details.get() == 1: self.UpdateDetails() def UpdateMainDisplay(self): if self.symmetry_group['manifold'] != None: self.manifold_group.set(self.symmetry_group['manifold'].__repr__()) if self.owner.triangulation.get_triangulation_is_orientable() == 1: if self.symmetry_group['manifold'].is_amphicheiral() == 1: self.manifold_chirality.set('amphicheiral') else: self.manifold_chirality.set('chiral') else: self.manifold_chirality.set('nonorientable') else: self.manifold_group.set('-') self.manifold_chirality.set('') if self.symmetry_group['link'] != None: self.link_group.set(self.symmetry_group['link'].__repr__()) if self.owner.triangulation.get_triangulation_is_orientable() == 1: if self.symmetry_group['link'].is_amphicheiral() == 1: self.link_chirality.set('amphicheiral') else: self.link_chirality.set('chiral') if self.owner.triangulation.get_num_cusps() == 1: if self.symmetry_group['link'].is_invertible_knot() == 1: self.knot_invertibility.set('invertible knot') else: self.knot_invertibility.set('noninvertible knot') else: self.knot_invertibility.set('') else: self.link_chirality.set('nonorientable') self.knot_invertibility.set('') else: self.link_group.set('-') self.link_chirality.set('') self.knot_invertibility.set('') def ToggleDetails(self): if self.show_details.get() == 1: self.CreateDetails() else: self.details_frame.pack_forget() self.details_frame.destroy() self.details_frame = None self.details.interior().configure(width = 1, height = 1) def CreateDetails(self): self.details_frame = Frame(self.details.interior()) self.details_frame.pack(fill='x', expand=1) Label( self.details_frame, text='commutator', font='Helvetica 12').pack() self.commutator_text_manifold = StringVar() Entry( self.details_frame, textvariable=self.commutator_text_manifold, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.commutator_text_link = StringVar() Entry( self.details_frame, textvariable=self.commutator_text_link, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) Label( self.details_frame, text='abelianization', font='Helvetica 12').pack() self.abelianization_text_manifold = StringVar() Entry( self.details_frame, textvariable=self.abelianization_text_manifold, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.abelianization_text_link = StringVar() Entry( self.details_frame, textvariable=self.abelianization_text_link, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) Label( self.details_frame, text='center', font='Helvetica 12').pack() self.center_text_manifold = StringVar() Entry( self.details_frame, textvariable=self.center_text_manifold, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.center_text_link = StringVar() Entry( self.details_frame, textvariable=self.center_text_link, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) Label( self.details_frame, text='presentation', font='Helvetica 12').pack() self.presentation_text_manifold = StringVar() Entry( self.details_frame, textvariable=self.presentation_text_manifold, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.presentation_text_link = StringVar() Entry( self.details_frame, textvariable=self.presentation_text_link, state=DISABLED, justify=CENTER, relief=FLAT).pack(pady=2, fill='x', expand=1) self.UpdateDetails() def UpdateDetails(self): if (self.symmetry_group['manifold'] != None and self.symmetry_group['manifold'].is_full_group): self.commutator_text_manifold.set( self.symmetry_group['manifold'].commutator_subgroup().__repr__()) self.abelianization_text_manifold.set( self.symmetry_group['manifold'].abelianization().__repr__()) self.center_text_manifold.set( self.symmetry_group['manifold'].center().__repr__()) self.presentation_text_manifold.set( self.symmetry_group['manifold'].presentation_text()) else: self.commutator_text_manifold.set('-') self.abelianization_text_manifold.set('-') self.center_text_manifold.set('-') self.presentation_text_manifold.set('-') if (self.symmetry_group['link'] != None and self.symmetry_group['link'].is_full_group): self.commutator_text_link.set( self.symmetry_group['link'].commutator_subgroup().__repr__()) self.abelianization_text_link.set( self.symmetry_group['link'].abelianization().__repr__()) self.center_text_link.set( self.symmetry_group['link'].center().__repr__()) self.presentation_text_link.set( self.symmetry_group['link'].presentation_text()) else: self.commutator_text_link.set('-') self.abelianization_text_link.set('-') self.center_text_link.set('-') self.presentation_text_link.set('-') class SnapPeaGUITriangulationTetShapesPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'tetrahedron shapes') self.text = Pmw.ScrolledText(self.interior(), usehullsize=1, hull_width=360, hull_height=80, hscrollmode='none', vscrollmode='dynamic') self.text.component('text').configure( state=DISABLED, relief=GROOVE, font='Courier 12') self.text.grid(row=0, column=0, columnspan=2, sticky=W+N+E+S, padx=2, pady=2) self.format_menu = Pmw.OptionMenu( self.interior(), items=[ 'rectangular', 'logarithmic'], initialitem='rectangular', command=self.UpdateFromMenu) self.format_menu.component('menu' ).configure(font='Helvetica 10') self.format_menu.component('menubutton').configure(font='Helvetica 10') self.format_menu.grid(row=1, column=0) self.coord_menu = Pmw.OptionMenu( self.interior(), items=[ 'nicest edge parameters', 'fixed edge parameters'], initialitem='nicest edge parameters', command=self.UpdateFromMenu) self.coord_menu.component('menu' ).configure(font='Helvetica 10') self.coord_menu.component('menubutton').configure(font='Helvetica 10') self.coord_menu.grid(row=1, column=1) self.Update() def UpdateFromMenu(self, menu_selection): self.Update() def Update(self): self.text.component('text').configure(state=NORMAL) self.text.delete(1.0, END) theTetShapes = self.owner.triangulation.tet_shapes( self.coord_menu.get() == 'fixed edge parameters') theFormat = self.format_menu.get() for i in range(self.owner.triangulation.get_num_tetrahedra()): if theTetShapes[i]['is geometric'] == 1: self.text.insert(END, ' ') else: self.text.insert(END, ' ! ') self.text.insert(END, '%2d '%i) if theFormat == 'rectangular': self.text.insert(END, '%*.*f%s %*.*f'%( 6 + theTetShapes[i]['precision rect real'], theTetShapes[i]['precision rect real'], theTetShapes[i]['shape rect'].real, ' '[:(16 - theTetShapes[i]['precision rect real'])], 6 + theTetShapes[i]['precision rect imag'], theTetShapes[i]['precision rect imag'], theTetShapes[i]['shape rect'].imag)) elif theFormat == 'logarithmic': self.text.insert(END, '%*.*f%s %*.*f'%( 6 + theTetShapes[i]['precision log real'], theTetShapes[i]['precision log real'], theTetShapes[i]['shape log'].real, ' '[:(16 - theTetShapes[i]['precision log real'])], 6 + theTetShapes[i]['precision log imag'], theTetShapes[i]['precision log imag'], theTetShapes[i]['shape log'].imag)) else: raise RuntimeError, 'bad tet shape format' self.text.insert(END, '\n') self.text.component('text').configure(state=DISABLED) class SnapPeaGUITriangulationDirichletPane(SnapPeaGUITriangulationDataPane): def __init__(self, owner): SnapPeaGUITriangulationDataPane.__init__(self, owner, 'Dirichlet domain (.off)') self.text = Pmw.ScrolledText(self.interior(), usehullsize=1, hull_width=16, hull_height=80, hscrollmode='none', vscrollmode='dynamic') self.text.component('text').configure(state=DISABLED, relief=GROOVE, font='Courier 12') self.text.pack(fill='x', expand=1, padx=2, pady=2) self.basepoint_menu = Pmw.OptionMenu( self.interior(), items=[ 'basepoint on edge', 'basepoint at centroid'], initialitem='basepoint at centroid', command=self.Update) self.basepoint_menu.component('menu' ).configure(font='Helvetica 10') self.basepoint_menu.component('menubutton').configure(font='Helvetica 10') self.basepoint_menu.pack() self.maximize_inj_rad = IntVar() self.maximize_inj_rad.set(0) Checkbutton( self.interior(), text='maximize injectivity radius', var=self.maximize_inj_rad, command=self.Update, selectcolor='LightBlue').pack() # Set up the face_pairings sub-pane. self.show_face_pairings = BooleanVar() self.show_face_pairings.set(0) self.face_pairings = Pmw.Group( self.interior(), tag_pyclass=Checkbutton, tag_text='face pairings') self.face_pairings.component('tag').configure( command = self.ToggleFacePairings, variable = self.show_face_pairings, selectcolor = '#F08000') self.face_pairings.pack(fill='x', padx=6, pady=2) self.face_pairings_text = None # Set up the basepoint displacement sub-pane. self.dx = DoubleVar() self.dx.set(0.0) self.dy = DoubleVar() self.dy.set(0.0) self.dz = DoubleVar() self.dz.set(0.0) self.show_displacement = BooleanVar() self.show_displacement.set(0) self.displacement = Pmw.Group( self.interior(), tag_pyclass=Checkbutton, tag_text='basepoint displacement') self.displacement.component('tag').configure( command = self.ToggleDisplacement, variable = self.show_displacement, selectcolor = '#F08000') self.displacement.pack(fill='x', padx=6, pady=2) self.Update() def Update(self, menu_choice=''): theDirichletDomain = self.owner.triangulation.Dirichlet( self.basepoint_menu.get() == 'basepoint at centroid', self.maximize_inj_rad.get(), (self.dx.get(), self.dy.get(), self.dz.get())) self.text.component('text').configure(state=NORMAL) self.text.delete(1.0, END) self.text.insert(END, theDirichletDomain.off()) self.text.component('text').configure(state=DISABLED) if self.face_pairings_text != None: self.face_pairings_text.component('text').configure(state=NORMAL) self.face_pairings_text.delete(1.0, END) theFacePairings = theDirichletDomain.face_pairings() for i in range(len(theFacePairings)): for j in range(4): for k in range(4): self.face_pairings_text.insert(END, ' %11.6f'%theFacePairings[i][j][k]) self.face_pairings_text.insert(END, '\n') self.face_pairings_text.insert(END, '\n') self.face_pairings_text.component('text').configure(state=DISABLED) # self.displacement requires no update. def ToggleFacePairings(self): if self.show_face_pairings.get() == 1: self.face_pairings_text = Pmw.ScrolledText( self.face_pairings.interior(), usehullsize=1, hull_width=16, hull_height=120, hscrollmode='none', vscrollmode='dynamic') self.face_pairings_text.component('text').configure(state=DISABLED, relief=GROOVE, font='Courier 12') self.face_pairings_text.pack(fill='x', expand=1, padx=2, pady=2) self.face_pairings_text.pack() self.Update() else: self.face_pairings_text.pack_forget() self.face_pairings_text.destroy() self.face_pairings_text = None self.face_pairings.interior().configure(width = 1, height = 1) def ToggleDisplacement(self): if self.show_displacement.get() == 1: self.displacement_frame = Frame(self.displacement.interior()) self.displacement_frame.pack(fill='x', expand=1) theEntryDx = Entry(self.displacement_frame, textvariable=self.dx, width=8) theEntryDx.grid(row=0, column=0, sticky=W+E, padx=6) theEntryDx.bind('', self.Update) theEntryDy = Entry(self.displacement_frame, textvariable=self.dy, width=8) theEntryDy.grid(row=0, column=1, sticky=W+E, padx=6) theEntryDy.bind('', self.Update) theEntryDz = Entry(self.displacement_frame, textvariable=self.dz, width=8) theEntryDz.grid(row=0, column=2, sticky=W+E, padx=6) theEntryDz.bind('', self.Update) else: self.displacement_frame.pack_forget() self.displacement_frame.destroy() self.displacement_frame = None self.displacement.interior().configure(width = 1, height = 1) # SnapPeaGUITriangulation represents a triangulation. class SnapPeaGUITriangulation(Pmw.MegaToplevel): def __init__(self, triangulation): Pmw.MegaToplevel.__init__(self) self.title(triangulation.get_name()) self.triangulation = triangulation self.InitDehnCoefficients() self.menubar = Frame(self.component('hull'), relief=RAISED, bd=2) self.menubar.pack(side=TOP, fill='x') theFileButton = Menubutton(self.menubar, text='File', underline=0) theFileButton.pack(side=LEFT) theFileMenu = Menu(theFileButton) theFileMenu.add_command( label='Save As...', command=self.SaveAs) theFileMenu.add_command( label='Clone', command=self.Clone) theFileMenu.add_command( label='Close', command=self.destroy) theFileButton['menu'] = theFileMenu theViewButton = Menubutton(self.menubar, text='View', underline=0) theViewButton.pack(side=LEFT) theViewMenu = Menu(theViewButton) theViewMenu.add_command( label='Dehn filling coefficients', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationDehnFillingPane)) theViewMenu.add_command( label='volume', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationVolumePane)) theViewMenu.add_command( label='homology', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationHomologyPane)) theViewMenu.add_command( label='orientability', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationOrientabilityPane)) theViewMenu.add_command( label='solution type', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationSolutionTypePane)) theViewMenu.add_command( label='drilling', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationDrillingPane)) theViewMenu.add_command( label='splittings', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationSplittingsPane)) theViewMenu.add_command( label='fundamental group', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationFundamentalGroupPane)) theViewMenu.add_command( label='core geodesics', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationCoreGeodesicsPane)) theViewMenu.add_command( label='change peripheral curves', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationChangePeripheralCurvesPane)) theViewMenu.add_command( label='retriangulation', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationRetriangulationPane)) theViewMenu.add_command( label='symmetry group', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationSymmetryGroupPane)) theViewMenu.add_command( label='tetrahedron shapes', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationTetShapesPane)) theViewMenu.add_command( label='Dirichlet domain (.off format)', command=lambda s=self: s.MakeDataPane(SnapPeaGUITriangulationDirichletPane)) theViewButton['menu'] = theViewMenu self.options_frame = Pmw.ScrolledFrame(self.component('hull'), usehullsize=1, hull_width=400, hull_height=450, hscrollmode='none', vscrollmode='dynamic', horizflex='expand', vertflex='fixed') self.options_frame.pack(fill='both', expand=1, padx=6, pady=6) self.displays = [] self.MakeDataPane(SnapPeaGUITriangulationDehnFillingPane) self.MakeDataPane(SnapPeaGUITriangulationSolutionTypePane) self.MakeDataPane(SnapPeaGUITriangulationVolumePane) self.MakeDataPane(SnapPeaGUITriangulationHomologyPane) # temporary __del__ method to investigate weird behavior # of embedded Python MegaWidgets. # def __del__(self): # print 'deleting manifold window' def destroy(self): # Due to a bug in Python Mega Widgets, a Pmw.MegaToplevel # is never freed. (Most likely the problem is circular # reference chains.) So if we are about to be destroyed, # we should at least free our Triangulation. # (After PMW fixes their bug, I'll probably have to free # my own panes manually to get this SnapPeaGUITriangulation # to be freed. At that point it'll no longer be necessary # to manually free the Triangulation.) self.triangulation = None Pmw.MegaToplevel.destroy(self) def InitDehnCoefficients(self): # Set up shadow variables for the Dehn filling coefficients. self.coef = [] for i in range(self.triangulation.get_num_cusps()): self.coef.append([DoubleVar(), DoubleVar()]) self.coef[i][0].set(self.triangulation.get_cusp_m(i)) self.coef[i][1].set(self.triangulation.get_cusp_l(i)) if self.coef[i][0].get() == 0.0 and self.coef[i][1].get() == 0.0: self.coef[i][0].set('') self.coef[i][1].set('') def MakeDataPane(self, DisplayType): theDisplay = DisplayType(self) theDisplay.pack(fill='x', padx=6, pady=6) self.displays.append(theDisplay) def DeleteDataPane(self, pane): self.displays.remove(pane) pane.destroy() def Recompute(self, event=None): for i in range(self.triangulation.get_num_cusps()): # Convert nonnumeric entries (including '') to (0,0). # (I wish I knew how to do this more elegantly.) for j in [0,1]: try: self.coef[i][j].get() except: self.coef[i][j].set(0.0) if ( # self.coef[i][0].get() == '' or self.coef[i][1].get() == '' or (self.coef[i][0].get() == 0.0 and self.coef[i][1].get() == 0.0)): self.triangulation.set_cusp(i) self.coef[i][0].set('') self.coef[i][1].set('') else: self.triangulation.set_cusp(i, self.coef[i][0].get(), self.coef[i][1].get()) self.coef[i][0].set(self.coef[i][0].get()) self.coef[i][1].set(self.coef[i][1].get()) self.Update() def Reset(self, event=None): for i in range(self.triangulation.get_num_cusps()): for j in [0,1]: self.coef[i][j].set('') self.triangulation.remove_Dehn_fillings() self.Update() # Update() handles mild changes to the manifold, # such as new Dehn filling coefficients and a new solution. def Update(self): for theDisplay in self.displays: theDisplay.Update() # Overhaul() handles severe changes to the manifold, # such as changing the combinatorics of the triangulation. def Overhaul(self): # Reinitialize the shadow variables # for the Dehn filling coefficients. self.InitDehnCoefficients() # Trash the old display panes, and # create new ones of the same types. theDisplayClasses = [] for theDisplay in self.displays: theDisplayClasses.append(theDisplay.__class__) theDisplay.destroy() self.displays = [] for theClass in theDisplayClasses: self.MakeDataPane(theClass) def SaveAs(self): theFileName = SaveAsDialog(self.triangulation.get_name()).file_name if (theFileName != ''): self.triangulation.save(theFileName) self.title(theFileName) def Clone(self): theCopy = SnapPeaGUITriangulation(self.triangulation.clone()) theDisplayClasses = [] for theDisplay in self.displays: theDisplayClasses.append(theDisplay.__class__) theCopy.SetDisplayClasses(theDisplayClasses) def SetDisplayClasses(self, new_display_classes): for theDisplay in self.displays: theDisplay.destroy() self.displays = [] for theClass in new_display_classes: self.MakeDataPane(theClass) def FillCusp(self, i): self.Recompute() self.triangulation.fill_cusp(i) self.Overhaul() def DrillCurve(self, i, max_segments): self.triangulation.drill_curve(i, max_segments) self.Overhaul() def ShortestCurvesBecomeMeridians(self): self.triangulation.shortest_curves_become_meridians() self.Update() def CurrentFillingsBecomeMeridians(self): self.triangulation.current_fillings_become_meridians() self.Update() def Simplify(self): self.triangulation.simplify() self.Overhaul() def Randomize(self): self.triangulation.randomize() self.Overhaul() def Reflect(self): self.triangulation.reflect() self.Overhaul() def Canonize(self): self.triangulation.canonize() self.Overhaul() def ManifoldFromFile(event=None): if len(theFileName.get()) > 0: SnapPeaGUITriangulation(Triangulation(theFileName.get())) else: raise ValueError, 'The file name is empty.' def ManifoldFromCensus(event=None): theCensusNumber = [5, 6, 6, 7, 7] theCensusOrientability = [1, 1, 0, 1, 0] SnapPeaGUITriangulation(Triangulation( theCensusNumber[theCuspedCensus.get()], theCensusOrientability[theCuspedCensus.get()], theCensusIndex.get())) if __name__ == '__main__': root = Tk() Pmw.initialise(root, fontScheme='pmw1') root.title('SnapPea 3.0 (experimental version)') theFileGroup = Pmw.Group(root, tag_text='manifold from file') theFileGroup.pack(fill='x', expand=0, padx=6, pady=6) theFileName = StringVar() theFileName.set('') theFileNameEntry = Entry( theFileGroup.interior(), textvariable=theFileName, width=24) theFileNameEntry.pack(fill='x', expand=0, padx=6, pady=6) theFileNameEntry.bind('', ManifoldFromFile) Button( theFileGroup.interior(), text='Open', command=ManifoldFromFile, font='Helvetica 10').pack() theCuspedCensus = IntVar() theCuspedCensus.set(0) theCuspedCensusGroup = Pmw.Group(root, tag_text='manifold from cusped census') theCuspedCensusGroup.pack(fill='x', expand=0, padx=6, pady=6) theCensusName = [ '<= 5 tetrahedra', '6 tetrahedra orientable', '6 tetrahedra nonorientable', '7 tetrahedra orientable', '7 tetrahedra nonorientable' ] for i in range(5): Radiobutton( theCuspedCensusGroup.interior(), text = theCensusName[i], variable = theCuspedCensus, value = i, selectcolor = 'LightBlue' ).grid(sticky=W, row=i, column=0, columnspan=2) theCensusIndex = IntVar() theCensusIndex.set(0) Label(theCuspedCensusGroup.interior(), text='manifold #').grid(row=5, column=0, sticky=E) theCensusIndexEntry = Entry( theCuspedCensusGroup.interior(), width = 4, textvariable = theCensusIndex) theCensusIndexEntry.grid(row=5, column=1, sticky=W, pady=4) theCensusIndexEntry.bind('', ManifoldFromCensus) Button( theCuspedCensusGroup.interior(), text='Open', command=ManifoldFromCensus, font='Helvetica 10').grid(row=6, column=0, columnspan=2) Button(root, text='Quit SnapPea', command=root.quit, font='Helvetica 12').pack() root.mainloop() VerifyMyMallocUsage() snappea-3.0d3/SnapPeaPython/Pmw.py0100644000175000017500000075117006774147100015154 0ustar babbab import PmwColor Color = PmwColor del PmwColor import PmwBlt Blt = PmwBlt del PmwBlt ### Loader functions: _VERSION = '0.8.1' def setversion(version): if version != _VERSION: raise ValueError, 'Dynamic versioning not available' def setalphaversions(*alpha_versions): if alpha_versions != (): raise ValueError, 'Dynamic versioning not available' def version(alpha = 0): if alpha: return () else: return _VERSION def installedversions(alpha = 0): if alpha: return () else: return (_VERSION,) ###################################################################### ### File: PmwBase.py # Pmw megawidget base classes. # This module provides a foundation for building megawidgets. It # contains the MegaArchetype class which manages component widgets and # configuration options. Also provided are the MegaToplevel and # MegaWidget classes, derived from the MegaArchetype class. The # MegaToplevel class contains a Tkinter Toplevel widget to act as the # container of the megawidget. This is used as the base class of all # megawidgets that are contained in their own top level window, such # as a Dialog window. The MegaWidget class contains a Tkinter Frame # to act as the container of the megawidget. This is used as the base # class of all other megawidgets, such as a ComboBox or ButtonBox. # # Megawidgets are built by creating a class that inherits from either # the MegaToplevel or MegaWidget class. import os import string import sys import traceback import types import Tkinter # Constant used to indicate that an option can only be set by a call # to the constructor. INITOPT = [42] _DEFAULT_OPTION_VALUE = [69] _useTkOptionDb = 0 # Symbolic constants for the indexes into an optionInfo list. _OPT_DEFAULT = 0 _OPT_VALUE = 1 _OPT_FUNCTION = 2 # Stacks _busyStack = [] # Stack which tracks nested calls to show/hidebusycursor (called # either directly or from activate()/deactivate()). Each element # is a dictionary containing: # 'newBusyWindows' : List of windows which had busy_hold called # on them during a call to showbusycursor(). # The corresponding call to hidebusycursor() # will call busy_release on these windows. # 'busyFocus' : The blt _Busy window which showbusycursor() # set the focus to. # 'previousFocus' : The focus as it was when showbusycursor() # was called. The corresponding call to # hidebusycursor() will restore this focus if # the focus has not been changed from busyFocus. _grabStack = [] # Stack of grabbed windows. It tracks calls to push/popgrab() # (called either directly or from activate()/deactivate()). The # window on the top of the stack is the window currently with the # grab. Each element is a dictionary containing: # 'grabWindow' : The window grabbed by pushgrab(). The # corresponding call to popgrab() will release # the grab on this window and restore the grab # on the next window in the stack (if there is one). # 'globalMode' : True if the grabWindow was grabbed with a # global grab and false if the grab was local. # 'previousFocus' : The focus as it was when pushgrab() # was called. The corresponding call to # popgrab() will restore this focus. # 'deactivateFunction' : # The function to call (usually grabWindow.deactivate) if # popgrab() is called (usually from a deactivate() method) # on a window which is not at the top of the stack (that is, # does not have the grab or focus). For example, if a modal # dialog is deleted by the window manager or deactivated by # a timer. In this case, all dialogs above and including # this one are deactivated, starting at the top of the # stack. # Note that when dealing with focus windows, the name of the Tk # widget is used, since it may be the '_Busy' window, which has no # python instance associated with it. #============================================================================= # Functions used to forward methods from a class to a component. # Fill in a flattened method resolution dictionary for a class (attributes are # filtered out). Flattening honours the MI method resolution rules # (depth-first search of bases in order). The dictionary has method names # for keys and functions for values. def __methodDict(cls, dict): # the strategy is to traverse the class in the _reverse_ of the normal # order, and overwrite any duplicates. baseList = list(cls.__bases__) baseList.reverse() # do bases in reverse order, so first base overrides last base for super in baseList: __methodDict(super, dict) # do my methods last to override base classes for key, value in cls.__dict__.items(): # ignore class attributes if type(value) == types.FunctionType: dict[key] = value def __methods(cls): # Return all method names for a class. # Return all method names for a class (attributes are filtered # out). Base classes are searched recursively. dict = {} __methodDict(cls, dict) return dict.keys() # Function body to resolve a forwarding given the target method name and the # attribute name. The resulting lambda requires only self, but will forward # any other parameters. __stringBody = ( 'def %(method)s(this, *args, **kw): return ' + 'apply(this.%(attribute)s.%(method)s, args, kw)') # Get a unique id __counter = 0 def __unique(): global __counter __counter = __counter + 1 return str(__counter) # Function body to resolve a forwarding given the target method name and the # index of the resolution function. The resulting lambda requires only self, # but will forward any other parameters. The target instance is identified # by invoking the resolution function. __funcBody = ( 'def %(method)s(this, *args, **kw): return ' + 'apply(this.%(forwardFunc)s().%(method)s, args, kw)') def forwardmethods(fromClass, toClass, toPart, exclude = ()): # Forward all methods from one class to another. # Forwarders will be created in fromClass to forward method # invocations to toClass. The methods to be forwarded are # identified by flattening the interface of toClass, and excluding # methods identified in the exclude list. Methods already defined # in fromClass, or special methods with one or more leading or # trailing underscores will not be forwarded. # For a given object of class fromClass, the corresponding toClass # object is identified using toPart. This can either be a String # denoting an attribute of fromClass objects, or a function taking # a fromClass object and returning a toClass object. # Example: # class MyClass: # ... # def __init__(self): # ... # self.__target = TargetClass() # ... # def findtarget(self): # return self.__target # forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2']) # # ...or... # forwardmethods(MyClass, TargetClass, MyClass.findtarget, # ['dangerous1', 'dangerous2']) # In both cases, all TargetClass methods will be forwarded from # MyClass except for dangerous1, dangerous2, special methods like # __str__, and pre-existing methods like findtarget. # Allow an attribute name (String) or a function to determine the instance if type(toPart) != types.StringType: # check that it is something like a function if callable(toPart): # If a method is passed, use the function within it if hasattr(toPart, 'im_func'): toPart = toPart.im_func # After this is set up, forwarders in this class will use # the forwarding function. The forwarding function name is # guaranteed to be unique, so that it can't be hidden by subclasses forwardName = '__fwdfunc__' + __unique() fromClass.__dict__[forwardName] = toPart # It's not a valid type else: raise TypeError, 'toPart must be attribute name, function or method' # get the full set of candidate methods dict = {} __methodDict(toClass, dict) # discard special methods for ex in dict.keys(): if ex[:1] == '_' or ex[-1:] == '_': del dict[ex] # discard dangerous methods supplied by the caller for ex in exclude: if dict.has_key(ex): del dict[ex] # discard methods already defined in fromClass for ex in __methods(fromClass): if dict.has_key(ex): del dict[ex] for method, func in dict.items(): d = {'method': method, 'func': func} if type(toPart) == types.StringType: execString = \ __stringBody % {'method' : method, 'attribute' : toPart} else: execString = \ __funcBody % {'forwardFunc' : forwardName, 'method' : method} exec execString in d # this creates a method fromClass.__dict__[method] = d[method] #============================================================================= class MegaArchetype: # Megawidget abstract root class. # This class provides methods which are inherited by classes # implementing useful bases (this class doesn't provide a # container widget inside which the megawidget can be built). def __init__(self, parent = None, hullClass = None): # Mapping from each megawidget option to a list of information # about the option # - default value # - current value # - function to call when the option is initialised in the # call to initialiseoptions() in the constructor or # modified via configure(). If this is INITOPT, the # option is an initialisation option (an option that can # be set by the call to the constructor but can not be # used with configure). # This mapping is not initialised here, but in the call to # defineoptions() which precedes construction of this base class. # # self._optionInfo = {} # Mapping from each component name to a tuple of information # about the component. # - component widget instance # - configure function of widget instance # - the class of the widget (Frame, EntryField, etc) # - cget function of widget instance # - the name of the component group of this component, if any self.__componentInfo = {} # Mapping from alias names to the names of components or # sub-components. self.__componentAliases = {} # Contains information about the keywords provided to the # constructor. It is a mapping from the keyword to a tuple # containing: # - value of keyword # - a boolean indicating if the keyword has been used. # A keyword is used if, during the construction of a megawidget, # - it is defined in a call to defineoptions() or addoptions(), or # - it references, by name, a component of the megawidget, or # - it references, by group, at least one component # At the end of megawidget construction, a call is made to # initialiseoptions() which reports an error if there are # unused options given to the constructor. # # self._constructorKeywords = {} if hullClass is None: self._hull = None else: if parent is None: parent = Tkinter._default_root # Create the hull. self._hull = self.createcomponent('hull', (), None, hullClass, (parent,)) _hullToMegaWidget[self._hull] = self if _useTkOptionDb: # Now that a widget has been created, query the Tk # option database to get the default values for the # options which have not been set in the call to the # constructor. This assumes that defineoptions() is # called before the __init__(). option_get = self.option_get VALUE = _OPT_VALUE DEFAULT = _OPT_DEFAULT for name, info in self._optionInfo.items(): value = info[VALUE] if value is _DEFAULT_OPTION_VALUE: resourceClass = string.upper(name[0]) + name[1:] value = option_get(name, resourceClass) if value != '': try: # Convert the string to int/float/tuple, etc value = eval(value, {'__builtins__': {}}) except: pass info[VALUE] = value else: info[VALUE] = info[DEFAULT] #====================================================================== # Methods used (mainly) during the construction of the megawidget. def defineoptions(self, keywords, optionDefs): # Create options, providing the default value and the method # to call when the value is changed. If any option created by # base classes has the same name as one in , the # base class's value and function will be overriden. # This should be called before the constructor of the base # class, so that default values defined in the derived class # override those in the base class. if not hasattr(self, '_constructorKeywords'): tmp = {} for option, value in keywords.items(): tmp[option] = [value, 0] self._constructorKeywords = tmp self._optionInfo = {} self.addoptions(optionDefs) def addoptions(self, optionDefs): # Add additional options, providing the default value and the # method to call when the value is changed. See # "defineoptions" for more details # optimisations: optionInfo = self._optionInfo optionInfo_has_key = optionInfo.has_key keywords = self._constructorKeywords keywords_has_key = keywords.has_key FUNCTION = _OPT_FUNCTION for name, default, function in optionDefs: if '_' not in name: # The option will already exist if it has been defined # in a derived class. In this case, do not override the # default value of the option or the callback function # if it is not None. if not optionInfo_has_key(name): if keywords_has_key(name): value = keywords[name][0] optionInfo[name] = [default, value, function] del keywords[name] else: if _useTkOptionDb: optionInfo[name] = \ [default, _DEFAULT_OPTION_VALUE, function] else: optionInfo[name] = [default, default, function] elif optionInfo[name][FUNCTION] is None: optionInfo[name][FUNCTION] = function else: # This option is of the form "component_option". If this is # not already defined in self._constructorKeywords add it. # This allows a derived class to override the default value # of an option of a component of a base class. if not keywords_has_key(name): keywords[name] = [default, 0] def createcomponent(self, name, aliases, group, widgetClass, *widgetArgs, **kw): # Create a component (during construction or later). if '_' in name: raise ValueError, 'Component name "%s" must not contain "_"' % name if hasattr(self, '_constructorKeywords'): keywords = self._constructorKeywords else: keywords = {} for alias, component in aliases: # Create aliases to the component and its sub-components. index = string.find(component, '_') if index < 0: self.__componentAliases[alias] = (component, None) else: mainComponent = component[:index] subComponent = component[(index + 1):] self.__componentAliases[alias] = (mainComponent, subComponent) # Remove aliases from the constructor keyword arguments by # replacing any keyword arguments that begin with *alias* # with corresponding keys beginning with *component*. alias = alias + '_' aliasLen = len(alias) for option in keywords.keys(): if len(option) > aliasLen and option[:aliasLen] == alias: newkey = component + '_' + option[aliasLen:] keywords[newkey] = keywords[option] del keywords[option] componentName = name + '_' nameLen = len(componentName) for option in keywords.keys(): if len(option) > nameLen and option[:nameLen] == componentName: # The keyword argument refers to this component, so add # this to the options to use when constructing the widget. kw[option[nameLen:]] = keywords[option][0] del keywords[option] else: # Check if this keyword argument refers to the group # of this component. If so, add this to the options # to use when constructing the widget. Mark the # keyword argument as being used, but do not remove it # since it may be required when creating another # component. index = string.find(option, '_') if index >= 0 and group == option[:index]: rest = option[(index + 1):] kw[rest] = keywords[option][0] keywords[option][1] = 1 if kw.has_key('pyclass'): widgetClass = kw['pyclass'] del kw['pyclass'] if widgetClass is None: return None if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType: # Arguments to the constructor can be specified as either # multiple trailing arguments to createcomponent() or as a # single tuple argument. widgetArgs = widgetArgs[0] widget = apply(widgetClass, widgetArgs, kw) componentClass = widget.__class__.__name__ self.__componentInfo[name] = (widget, widget.configure, componentClass, widget.cget, group) return widget def destroycomponent(self, name): # Remove a megawidget component. # This command is for use by megawidget designers to destroy a # megawidget component. self.__componentInfo[name][0].destroy() del self.__componentInfo[name] def createlabel(self, parent, childCols = 1, childRows = 1): labelpos = self['labelpos'] labelmargin = self['labelmargin'] if labelpos is None: return label = self.createcomponent('label', (), None, Tkinter.Label, (parent,)) if labelpos[0] in 'ns': # vertical layout if labelpos[0] == 'n': row = 0 margin = 1 else: row = childRows + 3 margin = row - 1 label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos) parent.grid_rowconfigure(margin, minsize=labelmargin) else: # horizontal layout if labelpos[0] == 'w': col = 0 margin = 1 else: col = childCols + 3 margin = col - 1 label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos) parent.grid_columnconfigure(margin, minsize=labelmargin) def initialiseoptions(self, myClass): if self.__class__ is myClass: unusedOptions = [] keywords = self._constructorKeywords for name in keywords.keys(): used = keywords[name][1] if not used: unusedOptions.append(name) self._constructorKeywords = {} if len(unusedOptions) > 0: if len(unusedOptions) == 1: text = 'Unknown option "' else: text = 'Unknown options "' raise KeyError, text + string.join(unusedOptions, ', ') + \ '" for ' + myClass.__name__ # Call the configuration callback function for every option. FUNCTION = _OPT_FUNCTION for info in self._optionInfo.values(): func = info[FUNCTION] if func is not None and func is not INITOPT: func() #====================================================================== # Method used to configure the megawidget. def configure(self, option=None, **kw): # Query or configure the megawidget options. # # If not empty, *kw* is a dictionary giving new # values for some of the options of this megawidget or its # components. For options defined for this megawidget, set # the value of the option to the new value and call the # configuration callback function, if any. For options of the # form _