All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.tdigest.AVLGroupTree Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. 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.
 *
 * This project is based on a modification of https://github.com/tdunning/t-digest which is licensed under the Apache 2.0 License.
 */

package org.elasticsearch.tdigest;

import org.apache.lucene.util.Accountable;
import org.apache.lucene.util.RamUsageEstimator;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.core.Releasables;
import org.elasticsearch.tdigest.arrays.TDigestArrays;
import org.elasticsearch.tdigest.arrays.TDigestDoubleArray;
import org.elasticsearch.tdigest.arrays.TDigestLongArray;

import java.util.AbstractCollection;
import java.util.Iterator;

/**
 * A tree of t-digest centroids.
 */
final class AVLGroupTree extends AbstractCollection implements Releasable, Accountable {
    private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(AVLGroupTree.class);

    private final TDigestArrays arrays;
    private boolean closed = false;

    /* For insertions into the tree */
    private double centroid;
    private long count;
    private final TDigestDoubleArray centroids;
    private final TDigestLongArray counts;
    private final TDigestLongArray aggregatedCounts;
    private final IntAVLTree tree;

    static AVLGroupTree create(TDigestArrays arrays) {
        arrays.adjustBreaker(SHALLOW_SIZE);
        try {
            return new AVLGroupTree(arrays);
        } catch (Exception e) {
            arrays.adjustBreaker(-SHALLOW_SIZE);
            throw e;
        }
    }

    private AVLGroupTree(TDigestArrays arrays) {
        this.arrays = arrays;

        IntAVLTree tree = null;
        TDigestDoubleArray centroids = null;
        TDigestLongArray counts = null;
        TDigestLongArray aggregatedCounts = null;

        try {
            this.tree = tree = createIntAvlTree(arrays);
            this.centroids = centroids = arrays.newDoubleArray(tree.capacity());
            this.counts = counts = arrays.newLongArray(tree.capacity());
            this.aggregatedCounts = aggregatedCounts = arrays.newLongArray(tree.capacity());

            tree = null;
            centroids = null;
            counts = null;
            aggregatedCounts = null;
        } finally {
            Releasables.close(tree, centroids, counts, aggregatedCounts);
        }
    }

    private IntAVLTree createIntAvlTree(TDigestArrays arrays) {
        arrays.adjustBreaker(IntAVLTree.SHALLOW_SIZE);
        try {
            return new InternalIntAvlTree(arrays);
        } catch (Exception e) {
            arrays.adjustBreaker(-IntAVLTree.SHALLOW_SIZE);
            throw e;
        }
    }

    private class InternalIntAvlTree extends IntAVLTree {
        private InternalIntAvlTree(TDigestArrays arrays) {
            super(arrays);
        }

        @Override
        protected void resize(int newCapacity) {
            super.resize(newCapacity);
            centroids.resize(newCapacity);
            counts.resize(newCapacity);
            aggregatedCounts.resize(newCapacity);
        }

        @Override
        protected void merge(int node) {
            // two nodes are never considered equal
            throw new UnsupportedOperationException();
        }

        @Override
        protected void copy(int node) {
            centroids.set(node, centroid);
            counts.set(node, count);
        }

        @Override
        protected int compare(int node) {
            if (centroid < centroids.get(node)) {
                return -1;
            } else {
                // upon equality, the newly added node is considered greater
                return 1;
            }
        }

        @Override
        protected void fixAggregates(int node) {
            super.fixAggregates(node);
            aggregatedCounts.set(node, counts.get(node) + aggregatedCounts.get(left(node)) + aggregatedCounts.get(right(node)));
        }

    }

    @Override
    public long ramBytesUsed() {
        return SHALLOW_SIZE + centroids.ramBytesUsed() + counts.ramBytesUsed() + aggregatedCounts.ramBytesUsed() + tree.ramBytesUsed();
    }

    /**
     * Return the number of centroids in the tree.
     */
    public int size() {
        return tree.size();
    }

    /**
     * Return the previous node.
     */
    public int prev(int node) {
        return tree.prev(node);
    }

    /**
     * Return the next node.
     */
    public int next(int node) {
        return tree.next(node);
    }

    /**
     * Return the mean for the provided node.
     */
    public double mean(int node) {
        return centroids.get(node);
    }

    /**
     * Return the count for the provided node.
     */
    public long count(int node) {
        return counts.get(node);
    }

    /**
     * Add the provided centroid to the tree.
     */
    public void add(double centroid, long count) {
        this.centroid = centroid;
        this.count = count;
        tree.add();
    }

    @Override
    public boolean add(Centroid centroid) {
        add(centroid.mean(), centroid.count());
        return true;
    }

    /**
     * Update values associated with a node, readjusting the tree if necessary.
     */
    public void update(int node, double centroid, long count) {
        // have to do full scale update
        this.centroid = centroid;
        this.count = count;
        tree.update(node);
    }

    /**
     * Return the last node whose centroid is less than centroid.
     */
    public int floor(double centroid) {
        int floor = IntAVLTree.NIL;
        for (int node = tree.root(); node != IntAVLTree.NIL;) {
            final int cmp = Double.compare(centroid, mean(node));
            if (cmp <= 0) {
                node = tree.left(node);
            } else {
                floor = node;
                node = tree.right(node);
            }
        }
        return floor;
    }

    /**
     * Return the last node so that the sum of counts of nodes that are before
     * it is less than or equal to sum.
     */
    public int floorSum(long sum) {
        int floor = IntAVLTree.NIL;
        for (int node = tree.root(); node != IntAVLTree.NIL;) {
            final int left = tree.left(node);
            final long leftCount = aggregatedCounts.get(left);
            if (leftCount <= sum) {
                floor = node;
                sum -= leftCount + count(node);
                node = tree.right(node);
            } else {
                node = tree.left(node);
            }
        }
        return floor;
    }

    /**
     * Return the least node in the tree.
     */
    public int first() {
        return tree.first(tree.root());
    }

    /**
     * Return the least node in the tree.
     */
    public int last() {
        return tree.last(tree.root());
    }

    /**
     * Compute the number of elements and sum of counts for every entry that
     * is strictly before node.
     */
    public long headSum(int node) {
        final int left = tree.left(node);
        long sum = aggregatedCounts.get(left);
        for (int n = node, p = tree.parent(node); p != IntAVLTree.NIL; n = p, p = tree.parent(n)) {
            if (n == tree.right(p)) {
                final int leftP = tree.left(p);
                sum += counts.get(p) + aggregatedCounts.get(leftP);
            }
        }
        return sum;
    }

    @Override
    public Iterator iterator() {
        return iterator(first());
    }

    private Iterator iterator(final int startNode) {
        return new Iterator<>() {

            int nextNode = startNode;

            @Override
            public boolean hasNext() {
                return nextNode != IntAVLTree.NIL;
            }

            @Override
            public Centroid next() {
                final Centroid next = new Centroid(mean(nextNode), count(nextNode));
                nextNode = tree.next(nextNode);
                return next;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Read-only iterator");
            }

        };
    }

    /**
     * Return the total count of points that have been added to the tree.
     */
    public long sum() {
        return aggregatedCounts.get(tree.root());
    }

    void checkBalance() {
        tree.checkBalance(tree.root());
    }

    void checkAggregates() {
        checkAggregates(tree.root());
    }

    private void checkAggregates(int node) {
        assert aggregatedCounts.get(node) == counts.get(node) + aggregatedCounts.get(tree.left(node)) + aggregatedCounts.get(
            tree.right(node)
        );
        if (node != IntAVLTree.NIL) {
            checkAggregates(tree.left(node));
            checkAggregates(tree.right(node));
        }
    }

    @Override
    public void close() {
        if (closed == false) {
            closed = true;
            arrays.adjustBreaker(-SHALLOW_SIZE);
            Releasables.close(centroids, counts, aggregatedCounts, tree);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy