poasta 0.1.0

Fast, optimal, gap-affine partial order alignment
Documentation
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::sync::mpsc;
use std::thread::JoinHandle;

use crate::errors::PoastaError;

pub mod messages {
    use petgraph::graph::IndexType;
    use serde::{Deserialize, Serialize};
    use crate::aligner::astar::AstarVisited;
    use crate::aligner::offsets::OffsetType;
    use crate::graphs::NodeIndexType;
    use crate::graphs::poa::POAGraph;

    #[derive(Debug, Serialize, Deserialize)]
    pub enum DebugOutputMessage {
        Empty,
        NewSequence { seq_name: String, sequence: String, max_rank: usize },
        IntermediateGraph { graph_dot: String },
        AstarData { visited_tsv: String },
        Terminate,
    }

    impl DebugOutputMessage {
        pub fn new_from_graph<GIx>(graph: &POAGraph<GIx>) -> Self
        where
            GIx: IndexType
        {
            Self::IntermediateGraph { graph_dot: format!("{}", graph) }
        }

        pub fn new_from_astar_data<D, N, O>(data: &D) -> Self
            where D: AstarVisited<N, O>,
                  N: NodeIndexType,
                  O: OffsetType,
        {
            let mut tsv = String::new();
            data.write_tsv(&mut tsv).unwrap();
            Self::AstarData { visited_tsv: tsv }
        }
    }
}

pub struct DebugOutputWriter {
    transmitter: mpsc::Sender<messages::DebugOutputMessage>,
    worker: DebugOutputWorker
}

impl DebugOutputWriter {
    pub fn init<T: AsRef<Path> + Send>(debug_output_dir: T) -> Self {
        let (tx, rx) = mpsc::channel();

        Self { transmitter: tx, worker: DebugOutputWorker::new(debug_output_dir, rx) }
    }

    pub fn log(&self, msg: messages::DebugOutputMessage) {
        if let Err(e) = self.transmitter.send(msg) {
            eprintln!("Could not log debug message!\nCause:{}", e)
        }
    }

    pub fn join(self) -> Result<(), PoastaError> {
        self.worker.join()
    }
}

struct DebugOutputWorker {
    thread: JoinHandle<Result<(), PoastaError>>,
}

impl DebugOutputWorker {
    fn new<T: AsRef<Path> + Send>(debug_output_dir: T, receiver: mpsc::Receiver<messages::DebugOutputMessage>) -> Self {
        let output_path = debug_output_dir.as_ref().to_path_buf();
        Self { thread: std::thread::spawn(move || {
            eprintln!("DEBUG: log output directory {:?}", output_path);
            std::fs::create_dir_all(output_path.join("astar_iterations"))?;

            let mut curr_seq_name = "none".to_string();
            let mut curr_seq = "".to_string();
            let mut curr_max_rank = 0;
            let mut curr_astar_iter = 0usize;

            for msg in receiver {
                match msg {
                    messages::DebugOutputMessage::Empty => (),
                    messages::DebugOutputMessage::NewSequence { seq_name , sequence, max_rank} => {
                        curr_seq_name = seq_name;
                        curr_seq = sequence;
                        curr_max_rank = max_rank;
                        curr_astar_iter = 0;
                    },
                    messages::DebugOutputMessage::IntermediateGraph { graph_dot } => {
                        let fname = output_path.join(format!("graph_for_{}.dot", &curr_seq_name));
                        let mut dot_file = File::create(fname)?;
                        write!(dot_file, "{}", graph_dot)?;
                        dot_file.flush()?;
                    },
                    messages::DebugOutputMessage::AstarData { visited_tsv } => {
                        let fname = output_path.join(format!("astar_iterations/{curr_seq_name}.iter{curr_astar_iter}.tsv"));
                        let mut tsv_file = File::create(fname)?;
                        writeln!(tsv_file, "# seq_name: {curr_seq_name} - seq: {curr_seq} - max_rank: {curr_max_rank}")?;
                        tsv_file.write_all(visited_tsv.as_bytes())?;
                        tsv_file.flush()?;
                        curr_astar_iter += 1;
                    }
                    messages::DebugOutputMessage::Terminate => break
                }
            }

            Ok(())
        })}
    }

    fn join(self) -> Result<(), PoastaError> {
        self.thread.join().unwrap()
    }
}