use super::SpatialGrid;
use crate::errors::{RSCMError, RSCMResult};
use crate::timeseries::FloatValue;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FourBoxRegion {
NorthernOcean = 0,
NorthernLand = 1,
SouthernOcean = 2,
SouthernLand = 3,
}
impl From<FourBoxRegion> for usize {
fn from(r: FourBoxRegion) -> usize {
r as usize
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct FourBoxGrid {
weights: [FloatValue; 4],
}
impl FourBoxGrid {
pub fn magicc_standard() -> Self {
Self {
weights: [0.25, 0.25, 0.25, 0.25],
}
}
}
impl Default for FourBoxGrid {
fn default() -> Self {
Self::magicc_standard()
}
}
impl FourBoxGrid {
pub fn with_weights(weights: [FloatValue; 4]) -> Self {
let sum: FloatValue = weights.iter().sum();
assert!(
(sum - 1.0).abs() < 1e-6,
"Weights must sum to 1.0, got {}",
sum
);
let northern_sum = weights[FourBoxRegion::NorthernOcean as usize]
+ weights[FourBoxRegion::NorthernLand as usize];
let southern_sum = weights[FourBoxRegion::SouthernOcean as usize]
+ weights[FourBoxRegion::SouthernLand as usize];
assert!(
northern_sum > 1e-10,
"Northern hemisphere weights must be non-zero for hemispheric transformation, got {}",
northern_sum
);
assert!(
southern_sum > 1e-10,
"Southern hemisphere weights must be non-zero for hemispheric transformation, got {}",
southern_sum
);
Self { weights }
}
pub fn weights(&self) -> &[FloatValue; 4] {
&self.weights
}
}
impl SpatialGrid for FourBoxGrid {
fn grid_name(&self) -> &'static str {
"FourBox"
}
fn size(&self) -> usize {
4
}
fn region_names(&self) -> &[String] {
static NAMES: std::sync::OnceLock<Vec<String>> = std::sync::OnceLock::new();
NAMES.get_or_init(|| {
vec![
"Northern Ocean".to_string(),
"Northern Land".to_string(),
"Southern Ocean".to_string(),
"Southern Land".to_string(),
]
})
}
fn aggregate_global(&self, values: &[FloatValue]) -> FloatValue {
assert_eq!(
values.len(),
4,
"FourBoxGrid expects exactly 4 regional values"
);
values
.iter()
.zip(self.weights.iter())
.map(|(v, w)| v * w)
.sum()
}
fn transform_to<G: SpatialGrid>(
&self,
values: &[FloatValue],
target: &G,
) -> RSCMResult<Vec<FloatValue>> {
assert_eq!(
values.len(),
self.size(),
"Values length must match grid size"
);
match target.size() {
1 => {
Ok(vec![self.aggregate_global(values)])
}
2 => {
let no = FourBoxRegion::NorthernOcean as usize;
let nl = FourBoxRegion::NorthernLand as usize;
let so = FourBoxRegion::SouthernOcean as usize;
let sl = FourBoxRegion::SouthernLand as usize;
let northern = (values[no] * self.weights[no] + values[nl] * self.weights[nl])
/ (self.weights[no] + self.weights[nl]);
let southern = (values[so] * self.weights[so] + values[sl] * self.weights[sl])
/ (self.weights[so] + self.weights[sl]);
Ok(vec![northern, southern])
}
4 => {
Ok(values.to_vec())
}
_ => Err(RSCMError::UnsupportedGridTransformation {
from: self.grid_name().to_string(),
to: target.grid_name().to_string(),
}),
}
}
}