///|
pub(all) enum Rule {
Conjunctive
Dempster
Disjunctive
Yager
DuboisPrade
PairwisePCR5
} derive(Debug, Eq)
///|
pub extend Rule with @debug.Debug::{to_repr}
///|
pub extend Rule with Eq::{equal, not_equal}
///|
pub fn fuse(
a : Mass,
b : Mass,
rule : Rule,
budget~ : Limits = default_limits(),
) -> Mass raise BeliefError {
match rule {
Conjunctive => conjunctive(a, b, budget~)
Dempster => dempster(a, b, budget~)
Disjunctive => disjunctive(a, b, budget~)
Yager => yager(a, b, budget~)
DuboisPrade => dubois_prade(a, b, budget~)
PairwisePCR5 => pcr5(a, b, budget~)
}
}
///|
pub struct Source {
priv name : String
priv evidence : Mass
priv reliability : Double
} derive(Debug, Eq)
///|
pub extend Source with @debug.Debug::{to_repr}
///|
pub extend Source with Eq::{equal, not_equal}
///|
pub fn source(
name : String,
evidence : Mass,
reliability~ : Double = 1.0,
) -> Source raise BeliefError {
if name.length() == 0 || name.length() > 256 {
raise Invalid("source name must have 1..256 characters")
}
if !finite(reliability) || reliability < 0.0 || reliability > 1.0 {
raise Invalid("source reliability must be finite and in [0,1]")
}
{ name, evidence, reliability, }
}
///|
pub fn Source::name(self : Source) -> String {
self.name
}
///|
pub fn Source::mass(self : Source) -> Mass {
self.evidence
}
///|
pub struct FusionStep {
source : String
input_conflict : Double
previous_conflict : Double
conjunctive_conflict : Double
output_conflict : Double
output_ignorance : Double
output_focals : Int32
} derive(Debug, Eq)
///|
pub extend FusionStep with @debug.Debug::{to_repr}
///|
pub extend FusionStep with Eq::{equal, not_equal}
///|
pub struct PipelineResult {
priv evidence : Mass
priv trace : Array[FusionStep]
priv pair_work : Int32
} derive(Debug, Eq)
///|
pub extend PipelineResult with @debug.Debug::{to_repr}
///|
pub extend PipelineResult with Eq::{equal, not_equal}
///|
pub fn PipelineResult::mass(self : PipelineResult) -> Mass {
self.evidence
}
///|
pub fn PipelineResult::steps(self : PipelineResult) -> Array[FusionStep] {
self.trace.copy()
}
///|
pub fn PipelineResult::pair_work(self : PipelineResult) -> Int32 {
self.pair_work
}
///|
fn validate_sources(sources : Array[Source]) -> Unit raise BeliefError {
if sources.length() == 0 || sources.length() > 32 {
raise Invalid("pipeline requires 1..32 sources")
}
for i = 0; i < sources.length(); i = i + 1 {
same_frame(sources[0].evidence, sources[i].evidence)
for j = 0; j < i; j = j + 1 {
if sources[i].name == sources[j].name {
raise Invalid("pipeline source names must be unique")
}
}
}
}
///|
/// Deterministic left fold in caller order. Not a generalized multisource
/// PCR5 or associativity guarantee. One source yields its discounted mass.
/// Pair budget covers BOTH diagnostic and fusion cross products globally.
pub fn pipeline(
sources : Array[Source],
rule : Rule,
budget~ : Limits = default_limits(),
) -> PipelineResult raise BeliefError {
validate_sources(sources)
let mut current = sources[0].evidence.discount(
sources[0].reliability,
budget~,
)
let trace : Array[FusionStep] = []
let mut work = 0
for i = 1; i < sources.length(); i = i + 1 {
let incoming = sources[i].evidence.discount(sources[i].reliability, budget~)
let pairs = current.focal_count() * incoming.focal_count()
if pairs > (budget.pairs - work) / 2 {
raise Budget(
"pipeline aggregate diagnostic and fusion pair budget exceeded",
)
}
work = work + pairs * 2
let raw = conjunctive(current, incoming, budget~)
let next = fuse(current, incoming, rule, budget~)
trace.push({
source: sources[i].name,
input_conflict: incoming.conflict(),
previous_conflict: current.conflict(),
conjunctive_conflict: raw.conflict(),
output_conflict: next.conflict(),
output_ignorance: next.ignorance(),
output_focals: next.focal_count(),
})
current = next
}
{ evidence: current, trace, pair_work: work, }
}