/****************************************************************************\ File: doubbub.cpp This program will be published in conjunction with the paper "Double Bubbles Minimize", by Joel Hass and Roger Schlafly, in the Annals of Mathematics. After publication, permission to use for the purpose of scholarly research is hereby granted. Last modified: Aug. 1, 1999 Contact information. Annals of Mathematics http://www.math.princeton.edu/~annals/web annals@math.princeton.edu Double bubble home page http://math.ucdavis.edu/~hass/bubbles.html Roger Schlafly UC Computer Science Dept, Santa Cruz CA 95064 USA http://schlafly.homepage.com http://bbs.cruzio.com/~schlafly real@ieee.org Joel Hass UC Math Dept, Davis CA 95616 USA http://math.ucdavis.edu/~hass hass@math.ucdavis.edu Instructions for compiling and running. On UNIX systems there is usually a C++ compiler called "CC". You can compile doubbub.cpp and name the output executable "doubbub" with: CC -o doubbub doubbub.cpp On some systems you have to insert the switch "-lm" to link in the math library. If the GNU C++ compiler is available, it is usually better, and you can compile with: g++ -o doubbub doubbub.cpp Linux systems will generally have the GNU C++ compiler. On a PC (ie, Microsoft/Intel platform), you can create a "console application" with Microsoft Visual C++. On Borland C++, you can do the same thing, or just compile with "bcc32 doubbub". You can also use the 16-bit compiler, or whatever optimization switches you please. To run, just type "doubbub all" at a command prompt. If it is set up correctly, the program will run for 8 to 60 seconds, on a typical 1999 PC or workstation, and eventually will announce that all torus bubbles are rejected. Otherwise, you should get some sort of error message indicating what the problem is. The program keeps track of the number of compuations performed. On all machines we have tested this has led to the following totals, which the program will print if it gets consistent results: Finished calculations, successes = 15016, integrals = 51256, dY = 0.00098 The program has been tested on Wintel, Sun, HP, SGI, Linux/Intel, and Macintosh platforms, with identical results. Variations in computer model, OS version, and installation options may be such that changes are required to compile this program. \****************************************************************************/ #ifndef __cplusplus #error Must use C++. #endif #include #include #include // for sqrt() #include #include typedef float real; // 'double' also works fine /****************************************************************************\ System dependent code. \****************************************************************************/ #if defined(_MSC_VER) #define Platform "Wintel, using Microsoft C++" // this code works for Microsoft or Borland C++ #include #define fpclear() _clear87() #define round_down() _control87(RC_DOWN,MCW_RC); #define round_up() _control87(RC_UP,MCW_RC); #define round_near() _control87(RC_NEAR,MCW_RC); #define round_chop() _control87(RC_CHOP,MCW_RC); #define maskall() _fpreset();_control87(MCW_EM,MCW_EM); #define status() (_status87() & SW_INVALID+SW_ZERODIVIDE\ +SW_OVERFLOW+SW_UNDERFLOW) real infinity() { long a = 0x7f800000l; return * (float *) &a; } real fsqrt(real x) { // library routine rounds to nearest only __asm fld x __asm fsqrt __asm fstp x return x; } #endif #if defined(__BORLANDC__) #define Platform "Wintel, using Borland C++" #include // similar to the above, but with some Intel/Borland optimizations int status() { asm fstsw ax asm and ax, 13 return _AX; } // all traps masked // to unmask, change F to 2 static unsigned short ctrl_up = 0x1B7F; static unsigned short ctrl_down = 0x177F; static unsigned short ctrl_near = 0x137F; #define round_down() asm fldcw ctrl_down #define round_up() asm fldcw ctrl_up #define round_near() asm fldcw ctrl_near #define maskall() _fpreset(),_control87(MCW_EM,MCW_EM) #define infinity() 1e9999 real fsqrt(real x) { asm fld x asm fsqrt asm fstp x return x; } #endif #if defined(sun) || defined(__sgi) #if defined(sun) #define Platform "Sun Unix" #else #define Platform "SGI Unix" #endif #include #define fpclear() fpsetsticky(FP_X_CLEAR) #define round_near() fpsetround(FP_RN) #define round_down() fpsetround(FP_RM) #define round_up() fpsetround(FP_RP) #define round_chop() fpsetround(FP_RZ) #define status() (fpgetsticky() & FP_X_INV+FP_X_DZ+FP_X_OFL+FP_X_UFL) #define maskall() fpsetmask(FP_X_CLEAR) #define FP_X_CLEAR 0 #define bool int real infinity() { long a = 0x7f800000l; return * (float *) &a; } real fsqrt(real x) { // assume library doesn't change rounding mode return sqrt(x); } #endif #if defined(__hpux) || defined(__MWERKS__) #if defined(__hpux) #define Platform "HP Unix" #else #define Platform "Macintosh" #endif #include #define fpclear() feclearexcept(FE_ALL_EXCEPT) #define round_near() fesetround(FE_TONEAREST) #define round_down() fesetround(FE_DOWNWARD) #define round_up() fesetround(FE_UPWARD) #define round_chop() fesetround(FE_TOWARDZERO) #define status() fetestexcept(FE_DIVBYZERO+FE_INVALID+\ FE_OVERFLOW+FE_UNDERFLOW) #define maskall() 0 #define infinity() 1e9999 #define bool int real fsqrt(real x) { // assume library doesn't change rounding return sqrt(x); } #endif #if defined(__GNUC__) && defined(__i386__) #define Platform "LINUX/GNU for Intel" #include #define fpclear() 0 void round_down() { __volatile unsigned short int __cw; __asm __volatile ("fnstcw %0" : "=m" (__cw)); __cw = (__cw & 0xf3ff) | _FPU_RC_DOWN; __asm __volatile ("fldcw %0" : : "m" (__cw)); } void round_near() { __volatile unsigned short int __cw; __asm __volatile ("fnstcw %0" : "=m" (__cw)); __cw = (__cw & 0xf3ff) | _FPU_RC_NEAREST; __asm __volatile ("fldcw %0" : : "m" (__cw)); } void round_up() { __volatile unsigned short int __cw; __asm __volatile ("fnstcw %0" : "=m" (__cw)); __cw = (__cw & 0xf3ff) | _FPU_RC_UP; __asm __volatile ("fldcw %0" : : "m" (__cw)); } int status() { __volatile unsigned short int __sw; __asm __volatile ("fnstsw %0" : "=m" (__sw)); return __sw & (_FPU_MASK_IM+_FPU_MASK_ZM+_FPU_MASK_OM+_FPU_MASK_UM); } void maskall() { __volatile unsigned short int __cw = _FPU_DEFAULT; __asm __volatile ("fldcw %0" : : "m" (__cw)); } real infinity() { long a = 0x7f800000l; return * (float *) &a; } real fsqrt(real x) { // assume library doesn't change rounding return sqrt(x); } #endif #ifndef Platform #error Must specify a Platform. #endif /****************************************************************************\ A couple of real functions for convenience. \****************************************************************************/ inline real fmin(real x, real y) { return x < y ? x : y; } inline real fmax(real x, real y) { return x < y ? y : x; } /****************************************************************************\ Definition of the basic interval type. Each interval is a pair of reals, and each real is a single or double precision (IEEE 754 4 or 8-byte) floating point number. The value (l,r) represents the interval { x : l <= x <= r }. Most operations assume that l <= r, but an intersection can give l > r, in which case the interval is the empty set. \****************************************************************************/ struct realpair; typedef struct realpair rp; typedef const struct realpair &crp; struct realpair { real l, r; // left, right endpoints typedef rp (*func)(rp); // constructors realpair(){} realpair(real x) { l = r = x;} realpair(real x, real y) { l = x; r = y;} realpair operator =(real x) { l = r = x; return *this;} // obscure internals real width() const; //{ return ((rp) r - l).r;} void split(rp &x, rp &y) const; // arithmetic & set operations friend bool operator <=(crp X, crp Y) { return X.r <= Y.l;} friend bool operator <(crp X, crp Y) { return X.r < Y.l;} friend bool operator >=(crp X, crp Y) { return X.l >= Y.r;} friend bool operator >(crp X, crp Y) { return X.l > Y.r;} friend bool operator !=(crp X, crp Y) { return X < Y || Y < X;} friend rp operator &(crp X, crp Y) // intersection { return rp(fmax(X.l,Y.l),fmin(X.r,Y.r));} friend rp operator |(crp X, crp Y) // union { return rp(fmin(X.l,Y.l),fmax(X.r,Y.r));} friend rp operator &=(rp &X, crp Y) { return X = X & Y;} bool isEmpty() const { return l > r;} friend bool operator <<=(rp X, rp Y) // is x contained in y? { return Y.l <= X.l && X.r <= Y.r;} }; rp Max(rp X, rp Y) { return rp(fmax(X.l,Y.l),fmax(X.r,Y.r)); } rp Min(rp X, rp Y) { return rp(fmin(X.l,Y.l),fmin(X.r,Y.r)); } rp operator -(crp X) { rp Y; Y.l = - X.r; Y.r = - X.l; return Y; } rp operator +(crp X, crp Y) { rp Z; round_down(); Z.l = X.l + Y.l; round_up(); Z.r = X.r + Y.r; round_near(); return Z; } rp operator -(crp X, crp Y) { rp Z; round_down(); Z.l = X.l - Y.r; round_up(); Z.r = X.r - Y.l; round_near(); return Z; } void operator +=(rp &X, crp Y) { round_down(); X.l += Y.l; round_up(); X.r += Y.r; round_near(); } rp operator *(crp X, crp Y) { rp Z; round_down(); if (X.l >= 0) { Z.l = (Y.l >= 0 ? X.l : X.r) * Y.l; round_up(); Z.r = (Y.r >= 0 ? X.r : X.l) * Y.r; } else if (Y.l >= 0) { // can assume X.l < 0 Z.l = X.l * Y.r; round_up(); Z.r = X.r * (X.r >= 0 ? Y.r : Y.l); } else if (X.r < 0) { // can assume Y.l < 0 Z.l = (Y.r >= 0 ? X.l : X.r) * Y.r; round_up(); Z.r = X.l * Y.l; } else if (Y.r < 0) { // can assume X.l < 0, X.r >= 0 Z.l = Y.l * X.r; round_up(); Z.r = X.l * Y.l; } else { // can assume X.l < 0, X.r >= 0, Y.l < 0, Y.r >= 0 if (X.r == 0) { if (Y.r == 0) Z.l = 0; else Z.l = X.l * Y.r; round_up(); Z.r = X.l * Y.l; } else { Z.l = fmin(X.l * Y.r, X.r * Y.l); round_up(); Z.r = fmax(X.l * Y.l, X.r * Y.r); } } round_near(); return Z; } static rp divide(crp X, crp Y) { rp Z; assert (Y.l > 0); round_down(); Z.l = X.l / (X.l >= 0 ? Y.r : Y.l); round_up(); Z.r = X.r / (X.r >= 0 ? Y.l : Y.r); round_near(); return Z; } rp operator /(crp X, crp Y) { if (Y.l > 0) return divide(X,Y); else if (Y.r < 0) return divide(-X,-Y); real z = infinity(); return rp(-z,z); } rp operator ^(crp X, int y) // return x to the power of y // warning: this operator has low precedence in C // so you will usually have to parenthesize it { assert (y == 2); rp Z; if (X.l >= 0) { round_down(); Z.l = X.l * X.l; round_up(); Z.r = X.r * X.r; round_near(); } else if (X.r <= 0) Z = (-X)^2; else { Z.l = 0; round_up(); Z.r = fmax(X.l*X.l,X.r*X.r); } return Z; } rp Sqrt(rp X) { // assume that X can be narrowed so x >= 0 if (X.l < 0) X.l = 0; if (X.r < 0) X.r = 0; rp Y; round_down(); Y.l = fsqrt(X.l); round_up(); Y.r = fsqrt(X.r); round_near(); return Y; } const rp Root3 = Sqrt((rp)3); void rp::split(rp &x, rp &y) const { // split this so it is x | y real xm = .5*(l + r); x = rp(l,xm); y = rp(xm,r); } real rp::width() const { return ((rp) r - l).r; } /****************************************************************************\ Numerical integration routine, and functions to be integrated. For simplicity in passing functions as parameters, some global data is used. The integration endpoints may overlap, but it is only necessary to examine integrals with the first endpoint less than the second. That is, integrate(F,A,B) should contain every value of the integral of f from a to b, where a is in A, b is in B, a < b, and f(x) is in F(X) for each x and X with x in X. \****************************************************************************/ // data for informative display about the number of compuations performed static long successes = 0, integcount = 0; static real minwidth = 1; // keeps track of minimum width of input rectangles // Allows tracking of maximal number of rectangle divisions rp Integrate(rp::func F, rp A, rp B) { // bound with upper and lower Riemann sums ++integcount; rp H, R = 0; if (B <= A) return 0; H = (rp)(B.l - A.r) / 32; if (H.r > 0) for (int i = 0; i < 32; ++i) { R += (*F)(A.r + i*H + (0 | H)); } return (0|(*F)(A))*A.width() + (0|(*F)(B))*B.width() + R * H; } struct globals { // this global data is used by functions to be integrated rp H, Force, Y_min, Y_max; } Global; static void setGlobal(rp H, rp Force) // assign globals for subsequent integration { Global.H = H; Global.Force = Force; } static rp Dx(rp X) { rp T = Global.H*(X^2) - Global.Force; return T / Sqrt((2*X + T)*(2*X - T)); } static rp Dxmin(rp Z) { rp Y = Global.Y_min + (Z^2); rp T = Global.H*(Y^2) - Global.Force; return 2 * T / Sqrt((2*Y + T)*(2 - Global.H * Global.Y_min - Y*Global.H)); } static rp Dxmax(rp Z) { rp Y = Global.Y_max - (Z^2); rp T = Global.H*(Y^2) - Global.Force; rp Y_min = Global.Force / (1 + Sqrt(1 + Global.Force*Global.H)); return 2 * T / Sqrt((2*Y + T)*Global.H*(Y + Y_min)); } static rp Dv(rp X) // volume integrand, singularity at Global.Y_min, Global.Y_max // omits factor of pi { return (X^2) * Dx(X); } static rp Dvmin(rp Z) // volume integrand, singularity at Global.Y_min resolved // omits factor of pi { rp Y = Global.Y_min + (Z^2); return (Y^2) * Dxmin(Z); } static rp Dvmax(rp Z) // volume integrand, singularity at Global.Y_max resolved // omits factor of pi { rp Y = Global.Y_max - (Z^2); return (Y^2) * Dxmax(Z); } /****************************************************************************\ main rejection routines \****************************************************************************/ real avgwt(rp X, rp Y, real wt) // returns a point between x and y. Rounding is not material here. { assert (X < Y); return (1-wt)*X.r + wt*Y.l; } rp Compare(rp X, rp Y, rp A, rp B) // interval version of x < y ? a : b { if (X < Y) return A; else if (X > Y) return B; else return A | B; // returns union if intervals X,Y overlap } // Rejection codes. // Values can be used to analyze why torus bubbles are rejected. #define NORESULT 0 #define REJECT(n) n int CheckRectangle(rp C_1, rp H_o) // return a positive exclusion code if successful { // step 1 // check based on Proposition 4.21. if (1000*C_1 >= 996 && 5*H_o <= 1) return REJECT(1); // step 2 rp H_i = 2 - H_o; rp Y_1 = Sqrt(1 - (C_1^2)); rp F_i = (H_i - 1)*(Y_1^2) - C_1*Y_1*Root3; rp F_o = - F_i; rp C_2 = (-C_1 | C_1) & rp(-.5,.5); if (C_2.isEmpty()) return REJECT(2); rp Ht = H_i - 1; rp T = (2*Ht*F_i + 3) / (3 + (Ht^2)) - (1 - (C_1^2)); if ((C_2^2) + T != 1) return REJECT(2); // step 3 rp Y_2 = Sqrt(T & rp(0,1)); if (!(Y_2 > 0)) return NORESULT; assert (Y_2 > 0); C_2 &= (Ht*Y_2 - F_i/Y_2) / Root3; if (C_2.isEmpty()) return REJECT(3); // never happens // step 4 // compare to standard double bubble if (C_1 <= .5 && Y_1 > 0 && H_o < 1 - Root3 * C_1 / Y_1) return REJECT(4); // step 5 // if the endpoint is too fat, go back and subdivide input if (C_2.width() > .5) return NORESULT; // step 6 // set Global.Y_min,Global.Y_max for use by integration routines Global.Y_min = - F_i / (1 + Sqrt(1 + F_i*H_i)); // local y min Global.Y_max = (1 + Sqrt(1 + F_o*H_o)) / H_o; // local y max rp W_ends = ((1-C_1)^2)*(2+C_1)/3 + ((1-C_2)^2)*(2+C_2)/3; rp Y = Compare(C_1,Root3/2,Global.Y_min,Y_1); if (Y * H_i < -1 && Y > 0) { // test for inefficient torus rp R = (Root3 / 2) / (-H_i - 1/Y); rp W = 2.5 * (R^2) * (Y + R*Root3/2); // upper bd for vol/pi if (W < W_ends) return REJECT(6); } // step 7 // calc some integration endpoints rp Y_left = Sqrt(F_o/H_o); if (Global.Y_min < Y_2 && Y_left < Global.Y_max) Y_left = Max(Y_1,Y_left); else return NORESULT; // need more accuracy rp Y_4 = avgwt(Y_left,Global.Y_max,1/16.); rp Z_2 = Sqrt(Global.Y_max-Y_2); rp Z_4 = - Sqrt(Global.Y_max-Y_4); rp Delta_i, Delta_o; rp yymin = Global.Y_max - 2/H_o; if (1000*C_1 >= 998) { T = avgwt(Y_1,Y_2.r,.5); Delta_i = (T - Y_1) * 33/16.; setGlobal(H_i,F_i); // set globals for integration Delta_i += Integrate(Dx,T,Y_2); Delta_o = - (Y_left - Y_1) * Root3; T = avgwt(Y_left,Y_4,1/16.); setGlobal(H_o,F_o); Delta_o += Integrate(Dx,T,Y_4); if (Delta_i < Delta_o) return REJECT(7); Delta_o += Integrate(Dxmax,Z_4,Z_2); if (Delta_i < Delta_o) return REJECT(7); if (1 <<= C_1) return NORESULT; } // step 8 // calc inner curve width assert (C_1 <= Root3/2 || Y_1 <= Y_2); T = Sqrt(Y_1-Global.Y_min); rp Z_1 = Compare(C_1,Root3/2,-T,T); rp Z_3 = Sqrt(Y_2-Global.Y_min); setGlobal(H_i,F_i); Delta_i = Integrate(Dxmin,Z_1,Z_3); // calc outer curve width setGlobal(H_o,F_o); Delta_o = Integrate(Dxmax,Z_4,Z_2); if (Delta_i < Delta_o) return REJECT(8); Delta_o += Integrate(Dx,Y_1,Y_4); if (Delta_i != Delta_o) return REJECT(8); // can prove with return here, but very slow // return NORESULT; // step 9 // calc inner curve volume setGlobal(H_i,F_i); rp W_base = Integrate(Dvmin,Z_1,Z_3); rp W_i = W_base + W_ends; // calc outer curve volume setGlobal(H_o,F_o); rp W_o = Integrate(Dv,Y_1,Y_4) + Integrate(Dvmax,Z_4,Z_2) - W_base; if (W_i != W_o) return REJECT(9); return NORESULT; } int DivideAndCheckRectangle(rp Y_1, rp H_o) // return -1 on failure { if (Y_1.width() < minwidth) minwidth = Y_1.width(); rp C_1 = Sqrt(1 - (Y_1^2)); assert (C_1 <= 1); int i = CheckRectangle(C_1,H_o); if (i) { if (i < 0) return -1; ++successes; return 1 << i; } assert (Y_1.width() > .001 && H_o.width() > .01); rp Ya, Yb, Ha, Hb; Y_1.split(Ya,Yb); H_o.split(Ha,Hb); i |= DivideAndCheckRectangle(Ya,Ha); if (i < 0) return i; i |= DivideAndCheckRectangle(Ya,Hb); if (i < 0) return i; i |= DivideAndCheckRectangle(Yb,Ha); if (i < 0) return i; i |= DivideAndCheckRectangle(Yb,Hb); if (i < 0) return i; return i; } /****************************************************************************\ stuff for DOS/UNIX command-line versions \****************************************************************************/ const char help[] = "Program to search for torus bubble counterexamples.\n" "See info at http://www.ucdavis.edu/~hass/bubbles.html\n" "Usage: doubbub all\n" "Program takes 8 to 60 seconds on typical 1999 computers.\n"; int main(int argc, char *argv[]) { if (argc <= 1) { printf(help); return 0; } if (argv[1][0] != 'a') { printf("Unknown option\n"); return 1; } time_t t = time(0); printf("Platform = %s\n",Platform); maskall(); // first some simple checks assert (sizeof(rp) == 8 || sizeof(rp) == 16); assert (Root3.width() > 0); assert (Root3.width() < 1e-6); rp X = Root3^2; assert (X.l < 3 && 3 < X.r); X = infinity(); assert (X > 1e37); // the main calculation rp Y_1 = rp(0,1); rp H_o = rp(0,10); if (DivideAndCheckRectangle(Y_1,H_o) < 0) { printf("Torus bubble calculation failed.\n"); exit(1); } int final = status(); if (final) printf("Warning: floating point exception occurred," " status = %.4X\n",final); t = time(0) - t; printf("Finished calculations, successes = %ld, integrals = %ld, " "dY = %.5f\n",successes,integcount,minwidth); printf("All torus bubbles rejected. Elapsed time: %ld sec.\n",t); if (successes != 15016l || integcount != 51256l || 1024*minwidth != 1) printf("WARNING: totals do not match published figures\n"); return 0; }