Skip to content

Commit

Permalink
an abstract super class providing utility methods for other hashing f…
Browse files Browse the repository at this point in the history
…unctions

Signed-off-by: Egon Willighagen <egonw@users.sourceforge.net>
  • Loading branch information
johnmay authored and egonw committed Apr 19, 2013
1 parent 8600e71 commit 2d60945
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 1 deletion.
177 changes: 177 additions & 0 deletions src/main/org/openscience/cdk/hash/AbstractHashGenerator.java
@@ -0,0 +1,177 @@
/*
* Copyright (c) 2013. John May <jwmay@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program 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.
* All we ask is that proper credit is given for our work, which includes
* - but is not limited to - adding the above copyright notice to the beginning
* of your source code files, and to any copyright notice that you may distribute
* with programs based on this work.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 U
*/

package org.openscience.cdk.hash;

import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;

import java.util.Arrays;

/**
* An abstract hash function providing several utility methods to be used by
* other hashing functions.
*
* @author John May
* @cdk.module hash
*/
@TestClass("org.openscience.cdk.hash.AbstractHashGeneratorTest")
class AbstractHashGenerator {

/* pseudorandom number generator */
private final Pseudorandom pseudorandom;

/**
* Construct an abstract hash function providing the pseudorandom number
* generator.
*
* @param pseudorandom a pseudorandom number generator
* @throws NullPointerException the pseudorandom number generator was null
*/
@TestMethod("testConstruction_Null")
public AbstractHashGenerator(Pseudorandom pseudorandom) {
if (pseudorandom == null)
throw new NullPointerException("null pseduorandom number generator provided");
this.pseudorandom = pseudorandom;
}

/**
* Create a copy of the array of long values.
*
* @param src original values
* @return copy of the original values
* @see Arrays#copyOf(long[], int)
*/
@TestMethod("testCopy")
static long[] copy(long[] src) {
return Arrays.copyOf(src, src.length);
}

/**
* Copy the values from the source (src) array to the destination (dest).
*
* @param src source values
* @param dest destination of the source copy
* @see System#arraycopy(Object, int, Object, int, int);
*/
@TestMethod("testCopy_SrcDest")
static void copy(long[] src, long[] dest) {
System.arraycopy(src, 0, dest, 0, dest.length);
}

/**
* Generate the next random number.
*
* @param seed a {@literal long} value to seed a pseudorandom number
* generator
* @return next pseudorandom number
*/
@TestMethod("testRotate") long rotate(long seed) {
return pseudorandom.next(seed);
}

/**
* Rotate a <i>value</i>, <i>n</i> times. The rotation uses a pseudorandom
* number generator to sequentially generate values seed on the previous
* value.
*
* @param value the {@literal long} value to rotate
* @param n the number of times to rotate the value
* @return the {@literal long} value rotated the specified number of times
*/
@TestMethod("testRotate_N") long rotate(long value, int n) {
while (n-- > 0)
value = pseudorandom.next(value);
return value;
}

/**
* Returns the value of the lowest three bits. This value is between 0 and 7
* inclusive.
*
* @param value a {@literal long} value
* @return the {@literal int} value of the lowest three bits.
*/
@TestMethod("testLowestThreeBits")
static int lowestThreeBits(long value) {
return (int) (value & 0x7);
}

/**
* Distribute the provided value across the set of {@literal long} values.
*
* @param value a {@literal long} value to distribute
* @return the {@literal long} value distributed a set amount
*/
@TestMethod("testDistribute") long distribute(long value) {
// rotate 1-8 times
return rotate(value, 1 + lowestThreeBits(value));
}

/**
* Convert an IAtomContainer to an adjacency list.
*
* @param container the container to convert
* @return adjacency list representation
*/
@TestMethod("testToAdjList")
static int[][] toAdjList(IAtomContainer container) {

if (container == null)
throw new IllegalArgumentException("atom container was null");

int n = container.getAtomCount();

int[][] graph = new int[n][16];
int[] degree = new int[n];

for (IBond bond : container.bonds()) {

int v = container.getAtomNumber(bond.getAtom(0));
int w = container.getAtomNumber(bond.getAtom(1));

if (v < 0 || w < 0)
throw new IllegalArgumentException("bond at index " + container.getBondNumber(bond)
+ " contained an atom not pressent in molecule");

graph[v][degree[v]++] = w;
graph[w][degree[w]++] = v;

// if the vertex degree of v or w reaches capacity, double the size
if (degree[v] == graph[v].length)
graph[v] = Arrays.copyOf(graph[v], degree[v] * 2);
if (degree[w] == graph[w].length)
graph[w] = Arrays.copyOf(graph[w], degree[w] * 2);
}

for (int v = 0; v < n; v++) {
graph[v] = Arrays.copyOf(graph[v], degree[v]);
}

return graph;

}
}
133 changes: 133 additions & 0 deletions src/test/org/openscience/cdk/hash/AbstractHashGeneratorTest.java
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2013. John May <jwmay@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program 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.
* All we ask is that proper credit is given for our work, which includes
* - but is not limited to - adding the above copyright notice to the beginning
* of your source code files, and to any copyright notice that you may distribute
* with programs based on this work.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 U
*/

package org.openscience.cdk.hash;

import org.junit.Ignore;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

/**
* @author John May
* @cdk.module test-hash
*/
public class AbstractHashGeneratorTest {

@Test(expected = NullPointerException.class)
public void testConstruction_Null() {
new AbstractHashGenerator(null);
}

@Test
public void testCopy() throws Exception {
long[] x = new long[]{2, 1, 3, 2};
long[] y = AbstractHashGenerator.copy(x);
assertThat(x, is(y));
assertThat(x, not(sameInstance(y)));
}

@Test
public void testCopy_SrcDest() throws Exception {
long[] x = new long[]{42, 23, 1, 72};
long[] y = new long[4];
AbstractHashGenerator.copy(x, y);
assertThat(x, is(y));
assertThat(x, not(sameInstance(y)));
}

@Test
public void testRotate() throws Exception {
Pseudorandom pseudorandom = mock(Pseudorandom.class);
AbstractHashGenerator f = new AbstractHashGenerator(pseudorandom);
f.rotate(5L);
verify(pseudorandom, times(1)).next(5L);
}

@Test
public void testRotate_N() throws Exception {
Pseudorandom pseudorandom = mock(Pseudorandom.class);
AbstractHashGenerator f = new AbstractHashGenerator(pseudorandom);
f.rotate(0, 5); // note 0 doesn't rotate...
verify(pseudorandom, times(5)).next(anyLong());
}

@Test
public void testLowestThreeBits() throws Exception {
assertThat(AbstractHashGenerator.lowestThreeBits(0L), is(0));
assertThat(AbstractHashGenerator.lowestThreeBits(1L), is(1));
assertThat(AbstractHashGenerator.lowestThreeBits(2L), is(2));
assertThat(AbstractHashGenerator.lowestThreeBits(3L), is(3));
assertThat(AbstractHashGenerator.lowestThreeBits(4L), is(4));
assertThat(AbstractHashGenerator.lowestThreeBits(5L), is(5));
assertThat(AbstractHashGenerator.lowestThreeBits(6L), is(6));
assertThat(AbstractHashGenerator.lowestThreeBits(7L), is(7));

// check we don't exceed 7
assertThat(AbstractHashGenerator.lowestThreeBits(8L), is(0));
assertThat(AbstractHashGenerator.lowestThreeBits(9L), is(1));
assertThat(AbstractHashGenerator.lowestThreeBits(10L), is(2));
assertThat(AbstractHashGenerator.lowestThreeBits(11L), is(3));
assertThat(AbstractHashGenerator.lowestThreeBits(12L), is(4));
assertThat(AbstractHashGenerator.lowestThreeBits(13L), is(5));
assertThat(AbstractHashGenerator.lowestThreeBits(14L), is(6));
assertThat(AbstractHashGenerator.lowestThreeBits(15L), is(7));
assertThat(AbstractHashGenerator.lowestThreeBits(16L), is(0));

// max/min numbers
assertThat(AbstractHashGenerator.lowestThreeBits(Long.MAX_VALUE), is(7));
assertThat(AbstractHashGenerator.lowestThreeBits(Long.MIN_VALUE), is(0));
}

@Test
public void testDistribute_AtLeastOnce() throws Exception {
Pseudorandom pseudorandom = mock(Pseudorandom.class);
AbstractHashGenerator f = new AbstractHashGenerator(pseudorandom);
long x = f.distribute(8L); // lowest 3 bits = 0, make sure we rotate 1
verify(pseudorandom, times(1)).next(anyLong());
assertThat(x, is(not(8L)));
}

@Test
public void testDistribute() throws Exception {
Pseudorandom pseudorandom = mock(Pseudorandom.class);
AbstractHashGenerator f = new AbstractHashGenerator(pseudorandom);
long x = f.distribute(5L); // lowest 3 bits = 5, rotate 6 times
verify(pseudorandom, times(6)).next(anyLong());
assertThat(x, is(not(5L)));
}

@Test
public void testToAdjList() {
// already tests in ShortestPaths... this method be moved once all
// pending patches are merged
}
}
4 changes: 3 additions & 1 deletion src/test/org/openscience/cdk/modulesuites/MhashTests.java
Expand Up @@ -21,6 +21,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.openscience.cdk.hash.AbstractHashGeneratorTest;
import org.openscience.cdk.hash.XorshiftTest;
import org.openscience.cdk.hash.seed.BasicAtomEncoderTest;
import org.openscience.cdk.hash.seed.ConjugatedAtomEncoderTest;
Expand All @@ -34,7 +35,8 @@
@SuiteClasses(value = {
XorshiftTest.class,
BasicAtomEncoderTest.class,
ConjugatedAtomEncoderTest.class
ConjugatedAtomEncoderTest.class,
AbstractHashGeneratorTest.class,
})
public class MhashTests {
}

0 comments on commit 2d60945

Please sign in to comment.