#[allow(dead_code)]
pub(crate) const CARBON_PROFILES_VINTAGE: &str = "2022-2024";
#[derive(Debug, Clone, PartialEq)]
pub enum HourlyProfile {
FlatYear([f64; 24]),
Monthly(Box<[[f64; 24]; 12]>),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum HourlyProfileRef<'a> {
FlatYear(&'a [f64; 24]),
Monthly(&'a [[f64; 24]; 12]),
}
impl HourlyProfileRef<'_> {
#[inline]
#[must_use]
pub(crate) fn intensity_at(self, hour: u8, month: Option<u8>) -> f64 {
debug_assert!(hour < 24, "hour must be 0..24, got {hour}");
match self {
Self::FlatYear(profile) => profile[hour as usize],
Self::Monthly(profiles) => {
if let Some(m) = month {
profiles[m.min(11) as usize][hour as usize]
} else {
let h = hour as usize;
profiles.iter().map(|m| m[h]).sum::<f64>() / 12.0
}
}
}
}
#[inline]
#[must_use]
pub(crate) fn is_monthly(self) -> bool {
matches!(self, Self::Monthly(_))
}
}
impl HourlyProfile {
#[inline]
fn as_ref(&self) -> HourlyProfileRef<'_> {
match self {
Self::FlatYear(arr) => HourlyProfileRef::FlatYear(arr),
Self::Monthly(arr) => HourlyProfileRef::Monthly(arr),
}
}
#[inline]
#[must_use]
pub fn intensity_at(&self, hour: u8, month: Option<u8>) -> f64 {
self.as_ref().intensity_at(hour, month)
}
#[inline]
#[must_use]
pub fn is_monthly(&self) -> bool {
self.as_ref().is_monthly()
}
#[must_use]
pub fn mean(&self) -> f64 {
match self {
Self::FlatYear(profile) => profile.iter().sum::<f64>() / 24.0,
Self::Monthly(profiles) => {
let total: f64 = profiles.iter().flat_map(|m| m.iter()).sum();
total / (12.0 * 24.0)
}
}
}
}
pub(crate) static MONTHLY_PROFILES: &[(&str, [[f64; 24]; 12])] = &[
(
"eu-west-3",
[
[
42.9, 41.4, 40.7, 40.0, 40.7, 42.2, 45.9, 50.3, 53.3, 51.8, 50.3, 48.8, 47.4, 45.9,
44.4, 45.9, 50.3, 57.7, 60.7, 57.7, 53.3, 48.8, 45.9, 44.4,
],
[
41.4, 40.0, 39.2, 38.5, 39.2, 40.7, 44.4, 48.8, 51.8, 50.3, 48.8, 47.4, 45.9, 44.4,
42.9, 44.4, 48.8, 56.2, 59.2, 56.2, 51.8, 47.4, 44.4, 42.9,
],
[
38.5, 37.0, 36.3, 35.5, 36.3, 37.7, 41.4, 45.9, 48.8, 47.4, 45.9, 44.4, 42.9, 41.4,
40.0, 41.4, 45.9, 53.3, 56.2, 53.3, 48.8, 44.4, 41.4, 40.0,
],
[
35.5, 34.0, 33.3, 32.6, 33.3, 34.8, 38.5, 42.9, 45.9, 44.4, 42.9, 41.4, 40.0, 38.5,
37.0, 38.5, 42.9, 50.3, 53.3, 50.3, 45.9, 41.4, 38.5, 37.0,
],
[
32.6, 31.1, 30.3, 29.6, 30.3, 31.8, 35.5, 40.0, 41.4, 40.0, 38.5, 37.0, 35.5, 34.0,
32.6, 34.0, 38.5, 45.9, 48.8, 45.9, 41.4, 37.0, 34.0, 32.6,
],
[
29.6, 28.1, 27.4, 26.6, 27.4, 28.9, 32.6, 37.0, 38.5, 37.0, 35.5, 34.0, 32.6, 31.1,
29.6, 31.1, 35.5, 41.4, 44.4, 41.4, 37.0, 32.6, 31.1, 29.6,
],
[
31.1, 29.6, 28.9, 28.1, 28.9, 30.3, 34.0, 38.5, 40.0, 38.5, 37.0, 35.5, 34.0, 32.6,
31.1, 32.6, 37.0, 42.9, 45.9, 42.9, 38.5, 34.0, 32.6, 31.1,
],
[
31.1, 29.6, 28.9, 28.1, 28.9, 30.3, 34.0, 38.5, 40.0, 38.5, 37.0, 35.5, 34.0, 32.6,
31.1, 32.6, 37.0, 42.9, 45.9, 42.9, 38.5, 34.0, 32.6, 31.1,
],
[
34.0, 32.6, 31.8, 31.1, 31.8, 33.3, 37.0, 41.4, 44.4, 42.9, 41.4, 40.0, 38.5, 37.0,
35.5, 37.0, 41.4, 48.8, 51.8, 48.8, 44.4, 40.0, 37.0, 35.5,
],
[
37.0, 35.5, 34.8, 34.0, 34.8, 36.3, 40.0, 44.4, 47.4, 45.9, 44.4, 42.9, 41.4, 40.0,
38.5, 40.0, 44.4, 51.8, 54.8, 51.8, 47.4, 42.9, 40.0, 38.5,
],
[
40.0, 38.5, 37.7, 37.0, 37.7, 39.2, 42.9, 47.4, 50.3, 48.8, 47.4, 45.9, 44.4, 42.9,
41.4, 42.9, 47.4, 54.8, 57.7, 54.8, 50.3, 45.9, 42.9, 41.4,
],
[
42.9, 41.4, 40.7, 40.0, 40.7, 42.2, 45.9, 50.3, 53.3, 51.8, 50.3, 48.8, 47.4, 45.9,
44.4, 45.9, 50.3, 57.7, 60.7, 57.7, 53.3, 48.8, 45.9, 44.4,
],
],
),
(
"eu-central-1",
[
[
332.2, 324.3, 320.4, 316.4, 324.3, 348.1, 395.5, 419.3, 427.2, 415.3, 403.4, 391.6,
379.7, 367.8, 371.8, 383.7, 411.3, 443.0, 454.8, 435.1, 411.3, 387.6, 363.9, 348.1,
],
[
324.3, 316.4, 312.5, 308.5, 316.4, 340.1, 387.6, 411.3, 419.3, 407.4, 395.5, 383.7,
371.8, 359.9, 363.9, 375.7, 403.4, 435.1, 446.9, 427.2, 403.4, 379.7, 356.0, 340.1,
],
[
308.5, 300.6, 296.6, 292.7, 300.6, 324.3, 367.8, 391.6, 395.5, 379.7, 363.9, 348.1,
332.2, 320.4, 324.3, 340.1, 371.8, 411.3, 423.2, 403.4, 379.7, 356.0, 332.2, 320.4,
],
[
292.7, 284.8, 280.8, 276.9, 284.8, 304.6, 348.1, 371.8, 375.7, 359.9, 344.1, 328.3,
312.5, 300.6, 304.6, 320.4, 352.0, 391.6, 403.4, 383.7, 359.9, 336.2, 312.5, 300.6,
],
[
276.9, 269.0, 265.0, 261.0, 269.0, 288.7, 332.2, 352.0, 352.0, 332.2, 312.5, 296.6,
284.8, 276.9, 284.8, 304.6, 336.2, 375.7, 387.6, 367.8, 344.1, 316.4, 292.7, 284.8,
],
[
269.0, 261.0, 257.1, 253.1, 261.0, 280.8, 320.4, 340.1, 336.2, 316.4, 296.6, 280.8,
269.0, 261.0, 269.0, 288.7, 324.3, 363.9, 379.7, 359.9, 336.2, 308.5, 284.8, 276.9,
],
[
272.9, 265.0, 261.0, 257.1, 265.0, 284.8, 324.3, 344.1, 340.1, 320.4, 300.6, 284.8,
272.9, 265.0, 272.9, 292.7, 328.3, 367.8, 383.7, 363.9, 340.1, 312.5, 288.7, 280.8,
],
[
276.9, 269.0, 265.0, 261.0, 269.0, 288.7, 328.3, 348.1, 348.1, 328.3, 308.5, 292.7,
280.8, 272.9, 280.8, 300.6, 332.2, 371.8, 387.6, 367.8, 344.1, 316.4, 292.7, 284.8,
],
[
292.7, 284.8, 280.8, 276.9, 284.8, 304.6, 348.1, 367.8, 371.8, 356.0, 340.1, 328.3,
316.4, 308.5, 312.5, 328.3, 359.9, 395.5, 407.4, 387.6, 363.9, 340.1, 316.4, 304.6,
],
[
308.5, 300.6, 296.6, 292.7, 300.6, 320.4, 363.9, 387.6, 391.6, 375.7, 359.9, 348.1,
336.2, 328.3, 332.2, 348.1, 375.7, 411.3, 423.2, 403.4, 379.7, 356.0, 332.2, 320.4,
],
[
320.4, 312.5, 308.5, 304.6, 312.5, 336.2, 379.7, 403.4, 411.3, 399.5, 387.6, 375.7,
363.9, 352.0, 356.0, 367.8, 395.5, 427.2, 439.0, 419.3, 395.5, 371.8, 348.1, 332.2,
],
[
332.2, 324.3, 320.4, 316.4, 324.3, 348.1, 395.5, 419.3, 427.2, 415.3, 403.4, 391.6,
379.7, 367.8, 371.8, 383.7, 411.3, 443.0, 454.8, 435.1, 411.3, 387.6, 363.9, 348.1,
],
],
),
(
"eu-west-2",
[
[
215.0, 205.0, 200.0, 195.0, 205.0, 230.0, 270.0, 300.0, 295.0, 280.0, 265.0, 255.0,
245.0, 235.0, 240.0, 255.0, 285.0, 315.0, 325.0, 305.0, 280.0, 260.0, 240.0, 225.0,
],
[
210.0, 200.0, 195.0, 190.0, 200.0, 225.0, 265.0, 295.0, 290.0, 275.0, 260.0, 250.0,
240.0, 230.0, 235.0, 250.0, 280.0, 310.0, 320.0, 300.0, 275.0, 255.0, 235.0, 220.0,
],
[
200.0, 190.0, 185.0, 180.0, 190.0, 215.0, 250.0, 280.0, 275.0, 260.0, 245.0, 235.0,
225.0, 215.0, 220.0, 235.0, 265.0, 295.0, 305.0, 285.0, 260.0, 240.0, 220.0, 210.0,
],
[
190.0, 180.0, 175.0, 170.0, 180.0, 205.0, 240.0, 270.0, 265.0, 250.0, 235.0, 225.0,
215.0, 205.0, 210.0, 225.0, 255.0, 285.0, 295.0, 275.0, 250.0, 230.0, 210.0, 200.0,
],
[
180.0, 170.0, 165.0, 160.0, 170.0, 195.0, 228.0, 255.0, 250.0, 235.0, 220.0, 210.0,
200.0, 190.0, 195.0, 210.0, 240.0, 270.0, 280.0, 260.0, 235.0, 215.0, 195.0, 185.0,
],
[
172.0, 162.0, 157.0, 152.0, 162.0, 185.0, 218.0, 245.0, 240.0, 225.0, 210.0, 200.0,
190.0, 180.0, 185.0, 200.0, 230.0, 260.0, 270.0, 250.0, 225.0, 205.0, 185.0, 177.0,
],
[
175.0, 165.0, 160.0, 155.0, 165.0, 188.0, 222.0, 250.0, 245.0, 230.0, 215.0, 205.0,
195.0, 185.0, 190.0, 205.0, 235.0, 265.0, 275.0, 255.0, 230.0, 210.0, 190.0, 180.0,
],
[
178.0, 168.0, 163.0, 158.0, 168.0, 192.0, 225.0, 253.0, 248.0, 233.0, 218.0, 208.0,
198.0, 188.0, 193.0, 208.0, 238.0, 268.0, 278.0, 258.0, 233.0, 213.0, 193.0, 183.0,
],
[
190.0, 180.0, 175.0, 170.0, 180.0, 205.0, 240.0, 268.0, 263.0, 248.0, 233.0, 223.0,
213.0, 203.0, 208.0, 223.0, 253.0, 283.0, 293.0, 273.0, 248.0, 228.0, 208.0, 198.0,
],
[
200.0, 190.0, 185.0, 180.0, 190.0, 215.0, 250.0, 280.0, 275.0, 260.0, 245.0, 235.0,
225.0, 215.0, 220.0, 235.0, 265.0, 295.0, 305.0, 285.0, 260.0, 240.0, 220.0, 210.0,
],
[
210.0, 200.0, 195.0, 190.0, 200.0, 225.0, 260.0, 290.0, 285.0, 270.0, 255.0, 245.0,
235.0, 225.0, 230.0, 245.0, 275.0, 305.0, 315.0, 295.0, 270.0, 250.0, 230.0, 215.0,
],
[
215.0, 205.0, 200.0, 195.0, 205.0, 230.0, 270.0, 300.0, 295.0, 280.0, 265.0, 255.0,
245.0, 235.0, 240.0, 255.0, 285.0, 315.0, 325.0, 305.0, 280.0, 260.0, 240.0, 225.0,
],
],
),
(
"us-east-1",
[
[
360.0, 345.0, 335.0, 330.0, 340.0, 360.0, 385.0, 405.0, 420.0, 430.0, 435.0, 440.0,
440.0, 445.0, 450.0, 445.0, 435.0, 420.0, 405.0, 390.0, 380.0, 375.0, 370.0, 365.0,
],
[
355.0, 340.0, 330.0, 325.0, 335.0, 355.0, 380.0, 400.0, 415.0, 425.0, 430.0, 435.0,
435.0, 440.0, 445.0, 440.0, 430.0, 415.0, 400.0, 385.0, 375.0, 370.0, 365.0, 360.0,
],
[
335.0, 320.0, 310.0, 305.0, 315.0, 335.0, 360.0, 380.0, 395.0, 405.0, 410.0, 415.0,
415.0, 420.0, 425.0, 420.0, 410.0, 395.0, 380.0, 365.0, 355.0, 350.0, 345.0, 340.0,
],
[
320.0, 305.0, 295.0, 290.0, 300.0, 320.0, 345.0, 365.0, 380.0, 390.0, 395.0, 400.0,
400.0, 405.0, 410.0, 405.0, 395.0, 380.0, 365.0, 350.0, 340.0, 335.0, 330.0, 325.0,
],
[
315.0, 300.0, 290.0, 285.0, 295.0, 315.0, 340.0, 360.0, 375.0, 385.0, 390.0, 395.0,
395.0, 400.0, 405.0, 400.0, 390.0, 375.0, 360.0, 345.0, 335.0, 330.0, 325.0, 320.0,
],
[
345.0, 330.0, 320.0, 315.0, 325.0, 345.0, 370.0, 390.0, 405.0, 420.0, 430.0, 435.0,
435.0, 440.0, 445.0, 440.0, 430.0, 415.0, 400.0, 385.0, 375.0, 370.0, 360.0, 350.0,
],
[
355.0, 340.0, 330.0, 325.0, 335.0, 355.0, 380.0, 400.0, 415.0, 430.0, 440.0, 445.0,
445.0, 450.0, 455.0, 450.0, 440.0, 425.0, 410.0, 395.0, 385.0, 380.0, 370.0, 360.0,
],
[
350.0, 335.0, 325.0, 320.0, 330.0, 350.0, 375.0, 395.0, 410.0, 425.0, 435.0, 440.0,
440.0, 445.0, 450.0, 445.0, 435.0, 420.0, 405.0, 390.0, 380.0, 375.0, 365.0, 355.0,
],
[
340.0, 325.0, 315.0, 310.0, 320.0, 340.0, 365.0, 385.0, 400.0, 412.0, 418.0, 422.0,
422.0, 426.0, 430.0, 426.0, 418.0, 402.0, 388.0, 375.0, 365.0, 358.0, 350.0, 345.0,
],
[
330.0, 315.0, 305.0, 300.0, 310.0, 330.0, 355.0, 375.0, 390.0, 400.0, 405.0, 410.0,
410.0, 415.0, 420.0, 415.0, 405.0, 390.0, 375.0, 360.0, 350.0, 345.0, 340.0, 335.0,
],
[
345.0, 330.0, 320.0, 315.0, 325.0, 345.0, 370.0, 390.0, 405.0, 415.0, 420.0, 425.0,
425.0, 430.0, 435.0, 430.0, 420.0, 405.0, 390.0, 375.0, 365.0, 360.0, 355.0, 350.0,
],
[
360.0, 345.0, 335.0, 330.0, 340.0, 360.0, 385.0, 405.0, 420.0, 430.0, 435.0, 440.0,
440.0, 445.0, 450.0, 445.0, 435.0, 420.0, 405.0, 390.0, 380.0, 375.0, 370.0, 365.0,
],
],
),
];
pub(crate) static FLAT_YEAR_PROFILES: &[(&str, [f64; 24])] = &[
(
"eu-west-1",
[
270.0, 260.0, 255.0, 250.0, 260.0, 275.0, 295.0, 315.0, 320.0, 315.0, 305.0, 295.0,
290.0, 285.0, 290.0, 300.0, 315.0, 335.0, 340.0, 325.0, 310.0, 295.0, 280.0, 275.0,
],
),
(
"eu-west-4",
[
300.0, 290.0, 285.0, 280.0, 290.0, 310.0, 335.0, 355.0, 360.0, 350.0, 340.0, 330.0,
320.0, 310.0, 315.0, 330.0, 350.0, 370.0, 375.0, 360.0, 345.0, 330.0, 315.0, 305.0,
],
),
(
"eu-north-1",
[
7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 8.0, 9.0, 9.0, 9.0, 9.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0,
9.0, 9.0, 9.0, 8.0, 8.0, 7.0, 7.0,
],
),
(
"europe-west1",
[
147.1, 141.8, 138.3, 135.7, 141.8, 153.2, 170.7, 183.8, 185.6, 179.4, 173.3, 166.3,
161.9, 157.6, 159.3, 166.3, 179.4, 190.8, 194.3, 185.6, 175.1, 166.3, 155.8, 150.6,
],
),
(
"europe-north1",
[
7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 8.0, 9.0, 9.0, 9.0, 9.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0,
9.0, 9.0, 9.0, 8.0, 8.0, 7.0, 7.0,
],
),
(
"eu-south-1",
[
345.0, 335.0, 330.0, 325.0, 335.0, 350.0, 375.0, 395.0, 390.0, 370.0, 350.0, 340.0,
335.0, 340.0, 350.0, 365.0, 385.0, 405.0, 415.0, 400.0, 385.0, 370.0, 358.0, 350.0,
],
),
(
"europe-southwest1",
[
195.0, 188.0, 184.0, 180.0, 184.0, 192.0, 210.0, 225.0, 215.0, 195.0, 178.0, 170.0,
168.0, 172.0, 180.0, 195.0, 215.0, 235.0, 240.0, 228.0, 215.0, 205.0, 200.0, 198.0,
],
),
(
"europe-central2",
[
660.0, 650.0, 645.0, 640.0, 650.0, 670.0, 705.0, 730.0, 735.0, 725.0, 715.0, 705.0,
695.0, 690.0, 695.0, 710.0, 730.0, 750.0, 755.0, 740.0, 720.0, 705.0, 685.0, 670.0,
],
),
(
"europe-north2",
[
6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 7.0, 7.0, 7.0, 7.0, 7.0, 8.0,
8.0, 8.0, 8.0, 7.0, 7.0, 7.0, 6.0,
],
),
(
"us-east-2",
[
375.0, 360.0, 350.0, 345.0, 355.0, 375.0, 400.0, 425.0, 435.0, 440.0, 445.0, 448.0,
448.0, 450.0, 452.0, 448.0, 440.0, 425.0, 412.0, 400.0, 390.0, 385.0, 382.0, 378.0,
],
),
(
"us-west-1",
[
215.0, 225.0, 238.0, 245.0, 242.0, 230.0, 218.0, 210.0, 200.0, 195.0, 192.0, 190.0,
188.0, 188.0, 190.0, 195.0, 188.0, 175.0, 162.0, 158.0, 160.0, 168.0, 185.0, 200.0,
],
),
(
"us-west-2",
[
82.0, 80.0, 78.0, 77.0, 80.0, 84.0, 90.0, 96.0, 98.0, 97.0, 95.0, 92.0, 90.0, 88.0,
87.0, 89.0, 93.0, 97.0, 98.0, 96.0, 93.0, 90.0, 86.0, 84.0,
],
),
(
"ca-central-1",
[
12.0, 12.0, 12.0, 12.0, 12.0, 12.0, 13.0, 14.0, 14.0, 14.0, 14.0, 13.0, 13.0, 13.0,
13.0, 13.0, 14.0, 14.0, 14.0, 14.0, 13.0, 13.0, 12.0, 12.0,
],
),
(
"ap-southeast-2",
[
530.0, 520.0, 510.0, 505.0, 515.0, 530.0, 545.0, 555.0, 560.0, 565.0, 570.0, 575.0,
575.0, 572.0, 568.0, 562.0, 555.0, 548.0, 545.0, 540.0, 542.0, 545.0, 540.0, 535.0,
],
),
(
"ap-northeast-1",
[
445.0, 440.0, 435.0, 430.0, 435.0, 445.0, 460.0, 475.0, 480.0, 485.0, 480.0, 475.0,
470.0, 465.0, 460.0, 455.0, 458.0, 462.0, 468.0, 472.0, 470.0, 465.0, 458.0, 450.0,
],
),
(
"ap-southeast-1",
[
395.0, 392.0, 390.0, 388.0, 392.0, 398.0, 408.0, 418.0, 420.0, 418.0, 415.0, 412.0,
410.0, 408.0, 406.0, 408.0, 412.0, 418.0, 420.0, 418.0, 415.0, 410.0, 402.0, 398.0,
],
),
(
"ap-south-1",
[
680.0, 672.0, 668.0, 665.0, 672.0, 688.0, 710.0, 728.0, 732.0, 728.0, 720.0, 712.0,
708.0, 705.0, 708.0, 715.0, 725.0, 738.0, 742.0, 735.0, 725.0, 715.0, 698.0, 688.0,
],
),
(
"sa-east-1",
[
89.8, 86.7, 85.2, 83.6, 86.7, 89.8, 96.0, 102.2, 103.7, 102.2, 100.6, 99.1, 97.5, 96.0,
94.5, 96.0, 99.1, 103.7, 105.3, 103.7, 100.6, 97.5, 92.9, 91.4,
],
),
];
pub(crate) static PROFILE_ALIASES: &[(&str, &str)] = &[
("fr", "eu-west-3"),
("francecentral", "eu-west-3"),
("europe-west9", "eu-west-3"),
("de", "eu-central-1"),
("gb", "eu-west-2"),
("uk", "eu-west-2"),
("uksouth", "eu-west-2"),
("ie", "eu-west-1"),
("northeurope", "eu-west-1"),
("nl", "eu-west-4"),
("westeurope", "eu-west-4"),
("europe-west4", "eu-west-4"),
("se", "eu-north-1"),
("be", "europe-west1"),
("fi", "europe-north1"),
("it", "eu-south-1"),
("europe-west8", "eu-south-1"),
("es", "europe-southwest1"),
("pl", "europe-central2"),
("no", "europe-north2"),
("us", "us-east-1"),
("eastus", "us-east-1"),
("us-east1", "us-east-1"),
("westus2", "us-west-2"),
("us-west1", "us-west-1"),
("ca", "ca-central-1"),
("au", "ap-southeast-2"),
("jp", "ap-northeast-1"),
("asia-northeast1", "ap-northeast-1"),
("sg", "ap-southeast-1"),
("in", "ap-south-1"),
("br", "sa-east-1"),
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profile_ref_flat_year_ignores_month() {
let data: &'static [f64; 24] = &[
10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0,
24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0,
];
let pr = HourlyProfileRef::FlatYear(data);
assert!((pr.intensity_at(0, None) - 10.0).abs() < f64::EPSILON);
assert!((pr.intensity_at(0, Some(6)) - 10.0).abs() < f64::EPSILON);
assert!((pr.intensity_at(23, Some(11)) - 33.0).abs() < f64::EPSILON);
}
static TEST_MONTHLY_TABLE: [[f64; 24]; 12] = {
let mut t = [[100.0; 24]; 12];
t[6] = [200.0; 24];
t
};
#[test]
fn profile_ref_monthly_uses_month() {
let pr = HourlyProfileRef::Monthly(&TEST_MONTHLY_TABLE);
assert!((pr.intensity_at(0, Some(0)) - 100.0).abs() < f64::EPSILON);
assert!((pr.intensity_at(0, Some(6)) - 200.0).abs() < f64::EPSILON);
}
#[test]
fn profile_ref_monthly_none_month_averages() {
let pr = HourlyProfileRef::Monthly(&TEST_MONTHLY_TABLE);
let expected = (11.0 * 100.0 + 200.0) / 12.0;
assert!((pr.intensity_at(0, None) - expected).abs() < 0.01);
}
#[test]
fn profile_ref_is_monthly() {
static DATA: [f64; 24] = [0.0; 24];
static TABLE: [[f64; 24]; 12] = [[0.0; 24]; 12];
assert!(!HourlyProfileRef::FlatYear(&DATA).is_monthly());
assert!(HourlyProfileRef::Monthly(&TABLE).is_monthly());
}
#[test]
fn hourly_profile_flat_year_mean() {
let profile = HourlyProfile::FlatYear([50.0; 24]);
assert!((profile.mean() - 50.0).abs() < f64::EPSILON);
}
#[test]
fn hourly_profile_monthly_mean() {
let mut months = [[100.0; 24]; 12];
months[6] = [200.0; 24];
let profile = HourlyProfile::Monthly(Box::new(months));
let expected = (11.0 * 100.0 + 200.0) / 12.0;
assert!((profile.mean() - expected).abs() < 0.01);
}
#[test]
fn all_flat_year_profiles_have_24_values() {
for (key, profile) in FLAT_YEAR_PROFILES {
assert_eq!(
profile.len(),
24,
"flat year profile for {key} must have 24 values"
);
}
}
#[test]
fn all_monthly_profiles_have_12x24_values() {
for (key, profile) in MONTHLY_PROFILES {
assert_eq!(
profile.len(),
12,
"monthly profile for {key} must have 12 months"
);
for (month_idx, month) in profile.iter().enumerate() {
assert_eq!(
month.len(),
24,
"monthly profile for {key} month {month_idx} must have 24 values"
);
}
}
}
#[test]
fn all_profile_values_are_finite_and_non_negative() {
for (key, profile) in FLAT_YEAR_PROFILES {
for (h, &val) in profile.iter().enumerate() {
assert!(
val.is_finite() && val >= 0.0,
"flat year {key} hour {h}: invalid value {val}"
);
}
}
for (key, months) in MONTHLY_PROFILES {
for (m, month) in months.iter().enumerate() {
for (h, &val) in month.iter().enumerate() {
assert!(
val.is_finite() && val >= 0.0,
"monthly {key} month {m} hour {h}: invalid value {val}"
);
}
}
}
}
#[test]
fn no_duplicate_keys_in_profiles() {
let mut keys = std::collections::HashSet::new();
for (key, _) in FLAT_YEAR_PROFILES {
assert!(
keys.insert(*key),
"duplicate key in FLAT_YEAR_PROFILES: {key}"
);
}
for (key, _) in MONTHLY_PROFILES {
assert!(
keys.insert(*key),
"duplicate key in MONTHLY_PROFILES: {key}"
);
}
}
#[test]
fn all_aliases_point_to_existing_canonical_keys() {
let mut canonical_keys = std::collections::HashSet::new();
for (key, _) in FLAT_YEAR_PROFILES {
canonical_keys.insert(*key);
}
for (key, _) in MONTHLY_PROFILES {
canonical_keys.insert(*key);
}
for &(alias, canonical) in PROFILE_ALIASES {
assert!(
canonical_keys.contains(canonical),
"alias '{alias}' points to non-existent canonical key '{canonical}'"
);
}
}
#[test]
fn no_duplicate_alias_keys() {
let mut seen = std::collections::HashSet::new();
for &(alias, _) in PROFILE_ALIASES {
assert!(
seen.insert(alias),
"duplicate alias key in PROFILE_ALIASES: {alias}"
);
}
}
#[test]
fn no_alias_shadows_canonical_key() {
let mut canonical_keys = std::collections::HashSet::new();
for (key, _) in FLAT_YEAR_PROFILES {
canonical_keys.insert(*key);
}
for (key, _) in MONTHLY_PROFILES {
canonical_keys.insert(*key);
}
for &(alias, _) in PROFILE_ALIASES {
assert!(
!canonical_keys.contains(alias),
"alias '{alias}' shadows a canonical profile key"
);
}
}
}