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