/* $Revision$ $Author$ $Date$ * * Copyright (C) 2003-2007 Miguel Howard <miguel@jmol.org> * * Contact: cdk-devel@lists.sf.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.openscience.cdk.graph.rebond; import org.openscience.cdk.annotations.TestClass; import org.openscience.cdk.annotations.TestMethod; import java.util.Enumeration; /** * BSP-Tree stands for Binary Space Partitioning Tree. * The tree partitions n-dimensional space (in our case 3) into little * boxes, facilitating searches for things which are *nearby*. * For some useful background info, search the web for "bsp tree faq". * Our application is somewhat simpler because we are storing points instead * of polygons. * * <p>We are working with three dimensions. For the purposes of the Bspt code * these dimensions are stored as 0, 1, or 2. Each node of the tree splits * along the next dimension, wrapping around to 0. * <pre> * mySplitDimension = (parentSplitDimension + 1) % 3; * </pre> * A split value is stored in the node. Values which are <= splitValue are * stored down the left branch. Values which are >= splitValue are stored * down the right branch. When this happens, the search must proceed down * both branches. * Planar and crystaline substructures can generate values which are == along * one dimension. * * <p>To get a good picture in your head, first think about it in one dimension, * points on a number line. The tree just partitions the points. * Now think about 2 dimensions. The first node of the tree splits the plane * into two rectangles along the x dimension. The second level of the tree * splits the subplanes (independently) along the y dimension into smaller * rectangles. The third level splits along the x dimension. * In three dimensions, we are doing the same thing, only working with * 3-d boxes. * * <p>Three enumerators are provided * <ul> * <li>enumNear(Bspt.Tuple center, double distance)<br> * returns all the points contained in of all the boxes which are within * distance from the center. * <li>enumSphere(Bspt.Tuple center, double distance)<br> * returns all the points which are contained within the sphere (inclusive) * defined by center + distance * <li>enumHemiSphere(Bspt.Tuple center, double distance)<br> * same as sphere, but only the points which are greater along the * x dimension * </ul> * * @author Miguel Howard * @cdk.created 2003-05 * * @cdk.module standard * @cdk.githash * @cdk.keyword rebonding * @cdk.keyword Binary Space Partitioning Tree * @cdk.keyword join-the-dots */ @TestClass("org.openscience.cdk.graph.rebond.BsptTest") public final class Bspt { private final static int leafCount = 4; private final static int stackDepth = 64; /* this corresponds to the max height of the tree */ int dimMax; Element eleRoot; /* static double distance(int dim, Tuple t1, Tuple t2) { return Math.sqrt(distance2(dim, t1, t2)); } static double distance2(int dim, Tuple t1, Tuple t2) { double distance2 = 0.0; while (--dim >= 0) { double distT = t1.getDimValue(dim) - t2.getDimValue(dim); distance2 += distT*distT; } return distance2; } */ public Bspt(int dimMax) { this.dimMax = dimMax; this.eleRoot = new Leaf(); } public void addTuple(Tuple tuple) { if (! eleRoot.addTuple(tuple)) { eleRoot = new Node(0, dimMax, (Leaf) eleRoot); if (! eleRoot.addTuple(tuple)) throw new Error("Bspt.addTuple() failed"); } } @TestMethod("testToString") public String toString() { return eleRoot.toString(); } protected void dump() { eleRoot.dump(0); } protected Enumeration enumeration() { return new EnumerateAll(); } class EnumerateAll implements Enumeration { Node[] stack; int sp; int i; Leaf leaf; EnumerateAll() { stack = new Node[stackDepth]; sp = 0; Element ele = eleRoot; while (ele instanceof Node) { Node node = (Node) ele; if (sp == stackDepth) throw new Error("Bspt.EnumerateAll tree stack overflow"); stack[sp++] = node; ele = node.eleLE; } leaf = (Leaf)ele; i = 0; } public boolean hasMoreElements() { return (i < leaf.count) || (sp > 0); } public Object nextElement() { if (i == leaf.count) { // logger.debug("-->" + stack[sp-1].splitValue); Element ele = stack[--sp].eleGE; while (ele instanceof Node) { Node node = (Node) ele; stack[sp++] = node; ele = node.eleLE; } leaf = (Leaf)ele; i = 0; } return leaf.tuples[i++]; } } protected Enumeration enumNear(Tuple center, double distance) { return new EnumerateNear(center, distance); } class EnumerateNear implements Enumeration { Node[] stack; int sp; int i; Leaf leaf; double distance; Tuple center; EnumerateNear(Tuple center, double distance) { this.distance = distance; this.center = center; stack = new Node[stackDepth]; sp = 0; Element ele = eleRoot; while (ele instanceof Node) { Node node = (Node) ele; if (center.getDimValue(node.dim) - distance <= node.splitValue) { if (sp == stackDepth) throw new Error("Bspt.EnumerateNear tree stack overflow"); stack[sp++] = node; ele = node.eleLE; } else { ele = node.eleGE; } } leaf = (Leaf)ele; i = 0; } public boolean hasMoreElements() { if (i < leaf.count) return true; if (sp == 0) return false; Element ele = stack[--sp]; while (ele instanceof Node) { Node node = (Node) ele; if (center.getDimValue(node.dim) + distance < node.splitValue) { if (sp == 0) return false; ele = stack[--sp]; } else { ele = node.eleGE; while (ele instanceof Node) { Node nodeLeft = (Node) ele; stack[sp++] = nodeLeft; ele = nodeLeft.eleLE; } } } leaf = (Leaf)ele; i = 0; return true; } public Object nextElement() { return leaf.tuples[i++]; } } protected EnumerateSphere enumSphere(Tuple center, double distance) { return new EnumerateSphere(center, distance, false); } protected EnumerateSphere enumHemiSphere(Tuple center, double distance) { return new EnumerateSphere(center, distance, true); } class EnumerateSphere implements Enumeration { Node[] stack; int sp; int i; Leaf leaf; double distance; double distance2; Tuple center; double centerValues[]; double foundDistance2; // the dist squared of a found Element; // when set, only the hemisphere sphere .GT. or .EQ. the point // (on the first dim) is returned boolean tHemisphere; EnumerateSphere(Tuple center, double distance, boolean tHemisphere) { this.distance = distance; this.distance2 = distance*distance; this.center = center; this.tHemisphere = tHemisphere; centerValues = new double[dimMax]; for (int dim = dimMax; --dim >= 0; ) centerValues[dim] = center.getDimValue(dim); stack = new Node[stackDepth]; sp = 0; Element ele = eleRoot; while (ele instanceof Node) { Node node = (Node) ele; if (center.getDimValue(node.dim) - distance <= node.splitValue) { if (sp == stackDepth) throw new Error("Bspt.EnumerateSphere tree stack overflow"); stack[sp++] = node; ele = node.eleLE; } else { ele = node.eleGE; } } leaf = (Leaf)ele; i = 0; } private boolean isWithin(Tuple t) { double dist2; double distT; distT = t.getDimValue(0) - centerValues[0]; if (tHemisphere && distT < 0) { return false; } dist2 = distT * distT; if (dist2 > distance2) { return false; } for (int dim = dimMax; --dim > 0; ) { distT = t.getDimValue(dim) - centerValues[dim]; dist2 += distT*distT; if (dist2 > distance2) { return false; } } this.foundDistance2 = dist2; return true; } public boolean hasMoreElements() { while (true) { for ( ; i < leaf.count; ++i) if (isWithin(leaf.tuples[i])) return true; if (sp == 0) return false; Element ele = stack[--sp]; while (ele instanceof Node) { Node node = (Node) ele; if (center.getDimValue(node.dim) + distance < node.splitValue) { if (sp == 0) return false; ele = stack[--sp]; } else { ele = node.eleGE; while (ele instanceof Node) { Node nodeLeft = (Node) ele; stack[sp++] = nodeLeft; ele = nodeLeft.eleLE; } } } leaf = (Leaf)ele; i = 0; } } public Object nextElement() { return leaf.tuples[i++]; } public double foundDistance2() { return foundDistance2; } } public interface Tuple { public double getDimValue(int dim); } interface Element { boolean addTuple(Tuple tuple); void dump(int level); boolean isLeafWithSpace(); } class Node implements Element { Element eleLE; int dim; int dimMax; double splitValue; Element eleGE; Node(int dim, int dimMax, Leaf leafLE) { this.eleLE = leafLE; this.dim = dim; this.dimMax = dimMax; this.splitValue = leafLE.getSplitValue(dim); this.eleGE = new Leaf(leafLE, dim, splitValue); } public boolean addTuple(Tuple tuple) { if (tuple.getDimValue(dim) < splitValue) { if (eleLE.addTuple(tuple)) return true; eleLE = new Node((dim + 1) % dimMax, dimMax, (Leaf)eleLE); return eleLE.addTuple(tuple); } if (tuple.getDimValue(dim) > splitValue) { if (eleGE.addTuple(tuple)) return true; eleGE = new Node((dim + 1) % dimMax, dimMax, (Leaf)eleGE); return eleGE.addTuple(tuple); } if (eleLE.isLeafWithSpace()) eleLE.addTuple(tuple); else if (eleGE.isLeafWithSpace()) eleGE.addTuple(tuple); else if (eleLE instanceof Node) eleLE.addTuple(tuple); else if (eleGE instanceof Node) eleGE.addTuple(tuple); else { eleLE = new Node((dim + 1) % dimMax, dimMax, (Leaf)eleLE); return eleLE.addTuple(tuple); } return true; } public String toString() { return eleLE.toString() + dim + ":" + splitValue + "\n" + eleGE.toString(); } public void dump(int level) { System.out.println(""); eleLE.dump(level + 1); for (int i = 0; i < level; ++i) System.out.print("-"); System.out.println(">" + splitValue); eleGE.dump(level + 1); } public boolean isLeafWithSpace() { return false; } } class Leaf implements Element { int count; Tuple[] tuples; Leaf() { count = 0; tuples = new Tuple[leafCount]; } Leaf(Leaf leaf, int dim, double splitValue) { this(); for (int i = leafCount; --i >= 0; ) { Tuple tuple = leaf.tuples[i]; double value = tuple.getDimValue(dim); if (value > splitValue || (value == splitValue && ((i & 1) == 1))) { leaf.tuples[i] = null; tuples[count++] = tuple; } } int dest = 0; for (int src = 0; src < leafCount; ++src) if (leaf.tuples[src] != null) leaf.tuples[dest++] = leaf.tuples[src]; leaf.count = dest; if (count == 0) tuples[leafCount] = null; // explode } public double getSplitValue(int dim) { if (count != leafCount) tuples[leafCount] = null; return (tuples[0].getDimValue(dim) + tuples[leafCount - 1].getDimValue(dim)) / 2; } public String toString() { return "leaf:" + count + "\n"; } public boolean addTuple(Tuple tuple) { if (count == leafCount) return false; tuples[count++] = tuple; return true; } public void dump(int level) { for (int i = 0; i < count; ++i) { Tuple t = tuples[i]; for (int j = 0; j < level; ++j) System.out.print("."); for (int dim = 0; dim < dimMax-1; ++dim) System.out.print("" + t.getDimValue(dim) + ","); System.out.println("" + t.getDimValue(dimMax - 1)); } } public boolean isLeafWithSpace() { return count < leafCount; } } }