// // Miniball extended to arbitrary Bregman divergences // // Java Applet that demonstrates the generalization of the MiniBall algorithm of Welzl // Compiled using SUN Java Standard Edition J2SE 5.0 // // Frank NIELSEN, May 2006 // // Frank.Nielsen@acm.org // // https://processing.org/ // Processing Adaptation: 28/11/2015, Antoine Chatalic, with further enhancements by Frank Nielsen (Dec 2015) import processing.pdf.*; String filenameprefix="SmallestEnclosingBregmanBall"; int snapshotnb=0; String L22="squared Euclidean", KL="Kullback-Leibler", IS="Itakura-Saito" ; String EXP="exponential", CSI="Csiszar (0.5)", MAH="squared Mahalanobis", LOG="Logistic Loss"; private int xbutton; private int ybutton; private int hbutton; private int mbutton; private int xpad; private int ypad; private int hmenu; private int maxcard; private int MAXCARD=200; private BregmanDivergence DF=new BregmanDivergence(); private PointSet dataset= new PointSet(maxcard,DF); // // Center point, maximum radius // private Point center; private double radius; private minibb mini; private BregmanDisk BD; // Window size private int w, h; private boolean justinitialized; private double worldminx=-0.5; private double worldmaxx=1.5; private double worldminy=-0.5; private double worldmaxy=1.5; // UI Buttons private Button applyButton; private Button newsetButton; private Button miniballButton; private DropDown dFList; // DropDown list (Bregman divergences) private color bgcol = color(253); // Background color private color bgbordercol = color(200); // Canvas frame color private color buttonBgCol = bgcol; // Buttons background color private color buttonOverBgCol = color(240); // Buttons background color private color buttonSelCol = color(200); // Buttons background color private color buttonBorderCol = color(0); // Buttons border color private color buttonTextCol = buttonBorderCol; // Buttons text color private color pointsCol = color(255,0,0); private color centerCol = color(10,0,255); private color geodesicCol = color(0,255,0); private color ballCol = color(255,204,0); // Bregman ball color boolean showTop=true; void keyPressed() { if (key=='a') {// add a new point double xx=Xtox(mouseX); double yy=Ytoy(mouseY); dataset=new PointSet(dataset,new Point(xx,yy)); } if (key==' ') initSet(); if ((key=='+') || (key =='=')) {if (maxcardmaxdiv) {maxdiv=div;} } return maxdiv; } // // Interpolation for the geodesic: draw path [pq] // public Point BBCPoint(double alpha, Point p, Point q) { Point gradfp,gradc; Point cc=new Point(); gradfp=DF.gradF(q); gradfp.MultCste(1.0-alpha); gradc=DF.gradF(p); gradc.MultCste(alpha); gradc.AddPoint(gradfp); cc=DF.gradFinv(gradc); return cc; } // // Class for manipulating a 2D point // class Point { public double x,y; // Constructor Point() { x=0.0; y=0.0; } Point (double xx, double yy) {x=xx; y=yy;} double distSqr(Point q) { return (q.x-x)*(q.x-x)+(q.y-y)*(q.y-y); } // // Java does not allow operator overloading // Thus, we need to do it coordinatewise // public void AddPoint(Point p) { x=x+p.x; y=y+p.y; } public void MultCste(double cste) { x=cste*x; y=cste*y; } } // // A Point set class // class PointSet { public int n; public Point array[]; public void PrintSet() { int i; /* for(i=0;i0) { array=new Point[card]; // Uniform point set on the unit square for(i=0;i radiusball ) { drawpoint.x=random(1.0); drawpoint.y=random(1.0); } array[i].x=drawpoint.x; array[i].y=drawpoint.y; } /*println("Point set drawn.");*/ } } // End of point set class // // Generic Bregman divergence class (here, we hard plug Itakura-Saito) // class BregmanDivergence { String name; double dd=0.001; // for computing gradients int type; BregmanDivergence() { name="Itakura-Saito Divergence"; type=0; // not linear } double Fy(double y) { return Fx(y); // by default the same on each axis } double F(Point p) { return Fx(p.x)+Fy(p.y); } double Fx(double x) { return -Math.log(x); } // Gradient operator Point gradF(Point p) { return new Point(-1.0/p.x, -1.0/p.y); } // Inverse Gradient Operator Point gradFinv(Point p) { return new Point(-1.0/p.x, -1.0/p.y); } // Itakura-Saito double divergence(Point p, Point q) { return (p.x/q.x)-Math.log(p.x/q.x)-1.0 + p.y/q.y-Math.log(p.y/q.y)-1.0; } double DotProduct(Point p,Point q) { return p.x*q.x + p.y*q.y; } PPoint BregmanBisector(Point p, Point q) { Point gradp,gradq; PPoint result=new PPoint(); gradp=gradF(p); gradq=gradF(q); // // Equation of the bisector stored as a projective point // result.x=gradp.x-gradq.x; result.y=gradp.y-gradq.y; result.w=F(p)-F(q)+DotProduct(q,gradq)-DotProduct(p,gradp); return result; } double Divergence(Point p, Point q) { return divergence(p,q)+divergence(q,p); } } // // Begin projective point // class PPoint { public double x,y,w; PPoint(Point p) { x=p.x; y=p.y; w=1.0; } PPoint(double xx, double yy) { x=xx; y=yy; w=1.0; } PPoint() {x=y=0.0; w=1.0;} // Dehomogenization (perspective division) void Normalize() { if (w!=0) {x/=w; y/=w; w=1.0;} } // Return the ycoord corresponding to xcoord double XtoY(double xcoord) { return (-w-x*xcoord)/y; } void SetInfinite() { w=0.0; } }; // // Class Bregman disk // class BregmanDisk { // Center and radius of the disk public Point center; public double rad; // Combinatorial basis public PointSet basis; BregmanDisk() { center=new Point(); center.x=0.0; center.y=0.0; rad=0.0; basis=new PointSet(1); basis.array[0].x=center.x; basis.array[0].y=center.y; } }; // // The Bregman MiniBall class // class minibb { /* intersection point is normalized */ public PPoint b12, b13, b23,intersection; PointSet set; BregmanDivergence DF; // Solution BregmanDisk bdisk; PointSet basis; minibb(PointSet ps, BregmanDivergence div) { set=new PointSet(ps,0); DF=div; // At most 3 points in a basis basis=new PointSet(0); //bdisk=SolveDisc3(set.array[0],set.array[1],set.array[2]); //bdisk=SolveDisc2(set.array[0],set.array[1]); bdisk=MiniDisc(set,basis); /*println("Solution to Miniball: "+bdisk.center.x+" "+bdisk.center.y+" radius="+bdisk.rad+" basis size="+bdisk.basis.n);*/ } PPoint CrossProduct(PPoint p1, PPoint p2) { PPoint result=new PPoint(); result.x=p1.y*p2.w-p1.w*p2.y; result.y=p1.w*p2.x-p1.x*p2.w; result.w=p1.x*p2.y-p1.y*p2.x; return result; } double DotProduct(Point p,Point q) { return p.x*q.x+p.y*q.y; } // // Compute the linear equation of a Bregman Bisector (type 1) // PPoint BregmanBisector(Point p, Point q) { Point gradp,gradq; PPoint result=new PPoint(); //System.out.println("Bregman bisector of "+p.x+" "+p.y+" with "+q.x+" "+q.y); gradp=DF.gradF(p); gradq=DF.gradF(q); // // Equation of the bisector stored as a projective point // result.x=gradp.x-gradq.x; result.y=gradp.y-gradq.y; result.w=DF.F(p)-DF.F(q)+DotProduct(q,gradq)-DotProduct(p,gradp); return result; } // // Solve the basic problem for three points: // Note that not all disks can pass by three points // BregmanDisk SolveDisc3(Point d1, Point d2, Point d3) { BregmanDisk result=new BregmanDisk(); b12=BregmanBisector(d1,d2); b13=BregmanBisector(d1,d3); b23=BregmanBisector(d2,d3); intersection=CrossProduct(b12,b13); intersection.Normalize(); result.center.x=intersection.x; result.center.y=intersection.y; result.rad=DF.divergence(result.center, d1); // trisector result.basis=new PointSet(3); result.basis.array[0]=d1; result.basis.array[1]=d2; result.basis.array[2]=d3; return result; } // // Solve minimum divergence for two points // public Point BBCPoint(double alpha, Point p, Point q) { Point gradfp,gradc; Point cc=new Point(); gradfp=DF.gradF(q); gradfp.MultCste(1.0-alpha); gradc=DF.gradF(p); gradc.MultCste(alpha); gradc.AddPoint(gradfp); cc=DF.gradFinv(gradc); return cc; } BregmanDisk SolveDisc2(Point d1, Point d2) { BregmanDisk result=new BregmanDisk(); double mindiv,div; int i,nbsteps=1000; double alpha; double increment=1.0/(double)nbsteps; /* Point p=new Point(); b12=BregmanBisector(d1,d2); mindiv=Double.MAX_VALUE; for(i=0;i1.0e-5) { lambda=0.5*(lambdamin+lambdamax); pq2=BBCPoint(lambda, d1,d2); if (DF.divergence(pq2,d1)>DF.divergence(pq2,d2)) lambdamin=lambda; else lambdamax=lambda; } result.center=pq2; result.rad=DF.divergence(result.center, d1); // should be mindiv result.basis=new PointSet(2); result.basis.array[0]=d1; result.basis.array[1]=d2; return result; } BregmanDisk SolveDisc1(Point c) { BregmanDisk result=new BregmanDisk(); result.rad=0.0; result.center=c; result.basis=new PointSet(1); result.basis.array[0]=result.center; return result; } // // FallOutside bypasses the computation of the exact casis // boolean FallOutside(BregmanDisk b, Point p) { /* if (b.basis.n==2) { // Exact computation BregmanDisk d3=SolveDisc3(p,b.basis.array[0],b.basis.array[1]); // Can the SEB of the basis be below d3.rad ? return SolveDisc2DP(b.basis.array[0],b.basis.array[1],d3.rad); } else */ if (DF.divergence(b.center,p)>b.rad) return true; else return false; } // // Miniball recursive algorithm "as is" // See Welzl's paper // BregmanDisk MiniDisc(PointSet set, PointSet basis) { int k,b,n; b=basis.n; n=set.n; //System.out.println("Set size:"+set.n+" Basis size:"+basis.n); //set.PrintSet(); //basis.PrintSet(); // // Terminal cases // if (b==3) { BregmanDisk result; // System.out.println("Solve terminal case with basis=3"); result=SolveDisc3(basis.array[0], basis.array[1], basis.array[2]); return result; } // divergence is zero by definition if ((n==1)&&(b==0)) { BregmanDisk result; result=SolveDisc1(set.array[0]); return result;} if ((n==2)&&(b==0)) { BregmanDisk result; result=SolveDisc2(set.array[0],set.array[1]); return result;} if ((n==0)&&(b==2)) {BregmanDisk result; result=SolveDisc2(basis.array[0],basis.array[1]); // System.out.println("Terminal case b=2 n=0 "+result.rad); return result; } if ((n==1)&&(b==1)) {BregmanDisk result; result=SolveDisc2(basis.array[0], set.array[0]); return result;} // // General case // if (n+b>2) { BregmanDisk result; // Randomization: choosing a pivot k=int(random(n)); // between 0 and n-1 if (k!=0) { // Swap two points for a randomized miniball Point tmp=new Point(); tmp=set.array[0]; set.array[0]=set.array[k]; set.array[k]=tmp; } // System.out.println("Pivot "+k); // Copy the point set except the first element PointSet remainset=new PointSet(set,1); // Remove the first element result=MiniDisc(remainset,basis); // If the point falls outside the Bregman disk, this means that // it should belong to the basis // if (DF.divergence(result.center,set.array[0])>result.rad) if (FallOutside(result,set.array[0])) { // Then point stored at set[0] necessarily belongs to the basis. // basis.array[basis.n]=new Point(); // System.out.println("Upgrade basis "+basis.n); PointSet newbasis=new PointSet(basis,set.array[0]); // System.out.println("... new basis "+newbasis.n); result=MiniDisc(remainset,newbasis); } return result; } // end of not terminal case // we should not reach that stage but Java requires to return some value for all paths return new BregmanDisk(); } } class Button{ int xpos, ypos, wid, hei; String label; boolean over = false; boolean down = false; boolean clicked = false; Button(String tlabel) { xpos = xbutton; ypos = ybutton; wid = ceil(textWidth(tlabel))+2*xpad; hei = hbutton; label = tlabel; xbutton = xbutton + wid + mbutton; } void update(){ //it is important that this comes first if(down&&over&&!mousePressed){ clicked=true; }else{ clicked=false; } //UP OVER AND DOWN STATE CONTROLS if(mouseX>xpos && mouseY>ypos && mouseX items; boolean over = false; boolean overDeployed = false; boolean down = false; DropDown(String tlabel) { wid = 100; hei = hbutton; xlabel = xbutton; ylabel = ybutton+hei; wlabel = ceil(textWidth(tlabel)); xbutton = xbutton + wlabel; xpos = xbutton; ypos = ybutton; label = tlabel; xbutton = xbutton + wid + mbutton; items = new ArrayList(); sel = 0; } void addItem(String s) { items.add(s); int nwid = max(wid, ceil(textWidth(s))+2*xpad); xbutton = xbutton + (nwid-wid); wid = nwid; } void update(){ //it is important that this comes first if(down&&!mousePressed){ int nsel = -ceil((ypos-mouseY)/hei)-1; if(nsel >= 0 && nsel < items.size() && overDeployed && mouseY>ylabel){ sel = nsel; switch(getSelectedString()) { /* dFList.addItem("squared Euclidean"); dFList.addItem("Kullback-Leibler"); dFList.addItem("Itakura-Saito"); dFList.addItem("Exponential"); dFList.addItem("Csiszar (0.5)"); dFList.addItem("squared Mahalanobis"); dFList.addItem("Logistic Loss" */ case "Itakura-Saito": DF=new BregmanDivergence(); changeDivergence(0.0,4.0,0.0,4.0); break; } } }else{ } //UP OVER AND DOWN STATE CONTROLS if(mouseX>xpos && mouseY>ypos && mouseXxpos && mouseY>ypos && mouseX