5 // Created by Sarah Westcott on 3/14/12.
6 // Copyright (c) 2012 Schloss Lab. All rights reserved.
9 #include "pcrseqscommand.h"
11 //**********************************************************************************************************************
12 vector<string> PcrSeqsCommand::setParameters(){
14 CommandParameter pfasta("fasta", "InputTypes", "", "", "none", "none", "none","fasta",false,true,true); parameters.push_back(pfasta);
15 CommandParameter poligos("oligos", "InputTypes", "", "", "ecolioligos", "none", "none","",false,false,true); parameters.push_back(poligos);
16 CommandParameter pname("name", "InputTypes", "", "", "NameCount", "none", "none","name",false,false,true); parameters.push_back(pname);
17 CommandParameter pcount("count", "InputTypes", "", "", "NameCount-CountGroup", "none", "none","count",false,false,true); parameters.push_back(pcount);
18 CommandParameter pgroup("group", "InputTypes", "", "", "CountGroup", "none", "none","group",false,false,true); parameters.push_back(pgroup);
19 CommandParameter ptax("taxonomy", "InputTypes", "", "", "none", "none", "none","taxonomy",false,false,true); parameters.push_back(ptax);
20 CommandParameter pecoli("ecoli", "InputTypes", "", "", "ecolioligos", "none", "none","",false,false); parameters.push_back(pecoli);
21 CommandParameter pstart("start", "Number", "", "-1", "", "", "","",false,false); parameters.push_back(pstart);
22 CommandParameter pend("end", "Number", "", "-1", "", "", "","",false,false); parameters.push_back(pend);
23 CommandParameter pnomatch("nomatch", "Multiple", "reject-keep", "reject", "", "", "","",false,false); parameters.push_back(pnomatch);
24 CommandParameter ppdiffs("pdiffs", "Number", "", "0", "", "", "","",false,false,true); parameters.push_back(ppdiffs);
26 CommandParameter pprocessors("processors", "Number", "", "1", "", "", "","",false,false,true); parameters.push_back(pprocessors);
27 CommandParameter pkeepprimer("keepprimer", "Boolean", "", "F", "", "", "","",false,false); parameters.push_back(pkeepprimer);
28 CommandParameter pkeepdots("keepdots", "Boolean", "", "T", "", "", "","",false,false); parameters.push_back(pkeepdots);
29 CommandParameter pinputdir("inputdir", "String", "", "", "", "", "","",false,false); parameters.push_back(pinputdir);
30 CommandParameter poutputdir("outputdir", "String", "", "", "", "", "","",false,false); parameters.push_back(poutputdir);
32 vector<string> myArray;
33 for (int i = 0; i < parameters.size(); i++) { myArray.push_back(parameters[i].name); }
37 m->errorOut(e, "PcrSeqsCommand", "setParameters");
41 //**********************************************************************************************************************
42 string PcrSeqsCommand::getHelpString(){
44 string helpString = "";
45 helpString += "The pcr.seqs command reads a fasta file.\n";
46 helpString += "The pcr.seqs command parameters are fasta, oligos, name, group, count, taxonomy, ecoli, start, end, nomatch, pdiffs, processors, keepprimer and keepdots.\n";
47 helpString += "The ecoli parameter is used to provide a fasta file containing a single reference sequence (e.g. for e. coli) this must be aligned. Mothur will trim to the start and end positions of the reference sequence.\n";
48 helpString += "The start parameter allows you to provide a starting position to trim to.\n";
49 helpString += "The end parameter allows you to provide a ending position to trim from.\n";
50 helpString += "The nomatch parameter allows you to decide what to do with sequences where the primer is not found. Default=reject, meaning remove from fasta file. if nomatch=true, then do nothing to sequence.\n";
51 helpString += "The processors parameter allows you to use multiple processors.\n";
52 helpString += "The keepprimer parameter allows you to keep the primer, default=false.\n";
53 helpString += "The keepdots parameter allows you to keep the leading and trailing .'s, default=true.\n";
54 helpString += "The pdiffs parameter is used to specify the number of differences allowed in the primer. The default is 0.\n";
55 helpString += "Note: No spaces between parameter labels (i.e. fasta), '=' and parameters (i.e.yourFasta).\n";
56 helpString += "For more details please check out the wiki http://www.mothur.org/wiki/Pcr.seqs .\n";
60 m->errorOut(e, "PcrSeqsCommand", "getHelpString");
64 //**********************************************************************************************************************
65 string PcrSeqsCommand::getOutputPattern(string type) {
69 if (type == "fasta") { pattern = "[filename],pcr,[extension]-[filename],[tag],pcr,[extension]"; }
70 else if (type == "taxonomy") { pattern = "[filename],pcr,[extension]"; }
71 else if (type == "name") { pattern = "[filename],pcr,[extension]"; }
72 else if (type == "group") { pattern = "[filename],pcr,[extension]"; }
73 else if (type == "count") { pattern = "[filename],pcr,[extension]"; }
74 else if (type == "accnos") { pattern = "[filename],bad.accnos"; }
75 else { m->mothurOut("[ERROR]: No definition for type " + type + " output pattern.\n"); m->control_pressed = true; }
80 m->errorOut(e, "PcrSeqsCommand", "getOutputPattern");
84 //**********************************************************************************************************************
86 PcrSeqsCommand::PcrSeqsCommand(){
88 abort = true; calledHelp = true;
90 vector<string> tempOutNames;
91 outputTypes["fasta"] = tempOutNames;
92 outputTypes["taxonomy"] = tempOutNames;
93 outputTypes["group"] = tempOutNames;
94 outputTypes["name"] = tempOutNames;
95 outputTypes["count"] = tempOutNames;
96 outputTypes["accnos"] = tempOutNames;
99 m->errorOut(e, "PcrSeqsCommand", "PcrSeqsCommand");
103 //***************************************************************************************************************
105 PcrSeqsCommand::PcrSeqsCommand(string option) {
108 abort = false; calledHelp = false;
110 //allow user to run help
111 if(option == "help") { help(); abort = true; calledHelp = true; }
112 else if(option == "citation") { citation(); abort = true; calledHelp = true;}
115 vector<string> myArray = setParameters();
117 OptionParser parser(option);
118 map<string,string> parameters = parser.getParameters();
120 ValidParameters validParameter;
121 map<string,string>::iterator it;
123 //check to make sure all parameters are valid for command
124 for (it = parameters.begin(); it != parameters.end(); it++) {
125 if (validParameter.isValidParameter(it->first, myArray, it->second) != true) { abort = true; }
128 //initialize outputTypes
129 vector<string> tempOutNames;
130 outputTypes["fasta"] = tempOutNames;
131 outputTypes["taxonomy"] = tempOutNames;
132 outputTypes["group"] = tempOutNames;
133 outputTypes["name"] = tempOutNames;
134 outputTypes["accnos"] = tempOutNames;
135 outputTypes["count"] = tempOutNames;
137 //if the user changes the input directory command factory will send this info to us in the output parameter
138 string inputDir = validParameter.validFile(parameters, "inputdir", false);
139 if (inputDir == "not found"){ inputDir = ""; }
142 it = parameters.find("fasta");
143 //user has given a template file
144 if(it != parameters.end()){
145 path = m->hasPath(it->second);
146 //if the user has not given a path then, add inputdir. else leave path alone.
147 if (path == "") { parameters["fasta"] = inputDir + it->second; }
150 it = parameters.find("oligos");
151 //user has given a template file
152 if(it != parameters.end()){
153 path = m->hasPath(it->second);
154 //if the user has not given a path then, add inputdir. else leave path alone.
155 if (path == "") { parameters["oligos"] = inputDir + it->second; }
158 it = parameters.find("ecoli");
159 //user has given a template file
160 if(it != parameters.end()){
161 path = m->hasPath(it->second);
162 //if the user has not given a path then, add inputdir. else leave path alone.
163 if (path == "") { parameters["ecoli"] = inputDir + it->second; }
166 it = parameters.find("taxonomy");
167 //user has given a template file
168 if(it != parameters.end()){
169 path = m->hasPath(it->second);
170 //if the user has not given a path then, add inputdir. else leave path alone.
171 if (path == "") { parameters["taxonomy"] = inputDir + it->second; }
174 it = parameters.find("name");
175 //user has given a template file
176 if(it != parameters.end()){
177 path = m->hasPath(it->second);
178 //if the user has not given a path then, add inputdir. else leave path alone.
179 if (path == "") { parameters["name"] = inputDir + it->second; }
182 it = parameters.find("group");
183 //user has given a template file
184 if(it != parameters.end()){
185 path = m->hasPath(it->second);
186 //if the user has not given a path then, add inputdir. else leave path alone.
187 if (path == "") { parameters["group"] = inputDir + it->second; }
190 it = parameters.find("count");
191 //user has given a template file
192 if(it != parameters.end()){
193 path = m->hasPath(it->second);
194 //if the user has not given a path then, add inputdir. else leave path alone.
195 if (path == "") { parameters["count"] = inputDir + it->second; }
201 //check for required parameters
202 fastafile = validParameter.validFile(parameters, "fasta", true);
203 if (fastafile == "not found") {
204 fastafile = m->getFastaFile();
205 if (fastafile != "") { m->mothurOut("Using " + fastafile + " as input file for the fasta parameter."); m->mothurOutEndLine(); }
206 else { m->mothurOut("You have no current fastafile and the fasta parameter is required."); m->mothurOutEndLine(); abort = true; }
207 }else if (fastafile == "not open") { fastafile = ""; abort = true; }
208 else { m->setFastaFile(fastafile); }
210 //if the user changes the output directory command factory will send this info to us in the output parameter
211 outputDir = validParameter.validFile(parameters, "outputdir", false); if (outputDir == "not found"){ outputDir = m->hasPath(fastafile); }
213 //check for optional parameter and set defaults
214 // ...at some point should added some additional type checking...
216 temp = validParameter.validFile(parameters, "keepprimer", false); if (temp == "not found") { temp = "f"; }
217 keepprimer = m->isTrue(temp);
219 temp = validParameter.validFile(parameters, "keepdots", false); if (temp == "not found") { temp = "t"; }
220 keepdots = m->isTrue(temp);
222 temp = validParameter.validFile(parameters, "oligos", true);
223 if (temp == "not found"){ oligosfile = ""; }
224 else if(temp == "not open"){ oligosfile = ""; abort = true; }
225 else { oligosfile = temp; m->setOligosFile(oligosfile); }
227 ecolifile = validParameter.validFile(parameters, "ecoli", true);
228 if (ecolifile == "not found"){ ecolifile = ""; }
229 else if(ecolifile == "not open"){ ecolifile = ""; abort = true; }
231 namefile = validParameter.validFile(parameters, "name", true);
232 if (namefile == "not found"){ namefile = ""; }
233 else if(namefile == "not open"){ namefile = ""; abort = true; }
234 else { m->setNameFile(namefile); }
236 groupfile = validParameter.validFile(parameters, "group", true);
237 if (groupfile == "not found"){ groupfile = ""; }
238 else if(groupfile == "not open"){ groupfile = ""; abort = true; }
239 else { m->setGroupFile(groupfile); }
241 countfile = validParameter.validFile(parameters, "count", true);
242 if (countfile == "not open") { countfile = ""; abort = true; }
243 else if (countfile == "not found") { countfile = ""; }
244 else { m->setCountTableFile(countfile); }
246 if ((namefile != "") && (countfile != "")) {
247 m->mothurOut("[ERROR]: you may only use one of the following: name or count."); m->mothurOutEndLine(); abort = true;
250 if ((groupfile != "") && (countfile != "")) {
251 m->mothurOut("[ERROR]: you may only use one of the following: group or count."); m->mothurOutEndLine(); abort=true;
254 taxfile = validParameter.validFile(parameters, "taxonomy", true);
255 if (taxfile == "not found"){ taxfile = ""; }
256 else if(taxfile == "not open"){ taxfile = ""; abort = true; }
257 else { m->setTaxonomyFile(taxfile); }
259 temp = validParameter.validFile(parameters, "start", false); if (temp == "not found") { temp = "-1"; }
260 m->mothurConvert(temp, start);
262 temp = validParameter.validFile(parameters, "end", false); if (temp == "not found") { temp = "-1"; }
263 m->mothurConvert(temp, end);
265 temp = validParameter.validFile(parameters, "processors", false); if (temp == "not found"){ temp = m->getProcessors(); }
266 m->setProcessors(temp);
267 m->mothurConvert(temp, processors);
269 temp = validParameter.validFile(parameters, "pdiffs", false); if (temp == "not found") { temp = "0"; }
270 m->mothurConvert(temp, pdiffs);
272 nomatch = validParameter.validFile(parameters, "nomatch", false); if (nomatch == "not found") { nomatch = "reject"; }
274 if ((nomatch != "reject") && (nomatch != "keep")) { m->mothurOut("[ERROR]: " + nomatch + " is not a valid entry for nomatch. Choices are reject and keep.\n"); abort = true; }
277 if ((oligosfile == "") && (ecolifile == "") && (start == -1) && (end == -1)) {
278 m->mothurOut("[ERROR]: You did not set any options. Please provide an oligos or ecoli file, or set start or end.\n"); abort = true;
281 if ((oligosfile == "") && (ecolifile == "") && (start < 0) && (end == -1)) { m->mothurOut("[ERROR]: Invalid start value.\n"); abort = true; }
283 if ((ecolifile != "") && (start != -1) && (end != -1)) {
284 m->mothurOut("[ERROR]: You provided an ecoli file , but set the start or end parameters. Unsure what you intend. When you provide the ecoli file, mothur thinks you want to use the start and end of the sequence in the ecoli file.\n"); abort = true;
288 if ((oligosfile != "") && (ecolifile != "")) {
289 m->mothurOut("[ERROR]: You can not use an ecoli file at the same time as an oligos file.\n"); abort = true;
292 //check to make sure you didn't forget the name file by mistake
293 if (countfile == "") {
294 if (namefile == "") {
295 vector<string> files; files.push_back(fastafile);
296 parser.getNameFile(files);
302 catch(exception& e) {
303 m->errorOut(e, "PcrSeqsCommand", "PcrSeqsCommand");
307 //***************************************************************************************************************
309 int PcrSeqsCommand::execute(){
312 if (abort == true) { if (calledHelp) { return 0; } return 2; }
314 int start = time(NULL);
317 string thisOutputDir = outputDir;
318 if (outputDir == "") { thisOutputDir += m->hasPath(fastafile); }
319 map<string, string> variables;
320 variables["[filename]"] = thisOutputDir + m->getRootName(m->getSimpleName(fastafile));
321 variables["[extension]"] = m->getExtension(fastafile);
322 string trimSeqFile = getOutputFileName("fasta",variables);
323 outputNames.push_back(trimSeqFile); outputTypes["fasta"].push_back(trimSeqFile);
324 variables["[tag]"] = "scrap";
325 string badSeqFile = getOutputFileName("fasta",variables);
329 if(oligosfile != ""){ readOligos(); if (m->debug) { m->mothurOut("[DEBUG]: read oligos file. numprimers = " + toString(primers.size()) + ", revprimers = " + toString(revPrimer.size()) + ".\n"); } } if (m->control_pressed) { return 0; }
330 if(ecolifile != "") { readEcoli(); } if (m->control_pressed) { return 0; }
332 vector<unsigned long long> positions;
333 int numFastaSeqs = 0;
334 #if defined (__APPLE__) || (__MACH__) || (linux) || (__linux) || (__linux__) || (__unix__) || (__unix)
335 positions = m->divideFile(fastafile, processors);
336 for (int i = 0; i < (positions.size()-1); i++) { lines.push_back(linePair(positions[i], positions[(i+1)])); }
338 if (processors == 1) {
339 lines.push_back(linePair(0, 1000));
341 positions = m->setFilePosFasta(fastafile, numFastaSeqs);
342 if (positions.size() < processors) { processors = positions.size(); }
344 //figure out how many sequences you have to process
345 int numSeqsPerProcessor = numFastaSeqs / processors;
346 for (int i = 0; i < processors; i++) {
347 int startIndex = i * numSeqsPerProcessor;
348 if(i == (processors - 1)){ numSeqsPerProcessor = numFastaSeqs - i * numSeqsPerProcessor; }
349 lines.push_back(linePair(positions[startIndex], numSeqsPerProcessor));
353 if (m->control_pressed) { return 0; }
355 set<string> badNames;
356 numFastaSeqs = createProcesses(fastafile, trimSeqFile, badSeqFile, badNames);
358 if (m->control_pressed) { return 0; }
360 //don't write or keep if blank
361 if (badNames.size() != 0) { writeAccnos(badNames); }
362 if (m->isBlank(badSeqFile)) { m->mothurRemove(badSeqFile); }
363 else { outputNames.push_back(badSeqFile); outputTypes["fasta"].push_back(badSeqFile); }
365 if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) { m->mothurRemove(outputNames[i]); } return 0; }
366 if (namefile != "") { readName(badNames); }
367 if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) { m->mothurRemove(outputNames[i]); } return 0; }
368 if (groupfile != "") { readGroup(badNames); }
369 if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) { m->mothurRemove(outputNames[i]); } return 0; }
370 if (taxfile != "") { readTax(badNames); }
371 if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) { m->mothurRemove(outputNames[i]); } return 0; }
372 if (countfile != "") { readCount(badNames); }
373 if (m->control_pressed) { for (int i = 0; i < outputNames.size(); i++) { m->mothurRemove(outputNames[i]); } return 0; }
375 m->mothurOutEndLine();
376 m->mothurOut("Output File Names: "); m->mothurOutEndLine();
377 for (int i = 0; i < outputNames.size(); i++) { m->mothurOut(outputNames[i]); m->mothurOutEndLine(); }
378 m->mothurOutEndLine();
379 m->mothurOutEndLine();
381 //set fasta file as new current fastafile
383 itTypes = outputTypes.find("fasta");
384 if (itTypes != outputTypes.end()) {
385 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setFastaFile(current); }
388 itTypes = outputTypes.find("name");
389 if (itTypes != outputTypes.end()) {
390 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setNameFile(current); }
393 itTypes = outputTypes.find("group");
394 if (itTypes != outputTypes.end()) {
395 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setGroupFile(current); }
398 itTypes = outputTypes.find("accnos");
399 if (itTypes != outputTypes.end()) {
400 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setAccnosFile(current); }
403 itTypes = outputTypes.find("taxonomy");
404 if (itTypes != outputTypes.end()) {
405 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setTaxonomyFile(current); }
408 itTypes = outputTypes.find("count");
409 if (itTypes != outputTypes.end()) {
410 if ((itTypes->second).size() != 0) { current = (itTypes->second)[0]; m->setCountTableFile(current); }
413 m->mothurOut("It took " + toString(time(NULL) - start) + " secs to screen " + toString(numFastaSeqs) + " sequences.");
414 m->mothurOutEndLine();
420 catch(exception& e) {
421 m->errorOut(e, "PcrSeqsCommand", "execute");
425 /**************************************************************************************************/
426 int PcrSeqsCommand::createProcesses(string filename, string goodFileName, string badFileName, set<string>& badSeqNames) {
429 vector<int> processIDS;
432 int pstart = -1; int pend = -1;
433 bool adjustNeeded = false;
435 #if defined (__APPLE__) || (__MACH__) || (linux) || (__linux) || (__linux__) || (__unix__) || (__unix)
437 //loop through and create all the processes you want
438 while (process != processors) {
442 processIDS.push_back(pid); //create map from line number to pid so you can append files in correct order later
445 string locationsFile = toString(getpid()) + ".temp";
446 num = driverPcr(filename, goodFileName + toString(getpid()) + ".temp", badFileName + toString(getpid()) + ".temp", locationsFile, badSeqNames, lines[process], pstart, adjustNeeded);
448 //pass numSeqs to parent
450 string tempFile = filename + toString(getpid()) + ".num.temp";
451 m->openOutputFile(tempFile, out);
452 out << pstart << '\t' << adjustNeeded << endl;
453 out << num << '\t' << badSeqNames.size() << endl;
454 for (set<string>::iterator it = badSeqNames.begin(); it != badSeqNames.end(); it++) {
455 out << (*it) << endl;
461 m->mothurOut("[ERROR]: unable to spawn the necessary processes."); m->mothurOutEndLine();
462 for (int i = 0; i < processIDS.size(); i++) { kill (processIDS[i], SIGINT); }
467 string locationsFile = toString(getpid()) + ".temp";
468 num = driverPcr(filename, goodFileName, badFileName, locationsFile, badSeqNames, lines[0], pstart, adjustNeeded);
470 //force parent to wait until all the processes are done
471 for (int i=0;i<processIDS.size();i++) {
472 int temp = processIDS[i];
476 for (int i = 0; i < processIDS.size(); i++) {
478 string tempFile = filename + toString(processIDS[i]) + ".num.temp";
479 m->openInputFile(tempFile, in);
480 int numBadNames = 0; string name = "";
481 int tpstart = -1; bool tempAdjust = false;
484 in >> tpstart >> tempAdjust; m->gobble(in);
486 if (tempAdjust) { adjustNeeded = true; }
488 if (tpstart != pstart) { adjustNeeded = true; }
489 if (tpstart < pstart) { pstart = tpstart; } //smallest start
491 int tempNum = 0; in >> tempNum >> numBadNames; num += tempNum; m->gobble(in);
493 for (int j = 0; j < numBadNames; j++) {
494 in >> name; m->gobble(in);
495 badSeqNames.insert(name);
497 in.close(); m->mothurRemove(tempFile);
499 m->appendFiles((goodFileName + toString(processIDS[i]) + ".temp"), goodFileName);
500 m->mothurRemove((goodFileName + toString(processIDS[i]) + ".temp"));
502 m->appendFiles((badFileName + toString(processIDS[i]) + ".temp"), badFileName);
503 m->mothurRemove((badFileName + toString(processIDS[i]) + ".temp"));
505 m->appendFiles((toString(processIDS[i]) + ".temp"), locationsFile);
506 m->mothurRemove((toString(processIDS[i]) + ".temp"));
510 //////////////////////////////////////////////////////////////////////////////////////////////////////
511 //Windows version shared memory, so be careful when passing variables through the sumScreenData struct.
512 //Above fork() will clone, so memory is separate, but that's not the case with windows,
513 //Taking advantage of shared memory to allow both threads to add info to badSeqNames.
514 //////////////////////////////////////////////////////////////////////////////////////////////////////
516 vector<pcrData*> pDataArray;
517 DWORD dwThreadIdArray[processors-1];
518 HANDLE hThreadArray[processors-1];
520 string locationsFile = "locationsFile.txt";
521 m->mothurRemove(locationsFile);
522 m->mothurRemove(goodFileName);
523 m->mothurRemove(badFileName);
525 //Create processor worker threads.
526 for( int i=0; i<processors-1; i++ ){
528 string extension = "";
529 if (i!=0) {extension += toString(i) + ".temp"; processIDS.push_back(i); }
531 // Allocate memory for thread data.
532 pcrData* tempPcr = new pcrData(filename, goodFileName+extension, badFileName+extension, locationsFile+extension, m, oligosfile, ecolifile, primers, revPrimer, nomatch, keepprimer, keepdots, start, end, length, pdiffs, lines[i].start, lines[i].end);
533 pDataArray.push_back(tempPcr);
535 //default security attributes, thread function name, argument to thread function, use default creation flags, returns the thread identifier
536 hThreadArray[i] = CreateThread(NULL, 0, MyPcrThreadFunction, pDataArray[i], 0, &dwThreadIdArray[i]);
540 num = driverPcr(filename, (goodFileName+toString(processors-1)+".temp"), (badFileName+toString(processors-1)+".temp"), (locationsFile+toString(processors-1)+".temp"), badSeqNames, lines[processors-1], pstart, adjustNeeded);
541 processIDS.push_back(processors-1);
543 //Wait until all threads have terminated.
544 WaitForMultipleObjects(processors-1, hThreadArray, TRUE, INFINITE);
546 //Close all thread handles and free memory allocations.
547 for(int i=0; i < pDataArray.size(); i++){
548 num += pDataArray[i]->count;
549 if (pDataArray[i]->count != pDataArray[i]->fend) {
550 m->mothurOut("[ERROR]: process " + toString(i) + " only processed " + toString(pDataArray[i]->count) + " of " + toString(pDataArray[i]->fend) + " sequences assigned to it, quitting. \n"); m->control_pressed = true;
552 if (pDataArray[i]->adjustNeeded) { adjustNeeded = true; }
553 if (pDataArray[i]->pstart != -1) {
554 if (pDataArray[i]->pstart != pstart) { adjustNeeded = true; }
555 if (pDataArray[i]->pstart < pstart) { pstart = pDataArray[i]->pstart; }
558 for (set<string>::iterator it = pDataArray[i]->badSeqNames.begin(); it != pDataArray[i]->badSeqNames.end(); it++) { badSeqNames.insert(*it); }
559 CloseHandle(hThreadArray[i]);
560 delete pDataArray[i];
563 for (int i = 0; i < processIDS.size(); i++) {
564 m->appendFiles((goodFileName + toString(processIDS[i]) + ".temp"), goodFileName);
565 m->mothurRemove((goodFileName + toString(processIDS[i]) + ".temp"));
567 m->appendFiles((badFileName + toString(processIDS[i]) + ".temp"), badFileName);
568 m->mothurRemove((badFileName + toString(processIDS[i]) + ".temp"));
570 m->appendFiles((locationsFile+toString(processIDS[i]) + ".temp"), locationsFile);
571 m->mothurRemove((locationsFile+toString(processIDS[i]) + ".temp"));
579 //find pend - pend is the biggest ending value, but we must account for when we adjust the start. That adjustment may make the "new" end larger then the largest end. So lets find out what that "new" end will be.
580 ifstream inLocations;
581 m->openInputFile(locationsFile, inLocations);
583 while(!inLocations.eof()) {
585 if (m->control_pressed) { break; }
588 int thisStart = -1; int thisEnd = -1;
589 if (primers.size() != 0) { inLocations >> name >> thisStart; m->gobble(inLocations); }
590 if (revPrimer.size() != 0) { inLocations >> name >> thisEnd; m->gobble(inLocations); }
591 else { pend = -1; break; }
595 if (thisStart != -1) {
596 if (thisStart != pstart) { myDiff += (thisStart - pstart); }
600 int myEnd = thisEnd + myDiff;
601 //cout << name << '\t' << thisStart << '\t' << thisEnd << " diff = " << myDiff << '\t' << myEnd << endl;
604 if (myEnd > pend) { pend = myEnd; }
610 adjustDots(goodFileName, locationsFile, pstart, pend);
616 catch(exception& e) {
617 m->errorOut(e, "PcrSeqsCommand", "createProcesses");
622 //**********************************************************************************************************************
623 int PcrSeqsCommand::driverPcr(string filename, string goodFasta, string badFasta, string locationsName, set<string>& badSeqNames, linePair filePos, int& pstart, bool& adjustNeeded){
626 m->openOutputFile(goodFasta, goodFile);
629 m->openOutputFile(badFasta, badFile);
631 ofstream locationsFile;
632 m->openOutputFile(locationsName, locationsFile);
635 m->openInputFile(filename, inFASTA);
637 inFASTA.seekg(filePos.start);
642 set<int> locations; //locations[0] = beginning locations,
644 //pdiffs, bdiffs, primers, barcodes, revPrimers
645 map<string, int> faked;
646 TrimOligos trim(pdiffs, 0, primers, faked, revPrimer);
650 if (m->control_pressed) { break; }
652 Sequence currSeq(inFASTA); m->gobble(inFASTA);
654 if (fileAligned) { //assume aligned until proven otherwise
655 lengths.insert(currSeq.getAligned().length());
656 if (lengths.size() > 1) { fileAligned = false; }
659 string trashCode = "";
660 string locationsString = "";
663 if (currSeq.getName() != "") {
665 if (m->debug) { m->mothurOut("[DEBUG]: seq name = " + currSeq.getName() + ".\n"); }
668 if (oligosfile != "") {
669 map<int, int> mapAligned;
670 bool aligned = isAligned(currSeq.getAligned(), mapAligned);
673 if (primers.size() != 0) {
674 int primerStart = 0; int primerEnd = 0;
675 bool good = trim.findForward(currSeq, primerStart, primerEnd);
677 if(!good){ if (nomatch == "reject") { goodSeq = false; } trashCode += "f"; }
682 if (keepdots) { currSeq.filterToPos(mapAligned[primerEnd-1]+1); } //mapAligned[primerEnd-1] is the location of the last base in the primer. we want to trim to the space just after that. The -1 & +1 ensures if the primer is followed by gaps they are not trimmed causing an aligned sequence dataset to become unaligned.
684 currSeq.setAligned(currSeq.getAligned().substr(mapAligned[primerEnd-1]+1));
686 thisPStart = mapAligned[primerEnd-1]+1; //locations[0].insert(mapAligned[primerEnd-1]+1);
687 locationsString += currSeq.getName() + "\t" + toString(mapAligned[primerEnd-1]+1) + "\n";
692 if (keepdots) { currSeq.filterToPos(mapAligned[primerStart]); }
694 currSeq.setAligned(currSeq.getAligned().substr(mapAligned[primerStart]));
696 thisPStart = mapAligned[primerStart]; //locations[0].insert(mapAligned[primerStart]);
697 locationsString += currSeq.getName() + "\t" + toString(mapAligned[primerStart]) + "\n";
701 isAligned(currSeq.getAligned(), mapAligned);
703 if (!keepprimer) { currSeq.setAligned(currSeq.getUnaligned().substr(primerEnd)); }
704 else { currSeq.setAligned(currSeq.getUnaligned().substr(primerStart)); }
709 //process reverse primers
710 if (revPrimer.size() != 0) {
711 int primerStart = 0; int primerEnd = 0;
712 bool good = trim.findReverse(currSeq, primerStart, primerEnd);
713 if(!good){ if (nomatch == "reject") { goodSeq = false; } trashCode += "r"; }
718 if (keepdots) { currSeq.filterFromPos(mapAligned[primerStart]); }
720 currSeq.setAligned(currSeq.getAligned().substr(0, mapAligned[primerStart]));
722 thisPEnd = mapAligned[primerStart]; //locations[1].insert(mapAligned[primerStart]);
723 locationsString += currSeq.getName() + "\t" + toString(mapAligned[primerStart]) + "\n";
728 if (keepdots) { currSeq.filterFromPos(mapAligned[primerEnd-1]+1); }
730 currSeq.setAligned(currSeq.getAligned().substr(0, mapAligned[primerEnd-1]+1));
732 thisPEnd = mapAligned[primerEnd-1]+1; //locations[1].insert(mapAligned[primerEnd-1]+1);
733 locationsString += currSeq.getName() + "\t" + toString(mapAligned[primerEnd-1]+1) + "\n";
739 if (!keepprimer) { currSeq.setAligned(currSeq.getUnaligned().substr(0, primerStart)); }
740 else { currSeq.setAligned(currSeq.getUnaligned().substr(0, primerEnd)); }
744 }else if (ecolifile != "") {
745 //make sure the seqs are aligned
746 if (!fileAligned) { m->mothurOut("[ERROR]: seqs are not aligned. When using start and end your sequences must be aligned.\n"); m->control_pressed = true; break; }
747 else if (currSeq.getAligned().length() != length) {
748 m->mothurOut("[ERROR]: seqs are not the same length as ecoli seq. When using ecoli option your sequences must be aligned and the same length as the ecoli sequence.\n"); m->control_pressed = true; break;
751 currSeq.filterToPos(start);
752 currSeq.filterFromPos(end);
754 string seqString = currSeq.getAligned().substr(0, end);
755 seqString = seqString.substr(start);
756 currSeq.setAligned(seqString);
759 }else{ //using start and end to trim
760 //make sure the seqs are aligned
761 if (!fileAligned) { m->mothurOut("[ERROR]: seqs are not aligned. When using start and end your sequences must be aligned.\n"); m->control_pressed = true; break; }
764 if (end > currSeq.getAligned().length()) { m->mothurOut("[ERROR]: end is longer than your sequence length, aborting.\n"); m->control_pressed = true; break; }
766 if (keepdots) { currSeq.filterFromPos(end); }
768 string seqString = currSeq.getAligned().substr(0, end);
769 currSeq.setAligned(seqString);
774 if (keepdots) { currSeq.filterToPos(start); }
776 string seqString = currSeq.getAligned().substr(start);
777 currSeq.setAligned(seqString);
783 //trimming removed all bases
784 if (currSeq.getUnaligned() == "") { goodSeq = false; }
787 currSeq.printSequence(goodFile);
788 if (m->debug) { m->mothurOut("[DEBUG]: " + locationsString + "\n"); }
789 if (thisPStart != -1) { locations.insert(thisPStart); }
790 if (locationsString != "") { locationsFile << locationsString; }
793 badSeqNames.insert(currSeq.getName());
794 currSeq.setName(currSeq.getName() + '|' + trashCode);
795 currSeq.printSequence(badFile);
800 #if defined (__APPLE__) || (__MACH__) || (linux) || (__linux) || (__linux__) || (__unix__) || (__unix)
801 unsigned long long pos = inFASTA.tellg();
802 if ((pos == -1) || (pos >= filePos.end)) { break; }
804 if (inFASTA.eof()) { break; }
808 if((count) % 100 == 0){ m->mothurOutJustToScreen("Processing sequence: " + toString(count)+"\n"); }
811 if((count) % 100 != 0){ m->mothurOutJustToScreen("Processing sequence: " + toString(count)+"\n"); }
816 locationsFile.close();
818 if (m->debug) { m->mothurOut("[DEBUG]: fileAligned = " + toString(fileAligned) +'\n'); }
820 if (fileAligned && !keepdots) { //print out smallest start value and largest end value
821 if (locations.size() > 1) { adjustNeeded = true; }
822 if (primers.size() != 0) { set<int>::iterator it = locations.begin(); pstart = *it; }
827 catch(exception& e) {
828 m->errorOut(e, "PcrSeqsCommand", "driverPcr");
832 //********************************************************************/
833 bool PcrSeqsCommand::isAligned(string seq, map<int, int>& aligned){
836 bool isAligned = false;
839 for (int i = 0; i < seq.length(); i++) {
840 if (!isalpha(seq[i])) { isAligned = true; }
841 else { aligned[countBases] = i; countBases++; } //maps location in unaligned -> location in aligned.
842 } //ie. the 3rd base may be at spot 10 in the alignment
843 //later when we trim we want to trim from spot 10.
846 catch(exception& e) {
847 m->errorOut(e, "PcrSeqsCommand", "isAligned");
851 //**********************************************************************************************************************
852 int PcrSeqsCommand::adjustDots(string goodFasta, string locations, int pstart, int pend){
855 m->openInputFile(goodFasta, inFasta);
857 ifstream inLocations;
858 m->openInputFile(locations, inLocations);
861 m->openOutputFile(goodFasta+".temp", out);
864 //cout << pstart << '\t' << pend << endl;
865 //if (pstart > pend) { //swap them
867 while(!inFasta.eof()) {
868 if(m->control_pressed) { break; }
870 Sequence seq(inFasta); m->gobble(inFasta);
873 int thisStart = -1; int thisEnd = -1;
874 if (primers.size() != 0) { inLocations >> name >> thisStart; m->gobble(inLocations); }
875 if (revPrimer.size() != 0) { inLocations >> name >> thisEnd; m->gobble(inLocations); }
878 //cout << seq.getName() << '\t' << thisStart << '\t' << thisEnd << '\t' << seq.getAligned().length() << endl;
879 //cout << seq.getName() << '\t' << pstart << '\t' << pend << endl;
881 if (name != seq.getName()) { m->mothurOut("[ERROR]: name mismatch in pcr.seqs.\n"); }
884 if (thisStart != -1) {
885 if (thisStart != pstart) {
887 for (int i = pstart; i < thisStart; i++) { dots += "."; }
888 thisEnd += dots.length();
889 dots += seq.getAligned();
890 seq.setAligned(dots);
897 if (thisEnd != pend) {
898 string dots = seq.getAligned();
899 for (int i = thisEnd; i < pend; i++) { dots += "."; }
900 seq.setAligned(dots);
904 lengths.insert(seq.getAligned().length());
907 seq.printSequence(out);
912 m->mothurRemove(locations);
913 m->mothurRemove(goodFasta);
914 m->renameFile(goodFasta+".temp", goodFasta);
916 //cout << "final lengths = \n";
917 //for (set<int>::iterator it = lengths.begin(); it != lengths.end(); it++) {
918 //cout << *it << endl;
919 // cout << lengths.count(*it) << endl;
924 catch(exception& e) {
925 m->errorOut(e, "PcrSeqsCommand", "adjustDots");
929 //********************************************************************/
930 string PcrSeqsCommand::reverseOligo(string oligo){
934 for(int i=oligo.length()-1;i>=0;i--){
936 if(oligo[i] == 'A') { reverse += 'T'; }
937 else if(oligo[i] == 'T'){ reverse += 'A'; }
938 else if(oligo[i] == 'U'){ reverse += 'A'; }
940 else if(oligo[i] == 'G'){ reverse += 'C'; }
941 else if(oligo[i] == 'C'){ reverse += 'G'; }
943 else if(oligo[i] == 'R'){ reverse += 'Y'; }
944 else if(oligo[i] == 'Y'){ reverse += 'R'; }
946 else if(oligo[i] == 'M'){ reverse += 'K'; }
947 else if(oligo[i] == 'K'){ reverse += 'M'; }
949 else if(oligo[i] == 'W'){ reverse += 'W'; }
950 else if(oligo[i] == 'S'){ reverse += 'S'; }
952 else if(oligo[i] == 'B'){ reverse += 'V'; }
953 else if(oligo[i] == 'V'){ reverse += 'B'; }
955 else if(oligo[i] == 'D'){ reverse += 'H'; }
956 else if(oligo[i] == 'H'){ reverse += 'D'; }
958 else { reverse += 'N'; }
964 catch(exception& e) {
965 m->errorOut(e, "PcrSeqsCommand", "reverseOligo");
970 //***************************************************************************************************************
971 bool PcrSeqsCommand::readOligos(){
974 m->openInputFile(oligosfile, inOligos);
976 string type, oligo, group;
979 while(!inOligos.eof()){
983 if(type[0] == '#'){ //ignore
984 while (!inOligos.eof()) { char c = inOligos.get(); if (c == 10 || c == 13){ break; } } // get rest of line if there's any crap there
988 //make type case insensitive
989 for(int i=0;i<type.length();i++){ type[i] = toupper(type[i]); }
993 for(int i=0;i<oligo.length();i++){
994 oligo[i] = toupper(oligo[i]);
995 if(oligo[i] == 'U') { oligo[i] = 'T'; }
998 if(type == "FORWARD"){
999 // get rest of line in case there is a primer name
1000 while (!inOligos.eof()) {
1001 char c = inOligos.get();
1002 if (c == 10 || c == 13 || c == -1){ break; }
1003 else if (c == 32 || c == 9){;} //space or tab
1005 primers[oligo] = primerCount; primerCount++;
1006 //cout << "for oligo = " << oligo << endl;
1007 }else if(type == "REVERSE"){
1008 string oligoRC = reverseOligo(oligo);
1009 revPrimer.push_back(oligoRC);
1010 //cout << "rev oligo = " << oligo << " reverse = " << oligoRC << endl;
1011 }else if(type == "BARCODE"){
1013 }else if(type == "PRIMER"){
1014 m->gobble(inOligos);
1015 primers[oligo] = primerCount; primerCount++;
1020 for(int i=0;i<roligo.length();i++){
1021 roligo[i] = toupper(roligo[i]);
1022 if(roligo[i] == 'U') { roligo[i] = 'T'; }
1024 revPrimer.push_back(reverseOligo(roligo));
1026 // get rest of line in case there is a primer name
1027 while (!inOligos.eof()) {
1028 char c = inOligos.get();
1029 if (c == 10 || c == 13 || c == -1){ break; }
1030 else if (c == 32 || c == 9){;} //space or tab
1032 //cout << "prim oligo = " << oligo << " reverse = " << roligo << endl;
1033 }else if((type == "LINKER")||(type == "SPACER")) {;}
1034 else{ m->mothurOut(type + " is not recognized as a valid type. Choices are primer, forward, reverse, linker, spacer and barcode. Ignoring " + oligo + "."); m->mothurOutEndLine(); m->control_pressed = true; }
1036 m->gobble(inOligos);
1040 if ((primers.size() == 0) && (revPrimer.size() == 0)) {
1041 m->mothurOut("[ERROR]: your oligos file does not contain valid primers or reverse primers. Please correct."); m->mothurOutEndLine();
1042 m->control_pressed = true;
1048 }catch(exception& e) {
1049 m->errorOut(e, "PcrSeqsCommand", "readOligos");
1053 //***************************************************************************************************************
1054 bool PcrSeqsCommand::readEcoli(){
1057 m->openInputFile(ecolifile, in);
1062 length = ecoli.getAligned().length();
1063 start = ecoli.getStartPos();
1064 end = ecoli.getEndPos();
1065 }else { in.close(); m->control_pressed = true; return false; }
1070 catch(exception& e) {
1071 m->errorOut(e, "PcrSeqsCommand", "readEcoli");
1076 //***************************************************************************************************************
1077 int PcrSeqsCommand::writeAccnos(set<string> badNames){
1079 string thisOutputDir = outputDir;
1080 if (outputDir == "") { thisOutputDir += m->hasPath(fastafile); }
1081 map<string, string> variables;
1082 variables["[filename]"] = thisOutputDir + m->getRootName(m->getSimpleName(fastafile));
1083 string outputFileName = getOutputFileName("accnos",variables);
1084 outputNames.push_back(outputFileName); outputTypes["accnos"].push_back(outputFileName);
1087 m->openOutputFile(outputFileName, out);
1089 for (set<string>::iterator it = badNames.begin(); it != badNames.end(); it++) {
1090 if (m->control_pressed) { break; }
1091 out << (*it) << endl;
1097 catch(exception& e) {
1098 m->errorOut(e, "PcrSeqsCommand", "writeAccnos");
1103 //***************************************************************************************************************
1104 int PcrSeqsCommand::readName(set<string>& names){
1106 string thisOutputDir = outputDir;
1107 if (outputDir == "") { thisOutputDir += m->hasPath(namefile); }
1108 map<string, string> variables;
1109 variables["[filename]"] = thisOutputDir + m->getRootName(m->getSimpleName(namefile));
1110 variables["[extension]"] = m->getExtension(namefile);
1111 string outputFileName = getOutputFileName("name", variables);
1114 m->openOutputFile(outputFileName, out);
1117 m->openInputFile(namefile, in);
1118 string name, firstCol, secondCol;
1120 bool wroteSomething = false;
1121 int removedCount = 0;
1124 if (m->control_pressed) { in.close(); out.close(); m->mothurRemove(outputFileName); return 0; }
1126 in >> firstCol; m->gobble(in);
1129 string savedSecond = secondCol;
1130 vector<string> parsedNames;
1131 m->splitAtComma(secondCol, parsedNames);
1133 vector<string> validSecond; validSecond.clear();
1134 for (int i = 0; i < parsedNames.size(); i++) {
1135 if (names.count(parsedNames[i]) == 0) {
1136 validSecond.push_back(parsedNames[i]);
1140 if (validSecond.size() != parsedNames.size()) { //we want to get rid of someone, so get rid of everyone
1141 for (int i = 0; i < parsedNames.size(); i++) { names.insert(parsedNames[i]); }
1142 removedCount += parsedNames.size();
1144 out << firstCol << '\t' << savedSecond << endl;
1145 wroteSomething = true;
1152 if (wroteSomething == false) { m->mothurOut("Your file contains only sequences from the .accnos file."); m->mothurOutEndLine(); }
1153 outputTypes["name"].push_back(outputFileName); outputNames.push_back(outputFileName);
1155 m->mothurOut("Removed " + toString(removedCount) + " sequences from your name file."); m->mothurOutEndLine();
1159 catch(exception& e) {
1160 m->errorOut(e, "PcrSeqsCommand", "readName");
1164 //**********************************************************************************************************************
1165 int PcrSeqsCommand::readGroup(set<string> names){
1167 string thisOutputDir = outputDir;
1168 if (outputDir == "") { thisOutputDir += m->hasPath(groupfile); }
1169 map<string, string> variables;
1170 variables["[filename]"] = thisOutputDir + m->getRootName(m->getSimpleName(groupfile));
1171 variables["[extension]"] = m->getExtension(groupfile);
1172 string outputFileName = getOutputFileName("group", variables);
1175 m->openOutputFile(outputFileName, out);
1178 m->openInputFile(groupfile, in);
1181 bool wroteSomething = false;
1182 int removedCount = 0;
1185 if (m->control_pressed) { in.close(); out.close(); m->mothurRemove(outputFileName); return 0; }
1187 in >> name; //read from first column
1188 in >> group; //read from second column
1190 //if this name is in the accnos file
1191 if (names.count(name) == 0) {
1192 wroteSomething = true;
1193 out << name << '\t' << group << endl;
1194 }else { removedCount++; }
1201 if (wroteSomething == false) { m->mothurOut("Your file contains only sequences from the .accnos file."); m->mothurOutEndLine(); }
1202 outputTypes["group"].push_back(outputFileName); outputNames.push_back(outputFileName);
1204 m->mothurOut("Removed " + toString(removedCount) + " sequences from your group file."); m->mothurOutEndLine();
1209 catch(exception& e) {
1210 m->errorOut(e, "PcrSeqsCommand", "readGroup");
1214 //**********************************************************************************************************************
1215 int PcrSeqsCommand::readTax(set<string> names){
1217 string thisOutputDir = outputDir;
1218 if (outputDir == "") { thisOutputDir += m->hasPath(taxfile); }
1219 map<string, string> variables;
1220 variables["[filename]"] = thisOutputDir + m->getRootName(m->getSimpleName(taxfile));
1221 variables["[extension]"] = m->getExtension(taxfile);
1222 string outputFileName = getOutputFileName("taxonomy", variables);
1225 m->openOutputFile(outputFileName, out);
1228 m->openInputFile(taxfile, in);
1231 bool wroteSomething = false;
1232 int removedCount = 0;
1235 if (m->control_pressed) { in.close(); out.close(); m->mothurRemove(outputFileName); return 0; }
1237 in >> name; //read from first column
1238 in >> tax; //read from second column
1240 //if this name is in the accnos file
1241 if (names.count(name) == 0) {
1242 wroteSomething = true;
1243 out << name << '\t' << tax << endl;
1244 }else { removedCount++; }
1251 if (wroteSomething == false) { m->mothurOut("Your file contains only sequences from the .accnos file."); m->mothurOutEndLine(); }
1252 outputTypes["taxonomy"].push_back(outputFileName); outputNames.push_back(outputFileName);
1254 m->mothurOut("Removed " + toString(removedCount) + " sequences from your taxonomy file."); m->mothurOutEndLine();
1258 catch(exception& e) {
1259 m->errorOut(e, "PcrSeqsCommand", "readTax");
1263 //***************************************************************************************************************
1264 int PcrSeqsCommand::readCount(set<string> badSeqNames){
1267 m->openInputFile(countfile, in);
1268 set<string>::iterator it;
1270 map<string, string> variables;
1271 variables["[filename]"] = outputDir + m->getRootName(m->getSimpleName(countfile));
1272 variables["[extension]"] = m->getExtension(countfile);
1273 string goodCountFile = getOutputFileName("count", variables);
1275 outputNames.push_back(goodCountFile); outputTypes["count"].push_back(goodCountFile);
1276 ofstream goodCountOut; m->openOutputFile(goodCountFile, goodCountOut);
1278 string headers = m->getline(in); m->gobble(in);
1279 goodCountOut << headers << endl;
1281 string name, rest; int thisTotal, removedCount; removedCount = 0;
1282 bool wroteSomething = false;
1285 if (m->control_pressed) { goodCountOut.close(); in.close(); m->mothurRemove(goodCountFile); return 0; }
1287 in >> name; m->gobble(in);
1288 in >> thisTotal; m->gobble(in);
1289 rest = m->getline(in); m->gobble(in);
1291 if (badSeqNames.count(name) != 0) { removedCount+=thisTotal; }
1293 wroteSomething = true;
1294 goodCountOut << name << '\t' << thisTotal << '\t' << rest << endl;
1298 goodCountOut.close();
1300 if (m->control_pressed) { m->mothurRemove(goodCountFile); }
1302 if (wroteSomething == false) { m->mothurOut("Your count file contains only sequences from the .accnos file."); m->mothurOutEndLine(); }
1304 //check for groups that have been eliminated
1306 if (ct.testGroups(goodCountFile)) {
1307 ct.readTable(goodCountFile, true, false);
1308 ct.printTable(goodCountFile);
1311 if (m->control_pressed) { m->mothurRemove(goodCountFile); }
1313 m->mothurOut("Removed " + toString(removedCount) + " sequences from your count file."); m->mothurOutEndLine();
1319 catch(exception& e) {
1320 m->errorOut(e, "PcrSeqsCommand", "readCOunt");
1324 /**************************************************************************************/