// // 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 import java.awt.*; import java.awt.event.*; import java.util.Random; import java.awt.image.*; // // Java Applet class // public class MiniBBall extends java.applet.Applet { int maxcard; BregmanDivergence DF=new L22(); PointSet dataset= new PointSet(maxcard,DF); // // Center point, maximum radius // Point center; double radius; minibb mini; BregmanDisk BD; // // Window size // int w, h; Dimension dim; boolean justinitialized; Font helveticafont; Label title; InputPanel inputPanel; ImageCanvas imageCanvas; // // Compute the maximal divergence to a point set // double MaxDivergence(Point p) { int i; double div,maxdiv=0.0; for(i=0;imaxdiv) {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; } // // Applet initialization // public void init() { int i; maxcard=20; dim=getSize(); w=getSize().width; h=getSize().height; System.out.println("MiniBBall: Computing Exactly the Smallest Bregman Balls by Frank Nielsen and Richard Nock. May 2006."); System.out.println("Applet MiniBBall launched on "+dim); helveticafont=new Font("Helvetica", Font.ITALIC,16); setLayout(new BorderLayout()); inputPanel = new InputPanel(this); inputPanel.setSize(w, 200); add("North", inputPanel); imageCanvas = new ImageCanvas(this); imageCanvas.setSize(500,500); imageCanvas.w=500; imageCanvas.h=500; add("Center", imageCanvas); DF=new L22(); imageCanvas.worldminx=-0.5; imageCanvas.worldmaxx=1.5; imageCanvas.worldminy=-0.5; imageCanvas.worldmaxy=1.5; dataset=new PointSet(maxcard, DF); mini=new minibb(dataset,DF); BD=mini.bdisk; justinitialized=false; } public void paint(Graphics g) {} public String getAppletInfo() { return "Computes the exact smallest enclosing Bregman ball of a point set"; } } // // Control panel // class InputPanel extends Panel implements ActionListener, ItemListener, AdjustmentListener { Button applyButton; Label title; // // UI // Button refreshButton, newsetButton; Button miniballButton; Scrollbar slider; // for parameter n CheckboxGroup radioDFGroup; Checkbox radioDFL22; Checkbox radioDFKL; Checkbox radioDFIS; Checkbox radioDFEXP; Checkbox radioDFCSIS; Checkbox radioDFMAH; Checkbox radioDFLL; MiniBBall applet; InputPanel(MiniBBall applet) { this.applet = applet; this.setBackground(Color.white); setLayout(new BorderLayout()); title = new Label("MiniBBall: Smallest Enclosing Bregman Ball by F. Nielsen and R. Nock", Label.CENTER); title.setFont(applet.helveticafont); title.setBackground(Color.white); add("North", title); Panel p = new Panel(); p.setLayout(new BorderLayout()); Panel pp=new Panel(); refreshButton=new Button("Refresh"); newsetButton=new Button("Draw a new Set"); pp.add("East",refreshButton); pp.add("Center",newsetButton); Panel pb= new Panel(); miniballButton=new Button("Mini Bregman Ball"); pb.add("Center", miniballButton); p.add("North",pp); p.add("Center",pb); slider = new Scrollbar(Scrollbar.HORIZONTAL,30 ,10, 2, 1000); p.add("South", slider); slider.addAdjustmentListener(this); add("Center", p); newsetButton.addActionListener(this); refreshButton.addActionListener(this); miniballButton.addActionListener(this); radioDFGroup = new CheckboxGroup(); radioDFL22 = new Checkbox("Squared Euclidean", radioDFGroup,true); radioDFKL = new Checkbox("KullBack-Leibler", radioDFGroup,false); radioDFIS= new Checkbox("Itakura-Saito", radioDFGroup,false); radioDFEXP= new Checkbox("Exponential", radioDFGroup,false); radioDFCSIS= new Checkbox("Csiszar", radioDFGroup,false); radioDFMAH= new Checkbox("Mahalanobis", radioDFGroup,false); radioDFLL= new Checkbox("Logistic Loss", radioDFGroup,false); radioDFL22.addItemListener(this); radioDFKL.addItemListener(this); radioDFIS.addItemListener(this); radioDFEXP.addItemListener(this); radioDFCSIS.addItemListener(this); radioDFMAH.addItemListener(this); radioDFLL.addItemListener(this); Panel pc=new Panel(); pc.setLayout(new GridLayout(4,2)); pc.add(new Label("Bregman divergence:")); pc.add(radioDFL22); pc.add(radioDFKL); pc.add(radioDFIS); pc.add(radioDFEXP); pc.add(radioDFCSIS); pc.add(radioDFMAH); pc.add(radioDFLL); add("East",pc); } public void actionPerformed(ActionEvent event) { Object obj = event.getSource(); if (obj==refreshButton) { applet.imageCanvas.repaint(); } if (obj==newsetButton) { applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.imageCanvas.repaint(); } // Compute the smallest enclosing ball if (obj==miniballButton) { // recompute the smallest enclosing disk applet.mini=new minibb(applet.dataset, applet.DF); // get the smallest enclosing disk applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } } // Slider public void adjustmentValueChanged(AdjustmentEvent e) { applet.maxcard=slider.getValue(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.justinitialized=true; applet.imageCanvas.repaint(); } // Get the choice item events here public void itemStateChanged(ItemEvent e) { if (radioDFKL.getState()==true) { applet.imageCanvas.worldminx=0.0; applet.imageCanvas.worldmaxx=1.5; applet.imageCanvas.worldminy=0.0; applet.imageCanvas.worldmaxy=1.5; applet.DF=new KullbackLeibler(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFIS.getState()==true) { applet.imageCanvas.worldminx=0.0; applet.imageCanvas.worldmaxx=4.0; applet.imageCanvas.worldminy=0.0; applet.imageCanvas.worldmaxy=4.0; applet.DF=new ItakuraSaito(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFL22.getState()==true) { applet.imageCanvas.worldminx=-0.5; applet.imageCanvas.worldmaxx=1.5; applet.imageCanvas.worldminy=-0.5; applet.imageCanvas.worldmaxy=1.5; applet.DF=new L22(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFEXP.getState()==true) { applet.imageCanvas.worldminx=-0.5; applet.imageCanvas.worldmaxx=1.5; applet.imageCanvas.worldminy=-0.5; applet.imageCanvas.worldmaxy=1.5; applet.DF=new EXP(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFCSIS.getState()==true) { applet.imageCanvas.worldminx=0.0; applet.imageCanvas.worldmaxx=2.0; applet.imageCanvas.worldminy=0.0; applet.imageCanvas.worldmaxy=2.0; applet.DF=new Csiszar(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFMAH.getState()==true) { applet.imageCanvas.worldminx=-0.5; applet.imageCanvas.worldmaxx=2; applet.imageCanvas.worldminy=-0.5; applet.imageCanvas.worldmaxy=2; applet.DF=new Mahalanobis(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } if (radioDFLL.getState()==true) { applet.imageCanvas.worldminx=0.0; applet.imageCanvas.worldmaxx=1.5; applet.imageCanvas.worldminy=0.0; applet.imageCanvas.worldmaxy=1.5; applet.DF=new LogisticLoss(); applet.dataset=new PointSet(applet.maxcard, applet.DF); applet.mini=new minibb(applet.dataset, applet.DF); applet.BD=applet.mini.bdisk; applet.justinitialized=false; applet.imageCanvas.repaint(); } } } // // 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=rand.nextDouble(); drawpoint.y=rand.nextDouble(); } array[i].x=drawpoint.x; array[i].y=drawpoint.y; } System.out.println("Point set drawn."); } } // End of point set class // // Canvas part for drawing the point set and Bregman ball // class ImageCanvas extends Canvas { MiniBBall applet; int w,h; // Dimension of the drawing canvas // // Bounding box of the world // double worldminx,worldmaxx; double worldminy,worldmaxy; BufferedImage bi; Graphics2D big; // // Convert point set coordinates to screen display coordinates // int xToX(double x) { return (int)Math.rint(w*(x-worldminx)/(worldmaxx-worldminx)); } int yToY(double y) { return h-(int)Math.rint(h*(y-worldminy)/(worldmaxy-worldminy)); } // // Convert screen display coordinates to point set coordinates // double Xtox(int X) { return worldminx+((double)X/(double)w)*(worldmaxx-worldminx); } double Ytoy(int Y) { return worldminy+((double)(h-Y)/(double)h)*(worldmaxy-worldminy); } // // Draw a geodesic // public void drawGeodesic(Graphics gg, Point source, Point dest) { int i; int xx1,yy1; int xx2,yy2; int nbsteps=10; double alpha; double increment=1.0/(double)nbsteps; for(i=0;i0 if both p<>q and p,q belongs to the domain // return <0 if p or q is out of the domain // F(p)-F(q)-DotProduct(p-q,gradF(q)) // double divergence(Point p, Point q) { Point gradFq=gradF(q); return F(p)-F(q)-((p.x-q.x)*gradFq.x+(p.y-q.y)*gradFq.y); } double Divergence(Point p, Point q) { return divergence(p,q)+divergence(q,p); } } // // The squared Euclidean distance is a usual Bregman divergence // F(x)=x^2 // class L22 extends BregmanDivergence { double Fx(double x) { return x*x; } L22() { name="squared Euclidean distance"; type=1; } // Gradient operator Point gradF(Point p) { return new Point(2*p.x,2*p.y); } // Inverse Gradient Operator Point gradFinv(Point q) { return new Point(0.5*q.x,0.5*q.y); } // Squared Euclidean distance double divergence(Point p, Point q) { return (p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y); } } // // The squared Euclidean distance is a usual Bregman divergence // F(x)=x^2 // class EXP extends BregmanDivergence { EXP() { name="Exponential distance"; } double Fx(double x) { return Math.exp(x); } // Gradient operator Point gradF(Point p) { return new Point(Math.exp(p.x),Math.exp(p.y)); } // Inverse Gradient Operator Point gradFinv(Point q) { return new Point(Math.log(q.x),Math.log(q.y)); } // Exponential Euclidean distance double divergence(Point p, Point q) { return Math.exp(p.x)-Math.exp(q.x)-(p.x-q.x)*Math.exp(q.x)+Math.exp(p.y)-Math.exp(q.y)-(p.y-q.y)*Math.exp(q.y); } } class KullbackLeibler extends BregmanDivergence { KullbackLeibler() { name="Kullback-Leibler divergence"; } double Fx(double x) { return x*Math.log(x); } // Gradient operator Point gradF(Point p) { return new Point(Math.log(p.x)+1.0, Math.log(p.y)+1.0); } // Inverse Gradient Operator Point gradFinv(Point p) { return new Point(Math.exp(p.x-1.0),Math.exp(p.y-1.0)); } // Kullback-Leibler divergence double divergence(Point p, Point q) { return p.x*Math.log(p.x/q.x)-(p.x-q.x)+p.y*Math.log(p.y/q.y)-(p.y-q.y); } } class ItakuraSaito extends BregmanDivergence { ItakuraSaito() { name="Itakura-Saito Divergence"; } 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; } } class Csiszar extends BregmanDivergence { Csiszar() { name="Csiszar Divergence (Alpha=0.5)"; } double Fx(double x) { return -4.0*Math.sqrt(x); } // Gradient operator Point gradF(Point p) { return new Point(-2.0/Math.sqrt(p.x), -2.0/Math.sqrt(p.y)); } // Inverse Gradient Operator Point gradFinv(Point p) { return new Point(4.0/(p.x*p.x), 4.0/(p.y*p.y)); } // Csiszar double divergence(Point p, Point q) { return 4.0 * (Math.sqrt(q.x) - Math.sqrt(p.x) + ((p.x-q.x) / (2.0*Math.sqrt(q.x)))) + 4.0 * (Math.sqrt(q.y) - Math.sqrt(p.y) + ((p.y-q.y) / (2.0*Math.sqrt(q.y)))) ; } } // // Logistic loss // class LogisticLoss extends BregmanDivergence { LogisticLoss() { name="Logistic Loss Divergence"; } double Fx(double x) { return x*Math.log(x)+(1.0-x)*Math.log(1.0-x); } // Gradient operator Point gradF(Point p) { return new Point( Math.log(p.x/(1.0-p.x)), Math.log(p.y/(1.0-p.y)) ); } Point gradFinv(Point p) { return new Point( Math.exp(p.x)/(1.0+Math.exp(p.x)), Math.exp(p.y)/(1.0+Math.exp(p.y))); } // Logistic loss double divergence(Point p, Point q) { return p.x*Math.log(p.x/q.x)+(1.0-p.x)*Math.log((1.0-p.x)/(1.0-q.x))+ p.y*Math.log(p.y/q.y)+(1.0-p.y)*Math.log((1.0-p.y)/(1.0-q.y)); } } // // Mahalanobis // class Mahalanobis extends BregmanDivergence { double[][]A; double s; Mahalanobis() { name="Mahalanobis Divergence"; //type=1; A=new double[2][2]; A[0][0]=0.6; A[0][1]=0.4; A[1][0]=0.1; A[1][1]=0.5; s=4.0*A[0][0]*A[1][1]-((A[0][1]+A[1][0])*(A[0][1]+A[1][0])); s=1.0/s; //A is the inverse of the covariance matrix for Mahalanobis distortion } double F(Point p) { return (A[0][0]*p.x*p.x+A[1][1]*p.y*p.y+(A[1][0]+A[0][1])*p.x*p.y); } double divergence(Point p, Point q) { Point r=new Point(p.x-q.x , p.y-q.y); return F(r); } Point gradF(Point p) { return new Point( 2.0*A[0][0]*p.x+p.y*(A[0][1]+A[1][0]) , 2.0*A[1][1]*p.y+p.x*(A[0][1]+A[1][0]) ); } Point gradFinv(Point p) { return new Point( s*(2.0*A[1][1]*p.x-(A[0][1]+A[1][0])*p.y) , s*(2.0*A[0][0]*p.y-(A[0][1]+A[1][0])*p.x ) ) ; } } // End of divergence classes // // 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; // Random generator for choosing the pivot Random random; minibb(PointSet ps, BregmanDivergence div) { set=new PointSet(ps,0); DF=div; // At most 3 points in a basis basis=new PointSet(0); random=new Random(); //bdisk=SolveDisc3(set.array[0],set.array[1],set.array[2]); //bdisk=SolveDisc2(set.array[0],set.array[1]); bdisk=MiniDisc(set,basis); System.out.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=Math.abs(random.nextInt())%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(); } }