use std::{
env::{self, VarError},
fs, io,
path::{Path, PathBuf},
};
use strum::IntoEnumIterator;
use crate::cfd::CfdCaseError;
use super::{Azimuth, CfdCase, Enclosure, WindSpeed, ZenithAngle};
#[derive(Debug, thiserror::Error)]
pub enum BaselineError {
#[error(r#""CFD_CASE" env var is not set"#)]
Env(#[from] VarError),
#[error(r#"neither "CFD_REPO", "CFD_2020_REPO", "CFD_2021_REPO" nor "CFD_2025_REPO" environment variable is set"#)]
Repo(#[source] VarError),
#[error("{0}")]
ReadFile(#[source] io::Error, String),
#[error("CFD case error")]
CfdCase(#[from] CfdCaseError),
}
type Result<T> = std::result::Result<T, BaselineError>;
#[derive(Debug)]
pub struct Baseline<const YEAR: u32>(Vec<CfdCase<YEAR>>);
impl<const YEAR: u32> Baseline<YEAR> {
pub fn from_env() -> Result<Self> {
let filename = env::var("CFD_CASES")?;
let contents =
fs::read_to_string(&filename).map_err(|e| BaselineError::ReadFile(e, filename))?;
let items = contents
.lines()
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.map(|case| CfdCase::try_from(case.as_str()))
.collect::<std::result::Result<Vec<CfdCase<YEAR>>, CfdCaseError>>()?;
Ok(Self(items))
}
}
impl<const YEAR: u32> From<Vec<CfdCase<YEAR>>> for Baseline<YEAR> {
fn from(cfd_cases: Vec<CfdCase<YEAR>>) -> Self {
Baseline::<YEAR>(cfd_cases)
}
}
impl<const YEAR: u32> FromIterator<CfdCase<YEAR>> for Baseline<YEAR> {
fn from_iter<T: IntoIterator<Item = CfdCase<YEAR>>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<const YEAR: u32> Default for Baseline<YEAR> {
fn default() -> Self {
Self(
ZenithAngle::iter()
.flat_map(|zenith_angle| <Self as BaselineTrait<YEAR>>::at_zenith(zenith_angle).0)
.collect(),
)
}
}
impl<const YEAR: u32> IntoIterator for Baseline<YEAR> {
type Item = CfdCase<YEAR>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
if cfg!(feature = "xcase") {
self.0
.into_iter()
.filter(|c| {
!(*c == CfdCase::new(
ZenithAngle::Zero,
Azimuth::Ninety,
Enclosure::ClosedDeployed,
WindSpeed::Seventeen,
) || *c
== CfdCase::new(
ZenithAngle::Zero,
Azimuth::OneEighty,
Enclosure::ClosedDeployed,
WindSpeed::Seven,
)
|| *c
== CfdCase::new(
ZenithAngle::Sixty,
Azimuth::Zero,
Enclosure::OpenStowed,
WindSpeed::Two,
))
})
.collect::<Vec<CfdCase<YEAR>>>()
} else {
self.0
}
.into_iter()
}
}
pub trait BaselineTrait<const YEAR: u32>:
Default + From<Vec<CfdCase<YEAR>>> + IntoIterator<Item = CfdCase<YEAR>>
{
fn path() -> Result<PathBuf> {
env::var(format!("CFD_{}_REPO", YEAR))
.or_else(|_| env::var("CFD_REPO"))
.map(|p| Path::new(&p).to_path_buf())
.map_err(|e| BaselineError::Repo(e))
}
fn configuration(zenith_angle: ZenithAngle) -> Vec<(WindSpeed, Enclosure)> {
match YEAR {
2020 => vec![
(WindSpeed::Two, Enclosure::OpenStowed),
(WindSpeed::Seven, Enclosure::OpenStowed),
(WindSpeed::Twelve, Enclosure::ClosedDeployed),
(WindSpeed::Seventeen, Enclosure::ClosedDeployed),
],
2021 | 2025 => match zenith_angle {
ZenithAngle::Sixty => vec![
(WindSpeed::Two, Enclosure::OpenStowed),
(WindSpeed::Seven, Enclosure::OpenStowed),
(WindSpeed::Twelve, Enclosure::ClosedStowed),
(WindSpeed::Seventeen, Enclosure::ClosedStowed),
],
_ => vec![
(WindSpeed::Two, Enclosure::OpenStowed),
(WindSpeed::Seven, Enclosure::OpenStowed),
(WindSpeed::Twelve, Enclosure::ClosedDeployed),
(WindSpeed::Seventeen, Enclosure::ClosedDeployed),
],
},
_ => vec![],
}
}
fn at_zenith(zenith_angle: ZenithAngle) -> Self {
let mut cfd_cases = vec![];
for (wind_speed, enclosure) in Self::configuration(zenith_angle.clone()) {
for azimuth in Azimuth::iter() {
cfd_cases.push(CfdCase::<YEAR>::new(
zenith_angle.clone(),
azimuth,
enclosure.clone(),
wind_speed.clone(),
));
}
}
cfd_cases.into()
}
fn find<const OTHER_YEAR: u32>(cfd_case_21: CfdCase<OTHER_YEAR>) -> Option<CfdCase<YEAR>> {
let CfdCase {
zenith,
azimuth,
enclosure,
wind_speed,
} = cfd_case_21;
Some(CfdCase {
zenith,
azimuth,
enclosure,
wind_speed,
})
}
}
impl<const YEAR: u32> BaselineTrait<YEAR> for Baseline<YEAR> {}
impl<const CFD_YEAR: u32> Baseline<CFD_YEAR> {
pub fn mount() -> Self {
Self(
WindSpeed::iter()
.take(3)
.filter_map(|wind_speed| match wind_speed {
WindSpeed::Two => Some(
Azimuth::iter()
.take(3)
.map(|azimuth| {
CfdCase::new(
ZenithAngle::Thirty,
azimuth,
Enclosure::OpenStowed,
wind_speed,
)
})
.collect::<Vec<CfdCase<CFD_YEAR>>>(),
),
WindSpeed::Seven => Some(
Azimuth::iter()
.take(4)
.map(|azimuth| {
CfdCase::new(
ZenithAngle::Thirty,
azimuth,
Enclosure::OpenStowed,
wind_speed,
)
})
.collect::<Vec<CfdCase<CFD_YEAR>>>(),
),
WindSpeed::Twelve => Some(
Azimuth::iter()
.filter(|azimuth| *azimuth != Azimuth::OneThirtyFive)
.map(|azimuth| {
CfdCase::new(
ZenithAngle::Thirty,
azimuth,
Enclosure::ClosedDeployed,
wind_speed,
)
})
.collect::<Vec<CfdCase<CFD_YEAR>>>(),
),
_ => None,
})
.flatten()
.collect::<Vec<CfdCase<CFD_YEAR>>>(),
)
}
pub fn redo() -> Self {
Self(vec![
CfdCase::new(
ZenithAngle::Zero,
Azimuth::Ninety,
Enclosure::ClosedDeployed,
WindSpeed::Seventeen,
),
CfdCase::new(
ZenithAngle::Zero,
Azimuth::OneEighty,
Enclosure::ClosedDeployed,
WindSpeed::Seven,
),
CfdCase::new(
ZenithAngle::Sixty,
Azimuth::Zero,
Enclosure::OpenStowed,
WindSpeed::Two,
),
])
}
pub fn thbound2() -> Self {
Self(vec![
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::FortyFive,
Enclosure::ClosedDeployed,
WindSpeed::Seven,
),
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::FortyFive,
Enclosure::OpenStowed,
WindSpeed::Seven,
),
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::OneThirtyFive,
Enclosure::ClosedDeployed,
WindSpeed::Seven,
),
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::OneThirtyFive,
Enclosure::OpenStowed,
WindSpeed::Seven,
),
])
}
pub fn extras(self) -> Self {
let mut cases = self.0;
cases.append(&mut vec![
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::Zero,
Enclosure::ClosedDeployed,
WindSpeed::TwentyTwo,
),
CfdCase::new(
ZenithAngle::Thirty,
Azimuth::FortyFive,
Enclosure::ClosedDeployed,
WindSpeed::TwentyTwo,
),
]);
Self(cases)
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use super::*;
#[test]
fn baseline() -> std::result::Result<(), Box<dyn Error>> {
let cases: Vec<_> = Baseline::<2025>::from_env()?
.into_iter()
.collect();
println!("{cases:?}");
Ok(())
}
}