blob: 45481b49f612630ebcbc01a7f3f92eaf0eaaac8f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.datasketches.theta;
import static org.apache.datasketches.common.ResizeFactor.X4;
import static org.apache.datasketches.theta.ThetaSketch.getMaxUpdateSketchBytes;
import static org.apache.datasketches.thetacommon.HashOperations.minLgHashTableSize;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.lang.foreign.MemorySegment;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.ResizeFactor;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.Util;
import org.apache.datasketches.thetacommon.ThetaUtil;
import org.testng.annotations.Test;
/**
* @author Lee Rhodes
*/
public class SetOperationTest {
@Test
public void checkBuilder() {
final int k = 2048;
final long seed = 1021;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setSeed(seed).setNominalEntries(k).build();
final UpdatableThetaSketch usk2 = UpdatableThetaSketch.builder().setSeed(seed).setNominalEntries(k).build();
for (int i=0; i<k/2; i++) {
usk1.update(i); //256
}
for (int i=k/2; i<k; i++) {
usk2.update(i); //256 no overlap
}
final ResizeFactor rf = X4;
//use default size
final ThetaUnion union = ThetaSetOperation.builder().setSeed(seed).setResizeFactor(rf).buildUnion();
union.union(usk1);
union.union(usk2);
final double exactUnionAnswer = k;
final CompactThetaSketch comp1 = union.getResult(false, null); //ordered: false
final double compEst = comp1.getEstimate();
assertEquals(compEst, exactUnionAnswer, 0.0);
}
@Test
public void checkBuilder2() {
final ThetaSetOperationBuilder bldr = ThetaSetOperation.builder();
final long seed = 12345L;
bldr.setSeed(seed);
assertEquals(bldr.getSeed(), seed);
final float p = (float)0.5;
bldr.setP(p);
assertEquals(bldr.getP(), p);
final ResizeFactor rf = ResizeFactor.X4;
bldr.setResizeFactor(rf);
assertEquals(bldr.getResizeFactor(), rf);
final int lgK = 10;
final int k = 1 << lgK;
bldr.setNominalEntries(k);
assertEquals(bldr.getLgNominalEntries(), lgK);
println(bldr.toString());
}
@Test
public void checkBuilderNonPowerOf2() {
ThetaSetOperation.builder().setNominalEntries(1000).buildUnion();
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderBadFamily() {
ThetaSetOperation.builder().build(Family.ALPHA);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderIllegalPhi() {
final float p = (float)1.5;
ThetaSetOperation.builder().setP(p).buildUnion();
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderIllegalPlo() {
final float p = 0;
ThetaSetOperation.builder().setP(p).buildUnion();
}
@Test
public void checkBuilderValidP() {
final float p = (float).5;
ThetaSetOperation.builder().setP(p).buildUnion();
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderAnotB_noSeg() {
final MemorySegment seg = MemorySegment.ofArray(new byte[64]);
ThetaSetOperation.builder().build(Family.A_NOT_B, seg);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderBadSeedHashes() {
final int k = 2048;
final long seed = 1021;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setSeed(seed).setNominalEntries(k).build();
final UpdatableThetaSketch usk2 = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=0; i<k/2; i++) {
usk1.update(i); //256
}
for (int i=k/2; i<k; i++) {
usk2.update(i); //256 no overlap
}
final ResizeFactor rf = X4;
final ThetaUnion union = ThetaSetOperation.builder().setSeed(seed).setResizeFactor(rf).setNominalEntries(k).buildUnion();
union.union(usk1);
union.union(usk2); //throws seed exception here
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkBuilderNomEntries() {
final int k = 1 << 27;
final ThetaSetOperationBuilder bldr = ThetaSetOperation.builder();
bldr.setNominalEntries(k);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkIllegalSetOpHeapify() {
final int k = 64;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=0; i<k; i++) {
usk1.update(i); //64
}
final byte[] byteArray = usk1.toByteArray();
final MemorySegment seg = MemorySegment.ofArray(byteArray).asReadOnly();
ThetaSetOperation.heapify(seg);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkIllegalSetOpWrap() {
final int k = 64;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=0; i<k; i++) {
usk1.update(i); //64
}
final byte[] byteArray = usk1.toByteArray();
final MemorySegment seg = MemorySegment.ofArray(byteArray).asReadOnly();
ThetaIntersection.wrap(seg);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkIllegalSetOpWrap2() {
final int k = 64;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=0; i<k; i++) {
usk1.update(i); //64
}
final MemorySegment wseg = MemorySegment.ofArray(usk1.toByteArray());
PreambleUtil.insertSerVer(wseg, 2); //corrupt
final MemorySegment seg = wseg.asReadOnly();
ThetaSetOperation.wrap(seg);
}
@Test(expectedExceptions = SketchesArgumentException.class)
public void checkIllegalSetOpWrap3() {
final int k = 64;
final UpdatableThetaSketch usk1 = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=0; i<k; i++) {
usk1.update(i); //64
}
final MemorySegment wseg = MemorySegment.ofArray(usk1.toByteArray());
ThetaSetOperation.wrap(wseg);
}
@Test
public void checkBuildSetOps() {
final ThetaSetOperationBuilder bldr = ThetaSetOperation.builder();
bldr.buildUnion();
bldr.buildIntersection();
bldr.buildANotB();
}
@Test
public void checkComputeLgArrLongs() {
assertEquals(minLgHashTableSize(30, ThetaUtil.REBUILD_THRESHOLD), 5);
assertEquals(minLgHashTableSize(31, ThetaUtil.REBUILD_THRESHOLD), 6);
}
/**
* The objective is to union 3 16K sketches into a union ThetaSetOperation and get the result.
* All operations are to be performed within a single direct ByteBuffer as the backing store.
* First we will make the union size large enough so that its answer will be exact (with this
* specific example).
* <p> Next, we recover the ThetaUnion SetOp and the 3 sketches and the space for the result. Then
* recompute the union using a ThetaUnion of the same size as the input sketches, where the end result
* will be an estimate.
*/
@Test
public void checkDirectUnionExample() {
//The first task is to compute how much off-heap space we need and set the heap large enough.
//For the first trial, we will set the ThetaUnion large enough for an exact result for THIS example.
final int sketchNomEntries = 1 << 14; //16K
int unionNomEntries = 1 << 15; //32K
final int[] heapLayout = getHeapLayout(sketchNomEntries, unionNomEntries);
//This BB belongs to you and you always retain a link to it until you are completely
// done and then let java garbage collect it.
//I use a heap backing array, because for this example it is easier to peak into it and
// see what is going on.
final byte[] backingArr = new byte[heapLayout[5]];
final ByteBuffer heapBuf = ByteBuffer.wrap(backingArr).order(ByteOrder.nativeOrder());
// Attaches a MemorySegment object to the underlying heap space of heapBuf.
// heapSeg will have a Read/Write view of the complete backing segment of heapBuf (direct or not).
// Any R/W action from heapSeg will be visible via heapBuf and visa versa.
//
// However, if you had created this WM object off-heap
// you would have the responsibility to close it when you are done.
// But, since it was allocated via BB, it closes it for you.
final MemorySegment heapSeg = MemorySegment.ofBuffer(heapBuf);
double result = directUnionTrial1(heapSeg, heapLayout, sketchNomEntries, unionNomEntries);
println("1st est: "+result);
final int expected = sketchNomEntries*2;
assertEquals(result, expected, 0.0); //est must be exact.
//For trial 2, we will use the same union space but use only part of it.
unionNomEntries = 1 << 14; //16K
result = directUnionTrial2(heapSeg, heapLayout, sketchNomEntries, unionNomEntries);
//intentionally loose bounds
assertEquals(result, expected, expected*0.05);
println("2nd est: "+result);
println("Error %: "+(result/expected -1.0)*100);
}
@Test
public void setOpsExample() {
println("Set Operations Example:");
final int k = 4096;
final UpdatableThetaSketch skA = UpdatableThetaSketch.builder().setNominalEntries(k).build();
final UpdatableThetaSketch skB = UpdatableThetaSketch.builder().setNominalEntries(k).build();
final UpdatableThetaSketch skC = UpdatableThetaSketch.builder().setNominalEntries(k).build();
for (int i=1; i<=10; i++) { skA.update(i); }
for (int i=1; i<=20; i++) { skB.update(i); }
for (int i=6; i<=15; i++) { skC.update(i); } //overlapping set
final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion();
union.union(skA);
union.union(skB);
// ... continue to iterate on the input sketches to union
final CompactThetaSketch unionSk = union.getResult(); //the result union sketch
println("A U B : "+unionSk.getEstimate()); //the estimate of the union
//ThetaIntersection is similar
final ThetaIntersection inter = ThetaSetOperation.builder().buildIntersection();
inter.intersect(unionSk);
inter.intersect(skC);
// ... continue to iterate on the input sketches to intersect
final CompactThetaSketch interSk = inter.getResult(); //the result intersection sketch
println("(A U B) ^ C: "+interSk.getEstimate()); //the estimate of the intersection
//The ThetaAnotB operation is a little different as it is stateless:
final ThetaAnotB aNotB = ThetaSetOperation.builder().buildANotB();
final CompactThetaSketch not = aNotB.aNotB(skA, skC);
println("A \\ C : "+not.getEstimate()); //the estimate of the ThetaAnotB operation
}
@Test
public void checkIsSameResource() {
final int k = 16;
final MemorySegment wseg = MemorySegment.ofArray(new byte[k*16 + 32]);//288
final MemorySegment emptySeg = MemorySegment.ofArray(new byte[8]);
final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(wseg);
assertTrue(union.isSameResource(wseg));
assertFalse(union.isSameResource(emptySeg));
final ThetaIntersection inter = ThetaSetOperation.builder().buildIntersection(wseg);
assertTrue(inter.isSameResource(wseg));
assertFalse(inter.isSameResource(emptySeg));
final ThetaAnotB aNotB = ThetaSetOperation.builder().buildANotB();
assertFalse(aNotB.isSameResource(emptySeg));
}
@Test
public void printlnTest() {
println("PRINTING: "+this.getClass().getName());
}
/**
* @param s value to print
*/
static void println(final String s) {
//System.out.println(s); //disable here
}
/**
* Compute offsets for MyHeap for ThetaUnion, sketch1, sketch2, sketch3, resultSketch, total layout.
* @param sketchNomEntries the configured nominal entries of the sketch
* @param unionNomEntries configured nominal entries of the union
* @return array of offsets for ThetaUnion, sketch1, sketch2, sketch3, resultSketch, total layout
*/
private static int[] getHeapLayout(final int sketchNomEntries, final int unionNomEntries) {
final int[] heapLayout = new int[6];
final int unionBytes = ThetaSetOperation.getMaxUnionBytes(unionNomEntries);
final int sketchBytes = getMaxUpdateSketchBytes(sketchNomEntries);
final int resultBytes = ThetaSketch.getMaxCompactSketchBytes(unionNomEntries);
heapLayout[0] = 0; //offset for ThetaUnion
heapLayout[1] = unionBytes; //offset for sketch1
heapLayout[2] = unionBytes + sketchBytes; //offset for sketch2
heapLayout[3] = unionBytes + 2*sketchBytes; //offset for sketch3
heapLayout[4] = unionBytes + 3*sketchBytes; //offset for result
heapLayout[5] = unionBytes + 3*sketchBytes + resultBytes; //total
return heapLayout;
}
private static double directUnionTrial1(
final MemorySegment heapSeg, final int[] heapLayout, final int sketchNomEntries, final int unionNomEntries) {
final int offset = heapLayout[0];
final int bytes = heapLayout[1] - offset;
final MemorySegment unionSeg = heapSeg.asSlice(offset, bytes);
ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(unionNomEntries).buildUnion(unionSeg);
final MemorySegment sketch1seg = heapSeg.asSlice(heapLayout[1], heapLayout[2]-heapLayout[1]);
final MemorySegment sketch2seg = heapSeg.asSlice(heapLayout[2], heapLayout[3]-heapLayout[2]);
final MemorySegment sketch3seg = heapSeg.asSlice(heapLayout[3], heapLayout[4]-heapLayout[3]);
final MemorySegment resultSeg = heapSeg.asSlice(heapLayout[4], heapLayout[5]-heapLayout[4]);
//Initialize the 3 sketches
final UpdatableThetaSketch sk1 = UpdatableThetaSketch.builder().setNominalEntries(sketchNomEntries).build(sketch1seg);
final UpdatableThetaSketch sk2 = UpdatableThetaSketch.builder().setNominalEntries(sketchNomEntries).build(sketch2seg);
final UpdatableThetaSketch sk3 = UpdatableThetaSketch.builder().setNominalEntries(sketchNomEntries).build(sketch3seg);
//This little trial has sk1 and sk2 distinct and sk2 overlap both.
//Build the sketches.
for (int i=0; i< sketchNomEntries; i++) {
sk1.update(i);
sk2.update(i + sketchNomEntries/2);
sk3.update(i + sketchNomEntries);
}
//confirm that each of these 3 sketches is exact.
assertEquals(sk1.getEstimate(), sketchNomEntries, 0.0);
assertEquals(sk2.getEstimate(), sketchNomEntries, 0.0);
assertEquals(sk3.getEstimate(), sketchNomEntries, 0.0);
//Let's union the first 2 sketches
union.union(sk1);
union.union(sk2);
//Let's recover the union and the 3rd sketch
union = ThetaUnion.wrap(unionSeg);
union.union(ThetaSketch.wrap(sketch3seg));
final ThetaSketch resSk = union.getResult(true, resultSeg);
final double est = resSk.getEstimate();
return est;
}
private static double directUnionTrial2(
final MemorySegment heapSeg, final int[] heapLayout, final int sketchNomEntries, final int unionNomEntries) {
final MemorySegment unionSeg = heapSeg.asSlice(heapLayout[0], heapLayout[1]-heapLayout[0]);
final MemorySegment sketch1seg = heapSeg.asSlice(heapLayout[1], heapLayout[2]-heapLayout[1]);
final MemorySegment sketch2seg = heapSeg.asSlice(heapLayout[2], heapLayout[3]-heapLayout[2]);
final MemorySegment sketch3seg = heapSeg.asSlice(heapLayout[3], heapLayout[4]-heapLayout[3]);
final MemorySegment resultSeg = heapSeg.asSlice(heapLayout[4], heapLayout[5]-heapLayout[4]);
//Recover the 3 sketches
final UpdatableThetaSketch sk1 = (UpdatableThetaSketch) ThetaSketch.wrap(sketch1seg);
final UpdatableThetaSketch sk2 = (UpdatableThetaSketch) ThetaSketch.wrap(sketch2seg);
final UpdatableThetaSketch sk3 = (UpdatableThetaSketch) ThetaSketch.wrap(sketch3seg);
//confirm that each of these 3 sketches is exact.
assertEquals(sk1.getEstimate(), sketchNomEntries, 0.0);
assertEquals(sk2.getEstimate(), sketchNomEntries, 0.0);
assertEquals(sk3.getEstimate(), sketchNomEntries, 0.0);
//Create a new union in the same space with a smaller size.
Util.clear(unionSeg);
final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(unionNomEntries).buildUnion(unionSeg);
union.union(sk1);
union.union(sk2);
union.union(sk3);
final ThetaSketch resSk = union.getResult(true, resultSeg);
final double est = resSk.getEstimate();
return est;
}
}