blob: 03fb2f48ceb00650204aa7ec6dc3bff7c931f19c [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.tuple;
import static org.apache.datasketches.common.Util.DEFAULT_UPDATE_SEED;
import static org.apache.datasketches.common.Util.computeSeedHash;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;
import java.lang.foreign.MemorySegment;
import org.apache.datasketches.common.ByteArrayUtil;
import org.apache.datasketches.common.ResizeFactor;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.SketchesStateException;
import org.apache.datasketches.fdt.FdtSketch;
import org.apache.datasketches.theta.UpdatableThetaSketch;
import org.apache.datasketches.tuple.adouble.DoubleSummary;
import org.apache.datasketches.tuple.adouble.DoubleSummaryDeserializer;
import org.apache.datasketches.tuple.adouble.DoubleSummaryFactory;
import org.apache.datasketches.tuple.adouble.DoubleSummarySetOperations;
import org.apache.datasketches.tuple.adouble.DoubleTupleSketch;
import org.apache.datasketches.tuple.aninteger.IntegerSummary;
import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
import org.apache.datasketches.tuple.strings.ArrayOfStringsTupleSketch;
import org.testng.annotations.Test;
/**
* Checks TupleSketches and their set operations created with a custom update seed.
*/
@SuppressWarnings("deprecation")
public class TupleCustomSeedTest {
private static final long SEED = 123L;
private static final short SEED_HASH = computeSeedHash(SEED);
private static final short DEFAULT_SEED_HASH = computeSeedHash(DEFAULT_UPDATE_SEED);
private static final int SEED_HASH_SHORT = 6;
private final DoubleSummaryFactory factory = new DoubleSummaryFactory(DoubleSummary.Mode.Sum);
private final DoubleSummarySetOperations dsso = new DoubleSummarySetOperations();
private final DoubleSummaryDeserializer deser = new DoubleSummaryDeserializer();
private UpdatableTupleSketch<Double, DoubleSummary> tupleSketch(final long seed, final int start, final int n) {
final UpdatableTupleSketch<Double, DoubleSummary> sk =
new UpdatableTupleSketchBuilder<>(factory).setSeed(seed).build();
for (int i = start; i < (start + n); i++) { sk.update(i, 1.0); }
return sk;
}
private static UpdatableThetaSketch thetaSketch(final long seed, final int start, final int n) {
final UpdatableThetaSketch sk = UpdatableThetaSketch.builder().setSeed(seed).build();
for (int i = start; i < (start + n); i++) { sk.update(i); }
return sk;
}
private static short seedHashOf(final byte[] bytes) {
return ByteArrayUtil.getShortLE(bytes, SEED_HASH_SHORT);
}
@Test
public void checkSeedHashIsCarried() {
final UpdatableTupleSketch<Double, DoubleSummary> sk = tupleSketch(SEED, 0, 10);
assertEquals(sk.getSeedHash(), SEED_HASH);
assertEquals(sk.copy().getSeedHash(), SEED_HASH);
assertEquals(sk.compact().getSeedHash(), SEED_HASH);
assertEquals(seedHashOf(sk.compact().toByteArray()), SEED_HASH);
assertEquals(tupleSketch(DEFAULT_UPDATE_SEED, 0, 10).getSeedHash(), DEFAULT_SEED_HASH);
}
@Test
public void checkDefaultSeedHashMatchesCpp() {
final byte[] bytes = tupleSketch(DEFAULT_UPDATE_SEED, 0, 10).compact().toByteArray();
assertEquals(seedHashOf(bytes), (short) 37836);
}
@Test
public void checkSeedChangesHashes() {
assertNotEquals(hashOf(tupleSketch(SEED, 0, 1)), hashOf(tupleSketch(DEFAULT_UPDATE_SEED, 0, 1)));
}
@Test
public void checkHeapifyCompact() {
final UpdatableTupleSketch<Double, DoubleSummary> sk = tupleSketch(SEED, 0, 100);
final MemorySegment seg = MemorySegment.ofArray(sk.compact().toByteArray());
expectThrows(SketchesArgumentException.class, () -> TupleSketch.heapifySketch(seg, deser));
final TupleSketch<DoubleSummary> sk2 = TupleSketch.heapifySketch(seg, deser, SEED);
assertEquals(sk2.getEstimate(), sk.getEstimate());
assertEquals(sk2.getSeedHash(), SEED_HASH);
//default seed image read with a custom seed is rejected
final MemorySegment segDef = MemorySegment.ofArray(tupleSketch(DEFAULT_UPDATE_SEED, 0, 100).compact().toByteArray());
expectThrows(SketchesArgumentException.class, () -> TupleSketch.heapifySketch(segDef, deser, SEED));
}
@Test
public void checkHeapifyEmptyCompact() {
final byte[] bytes = tupleSketch(SEED, 0, 0).compact().toByteArray();
assertEquals(seedHashOf(bytes), (short) 0); //empty image carries zero, as in theta
final MemorySegment seg = MemorySegment.ofArray(bytes);
assertTrue(TupleSketch.heapifySketch(seg, deser).isEmpty());
final TupleSketch<DoubleSummary> sk = TupleSketch.heapifySketch(seg, deser, SEED);
assertTrue(sk.isEmpty());
assertEquals(sk.getSeedHash(), SEED_HASH);
}
@Test
public void checkUpdatableSerialization() {
final UpdatableTupleSketch<Double, DoubleSummary> sk = tupleSketch(SEED, 0, 10);
expectThrows(SketchesStateException.class, () -> sk.toByteArray());
//the updatable form has no seed hash, so it is read as the default seed
final byte[] bytes = tupleSketch(DEFAULT_UPDATE_SEED, 0, 10).toByteArray();
final MemorySegment seg = MemorySegment.ofArray(bytes);
assertEquals(TupleSketch.heapifySketch(seg, deser).getRetainedEntries(), 10);
expectThrows(SketchesArgumentException.class, () -> TupleSketch.heapifySketch(seg, deser, SEED));
}
@Test
public void checkUpdatableSerializationComparesFullSeed() {
//a custom seed whose 16-bit seed hash equals that of the default seed must still be refused
final short defaultSeedHash = computeSeedHash(DEFAULT_UPDATE_SEED);
long seed = 1;
while ((seed == DEFAULT_UPDATE_SEED) || (computeSeedHash(seed) != defaultSeedHash)) { seed++; }
final UpdatableTupleSketch<Double, DoubleSummary> sk = tupleSketch(seed, 0, 10);
assertEquals(sk.getSeedHash(), defaultSeedHash);
expectThrows(SketchesStateException.class, () -> sk.toByteArray());
expectThrows(SketchesStateException.class, () -> sk.copy().toByteArray());
expectThrows(SketchesStateException.class,
() -> new DoubleTupleSketch(12, 3, 1f, DoubleSummary.Mode.Sum, SEED).toByteArray());
assertEquals(sk.compact().toByteArray().length, tupleSketch(DEFAULT_UPDATE_SEED, 0, 10).compact().toByteArray().length);
}
@Test
public void checkUnion() {
final TupleUnion<DoubleSummary> union = new TupleUnion<>(4096, dsso, SEED);
union.union(tupleSketch(SEED, 0, 10));
union.union(thetaSketch(SEED, 5, 10), factory.newSummary());
expectThrows(SketchesArgumentException.class, () -> union.union(tupleSketch(DEFAULT_UPDATE_SEED, 0, 10)));
expectThrows(SketchesArgumentException.class,
() -> union.union(thetaSketch(DEFAULT_UPDATE_SEED, 0, 10), factory.newSummary()));
union.union(tupleSketch(DEFAULT_UPDATE_SEED, 0, 0)); //empty is ignored
final CompactTupleSketch<DoubleSummary> result = union.getResult();
assertEquals(result.getRetainedEntries(), 15);
assertEquals(result.getSeedHash(), SEED_HASH);
//the default union rejects custom seed sketches
final TupleUnion<DoubleSummary> defUnion = new TupleUnion<>(dsso);
expectThrows(SketchesArgumentException.class, () -> defUnion.union(tupleSketch(SEED, 0, 10)));
}
@Test
public void checkIntersection() {
final TupleIntersection<DoubleSummary> inter = new TupleIntersection<>(dsso, SEED);
inter.intersect(tupleSketch(SEED, 0, 10));
inter.intersect(thetaSketch(SEED, 5, 10), factory.newSummary());
expectThrows(SketchesArgumentException.class, () -> inter.intersect(tupleSketch(DEFAULT_UPDATE_SEED, 0, 10)));
final CompactTupleSketch<DoubleSummary> result = inter.getResult();
assertEquals(result.getRetainedEntries(), 5);
assertEquals(result.getSeedHash(), SEED_HASH);
final TupleIntersection<DoubleSummary> defInter = new TupleIntersection<>(dsso);
expectThrows(SketchesArgumentException.class, () -> defInter.intersect(tupleSketch(SEED, 0, 10)));
}
@Test
public void checkAnotB() {
final TupleAnotB<DoubleSummary> anotb = new TupleAnotB<>(SEED);
anotb.setA(tupleSketch(SEED, 0, 10));
anotb.notB(tupleSketch(SEED, 0, 2));
anotb.notB(thetaSketch(SEED, 2, 2));
expectThrows(SketchesArgumentException.class, () -> anotb.notB(tupleSketch(DEFAULT_UPDATE_SEED, 0, 10)));
final CompactTupleSketch<DoubleSummary> result = anotb.getResult(true);
assertEquals(result.getRetainedEntries(), 6);
assertEquals(result.getSeedHash(), SEED_HASH);
final TupleAnotB<DoubleSummary> defAnotB = new TupleAnotB<>();
expectThrows(SketchesArgumentException.class, () -> defAnotB.setA(tupleSketch(SEED, 0, 10)));
}
@Test
public void checkStatelessAnotB() {
final CompactTupleSketch<DoubleSummary> r1 =
TupleAnotB.aNotB(tupleSketch(SEED, 0, 10), tupleSketch(SEED, 0, 4), SEED);
assertEquals(r1.getRetainedEntries(), 6);
assertEquals(r1.getSeedHash(), SEED_HASH);
final CompactTupleSketch<DoubleSummary> r2 =
TupleAnotB.aNotB(tupleSketch(SEED, 0, 10), thetaSketch(SEED, 0, 4), SEED);
assertEquals(r2.getRetainedEntries(), 6);
assertEquals(r2.getSeedHash(), SEED_HASH);
expectThrows(SketchesArgumentException.class,
() -> TupleAnotB.aNotB(tupleSketch(SEED, 0, 10), tupleSketch(SEED, 0, 4)));
expectThrows(SketchesArgumentException.class,
() -> TupleAnotB.aNotB(tupleSketch(SEED, 0, 10), thetaSketch(DEFAULT_UPDATE_SEED, 0, 4), SEED));
}
@Test
public void checkJaccard() {
final double[] j = JaccardSimilarity.jaccard(tupleSketch(SEED, 0, 10), tupleSketch(SEED, 5, 10), dsso, SEED);
assertEquals(j[1], 5.0 / 15.0, 1e-9);
final double[] jt = JaccardSimilarity.jaccard(tupleSketch(SEED, 0, 10), thetaSketch(SEED, 5, 10),
factory.newSummary(), dsso, SEED);
assertEquals(jt[1], 5.0 / 15.0, 1e-9);
assertTrue(JaccardSimilarity.exactlyEqual(tupleSketch(SEED, 0, 10), tupleSketch(SEED, 0, 10), dsso, SEED));
expectThrows(SketchesArgumentException.class,
() -> JaccardSimilarity.jaccard(tupleSketch(SEED, 0, 10), tupleSketch(SEED, 5, 10), dsso));
}
@Test
public void checkFilter() {
final Filter<DoubleSummary> filter = new Filter<>(s -> s.getValue() > 0);
assertEquals(filter.filter(tupleSketch(SEED, 0, 10)).getSeedHash(), SEED_HASH);
}
@Test
public void checkDerivedSketches() {
final DoubleTupleSketch dsk = new DoubleTupleSketch(12, ResizeFactor.X8.lg(), 1f, DoubleSummary.Mode.Sum, SEED);
dsk.update(1, 1.0);
assertEquals(dsk.compact().getSeedHash(), SEED_HASH);
final IntegerTupleSketch isk = new IntegerTupleSketch(12, ResizeFactor.X8.lg(), 1f, IntegerSummary.Mode.Sum, SEED);
isk.update(1, 1);
assertEquals(isk.compact().getSeedHash(), SEED_HASH);
final ArrayOfStringsTupleSketch ask = new ArrayOfStringsTupleSketch(12, ResizeFactor.X8, 1f, SEED);
ask.update(new String[] {"a"}, new String[] {"b"});
assertEquals(ask.compact().getSeedHash(), SEED_HASH);
assertEquals(ask.copy().compact().getSeedHash(), SEED_HASH);
final FdtSketch fsk = new FdtSketch(12, SEED);
fsk.update(new String[] {"a", "b"});
assertEquals(fsk.compact().getSeedHash(), SEED_HASH);
assertEquals(fsk.copy().compact().getSeedHash(), SEED_HASH);
final ArrayOfStringsTupleSketch askDef = new ArrayOfStringsTupleSketch();
askDef.update(new String[] {"a"}, new String[] {"b"});
assertNotEquals(hashOf(ask), hashOf(askDef));
}
private static long hashOf(final TupleSketch<?> sk) {
final TupleSketchIterator<?> it = sk.iterator();
while (it.next()) { return it.getHash(); }
return 0;
}
}