libatomprobe
Library for Atom Probe Tomography (APT) computation
multiRange.cpp
Go to the documentation of this file.
1 /*
2  * multiRanges.cpp - Atom probe rangefile class, with multiple identities
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 //Public headers
24 
25 //Local headers
27 #include "helper/helpFuncs.h"
28 
29 
30 #include <algorithm>
31 #include <fstream>
32 #include <iomanip>
33 #include <ctime>
34 #include <memory>
35 #include <stack>
36 #include <map>
37 
38 
39 namespace AtomProbe
40 {
41 
42 using std::pair;
43 using std::map;
44 using std::string;
45 
46 const char MULTIRANGE_FORMAT_VERSION[] = "0.0.1";
47 
48 enum
49 {
71 };
72 
73 
74 using std::set;
75 using std::vector;
76 
77 
78 //Given a set of identifiers with a specific position
79 // where the value points to another position
80 // |0 1 2 3 4|
81 // {0,1,2,1,3}, link all identifiers such that
82 // they have the same base value
83 // eg the above would become {0,0,1,0,0}
84 // as 0->0, 1->0,2->2, 1->0, 3->1->0
85 // to give {0,0,2,0,0}
86 // and we renumber them so they fill a contigous set of numbers
87 template<class T>
88 void linkIdentifiers(vector<T> &link)
89 {
90  //Chain the identifiers to make them unique
91  for(auto ui=0; ui<link.size(); ui++)
92  {
93  if(link[ui] < ui)
94  link[ui] = link[link[ui]];
95  }
96 
97  //Make them contiguous
98  map<size_t,size_t> uniqMap;
99  for(auto &v : link)
100  {
101  if(uniqMap.find(v) != uniqMap.end())
102  v=uniqMap[v];
103  else
104  {
105  size_t mapSize=uniqMap.size();
106  uniqMap[v]=mapSize;
107  v=mapSize;
108  }
109  }
110 }
111 
113 {
114  return (atomicNumber == oth.atomicNumber && count == oth.count) ;
115 }
116 
117 bool operator<(const SIMPLE_SPECIES &a, const SIMPLE_SPECIES &b)
118 {
119  return std::tie(a.atomicNumber,a.count) < std::tie(b.atomicNumber,b.count);
120 }
121 
122 bool pairOverlaps(float aStart, float aEnd, float bStart,float bEnd)
123 {
124  ASSERT(aStart < aEnd);
125  ASSERT(bStart < bEnd);
126 
127  if(aStart >=bStart && aStart<= bEnd)
128  return true;
129 
130  if(aEnd >=bStart && aEnd<= bEnd)
131  return true;
132 
133  return false;
134 }
135 
137 {
138 }
139 
140 
141 MultiRange::MultiRange(const RangeFile &r, const AbundanceData &aTable) :errState(0)
142 {
143  set<size_t> badIons;
144 
145  for(auto i=0; i <r.getNumIons(); i++)
146  {
147  //try to decompose the ion name into usable fragements
148  vector<pair<string,size_t> > fragments;
149  if(!RangeFile::decomposeIonNames(r.getName(i),fragments))
150  {
151  badIons.insert(i);
152  continue;
153  }
154 
155  //Obtain the symbol index for each fragment
156  vector<pair<size_t,unsigned int> > symbolIndices;
157  aTable.getSymbolIndices(fragments,symbolIndices);
158 
159  bool bad;
160  bad=false;
161 
162  //work out what this molecule is
163  set<SIMPLE_SPECIES> molecule;
164  molecule.clear();
165  for(const auto &idxPair: symbolIndices)
166  {
167  if(idxPair.first == -1)
168  {
169  bad=true;
170  break;
171  }
172 
173  SIMPLE_SPECIES species;
174 
175  species.atomicNumber= aTable.getAtomicNumber(idxPair.first);
176  species.count=idxPair.second;
177 
178  molecule.insert(species);
179 
180  }
181 
182  if(bad)
183  {
184  badIons.insert(i);
185  continue;
186  }
187 
188  moleculeData.push_back(molecule);
189  colours.push_back(r.getColour(i));
190  ionNames.push_back(r.getName(i));
191  }
192 
193  //Find each range, check if we were able to convert the ranges to a multirange
194  for(unsigned int i=0; i<r.getNumRanges(); i++)
195  {
196  //Ignore any ranges that belonged to ions we couldnt identify
197  unsigned int ionID;
198  ionID = r.getIonID(i);
199  if(badIons.find(ionID) != badIons.end())
200  continue;
201  ranges.push_back(r.getRange(i));
202  ionIDs.push_back(r.getIonID(i));
203  }
204 
206 }
207 
208 bool MultiRange::operator==(const MultiRange &other) const
209 {
210  if(ionNames != other.ionNames)
211  return false;
212  if(moleculeData != other.moleculeData)
213  return false;
214  if(colours != other.colours)
215  return false;
216 
217  if(ranges != other.ranges)
218  return false;
219 
220  if(rangeGrouping != other.rangeGrouping)
221  return false;
222 
223  if(ionIDs !=other.ionIDs)
224  return false;
225 
226  return true;
227 }
228 
229 
231 {
232  unsigned int ionSize=moleculeData.size();
233 
234 
235  if(ionSize !=colours.size())
236  return false;
237 
238  unsigned int rangeSize=ranges.size();
239 
240  if(rangeSize != ionIDs.size())
241  return false;
242 
243 
244  for(auto &i :moleculeData)
245  {
246  if(i.empty())
247  return false;
248  }
249 
250  for(auto &name : ionNames )
251  {
252  if(name.empty())
253  return false;
254  }
255 
256  for(auto &rng : ranges)
257  {
258  if(rng.first >= rng.second)
259  return false;
260  }
261 
262 
263  //Range groups should either be absent, or identically sized to ranges
264  if((rangeGrouping.size()) && (rangeGrouping.size() != ranges.size()))
265  return false;
266 
267  return true;
268 }
269 
270 
271 unsigned int MultiRange::addIon(const set<SIMPLE_SPECIES> &ions,
272  const std::string &name, const RGBf &ionCol)
273 {
275 
276  //Molecule data must be unique
277  for(auto i:moleculeData)
278  {
279  if( i == ions)
280  return -1;
281  }
282 
283  moleculeData.push_back(ions);
284  colours.push_back(ionCol);
285  ionNames.push_back(name);
286 
287 
289 
290  return moleculeData.size()-1;
291 }
292 
293 unsigned int MultiRange::addIon(const SIMPLE_SPECIES &ion,
294  const std::string &name, const RGBf &ionCol)
295 {
297 
298  set<SIMPLE_SPECIES> sSet;
299  sSet.insert(ion);
300  return addIon(sSet,name,ionCol);
301 }
302 
303 unsigned int MultiRange::addRange(float start, float end, unsigned int ionID)
304 {
305  ASSERT(ionID < moleculeData.size());
307  if(end <= start)
308  return -1;
309 
310  ionIDs.push_back(ionID);
311  ranges.push_back(std::make_pair(start,end));
312 
313  return ionIDs.size()-1;
314 }
315 
316 unsigned int MultiRange::addRange(const pair<float, float > &rng, unsigned int ionID)
317 {
318  return addRange(rng.first,rng.second,ionID);
319 }
320 
321 void MultiRange::setRangeGroups(const std::vector<unsigned int> &groups)
322 {
323  ASSERT(groups.empty() || groups.size() == ranges.size());
324  rangeGrouping=groups;
325 }
326 
327 bool MultiRange::write(const char *fileName, unsigned int format) const
328 {
329  std::ofstream f(fileName);
330  if(!f)
331  return false;
332 
334  using std::endl;
335  switch(format)
336  {
338  {
339 
340  f << "<multirange version=\"" << MULTIRANGE_FORMAT_VERSION <<"\">" << endl;
341  std::time_t t = std::time(nullptr);
342 
343  string timeStr;
344 //Workaround for missing std::put_time (GCC bug 54354). Affects some ubuntu 18.04 installs
345 #if __GNU_C__ > 4
346  timeStr=std::put_time(std::gmtime(&t), "%c %Z");
347 #else
348  timeStr="";
349 #endif
350  f << tabs(1) << "<created date=\"" << timeStr<<
351  "\" writer=\"libatomprobe " << LibVersion::getVersionStr() << "\"/>" << endl;
352  f << endl;
353 
354  f << tabs(1) << "<molecules>" << endl;
355 
356  for(auto ui=0; ui<moleculeData.size(); ui++)
357  {
358  f << tabs(2) << "<entry>" << endl;
359  f << tabs(3) << "<name string=\"" << ionNames[ui] << "\"/>" << endl;
360  f << tabs(3) << "<colour hex=\"" << colours[ui].toHex() << "\"/>" << endl;
361 
362  f << tabs(3) << "<components>" << endl;
363  for( auto mol : moleculeData[ui])
364  {
365  f << tabs(4) << "<isotope atomicnumber=\"" << mol.atomicNumber <<
366  "\" count=\"" << mol.count << "\"/>" << endl;
367 
368  }
369  f << tabs(3) << "</components>" << endl;
370  f << tabs(2) << "</entry>" << endl;
371 
372  }
373 
374  f << tabs(1) << "</molecules>" << endl;
375  f << tabs(1) << "<ranges>" << endl;
376  for(auto ui=0;ui<ranges.size(); ui++)
377  {
378  f << tabs(2) << "<range start=\"" << ranges[ui].first << "\" end=\"" << ranges[ui].second << "\" parent=\"" << ionNames[ionIDs[ui]];
379 
380  if(rangeGrouping.size())
381  f << " group=\"" << rangeGrouping[ui] << "\" ";
382 
383  f << "\"/>" << endl;
384  }
385  f << tabs(1) << "</ranges>" << endl;
386  f << "</multirange>" <<endl;
387 
388  break;
389  }
390  default:
391  return false;
392  }
393 
394  return true;
395 }
396 
397 
398 bool MultiRange::open(const char *filename, unsigned int format)
399 {
400  using std::stack;
401 
402  //Check the file opens OK without bothering with an XML
403  // parse - this is a bit of a hack
404  std::ifstream f(filename);
405  if(!f)
406  return false;
407  clear();
408 
409  f.close();
410 
411 
412 
413 
414  //parse XML document
415  xmlDocPtr doc;
416  xmlParserCtxtPtr context;
417  context = xmlNewParserCtxt();
418 
419 
420  if(!context)
421  {
422  errState=MULTIRANGE_ERR_NO_PARSER;
423  return false;
424  }
425 
426  doc = xmlCtxtReadFile(context, filename, NULL,
427  XML_PARSE_NOENT| XML_PARSE_NONET);
428 
429  if(!doc)
430  {
431  errState=MULTIRANGE_ERR_NO_XML_DOC;
432  return false;
433  }
434 
435  //Automatic XML free device.
436  // - will automatically call free function on exit
437  std::unique_ptr<_xmlDoc,void (*)(void *)> cleanup{doc,XMLFreeDoc};
438 
439  if(!context->valid)
440  {
441  xmlFreeParserCtxt(context);
443  return false;
444  }
445  xmlFreeParserCtxt(context);
446 
447  //retrieve root node
448  xmlNodePtr nodePtr = xmlDocGetRootElement(doc);
449  if(!nodePtr)
450  {
451  errState=MULTIRANGE_ERR_NO_NODEPTR;
452  return false;
453  }
454 
455  //check value of root node
456  if(xmlStrcmp(nodePtr->name, (const xmlChar *)"multirange"))
457  {
459  return false;
460  }
461  //FIXME: Do something with the version data.
462 
463  if(!nodePtr->xmlChildrenNode)
464  {
465  errState=MULTIRANGE_ERR_NO_CONTENT;
466  return false;
467  }
468  nodePtr = nodePtr->xmlChildrenNode;
469 
470  if(XMLHelpFwdToElem(nodePtr,"created"))
471  {
473  return false;
474  }
475 
476  //FIXME: Do someting with the timestamp data
477 
478  //Read the molecule section (the species in the rangefile)
479  //----
480  if(XMLHelpFwdToElem(nodePtr,"molecules"))
481  {
483  return false;
484  }
485 
486  if(!nodePtr->xmlChildrenNode)
487  {
489  return false;
490  }
491 
492  stack<xmlNodePtr> nodeStack;
493  nodeStack.push(nodePtr);
494  nodePtr=nodePtr->xmlChildrenNode;
495 
496  while(nodePtr)
497  {
498  if(XMLHelpFwdToElem(nodePtr,"entry"))
499  {
501  return false;
502  }
503  nodeStack.push(nodePtr);
504  nodePtr=nodePtr->xmlChildrenNode;
505 
506  if(XMLHelpFwdToElem(nodePtr,"name"))
507  {
509  return false;
510  }
511 
512  std::string str;
513  if(XMLHelpGetProp(str,nodePtr,"string"))
514  {
516  return false;
517  }
518  ionNames.push_back(str);
519 
520  if(XMLHelpFwdToElem(nodePtr,"colour"))
521  {
523  return false;
524  }
525 
526  if(XMLHelpGetProp(str,nodePtr,"hex"))
527  {
529  return false;
530  }
531  unsigned char v[4];
532  RGBf col;
533  if(!parseColString(str,v[0],v[1],v[2],v[3]))
534  {
536  return false;
537  }
538  col.red=v[0]/255.0; col.green =v[1]/255.0; col.blue=v[2]/255.0;
539  colours.push_back(col);
540 
541  if(XMLHelpFwdToElem(nodePtr,"components"))
542  {
544  return false;
545  }
546 
547 
548  set<SIMPLE_SPECIES> mol;
549  nodeStack.push(nodePtr);
550  nodePtr = nodePtr->xmlChildrenNode;
551  while(nodePtr)
552  {
553  SIMPLE_SPECIES species;
554  if(XMLHelpFwdToElem(nodePtr,"isotope"))
555  {
557  return false;
558  }
559  if(XMLHelpGetProp(species.atomicNumber,nodePtr,"atomicnumber"))
560  {
562  return false;
563  }
564  if(XMLHelpGetProp(species.count,nodePtr,"count"))
565  {
567  return false;
568  }
569  mol.insert(species);
570 
571  nodePtr=nodePtr->next;
572 
573 
574  while(nodePtr && std::string((char*)nodePtr->name) == "text")
575  nodePtr=nodePtr->next;
576  }
577 
578  moleculeData.push_back(mol);
579  nodeStack.pop();
580 
581  nodePtr=nodeStack.top();
582  nodeStack.pop();
583 
584  nodePtr=nodePtr->next;
585  while(nodePtr && std::string((char*)nodePtr->name) == "text")
586  nodePtr=nodePtr->next;
587  }
588 
589  nodePtr=nodeStack.top();
590  nodeStack.pop();
591  //----
592 
593  //Read the ranges section
594  //----
595  if(XMLHelpFwdToElem(nodePtr,"ranges"))
596  {
597  errState=MULTIRANGE_ERR_NO_RANGES;
598  return false;
599  }
600 
601  if(!nodePtr->xmlChildrenNode)
602  {
603  errState=MULTIRANGE_ERR_NO_RANGES;
604  return false;
605  }
606 
607  nodeStack.push(nodePtr);
608  nodePtr=nodePtr->xmlChildrenNode;
609 
610  while(nodePtr)
611  {
612  if(XMLHelpFwdToElem(nodePtr,"range"))
613  {
614  errState=MULTIRANGE_ERR_BAD_RANGES;
615  return false;
616  }
617  std::pair<float,float> p;
618  if(XMLHelpGetProp(p.first,nodePtr,"start"))
619  {
621  return false;
622  }
623  if(XMLHelpGetProp(p.second,nodePtr,"end"))
624  {
626  return false;
627  }
628 
629  if(p.first > p.second)
630  {
631  errState=MULTIRANGE_ERR_BAD_RANGE;
632  return false;
633  }
634 
635  ranges.push_back(p);
636  //Look up the parent string from the existing list
637  std::string parentStr;
638  if(XMLHelpGetProp(parentStr,nodePtr,"parent"))
639  {
641  return false;
642  }
643 
644  auto parentIt = std::find(ionNames.begin(),ionNames.end(),parentStr);
645  if(parentIt ==ionNames.end())
646  {
648  return false;
649  }
650 
651  unsigned int parentId=parentIt-ionNames.begin();
652  ionIDs.push_back(parentId);
653 
654 
655  //Look up the group identifier, if present
656  std::string groupStr;
657  if(!XMLHelpGetProp(groupStr,nodePtr,"group"))
658  {
659  unsigned int rGroup;
660  if(stream_cast(rGroup,groupStr))
661  {
662  errState=MULTIRANGE_ERR_BAD_GROUP;
663  return false;
664  }
665  rangeGrouping.push_back(rGroup);
666  }
667 
668  //Spin past text nodes
669  nodePtr=nodePtr->next;
670  while(nodePtr && std::string((char*)nodePtr->name) == "text")
671  nodePtr=nodePtr->next;
672  }
673  if(rangeGrouping.size() && (rangeGrouping.size() !=ranges.size()))
674  {
675  errState=MULTIRANGE_ERR_BAD_GROUP;
676  return false;
677  }
678  //----
679 
680 
681  //nodePtr=nodeStack.top();
682  nodeStack.pop();
683 
684  if(!isSelfConsistent())
685  {
687  return false;
688  }
689 
690  return true;
691 }
692 
693 std::string MultiRange::getErrString() const
694 {
695  const char *errMsg[] = { "Unable to create valid parser",
696  "Unable to create XML document structure",
697  "Unable to create XML document context",
698  "Unable to create node pointer",
699  "Unable to find valid root node",
700  "Unable to find content node",
701  "Unable to find timestamp node",
702  "Unable to find molecules node",
703  "Unable to find molecule entry node",
704  "Missing molecule entry",
705  "Missing molecule name",
706  "Missing molecule components",
707  "Bad range found",
708  "Bad ranges found",
709  "Missing molecule colour",
710  "Bad molecule colour",
711  "Bad group identifier",
712  "Data is not self-consistent",
713  "No parent range found"};
714 
715  ASSERT(errState <MULTIRANGE_ERR_ENUM_END);
716 
717  return errMsg[errState];
718 }
719 
720 unsigned int MultiRange::getNumRanges() const
721 {
722  return ranges.size();
723 }
724 
725 
727 set<SIMPLE_SPECIES> MultiRange::getMolecule(unsigned int ionID) const
728 {
729  ASSERT(ionID < moleculeData.size());
730  return moleculeData[ionID];
731 }
732 
733 std::string MultiRange::getIonName(unsigned int ionID) const
734 {
735  ASSERT(ionID < ionNames.size());
736  return ionNames[ionID];
737 }
738 
739 unsigned int MultiRange::getNumRanges(unsigned int ionID) const
740 {
741  ASSERT(ionID < moleculeData.size());
742  unsigned int v=std::count(ionIDs.begin(),ionIDs.end(),ionID);
743 
744  return v;
745 }
746 
747 unsigned int MultiRange::getNumIons() const
748 {
749  return moleculeData.size();
750 }
751 
752 unsigned int MultiRange::getIonID(unsigned int range) const
753 {
754  ASSERT(range < ranges.size());
755 
756  return ionIDs[range];
757 }
758 
759 std::pair<float,float> MultiRange::getRange(unsigned int rng) const
760 {
761  ASSERT(rng<ranges.size());
762  return ranges[rng];
763 }
764 
765 std::vector<unsigned int> MultiRange::getRanges(float mass) const
766 {
767  vector<unsigned int> tmp;
768  for(auto ui=0;ui<ranges.size();ui++)
769  {
770  if(ranges[ui].first <= mass && ranges[ui].second > mass)
771  tmp.push_back(ui);
772  }
773 
774  return tmp;
775 }
776 
777 
778 void MultiRange::setIonID(unsigned int range, unsigned int newIonID)
779 {
780  ASSERT(newIonID < ionIDs.size());
781  ionIDs[range] = newIonID;
782 }
783 
784 
785 RGBf MultiRange::getColour(unsigned int ionID) const
786 {
787  ASSERT(ionID<moleculeData.size());
788  return colours[ionID];
789 }
790 
791 void MultiRange::setColour(unsigned int ionID, const RGBf &col)
792 {
793  ASSERT(ionID<moleculeData.size());
794  colours[ionID]=col;
795 }
796 
797 bool MultiRange::isRanged(float mass) const
798 {
799  for(auto range : ranges)
800  {
801  if( mass >= range.first &&
802  mass < range.second )
803  return true;
804  }
805 
806  return false;
807 }
808 
809 bool MultiRange::isRanged(const IonHit &ion) const
810 {
811  return isRanged(ion.getMassToCharge());
812 }
813 
814 void MultiRange::range(vector<IonHit> &ions) const
815 {
816  vector<IonHit> rangedVec;
817 
818  unsigned int numIons=ions.size();
819  rangedVec.reserve(numIons);
820 
821  //Range each ion
822  for(auto ion :ions)
823  {
824  if(isRanged(ion))
825  rangedVec.push_back(ion);
826  }
827 
828  ions.swap(rangedVec);
829 }
830 
832 {
833  ionNames.clear();
834  moleculeData.clear();
835  colours.clear();
836  ranges.clear();
837  ionIDs.clear();
838  rangeGrouping.clear();
839 
840  errState=0;
841 
842 }
843 
844 void MultiRange::splitOverlapping(std::vector<MultiRange> &splitMR,float tolerance) const
845 {
846  ASSERT(rangeGrouping.size());
847 
848  //Obtain a "flattened" (join ranges together in mass blocks if overlapping)
849  // representation.
850  vector<FLATTENED_RANGE> flatRange;
851  flattenToMassAxis(flatRange);
852 
853  //Now group the ranges according to if they sit in a given flattened range rep.
854  map<size_t,size_t> groupMapping;
855  vector<size_t> link(flatRange.size(),-1);
856 
857  //Construct graph to identify the edges that link different sections of the range grouping together
858  for(auto ui=0;ui<flatRange.size();ui++)
859  {
860 
861  //Loop over the connected ranges, map back to this flatRange
862  for(auto contained : flatRange[ui].containedRangeIDs)
863  {
864  size_t gId = rangeGrouping[contained];
865  if(groupMapping.find(gId) == groupMapping.end())
866  groupMapping[gId] = ui;
867 
868  //If we found one, remember the first occurrence
869  if(link[ui] == -1)
870  link[ui] = groupMapping[gId];
871  else
872  {
873  //always map back to the first occurrence, we will link these later
874  link[ui] = std::min((size_t)groupMapping[gId],(size_t)link[ui]);
875  }
876  }
877 
878  }
879 
880  ASSERT(std::find(link.begin(),link.end(),-1) == link.end());
881 
882  //Remap them such that they refer to a specific group
883  // -we always mapped back to the first occurrence.
884  // - this also ensures a contigous grouping
885  linkIdentifiers(link);
886 
887  //Reserve space for the groups
888  splitMR.resize(
889  *std::max_element(link.begin(),link.end())+1);
890 
891  //examine each group, and add range and other supporting data
892  // into each split group
893  for(auto ui=0;ui<link.size();ui++)
894  {
895  size_t dest;
896  dest=link[ui];
897  for(auto uj=0;uj<ranges.size();uj++)
898  {
899  //If the range overlaps a given pair, then add it
900  if(pairOverlaps(ranges[uj].first,ranges[uj].second,
901  flatRange[ui].startMass,flatRange[ui].endMass))
902  splitMR[dest].copyDataFromRange(*this,uj);
903  }
904  }
905 
906 
907 #ifdef DEBUG
908  for(auto &s : splitMR)
909  {
910  ASSERT(s.isSelfConsistent());
911  }
912 #endif
913 }
914 
915 void MultiRange::flattenToMassAxis(std::vector<FLATTENED_RANGE> &ionMapping, float tolerance) const
916 {
917  ionMapping.clear();
918 
919  if(ranges.empty())
920  return;
921 
922  auto rngCopy = ranges;
923 
924  ComparePairFirst cmp;
925  std::sort(rngCopy.begin(),rngCopy.end(),cmp);
926 
927  FLATTENED_RANGE fRng;
928 
929  fRng.startMass=rngCopy[0].first;
930  fRng.endMass=rngCopy[0].second;
931 
932  auto offFirst= std::distance(ranges.begin(),
933  std::find(ranges.begin(),ranges.end(),rngCopy[0]));
934 
935  fRng.containedRangeIDs.push_back(offFirst);
936  fRng.containedIonIDs.push_back(ionIDs[offFirst]);
937 
938  for(auto ui=1u;ui<rngCopy.size();ui++)
939  {
940  if(fRng.endMass > rngCopy[ui].first-tolerance)
941  {
942  //Overlaps, extend fRng
943  fRng.endMass = rngCopy[ui].second+tolerance;
944 
945 
946  auto off= std::distance(ranges.begin(),
947  std::find(ranges.begin(),ranges.end(),rngCopy[ui]));
948  fRng.containedRangeIDs.push_back(off);
949  fRng.containedIonIDs.push_back(ionIDs[off]);
950  }
951  else
952  {
953  //does not overlap. Close out group and create new
954  ionMapping.push_back(fRng);
955  fRng.containedRangeIDs.clear();
956  fRng.containedIonIDs.clear();
957 
958  fRng.startMass=rngCopy[ui].first;
959  fRng.endMass=rngCopy[ui].second;
960 
961  //Convert back to ID of original range
962  auto off= std::distance(ranges.begin(),
963  std::find(ranges.begin(),ranges.end(),rngCopy[ui]));
964  fRng.containedRangeIDs.push_back(off);
965  fRng.containedIonIDs.push_back(ionIDs[off]);
966  }
967 
968 
969  }
970  ionMapping.push_back(fRng);
971 
972 
973 #ifdef DEBUG
974  //Check masses are strictly increasing
975  for(auto v : ionMapping)
976  {
977  ASSERT(v.startMass < v.endMass);
978  }
979 #endif
980 }
981 
982 
983 void MultiRange::copyDataFromRange(const MultiRange &src,unsigned int srcRngID)
984 {
985  ASSERT(srcRngID < src.ranges.size());
986 
987  unsigned int srcIonID;
988  srcIonID=src.ionIDs[srcRngID];
989 
990  //Check to see if we have the ion name already
991  auto it = std::find(ionNames.begin(),ionNames.end(),
992  src.ionNames[srcIonID]);
993 
994  //if so, just record ionID data
995  if(it !=ionNames.end())
996  {
997  size_t destID = std::distance(ionNames.begin(),it);
998  ionIDs.push_back(destID);
999 
1000  }
1001  else
1002  {
1003  //copy other other data too
1004  ionIDs.push_back(ionNames.size());
1005  moleculeData.push_back(src.moleculeData[srcIonID]);
1006  ionNames.push_back(src.ionNames[srcIonID]);
1007  colours.push_back(src.colours[srcIonID]);
1008  }
1009 
1010  ranges.push_back(src.ranges[srcRngID]);
1011 
1013  rangeGrouping.clear();
1014 }
1015 
1016 #ifdef DEBUG
1017 
1018 set<SIMPLE_SPECIES> createElement(const AbundanceData &abundance,
1019  const string &symbol, unsigned int count)
1020 {
1021  SIMPLE_SPECIES ss;
1022  set<SIMPLE_SPECIES> s;
1023  auto elemIdx= abundance.symbolIndex(symbol.c_str());
1024  ss.atomicNumber = abundance.isotope(elemIdx,0).atomicNumber;
1025  ss.count=count;
1026 
1027  s.insert(ss);
1028  return s;
1029 }
1030 
1031 bool MultiRange::testSplit(const AbundanceData &abundance)
1032 {
1033  using std::pair;
1034 
1035  //Create an Fe-Ni and Si-N overlap
1036  MultiRange m;
1037 
1038  RGBf ionCol;
1039  ionCol.red=ionCol.green=ionCol.blue=1.0f;
1040 
1041  vector<string> symbols = {"Fe","Ni","Si","N"};
1042  enum{ FE=0,NI,SI,N,ATOM_END};
1043 
1044  vector<size_t> abundanceIdx,ionID;
1045  //Add each atom as an ion
1046  for(auto ui=0;ui<ATOM_END;ui++)
1047  {
1048  auto s=createElement(abundance,symbols[ui],1);
1049  ionID.push_back(m.addIon(s,symbols[ui],ionCol));
1050  }
1051  abundance.getSymbolIndices(symbols,abundanceIdx);
1052 
1053  //Construct ranges in both the 1+ and 2+ charge state for all, but N and Si.
1054  // as these are not molecules, we don't need to group.
1055  //For N, Si, only 1+ and 2+ respectively
1056  const float MASS_TOL=0.1;
1057  vector<unsigned int> rangeGrouping;
1058  for(auto ui=0;ui<ATOM_END;ui++)
1059  {
1060  vector<pair<float,float> > massDist;
1061  abundance.generateSingleAtomDist(abundanceIdx[ui], 1,massDist);
1062  for(auto &md : massDist)
1063  {
1064  pair<float,float> rng;
1065  rng = std::make_pair(md.first-MASS_TOL,md.first+MASS_TOL);
1066  if(ui != N)
1067  {
1068  rng.first/=2;
1069  rng.second/=2;
1070  }
1071 
1072 
1073  //Add the range - we will use the mass distribution
1074  // to have multiRange recompute the range-> ion mapping for us
1075  m.addRange(rng,ui);
1076 
1077  //Record the atom's type for the overlap. All ranges for the same ion belong together (as we only have 1 charge state per ion)
1078  rangeGrouping.push_back(ui);
1079  }
1080  }
1081 
1082  m.setRangeGroups(rangeGrouping);
1083 
1084  ASSERT(m.isSelfConsistent());
1085 
1086  //Now attempt to split this. we shoudl end up with an Fe-Ni
1087  // system and an Si-N system
1088  vector<MultiRange> splitMulti;
1089  m.splitOverlapping(splitMulti);
1090 
1091  ASSERT(splitMulti.size() ==2);
1092 
1093  for(auto ui=0;ui<splitMulti.size();ui++)
1094  {
1095  ASSERT(splitMulti[ui].getNumIons() ==2);
1096  ASSERT(splitMulti[ui].getNumRanges() > 2);
1097 
1098 
1099  vector<string> names;
1100  for(auto uj=0;uj<splitMulti[ui].getNumIons();uj++)
1101  {
1102  string name = splitMulti[ui].getIonName(uj);
1103  ASSERT(std::find(names.begin(),names.end(),name)
1104  == names.end())
1105  names.push_back(name);
1106  }
1107 
1108 
1109  //If we have Fe, we should have Ni
1110  if(std::find(names.begin(),names.end(),symbols[FE]) !=names.end())
1111  {
1112  ASSERT(std::find(names.begin(),names.end(),symbols[NI]) != names.end())
1113  }
1114 
1115  //If we have Si, we should have N
1116  if(std::find(names.begin(),names.end(),symbols[SI]) !=names.end())
1117  {
1118  ASSERT(std::find(names.begin(),names.end(),symbols[N]) != names.end())
1119  }
1120 
1121  }
1122 
1123 
1124  return true;
1125 }
1126 
1127 bool MultiRange::test()
1128 {
1129  MultiRange rng;
1130 
1131 
1132  set<SIMPLE_SPECIES> molecule;
1133 
1134  //Make FeH2+
1135  SIMPLE_SPECIES s;
1136  s.atomicNumber=26;
1137  s.count=1;
1138  molecule.insert(s);
1139  s.atomicNumber=1;
1140  s.count=2;
1141  molecule.insert(s);
1142 
1143  RGBf col;
1144  col.fromHex("0xFF0000");
1145  auto feH2Id = rng.addIon(molecule,"FeH",col);
1146  rng.addRange(28.9,29.3,feH2Id);
1147 
1148  //write test file
1149  rng.write("test.rngx");
1150 
1151 
1152  //Read test file
1153  {
1154  MultiRange rngB;
1155 
1156  TEST(rngB.open("test.rngx"), "Multirange load");
1157 
1158 
1159  TEST(rngB == rng,"write/load check");
1160  }
1161 
1162  //Add H2+ to rangefile
1163  s.atomicNumber=1;
1164  s.count=2;
1165  set<SIMPLE_SPECIES> newMol;
1166 
1167  {
1168  newMol.insert(s);
1169 
1170  auto h2Id = rng.addIon(newMol,"H2",col);
1171  rng.addRange(1.7, 2.5, h2Id);
1172 
1173 
1174  //Set the grouping for the ranges
1175  vector<unsigned int> rangeGroup= { 0,1 };
1176  rng.setRangeGroups(rangeGroup);
1177 
1178  //Now split the problem, as we have FeH2+ and H2+,
1179  // we should get two muti-ranges
1180  vector<MultiRange> mrSplit;
1181  rng.splitOverlapping(mrSplit);
1182 
1183 
1184  TEST(mrSplit.size() == 2,"Split test");
1185 
1186  TEST(mrSplit[0].getNumIons() ==1,"split ion count");
1187  TEST(mrSplit[1].getNumIons() ==1,"split ion count");
1188 
1189  TEST(mrSplit[0].getIonName(0) != mrSplit[1].getIonName(0),"split : unique ion names");
1190  }
1191 
1192 
1193 
1194  return true;
1195 }
1196 #endif
1197 }
unsigned int atomicNumber
Number of protons (element number)
Definition: multiRange.h:37
std::set< SIMPLE_SPECIES > getMolecule(unsigned int ionID) const
Return the molecule that is associated with this ion.
Definition: multiRange.cpp:727
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
size_t atomicNumber
Definition: abundance.h:46
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
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 XMLHelpFwdToElem(xmlNodePtr &node, const char *nodeName)
Definition: XMLHelper.cpp:41
bool operator<(const SIMPLE_SPECIES &a, const SIMPLE_SPECIES &b)
Definition: multiRange.cpp:117
bool isRanged(float mass) const
Returns true if a specified mass is ranged.
Definition: multiRange.cpp:797
Data holder for colour as float.
Definition: misc.h:26
bool isSelfConsistent() const
Check to see if data structure is internally consistent.
Definition: multiRange.cpp:230
std::string tabs(unsigned int nTabs)
Definition: stringFuncs.h:99
bool pairOverlaps(float aStart, float aEnd, float bStart, float bEnd)
Definition: multiRange.cpp:122
std::vector< unsigned int > getRanges(float mass) const
Retrieve all of the ranges that specify the given mass.
Definition: multiRange.cpp:765
float green
Definition: misc.h:30
void linkIdentifiers(vector< T > &link)
Definition: multiRange.cpp:88
void setColour(unsigned int ionID, const RGBf &r)
Set the colour using the ion ID.
Definition: multiRange.cpp:791
void fromHex(const std::string &s)
Definition: misc.h:40
void generateSingleAtomDist(size_t atomIdx, unsigned int repeatCount, std::vector< std::pair< float, float > > &massDist) const
Obtain the mass distribution from a single isotope that repeats. Output is not grouped.
Definition: abundance.cpp:517
static bool decomposeIonNames(const std::string &name, std::vector< std::pair< std::string, size_t > > &fragments)
Definition: ranges.cpp:121
void XMLFreeDoc(void *data)
Free a xmlDoc pointer. For use in conjunction with std::unique_ptr for auto-deallocation.
Definition: XMLHelper.cpp:23
unsigned int addRange(float start, float end, unsigned int ionID)
Add a range to the rangefile. Returns ID number of added range if adding successful, (unsigned int)-1 otherwise.
Definition: multiRange.cpp:303
std::string getIonName(unsigned int ionID) const
Get the name of a specified ionID.
Definition: multiRange.cpp:733
#define TEST(f, g)
Definition: aptAssert.h:49
void getSymbolIndices(const std::vector< std::string > &symbols, std::vector< size_t > &indices) const
Return a vector of symbol indices.
Definition: abundance.cpp:559
unsigned int addIon(const std::set< SIMPLE_SPECIES > &molecule, const std::string &name, const RGBf &ionCol)
Add the ion to the database returns ion ID if successful, -1 otherwise.
Definition: multiRange.cpp:271
unsigned int getNumRanges() const
Get the number of unique ranges.
Definition: ranges.cpp:2846
Data storage and retrieval class for "ranging" a spectra, where overlapping ranges are permitted...
Definition: multiRange.h:63
void clear()
Erase the contents of the rangefile.
Definition: multiRange.cpp:831
float blue
Definition: misc.h:31
unsigned int getNumRanges() const
Get the number of unique ranges.
Definition: multiRange.cpp:720
void setRangeGroups(const std::vector< unsigned int > &groups)
Set the groupings for the ranges : this is used when splitting overlapping ranges.
Definition: multiRange.cpp:321
void setIonID(unsigned int range, unsigned int newIonId)
Set the ion ID for a given range.
Definition: multiRange.cpp:778
void splitOverlapping(std::vector< MultiRange > &decomposedRanges, float massTolerance=0) const
Definition: multiRange.cpp:844
bool operator==(const SIMPLE_SPECIES &oth) const
Definition: multiRange.cpp:112
float getMassToCharge() const
Definition: ionhit.cpp:65
bool write(const char *fileName, unsigned int format=MULTIRANGE_FORMAT_XML) const
Save the structure to a file. format specifies output file format type.
Definition: multiRange.cpp:327
bool operator==(const MultiRange &oth) const
Check for equality with other multirange.
Definition: multiRange.cpp:208
unsigned int XMLHelpGetProp(T &prop, xmlNodePtr node, std::string propName)
Definition: XMLHelper.h:87
Class to load abundance information for natural isotopes.
Definition: abundance.h:54
unsigned int getNumIons() const
Get the number of unique ions.
Definition: multiRange.cpp:747
std::pair< float, float > getRange(unsigned int rangeID) const
Retrieve the start and end of a given range as a pair(start,end)
Definition: multiRange.cpp:759
unsigned int getIonID(unsigned int rangeId) const
Get the ion&#39;s ID from a specified mass.
Definition: multiRange.cpp:752
Structure that allows for the multirange data to be mapped into.
Definition: multiRange.h:47
This is a data holding class for POS file ions, from.
Definition: ionHit.h:36
#define ASSERT(f)
RGBf getColour(unsigned int ionID) const
Obtain the colour for a given ion.
Definition: multiRange.cpp:785
std::string getErrString() const
Retrieve the human-readable error associated with the current range file state.
Definition: multiRange.cpp:693
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 #.
Data storage and retrieval class for various range files.
Definition: ranges.h:95
unsigned int count
Number of copies of this isotope.
Definition: multiRange.h:40
const ISOTOPE_ENTRY & isotope(size_t elementIdx, size_t isotopeIdx) const
Obtain a reference to a particular isotope, using the element&#39;s index in the table, and the isotope index.
Definition: abundance.cpp:271
void flattenToMassAxis(std::vector< FLATTENED_RANGE > &ionMapping, float tolerance=0) const
Obtain a projection onto the mass axis of ranges that do not touch one another.
Definition: multiRange.cpp:915
bool stream_cast(T1 &result, const T2 &obj)
Template function to cast and object to another by the stringstream.
Definition: stringFuncs.h:32
const char MULTIRANGE_FORMAT_VERSION[]
Definition: multiRange.cpp:46
const float MASS_TOL
Definition: specsim.cpp:9
void copyDataFromRange(const MultiRange &src, unsigned int srcRngId)
Copy range from a different multirange into this one, including all dependant data.
Definition: multiRange.cpp:983
unsigned int getNumIons() const
Get the number of unique ions.
Definition: ranges.cpp:2863
float red
Definition: misc.h:29
void range(std::vector< IonHit > &ionHits) const
Clips out ions that are not inside the rangefile&#39;s ranges.
Definition: multiRange.cpp:814
unsigned int getAtomicNumber(size_t elemIdx) const
Obtain the atomic number for the given element, by element index.
Definition: abundance.cpp:503
bool open(const char *fileName, unsigned int format=-1)
read the contents of the file into class
Definition: multiRange.cpp:398
static std::string getVersionStr()
Obtain the version of the program as a string.
Definition: version.h:60
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