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 */ 017package org.apache.commons.numbers.gamma; 018 019/** 020 * Computes \( log_e \Beta(p, q) \). 021 * <p> 022 * This class is immutable. 023 * </p> 024 */ 025public final class LogBeta { 026 /** The threshold value of 10 where the series expansion of the Δ function applies. */ 027 private static final double TEN = 10; 028 /** The threshold value of 2 for algorithm switch. */ 029 private static final double TWO = 2; 030 /** The threshold value of 1000 for algorithm switch. */ 031 private static final double THOUSAND = 1000; 032 033 /** The constant value of ½log 2π. */ 034 private static final double HALF_LOG_TWO_PI = 0.9189385332046727; 035 036 /** 037 * The coefficients of the series expansion of the Δ function. This function 038 * is defined as follows: 039 * <pre> 040 * Δ(x) = log Γ(x) - (x - 0.5) log a + a - 0.5 log 2π, 041 * </pre> 042 * <p> 043 * See equation (23) in Didonato and Morris (1992). The series expansion, 044 * which applies for x ≥ 10, reads 045 * </p> 046 * <pre> 047 * 14 048 * ==== 049 * 1 \ 2 n 050 * Δ(x) = --- > d (10 / x) 051 * x / n 052 * ==== 053 * n = 0 054 * </pre> 055 */ 056 private static final double[] DELTA = { 057 .833333333333333333333333333333E-01, 058 -.277777777777777777777777752282E-04, 059 .793650793650793650791732130419E-07, 060 -.595238095238095232389839236182E-09, 061 .841750841750832853294451671990E-11, 062 -.191752691751854612334149171243E-12, 063 .641025640510325475730918472625E-14, 064 -.295506514125338232839867823991E-15, 065 .179643716359402238723287696452E-16, 066 -.139228964661627791231203060395E-17, 067 .133802855014020915603275339093E-18, 068 -.154246009867966094273710216533E-19, 069 .197701992980957427278370133333E-20, 070 -.234065664793997056856992426667E-21, 071 .171348014966398575409015466667E-22 072 }; 073 074 /** Private constructor. */ 075 private LogBeta() { 076 // intentional empty. 077 } 078 079 /** 080 * Returns the value of Δ(b) - Δ(a + b), with 0 ≤ a ≤ b and b ≥ 10. Based 081 * on equations (26), (27) and (28) in Didonato and Morris (1992). 082 * 083 * @param a First argument. 084 * @param b Second argument. 085 * @return the value of {@code Delta(b) - Delta(a + b)} 086 * @throws IllegalArgumentException if {@code a < 0} or {@code a > b} 087 * @throws IllegalArgumentException if {@code b < 10} 088 */ 089 private static double deltaMinusDeltaSum(final double a, 090 final double b) { 091 if (a < 0 || 092 a > b) { 093 throw new GammaException(GammaException.OUT_OF_RANGE, a, 0, b); 094 } 095 if (b < TEN) { 096 throw new GammaException(GammaException.OUT_OF_RANGE, b, TEN, Double.POSITIVE_INFINITY); 097 } 098 099 final double h = a / b; 100 final double p = h / (1 + h); 101 final double q = 1 / (1 + h); 102 final double q2 = q * q; 103 /* 104 * s[i] = 1 + q + ... - q**(2 * i) 105 */ 106 final double[] s = new double[DELTA.length]; 107 s[0] = 1; 108 for (int i = 1; i < s.length; i++) { 109 s[i] = 1 + (q + q2 * s[i - 1]); 110 } 111 /* 112 * w = Delta(b) - Delta(a + b) 113 */ 114 final double sqrtT = 10 / b; 115 final double t = sqrtT * sqrtT; 116 double w = DELTA[DELTA.length - 1] * s[s.length - 1]; 117 for (int i = DELTA.length - 2; i >= 0; i--) { 118 w = t * w + DELTA[i] * s[i]; 119 } 120 return w * p / b; 121 } 122 123 /** 124 * Returns the value of Δ(p) + Δ(q) - Δ(p + q), with p, q ≥ 10. 125 * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation, 126 * {@code DBCORR}. 127 * 128 * @param p First argument. 129 * @param q Second argument. 130 * @return the value of {@code Delta(p) + Delta(q) - Delta(p + q)}. 131 * @throws IllegalArgumentException if {@code p < 10} or {@code q < 10}. 132 */ 133 private static double sumDeltaMinusDeltaSum(final double p, 134 final double q) { 135 136 if (p < TEN) { 137 throw new GammaException(GammaException.OUT_OF_RANGE, p, TEN, Double.POSITIVE_INFINITY); 138 } 139 if (q < TEN) { 140 throw new GammaException(GammaException.OUT_OF_RANGE, q, TEN, Double.POSITIVE_INFINITY); 141 } 142 143 final double a = Math.min(p, q); 144 final double b = Math.max(p, q); 145 final double sqrtT = 10 / a; 146 final double t = sqrtT * sqrtT; 147 double z = DELTA[DELTA.length - 1]; 148 for (int i = DELTA.length - 2; i >= 0; i--) { 149 z = t * z + DELTA[i]; 150 } 151 return z / a + deltaMinusDeltaSum(a, b); 152 } 153 154 /** 155 * Returns the value of {@code log B(p, q)} for {@code 0 ≤ x ≤ 1} and {@code p, q > 0}. 156 * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation, 157 * {@code DBETLN}. 158 * 159 * @param p First argument. 160 * @param q Second argument. 161 * @return the value of {@code log(Beta(p, q))}, {@code NaN} if 162 * {@code p <= 0} or {@code q <= 0}. 163 */ 164 public static double value(double p, 165 double q) { 166 if (Double.isNaN(p) || 167 Double.isNaN(q) || 168 p <= 0 || 169 q <= 0) { 170 return Double.NaN; 171 } 172 173 final double a = Math.min(p, q); 174 final double b = Math.max(p, q); 175 if (a >= TEN) { 176 final double w = sumDeltaMinusDeltaSum(a, b); 177 final double h = a / b; 178 final double c = h / (1 + h); 179 final double u = -(a - 0.5) * Math.log(c); 180 final double v = b * Math.log1p(h); 181 if (u <= v) { 182 return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - u) - v; 183 } else { 184 return (((-0.5 * Math.log(b) + HALF_LOG_TWO_PI) + w) - v) - u; 185 } 186 } else if (a > TWO) { 187 if (b > THOUSAND) { 188 final int n = (int) Math.floor(a - 1); 189 double prod = 1; 190 double ared = a; 191 for (int i = 0; i < n; i++) { 192 ared -= 1; 193 prod *= ared / (1 + ared / b); 194 } 195 return (Math.log(prod) - n * Math.log(b)) + 196 (LogGamma.value(ared) + 197 logGammaMinusLogGammaSum(ared, b)); 198 } else { 199 double prod1 = 1; 200 double ared = a; 201 while (ared > 2) { 202 ared -= 1; 203 final double h = ared / b; 204 prod1 *= h / (1 + h); 205 } 206 if (b < TEN) { 207 double prod2 = 1; 208 double bred = b; 209 while (bred > 2) { 210 bred -= 1; 211 prod2 *= bred / (ared + bred); 212 } 213 return Math.log(prod1) + 214 Math.log(prod2) + 215 (LogGamma.value(ared) + 216 (LogGamma.value(bred) - 217 LogGammaSum.value(ared, bred))); 218 } else { 219 return Math.log(prod1) + 220 LogGamma.value(ared) + 221 logGammaMinusLogGammaSum(ared, b); 222 } 223 } 224 } else if (a >= 1) { 225 if (b > TWO) { 226 if (b < TEN) { 227 double prod = 1; 228 double bred = b; 229 while (bred > 2) { 230 bred -= 1; 231 prod *= bred / (a + bred); 232 } 233 return Math.log(prod) + 234 (LogGamma.value(a) + 235 (LogGamma.value(bred) - 236 LogGammaSum.value(a, bred))); 237 } else { 238 return LogGamma.value(a) + 239 logGammaMinusLogGammaSum(a, b); 240 } 241 } else { 242 return LogGamma.value(a) + 243 LogGamma.value(b) - 244 LogGammaSum.value(a, b); 245 } 246 } else { 247 if (b >= TEN) { 248 return LogGamma.value(a) + 249 logGammaMinusLogGammaSum(a, b); 250 } else { 251 // The original NSWC implementation was 252 // LogGamma.value(a) + (LogGamma.value(b) - LogGamma.value(a + b)); 253 // but the following command turned out to be more accurate. 254 return Math.log(Gamma.value(a) * Gamma.value(b) / 255 Gamma.value(a + b)); 256 } 257 } 258 } 259 260 /** 261 * Returns the value of log[Γ(b) / Γ(a + b)] for a ≥ 0 and b ≥ 10. 262 * Based on the <em>NSWC Library of Mathematics Subroutines</em> implementation, 263 * {@code DLGDIV}. 264 * 265 * @param a First argument. 266 * @param b Second argument. 267 * @return the value of {@code log(Gamma(b) / Gamma(a + b))}. 268 * @throws IllegalArgumentException if {@code a < 0} or {@code b < 10}. 269 */ 270 private static double logGammaMinusLogGammaSum(double a, 271 double b) { 272 if (a < 0) { 273 throw new GammaException(GammaException.OUT_OF_RANGE, a, 0, Double.POSITIVE_INFINITY); 274 } 275 if (b < TEN) { 276 throw new GammaException(GammaException.OUT_OF_RANGE, b, TEN, Double.POSITIVE_INFINITY); 277 } 278 279 /* 280 * d = a + b - 0.5 281 */ 282 final double d; 283 final double w; 284 if (a <= b) { 285 d = b + (a - 0.5); 286 w = deltaMinusDeltaSum(a, b); 287 } else { 288 d = a + (b - 0.5); 289 w = deltaMinusDeltaSum(b, a); 290 } 291 292 final double u = d * Math.log1p(a / b); 293 final double v = a * (Math.log(b) - 1); 294 295 return u <= v ? 296 (w - u) - v : 297 (w - v) - u; 298 } 299}