@@ -953,6 +953,101 @@ fn ia_asech(x: IntervalResult) -> IntervalResult {
953953 return ia_asech_v(x.value);
954954}
955955
956+ // Gamma function using Lanczos approximation (g=7, n=9 coefficients)
957+ // Poles at non-positive integers; minimum at x ≈ 1.4616
958+ fn _gpu_gamma(z_in: f32) -> f32 {
959+ let PI = 3.14159265358979;
960+ var z = z_in;
961+ if (z < 0.5) {
962+ return PI / (sin(PI * z) * _gpu_gamma(1.0 - z));
963+ }
964+ z -= 1.0;
965+ var x = 0.99999999999980993;
966+ x += 676.5203681218851 / (z + 1.0);
967+ x += -1259.1392167224028 / (z + 2.0);
968+ x += 771.32342877765313 / (z + 3.0);
969+ x += -176.61502916214059 / (z + 4.0);
970+ x += 12.507343278686905 / (z + 5.0);
971+ x += -0.13857109526572012 / (z + 6.0);
972+ x += 9.9843695780195716e-6 / (z + 7.0);
973+ x += 1.5056327351493116e-7 / (z + 8.0);
974+ let t = z + 7.5;
975+ return sqrt(2.0 * PI) * pow(t, z + 0.5) * exp(-t) * x;
976+ }
977+
978+ // Interval gamma function
979+ // Handles poles at non-positive integers and the minimum at x ≈ 1.4616
980+ fn ia_gamma_v(x: vec2f) -> IntervalResult {
981+ let GAMMA_MIN_X = 1.4616321;
982+ let GAMMA_MIN_Y = 0.8856032;
983+
984+ // Check for poles: interval crosses or touches zero
985+ if (x.x <= 0.0 && x.y >= 0.0) {
986+ return ia_singular(0.0);
987+ }
988+
989+ // Entirely negative: check if interval spans a negative integer
990+ if (x.x < 0.0) {
991+ let ceilLo = ceil(x.x);
992+ let floorHi = floor(x.y);
993+ if (ceilLo <= floorHi) {
994+ return ia_singular(ceilLo);
995+ }
996+ // No pole — both endpoints between same consecutive negative integers
997+ let gLo = _gpu_gamma(x.x);
998+ let gHi = _gpu_gamma(x.y);
999+ return ia_ok(vec2f(min(gLo, gHi) - IA_EPS, max(gLo, gHi) + IA_EPS));
1000+ }
1001+
1002+ // Entirely positive
1003+ if (x.x >= GAMMA_MIN_X) {
1004+ // Monotonically increasing
1005+ return ia_ok(vec2f(_gpu_gamma(x.x) - IA_EPS, _gpu_gamma(x.y) + IA_EPS));
1006+ }
1007+ if (x.y <= GAMMA_MIN_X) {
1008+ // Monotonically decreasing
1009+ return ia_ok(vec2f(_gpu_gamma(x.y) - IA_EPS, _gpu_gamma(x.x) + IA_EPS));
1010+ }
1011+ // Crosses the minimum
1012+ let gMax = max(_gpu_gamma(x.x), _gpu_gamma(x.y));
1013+ return ia_ok(vec2f(GAMMA_MIN_Y - IA_EPS, gMax + IA_EPS));
1014+ }
1015+
1016+ fn ia_gamma(x: IntervalResult) -> IntervalResult {
1017+ if (ia_is_error(x.status)) { return x; }
1018+ return ia_gamma_v(x.value);
1019+ }
1020+
1021+ // Log-gamma using Stirling asymptotic expansion, z > 0
1022+ fn _gpu_gammaln(z: f32) -> f32 {
1023+ let z3 = z * z * z;
1024+ return z * log(z) - z - 0.5 * log(z)
1025+ + 0.5 * log(2.0 * 3.14159265358979)
1026+ + 1.0 / (12.0 * z)
1027+ - 1.0 / (360.0 * z3)
1028+ + 1.0 / (1260.0 * z3 * z * z);
1029+ }
1030+
1031+ // Interval log-gamma — monotonically increasing for x > 0
1032+ fn ia_gammaln_v(x: vec2f) -> IntervalResult {
1033+ if (x.y <= 0.0) { return ia_empty(); }
1034+ if (x.x > 0.0) {
1035+ return ia_ok(vec2f(_gpu_gammaln(x.x) - IA_EPS, _gpu_gammaln(x.y) + IA_EPS));
1036+ }
1037+ // Partial: clipped at lo
1038+ return ia_partial(vec2f(0.0, _gpu_gammaln(x.y) + IA_EPS), IA_PARTIAL_LO);
1039+ }
1040+
1041+ fn ia_gammaln(x: IntervalResult) -> IntervalResult {
1042+ if (ia_is_error(x.status)) { return x; }
1043+ return ia_gammaln_v(x.value);
1044+ }
1045+
1046+ // Factorial via gamma: n! = gamma(n+1)
1047+ fn ia_factorial(x: IntervalResult) -> IntervalResult {
1048+ return ia_gamma(ia_add(x, ia_point(1.0)));
1049+ }
1050+
9561051// Boolean interval comparisons
9571052// Returns 1.0 = true, 0.0 = false, 0.5 = maybe
9581053const IA_TRUE: f32 = 1.0;
@@ -1103,6 +1198,11 @@ const INTERVAL_WGSL_FUNCTIONS: CompiledFunctions<Expression> = {
11031198 } ,
11041199 Negate : ( args , compile ) => `ia_negate(${ compile ( args [ 0 ] ) } )` ,
11051200
1201+ // Special functions
1202+ Gamma : ( args , compile ) => `ia_gamma(${ compile ( args [ 0 ] ) } )` ,
1203+ GammaLn : ( args , compile ) => `ia_gammaln(${ compile ( args [ 0 ] ) } )` ,
1204+ Factorial : ( args , compile ) => `ia_factorial(${ compile ( args [ 0 ] ) } )` ,
1205+
11061206 // Elementary functions
11071207 Abs : ( args , compile ) => `ia_abs(${ compile ( args [ 0 ] ) } )` ,
11081208 Ceil : ( args , compile ) => `ia_ceil(${ compile ( args [ 0 ] ) } )` ,
0 commit comments