switch to using boost::filesystem
[mussa.git] / alg / mussa.cpp
1 //  This file is part of the Mussa source distribution.
2 //  http://mussa.caltech.edu/
3 //  Contact author: Tristan  De Buysscher, tristan@caltech.edu
4
5 // This program and all associated source code files are Copyright (C) 2005
6 // the California Institute of Technology, Pasadena, CA, 91125 USA.  It is
7 // under the GNU Public License; please see the included LICENSE.txt
8 // file for more information, or contact Tristan directly.
9
10
11 //                        ----------------------------------------
12 //                          ---------- mussa_class.cc -----------
13 //                        ----------------------------------------
14 #include <boost/filesystem/operations.hpp>
15 #include <boost/filesystem/fstream.hpp>
16 namespace fs = boost::filesystem;
17
18 #include <iostream>
19 #include <sstream>
20
21 #include "mussa_exceptions.hpp"
22 #include "alg/mussa.hpp"
23 #include "alg/flp.hpp"
24
25 using namespace std;
26
27 Mussa::Mussa()
28 {
29   clear();
30 }
31
32 Mussa::Mussa(const Mussa& m)
33   : analysis_name(m.analysis_name),
34     window(m.window),
35     threshold(m.threshold),
36     soft_thres(m.soft_thres),
37     ana_mode(m.ana_mode),
38     win_append(m.win_append),
39     thres_append(m.thres_append),
40     motif_sequences(m.motif_sequences),
41     color_mapper(m.color_mapper)
42 {
43 }
44
45 // set all parameters to null state
46 void
47 Mussa::clear()
48 {
49   analysis_name = "";
50   ana_mode = TransitiveNway;
51   window = 0;
52   threshold = 0;
53   soft_thres = 0;
54   win_append = false;
55   thres_append = false;
56   motif_sequences.clear();
57   color_mapper.clear();
58 }
59
60 // these 5 simple methods manually set the parameters for doing an analysis
61 // used so that the gui can take input from user and setup the analysis
62 // note - still need a set_append(bool, bool) method...
63 void
64 Mussa::set_name(string a_name)
65 {
66   analysis_name = a_name;
67 }
68
69 string Mussa::get_name()
70 {
71   return analysis_name;
72 }
73
74 int 
75 Mussa::size() const
76 {
77   if (the_seqs.size() > 0)
78     return the_seqs.size();
79   else
80     return 0;
81 }
82
83 void
84 Mussa::set_window(int a_window)
85 {
86   window = a_window;
87 }
88
89 int Mussa::get_window() const
90 {
91   return window;
92 }
93
94 void
95 Mussa::set_threshold(int a_threshold)
96 {
97   threshold = a_threshold;
98   //soft_thres = a_threshold;
99 }
100
101 int Mussa::get_threshold() const
102 {
103   return threshold;
104 }
105
106 void
107 Mussa::set_soft_thres(int sft_thres)
108 {
109   soft_thres = sft_thres;
110 }
111
112 int Mussa::get_soft_thres() const
113 {
114   return soft_thres;
115 }
116
117 void
118 Mussa::set_analysis_mode(enum analysis_modes new_ana_mode)
119 {
120   ana_mode = new_ana_mode;
121 }
122
123 enum Mussa::analysis_modes Mussa::get_analysis_mode() const
124 {
125   return ana_mode;
126 }
127
128 string Mussa::get_analysis_mode_name() const
129 {
130   switch (ana_mode)
131   {
132   case TransitiveNway:
133     return string("Transitive");
134     break;
135   case RadialNway:
136     return string("Radial");
137     break;
138   case EntropyNway:
139     return string("Entropy");
140     break;
141   case RecursiveNway:
142     return string("[deprecated] Recursive");
143     break;
144   default:
145     throw runtime_error("invalid analysis mode type");
146     break;
147   }
148 }
149
150 const NwayPaths& Mussa::paths() const
151 {
152   return the_paths;
153 }
154
155 // takes a string and sets it as the next seq 
156 void
157 Mussa::add_a_seq(string a_seq)
158 {
159   Sequence aSeq;
160
161   aSeq.set_seq(a_seq);
162   the_seqs.push_back(aSeq);
163 }
164
165 const vector<Sequence>& 
166 Mussa::sequences() const
167 {
168   return the_seqs;
169 }
170
171 void Mussa::load_sequence(fs::path seq_file, fs::path annot_file, 
172                           int fasta_index, int sub_seq_start, int sub_seq_end)
173 {
174   Sequence aseq;
175   aseq.load_fasta(seq_file, fasta_index, sub_seq_start, sub_seq_end);
176   if ( not annot_file.empty() ) {
177     aseq.load_annot(annot_file, sub_seq_start, sub_seq_end);
178   }
179   the_seqs.push_back(aseq);
180 }
181
182 void
183 Mussa::load_mupa_file(fs::path para_file_path)
184 {
185   fs::ifstream para_file;
186   string file_data_line;
187   string param, value; 
188   fs::path annot_file;
189   int split_index, fasta_index;
190   int sub_seq_start, sub_seq_end;
191   bool seq_params, did_seq;
192   string err_msg;
193   bool parsing_path;
194   string::size_type new_index, dir_index;
195
196   // initialize values
197   clear();
198
199   // if file was opened, read the parameter values
200   if (fs::exists(para_file_path))
201   {
202     para_file.open(para_file_path, ios::in);
203
204     // what directory is the mupa file in?
205     fs::path file_path_base = para_file_path.branch_path();
206
207     // setup loop by getting file's first line
208     getline(para_file,file_data_line);
209     split_index = file_data_line.find(" ");
210     param = file_data_line.substr(0,split_index);
211     value = file_data_line.substr(split_index+1);
212     
213     while (!para_file.eof())
214     {
215       did_seq = false;
216       if (param == "ANA_NAME")
217         analysis_name = value;
218       else if (param == "APPEND_WIN")
219         win_append = true;
220       else if (param == "APPEND_THRES")
221         thres_append = true;
222       else if (param == "SEQUENCE_NUM")
223         ; // ignore sequence_num now
224       else if (param == "WINDOW")
225         window = atoi(value.c_str());
226       else if (param == "THRESHOLD")
227         threshold = atoi(value.c_str());
228       else if (param == "SEQUENCE")
229       {
230         fs::path seq_file = file_path_base / value;
231         //cout << "seq_file_name " << seq_files.back() << endl;
232         fasta_index = 1;
233         annot_file = "";
234         sub_seq_start = 0;
235         sub_seq_end = 0;
236         seq_params = true;
237
238         while ((!para_file.eof()) && seq_params)
239         {
240           getline(para_file,file_data_line);
241           split_index = file_data_line.find(" ");
242           param = file_data_line.substr(0,split_index);
243           value = file_data_line.substr(split_index+1);
244
245           if (param == "FASTA_INDEX")
246             fasta_index = atoi(value.c_str());
247           else if (param == "ANNOTATION")
248             annot_file = file_path_base / value;
249           else if (param == "SEQ_START")
250             sub_seq_start = atoi(value.c_str());
251           else if (param == "SEQ_END")
252           {
253             sub_seq_end = atoi(value.c_str());
254           }
255           //ignore empty lines or that start with '#'
256           else if ((param == "") || (param == "#")) {}
257           else seq_params = false;
258         }
259         load_sequence(seq_file, annot_file, fasta_index, sub_seq_start, 
260                       sub_seq_end);
261         did_seq = true;
262       }
263       //ignore empty lines or that start with '#'
264       else if ((param == "") || (param == "#")) {}
265       else
266       {
267         clog << "Illegal/misplaced mussa parameter in file\n";
268         clog << param << "\n";
269       }
270
271       if (!did_seq)
272       {
273         getline(para_file,file_data_line);
274         split_index = file_data_line.find(" ");
275         param = file_data_line.substr(0,split_index);
276         value = file_data_line.substr(split_index+1);
277         did_seq = false;
278       }
279     }
280
281     para_file.close();
282
283     soft_thres = threshold;
284     //cout << "nway mupa: analysis_name = " << analysis_name 
285     //     << " window = " << window 
286     //     << " threshold = " << threshold << endl;
287   }
288   // no file was loaded, signal error
289   else
290   {
291     throw mussa_load_error("Config File: " + para_file_path.string() + " not found");
292   }
293 }
294
295
296 void
297 Mussa::analyze(int w, int t, enum Mussa::analysis_modes the_ana_mode, double new_ent_thres)
298 {
299   time_t t1, t2, begin, end;
300   double seqloadtime, seqcomptime, nwaytime, savetime, totaltime;
301
302   begin = time(NULL);
303
304   ana_mode = the_ana_mode;
305   ent_thres = new_ent_thres;
306   if (w > 0)
307     window  = w;
308   if (t > 0)
309   {
310     threshold = t;
311     soft_thres = t;
312   }
313
314   t1 = time(NULL);
315         
316   if (the_seqs.size() < 2) {
317     throw mussa_analysis_error("you need to have at least 2 sequences to "
318                                "do an analysis.");
319   }
320   //cout << "nway ana: seq_num = " << the_seqs.size() << endl;
321
322   t2 = time(NULL);
323   seqloadtime = difftime(t2, t1);
324
325   t1 = time(NULL);
326   seqcomp();
327   t2 = time(NULL);
328   seqcomptime = difftime(t2, t1);
329
330
331   t1 = time(NULL);
332   the_paths.setup(window, threshold);
333   nway();
334   t2 = time(NULL);
335   nwaytime = difftime(t2, t1);
336
337   t1 = time(NULL);
338   save();
339   t2 = time(NULL);
340   savetime = difftime(t2, t1);
341
342   end = time(NULL);
343   totaltime = difftime(end, begin);
344
345
346   //cout << "seqload\tseqcomp\tnway\tsave\ttotal\n";
347   //cout << seqloadtime << "\t";
348   //cout << seqcomptime << "\t";
349   //cout << nwaytime << "\t";
350   //cout << savetime << "\t";
351   //cout << totaltime << "\n";
352 }
353
354 void
355 Mussa::seqcomp()
356 {
357   vector<int> seq_lens;
358   vector<FLPs> empty_FLP_vector;
359   FLPs dummy_comp;
360   string save_file_string;
361
362   empty_FLP_vector.clear();
363   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
364   {
365     all_comps.push_back(empty_FLP_vector); 
366     for(vector<Sequence>::size_type i2 = 0; i2 < the_seqs.size(); i2++)
367       all_comps[i].push_back(dummy_comp);
368   }
369   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
370     seq_lens.push_back(the_seqs[i].size());
371
372   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
373     for(vector<Sequence>::size_type i2 = i+1; i2 < the_seqs.size(); i2++)
374     {
375       //cout << "seqcomping: " << i << " v. " << i2 << endl;
376       all_comps[i][i2].setup(window, threshold);
377       all_comps[i][i2].seqcomp(the_seqs[i].get_seq(), the_seqs[i2].get_seq(), false);
378       all_comps[i][i2].seqcomp(the_seqs[i].get_seq(),the_seqs[i2].rev_comp(),true);
379     }
380 }
381
382 void
383 Mussa::nway()
384 {
385   vector<string> some_Seqs;
386
387   the_paths.set_soft_thres(soft_thres);
388
389   if (ana_mode == TransitiveNway) {
390     the_paths.trans_path_search(all_comps);
391   }
392   else if (ana_mode == RadialNway) {
393     the_paths.radiate_path_search(all_comps);
394   }
395   else if (ana_mode == EntropyNway)
396   {
397     //unlike other methods, entropy needs to look at the sequence at this stage
398     some_Seqs.clear();
399     for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
400     {
401       some_Seqs.push_back(the_seqs[i].get_seq());
402     }
403
404     the_paths.setup_ent(ent_thres, some_Seqs); // ent analysis extra setup
405     the_paths.entropy_path_search(all_comps);
406   }
407
408   // old recursive transitive analysis function
409   else if (ana_mode == RecursiveNway)
410     the_paths.find_paths_r(all_comps);
411
412   the_paths.simple_refine();
413 }
414
415 void
416 Mussa::save()
417 {
418   string save_name;
419   fs::path flp_filepath;
420   fs::fstream save_file;
421   ostringstream append_info;
422   int dir_create_status;
423
424   if (not analysis_name.empty()) {
425     // not sure why, but gotta close file each time since can't pass 
426     // file streams
427     save_name = analysis_name;
428
429     // gotta do bit with adding win & thres if to be appended
430     if (win_append)
431     {
432       append_info.str("");
433       append_info <<  "_w" << window;
434       save_name += append_info.str();
435     }
436
437     if (thres_append)
438     {
439       append_info.str("");
440       append_info <<  "_t" << threshold;
441       save_name += append_info.str();
442     }
443     fs::path save_path( save_name);
444
445     if (not fs::exists(save_path)) {
446       fs::create_directory(save_path);
447     }
448     // save sequence and annots to a special mussa file
449     save_file.open(save_path / (save_name+".museq"), ios::out);
450     save_file << "<Mussa_Sequence>" << endl;
451
452     for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
453     {
454       the_seqs[i].save(save_file);
455     }
456
457     save_file << "</Mussa_Sequence>" << endl;
458     save_file.close();
459
460     // save nway paths to its mussa save file
461     the_paths.save(save_path / (save_name + ".muway"));
462
463     for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
464       for(vector<Sequence>::size_type i2 = i+1; i2 < the_seqs.size(); i2++)
465       {
466         append_info.str("");
467         append_info <<  "_sp_" << i << "v" << i2;
468         all_comps[i][i2].save(save_path/(save_name+append_info.str()+".flp"));
469       }
470   }
471 }
472
473 void
474 Mussa::save_muway(fs::path save_path)
475 {
476   the_paths.save(save_path);
477 }
478
479 void
480 Mussa::load(fs::path ana_file)
481 {
482   int i, i2;
483   fs::path file_path_base;
484   fs::path a_file_path; 
485   fs::path ana_path(ana_file);
486   bool parsing_path;
487   Sequence tmp_seq;
488   string err_msg;
489   ostringstream append_info;
490   vector<FLPs> empty_FLP_vector;
491   FLPs dummy_comp;
492
493   //cout << "ana_file name " << ana_file.string() << endl;
494   analysis_name = ana_path.leaf();
495   //cout << " ana_name " << analysis_name << endl;
496   file_path_base =  ana_path.branch_path() / analysis_name;
497   a_file_path = file_path_base / (analysis_name + ".muway");
498   //cout << " loading museq: " << a_file_path.string() << endl;
499   the_paths.load(a_file_path);
500   // perhaps this could be more elegent, but at least this'll let
501   // us know what our threshold and window sizes were when we load a muway
502   window = the_paths.get_window();
503   threshold = the_paths.get_threshold();
504   soft_thres = threshold;
505
506   int seq_num = the_paths.sequence_count();
507
508   a_file_path = file_path_base / (analysis_name + ".museq");
509
510   // this is a bit of a hack due to C++ not acting like it should with files
511   for (i = 1; i <= seq_num; i++)
512   {
513     tmp_seq.clear();
514     //cout << "mussa_class: loading museq frag... " << a_file_path.string() << endl;
515     tmp_seq.load_museq(a_file_path, i);
516     the_seqs.push_back(tmp_seq);
517   }
518   
519   empty_FLP_vector.clear();
520   for(i = 0; i < seq_num; i++)
521   {
522     all_comps.push_back(empty_FLP_vector); 
523     for(i2 = 0; i2 < seq_num; i2++)
524       all_comps[i].push_back(dummy_comp);
525   }
526   
527   for(i = 0; i < seq_num; i++)
528   {
529     for(i2 = i+1; i2 < seq_num; i2++)
530     {
531       append_info.str("");
532       append_info << analysis_name <<  "_sp_" << i << "v" << i2 << ".flp";
533       //cout << append_info.str() << endl;
534       a_file_path = file_path_base / append_info.str();
535       //cout << "path " << a_file_path.string() << endl;
536       all_comps[i][i2].load(a_file_path);
537       //cout << "real size = " << all_comps[i][i2].size() << endl;
538     }
539   }
540 }
541
542
543 void
544 Mussa::save_old()
545 {
546   fs::fstream save_file;
547
548   save_file.open(analysis_name, ios::out);
549
550   for(vector<Sequence>::size_type i = 0; i < the_seqs.size(); i++)
551     save_file << the_seqs[i].get_seq() << endl;
552
553   save_file << window << endl;
554   save_file.close();
555   //note more complex eventually since analysis_name may need to have
556   //window size, threshold and other stuff to modify it...
557   the_paths.save_old(analysis_name);
558 }
559
560
561 void
562 Mussa::load_old(char * load_file_path, int s_num)
563 {
564   fstream save_file;
565   string file_data_line; 
566   int i, space_split_i, comma_split_i;
567   vector<int> loaded_path;
568   string node_pair, node;
569   Sequence a_seq;
570
571   int seq_num = s_num;
572   the_paths.setup(0, 0);
573   save_file.open(load_file_path, ios::in);
574
575   // currently loads old mussa format
576
577   // get sequences
578   for(i = 0; i < seq_num; i++)
579   {
580     getline(save_file, file_data_line);
581     a_seq.set_seq(file_data_line);
582     the_seqs.push_back(a_seq);
583   }
584
585   // get window size
586   getline(save_file, file_data_line);
587   window = atoi(file_data_line.c_str());
588   // get paths
589
590   while (!save_file.eof())
591   {
592     loaded_path.clear();
593     getline(save_file, file_data_line);
594     if (file_data_line != "")
595     for(i = 0; i < seq_num; i++)
596     {
597       space_split_i = file_data_line.find(" ");
598       node_pair = file_data_line.substr(0,space_split_i);
599       //cout << "np= " << node_pair;
600       comma_split_i = node_pair.find(",");
601       node = node_pair.substr(comma_split_i+1);
602       //cout << "n= " << node << " ";
603       loaded_path.push_back(atoi (node.c_str()));
604       file_data_line = file_data_line.substr(space_split_i+1);
605     }
606     //cout << endl;
607     // FIXME: do we have any information about what the threshold should be?
608     the_paths.add_path(0, loaded_path);
609   }
610   save_file.close();
611
612   //the_paths.save("tmp.save");
613 }
614
615 void Mussa::add_motifs(const vector<string>& motifs, 
616                        const vector<Color>& colors)
617 {
618   if (motifs.size() != colors.size()) {
619     throw mussa_error("motif and color vectors must be the same size");
620   }
621
622   for(size_t i = 0; i != motifs.size(); ++i)
623   {
624     motif_sequences.insert(motifs[i]);
625     color_mapper.appendInstanceColor("motif", motifs[i], colors[i]);
626   }
627   update_sequences_motifs();
628 }
629
630 // I mostly split the ifstream out so I can use a stringstream to test it.
631 void Mussa::load_motifs(std::istream &in)
632 {
633   string seq;
634   float red;
635   float green;
636   float blue;
637
638   while(in.good())
639   {
640     in >> seq >> red >> green >> blue;
641     // if we couldn't read this line 'cause we're like at the end of the file
642     // try to exit the loop
643     if (!in.good())
644       break;
645     try {
646       seq = Sequence::motif_normalize(seq);
647     } catch(motif_normalize_error e) {
648       clog << "unable to parse " << seq << " skipping" << endl;
649       clog << e.what() << endl;
650       continue;
651     }
652     if (red < 0.0 or red > 1.0) {
653       clog << "invalid red value " << red << ". must be in range [0..1]" 
654            << endl;
655       continue;
656     }
657     if (green < 0.0 or green > 1.0) {
658       clog << "invalid green value " << green << ". must be in range [0..1]" 
659            << endl;
660       continue;
661     }
662     if (blue < 0.0 or blue > 1.0) {
663       clog << "invalid blue value " << blue << ". must be in range [0..1]" 
664            << endl;
665       continue;
666     }
667     if (motif_sequences.find(seq) == motif_sequences.end()) {
668       // sequence wasn't found
669       motif_sequences.insert(seq);
670       Color c(red, green, blue);
671       color_mapper.appendInstanceColor("motif", seq, c);
672     } else {
673       clog << "sequence " << seq << " was already defined skipping" 
674            << endl;
675       continue;
676     }
677   }
678   update_sequences_motifs();
679 }
680
681 void Mussa::load_motifs(fs::path filename)
682 {
683   fs::ifstream f;
684   f.open(filename, ifstream::in);
685   load_motifs(f);
686 }
687
688 void Mussa::update_sequences_motifs()
689 {
690   // once we've loaded all the motifs from the file, 
691   // lets attach them to the sequences
692   for(vector<Sequence>::iterator seq_i = the_seqs.begin();
693       seq_i != the_seqs.end();
694       ++seq_i)
695   {
696     // clear out old motifs
697     seq_i->clear_motifs();
698     // for all the motifs in our set, attach them to the current sequence
699     for(set<string>::iterator motif_i = motif_sequences.begin();
700         motif_i != motif_sequences.end();
701         ++motif_i)
702     {
703       seq_i->add_motif(*motif_i);
704     }
705   }
706 }
707
708 const set<string>& Mussa::motifs() const
709 {
710   return motif_sequences;
711 }
712
713 AnnotationColors& Mussa::colorMapper()
714 {
715   return color_mapper;
716 }