001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.rng.sampling.distribution; 019 020import org.apache.commons.rng.UniformRandomProvider; 021 022/** 023 * Implementation of the <a href="https://en.wikipedia.org/wiki/Zipf's_law">Zipf distribution</a>. 024 * 025 * @since 1.0 026 */ 027public class RejectionInversionZipfSampler 028 extends SamplerBase 029 implements DiscreteSampler { 030 /** Threshold below which Taylor series will be used. */ 031 private static final double TAYLOR_THRESHOLD = 1e-8; 032 /** 1/2 */ 033 private static final double F_1_2 = 0.5; 034 /** 1/3 */ 035 private static final double F_1_3 = 1d / 3; 036 /** 1/4 */ 037 private static final double F_1_4 = 0.25; 038 /** Number of elements. */ 039 private final int numberOfElements; 040 /** Exponent parameter of the distribution. */ 041 private final double exponent; 042 /** {@code hIntegral(1.5) - 1}. */ 043 private final double hIntegralX1; 044 /** {@code hIntegral(numberOfElements + 0.5)}. */ 045 private final double hIntegralNumberOfElements; 046 /** {@code 2 - hIntegralInverse(hIntegral(2.5) - h(2)}. */ 047 private final double s; 048 /** Underlying source of randomness. */ 049 private final UniformRandomProvider rng; 050 051 /** 052 * @param rng Generator of uniformly distributed random numbers. 053 * @param numberOfElements Number of elements. 054 * @param exponent Exponent. 055 * @throws IllegalArgumentException if {@code numberOfElements <= 0} 056 * or {@code exponent <= 0}. 057 */ 058 public RejectionInversionZipfSampler(UniformRandomProvider rng, 059 int numberOfElements, 060 double exponent) { 061 super(null); 062 this.rng = rng; 063 if (numberOfElements <= 0) { 064 throw new IllegalArgumentException("number of elements is not strictly positive: " + numberOfElements); 065 } 066 if (exponent <= 0) { 067 throw new IllegalArgumentException("exponent is not strictly positive: " + exponent); 068 } 069 070 this.numberOfElements = numberOfElements; 071 this.exponent = exponent; 072 this.hIntegralX1 = hIntegral(1.5) - 1; 073 this.hIntegralNumberOfElements = hIntegral(numberOfElements + F_1_2); 074 this.s = 2 - hIntegralInverse(hIntegral(2.5) - h(2)); 075 } 076 077 /** 078 * Rejection inversion sampling method for a discrete, bounded Zipf 079 * distribution that is based on the method described in 080 * <blockquote> 081 * Wolfgang Hörmann and Gerhard Derflinger. 082 * <i>"Rejection-inversion to generate variates from monotone discrete 083 * distributions",</i><br> 084 * <strong>ACM Transactions on Modeling and Computer Simulation</strong> (TOMACS) 6.3 (1996): 169-184. 085 * </blockquote> 086 */ 087 @Override 088 public int sample() { 089 // The paper describes an algorithm for exponents larger than 1 090 // (Algorithm ZRI). 091 // The original method uses 092 // H(x) = (v + x)^(1 - q) / (1 - q) 093 // as the integral of the hat function. 094 // This function is undefined for q = 1, which is the reason for 095 // the limitation of the exponent. 096 // If instead the integral function 097 // H(x) = ((v + x)^(1 - q) - 1) / (1 - q) 098 // is used, 099 // for which a meaningful limit exists for q = 1, the method works 100 // for all positive exponents. 101 // The following implementation uses v = 0 and generates integral 102 // number in the range [1, numberOfElements]. 103 // This is different to the original method where v is defined to 104 // be positive and numbers are taken from [0, i_max]. 105 // This explains why the implementation looks slightly different. 106 107 while(true) { 108 final double u = hIntegralNumberOfElements + rng.nextDouble() * (hIntegralX1 - hIntegralNumberOfElements); 109 // u is uniformly distributed in (hIntegralX1, hIntegralNumberOfElements] 110 111 double x = hIntegralInverse(u); 112 int k = (int) (x + F_1_2); 113 114 // Limit k to the range [1, numberOfElements] if it would be outside 115 // due to numerical inaccuracies. 116 if (k < 1) { 117 k = 1; 118 } else if (k > numberOfElements) { 119 k = numberOfElements; 120 } 121 122 // Here, the distribution of k is given by: 123 // 124 // P(k = 1) = C * (hIntegral(1.5) - hIntegralX1) = C 125 // P(k = m) = C * (hIntegral(m + 1/2) - hIntegral(m - 1/2)) for m >= 2 126 // 127 // where C = 1 / (hIntegralNumberOfElements - hIntegralX1) 128 129 if (k - x <= s || u >= hIntegral(k + F_1_2) - h(k)) { 130 131 // Case k = 1: 132 // 133 // The right inequality is always true, because replacing k by 1 gives 134 // u >= hIntegral(1.5) - h(1) = hIntegralX1 and u is taken from 135 // (hIntegralX1, hIntegralNumberOfElements]. 136 // 137 // Therefore, the acceptance rate for k = 1 is P(accepted | k = 1) = 1 138 // and the probability that 1 is returned as random value is 139 // P(k = 1 and accepted) = P(accepted | k = 1) * P(k = 1) = C = C / 1^exponent 140 // 141 // Case k >= 2: 142 // 143 // The left inequality (k - x <= s) is just a short cut 144 // to avoid the more expensive evaluation of the right inequality 145 // (u >= hIntegral(k + 0.5) - h(k)) in many cases. 146 // 147 // If the left inequality is true, the right inequality is also true: 148 // Theorem 2 in the paper is valid for all positive exponents, because 149 // the requirements h'(x) = -exponent/x^(exponent + 1) < 0 and 150 // (-1/hInverse'(x))'' = (1+1/exponent) * x^(1/exponent-1) >= 0 151 // are both fulfilled. 152 // Therefore, f(x) = x - hIntegralInverse(hIntegral(x + 0.5) - h(x)) 153 // is a non-decreasing function. If k - x <= s holds, 154 // k - x <= s + f(k) - f(2) is obviously also true which is equivalent to 155 // -x <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), 156 // -hIntegralInverse(u) <= -hIntegralInverse(hIntegral(k + 0.5) - h(k)), 157 // and finally u >= hIntegral(k + 0.5) - h(k). 158 // 159 // Hence, the right inequality determines the acceptance rate: 160 // P(accepted | k = m) = h(m) / (hIntegrated(m+1/2) - hIntegrated(m-1/2)) 161 // The probability that m is returned is given by 162 // P(k = m and accepted) = P(accepted | k = m) * P(k = m) = C * h(m) = C / m^exponent. 163 // 164 // In both cases the probabilities are proportional to the probability mass function 165 // of the Zipf distribution. 166 167 return k; 168 } 169 } 170 } 171 172 /** {@inheritDoc} */ 173 @Override 174 public String toString() { 175 return "Rejection inversion Zipf deviate [" + rng.toString() + "]"; 176 } 177 178 /** 179 * {@code H(x)} is defined as 180 * <ul> 181 * <li>{@code (x^(1 - exponent) - 1) / (1 - exponent)}, if {@code exponent != 1}</li> 182 * <li>{@code log(x)}, if {@code exponent == 1}</li> 183 * </ul> 184 * H(x) is an integral function of h(x), the derivative of H(x) is h(x). 185 * 186 * @param x Free parameter. 187 * @return {@code H(x)}. 188 */ 189 private double hIntegral(final double x) { 190 final double logX = Math.log(x); 191 return helper2((1 - exponent) * logX) * logX; 192 } 193 194 /** 195 * {@code h(x) = 1 / x^exponent} 196 * 197 * @param x Free parameter. 198 * @return {@code h(x)}. 199 */ 200 private double h(final double x) { 201 return Math.exp(-exponent * Math.log(x)); 202 } 203 204 /** 205 * The inverse function of {@code H(x)}. 206 * 207 * @param x Free parameter 208 * @return y for which {@code H(y) = x}. 209 */ 210 private double hIntegralInverse(final double x) { 211 double t = x * (1 - exponent); 212 if (t < -1) { 213 // Limit value to the range [-1, +inf). 214 // t could be smaller than -1 in some rare cases due to numerical errors. 215 t = -1; 216 } 217 return Math.exp(helper1(t) * x); 218 } 219 220 /** 221 * Helper function that calculates {@code log(1 + x) / x}. 222 * <p> 223 * A Taylor series expansion is used, if x is close to 0. 224 * </p> 225 * 226 * @param x A value larger than or equal to -1. 227 * @return {@code log(1 + x) / x}. 228 */ 229 private static double helper1(final double x) { 230 if (Math.abs(x) > TAYLOR_THRESHOLD) { 231 return Math.log1p(x) / x; 232 } else { 233 return 1 - x * (F_1_2 - x * (F_1_3 - F_1_4 * x)); 234 } 235 } 236 237 /** 238 * Helper function to calculate {@code (exp(x) - 1) / x}. 239 * <p> 240 * A Taylor series expansion is used, if x is close to 0. 241 * </p> 242 * 243 * @param x Free parameter. 244 * @return {@code (exp(x) - 1) / x} if x is non-zero, or 1 if x = 0. 245 */ 246 private static double helper2(final double x) { 247 if (Math.abs(x) > TAYLOR_THRESHOLD) { 248 return Math.expm1(x) / x; 249 } else { 250 return 1 + x * F_1_2 * (1 + x * F_1_3 * (1 + F_1_4 * x)); 251 } 252 } 253}