/**
* ProbLog Formatter - Output Formatting
* Formats probabilistic results for display
*/
import {
ProbabilisticProgram,
ProbabilisticResult,
BayesianInference,
DecisionRecommendation,
DerivationStep
} from '../../types.js';
export class ProbLogFormatter {
/**
* Format probabilistic program for display
*/
formatProgram(program: ProbabilisticProgram): string {
const lines: string[] = [];
lines.push('Probabilistic Logic Program');
lines.push('==========================\n');
if (program.facts.length > 0) {
lines.push('Facts:');
for (const fact of program.facts) {
const label = fact.label ? ` // ${fact.label}` : '';
lines.push(` ${this.formatProb(fact.probability)}::${fact.fact}${label}`);
}
lines.push('');
}
if (program.rules.length > 0) {
lines.push('Rules:');
for (const rule of program.rules) {
const bodyStr = rule.body.join(', ');
const prob = rule.probability !== undefined ? `${this.formatProb(rule.probability)}::` : '';
const label = rule.label ? ` // ${rule.label}` : '';
lines.push(` ${prob}${rule.head} :- ${bodyStr}${label}`);
}
lines.push('');
}
if (program.evidence && Object.keys(program.evidence).length > 0) {
lines.push('Evidence:');
for (const [fact, value] of Object.entries(program.evidence)) {
lines.push(` ${fact} = ${value}`);
}
lines.push('');
}
if (program.queries && program.queries.length > 0) {
lines.push('Queries:');
for (const query of program.queries) {
lines.push(` query(${query})`);
}
}
return lines.join('\n');
}
/**
* Format probabilistic result
*/
formatResult(result: ProbabilisticResult, detailed: boolean = true): string {
const lines: string[] = [];
lines.push(`Query: P(${result.query}${this.formatEvidenceCompact(result.evidence)})`);
lines.push(`Probability: ${this.formatProb(result.probability)} (${this.formatPercent(result.probability)})`);
if (result.confidence) {
lines.push(`Confidence Interval: [${this.formatProb(result.confidence.lower)}, ${this.formatProb(result.confidence.upper)}]`);
}
if (result.evidence && Object.keys(result.evidence).length > 0) {
lines.push('\nEvidence:');
for (const [fact, value] of Object.entries(result.evidence)) {
lines.push(` ${fact} = ${value}`);
}
}
if (detailed && result.explanation) {
lines.push('\nExplanation:');
lines.push(result.explanation);
}
if (detailed && result.derivationTrace && result.derivationTrace.length > 0) {
lines.push('\nDerivation Trace:');
for (const step of result.derivationTrace) {
lines.push(this.formatDerivationStep(step));
}
}
return lines.join('\n');
}
/**
* Format Bayesian inference result
*/
formatInference(inference: BayesianInference): string {
const lines: string[] = [];
lines.push('Bayesian Inference');
lines.push('==================\n');
lines.push('Prior Probabilities:');
for (const [query, prob] of Object.entries(inference.prior)) {
lines.push(` P(${query}) = ${this.formatProb(prob)} (${this.formatPercent(prob)})`);
}
lines.push('\nPosterior Probabilities (given evidence):');
for (const [query, prob] of Object.entries(inference.posterior)) {
const priorProb = inference.prior[query] || 0;
const change = prob - priorProb;
const changeStr = change > 0 ? `+${this.formatProb(change)}` : this.formatProb(change);
const arrow = change > 0 ? '↑' : change < 0 ? '↓' : '=';
lines.push(` P(${query}|evidence) = ${this.formatProb(prob)} (${this.formatPercent(prob)}) ${arrow} ${changeStr}`);
}
lines.push(`\nLikelihood: ${this.formatProb(inference.likelihood)}`);
lines.push(`Method: ${inference.method}`);
if (inference.evidence && Object.keys(inference.evidence).length > 0) {
lines.push('\nEvidence:');
for (const [fact, value] of Object.entries(inference.evidence)) {
lines.push(` ${fact} = ${value}`);
}
}
if (inference.explanation) {
lines.push('\nExplanation:');
lines.push(inference.explanation);
}
return lines.join('\n');
}
/**
* Format decision recommendation
*/
formatDecision(decision: DecisionRecommendation): string {
const lines: string[] = [];
lines.push('Decision Analysis');
lines.push('=================\n');
lines.push(`Recommended Action: ${decision.recommendedAction}`);
lines.push(`Expected Utility: ${decision.expectedUtility.toFixed(2)}`);
lines.push(`Confidence: ${this.formatPercent(decision.confidence)}\n`);
if (decision.alternatives.length > 0) {
lines.push('Alternatives Considered:');
const sorted = [...decision.alternatives].sort((a, b) => b.expectedUtility - a.expectedUtility);
for (let i = 0; i < sorted.length; i++) {
const alt = sorted[i];
const marker = alt.action === decision.recommendedAction ? '✓' : ' ';
lines.push(` ${marker} ${alt.action}: EU = ${alt.expectedUtility.toFixed(2)}`);
}
lines.push('');
}
lines.push('Risk Analysis:');
lines.push(` Best Case: utility = ${decision.riskAnalysis.bestCase.utility.toFixed(2)}, ` +
`P = ${this.formatPercent(decision.riskAnalysis.bestCase.probability)}`);
lines.push(` Worst Case: utility = ${decision.riskAnalysis.worstCase.utility.toFixed(2)}, ` +
`P = ${this.formatPercent(decision.riskAnalysis.worstCase.probability)}`);
lines.push(` Variance: ${decision.riskAnalysis.variance.toFixed(2)}\n`);
if (decision.explanation) {
lines.push('Explanation:');
lines.push(decision.explanation);
}
return lines.join('\n');
}
/**
* Format derivation step
*/
private formatDerivationStep(step: DerivationStep): string {
const deps = step.dependencies.length > 0 ? ` [depends on: ${step.dependencies.join(', ')}]` : '';
return ` ${step.stepNumber}. ${step.rule} → P = ${this.formatProb(step.probability)}${deps}`;
}
/**
* Format probability as decimal
*/
private formatProb(prob: number): string {
return prob.toFixed(3);
}
/**
* Format probability as percentage
*/
private formatPercent(prob: number): string {
return `${(prob * 100).toFixed(1)}%`;
}
/**
* Format evidence compactly for query display
*/
private formatEvidenceCompact(evidence?: Record<string, boolean>): string {
if (!evidence || Object.keys(evidence).length === 0) {
return '';
}
const parts = Object.entries(evidence).map(([fact, value]) => `${fact}=${value}`);
return `|${parts.join(',')}`;
}
/**
* Format probability distribution as bar chart
*/
formatDistribution(dist: Record<string, number>, maxWidth: number = 40): string {
const lines: string[] = [];
const maxProb = Math.max(...Object.values(dist));
for (const [label, prob] of Object.entries(dist)) {
const barLength = Math.round((prob / maxProb) * maxWidth);
const bar = '█'.repeat(barLength) + '░'.repeat(maxWidth - barLength);
lines.push(`${label.padEnd(15)} ${bar} ${this.formatPercent(prob)}`);
}
return lines.join('\n');
}
/**
* Format table of probabilities
*/
formatProbabilityTable(data: Record<string, number>[]): string {
const lines: string[] = [];
// Header
lines.push('┌─────────────────────┬─────────────┐');
lines.push('│ Query │ Probability │');
lines.push('├─────────────────────┼─────────────┤');
// Data rows
for (const row of data) {
for (const [query, prob] of Object.entries(row)) {
const queryStr = query.padEnd(20).substring(0, 20);
const probStr = this.formatPercent(prob).padStart(11);
lines.push(`│ ${queryStr} │ ${probStr} │`);
}
}
// Footer
lines.push('└─────────────────────┴─────────────┘');
return lines.join('\n');
}
/**
* Format comparison table
*/
formatComparison(before: Record<string, number>, after: Record<string, number>): string {
const lines: string[] = [];
lines.push('┌─────────────┬────────┬────────┬─────────┐');
lines.push('│ Query │ Before │ After │ Change │');
lines.push('├─────────────┼────────┼────────┼─────────┤');
for (const query of Object.keys(before)) {
const beforeProb = before[query];
const afterProb = after[query] || 0;
const change = afterProb - beforeProb;
const changeStr = (change >= 0 ? '+' : '') + this.formatProb(change);
lines.push(`│ ${query.padEnd(12).substring(0, 12)} │ ` +
`${this.formatProb(beforeProb)} │ ` +
`${this.formatProb(afterProb)} │ ` +
`${changeStr.padStart(7)} │`);
}
lines.push('└─────────────┴────────┴────────┴─────────┘');
return lines.join('\n');
}
/**
* Format execution summary
*/
formatSummary(results: {
query: string;
probability: number;
time_ms?: number;
}[]): string {
const lines: string[] = [];
lines.push('Execution Summary');
lines.push('=================\n');
lines.push(`Total Queries: ${results.length}`);
if (results.length > 0 && results[0].time_ms !== undefined) {
const totalTime = results.reduce((sum, r) => sum + (r.time_ms || 0), 0);
const avgTime = totalTime / results.length;
lines.push(`Total Time: ${totalTime.toFixed(2)}ms`);
lines.push(`Average Time per Query: ${avgTime.toFixed(2)}ms`);
}
lines.push('\nResults:');
for (const result of results) {
const timeStr = result.time_ms ? ` (${result.time_ms.toFixed(2)}ms)` : '';
lines.push(` ${result.query}: ${this.formatPercent(result.probability)}${timeStr}`);
}
return lines.join('\n');
}
}