Java implementation of well-separated pair decomposition












2












$begingroup$


I was trying to implement the algorithm of the 3D well-separated pair decomposition WSPD using an octree.



First, I begin by implementing the class OctreeNode as follows:



public class OctreeNode {

public static final int BSW = 0;
public static final int BSE = 1;
public static final int BNW = 2;
public static final int BNE = 3;
public static final int ASW = 4;
public static final int ASE = 5;
public static final int ANW = 6;
public static final int ANE = 7;

public int level = 0; // level set to 0.
public OctreeNode children = null;
public OctreeNode father = null; // father is null
public Point_3 p = null; //point stored in a leaf
public Point_3 center = null; // point to define the center of the box of the node.
public double height = 0.0, width = 0.0, depth = 0.0;


Every non-leaf Node has exactly 8 children. I implement a constructor for the class which takes a list of points in 3D.



/**
* Create the octree for storing an input point cloud.
*/
public OctreeNode(List<Point_3> points) {
// We construct the octree only for non empty point cloud.
assert(points.size() > 0);
/**
* We start by computing a box containing the point cloud.
*/
// The minimum value of x,y,z in the point cloud.
double minX = points.get(0).getX().doubleValue();
double minY = points.get(0).getY().doubleValue();
double minZ = points.get(0).getZ().doubleValue();

// The maximum value of x,y,z in the point cloud.
double maxX = points.get(0).getX().doubleValue();
double maxY = points.get(0).getY().doubleValue();
double maxZ = points.get(0).getZ().doubleValue();


for(Point_3 point : points) {

// update the minimum.
if(minX > point.getX().doubleValue()) {
minX = point.getX().doubleValue();
}

// update the minimum.
if(minY > point.getY().doubleValue()) {
minY = point.getY().doubleValue();
}

// update the minimum.
if(minZ > point.getZ().doubleValue()) {
minZ = point.getZ().doubleValue();
}

// update the maximum.
if(maxX < point.getX().doubleValue()) {
maxX = point.getX().doubleValue();
}

// update the maximum.
if(maxY < point.getY().doubleValue()) {
maxY = point.getY().doubleValue();
}

// update the maximum.
if(maxZ < point.getZ().doubleValue()) {
maxZ = point.getZ().doubleValue();
}
}

this.center = new Point_3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
this.width = 0.75*(maxX - minX);
this.height = 0.75*(maxY - minY);
this.depth = 0.75*(maxZ - minZ);

for(Point_3 point : points) {
this.add(point);
}

}


After, I implement the add function of my class:



/**
* @return true if the current node is a leaf.
*/
public boolean isLeaf() {
return (this.children == null);
}
/**
* @return true if the current node is empty.
*/
public boolean isEmpty() {
return (this.children == null && this.p == null);
}

/**
* @return true if the current node is single point.
*/
public boolean isSinglePoint() {
return (this.children == null && this.p != null);
}

/**
* @param o an Object.
* @return true if the current node is equals to o
*/
@Override
public boolean equals(Object o) {
OctreeNode n = (OctreeNode) o;
return (n != null) && (this.center.equals(n.center));
}

/**
* @return the diameter of the node : 0 if is empty or single point and the diameter of the box otherwise.
*/
public double diam() {
if(this.isLeaf()) {
return 0.0;
}
return 2*Math.sqrt(this.width*this.width
+ this.height*this.height
+ this.depth*this.depth);
}
/**
* Check if the point is in the boundary of the OctreeNode
* @param p a Point_3.
* @return true if p is in the cube of the OctreeNode
*/
public boolean inBoundary(Point_3 p) {
Vector_3 v = (Vector_3) this.center.minus(p);
return (Math.abs(v.getX().doubleValue()) <= this.width && Math.abs(v.getY().doubleValue()) <= this.height && Math.abs(v.getZ().doubleValue()) <= this.depth);
}
/**
* Add a node into the OctreeNode
*/

public void add(Point_3 p) {
assert(this.center != null);
if(!this.inBoundary(p))
return;
// Check if the current node is a leaf.
if(this.isLeaf()) {
// Check if the current node is empty.
if(this.isEmpty()) {
this.p = p;
return;
}
else {
// Check if the node contains the same point already.
if(this.p.equals(p)) {
return;
}
// The current node contains only one point and the new point
// is different from the point of the current OctreeNode.
// We create the set of children for the current OctreeNode.
this.children = new OctreeNode[8];

// Initialize the children.
for(int i = 0; i < 8; i++) {
this.children[i] = new OctreeNode();
}

// For all children we put the current OctreeNode as father and
// we increment the level.
for(OctreeNode child : this.children) {
child.father = this;
child.level = this.level + 1;
}

// We compute then the center points for every child

Vector_3 vi = new Vector_3(this.width / 2, 0.0, 0.0);
Vector_3 vj = new Vector_3(0.0, this.height / 2, 0.0);
Vector_3 vk = new Vector_3(0.0, 0.0, this.depth / 2);

this.children[BSW].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi.opposite());
this.children[BSE].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi);
this.children[BNW].center = this.center.sum(vk.opposite()).sum(vj).sum(vi.opposite());
this.children[BNE].center = this.center.sum(vk.opposite()).sum(vj).sum(vi);
this.children[ASW].center = this.center.sum(vk).sum(vj.opposite()).sum(vi.opposite());
this.children[ASE].center = this.center.sum(vk).sum(vj.opposite()).sum(vi);
this.children[ANW].center = this.center.sum(vk).sum(vj).sum(vi.opposite());
this.children[ANE].center = this.center.sum(vk).sum(vj).sum(vi);

// We put half of the dimensions of the cube for every child.
for(OctreeNode child : children) {
child.width = this.width / 2;
child.depth = this.depth / 2;
child.height = this.height / 2;
}

// Look for the child to add the point of the current OctreeNode.
for(OctreeNode child : children) {
if(child.inBoundary(this.p)) {
child.add(this.p);
break;
}
}

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
// this.p = null;
}
}
else {

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
}
}


I also implement a function distance to compute the distance between two nodes in the Octree which is the distance between the two boxes of the nodes:



/**
* @param o an OctreeNode.
* @param u an OctreeNode.
* @return the distance between the two nodes.
*/
public static double distance(OctreeNode o, OctreeNode u) {
if(o == null || o.isEmpty() || u == null || u.isEmpty()) {
return 0.0;
}
if(u.isLeaf() && o.isLeaf()) {
return u.p.distanceFrom(o.p).doubleValue();
}
double x = 0.0;
double y = 0.0;
double z = 0.0;
Vector_3 v;
if(u.isLeaf()) {
v = (Vector_3) u.p.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
if(o.isLeaf()) {
return distance(u, o);
}
v = (Vector_3) u.center.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
}


Now, I implement the class Octree representing and Octree



public class Octree {
public OctreeNode root;

/**
* Create an octree from the array points.
*/
public Octree(Point_3 points){
/**
* We use the constructor provided by the class OctreeNode to
* construct the root.
*/
this.root = new OctreeNode(Arrays.asList(points));
}
}


Here I have a question. Do you think my implementation is the best way to do it?



After this part I implement the well-separated pair decomposition class:



public class WellSeparatedPairDecomposition {
class Pair<X,Y> {
X x; Y y;
Pair(X xx, Y yy) {
x = xx;
y = yy;
}
public X getFirst() {
return x;
}
public Y getSecond() {
return y;
}

@Override
public boolean equals(Object o) {
Pair<X,Y> p = (Pair<X,Y>) o;
return (p!=null && p.getFirst() == x && p.getSecond() == y);
}
}
/**
* Compute the WSPD from an octree and a threshold s.
* @param T the Octree,
* @param s threshold.
* @return the pairs of WSPD.
*/
public OctreeNode wspd (Octree T, double epsilon) {
Set<Pair<OctreeNode, OctreeNode>> H = new HashSet<Pair<OctreeNode, OctreeNode>>();

wspd(T.root, T.root, epsilon, H);

int n = H.size();
OctreeNode result = new OctreeNode[n][2];
int i = 0;
for(Pair<OctreeNode, OctreeNode> pair : H) {
result[i][0] = pair.getFirst();
result[i][1] = pair.getSecond();
i++;
}
return result;
}

boolean isWS(OctreeNode u, OctreeNode v, double epsilon) {
if(u == null || v == null || u.isEmpty() || v.isEmpty()) {
return false;
}
double distance = OctreeNode.distance(u, v);
return (u.diam() < epsilon*distance && v.diam() < epsilon*distance);
}

void wspd(OctreeNode u, OctreeNode v, double epsilon, Set<Pair<OctreeNode, OctreeNode>> H) {
if(u.isEmpty() || v.isEmpty() || (v.isLeaf() && u.isLeaf() && v.equals(u)))
return;
if(isWS(u, v, epsilon)) {
H.add(new Pair<OctreeNode, OctreeNode>(u, v));
return;
}
if(u.level > v.level) {
OctreeNode temp = u;
u = v;
v = temp;
}
if(!u.isLeaf()) {
for(OctreeNode uchild : u.children) {
wspd(uchild, v, epsilon, H);
}
}
}
}


My implementation works without errors. However, when I tested, it is very slow and for big size tests it gives an outOfMemoryError. How can I improve this implementation?










share|improve this question











$endgroup$




bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 1




    $begingroup$
    The lack of indentation makes parts of the code hard to follow.
    $endgroup$
    – 1201ProgramAlarm
    Dec 21 '18 at 21:23










  • $begingroup$
    I think I corrected it. Thank you
    $endgroup$
    – Youem
    Dec 21 '18 at 21:47
















2












$begingroup$


I was trying to implement the algorithm of the 3D well-separated pair decomposition WSPD using an octree.



First, I begin by implementing the class OctreeNode as follows:



public class OctreeNode {

public static final int BSW = 0;
public static final int BSE = 1;
public static final int BNW = 2;
public static final int BNE = 3;
public static final int ASW = 4;
public static final int ASE = 5;
public static final int ANW = 6;
public static final int ANE = 7;

public int level = 0; // level set to 0.
public OctreeNode children = null;
public OctreeNode father = null; // father is null
public Point_3 p = null; //point stored in a leaf
public Point_3 center = null; // point to define the center of the box of the node.
public double height = 0.0, width = 0.0, depth = 0.0;


Every non-leaf Node has exactly 8 children. I implement a constructor for the class which takes a list of points in 3D.



/**
* Create the octree for storing an input point cloud.
*/
public OctreeNode(List<Point_3> points) {
// We construct the octree only for non empty point cloud.
assert(points.size() > 0);
/**
* We start by computing a box containing the point cloud.
*/
// The minimum value of x,y,z in the point cloud.
double minX = points.get(0).getX().doubleValue();
double minY = points.get(0).getY().doubleValue();
double minZ = points.get(0).getZ().doubleValue();

// The maximum value of x,y,z in the point cloud.
double maxX = points.get(0).getX().doubleValue();
double maxY = points.get(0).getY().doubleValue();
double maxZ = points.get(0).getZ().doubleValue();


for(Point_3 point : points) {

// update the minimum.
if(minX > point.getX().doubleValue()) {
minX = point.getX().doubleValue();
}

// update the minimum.
if(minY > point.getY().doubleValue()) {
minY = point.getY().doubleValue();
}

// update the minimum.
if(minZ > point.getZ().doubleValue()) {
minZ = point.getZ().doubleValue();
}

// update the maximum.
if(maxX < point.getX().doubleValue()) {
maxX = point.getX().doubleValue();
}

// update the maximum.
if(maxY < point.getY().doubleValue()) {
maxY = point.getY().doubleValue();
}

// update the maximum.
if(maxZ < point.getZ().doubleValue()) {
maxZ = point.getZ().doubleValue();
}
}

this.center = new Point_3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
this.width = 0.75*(maxX - minX);
this.height = 0.75*(maxY - minY);
this.depth = 0.75*(maxZ - minZ);

for(Point_3 point : points) {
this.add(point);
}

}


After, I implement the add function of my class:



/**
* @return true if the current node is a leaf.
*/
public boolean isLeaf() {
return (this.children == null);
}
/**
* @return true if the current node is empty.
*/
public boolean isEmpty() {
return (this.children == null && this.p == null);
}

/**
* @return true if the current node is single point.
*/
public boolean isSinglePoint() {
return (this.children == null && this.p != null);
}

/**
* @param o an Object.
* @return true if the current node is equals to o
*/
@Override
public boolean equals(Object o) {
OctreeNode n = (OctreeNode) o;
return (n != null) && (this.center.equals(n.center));
}

/**
* @return the diameter of the node : 0 if is empty or single point and the diameter of the box otherwise.
*/
public double diam() {
if(this.isLeaf()) {
return 0.0;
}
return 2*Math.sqrt(this.width*this.width
+ this.height*this.height
+ this.depth*this.depth);
}
/**
* Check if the point is in the boundary of the OctreeNode
* @param p a Point_3.
* @return true if p is in the cube of the OctreeNode
*/
public boolean inBoundary(Point_3 p) {
Vector_3 v = (Vector_3) this.center.minus(p);
return (Math.abs(v.getX().doubleValue()) <= this.width && Math.abs(v.getY().doubleValue()) <= this.height && Math.abs(v.getZ().doubleValue()) <= this.depth);
}
/**
* Add a node into the OctreeNode
*/

public void add(Point_3 p) {
assert(this.center != null);
if(!this.inBoundary(p))
return;
// Check if the current node is a leaf.
if(this.isLeaf()) {
// Check if the current node is empty.
if(this.isEmpty()) {
this.p = p;
return;
}
else {
// Check if the node contains the same point already.
if(this.p.equals(p)) {
return;
}
// The current node contains only one point and the new point
// is different from the point of the current OctreeNode.
// We create the set of children for the current OctreeNode.
this.children = new OctreeNode[8];

// Initialize the children.
for(int i = 0; i < 8; i++) {
this.children[i] = new OctreeNode();
}

// For all children we put the current OctreeNode as father and
// we increment the level.
for(OctreeNode child : this.children) {
child.father = this;
child.level = this.level + 1;
}

// We compute then the center points for every child

Vector_3 vi = new Vector_3(this.width / 2, 0.0, 0.0);
Vector_3 vj = new Vector_3(0.0, this.height / 2, 0.0);
Vector_3 vk = new Vector_3(0.0, 0.0, this.depth / 2);

this.children[BSW].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi.opposite());
this.children[BSE].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi);
this.children[BNW].center = this.center.sum(vk.opposite()).sum(vj).sum(vi.opposite());
this.children[BNE].center = this.center.sum(vk.opposite()).sum(vj).sum(vi);
this.children[ASW].center = this.center.sum(vk).sum(vj.opposite()).sum(vi.opposite());
this.children[ASE].center = this.center.sum(vk).sum(vj.opposite()).sum(vi);
this.children[ANW].center = this.center.sum(vk).sum(vj).sum(vi.opposite());
this.children[ANE].center = this.center.sum(vk).sum(vj).sum(vi);

// We put half of the dimensions of the cube for every child.
for(OctreeNode child : children) {
child.width = this.width / 2;
child.depth = this.depth / 2;
child.height = this.height / 2;
}

// Look for the child to add the point of the current OctreeNode.
for(OctreeNode child : children) {
if(child.inBoundary(this.p)) {
child.add(this.p);
break;
}
}

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
// this.p = null;
}
}
else {

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
}
}


I also implement a function distance to compute the distance between two nodes in the Octree which is the distance between the two boxes of the nodes:



/**
* @param o an OctreeNode.
* @param u an OctreeNode.
* @return the distance between the two nodes.
*/
public static double distance(OctreeNode o, OctreeNode u) {
if(o == null || o.isEmpty() || u == null || u.isEmpty()) {
return 0.0;
}
if(u.isLeaf() && o.isLeaf()) {
return u.p.distanceFrom(o.p).doubleValue();
}
double x = 0.0;
double y = 0.0;
double z = 0.0;
Vector_3 v;
if(u.isLeaf()) {
v = (Vector_3) u.p.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
if(o.isLeaf()) {
return distance(u, o);
}
v = (Vector_3) u.center.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
}


Now, I implement the class Octree representing and Octree



public class Octree {
public OctreeNode root;

/**
* Create an octree from the array points.
*/
public Octree(Point_3 points){
/**
* We use the constructor provided by the class OctreeNode to
* construct the root.
*/
this.root = new OctreeNode(Arrays.asList(points));
}
}


Here I have a question. Do you think my implementation is the best way to do it?



After this part I implement the well-separated pair decomposition class:



public class WellSeparatedPairDecomposition {
class Pair<X,Y> {
X x; Y y;
Pair(X xx, Y yy) {
x = xx;
y = yy;
}
public X getFirst() {
return x;
}
public Y getSecond() {
return y;
}

@Override
public boolean equals(Object o) {
Pair<X,Y> p = (Pair<X,Y>) o;
return (p!=null && p.getFirst() == x && p.getSecond() == y);
}
}
/**
* Compute the WSPD from an octree and a threshold s.
* @param T the Octree,
* @param s threshold.
* @return the pairs of WSPD.
*/
public OctreeNode wspd (Octree T, double epsilon) {
Set<Pair<OctreeNode, OctreeNode>> H = new HashSet<Pair<OctreeNode, OctreeNode>>();

wspd(T.root, T.root, epsilon, H);

int n = H.size();
OctreeNode result = new OctreeNode[n][2];
int i = 0;
for(Pair<OctreeNode, OctreeNode> pair : H) {
result[i][0] = pair.getFirst();
result[i][1] = pair.getSecond();
i++;
}
return result;
}

boolean isWS(OctreeNode u, OctreeNode v, double epsilon) {
if(u == null || v == null || u.isEmpty() || v.isEmpty()) {
return false;
}
double distance = OctreeNode.distance(u, v);
return (u.diam() < epsilon*distance && v.diam() < epsilon*distance);
}

void wspd(OctreeNode u, OctreeNode v, double epsilon, Set<Pair<OctreeNode, OctreeNode>> H) {
if(u.isEmpty() || v.isEmpty() || (v.isLeaf() && u.isLeaf() && v.equals(u)))
return;
if(isWS(u, v, epsilon)) {
H.add(new Pair<OctreeNode, OctreeNode>(u, v));
return;
}
if(u.level > v.level) {
OctreeNode temp = u;
u = v;
v = temp;
}
if(!u.isLeaf()) {
for(OctreeNode uchild : u.children) {
wspd(uchild, v, epsilon, H);
}
}
}
}


My implementation works without errors. However, when I tested, it is very slow and for big size tests it gives an outOfMemoryError. How can I improve this implementation?










share|improve this question











$endgroup$




bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.











  • 1




    $begingroup$
    The lack of indentation makes parts of the code hard to follow.
    $endgroup$
    – 1201ProgramAlarm
    Dec 21 '18 at 21:23










  • $begingroup$
    I think I corrected it. Thank you
    $endgroup$
    – Youem
    Dec 21 '18 at 21:47














2












2








2


1



$begingroup$


I was trying to implement the algorithm of the 3D well-separated pair decomposition WSPD using an octree.



First, I begin by implementing the class OctreeNode as follows:



public class OctreeNode {

public static final int BSW = 0;
public static final int BSE = 1;
public static final int BNW = 2;
public static final int BNE = 3;
public static final int ASW = 4;
public static final int ASE = 5;
public static final int ANW = 6;
public static final int ANE = 7;

public int level = 0; // level set to 0.
public OctreeNode children = null;
public OctreeNode father = null; // father is null
public Point_3 p = null; //point stored in a leaf
public Point_3 center = null; // point to define the center of the box of the node.
public double height = 0.0, width = 0.0, depth = 0.0;


Every non-leaf Node has exactly 8 children. I implement a constructor for the class which takes a list of points in 3D.



/**
* Create the octree for storing an input point cloud.
*/
public OctreeNode(List<Point_3> points) {
// We construct the octree only for non empty point cloud.
assert(points.size() > 0);
/**
* We start by computing a box containing the point cloud.
*/
// The minimum value of x,y,z in the point cloud.
double minX = points.get(0).getX().doubleValue();
double minY = points.get(0).getY().doubleValue();
double minZ = points.get(0).getZ().doubleValue();

// The maximum value of x,y,z in the point cloud.
double maxX = points.get(0).getX().doubleValue();
double maxY = points.get(0).getY().doubleValue();
double maxZ = points.get(0).getZ().doubleValue();


for(Point_3 point : points) {

// update the minimum.
if(minX > point.getX().doubleValue()) {
minX = point.getX().doubleValue();
}

// update the minimum.
if(minY > point.getY().doubleValue()) {
minY = point.getY().doubleValue();
}

// update the minimum.
if(minZ > point.getZ().doubleValue()) {
minZ = point.getZ().doubleValue();
}

// update the maximum.
if(maxX < point.getX().doubleValue()) {
maxX = point.getX().doubleValue();
}

// update the maximum.
if(maxY < point.getY().doubleValue()) {
maxY = point.getY().doubleValue();
}

// update the maximum.
if(maxZ < point.getZ().doubleValue()) {
maxZ = point.getZ().doubleValue();
}
}

this.center = new Point_3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
this.width = 0.75*(maxX - minX);
this.height = 0.75*(maxY - minY);
this.depth = 0.75*(maxZ - minZ);

for(Point_3 point : points) {
this.add(point);
}

}


After, I implement the add function of my class:



/**
* @return true if the current node is a leaf.
*/
public boolean isLeaf() {
return (this.children == null);
}
/**
* @return true if the current node is empty.
*/
public boolean isEmpty() {
return (this.children == null && this.p == null);
}

/**
* @return true if the current node is single point.
*/
public boolean isSinglePoint() {
return (this.children == null && this.p != null);
}

/**
* @param o an Object.
* @return true if the current node is equals to o
*/
@Override
public boolean equals(Object o) {
OctreeNode n = (OctreeNode) o;
return (n != null) && (this.center.equals(n.center));
}

/**
* @return the diameter of the node : 0 if is empty or single point and the diameter of the box otherwise.
*/
public double diam() {
if(this.isLeaf()) {
return 0.0;
}
return 2*Math.sqrt(this.width*this.width
+ this.height*this.height
+ this.depth*this.depth);
}
/**
* Check if the point is in the boundary of the OctreeNode
* @param p a Point_3.
* @return true if p is in the cube of the OctreeNode
*/
public boolean inBoundary(Point_3 p) {
Vector_3 v = (Vector_3) this.center.minus(p);
return (Math.abs(v.getX().doubleValue()) <= this.width && Math.abs(v.getY().doubleValue()) <= this.height && Math.abs(v.getZ().doubleValue()) <= this.depth);
}
/**
* Add a node into the OctreeNode
*/

public void add(Point_3 p) {
assert(this.center != null);
if(!this.inBoundary(p))
return;
// Check if the current node is a leaf.
if(this.isLeaf()) {
// Check if the current node is empty.
if(this.isEmpty()) {
this.p = p;
return;
}
else {
// Check if the node contains the same point already.
if(this.p.equals(p)) {
return;
}
// The current node contains only one point and the new point
// is different from the point of the current OctreeNode.
// We create the set of children for the current OctreeNode.
this.children = new OctreeNode[8];

// Initialize the children.
for(int i = 0; i < 8; i++) {
this.children[i] = new OctreeNode();
}

// For all children we put the current OctreeNode as father and
// we increment the level.
for(OctreeNode child : this.children) {
child.father = this;
child.level = this.level + 1;
}

// We compute then the center points for every child

Vector_3 vi = new Vector_3(this.width / 2, 0.0, 0.0);
Vector_3 vj = new Vector_3(0.0, this.height / 2, 0.0);
Vector_3 vk = new Vector_3(0.0, 0.0, this.depth / 2);

this.children[BSW].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi.opposite());
this.children[BSE].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi);
this.children[BNW].center = this.center.sum(vk.opposite()).sum(vj).sum(vi.opposite());
this.children[BNE].center = this.center.sum(vk.opposite()).sum(vj).sum(vi);
this.children[ASW].center = this.center.sum(vk).sum(vj.opposite()).sum(vi.opposite());
this.children[ASE].center = this.center.sum(vk).sum(vj.opposite()).sum(vi);
this.children[ANW].center = this.center.sum(vk).sum(vj).sum(vi.opposite());
this.children[ANE].center = this.center.sum(vk).sum(vj).sum(vi);

// We put half of the dimensions of the cube for every child.
for(OctreeNode child : children) {
child.width = this.width / 2;
child.depth = this.depth / 2;
child.height = this.height / 2;
}

// Look for the child to add the point of the current OctreeNode.
for(OctreeNode child : children) {
if(child.inBoundary(this.p)) {
child.add(this.p);
break;
}
}

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
// this.p = null;
}
}
else {

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
}
}


I also implement a function distance to compute the distance between two nodes in the Octree which is the distance between the two boxes of the nodes:



/**
* @param o an OctreeNode.
* @param u an OctreeNode.
* @return the distance between the two nodes.
*/
public static double distance(OctreeNode o, OctreeNode u) {
if(o == null || o.isEmpty() || u == null || u.isEmpty()) {
return 0.0;
}
if(u.isLeaf() && o.isLeaf()) {
return u.p.distanceFrom(o.p).doubleValue();
}
double x = 0.0;
double y = 0.0;
double z = 0.0;
Vector_3 v;
if(u.isLeaf()) {
v = (Vector_3) u.p.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
if(o.isLeaf()) {
return distance(u, o);
}
v = (Vector_3) u.center.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
}


Now, I implement the class Octree representing and Octree



public class Octree {
public OctreeNode root;

/**
* Create an octree from the array points.
*/
public Octree(Point_3 points){
/**
* We use the constructor provided by the class OctreeNode to
* construct the root.
*/
this.root = new OctreeNode(Arrays.asList(points));
}
}


Here I have a question. Do you think my implementation is the best way to do it?



After this part I implement the well-separated pair decomposition class:



public class WellSeparatedPairDecomposition {
class Pair<X,Y> {
X x; Y y;
Pair(X xx, Y yy) {
x = xx;
y = yy;
}
public X getFirst() {
return x;
}
public Y getSecond() {
return y;
}

@Override
public boolean equals(Object o) {
Pair<X,Y> p = (Pair<X,Y>) o;
return (p!=null && p.getFirst() == x && p.getSecond() == y);
}
}
/**
* Compute the WSPD from an octree and a threshold s.
* @param T the Octree,
* @param s threshold.
* @return the pairs of WSPD.
*/
public OctreeNode wspd (Octree T, double epsilon) {
Set<Pair<OctreeNode, OctreeNode>> H = new HashSet<Pair<OctreeNode, OctreeNode>>();

wspd(T.root, T.root, epsilon, H);

int n = H.size();
OctreeNode result = new OctreeNode[n][2];
int i = 0;
for(Pair<OctreeNode, OctreeNode> pair : H) {
result[i][0] = pair.getFirst();
result[i][1] = pair.getSecond();
i++;
}
return result;
}

boolean isWS(OctreeNode u, OctreeNode v, double epsilon) {
if(u == null || v == null || u.isEmpty() || v.isEmpty()) {
return false;
}
double distance = OctreeNode.distance(u, v);
return (u.diam() < epsilon*distance && v.diam() < epsilon*distance);
}

void wspd(OctreeNode u, OctreeNode v, double epsilon, Set<Pair<OctreeNode, OctreeNode>> H) {
if(u.isEmpty() || v.isEmpty() || (v.isLeaf() && u.isLeaf() && v.equals(u)))
return;
if(isWS(u, v, epsilon)) {
H.add(new Pair<OctreeNode, OctreeNode>(u, v));
return;
}
if(u.level > v.level) {
OctreeNode temp = u;
u = v;
v = temp;
}
if(!u.isLeaf()) {
for(OctreeNode uchild : u.children) {
wspd(uchild, v, epsilon, H);
}
}
}
}


My implementation works without errors. However, when I tested, it is very slow and for big size tests it gives an outOfMemoryError. How can I improve this implementation?










share|improve this question











$endgroup$




I was trying to implement the algorithm of the 3D well-separated pair decomposition WSPD using an octree.



First, I begin by implementing the class OctreeNode as follows:



public class OctreeNode {

public static final int BSW = 0;
public static final int BSE = 1;
public static final int BNW = 2;
public static final int BNE = 3;
public static final int ASW = 4;
public static final int ASE = 5;
public static final int ANW = 6;
public static final int ANE = 7;

public int level = 0; // level set to 0.
public OctreeNode children = null;
public OctreeNode father = null; // father is null
public Point_3 p = null; //point stored in a leaf
public Point_3 center = null; // point to define the center of the box of the node.
public double height = 0.0, width = 0.0, depth = 0.0;


Every non-leaf Node has exactly 8 children. I implement a constructor for the class which takes a list of points in 3D.



/**
* Create the octree for storing an input point cloud.
*/
public OctreeNode(List<Point_3> points) {
// We construct the octree only for non empty point cloud.
assert(points.size() > 0);
/**
* We start by computing a box containing the point cloud.
*/
// The minimum value of x,y,z in the point cloud.
double minX = points.get(0).getX().doubleValue();
double minY = points.get(0).getY().doubleValue();
double minZ = points.get(0).getZ().doubleValue();

// The maximum value of x,y,z in the point cloud.
double maxX = points.get(0).getX().doubleValue();
double maxY = points.get(0).getY().doubleValue();
double maxZ = points.get(0).getZ().doubleValue();


for(Point_3 point : points) {

// update the minimum.
if(minX > point.getX().doubleValue()) {
minX = point.getX().doubleValue();
}

// update the minimum.
if(minY > point.getY().doubleValue()) {
minY = point.getY().doubleValue();
}

// update the minimum.
if(minZ > point.getZ().doubleValue()) {
minZ = point.getZ().doubleValue();
}

// update the maximum.
if(maxX < point.getX().doubleValue()) {
maxX = point.getX().doubleValue();
}

// update the maximum.
if(maxY < point.getY().doubleValue()) {
maxY = point.getY().doubleValue();
}

// update the maximum.
if(maxZ < point.getZ().doubleValue()) {
maxZ = point.getZ().doubleValue();
}
}

this.center = new Point_3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
this.width = 0.75*(maxX - minX);
this.height = 0.75*(maxY - minY);
this.depth = 0.75*(maxZ - minZ);

for(Point_3 point : points) {
this.add(point);
}

}


After, I implement the add function of my class:



/**
* @return true if the current node is a leaf.
*/
public boolean isLeaf() {
return (this.children == null);
}
/**
* @return true if the current node is empty.
*/
public boolean isEmpty() {
return (this.children == null && this.p == null);
}

/**
* @return true if the current node is single point.
*/
public boolean isSinglePoint() {
return (this.children == null && this.p != null);
}

/**
* @param o an Object.
* @return true if the current node is equals to o
*/
@Override
public boolean equals(Object o) {
OctreeNode n = (OctreeNode) o;
return (n != null) && (this.center.equals(n.center));
}

/**
* @return the diameter of the node : 0 if is empty or single point and the diameter of the box otherwise.
*/
public double diam() {
if(this.isLeaf()) {
return 0.0;
}
return 2*Math.sqrt(this.width*this.width
+ this.height*this.height
+ this.depth*this.depth);
}
/**
* Check if the point is in the boundary of the OctreeNode
* @param p a Point_3.
* @return true if p is in the cube of the OctreeNode
*/
public boolean inBoundary(Point_3 p) {
Vector_3 v = (Vector_3) this.center.minus(p);
return (Math.abs(v.getX().doubleValue()) <= this.width && Math.abs(v.getY().doubleValue()) <= this.height && Math.abs(v.getZ().doubleValue()) <= this.depth);
}
/**
* Add a node into the OctreeNode
*/

public void add(Point_3 p) {
assert(this.center != null);
if(!this.inBoundary(p))
return;
// Check if the current node is a leaf.
if(this.isLeaf()) {
// Check if the current node is empty.
if(this.isEmpty()) {
this.p = p;
return;
}
else {
// Check if the node contains the same point already.
if(this.p.equals(p)) {
return;
}
// The current node contains only one point and the new point
// is different from the point of the current OctreeNode.
// We create the set of children for the current OctreeNode.
this.children = new OctreeNode[8];

// Initialize the children.
for(int i = 0; i < 8; i++) {
this.children[i] = new OctreeNode();
}

// For all children we put the current OctreeNode as father and
// we increment the level.
for(OctreeNode child : this.children) {
child.father = this;
child.level = this.level + 1;
}

// We compute then the center points for every child

Vector_3 vi = new Vector_3(this.width / 2, 0.0, 0.0);
Vector_3 vj = new Vector_3(0.0, this.height / 2, 0.0);
Vector_3 vk = new Vector_3(0.0, 0.0, this.depth / 2);

this.children[BSW].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi.opposite());
this.children[BSE].center = this.center.sum(vk.opposite()).sum(vj.opposite()).sum(vi);
this.children[BNW].center = this.center.sum(vk.opposite()).sum(vj).sum(vi.opposite());
this.children[BNE].center = this.center.sum(vk.opposite()).sum(vj).sum(vi);
this.children[ASW].center = this.center.sum(vk).sum(vj.opposite()).sum(vi.opposite());
this.children[ASE].center = this.center.sum(vk).sum(vj.opposite()).sum(vi);
this.children[ANW].center = this.center.sum(vk).sum(vj).sum(vi.opposite());
this.children[ANE].center = this.center.sum(vk).sum(vj).sum(vi);

// We put half of the dimensions of the cube for every child.
for(OctreeNode child : children) {
child.width = this.width / 2;
child.depth = this.depth / 2;
child.height = this.height / 2;
}

// Look for the child to add the point of the current OctreeNode.
for(OctreeNode child : children) {
if(child.inBoundary(this.p)) {
child.add(this.p);
break;
}
}

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
// this.p = null;
}
}
else {

// Look for the child to add the point p.
for(OctreeNode child : children) {
if(child.inBoundary(p)) {
child.add(p);
break;
}
}
}
}


I also implement a function distance to compute the distance between two nodes in the Octree which is the distance between the two boxes of the nodes:



/**
* @param o an OctreeNode.
* @param u an OctreeNode.
* @return the distance between the two nodes.
*/
public static double distance(OctreeNode o, OctreeNode u) {
if(o == null || o.isEmpty() || u == null || u.isEmpty()) {
return 0.0;
}
if(u.isLeaf() && o.isLeaf()) {
return u.p.distanceFrom(o.p).doubleValue();
}
double x = 0.0;
double y = 0.0;
double z = 0.0;
Vector_3 v;
if(u.isLeaf()) {
v = (Vector_3) u.p.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
if(o.isLeaf()) {
return distance(u, o);
}
v = (Vector_3) u.center.minus(o.center);
x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
return Math.sqrt(x*x + y*y + z*z);
}
}


Now, I implement the class Octree representing and Octree



public class Octree {
public OctreeNode root;

/**
* Create an octree from the array points.
*/
public Octree(Point_3 points){
/**
* We use the constructor provided by the class OctreeNode to
* construct the root.
*/
this.root = new OctreeNode(Arrays.asList(points));
}
}


Here I have a question. Do you think my implementation is the best way to do it?



After this part I implement the well-separated pair decomposition class:



public class WellSeparatedPairDecomposition {
class Pair<X,Y> {
X x; Y y;
Pair(X xx, Y yy) {
x = xx;
y = yy;
}
public X getFirst() {
return x;
}
public Y getSecond() {
return y;
}

@Override
public boolean equals(Object o) {
Pair<X,Y> p = (Pair<X,Y>) o;
return (p!=null && p.getFirst() == x && p.getSecond() == y);
}
}
/**
* Compute the WSPD from an octree and a threshold s.
* @param T the Octree,
* @param s threshold.
* @return the pairs of WSPD.
*/
public OctreeNode wspd (Octree T, double epsilon) {
Set<Pair<OctreeNode, OctreeNode>> H = new HashSet<Pair<OctreeNode, OctreeNode>>();

wspd(T.root, T.root, epsilon, H);

int n = H.size();
OctreeNode result = new OctreeNode[n][2];
int i = 0;
for(Pair<OctreeNode, OctreeNode> pair : H) {
result[i][0] = pair.getFirst();
result[i][1] = pair.getSecond();
i++;
}
return result;
}

boolean isWS(OctreeNode u, OctreeNode v, double epsilon) {
if(u == null || v == null || u.isEmpty() || v.isEmpty()) {
return false;
}
double distance = OctreeNode.distance(u, v);
return (u.diam() < epsilon*distance && v.diam() < epsilon*distance);
}

void wspd(OctreeNode u, OctreeNode v, double epsilon, Set<Pair<OctreeNode, OctreeNode>> H) {
if(u.isEmpty() || v.isEmpty() || (v.isLeaf() && u.isLeaf() && v.equals(u)))
return;
if(isWS(u, v, epsilon)) {
H.add(new Pair<OctreeNode, OctreeNode>(u, v));
return;
}
if(u.level > v.level) {
OctreeNode temp = u;
u = v;
v = temp;
}
if(!u.isLeaf()) {
for(OctreeNode uchild : u.children) {
wspd(uchild, v, epsilon, H);
}
}
}
}


My implementation works without errors. However, when I tested, it is very slow and for big size tests it gives an outOfMemoryError. How can I improve this implementation?







java tree






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 24 '18 at 2:36









Jamal

30.3k11116227




30.3k11116227










asked Dec 21 '18 at 20:38









YouemYouem

114




114





bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.










  • 1




    $begingroup$
    The lack of indentation makes parts of the code hard to follow.
    $endgroup$
    – 1201ProgramAlarm
    Dec 21 '18 at 21:23










  • $begingroup$
    I think I corrected it. Thank you
    $endgroup$
    – Youem
    Dec 21 '18 at 21:47














  • 1




    $begingroup$
    The lack of indentation makes parts of the code hard to follow.
    $endgroup$
    – 1201ProgramAlarm
    Dec 21 '18 at 21:23










  • $begingroup$
    I think I corrected it. Thank you
    $endgroup$
    – Youem
    Dec 21 '18 at 21:47








1




1




$begingroup$
The lack of indentation makes parts of the code hard to follow.
$endgroup$
– 1201ProgramAlarm
Dec 21 '18 at 21:23




$begingroup$
The lack of indentation makes parts of the code hard to follow.
$endgroup$
– 1201ProgramAlarm
Dec 21 '18 at 21:23












$begingroup$
I think I corrected it. Thank you
$endgroup$
– Youem
Dec 21 '18 at 21:47




$begingroup$
I think I corrected it. Thank you
$endgroup$
– Youem
Dec 21 '18 at 21:47










2 Answers
2






active

oldest

votes


















0












$begingroup$

Overall the code is okay but can definitely be improved.



Some things I would however change.



Instead of writing short variable names and then explaining them in comments, a better approach is to have "self-documenting code". What this means is to make your variables descriptive enough to convey to the reader what they represent.



An example is in your method



/**
* @param o an OctreeNode.
* @param u an OctreeNode.
* @return the distance between the two nodes.
*/
public static double distance(OctreeNode o, OctreeNode u) {


Here you can simply use firstNode and secondNode since we are returning the distance between them.



Here is another example:



/**
* Compute the WSPD from an octree and a threshold s.
* @param T the Octree,
* @param s threshold.
* @return the pairs of WSPD.
*/
public OctreeNode wspd (Octree T, double epsilon) {


Here the T would be better called a tree, or the threshold would be better called exactly that, the threshold. Also, I don't see the parameter you mentioned passed into the function (possibly a bug)?



Pair(X xx, Y yy) {
x = xx;
y = yy;
}


Here I would change xx and yy, as they could be better represented with a different variable name.



Over here, what are these nodes?



OctreeNode u, OctreeNode v


It would be preferred to describe them, so that it's easier to see what's going on when you use these variables throughout your code.



@param p a Point_3.


Why is a Point_3 here is the only object which has an underscore? You want to pick a naming style and consistently use it. Inconsistency in code leads to possible subtle bugs down the road - along with difficulty for an outside reader to follow it.






share|improve this answer









$endgroup$





















    0












    $begingroup$

    You should make an attempt to vectorize much of your code. It's an important way to re-think your code in terms that allow for computers to efficiently execute SIMD (single-instruction multiple-data) and achieve massive speedup as compared to naive implementations. There are many libraries to allow you to vectorize your code. So far the most promising-looking one (caveat: I haven't tried it) seems to be JBLAS.



    Specifically, this:



    double minX = points.get(0).getX().doubleValue();
    double minY = points.get(0).getY().doubleValue();
    double minZ = points.get(0).getZ().doubleValue();


    should be represented by one vector, and this:



    double maxX = points.get(0).getX().doubleValue();
    double maxY = points.get(0).getY().doubleValue();
    double maxZ = points.get(0).getZ().doubleValue();


    should be represented by one vector. This loop:



    for(Point_3 point : points) {

    // update the minimum.
    // ...


    should not exist at all, and you should be calling a vectorized routine instead.



    Similar to the above, this:



    this.width = 0.75*(maxX - minX);
    this.height = 0.75*(maxY - minY);
    this.depth = 0.75*(maxZ - minZ);


    should not be represented by three separate members, nor should you be repeating the same operation three times. Instead, represent it as one vector perhaps called this.size.



    This:



    public double diam() {
    if(this.isLeaf()) {
    return 0.0;
    }
    return 2*Math.sqrt(this.width*this.width +
    this.height*this.height +
    this.depth*this.depth);
    }


    is you rolling your own Euclidean norm, which you shouldn't do. BLAS has functions for this. Similar vectorization should be done elsewhere in your code, such as inBoundary. This:



    for(OctreeNode child : children) {
    child.width = this.width / 2;
    child.depth = this.depth / 2;
    child.height = this.height / 2;
    }


    should treat every child's size as a single vector rather than separate components. You should also compute this.size / 2 outside of the loop. It may even be possible to represent children as a matrix with each row representing a child and the columns representing x, y and z respectively, which would eliminate the need for a loop altogether.



    Along the same lines, distance can be greatly tightened up by dealing with vectors rather than individual components, particularly lines like these:



    x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
    y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
    z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
    return Math.sqrt(x*x + y*y + z*z);


    This can be done in one line of vectorized code.






    share|improve this answer









    $endgroup$













      Your Answer





      StackExchange.ifUsing("editor", function () {
      return StackExchange.using("mathjaxEditing", function () {
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
      });
      });
      }, "mathjax-editing");

      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "196"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210138%2fjava-implementation-of-well-separated-pair-decomposition%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      0












      $begingroup$

      Overall the code is okay but can definitely be improved.



      Some things I would however change.



      Instead of writing short variable names and then explaining them in comments, a better approach is to have "self-documenting code". What this means is to make your variables descriptive enough to convey to the reader what they represent.



      An example is in your method



      /**
      * @param o an OctreeNode.
      * @param u an OctreeNode.
      * @return the distance between the two nodes.
      */
      public static double distance(OctreeNode o, OctreeNode u) {


      Here you can simply use firstNode and secondNode since we are returning the distance between them.



      Here is another example:



      /**
      * Compute the WSPD from an octree and a threshold s.
      * @param T the Octree,
      * @param s threshold.
      * @return the pairs of WSPD.
      */
      public OctreeNode wspd (Octree T, double epsilon) {


      Here the T would be better called a tree, or the threshold would be better called exactly that, the threshold. Also, I don't see the parameter you mentioned passed into the function (possibly a bug)?



      Pair(X xx, Y yy) {
      x = xx;
      y = yy;
      }


      Here I would change xx and yy, as they could be better represented with a different variable name.



      Over here, what are these nodes?



      OctreeNode u, OctreeNode v


      It would be preferred to describe them, so that it's easier to see what's going on when you use these variables throughout your code.



      @param p a Point_3.


      Why is a Point_3 here is the only object which has an underscore? You want to pick a naming style and consistently use it. Inconsistency in code leads to possible subtle bugs down the road - along with difficulty for an outside reader to follow it.






      share|improve this answer









      $endgroup$


















        0












        $begingroup$

        Overall the code is okay but can definitely be improved.



        Some things I would however change.



        Instead of writing short variable names and then explaining them in comments, a better approach is to have "self-documenting code". What this means is to make your variables descriptive enough to convey to the reader what they represent.



        An example is in your method



        /**
        * @param o an OctreeNode.
        * @param u an OctreeNode.
        * @return the distance between the two nodes.
        */
        public static double distance(OctreeNode o, OctreeNode u) {


        Here you can simply use firstNode and secondNode since we are returning the distance between them.



        Here is another example:



        /**
        * Compute the WSPD from an octree and a threshold s.
        * @param T the Octree,
        * @param s threshold.
        * @return the pairs of WSPD.
        */
        public OctreeNode wspd (Octree T, double epsilon) {


        Here the T would be better called a tree, or the threshold would be better called exactly that, the threshold. Also, I don't see the parameter you mentioned passed into the function (possibly a bug)?



        Pair(X xx, Y yy) {
        x = xx;
        y = yy;
        }


        Here I would change xx and yy, as they could be better represented with a different variable name.



        Over here, what are these nodes?



        OctreeNode u, OctreeNode v


        It would be preferred to describe them, so that it's easier to see what's going on when you use these variables throughout your code.



        @param p a Point_3.


        Why is a Point_3 here is the only object which has an underscore? You want to pick a naming style and consistently use it. Inconsistency in code leads to possible subtle bugs down the road - along with difficulty for an outside reader to follow it.






        share|improve this answer









        $endgroup$
















          0












          0








          0





          $begingroup$

          Overall the code is okay but can definitely be improved.



          Some things I would however change.



          Instead of writing short variable names and then explaining them in comments, a better approach is to have "self-documenting code". What this means is to make your variables descriptive enough to convey to the reader what they represent.



          An example is in your method



          /**
          * @param o an OctreeNode.
          * @param u an OctreeNode.
          * @return the distance between the two nodes.
          */
          public static double distance(OctreeNode o, OctreeNode u) {


          Here you can simply use firstNode and secondNode since we are returning the distance between them.



          Here is another example:



          /**
          * Compute the WSPD from an octree and a threshold s.
          * @param T the Octree,
          * @param s threshold.
          * @return the pairs of WSPD.
          */
          public OctreeNode wspd (Octree T, double epsilon) {


          Here the T would be better called a tree, or the threshold would be better called exactly that, the threshold. Also, I don't see the parameter you mentioned passed into the function (possibly a bug)?



          Pair(X xx, Y yy) {
          x = xx;
          y = yy;
          }


          Here I would change xx and yy, as they could be better represented with a different variable name.



          Over here, what are these nodes?



          OctreeNode u, OctreeNode v


          It would be preferred to describe them, so that it's easier to see what's going on when you use these variables throughout your code.



          @param p a Point_3.


          Why is a Point_3 here is the only object which has an underscore? You want to pick a naming style and consistently use it. Inconsistency in code leads to possible subtle bugs down the road - along with difficulty for an outside reader to follow it.






          share|improve this answer









          $endgroup$



          Overall the code is okay but can definitely be improved.



          Some things I would however change.



          Instead of writing short variable names and then explaining them in comments, a better approach is to have "self-documenting code". What this means is to make your variables descriptive enough to convey to the reader what they represent.



          An example is in your method



          /**
          * @param o an OctreeNode.
          * @param u an OctreeNode.
          * @return the distance between the two nodes.
          */
          public static double distance(OctreeNode o, OctreeNode u) {


          Here you can simply use firstNode and secondNode since we are returning the distance between them.



          Here is another example:



          /**
          * Compute the WSPD from an octree and a threshold s.
          * @param T the Octree,
          * @param s threshold.
          * @return the pairs of WSPD.
          */
          public OctreeNode wspd (Octree T, double epsilon) {


          Here the T would be better called a tree, or the threshold would be better called exactly that, the threshold. Also, I don't see the parameter you mentioned passed into the function (possibly a bug)?



          Pair(X xx, Y yy) {
          x = xx;
          y = yy;
          }


          Here I would change xx and yy, as they could be better represented with a different variable name.



          Over here, what are these nodes?



          OctreeNode u, OctreeNode v


          It would be preferred to describe them, so that it's easier to see what's going on when you use these variables throughout your code.



          @param p a Point_3.


          Why is a Point_3 here is the only object which has an underscore? You want to pick a naming style and consistently use it. Inconsistency in code leads to possible subtle bugs down the road - along with difficulty for an outside reader to follow it.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Dec 26 '18 at 6:51









          FarazFaraz

          373111




          373111

























              0












              $begingroup$

              You should make an attempt to vectorize much of your code. It's an important way to re-think your code in terms that allow for computers to efficiently execute SIMD (single-instruction multiple-data) and achieve massive speedup as compared to naive implementations. There are many libraries to allow you to vectorize your code. So far the most promising-looking one (caveat: I haven't tried it) seems to be JBLAS.



              Specifically, this:



              double minX = points.get(0).getX().doubleValue();
              double minY = points.get(0).getY().doubleValue();
              double minZ = points.get(0).getZ().doubleValue();


              should be represented by one vector, and this:



              double maxX = points.get(0).getX().doubleValue();
              double maxY = points.get(0).getY().doubleValue();
              double maxZ = points.get(0).getZ().doubleValue();


              should be represented by one vector. This loop:



              for(Point_3 point : points) {

              // update the minimum.
              // ...


              should not exist at all, and you should be calling a vectorized routine instead.



              Similar to the above, this:



              this.width = 0.75*(maxX - minX);
              this.height = 0.75*(maxY - minY);
              this.depth = 0.75*(maxZ - minZ);


              should not be represented by three separate members, nor should you be repeating the same operation three times. Instead, represent it as one vector perhaps called this.size.



              This:



              public double diam() {
              if(this.isLeaf()) {
              return 0.0;
              }
              return 2*Math.sqrt(this.width*this.width +
              this.height*this.height +
              this.depth*this.depth);
              }


              is you rolling your own Euclidean norm, which you shouldn't do. BLAS has functions for this. Similar vectorization should be done elsewhere in your code, such as inBoundary. This:



              for(OctreeNode child : children) {
              child.width = this.width / 2;
              child.depth = this.depth / 2;
              child.height = this.height / 2;
              }


              should treat every child's size as a single vector rather than separate components. You should also compute this.size / 2 outside of the loop. It may even be possible to represent children as a matrix with each row representing a child and the columns representing x, y and z respectively, which would eliminate the need for a loop altogether.



              Along the same lines, distance can be greatly tightened up by dealing with vectors rather than individual components, particularly lines like these:



              x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
              y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
              z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
              return Math.sqrt(x*x + y*y + z*z);


              This can be done in one line of vectorized code.






              share|improve this answer









              $endgroup$


















                0












                $begingroup$

                You should make an attempt to vectorize much of your code. It's an important way to re-think your code in terms that allow for computers to efficiently execute SIMD (single-instruction multiple-data) and achieve massive speedup as compared to naive implementations. There are many libraries to allow you to vectorize your code. So far the most promising-looking one (caveat: I haven't tried it) seems to be JBLAS.



                Specifically, this:



                double minX = points.get(0).getX().doubleValue();
                double minY = points.get(0).getY().doubleValue();
                double minZ = points.get(0).getZ().doubleValue();


                should be represented by one vector, and this:



                double maxX = points.get(0).getX().doubleValue();
                double maxY = points.get(0).getY().doubleValue();
                double maxZ = points.get(0).getZ().doubleValue();


                should be represented by one vector. This loop:



                for(Point_3 point : points) {

                // update the minimum.
                // ...


                should not exist at all, and you should be calling a vectorized routine instead.



                Similar to the above, this:



                this.width = 0.75*(maxX - minX);
                this.height = 0.75*(maxY - minY);
                this.depth = 0.75*(maxZ - minZ);


                should not be represented by three separate members, nor should you be repeating the same operation three times. Instead, represent it as one vector perhaps called this.size.



                This:



                public double diam() {
                if(this.isLeaf()) {
                return 0.0;
                }
                return 2*Math.sqrt(this.width*this.width +
                this.height*this.height +
                this.depth*this.depth);
                }


                is you rolling your own Euclidean norm, which you shouldn't do. BLAS has functions for this. Similar vectorization should be done elsewhere in your code, such as inBoundary. This:



                for(OctreeNode child : children) {
                child.width = this.width / 2;
                child.depth = this.depth / 2;
                child.height = this.height / 2;
                }


                should treat every child's size as a single vector rather than separate components. You should also compute this.size / 2 outside of the loop. It may even be possible to represent children as a matrix with each row representing a child and the columns representing x, y and z respectively, which would eliminate the need for a loop altogether.



                Along the same lines, distance can be greatly tightened up by dealing with vectors rather than individual components, particularly lines like these:



                x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
                y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
                z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
                return Math.sqrt(x*x + y*y + z*z);


                This can be done in one line of vectorized code.






                share|improve this answer









                $endgroup$
















                  0












                  0








                  0





                  $begingroup$

                  You should make an attempt to vectorize much of your code. It's an important way to re-think your code in terms that allow for computers to efficiently execute SIMD (single-instruction multiple-data) and achieve massive speedup as compared to naive implementations. There are many libraries to allow you to vectorize your code. So far the most promising-looking one (caveat: I haven't tried it) seems to be JBLAS.



                  Specifically, this:



                  double minX = points.get(0).getX().doubleValue();
                  double minY = points.get(0).getY().doubleValue();
                  double minZ = points.get(0).getZ().doubleValue();


                  should be represented by one vector, and this:



                  double maxX = points.get(0).getX().doubleValue();
                  double maxY = points.get(0).getY().doubleValue();
                  double maxZ = points.get(0).getZ().doubleValue();


                  should be represented by one vector. This loop:



                  for(Point_3 point : points) {

                  // update the minimum.
                  // ...


                  should not exist at all, and you should be calling a vectorized routine instead.



                  Similar to the above, this:



                  this.width = 0.75*(maxX - minX);
                  this.height = 0.75*(maxY - minY);
                  this.depth = 0.75*(maxZ - minZ);


                  should not be represented by three separate members, nor should you be repeating the same operation three times. Instead, represent it as one vector perhaps called this.size.



                  This:



                  public double diam() {
                  if(this.isLeaf()) {
                  return 0.0;
                  }
                  return 2*Math.sqrt(this.width*this.width +
                  this.height*this.height +
                  this.depth*this.depth);
                  }


                  is you rolling your own Euclidean norm, which you shouldn't do. BLAS has functions for this. Similar vectorization should be done elsewhere in your code, such as inBoundary. This:



                  for(OctreeNode child : children) {
                  child.width = this.width / 2;
                  child.depth = this.depth / 2;
                  child.height = this.height / 2;
                  }


                  should treat every child's size as a single vector rather than separate components. You should also compute this.size / 2 outside of the loop. It may even be possible to represent children as a matrix with each row representing a child and the columns representing x, y and z respectively, which would eliminate the need for a loop altogether.



                  Along the same lines, distance can be greatly tightened up by dealing with vectors rather than individual components, particularly lines like these:



                  x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
                  y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
                  z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
                  return Math.sqrt(x*x + y*y + z*z);


                  This can be done in one line of vectorized code.






                  share|improve this answer









                  $endgroup$



                  You should make an attempt to vectorize much of your code. It's an important way to re-think your code in terms that allow for computers to efficiently execute SIMD (single-instruction multiple-data) and achieve massive speedup as compared to naive implementations. There are many libraries to allow you to vectorize your code. So far the most promising-looking one (caveat: I haven't tried it) seems to be JBLAS.



                  Specifically, this:



                  double minX = points.get(0).getX().doubleValue();
                  double minY = points.get(0).getY().doubleValue();
                  double minZ = points.get(0).getZ().doubleValue();


                  should be represented by one vector, and this:



                  double maxX = points.get(0).getX().doubleValue();
                  double maxY = points.get(0).getY().doubleValue();
                  double maxZ = points.get(0).getZ().doubleValue();


                  should be represented by one vector. This loop:



                  for(Point_3 point : points) {

                  // update the minimum.
                  // ...


                  should not exist at all, and you should be calling a vectorized routine instead.



                  Similar to the above, this:



                  this.width = 0.75*(maxX - minX);
                  this.height = 0.75*(maxY - minY);
                  this.depth = 0.75*(maxZ - minZ);


                  should not be represented by three separate members, nor should you be repeating the same operation three times. Instead, represent it as one vector perhaps called this.size.



                  This:



                  public double diam() {
                  if(this.isLeaf()) {
                  return 0.0;
                  }
                  return 2*Math.sqrt(this.width*this.width +
                  this.height*this.height +
                  this.depth*this.depth);
                  }


                  is you rolling your own Euclidean norm, which you shouldn't do. BLAS has functions for this. Similar vectorization should be done elsewhere in your code, such as inBoundary. This:



                  for(OctreeNode child : children) {
                  child.width = this.width / 2;
                  child.depth = this.depth / 2;
                  child.height = this.height / 2;
                  }


                  should treat every child's size as a single vector rather than separate components. You should also compute this.size / 2 outside of the loop. It may even be possible to represent children as a matrix with each row representing a child and the columns representing x, y and z respectively, which would eliminate the need for a loop altogether.



                  Along the same lines, distance can be greatly tightened up by dealing with vectors rather than individual components, particularly lines like these:



                  x = Math.min(Math.abs(v.getX().doubleValue()) - o.width - u.width, 0.0);
                  y = Math.min(Math.abs(v.getX().doubleValue()) - o.height - u.height, 0.0);
                  z = Math.min(Math.abs(v.getX().doubleValue()) - o.depth - u.depth, 0.0);
                  return Math.sqrt(x*x + y*y + z*z);


                  This can be done in one line of vectorized code.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Dec 29 '18 at 3:16









                  ReinderienReinderien

                  4,140822




                  4,140822






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Code Review Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      Use MathJax to format equations. MathJax reference.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210138%2fjava-implementation-of-well-separated-pair-decomposition%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

                      is 'sed' thread safe

                      How to make a Squid Proxy server?