import { Node, NodeType } from "../model/Argument"; import { CertusContext, FZ } from "../model/Context"; import { EvaluationError } from "../parser/Evaluator"; import { DeductiveMacro } from "./DeductiveMacro"; export class FuseStrictMacro extends DeductiveMacro { constructor(name?: string | undefined) { super(name || "fuse_strict"); } public evaluate(ctx: CertusContext): FZ { const root: Node = ctx.getRoot(); const nodes = this.getNodesFromContext(ctx); const P = nodes.filter(n => NodeType.checkSubType(NodeType.PREMISE, n.type)); const D = nodes.filter(n => NodeType.checkSubType(NodeType.DEFEATER, n.type)); if (P.length == 0 && D.length == 0) { return ctx.getDefaultCanonical(root.type); } else { const CS = ctx.getCanonicalSets(); const pScores = P.map(x => this.scoreNode(ctx, x, CS)); const dScores = D.map(x => this.scoreNode(ctx, x, CS)); let result: number = 0; if (NodeType.checkSubType(NodeType.PREMISE, root.type)) { if (dScores.some(x => x > -4)) { result = -4; } else if (pScores.length == 0 && dScores.every(x => x <= -4)) { result = 0; } else { result = pScores.reduce((a,x) => a + x, 0)/ pScores.length } } else if (NodeType.checkSubType(NodeType.DEFEATER, root.type)) { if (dScores.every(x => x <= -4) && pScores.every(x => x >= 4)) { result = -4; } else { const pSum = pScores.reduce((a,x) => a + x, 0); const dSum = dScores.reduce((a,x) => a + x, 0); result = (dSum - pSum) / (pScores.length + dScores.length); } } else { throw new EvaluationError(`Unsupported parent node type ${root.type} when evaluating ${this.name} macro`) } return this.scoreToSet(result, CS); } } private strictCases(parent: NodeType, premises: string[], defeaters: string[], sets: FZ[], defaultFz: FZ): string[] { const cases: string[] = []; const setNums = this.setsToNumbers(sets); const combs = this.combinations(setNums, premises.length + defeaters.length); for (const C of combs) { // List of combinations is interpreted as: // - Indices 0..k-1 are premise-typed nodes // - Indices k .. N-1 are defeater-typed nodes const k = premises.length; const combPremises = C.slice(0, k); const combDefeaters = C.slice(k) const caseScore = this.scoreCase(parent, combPremises, combDefeaters); const outcome = this.scoreToSet(caseScore, sets); const caseFzSets = combPremises.concat(combDefeaters).map(n => this.scoreToSet(n, sets)); const theCase = this.buildCase(premises.concat(defeaters), caseFzSets, outcome, "and", "<="); cases.push(theCase); } cases.push(`otherwise ${defaultFz.name}`); return cases; } protected scoreCase(parent: NodeType, P: number[], D: number[]): number { if (NodeType.checkSubType(NodeType.PREMISE, parent)) { const pSum = P.reduce((S,x) => S+x, 0); if (P.length > 0) { return this.roundScore(pSum/(P.length)) } else { throw new EvaluationError(`Cannot score strict macro case for premise-typed parent without premise-typed children`); } } else if (NodeType.checkSubType(NodeType.DEFEATER, parent)) { const pSum = P.reduce((S,x) => S+x, 0); const dSum = D.reduce((S,x) => S+x, 0); return this.roundScore((dSum - pSum)/(P.length + D.length)); } } public expand(ctx: CertusContext, target?: string): string { const root: Node = ctx.getRoot(); const nodes = this.getNodesFromContext(ctx); const premises = nodes.filter(n => NodeType.checkSubType(NodeType.PREMISE, n.type)).map(x => x.id); const defeaters = nodes.filter(n => NodeType.checkSubType(NodeType.DEFEATER, n.type)).map(x => x.id); if (target) { if (premises.length == 0 && defeaters.length == 0) { return `${target} is ${ctx.getDefaultCanonical(root.type)}`; } if (NodeType.checkSubType(NodeType.PREMISE, root.type)) { if (premises.length == 0 && defeaters.length > 0) { const cases = defeaters.map(d => `${d} > reject -> reject`); cases.push(`otherwise uncert`); return `${target} is cases\n\t${cases.join(",\n\t")}`; } else if (premises.length > 0 && defeaters.length == 0) { const cases = this.strictCases(root.type, premises, [], ctx.getCanonicalSets(), ctx.getDefaultCanonical(root.type)); return `${target} is cases\n\t${cases.join(",\n\t")}` } else if (premises.length > 0 && defeaters.length > 0) { const cases = defeaters.map(d => `${d} > reject -> reject`); cases.push(...this.strictCases(root.type, premises, [], ctx.getCanonicalSets(), ctx.getDefaultCanonical(root.type))); return `${target} is cases\n\t${cases.join(",\n\t")}` } else { throw new EvaluationError(`${this.name} macro expansion fails for unknown alternative`); } } else if (NodeType.checkSubType(NodeType.DEFEATER, root.type)) { if (premises.length == 0 && defeaters.length > 0) { const cases: string[] = []; const dMap = defeaters.map(d => `${d} is reject`) cases.push(dMap.join(" and ") + " -> reject") cases.push(...this.strictCases(root.type, premises, defeaters, ctx.getCanonicalSets(), ctx.getDefaultCanonical(root.type))); return `${target} is cases\n\t${cases.join(",\n\t")}`; } else if (premises.length > 0 && defeaters.length == 0) { const cases = this.strictCases(root.type, premises, [], ctx.getCanonicalSets(), ctx.getDefaultCanonical(root.type)); return `${target} is cases\n\t${cases.join(",\n\t")}` } else if (premises.length > 0 && defeaters.length > 0) { const cases: string[] = []; const pMap = premises.map(c => `${c} is certain`) const dMap = defeaters.map(d => `${d} is reject`) cases.push((dMap.concat(pMap)).join(" and ") + " -> reject") cases.push(...this.strictCases(root.type, premises, defeaters, ctx.getCanonicalSets(), ctx.getDefaultCanonical(root.type))); return `${target} is cases\n\t${cases.join(",\n\t")}` } else { throw new EvaluationError(`${this.name} macro expansion fails for unknown alternative`); } } } else { throw new EvaluationError(`${this.name} macro expansion requires an assignment target, none provided.`) } } }