use scirs2_io::csv::{read_csv_typed, write_csv_typed, ColumnType, CsvReaderConfig, DataValue};
use statrs::statistics::Statistics;
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::{BufWriter, Write};
#[allow(dead_code)]
fn main() -> Result<(), Box<dyn Error>> {
println!("=== Scientific Data CSV Example ===\n");
let output_dir = env::var("SCIRS2_EXAMPLE_OUTPUT_DIR")
.unwrap_or_else(|_| env::temp_dir().to_string_lossy().to_string());
create_sample_scientific_data(&output_dir)?;
read_scientific_data_with_metadata(&output_dir)?;
convert_scientific_data_with_units(&output_dir)?;
process_time_series_data(&output_dir)?;
println!("\nScientific CSV example completed successfully!");
Ok(())
}
#[allow(dead_code)]
fn create_sample_scientific_data(output_dir: &str) -> Result<(), Box<dyn Error>> {
println!("Creating sample scientific data file...");
let file_path = format!("{}/scirs2_scientific_data.csv", output_dir);
let file = File::create(&file_path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "# Title: Sample Scientific Experiment Data")?;
writeln!(writer, "# Author: SciRS2 Team")?;
writeln!(writer, "# Date: 2025-04-10")?;
writeln!(
writer,
"# Description: Temperature measurements across different materials"
)?;
writeln!(writer, "# Units:")?;
writeln!(writer, "# Time: seconds (s)")?;
writeln!(writer, "# Temperature: Celsius (°C)")?;
writeln!(writer, "# Pressure: kilopascals (kPa)")?;
writeln!(writer, "# Concentration: moles per liter (mol/L)")?;
writeln!(writer, "# Energy: joules (J)")?;
writeln!(writer, "# Experiment ID: EXP-20250410-001")?;
writeln!(writer, "# Instrument: HighPrecThermometer-5000")?;
writeln!(
writer,
"Time,Temperature,Pressure,Concentration,Energy,Material,Valid"
)?;
writeln!(writer, "0.0,25.0,101.3,0.5,0.0,\"Aluminum\",true")?;
writeln!(writer, "0.5,26.2,101.4,0.48,125.5,\"Aluminum\",true")?;
writeln!(writer, "1.0,27.5,101.6,0.45,250.0,\"Aluminum\",true")?;
writeln!(writer, "1.5,28.7,101.7,0.43,374.5,\"Aluminum\",true")?;
writeln!(writer, "2.0,30.0,101.9,0.40,500.0,\"Aluminum\",true")?;
writeln!(writer, "0.0,25.0,101.3,0.5,0.0,\"Copper\",true")?;
writeln!(writer, "0.5,28.1,101.4,0.47,225.5,\"Copper\",true")?;
writeln!(writer, "1.0,32.5,101.6,0.42,450.0,\"Copper\",true")?;
writeln!(writer, "1.5,36.7,101.7,0.38,674.5,\"Copper\",true")?;
writeln!(writer, "2.0,40.0,101.9,0.35,900.0,\"Copper\",true")?;
writeln!(writer, "0.0,25.0,101.3,0.5,0.0,\"Glass\",true")?;
writeln!(writer, "0.5,25.3,101.4,0.49,75.5,\"Glass\",true")?;
writeln!(writer, "1.0,25.8,101.6,0.48,150.0,\"Glass\",true")?;
writeln!(writer, "1.5,26.2,101.7,0.47,224.5,\"Glass\",true")?;
writeln!(writer, "2.0,26.5,101.9,0.46,300.0,\"Glass\",true")?;
writer.flush()?;
println!("Sample scientific data created at {}", file_path);
Ok(())
}
#[allow(dead_code)]
fn extract_metadata_from_file(_filepath: &str) -> Result<HashMap<String, String>, Box<dyn Error>> {
let file = File::open(_filepath)?;
let reader = std::io::BufReader::new(file);
let mut metadata = HashMap::new();
for line in std::io::BufRead::lines(reader) {
let line = line?;
if line.starts_with('#') {
if let Some(pos) = line.find(':') {
let key = line[1..pos].trim().to_string();
let value = line[pos + 1..].trim().to_string();
metadata.insert(key, value);
}
} else {
break;
}
}
Ok(metadata)
}
#[allow(dead_code)]
fn read_scientific_data_with_metadata(output_dir: &str) -> Result<(), Box<dyn Error>> {
println!("\nReading scientific data file with metadata...");
let file_path = format!("{}/scirs2_scientific_data.csv", output_dir);
let metadata = extract_metadata_from_file(&file_path)?;
println!("Metadata:");
for (key, value) in &metadata {
println!(" {}: {}", key, value);
}
let config = CsvReaderConfig {
comment_char: Some('#'),
has_header: true,
..Default::default()
};
let col_types = vec![
ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::String, ColumnType::Boolean, ];
let (_headers, data) = read_csv_typed(&file_path, Some(config), Some(&col_types), None)?;
println!("\nData headers: Time, Temperature, Pressure, Concentration, Energy, Material, Valid");
println!("Number of data rows: {}", data.len());
let mut temp_stats = ColumnStats::new("Temperature");
let mut pressure_stats = ColumnStats::new("Pressure");
let mut energy_stats = ColumnStats::new("Energy");
for row in &data {
if let DataValue::Float(temp) = row[1] {
temp_stats.add_value(temp);
}
if let DataValue::Float(pressure) = row[2] {
pressure_stats.add_value(pressure);
}
if let DataValue::Float(energy) = row[4] {
energy_stats.add_value(energy);
}
}
println!("\nStatistical summary:");
println!("{}", temp_stats);
println!("{}", pressure_stats);
println!("{}", energy_stats);
let mut materials = HashMap::new();
for row in &data {
if let DataValue::String(material) = &row[5] {
materials
.entry(material.clone())
.or_insert_with(Vec::new)
.push(row.clone());
}
}
println!("\nData by material:");
for (material, rows) in &materials {
println!(" {}: {} measurements", material, rows.len());
let mut total_rate = 0.0;
let mut count = 0;
for i in 1..rows.len() {
if let (
DataValue::Float(t0),
DataValue::Float(temp0),
DataValue::Float(t1),
DataValue::Float(temp1),
) = (&rows[i - 1][0], &rows[i - 1][1], &rows[i][0], &rows[i][1])
{
let time_diff = t1 - t0;
if time_diff > 0.0 {
let temp_rate = (temp1 - temp0) / time_diff;
total_rate += temp_rate;
count += 1;
}
}
}
if count > 0 {
let avg_rate = total_rate / count as f64;
println!(" Average temperature change rate: {:.2} °C/s", avg_rate);
}
}
Ok(())
}
#[allow(dead_code)]
fn convert_scientific_data_with_units(output_dir: &str) -> Result<(), Box<dyn Error>> {
println!("\nConverting scientific data with unit conversions...");
let file_path = format!("{}/scirs2_scientific_data.csv", output_dir);
let config = CsvReaderConfig {
comment_char: Some('#'),
has_header: true,
..Default::default()
};
let col_types = vec![
ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::String, ColumnType::Boolean, ];
let (_headers, data) = read_csv_typed(&file_path, Some(config), Some(&col_types), None)?;
let mut converted_data = Vec::new();
let new_headers = vec![
"Time (min)".to_string(), "Temperature (K)".to_string(), "Pressure (atm)".to_string(), "Concentration (mmol/L)".to_string(), "Energy (cal)".to_string(), "Material".to_string(), "Valid".to_string(), ];
for row in data {
let mut new_row = Vec::new();
if let DataValue::Float(time) = row[0] {
new_row.push(DataValue::Float(time / 60.0));
} else {
new_row.push(row[0].clone());
}
if let DataValue::Float(temp) = row[1] {
new_row.push(DataValue::Float(temp + 273.15));
} else {
new_row.push(row[1].clone());
}
if let DataValue::Float(pressure) = row[2] {
new_row.push(DataValue::Float(pressure / 101.325));
} else {
new_row.push(row[2].clone());
}
if let DataValue::Float(conc) = row[3] {
new_row.push(DataValue::Float(conc * 1000.0));
} else {
new_row.push(row[3].clone());
}
if let DataValue::Float(energy) = row[4] {
new_row.push(DataValue::Float(energy / 4.184));
} else {
new_row.push(row[4].clone());
}
for item in row.iter().skip(5) {
new_row.push(item.clone());
}
converted_data.push(new_row);
}
let output_path = format!("{}/scirs2_scientific_data_converted.csv", output_dir);
write_csv_typed(&output_path, &converted_data, Some(&new_headers), None)?;
println!("Converted data written to {}", output_path);
println!("Unit conversions performed:");
println!(" - Time: seconds -> minutes");
println!(" - Temperature: Celsius -> Kelvin");
println!(" - Pressure: kilopascals -> atmospheres");
println!(" - Concentration: mol/L -> mmol/L");
println!(" - Energy: joules -> calories");
Ok(())
}
#[allow(dead_code)]
fn process_time_series_data(output_dir: &str) -> Result<(), Box<dyn Error>> {
println!("\nProcessing time series data...");
let file_path = format!("{}/scirs2_scientific_data.csv", output_dir);
let config = CsvReaderConfig {
comment_char: Some('#'),
has_header: true,
..Default::default()
};
let col_types = vec![
ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::Float, ColumnType::String, ColumnType::Boolean, ];
let (_headers, data) = read_csv_typed(&file_path, Some(config), Some(&col_types), None)?;
let mut time_series = HashMap::new();
for row in &data {
if let (DataValue::String(material), DataValue::Float(time), DataValue::Float(temp)) =
(&row[5], &row[0], &row[1])
{
time_series
.entry(material.clone())
.or_insert_with(Vec::new)
.push((*time, *temp));
}
}
for (_, series) in time_series.iter_mut() {
series.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
}
let mut derived_data = Vec::new();
for (material, series) in &time_series {
let mut times = Vec::new();
let mut temps = Vec::new();
let mut rates = Vec::new();
let mut avg_temps = Vec::new();
for (i, &(time, temp)) in series.iter().enumerate() {
times.push(time);
temps.push(temp);
if i > 0 {
let prev_time = series[i - 1].0;
let prev_temp = series[i - 1].1;
let time_diff = time - prev_time;
if time_diff > 0.0 {
let rate = (temp - prev_temp) / time_diff;
rates.push(rate);
} else {
rates.push(0.0);
}
} else {
rates.push(0.0); }
if i >= 1 && i < series.len() - 1 {
let avg = (series[i - 1].1 + temp + series[i + 1].1) / 3.0;
avg_temps.push(avg);
} else {
avg_temps.push(temp); }
}
for i in 0..times.len() {
derived_data.push(vec![
DataValue::Float(times[i]),
DataValue::String(material.clone()),
DataValue::Float(temps[i]),
DataValue::Float(rates[i]),
DataValue::Float(avg_temps[i]),
]);
}
}
let derived_headers = vec![
"Time (s)".to_string(),
"Material".to_string(),
"Temperature (°C)".to_string(),
"Temperature Rate (°C/s)".to_string(),
"Temperature Moving Avg (°C)".to_string(),
];
let output_path = format!("{}/scirs2_scientific_data_derived.csv", output_dir);
write_csv_typed(&output_path, &derived_data, Some(&derived_headers), None)?;
println!("Derived time series data written to {}", output_path);
println!("Derived measurements include:");
println!(" - Original temperature values");
println!(" - Rate of temperature change (°C/s)");
println!(" - 3-point centered moving average of temperature");
Ok(())
}
struct ColumnStats {
name: String,
count: usize,
min: f64,
max: f64,
sum: f64,
sum_squared: f64,
}
impl ColumnStats {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
count: 0,
min: f64::INFINITY,
max: f64::NEG_INFINITY,
sum: 0.0,
sum_squared: 0.0,
}
}
fn add_value(&mut self, value: f64) {
self.count += 1;
self.min = self.min.min(value);
self.max = self.max.max(value);
self.sum += value;
self.sum_squared += value * value;
}
fn mean(&self) -> f64 {
if self.count > 0 {
self.sum / self.count as f64
} else {
0.0
}
}
fn variance(&self) -> f64 {
if self.count > 1 {
let mean = self.mean();
self.sum_squared / self.count as f64 - mean * mean
} else {
0.0
}
}
fn std_dev(&self) -> f64 {
self.variance().sqrt()
}
}
impl std::fmt::Display for ColumnStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
" {}: count={}, min={:.2}, max={:.2}, mean={:.2}, std_dev={:.2}",
self.name,
self.count,
self.min,
self.max,
self.mean(),
self.std_dev()
)
}
}