#[allow(dead_code)]
pub(crate) const CARBON_PROFILES_VINTAGE: &str = "2022-2024 shapes, ember-2025 levels";
#[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",
[
[
202.3, 192.9, 188.2, 183.5, 192.9, 216.5, 254.1, 282.3, 277.6, 263.5, 249.4, 240.0,
230.6, 221.2, 225.9, 240.0, 268.2, 296.5, 305.9, 287.0, 263.5, 244.7, 225.9, 211.8,
],
[
197.6, 188.2, 183.5, 178.8, 188.2, 211.8, 249.4, 277.6, 272.9, 258.8, 244.7, 235.3,
225.9, 216.5, 221.2, 235.3, 263.5, 291.7, 301.2, 282.3, 258.8, 240.0, 221.2, 207.0,
],
[
188.2, 178.8, 174.1, 169.4, 178.8, 202.3, 235.3, 263.5, 258.8, 244.7, 230.6, 221.2,
211.8, 202.3, 207.0, 221.2, 249.4, 277.6, 287.0, 268.2, 244.7, 225.9, 207.0, 197.6,
],
[
178.8, 169.4, 164.7, 160.0, 169.4, 192.9, 225.9, 254.1, 249.4, 235.3, 221.2, 211.8,
202.3, 192.9, 197.6, 211.8, 240.0, 268.2, 277.6, 258.8, 235.3, 216.5, 197.6, 188.2,
],
[
169.4, 160.0, 155.3, 150.6, 160.0, 183.5, 214.6, 240.0, 235.3, 221.2, 207.0, 197.6,
188.2, 178.8, 183.5, 197.6, 225.9, 254.1, 263.5, 244.7, 221.2, 202.3, 183.5, 174.1,
],
[
161.9, 152.5, 147.8, 143.1, 152.5, 174.1, 205.2, 230.6, 225.9, 211.8, 197.6, 188.2,
178.8, 169.4, 174.1, 188.2, 216.5, 244.7, 254.1, 235.3, 211.8, 192.9, 174.1, 166.6,
],
[
164.7, 155.3, 150.6, 145.9, 155.3, 176.9, 208.9, 235.3, 230.6, 216.5, 202.3, 192.9,
183.5, 174.1, 178.8, 192.9, 221.2, 249.4, 258.8, 240.0, 216.5, 197.6, 178.8, 169.4,
],
[
167.5, 158.1, 153.4, 148.7, 158.1, 180.7, 211.8, 238.1, 233.4, 219.3, 205.2, 195.8,
186.3, 176.9, 181.6, 195.8, 224.0, 252.2, 261.6, 242.8, 219.3, 200.5, 181.6, 172.2,
],
[
178.8, 169.4, 164.7, 160.0, 169.4, 192.9, 225.9, 252.2, 247.5, 233.4, 219.3, 209.9,
200.5, 191.0, 195.8, 209.9, 238.1, 266.3, 275.7, 256.9, 233.4, 214.6, 195.8, 186.3,
],
[
188.2, 178.8, 174.1, 169.4, 178.8, 202.3, 235.3, 263.5, 258.8, 244.7, 230.6, 221.2,
211.8, 202.3, 207.0, 221.2, 249.4, 277.6, 287.0, 268.2, 244.7, 225.9, 207.0, 197.6,
],
[
197.6, 188.2, 183.5, 178.8, 188.2, 211.8, 244.7, 272.9, 268.2, 254.1, 240.0, 230.6,
221.2, 211.8, 216.5, 230.6, 258.8, 287.0, 296.5, 277.6, 254.1, 235.3, 216.5, 202.3,
],
[
202.3, 192.9, 188.2, 183.5, 192.9, 216.5, 254.1, 282.3, 277.6, 263.5, 249.4, 240.0,
230.6, 221.2, 225.9, 240.0, 268.2, 296.5, 305.9, 287.0, 263.5, 244.7, 225.9, 211.8,
],
],
),
(
"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",
[
233.4, 224.8, 220.5, 216.1, 224.8, 237.7, 255.0, 272.3, 276.6, 272.3, 263.7, 255.0,
250.7, 246.4, 250.7, 259.4, 272.3, 289.6, 293.9, 281.0, 268.0, 255.0, 242.1, 237.7,
],
),
(
"eu-west-4",
[
232.0, 224.2, 220.4, 216.5, 224.2, 239.7, 259.0, 274.5, 278.3, 270.6, 262.9, 255.1,
247.4, 239.7, 243.5, 255.1, 270.6, 286.1, 289.9, 278.3, 266.7, 255.1, 243.5, 235.8,
],
),
(
"eu-north-1",
[
31.0, 31.0, 31.0, 31.0, 31.0, 31.0, 35.4, 39.8, 39.8, 39.8, 39.8, 35.4, 35.4, 35.4,
35.4, 35.4, 39.8, 39.8, 39.8, 39.8, 35.4, 35.4, 31.0, 31.0,
],
),
(
"europe-west1",
[
97.4, 93.9, 91.6, 89.9, 93.9, 101.5, 113.1, 121.8, 122.9, 118.8, 114.8, 110.2, 107.2,
104.4, 105.5, 110.2, 118.8, 126.4, 128.7, 122.9, 116.0, 110.2, 103.2, 99.8,
],
),
(
"europe-north1",
[
50.3, 50.3, 50.3, 50.3, 50.3, 50.3, 57.5, 64.7, 64.7, 64.7, 64.7, 57.5, 57.5, 57.5,
57.5, 57.5, 64.7, 64.7, 64.7, 64.7, 57.5, 57.5, 50.3, 50.3,
],
),
(
"eu-south-1",
[
265.4, 257.7, 253.8, 250.0, 257.7, 269.2, 288.4, 303.8, 300.0, 284.6, 269.2, 261.5,
257.7, 261.5, 269.2, 280.8, 296.1, 311.5, 319.2, 307.7, 296.1, 284.6, 275.4, 269.2,
],
),
(
"europe-southwest1",
[
149.8, 144.4, 141.3, 138.2, 141.3, 147.5, 161.3, 172.8, 165.1, 149.8, 136.7, 130.6,
129.0, 132.1, 138.2, 149.8, 165.1, 180.5, 184.3, 175.1, 165.1, 157.4, 153.6, 152.1,
],
),
(
"europe-central2",
[
557.0, 548.6, 544.4, 540.2, 548.6, 565.5, 595.0, 616.1, 620.3, 611.9, 603.5, 595.0,
586.6, 582.4, 586.6, 599.2, 616.1, 633.0, 637.2, 624.6, 607.7, 595.0, 578.1, 565.5,
],
),
(
"europe-north2",
[
24.1, 24.1, 24.1, 24.1, 24.1, 28.1, 28.1, 32.1, 32.1, 32.1, 32.1, 28.1, 28.1, 28.1,
28.1, 28.1, 32.1, 32.1, 32.1, 32.1, 28.1, 28.1, 28.1, 24.1,
],
),
(
"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",
[
481.3, 477.6, 475.2, 472.7, 477.6, 484.9, 497.1, 509.3, 511.7, 509.3, 505.6, 502.0,
499.5, 497.1, 494.7, 497.1, 502.0, 509.3, 511.7, 509.3, 505.6, 499.5, 489.8, 484.9,
],
),
(
"ap-south-1",
[
644.0, 636.4, 632.6, 629.8, 636.4, 651.6, 672.4, 689.4, 693.2, 689.4, 681.9, 674.3,
670.5, 667.7, 670.5, 677.1, 686.6, 698.9, 702.7, 696.1, 686.6, 677.1, 661.0, 651.6,
],
),
(
"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"
);
}
}
}