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}