1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Utilities to model the cost of data centers.

use std::{iter::Sum, ops::Add};

use super::{ModelOutput, ModelOutputFailure, ModelOutputSuccess};
use noisy_float::prelude::*;
use pyo3::prelude::*;
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;

pub mod loads;
pub mod model;
pub mod models;

/// Model result.
#[pyclass]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct DataCenterModelOutputSuccess {
    /// Energy cost of model.
    #[pyo3(get)]
    pub energy_cost: f64,
    /// Revenue loss of model.
    #[pyo3(get)]
    pub revenue_loss: f64,
    /// All possible assignments of fractions of loads to server types for each time slot.
    #[pyo3(get)]
    pub assignments: Vec<Vec<Vec<f64>>>,
}
impl ModelOutputSuccess for DataCenterModelOutputSuccess {
    fn horizontal_merge(mut self, output: Self) -> Self {
        self.assignments.extend(output.assignments.into_iter());
        Self {
            energy_cost: self.energy_cost + output.energy_cost,
            revenue_loss: self.revenue_loss + output.revenue_loss,
            assignments: self.assignments,
        }
    }

    /// Computes the mean energy cost and revenue loss.
    fn vertical_merge(mut self, output: Self) -> Self {
        assert!(self.assignments.len() == 1);
        assert!(output.assignments.len() == 1);
        self.assignments[0]
            .extend(output.assignments.into_iter().next().unwrap().into_iter());
        Self {
            energy_cost: (self.energy_cost + output.energy_cost) / 2.,
            revenue_loss: (self.revenue_loss + output.revenue_loss) / 2.,
            assignments: self.assignments,
        }
    }
}
impl DataCenterModelOutputSuccess {
    pub fn new(
        energy_cost: f64,
        revenue_loss: f64,
        assignment: Vec<f64>,
    ) -> Self {
        Self {
            energy_cost,
            revenue_loss,
            assignments: vec![vec![assignment]],
        }
    }
}

/// Model failure.
#[derive(Clone, Debug, Error, Deserialize, PartialEq, Serialize)]
pub enum DataCenterModelOutputFailure {
    #[error("The configuration is unable to support the given load profile.")]
    DemandExceedingSupply,
    #[error("The delay is infinite. The arrival rate is too close to (or larger than) the service rate.")]
    InfiniteDelay {
        server_type: String,
        number_of_jobs: f64,
        mean_job_duration: f64,
    },
    #[error("A positive load was assigned to a server type without any active servers.")]
    LoadToInactiveServer,
    #[error("The configuration is outside the decision space.")]
    OutsideDecisionSpace,
    #[error("A server cannot handle more than one job during a time slot.")]
    SLOMaxUtilizationExceeded,
}
impl IntoPy<PyObject> for DataCenterModelOutputFailure {
    fn into_py(self, py: Python) -> PyObject {
        self.to_string().into_py(py)
    }
}
impl ModelOutputFailure for DataCenterModelOutputFailure {
    fn outside_decision_space() -> DataCenterModelOutputFailure {
        DataCenterModelOutputFailure::OutsideDecisionSpace
    }
}

/// Model output.
pub type DataCenterModelOutput =
    ModelOutput<DataCenterModelOutputSuccess, DataCenterModelOutputFailure>;

/// Encapsulating the constituents of the hitting cost.
#[derive(Debug)]
pub struct DataCenterObjective {
    pub energy_cost: N64,
    pub revenue_loss: N64,
}
impl DataCenterObjective {
    pub fn new(energy_cost: N64, revenue_loss: N64) -> Self {
        Self {
            energy_cost,
            revenue_loss,
        }
    }

    pub fn failure(_failure: DataCenterModelOutputFailure) -> Self {
        Self {
            energy_cost: n64(f64::INFINITY),
            revenue_loss: n64(f64::INFINITY),
        }
    }
}
impl Default for DataCenterObjective {
    fn default() -> Self {
        Self {
            energy_cost: n64(0.),
            revenue_loss: n64(0.),
        }
    }
}
impl Add for DataCenterObjective {
    type Output = DataCenterObjective;
    fn add(self, rhs: Self) -> Self::Output {
        DataCenterObjective {
            energy_cost: self.energy_cost + rhs.energy_cost,
            revenue_loss: self.revenue_loss + rhs.revenue_loss,
        }
    }
}
impl Sum for DataCenterObjective {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.reduce(|result, value| result + value)
            .unwrap_or_default()
    }
}

pub type IntermediateResult = Result<N64, DataCenterModelOutputFailure>;

pub type IntermediateObjective =
    Result<DataCenterObjective, DataCenterModelOutputFailure>;

/// Ensures that $x$ is greater than zero and handles edge cases appropriately.
pub fn safe_balancing(
    x: N64,
    total_load: N64,
    f: impl Fn() -> IntermediateResult,
) -> IntermediateResult {
    if x > 0. {
        f()
    } else if total_load > 0. {
        Err(DataCenterModelOutputFailure::LoadToInactiveServer)
    } else {
        assert!(total_load == 0.);
        Ok(n64(0.))
    }
}