/**
* ASP Formatter
* Formats ASP programs and solutions for display
*/
import { ASPProgram, ASPSolution, AnswerSet, ASPAtom, ASPRule, ASPLiteral } from '../../types.js';
export class ASPFormatter {
/**
* Format ASP program for display
*/
formatProgram(program: ASPProgram): string {
let output = '';
// Facts
if (program.facts.length > 0) {
output += '% Facts\n';
for (const fact of program.facts) {
output += this.formatAtom(fact) + '.\n';
}
output += '\n';
}
// Rules
if (program.rules.length > 0) {
output += '% Rules\n';
for (const rule of program.rules) {
output += this.formatRule(rule) + '\n';
}
output += '\n';
}
// Constraints
if (program.constraints.length > 0) {
output += '% Constraints\n';
for (const constraint of program.constraints) {
if (constraint.type === 'integrity') {
output += ':- ' + constraint.body.map(lit => this.formatLiteral(lit)).join(', ') + '.\n';
}
}
output += '\n';
}
// Weak constraints
if (program.weakConstraints && program.weakConstraints.length > 0) {
output += '% Weak Constraints (Optimization)\n';
for (const wc of program.weakConstraints) {
output += ':~ ' + wc.body.map(lit => this.formatLiteral(lit)).join(', ');
output += `. [${wc.weight}@${wc.priority}]\n`;
}
output += '\n';
}
// Choice rules
if (program.choices && program.choices.length > 0) {
output += '% Choice Rules\n';
for (const choice of program.choices) {
const bounds = choice.bounds.lower === choice.bounds.upper
? ` = ${choice.bounds.lower}`
: ` ${choice.bounds.lower}..${choice.bounds.upper}`;
output += `{${choice.head.map(h => this.formatAtom(h)).join('; ')}}${bounds}`;
if (choice.body.length > 0) {
output += ' :- ' + choice.body.map(lit => this.formatLiteral(lit)).join(', ');
}
output += '.\n';
}
output += '\n';
}
return output.trim();
}
/**
* Format solution for display
*/
formatSolution(solution: ASPSolution): string {
let output = '';
if (!solution.satisfiable) {
output += 'UNSATISFIABLE\n';
output += 'No answer sets exist for this program.\n';
return output;
}
output += `Found ${solution.totalModels} answer set(s)\n`;
if (solution.optimal) {
output += '(Optimal solutions)\n';
}
output += `Computation time: ${solution.computationTime}ms\n\n`;
for (const answerSet of solution.answerSets) {
output += this.formatAnswerSet(answerSet) + '\n';
}
return output.trim();
}
/**
* Format single answer set
*/
formatAnswerSet(answerSet: AnswerSet): string {
let output = `Answer Set ${answerSet.id}`;
if (answerSet.isOptimal) {
output += ' (OPTIMAL)';
}
if (answerSet.cost) {
output += ` Cost: [${answerSet.cost.join(', ')}]`;
}
output += ':\n';
// Check if atoms array is empty or undefined
if (!answerSet.atoms || answerSet.atoms.length === 0) {
output += ' (empty)\n';
return output;
}
// Group atoms by predicate for better readability
const groupedAtoms = this.groupAtomsByPredicate(answerSet.atoms);
// Check if grouping resulted in empty object
if (Object.keys(groupedAtoms).length === 0) {
output += ' (no atoms parsed)\n';
return output;
}
for (const [predicate, atoms] of Object.entries(groupedAtoms)) {
output += ` ${predicate}`;
if (atoms.length > 0 && atoms[0].terms.length > 0) {
output += '(';
output += atoms.map(a => a.terms.join(', ')).join('), (');
output += ')';
}
output += '\n';
}
return output;
}
/**
* Format optimization result
*/
formatOptimization(result: any): string {
let output = '';
if (result.optimalModels.length === 0) {
output += 'No optimal models found.\n';
return output;
}
output += `Optimum cost: [${result.optimumCost.join(', ')}]\n`;
output += `Found ${result.optimalModels.length} optimal model(s)\n\n`;
// Show improvement trace
if (result.improvementTrace && result.improvementTrace.length > 1) {
output += 'Optimization trace:\n';
for (const step of result.improvementTrace) {
output += ` Iteration ${step.iteration}: cost [${step.cost.join(', ')}]\n`;
}
output += '\n';
}
// Show optimal models
for (const model of result.optimalModels) {
output += this.formatAnswerSet(model) + '\n';
}
return output.trim();
}
/**
* Format comparison between answer sets
*/
formatComparison(as1: AnswerSet, as2: AnswerSet): string {
let output = `Comparing Answer Set ${as1.id} vs Answer Set ${as2.id}:\n\n`;
const atoms1 = new Set(as1.atoms.map(a => this.atomToString(a)));
const atoms2 = new Set(as2.atoms.map(a => this.atomToString(a)));
// Common atoms
const common = [...atoms1].filter(a => atoms2.has(a));
if (common.length > 0) {
output += `Common atoms (${common.length}):\n`;
for (const atom of common) {
output += ` ${atom}\n`;
}
output += '\n';
}
// Only in AS1
const only1 = [...atoms1].filter(a => !atoms2.has(a));
if (only1.length > 0) {
output += `Only in Answer Set ${as1.id} (${only1.length}):\n`;
for (const atom of only1) {
output += ` ${atom}\n`;
}
output += '\n';
}
// Only in AS2
const only2 = [...atoms2].filter(a => !atoms1.has(a));
if (only2.length > 0) {
output += `Only in Answer Set ${as2.id} (${only2.length}):\n`;
for (const atom of only2) {
output += ` ${atom}\n`;
}
output += '\n';
}
return output.trim();
}
/**
* Format atom for display
*/
private formatAtom(atom: ASPAtom): string {
let result = '';
if (atom.negation === 'default') {
result += 'not ';
} else if (atom.negation === 'classical') {
result += '-';
}
result += atom.predicate;
if (atom.terms.length > 0) {
result += '(' + atom.terms.join(', ') + ')';
}
return result;
}
/**
* Format literal for display
*/
private formatLiteral(literal: ASPLiteral): string {
let result = '';
if (literal.negation === 'default') {
result += 'not ';
} else if (literal.negation === 'classical') {
result += '-';
}
result += this.formatAtom(literal.atom);
return result;
}
/**
* Format rule for display
*/
private formatRule(rule: ASPRule): string {
let result = '';
// Head
if (Array.isArray(rule.head)) {
result += rule.head.map(h => this.formatAtom(h)).join(' | ');
} else {
result += this.formatAtom(rule.head);
}
// Body
if (rule.body.length > 0) {
result += ' :- ' + rule.body.map(lit => this.formatLiteral(lit)).join(', ');
}
result += '.';
return result;
}
/**
* Group atoms by predicate
*/
private groupAtomsByPredicate(atoms: ASPAtom[]): Record<string, ASPAtom[]> {
const grouped: Record<string, ASPAtom[]> = {};
for (const atom of atoms) {
if (!grouped[atom.predicate]) {
grouped[atom.predicate] = [];
}
grouped[atom.predicate].push(atom);
}
return grouped;
}
/**
* Convert atom to string
*/
private atomToString(atom: ASPAtom): string {
return this.formatAtom(atom);
}
}