/**
* ASP Explainer
* Explain why atoms are or aren't in answer sets
*/
import { ASPProgram, ASPSolution, ASPExplanation, AnswerSet, RuleApplication } from '../../types.js';
export class ASPExplainer {
/**
* Explain why an atom is in an answer set
*/
explainDerivation(
atom: string,
answerSet: AnswerSet,
program: ASPProgram
): ASPExplanation {
const ruleApplications: RuleApplication[] = [];
// Find rules that could derive this atom
const derivingRules = program.rules.filter(rule => {
if (Array.isArray(rule.head)) {
return rule.head.some(h => h.predicate === atom);
}
return rule.head.predicate === atom;
});
let step = 1;
for (const rule of derivingRules) {
ruleApplications.push({
step: step++,
rule,
substitution: {},
derived: atom,
justification: 'Rule application'
});
}
return {
type: 'derivation',
summary: `${atom} is derived via ${ruleApplications.length} rule(s)`,
ruleApplications,
insights: [
`The atom ${atom} appears in answer set ${answerSet.id}`,
`It is derived through stable model semantics`
]
};
}
/**
* Explain why an atom is NOT in an answer set
*/
explainWhyNot(
atom: string,
answerSet: AnswerSet,
program: ASPProgram
): ASPExplanation {
const reasons: string[] = [];
// Check if atom is explicitly constrained out
for (const constraint of program.constraints) {
const mentions = constraint.body.some(lit => lit.atom.predicate === atom);
if (mentions) {
reasons.push('Atom is constrained by an integrity constraint');
}
}
// Check if no rules derive it
const derivingRules = program.rules.filter(rule => {
if (Array.isArray(rule.head)) {
return rule.head.some(h => h.predicate === atom);
}
return rule.head.predicate === atom;
});
if (derivingRules.length === 0) {
reasons.push('No rules have this atom in their head');
}
return {
type: 'why_not',
summary: `${atom} is NOT in answer set ${answerSet.id}`,
ruleApplications: [],
insights: reasons.length > 0 ? reasons : ['Atom is not derivable in this stable model']
};
}
/**
* Explain stability of answer set
*/
explainStability(answerSet: AnswerSet, program: ASPProgram): ASPExplanation {
return {
type: 'stability',
summary: `Answer set ${answerSet.id} is stable`,
ruleApplications: [],
insights: [
'All applicable rules have their heads in the set',
'No additional atoms can be derived',
'The set is minimal and stable'
]
};
}
/**
* Compare two answer sets
*/
explainComparison(as1: AnswerSet, as2: AnswerSet): ASPExplanation {
const atoms1 = new Set(as1.atoms.map(a => `${a.predicate}(${a.terms.join(',')})`));
const atoms2 = new Set(as2.atoms.map(a => `${a.predicate}(${a.terms.join(',')})`));
const common = [...atoms1].filter(a => atoms2.has(a));
const only1 = [...atoms1].filter(a => !atoms2.has(a));
const only2 = [...atoms2].filter(a => !atoms1.has(a));
const insights = [
`${common.length} atoms are common to both answer sets`,
`${only1.length} atoms are unique to answer set ${as1.id}`,
`${only2.length} atoms are unique to answer set ${as2.id}`
];
if (only1.length > 0) {
insights.push(`Unique to AS ${as1.id}: ${only1.slice(0, 3).join(', ')}...`);
}
if (only2.length > 0) {
insights.push(`Unique to AS ${as2.id}: ${only2.slice(0, 3).join(', ')}...`);
}
return {
type: 'comparison',
summary: `Comparing answer sets ${as1.id} and ${as2.id}`,
ruleApplications: [],
insights
};
}
}