use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[cfg(feature = "python")]
use pyo3::exceptions::PyValueError;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::core::OError;
fn binomial_coefficient(mut n: u64, k: u64) -> u64 {
let mut r: u64 = 1;
if k > n {
0
} else {
for d in 1..=k {
r *= n;
n -= 1;
r /= d;
}
r
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(feature = "python", pyclass)]
pub struct TwoLayerPartitions {
pub boundary_layer: usize,
pub inner_layer: usize,
pub scaling: Option<f64>,
}
#[cfg(feature = "python")]
#[pymethods]
impl TwoLayerPartitions {
#[new]
#[pyo3(signature = (boundary_layer, inner_layer, scaling=None))]
fn new(boundary_layer: usize, inner_layer: usize, scaling: Option<f64>) -> Self {
TwoLayerPartitions {
boundary_layer,
inner_layer,
scaling,
}
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"TwoLayerPartitions(boundary_layer={}, inner_layer={}, scaling={:?})",
self.boundary_layer, self.inner_layer, self.scaling
))
}
fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
#[derive(Serialize, Clone, Deserialize, Debug)]
#[cfg_attr(feature = "python", derive(FromPyObject, IntoPyObject))]
pub enum NumberOfPartitions {
OneLayer(usize),
TwoLayers(TwoLayerPartitions),
}
#[doc = include_str!("../../examples/reference_points_1layer.rs")]
#[doc = include_str!("../../examples/reference_points_2layers.rs")]
#[cfg_attr(feature = "python", pyclass(name = "DasDarren1998", get_all))]
pub struct DasDarren1998 {
number_of_objectives: usize,
number_of_partitions: NumberOfPartitions,
}
impl DasDarren1998 {
pub fn new(
number_of_objectives: usize,
number_of_partitions: &NumberOfPartitions,
) -> Result<Self, OError> {
match &number_of_partitions {
NumberOfPartitions::OneLayer(_) => {}
NumberOfPartitions::TwoLayers(layers) => {
if let Some(scaling) = layers.scaling {
if scaling < f64::EPSILON {
return Err(OError::Generic(
"The inner layer scaling factor must be larger 0".to_string(),
));
}
}
}
}
Ok(DasDarren1998 {
number_of_objectives,
number_of_partitions: number_of_partitions.clone(),
})
}
pub fn number_of_points(&self) -> u64 {
match &self.number_of_partitions {
NumberOfPartitions::OneLayer(number_of_partitions) => {
binomial_coefficient(
self.number_of_objectives as u64 + *number_of_partitions as u64 - 1,
*number_of_partitions as u64,
)
}
NumberOfPartitions::TwoLayers(layers) => {
binomial_coefficient(
self.number_of_objectives as u64 + layers.boundary_layer as u64 - 1,
layers.boundary_layer as u64,
) + binomial_coefficient(
self.number_of_objectives as u64 + layers.inner_layer as u64 - 1,
layers.inner_layer as u64,
)
}
}
}
pub fn gap(&self) -> f64 {
match &self.number_of_partitions {
NumberOfPartitions::OneLayer(l) => 1.0 / *l as f64,
NumberOfPartitions::TwoLayers(l) => {
let g1 = 1.0 / l.inner_layer as f64;
let g2 = 1.0 / l.boundary_layer as f64;
(g1 + g2) / 2.0
}
}
}
pub fn get_weights(&self) -> Vec<Vec<f64>> {
match &self.number_of_partitions {
NumberOfPartitions::OneLayer(number_of_partitions) => {
let mut final_weights: Vec<Vec<f64>> = vec![];
let mut initial_empty_weight: Vec<usize> = vec![0; self.number_of_objectives];
let obj_index: usize = 0;
self.recursive_weights(
&mut final_weights,
&mut initial_empty_weight,
*number_of_partitions,
*number_of_partitions,
obj_index,
);
final_weights
}
NumberOfPartitions::TwoLayers(layers) => {
let mut final_weights: Vec<Vec<f64>> = vec![];
let mut initial_empty_weight: Vec<usize> = vec![0; self.number_of_objectives];
let obj_index: usize = 0;
self.recursive_weights(
&mut final_weights,
&mut initial_empty_weight,
layers.boundary_layer,
layers.boundary_layer,
obj_index,
);
let mut inner_points: Vec<Vec<f64>> = vec![];
let mut initial_empty_weight: Vec<usize> = vec![0; self.number_of_objectives];
let obj_index: usize = 0;
self.recursive_weights(
&mut inner_points,
&mut initial_empty_weight,
layers.inner_layer,
layers.inner_layer,
obj_index,
);
let scaling = layers.scaling.unwrap_or(0.5);
for inner_point in inner_points {
let new_points = inner_point
.iter()
.map(|value| (1.0 / self.number_of_objectives as f64 + value) * scaling)
.collect();
final_weights.push(new_points);
}
final_weights
}
}
}
fn recursive_weights(
&self,
final_weights: &mut Vec<Vec<f64>>,
weight: &mut Vec<usize>,
left_partitions: usize,
number_of_partitions: usize,
obj_index: usize,
) {
for k in 0..=left_partitions {
if obj_index != self.number_of_objectives - 1 {
weight[obj_index] = k;
self.recursive_weights(
final_weights,
weight,
left_partitions - k,
number_of_partitions,
obj_index + 1,
)
} else {
weight[obj_index] = left_partitions;
final_weights.push(
weight
.iter()
.map(|v| *v as f64 / number_of_partitions as f64)
.collect(),
);
break;
}
}
}
pub fn serialise(ref_points: &[Vec<f64>], file: &PathBuf) -> Result<(), OError> {
#[derive(Serialize)]
pub struct Points<'a>(pub &'a [Vec<f64>]);
let p = Points(ref_points);
let data = serde_json::to_string_pretty(&p.0).map_err(|e| {
OError::AlgorithmExport(format!(
"The following error occurred while converting the history struct: {e}"
))
})?;
fs::write(file, data).map_err(|e| {
OError::AlgorithmExport(format!(
"The following error occurred while exporting the history JSON file: {e}",
))
})?;
Ok(())
}
}
#[cfg(feature = "python")]
#[pymethods]
impl DasDarren1998 {
#[new]
pub fn py_new(
number_of_objectives: usize,
number_of_partitions: NumberOfPartitions,
) -> PyResult<Self> {
Ok(Self {
number_of_objectives,
number_of_partitions,
})
}
pub fn calculate(&self) -> PyResult<Vec<Vec<f64>>> {
let number_of_partitions = match &self.number_of_partitions {
NumberOfPartitions::OneLayer(n) => NumberOfPartitions::OneLayer(*n),
NumberOfPartitions::TwoLayers(data) => {
NumberOfPartitions::TwoLayers(TwoLayerPartitions {
boundary_layer: data.boundary_layer,
inner_layer: data.inner_layer,
scaling: data.scaling,
})
}
};
let ds = DasDarren1998::new(self.number_of_objectives, &number_of_partitions)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
Ok(ds.get_weights())
}
pub fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"DasDarren1998(number_of_objectives={}, number_of_partitions={:?})",
self.number_of_objectives, self.number_of_partitions
))
}
pub fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
#[cfg(test)]
mod test {
use crate::core::test_utils::assert_approx_array_eq;
use crate::utils::reference_points::{binomial_coefficient, DasDarren1998};
use crate::utils::{NumberOfPartitions, TwoLayerPartitions};
#[test]
fn test_binomial_coefficient() {
assert_eq!(binomial_coefficient(6, 4), 15);
assert_eq!(binomial_coefficient(1, 3), 0);
assert_eq!(binomial_coefficient(7, 3), 35);
assert_eq!(binomial_coefficient(100, 2), 4950);
}
#[test]
fn test_das_darren_3obj() {
let m = DasDarren1998::new(3, &NumberOfPartitions::OneLayer(3)).unwrap();
let weights = m.get_weights();
let expected_weights = [
[0.0, 0.0, 1.0],
[0.0, 0.333, 0.666],
[0.0, 0.666, 0.333],
[0.0, 1.0, 0.0],
[0.333, 0.0, 0.666],
[0.333, 0.333, 0.333],
[0.333, 0.666, 0.0],
[0.666, 0.0, 0.333],
[0.666, 0.333, 0.0],
[1.0, 0.0, 0.0],
];
assert_eq!(weights.len() as u64, m.number_of_points());
assert_eq!(expected_weights.len(), weights.len());
for (wi, exp_weight_coordinates) in expected_weights.iter().enumerate() {
assert_approx_array_eq(&weights[wi], exp_weight_coordinates, Some(0.001));
}
}
#[test]
fn test_das_darren_5obj() {
let m = DasDarren1998::new(4, &NumberOfPartitions::OneLayer(5)).unwrap();
let weights = m.get_weights();
let expected_weights = [
[0.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.2, 0.8],
[0.0, 0.0, 0.4, 0.6],
[0.0, 0.0, 0.6, 0.4],
[0.0, 0.0, 0.8, 0.2],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.2, 0.0, 0.8],
[0.0, 0.2, 0.2, 0.6],
[0.0, 0.2, 0.4, 0.4],
[0.0, 0.2, 0.6, 0.2],
[0.0, 0.2, 0.8, 0.0],
[0.0, 0.4, 0.0, 0.6],
[0.0, 0.4, 0.2, 0.4],
[0.0, 0.4, 0.4, 0.2],
[0.0, 0.4, 0.6, 0.0],
[0.0, 0.6, 0.0, 0.4],
[0.0, 0.6, 0.2, 0.2],
[0.0, 0.6, 0.4, 0.0],
[0.0, 0.8, 0.0, 0.2],
[0.0, 0.8, 0.2, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.2, 0.0, 0.0, 0.8],
[0.2, 0.0, 0.2, 0.6],
[0.2, 0.0, 0.4, 0.4],
[0.2, 0.0, 0.6, 0.2],
[0.2, 0.0, 0.8, 0.0],
[0.2, 0.2, 0.0, 0.6],
[0.2, 0.2, 0.2, 0.4],
[0.2, 0.2, 0.4, 0.2],
[0.2, 0.2, 0.6, 0.0],
[0.2, 0.4, 0.0, 0.4],
[0.2, 0.4, 0.2, 0.2],
[0.2, 0.4, 0.4, 0.0],
[0.2, 0.6, 0.0, 0.2],
[0.2, 0.6, 0.2, 0.0],
[0.2, 0.8, 0.0, 0.0],
[0.4, 0.0, 0.0, 0.6],
[0.4, 0.0, 0.2, 0.4],
[0.4, 0.0, 0.4, 0.2],
[0.4, 0.0, 0.6, 0.0],
[0.4, 0.2, 0.0, 0.4],
[0.4, 0.2, 0.2, 0.2],
[0.4, 0.2, 0.4, 0.0],
[0.4, 0.4, 0.0, 0.2],
[0.4, 0.4, 0.2, 0.0],
[0.4, 0.6, 0.0, 0.0],
[0.6, 0.0, 0.0, 0.4],
[0.6, 0.0, 0.2, 0.2],
[0.6, 0.0, 0.4, 0.0],
[0.6, 0.2, 0.0, 0.2],
[0.6, 0.2, 0.2, 0.0],
[0.6, 0.4, 0.0, 0.0],
[0.8, 0.0, 0.0, 0.2],
[0.8, 0.0, 0.2, 0.0],
[0.8, 0.2, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
];
assert_eq!(weights.len() as u64, m.number_of_points());
assert_eq!(expected_weights.len(), weights.len());
for (wi, exp_weight_coordinates) in expected_weights.iter().enumerate() {
assert_approx_array_eq(&weights[wi], exp_weight_coordinates, None);
}
}
#[test]
fn test_das_darren_two_layers() {
let layers = TwoLayerPartitions {
boundary_layer: 4,
inner_layer: 3,
scaling: Some(0.5),
};
let m = DasDarren1998::new(3, &NumberOfPartitions::TwoLayers(layers)).unwrap();
let weights = m.get_weights();
let expected_weights = [
[0., 0., 1.],
[0., 0.25, 0.75],
[0., 0.5, 0.5],
[0., 0.75, 0.25],
[0., 1., 0.],
[0.25, 0., 0.75],
[0.25, 0.25, 0.5],
[0.25, 0.5, 0.25],
[0.25, 0.75, 0.],
[0.5, 0., 0.5],
[0.5, 0.25, 0.25],
[0.5, 0.5, 0.],
[0.75, 0., 0.25],
[0.75, 0.25, 0.],
[1., 0., 0.],
[0.16666667, 0.16666667, 0.66666667],
[0.16666667, 0.33333333, 0.5],
[0.16666667, 0.5, 0.33333333],
[0.16666667, 0.66666667, 0.16666667],
[0.33333333, 0.16666667, 0.5],
[0.33333333, 0.33333333, 0.33333333],
[0.33333333, 0.5, 0.16666667],
[0.5, 0.16666667, 0.33333333],
[0.5, 0.33333333, 0.16666667],
[0.66666667, 0.16666667, 0.16666667],
];
assert_eq!(weights.len() as u64, m.number_of_points());
assert_eq!(expected_weights.len(), weights.len());
for (wi, exp_weight_coordinates) in expected_weights.iter().enumerate() {
assert_approx_array_eq(&weights[wi], exp_weight_coordinates, None);
}
}
}