171 lines
4.0 KiB
JavaScript
171 lines
4.0 KiB
JavaScript
|
|
import { BaseNode } from './BaseNode.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* AST node for fusion statements
|
||
|
|
* Represents: fusion max { ... }
|
||
|
|
*/
|
||
|
|
export class FusionNode extends BaseNode {
|
||
|
|
constructor(strategy, location = null) {
|
||
|
|
super('Fusion', location);
|
||
|
|
this.strategy = strategy; // 'max', 'min', 'majority', 'average', etc.
|
||
|
|
this.evidence = []; // Array of evidence statements
|
||
|
|
this.weights = null; // Optional weights array
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add evidence to this fusion
|
||
|
|
* @param {BaseNode} evidence - Evidence to add
|
||
|
|
*/
|
||
|
|
addEvidence(evidence) {
|
||
|
|
this.evidence.push(evidence);
|
||
|
|
this.addChild(evidence);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set weights for this fusion
|
||
|
|
* @param {number[]} weights - Weights array
|
||
|
|
*/
|
||
|
|
setWeights(weights) {
|
||
|
|
this.weights = weights;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the fusion strategy
|
||
|
|
* @returns {string} Fusion strategy
|
||
|
|
*/
|
||
|
|
getStrategy() {
|
||
|
|
return this.strategy;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all evidence statements
|
||
|
|
* @returns {BaseNode[]} Evidence statements
|
||
|
|
*/
|
||
|
|
getEvidence() {
|
||
|
|
return this.evidence;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the weights for this fusion
|
||
|
|
* @returns {number[]|null} Weights or null
|
||
|
|
*/
|
||
|
|
getWeights() {
|
||
|
|
return this.weights;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this fusion has weights
|
||
|
|
* @returns {boolean} True if has weights
|
||
|
|
*/
|
||
|
|
hasWeights() {
|
||
|
|
return this.weights !== null && this.weights.length > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this is a max fusion
|
||
|
|
* @returns {boolean} True if max fusion
|
||
|
|
*/
|
||
|
|
isMax() {
|
||
|
|
return this.strategy === 'max';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this is a min fusion
|
||
|
|
* @returns {boolean} True if min fusion
|
||
|
|
*/
|
||
|
|
isMin() {
|
||
|
|
return this.strategy === 'min';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this is a majority fusion
|
||
|
|
* @returns {boolean} True if majority fusion
|
||
|
|
*/
|
||
|
|
isMajority() {
|
||
|
|
return this.strategy === 'majority';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if this is an average fusion
|
||
|
|
* @returns {boolean} True if average fusion
|
||
|
|
*/
|
||
|
|
isAverage() {
|
||
|
|
return this.strategy === 'average';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the number of evidence statements
|
||
|
|
* @returns {number} Number of evidence statements
|
||
|
|
*/
|
||
|
|
getEvidenceCount() {
|
||
|
|
return this.evidence.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Validate the fusion
|
||
|
|
* @returns {string[]} Array of error messages
|
||
|
|
*/
|
||
|
|
validate() {
|
||
|
|
const errors = [];
|
||
|
|
|
||
|
|
// Validate strategy
|
||
|
|
const validStrategies = [
|
||
|
|
'max',
|
||
|
|
'min',
|
||
|
|
'majority',
|
||
|
|
'average',
|
||
|
|
'sum',
|
||
|
|
'sum_unbounded',
|
||
|
|
'median',
|
||
|
|
'optimistic',
|
||
|
|
'pessimistic',
|
||
|
|
'top2',
|
||
|
|
'top3',
|
||
|
|
'priority',
|
||
|
|
'custom',
|
||
|
|
'count'
|
||
|
|
];
|
||
|
|
if (!validStrategies.includes(this.strategy)) {
|
||
|
|
errors.push(`Invalid fusion strategy: ${this.strategy}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate evidence
|
||
|
|
if (this.evidence.length === 0) {
|
||
|
|
errors.push('Fusion must have at least one evidence statement');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate each evidence statement
|
||
|
|
this.evidence.forEach((ev, index) => {
|
||
|
|
const evErrors = ev.validate ? ev.validate() : [];
|
||
|
|
errors.push(...evErrors.map(err => `Evidence ${index + 1}: ${err}`));
|
||
|
|
});
|
||
|
|
|
||
|
|
// Validate weights
|
||
|
|
if (this.weights !== null) {
|
||
|
|
if (!Array.isArray(this.weights)) {
|
||
|
|
errors.push('Weights must be an array');
|
||
|
|
} else if (this.weights.length !== this.evidence.length) {
|
||
|
|
errors.push('Weights array length must match evidence count');
|
||
|
|
} else if (this.weights.some(w => typeof w !== 'number' || w < 0)) {
|
||
|
|
errors.push('All weights must be non-negative numbers');
|
||
|
|
} else if (this.strategy === 'custom') {
|
||
|
|
const total = this.weights.reduce((sum, w) => sum + w, 0);
|
||
|
|
if (Math.abs(total - 1.0) > 1e-6) {
|
||
|
|
errors.push('Custom weights must sum to 1.0');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.strategy === 'custom' && (!this.weights || this.weights.length === 0)) {
|
||
|
|
errors.push('Custom fusion requires weights');
|
||
|
|
}
|
||
|
|
|
||
|
|
return errors;
|
||
|
|
}
|
||
|
|
|
||
|
|
toString() {
|
||
|
|
const weightsStr = this.hasWeights() ? ` weights[${this.weights.length}]` : '';
|
||
|
|
return `Fusion(${this.strategy}, ${this.evidence.length} evidence${weightsStr})`;
|
||
|
|
}
|
||
|
|
}
|