drop Topsis prefix of all the types in topsis::

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2022-11-11 11:35:53 +01:00
parent 4ac19a0016
commit 5a50196a13
3 changed files with 102 additions and 106 deletions

View File

@ -1,7 +1,7 @@
use anyhow::Error;
use serde::{Deserialize, Serialize};
use crate::topsis::TopsisMatrix;
use crate::topsis;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
@ -115,7 +115,7 @@ pub fn score_nodes_to_start_service(
.collect::<Vec<_>>();
let scores =
crate::topsis::score_alternatives(&TopsisMatrix::new(matrix)?, &PVE_HA_TOPSIS_CRITERIA)?;
topsis::score_alternatives(&topsis::Matrix::new(matrix)?, &PVE_HA_TOPSIS_CRITERIA)?;
Ok(scores
.into_iter()

View File

@ -17,7 +17,7 @@ fn l2_norm(values: &[f64]) -> f64 {
}
/// A criterion that can be used when scoring with the TOPSIS algorithm.
pub struct TopsisCriterion {
pub struct Criterion {
/// Name of the criterion.
name: String,
/// The relative weight of the criterion. Is non-negative.
@ -26,8 +26,8 @@ pub struct TopsisCriterion {
maximize: bool,
}
impl TopsisCriterion {
/// Construct a new `TopsisCriterion`. Use a negative weight if the value for the criterion
impl Criterion {
/// Construct a new `Criterion`. Use a negative weight if the value for the criterion
/// should be minimized rather than maximized.
///
/// Assumes that `weight` is finite.
@ -38,7 +38,7 @@ impl TopsisCriterion {
(false, -weight)
};
TopsisCriterion {
Criterion {
name,
weight,
maximize,
@ -46,14 +46,14 @@ impl TopsisCriterion {
}
}
/// A normalized array of `TopsisCriterion`.
pub struct TopsisCriteria<const N_CRITERIA: usize>([TopsisCriterion; N_CRITERIA]);
/// A normalized array of `Criterion`.
pub struct Criteria<const N_CRITERIA: usize>([Criterion; N_CRITERIA]);
impl<const N: usize> TopsisCriteria<N> {
impl<const N: usize> Criteria<N> {
/// Create a new instance of normalized TOPSIS criteria.
///
/// Assumes that the sum of weights can be calculated to a finite, non-zero value.
pub fn new(mut criteria: [TopsisCriterion; N]) -> Result<Self, Error> {
pub fn new(mut criteria: [Criterion; N]) -> Result<Self, Error> {
let divisor = criteria
.iter()
.fold(0.0, |sum, criterion| sum + criterion.weight);
@ -73,7 +73,7 @@ impl<const N: usize> TopsisCriteria<N> {
}
}
Ok(TopsisCriteria(criteria))
Ok(Criteria(criteria))
}
/// Weigh each value according to the weight of its corresponding criterion.
@ -86,9 +86,9 @@ impl<const N: usize> TopsisCriteria<N> {
}
/// A normalized matrix used for scoring with the TOPSIS algorithm.
pub struct TopsisMatrix<const N_CRITERIA: usize>(Vec<[f64; N_CRITERIA]>);
pub struct Matrix<const N_CRITERIA: usize>(Vec<[f64; N_CRITERIA]>);
impl<const N: usize> TopsisMatrix<N> {
impl<const N: usize> Matrix<N> {
/// Values of the matrix for the fixed critierion with index `index`.
fn fixed_criterion(&self, index: usize) -> Vec<f64> {
self.0
@ -105,12 +105,12 @@ impl<const N: usize> TopsisMatrix<N> {
.collect::<Vec<&mut _>>()
}
/// Create a normalized `TopsisMatrix` based on the given values.
/// Create a normalized `Matrix` based on the given values.
///
/// Assumes that the sum of squares for each fixed criterion in `matrix` can be calculated to a
/// finite value.
pub fn new(matrix: Vec<[f64; N]>) -> Result<Self, Error> {
let mut matrix = TopsisMatrix(matrix);
let mut matrix = Matrix(matrix);
for n in 0..N {
let divisor = l2_norm(&matrix.fixed_criterion(n));
@ -129,17 +129,17 @@ impl<const N: usize> TopsisMatrix<N> {
}
}
/// Idealized alternatives from a `TopsisMatrix`. That is, the alternative consisting of the best
/// Idealized alternatives from a `Matrix`. That is, the alternative consisting of the best
/// (respectively worst) value among the alternatives in the matrix for each single criterion.
struct TopsisIdealAlternatives<const N_CRITERIA: usize> {
struct IdealAlternatives<const N_CRITERIA: usize> {
best: [f64; N_CRITERIA],
worst: [f64; N_CRITERIA],
}
impl<const N: usize> TopsisIdealAlternatives<N> {
impl<const N: usize> IdealAlternatives<N> {
/// Compute the idealized alternatives from the given `matrix`. The `criteria` are required to know
/// if a critierion should be maximized or minimized.
fn compute(matrix: &TopsisMatrix<N>, criteria: &TopsisCriteria<N>) -> Self {
fn compute(matrix: &Matrix<N>, criteria: &Criteria<N>) -> Self {
let criteria = &criteria.0;
let mut best = [0.0; N];
@ -170,10 +170,10 @@ impl<const N: usize> TopsisIdealAlternatives<N> {
/// alternative. Scores range from 0.0 to 1.0 and a low score indicates closness to the ideal worst
/// and/or distance to the ideal best alternatives.
pub fn score_alternatives<const N: usize>(
matrix: &TopsisMatrix<N>,
criteria: &TopsisCriteria<N>,
matrix: &Matrix<N>,
criteria: &Criteria<N>,
) -> Result<Vec<f64>, Error> {
let ideal_alternatives = TopsisIdealAlternatives::compute(matrix, criteria);
let ideal_alternatives = IdealAlternatives::compute(matrix, criteria);
let ideal_best = &ideal_alternatives.best;
let ideal_worst = &ideal_alternatives.worst;
@ -201,8 +201,8 @@ pub fn score_alternatives<const N: usize>(
/// Similar to `score_alternatives`, but returns the list of indices decreasing by score.
pub fn rank_alternatives<const N: usize>(
matrix: &TopsisMatrix<N>,
criteria: &TopsisCriteria<N>,
matrix: &Matrix<N>,
criteria: &Criteria<N>,
) -> Result<Vec<usize>, Error> {
let scores = score_alternatives(matrix, criteria)?;
@ -243,10 +243,10 @@ macro_rules! criteria_struct {
}
::lazy_static::lazy_static! {
static ref $criteria_name: $crate::topsis::TopsisCriteria<$count_name> =
$crate::topsis::TopsisCriteria::new([
static ref $criteria_name: $crate::topsis::Criteria<$count_name> =
$crate::topsis::Criteria::new([
$(
$crate::topsis::TopsisCriterion::new($crit_name.to_string(), $crit_weight),
$crate::topsis::Criterion::new($crit_name.to_string(), $crit_weight),
)*
])
.unwrap();

View File

@ -1,64 +1,60 @@
use anyhow::Error;
use proxmox_resource_scheduling::topsis::{
rank_alternatives, TopsisCriteria, TopsisCriterion, TopsisMatrix,
};
use proxmox_resource_scheduling::topsis::{rank_alternatives, Criteria, Criterion, Matrix};
#[test]
fn test_topsis_single_criterion() -> Result<(), Error> {
let criteria_pos =
TopsisCriteria::new([TopsisCriterion::new("the one and only".to_string(), 1.0)])?;
let criteria_pos = Criteria::new([Criterion::new("the one and only".to_string(), 1.0)])?;
let criteria_neg =
TopsisCriteria::new([TopsisCriterion::new("the one and only".to_string(), -1.0)])?;
let criteria_neg = Criteria::new([Criterion::new("the one and only".to_string(), -1.0)])?;
let matrix = vec![[0.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_pos)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_pos)?,
vec![0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_neg)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_neg)?,
vec![0],
);
let matrix = vec![[0.0], [2.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_pos)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_pos)?,
vec![1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_neg)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_neg)?,
vec![0, 1],
);
let matrix = vec![[1.0], [2.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_pos)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_pos)?,
vec![1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_neg)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_neg)?,
vec![0, 1],
);
let matrix = vec![[1.0], [2.0], [5.0], [3.0], [4.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_pos)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_pos)?,
vec![2, 4, 3, 1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_neg)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_neg)?,
vec![0, 1, 3, 4, 2],
);
let matrix = vec![[-2.0], [-5.0], [-4.0], [1.0], [-3.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_pos)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_pos)?,
vec![3, 0, 4, 2, 1],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_neg)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_neg)?,
vec![1, 2, 4, 0, 3],
);
@ -67,38 +63,38 @@ fn test_topsis_single_criterion() -> Result<(), Error> {
#[test]
fn test_topsis_two_criteria() -> Result<(), Error> {
let criteria_max_min = TopsisCriteria::new([
TopsisCriterion::new("max".to_string(), 1.0),
TopsisCriterion::new("min".to_string(), -1.0),
let criteria_max_min = Criteria::new([
Criterion::new("max".to_string(), 1.0),
Criterion::new("min".to_string(), -1.0),
])?;
let criteria_min_max = TopsisCriteria::new([
TopsisCriterion::new("min".to_string(), -1.0),
TopsisCriterion::new("max".to_string(), 1.0),
let criteria_min_max = Criteria::new([
Criterion::new("min".to_string(), -1.0),
Criterion::new("max".to_string(), 1.0),
])?;
let criteria_min_min = TopsisCriteria::new([
TopsisCriterion::new("min1".to_string(), -1.0),
TopsisCriterion::new("min2".to_string(), -1.0),
let criteria_min_min = Criteria::new([
Criterion::new("min1".to_string(), -1.0),
Criterion::new("min2".to_string(), -1.0),
])?;
let criteria_max_max = TopsisCriteria::new([
TopsisCriterion::new("max1".to_string(), 1.0),
TopsisCriterion::new("max2".to_string(), 1.0),
let criteria_max_max = Criteria::new([
Criterion::new("max1".to_string(), 1.0),
Criterion::new("max2".to_string(), 1.0),
])?;
let matrix = vec![[0.0, 0.0]];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_max_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_max_min)?,
vec![0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_max)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_max)?,
vec![0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_min)?,
vec![0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_max_max)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_max_max)?,
vec![0],
);
@ -111,19 +107,19 @@ fn test_topsis_two_criteria() -> Result<(), Error> {
[0.0, 0.0],
];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_max_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_max_min)?,
vec![2, 1, 4, 3, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_max)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_max)?,
vec![0, 3, 4, 1, 2],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_min)?,
vec![2, 4, 1, 0, 3],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_max_max)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_max_max)?,
vec![3, 0, 1, 4, 2],
);
@ -137,19 +133,19 @@ fn test_topsis_two_criteria() -> Result<(), Error> {
[4.0, 7.0],
];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_max_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_max_min)?,
vec![0, 1, 4, 2, 3],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_max)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_max)?,
vec![3, 2, 4, 1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_min_min)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_min_min)?,
vec![2, 3, 1, 0, 4],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_max_max)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_max_max)?,
vec![4, 0, 1, 3, 2],
);
@ -158,25 +154,25 @@ fn test_topsis_two_criteria() -> Result<(), Error> {
#[test]
fn test_topsis_three_criteria() -> Result<(), Error> {
let criteria_first = TopsisCriteria::new([
TopsisCriterion::new("more".to_string(), 10.0),
TopsisCriterion::new("less".to_string(), 0.2),
TopsisCriterion::new("least".to_string(), 0.1),
let criteria_first = Criteria::new([
Criterion::new("more".to_string(), 10.0),
Criterion::new("less".to_string(), 0.2),
Criterion::new("least".to_string(), 0.1),
])?;
let criteria_second = TopsisCriteria::new([
TopsisCriterion::new("less".to_string(), 0.2),
TopsisCriterion::new("more".to_string(), 10.0),
TopsisCriterion::new("least".to_string(), 0.1),
let criteria_second = Criteria::new([
Criterion::new("less".to_string(), 0.2),
Criterion::new("more".to_string(), 10.0),
Criterion::new("least".to_string(), 0.1),
])?;
let criteria_third = TopsisCriteria::new([
TopsisCriterion::new("less".to_string(), 0.2),
TopsisCriterion::new("least".to_string(), 0.1),
TopsisCriterion::new("more".to_string(), 10.0),
let criteria_third = Criteria::new([
Criterion::new("less".to_string(), 0.2),
Criterion::new("least".to_string(), 0.1),
Criterion::new("more".to_string(), 10.0),
])?;
let criteria_min = TopsisCriteria::new([
TopsisCriterion::new("most".to_string(), -10.0),
TopsisCriterion::new("more".to_string(), -5.0),
TopsisCriterion::new("less".to_string(), 0.1),
let criteria_min = Criteria::new([
Criterion::new("most".to_string(), -10.0),
Criterion::new("more".to_string(), -5.0),
Criterion::new("less".to_string(), 0.1),
])?;
#[rustfmt::skip]
@ -186,19 +182,19 @@ fn test_topsis_three_criteria() -> Result<(), Error> {
[0.0, 0.0, 1.0],
];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_first)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_first)?,
vec![0, 1, 2],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_second)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_second)?,
vec![1, 0, 2],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_third)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_third)?,
vec![2, 0, 1],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_min)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_min)?,
vec![2, 1, 0],
);
@ -210,19 +206,19 @@ fn test_topsis_three_criteria() -> Result<(), Error> {
];
// normalization ensures that it's still the same as above
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_first)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_first)?,
vec![0, 1, 2],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_second)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_second)?,
vec![1, 0, 2],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_third)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_third)?,
vec![2, 0, 1],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_min)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_min)?,
vec![2, 1, 0],
);
@ -233,19 +229,19 @@ fn test_topsis_three_criteria() -> Result<(), Error> {
[0.0, 0.0, 1.0],
];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_first)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_first)?,
vec![2, 1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_second)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_second)?,
vec![2, 0, 1],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix.clone())?, &criteria_third)?,
rank_alternatives(&Matrix::new(matrix.clone())?, &criteria_third)?,
vec![2, 1, 0],
);
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_min)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_min)?,
vec![0, 1, 2],
);
@ -260,22 +256,22 @@ fn test_nan() {
[0.0, -1.0, 0.0],
[0.0, f64::NAN, 1.0],
];
assert!(TopsisMatrix::new(matrix).is_err());
assert!(Matrix::new(matrix).is_err());
}
#[test]
fn test_zero() -> Result<(), Error> {
let criteria_zero = TopsisCriteria::new([
TopsisCriterion::new("z".to_string(), 0.0),
TopsisCriterion::new("e".to_string(), 0.0),
TopsisCriterion::new("ro".to_string(), 0.0),
let criteria_zero = Criteria::new([
Criterion::new("z".to_string(), 0.0),
Criterion::new("e".to_string(), 0.0),
Criterion::new("ro".to_string(), 0.0),
]);
assert!(criteria_zero.is_err());
let criteria_first = TopsisCriteria::new([
TopsisCriterion::new("more".to_string(), 10.0),
TopsisCriterion::new("less".to_string(), 0.2),
TopsisCriterion::new("least".to_string(), 0.1),
let criteria_first = Criteria::new([
Criterion::new("more".to_string(), 10.0),
Criterion::new("less".to_string(), 0.2),
Criterion::new("least".to_string(), 0.1),
])?;
#[rustfmt::skip]
@ -285,7 +281,7 @@ fn test_zero() -> Result<(), Error> {
[0.0, 0.0, 1.0],
];
assert_eq!(
rank_alternatives(&TopsisMatrix::new(matrix)?, &criteria_first)?,
rank_alternatives(&Matrix::new(matrix)?, &criteria_first)?,
vec![1, 2, 0],
);