| /* |
| * 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 java.lang.foreign.ValueLayout.JAVA_BYTE; |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertFalse; |
| import static org.testng.Assert.assertTrue; |
| |
| import java.lang.foreign.Arena; |
| import java.lang.foreign.MemorySegment; |
| |
| import org.apache.datasketches.common.MemorySegmentStatus; |
| import org.apache.datasketches.common.SketchesArgumentException; |
| import org.apache.datasketches.common.Util; |
| import org.testng.annotations.Test; |
| |
| public class UnionImplTest { |
| |
| @Test |
| public void checkGetCurrentAndMaxBytes() { |
| final int lgK = 10; |
| final ThetaUnion union = ThetaSetOperation.builder().setLogNominalEntries(lgK).buildUnion(); |
| assertEquals(union.getCurrentBytes(), 288); |
| assertEquals(union.getMaxUnionBytes(), 16416); |
| } |
| |
| @Test |
| public void checkUpdateWithSketch() { |
| final int k = 16; |
| final MemorySegment seg = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final MemorySegment seg2 = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final UpdatableThetaSketch sketch = UpdatableThetaSketch.builder().setNominalEntries(k).build(); |
| for (int i=0; i<k; i++) { sketch.update(i); } |
| final CompactThetaSketch sketchInDirectOrd = sketch.compact(true, seg); |
| final CompactThetaSketch sketchInDirectUnord = sketch.compact(false, seg2); |
| final CompactThetaSketch sketchInHeap = sketch.compact(true, null); |
| |
| final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(); |
| union.union(sketchInDirectOrd); |
| union.union(sketchInHeap); |
| union.union(sketchInDirectUnord); |
| assertEquals(union.getResult().getEstimate(), k, 0.0); |
| } |
| |
| @Test |
| public void checkUnorderedAndOrderedMemorySegment() { |
| final int k = 16; |
| final MemorySegment seg = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final UpdatableThetaSketch sketch = UpdatableThetaSketch.builder().setNominalEntries(k).build(); |
| for (int i = 0; i < k; i++) { sketch.update(i); } |
| final CompactThetaSketch sketchInDirectOrd = sketch.compact(false, seg); |
| assertFalse(sketchInDirectOrd.isOrdered()); |
| final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(); |
| union.union(sketchInDirectOrd); |
| final double est1 = union.getResult().getEstimate(); |
| sketch.compact(true, seg); //change the order as a side effect |
| assertTrue(sketchInDirectOrd.isOrdered()); |
| union.union(sketchInDirectOrd); |
| final double est2 = union.getResult().getEstimate(); |
| assertEquals(est1, est2); |
| assertEquals((int)est1, k); |
| } |
| |
| @Test |
| public void checkUpdateWithSeg() { |
| final int k = 16; |
| final MemorySegment skSeg = MemorySegment.ofArray(new byte[(2*k*8) + 24]); |
| final MemorySegment dirOrdCskSeg = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final MemorySegment dirUnordCskSeg = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final UpdatableThetaSketch udSketch = UpdatableThetaSketch.builder().setNominalEntries(k).build(skSeg); |
| for (int i = 0; i < k; i++) { udSketch.update(i); } //exact |
| udSketch.compact(true, dirOrdCskSeg); |
| udSketch.compact(false, dirUnordCskSeg); |
| |
| final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(); |
| union.union(skSeg); |
| union.union(dirOrdCskSeg); |
| union.union(dirUnordCskSeg); |
| assertEquals(union.getResult().getEstimate(), k, 0.0); |
| } |
| |
| @Test |
| public void checkUpdateWithSegV4Exact() { |
| final int n = 1000; |
| final UpdatableThetaSketch sk = UpdatableThetaSketch.builder().build(); |
| for (int i = 0; i < n; i++) { |
| sk.update(i); |
| } |
| final CompactThetaSketch cs = sk.compact(); |
| assertFalse(cs.isEstimationMode()); |
| |
| final byte[] bytes = cs.toByteArrayCompressed(); |
| |
| final ThetaUnion union = ThetaSetOperation.builder().buildUnion(); |
| union.union(MemorySegment.ofArray(bytes)); |
| assertEquals(union.getResult().getEstimate(), n, 0.0); |
| } |
| |
| @Test |
| public void checkFastWrap() { |
| final int k = 16; |
| final long seed = Util.DEFAULT_UPDATE_SEED; |
| final int unionSize = ThetaSetOperation.getMaxUnionBytes(k); |
| final MemorySegment srcSeg = MemorySegment.ofArray(new byte[unionSize]); |
| final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(srcSeg); |
| for (int i = 0; i < k; i++) { union.update(i); } //exact |
| assertEquals(union.getResult().getEstimate(), k, 0.0); |
| final ThetaUnion union2 = ThetaUnionImpl.fastWrapInstance(srcSeg, seed); |
| assertEquals(union2.getResult().getEstimate(), k, 0.0); |
| final MemorySegment srcSegR = srcSeg; |
| final ThetaUnion union3 = ThetaUnionImpl.fastWrapInstance(srcSegR, seed); |
| assertEquals(union3.getResult().getEstimate(), k, 0.0); |
| } |
| |
| @Test(expectedExceptions = SketchesArgumentException.class) |
| public void checkCorruptFamilyException() { |
| final int k = 16; |
| final MemorySegment seg = MemorySegment.ofArray(new byte[(k*8) + 24]); |
| final UpdatableThetaSketch sketch = UpdatableThetaSketch.builder().setNominalEntries(k).build(); |
| for (int i=0; i<k; i++) { |
| sketch.update(i); |
| } |
| sketch.compact(true, seg); |
| |
| seg.set(JAVA_BYTE, PreambleUtil.FAMILY_BYTE, (byte)0); //corrupt family |
| |
| final ThetaUnion union = ThetaSetOperation.builder().setNominalEntries(k).buildUnion(); |
| union.union(seg); |
| } |
| |
| @Test |
| public void checkMoveAndResizeOffHeap() { |
| final int k = 1 << 12; |
| final int u = 2 * k; |
| final int bytes = ThetaSketch.getMaxUpdateSketchBytes(k); |
| MemorySegment skWseg, uWseg; |
| try (Arena arena = Arena.ofConfined()) { |
| skWseg = arena.allocate(bytes / 2); //we never populate the sketch so this size is not relevant |
| final UpdatableThetaSketch sketch = UpdatableThetaSketch.builder().setNominalEntries(k).build(skWseg); |
| //The sketch has not changed, so the sketch's internal segment is what we gave it. |
| assertTrue(sketch.isSameResource(skWseg)); |
| |
| //We now do the same with an independent ThetaUnion |
| uWseg = arena.allocate(bytes / 2); // way too small, this will forces overflow |
| final ThetaUnion union = ThetaSetOperation.builder().buildUnion(uWseg); //off-heap union |
| assertTrue(union.isSameResource(uWseg)); //ThetaUnion has not changed |
| |
| for (int i = 0; i < u; i++) { union.update(i); } //populate first union |
| assertFalse(union.isSameResource(skWseg)); //ThetaSketch and ThetaUnion segments are totally distinct |
| //Here the union's segment overflowed and moved to the heap, |
| // thus, its reference to its segment is not the one we gave it! |
| assertFalse(union.isSameResource(uWseg)); //moved. |
| |
| //Here we create a 2nd ThetaUnion with a heap segment |
| final MemorySegment uWsegHeap = MemorySegment.ofArray(new byte[bytes / 2]); |
| final ThetaUnion union2 = ThetaSetOperation.builder().buildUnion(uWsegHeap); //union with on-heap segment |
| //As before, this establishes that the empty union2 has the same segment that we gave it. |
| assertTrue(union2.isSameResource(uWsegHeap)); //Copilot complained this was absent, so here it is! |
| |
| //These two asserts are calling the Interface static method directly and establish |
| // what should be obvious, that these are all different segments. |
| assertFalse(MemorySegmentStatus.isSameResource(uWseg, uWsegHeap)); //obviously not |
| assertFalse(MemorySegmentStatus.isSameResource(uWsegHeap, union.getMemorySegment())); //tgt moved |
| } //the off-heap segments will be closed here |
| assertFalse(skWseg.scope().isAlive()); //closed as part of the same Arena |
| assertFalse(uWseg.scope().isAlive()); //closed as part of the same Arena |
| //we don't need to close the heap segment. |
| } |
| |
| @Test |
| public void checkRestricted() { |
| final ThetaUnion union = ThetaSetOperation.builder().buildUnion(); |
| assertTrue(union.isEmpty()); |
| assertEquals(union.getThetaLong(), Long.MAX_VALUE); |
| assertEquals(union.getSeedHash(), Util.computeSeedHash(Util.DEFAULT_UPDATE_SEED)); |
| assertEquals(union.getRetainedEntries(), 0); |
| assertEquals(union.getCache().length, 128); //only applies to stateful |
| } |
| |
| @Test |
| public void checkUnionCompactOrderedSource() { |
| final int lgK = 12; |
| final int k = 1 << lgK; |
| final UpdatableThetaSketch sk = UpdatableThetaSketch.builder().build(); |
| for (int i = 0; i < k; i++) { sk.update(i); } |
| final double est1 = sk.getEstimate(); |
| |
| final int bytes = ThetaSketch.getCompactSketchMaxBytes(lgK); |
| try (Arena arena = Arena.ofConfined()) { |
| final MemorySegment wseg = arena.allocate(bytes); |
| |
| final CompactThetaSketch csk = sk.compact(true, wseg); //ordered, direct |
| final ThetaUnion union = ThetaSetOperation.builder().buildUnion(); |
| union.union(csk); |
| final double est2 = union.getResult().getEstimate(); |
| assertEquals(est2, est1); |
| } catch (final Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Test(expectedExceptions = SketchesArgumentException.class) |
| public void checkCompactFlagCorruption() { |
| final int k = 1 << 12; |
| final int bytes = ThetaSketch.getMaxUpdateSketchBytes(k); |
| final MemorySegment wseg1 = MemorySegment.ofArray(new byte[bytes]); |
| final UpdatableThetaSketch sk = UpdatableThetaSketch.builder().setNominalEntries(k).build(wseg1); |
| for (int i = 0; i < k; i++) { sk.update(i); } |
| sk.compact(true, wseg1); //corrupt the wseg1 to be a compact sketch |
| |
| final ThetaUnion union = ThetaSetOperation.builder().buildUnion(); |
| union.union(sk); //update the union with the UpdatableThetaSketch object |
| final CompactThetaSketch csk1 = union.getResult(); |
| println(""+csk1.getEstimate()); |
| } |
| |
| @Test //checks for bug introduced in 1.0.0-incubating. |
| public void checkDirectUnionSingleItem() { |
| final int num = 2; |
| final UpdatableThetaSketch[] skArr = new UpdatableThetaSketch[num]; |
| for (int i = 0; i < num; i++) { |
| skArr[i] = new UpdatableThetaSketchBuilder().build(); |
| } |
| for (int i = 0; i < (num/2); i++) { |
| skArr[i].update(i); |
| skArr[i + (num/2)].update(i); |
| skArr[i].update(i + num); |
| } |
| |
| ThetaUnion union = new ThetaSetOperationBuilder().buildUnion(); |
| for (int i = 0; i < num; i++) { |
| union.union(skArr[i]); |
| } |
| |
| CompactThetaSketch csk = union.getResult(); |
| assertEquals(csk.getEstimate(), 2.0); |
| //println(csk.toString(true, true, 1, true)); |
| |
| final MemorySegment[] segArr = new MemorySegment[num]; |
| for (int i = 0; i < num; i++) { |
| segArr[i] = MemorySegment.ofArray(skArr[i].compact().toByteArray()); |
| } |
| union = new ThetaSetOperationBuilder().buildUnion(); |
| for (int i = 0; i < num; i++) { |
| union.union(segArr[i]); |
| } |
| |
| csk = union.getResult(); |
| assertEquals(csk.getEstimate(), 2.0); |
| //println(csk.toString(true, true, 1, true)); |
| } |
| |
| @Test |
| public void printlnTest() { |
| println("PRINTING: "+this.getClass().getName()); |
| } |
| |
| /** |
| * @param o value to print |
| */ |
| static void println(final Object o) { |
| //System.out.println(o.toString()); //disable here |
| } |
| |
| } |