Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions playground/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions playground/examples/uncertainty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// SPDX-FileCopyrightText: 2025 hyperpolymath
//
// BetLang playground — uncertainty-aware number tower (sample).
//
// The full language exposes ~14 uncertainty number systems as its type
// system. This example demonstrates two of them — interval arithmetic and
// Gaussian (mean ± sd) propagation — and how the lazy ternary core lets a
// comparison return Unknown when two uncertain quantities overlap.

import { type Tri } from '../src/ternary.ts';

/** Closed real interval [lo, hi]. */
export interface Interval {
lo: number;
hi: number;
}

export const iv = (lo: number, hi: number): Interval => ({
lo: Math.min(lo, hi),
hi: Math.max(lo, hi),
});

export function addI(a: Interval, b: Interval): Interval {
return iv(a.lo + b.lo, a.hi + b.hi);
}

export function mulI(a: Interval, b: Interval): Interval {
const ps = [a.lo * b.lo, a.lo * b.hi, a.hi * b.lo, a.hi * b.hi];
return iv(Math.min(...ps), Math.max(...ps));
}

/** Ternary comparison: definite when disjoint, Unknown when they overlap. */
export function ltI(a: Interval, b: Interval): Tri {
if (a.hi < b.lo) return 'T';
if (a.lo > b.hi) return 'F';
return 'U';
}

/** Gaussian number: mean with standard deviation. */
export interface Gaussian {
mu: number;
sd: number;
}

export const gauss = (mu: number, sd: number): Gaussian => ({ mu, sd: Math.abs(sd) });

/** First-order (uncorrelated) propagation through sum and product. */
export function addG(a: Gaussian, b: Gaussian): Gaussian {
return gauss(a.mu + b.mu, Math.hypot(a.sd, b.sd));
}

export function mulG(a: Gaussian, b: Gaussian): Gaussian {
const mu = a.mu * b.mu;
const rel = Math.hypot(a.sd / a.mu, b.sd / b.mu);
return gauss(mu, Math.abs(mu) * rel);
}

function main(): void {
console.log('=== BetLang Uncertainty Modeling ===\n');

const a = iv(2, 4);
const b = iv(3, 5);
console.log(`Intervals: a=[${a.lo},${a.hi}] b=[${b.lo},${b.hi}]`);
console.log(` a + b = [${addI(a, b).lo}, ${addI(a, b).hi}]`);
console.log(` a * b = [${mulI(a, b).lo}, ${mulI(a, b).hi}]`);
console.log(` a < b ? -> ${ltI(a, b)} (overlap => Unknown, not a false certainty)`);
console.log(` [0,1] < [5,6] ? -> ${ltI(iv(0, 1), iv(5, 6))}`);

const g1 = gauss(10, 1);
const g2 = gauss(20, 2);
const s = addG(g1, g2);
const p = mulG(g1, g2);
console.log(`\nGaussians: g1=${g1.mu}±${g1.sd} g2=${g2.mu}±${g2.sd}`);
console.log(` g1 + g2 = ${s.mu}±${s.sd.toFixed(4)}`);
console.log(` g1 * g2 = ${p.mu}±${p.sd.toFixed(4)}`);
}

if (import.meta.main) {
main();
}
22 changes: 22 additions & 0 deletions playground/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// SPDX-FileCopyrightText: 2025 hyperpolymath
//
// BetLang playground entry point (`deno task dev` / `just dev`).
//
// Runs the ternary core demo and the probabilistic layer demo in sequence,
// giving a contributor a single runnable surface for the sandbox.

import { main as ternaryDemo } from './ternary.ts';
import { main as probabilityDemo } from './probability.ts';

function main(): void {
console.log('BetLang Playground — Symbolic Probabilistic Metalanguage\n');
ternaryDemo();
console.log('\n' + '-'.repeat(60) + '\n');
probabilityDemo();
console.log('\nDone. See `just --list` for individual demos.');
}

if (import.meta.main) {
main();
}
106 changes: 106 additions & 0 deletions playground/src/probability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// SPDX-FileCopyrightText: 2025 hyperpolymath
//
// BetLang playground — probabilistic layer.
//
// Builds the non-uniform / predicate-driven choice forms on top of the
// lazy ternary core:
// (bet/weighted ...) — non-uniform probabilities
// (bet_conditional ...) — predicate-driven selection
//
// Evaluation stays lazy: a Bet is a *description* of a choice; only the
// branch that wins a draw is forced.

import { bet, type Tri } from './ternary.ts';

/** A lazy, weighted branch: a relative weight and a thunk producing a value. */
export interface Branch<A> {
weight: number;
value: () => A;
}

/** Deterministic, seedable PRNG (mulberry32) so demos/tests are reproducible. */
export function rng(seed: number): () => number {
let s = seed >>> 0;
return () => {
s = (s + 0x6d2b79f5) >>> 0;
let t = s;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}

/** Force exactly one branch, chosen with probability proportional to weight. */
export function betWeighted<A>(branches: Branch<A>[], draw: () => number): A {
const total = branches.reduce((acc, b) => acc + b.weight, 0);
if (total <= 0) throw new Error('betWeighted: weights must sum to a positive number');
let r = draw() * total;
for (const b of branches) {
r -= b.weight;
if (r <= 0) return b.value();
}
return branches[branches.length - 1].value(); // float-rounding fallback
}

/**
* Predicate-driven ternary selection. The predicate yields a Tri; a definite
* answer takes the matching branch, Unknown defers to the `uncertain` branch.
*/
export function betConditional<A>(
predicate: () => Tri,
ifTrue: () => A,
uncertain: () => A,
ifFalse: () => A,
): A {
return bet(predicate, ifTrue, uncertain, ifFalse);
}

/** Monte-Carlo expectation of a numeric weighted bet over `n` samples. */
export function expectation(branches: Branch<number>[], n: number, draw: () => number): number {
let sum = 0;
for (let i = 0; i < n; i++) sum += betWeighted(branches, draw);
return sum / n;
}

export function main(): void {
console.log('=== BetLang Probabilistic Layer ===\n');

// A loaded three-sided "coin": 60% True, 30% Unknown, 10% False.
const loaded: Branch<Tri>[] = [
{ weight: 0.6, value: () => 'T' as Tri },
{ weight: 0.3, value: () => 'U' as Tri },
{ weight: 0.1, value: () => 'F' as Tri },
];
const draw = rng(42);
const counts: Record<Tri, number> = { T: 0, U: 0, F: 0 };
const N = 100_000;
for (let i = 0; i < N; i++) counts[betWeighted(loaded, draw)]++;
console.log(`Empirical distribution over ${N.toLocaleString()} draws (target 0.60/0.30/0.10):`);
console.log(
` T=${(counts.T / N).toFixed(3)} U=${(counts.U / N).toFixed(3)} ` +
`F=${(counts.F / N).toFixed(3)}`,
);

// Expected payout of a weighted numeric bet.
const payout: Branch<number>[] = [
{ weight: 1, value: () => 100 },
{ weight: 2, value: () => 10 },
{ weight: 7, value: () => 0 },
];
const ev = expectation(payout, 200_000, rng(7));
console.log(`\nExpected payout (analytic = 12.0): ${ev.toFixed(2)}`);

// Conditional choice that stays total under Unknown.
const choice = betConditional(
() => 'U' as Tri,
() => 'committed',
() => 'hedged (predicate was Unknown)',
() => 'declined',
);
console.log(`\nbet_conditional under Unknown -> ${choice}`);
}

if (import.meta.main) {
main();
}
106 changes: 106 additions & 0 deletions playground/src/ternary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: PMPL-1.0-or-later
// SPDX-FileCopyrightText: 2025 hyperpolymath
//
// BetLang playground — ternary core.
//
// BetLang's minimal core is a Kleene strong three-valued logic over
// { True, False, Unknown } plus the lazy ternary choice primitive
// (bet A B C)
// where only the selected branch is ever evaluated.
//
// The AND truth table here is exactly the one documented in the playground
// Justfile (`just ternary-demo`): conjunction is `min`, disjunction is `max`
// under the order F < U < T.

/** The three logical values of the BetLang core. */
export type Tri = 'T' | 'F' | 'U';

const ORDER: Record<Tri, number> = { F: 0, U: 1, T: 2 };
const BY_RANK: Tri[] = ['F', 'U', 'T'];

/** Kleene negation: swaps T/F, fixes U. */
export function not(a: Tri): Tri {
if (a === 'T') return 'F';
if (a === 'F') return 'T';
return 'U';
}

/** Kleene conjunction = min under F < U < T. */
export function and(a: Tri, b: Tri): Tri {
return BY_RANK[Math.min(ORDER[a], ORDER[b])];
}

/** Kleene disjunction = max under F < U < T. */
export function or(a: Tri, b: Tri): Tri {
return BY_RANK[Math.max(ORDER[a], ORDER[b])];
}

/** Material implication, defined as `or(not(a), b)`. */
export function implies(a: Tri, b: Tri): Tri {
return or(not(a), b);
}

/**
* The lazy ternary choice primitive `(bet A B C)`.
*
* Branches are passed as thunks; exactly one is forced. `selector` decides
* which branch wins — when it returns 'U' the middle branch is taken, which
* is what makes the choice *total* even under uncertainty.
*/
export function bet<A>(
selector: () => Tri,
onTrue: () => A,
onUnknown: () => A,
onFalse: () => A,
): A {
switch (selector()) {
case 'T':
return onTrue();
case 'F':
return onFalse();
default:
return onUnknown();
}
}

/** Render a full binary truth table for a Tri operator. */
function table(name: string, op: (a: Tri, b: Tri) => Tri): string {
const vals: Tri[] = ['T', 'U', 'F'];
const rows = vals.flatMap((a) => vals.map((b) => ` ${a} ${name} ${b} = ${op(a, b)}`));
return [`${name} truth table:`, ...rows].join('\n');
}

export function main(): void {
console.log('=== BetLang Ternary Core ===');
console.log('Values: True (T), False (F), Unknown (U)\n');
console.log(table('AND', and));
console.log();
console.log(table('OR', or));
console.log();
console.log('NOT: NOT T = ' + not('T') + ', NOT U = ' + not('U') + ', NOT F = ' + not('F'));
console.log();

// Laziness demonstration: only the selected thunk runs.
let evaluated = '';
const result = bet(
() => 'U',
() => {
evaluated = 'true-branch';
return 1;
},
() => {
evaluated = 'unknown-branch';
return 0;
},
() => {
evaluated = 'false-branch';
return -1;
},
);
console.log(`Lazy (bet ? : :) on Unknown -> result=${result}, evaluated=${evaluated}`);
console.log('(only the Unknown branch ran; True/False thunks were never forced)');
}

if (import.meta.main) {
main();
}
Loading
Loading