use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{fs::File, io::BufWriter};
use anyhow::Result;
use serde::Serialize;
use crate::bins::SolidAngleBin;
use crate::result::{Mueller, MuellerMatrix, Results};
use crate::settings::{OutputConfig, Settings};
use crate::zones::Zone;
pub trait OutputWriter {
fn write(&self, output_dir: &Path) -> Result<()>;
fn filename(&self) -> String;
fn is_enabled(&self, config: &OutputConfig) -> bool;
}
pub struct OutputManager<'a> {
pub settings: &'a Settings,
pub results: &'a Results,
}
impl<'a> OutputManager<'a> {
pub fn new(settings: &'a Settings, results: &'a Results) -> Self {
Self { settings, results }
}
pub fn write_all(&self) -> Result<()> {
let output_dir = &self.settings.directory;
fs::create_dir_all(output_dir)?;
let writers: Vec<Box<dyn OutputWriter>> = vec![
Box::new(SettingsJsonWriter::new(self.settings)),
Box::new(ConsolidatedResultsWriter::new(self.results)),
];
for writer in writers {
if writer.is_enabled(&self.settings.output) {
writer.write(output_dir)?;
}
}
self.write_mueller_matrices()?;
Ok(())
}
fn write_mueller_matrices(&self) -> Result<()> {
if !self.settings.output.mueller_2d && !self.settings.output.mueller_1d {
return Ok(());
}
let output_dir = &self.settings.directory;
let config = &self.settings.output.mueller_components;
for zone in self.results.zones.iter() {
let zone_dir = output_dir.join(zone.display_name());
fs::create_dir_all(&zone_dir)?;
self.write_zone_mueller(zone, &zone_dir, config)?;
}
Ok(())
}
fn write_zone_mueller(
&self,
zone: &Zone,
zone_dir: &Path,
config: &crate::settings::MuellerComponentConfig,
) -> Result<()> {
if self.settings.output.mueller_2d && !zone.field_2d.is_empty() {
let bins: Vec<_> = zone.bins.clone();
if config.total {
let muellers: Vec<_> = zone.field_2d.iter().map(|f| f.mueller_total).collect();
write_mueller(&bins, &muellers, "", zone_dir)?;
}
if config.beam {
let muellers: Vec<_> = zone.field_2d.iter().map(|f| f.mueller_beam).collect();
write_mueller(&bins, &muellers, "_beam", zone_dir)?;
}
if config.external {
let muellers: Vec<_> = zone.field_2d.iter().map(|f| f.mueller_ext).collect();
write_mueller(&bins, &muellers, "_ext", zone_dir)?;
}
}
if self.settings.output.mueller_1d {
if let Some(field_1d) = &zone.field_1d {
if config.total {
write_mueller_1d(
"",
field_1d,
&|r: &crate::result::ScattResult1D| r.mueller_total.clone(),
zone_dir,
)?;
}
if config.beam {
write_mueller_1d(
"_beam",
field_1d,
&|r: &crate::result::ScattResult1D| r.mueller_beam.clone(),
zone_dir,
)?;
}
if config.external {
write_mueller_1d(
"_ext",
field_1d,
&|r: &crate::result::ScattResult1D| r.mueller_ext.clone(),
zone_dir,
)?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_unique() {
let mut arr = vec![1.0, 1.0, 1.2, 2.0, 2.0, 3.0];
arr.sort_by(|a, b| a.partial_cmp(b).expect("NaN encountered"));
arr.dedup();
let expected = vec![1.0, 1.2, 2.0, 3.0];
assert_eq!(arr, expected);
let mut arr = vec![1.0, 1.2, 1.0, 3.0, 2.01, 2.0];
arr.sort_by(|a, b| a.partial_cmp(b).expect("NaN encountered"));
arr.dedup();
let expected = vec![1.0, 1.2, 2.0, 2.01, 3.0];
assert_eq!(arr, expected);
}
}
pub fn write_mueller(
bins: &[SolidAngleBin],
muellers: &[Mueller],
suffix: &str,
output_dir: &Path,
) -> Result<()> {
let file_name_total = format!("mueller_scatgrid{}", suffix);
let path_total = output_path(Some(output_dir), &file_name_total)?;
let file_total = File::create(&path_total)?;
let mut writer = BufWriter::new(file_total);
for (index, mueller) in muellers.iter().enumerate() {
let bin = bins[index];
write!(writer, "{} {} ", bin.theta.center, bin.phi.center)?;
for element in mueller.to_vec().into_iter() {
write!(writer, "{} ", element)?;
}
writeln!(writer)?;
}
Ok(())
}
pub fn write_mueller_1d<F>(
suffix: &str,
field_1d: &[crate::result::ScattResult1D],
mueller_getter: F,
output_dir: &Path,
) -> Result<()>
where
F: Fn(&crate::result::ScattResult1D) -> Mueller,
{
let file_name = format!("mueller_scatgrid_1d{}", suffix);
let path = output_path(Some(output_dir), &file_name)?;
let file = File::create(&path)?;
let mut writer = BufWriter::new(file);
for result in field_1d {
let bin = result.bin;
write!(writer, "{} ", bin.center)?;
let mueller = mueller_getter(result);
for element in mueller.to_vec() {
write!(writer, "{} ", element)?;
}
writeln!(writer)?;
}
Ok(())
}
pub fn output_path(output_dir: Option<&Path>, file_name: &str) -> Result<PathBuf> {
match output_dir {
Some(dir) => {
fs::create_dir_all(dir)?;
Ok(dir.join(file_name))
}
None => Ok(PathBuf::from(file_name)),
}
}
pub struct SettingsJsonWriter<'a> {
settings: &'a Settings,
}
impl<'a> SettingsJsonWriter<'a> {
pub fn new(settings: &'a Settings) -> Self {
Self { settings }
}
}
impl<'a> OutputWriter for SettingsJsonWriter<'a> {
fn write(&self, output_dir: &Path) -> Result<()> {
let path = output_path(Some(output_dir), &self.filename())?;
let json = serde_json::to_string_pretty(self.settings)?;
fs::write(path, json)?;
Ok(())
}
fn filename(&self) -> String {
"settings.json".to_string()
}
fn is_enabled(&self, config: &OutputConfig) -> bool {
config.settings_json
}
}
#[derive(Serialize)]
struct ZoneOutput {
label: String,
zone_type: String,
num_bins: usize,
params: crate::params::Params,
mueller_dir: String,
}
#[derive(Serialize)]
struct ConsolidatedResults {
powers: crate::powers::Powers,
zones: Vec<ZoneOutput>,
}
pub struct ConsolidatedResultsWriter<'a> {
results: &'a Results,
}
impl<'a> ConsolidatedResultsWriter<'a> {
pub fn new(results: &'a Results) -> Self {
Self { results }
}
}
impl<'a> OutputWriter for ConsolidatedResultsWriter<'a> {
fn write(&self, output_dir: &Path) -> Result<()> {
let zones: Vec<ZoneOutput> = self
.results
.zones
.iter()
.map(|zone| {
let label = zone.display_name();
ZoneOutput {
zone_type: format!("{:?}", zone.zone_type),
num_bins: zone.bins.len(),
params: zone.params.clone(),
mueller_dir: label.clone(),
label,
}
})
.collect();
let consolidated = ConsolidatedResults {
powers: self.results.powers.clone(),
zones,
};
let path = output_path(Some(output_dir), &self.filename())?;
let json = serde_json::to_string_pretty(&consolidated)?;
fs::write(path, json)?;
Ok(())
}
fn filename(&self) -> String {
"results.json".to_string()
}
fn is_enabled(&self, _config: &OutputConfig) -> bool {
true
}
}