//****************************************************************************** // Diffuse.cpp Copyright(c) 2003 Jonathan D. Lettvin, All Rights Reserved. //****************************************************************************** // Permission to use this code if and only if the first eight lines are first. // This code is offered under the GPL. // Please send improvements to the author: jdl@alum.mit.edu // The User is responsible for errors. //****************************************************************************** static char *usage_string= "QUIT because \"%s\"\n" "Usage: %s {Number} [{Jiggle} [{Rounds}]]\n" " Number: count of points to distribute over unit sphere\n" " Jiggle: lower limit of largest movement below which relaxation stops\n" " Rounds: upper limit of relaxation steps above which relaxation stops\n" "Fix one charged point at (1,0,0) on a unit sphere.\n" "Randomly distribute additional charged points over unit sphere.\n" "Prevent identical positioning.\n" "Apply formula for charge repulsion to calculate movement vector.\n" "After all movement vectors have been calculated, apply to charges.\n" "Normalize resulting vectors to constrain movement to sphere.\n" "Repeat until movement falls below limit, or rounds rises above limit." "Output as a C static double array of coordinate triples." ; //****************************************************************************** // Overview: // Diffuse a number of points to an approximately stationary position using // the standard physics formula for charge repulsion (inverse square law) // over the surface of a sphere. For vertex counts equalling platonic solids, // the result will be a distribution approximating that platonic solid. // Otherwise, distributions are sufficiently random to make // every possible great circle cross the same number of edges, // give or take a small variation. // Use the first point as an anchor at (1,0,0) then randomly distribute points. // Precautions are taken to avoid identical points (a zerodivide, or "pole"). // The expected area of an inscribed circle around any point should converge to // the area of the sphere divided by the number of points, therefore the radius // of said circle should be approximately linear with 1/sqrt(N). This program // arbitrarily uses the default minimum incremental movement value .03/sqrt(N). //****************************************************************************** // OVERALL SEQUENCE OF OPERATION: // ---- // main: Sequence of operations: // 1) gather, constrain, and default command-line arguments // 2) construct "points" object (vector of spherically constrained coordinates) // 3) output the coordinates calculated during construction // 4) quit // ---- // 2: sequence of operations // a) Initialize random number generator // b) construct vector of coordinates fixing the first and randomizing others // c) relax the vector (cause point-charge forces to push points apart) // d) calculate nearest neighbor for all points and record that minimum radius // ---- // 2b: sequence of operations // e) push (1,0,0) on vector // f) push random normalized coordinates on vector, pop if another is identical // g) push slot for per-coordinate minimum radius // ---- // 2c: sequence of operations // h) for every point get inverse-square increment from all other points // i) zero the force on the (1,0,0) point to keep anchor // j) after acquiring all increments, sum and normalize increments, and apply // k) repeat until maximum movement falls below a minimum // ---- // 2be and 2bf: sequence of operations // l) construct either the (1,0,0) point or a random point // m) normalize to place the point on the unit sphere // ---- // 2ch: sequence of operations // n) calculate distance between indexed point and a specific other point // o) take inverse square of distance // p) add directly to increment coordinates // ---- // 2cj: sequence of operations // q) keep a copy of the starting coordinates for the point // r) add increment to move point, and normalize it back onto the sphere // s) calculate distance to starting coordinates and // t) remember largest movement on surface of sphere // ---- // 3: sequence of operations // u) output all point coordinates as a C style static double array //****************************************************************************** #include // sync_with_stdio #include // printf #include // I believe sqrt is in here #include // Used to salt the random number generator #include // Various mathematical needs #include // Used to contain many points #include // Used to implement a true XYZ vector //****************************************************************************** using namespace std; // Necessary to gain access to many C++ names //****************************************************************************** typedef valarraycoordinates; // To simplify declarations // A global function to document how to use this program. void usage(char *program_name,char *quit_reason) { fprintf(stderr,usage_string,quit_reason,program_name); exit(1); } //****************************************************************************** class XYZ { // This class contains operations for placing and moving points private: double change_magnitude; coordinates xyz; // This holds the coordinates of the point. coordinates dxyz; // This holds the summed force vectors from other points inline double random() { return(double((rand()%1000)-500)); } // ordinates inline double square(const double& n) { return(n*n); } inline coordinates square(const coordinates& n) { return(n*n); } inline double inverse(const double& n) { return(1.0/n); } XYZ& inverse_square() { xyz*=inverse(square(magnitude())); return *this; } inline double magnitude() { return(sqrt((xyz*xyz).sum())); } void normalize() { xyz/=magnitude(); } // unit vector public: XYZ(): xyz(3), dxyz(3) { xyz[0]=random(); xyz[1]=random(); xyz[2]=random(); normalize(); } XYZ(const double& x,const double& y,const double& z) : xyz(3), dxyz(3) { xyz[0]=x; xyz[1]=y; xyz[2]=z; } XYZ(const coordinates& p) : xyz(3), dxyz(3) { xyz=p; } ~XYZ() { } coordinates& array() { return xyz; } void zero_force() { dxyz=0.0; } double change() { return(change_magnitude); } double magnitude(XYZ& b) { // Return length of vector. (not const) return(sqrt( square(b.array()-xyz).sum() )); } void sum_force(XYZ& b) { // Cause force from each point to sum. (not const) dxyz+=(XYZ(b.array()-xyz).inverse_square().array()); // Calculate and add } void move_over_sphere() { // Cause point to move due to force coordinates before=xyz; // Save previous position xyz-=dxyz; // Follow movement vector normalize(); // Project back to sphere before-=xyz; // Calculate traversal change_magnitude=sqrt((before*before).sum()); // Record largest } void report(const double& d) { printf(" { %+1.3e,%+1.3e,%+1.3e,%+1.3e }",xyz[0],xyz[1],xyz[2],d); } }; //****************************************************************************** class points { // This class organizes expression of relations between points private: const size_t N; // Number of point charges on surface of sphere const size_t R; // Number of rounds after which to stop const double L; // Threshold of movement below which to stop char *S; // Name of this vertex set size_t rounds; // Index of rounds processed vectorV; // List of point charges vectorH; // List of minimum distances double maximum_change; // The distance traversed by the most moved point double minimum_radius; // The radius of the smallest circle time_t T0; // Timing values void relax() { // Cause all points to sum forces from all other points size_t i, j; rounds=0; do { maximum_change=0.0; for(i=1;imaximum_change) maximum_change=V[i].change(); } } while(maximum_change>L&&++rounds4) usage(argv[0],"Bad argument count"); if((Number=atoi(argv[1]))<2) usage(argv[0],"Constrain Number>1"); Jiggle=0.03/sqrt(double(Number)); // default Jiggle=.03 of expected radius if(argc>=3&&((Jiggle=atof(argv[2]))<=0||Jiggle>(0.03/sqrt(double(Number))))) usage(argv[0],"Constrain 010000) usage(argv[0],"Constrain 50<=Rounds<=10000"); points P("default",Number,Jiggle,Rounds); // P can now be addressed for specific XYZ values for scaling and reporting // For instance P[0] should return a valarray& of three elements (1,0,0) // In this program, the array is simply output as a C static double array P.report(); return(0); } //****************************************************************************** // End of file //******************************************************************************