furtif_core/traits/fusers/discounted.rs
1// This program is free software: you can redistribute it and/or modify
2// it under the terms of the Lesser GNU General Public License as published
3// by the Free Software Foundation, either version 3 of the License, or
4// (at your option) any later version.
5
6// This program is distributed in the hope that it will be useful,
7// but WITHOUT ANY WARRANTY; without even the implied warranty of
8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9// Lesser GNU General Public License for more details.
10
11// You should have received a copy of the Lesser GNU General Public License
12// along with this program. If not, see <https://www.gnu.org/licenses/>.
13
14// Copyright 2024 Frederic Dambreville, Jean Dezert Developers.
15
16
17use std::{ hash::Hash, iter::once, ops::RangeInclusive, };
18
19#[cfg(feature = "silx-types")] use silx_types::Float;
20// #[cfg(feature = "silx-types")]use silx_types::{ f64slx, IntoSlx, Float, };
21// #[cfg(not(feature = "silx-types"))] use crate::fake_slx::{f64slx, FakeSlx};
22use crate::{
23 types::{ f64slx, IntoSlx, },
24 structs::{ Assignment, SafeArray, one_f64slx, zero_f64slx, },
25 traits::{ Lattice, Referee, CollectionFamily1, },
26};
27
28/// For intern use: produce tensor product combination of the bbas
29/// * `lattice: &'a L` : lattice of definition of the assignments
30/// * `bbas: &'a[&'a Assignment<L::Item>]` : collection of assignments
31/// * `L` : type of lattice
32/// * Output: tensor product combination of the bbas or an error
33fn product_bba<'a,L>(lattice: &'a L, bbas: &'a[&'a Assignment<L::Item>]) -> Result<Vec<(SafeArray<'a,L::Item>,f64slx)>, String>
34 where L: Lattice, L::Item: Eq + Ord + Hash, {
35 let lattice_hash = lattice.lattice_hash();
36 // Compatibility tests:
37 for (u,bba) in bbas.into_iter().enumerate() {
38 if bba.lattice_hash != lattice_hash { return Err(format!("bbas of index {u} is not defined over lattice")); }
39 }
40 let mut products = vec![(Vec::<&L::Item>::with_capacity(bbas.len()),1.0f64.slx())];
41 for bba in bbas {
42 products = products.iter().flat_map(|(left,left_w)| {
43 bba.elements.iter().map(move |(right,right_w)|
44 (left.iter().map(|l|*l).chain(once(right)).collect(), *left_w * *right_w)
45 )
46 }).collect::<Vec<_>>();
47 }
48 Ok(products.into_iter().map(|(product,weight)|
49 (SafeArray{ lattice_hash, product,}, weight)
50 ).collect())
51}
52
53
54/// Trait defining generic discounted fusion processes
55/// * Smallest assignments are reduced until assignment cardinal is below given range
56pub trait DiscountedFusion {
57 /// Range defining an hysteresis for assignment reduction
58 /// * Principle:
59 /// 1) Reduction is started when above range max
60 /// 2) Reduction is done until below or equal to range min
61 /// * Reduction strategy is defined by means of `AssignmentBuilder` mechanisms
62 fn size_range(&self) -> RangeInclusive<usize>;
63
64 /// Fusing bbas returning fused assignment and conflict
65 /// * `lattice: &L` : lattice of definition of the assignments
66 /// * `referee: &F` : referee function
67 /// * `bbas: &[&Assignment<L::Item>]` : assignments sequence
68 /// * `L` : type of the lattice
69 /// * `F` : type of the referee function
70 /// * Output: an error or a pair composed of:
71 /// * the fused assignment
72 /// * the conflict
73 fn fuse<L,F>(&self, lattice: &L, referee: &F, bbas: &[&Assignment<L::Item>])
74 -> Result<(Assignment<L::Item>,f64slx),String> where L: Lattice, L::Item: Eq + Ord + Hash, F: Referee {
75 let (length_mid, length_max) = {
76 let range = self.size_range();
77 (*range.start() as u32,*range.end() as u32)
78 };
79 let mut bba = lattice.prunable(length_mid, length_max);
80 let products = product_bba(lattice,bbas)?;
81 for (conditions,weight) in products {
82 let output = referee.from_conditions(lattice, bbas, conditions)?;
83 // optimizable ==>
84 for (safe_element, sub_weight) in output {
85 bba.push(safe_element, sub_weight * weight)?;
86 } // <==
87 }
88 bba.prune(|x,y| unsafe{ lattice.unsafe_meet(&x, &y) });
89 let norm = bba.cumul_weight()?;
90 let z = *one_f64slx() - norm;
91 if &norm == zero_f64slx() {
92 Err("Cumulative weight is zero, cannot be normalized".to_string())
93 } else {
94 bba.scale(norm.recip())?;
95 Ok((bba.into(),z))
96 }
97 }
98
99 /// fusing bbas sequentially returning collected fused assignments and conflicts
100 /// * `lattice: &L` : lattice of definition of the assignments
101 /// * `referees: &[&F]` : collection of referee functions
102 /// * `slice_bbas: &[&[&Assignment<L::Item>]]` : collection of assignments sequence
103 /// * `L` : type of the lattice
104 /// * `F` : type of the referee function
105 /// * `I` : type of the returned collection
106 /// * Output: an error or a collection of pairs composed of:
107 /// * a fused assignment
108 /// * a conflict
109 fn fuse_seq<L,F,I>(&self, lattice: &L, referees: &[&F], slice_bbas: &[&[&Assignment<L::Item>]])
110 -> Result<I::Type<(Assignment<L::Item>,f64slx)>,String>
111 where L: Lattice, L::Item: Eq + Ord + Hash, F: Referee, I: CollectionFamily1 {
112 let (referees_len, seq_bbas_len) = (referees.len(), slice_bbas.len());
113 if referees_len != seq_bbas_len {
114 return Err(format!("mismatching lengths {} vs {}", referees_len, seq_bbas_len));
115 }
116 let mut results = Vec::with_capacity(referees_len);
117 for (referee,bbas) in referees.into_iter().zip(slice_bbas) {
118 results.push(self.fuse(lattice,*referee, *bbas)?);
119 }
120 Ok(results.into_iter().collect())
121 }
122}