/**
* Mathematical utilities for Random Tree Model calculations
* @module core/rtm-math
*/
import { TemporalScale, RTMParameters } from '../types/rtm.js';
/**
* Calculate the number of weak compositions of n into k parts
* This is the count of ways to partition n items into k bins (allowing empty bins)
* @param n - Total number to partition
* @param k - Number of parts/bins
* @returns Number of weak compositions
*/
export function weakCompositions(n: number, k: number): number {
if (k === 0) return n === 0 ? 1 : 0;
if (k === 1) return 1;
// Using stars and bars: C(n+k-1, k-1)
return binomialCoefficient(n + k - 1, k - 1);
}
/**
* Calculate binomial coefficient C(n, k)
* @param n - Total items
* @param k - Items to choose
* @returns Binomial coefficient
*/
export function binomialCoefficient(n: number, k: number): number {
if (k > n) return 0;
if (k === 0 || k === n) return 1;
// Optimize by using smaller k
k = Math.min(k, n - k);
let result = 1;
for (let i = 0; i < k; i++) {
result = (result * (n - i)) / (i + 1);
}
return Math.round(result);
}
/**
* Calculate the probability of obtaining child node sizes from a parent
* Based on the RTM recurrence relation
* @param parentSize - Size of parent node
* @param childSizes - Array of child node sizes
* @param maxBranching - Maximum branching factor K
* @returns Probability of this partition
*/
export function partitionProbability(
parentSize: number,
childSizes: number[],
maxBranching: number
): number {
const k = childSizes.length;
if (k > maxBranching || k === 0) return 0;
const childSum = childSizes.reduce((sum, size) => sum + size, 0);
if (childSum !== parentSize) return 0;
// P(n^(l+1)|n^(l)) = Z_K(n^(l)) / Z_{K-1}(n^(l) - n^(l+1))
const numerator = weakCompositions(parentSize - childSum, maxBranching - 1);
const denominator = weakCompositions(parentSize, maxBranching);
return numerator / denominator;
}
/**
* Calculate compression ratio for a given temporal scale
* @param nodeSize - Number of clauses in the node
* @param scale - Temporal scale
* @param parameters - RTM parameters
* @returns Compression ratio
*/
export function calculateCompressionRatio(
nodeSize: number,
scale: TemporalScale,
parameters: RTMParameters
): number {
const targetCompression = parameters.compressionTargets[scale];
// Base compression increases with node size
const baseCompression = Math.min(nodeSize / 10, targetCompression);
// Apply scale-specific adjustment
const scaleMultiplier = getScaleMultiplier(scale);
return baseCompression * scaleMultiplier;
}
/**
* Get the hierarchical depth multiplier for a temporal scale
* @param scale - Temporal scale
* @returns Scale multiplier
*/
function getScaleMultiplier(scale: TemporalScale): number {
const multipliers: Record<TemporalScale, number> = {
'clause': 1.0,
'sentence': 1.2,
'paragraph': 1.5,
'section': 2.0,
'chapter': 3.0,
'document': 4.0
};
return multipliers[scale] || 1.0;
}
/**
* Calculate the expected recall length given narrative length and parameters
* @param narrativeLength - Total number of clauses
* @param parameters - RTM parameters
* @returns Expected recall length
*/
export function expectedRecallLength(
narrativeLength: number,
parameters: RTMParameters
): number {
const { maxBranchingFactor: K, maxRecallDepth: D } = parameters;
// For large N, recall length approaches K^D (soft limit)
const softLimit = Math.pow(K, D);
// Sublinear growth: C ~ N^(1/D)
const sublinearGrowth = Math.pow(narrativeLength, 1 / D);
// Combine both effects
return Math.min(softLimit, sublinearGrowth * Math.sqrt(K));
}
/**
* Calculate the variance in recall length across an ensemble
* @param narrativeLength - Total number of clauses
* @param parameters - RTM parameters
* @param ensembleSize - Number of trees in ensemble
* @returns Variance in recall length
*/
export function recallLengthVariance(
narrativeLength: number,
parameters: RTMParameters,
ensembleSize: number
): number {
const meanRecall = expectedRecallLength(narrativeLength, parameters);
// Variance decreases with ensemble size
const baseVariance = meanRecall * 0.2; // 20% coefficient of variation
return baseVariance / Math.sqrt(ensembleSize);
}
/**
* Check if a narrative length exhibits scale-invariant properties
* @param narrativeLength - Total number of clauses
* @param threshold - Minimum length for scale invariance
* @returns True if scale-invariant regime
*/
export function isScaleInvariant(
narrativeLength: number,
threshold: number = 1000
): boolean {
return narrativeLength >= threshold;
}
/**
* Calculate the scaling exponent for compression distribution
* @param compressionRatios - Array of compression ratios
* @returns Scaling exponent (slope in log-log plot)
*/
export function calculateScalingExponent(compressionRatios: number[]): number {
if (compressionRatios.length < 2) return 0;
// Convert to log scale
const logRatios = compressionRatios.map(r => Math.log(r));
const logIndices = compressionRatios.map((_, i) => Math.log(i + 1));
// Linear regression in log-log space
const n = logRatios.length;
const sumX = logIndices.reduce((a, b) => a + b, 0);
const sumY = logRatios.reduce((a, b) => a + b, 0);
const sumXY = logIndices.reduce((sum, x, i) => sum + x * logRatios[i]!, 0);
const sumX2 = logIndices.reduce((sum, x) => sum + x * x, 0);
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
return slope;
}