org.openscience.cdk.graph.rebond.Bspt Maven / Gradle / Ivy
/* Copyright (C) 2003-2007 Miguel Howard
*
* Contact: [email protected]
*
* 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 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.
*
* 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.
*
* mySplitDimension = (parentSplitDimension + 1) % 3;
*
* 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 crystalline substructures can generate values which are == along
* one dimension.
*
* 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.
*
*
Three enumerators are provided
*
* - enumNear(Bspt.Tuple center, double distance)
* returns all the points contained in of all the boxes which are within
* distance from the center.
* - enumSphere(Bspt.Tuple center, double distance)
* returns all the points which are contained within the sphere (inclusive)
* defined by center + distance
* - enumHemiSphere(Bspt.Tuple center, double distance)
* same as sphere, but only the points which are greater along the
* x dimension
*
*
* @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
*/
public final class Bspt {
private final static int LEAF_COUNT = 4;
private final static int STACK_DEPTH = 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");
}
}
@Override
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[STACK_DEPTH];
sp = 0;
Element ele = eleRoot;
while (ele instanceof Node) {
Node node = (Node) ele;
if (sp == STACK_DEPTH) throw new Error("Bspt.EnumerateAll tree stack overflow");
stack[sp++] = node;
ele = node.eleLE;
}
leaf = (Leaf) ele;
i = 0;
}
@Override
public boolean hasMoreElements() {
return (i < leaf.count) || (sp > 0);
}
@Override
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[STACK_DEPTH];
sp = 0;
Element ele = eleRoot;
while (ele instanceof Node) {
Node node = (Node) ele;
if (center.getDimValue(node.dim) - distance <= node.splitValue) {
if (sp == STACK_DEPTH) throw new Error("Bspt.EnumerateNear tree stack overflow");
stack[sp++] = node;
ele = node.eleLE;
} else {
ele = node.eleGE;
}
}
leaf = (Leaf) ele;
i = 0;
}
@Override
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;
}
@Override
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[STACK_DEPTH];
sp = 0;
Element ele = eleRoot;
while (ele instanceof Node) {
Node node = (Node) ele;
if (center.getDimValue(node.dim) - distance <= node.splitValue) {
if (sp == STACK_DEPTH) 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;
}
@Override
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;
}
}
@Override
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);
}
@Override
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;
}
@Override
public String toString() {
return eleLE.toString() + dim + ":" + splitValue + "\n" + eleGE.toString();
}
@Override
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);
}
@Override
public boolean isLeafWithSpace() {
return false;
}
}
class Leaf implements Element {
int count;
Tuple[] tuples;
Leaf() {
count = 0;
tuples = new Tuple[LEAF_COUNT];
}
Leaf(Leaf leaf, int dim, double splitValue) {
this();
for (int i = LEAF_COUNT; --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 < LEAF_COUNT; ++src)
if (leaf.tuples[src] != null) leaf.tuples[dest++] = leaf.tuples[src];
leaf.count = dest;
if (count == 0) tuples[LEAF_COUNT] = null; // explode
}
public double getSplitValue(int dim) {
if (count != LEAF_COUNT) tuples[LEAF_COUNT] = null;
return (tuples[0].getDimValue(dim) + tuples[LEAF_COUNT - 1].getDimValue(dim)) / 2;
}
@Override
public String toString() {
return "leaf:" + count + "\n";
}
@Override
public boolean addTuple(Tuple tuple) {
if (count == LEAF_COUNT) return false;
tuples[count++] = tuple;
return true;
}
@Override
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));
}
}
@Override
public boolean isLeafWithSpace() {
return count < LEAF_COUNT;
}
}
}