/ test / probability_test.ts
probability_test.ts
  1  // SPDX-License-Identifier: AGPL-3.0-or-later
  2  // SPDX-FileCopyrightText: 2025 hyperpolymath
  3  //
  4  // Tests for BetLang Probability Module
  5  
  6  import { assertEquals, assertThrows, assertAlmostEquals } from '@std/assert';
  7  import {
  8    probability,
  9    complement,
 10    joint,
 11    unionExclusive,
 12    conditional,
 13    expectedValue,
 14    variance,
 15    standardDeviation,
 16    bayes,
 17  } from '../src/probability.ts';
 18  
 19  // Probability creation tests
 20  Deno.test('probability - creates valid probability', () => {
 21    const p = probability(0.5);
 22    assertEquals(p.value, 0.5);
 23  });
 24  
 25  Deno.test('probability - accepts 0', () => {
 26    const p = probability(0);
 27    assertEquals(p.value, 0);
 28  });
 29  
 30  Deno.test('probability - accepts 1', () => {
 31    const p = probability(1);
 32    assertEquals(p.value, 1);
 33  });
 34  
 35  Deno.test('probability - throws on negative', () => {
 36    assertThrows(
 37      () => probability(-0.1),
 38      Error,
 39      'Invalid probability'
 40    );
 41  });
 42  
 43  Deno.test('probability - throws on > 1', () => {
 44    assertThrows(
 45      () => probability(1.1),
 46      Error,
 47      'Invalid probability'
 48    );
 49  });
 50  
 51  // Complement tests
 52  Deno.test('complement - P(not A) = 1 - P(A)', () => {
 53    const p = probability(0.3);
 54    const c = complement(p);
 55    assertAlmostEquals(c.value, 0.7, 1e-10);
 56  });
 57  
 58  // Joint probability tests
 59  Deno.test('joint - P(A and B) = P(A) * P(B)', () => {
 60    const pA = probability(0.5);
 61    const pB = probability(0.4);
 62    const pAB = joint(pA, pB);
 63    assertAlmostEquals(pAB.value, 0.2, 1e-10);
 64  });
 65  
 66  // Union tests
 67  Deno.test('unionExclusive - P(A or B) = P(A) + P(B)', () => {
 68    const pA = probability(0.3);
 69    const pB = probability(0.4);
 70    const pAorB = unionExclusive(pA, pB);
 71    assertAlmostEquals(pAorB.value, 0.7, 1e-10);
 72  });
 73  
 74  Deno.test('unionExclusive - throws if sum > 1', () => {
 75    const pA = probability(0.6);
 76    const pB = probability(0.5);
 77    assertThrows(
 78      () => unionExclusive(pA, pB),
 79      Error,
 80      'Union of mutually exclusive events exceeds 1'
 81    );
 82  });
 83  
 84  // Conditional probability tests
 85  Deno.test('conditional - P(A|B) = P(A and B) / P(B)', () => {
 86    const pAandB = probability(0.2);
 87    const pB = probability(0.5);
 88    const pAgivenB = conditional(pAandB, pB);
 89    assertAlmostEquals(pAgivenB.value, 0.4, 1e-10);
 90  });
 91  
 92  Deno.test('conditional - throws when P(B) = 0', () => {
 93    const pAandB = probability(0);
 94    const pB = probability(0);
 95    assertThrows(
 96      () => conditional(pAandB, pB),
 97      Error,
 98      'Cannot compute conditional probability when P(B) = 0'
 99    );
100  });
101  
102  // Statistical tests
103  Deno.test('expectedValue - calculates mean correctly', () => {
104    const data = [1, 2, 3, 4, 5];
105    assertEquals(expectedValue(data), 3);
106  });
107  
108  Deno.test('expectedValue - throws on empty array', () => {
109    assertThrows(
110      () => expectedValue([]),
111      Error,
112      'Cannot compute expected value of empty array'
113    );
114  });
115  
116  Deno.test('variance - calculates correctly', () => {
117    const data = [1, 2, 3, 4, 5];
118    // Mean = 3, variance = ((1-3)^2 + (2-3)^2 + (3-3)^2 + (4-3)^2 + (5-3)^2) / 5
119    // = (4 + 1 + 0 + 1 + 4) / 5 = 2
120    assertEquals(variance(data), 2);
121  });
122  
123  Deno.test('standardDeviation - is sqrt of variance', () => {
124    const data = [1, 2, 3, 4, 5];
125    assertAlmostEquals(standardDeviation(data), Math.sqrt(2), 1e-10);
126  });
127  
128  // Bayes theorem test
129  Deno.test('bayes - P(A|B) = P(B|A) * P(A) / P(B)', () => {
130    // Example: disease testing
131    // P(positive|disease) = 0.99 (sensitivity)
132    // P(disease) = 0.01 (prior - rare disease)
133    // P(positive) = 0.01 * 0.99 + 0.99 * 0.05 = 0.0594 (total positive rate)
134    // P(disease|positive) = 0.99 * 0.01 / 0.0594 = 0.1667
135  
136    const pPositiveGivenDisease = probability(0.99);
137    const pDisease = probability(0.01);
138    const pPositive = probability(0.0594);
139  
140    const pDiseaseGivenPositive = bayes(pPositiveGivenDisease, pDisease, pPositive);
141    assertAlmostEquals(pDiseaseGivenPositive.value, 0.1667, 0.001);
142  });