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
44 changes: 20 additions & 24 deletions playground/examples/uncertainty.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module uncertainty;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand All @@ -20,66 +17,66 @@ module uncertainty;
import { type Tri } from '../src/ternary.ts';

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

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

export function addI(a: Interval, b: Interval): Interval {
fn 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];
fn mulI(a: Interval, b: Interval): Interval {
let 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 {
fn 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 {
struct Gaussian {
mu: number;
sd: number;
}

export const gauss = (mu: number, sd: number): Gaussian => ({ mu, sd: Math.abs(sd) });
let 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 {
fn 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);
fn mulG(a: Gaussian, b: Gaussian): Gaussian {
let mu = a.mu * b.mu;
let rel = Math.hypot(a.sd / a.mu, b.sd / b.mu);
return gauss(mu, Math.abs(mu) * rel);
}

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

const a = iv(2, 4);
const b = iv(3, 5);
let a = iv(2, 4);
let 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);
let g1 = gauss(10, 1);
let g2 = gauss(20, 2);
let s = addG(g1, g2);
let 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)}`);
Expand All @@ -89,4 +86,3 @@ if (import.meta.main) {
main();
}

==================================== */
8 changes: 2 additions & 6 deletions playground/src/main.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module main;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand All @@ -18,7 +15,7 @@ module main;
import { main as ternaryDemo } from './ternary.ts';
import { main as probabilityDemo } from './probability.ts';

function main(): void {
fn main(): void {
console.log('BetLang Playground — Symbolic Probabilistic Metalanguage\n');
ternaryDemo();
console.log('\n' + '-'.repeat(60) + '\n');
Expand All @@ -30,4 +27,3 @@ if (import.meta.main) {
main();
}

==================================== */
28 changes: 12 additions & 16 deletions playground/src/probability.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module probability;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand All @@ -23,13 +20,13 @@ module probability;
import { bet, type Tri } from './ternary.ts';

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

/** Deterministic, seedable PRNG (mulberry32) so demos/tests are reproducible. */
export function rng(seed: number): () => number {
fn rng(seed: number): () => number {
let s = seed >>> 0;
return () => {
s = (s + 0x6d2b79f5) >>> 0;
Expand All @@ -41,8 +38,8 @@ export function rng(seed: number): () => number {
}

/** 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);
fn betWeighted<A>(branches: Branch<A>[], draw: () => number): A {
let 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) {
Expand All @@ -56,7 +53,7 @@ export function betWeighted<A>(branches: Branch<A>[], draw: () => number): A {
* 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>(
fn betConditional<A>(
predicate: () => Tri,
ifTrue: () => A,
uncertain: () => A,
Expand All @@ -66,13 +63,13 @@ export function betConditional<A>(
}

/** Monte-Carlo expectation of a numeric weighted bet over `n` samples. */
export function expectation(branches: Branch<number>[], n: number, draw: () => number): number {
fn 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 {
fn main(): void {
console.log('=== BetLang Probabilistic Layer ===\n');

// A loaded three-sided "coin": 60% True, 30% Unknown, 10% False.
Expand All @@ -81,9 +78,9 @@ export function main(): void {
{ weight: 0.3, value: () => 'U' as Tri },
{ weight: 0.1, value: () => 'F' as Tri },
];
const draw = rng(42);
let draw = rng(42);
const counts: Record<Tri, number> = { T: 0, U: 0, F: 0 };
const N = 100_000;
let 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(
Expand All @@ -97,11 +94,11 @@ export function main(): void {
{ weight: 2, value: () => 10 },
{ weight: 7, value: () => 0 },
];
const ev = expectation(payout, 200_000, rng(7));
let 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(
let choice = betConditional(
() => 'U' as Tri,
() => 'committed',
() => 'hedged (predicate was Unknown)',
Expand All @@ -114,4 +111,3 @@ if (import.meta.main) {
main();
}

==================================== */
26 changes: 11 additions & 15 deletions playground/src/ternary.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module ternary;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand All @@ -22,30 +19,30 @@ module ternary;
// under the order F < U < T.

/** The three logical values of the BetLang core. */
export type Tri = 'T' | 'F' | 'U';
struct 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 {
fn 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 {
fn 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 {
fn 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 {
fn implies(a: Tri, b: Tri): Tri {
return or(not(a), b);
}

Expand All @@ -56,7 +53,7 @@ export function implies(a: Tri, b: Tri): Tri {
* 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>(
fn bet<A>(
selector: () => Tri,
onTrue: () => A,
onUnknown: () => A,
Expand All @@ -73,13 +70,13 @@ export function bet<A>(
}

/** Render a full binary truth table for a Tri operator. */
function table(name: string, op: (a: Tri, b: Tri) => Tri): string {
fn 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)}`));
let rows = vals.flatMap((a) => vals.map((b) => ` ${a} ${name} ${b} = ${op(a, b)}`));
return [`${name} truth table:`, ...rows].join('\n');
}

export function main(): void {
fn main(): void {
console.log('=== BetLang Ternary Core ===');
console.log('Values: True (T), False (F), Unknown (U)\n');
console.log(table('AND', and));
Expand All @@ -91,7 +88,7 @@ export function main(): void {

// Laziness demonstration: only the selected thunk runs.
let evaluated = '';
const result = bet(
let result = bet(
() => 'U',
() => {
evaluated = 'true-branch';
Expand All @@ -114,4 +111,3 @@ if (import.meta.main) {
main();
}

==================================== */
16 changes: 6 additions & 10 deletions playground/test/probability_test.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module probability_test;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand All @@ -15,10 +12,10 @@ import { betConditional, betWeighted, type Branch, expectation, rng } from '../s
import { type Tri } from '../src/ternary.ts';

Deno.test('rng is deterministic for a fixed seed and stays in [0,1)', () => {
const a = rng(123);
const b = rng(123);
let a = rng(123);
let b = rng(123);
for (let i = 0; i < 50; i++) {
const x = a();
let x = a();
assertEquals(x, b());
assert(x >= 0 && x < 1);
}
Expand All @@ -30,9 +27,9 @@ Deno.test('betWeighted respects weights within Monte-Carlo tolerance', () => {
{ weight: 0.3, value: () => 'U' },
{ weight: 0.1, value: () => 'F' },
];
const draw = rng(42);
let draw = rng(42);
const counts: Record<Tri, number> = { T: 0, U: 0, F: 0 };
const N = 50_000;
let N = 50_000;
for (let i = 0; i < N; i++) counts[betWeighted(branches, draw)]++;
assert(Math.abs(counts.T / N - 0.6) < 0.02);
assert(Math.abs(counts.U / N - 0.3) < 0.02);
Expand Down Expand Up @@ -64,4 +61,3 @@ Deno.test('betConditional defers to the uncertain branch on Unknown', () => {
);
});

==================================== */
8 changes: 2 additions & 6 deletions playground/test/ternary_test.affine
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
// Ported via Harvard Engine mechanical processor
// Ported via Harvard Engine (Semantic pass)

module ternary_test;

// TODO: Complete semantic implementation

/* === ORIGINAL TYPESCRIPT CONTEXT ===
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
// SPDX-FileCopyrightText: 2025 hyperpolymath
Expand Down Expand Up @@ -49,7 +46,7 @@ Deno.test('implies(a,b) == or(not a, b)', () => {

Deno.test('bet is lazy: only the selected branch is forced', () => {
const forced: string[] = [];
const r = bet(
let r = bet(
() => 'F',
() => {
forced.push('T');
Expand All @@ -68,4 +65,3 @@ Deno.test('bet is lazy: only the selected branch is forced', () => {
assertEquals(forced, ['F']);
});

==================================== */
Loading