/**
   * Compute SALSA on graph initialized in initializeGraph() method.
   *
   * @param nIterations
   */
  public void computeSALSA(int nIterations) {
    for (int iter = 0; iter < nIterations; iter++) {
      // Hubs: sum of authority-neighbors values divided by their degree
      for (SalsaVertex hub : hubs.values()) {
        double nbSum = 0.0;
        // Update the degree because not all authorities were selected
        int degree = 0;
        for (int authId : hub.neighbors) {
          SalsaVertex auth = authorities.get(authId);
          if (auth != null) {
            nbSum += auth.value / auth.degree;
            degree++;
          }
        }
        hub.value = nbSum;
        hub.degree = degree;
      }

      // Authorities: push from authority side.
      // First: set values to zero
      for (SalsaVertex auth : authorities.values()) {
        auth.value = 0;
      }

      // Then, push hubs values to their auths
      for (SalsaVertex hub : hubs.values()) {
        double myContribution = hub.value / hub.degree;
        for (int authId : hub.neighbors) {
          SalsaVertex auth = authorities.get(authId);
          if (auth != null) {
            auth.value += myContribution;
          }
        }
      }
    }
  }
  public void initializeGraph(Collection<Integer> circleOfTrust) {
    hubs = new HashMap<Integer, SalsaVertex>(circleOfTrust.size(), 1.0f);
    long t = System.currentTimeMillis();

    int totalNeighbors = 0;

    int cacheHits = 0;
    HashSet<Integer> querySet = new HashSet<Integer>(circleOfTrust.size());
    for (int v : circleOfTrust) {
      hubs.put(v, new SalsaVertex(v));
      if (cache.containsKey(v)) {
        SalsaVertex hub = hubs.get(v);
        hub.neighbors = cache.get(v);
        hub.degree = hub.neighbors.size();
        cacheHits++;
      } else {
        querySet.add(v);
      }
    }

    /* Load neighbors of the circle of trust -- we probably would need some limitation here?? */
    HashMap<Integer, ArrayList<Integer>> hubNeighbors = queryService.queryOutNeighbors(querySet);
    long queryTime = System.currentTimeMillis() - t;

    /* Initialize salsa */
    t = System.currentTimeMillis();

    for (Map.Entry<Integer, ArrayList<Integer>> entry : hubNeighbors.entrySet()) {
      int hubId = entry.getKey();
      SalsaVertex hub = hubs.get(hubId);
      hub.neighbors = entry.getValue();
      hub.degree = entry.getValue().size();
      cache.put(hubId, entry.getValue());
    }

    // Count total neighbors
    for (SalsaVertex hub : hubs.values()) {
      totalNeighbors += hub.neighbors.size();
    }

    long salsaInitTime0 = System.currentTimeMillis() - t;

    // We do not add neighbors to authorities -- we can push values to authorities
    // and pull to hubs.

    int[] authEntries = new int[totalNeighbors];

    int j = 0;
    for (SalsaVertex hub : hubs.values()) {
      for (int authId : hub.neighbors) {
        authEntries[j++] = authId;
      }
    }
    assert (j == authEntries.length);

    // Create map efficiently
    Arrays.sort(authEntries);

    int lastId = -1;
    int count = 0;
    //    int filtered = 0;

    ArrayList<SalsaVertex> tmpAuth = new ArrayList<SalsaVertex>(1 + authEntries.length / 100);
    for (int i = 0; i < authEntries.length; i++) {
      int authId = authEntries[i];
      if (lastId != authId) {
        if (lastId >= 0) {
          if (count > FILTER_LIMIT) {
            SalsaVertex auth = new SalsaVertex(lastId);
            auth.degree = count;
            tmpAuth.add(auth);
          } else {
            //  filtered++;
          }
          count = 0;
        }
        lastId = authId;
      }
      count++;
    }

    authorities = new HashMap<Integer, SalsaVertex>(tmpAuth.size());
    for (SalsaVertex auth : tmpAuth) {
      authorities.put(auth.id, auth);
    }

    // NOTE: remove neighbors!
    /*
    long salsaInitTime = System.currentTimeMillis() - t;
    logger.info("Query took: " + queryTime + " ms, circle of trust size=" + circleOfTrust.size() + ", cache size=" +
            cache.size() + ", hits=" + cacheHits);
    logger.info("Salsa init: " + salsaInitTime + " ms, first phase=" + salsaInitTime0 + " ms, hubs="
            + hubs.size() + ", auths=" + authorities.size());
    logger.info("Filtered: " + filtered);            */
  }