epanet-rs 0.2.2

A fast, modern and safe re-implementation of the EPANET2 hydraulic solver, written in Rust.
Documentation
//! Serializers for simulation results to JSON and MessagePack.

use std::fs::File;
use std::io::BufWriter;

use crate::model::network::Network;
use crate::model::units::UnitConversion;
use crate::solver::result::SolverResult;

use crate::io::inp::write_inp;

use rmp_serde::Serializer;
use serde::Serialize;

#[derive(Serialize)]
struct JsonOutput {
    nodes: Vec<String>,
    links: Vec<String>,
    heads: Vec<Vec<f64>>,
    flows: Vec<Vec<f64>>,
}

const DIGITS: usize = 3;

// helper function to round to a given number of digits (prevent JSON file bloat due to floating point precision)
fn round_to_digits(value: f64, digits: usize) -> f64 {
    let factor = 10.0_f64.powi(digits as i32);
    (value * factor).round() / factor
}

impl Network {
    pub fn write_results(&self, results: &SolverResult, file: &str) -> Result<(), String> {
        // get file extension
        let file_extension = file.split('.').next_back().unwrap();

        let file =
            File::create(file).map_err(|e| format!("Failed to create output file: {}", e))?;
        let writer = BufWriter::new(file);

        let nodes = self.nodes.iter().map(|n| n.id.to_string()).collect();
        let links = self.links.iter().map(|l| l.id.to_string()).collect();

        let heads = results
            .heads
            .iter()
            .map(|h| h.iter().map(|h| round_to_digits(*h, DIGITS)).collect())
            .collect();
        let flows = results
            .flows
            .iter()
            .map(|f| f.iter().map(|f| round_to_digits(*f, DIGITS)).collect())
            .collect();

        let output = JsonOutput {
            nodes,
            links,
            heads,
            flows,
        };
        if file_extension == "json" {
            serde_json::to_writer(writer, &output)
                .map_err(|e| format!("Failed to write results to file: {}", e))?;
        } else if file_extension == "mpk" || file_extension == "msgpack" {
            let mut serializer = Serializer::new(writer);
            output
                .serialize(&mut serializer)
                .map_err(|e| format!("Failed to write results to file: {}", e))?;
        } else {
            return Err(format!("Unsupported file extension: {}", file_extension));
        }

        Ok(())
    }

    pub fn save_network(&self, file: &str) -> Result<(), String> {
        // clone the network and convert the units to standard units
        let mut network = self.clone();

        // convert the units back to their original units
        for node in network.nodes.iter_mut() {
            node.convert_from_standard(&network.options);
        }
        for link in network.links.iter_mut() {
            link.convert_from_standard(&network.options);
        }
        for control in network.controls.iter_mut() {
            if let Some(link_index) = network.link_map.get(&control.link_id).copied() {
                control.convert_setting_from_standard(&network.links[link_index], &network.options);
            }
        }
        for control in network.controls.iter_mut() {
            control.convert_from_standard(&network.options);
        }
        network.options.convert_from_standard();

        let file_extension = file.split('.').next_back().unwrap();
        let file =
            File::create(file).map_err(|e| format!("Failed to create network file: {}", e))?;
        let writer = BufWriter::new(file);

        if file_extension == "inp" {
            write_inp(&network, writer)
                .map_err(|e| format!("Failed to write network to file: {}", e))?;
        } else if file_extension == "json" {
            serde_json::to_writer(writer, &network)
                .map_err(|e| format!("Failed to write network to file: {}", e))?;
        } else if file_extension == "mpk" || file_extension == "msgpack" {
            let mut serializer = Serializer::new(writer);
            network
                .serialize(&mut serializer)
                .map_err(|e| format!("Failed to write network to file: {}", e))?;
        } else {
            return Err(format!("Unsupported file extension: {}", file_extension));
        }
        Ok(())
    }
}