Add feature-gated modules to evaluate several statically
dispatched variants of TOPSIS-based static resource scheduling.

The 'lab' feature ought to be activated during tests that involve
variants of TOPSIS-based static resource scheduling.

Signed-off-by: Dominik Rusovac <[email protected]>
---
 proxmox-resource-scheduling/Cargo.toml        |  3 ++
 proxmox-resource-scheduling/src/pve_static.rs | 44 +++++++++++++++++
 proxmox-resource-scheduling/src/topsis.rs     | 48 +++++++++++++++++++
 3 files changed, 95 insertions(+)

diff --git a/proxmox-resource-scheduling/Cargo.toml 
b/proxmox-resource-scheduling/Cargo.toml
index a73d8884..5060fae3 100644
--- a/proxmox-resource-scheduling/Cargo.toml
+++ b/proxmox-resource-scheduling/Cargo.toml
@@ -11,3 +11,6 @@ exclude.workspace = true
 [dependencies]
 anyhow.workspace = true
 serde = { workspace = true, features = [ "derive" ] }
+
+[features]
+lab = []
diff --git a/proxmox-resource-scheduling/src/pve_static.rs 
b/proxmox-resource-scheduling/src/pve_static.rs
index b81086dd..0afbec05 100644
--- a/proxmox-resource-scheduling/src/pve_static.rs
+++ b/proxmox-resource-scheduling/src/pve_static.rs
@@ -132,3 +132,47 @@ pub fn score_nodes_to_start_service<T: 
AsRef<StaticNodeUsage>>(
         .map(|(n, score)| (nodes[n].as_ref().name.clone(), score))
         .collect())
 }
+
+#[cfg(feature = "lab")]
+pub mod evaluate {
+    use crate::{
+        pve_static::score_nodes_to_start_service,
+        topsis::evaluate::{score_alternatives_with_variant, DispatchedTopsis},
+    };
+
+    use super::{StaticNodeUsage, StaticServiceUsage, N_CRITERIA, 
PVE_HA_TOPSIS_CRITERIA};
+
+    /// Dispatched parts of static resource scheduling
+    pub trait DispatchedStaticResourceScheduler {
+        /// The method to turn the stats of `nodes` and `service` into 
alternatives
+        fn preprocess<T: AsRef<StaticNodeUsage>>(
+            &self,
+            nodes: &[T],
+            service: &StaticServiceUsage,
+        ) -> Vec<[f64; N_CRITERIA]>;
+    }
+
+    /// Score `nodes` using specific `topsis_variant` and 
`static_resource_scheduling_variant`.
+    ///
+    /// Calls [`crate::topsis::score_nodes_to_start_service`] if 
`static_resource_scheduling_variant` is [`None`]
+    pub fn score_nodes_to_start_service_with_variant<T: 
AsRef<StaticNodeUsage>>(
+        nodes: &[T],
+        service: &StaticServiceUsage,
+        topsis_variant: Option<&impl DispatchedTopsis<N_CRITERIA>>,
+        static_resource_scheduling_variant: Option<&impl 
DispatchedStaticResourceScheduler>,
+    ) -> Vec<(String, f64)> {
+        match static_resource_scheduling_variant {
+            Some(static_resource_scheduling) => 
score_alternatives_with_variant(
+                topsis_variant,
+                static_resource_scheduling.preprocess(nodes, service),
+                &PVE_HA_TOPSIS_CRITERIA,
+            )
+            .into_iter()
+            .enumerate()
+            .map(|(n, score)| (nodes[n].as_ref().name.clone(), score))
+            .collect(),
+            _ => score_nodes_to_start_service(nodes, service)
+                .unwrap_or_else(|err| panic!("scoring nodes to start service 
failed: {err}")),
+        }
+    }
+}
diff --git a/proxmox-resource-scheduling/src/topsis.rs 
b/proxmox-resource-scheduling/src/topsis.rs
index 6d078aa6..f52ee27d 100644
--- a/proxmox-resource-scheduling/src/topsis.rs
+++ b/proxmox-resource-scheduling/src/topsis.rs
@@ -245,3 +245,51 @@ macro_rules! criteria_struct {
         }
     };
 }
+
+#[cfg(feature = "lab")]
+pub mod evaluate {
+    use super::{score_alternatives, Criteria, IdealAlternatives, Matrix};
+
+    /// Dispatched parts of TOPSIS algorithm
+    pub trait DispatchedTopsis<const N: usize> {
+        /// The method to normalize `alternatives`
+        fn normalize(&self, alternatives: &mut Matrix<N>);
+
+        /// The method to compute the designated distance of `alternative` to 
`ideals`
+        #[allow(private_interfaces)]
+        fn distance(
+            &self,
+            alternative: &[f64; N],
+            criteria: &Criteria<N>,
+            ideals: &IdealAlternatives<N>,
+        ) -> f64;
+    }
+
+    /// Score alternatives with specific dispatched `topsis_variant`.
+    ///
+    /// Calls [`crate::topsis::score_alternatives`] if `topsis_variant` is 
[`None`]
+    pub fn score_alternatives_with_variant<const N: usize>(
+        topsis_variant: Option<&impl DispatchedTopsis<N>>,
+        instance: Vec<[f64; N]>,
+        criteria: &Criteria<N>,
+    ) -> Vec<f64> {
+        match topsis_variant {
+            Some(topsis) => {
+                let mut alternatives = Matrix(instance);
+
+                topsis.normalize(&mut alternatives);
+
+                let ideals = IdealAlternatives::compute(&alternatives, 
criteria);
+
+                alternatives
+                    .0
+                    .iter()
+                    .map(|alternative| topsis.distance(alternative, criteria, 
&ideals))
+                    .collect()
+            }
+            _ => Matrix::new(instance)
+                .and_then(|matrix| score_alternatives(&matrix, criteria))
+                .unwrap_or_else(|err| panic!("scoring alternatives failed: 
{err}")),
+        }
+    }
+}
-- 
2.47.3




Reply via email to