Package de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants

Source Code of de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.AbstractMTree$SplitResult

package de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants;

/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures

Copyright (C) 2011
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import de.lmu.ifi.dbs.elki.database.ids.DBID;
import de.lmu.ifi.dbs.elki.database.ids.DBIDs;
import de.lmu.ifi.dbs.elki.database.query.distance.DistanceQuery;
import de.lmu.ifi.dbs.elki.distance.DistanceUtil;
import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction;
import de.lmu.ifi.dbs.elki.distance.distancevalue.Distance;
import de.lmu.ifi.dbs.elki.index.tree.BreadthFirstEnumeration;
import de.lmu.ifi.dbs.elki.index.tree.DistanceEntry;
import de.lmu.ifi.dbs.elki.index.tree.IndexTreePath;
import de.lmu.ifi.dbs.elki.index.tree.TreeIndexPathComponent;
import de.lmu.ifi.dbs.elki.index.tree.metrical.MetricalIndexTree;
import de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.split.Assignments;
import de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.split.MLBDistSplit;
import de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.split.MTreeSplit;
import de.lmu.ifi.dbs.elki.index.tree.query.GenericMTreeDistanceSearchCandidate;
import de.lmu.ifi.dbs.elki.persistent.PageFile;
import de.lmu.ifi.dbs.elki.persistent.PageFileUtil;
import de.lmu.ifi.dbs.elki.utilities.datastructures.heap.Heap;
import de.lmu.ifi.dbs.elki.utilities.datastructures.heap.KNNHeap;
import de.lmu.ifi.dbs.elki.utilities.datastructures.heap.UpdatableHeap;

/**
* Abstract super class for all M-Tree variants.
*
* @author Elke Achtert
*
* @apiviz.has SplitResult oneway - - computes
* @apiviz.has AbstractMTreeNode oneway - - contains
*
* @param <O> the type of DatabaseObject to be stored in the metrical index
* @param <D> the type of Distance used in the metrical index
* @param <N> the type of MetricalNode used in the metrical index
* @param <E> the type of MetricalEntry used in the metrical index
*/
public abstract class AbstractMTree<O, D extends Distance<D>, N extends AbstractMTreeNode<O, D, N, E>, E extends MTreeEntry<D>> extends MetricalIndexTree<O, D, N, E> {
  /**
   * Debugging flag: do extra integrity checks
   */
  protected final static boolean extraIntegrityChecks = true;

  /**
   * Holds the instance of the trees distance function
   */
  protected DistanceFunction<O, D> distanceFunction;

  /**
   * The distance query
   */
  protected DistanceQuery<O, D> distanceQuery;

  /**
   * Constructor.
   *
   * @param pagefile Page file
   * @param distanceQuery Distance query
   * @param distanceFunction Distance function
   */
  public AbstractMTree(PageFile<N> pagefile, DistanceQuery<O, D> distanceQuery, DistanceFunction<O, D> distanceFunction) {
    super(pagefile);
    this.distanceQuery = distanceQuery;
    this.distanceFunction = distanceFunction;
  }

  @Override
  public final DistanceFunction<O, D> getDistanceFunction() {
    return distanceFunction;
  }

  @Override
  public final DistanceQuery<O, D> getDistanceQuery() {
    return distanceQuery;
  }

  /**
   * Get the distance factory
   *
   * @return the distance factory used
   */
  protected final D getDistanceFactory() {
    return distanceFunction.getDistanceFactory();
  }

  /**
   * Returns a string representation of this M-Tree by performing a breadth
   * first enumeration on the tree and adding the string representation of the
   * visited nodes and their entries to the result.
   *
   * @return a string representation of this M-Tree
   */
  @Override
  public String toString() {
    StringBuffer result = new StringBuffer();
    int dirNodes = 0;
    int leafNodes = 0;
    int objects = 0;
    int levels = 0;

    N node = getRoot();

    while(!node.isLeaf()) {
      if(node.getNumEntries() > 0) {
        E entry = node.getEntry(0);
        node = getNode(entry);
        levels++;
      }
    }

    BreadthFirstEnumeration<N, E> enumeration = new BreadthFirstEnumeration<N, E>(this, getRootPath());
    while(enumeration.hasMoreElements()) {
      IndexTreePath<E> path = enumeration.nextElement();
      E entry = path.getLastPathComponent().getEntry();
      if(entry.isLeafEntry()) {
        objects++;
        result.append("\n    ").append(entry.toString());
      }
      else {
        node = getNode(entry);
        result.append("\n\n").append(node).append(", numEntries = ").append(node.getNumEntries());
        result.append("\n").append(entry.toString());

        if(node.isLeaf()) {
          leafNodes++;
        }
        else {
          dirNodes++;
        }
      }
    }

    result.append(getClass().getName()).append(" hat ").append((levels + 1)).append(" Ebenen \n");
    result.append("DirCapacity = ").append(dirCapacity).append("\n");
    result.append("LeafCapacity = ").append(leafCapacity).append("\n");
    result.append(dirNodes).append(" Directory Nodes \n");
    result.append(leafNodes).append(" Leaf Nodes \n");
    result.append(objects).append(" Objects \n");

    PageFileUtil.appendPageFileStatistics(result, getPageFileStatistics());
    return result.toString();
  }

  /**
   * Inserts the specified object into this M-Tree.
   *
   * @param entry the entry to be inserted
   * @param withPreInsert if this flag is true, the preInsert method will be
   *        called before inserting the object
   */
  // todo: implement a bulk load for M-Tree and remove this method
  public void insert(E entry, boolean withPreInsert) {
    if(getLogger().isDebugging()) {
      getLogger().debugFine("insert " + entry.getRoutingObjectID() + "\n");
    }

    if(!initialized) {
      initialize(entry);
    }

    // choose subtree for insertion
    IndexTreePath<E> subtree = choosePath(entry, getRootPath());
    if(getLogger().isDebugging()) {
      getLogger().debugFine("insertion-subtree " + subtree + "\n");
    }

    // determine parent distance
    E parentEntry = subtree.getLastPathComponent().getEntry();
    D parentDistance = distance(parentEntry.getRoutingObjectID(), entry.getRoutingObjectID());
    entry.setParentDistance(parentDistance);

    // create leaf entry and do pre insert
    if(withPreInsert) {
      preInsert(entry);
    }

    // get parent node
    N parent = getNode(parentEntry);
    parent.addLeafEntry(entry);
    writeNode(parent);

    // adjust the tree from subtree to root
    adjustTree(subtree);

    // test
    if(extraIntegrityChecks) {
      if(withPreInsert) {
        getRoot().integrityCheck(this, getRootEntry());
      }
    }
  }

  /**
   * Bulk insert.
   *
   * @param entries
   */
  public void insertAll(List<E> entries) {
    if(!initialized && entries.size() > 0) {
      initialize(entries.get(0));
    }
    for(E entry : entries) {
      insert(entry, false);
    }
  }

  @Override
  protected final void createEmptyRoot(E exampleLeaf) {
    N root = createNewLeafNode();
    writeNode(root);
  }

  /**
   * Performs a k-nearest neighbor query for the given FeatureVector with the
   * given parameter k and the according distance function. The query result is
   * in ascending order to the distance to the query object.
   *
   * @param q the id of the query object
   * @param knnList the query result list
   */
  protected final void doKNNQuery(DBID q, KNNHeap<D> knnList) {
    final Heap<GenericMTreeDistanceSearchCandidate<D>> pq = new UpdatableHeap<GenericMTreeDistanceSearchCandidate<D>>();

    // push root
    pq.add(new GenericMTreeDistanceSearchCandidate<D>(getDistanceFactory().nullDistance(), getRootID(), null));
    D d_k = knnList.getKNNDistance();

    if (d_k == null) {
      // Empty tree?
      return;
    }
   
    // search in tree
    while(!pq.isEmpty()) {
      GenericMTreeDistanceSearchCandidate<D> pqNode = pq.poll();

      if(pqNode.mindist.compareTo(d_k) > 0) {
        return;
      }

      N node = getNode(pqNode.nodeID);
      DBID o_p = pqNode.routingObjectID;

      // directory node
      if(!node.isLeaf()) {
        for(int i = 0; i < node.getNumEntries(); i++) {
          E entry = node.getEntry(i);
          DBID o_r = entry.getRoutingObjectID();
          D r_or = entry.getCoveringRadius();
          D d1 = o_p != null ? distanceQuery.distance(o_p, q) : getDistanceFactory().nullDistance();
          D d2 = o_p != null ? distanceQuery.distance(o_r, o_p) : getDistanceFactory().nullDistance();

          D diff = d1.compareTo(d2) > 0 ? d1.minus(d2) : d2.minus(d1);

          D sum = d_k.plus(r_or);

          if(diff.compareTo(sum) <= 0) {
            D d3 = distance(o_r, q);
            D d_min = DistanceUtil.max(d3.minus(r_or), getDistanceFactory().nullDistance());
            if(d_min.compareTo(d_k) <= 0) {
              pq.add(new GenericMTreeDistanceSearchCandidate<D>(d_min, getPageID(entry), o_r));
            }
          }
        }

      }

      // data node
      else {
        for(int i = 0; i < node.getNumEntries(); i++) {
          E entry = node.getEntry(i);
          DBID o_j = entry.getRoutingObjectID();

          D d1 = o_p != null ? distanceQuery.distance(o_p, q) : getDistanceFactory().nullDistance();
          D d2 = o_p != null ? distanceQuery.distance(o_j, o_p) : getDistanceFactory().nullDistance();

          D diff = d1.compareTo(d2) > 0 ? d1.minus(d2) : d2.minus(d1);

          if(diff.compareTo(d_k) <= 0) {
            D d3 = distanceQuery.distance(o_j, q);
            if(d3.compareTo(d_k) <= 0) {
              knnList.add(d3, o_j);
              d_k = knnList.getKNNDistance();
            }
          }
        }
      }
    }
  }

  /**
   * Chooses the best path of the specified subtree for insertion of the given
   * object.
   *
   * @param object the entry to search
   * @param subtree the subtree to be tested for insertion
   * @return the path of the appropriate subtree to insert the given object
   */
  private IndexTreePath<E> choosePath(E object, IndexTreePath<E> subtree) {
    N node = getNode(subtree.getLastPathComponent().getEntry());

    // leaf
    if(node.isLeaf()) {
      return subtree;
    }

    D nullDistance = getDistanceFactory().nullDistance();
    List<DistanceEntry<D, E>> candidatesWithoutExtension = new ArrayList<DistanceEntry<D, E>>();
    List<DistanceEntry<D, E>> candidatesWithExtension = new ArrayList<DistanceEntry<D, E>>();

    for(int i = 0; i < node.getNumEntries(); i++) {
      E entry = node.getEntry(i);
      D distance = distance(object.getRoutingObjectID(), entry.getRoutingObjectID());
      D enlrg = distance.minus(entry.getCoveringRadius());

      if(enlrg.compareTo(nullDistance) <= 0) {
        candidatesWithoutExtension.add(new DistanceEntry<D, E>(entry, distance, i));
      }
      else {
        candidatesWithExtension.add(new DistanceEntry<D, E>(entry, enlrg, i));
      }
    }

    DistanceEntry<D, E> bestCandidate;
    if(!candidatesWithoutExtension.isEmpty()) {
      bestCandidate = Collections.min(candidatesWithoutExtension);
    }
    else {
      Collections.sort(candidatesWithExtension);
      bestCandidate = Collections.min(candidatesWithExtension);
      E entry = bestCandidate.getEntry();
      D cr = entry.getCoveringRadius();
      entry.setCoveringRadius(cr.plus(bestCandidate.getDistance()));
    }

    return choosePath(object, subtree.pathByAddingChild(new TreeIndexPathComponent<E>(bestCandidate.getEntry(), bestCandidate.getIndex())));
  }

  /**
   * Performs a batch k-nearest neigbor query for a list of query objects.
   *
   * @param node the node reprsenting the subtree on which the query should be
   *        performed
   * @param ids the ids of th query objects
   * @param knnLists the knn lists of the query objcets
   *
   * @deprecated Change to use by-object NN lookups instead.
   */
  @Deprecated
  protected final void batchNN(N node, DBIDs ids, Map<DBID, KNNHeap<D>> knnLists) {
    if(node.isLeaf()) {
      for(int i = 0; i < node.getNumEntries(); i++) {
        E p = node.getEntry(i);
        for(DBID q : ids) {
          KNNHeap<D> knns_q = knnLists.get(q);
          D knn_q_maxDist = knns_q.getKNNDistance();

          D dist_pq = distanceQuery.distance(p.getRoutingObjectID(), q);
          if(dist_pq.compareTo(knn_q_maxDist) <= 0) {
            knns_q.add(dist_pq, p.getRoutingObjectID());
          }
        }
      }
    }
    else {
      List<DistanceEntry<D, E>> entries = getSortedEntries(node, ids);
      for(DistanceEntry<D, E> distEntry : entries) {
        D minDist = distEntry.getDistance();
        for(DBID q : ids) {
          KNNHeap<D> knns_q = knnLists.get(q);
          D knn_q_maxDist = knns_q.getKNNDistance();

          if(minDist.compareTo(knn_q_maxDist) <= 0) {
            E entry = distEntry.getEntry();
            N child = getNode(entry);
            batchNN(child, ids, knnLists);
            break;
          }
        }
      }
    }
  }

  /**
   * Sorts the entries of the specified node according to their minimum distance
   * to the specified object.
   *
   * @param node the node
   * @param q the id of the object
   * @return a list of the sorted entries
   */
  protected final List<DistanceEntry<D, E>> getSortedEntries(N node, DBID q) {
    List<DistanceEntry<D, E>> result = new ArrayList<DistanceEntry<D, E>>();

    for(int i = 0; i < node.getNumEntries(); i++) {
      E entry = node.getEntry(i);
      D distance = distance(entry.getRoutingObjectID(), q);
      D minDist = entry.getCoveringRadius().compareTo(distance) > 0 ? getDistanceFactory().nullDistance() : distance.minus(entry.getCoveringRadius());

      result.add(new DistanceEntry<D, E>(entry, minDist, i));
    }

    Collections.sort(result);
    return result;
  }

  /**
   * Sorts the entries of the specified node according to their minimum distance
   * to the specified objects.
   *
   * @param node the node
   * @param ids the ids of the objects
   * @return a list of the sorted entries
   */
  protected final List<DistanceEntry<D, E>> getSortedEntries(N node, DBIDs ids) {
    List<DistanceEntry<D, E>> result = new ArrayList<DistanceEntry<D, E>>();

    for(int i = 0; i < node.getNumEntries(); i++) {
      E entry = node.getEntry(i);

      D minMinDist = getDistanceFactory().infiniteDistance();
      for(DBID q : ids) {
        D distance = distanceQuery.distance(entry.getRoutingObjectID(), q);
        D minDist = entry.getCoveringRadius().compareTo(distance) > 0 ? getDistanceFactory().nullDistance() : distance.minus(entry.getCoveringRadius());
        minMinDist = DistanceUtil.max(minMinDist, minDist);
      }
      result.add(new DistanceEntry<D, E>(entry, minMinDist, i));
    }

    Collections.sort(result);
    return result;
  }

  /**
   * Returns the distance between the two specified ids.
   *
   * @param id1 the first id
   * @param id2 the second id
   * @return the distance between the two specified ids
   */
  protected final D distance(DBID id1, DBID id2) {
    if(id1 == null || id2 == null) {
      return getDistanceFactory().undefinedDistance();
    }
    return distanceQuery.distance(id1, id2);
  }

  /**
   * Returns the distance between the given object and the id.
   *
   * @param id1 the first id
   * @param o2 the second object
   * @return the distance between the two specified objects
   */
  protected final D distance(DBID id1, O o2) {
    if(id1 == null) {
      return getDistanceFactory().undefinedDistance();
    }
    return distanceQuery.distance(id1, o2);
  }

  /**
   * Creates a new directory entry representing the specified node.
   *
   * @param node the node to be represented by the new entry
   * @param routingObjectID the id of the routing object of the node
   * @param parentDistance the distance from the routing object of the node to
   *        the routing object of the parent node
   * @return the newly created directory entry
   */
  abstract protected E createNewDirectoryEntry(N node, DBID routingObjectID, D parentDistance);

  /**
   * Splits the specified node and returns the split result.
   *
   * @param node the node to be split
   * @return the split result
   */
  private SplitResult split(N node) {
    // do the split
    // todo split stratgey
    MTreeSplit<O, D, N, E> split = new MLBDistSplit<O, D, N, E>(node, distanceQuery);
    Assignments<D, E> assignments = split.getAssignments();
    final N newNode;
    if(node.isLeaf()) {
      newNode = createNewLeafNode();
    }
    else {
      newNode = createNewDirectoryNode();
    }
    node.splitTo(newNode, assignments.getFirstAssignments(), assignments.getSecondAssignments());

    // write changes to file
    writeNode(node);
    writeNode(newNode);

    if(getLogger().isDebugging()) {
      String msg = "Split Node " + node.getPageID() + " (" + this.getClass() + ")\n" + "      newNode " + newNode.getPageID() + "\n" + "      firstPromoted " + assignments.getFirstRoutingObject() + "\n" + "      firstAssignments(" + node.getPageID() + ") " + assignments.getFirstAssignments() + "\n" + "      firstCR " + assignments.getFirstCoveringRadius() + "\n" + "      secondPromoted " + assignments.getSecondRoutingObject() + "\n" + "      secondAssignments(" + newNode.getPageID() + ") " + assignments.getSecondAssignments() + "\n" + "      secondCR " + assignments.getSecondCoveringRadius() + "\n";
      getLogger().debugFine(msg);
    }

    return new SplitResult(split, newNode);
  }

  /**
   * Sorts the entries of the specified node according to their minimum distance
   * to the specified objects.
   *
   * @param node the node
   * @param ids the ids of the objects
   * @return a list of the sorted entries
   */
  // FIXME: Duplicate from above?
  /*
   * private List<DistanceEntry<D, E>> getSortedEntries(N node, DBIDs ids) {
   * List<DistanceEntry<D, E>> result = new ArrayList<DistanceEntry<D, E>>();
   *
   * for(int i = 0; i < node.getNumEntries(); i++) { E entry = node.getEntry(i);
   *
   * D minMinDist = distanceFunction.infiniteDistance(); for(DBID q : ids) { D
   * distance = distance(entry.getRoutingObjectID(), q); D minDist =
   * entry.getCoveringRadius().compareTo(distance) > 0 ?
   * distanceFunction.nullDistance() :
   * distance.minus(entry.getCoveringRadius()); if(minDist.compareTo(minMinDist)
   * < 0) { minMinDist = minDist; } } result.add(new DistanceEntry<D, E>(entry,
   * minMinDist, i)); }
   *
   * Collections.sort(result); return result; }
   */

  /**
   * Adjusts the tree after insertion of some nodes.
   *
   * @param subtree the subtree to be adjusted
   */
  private void adjustTree(IndexTreePath<E> subtree) {
    if(getLogger().isDebugging()) {
      getLogger().debugFine("Adjust tree " + subtree + "\n");
    }

    // get the root of the subtree
    Integer nodeIndex = subtree.getLastPathComponent().getIndex();
    N node = getNode(subtree.getLastPathComponent().getEntry());

    // overflow in node; split the node
    if(hasOverflow(node)) {
      SplitResult splitResult = split(node);
      N splitNode = splitResult.newNode;
      Assignments<D, E> assignments = splitResult.split.getAssignments();

      // if root was split: create a new root that points the two split
      // nodes
      if(isRoot(node)) {
        // FIXME: stimmen die parentDistance der Kinder in node & splitNode?
        IndexTreePath<E> newRootPath = createNewRoot(node, splitNode, assignments.getFirstRoutingObject(), assignments.getSecondRoutingObject());
        adjustTree(newRootPath);
      }
      // node is not root
      else {
        // get the parent and add the new split node
        E parentEntry = subtree.getParentPath().getLastPathComponent().getEntry();
        N parent = getNode(parentEntry);
        if(getLogger().isDebugging()) {
          getLogger().debugFine("parent " + parent);
        }
        D parentDistance2 = distance(parentEntry.getRoutingObjectID(), assignments.getSecondRoutingObject());
        // logger.warning("parent: "+parent.toString()+" split: " +
        // splitNode.toString()+ " dist:"+parentDistance2);
        parent.addDirectoryEntry(createNewDirectoryEntry(splitNode, assignments.getSecondRoutingObject(), parentDistance2));

        // adjust the entry representing the (old) node, that has been split
        D parentDistance1 = distance(parentEntry.getRoutingObjectID(), assignments.getFirstRoutingObject());
        // logger.warning("parent: "+parent.toString()+" node: " +
        // node.toString()+ " dist:"+parentDistance1);
        node.adjustEntry(parent.getEntry(nodeIndex), assignments.getFirstRoutingObject(), parentDistance1, this);

        // write changes in parent to file
        writeNode(parent);
        adjustTree(subtree.getParentPath());
      }
    }
    // no overflow, only adjust parameters of the entry representing the
    // node
    else {
      if(!isRoot(node)) {
        E parentEntry = subtree.getParentPath().getLastPathComponent().getEntry();
        N parent = getNode(parentEntry);
        int index = subtree.getLastPathComponent().getIndex();
        E entry = parent.getEntry(index);
        node.adjustEntry(entry, entry.getRoutingObjectID(), entry.getParentDistance(), this);
        // write changes in parent to file
        writeNode(parent);
        adjustTree(subtree.getParentPath());
      }
      // root level is reached
      else {
        E rootEntry = getRootEntry();
        node.adjustEntry(rootEntry, rootEntry.getRoutingObjectID(), rootEntry.getParentDistance(), this);
      }
    }
  }

  /**
   * Returns true if in the specified node an overflow has occurred, false
   * otherwise.
   *
   * @param node the node to be tested for overflow
   * @return true if in the specified node an overflow has occurred, false
   *         otherwise
   */
  private boolean hasOverflow(N node) {
    if(node.isLeaf()) {
      return node.getNumEntries() == leafCapacity;
    }

    return node.getNumEntries() == dirCapacity;
  }

  /**
   * Creates a new root node that points to the two specified child nodes and
   * return the path to the new root.
   *
   * @param oldRoot the old root of this RTree
   * @param newNode the new split node
   * @param firstRoutingObjectID the id of the routing objects of the first
   *        child node
   * @param secondRoutingObjectID the id of the routing objects of the second
   *        child node
   * @return the path to the new root node that points to the two specified
   *         child nodes
   */
  private IndexTreePath<E> createNewRoot(final N oldRoot, final N newNode, DBID firstRoutingObjectID, DBID secondRoutingObjectID) {
    N root = createNewDirectoryNode();
    writeNode(root);

    // switch the ids
    oldRoot.setPageID(root.getPageID());
    if(!oldRoot.isLeaf()) {
      // FIXME: what is happening here?
      for(int i = 0; i < oldRoot.getNumEntries(); i++) {
        N node = getNode(oldRoot.getEntry(i));
        writeNode(node);
      }
    }

    root.setPageID(getRootID());
    // FIXME: doesn't the root by definition not have a routing object?
    // D parentDistance1 = distance(getRootEntry().getRoutingObjectID(),
    // firstRoutingObjectID);
    // D parentDistance2 = distance(getRootEntry().getRoutingObjectID(),
    // secondRoutingObjectID);
    E oldRootEntry = createNewDirectoryEntry(oldRoot, firstRoutingObjectID, null);
    E newRootEntry = createNewDirectoryEntry(newNode, secondRoutingObjectID, null);
    root.addDirectoryEntry(oldRootEntry);
    root.addDirectoryEntry(newRootEntry);

    // logger.warning("new root: " + getRootEntry().toString() + " childs: " +
    // oldRootEntry.toString() + "," + newRootEntry.toString() + " dists: " +
    // parentDistance1 + ", " + parentDistance2);

    writeNode(root);
    writeNode(oldRoot);
    writeNode(newNode);
    if(getLogger().isDebugging()) {
      String msg = "Create new Root: ID=" + root.getPageID();
      msg += "\nchild1 " + oldRoot;
      msg += "\nchild2 " + newNode;
      getLogger().debugFine(msg);
    }

    return new IndexTreePath<E>(new TreeIndexPathComponent<E>(getRootEntry(), null));
  }

  /**
   * Encapsulates a split object and the newly created node.
   *
   * @apiviz.composedOf MTreeSplit
   */
  private class SplitResult {
    protected MTreeSplit<O, D, N, E> split;

    protected N newNode;

    public SplitResult(MTreeSplit<O, D, N, E> split, N newNode) {
      this.split = split;
      this.newNode = newNode;
    }
  }

  @Override
  public List<E> getLeaves() {
    List<E> result = new ArrayList<E>();
    BreadthFirstEnumeration<N, E> enumeration = new BreadthFirstEnumeration<N, E>(this, getRootPath());
    while(enumeration.hasMoreElements()) {
      IndexTreePath<E> path = enumeration.nextElement();
      E entry = path.getLastPathComponent().getEntry();
      if(entry.isLeafEntry()) {
        // ignore, we are within a leaf!
      }
      else {
        // TODO: any way to skip unnecessary reads?
        N node = getNode(entry);
        if(node.isLeaf()) {
          result.add(entry);
        }
      }
    }
    return result;
  }

  /**
   * FIXME: expensive depth computation by following a path.
   *
   * @return depth
   */
  public int getHeight() {
    int levels = 0;
    N node = getRoot();

    while(!node.isLeaf()) {
      if(node.getNumEntries() > 0) {
        E entry = node.getEntry(0);
        node = getNode(entry);
        levels++;
      }
    }
    return levels;
  }
}
TOP

Related Classes of de.lmu.ifi.dbs.elki.index.tree.metrical.mtreevariants.AbstractMTree$SplitResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.