1use std::marker::PhantomData;
2use std::path::Path;
3use std::sync::OnceLock;
4
5use crate::algorithms::dynamic_programming::eliminate_bad_genes;
6use crate::algorithms::dynamic_programming::predict_genes;
7use crate::algorithms::gene_finding::GeneBuilder;
8use crate::config::OrphosConfig;
9use crate::constants::MIN_SEQUENCE_LENGTH;
10use crate::metagenomic::bins;
11use crate::metagenomic::get_preset_training_ref;
12use crate::node::{
13 add_nodes, calculate_dicodon_gene, raw_coding_score, rbs_score, record_gc_bias,
14 record_overlapping_starts, reset_node_scores, score_nodes, sort_nodes_by_position,
15};
16use crate::results::{OrphosResults, SequenceInfo};
17use crate::sequence::calc_most_gc_frame;
18use crate::sequence::encoded::EncodedSequence;
19use crate::sequence::read_fasta_sequences;
20use crate::training::non_sd_training::train_starts_nonsd;
21use crate::training::sd_training::train_starts_sd;
22use crate::training::should_use_sd;
23use crate::types::Gene;
24use crate::types::{CodonType, Node, OrphosError, Training};
25use bio::bio_types::strand::Strand;
26use rayon::prelude::*;
27
28static RAYON_POOL_INIT: OnceLock<()> = OnceLock::new();
29
30pub trait TrainingState {}
36
37#[derive(Debug, Clone)]
42pub struct Untrained;
43
44#[derive(Debug, Clone)]
49pub struct Trained;
50
51impl TrainingState for Untrained {}
52impl TrainingState for Trained {}
53
54#[derive(Debug, Default)]
89pub struct Orphos<S: TrainingState> {
90 pub config: OrphosConfig,
92 training: Option<Training>,
94 _state: PhantomData<S>,
96}
97
98pub type UntrainedOrphos = Orphos<Untrained>;
102
103pub type TrainedOrphos = Orphos<Trained>;
107
108impl UntrainedOrphos {
109 pub fn new() -> Self {
119 Self {
120 config: OrphosConfig::default(),
121 training: None,
122 _state: PhantomData,
123 }
124 }
125
126 pub fn with_config(config: OrphosConfig) -> Result<Self, OrphosError> {
152 if config.circular && config.closed_ends {
153 return Err(OrphosError::InvalidSequence(
154 "Invalid configuration: circular and closed_ends cannot both be true".to_string(),
155 ));
156 }
157
158 let orphos = Self {
159 config,
160 training: None,
161 _state: PhantomData,
162 };
163
164 if let Some(num_threads) = orphos.config.num_threads {
165 RAYON_POOL_INIT.get_or_init(|| {
166 let _ = rayon::ThreadPoolBuilder::new()
168 .num_threads(num_threads)
169 .build_global();
170 });
171 }
172
173 Ok(orphos)
174 }
175
176 pub fn train_single_genome(
214 &mut self,
215 encoded_sequence: &EncodedSequence,
216 ) -> Result<TrainedOrphos, OrphosError> {
217 let sequence_length = encoded_sequence.sequence_length;
218 if sequence_length < MIN_SEQUENCE_LENGTH {
219 return Err(OrphosError::InvalidSequence(format!(
220 "Sequence too short for gene prediction: {} bp (minimum {} bp required)",
221 sequence_length, MIN_SEQUENCE_LENGTH
222 )));
223 }
224
225 if !self.config.quiet {
226 eprintln!(
227 "Training on single genome ({} bp, {:.2}% GC)...",
228 sequence_length,
229 encoded_sequence.gc_content * 100.0
230 );
231 }
232 let mut training = Training {
233 gc_content: 0.0,
234 ..Default::default()
235 };
236 training.uses_shine_dalgarno = false;
237 training.gc_bias_factors = [0.0; 3];
238
239 training.gc_content = encoded_sequence.gc_content;
240
241 let mut nodes = Vec::new();
242 let num_nodes = add_nodes(
243 encoded_sequence,
244 &mut nodes,
245 self.config.closed_ends,
246 self.config.circular,
247 &training,
248 )?;
249
250 if !self.config.quiet {
251 eprintln!(
252 "Located {} potential start/stop nodes, closed {}",
253 num_nodes, self.config.closed_ends
254 );
255 }
256
257 sort_nodes_by_position(&mut nodes);
258
259 let gc_frame = calc_most_gc_frame(&encoded_sequence.forward_sequence, sequence_length);
260
261 record_gc_bias(&gc_frame, &mut nodes, &mut training);
262
263 if !self.config.quiet {
264 eprintln!(
265 "Frame bias scores: {:.8} {:.8} {:.8}",
266 training.gc_bias_factors[0],
267 training.gc_bias_factors[1],
268 training.gc_bias_factors[2]
269 );
270 }
271
272 record_overlapping_starts(&mut nodes, &training, false);
273
274 let initial_path = predict_genes(&mut nodes, &training, false).unwrap_or(0);
275 calculate_dicodon_gene(
276 &mut training,
277 &encoded_sequence.forward_sequence,
278 &encoded_sequence.reverse_complement_sequence,
279 sequence_length,
280 &nodes,
281 initial_path,
282 );
283
284 raw_coding_score(
285 &encoded_sequence.forward_sequence,
286 &encoded_sequence.reverse_complement_sequence,
287 sequence_length,
288 &mut nodes,
289 &training,
290 );
291
292 rbs_score(
293 &encoded_sequence.forward_sequence,
294 &encoded_sequence.reverse_complement_sequence,
295 sequence_length,
296 &mut nodes,
297 &training,
298 );
299
300 train_starts_sd(
301 &encoded_sequence.forward_sequence,
302 &encoded_sequence.reverse_complement_sequence,
303 sequence_length,
304 &nodes,
305 &mut training,
306 );
307 training.uses_shine_dalgarno = should_use_sd(&training);
308 if self.config.force_non_sd {
309 training.uses_shine_dalgarno = false;
310 }
311
312 if !training.uses_shine_dalgarno {
313 train_starts_nonsd(
314 &encoded_sequence.forward_sequence,
315 &encoded_sequence.reverse_complement_sequence,
316 sequence_length,
317 &mut nodes,
318 &mut training,
319 );
320 }
321
322 if !self.config.quiet {
323 eprintln!("Training complete!");
324 }
325
326 Ok(Orphos {
327 config: self.config.clone(),
328 training: Some(training),
329 _state: PhantomData,
330 })
331 }
332
333 pub fn train_meta_genome(
334 &mut self,
335 _encoded_sequence: &EncodedSequence,
336 ) -> Result<TrainedOrphos, OrphosError> {
337 if !self.config.quiet {
338 eprintln!("Request: Metagenomic, Phase: Training");
339 eprintln!("Initializing training files...");
340 }
341 if !self.config.quiet {
343 eprintln!("Metagenomic training initialized.");
344 }
345 Ok(Orphos {
346 config: self.config.clone(),
347 training: None,
348 _state: PhantomData,
349 })
350 }
351}
352
353impl TrainedOrphos {
354 pub const fn new(config: OrphosConfig, training: Training) -> Self {
377 Self {
378 config,
379 training: Some(training),
380 _state: PhantomData,
381 }
382 }
383
384 fn find_genes_single(
435 &self,
436 encoded_sequence: &EncodedSequence,
437 ) -> Result<Vec<Gene>, OrphosError> {
438 let mut nodes = Vec::new();
439 let training = self
440 .training
441 .as_ref()
442 .ok_or_else(|| OrphosError::InvalidSequence("Orphos is not trained".to_string()))?;
443
444 let _num_nodes = add_nodes(
445 encoded_sequence,
446 &mut nodes,
447 self.config.closed_ends,
448 self.config.circular,
449 training,
450 )?;
451
452 sort_nodes_by_position(&mut nodes);
453
454 score_nodes(
457 encoded_sequence,
458 &mut nodes,
459 training,
460 self.config.closed_ends,
461 false,
462 )?;
463
464 record_overlapping_starts(&mut nodes, training, true);
465
466 let gene_path = match predict_genes(&mut nodes, training, true) {
467 Some(path) => path,
468 None => {
469 return Ok(vec![]);
471 }
472 };
473
474 eliminate_bad_genes(&mut nodes, Some(gene_path), training);
475
476 let genes = GeneBuilder::from_nodes(&nodes, gene_path, training, 1)
477 .with_tweaked_starts()
478 .with_annotations()
479 .build();
480
481 Ok(genes)
482 }
483
484 fn find_genes_meta(
485 &self,
486 encoded_sequence: &EncodedSequence,
487 ) -> Result<(Vec<Gene>, usize), OrphosError> {
488 if !self.config.quiet {
489 eprintln!("Request: Metagenomic, Phase: Gene Finding");
490 }
491 let mut low = 0.88495 * encoded_sequence.gc_content - 0.0102337;
492 if low > 0.65 {
493 low = 0.65;
494 }
495 let mut high = 0.86596 * encoded_sequence.gc_content + 0.1131991;
496 if high < 0.35 {
497 high = 0.35;
498 }
499 let mut max_score = -100.0;
500 let mut max_phase = 0;
501
502 let mut nodes = Vec::new();
503 let mut genes = Vec::new();
504
505 for (i, _bin) in bins().iter().enumerate() {
506 let preset_training = get_preset_training_ref(i).unwrap();
507 if i == 0
508 || preset_training.translation_table
509 != get_preset_training_ref(i - 1).unwrap().translation_table
510 {
511 let _num_nodes = add_nodes(
512 encoded_sequence,
513 &mut nodes,
514 self.config.closed_ends,
515 self.config.circular,
516 preset_training,
517 )?;
518 sort_nodes_by_position(&mut nodes);
519 }
520 if preset_training.gc_content < low || preset_training.gc_content > high {
521 continue;
522 }
523 reset_node_scores(&mut nodes);
524 score_nodes(
525 encoded_sequence,
526 &mut nodes,
527 preset_training,
528 self.config.closed_ends,
529 true,
530 )?;
531 record_overlapping_starts(&mut nodes, preset_training, true);
532 let gene_path = predict_genes(&mut nodes, preset_training, true);
533 if let Some(path) = gene_path
534 && let Some(node) = nodes.get(path)
535 && node.scores.total_score > max_score
536 {
537 max_phase = i;
538 max_score = node.scores.total_score;
539 eliminate_bad_genes(&mut nodes, gene_path, preset_training);
540 genes = GeneBuilder::from_nodes(&nodes, path, preset_training, 1)
541 .with_tweaked_starts()
542 .with_annotations()
543 .build();
544 }
545 }
547 nodes.clear();
548 let best_training = get_preset_training_ref(max_phase).unwrap();
549 let _ = add_nodes(
550 encoded_sequence,
551 &mut nodes,
552 self.config.closed_ends,
553 self.config.circular,
554 best_training,
555 )?;
556 sort_nodes_by_position(&mut nodes);
557 score_nodes(
558 encoded_sequence,
559 &mut nodes,
560 best_training,
561 self.config.closed_ends,
562 true,
563 )?;
564 update_display_scores(&mut genes, &nodes);
568 Ok((genes, max_phase))
569 }
570}
571
572fn update_display_scores(genes: &mut [Gene], nodes: &[Node]) {
575 for gene in genes.iter_mut() {
576 let target_pos = if gene.coordinates.strand == Strand::Forward {
578 gene.coordinates.begin.saturating_sub(1)
579 } else {
580 gene.coordinates.end.saturating_sub(1)
581 };
582
583 let lo = nodes.partition_point(|n| n.position.index < target_pos);
584 if let Some(start_node) = nodes[lo..]
585 .iter()
586 .take_while(|n| n.position.index == target_pos)
587 .find(|n| {
588 n.position.strand == gene.coordinates.strand
589 && n.position.codon_type != CodonType::Stop
590 })
591 {
592 gene.display_score =
593 Some(start_node.scores.coding_score + start_node.scores.start_score);
594 }
595 }
596}
597
598#[derive(Debug)]
664pub struct OrphosAnalyzer {
665 pub config: OrphosConfig,
667}
668
669impl OrphosAnalyzer {
670 pub const fn new(config: OrphosConfig) -> Self {
684 Self { config }
685 }
686
687 pub fn analyze_fasta_file<P: AsRef<Path>>(
721 &self,
722 path: P,
723 ) -> Result<Vec<OrphosResults>, OrphosError> {
724 let sequences = read_fasta_sequences(path.as_ref().to_str().unwrap())?;
725
726 sequences
727 .into_par_iter()
728 .map(|(header, description, seq_bytes)| {
729 self.analyze_sequence_bytes(&seq_bytes, header, description)
730 })
731 .collect()
732 }
733
734 pub fn analyze_sequence(
772 &self,
773 sequence: &str,
774 header: Option<String>,
775 ) -> Result<OrphosResults, OrphosError> {
776 let seq_bytes = sequence.as_bytes();
777 let header = header.unwrap_or_else(|| "Orphos_Seq_1".to_string());
778
779 self.analyze_sequence_bytes(seq_bytes, header, None)
780 }
781
782 pub fn analyze_sequence_bytes(
825 &self,
826 sequence: &[u8],
827 header: String,
828 description: Option<String>,
829 ) -> Result<OrphosResults, OrphosError> {
830 let sequence_length = sequence.len();
831 let encoded_sequence = self.encode_sequence(sequence);
832
833 let mut untrained_orphos = UntrainedOrphos::with_config(self.config.clone())?;
834 let trained_orphos = if self.config.metagenomic {
835 untrained_orphos.train_meta_genome(&encoded_sequence)?
836 } else {
837 untrained_orphos.train_single_genome(&encoded_sequence)?
838 };
839 let (genes, metagenomic_model, best_training) = if self.config.metagenomic {
840 let (genes, best_phase) = trained_orphos.find_genes_meta(&encoded_sequence)?;
841 let bin = &bins()[best_phase];
842 let training = get_preset_training_ref(best_phase).unwrap();
843 let model_desc = format!(
844 "{}|{}|{}|{:.1}|{}|{}",
845 bin.id,
846 bin.name,
847 bin.domain,
848 bin.gc_percent,
849 training.translation_table,
850 if training.uses_shine_dalgarno { 1 } else { 0 }
851 );
852 (genes, Some(model_desc), training.clone())
853 } else {
854 let genes = trained_orphos.find_genes_single(&encoded_sequence)?;
855 let training = trained_orphos.training.clone().unwrap_or_default();
856 (genes, None, training)
857 };
858
859 let num_genes = genes.len();
860 Ok(OrphosResults {
861 genes,
862 training_used: best_training,
863 sequence_info: SequenceInfo {
864 length: sequence_length,
865 gc_content: encoded_sequence.gc_content,
866 num_genes,
867 header,
868 description,
869 },
870 metagenomic_model,
871 })
872 }
873
874 fn encode_sequence(&self, sequence: &[u8]) -> EncodedSequence {
888 if self.config.mask_n_runs {
889 EncodedSequence::with_masking(sequence)
890 } else {
891 EncodedSequence::without_masking(sequence)
892 }
893 }
894}
895
896#[cfg(test)]
897mod tests {
898 use super::*;
899 use crate::config::OutputFormat;
900 use crate::constants::TEST_SEQUENCE_REPEAT_FACTOR;
901 use std::env;
902 use std::fs;
903
904 fn create_test_sequence() -> Vec<u8> {
906 "ATGAAACGTAAATAG".as_bytes().to_vec()
908 }
909
910 fn create_training_sequence() -> Vec<u8> {
912 let basic_gene = "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG";
914 basic_gene
915 .repeat(TEST_SEQUENCE_REPEAT_FACTOR)
916 .as_bytes()
917 .to_vec()
918 }
919
920 fn create_encoded_sequence_for_test(seq: &[u8]) -> EncodedSequence {
922 EncodedSequence::without_masking(seq)
923 }
924
925 #[test]
926 fn test_training_state_traits() {
927 let _untrained: Untrained = Untrained;
929 let _trained: Trained = Trained;
930
931 let untrained_clone = _untrained.clone();
933 let trained_clone = _trained.clone();
934
935 assert_eq!(format!("{:?}", untrained_clone), "Untrained");
936 assert_eq!(format!("{:?}", trained_clone), "Trained");
937 }
938
939 #[test]
940 fn test_untrained_orphos_new() {
941 let orphos = UntrainedOrphos::new();
942
943 assert!(!orphos.config.metagenomic);
944 assert!(!orphos.config.closed_ends);
945 assert!(!orphos.config.mask_n_runs);
946 assert!(!orphos.config.force_non_sd);
947 assert!(!orphos.config.quiet);
948 assert_eq!(orphos.config.output_format, OutputFormat::Genbank);
949 assert!(orphos.training.is_none());
950 }
951
952 #[test]
953 fn test_untrained_orphos_with_config() {
954 let config = OrphosConfig {
955 metagenomic: true,
956 closed_ends: true,
957 quiet: true,
958 ..OrphosConfig::default()
959 };
960
961 let result = UntrainedOrphos::with_config(config.clone());
962 assert!(result.is_ok());
963
964 let orphos = result.unwrap();
965 assert!(orphos.config.metagenomic);
966 assert!(orphos.config.closed_ends);
967 assert!(orphos.config.quiet);
968 assert!(orphos.training.is_none());
969 }
970
971 #[test]
972 fn test_untrained_orphos_with_thread_config() {
973 let config = OrphosConfig {
974 num_threads: Some(2),
975 ..OrphosConfig::default()
976 };
977
978 let result = UntrainedOrphos::with_config(config);
979 let _ = result;
982 }
983
984 #[test]
985 fn test_untrained_orphos_with_invalid_thread_config() {
986 let config = OrphosConfig {
987 num_threads: Some(0),
988 ..OrphosConfig::default()
989 };
990
991 let result = UntrainedOrphos::with_config(config);
992 let _ = result;
995 }
996
997 #[test]
998 fn test_untrained_orphos_with_conflicting_topology_config() {
999 let config = OrphosConfig {
1000 circular: true,
1001 closed_ends: true,
1002 ..OrphosConfig::default()
1003 };
1004
1005 let result = UntrainedOrphos::with_config(config);
1006 assert!(result.is_err());
1007 if let Err(OrphosError::InvalidSequence(msg)) = result {
1008 assert!(msg.contains("circular"));
1009 assert!(msg.contains("closed_ends"));
1010 } else {
1011 panic!("Expected InvalidSequence error");
1012 }
1013 }
1014
1015 #[test]
1016 fn test_trained_orphos_new() {
1017 let config = OrphosConfig::default();
1018 let training = Training::default();
1019
1020 let orphos = TrainedOrphos::new(config, training);
1021
1022 assert!(orphos.training.is_some());
1023 }
1024
1025 #[test]
1026 fn test_train_single_genome_basic() {
1027 let mut orphos = UntrainedOrphos::new();
1028 let sequence = create_training_sequence();
1029 let encoded_sequence = create_encoded_sequence_for_test(&sequence);
1030
1031 let result = orphos.train_single_genome(&encoded_sequence);
1032
1033 assert!(result.is_ok());
1034 let trained = result.unwrap();
1035 assert!(trained.training.is_some());
1036 }
1037
1038 #[test]
1039 fn test_train_single_genome_quiet_mode() {
1040 let config = OrphosConfig {
1041 quiet: true,
1042 ..OrphosConfig::default()
1043 };
1044 let mut orphos = UntrainedOrphos::with_config(config).unwrap();
1045
1046 let sequence = create_training_sequence();
1047 let encoded_sequence = create_encoded_sequence_for_test(&sequence);
1048
1049 let result = orphos.train_single_genome(&encoded_sequence);
1050
1051 assert!(result.is_ok());
1052 }
1053
1054 #[test]
1055 fn test_train_single_genome_force_non_sd() {
1056 let config = OrphosConfig {
1057 force_non_sd: true,
1058 ..OrphosConfig::default()
1059 };
1060 let mut orphos = UntrainedOrphos::with_config(config).unwrap();
1061
1062 let sequence = create_training_sequence();
1063 let encoded_sequence = create_encoded_sequence_for_test(&sequence);
1064
1065 let result = orphos.train_single_genome(&encoded_sequence);
1066
1067 assert!(result.is_ok());
1068 let trained = result.unwrap();
1069 let training = trained.training.unwrap();
1070 assert!(!training.uses_shine_dalgarno); }
1072
1073 #[test]
1074 fn test_trained_orphos_find_genes_single() {
1075 let mut orphos = UntrainedOrphos::new();
1077 let sequence = create_training_sequence();
1078 let encoded_sequence = create_encoded_sequence_for_test(&sequence);
1079
1080 let trained = orphos.train_single_genome(&encoded_sequence).unwrap();
1081
1082 let test_seq = create_test_sequence();
1084 let test_encoded_sequence = create_encoded_sequence_for_test(&test_seq);
1085
1086 let result = trained.find_genes_single(&test_encoded_sequence);
1087
1088 assert!(result.is_ok());
1089 let _genes = result.unwrap();
1090 }
1092
1093 #[test]
1094 fn test_trained_orphos_find_genes_without_training() {
1095 let config = OrphosConfig::default();
1096 let orphos = TrainedOrphos {
1097 config,
1098 training: None,
1099 _state: PhantomData,
1100 };
1101
1102 let test_seq = create_test_sequence();
1103 let test_encoded_sequence = create_encoded_sequence_for_test(&test_seq);
1104
1105 let result = orphos.find_genes_single(&test_encoded_sequence);
1106
1107 assert!(result.is_err());
1108 if let Err(OrphosError::InvalidSequence(msg)) = result {
1109 assert!(msg.contains("not trained"));
1110 } else {
1111 panic!("Expected InvalidSequence error");
1112 }
1113 }
1114
1115 #[test]
1116 fn test_orphos_analyzer_new() {
1117 let config = OrphosConfig::default();
1118 let analyzer = OrphosAnalyzer::new(config.clone());
1119
1120 assert_eq!(analyzer.config.metagenomic, config.metagenomic);
1121 assert_eq!(analyzer.config.closed_ends, config.closed_ends);
1122 }
1123
1124 #[test]
1125 fn test_analyze_sequence_basic() {
1126 let config = OrphosConfig::default();
1127 let analyzer = OrphosAnalyzer::new(config);
1128
1129 let sequence =
1130 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(300);
1131
1132 let result = analyzer.analyze_sequence(&sequence, None);
1133 assert!(result.is_ok());
1134
1135 let analysis = result.unwrap();
1136 assert_eq!(analysis.sequence_info.header, "Orphos_Seq_1");
1137 assert_eq!(analysis.sequence_info.length, sequence.len());
1138 assert!(
1139 analysis.sequence_info.gc_content >= 0.0 && analysis.sequence_info.gc_content <= 1.0
1140 );
1141 assert!(analysis.metagenomic_model.is_none());
1142 }
1143
1144 #[test]
1145 fn test_analyze_sequence_with_header() {
1146 let config = OrphosConfig::default();
1147 let analyzer = OrphosAnalyzer::new(config);
1148
1149 let sequence =
1150 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(300);
1151 let header = Some("test_sequence".to_string());
1152
1153 let result = analyzer.analyze_sequence(&sequence, header);
1154 assert!(result.is_ok());
1155
1156 let analysis = result.unwrap();
1157 assert_eq!(analysis.sequence_info.header, "test_sequence");
1158 }
1159
1160 #[test]
1161 fn test_analyze_sequence_bytes() {
1162 let config = OrphosConfig::default();
1163 let analyzer = OrphosAnalyzer::new(config);
1164
1165 let sequence =
1166 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(300);
1167 let seq_bytes = sequence.as_bytes();
1168 let header = "test_sequence".to_string();
1169 let description = Some("Test description".to_string());
1170
1171 let result =
1172 analyzer.analyze_sequence_bytes(seq_bytes, header.clone(), description.clone());
1173 assert!(result.is_ok());
1174
1175 let analysis = result.unwrap();
1176 assert_eq!(analysis.sequence_info.header, header);
1177 assert_eq!(analysis.sequence_info.description, description);
1178 assert_eq!(analysis.sequence_info.length, seq_bytes.len());
1179 }
1180
1181 #[test]
1182 fn test_analyze_sequence_metagenomic_config() {
1183 let config = OrphosConfig {
1184 metagenomic: true,
1185 ..OrphosConfig::default()
1186 };
1187 let analyzer = OrphosAnalyzer::new(config);
1188
1189 let sequence =
1190 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(300);
1191
1192 let result = analyzer.analyze_sequence(&sequence, None);
1193 assert!(result.is_ok());
1194
1195 let analysis = result.unwrap();
1196 assert!(analysis.metagenomic_model.is_some());
1197 let model = analysis.metagenomic_model.unwrap();
1199 assert!(
1200 model.contains("|"),
1201 "Expected metagenomic model description with | separator"
1202 );
1203 }
1204
1205 #[test]
1243 fn test_analyze_fasta_file_not_found() {
1244 let config = OrphosConfig::default();
1245 let analyzer = OrphosAnalyzer::new(config);
1246
1247 let result = analyzer.analyze_fasta_file("nonexistent_file.fa");
1248 assert!(result.is_err());
1249 }
1250
1251 #[test]
1252 fn test_analyze_fasta_file_metagenomic() {
1253 let config = OrphosConfig {
1254 metagenomic: true,
1255 ..OrphosConfig::default()
1256 };
1257 let analyzer = OrphosAnalyzer::new(config);
1258
1259 let fasta_content = ">test_seq\nATCG\n";
1261 let temp_dir = env::temp_dir();
1262 let temp_file = temp_dir.join("test_metagenomic.fa");
1263 fs::write(&temp_file, fasta_content).unwrap();
1264
1265 let result = analyzer.analyze_fasta_file(&temp_file);
1266 assert!(result.is_ok());
1267
1268 let results = result.unwrap();
1269 assert_eq!(results.len(), 1); assert!(results[0].genes.is_empty()); let _ = fs::remove_file(temp_file);
1273 }
1274
1275 #[test]
1276 fn test_analyze_fasta_file_single_genome() {
1277 let config = OrphosConfig::default();
1278 let analyzer = OrphosAnalyzer::new(config);
1279
1280 let sequence =
1282 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(300);
1283 let fasta_content = format!(">test_seq\n{}\n", sequence);
1284 let temp_dir = env::temp_dir();
1285 let temp_file = temp_dir.join("test_single_genome.fa");
1286 fs::write(&temp_file, fasta_content).unwrap();
1287
1288 let result = analyzer.analyze_fasta_file(&temp_file);
1289 assert!(result.is_ok());
1290
1291 let results = result.unwrap();
1292 assert_eq!(results.len(), 1);
1293 assert_eq!(results[0].sequence_info.header, "test_seq");
1294
1295 let _ = fs::remove_file(temp_file);
1296 }
1297
1298 #[test]
1299 fn test_analyze_empty_sequence() {
1300 let config = OrphosConfig::default();
1301 let analyzer = OrphosAnalyzer::new(config);
1302
1303 let sequence = "";
1304 let result = analyzer.analyze_sequence(sequence, None);
1305
1306 assert!(result.is_err());
1307 if let Err(e) = result {
1308 match e {
1310 OrphosError::InvalidSequence(msg) => {
1311 assert!(msg.contains("too short"));
1312 }
1313 _ => panic!("Expected InvalidSequence error for empty sequence"),
1314 }
1315 }
1316 }
1317
1318 #[test]
1319 fn test_analyze_very_short_sequence() {
1320 let config = OrphosConfig::default();
1321 let analyzer = OrphosAnalyzer::new(config);
1322
1323 let sequence = "ATG"; let result = analyzer.analyze_sequence(sequence, None);
1325
1326 assert!(result.is_err());
1327 if let Err(e) = result {
1328 match e {
1330 OrphosError::InvalidSequence(msg) => {
1331 assert!(msg.contains("too short"));
1332 }
1333 _ => panic!("Expected InvalidSequence error for very short sequence"),
1334 }
1335 }
1336 }
1337
1338 #[test]
1339 fn test_config_cloning() {
1340 let config1 = OrphosConfig::default();
1341 let orphos1 = UntrainedOrphos::with_config(config1.clone()).unwrap();
1342
1343 let config2 = orphos1.config.clone();
1344 let _orphos2 = UntrainedOrphos::with_config(config2).unwrap();
1345 }
1346
1347 #[test]
1348 fn test_debug_formatting() {
1349 let orphos = UntrainedOrphos::new();
1350 let debug_str = format!("{:?}", orphos);
1351 assert!(debug_str.contains("Orphos"));
1352 assert!(debug_str.contains("config"));
1353
1354 let analyzer = OrphosAnalyzer::new(OrphosConfig::default());
1355 let debug_str2 = format!("{:?}", analyzer);
1356 assert!(debug_str2.contains("OrphosAnalyzer"));
1357 assert!(debug_str2.contains("config"));
1358 }
1359
1360 #[test]
1361 fn test_type_aliases() {
1362 let _untrained: UntrainedOrphos = UntrainedOrphos::new();
1364 let _trained: TrainedOrphos =
1365 TrainedOrphos::new(OrphosConfig::default(), Training::default());
1366
1367 assert_eq!(
1368 std::any::type_name::<UntrainedOrphos>(),
1369 std::any::type_name::<Orphos<Untrained>>()
1370 );
1371 assert_eq!(
1372 std::any::type_name::<TrainedOrphos>(),
1373 std::any::type_name::<Orphos<Trained>>()
1374 );
1375 }
1376
1377 #[test]
1378 fn test_training_state_phantom_data() {
1379 let untrained = UntrainedOrphos::new();
1380 let trained = TrainedOrphos::new(OrphosConfig::default(), Training::default());
1381
1382 assert_eq!(std::mem::size_of_val(&untrained._state), 0);
1383 assert_eq!(std::mem::size_of_val(&trained._state), 0);
1384 }
1385
1386 #[test]
1387 fn test_analyzer_multiple_sequences() {
1388 let config = OrphosConfig::default();
1389 let analyzer = OrphosAnalyzer::new(config);
1390
1391 let sequence1 =
1392 "ATGAAACGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTCGTAAATAG".repeat(150);
1393 let sequence2 =
1394 "ATGCCCGGGAAATTTCCCGGGAAATTTCCCGGGAAATTTCCCGGGAAATTTCCCGGGAAATAG".repeat(200);
1395
1396 let result1 = analyzer.analyze_sequence(&sequence1, Some("seq1".to_string()));
1397 let result2 = analyzer.analyze_sequence(&sequence2, Some("seq2".to_string()));
1398
1399 assert!(result1.is_ok());
1400 assert!(result2.is_ok());
1401
1402 let analysis1 = result1.unwrap();
1403 let analysis2 = result2.unwrap();
1404
1405 assert_eq!(analysis1.sequence_info.header, "seq1");
1406 assert_eq!(analysis2.sequence_info.header, "seq2");
1407 assert_ne!(
1408 analysis1.sequence_info.length,
1409 analysis2.sequence_info.length
1410 );
1411 }
1412
1413 #[test]
1414 fn test_error_handling_edge_cases() {
1415 let config = OrphosConfig::default();
1416 let analyzer = OrphosAnalyzer::new(config);
1417
1418 let invalid_sequence = "ATCGXYZ123";
1420 let result = analyzer.analyze_sequence(invalid_sequence, None);
1421
1422 let _ = result;
1424 }
1425}