public double runSimulatedAnnealing(int iterations) {
    if (!locked) lock();
    double temp = 1;
    double coolingFactor = Math.pow(0.001, 1d / iterations);
    double currentEnergy = getEnergy();
    if (nodes.isEmpty()) return currentEnergy;
    Collections.shuffle(nodesWithSeveralChildrenScaled, random);
    Collections.shuffle(sourceFakeEdges, random);

    while (temp > 0.001) {
      double action = random.nextDouble();
      if (action < 0.19) { // add fake edge
        currentEnergy = tryAddFakeEdge(temp, currentEnergy);
        assert currentEnergy == getEnergy();
      } else if (action
          < 0.36) { // remove fake edge (slightly higher probability - we don't want too many fake
        // edges) - update: apparently, that hypothesis is wrong
        currentEnergy = tryRemoveFakeEdge(temp, currentEnergy);
        assert currentEnergy == getEnergy();
      } else if (action < 0.7) { // swap two children of some node
        currentEnergy = trySwapChildren(temp, currentEnergy);
        assert currentEnergy == getEnergy();
      } else { // change where one of the sources is attached to
        currentEnergy = tryChangeSource(temp, currentEnergy);
        assert currentEnergy == getEnergy();
      }
      temp *= coolingFactor;
    }
    return currentEnergy;
  }
  public double tryChangeSource(double temp, double currentEnergy) {
    PEdge<V, E> fakeEdge = sourceFakeEdges.get(random.nextInt(sourceFakeEdges.size()));
    PNode<V, E> child = fakeEdge.to;
    int oldIndex = fakeEdge.from.getChildren().indexOf(fakeEdge);
    child.removeParent(fakeEdge);
    fakeEdge.from.removeChild(fakeEdge);
    PEdge<V, E> fakeEdge2;

    int fakeEdge2Swap = -1;

    if (fakeEdge.from.isRoot || random.nextDouble() < 0.4) { // try attaching it somewhere else
      PNode<V, E> parent;
      if (Math.random() < 0.5
          || nodesWithSeveralChildrenScaled.size() == 0) { // try some completely random node
        parent = nodes.get(random.nextInt(nodes.size()));
      } else {
        parent =
            nodesWithSeveralChildrenScaled.get(
                random.nextInt(nodesWithSeveralChildrenScaled.size()));
      }
      fakeEdge2 = new PEdge<V, E>(parent, child);
      parent.addChild(fakeEdge2);
      if (parent.getChildren().size() > 1) {
        fakeEdge2Swap = random.nextInt(parent.getChildren().size() - 1);
        parent.swapChildren(parent.getChildren().size() - 1, fakeEdge2Swap);
      }
      child.addParent(fakeEdge2);
    } else { // try attaching it back to root.
      fakeEdge2 = new PEdge<V, E>(root, child);
      root.addChild(fakeEdge2);
      if (root.getChildren().size() > 1) {
        fakeEdge2Swap = random.nextInt(root.getChildren().size() - 1);
        root.swapChildren(root.getChildren().size() - 1, fakeEdge2Swap);
      }
      child.addParent(fakeEdge2);
    }
    if (!graphHasCycle(fakeEdge2.to)) {
      double newEnergy = getEnergy();
      if (acceptChange(currentEnergy, newEnergy, temp)) {
        sourceFakeEdges.remove(fakeEdge);
        sourceFakeEdges.add(fakeEdge2);
        return newEnergy;
      }
    }

    fakeEdge2.from.removeChild(fakeEdge2);
    child.removeParent(fakeEdge2);
    if (fakeEdge2Swap != -1) {
      PEdge<V, E> e = fakeEdge2.from.getChildren().get(fakeEdge2.from.getChildren().size() - 1);
      fakeEdge2.from.getChildren().remove(fakeEdge2.from.getChildren().size() - 1);
      fakeEdge2.from.getChildren().add(fakeEdge2Swap, e);
    }
    // fakeEdge.from.addChild(fakeEdge);
    fakeEdge.from.getChildren().add(oldIndex, fakeEdge);
    child.addParent(fakeEdge);
    return currentEnergy;
  }
 public static int quadraticRandint(int limit) {
   // return (int)(random.nextDouble() * limit);
   return (int) (random.nextDouble() * random.nextDouble() * limit);
   // return 0;
 }