use std::fs::File;
use std::io::{BufWriter, Write as _};
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use rand::SeedableRng as _;
use super::command::Command;
use super::common::{ReferenceOptions, SeedOptions, VcfOptions};
#[derive(Parser, Debug)]
#[command(after_long_help = "EXAMPLES:\n \
holodeck methylate -r ref.fa -o meth.vcf.gz\n \
holodeck methylate -r ref.fa -v variants.vcf -o meth.vcf.gz --seed 42\n \
holodeck methylate -r ref.fa -o meth.vcf.gz --methylation-rate-open-sea 0.9")]
pub struct Methylate {
#[command(flatten)]
pub reference: ReferenceOptions,
#[command(flatten)]
pub vcf: VcfOptions,
#[command(flatten)]
pub seed: SeedOptions,
#[arg(long, default_value_t = crate::meth::DEFAULT_ISLAND_RATE, value_name = "FLOAT")]
pub methylation_rate_island: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_SHORE_RATE, value_name = "FLOAT")]
pub methylation_rate_shore: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_OPEN_SEA_RATE, value_name = "FLOAT")]
pub methylation_rate_open_sea: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_CORRELATION_LENGTH_BP, value_name = "BP")]
pub methylation_correlation_length_island: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_CORRELATION_LENGTH_BP, value_name = "BP")]
pub methylation_correlation_length_shore: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_CORRELATION_LENGTH_BP, value_name = "BP")]
pub methylation_correlation_length_open_sea: f64,
#[arg(long, default_value_t = crate::meth::DEFAULT_HEMI_RATE, value_name = "FLOAT")]
pub hemimethylation_rate: f64,
#[arg(long, short = 'o', value_name = "PATH")]
pub output: PathBuf,
#[arg(long, value_name = "PATH")]
pub bedgraph: Option<PathBuf>,
}
impl Methylate {
fn methylation_model(&self) -> crate::meth::MethylationModel {
use crate::meth::{ContextParams, MethylationModel};
MethylationModel {
island: ContextParams {
rate: self.methylation_rate_island,
correlation_length_bp: self.methylation_correlation_length_island,
},
shore: ContextParams {
rate: self.methylation_rate_shore,
correlation_length_bp: self.methylation_correlation_length_shore,
},
open_sea: ContextParams {
rate: self.methylation_rate_open_sea,
correlation_length_bp: self.methylation_correlation_length_open_sea,
},
hemi_rate: self.hemimethylation_rate,
}
}
}
impl Command for Methylate {
fn execute(&self) -> Result<()> {
use noodles_bgzf as bgzf;
use crate::fasta::Fasta;
use crate::haplotype::build_haplotypes;
use crate::meth::ContigMethylation;
use crate::seed::{derive_seed, resolve_seed};
use crate::vcf::methylation::write_contig;
use crate::vcf::methylation::write_vcf_header;
use crate::version::VERSION;
let model = self.methylation_model();
model.validate()?;
let seed_desc = format!(
"{}:methylate:{}:{}:{}:{}:{}:{}:{}",
self.reference.reference.display(),
self.methylation_rate_island,
self.methylation_rate_shore,
self.methylation_rate_open_sea,
self.methylation_correlation_length_island,
self.methylation_correlation_length_shore,
self.methylation_correlation_length_open_sea,
self.hemimethylation_rate,
);
let seed = resolve_seed(self.seed.seed, &seed_desc);
log::info!("Using random seed: {seed}");
let mut fasta = Fasta::from_path(&self.reference.reference)?;
let dict = fasta.dict().clone();
let contig_names: Vec<String> = dict.names().into_iter().map(String::from).collect();
if self.vcf.sample.is_some() && self.vcf.vcf.is_none() {
anyhow::bail!("--sample requires --vcf");
}
let resolved_sample = if let Some(vcf_path) = &self.vcf.vcf {
Some(crate::vcf::validate_vcf_sample(vcf_path, self.vcf.sample.as_deref())?)
} else {
None
};
let file = File::create(&self.output)?;
let mut bgzf = bgzf::io::Writer::new(file);
let cmd_line = capture_command_line();
write_vcf_header(&mut bgzf, &dict, resolved_sample.as_deref(), &VERSION, &cmd_line)?;
let mut bedgraph_writer: Option<BufWriter<File>> =
if let Some(bedgraph_path) = &self.bedgraph {
let bg_file = File::create(bedgraph_path)?;
let mut w = BufWriter::new(bg_file);
crate::output::methylation_bedgraph::write_bedgraph_header(&mut w)?;
Some(w)
} else {
None
};
let parsed_variants = if let Some(vcf_path) = &self.vcf.vcf {
Some(crate::vcf::parse_variants_by_contig(vcf_path, resolved_sample.as_deref(), &dict)?)
} else {
None
};
let sample_ploidy = parsed_variants.as_ref().map_or(2, |p| p.sample_ploidy);
for contig_name in &contig_names {
let ref_seed = derive_seed(seed, contig_name);
let mut ref_rng = rand::rngs::SmallRng::seed_from_u64(ref_seed);
let reference = fasta.load_contig(contig_name, &mut ref_rng)?;
let variants: &[crate::vcf::genotype::VariantRecord] = parsed_variants
.as_ref()
.and_then(|p| p.by_contig.get(contig_name))
.map_or(&[], Vec::as_slice);
let hap_seed = derive_seed(seed, &format!("haps@{contig_name}"));
let mut hap_rng = rand::rngs::SmallRng::seed_from_u64(hap_seed);
let haplotypes = build_haplotypes(variants, sample_ploidy, &mut hap_rng);
let meth_seed = derive_seed(seed, &format!("meth@{contig_name}"));
let mut meth_rng = rand::rngs::SmallRng::seed_from_u64(meth_seed);
let methylation =
ContigMethylation::from_haplotypes(&haplotypes, &reference, &model, &mut meth_rng);
write_contig(
&mut bgzf,
contig_name,
&reference,
variants,
&methylation,
sample_ploidy,
)?;
if let Some(bg) = bedgraph_writer.as_mut() {
crate::output::methylation_bedgraph::write_bedgraph_records(
bg,
contig_name,
&reference,
&haplotypes,
&methylation,
)?;
}
}
bgzf.flush()?;
drop(bgzf);
if let Some(mut bg) = bedgraph_writer {
bg.flush()?;
if let Some(path) = &self.bedgraph {
log::info!("Wrote population-fraction bedGraph to {}", path.display());
}
}
log::info!("Wrote methylation VCF to {}", self.output.display());
Ok(())
}
}
fn capture_command_line() -> String {
std::env::args_os().map(|arg| arg.to_string_lossy().into_owned()).collect::<Vec<_>>().join(" ")
}