use super::functions::*;
pub struct FuzzyInferenceSystem {
pub fis_type: FISType,
pub n_inputs: usize,
pub output_size: usize,
pub output_domain: Vec<f64>,
pub defuzz_method: DefuzzMethod,
pub mamdani_rules: Vec<MamdaniRule>,
pub sugeno_rules: Vec<SugenoRule>,
}
impl FuzzyInferenceSystem {
pub fn mamdani(output_size: usize, output_domain: Vec<f64>) -> Self {
FuzzyInferenceSystem {
fis_type: FISType::Mamdani,
n_inputs: 0,
output_size,
output_domain,
defuzz_method: DefuzzMethod::CentroidOfArea,
mamdani_rules: Vec::new(),
sugeno_rules: Vec::new(),
}
}
pub fn sugeno(n_inputs: usize) -> Self {
FuzzyInferenceSystem {
fis_type: FISType::Sugeno,
n_inputs,
output_size: 0,
output_domain: Vec::new(),
defuzz_method: DefuzzMethod::CentroidOfArea,
mamdani_rules: Vec::new(),
sugeno_rules: Vec::new(),
}
}
pub fn with_defuzz(mut self, method: DefuzzMethod) -> Self {
self.defuzz_method = method;
self
}
pub fn add_mamdani_rule(&mut self, antecedent_mf: Vec<f64>, consequent: FuzzySet) {
self.mamdani_rules.push(MamdaniRule {
antecedent_mf,
consequent,
});
}
pub fn add_sugeno_rule(
&mut self,
antecedent_mf: Vec<f64>,
output_coeffs: Vec<f64>,
output_const: f64,
) {
self.sugeno_rules.push(SugenoRule {
antecedent_mf,
output_coeffs,
output_const,
});
}
pub fn infer_mamdani(&self, input_degrees: &[Vec<f64>]) -> f64 {
let sys = MamdaniSystem {
rules: self.mamdani_rules.clone(),
output_size: self.output_size,
};
let out_fuzzy = sys.infer(input_degrees);
defuzzify(&out_fuzzy, &self.output_domain, self.defuzz_method)
}
pub fn infer_sugeno(&self, inputs: &[f64], input_degrees: &[Vec<f64>]) -> f64 {
let sys = SugenoSystem {
rules: self.sugeno_rules.clone(),
};
sys.infer(inputs, input_degrees)
}
pub fn is_configured(&self) -> bool {
match self.fis_type {
FISType::Mamdani => !self.mamdani_rules.is_empty(),
FISType::Sugeno => !self.sugeno_rules.is_empty(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TConorm {
Maximum,
ProbabilisticSum,
BoundedSum,
Drastic,
}
impl TConorm {
pub fn eval(self, a: f64, b: f64) -> f64 {
match self {
TConorm::Maximum => a.max(b),
TConorm::ProbabilisticSum => a + b - a * b,
TConorm::BoundedSum => (a + b).min(1.0),
TConorm::Drastic => {
if a < 1e-9 {
b
} else if b < 1e-9 {
a
} else {
1.0
}
}
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FuzzyClustering {
pub n_samples: usize,
pub n_clusters: usize,
pub fuzziness: f64,
pub membership: Vec<Vec<f64>>,
}
#[allow(dead_code)]
impl FuzzyClustering {
pub fn new(n_samples: usize, n_clusters: usize, fuzziness: f64) -> Self {
let membership = vec![vec![1.0 / n_clusters as f64; n_samples]; n_clusters];
FuzzyClustering {
n_samples,
n_clusters,
fuzziness,
membership,
}
}
pub fn get_membership(&self, cluster: usize, sample: usize) -> f64 {
self.membership[cluster][sample]
}
pub fn hard_assignment(&self, sample: usize) -> usize {
(0..self.n_clusters)
.max_by(|&a, &b| {
self.membership[a][sample]
.partial_cmp(&self.membership[b][sample])
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(0)
}
pub fn partition_coefficient(&self) -> f64 {
let mut sum = 0.0;
for c in 0..self.n_clusters {
for s in 0..self.n_samples {
sum += self.membership[c][s].powi(2);
}
}
sum / self.n_samples as f64
}
pub fn set_membership(&mut self, cluster: usize, sample: usize, val: f64) {
self.membership[cluster][sample] = val.clamp(0.0, 1.0);
}
}
pub struct FuzzyCMeans {
pub c: usize,
pub m: f64,
pub max_iter: usize,
pub tol: f64,
}
impl FuzzyCMeans {
pub fn new(c: usize) -> Self {
FuzzyCMeans {
c,
m: 2.0,
max_iter: 100,
tol: 1e-6,
}
}
pub fn with_m(mut self, m: f64) -> Self {
self.m = m;
self
}
pub fn with_max_iter(mut self, max_iter: usize) -> Self {
self.max_iter = max_iter;
self
}
fn dist(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
pub fn fit(&self, data: &[Vec<f64>]) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
let n = data.len();
let dim = if n > 0 { data[0].len() } else { 1 };
if n == 0 || self.c == 0 {
return (Vec::new(), Vec::new());
}
let mut u: Vec<Vec<f64>> = (0..n)
.map(|i| {
let mut row: Vec<f64> = (0..self.c)
.map(|k| {
let base = 1.0 / self.c as f64;
let delta = 0.1 * ((i + k) % 3) as f64 / 3.0 - 0.05;
(base + delta).clamp(0.01, 0.99)
})
.collect();
let s: f64 = row.iter().sum();
for v in &mut row {
*v /= s;
}
row
})
.collect();
let mut centers: Vec<Vec<f64>> = vec![vec![0.0; dim]; self.c];
for _iter in 0..self.max_iter {
let mut old_centers = centers.clone();
for k in 0..self.c {
let mut num = vec![0.0_f64; dim];
let mut denom = 0.0_f64;
for i in 0..n {
let w = u[i][k].powf(self.m);
denom += w;
for d in 0..dim {
num[d] += w * data[i][d];
}
}
if denom.abs() > 1e-15 {
for d in 0..dim {
centers[k][d] = num[d] / denom;
}
}
}
for i in 0..n {
let dists: Vec<f64> = (0..self.c)
.map(|k| Self::dist(&data[i], ¢ers[k]).max(1e-15))
.collect();
let zero_k: Vec<usize> = (0..self.c).filter(|&k| dists[k] < 1e-12).collect();
if !zero_k.is_empty() {
for k in 0..self.c {
u[i][k] = 0.0;
}
let share = 1.0 / zero_k.len() as f64;
for &k in &zero_k {
u[i][k] = share;
}
} else {
let exp = 2.0 / (self.m - 1.0);
for k in 0..self.c {
let sum: f64 = (0..self.c).map(|j| (dists[k] / dists[j]).powf(exp)).sum();
u[i][k] = 1.0 / sum;
}
}
}
let movement: f64 = old_centers
.iter_mut()
.zip(centers.iter())
.map(|(oc, nc)| Self::dist(oc, nc))
.sum();
if movement < self.tol {
break;
}
}
(u, centers)
}
pub fn partition_coefficient(membership: &[Vec<f64>]) -> f64 {
let n = membership.len();
if n == 0 {
return 0.0;
}
let sum: f64 = membership
.iter()
.flat_map(|row| row.iter())
.map(|&u| u * u)
.sum();
sum / n as f64
}
pub fn classification_entropy(membership: &[Vec<f64>]) -> f64 {
let n = membership.len();
if n == 0 {
return 0.0;
}
let sum: f64 = membership
.iter()
.flat_map(|row| row.iter())
.map(|&u| if u > 1e-15 { -u * u.ln() } else { 0.0 })
.sum();
sum / n as f64
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MamdaniEngine {
pub input_names: Vec<String>,
pub output_name: String,
pub n_rules: usize,
pub defuzz_method: DefuzzMethod,
}
#[allow(dead_code)]
impl MamdaniEngine {
pub fn new(inputs: Vec<&str>, output: &str, n_rules: usize) -> Self {
MamdaniEngine {
input_names: inputs.iter().map(|s| s.to_string()).collect(),
output_name: output.to_string(),
n_rules,
defuzz_method: DefuzzMethod::CentroidOfArea,
}
}
pub fn set_defuzz(&mut self, method: DefuzzMethod) {
self.defuzz_method = method;
}
pub fn n_inputs(&self) -> usize {
self.input_names.len()
}
pub fn centroid_defuzz(values: &[f64], memberships: &[f64]) -> f64 {
let num: f64 = values
.iter()
.zip(memberships.iter())
.map(|(v, m)| v * m)
.sum();
let den: f64 = memberships.iter().sum();
if den.abs() < 1e-12 {
0.0
} else {
num / den
}
}
}
#[derive(Debug, Clone)]
pub struct SugenoRule {
pub antecedent_mf: Vec<f64>,
pub output_coeffs: Vec<f64>,
pub output_const: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ManyValuedLogic {
Lukasiewicz,
Godel,
Product,
}
impl ManyValuedLogic {
pub fn conj(self, a: f64, b: f64) -> f64 {
match self {
ManyValuedLogic::Lukasiewicz => (a + b - 1.0).max(0.0),
ManyValuedLogic::Godel => a.min(b),
ManyValuedLogic::Product => a * b,
}
}
pub fn disj(self, a: f64, b: f64) -> f64 {
match self {
ManyValuedLogic::Lukasiewicz => (a + b).min(1.0),
ManyValuedLogic::Godel => a.max(b),
ManyValuedLogic::Product => a + b - a * b,
}
}
pub fn residuum(self, a: f64, b: f64) -> f64 {
match self {
ManyValuedLogic::Lukasiewicz => (1.0 - a + b).min(1.0),
ManyValuedLogic::Godel => {
if a <= b {
1.0
} else {
b
}
}
ManyValuedLogic::Product => {
if a <= b {
1.0
} else {
b / a
}
}
}
}
pub fn neg(self, a: f64) -> f64 {
self.residuum(a, 0.0)
}
pub fn iff(self, a: f64, b: f64) -> f64 {
self.conj(self.residuum(a, b), self.residuum(b, a))
}
}
#[derive(Debug, Clone)]
pub struct FiniteMTLAlgebra {
pub size: usize,
pub tnorm: Vec<Vec<usize>>,
pub residuum: Vec<Vec<usize>>,
}
impl FiniteMTLAlgebra {
pub fn from_tnorm(size: usize, tnorm: Vec<Vec<usize>>) -> Self {
let mut residuum = vec![vec![0usize; size]; size];
for a in 0..size {
for b in 0..size {
let mut best = 0usize;
for k in 0..size {
if tnorm[k][a] <= b {
best = best.max(k);
}
}
residuum[a][b] = best;
}
}
FiniteMTLAlgebra {
size,
tnorm,
residuum,
}
}
pub fn t(&self, a: usize, b: usize) -> usize {
self.tnorm[a][b]
}
pub fn r(&self, a: usize, b: usize) -> usize {
self.residuum[a][b]
}
pub fn neg(&self, a: usize) -> usize {
self.r(a, 0)
}
pub fn satisfies_prelinearity(&self) -> bool {
let top = self.size - 1;
for a in 0..self.size {
for b in 0..self.size {
let imp_ab = self.r(a, b);
let imp_ba = self.r(b, a);
let join = imp_ab.max(imp_ba);
if join != top {
return false;
}
}
}
true
}
pub fn satisfies_divisibility(&self) -> bool {
for a in 0..self.size {
for b in 0..self.size {
let meet = a.min(b);
let product = self.t(a, self.r(a, b));
if meet != product {
return false;
}
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct MamdaniRule {
pub antecedent_mf: Vec<f64>,
pub consequent: FuzzySet,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FISType {
Mamdani,
Sugeno,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DefuzzMethod {
CentroidOfArea,
BisectorOfArea,
MeanOfMaxima,
SmallestOfMaxima,
LargestOfMaxima,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TNorm {
Minimum,
Product,
Lukasiewicz,
Drastic,
}
impl TNorm {
pub fn eval(self, a: f64, b: f64) -> f64 {
match self {
TNorm::Minimum => a.min(b),
TNorm::Product => a * b,
TNorm::Lukasiewicz => (a + b - 1.0).max(0.0),
TNorm::Drastic => {
if (a - 1.0).abs() < 1e-9 {
b
} else if (b - 1.0).abs() < 1e-9 {
a
} else {
0.0
}
}
}
}
pub fn is_commutative_sample(&self, a: f64, b: f64) -> bool {
(self.eval(a, b) - self.eval(b, a)).abs() < 1e-9
}
pub fn is_associative_sample(&self, a: f64, b: f64, c: f64) -> bool {
(self.eval(self.eval(a, b), c) - self.eval(a, self.eval(b, c))).abs() < 1e-9
}
}
#[derive(Debug, Clone)]
pub struct FuzzySet {
pub universe_size: usize,
pub membership: Vec<f64>,
}
impl FuzzySet {
pub fn new(universe_size: usize) -> Self {
FuzzySet {
universe_size,
membership: vec![0.0; universe_size],
}
}
pub fn set(&mut self, i: usize, degree: f64) {
self.membership[i] = degree.clamp(0.0, 1.0);
}
pub fn get(&self, i: usize) -> f64 {
self.membership[i]
}
pub fn alpha_cut(&self, alpha: f64) -> Vec<usize> {
self.membership
.iter()
.enumerate()
.filter(|(_, &d)| d >= alpha)
.map(|(i, _)| i)
.collect()
}
pub fn strong_alpha_cut(&self, alpha: f64) -> Vec<usize> {
self.membership
.iter()
.enumerate()
.filter(|(_, &d)| d > alpha)
.map(|(i, _)| i)
.collect()
}
pub fn complement(&self) -> FuzzySet {
let membership = self.membership.iter().map(|&d| 1.0 - d).collect();
FuzzySet {
universe_size: self.universe_size,
membership,
}
}
pub fn height(&self) -> f64 {
self.membership.iter().cloned().fold(0.0_f64, f64::max)
}
pub fn support(&self) -> Vec<usize> {
self.strong_alpha_cut(0.0)
}
pub fn core(&self) -> Vec<usize> {
self.membership
.iter()
.enumerate()
.filter(|(_, &d)| (d - 1.0).abs() < 1e-9)
.map(|(i, _)| i)
.collect()
}
pub fn is_normal(&self) -> bool {
(self.height() - 1.0).abs() < 1e-9
}
}
#[derive(Debug, Clone)]
pub struct FuzzyTopology {
pub universe_size: usize,
pub open_sets: Vec<Vec<f64>>,
}
impl FuzzyTopology {
pub fn new(universe_size: usize) -> Self {
let mut ft = FuzzyTopology {
universe_size,
open_sets: Vec::new(),
};
ft.open_sets.push(vec![0.0; universe_size]);
ft.open_sets.push(vec![1.0; universe_size]);
ft
}
pub fn add_open_set(&mut self, set: Vec<f64>) {
assert_eq!(set.len(), self.universe_size);
self.open_sets.push(set);
}
pub fn closed_under_intersection(&self) -> bool {
let n = self.open_sets.len();
for i in 0..n {
for j in i..n {
let inter: Vec<f64> = self.open_sets[i]
.iter()
.zip(self.open_sets[j].iter())
.map(|(&a, &b)| a.min(b))
.collect();
if !self.contains_set(&inter) {
return false;
}
}
}
true
}
pub fn closed_under_union(&self) -> bool {
let n = self.open_sets.len();
for i in 0..n {
for j in i..n {
let union: Vec<f64> = self.open_sets[i]
.iter()
.zip(self.open_sets[j].iter())
.map(|(&a, &b)| a.max(b))
.collect();
if !self.contains_set(&union) {
return false;
}
}
}
true
}
fn contains_set(&self, s: &[f64]) -> bool {
self.open_sets
.iter()
.any(|o| o.iter().zip(s.iter()).all(|(&a, &b)| (a - b).abs() < 1e-9))
}
}
pub struct TNormComputer;
impl TNormComputer {
pub fn frank(s: f64, a: f64, b: f64) -> f64 {
if s <= 0.0 {
return a.min(b);
}
if (s - 1.0).abs() < 1e-9 {
return a * b;
}
if s > 1e9 {
return a.min(b);
}
let sa = s.powf(a) - 1.0;
let sb = s.powf(b) - 1.0;
let denom = s - 1.0;
if denom.abs() < 1e-15 {
return a.min(b);
}
let inner = 1.0 + sa * sb / denom;
if inner <= 0.0 {
return 0.0;
}
inner.log(s).clamp(0.0, 1.0)
}
pub fn yager(p: f64, a: f64, b: f64) -> f64 {
if p <= 0.0 {
return a.min(b);
}
let sum = (1.0 - a).powf(p) + (1.0 - b).powf(p);
(1.0 - sum.powf(1.0 / p)).max(0.0).min(1.0)
}
pub fn schweizer_sklar(p: f64, a: f64, b: f64) -> f64 {
if p == 0.0 {
return a * b;
}
if p < 0.0 {
let val = (a.powf(p) + b.powf(p) - 1.0).powf(1.0 / p);
return val.max(0.0).min(1.0);
}
let val = (a.powf(p) + b.powf(p) - 1.0).powf(1.0 / p);
val.max(0.0).min(1.0)
}
pub fn check_commutativity<F: Fn(f64, f64) -> f64>(t: &F, samples: &[(f64, f64)]) -> bool {
samples
.iter()
.all(|&(a, b)| (t(a, b) - t(b, a)).abs() < 1e-9)
}
pub fn check_associativity<F: Fn(f64, f64) -> f64>(t: &F, triples: &[(f64, f64, f64)]) -> bool {
triples
.iter()
.all(|&(a, b, c)| (t(t(a, b), c) - t(a, t(b, c))).abs() < 1e-9)
}
pub fn check_boundary<F: Fn(f64, f64) -> f64>(t: &F, samples: &[f64]) -> bool {
samples.iter().all(|&a| (t(a, 1.0) - a).abs() < 1e-9)
}
pub fn check_monotonicity<F: Fn(f64, f64) -> f64>(t: &F, samples: &[(f64, f64, f64)]) -> bool {
samples
.iter()
.all(|&(a, b, c)| a > b || t(a, c) <= t(b, c) + 1e-9)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GradualElement {
pub name: String,
pub degree: f64,
}
#[allow(dead_code)]
impl GradualElement {
pub fn new(name: &str, degree: f64) -> Self {
GradualElement {
name: name.to_string(),
degree: degree.clamp(0.0, 1.0),
}
}
pub fn is_true(&self) -> bool {
self.degree > 0.5
}
pub fn complement(&self) -> Self {
GradualElement::new(&format!("not_{}", self.name), 1.0 - self.degree)
}
pub fn conjunction(&self, other: &GradualElement) -> GradualElement {
let deg = self.degree.min(other.degree);
GradualElement::new(&format!("({} AND {})", self.name, other.name), deg)
}
pub fn disjunction(&self, other: &GradualElement) -> GradualElement {
let deg = self.degree.max(other.degree);
GradualElement::new(&format!("({} OR {})", self.name, other.name), deg)
}
}
#[derive(Debug, Clone)]
pub struct FuzzyMetricSpace {
pub points: usize,
pub metric: Vec<Vec<Vec<f64>>>,
pub t_grid: Vec<f64>,
}
impl FuzzyMetricSpace {
pub fn new(points: usize, t_grid: Vec<f64>) -> Self {
let nt = t_grid.len();
FuzzyMetricSpace {
points,
metric: vec![vec![vec![0.0; nt]; points]; points],
t_grid,
}
}
pub fn set_metric(&mut self, x: usize, y: usize, t_idx: usize, value: f64) {
self.metric[x][y][t_idx] = value.clamp(0.0, 1.0);
self.metric[y][x][t_idx] = value.clamp(0.0, 1.0);
}
pub fn check_limit_axiom(&self) -> bool {
let last = self.t_grid.len() - 1;
for x in 0..self.points {
for y in 0..self.points {
if (self.metric[x][y][last] - 1.0).abs() > 1e-6 {
return false;
}
}
}
true
}
pub fn check_diagonal_axiom(&self) -> bool {
for x in 0..self.points {
for t_idx in 0..self.t_grid.len() {
if (self.metric[x][x][t_idx] - 1.0).abs() > 1e-6 {
return false;
}
}
}
true
}
pub fn check_non_separability(&self) -> bool {
for x in 0..self.points {
for y in 0..self.points {
if x == y {
continue;
}
let all_one = self.metric[x][y].iter().all(|&v| (v - 1.0).abs() < 1e-6);
if all_one {
return false;
}
}
}
true
}
}
pub struct LinguisticHedgeApplier;
impl LinguisticHedgeApplier {
pub fn very(degree: f64) -> f64 {
degree * degree
}
pub fn more_or_less(degree: f64) -> f64 {
degree.sqrt()
}
pub fn somewhat(degree: f64) -> f64 {
degree.powf(1.0 / 3.0)
}
pub fn extremely(degree: f64) -> f64 {
degree.powi(3)
}
pub fn not(degree: f64) -> f64 {
1.0 - degree
}
pub fn slightly(degree: f64) -> f64 {
degree.powf(1.7)
}
pub fn indeed(degree: f64) -> f64 {
if degree <= 0.5 {
2.0 * degree * degree
} else {
1.0 - 2.0 * (1.0 - degree).powi(2)
}
}
pub fn plus(degree: f64) -> f64 {
degree.powf(1.25)
}
pub fn apply(hedge: &str, degree: f64) -> f64 {
match hedge {
"very" => Self::very(degree),
"more_or_less" | "more-or-less" | "sort_of" => Self::more_or_less(degree),
"somewhat" => Self::somewhat(degree),
"extremely" => Self::extremely(degree),
"not" => Self::not(degree),
"slightly" => Self::slightly(degree),
"indeed" => Self::indeed(degree),
"plus" => Self::plus(degree),
_ => degree,
}
}
pub fn apply_chain(hedges: &[&str], degree: f64) -> f64 {
hedges.iter().fold(degree, |d, h| Self::apply(h, d))
}
pub fn apply_to_set(hedge: &str, set: &FuzzySet) -> FuzzySet {
let membership = set
.membership
.iter()
.map(|&d| Self::apply(hedge, d))
.collect();
FuzzySet {
universe_size: set.universe_size,
membership,
}
}
pub fn exponent(hedge: &str) -> Option<f64> {
match hedge {
"very" => Some(2.0),
"more_or_less" => Some(0.5),
"somewhat" => Some(1.0 / 3.0),
"extremely" => Some(3.0),
"slightly" => Some(1.7),
"plus" => Some(1.25),
_ => None,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FuzzyRoughApprox {
pub universe_size: usize,
pub similarity: Vec<Vec<f64>>,
}
#[allow(dead_code)]
impl FuzzyRoughApprox {
pub fn new(n: usize) -> Self {
let mut sim = vec![vec![0.0; n]; n];
for i in 0..n {
sim[i][i] = 1.0;
}
FuzzyRoughApprox {
universe_size: n,
similarity: sim,
}
}
pub fn set_similarity(&mut self, x: usize, y: usize, val: f64) {
self.similarity[x][y] = val.clamp(0.0, 1.0);
self.similarity[y][x] = val.clamp(0.0, 1.0);
}
pub fn lower_approx(&self, a: &[f64]) -> Vec<f64> {
(0..self.universe_size)
.map(|x| {
(0..self.universe_size)
.map(|y| {
let r = self.similarity[x][y];
let ay = a[y];
(1.0 - r + ay).min(1.0)
})
.fold(f64::INFINITY, f64::min)
})
.collect()
}
pub fn upper_approx(&self, a: &[f64]) -> Vec<f64> {
(0..self.universe_size)
.map(|x| {
(0..self.universe_size)
.map(|y| self.similarity[x][y].min(a[y]))
.fold(0.0f64, f64::max)
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct MamdaniSystem {
pub rules: Vec<MamdaniRule>,
pub output_size: usize,
}
impl MamdaniSystem {
pub fn new(output_size: usize) -> Self {
MamdaniSystem {
rules: Vec::new(),
output_size,
}
}
pub fn add_rule(&mut self, rule: MamdaniRule) {
self.rules.push(rule);
}
pub fn infer(&self, input_degrees: &[Vec<f64>]) -> FuzzySet {
let mut agg = FuzzySet::new(self.output_size);
for rule in &self.rules {
let strength = rule
.antecedent_mf
.iter()
.zip(input_degrees.iter().flatten())
.map(|(&a, &b)| a.min(b))
.fold(1.0_f64, f64::min);
for i in 0..self.output_size {
let clipped = rule.consequent.get(i).min(strength);
let current = agg.get(i);
agg.set(i, current.max(clipped));
}
}
agg
}
}
#[derive(Debug, Clone)]
pub struct SugenoSystem {
pub rules: Vec<SugenoRule>,
}
impl SugenoSystem {
pub fn new() -> Self {
SugenoSystem { rules: Vec::new() }
}
pub fn add_rule(&mut self, rule: SugenoRule) {
self.rules.push(rule);
}
pub fn infer(&self, inputs: &[f64], input_degrees: &[Vec<f64>]) -> f64 {
let mut weighted_sum = 0.0;
let mut weight_total = 0.0;
for rule in &self.rules {
let strength: f64 = rule
.antecedent_mf
.iter()
.zip(input_degrees.iter().flatten())
.map(|(&a, &b)| a.min(b))
.fold(1.0_f64, f64::min);
let z = rule.output_const
+ rule
.output_coeffs
.iter()
.zip(inputs.iter())
.map(|(&c, &x)| c * x)
.sum::<f64>();
weighted_sum += strength * z;
weight_total += strength;
}
if weight_total.abs() < 1e-12 {
0.0
} else {
weighted_sum / weight_total
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TriangularFuzzyNum {
pub lower: f64,
pub modal: f64,
pub upper: f64,
}
#[allow(dead_code)]
impl TriangularFuzzyNum {
pub fn new(lower: f64, modal: f64, upper: f64) -> Self {
assert!(lower <= modal && modal <= upper);
TriangularFuzzyNum {
lower,
modal,
upper,
}
}
pub fn membership(&self, x: f64) -> f64 {
if x < self.lower || x > self.upper {
0.0
} else if x <= self.modal {
(x - self.lower) / (self.modal - self.lower).max(1e-12)
} else {
(self.upper - x) / (self.upper - self.modal).max(1e-12)
}
}
pub fn add(&self, other: &TriangularFuzzyNum) -> TriangularFuzzyNum {
TriangularFuzzyNum::new(
self.lower + other.lower,
self.modal + other.modal,
self.upper + other.upper,
)
}
pub fn scale(&self, k: f64) -> TriangularFuzzyNum {
if k >= 0.0 {
TriangularFuzzyNum::new(k * self.lower, k * self.modal, k * self.upper)
} else {
TriangularFuzzyNum::new(k * self.upper, k * self.modal, k * self.lower)
}
}
pub fn defuzzify_centroid(&self) -> f64 {
(self.lower + self.modal + self.upper) / 3.0
}
pub fn alpha_cut(&self, alpha: f64) -> (f64, f64) {
let lo = self.lower + alpha * (self.modal - self.lower);
let hi = self.upper - alpha * (self.upper - self.modal);
(lo, hi)
}
}