/*
* Copyright (c) 2010 Yahoo! Inc. All rights reserved.
*
* Licensed 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. See accompanying LICENSE file.
*/
package voldemort.performance.benchmark.generator;
import java.util.Random;
/**
* A generator of a Zipfian distribution. It produces a sequence of items, such
* that some items are more popular than others, according to a zipfian
* distribution.
*
* Note that the popular items will be clustered together, e.g. item 0 is the
* most popular, item 1 the second most popular, and so on (or min is the most
* popular, min+1 the next most popular, etc.)
*
*/
public class ZipfianGenerator extends IntegerGenerator {
public static final double ZIPFIAN_CONSTANT = 0.99;
private long items, base, countZeta;
private double zipfianConstant, alpha, zetan, eta, theta, zeta2theta;
private Random random;
boolean allowItemCountDecrease = false;
public ZipfianGenerator(long items) {
this(0, items - 1);
}
public ZipfianGenerator(long min, long max) {
this(min, max, ZIPFIAN_CONSTANT);
}
public ZipfianGenerator(long items, double zipfianConstant) {
this(0, items - 1, zipfianConstant);
}
public ZipfianGenerator(long min, long max, double zipfianConstant) {
this(min, max, zipfianConstant, zetaStatic(max - min + 1, zipfianConstant));
}
public ZipfianGenerator(long min, long max, double zipfianConstant, double zetan) {
this.items = max - min + 1;
this.base = min;
this.zipfianConstant = zipfianConstant;
this.random = new Random();
this.theta = this.zipfianConstant;
this.zeta2theta = zeta(2, theta);
this.alpha = 1.0 / (1.0 - theta);
this.zetan = zetan;
this.countZeta = items;
this.eta = (1 - Math.pow(2.0 / items, 1 - theta)) / (1 - zeta2theta / zetan);
nextInt();
}
double zeta(long n, double theta) {
countZeta = n;
return zetaStatic(n, theta);
}
static double zetaStatic(long n, double theta) {
return zetaStatic(0, n, theta, 0);
}
double zeta(long st, long n, double theta, double initialSum) {
countZeta = n;
return zetaStatic(st, n, theta, initialSum);
}
static double zetaStatic(long st, long n, double theta, double initialSum) {
double sum = initialSum;
for(long i = st; i < n; i++) {
sum += 1 / (Math.pow(i + 1, theta));
}
return sum;
}
public int nextInt(int itemcount) {
return (int) nextLong(itemcount);
}
public long nextLong(long itemCount) {
// from "Quickly Generating Billion-Record Synthetic Databases"
if(itemCount != countZeta) {
synchronized(this) {
if(itemCount > countZeta) {
zetan = zeta(countZeta, itemCount, theta, zetan);
eta = (1 - Math.pow(2.0 / items, 1 - theta)) / (1 - zeta2theta / zetan);
} else if((itemCount < countZeta) && (allowItemCountDecrease)) {
zetan = zeta(itemCount, theta);
eta = (1 - Math.pow(2.0 / items, 1 - theta)) / (1 - zeta2theta / zetan);
}
}
}
double u = random.nextDouble();
double uz = u * zetan;
if(uz < 1.0) {
return 0;
}
if(uz < 1.0 + Math.pow(0.5, theta)) {
return 1;
}
long ret = base + (long) ((itemCount) * Math.pow(eta * u - eta + 1, alpha));
setLastInt((int) ret);
return ret;
}
@Override
public int nextInt() {
return (int) nextLong(items);
}
public long nextLong() {
return nextLong(items);
}
}