1 /*  _______________________________________________________________________
2 
3     Surfpack: A Software Library of Multidimensional Surface Fitting Methods
4     Copyright (c) 2006, Sandia National Laboratories.
5     This software is distributed under the GNU Lesser General Public License.
6     For more information, see the README file in the top Surfpack directory.
7     _______________________________________________________________________ */
8 
9 #include "surfpack.h"
10 #include "SurfData.h"
11 
12 using std::cerr;
13 using std::cout;
14 using std::endl;
15 using std::ifstream;
16 using std::istream;
17 using std::istringstream;
18 using std::ios;
19 using std::list;
20 using std::ofstream;
21 using std::ostream;
22 using std::ostream_iterator;
23 using std::ostringstream;
24 using std::range_error;
25 using std::set;
26 using std::setw;
27 using std::string;
28 using std::vector;
29 
30 
31 #ifdef SURFPACK_HAVE_BOOST_SERIALIZATION
BOOST_CLASS_EXPORT(SurfData)32 BOOST_CLASS_EXPORT(SurfData)
33 #endif
34 
35 
36 // ____________________________________________________________________________
37 // Creation, Destruction, Initialization
38 // ____________________________________________________________________________
39 
40 /// Vector of points will be copied and checked for duplicates
41 SurfData::SurfData(const vector<SurfPoint>& points_)
42 {
43   if (points_.empty()) {
44     this->xsize = 0;
45     this->fsize = 0;
46     this->gradsize = 0;
47     this->hesssize = 0;
48   } else {
49     this->xsize = points_[0].xSize();
50     this->fsize = points_[0].fSize();
51     this->gradsize = points_[0].fGradientsSize();
52     this->hesssize = points_[0].fHessiansSize();
53     defaultLabels();
54     for (unsigned i = 0; i < points_.size(); i++) {
55       this->addPoint(points_[i]);
56     }
57   }
58   init();
59   // Check to make sure data points all have the same number of dimensions
60   // and response values.  An exception will be thrown otherwise.
61   sanityCheck();
62 }
63 
64 /// Read a set of SurfPoints from a file
SurfData(const string filename)65 SurfData::SurfData(const string filename):
66   xsize(0), fsize(0), gradsize(0), hesssize(0)
67 {
68   init();
69   read(filename);
70 }
71 
72 /// Read a set of SurfPoints from a istream.  The stream does not
73 /// contain the normal header information (#points, #vars, #responses).
74 /// The #vars and #responses are explicitly specified in the constructor;
75 /// The stream reader processes data until eof, assuming one point per line.
SurfData(const string filename,unsigned n_vars,unsigned n_responses,unsigned n_cols_to_skip)76 SurfData::SurfData(const string filename, unsigned n_vars,
77 		   unsigned n_responses, unsigned n_cols_to_skip):
78   xsize(n_vars), fsize(n_responses), gradsize(0), hesssize(0)
79 {
80   init();
81 
82   if (!surfpack::hasExtension(filename,".dat")
83     && !surfpack::hasExtension(filename,".spd")) {
84     cerr << "Bad filename: " << filename << endl;
85     throw surfpack::io_exception(
86       "Expected .dat extension for filename"
87     );
88   }
89   ifstream infile(filename.c_str(),ios::in);
90   if (!infile) {
91     throw surfpack::file_open_failure(filename);
92   }
93   bool read_header = false;
94   readText(infile, read_header, n_cols_to_skip);
95 }
96 
97 /// Read a set of SurfPoints from a istream
SurfData(istream & is,bool binary)98 SurfData::SurfData(istream& is, bool binary):
99   xsize(0), fsize(0), gradsize(0), hesssize(0)
100 {
101   init();
102   if (binary) {
103     readBinary(is);
104   } else {
105     readText(is);
106   }
107 }
108 
109 /// Makes a deep copy of the object
SurfData(const SurfData & other)110 SurfData::SurfData(const SurfData& other):
111   xsize(other.xsize), fsize(other.fsize),
112   gradsize(other.gradsize), hesssize(other.hesssize),
113   excludedPoints(other.excludedPoints), defaultIndex(other.defaultIndex),
114   xLabels(other.xLabels), fLabels(other.fLabels)
115 {
116   for (unsigned i = 0; i < other.points.size(); i++) {
117     this->addPoint(*other.points[i]);
118   }
119   mapping = other.mapping;
120   buildOrderedPoints();
121 }
122 
123 /// First SurfPoint added will determine the dimensions of the data set
SurfData()124 SurfData::SurfData(): xsize(0), fsize(0), gradsize(0), hesssize(0)
125 {
126     init();
127 }
128 
129 /// STL data members' resources automatically deallocated
~SurfData()130 SurfData::~SurfData()
131 {
132   cleanup();
133 }
134 
135 /// Data member initialization that is common to all constructors
init()136 void SurfData::init()
137 {
138   defaultIndex = 0;
139   defaultMapping();
140 }
141 
142 /// Copy only the points which have not been marked for exclusion
copyActive()143 SurfData SurfData::copyActive()
144 {
145   // This is not a very efficient method.  It is not expected that it will
146   // be called often.  The active points are copied into the local
147   // activePoints vector.  Then, a new SurfData object is created, which
148   // requires that all the points be recopied.  Then, at the end of the method
149   // the new SurfData object is returned by value, which means all of the data
150   // are most likely copied again.  If this is a bottleneck, it would not be
151   // terribly difficult to eliminate at least two of the three copies.
152   vector<SurfPoint> activePoints;
153   for (unsigned i = 0; i < mapping.size(); i++) {
154     activePoints.push_back(*points[mapping[i]]);
155   }
156   SurfData newSD(activePoints);
157   if (!activePoints.empty()) {
158     newSD.setDefaultIndex(defaultIndex);
159   }
160   return newSD;
161 }
162 
163 /// Call delete on the SurfPoint* in the data set.
cleanup()164 void SurfData::cleanup()
165 {
166   mapping.clear();
167   orderedPoints.clear();
168   for (unsigned j = 0; j < points.size(); j++) {
169     delete points[j];
170     points[j] =0;
171   }
172   points.clear();
173   excludedPoints.clear();
174 }
175 
176 // ____________________________________________________________________________
177 // Overloaded operators
178 // ____________________________________________________________________________
179 
180 /// Makes deep copy of other
operator =(const SurfData & other)181 SurfData& SurfData::operator=(const SurfData& other)
182 {
183   if (*this != other) {
184     xLabels = other.xLabels;
185     fLabels = other.fLabels;
186     cleanup();
187     this->xsize = other.xsize;
188     this->fsize = other.fsize;
189     this->gradsize = other.gradsize;
190     this->hesssize = other.hesssize;
191     for (unsigned i = 0; i < other.points.size(); i++) {
192       this->addPoint(*other.points[i]);
193     }
194     this->excludedPoints = other.excludedPoints;
195     this->mapping = other.mapping;
196     this->defaultIndex = other.defaultIndex;
197   }
198   buildOrderedPoints();
199   return (*this);
200 }
201 
202 /// Makes deep comparison
operator ==(const SurfData & other) const203 bool SurfData::operator==(const SurfData& other) const
204 {
205   if (this->xsize == other.xsize &&
206       this->fsize == other.fsize &&
207       this->gradsize == other.gradsize &&
208       this->hesssize == other.hesssize &&
209       this->size() == other.size()) {
210     for (unsigned i = 0; i < points.size(); i++) {
211       if (*this->points[i] != *other.points[i]) {
212         return false;
213       }
214     }
215     return true;
216   } else {
217     return false;
218   }
219 }
220 
221 /// Makes deep comparison
operator !=(const SurfData & other) const222 bool SurfData::operator!=(const SurfData& other) const
223 {
224   return !(*this == other);
225 }
226 
227 /// Return a const reference to SurfPoint at given index
operator [](unsigned index) const228 const SurfPoint& SurfData::operator[](unsigned index) const
229 {
230   static string header("Indexing error in SurfData::operator[] const.");
231   checkRangeNumPoints(header, index);
232   return *points[mapping[index]];
233 }
234 
235 /// Return the x-value for point pt along dimension dim
operator ()(unsigned pt,unsigned dim) const236 double SurfData::operator()(unsigned pt, unsigned dim) const
237 {
238   assert(pt < size());
239   assert(dim < xSize());
240   return points[mapping[pt]]->X()[dim];
241 }
242 
243 /// Return the vector of predictor vars for point index
operator ()(unsigned pt) const244 const vector<double>& SurfData::operator()(unsigned pt) const
245 {
246   if (pt >= size()) {
247     cout << "Assertion failure.  Pt: " << pt << " size: " << size() << endl;
248   }
249   assert(pt < size());
250   return points[mapping[pt]]->X();
251 }
252 
253 // ____________________________________________________________________________
254 // Queries
255 // ____________________________________________________________________________
256 
257 /// Return the number of SurfPoints in the data set
size() const258 unsigned SurfData::size() const
259 {
260   return mapping.size();
261 }
262 
263 /// True if there are no points
empty() const264 bool SurfData::empty() const
265 {
266   return mapping.empty();
267 }
268 
269 /// Return the dimensionality of the SurfPoints
xSize() const270 unsigned SurfData::xSize() const
271 {
272   return xsize;
273 }
274 
275 /// Return the number of response variables in the data set
fSize() const276 unsigned SurfData::fSize() const
277 {
278   return fsize;
279 }
280 
281 /// Return the set of excluded points (the indices)
getExcludedPoints() const282 const set<unsigned>& SurfData::getExcludedPoints() const
283 {
284   return excludedPoints;
285 }
286 
287 /// Get the response value of the (index)th point that corresponds to this
288 /// surface
getResponse(unsigned index) const289 double SurfData::getResponse(unsigned index) const
290 {
291   static string header("Indexing error in SurfData::getResponse.");
292   checkRangeNumPoints(header, index);
293   return points[mapping[index]]->F(defaultIndex);
294 }
295 
296 // const std::vector<double>& SurfData::getGradient(unsigned index) const
297 // {
298 //   static string header("Indexing error in SurfData::getResponse.");
299 //   checkRangeNumPoints(header, index);
300 //   return points[mapping[index]]->fGradient(defaultIndex);
301 // }
302 
303 // const SurfpackMatrix<double>& SurfData::getHessian(unsigned index) const
304 // {
305 //   static string header("Indexing error in SurfData::getResponse.");
306 //   checkRangeNumPoints(header, index);
307 //   return points[mapping[index]]->fHessian(defaultIndex);
308 // }
309 
310 /// Get the default response for all of the points as a vector
getResponses() const311 std::vector< double > SurfData::getResponses() const
312 {
313   vector< double > result(mapping.size());
314   for (unsigned i = 0; i < mapping.size(); i++) {
315     result[i] = points[mapping[i]]->F(defaultIndex);
316   }
317   return result;
318 }
319 
320 /// Get the predictor (index-th x-value) for all the active points as a vector
getPredictor(unsigned index) const321 std::vector< double > SurfData::getPredictor(unsigned index) const
322 {
323   assert(index < xSize());
324   vector< double > result(mapping.size());
325   for (unsigned i = 0; i < mapping.size(); i++) {
326     result[i] = (*this)(i,index);
327   }
328   return result;
329 }
330 
getConstraintPoint() const331 const SurfPoint& SurfData::getConstraintPoint() const
332 {
333   return constraintPoint;
334 }
335 
336 /** Calculate the number of constraint data present in functions,
337     gradients, and Hessians in the constraintPoint. */
numConstraints() const338 unsigned SurfData::numConstraints() const
339 {
340   unsigned num_constraints = 0;
341   if (constraintPoint.fSize() > 0)
342     num_constraints += 1; // value at a particular point
343   if (constraintPoint.fGradientsSize() > 0)
344     num_constraints += xsize; // gradient at a point
345   if (constraintPoint.fHessiansSize() > 0)
346     num_constraints += (xsize*xsize+xsize)/2; // hessian at a point
347   return num_constraints;
348 }
349 
350 
351 /// Get default index
getDefaultIndex() const352 unsigned SurfData::getDefaultIndex() const
353 {
354   return defaultIndex;
355 }
356 
357 /// Retrieve the label for one of the predictor variables
getXLabel(unsigned index) const358 const string& SurfData::getXLabel(unsigned index) const
359 {
360   return xLabels[index];
361 }
362 
363 /// Retrieve the label for one of the response variables
getFLabel(unsigned index) const364 const string& SurfData::getFLabel(unsigned index) const
365 {
366   return fLabels[index];
367 }
368 
369 /// Retrieve the index and variable type (predictor/response) for a given
370 /// name.  Return false if not found
varIndex(const string & name,unsigned & index,bool & isResponse) const371 bool SurfData::varIndex(const string& name, unsigned& index,
372   bool& isResponse) const
373 {
374   //\todo check for apostrophes rather than just assuming they're there
375   // Strip off the apostrophes at the beginning and end
376   string unquoted_name = name;
377   if (name.find('\'') != string::npos) {
378     unquoted_name = string(name,1,name.size()-2);
379   }
380   vector< string>::const_iterator iter =
381     find(xLabels.begin(),xLabels.end(),unquoted_name);
382   if (iter != xLabels.end()) {
383     index = iter - xLabels.begin();
384     isResponse = false;
385     return true;
386   } else {
387     iter = find(fLabels.begin(),fLabels.end(),unquoted_name);
388     if (iter != fLabels.end()) {
389       index = iter - fLabels.begin();
390       isResponse = true;
391       return true;
392     } else {
393       cout << "Name sought: " << unquoted_name << endl;
394       cout << "Predictors: " << endl;
395       copy(xLabels.begin(),xLabels.end(),ostream_iterator<string>(cout,"\n"));
396       cout << "Responses: " << endl;
397       copy(fLabels.begin(),fLabels.end(),ostream_iterator<string>(cout,"\n"));
398       return false;
399     }
400   }
401 }
402 
403 // ____________________________________________________________________________
404 // Commands
405 // ____________________________________________________________________________
406 
407 /// Specify which response value getResponse will return. When a Surface
408 /// object that is associated with the SurfData object operates on the data,
409 /// it sets this value so that the response value lookup function will return
410 /// the value for the response variable that that particular Surface object
411 /// is interested in.
setDefaultIndex(unsigned index) const412 void SurfData::setDefaultIndex(unsigned index) const
413 {
414   static string header("Indexing error in SurfData::setDefaultIndex.");
415   checkRangeNumResponses(header, index);
416   defaultIndex = index;
417 }
418 
419 /// Set the response value of the (index)th point that corresponds to this
420 /// surface
setResponse(unsigned index,double value)421 void SurfData::setResponse(unsigned index, double value)
422 {
423   static string header("Indexing error in SurfData::setResponse.");
424   checkRangeNumPoints(header, index);
425   points[mapping[index]]->F(defaultIndex, value);
426 }
427 
428 /// Add a point to the data set. The parameter point will be copied.
addPoint(const SurfPoint & sp)429 void SurfData::addPoint(const SurfPoint& sp)
430 {
431   if (points.empty()) {
432     xsize = sp.xSize();
433     fsize = sp.fSize();
434     gradsize = sp.fGradientsSize();
435     hesssize = sp.fHessiansSize();
436     if (xLabels.empty()) {
437       defaultLabels();
438     }
439   } else {
440     if (sp.xSize() != xsize || sp.fSize() != fsize ||
441 	sp.fGradientsSize() != gradsize || sp.fHessiansSize() != hesssize) {
442       ostringstream errormsg;
443       errormsg << "Error in SurfData::addPoint.  Points in this data set "
444 	       << "have " << xsize << " dimensions and " << fsize
445 	       << " response values; point to be added has "
446 	       << sp.xSize() << " dimensions and " << sp.fSize()
447 	       << " response values. (Or gradient and Hessian sizes don't "
448 	       << "match.)" << endl;
449       throw bad_surf_data(errormsg.str());
450     }
451   }
452   SurfPointSet::iterator iter;
453   // This should be a safe const cast.  All that's happening is a check
454   // to see if another data point at the same location in the space has already
455   // been added.
456   iter = orderedPoints.find(const_cast<SurfPoint*>(&sp));
457   if (iter == orderedPoints.end()) {
458     // This SurfPoint is not already in the data set.  Add it.
459     points.push_back(new SurfPoint(sp));
460     orderedPoints.insert(points[points.size()-1]);
461     mapping.push_back(points.size()-1);
462   } else {
463     // Another SurfPoint in this SurfData object has the same location and
464     // may have different response value(s).  Replace the old point with
465     // this new one.
466     SurfPoint* spPtr = *iter;
467     *spPtr = sp;
468   }
469 }
470 
471 /// Add a new response variable to each point. Return the index of the new
472 /// variable.
addResponse(const vector<double> & newValues,string label)473 unsigned SurfData::addResponse(const vector<double>& newValues,
474   string label)
475 {
476   unsigned new_index;
477   ostringstream errormsg;
478   if (points.empty()) {
479     throw bad_surf_data(
480              "Cannot add response because there are no data points"
481           );
482   } else if (points.size() != mapping.size()) {
483     errormsg << "Cannot add response because physical set size is different "
484 	     << "than logical set size.\nBefore adding another response, "
485              << "clear \"excluded points\" or create a new data set by using "
486 	     << "the SurfData::copyActive method." << endl;
487     throw bad_surf_data(errormsg.str());
488   } else if (newValues.size() != points.size()) {
489     errormsg << "Cannot add another response: the number of new response"
490              << " values does not match the size of the physical data set."
491              << endl;
492     throw bad_surf_data(errormsg.str());
493   } else {
494     new_index = points[mapping[0]]->addResponse(newValues[0]);
495     fsize++;
496     for (unsigned i = 1; i < points.size(); i++) {
497       new_index = points[mapping[i]]->addResponse(newValues[i]);
498       assert(new_index == fsize - 1);
499     }
500   }
501   if (label != "") {
502     fLabels.push_back(label);
503   } else {
504     ostringstream labelos;
505     labelos << "f" << new_index ;
506     fLabels.push_back(labelos.str());
507   }
508   return new_index;
509 }
510 
setConstraintPoint(const SurfPoint & sp)511 void SurfData::setConstraintPoint(const SurfPoint& sp)
512 {
513   // handle the case of this being the first point in the data set
514   if (points.empty()) {
515     xsize = sp.xSize();
516     fsize = sp.fSize();
517     gradsize = sp.fGradientsSize();
518     hesssize = sp.fHessiansSize();
519     if (xLabels.empty()) {
520       defaultLabels();
521     }
522   } else {
523     if (sp.xSize() != xsize || sp.fSize() != fsize ||
524 	sp.fGradientsSize() != gradsize || sp.fHessiansSize() != hesssize) {
525       ostringstream errormsg;
526       errormsg << "Error in SurfData::setConstraintPoint.  Points in this data set "
527                << "have " << xsize << " dimensions and " << fsize
528                << " response values; point to be added has "
529                << sp.xSize() << " dimensions and " << sp.fSize()
530                << " response values. (Or gradient and Hessian sizes don't "
531 	       << "match.)" << endl;
532       throw bad_surf_data(errormsg.str());
533     }
534   }
535   constraintPoint = sp;
536 }
537 
538 /// Specify which points should be skipped.  This can be used when only a
539 /// subset of the SurfPoints should be used for some computation.
setExcludedPoints(const set<unsigned> & excluded_points)540 void SurfData::setExcludedPoints(const set<unsigned>& excluded_points)
541 {
542   if (excluded_points.size() > points.size()) {
543     throw bad_surf_data(
544       "Size of set of excluded points exceeds size of SurfPoint set"
545     );
546   } else if (excluded_points.empty()) {
547     defaultMapping();
548     this->excludedPoints.clear();
549   } else {
550     // The size of the logical data set is the size of the physical
551     // data set less the number of excluded points
552     mapping.resize(points.size() - excluded_points.size());
553     unsigned mappingIndex = 0;
554     unsigned sdIndex = 0;
555     // map the valid indices to the physical points in points
556     while (sdIndex < points.size()) {
557       if (excluded_points.find(sdIndex) == excluded_points.end()) {
558         mapping[mappingIndex++] = sdIndex;
559       }
560       sdIndex++;
561     }
562     this->excludedPoints = excluded_points;
563     assert(mappingIndex == mapping.size());
564   }
565 }
566 
567 /// For use with copy constructor and assignment operator-- creates a list of
568 /// pointers to the points in the data set which is used to check for
569 /// duplication when other points are added in the future
buildOrderedPoints()570 void SurfData::buildOrderedPoints()
571 {
572   orderedPoints.clear();
573   for (unsigned i = 0; i < points.size(); i++) {
574     orderedPoints.insert(points[i]);
575   }
576 }
577 
578 /// Maps all indices to themselves in the mapping data member
defaultMapping()579 void SurfData::defaultMapping()
580 {
581   mapping.resize(points.size());
582   for (unsigned i = 0; i < points.size(); i++) {
583     mapping[i] = i;
584   }
585 }
586 
587 /// Set the labels for the predictor variables
setXLabels(const vector<string> & labels)588 void SurfData::setXLabels(const vector<string>& labels)
589 {
590   if (labels.size() != xsize) {
591     throw string("Dim mismatch in SurfData::setXLabels");
592   }
593   xLabels = labels;
594 }
595 
596 /// Set the labels for the response variables
setFLabels(const vector<string> & labels)597 void SurfData::setFLabels(const vector<string>& labels)
598 {
599   if (labels.size() != fsize) {
600     throw string("Dim mismatch in SurfData::setFLabels");
601   }
602   fLabels = labels;
603 }
604 
605 /// Set the label for a single response variable
setFLabel(unsigned index,const string & response_name)606 void SurfData::setFLabel(unsigned index, const string& response_name)
607 {
608   if (index >= fsize) {
609     throw string("Dim mismatch in SurfData::setFLabel");
610   }
611   fLabels[index] = response_name;
612 }
613 
614 // ____________________________________________________________________________
615 // I/O
616 // ____________________________________________________________________________
617 
618 /// Write a set of SurfPoints to a file.  Opens the file and calls
619 /// other version of write.  All files include dimensions, but some
620 /// contain additional header and label information.
write(const string & filename) const621 void SurfData::write(const string& filename) const
622 {
623   if (mapping.empty()) {
624     ostringstream errormsg;
625     errormsg << "Cannot write SurfData object to stream."
626 	     << "  No active data points." << endl;
627     throw bad_surf_data(errormsg.str());
628   }
629   bool binary = hasBinaryFileExtension(filename);
630   ofstream outfile(filename.c_str(),
631     (binary ? ios::out|ios::binary : ios::out));
632   if (!outfile) {
633     throw surfpack::file_open_failure(filename);
634   } else if (binary) {
635     writeBinary(outfile);
636   } else {
637     bool write_header = false;
638     // Write the header and label info for .spd, not for .dat
639     bool metadata = surfpack::hasExtension(filename,".spd");
640     writeText(outfile, write_header, metadata);
641   }
642   outfile.close();
643 }
644 
645 /// Read a set of SurfPoints from a file.  Opens file and calls other version.
read(const string & filename)646 void SurfData::read(const string& filename)
647 {
648   // Open file in binary or text mode based on filename extension (.bspd or .spd)
649   bool binary = hasBinaryFileExtension(filename);
650   ifstream infile(filename.c_str(), (binary ? ios::in|ios::binary : ios::in));
651   if (!infile) {
652     throw surfpack::file_open_failure(filename);
653   } else if (binary) {
654     readBinary(infile);
655   } else {
656     readText(infile);
657   }
658   // Object may have already been created
659   infile.close();
660 }
661 
662 /// Write a set of SurfPoints to an output stream
writeBinary(ostream & os) const663 void SurfData::writeBinary(ostream& os) const
664 {
665   unsigned s = mapping.size();
666   os.write((char*)&s,sizeof(s));
667   os.write((char*)&xsize,sizeof(xsize));
668   os.write((char*)&fsize,sizeof(fsize));
669   os.write((char*)&gradsize,sizeof(gradsize));
670   os.write((char*)&hesssize,sizeof(hesssize));
671   ///\todo accumulate all the writes into one big chunk of data
672   /// and write it out all at once.
673   for (unsigned i = 0; i < mapping.size(); i++) {
674     points[mapping[i]]->writeBinary(os);
675   }
676 }
677 
678 /// Write a set of SurfPoints to an output stream
writeText(ostream & os,bool write_header,bool write_labels) const679 void SurfData::writeText(ostream& os,
680 			 bool write_header, bool write_labels) const
681 {
682     if (write_header) {
683       os << mapping.size() << endl
684          << xsize << endl
685          << fsize << endl
686          << gradsize << endl
687          << hesssize << endl;
688     }
689     if (write_labels) {
690       os << '%';
691       for (unsigned i = 0; i < xLabels.size(); i++) {
692         int correction = i ? 0 : 1; // the '%' takes up one space
693         os << setw(surfpack::field_width - correction) << xLabels[i];
694       }
695       for (unsigned i = 0; i < fLabels.size(); i++) {
696         os << setw(surfpack::field_width) << fLabels[i];
697       }
698       os << endl;
699     }
700     for (unsigned i = 0; i < mapping.size(); i++) {
701       //if (!write_header) {
702       //  os << setw((int)log10((double)(mapping.size())+2)) << (i+1);
703       //}
704       points[mapping[i]]->writeText(os);
705     }
706 }
707 
708 /// Read a set of SurfPoints from an input stream
readBinary(istream & is)709 void SurfData::readBinary(istream& is)
710 {
711   ///\todo Eliminate need for SurfPoint constructor that takes istream
712   ///Instead, allocate a big chunk of memory, read all of the data in
713   /// at the same time, then iterate through the block of data and
714   /// create SurfPoints using the constructor that takes x and f.
715   unsigned n_points_read = 0;
716   unsigned size;
717   try {
718     cleanup();
719     is.read((char*)&size,sizeof(size));
720     is.read((char*)&xsize,sizeof(xsize));
721     is.read((char*)&fsize,sizeof(fsize));
722     is.read((char*)&gradsize,sizeof(gradsize));
723     is.read((char*)&hesssize,sizeof(hesssize));
724     points.clear();
725     for (n_points_read = 0; n_points_read < size; n_points_read++) {
726       // Throw an exception if we hit the end-of-file before we've
727       // read the number of points that were supposed to be there.
728       surfpack::checkForEOF(is);
729       this->addPoint(SurfPoint(is, xsize, fsize, gradsize, hesssize));
730     }
731     defaultMapping();
732   } catch(surfpack::io_exception&) {
733     cerr << "Expected: " << size << " points.  "
734          << "Read: " << n_points_read << " points." << endl;
735     throw;
736   }
737 }
738 
readHeaderInfo(istream & is)739 unsigned SurfData::readHeaderInfo(istream& is)
740 {
741   string single_line;
742 
743   getline(is,single_line);
744   istringstream streamline(single_line);
745   unsigned declared_size;
746   streamline >> declared_size;
747 
748   getline(is,single_line);
749   streamline.str(single_line); streamline.clear();
750   streamline >> xsize;
751 
752   getline(is,single_line);
753   streamline.str(single_line); streamline.clear();
754   streamline >> fsize;
755 
756   getline(is,single_line);
757   streamline.str(single_line); streamline.clear();
758   streamline >> gradsize;
759 
760   getline(is,single_line);
761   streamline.str(single_line); streamline.clear();
762   streamline >> hesssize;
763 
764   return declared_size;
765 }
766 
767 /// Read a set of SurfPoints from a text input stream
readText(istream & is,bool read_header,unsigned skip_columns)768 void SurfData::readText(istream& is, bool read_header, unsigned skip_columns)
769 {
770   unsigned n_points_read = 0;
771   unsigned declared_size = 0;
772   string single_line;
773   try {
774     cleanup();
775     points.clear();
776     if (read_header) declared_size = readHeaderInfo(is);
777 
778     getline(is,single_line);
779     istringstream streamline(single_line);
780     if (!readLabelsIfPresent(single_line)) {
781       if (single_line != "" && single_line != "\n" && single_line[0] != '%') {
782         this->addPoint(SurfPoint(single_line, xsize,
783 				 fsize, gradsize, hesssize, skip_columns));
784         n_points_read = 1;
785       }
786     }
787     while (!is.eof()) {
788       // Throw an exception if we hit the end-of-file before we've
789       // read the number of points that were supposed to be there.
790       //surfpack::checkForEOF(is);
791       getline(is,single_line);
792       if (single_line[0] == '%' || single_line == "") continue;
793       // False for last argument signals a text read
794       this->addPoint(SurfPoint(single_line, xsize,
795 			       fsize, gradsize, hesssize, skip_columns));
796       n_points_read++;
797     }
798     defaultMapping();
799   } catch(surfpack::io_exception& exception) {
800     cerr << exception.what() << endl;
801     throw;
802   }
803   if (read_header && n_points_read != declared_size) {
804     ostringstream errormsg;
805     errormsg << "Expected: " << declared_size << " points.  "
806          << "Read: " << n_points_read << " points." << endl;
807     throw surfpack::io_exception(errormsg.str());
808   }
809 }
810 
811 // Print set of data points to a stream.
operator <<(ostream & os,const SurfData & sd)812 ostream& operator<<(ostream& os, const SurfData& sd)
813 {
814   sd.writeText(os);
815   return os;
816 }
817 
818 // ____________________________________________________________________________
819 // Helper methods
820 // ____________________________________________________________________________
821 
822 /// Returns true if file has .bspd extension, false if it has .spd extension.
823 /// Otherwise, an exception is thrown.
hasBinaryFileExtension(const string & filename) const824 bool SurfData::hasBinaryFileExtension(const string& filename) const
825 {
826   if (surfpack::hasExtension(filename,".bspd")) {
827     return true;
828   } else if (surfpack::hasExtension(filename,".spd")) {
829     return false;
830   } else if (surfpack::hasExtension(filename,".dat")) {
831     return false;
832   } else {
833     throw surfpack::io_exception(
834       "Unrecognized filename extension.  Use .bspd, or .spd"
835     );
836   }
837 }
838 
839 /// Set x vars labels to x0 x1, etc.; resp. vars to f0 f1, etc.
defaultLabels()840 void SurfData::defaultLabels()
841 {
842   xLabels.resize(xsize);
843   for (unsigned i = 0; i < xsize; i++) {
844     ostringstream os;
845     os << "x" << i ;
846     xLabels[i] = os.str();
847   }
848   fLabels.resize(fsize);
849   for (unsigned i = 0; i < fsize; i++) {
850     ostringstream os;
851     os << "f" << i ;
852     fLabels[i] = os.str();
853   }
854 }
855 
readLabelsIfPresent(string single_line)856 bool SurfData::readLabelsIfPresent(string single_line)
857 {
858   if (single_line[0] != '%') {
859     defaultLabels();
860     return false;
861   } else {
862     single_line[0] = ' ';
863     string label;
864     xLabels.resize(xsize);
865     istringstream is(single_line);
866     for (unsigned i = 0; i < xsize; i++) {
867       is >> xLabels[i];
868       if (xLabels[i] == "") {
869         // not enough heading names
870         // line of column headings.  Use the default headings and return.
871         defaultLabels();
872         return false;
873       }
874     } // predictor variable labels
875     fLabels.resize(fsize);
876     for (unsigned i = 0; i < fsize; i++) {
877       is >> fLabels[i];
878       if (fLabels[i] == "") {
879         // not enough heading names
880         // line of column headings.  Use the default headings and return.
881         defaultLabels();
882         return false;
883       }
884     } // response variable labels
885   } // custom labels
886   return true;
887 }
888 // ____________________________________________________________________________
889 // Testing
890 // ____________________________________________________________________________
891 
892 // Throw an exception if there are any mismatches in the number of
893 // dimensions or number of response values among points in the data set
sanityCheck() const894 void SurfData::sanityCheck() const
895 {
896   if (!points.empty()) {
897     unsigned dimensionality = points[0]->xSize();
898     unsigned numResponses = points[0]->fSize();
899     unsigned numGrad = points[0]->fGradientsSize();
900     unsigned numHess = points[0]->fHessiansSize();
901     for (unsigned i = 1; i < points.size(); i++) {
902       if (points[i]->xSize() != dimensionality ||
903           points[i]->fSize() != numResponses ||
904           points[i]->fGradientsSize() != numGrad ||
905           points[i]->fHessiansSize() != numHess ) {
906         ostringstream errormsg;
907         errormsg << "Error in SurfData::sanityCheck." << endl
908 		 << "Point 0 has " << dimensionality << " dimensions "
909                  << "and " << numResponses << " response values, " << endl
910                  << "but point " << i << " has " << points[i]->xSize()
911  		 << " dimensions and " << points[i]->fSize() << "response "
912  		 << " values. (Or gradient and Hessian sizes are wrong.)";
913 	throw bad_surf_data(errormsg.str());
914       } // if mismatch
915     } // for each point
916   } // if !empty
917 }
918 
919 /// Check that the index falls within acceptable boundaries (i.e., is
920 /// less than mapping.size()
checkRangeNumPoints(const string & header,unsigned index) const921 void SurfData::checkRangeNumPoints(const string& header, unsigned index) const
922 {
923   if (index >= mapping.size()) {
924     ostringstream errormsg;
925     errormsg << header << endl;
926     if (mapping.empty()) {
927       errormsg << "Index " << index << " specified, but there are zero points "
928 	       << "in the logical data set."
929                << endl;
930     } else {
931       errormsg << "Requested: "
932 	     << index
933 	     << "; actual max index: "
934 	     << mapping.size() - 1
935 	     << endl;
936     }
937     throw range_error(errormsg.str());
938   }
939 }
940 
941 /// Make sure an index falls within acceptable boundaries (i.e., index is less
942 /// than fsize)
checkRangeNumResponses(const string & header,unsigned index) const943 void SurfData::checkRangeNumResponses(const string& header,
944   unsigned index) const
945 {
946   if (index >= fsize) {
947     ostringstream errormsg;
948     errormsg << header << endl;
949     if (fsize == 0) {
950       errormsg << "Index " << index
951 	       << " specified, but there are zero response"
952 	       << "values."
953                << endl;
954     } else {
955       errormsg << "Requested: "
956 	     << index
957 	     << "; actual max index: "
958 	     << fsize - 1
959 	     << endl;
960     }
961     throw range_error(errormsg.str());
962   }
963 }
964 
asVecVecDbl(const SurfData & data)965 VecVecDbl SurfData::asVecVecDbl(const SurfData& data)
966 {
967   VecVecDbl result(data.size());
968   for (unsigned i = 0; i < data.size(); i++) {
969     result[i].resize(data.xSize());
970     for (unsigned j = 0; j < data.xSize(); j++) {
971       result[i][j] = data(i,j);
972     }
973   }
974   return result;
975 }
976