libatomprobe
Library for Atom Probe Tomography (APT) computation
ranges.cpp
Go to the documentation of this file.
1 /*
2  * ranges.cpp - Atom probe rangefile class
3  * Copyright (C) 2018, D Haley
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "atomprobe/io/ranges.h"
21 #include "atomprobe/helper/misc.h"
22 
24 #include "helper/helpFuncs.h"
25 
26 #include <set>
27 #include <map>
28 #include <fstream>
29 #include <numeric>
30 #include <cstring>
31 #include <algorithm>
32 #include <limits>
33 #include <cmath>
34 #include <iomanip>
35 
36 
37 using std::string;
38 using std::vector;
39 using std::pair;
40 using std::make_pair;
41 using std::map;
42 using std::set;
43 using std::accumulate;
44 
45 namespace AtomProbe
46 {
47 
48 bool RangeFile::enforceConsistency =false ;
49 
50 //Arbitrary maximum range file line size
51 const size_t MAX_LINE_SIZE = 16536;
52 //Arbitrary maximum size to accept a range file, in bytes
53 const size_t MAX_RANGEFILE_SIZE= 50*1024*1024;
54 
55 const char *RANGE_EXTS[] = { "rng",
56  "env",
57  "rng",
58  "rrng",
59  ""};
60 
61 
62 //List of symbols in the periodic table
63 const char *elementList[] = {
64 "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
65 
66 "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar",
67 
68 "K", "Ca", "Sc","Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni",
69  "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr",
70 
71 "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd",
72  "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe",
73 
74 "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd",
75  "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta",
76  "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb",
77  "Bi", "Po", "At", "Rn",
78 
79 "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm",
80  "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db",
81  "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Uut", "Fl",
82  "Uup", "Lv", "Uus", "Uuo", ""
83 };
84 
85 // Potential way to validate element symbols (avoids regex)
86 bool isValidElementSymbol(std::string &symbol)
87 {
88  // Should have capital first char
89  if (!isupper(symbol[0]))
90  return false;
91 
92  // Should not be longer than 3 chars
93  if (symbol.size()>3)
94  {
95  return false;
96  } else if (symbol.size()==1) {
97  // If only length one and 0th = Upper case, return OK
98  return true;
99  }
100 
101  // Should only contain lower case for the remainder
102  for(size_t ui=1;ui<symbol.size();ui++)
103  {
104  if (!islower(symbol[ui]))
105  return false;
106  }
107 
108  return true;
109 }
110 
111 string rangeToStr(const pair<float,float> &rng)
112 {
113  string tmp,tmp2;
114  stream_cast(tmp,rng.first);
115  stream_cast(tmp2,rng.second);
116 
117  return string("(") + tmp + string(",") + tmp2 + string(")");
118 }
119 
120 
121 bool RangeFile::decomposeIonNames(const std::string &name,
122  std::vector<pair<string,size_t> > &fragments)
123 {
124  size_t lastMarker=0;
125  size_t digitMarker=0;
126 
127  if(!name.size())
128  return true;
129 
130  //Atomic naming systems use uppercase ascii
131  // letters, like "A" in Au, or Ag as delimiters.
132  //
133  // numerals are multipliers, and are forbidden
134  // for the first char...
135  if(!isascii(name[0]) ||
136  isdigit(name[0]) || islower(name[0]))
137  return false;
138 
139  //true - was last, or now am on ion name
140  //false - am still on multiplier
141  bool nameMode=true;
142  for(auto ui=1;ui<name.size();ui++)
143  {
144  if(!isascii(name[ui]))
145  return false;
146 
147  if(nameMode)
148  {
149  //If we hit a digit,
150  //this means that we
151  //are now on a multiplier
152  if(isdigit(name[ui]))
153  {
154  digitMarker=ui;
155  nameMode=false;
156  continue;
157  }
158 
159  if(isupper(name[ui]))
160  {
161  //Looks like we hit another
162  // ion name, without hitting any
163  // specification for the number.
164  // This means unitary spec.
165  std::string s;
166  s=name.substr(lastMarker,ui-lastMarker);
167  fragments.emplace_back(s,1);
168  lastMarker=ui;
169  }
170  }
171  else
172  {
173  //OK, we still have a digit.
174  // keep moving
175  if(isdigit(name[ui]))
176  continue;
177 
178  if(isalpha(name[ui]))
179  {
180  //OK, this looks like a new ion
181  //but we need to record multiplicity
182  std::string s,sDigit;
183  s=name.substr(lastMarker,digitMarker-lastMarker);
184 
185  sDigit=name.substr(digitMarker,ui-digitMarker);
186 
187  size_t multiplicity;
188  stream_cast(multiplicity,sDigit);
189 
190  fragments.emplace_back(s,multiplicity);
191 
192  lastMarker=ui;
193  nameMode=true;
194  }
195 
196  }
197  }
198 
199  if(nameMode)
200  {
201  //Hit end of string.
202  //record fragment
203  std::string s;
204  s=name.substr(lastMarker,name.size()-lastMarker);
205  fragments.emplace_back(s,1);
206  }
207  else
208  {
209  std::string s,sDigit;
210  s=name.substr(lastMarker,digitMarker-lastMarker);
211  sDigit=name.substr(digitMarker,name.size()-digitMarker);
212 
213  size_t multiplicity;
214  stream_cast(multiplicity,sDigit);
215 
216  fragments.emplace_back(s,multiplicity);
217  }
218 
219 
220  //Spin through dataset and collect the non-unique fragment
221  vector<bool> toKill(fragments.size(),false);
222  for(auto ui=0u;ui<fragments.size();ui++)
223  {
224  //skip empty fragments
225  if(fragments[ui].first.empty())
226  continue;
227 
228  for(auto uj=ui+1;uj<fragments.size();uj++)
229  {
230  //skip empty fragments
231  if(fragments[uj].first.empty())
232  continue;
233 
234  //Collect fragment multiplicities if they have the same name
235  if(fragments[uj].first == fragments[ui].first)
236  {
237  fragments[ui].second+=fragments[uj].second;
238  fragments[uj].first=""; //Zero out name so we don't hit it
239 
240  toKill[uj]=true;
241 
242  }
243  }
244  }
245 
246  vectorMultiErase(fragments,toKill);
247 
248  return true;
249 }
250 
251 
252 bool RangeFile::decomposeIonById(unsigned int ionId, vector<pair<string,size_t> > &fragments) const
253 {
254  //TODO: Less lazy implementation
255  string sName = getName(ionId);
256  return decomposeIonNames(sName,fragments);
257 
258 }
259 
260 
261 void RangeFile::decomposeIonNameToFormula(const std::string &name, std::map<std::string,size_t> &formula)
262 {
263  formula.clear();
264 
265  // attempt to add the ion formula from the (short) name
266  std::vector<std::pair<std::string,size_t> > ionFrags;
267  if (decomposeIonNames(name, ionFrags))
268  {
269  // decomposeIonNames handles the case of duplicated elements
270  // convert from vector<pair> to map...
271  for(auto& p:ionFrags) {
272  formula[p.first] = p.second;
273  }
274 
275  }
276 }
277 
278 //ENV files sometimes have charge state information in the ion
279 // names, use this function as a helper to strip it out
280 std::string RangeFile::envDropChargeState(const std::string &strName)
281 {
282  std::string res=strName;
283  if(strName[strName.size()-1] == '+')
284  {
285  size_t chargeStateOffset;
286  chargeStateOffset=strName.find_last_of("_");
287  if(chargeStateOffset != std::string::npos)
288  res=strName.substr(0,chargeStateOffset);
289  }
290 
291  return res;
292 }
293 
294 //Given the name-frequency pairing vector, see if there
295 // is a match in the map of composed names
296 //Returns the "matchOffset", which is the ionID of the correct range
297 bool matchComposedName(const std::map<string,size_t> &composedNames,
298 
299  const vector<pair<string,size_t> > &namesToFind, size_t &matchOffset)
300 {
301  //Decomposition of composed names.
302  std::vector<vector<pair<string,size_t> > > fragmentVec;
303 
304  //break each composed name into a vector of decomposed fragments
305  // and the multiplicity
306  //of that fragment (eg, AuHg2 would become { Au ,1} {Hg,2})
307  fragmentVec.reserve(composedNames.size());
308  for(const auto & composedName : composedNames)
309  {
310  vector<pair<string,size_t> > frags;
311  if(!RangeFile::decomposeIonNames(composedName.first,frags))
312  frags.clear();
313 
314 
315  fragmentVec.push_back(frags);
316 
317  frags.clear();
318  }
319 
320 
321  //Try to match all fragments in "namesToFind" (name-frequency pairings)
322  //in the master list of fragments of
323  // which consists of the decomposed composed names (fragmentVec entries)
324 
325  //If the decomposed fragments wholly constitute the
326  //master list, then thats good, and we have a match.
327  //Note that the master list will not necessarily be in
328  //the same order as the fragment list
329  //match tally for fragments
330 
331 
332  vector<bool> excludedMatch;
333  excludedMatch.resize(fragmentVec.size(),false);
334 
335  for(size_t uj=0;uj<namesToFind.size();uj++)
336  {
337  pair<string,size_t> curFrag;
338  curFrag=namesToFind[uj];
339  for(size_t ui=0;ui<fragmentVec.size();ui++)
340  {
341  //Was disqualified from matching before
342  if(excludedMatch[ui])
343  continue;
344 
345  //If we cannot find this particular fragment, then
346  // this is excluded from future searches
347  if(std::find(fragmentVec[ui].begin(),
348  fragmentVec[ui].end(),curFrag) == fragmentVec[ui].end())
349  excludedMatch[ui]=true;
350  }
351  }
352  //-------
353 
354  //Scan through the candidate matches to find if there is
355  // a single unique candidate
356  matchOffset=-1;
357  for(size_t ui=0;ui<fragmentVec.size();ui++)
358  {
359  if(!excludedMatch[ui])
360  {
361  //Check for bijection in the mapping. Currently
362  // we know that fragmentVec is a superset of
363  // namesToFind, but we don't know it matches -
364  // it could exceed it, for example.
365  bool doesMatch=true;
366  for(size_t uj=0;uj<fragmentVec[ui].size();uj++)
367  {
368  if(std::find(namesToFind.begin(),
369  namesToFind.end(),fragmentVec[ui][uj]) ==
370  namesToFind.end())
371  {
372  doesMatch=false;
373  break;
374 
375  }
376  }
377 
378  //Duplicate match
379  if(doesMatch)
380  {
381  if(matchOffset !=(size_t)-1)
382  return false;
383  else
384  {
385  //OK, we found a match.
386  matchOffset=ui;
387  }
388  }
389 
390  }
391  }
392 
393  //Convert the fragment vector offset (which matches the order from
394  // the incoming composedNames map) to the ionID (given by the composed names second entry)
395  unsigned int mapOffset=0;
396  for(const auto & composedName : composedNames)
397  {
398 
399  if(matchOffset==mapOffset)
400  {
401  //Fix the meaning of matchOffset to be useful to the outside world
402  matchOffset=composedName.second;
403  break;
404  }
405 
406  mapOffset++;
407  }
408 
409  //Return OK, iff we have exactly one match
410  return (matchOffset !=(size_t) - 1);
411 }
412 
413 RangeFile::RangeFile() : errState(0)
414 {
415  static_assert(ARRAYSIZE(RANGE_EXTS)==RANGE_FORMAT_END_OF_ENUM+1, "Range error");
416 }
417 
419 {
420  ionNames.clear();
421  ionFormulas.clear();
422  colours.clear();
423  ranges.clear();
424  ionIDs.clear();
425 
426 
427  ionNames=oth.ionNames;
428  ionFormulas=oth.ionFormulas;
429  colours=oth.colours;
430  ranges=oth.ranges;
431  ionIDs=oth.ionIDs;
432  rangeVolumes=oth.rangeVolumes;
433 
434  enforceConsistency=oth.enforceConsistency;
435  errState=oth.errState;
436  warnMessages=oth.warnMessages;
437 
438  return *this;
439 }
440 
441 unsigned int RangeFile::write(std::ostream &f, size_t format) const
442 {
443 
444  using std::endl;
445  ASSERT(colours.size() == ionNames.size());
446 
447  switch(format)
448  {
449  case RANGE_FORMAT_ORNL:
450  {
451  //File header
452  f << ionNames.size() << " " ;
453  f << ranges.size() << std::endl;
454 
455  //Colour and longname data
456  for(unsigned int ui=0;ui<ionNames.size() ; ui++)
457  {
458  f << ionNames[ui].second<< std::endl;
459  f << ionNames[ui].first << " " << colours[ui].red << " " << colours[ui].green << " " << colours[ui].blue << std::endl;
460 
461  }
462 
463  //Construct the table header
464  f<< "-------------";
465  for(const auto & ionName : ionNames)
466  {
467  f << " " << ionName.first;
468  }
469 
470  f << std::endl;
471  //Construct the range table
472  for(unsigned int ui=0;ui<ranges.size() ; ui++)
473  {
474  f << ". " << ranges[ui].first << " " << ranges[ui].second ;
475 
476  //Now put the "1" in the correct column
477  for(unsigned int uj=0;uj<ionNames.size(); uj++)
478  {
479  if(uj == ionIDs[ui])
480  f << " " << 1;
481  else
482  f << " " << 0;
483  }
484 
485  f << std::endl;
486 
487  }
488  break;
489  }
490  case RANGE_FORMAT_ENV:
491  {
492  //Comment indicating it came from this program
493  f << "#" << " From libatomprobe" <<std::endl;
494 
495  //File header
496  f << ionNames.size() << " " ;
497  f << ranges.size() << std::endl;
498 
499  //Colour and name data
500  for(unsigned int ui=0;ui<ionNames.size() ; ui++)
501  {
502  f << ionNames[ui].first << " " << colours[ui].red << " " << colours[ui].green << " " << colours[ui].blue << std::endl;
503  }
504  //Construct the range table
505  for(unsigned int ui=0;ui<ranges.size() ; ui++)
506  {
507  f << ionNames[ionIDs[ui]].first << " " << ranges[ui].first
508  << " " << ranges[ui].second << " 1.0 1.0" << std::endl;
509  }
510 
511  break;
512  }
513  case RANGE_FORMAT_RRNG:
514  {
515 
516  set<string> elementSet;
517 
518  {
519  size_t offset=0;
520  while(strlen(elementList[offset]))
521  {
522  elementSet.insert(elementList[offset]);
523  offset++;
524  }
525 
526  }
527 
528  f << "[Ions]" << endl;
529  f << "Number=" << ionNames.size() << endl;
530 
531  for(auto ui=0;ui<ionNames.size();ui++)
532  {
533  f << "Ion" << ui+1 << "=" << ionNames[ui].first << endl;
534  }
535 
536 
537  f << "[Ranges] " << endl;
538  f << "Number=" << ranges.size() << endl;
539 
540 
541 
542  for(auto ui=0u;ui<ranges.size();ui++)
543  {
544  RGBf col = getColour(ionIDs[ui]);
545  std::string colString = col.toHex();
546  for (auto & c: colString) c = toupper(c); // convert to upper case
547 
548  //strip leading #
549  colString=colString.substr(1);
550  ASSERT(colString.size() == 6);
551 
552  // Range part
553  f << "Range" << ui+1 <<"=";
554  f << ranges[ui].first << " " << ranges[ui].second << " ";
555 
556  // Volume part (optional)
557  if(rangeVolumes.size())
558  f << "Vol:" << rangeVolumes[ui];
559 
560 
561  // Ion information or Name
562  string strName;
563  strName=ionNames[ionIDs[ui]].first; // prefer short name for the "Name:"
564 
565  f << " Name:" << strName;
566  if (ionFormulas.size())
567  {
568  // go ahead and use formula
569  for(const auto& it : ionFormulas[ionIDs[ui]])
570  f << " " << it.first << ":" << it.second;
571 
572  }
573 
574  // Colour part
575  f << " Color:" << colString << endl;
576  }
577 
578  break;
579  }
580 
581  default:
582  ASSERT(false);
583  }
584  return 0;
585 }
586 
587 unsigned int RangeFile::write(const char *filename,size_t format) const
588 {
589  std::ofstream f(filename);
590  if(!f)
591  return 1;
592 
593  return write(f,format);
594 }
595 
596 void RangeFile::clear()
597 {
598  ionIDs.clear();
599  warnMessages.clear();
600  ionNames.clear();
601  ionFormulas.clear();
602  colours.clear();
603  ranges.clear();
604  rangeVolumes.clear();
605 
606  errState=0;
607 }
608 
609 unsigned int RangeFile::openFormat(const char *rangeFilename, unsigned int fileFormat)
610 {
611  size_t fileSize;
612  getFilesize(rangeFilename,fileSize);
613 
614  if(fileSize > MAX_RANGEFILE_SIZE)
615  return RANGE_ERR_FILESIZE;
616 
617  FILE *fpRange;
618  fpRange=fopen(rangeFilename,"r");
619  if (fpRange== NULL)
620  {
621  errState = RANGE_ERR_OPEN;
622  return errState;
623  }
624 
625 
626  pushLocale("C",LC_NUMERIC);
627 
628  size_t errCode;
629  switch(fileFormat)
630  {
631  case RANGE_FORMAT_ORNL:
632  {
633  errCode=openRNG(fpRange);
634  break;
635  }
636  //Cameca "ENV" file format.. I have a couple of examples, but nothing more.
637  //There is no specification of this format publicly available
638  case RANGE_FORMAT_ENV:
639  {
640  errCode=openENV(fpRange);
641  break;
642  }
643  //Imago (now Cameca), "RRNG" file format. Again, neither standard
644  //nor formal specification exists (sigh). Purely implemented by example.
645  case RANGE_FORMAT_RRNG:
646  errCode=openRRNG(fpRange);
647  break;
648  //Cameca "double" rng format, in the wild as of mid 2013
650  errCode=openDoubleRNG(fpRange);
651  break;
652  default:
653  ASSERT(false);
654  fclose(fpRange);
655  popLocale();
656  return RANGE_ERR_FORMAT;
657  }
658 
659  popLocale();
660  fclose(fpRange);
661  if(errCode)
662  {
663  errState=errCode;
664 
665  return errState;
666  }
667 
668  //Run self consistency check on freshly loaded data
669  if(!isSelfConsistent())
670  {
672  return errState;
673  }
674 
675  return 0;
676 }
677 
678 bool RangeFile::open(const char *rangeFilename)
679 {
680 
681  //Try to auto-detect the filetype
682  unsigned int assumedFileFormat;
683  assumedFileFormat=detectFileType(rangeFilename);
684 
685  if(assumedFileFormat < RANGE_FORMAT_END_OF_ENUM)
686  {
687  if(!openFormat(rangeFilename,assumedFileFormat))
688  return true;
689  }
690  //Use the guessed format
691  unsigned int errStateRestore;
692  errStateRestore=errState;
693  //If that failed, go to plan B-- Brute force.
694  //try all readers
695  bool openOK=false;
696 
697  for(unsigned int ui=0;ui<RANGE_FORMAT_END_OF_ENUM; ui++)
698  {
699  if(ui == assumedFileFormat)
700  continue;
701 
702  if(!openFormat(rangeFilename,ui))
703  {
704  openOK=true;
705  break;
706  }
707  }
708 
709  if(!openOK)
710  {
711  //Restore the error state for the assumed file format
712  errState=errStateRestore;
713  return false;
714  }
715 
716  return true;
717 }
718 
719 unsigned int RangeFile::openDoubleRNG(FILE *fpRange)
720 {
721  //Cameca modified range file format, as discussed here:
722  // https://sourceforge.net/apps/phpbb/threedepict/viewtopic.php?f=1&t=25
723 
724  //Basically, the file is two range files back to back, with a single separator line
725  // where the multiple ion names and colours and table are given in the second file
726 
727  clear();
728  RangeFile tmpRange[2];
729 
730  unsigned int errCode;
731  errCode=tmpRange[0].openRNG(fpRange);
732  if(errCode)
733  return errCode;
734 
735  //Spin forwards to the "polyatomic extension" line
736 
737 
738  auto inBuffer = new char[MAX_LINE_SIZE];
739  // skip over <LF>
740  char *ret;
741  ret=fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange);
742 
743  while(ret && strlen(ret) < MAX_LINE_SIZE-1 && ret[0] != '-')
744  {
745  ret=fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange);
746  }
747 
748  if(!ret || strlen(ret) >= MAX_LINE_SIZE -1)
749  {
750  delete[] inBuffer;
751  return RANGE_ERR_FORMAT;
752  }
753 
754  //Read the "polyatomic extension" line
755  errCode=tmpRange[1].openRNG(fpRange);
756 
757  if(errCode)
758  {
759  delete[] inBuffer;
760  return errCode;
761  }
762  //Now merge the two range files by using the mass pair data as a key
763 
764  //Find the matching ranges
765  //range IDs from first and second file who have matching range values
766  vector< pair<size_t,size_t> > rangeMatches;
767  //IonID from the first dataset that we will need to replace
768  vector<size_t> overrideIonID;
769  for(auto ui=0u;ui<tmpRange[0].getNumRanges();ui++)
770  {
771  for(auto uj=0u;uj<tmpRange[1].getNumRanges();uj++)
772  {
773  if (tmpRange[0].getRange(ui) == tmpRange[1].getRange(uj))
774  {
775  rangeMatches.emplace_back(ui,uj);
776  overrideIonID.push_back(tmpRange[0].getIonID((unsigned int)ui));
777  }
778  }
779  }
780 
781  //Take the data from the first range,
782  // then discard the overlapping ions
783  tmpRange[0].ionNames.swap(ionNames);
784  tmpRange[0].ionFormulas.swap(ionFormulas);
785  tmpRange[0].colours.swap(colours);
786  tmpRange[0].ionIDs.swap(ionIDs);
787  tmpRange[0].ranges.swap(ranges);
788 
789 
790  //Ensure there are no non-unique ion entries
791  {
792  vector<size_t> uniqItems=overrideIonID;
793  std::sort(uniqItems.begin(),uniqItems.end());
794 
795  if(std::unique(uniqItems.begin(),uniqItems.end()) != uniqItems.end())
796  {
797  delete[] inBuffer;
799  }
800  }
801 
802  //Replace ionnames with new ion name and colour
803  for(auto ui=0;ui<overrideIonID.size();ui++)
804  {
805  size_t ids[2];
806  ids[0]=overrideIonID[ui];
807  ids[1] = tmpRange[1].getIonID((unsigned int)rangeMatches[ui].second);
808  //Replace first rangefile colour and name with that of the second
809  ionNames[ids[0]] = tmpRange[1].ionNames[ids[1]];
810  colours[ids[0]] = tmpRange[1].colours[ids[1]];
811  if(ionFormulas.size())
812  ionFormulas[ids[0]] = tmpRange[1].ionFormulas[ids[1]];
813  }
814 
816 
817  delete[] inBuffer;
818  return 0;
819 
820 }
821 
822 unsigned int RangeFile::openRNG( FILE *fpRange)
823 {
824  clear();
825 
826  //Oak-Ridge "Format" - this is based purely on example, as no standard exists
827  //the classic example is from Miller, "Atom probe: Analysis at the atomic scale"
828  //but alternate output forms exist. Our only strategy is to try to be as accommodating
829  //as reasonably possible
830  unsigned int errCode;
831 
832  unsigned int numRanges;
833  unsigned int numIons;
834 
835  //Load the range file header
836  if((errCode=readRNGHeader(fpRange, ionNames, colours, numRanges,numIons)))
837  return errCode;
838 
839  auto inBuffer = new char[MAX_LINE_SIZE];
840  // skip over <LF>
841  char *ret;
842  ret=fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange);
843  if(!ret || strlen(ret) >= MAX_LINE_SIZE-1)
844  {
845  delete[] inBuffer;
846  return RANGE_ERR_DASHHEADER;
847  }
848  // read the column header line
849  ret=fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange);
850 
851  if(!ret || strlen(ret) >= MAX_LINE_SIZE-1)
852  {
853  delete[] inBuffer;
854  return RANGE_ERR_DASHHEADER;
855  }
856 
857  //We should be at the line which has lots of dashes
858  if(inBuffer[0] != '-')
859  {
860  delete[] inBuffer;
862  }
863 
864 
865  //Load the rangefile frequency table
866  vector<string> colHeaders;
867  vector<unsigned int > frequencyEntries;
868 
869  {
870  vector<string> warnings;
871  vector<pair<float,float> > massData;
872  errCode=readRNGFreqTable(fpRange,inBuffer,numIons,numRanges,ionNames,
873  colHeaders, frequencyEntries,massData,warnings);
874  if(errCode)
875  {
876  delete[] inBuffer;
877  return errCode;
878  }
879 
880  warnings.swap(warnMessages);
881  ranges.swap(massData);
882  }
883 
884 
885 
886  //Because of a certain software's output
887  //it can generate a range listing like this (table +example colour line only)
888  //
889  // these are invalid according to white book
890  // and the "Atom Probe Microscopy", (Springer series in Materials Science
891  // vol. 160) descriptions
892  //
893  // Cu2 1.0 1.0 1.0 (Cu2)
894  // ------------Cu Ni Mg Si CuNi4 Mg3Si2 Cu2
895  // . 12.3 23.5 1 4 0 0 0 0 0
896  // . 32.1 43.2 0 0 3 2 0 0 0
897  // . 56.7 89.0 2 0 0 0 0 0 0
898  //
899  // which we get blamed for not supporting :(
900  //
901  //So, we have to scan the lists of ions, and create
902  //tentative "combined" ion names, from the list of ions at the top.
903  //
904  //However, this violates the naming system in the "white book" (Miller, Atom probe: Analysis at the atomic scale)
905  // scheme, which might allow something like
906  // U235U238
907  // as a single range representing a single ion. So they bastardise it by placing the zero columns
908  // as a hint marker. Technically speaking, the zero column entries should not exist
909  // in the format - as they would correspond to a non-existent species (or rather, an unreferenced species).
910  // To check the case of certain programs using cluster ions like this, we have to engage in some
911  // hacky-heuristics, check for
912  // - Columns with only zero entries,
913  // - which have combined numerical headers
914  //
915  // Then handle this as a separate case. FFS.
916 
917  //-- Build a sparse vector of composed ions
918  std::map<string,size_t> composeMap;
919 
920  for(auto uj=0u;uj<numIons;uj++)
921  {
922  bool maybeComposed;
923  maybeComposed=true;
924  for(auto ui=0u; ui<numRanges;ui++)
925  {
926  //Scan through the range listing to try to
927  //find a non-zero entries
928  if(frequencyEntries[numIons*ui + uj])
929  {
930  maybeComposed=false;
931  break;
932  }
933  }
934 
935  //If the ion has a column full of zeroes, and
936  //the ion looks like its composable, then
937  //decompose it for later references
938  if(maybeComposed)
939  composeMap.insert(make_pair(ionNames[uj].first,uj));
940  }
941  //--
942 
943  //vector of entries that are multiples, but don't have a
944  // matching compose key
945  std::vector<pair<size_t, map<size_t,size_t > > > unassignedMultiples;
946 
947  //Loop through each range's freq table entries, to determine if
948  // the ion is composed (has more than one "1" in multiplicity listing)
949  for(auto ui=0u;ui<numRanges;ui++)
950  {
951  std::map<size_t,size_t > freqEntries;
952  size_t freq;
953 
954 
955  //Find the nonzero entries in this row
956  //--
957  freqEntries.clear();
958  freq=0;
959  for(auto uj=0u;uj<numIons;uj++)
960  {
961  size_t thisEntry;
962  thisEntry=frequencyEntries[numIons*ui+uj];
963  if(!thisEntry)
964  continue;
965 
966  //Record nonzero entries
967  freq+=thisEntry;
968  freqEntries.insert(make_pair(uj,thisEntry));
969  }
970  //--
971 
972  if(freq ==1)
973  {
974  //Simple case - we only had a single [1] in
975  //a row for the
976  ASSERT(freqEntries.size() == 1);
977  ionIDs.push_back( freqEntries.begin()->first);
978  }
979  else if (freq > 1)
980  {
981  if(composeMap.empty())
982  {
983  if(enforceConsistency)
985  //We have a multiple, but no way of composing it!
986  // we will need to build our own table entry.
987  // For now, just store the freq tableentry
988  unassignedMultiples.emplace_back(ui,freqEntries);
989  ionIDs.push_back(-2);
990  }
991  else
992  {
993  //More complex case
994  // ion appears to be composed of multiple fragments.
995  //First entry is the ion name, second is the number of times it occurs
996  // (ie value in freq table, on this range line)
997  vector<pair<string,size_t> > entries;
998 
999  for(map<size_t,size_t>::iterator it=freqEntries.begin();it!=freqEntries.end();++it)
1000  entries.push_back(make_pair(ionNames[it->first].first,it->second));
1001 
1002  //try to match the composed name to the entries
1003  size_t offset;
1004  if(!matchComposedName(composeMap,entries,offset))
1005  {
1006  //We failed to match the ion against a composed name.
1007  // cannot deal with this case.
1008  //
1009  // we can't just build a new ion name,
1010  // as we don't have a colour specification for this.
1011  //
1012  // We can't use the regular ion name, without
1013  // tracking multiplicity (and then what to do with it in every case -
1014  // seems only a special case for composition? eg.
1015  // Is it a sep. species when clustering? Who knows!)
1016  delete[] inBuffer;
1017 
1019  }
1020  ASSERT(offset < ionNames.size());
1021  ionIDs.push_back( offset);
1022  }
1023 
1024 
1025  }
1026  else //0
1027  {
1028  //Range was useless - had no nonzero values
1029  //in frequency table.
1030  //Set to bad ionID - we will kill this later.
1031  if(enforceConsistency)
1033  ionIDs.push_back(-1);
1034  }
1035  }
1036 
1037 
1038 
1039  //Check for any leftover unhandled cases
1040  if(unassignedMultiples.size())
1041  {
1042  //OK, so we didn't deal with a few cases before
1043  // lets sort these out now
1044 
1045  //Create a name + list of ranges mapping
1046  map<string,vector<int> > newNames;
1047 
1048  for(auto ui=0u;ui<unassignedMultiples.size();ui++)
1049  {
1051  //create a flattened name from the map as a unique key
1052  //eg { Cu , 2 } | { Au, 1} | {O , 3} => O3Cu2Au
1053  //--
1054  {
1055  vector<pair<size_t,size_t> > flatData;
1056 
1057  std::map<size_t,size_t> &m=unassignedMultiples[ui].second;
1058  for( const auto &i : m )
1059  flatData.emplace_back(i.first,i.second);
1060  std::sort(flatData.begin(),flatData.end(),cmp);
1061 
1062  string nameStr;
1063  for(auto uj=0u;uj<flatData.size();uj++)
1064  {
1065  string tmpStr;
1066 
1067  stream_cast(tmpStr,flatData[uj].second);
1068  //Use short name then value
1069  nameStr+=ionNames[flatData[uj].first].first + tmpStr;
1070  }
1071 
1072  //Append the new range/create a new entry
1073  newNames[nameStr].push_back(unassignedMultiples[ui].first);
1074 
1075 
1076  }
1077  }
1078 
1079  //Create one new name per new string we generated,
1080  for(map<string,vector<int> >::iterator it=newNames.begin(); it!=newNames.end();++it)
1081  {
1082  for(auto ui=0u;ui<it->second.size();ui++)
1083  {
1084  ASSERT(ionIDs[it->second[ui]] == (unsigned int)-2);
1085  ionIDs[it->second[ui]] =ionNames.size();
1086  }
1087  ionNames.emplace_back(it->first,it->first);
1088  //make a new random colour
1089  RGBf col;
1090  col.red=rand()/(float)std::numeric_limits<int>::max();
1091  col.green=rand()/(float)std::numeric_limits<int>::max();
1092  col.blue=rand()/(float)std::numeric_limits<int>::max();
1093 
1094  colours.push_back(col);
1095  }
1096 
1097  }
1098 
1099  //Loop through any ranges with a bad ionID (== -1), then delete them by popping
1100  for(auto ui=ionIDs.size()-1;ui--;)
1101  {
1102  if(ionIDs[ui] == (unsigned int)-1)
1103  {
1104  std::swap(ranges[ui],ranges.back());
1105  ranges.pop_back();
1106 
1107  std::swap(ionIDs[ui],ionIDs.back());
1108  ionIDs.pop_back();
1109  }
1110  }
1111 
1112  delete[] inBuffer;
1113  return 0;
1114 }
1115 
1116 unsigned int RangeFile::detectFileType(const char *rangeFile)
1117 {
1118  enum
1119  {
1120  STATUS_NOT_CHECKED=0,
1121  STATUS_IS_NOT,
1122  STATUS_IS_MAYBE,
1123  };
1124 
1125 #if !defined(__WIN32__) && !defined(__WIN64)
1126  //Under posix platforms (linux) directories can be opened by fopen. disallow
1127  if(isNotDirectory(rangeFile) == false)
1128  return RANGE_FORMAT_END_OF_ENUM;
1129 #endif
1130 
1131  //create a fail-on-unimplemnted type detection scheme
1132  vector<unsigned int > typeStatus(RANGE_FORMAT_END_OF_ENUM,STATUS_NOT_CHECKED);
1133 
1134  //Check for RNG/Double RNG
1135  //--
1136  //first line in file should be two digits
1137  {
1138  std::ifstream f(rangeFile);
1139 
1140  if(!f)
1141  return RANGE_FORMAT_END_OF_ENUM;
1142 
1143  std::string tmpStr;
1144  size_t nCount;
1145 
1146  //retrieve the line
1147  getline(f,tmpStr);
1148  //strip exterior whitespace
1149  tmpStr=stripWhite(tmpStr);
1150 
1151  //break it apart
1152  vector<string> strs;
1153  splitStrsRef(tmpStr.c_str()," ",strs);
1154 
1155  //Drop the whitepace
1156  stripZeroEntries(strs);
1157 
1158  if( strs.size() != 2)
1159  {
1160  //OK, quick parse failed. give up
1161  typeStatus[RANGE_FORMAT_ORNL]=STATUS_IS_NOT;
1162  typeStatus[RANGE_FORMAT_DBL_ORNL]=STATUS_IS_NOT;
1163  goto skipoutRNGChecks;
1164  }
1165 
1166  //Check for two space separated markers, parsable as ints
1167  size_t nIons,nRanges;
1168  bool castRes[2];
1169  castRes[0]=stream_cast(nIons,strs[0]);
1170  castRes[1]=stream_cast(nRanges,strs[1]);
1171 
1172  if( castRes[0] || castRes[1])
1173  {
1174  //OK, quick parse failed. give up
1175  typeStatus[RANGE_FORMAT_ORNL]=STATUS_IS_NOT;
1176  typeStatus[RANGE_FORMAT_DBL_ORNL]=STATUS_IS_NOT;
1177  goto skipoutRNGChecks;
1178  }
1179 
1180 
1181  typeStatus[RANGE_FORMAT_ORNL]=STATUS_IS_MAYBE;
1182  typeStatus[RANGE_FORMAT_DBL_ORNL]=STATUS_IS_MAYBE;
1183 
1184  //spin forwards to find dash line
1185  nCount=2*nIons+1;
1186 
1187  while(nCount--)
1188  {
1189  getline(f,tmpStr);
1190 
1191  //shouldn't hit eof
1192  if(f.eof() || (!f.good()) )
1193  {
1194  tmpStr.clear();
1195  break;
1196  }
1197  }
1198 
1199  if(!tmpStr.size() || tmpStr[0] != '-')
1200  {
1201  typeStatus[RANGE_FORMAT_ORNL]=STATUS_IS_NOT;
1202  typeStatus[RANGE_FORMAT_DBL_ORNL]=STATUS_IS_NOT;
1203  goto skipoutRNGChecks;
1204 
1205  }
1206 
1207  //Now, spin forwards until we either hit EOF or our double-dash marker
1208  while( ! (f.eof() || f.bad()) )
1209  {
1210  if(!getline(f,tmpStr))
1211  break;
1212 
1213  if(tmpStr.size() > 2 &&
1214  tmpStr[0] == '-' && tmpStr[1] == '-')
1215  {
1216  //OK, we saw a double dash. Thats forbidden under
1217  // ORNL , and allowable under double ORNL
1218  typeStatus[RANGE_FORMAT_ORNL]=STATUS_IS_NOT;
1219  break;
1220  }
1221  }
1222 
1223  if(!f.good())
1224  {
1225  //we did not see a double-dash, must be a vanilla ORNL file
1226  typeStatus[RANGE_FORMAT_DBL_ORNL]=STATUS_IS_NOT;
1227  }
1228 
1229 skipoutRNGChecks:
1230  ;
1231  }
1232  //--
1233 
1234  //Check for RRNG, if RNG did not match
1235  //--
1236  if(typeStatus[RANGE_FORMAT_ORNL] != STATUS_IS_MAYBE && typeStatus[RANGE_FORMAT_DBL_ORNL] !=STATUS_IS_MAYBE)
1237  {
1238  std::ifstream f(rangeFile);
1239 
1240  if(!f)
1241  return RANGE_FORMAT_END_OF_ENUM;
1242 
1243  //Check for existence of lines that match format section
1244  std::vector<string> sections;
1245  sections.emplace_back("[ions]");
1246  sections.emplace_back("[ranges]");
1247 
1248  vector<bool> haveSection(sections.size(),false);
1249 
1250  bool foundAllSections=false;
1251 
1252  //Scan through each line, looking for a matching section header
1253  while((!f.eof()) &&f.good())
1254  {
1255 
1256  //Get line, stripped of whitespace
1257  std::string tmpStr;
1258  getline(f,tmpStr);
1259  tmpStr=stripWhite(tmpStr);
1260 
1261  //See if we have this header
1262  for(auto ui=0u;ui<sections.size();ui++)
1263  {
1264  if(sections[ui] == lowercase(tmpStr))
1265  {
1266  haveSection[ui]=true;
1267 
1268  //Check to see if we have any sections left
1269  if(std::find(haveSection.begin(),haveSection.end(),false)
1270  == haveSection.end())
1271  foundAllSections=true;
1272 
1273  break;
1274  }
1275  }
1276 
1277  if(foundAllSections)
1278  break;
1279  }
1280 
1281  if(foundAllSections)
1282  typeStatus[RANGE_FORMAT_RRNG]=STATUS_IS_MAYBE;
1283  else
1284  typeStatus[RANGE_FORMAT_RRNG]=STATUS_IS_NOT;
1285 
1286  }
1287  else
1288  {
1289  //cannot be both maybe an RNG/Double RNG and an RRNG
1290  typeStatus[RANGE_FORMAT_RRNG]=STATUS_IS_NOT;
1291  }
1292  //--
1293 
1294  //Check for ENV
1295  //--
1296  if(typeStatus[RANGE_FORMAT_ORNL] != STATUS_IS_MAYBE && typeStatus[RANGE_FORMAT_DBL_ORNL] !=STATUS_IS_MAYBE &&
1297  typeStatus[RANGE_FORMAT_RRNG] != STATUS_IS_MAYBE)
1298  {
1299  //TODO: Less lazy implementation
1300  RangeFile tmpRng;
1301  FILE *f;
1302  f=fopen(rangeFile,"r");
1303 
1304  if(!f || tmpRng.openENV(f))
1305  typeStatus[RANGE_FORMAT_ENV]=STATUS_IS_NOT;
1306  else
1307  typeStatus[RANGE_FORMAT_ENV]=STATUS_IS_MAYBE;
1308  if(f)
1309  fclose(f);
1310  }
1311  else
1312  {
1313  //cannot be both maybe an RNG/Double RNG/RRNG and an env
1314  typeStatus[RANGE_FORMAT_ENV]=STATUS_IS_NOT;
1315  }
1316 
1317  //--
1318 
1319  //Check there is only one STATUS_IS_MAYBE or STATUS_NOT_CHECKED
1320  if((size_t)std::count(typeStatus.begin(),typeStatus.end(),(unsigned int)STATUS_IS_NOT) == typeStatus.size()-1)
1321  {
1322  //OK, there can only be one. Return the format that has not
1323  // been rejected
1324  for(auto ui=0u;ui<typeStatus.size();ui++)
1325  {
1326  if(typeStatus[ui]==STATUS_IS_MAYBE)
1327  return ui;
1328 
1329  }
1330  //in this case, we only have NOT_CHECKED remaining. So, we have no idea
1331  return RANGE_FORMAT_END_OF_ENUM;
1332 
1333  }
1334 
1335 
1336  return RANGE_FORMAT_END_OF_ENUM;
1337 
1338 }
1339 
1340 string RangeFile::rangeTypeString(unsigned int type)
1341 {
1343 
1344  const char *typeNames[] = { "RNG - ORNL style",
1345  "RNG - ORNL Style - with polyatomic extension",
1346  "ENV - Cameca environment",
1347  "RRNG - Imago/Cameca \"RRNG\""
1348  };
1349 
1350  static_assert(ARRAYSIZE(typeNames) == RANGE_FORMAT_END_OF_ENUM, "Range type names and format enum mismatched");
1351  return typeNames[type];
1352 }
1353 
1354 
1355 
1356 unsigned int RangeFile::readRNGHeader(FILE *fpRange, vector<pair<string,string> > &strNames,
1357  vector<RGBf> &fileColours, unsigned int &numRanges, unsigned int &numIons)
1358 {
1359 
1360  auto inBuffer= new char[MAX_LINE_SIZE];
1361 
1362  //Read out the number of ions and ranges in the file
1363  if(fscanf(fpRange, "%64u %64u", &numIons, &numRanges) != 2)
1364  {
1365 
1366  delete[] inBuffer;
1367  return RANGE_ERR_FORMAT_HEADER;
1368  }
1369 
1370  if (!(numIons && numRanges))
1371  {
1372  delete[] inBuffer;
1373  return RANGE_ERR_EMPTY;
1374  }
1375 
1376 
1377  RGBf colourStruct;
1378  pair<string,string> namePair;
1379  //Read ion short and full names as well as colour info
1380  for(unsigned int i=0; i<numIons; i++)
1381  {
1382  //Spin until we get to a new line.
1383  //Certain programs emit range files that have
1384  //some string of unknown purpose
1385  //after the colour specification
1386  if(fpeek(fpRange)== ' ')
1387  {
1388  int peekVal;
1389  //Gobble chars until we hit the newline
1390  do
1391  {
1392  if(fgetc(fpRange) == EOF)
1393  {
1394  delete[] inBuffer;
1395  return RANGE_ERR_FORMAT_COLOUR;
1396  }
1397  peekVal=fpeek(fpRange);
1398  }
1399  while(peekVal != (int)'\n'&&
1400  peekVal !=(int)'\r' && peekVal != EOF);
1401 
1402  //eat another char if we are using
1403  //windows newlines
1404  if(peekVal== '\r')
1405  {
1406  if(fgetc(fpRange) == EOF)
1407  {
1408  delete[] inBuffer;
1409  return RANGE_ERR_FORMAT_COLOUR;
1410  }
1411  }
1412  }
1413  //Read the input for long name (max 255 chars)
1414  if(!fscanf(fpRange, " %255s", inBuffer))
1415  {
1416  delete[] inBuffer;
1418  }
1419 
1420  namePair.second = inBuffer;
1421 
1422 
1423  //Read short name
1424  if(!fscanf(fpRange, " %255s", inBuffer))
1425  {
1426  delete[] inBuffer;
1428  }
1429 
1430  namePair.first= inBuffer;
1431  //Read Red green blue data
1432  if(!fscanf(fpRange,"%128f %128f %128f",&(colourStruct.red),
1433  &(colourStruct.green),&(colourStruct.blue)))
1434  {
1435  delete[] inBuffer;
1436  return RANGE_ERR_FORMAT_COLOUR;
1437  }
1438 
1439  strNames.push_back(namePair);
1440  fileColours.push_back(colourStruct);
1441  }
1442 
1443  delete[] inBuffer;
1444  return 0;
1445 }
1446 
1447 unsigned int RangeFile::readRNGFreqTable(FILE *fpRange, char *inBuffer,const unsigned int numIons,
1448  const unsigned int numRanges,const vector<pair<string,string> > &names,
1449  vector<string> &colHeaders, vector<unsigned int > &tableEntries,
1450  vector<pair<float,float> > &massData, vector<string> &warnings)
1451 {
1452  string entry;
1453  char *ptrBegin;
1454  ptrBegin=inBuffer;
1455  while(*ptrBegin == '-')
1456  ptrBegin++;
1457  splitStrsRef(ptrBegin," \n",colHeaders);
1458  if(!colHeaders.size() )
1460 
1461  //remove whitespace from each entry
1462  for(auto & colHeader : colHeaders)
1463  {
1464  colHeader = stripChars(colHeader,"\f\n\r\t ");
1465  }
1466 
1467  stripZeroEntries(colHeaders);
1468 
1469 
1470  if(colHeaders.size() > 1)
1471  {
1472 
1473  if(colHeaders.size() !=numIons)
1474  {
1475  // Emit warning
1477  }
1478 
1479  //Strip any trailing newlines off the last of the colheaders,
1480  // to avoid dos->unix conversion problems
1481  std::string str = colHeaders[colHeaders.size() -1];
1482 
1483  if(str[str.size() -1 ] == '\n')
1484  colHeaders[colHeaders.size()-1]=str.substr(0,str.size()-1);
1485 
1486  //Each column header should match the original ion name
1487  for(auto ui=1u;ui<colHeaders.size();ui++)
1488  {
1489  //look for a corresponding entry in the column headers
1490  if(names[ui-1].second != colHeaders[ui])
1491  {
1492  warnings.emplace_back("Range headings do not match order of the ions listed in the name specifications. The name specification ordering will be used when reading the range table, as the range heading section is declared as a comment in the file-format specifications, and is not to be intepreted by this program. Check range-species associations actually match what you expect.");
1493  break;
1494  }
1495 
1496  }
1497 
1498  }
1499 
1500  tableEntries.resize(numRanges*numIons,0);
1501  //Load in each range file line
1502  pair<float,float> massPair;
1503 
1504  for(unsigned int i=0; i<numRanges; i++)
1505  {
1506 
1507  //grab the line
1508  if(fgets(inBuffer,MAX_LINE_SIZE,fpRange) == nullptr)
1510 
1511 
1512  vector<string> entries;
1513  std::string tmpStr;
1514  tmpStr=stripWhite(inBuffer);
1515 
1516  splitStrsRef(tmpStr.c_str()," ",entries);
1517  stripZeroEntries(entries);
1518 
1519  //Should be two entries for the mass pair,
1520  // one entry per ion, and optionally one
1521  // entry for the marker (not used)
1522  if(entries.size() != numIons + 2 &&
1523  entries.size() !=numIons+3)
1524  {
1526  }
1527 
1528  size_t entryOff;
1529  entryOff=0;
1530  //if we have a leading entry, ignore it
1531  if(entries.size() == numIons +3)
1532  entryOff=1;
1533 
1534  if(stream_cast(massPair.first,entries[entryOff]))
1535  {
1537  }
1538  if(stream_cast(massPair.second,entries[entryOff+1]))
1539  {
1541  }
1542 
1543  if(massPair.first >= massPair.second)
1544  {
1545  return RANGE_ERR_DATA_FLIPPED;
1546  }
1547 
1548  massData.push_back(massPair);
1549 
1550  //Load the range data line
1551  entryOff+=2;
1552  for(unsigned int j=0; j<numIons; j++)
1553  {
1554  size_t tempInt;
1555  if(stream_cast(tempInt,entries[entryOff+j]))
1556  {
1558  }
1559 
1560  if(tempInt)
1561  tableEntries[numIons*i + j]=tempInt;
1562 
1563  }
1564 
1565 
1566  }
1567 
1568 
1569  //Do some post-processing on the range table
1570  //
1571  //Prevent rangefiles that have no valid ranges
1572  //from being loaded
1573  size_t nMax=std::accumulate(tableEntries.begin(),tableEntries.end(),0);
1574  if(!nMax)
1575  {
1577  }
1578 
1579  return 0;
1580 }
1581 
1582 unsigned int RangeFile::openENV(FILE *fpRange)
1583 {
1584  clear();
1585 
1586  //Ruoen group "environment file" format
1587  //This is not a standard file format, so the
1588  //reader is a best-effort implementation, based upon examples
1589  auto inBuffer = new char[MAX_LINE_SIZE];
1590  unsigned int numRanges;
1591  unsigned int numIons;
1592 
1593  bool beyondRanges=false;
1594  bool haveNumRanges=false;
1595  bool haveNameBlock=false;
1596  bool haveSeenRevHeader=false;
1597  vector<string> strVec;
1598 
1599  //Read file until we get beyond the range length
1600  while(!beyondRanges && !feof(fpRange) )
1601  {
1602  if(!fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange)
1603  || strlen(inBuffer) >=MAX_LINE_SIZE-1)
1604  {
1605  delete[] inBuffer;
1606  return RANGE_ERR_FORMAT;
1607  }
1608  //Trick to "strip" the buffer
1609  nullifyMarker(inBuffer,'#');
1610 
1611  string s;
1612  s=inBuffer;
1613 
1614  s=stripWhite(s);
1615 
1616  if(!s.size())
1617  continue;
1618 
1619  //If we have
1620  if(!haveSeenRevHeader && (s== "Rev_2.0"))
1621  {
1622  haveSeenRevHeader=true;
1623  continue;
1624  }
1625 
1626  //Try different delimiters to split string
1627  splitStrsRef(s.c_str(),"\t ",strVec);
1628 
1629  stripZeroEntries(strVec);
1630 
1631  //Drop any entry data including and after ';'.
1632  // Revision 0.3 files show this ;, but it is not clear what
1633  // it is supposed to be for. Sample files I have show only zeros in these positions
1634  for(auto ui=0u;ui<strVec.size();ui++)
1635  {
1636  size_t offset;
1637  offset=strVec[ui].find(';');
1638  if(offset == std::string::npos)
1639  continue;
1640 
1641  strVec[ui]=strVec[ui].substr(0,offset);
1642  }
1643 
1644 
1645  stripZeroEntries(strVec);
1646 
1647  if(strVec.empty())
1648  continue;
1649 
1650 
1651  if(!haveNumRanges)
1652  {
1653  //num ranges should have two entries, num ions and ranges
1654  if(strVec.size() != 2)
1655  {
1656  delete[] inBuffer;
1657  return RANGE_ERR_FORMAT;
1658  }
1659 
1660  if(stream_cast(numIons,strVec[0]))
1661  {
1662  delete[] inBuffer;
1663  return RANGE_ERR_FORMAT;
1664  }
1665  if(stream_cast(numRanges,strVec[1]))
1666  {
1667  delete[] inBuffer;
1668  return RANGE_ERR_FORMAT;
1669  }
1670 
1671  haveNumRanges=true;
1672  }
1673  else
1674  {
1675  //Do we still have to process the name block?
1676  if(!haveNameBlock)
1677  {
1678  //Just exiting name block,
1679  if(strVec.size() == 5)
1680  haveNameBlock=true;
1681  else if(strVec.size() == 4)
1682  {
1683  //Entry in name block
1684  if(!strVec[0].size())
1685  {
1686  delete[] inBuffer;
1687  return RANGE_ERR_FORMAT;
1688  }
1689 
1690  //Strip the charge state, if it exists
1691  strVec[0]=envDropChargeState(strVec[0]);
1692 
1693  //Check that name consists only of
1694  //readable ascii chars, or period
1695  for(unsigned int ui=0; ui<strVec[0].size(); ui++)
1696  {
1697  if(!isdigit(strVec[0][ui]) &&
1698  !isalpha(strVec[0][ui]) && strVec[0][ui] != '.')
1699  {
1700  delete[] inBuffer;
1701 
1702  return RANGE_ERR_FORMAT;
1703  }
1704  }
1705 
1706  //Env file contains only the
1707  // long name, so use that for both
1708  // the short and long names
1709  bool nameExists=false;
1710  for(auto ui=0u;ui<ionNames.size();ui++)
1711  {
1712  if (ionNames[ui].first == strVec[0])
1713  {
1714  nameExists=true;
1715  break;
1716  }
1717  }
1718 
1719  //ion name already exists (eg as another charge state). nothing to do.
1720  // Note that env files can have two colours for same ion at different states
1721  // but we don't support this
1722  if(nameExists)
1723  continue;
1724 
1725  ionNames.emplace_back(strVec[0],strVec[0]);
1726  //Use the colours (positions 1-3)
1727  RGBf colourStruct;
1728  if(stream_cast(colourStruct.red,strVec[1])
1729  ||stream_cast(colourStruct.green,strVec[2])
1730  ||stream_cast(colourStruct.blue,strVec[3]) )
1731  {
1732  delete[] inBuffer;
1733  return RANGE_ERR_FORMAT;
1734 
1735  }
1736 
1737  if(colourStruct.red >1.0 || colourStruct.red < 0.0f ||
1738  colourStruct.green >1.0 || colourStruct.green < 0.0f ||
1739  colourStruct.blue >1.0 || colourStruct.blue < 0.0f )
1740  {
1741  delete[] inBuffer;
1742  return RANGE_ERR_FORMAT;
1743 
1744  }
1745 
1746 
1747  colours.push_back(colourStruct);
1748  }
1749  else
1750  {
1751  //Well thats not right,
1752  //we should be looking at the first entry in the
1753  //range block....
1754  delete[] inBuffer;
1755  return RANGE_ERR_FORMAT;
1756  }
1757  }
1758 
1759  if(haveNameBlock)
1760  {
1761  //We are finished
1762  if(strVec.size() == 5)
1763  {
1764  unsigned int thisIonID;
1765  thisIonID=(unsigned int)-1;
1766  strVec[0] = envDropChargeState(strVec[0]);
1767  for(unsigned int ui=0;ui<ionNames.size();ui++)
1768  {
1769  if(strVec[0] == ionNames[ui].first)
1770  {
1771  thisIonID=ui;
1772  break;
1773  }
1774 
1775  }
1776 
1777  if(thisIonID==(unsigned int) -1)
1778  {
1779  delete[] inBuffer;
1780  return RANGE_ERR_FORMAT;
1781  }
1782 
1783  float rangeStart,rangeEnd;
1784  if(stream_cast(rangeStart,strVec[1]))
1785  {
1786  delete[] inBuffer;
1787  return RANGE_ERR_FORMAT;
1788  }
1789  if(stream_cast(rangeEnd,strVec[2]))
1790  {
1791  delete[] inBuffer;
1792  return RANGE_ERR_FORMAT;
1793  }
1794 
1795  //Disallow reversed ranges
1796  if(rangeStart > rangeEnd)
1797  {
1798  delete[] inBuffer;
1799  return RANGE_ERR_FORMAT;
1800  }
1801 
1802  ranges.emplace_back(rangeStart,rangeEnd);
1803 
1804  ionIDs.push_back(thisIonID);
1805  }
1806  else
1807  beyondRanges=true;
1808 
1809  }
1810 
1811  }
1812 
1813 
1814  }
1815 
1816  delete[] inBuffer;
1817 
1818  //Must have encountered a name and range sections,
1819  // with at least one range
1820  if(!haveNumRanges || ! haveNameBlock)
1821  return RANGE_ERR_FORMAT;
1822 
1823  //Note that the in-file reported number of ions might actually be too big
1824  //as we "fold" some ions together (eg Si_+ and Si_2+ for us are both considered Si)
1825  if(ionNames.empty() || ionNames.size() > numIons || ranges.size() > numRanges)
1826  return RANGE_ERR_FORMAT;
1827 
1828  //There should be more data following the range information.
1829  // if not, this is not really an env file
1830  if(feof(fpRange))
1831  {
1832  return RANGE_ERR_FORMAT;
1833  }
1834 
1835  return 0;
1836 }
1837 
1838 unsigned int RangeFile::openRRNG(FILE *fpRange)
1839 {
1840  clear();
1841 
1842  auto inBuffer = new char[MAX_LINE_SIZE];
1843  unsigned int numRanges;
1844  unsigned int numBasicIons;
1845 
1846 
1847  //Different "blocks" that can be read
1848  enum {
1849  BLOCK_NONE,
1850  BLOCK_IONS,
1851  BLOCK_RANGES,
1852  };
1853 
1854  unsigned int curBlock=BLOCK_NONE;
1855  bool haveSeenIonBlock;
1856  haveSeenIonBlock=false;
1857  bool haveSeenVolumes=false;
1858  numRanges=0;
1859  numBasicIons=0;
1860  vector<string> basicIonNames;
1861 
1862  while (!feof(fpRange))
1863  {
1864  if(!fgets((char *)inBuffer, MAX_LINE_SIZE, fpRange)
1865  || strlen(inBuffer) >=MAX_LINE_SIZE-1)
1866  break;
1867  //Trick to "strip" the buffer, assuming # is a comment
1868  nullifyMarker(inBuffer,'#');
1869 
1870  string s;
1871  s=inBuffer;
1872 
1873  s=stripWhite(s);
1874  if (!s.size())
1875  continue;
1876 
1877  if (lowercase(s) == "[ions]")
1878  {
1879  curBlock=BLOCK_IONS;
1880  continue;
1881  }
1882  else if (lowercase(s) == "[ranges]")
1883  {
1884  curBlock=BLOCK_RANGES;
1885  continue;
1886  }
1887 
1888  switch (curBlock)
1889  {
1890  case BLOCK_NONE:
1891  break;
1892  case BLOCK_IONS:
1893  {
1894 
1895  //The ion section in RRNG seems almost completely redundant.
1896  //This does not actually contain information about the ions
1897  //in the file (this is in the ranges section).
1898  //Rather it tells you what the constituents are from which
1899  //complex ions may be formed. Apply Palm to Face.
1900 
1901  vector<string> split;
1902  splitStrsRef(s.c_str(),'=',split);
1903 
1904  if (split.size() != 2)
1905  {
1906  delete[] inBuffer;
1908  }
1909 
1910  std::string stmp;
1911  stmp=lowercase(split[0]);
1912 
1913  haveSeenIonBlock=true;
1914 
1915 
1916  if (stmp == "number")
1917  {
1918  //Should not have set the
1919  //number of ions yet.
1920  if (numBasicIons !=0)
1921  {
1922  delete[] inBuffer;
1924  }
1925  //Set the number of ions
1926  if(stream_cast(numBasicIons, split[1]))
1927  {
1928  delete[] inBuffer;
1929  return RANGE_ERR_NUMIONS;
1930  }
1931 
1932  if (numBasicIons == 0)
1933  {
1934  delete[] inBuffer;
1935  return RANGE_ERR_NUMIONS;
1936  }
1937  }
1938  else if (split[0].size() >3)
1939  {
1940  stmp = lowercase(split[0].substr(0,3));
1941  if (stmp == "ion")
1942  {
1943  //OK, its an ion.
1944  basicIonNames.push_back(split[1]);
1945 
1946  if (basicIonNames.size() > numBasicIons)
1947  {
1948  delete[] inBuffer;
1949  return RANGE_ERR_TOO_MANYIONS;
1950  }
1951  }
1952  else
1953  {
1954  delete[] inBuffer;
1956  }
1957  }
1958  break;
1959  }
1960  case BLOCK_RANGES:
1961  {
1962  //Although it looks like the blocks are independent.
1963  //it is more complicated to juggle a parser with them
1964  //out of dependency order, as a second pass would
1965  //need to be done.
1966  if (!haveSeenIonBlock)
1967  {
1968  delete[] inBuffer;
1970  }
1971  vector<string> split;
1972 
1973  if (s.size() > 6)
1974  {
1975  splitStrsRef(s.c_str(),'=',split);
1976 
1977  if (split.size() != 2)
1978  {
1979  delete[] inBuffer;
1981  }
1982 
1983  if (lowercase(split[0].substr(0,5)) == "numbe")
1984  {
1985 
1986  //Should not have set the num ranges yet
1987  if (numRanges)
1988  {
1989  delete[] inBuffer;
1991  }
1992 
1993  if (stream_cast(numRanges,split[1]))
1994  {
1995 
1996  delete[] inBuffer;
1997  return RANGE_ERR_NUMRANGE_PARSE;
1998  }
1999 
2000  if (!numRanges)
2001  {
2002  delete[] inBuffer;
2003  return RANGE_ERR_NUMRANGE_PARSE;
2004  }
2005 
2006  }
2007  else if ( lowercase(split[0].substr(0,5)) == "range")
2008  {
2009  //Try to decode a range line, as best we can.
2010  //These appear to come in a variety of flavours,
2011  //which makes this section quite long.
2012 
2013  //OK, so the format here is a bit weird
2014  //we first have to strip the = bit
2015  //then we have to step across fields,
2016  //I assume the fields are in order (or missing)
2017  //* denotes 0 or more, + denotes 1 or more, brackets denote grouping
2018  // Vol: [0-9]* ([A-z]+: [0-9]+)* (Name:[0-9]*([A-z]+[0-9]*)+ Color:[0-F][0-F][0-F][0-F][0-F][0-F]
2019  // Examples:
2020  // Range1=31.8372 32.2963 Vol:0.01521 Zn:1 Color:999999
2021  // or
2022  // Range1=31.8372 32.2963 Zn:1 Color:999999
2023  // or
2024  // Range1=95.3100 95.5800 Vol:0.04542 Zn:1 Sb:1 Color:00FFFF
2025  // or
2026  // Range1=95.3100 95.5800 Vol:0.04542 Name:1Zn1Sb1 Color:00FFFF
2027  // or
2028  // Range1=95.3100 95.5800 Vol:0.04542 Zn:1 Sb:1 Name:1Zn1Sb1 Color:00FFFF
2029  // or
2030  // Range1= 95.3100 95.5800 Color:00FFFF Vol:0.04542 Zn:1 Sb:1 Name:1Zn1Sb1
2031 
2032  //Starting positions (string index)
2033  //of range start and end
2034  //Range1=31.8372 32.2963 Vol:0.01521 Zn:1 Color:999999
2035  // ^rngmid ^rngend
2036  string rngStart,rngEnd;
2037  string strTmp=stripWhite(split[1]);
2038 
2039  //Split the remaining field into key:value pairs, with whitespace delimiters
2040  split.clear();
2041  splitStrsRef(strTmp.c_str(),"\t ",split);
2042  stripZeroEntries(split);
2043  //Need a minimum of 4 entries here
2044  if(split.size() < 4)
2046 
2047  //First two contains the range data
2048  rngStart=split[0];
2049  rngEnd=split[1];
2050 
2051  //Remove the two leading elements, the rest are : separated
2052  split.erase(split.begin());
2053  split.erase(split.begin());
2054 
2055  //Retrieve the remaining fields
2056  RGBf col;
2057  bool haveColour,haveNameField;
2058  haveColour=false;
2059  haveNameField=false;
2060  string strIonNameTmp,strNameFieldValue;
2061 
2062  map<string,size_t> tmpFormula;
2063 
2064  for (unsigned int ui=0; ui<split.size(); ui++)
2065  {
2066  //':' is the key:value delimiter (Self reference is hilarious.)
2067  size_t colonPos;
2068 
2069 
2070  colonPos = split[ui].find_first_of(':');
2071 
2072  if (colonPos == std::string::npos)
2073  {
2074  delete[] inBuffer;
2076  }
2077 
2078  //Extract the key value pairs
2079  string key,value;
2080  key=split[ui].substr(0,colonPos);
2081  value=split[ui].substr(colonPos+1);
2082  if (lowercase(key) == "vol")
2083  {
2084  //Ion volumes are optional.
2085  // if this is the first time we have seen them
2086  // then we should allocate space for these
2087  if(!rangeVolumes.size())
2088  rangeVolumes.resize(numRanges,0);
2089 
2090  if(ui >=numRanges)
2091  {
2092  delete[] inBuffer;
2094  }
2095 
2096 
2097 
2098  if(stream_cast(rangeVolumes[ui],value))
2099  {
2100  delete[] inBuffer;
2101  return RANGE_ERR_VOLUME_PARSE;
2102  }
2103 
2104  }
2105  else if (lowercase(key) == "name")
2106  {
2107  //OK, so we need to handle two weird cases
2108  //1) we have the name section and the ion section
2109  //2) we have the name section and no ion section
2110 
2111  //Rant:WTF is the point of the "name" section. They are
2112  //encoding information in a redundant and irksome fashion!
2113  //Better to store the charge (if that's what you want) in a F-ing charge
2114  //section, and the multiplicity is already bloody defined elsewhere!
2115 
2116  //This won't be known until we complete the parse
2117  haveNameField=true;
2118  strNameFieldValue=value;
2119  }
2120  else if (lowercase(key) == "color")
2121  {
2122  haveColour=true;
2123 
2124 
2125  if (value.size() !=6)
2126  {
2127  delete[] inBuffer;
2128  return RANGE_ERR_BADCOLOUR;
2129  }
2130 
2131  //Use our custom string parser,
2132  //which requires a leading #,
2133  //in lowercase
2134  value = string("#") + lowercase(value);
2135  unsigned char r,g,b,a;
2136 
2137  if(!parseColString(value,r,g,b,a))
2138  {
2139  delete[] inBuffer;
2140  return RANGE_ERR_FORMAT;
2141  }
2142 
2143  col.red = (float)r/255.0f;
2144  col.green=(float)g/255.0f;
2145  col.blue=(float)b/255.0f;
2146  }
2147  else
2148  {
2149  vector<string> commaSplit;
2150  splitStrsRef(key.c_str(),',',commaSplit);
2151 
2152  if(commaSplit.size() >1)
2153  {
2154  if(haveNameField)
2155  return RANGE_ERR_FORMAT;
2156  //we have something like C,C,Cu (which does happen)
2157  // count each case and then create result
2158  map<string, unsigned int> uniqMap;
2159 
2160  for(const auto str : commaSplit)
2161  {
2162  //If the ion does not exist in the initial ions declaration, abandon ship
2163  if(std::find(basicIonNames.begin(),basicIonNames.end(),str) == basicIonNames.end())
2164  return RANGE_ERR_ION_NOT_MAPPED;
2165 
2166 
2167  //Count the number of occurences in our comma sparated list
2168  auto it=uniqMap.find(str);
2169  if( it == uniqMap.end())
2170  uniqMap.insert(make_pair(str,1));
2171  else
2172  it->second++;
2173  }
2174 
2175  //FIXME: Use formula, rather than encoding name. This is an unusual case,
2176  // but we have seen this.
2177 
2178  //Rename the ion by using the uniq map,
2179  // such that e.g. "C,C,Cu" -> "C2Cu"
2180  strIonNameTmp="";
2181  for (auto &entry : uniqMap)
2182  {
2183 
2184  strIonNameTmp+=entry.first;
2185 
2186  //if multiplicity is non-unitary, append modifier
2187  if(entry.second > 1)
2188  {
2189  string tmpStr;
2190  stream_cast(tmpStr,entry.second);
2191 
2192  strIonNameTmp+=tmpStr;
2193  }
2194  else if (entry.second <=0)
2195  return RANGE_ERR_ION_NOT_MAPPED;
2196  }
2197 
2198 
2199  }
2200  else
2201  {
2202  //Basic ion was not in the original [Ions] list. This is not right
2203  unsigned int pos=(unsigned int)-1;
2204  for (unsigned int uj=0; uj<basicIonNames.size(); uj++)
2205  {
2206  if (basicIonNames[uj] == key)
2207  {
2208  pos = uj;
2209  break;
2210  }
2211  }
2212 
2213  if (pos == (unsigned int)-1)
2214  {
2215  delete[] inBuffer;
2216  return RANGE_ERR_ION_NOT_MAPPED;
2217  }
2218  //Check the multiplicity of the ion. Should be an integer > 0
2219  unsigned int uintVal;
2220  if (stream_cast(uintVal,value) || !uintVal)
2221  {
2222  delete[] inBuffer;
2224  }
2225 
2226  //Store the formula
2227  tmpFormula[key]=uintVal;
2228  }
2229  }
2230  }
2231 
2232  if (!haveColour )
2233  {
2234  //Okay, so a colour wasn't provided.
2235  // to make our users lives a bit less
2236  // boring, lets just use some semi-random ones
2237  col.red=rand()/(float)std::numeric_limits<int>::max();
2238  col.green=rand()/(float)std::numeric_limits<int>::max();
2239  col.blue=rand()/(float)std::numeric_limits<int>::max();
2240 
2241  }
2242 
2243  //Get the range values
2244  float rngStartV,rngEndV;
2245  if (stream_cast(rngStartV,rngStart))
2246  {
2247  delete[] inBuffer;
2248 
2249  return RANGE_ERR_BADSTART;
2250  }
2251 
2252  if (stream_cast(rngEndV,rngEnd))
2253  {
2254  delete[] inBuffer;
2255 
2256  return RANGE_ERR_BADEND;
2257  }
2258 
2259  // --- So the ion field appears to be optional (that is,
2260  //ivas emits RRNGs that do not contain an ion listed in the
2261  //ion field). It is unclear what the purpose of these lines is,
2262  //so we shall ignore it, in preference to aborting
2263  // A range entry may have:
2264  // 1) Ion information
2265  // 2) Name tag
2266  // 3) Ion information + Name tag
2267  // 4) Neither - results in error
2268  // Result:
2269  // 1) short and long name from ion info
2270  // 2) short and long name from ion tag, try and generate ion info from name
2271  // 3) short name from ion info, long name from name tag
2272 
2273  // If there is a name field, store this as a new long name
2274  string strIonNameNew_long;
2275  if(haveNameField)
2276  {
2277  //OK, so we don't have the ion section
2278  //but we do have the name field section
2279  //let us decode the name field in order to retrieve
2280  //the ion information which was missing (for whatever reason)
2281  if(!strNameFieldValue.size())
2282  {
2283  delete[] inBuffer;
2284 
2285  return RANGE_ERR_FORMAT;
2286  }
2287 
2288  // strip off charge state, appaerently names may start with it to indicate charge state
2289  unsigned int chargeStrStop=0;
2290  for(unsigned int ui=0; ui<strNameFieldValue.size(); ui++)
2291  {
2292  if(strNameFieldValue[ui] <'0' || strNameFieldValue[ui] > '9')
2293  {
2294  chargeStrStop = ui;
2295  break;
2296  }
2297 
2298  }
2299 
2300  //Strip off the charge value, to get the ion name
2301  strIonNameNew_long = strNameFieldValue.substr(chargeStrStop,strNameFieldValue.size()-chargeStrStop);
2302 
2303  if (strIonNameNew_long.empty())
2304  {
2305  //ERROR: empty name after charge state trim
2306  //TODO: could handle this as just use the number as the name
2307  delete[] inBuffer;
2308  return RANGE_ERR_FORMAT;
2309  }
2310  }
2311 
2312  // Figure out if we have situation 1/2/3 above
2313  string strIonNameNew;
2314  if(strIonNameTmp.size())
2315  {
2316  // have short name from ion information, may also have long name above
2317  strIonNameNew = strIonNameTmp;
2318  // If it has no long name, add the name from the ion definition
2319  if (strIonNameNew_long.empty())
2320  strIonNameNew_long = strIonNameTmp;
2321  }
2322  else if(haveNameField)
2323  {
2324  // only have name field, set short name to name field too
2325  strIonNameNew = strIonNameNew_long;
2326  }
2327  else if(tmpFormula.size())
2328  {
2329  //We have a formula, but no name data, build it from the formula data
2330  for(const auto &v : tmpFormula)
2331  {
2332  strIonNameNew += v.first;
2333  if(v.second >1)
2334  strIonNameNew+=std::to_string(v.second);
2335  }
2336 
2337  }
2338  else
2339  {
2340  delete[] inBuffer;
2341  return RANGE_ERR_FORMAT;
2342  }
2343  // ---
2344 
2345 
2346  //Check to see if we have this ion.
2347  //If we don't, we create a new one.
2348  unsigned int pos=(unsigned int)(-1);
2349  for (unsigned int ui=0; ui<ionNames.size(); ui++)
2350  {
2351  if (ionNames[ui].first == strIonNameNew)
2352  {
2353  pos=ui;
2354  break;
2355  }
2356  }
2357 
2358  // add range entry
2359  ranges.push_back(std::make_pair(rngStartV,rngEndV));
2360 
2361  if (pos == (unsigned int) -1)
2362  {
2363  ionIDs.push_back(ionNames.size()); // add new ID for ion which will be added
2364  ionNames.push_back(make_pair(strIonNameNew,strIonNameNew_long));
2365  colours.push_back(col);
2366 
2367  if(ionFormulas.size())
2368  ionFormulas.resize(ionNames.size());
2369 
2370  if(tmpFormula.size())
2371  {
2372  ionFormulas.resize(ionNames.size());
2373  ionFormulas[ionNames.size()-1]=tmpFormula;
2374  tmpFormula.clear();
2375  }
2376  }
2377  else
2378  {
2379  //we have the name already
2380  ionIDs.push_back(pos);
2381  }
2382  }
2383  else
2384  {
2385  delete[] inBuffer;
2387  }
2388  }
2389  break;
2390  }
2391  default:
2392  ASSERT(false);
2393  break;
2394  }
2395  }
2396 
2397  delete[] inBuffer;
2398 
2399  //If we didn't find anything useful, then thats a formatting error
2400  if(!haveSeenIonBlock )
2402  if(!numRanges)
2403  return RANGE_ERR_NO_RANGES;
2404 
2405  if(!numBasicIons)
2406  return RANGE_ERR_NO_BASIC_IONS;
2407 
2408 
2409  if(numRanges != ranges.size())
2411 
2412  // If we haven't seen volumes, clear the volumes (which were zero)
2413  if (!haveSeenVolumes)
2414  rangeVolumes.clear();
2415 
2416  return 0;
2417 }
2418 
2419 bool RangeFile::extensionIsRange(const char *ext)
2420 {
2421  bool isRange=false;
2422  unsigned int extOff=0;
2423  while(strlen(RANGE_EXTS[extOff]))
2424  {
2425  if(!strcmp(ext,RANGE_EXTS[extOff]))
2426  {
2427  isRange=true;
2428  break;
2429  }
2430  extOff++;
2431  }
2432 
2433  return isRange;
2434 }
2435 
2436 
2437 void RangeFile::getAllExts(std::vector<std::string> &exts)
2438 {
2439  //subtract one for the guard terminator, which
2440  // we do not want to return
2441  exts.resize(ARRAYSIZE(RANGE_EXTS)-1);
2442 
2443  for(unsigned int ui=0;ui<ARRAYSIZE(RANGE_EXTS)-1; ui++)
2444  {
2445  exts[ui]=RANGE_EXTS[ui];
2446  ASSERT(exts[ui].size());
2447  }
2448 }
2449 
2451 {
2452  ASSERT(ionIDs.size() == ranges.size());
2453  ASSERT(ionIDs.empty() || *(std::max_element(ionIDs.begin(),ionIDs.end())) < ionNames.size());
2454  ASSERT(rangeVolumes.empty() || ( rangeVolumes.size() == ranges.size()) );
2455 
2456  ASSERT(colours.size() == ionNames.size());
2457 
2458  ASSERT(ionFormulas.empty() || ionFormulas.size() == ionNames.size());
2459 
2460 
2461  for(unsigned int ui=0;ui<ranges.size();ui++)
2462  {
2463  //Check for range zero width
2464  if(ranges[ui].first == ranges[ui].second)
2465  return false;
2466 
2467  //Check that ranges don't overlap.
2468  for(unsigned int uj=ui+1; uj<ranges.size();uj++)
2469  {
2470  //check that not sitting inside a range
2471  if(ranges[ui].first > ranges[uj].first &&
2472  ranges[ui].first < ranges[uj].second )
2473  return false;
2474  if(ranges[ui].second > ranges[uj].first &&
2475  ranges[ui].second < ranges[uj].second )
2476  return false;
2477 
2478  //check for not spanning a range
2479  if(ranges[ui].first < ranges[uj].first &&
2480  ranges[ui].second > ranges[uj].second)
2481  return false;
2482 
2483  //Check for range duplication
2484  if(ranges[ui].first == ranges[uj].first &&
2485  ranges[ui].second == ranges[uj].second)
2486  return false;
2487 
2488  }
2489  }
2490 
2491 
2492  //Ensure that the names don't clash
2493  for(auto ui=0u;ui<ionNames.size();ui++)
2494  {
2495  for(auto uj=ui+1;uj<ionNames.size();uj++)
2496  {
2497  if(ionNames[ui].first == ionNames[uj].first ||
2498  (ionNames[ui].second.size() && (ionNames[ui].second == ionNames[uj].second) ) )
2499  return false;
2500  }
2501  }
2502 
2503 
2504  //Ensure that names conform to allowed naming scheme
2505  for(const auto & ionName : ionNames)
2506  {
2507  string tmp;
2508  tmp=ionName.first;
2509 
2510  const char *DISALLOWED_ION_NAMES=" \t\r\n";
2511  //TODO : Use whitelist, rather than blacklist
2512  if(tmp.find_first_of(DISALLOWED_ION_NAMES)!= string::npos)
2513  return false;
2514 
2515  tmp=ionName.second;
2516  if(tmp.find_first_of(DISALLOWED_ION_NAMES)!= string::npos)
2517  return false;
2518  }
2519 
2520 
2521  // Ensure forumlae are unique
2522  for(size_t ui=0;ui<ionFormulas.size();ui++)
2523  {
2524  for(size_t uj=ui+1;uj<ionFormulas.size();uj++)
2525  {
2526  if(ionFormulas[ui] == ionFormulas[uj])
2527  return false;
2528  }
2529  }
2530 
2531 
2532  return true;
2533 }
2534 
2536 {
2537  vector<bool> killRange;
2538  killRange.resize(ranges.size(),false);
2539  //Enforce non-overlapped ranges
2540  for(unsigned int ui=0;ui<ranges.size();ui++)
2541  {
2542  //Enforce non-zero width for ranges
2543  if(ranges[ui].first == ranges[ui].second)
2544  {
2545  killRange[ui]=true;
2546  continue;
2547  }
2548 
2549  //Enforce that ranges don't overlap.
2550  for(unsigned int uj=ui+1; uj<ranges.size();uj++)
2551  {
2552  //check that not sitting inside a range
2553  if(ranges[ui].first > ranges[uj].first &&
2554  ranges[ui].first < ranges[uj].second )
2555  {
2556  killRange[ui]=true;
2557  continue;
2558  }
2559  if(ranges[ui].second > ranges[uj].first &&
2560  ranges[ui].second < ranges[uj].second )
2561  {
2562  killRange[ui]=true;
2563  continue;
2564  }
2565 
2566  //check for not spanning a range
2567  if(ranges[ui].first < ranges[uj].first &&
2568  ranges[ui].second > ranges[uj].second)
2569  {
2570  killRange[ui]=true;
2571  continue;
2572  }
2573 
2574  //Check for range duplication
2575  if(ranges[ui].first == ranges[uj].first &&
2576  ranges[ui].second == ranges[uj].second)
2577  {
2578  killRange[ui]=true;
2579  continue;
2580  }
2581 
2582  }
2583 
2584  }
2585 
2586  vectorMultiErase(ranges,killRange);
2587  vectorMultiErase(ionIDs,killRange);
2588  return isSelfConsistent();
2589 }
2590 
2591 bool RangeFile::isSelfConsistent(std::vector<std::string> &messages) const
2592 {
2593  bool inconsistent=false;
2594  string msg;
2595  for(unsigned int ui=0;ui<ranges.size();ui++)
2596  {
2597  //Check for range zero width
2598  if(ranges[ui].first == ranges[ui].second)
2599  {
2600  msg = "Zero width range : ";
2601  strAppend(msg,ranges[ui].first);
2602  msg+="-";
2603  strAppend(msg,ranges[ui].first);
2604 
2605  messages.push_back(msg);
2606  inconsistent = false;
2607  }
2608 
2609  //Check that ranges don't overlap.
2610  for(unsigned int uj=ui+1; uj<ranges.size();uj++)
2611  {
2612  //check that not sitting inside a range
2613  if(ranges[ui].first > ranges[uj].first &&
2614  ranges[ui].first < ranges[uj].second )
2615  {
2616  msg="One Range (A) contains another (B) - A: ";
2617  msg+=rangeToStr(ranges[ui]);
2618  msg+="B: ";
2619  msg+=rangeToStr(ranges[uj]);
2620 
2621  messages.push_back(msg);
2622  inconsistent=true;
2623  }
2624  if(ranges[ui].second > ranges[uj].first &&
2625  ranges[ui].second < ranges[uj].second )
2626  {
2627  msg="One Range (A) contains another (B) - A: ";
2628  msg+=rangeToStr(ranges[uj]);
2629  msg+="B: ";
2630  msg+=rangeToStr(ranges[ui]);
2631 
2632  messages.push_back(msg);
2633  inconsistent=true;
2634  }
2635 
2636  //check for not spanning a range
2637  if(ranges[ui].first < ranges[uj].first &&
2638  ranges[ui].second > ranges[uj].second)
2639  {
2640  msg="Ranges span one another: ";
2641  msg+=rangeToStr(ranges[ui]);
2642  msg+= " ";
2643  msg+=rangeToStr(ranges[uj]);
2644  messages.push_back(msg);
2645  inconsistent=true;
2646  }
2647 
2648  //Check for range duplication
2649  if(ranges[ui].first == ranges[uj].first &&
2650  ranges[ui].second == ranges[uj].second)
2651  {
2652  msg="Duplicated range :";
2653  msg+=rangeToStr(ranges[ui]);
2654 
2655  messages.push_back(msg);
2656  inconsistent=true;
2657  }
2658 
2659  }
2660  }
2661 
2662 
2663  //Ensure that the names don't clash
2664  for(size_t ui=0;ui<ionNames.size();ui++)
2665  {
2666  for(size_t uj=ui+1;uj<ionNames.size();uj++)
2667  {
2668  if(ionNames[ui].first == ionNames[uj].first ||
2669  ionNames[ui].second == ionNames[uj].second)
2670  {
2671  msg="Duplicated ion names";
2672  msg+=ionNames[ui].first;
2673 
2674  messages.push_back(msg);
2675  inconsistent=true;
2676  }
2677  }
2678  }
2679 
2680 
2681  //Ensure that names conform to allowed naming scheme
2682  for(size_t ui=0;ui<ionNames.size();ui++)
2683  {
2684  string tmp;
2685  tmp=ionNames[ui].first;
2686 
2687  const char *DISALLOWED_ION_NAMES=" \t\r\n";
2688  //TODO : Use whitelist, rather than blacklist
2689  if(tmp.find_first_of(DISALLOWED_ION_NAMES)!= string::npos)
2690  {
2691  msg="Invalid character in short ion name, for ion ";
2692  strAppend(msg,ui);
2693 
2694  messages.push_back(msg);
2695  inconsistent=true;
2696  }
2697 
2698  tmp=ionNames[ui].second;
2699  if(tmp.find_first_of(DISALLOWED_ION_NAMES)!= string::npos)
2700  {
2701  msg="Invalid character in long ion name, for ion ";
2702  strAppend(msg,ui);
2703 
2704  messages.push_back(msg);
2705  inconsistent=true;
2706  }
2707  }
2708 
2709 
2710  return !inconsistent;
2711 }
2712 
2713 bool RangeFile::isRanged(float mass) const
2714 {
2715  unsigned int numRanges = ranges.size();
2716 
2717  for(unsigned int ui=0; ui<numRanges; ui++)
2718  {
2719  if( mass >= ranges[ui].first &&
2720  mass <= ranges[ui].second )
2721  return true;
2722  }
2723 
2724  return false;
2725 }
2726 
2727 bool RangeFile::isRanged(const IonHit &ion) const
2728 {
2729  return isRanged(ion.getMassToCharge());
2730 }
2731 
2732 bool RangeFile::range(vector<IonHit> &ions, const string &ionShortName)
2733 {
2734  vector<IonHit> rangedVec;
2735 
2736  //find the Ion ID of what we want
2737  unsigned int targetIonID=(unsigned int)-1;
2738  for(unsigned int ui=0; ui<ionNames.size(); ui++)
2739  {
2740  if(ionNames[ui].first == ionShortName)
2741  {
2742  targetIonID = ui;
2743  break;
2744  }
2745  }
2746 
2747  if(targetIonID == (unsigned int)(-1))
2748  return false;
2749 
2750  //find the ranges that have that ionID
2751  vector<unsigned int> subRanges;
2752  for(unsigned int ui=0; ui<ionIDs.size(); ui++)
2753  {
2754  if(ionIDs[ui] == targetIonID)
2755  subRanges.push_back(ui);
2756  }
2757 
2758  unsigned int numIons=ions.size();
2759  rangedVec.reserve(numIons);
2760 
2761  unsigned int numSubRanges = subRanges.size();
2762 
2763  for(unsigned int ui=0; ui<numIons; ui++)
2764  {
2765  for(unsigned int uj=0; uj<numSubRanges; uj++)
2766  {
2767  if( ions[ui].getMassToCharge() >= ranges[subRanges[uj]].first &&
2768  ions[ui].getMassToCharge() <= ranges[subRanges[uj]].second )
2769  {
2770  rangedVec.push_back(ions[ui]);
2771  break;
2772  }
2773  }
2774  }
2775 
2776  //Do the switcheroonie
2777  //such that the un-ranged ions are destroyed
2778  //and the ranged ions are kept
2779  ions.swap(rangedVec);
2780  return true;
2781 }
2782 
2783 void RangeFile::range(vector<IonHit> &ions) const
2784 {
2785  vector<IonHit> rangedVec;
2786 
2787  unsigned int numIons=ions.size();
2788  rangedVec.reserve(numIons);
2789 
2790  for(unsigned int ui=0; ui<numIons; ui++)
2791  {
2792  if(isRanged(ions[ui]))
2793  rangedVec.push_back(ions[ui]);
2794  }
2795 
2796  //Do the switcheroonie
2797  //such that the un-ranged ions are destroyed
2798  //and the ranged ions are kept
2799  ions.swap(rangedVec);
2800 }
2801 // Note this is specifically not called ::range(...) to prevent
2802 // boolean promotion ahead of char *->string for other overloads
2803 void RangeFile::rangeInvertable(vector<IonHit> &ions, bool invert)
2804 {
2805  vector<IonHit> rangedVec;
2806 
2807  unsigned int numIons=ions.size();
2808  rangedVec.reserve(numIons);
2809 
2810  for(unsigned int ui=0; ui<numIons; ui++)
2811  {
2812  if(XOR(isRanged(ions[ui]),invert) )
2813  rangedVec.push_back(ions[ui]);
2814  }
2815 
2816  //Do the switcheroonie
2817  //such that the un-ranged ions are destroyed
2818  //and the ranged ions are kept
2819  ions.swap(rangedVec);
2820 }
2821 
2822 void RangeFile::rangeByRangeID(vector<IonHit> &ions, unsigned int rangeID)
2823 {
2824  vector<IonHit> rangedVec;
2825 
2826  unsigned int numIons=ions.size();
2827  rangedVec.reserve(numIons);
2828 
2829  for(unsigned int ui=0; ui<numIons; ui++)
2830  {
2831  if( ions[ui].getMassToCharge() >= ranges[rangeID].first &&
2832  ions[ui].getMassToCharge() <= ranges[rangeID].second )
2833  {
2834  rangedVec.push_back(ions[ui]);
2835  break;
2836  }
2837  }
2838 
2839  //Do the switcheroonie
2840  //such that the un-ranged ions are destroyed
2841  //and the ranged ions are kept
2842  ions.swap(rangedVec);
2843 }
2844 
2845 
2846 unsigned int RangeFile::getNumRanges() const
2847 {
2848  return ranges.size();
2849 }
2850 
2851 unsigned int RangeFile::getNumRanges(unsigned int ionID) const
2852 {
2853  unsigned int res=0;
2854  for(unsigned int ui=0;ui<ranges.size();ui++)
2855  {
2856  if( getIonID(ui) == ionID)
2857  res++;
2858  }
2859 
2860  return res;
2861 }
2862 
2863 unsigned int RangeFile::getNumIons() const
2864 {
2865  return ionNames.size();
2866 }
2867 
2868 pair<float,float> RangeFile::getRange(unsigned int ui) const
2869 {
2870  return ranges[ui];
2871 }
2872 
2873 pair<float,float> &RangeFile::getRangeByRef(unsigned int ui)
2874 {
2875  return ranges[ui];
2876 }
2877 
2878 RGBf RangeFile::getColour(unsigned int ui) const
2879 {
2880  ASSERT(ui < colours.size());
2881  return colours[ui];
2882 }
2883 
2884 unsigned int RangeFile::getIonID(float mass) const
2885 {
2886  unsigned int numRanges = ranges.size();
2887 
2888  for(unsigned int ui=0; ui<numRanges; ui++)
2889  {
2890  if( mass >= ranges[ui].first &&
2891  mass <= ranges[ui].second )
2892  return ionIDs[ui];
2893  }
2894 
2895  return (unsigned int)-1;
2896 }
2897 
2898 unsigned int RangeFile::getIonID(const IonHit &hit) const
2899 {
2900  return getIonID(hit.getMassToCharge());
2901 }
2902 
2903 
2904 unsigned int RangeFile::getRangeID(float mass) const
2905 {
2906  unsigned int numRanges = ranges.size();
2907 
2908  for(unsigned int ui=0; ui<numRanges; ui++)
2909  {
2910  if( mass >= ranges[ui].first &&
2911  mass <= ranges[ui].second )
2912  return ui;
2913  }
2914 
2915  return (unsigned int)-1;
2916 }
2917 
2918 unsigned int RangeFile::getIonID(unsigned int range) const
2919 {
2920  ASSERT(range < ranges.size());
2921 
2922  return ionIDs[range];
2923 }
2924 
2925 unsigned int RangeFile::getIonID(const char *name, bool useShortName) const
2926 {
2927  if(useShortName)
2928  {
2929  //find the first element in the sequence
2930  for(unsigned int ui=0; ui<ionNames.size(); ui++)
2931  {
2932  if(ionNames[ui].first == name)
2933  return ui;
2934  }
2935  }
2936  else
2937  {
2938  //Find using the second element in the sequence (longName)
2939  for(unsigned int ui=0; ui<ionNames.size(); ui++)
2940  {
2941  if(ionNames[ui].second == name)
2942  return ui;
2943  }
2944 
2945  }
2946  return (unsigned int)-1;
2947 }
2948 
2949 
2950 std::map<std::string,size_t> RangeFile::getIonFormula(unsigned int ionID) const
2951 {
2952  ASSERT(ionID < ionFormulas.size());
2953  return ionFormulas[ionID];
2954 }
2955 
2957 void RangeFile::setIonFormula(const std::string &name, const std::map<std::string,size_t> &formula, bool useShortName)
2958 {
2959  unsigned int ui = getIonID(name.c_str(), useShortName);
2960 
2961  ASSERT(ui < ionFormulas.size());
2962  setIonFormula(ui, formula);
2963 }
2964 
2966 void RangeFile::setIonFormula(const unsigned int ionID, const std::map<std::string,size_t> &formula)
2967 {
2968  ASSERT(ionID < ionFormulas.size());
2969  if(!ionFormulas.size())
2970  ionFormulas.resize(ionNames.size());
2971  ionFormulas[ionID] = formula;
2972 }
2973 
2974 
2975 std::string RangeFile::getName(unsigned int ionID,bool shortName) const
2976 {
2977  ASSERT(ionID < ionNames.size());
2978  if(shortName)
2979  return ionNames[ionID].first;
2980  else
2981  return ionNames[ionID].second;
2982 }
2983 
2984 std::string RangeFile::getName(const IonHit &ion, bool shortName) const
2985 {
2986  if(!isRanged(ion))
2987  return string("");
2988 
2989  if(shortName)
2990  return ionNames[getIonID(ion.getMassToCharge())].first;
2991  else
2992  return ionNames[getIonID(ion.getMassToCharge())].second;
2993 }
2994 
2995 bool RangeFile::rangeByID(vector<IonHit> &ionHits, unsigned int rng)
2996 {
2997  //This is a bit slack, could be faster, but should work.
2998  return range(ionHits,ionNames[rng].first);
2999 }
3000 
3001 void RangeFile::extractIons(const std::vector<IonHit> &ions,
3002  const vector<unsigned int> &ionID, vector<IonHit> &newIons) const
3003 {
3004  vector<unsigned int> sortedIDs;
3005  sortedIDs = ionID;
3006 
3007  std::sort(sortedIDs.begin(),sortedIDs.end());
3008 
3009  //Go through each ion, and check that the ionID
3010 #pragma omp parallel for
3011  for(unsigned int ui=0;ui<ions.size();ui++)
3012  {
3013  unsigned int curId;
3014  curId = getIonID(ions[ui].getMassToCharge());
3015  if(std::binary_search(sortedIDs.begin(),sortedIDs.end(),curId))
3016  {
3017  #pragma omp critical
3018  newIons.push_back(ions[ui]);
3019  }
3020  }
3021 }
3022 
3023 
3024 bool RangeFile::isRanged(string shortName, bool caseSensitive)
3025 {
3026  if(caseSensitive)
3027  {
3028  for(unsigned int ui=ionNames.size(); ui--; )
3029  {
3030  if(ionNames[ui].first == shortName)
3031  return true;
3032  }
3033  }
3034  else
3035  {
3036  for(unsigned int ui=ionNames.size(); ui--; )
3037  {
3038  //perform a series of case independent
3039  //string comparisons
3040  string str;
3041  str = ionNames[ui].first;
3042 
3043  if(str.size() !=shortName.size())
3044  continue;
3045 
3046  bool next;
3047  next=false;
3048  for(unsigned int uj=str.size(); uj--; )
3049  {
3050  if(tolower(str[uj]) !=
3051  tolower(shortName[uj]))
3052  {
3053  next=true;
3054  break;
3055  }
3056  }
3057 
3058  if(!next)
3059  return true;
3060  }
3061  }
3062 
3063  return false;
3064 }
3065 
3066 void RangeFile::setColour(unsigned int id, const RGBf &r)
3067 {
3068  ASSERT(id < colours.size());
3069  colours[id] = r;
3070 }
3071 
3072 
3073 void RangeFile::setIonShortName(unsigned int id, const std::string &newName)
3074 {
3075  ionNames[id].first=newName;
3076 }
3077 
3078 void RangeFile::setIonLongName(unsigned int id, const std::string &newName)
3079 {
3080  ionNames[id].second = newName;
3081 }
3082 
3083 bool RangeFile::setRangeStart(unsigned int rangeId, float v)
3084 {
3085  ASSERT(!enforceConsistency || isSelfConsistent());
3086 
3087  float &f=ranges[rangeId].first;
3088  float tmp;
3089  tmp=f;
3090  f=v;
3091 
3092  //TODO: I think this does excess calculatioN?
3093  if(enforceConsistency && !isSelfConsistent())
3094  {
3095  //restore original value
3096  f=tmp;
3097  return false;
3098  }
3099 
3100  return true;
3101 }
3102 
3103 bool RangeFile::setRangeEnd(unsigned int rangeId, float v)
3104 {
3105 
3106  ASSERT(!enforceConsistency || isSelfConsistent());
3107 
3108  float &f=ranges[rangeId].second;
3109  float tmp;
3110  tmp=f;
3111  f=v;
3112 
3113  //TODO: I think this does excess calculatioN?
3114  if(enforceConsistency && !isSelfConsistent())
3115  {
3116  //restore original value
3117  f=tmp;
3118  return false;
3119  }
3120 
3121  return true;
3122 }
3124 {
3125  using std::swap;
3126  swap(ionNames,r.ionNames);
3127  swap(ionFormulas,r.ionFormulas);
3128  swap(colours,r.colours);
3129  swap(ranges,r.ranges);
3130  swap(ionIDs,r.ionIDs);
3131  swap(warnMessages,r.warnMessages);
3132  swap(errState,r.errState);
3133 
3134 }
3135 
3136 bool RangeFile::moveRange(unsigned int rangeId, bool upperLimit, float newMass)
3137 {
3138  if(enforceConsistency)
3139  {
3140  //Check for moving past other part of range -- "inversion"
3141  if(upperLimit)
3142  {
3143  //Move upper range
3144  if(newMass <= ranges[rangeId].first)
3145  return false;
3146  }
3147  else
3148  {
3149  if(newMass >= ranges[rangeId].second)
3150  return false;
3151  }
3152 
3153  //Check that moving this range will not cause any overlaps with
3154  //other ranges
3155  for(unsigned int ui=0; ui<ranges.size(); ui++)
3156  {
3157  if( ui == rangeId)
3158  continue;
3159 
3160  if(upperLimit)
3161  {
3162  //moving high range
3163  //check for overlap on first
3164  if((ranges[rangeId].first < ranges[ui].first &&
3165  newMass > ranges[ui].first))
3166  return false;
3167 
3168  if((ranges[rangeId].first < ranges[ui].second &&
3169  newMass > ranges[ui].second))
3170  return false;
3171  }
3172  else
3173  {
3174  //moving low range
3175  //check for overlap on first
3176  if((ranges[rangeId].second > ranges[ui].first &&
3177  newMass < ranges[ui].first))
3178  return false;
3179 
3180  if((ranges[rangeId].second > ranges[ui].second &&
3181  newMass < ranges[ui].second))
3182  return false;
3183  }
3184 
3185  }
3186 
3187  }
3188 
3189  if(upperLimit)
3190  ranges[rangeId].second = newMass;
3191  else
3192  ranges[rangeId].first= newMass;
3193 
3194  return true;
3195 }
3196 
3197 bool RangeFile::moveBothRanges(unsigned int rangeId, float newLow, float newHigh)
3198 {
3199 
3200  //Check that moving this range will not cause any overlaps with
3201  //other ranges
3202  for(unsigned int ui=0; ui<ranges.size(); ui++)
3203  {
3204  if( ui == rangeId)
3205  continue;
3206 
3207  //moving high range
3208  //check for overlap on first
3209  if((ranges[rangeId].first < ranges[ui].first &&
3210  newHigh > ranges[ui].first))
3211  return false;
3212 
3213  if((ranges[rangeId].first < ranges[ui].second &&
3214  newHigh > ranges[ui].second))
3215  return false;
3216  //moving low range
3217  //check for overlap on first
3218  if((ranges[rangeId].second > ranges[ui].first &&
3219  newLow < ranges[ui].first))
3220  return false;
3221 
3222  if((ranges[rangeId].second > ranges[ui].second &&
3223  newLow < ranges[ui].second))
3224  return false;
3225  }
3226 
3227  ranges[rangeId].second = newHigh;
3228  ranges[rangeId].first= newLow;
3229 
3230  return true;
3231 }
3232 
3233 
3234 
3235 unsigned int RangeFile::addRange(float start, float end, unsigned int parentIonID)
3236 {
3237  ASSERT(start < end);
3238  if(enforceConsistency)
3239  {
3240  //Ensure that they do NOT overlap
3241  for(auto & range : ranges)
3242  {
3243  //Check for start end inside other range
3244  if(start > range.first &&
3245  start<=range.second)
3246  return -1;
3247 
3248  if(end > range.first &&
3249  end<=range.second)
3250  return -1;
3251 
3252  //check for start/end spanning range entirely
3253  if(start < range.first && end > range.second)
3254  return -1;
3255  }
3256  }
3257  //Got this far? Good - valid range. Insert it and move on
3258  ionIDs.push_back(parentIonID);
3259  ranges.emplace_back(start,end);
3260 
3261  if(rangeVolumes.size())
3262  rangeVolumes.push_back(0);
3263 
3264 
3265 #ifdef DEBUG
3266  if(enforceConsistency)
3267  {
3269  }
3270 #endif
3271  return ranges.size() -1;
3272 }
3273 
3274 unsigned int RangeFile::addIon(const std::string &shortN, const std::string &longN, const RGBf &newCol)
3275 {
3276  for(auto & ionName : ionNames)
3277  {
3278  if(ionName.first == shortN || ionName.second == longN)
3279  return -1;
3280  }
3281 
3282  ionNames.emplace_back(shortN,longN);
3283  colours.push_back(newCol);
3284 
3285  if(ionFormulas.size())
3286  {
3287  map<string,size_t> m;
3288  ionFormulas.push_back(m);
3289  }
3291  return ionNames.size()-1;
3292 }
3293 
3294 
3295 void RangeFile::setIonID(unsigned int range, unsigned int newIonId)
3296 {
3297  ASSERT(newIonId < ionIDs.size());
3298  ionIDs[range] = newIonId;
3299 }
3300 
3301 void RangeFile::eraseRange(unsigned int rangeId)
3302 {
3303  ASSERT(rangeId < ranges.size());
3304  std::swap(ranges.back(),ranges[rangeId]);
3305  ranges.pop_back();
3306 
3307  std::swap(ionIDs.back(),ionIDs[rangeId]);
3308  ionIDs.pop_back();
3309 
3310  if(rangeVolumes.size())
3311  {
3312  std::swap(rangeVolumes.back(),rangeVolumes[rangeId]);
3313  rangeVolumes.pop_back();
3314  }
3315 }
3316 
3317 void RangeFile::eraseIon(unsigned int ionId)
3318 {
3319 
3320  //Kill any ranges that belong to this ion
3321  vector<bool> killRange(ranges.size(),false);
3322  for(auto ui=0u;ui<ionIDs.size(); ui++)
3323  {
3324  if(ionIDs[ui] == ionId)
3325  killRange[ui]=true;
3326  }
3327 
3328  //Remove the desired range and ionID mappings.
3329  vectorMultiErase(ranges,killRange);
3330  if(rangeVolumes.size())
3331  vectorMultiErase(ranges,killRange);
3332  vectorMultiErase(ionIDs,killRange);
3333 
3334  //Remove the ion name, colour and formula for the selected ion
3335  ionNames.erase(ionNames.begin()+ionId);
3336  colours.erase(colours.begin()+ionId);
3337  if(ionFormulas.size())
3338  ionFormulas.erase(ionFormulas.begin()+ionId);
3339 
3340  //Now, we have to renumber the existing ionIDs, which have shifted down a peg
3341  for(auto ui=0u;ui<ionIDs.size();ui++)
3342  {
3343  //We should have deleted this ionsID
3344  ASSERT(ionIDs[ui] != ionId);
3345  //Shift any ionIDs that are higher down one peg
3346  if(ionIDs[ui] >ionId)
3347  ionIDs[ui]--;
3348 #ifdef DEBUG
3349  ASSERT(ionIDs[ui] < ionNames.size());
3350 #endif
3351  }
3352 
3353 }
3354 
3355 std::string RangeFile::getErrString() const
3356 {
3357  const char *rangeErrStrings[] =
3358  {
3359  "",
3360  "Error interpreting file, check format, name or permissions.",
3361  "Error interpreting range file header, expecting ion count and range count, respectively.",
3362  "Range file appears to be empty, check file is a proper range file and is not empty.",
3363  "Error reading the long name for ion.",
3364  "Error reading the short name for ion.",
3365  "Error reading colour data in the file, expecting 3 decimal values, space separated.",
3366  "Tried skipping to table separator line (line with dashes), but did not find it.", //TABLESEPARATOR
3367  "Unexpected failure whilst trying to skip over range lead-in data (bit before range start value)",
3368  "Unable to read range start and end values",
3369  "Unable to read range table entry",
3370  "Error reading file, unexpected format, are you sure it is a proper range file?",
3371  "Too many ranges appeared to have range entries with no usable data (eg, all blank)",
3372  "Range file appears to contain malformed data, check things like start and ends of m/c are not equal or flipped.",
3373  "Range file appears to be inconsistent (eg, overlapping ranges)",
3374  "No ion name mapped", //NOMAPPED_IONNAME
3375  "Opening of range file failed",
3376  "Unable to read range table",
3377  "Number of ions in the table header did not match the number specified at the start of the file",
3378  "Polyatomic extension range matches multiple masses in first section",
3379  "Range file is exceedingly large. Refusing to open",
3380  "Unable to read range table header (aka table separator)",
3381  "Unable to read [Ions] contents, should be of form A=B",
3382  "Unable to parse number of ions",
3383  "Number of ions seems to be duplicated",
3384  "Too many ions in ion block",
3385  "Found ranges block before ion block",
3386  "Number of ranges seems to be duplicated",
3387  "Unable to read number of ranges",
3388  "Range line was too short, needs 4 entries per line, minimum",
3389  "Range line missing required \":\" separator",
3390  "Range colour incorrect size, should be 6 bytes",
3391  "Range specified as named ion, but ion not found in [Ions] block",
3392  "Range had unreadable start value",
3393  "Range had unreadable endvalue",
3394  "Range name was empty",
3395  "Range block had unparsable line, should start with number or range",
3396  "Ion block missing",
3397  "No ranges found",
3398  "Unable to identify the number of basic ions",
3399  "Number of ranges specfied in range block, did not match number of ranges read",
3400  "Range block contained line that did not match A=xxx format",
3401  "Range had unreadable ion multiplicity",
3402  "Unable to read volume from range",
3403  "Range block had range with empty range row",
3404  "Range file is too large - refusing to process"
3405  };
3406 
3407  static_assert(ARRAYSIZE(rangeErrStrings) == RANGE_ERR_ENUM_END, "Range error strings and error enum sizes mismatched");
3408 
3409  return string(rangeErrStrings[errState]);
3410 }
3411 
3412 void RangeFile::setRangeVolume(unsigned int rangeId, float newVol)
3413 {
3414  ASSERT(rangeId < ranges.size());
3415  if(rangeVolumes.empty())
3416  rangeVolumes.resize(ranges.size(),0);
3417 
3418  ASSERT(rangeVolumes.size() == ranges.size());
3419 
3420  rangeVolumes[rangeId] = newVol;
3421 }
3422 
3423 float RangeFile::getRangeVolume(unsigned int rangeId) const
3424 {
3425  ASSERT(rangeId < ranges.size());
3426 
3427  if(rangeVolumes.empty())
3428  return 0;
3429  else
3430  return rangeVolumes[rangeId];
3431 }
3432 
3434 {
3435  return rangeVolumes.size();
3436 }
3437 
3438 void RangeFile::guessFormulas(bool overwrite)
3439 {
3440  if(ionFormulas.empty() || overwrite)
3441  {
3442  ionFormulas.clear();
3443  ionFormulas.resize(ionNames.size());
3444  for(auto ui=0;ui<ionNames.size();ui++)
3445  {
3446  decomposeIonNameToFormula(ionNames[ui].first,
3447  ionFormulas[ui]);
3448  }
3449  }
3450  else
3451  {
3452  ionFormulas.resize(ionNames.size());
3453  for(auto ui=0;ui<ionNames.size();ui++)
3454  {
3455  if(ionFormulas[ui].empty())
3456  decomposeIonNameToFormula(ionNames[ui].first,
3457  ionFormulas[ui]);
3458  }
3459  }
3460 }
3461 
3462 bool RangeFile::guessChargeState(unsigned int rangeId,
3463  const AtomProbe::AbundanceData &massTable,
3464  unsigned int &charge, float tolerance) const
3465 {
3466  std::string ionString;
3467  ionString = getName(getIonID(rangeId));
3468 
3469  //Decompose the ion into sub-fragments
3470  vector<pair<string,size_t> > fragments;
3471  if(!decomposeIonNames(ionString,fragments))
3472  return false;
3473 
3474  //Break the fragments/frequency into parts
3475  //--
3476  vector<size_t> elementIdx,frequency;
3477  for(unsigned int ui=0;ui<fragments.size(); ui++)
3478  {
3479  size_t thisIdx;
3480  thisIdx= massTable.symbolIndex(fragments[ui].first.c_str());
3481  if(thisIdx == (size_t) -1)
3482  return false;
3483  elementIdx.push_back(thisIdx);
3484  frequency.push_back(fragments[ui].second);
3485  }
3486  //--
3487 
3488  //Create the isotope distribution for this set of fragments
3489  vector<pair<float,float> > massDist;
3490  massTable.generateIsotopeDist(elementIdx, frequency, massDist);
3491 
3492  pair<float,float> rangeValues;
3493  rangeValues = getRange(rangeId);
3494  float rangeMid = (rangeValues.first + rangeValues.second)/2.0f;
3495 
3496  //first entry is mass error for isotope
3497  // second is the charge state
3498  vector<pair<float,unsigned int> > massErrors;
3499  for(unsigned int ui=0;ui<massDist.size();ui++)
3500  {
3501  float f;
3502  f= massDist[ui].first/rangeMid;
3503 
3504  int nearInt;
3505  nearInt = round(f);
3506  massErrors.push_back(make_pair(fabs(massDist[ui].first-nearInt*rangeMid),nearInt));
3507  }
3508 
3509  //pick the smallest mass error;
3510  float minErr=massErrors[0].first;
3511  float minIdx=0;
3512  for(unsigned int ui=1;ui<massErrors.size(); ui++)
3513  {
3514  if(massErrors[ui].first < minErr)
3515  {
3516  minErr = massErrors[ui].first;
3517  minIdx=ui;
3518  }
3519  }
3520 
3521  //check to see if the error lies within our tolerance
3522  if(massErrors[minIdx].first > tolerance)
3523  return false;
3524 
3525  //return the charge state
3526  charge=massErrors[minIdx].second;
3527  return true;
3528 }
3529 
3530 
3531 #ifdef DEBUG
3532 
3533 bool RangeFile::test()
3534 {
3535  AbundanceData natData;
3536  if(natData.open("naturalAbundance.xml"))
3537  return false;
3538 
3539  RangeFile rng;
3540  RGBf c;
3541  c.red=c.green=c.blue=0.5f;
3542  auto idTi = rng.addIon("Ti","Ti",c);
3543  auto idRngTi=rng.addRange(23.5,24.5,idTi);
3544  rng.addRange(24.6,25.6,idTi);
3545 
3546  TEST_Q(rng.getIonID(idRngTi) == idTi);
3547  TEST_Q(rng.getIonID("Ti") == idTi);
3548  TEST_Q(rng.getRangeID(24.0) == idRngTi);
3549  TEST_Q(rng.getRangeID(225.0) == (unsigned int)-1);
3550 
3551  //Copy for later
3552  RangeFile rngDup=rng;
3553 
3554  unsigned int charge;
3555  TEST(rng.guessChargeState(idRngTi,natData,charge),"Charge state guessing");
3556  TEST(charge == 2,"Charge state guessing (2)");
3557 
3558 
3559  bool enforceConsistencyOn = enforceConsistency;
3560  enforceConsistency=true;
3561  auto idC2 = rng.addIon("C2","C2",c);
3562  TEST(rng.addRange(23.7,24.6,idC2) == (unsigned int) -1,"Add bad range (strict on), should be disallowed");
3563  enforceConsistency=false;
3564  TEST(rng.addRange(23.7,24.6,idC2) != (unsigned int) -1,"Add bad range (non-strict), should be allowed");
3565 
3566 
3567  TEST(!rng.isSelfConsistent(),"rng inconsistency");
3568 
3569  //enforce consistency. Will arbitrarily kill a range.
3570  rng.makeSelfConsistent();
3571 
3572  TEST(rng.isSelfConsistent(),"Enforce consistency");
3573 
3574  //Restore strict mode status
3575  enforceConsistency=enforceConsistencyOn;
3576 
3577  TEST(rng.getNumRanges() == 2, "range count after consistency");
3578 
3579  TEST(rng.isSelfConsistent(),"Check consistency");
3580 
3581  // test ion forumlae
3582  auto idTiO2 = rng.addIon("TiO2O","TitaniumTrioxide", c);
3583  rng.guessFormulas();
3584  map<string, size_t> formula = rng.getIonFormula(idTiO2);
3585  TEST(formula.at("Ti")==1 && formula.at("O")==3, "Formula not correct");
3586 
3587 #ifndef WIN32
3588  //write to null device
3589  std::ofstream nullStr("/dev/null");
3590 
3591  if(!nullStr)
3592  return false;
3593 
3594  for(auto ui=0;ui<RANGE_FORMAT_END_OF_ENUM; ui++)
3595  {
3596  //DBL_ORNL is not a supported output format
3597  if(ui == RANGE_FORMAT_DBL_ORNL)
3598  continue;
3599 
3600  TEST(rng.write(nullStr,ui) == 0, "null write test");
3601  }
3602 #endif
3603 
3604 
3605  vector<IonHit> ions;
3606  const unsigned int NIONS=100;
3607  for(auto ui=0;ui<NIONS;ui++)
3608  ions.push_back(IonHit(Point3D(ui,ui,ui),ui));
3609 
3610  rngDup.range(ions,string("Ti"));
3611 
3612  TEST_Q(ions.size() < NIONS);
3613  return true;
3614 }
3615 
3616 #endif
3617 }
void range(std::vector< IonHit > &ionHits) const
Clips out ions that are not inside the rangefile&#39;s ranges.
Definition: ranges.cpp:2783
size_t symbolIndex(const char *symbol, bool caseSensitive=true) const
Return the element&#39;s position in table, starting from 0.
Definition: abundance.cpp:214
std::string getName(unsigned int ionID, bool shortName=true) const
Get the short name or long name of a specified ionID.
Definition: ranges.cpp:2975
static void decomposeIonNameToFormula(const std::string &name, std::map< std::string, size_t > &formula)
Break a given string down into a chemical formula with a count of each element.
Definition: ranges.cpp:261
RGBf getColour(unsigned int) const
Retrieve a given colour from the ion ID.
Definition: ranges.cpp:2878
unsigned int getIonID(float mass) const
Get the ion&#39;s ID from a specified mass.
Definition: ranges.cpp:2884
unsigned int addIon(const std::string &shortName, const std::string &longName, const RGBf &ionCol)
Add the ion to the database returns ion ID if successful, -1 otherwise.
Definition: ranges.cpp:3274
void eraseRange(unsigned int rangeId)
Erase given range - this will reorder rangeIDs, so any ids you had previously will not longer be vali...
Definition: ranges.cpp:3301
Data holder for colour as float.
Definition: misc.h:26
float getRangeVolume(unsigned int rangeId) const
Obtain an ions volume, if we have it. Zero may indicate no ion volume specfied for that range...
Definition: ranges.cpp:3423
bool makeSelfConsistent()
Modify range file to ensure self-consistency, by arbitrary heuristics.
Definition: ranges.cpp:2535
float green
Definition: misc.h:30
std::string stripWhite(const std::string &str)
Strip whitespace, (eg tab,space) from either side of a string.
bool moveBothRanges(unsigned int range, float newLow, float newHigh)
Move both of a range&#39;s masses to a new location.
Definition: ranges.cpp:3197
bool setRangeEnd(unsigned int rangeID, float v)
Set the upper bound of the given range.
Definition: ranges.cpp:3103
void generateIsotopeDist(const std::vector< size_t > &elementIdx, const std::vector< size_t > &frequency, std::vector< std::pair< float, float > > &massDist) const
Compute the mass-probability distribution for the grouped set of ions.
Definition: abundance.cpp:277
static bool decomposeIonNames(const std::string &name, std::vector< std::pair< std::string, size_t > > &fragments)
Definition: ranges.cpp:121
bool isNotDirectory(const char *filename)
Definition: helpFuncs.cpp:170
void vectorMultiErase(std::vector< T > &vec, const std::vector< bool > &wantKill)
Remove elements from the vector, without preserving order.
Definition: misc.h:112
void nullifyMarker(char *buffer, char marker)
Definition: stringFuncs.cpp:55
static void getAllExts(std::vector< std::string > &exts)
Grab a vector that contains all the extensions that are valid for range files.
Definition: ranges.cpp:2437
const size_t MAX_LINE_SIZE
Definition: ranges.cpp:51
static std::string rangeTypeString(unsigned int rangeType)
Return the human readable name for the given RANGE_FORMAT value.
Definition: ranges.cpp:1340
#define TEST(f, g)
Definition: aptAssert.h:49
bool guessChargeState(unsigned int rangeId, const AtomProbe::AbundanceData &massTable, unsigned int &charge, float tolerance=0.5f) const
Guess the charge state for the ion that corresponds to the given range&#39;s midpoint.
Definition: ranges.cpp:3462
static bool extensionIsRange(const char *ext)
is the extension string the same as that for a range file? I don&#39;t advocate this method, but it is convenient in a pinch.
Definition: ranges.cpp:2419
bool isValidElementSymbol(std::string &symbol)
Definition: ranges.cpp:86
void setIonFormula(const std::string &name, const std::map< std::string, size_t > &formula, bool useShortName=true)
Set ion formula using ion short or long name (if it is already set)
Definition: ranges.cpp:2957
bool isSelfConsistent() const
Performs checks for self consistency.
Definition: ranges.cpp:2450
unsigned int getNumRanges() const
Get the number of unique ranges.
Definition: ranges.cpp:2846
float blue
Definition: misc.h:31
void pushLocale(const char *newLocale, int type)
Definition: helpFuncs.cpp:62
void extractIons(const std::vector< IonHit > &ionHits, const std::vector< unsigned int > &ionIDs, std::vector< IonHit > &hits) const
Extract ion hit events in the specified ion ID values, from the input ion ionhit sequence.
Definition: ranges.cpp:3001
void setColour(unsigned int, const RGBf &r)
Set the colour using the ion ID.
Definition: ranges.cpp:3066
void splitStrsRef(const char *cpStr, const char delim, std::vector< std::string > &v)
Split string references using a single delimiter.
A 3D point data class storage.
Definition: point3D.h:39
const char * elementList[]
Definition: ranges.cpp:63
void rangeByRangeID(std::vector< IonHit > &ionHits, unsigned int rangeID)
Range the given ions, allowing only ions in the specified range. Input data will be modified to retur...
Definition: ranges.cpp:2822
bool setRangeStart(unsigned int rangeID, float v)
Set the lower bound of the given range.
Definition: ranges.cpp:3083
bool moveRange(unsigned int range, bool limit, float newMass)
Move a range&#39;s mass to a new location.
Definition: ranges.cpp:3136
bool matchComposedName(const std::map< string, size_t > &composedNames, const vector< pair< string, size_t > > &namesToFind, size_t &matchOffset)
Definition: ranges.cpp:297
string rangeToStr(const pair< float, float > &rng)
Definition: ranges.cpp:111
bool getFilesize(const char *fname, size_t &size)
Definition: helpFuncs.cpp:107
std::string toHex() const
Convert to hex-256 representation #rrggbb.
Definition: misc.h:34
#define ARRAYSIZE(f)
Definition: helpFuncs.h:34
float getMassToCharge() const
Definition: ionhit.cpp:65
void setIonShortName(unsigned int ionID, const std::string &newName)
set the short name for a given ion
Definition: ranges.cpp:3073
const RangeFile & operator=(const RangeFile &other)
Definition: ranges.cpp:418
void guessFormulas(bool overwrite=false)
Use heuristics to guess missing formula data from the shortname.
Definition: ranges.cpp:3438
bool isRanged(float mass) const
Returns true if a specified mass is ranged.
Definition: ranges.cpp:2713
size_t open(const char *file, bool strict=false)
Attempt to open the abundance data file, return 0 on success, nonzero on failure. ...
Definition: abundance.cpp:79
const char * RANGE_EXTS[]
Definition: ranges.cpp:55
Class to load abundance information for natural isotopes.
Definition: abundance.h:54
void popLocale()
Definition: helpFuncs.cpp:96
std::pair< float, float > & getRangeByRef(unsigned int)
Retrieve the start and end of a given range as a pair(start,end)
Definition: ranges.cpp:2873
std::string lowercase(std::string s)
Return a lowercase version for a given string.
bool decomposeIonById(unsigned int ionId, std::vector< std::pair< std::string, size_t > > &fragments) const
Perform decomposeIonNames(...) for a given ionID.
Definition: ranges.cpp:252
bool open(const char *rangeFile)
Open a specified range file - returns true on success.
Definition: ranges.cpp:678
This is a data holding class for POS file ions, from.
Definition: ionHit.h:36
void setIonLongName(unsigned int ionID, const std::string &newName)
Set the long name for a given ion.
Definition: ranges.cpp:3078
#define ASSERT(f)
void rangeInvertable(std::vector< IonHit > &ionHits, bool invert)
Clips out ions that are inside the rangefile&#39;s ranges, if invert is false. If true, then reverse the set.
Definition: ranges.cpp:2803
bool haveRangeVolumes() const
Return true if we have range volume data, false otherwise.
Definition: ranges.cpp:3433
bool parseColString(const std::string &str, unsigned char &r, unsigned char &g, unsigned char &b, unsigned char &a)
Parse a colour string containing rgb[a]; hex for with leading #.
bool rangeByID(std::vector< IonHit > &ionHits, unsigned int range)
Clips out ions that don&#39;t lie in the specified range number.
Definition: ranges.cpp:2995
Data storage and retrieval class for various range files.
Definition: ranges.h:95
bool stream_cast(T1 &result, const T2 &obj)
Template function to cast and object to another by the stringstream.
Definition: stringFuncs.h:32
static unsigned int detectFileType(const char *file)
Attempt to detect the file format of an unknown rangefile.
Definition: ranges.cpp:1116
int fpeek(FILE *stream)
Definition: helpFuncs.h:69
const size_t MAX_RANGEFILE_SIZE
Definition: ranges.cpp:53
unsigned int openFormat(const char *rangeFile, unsigned int format)
Open a specified range file using a given file format. Returns nonzero on failure.
Definition: ranges.cpp:609
unsigned int getNumIons() const
Get the number of unique ions.
Definition: ranges.cpp:2863
void swap(RangeFile &rng)
Swap a range file with this one.
Definition: ranges.cpp:3123
#define XOR(a, b)
Definition: voxels.h:39
unsigned int write(std::ostream &o, size_t format=RANGE_FORMAT_ORNL) const
Write the rangefile to the specified output stream (default ORNL format)
Definition: ranges.cpp:441
void setIonID(unsigned int range, unsigned int newIonId)
Set the ion ID for a given range.
Definition: ranges.cpp:3295
std::string getErrString() const
Retrieve the translated error associated with the current range file state.
Definition: ranges.cpp:3355
float red
Definition: misc.h:29
void stripZeroEntries(std::vector< std::string > &s)
std::map< std::string, size_t > getIonFormula(unsigned int ionID) const
Get ion formula by ion ID.
Definition: ranges.cpp:2950
unsigned int getRangeID(float mass) const
Get a range ID from mass to charge.
Definition: ranges.cpp:2904
void strAppend(std::string s, const T &a)
Definition: stringFuncs.h:41
unsigned int addRange(float start, float end, unsigned int ionID)
Add a range to the rangefile. Returns ID number of added range.
Definition: ranges.cpp:3235
void setRangeVolume(unsigned int rangeId, float newVolume)
Set the ion volume for a given range. If ion volumes are not tracked, these will be created...
Definition: ranges.cpp:3412
std::string stripChars(const std::string &Str, const char *chars)
std::pair< float, float > getRange(unsigned int) const
Retrieve the start and end of a given range as a pair(start,end)
Definition: ranges.cpp:2868
void eraseIon(unsigned int ionId)
erase given ions and associated ranges)
Definition: ranges.cpp:3317