UltraScan III
us_2dsa.cpp
Go to the documentation of this file.
1 
3 #include <QApplication>
4 #include <QtSvg>
5 
6 #include "us_2dsa.h"
7 #include "us_resids_bitmap.h"
8 #include "us_plot_control_2d.h"
10 #include "us_license_t.h"
11 #include "us_license.h"
12 #include "us_settings.h"
13 #include "us_gui_settings.h"
14 #include "us_matrix.h"
15 #include "us_constants.h"
16 #include "us_analyte_gui.h"
17 #include "us_passwd.h"
18 #include "us_db2.h"
19 #include "us_data_loader.h"
20 #include "us_util.h"
21 #include "us_investigator.h"
22 #include "us_noise_loader.h"
23 #include "us_loadable_noise.h"
24 
26 // the class US_2dsa.
27 
28 int main( int argc, char* argv[] )
29 {
30  QApplication application( argc, argv );
31 
32  #include "main1.inc"
33 
34  // License is OK. Start up.
35 
36  US_2dsa w;
37  w.show();
38  return application.exec();
39 }
40 
41 // constructor, based on AnalysisBase
43 {
44  setWindowTitle( tr( "2-Dimensional Spectrum Analysis" ) );
45  setObjectName( "US_2dsa" );
47  // Insure working etc is populated with color maps
48  clean_etc_dir();
49 
50  // Build local and 2dsa-specific GUI elements
51  te_results = NULL;
52  baserss = 0;
53 
54  QLabel* lb_analysis = us_banner( tr( "Analysis Controls" ) );
55  QLabel* lb_scan = us_banner( tr( "Scan Control" ) );
56 
57  QLabel* lb_status = us_label ( tr( "Status\nInfo:" ) );
59  QLabel* lb_from = us_label ( tr( "Scan focus from:" ) );
60  QLabel* lb_to = us_label ( tr( "to:" ) );
61 
62  pb_exclude = us_pushbutton( tr( "Exclude Scan Range" ) );
63  pb_exclude->setEnabled( false );
64  connect( pb_exclude, SIGNAL( clicked() ), SLOT( exclude() ) );
65 
66  connect( ct_from, SIGNAL( valueChanged( double ) ),
67  SLOT ( exclude_from( double ) ) );
68  connect( ct_to, SIGNAL( valueChanged( double ) ),
69  SLOT ( exclude_to ( double ) ) );
70  pb_fitcntl = us_pushbutton( tr( "Fit Control" ) );
71  pb_plt3d = us_pushbutton( tr( "3-D Plot" ) );
72  pb_pltres = us_pushbutton( tr( "Residual Plot" ) );
73 
74  connect( pb_fitcntl, SIGNAL( clicked() ), SLOT( open_fitcntl() ) );
75  connect( pb_plt3d, SIGNAL( clicked() ), SLOT( open_3dplot() ) );
76  connect( pb_pltres, SIGNAL( clicked() ), SLOT( open_resplot() ) );
77 
78  // To modify controls layout, first make Base elements invisible
79 
80  QWidget* widg;
81  for ( int ii = 0; ii < controlsLayout->count(); ii++ )
82  {
83  if ( ( widg = controlsLayout->itemAt( ii )->widget() ) != 0 )
84  widg ->setVisible( false );
85  }
86  ct_from ->setVisible(true);
87  ct_to ->setVisible(true);
88  pb_exclude ->setVisible(true);
89  pb_reset_exclude->setVisible(true);
90  // Effectively disable boundaries to turn off cyan portion of plot2
91  ct_boundaryPercent->disconnect();
92  ct_boundaryPercent->setRange ( 0.0, 300.0, 1.0 );
93  ct_boundaryPercent->setValue ( 300.0 );
94  ct_boundaryPercent->setEnabled( false );
95  ct_boundaryPos ->disconnect();
96  ct_boundaryPos ->setRange ( -50.0, 300.0, 1.0 );
97  ct_boundaryPos ->setValue ( -50.0 );
98  ct_boundaryPos ->setEnabled( false );
99 
100 
101  // Add variance and rmsd to parameters layout
102  QLabel* lb_vari = us_label ( tr( "Variance:" ) );
103  le_vari = us_lineedit( "0.00000", 0, true );
104  QLabel* lb_rmsd = us_label ( tr( "RMSD:" ) );
105  le_rmsd = us_lineedit( "0.00000", 0, true );
106  int row = parameterLayout->rowCount();
107  parameterLayout->addWidget( lb_vari, row, 0, 1, 1 );
108  parameterLayout->addWidget( le_vari, row, 1, 1, 1 );
109  parameterLayout->addWidget( lb_rmsd, row, 2, 1, 1 );
110  parameterLayout->addWidget( le_rmsd, row++, 3, 1, 1 );
111 
112  // Reconstruct controls layout with some 2dsa-specific elements
113  row = 0;
114  controlsLayout->addWidget( lb_scan, row++, 0, 1, 3 );
115  controlsLayout->addWidget( lb_from, row, 0, 1, 1 );
116  controlsLayout->addWidget( ct_from, row++, 1, 1, 2 );
117  controlsLayout->addWidget( lb_to, row, 0, 1, 1 );
118  controlsLayout->addWidget( ct_to, row++, 1, 1, 2 );
119  controlsLayout->addWidget( pb_exclude, row, 0, 1, 1 );
120  controlsLayout->addWidget( pb_reset_exclude, row++, 1, 1, 2 );
121  controlsLayout->addWidget( lb_analysis, row++, 0, 1, 3 );
122  controlsLayout->addWidget( pb_fitcntl, row, 0, 1, 1 );
123  controlsLayout->addWidget( pb_plt3d, row, 1, 1, 1 );
124  controlsLayout->addWidget( pb_pltres, row++, 2, 1, 1 );
125  controlsLayout->addWidget( lb_status, row, 0, 1, 1 );
126  controlsLayout->addWidget( te_status, row, 1, 1, 2 );
127  row += 3;
128 
129  // Set initial status text
130  te_status->setAlignment( Qt::AlignCenter | Qt::AlignVCenter );
131  te_status->setText( tr(
132  "Solution not initiated...\n"
133  "RMSD: 0.000000,\n"
134  "Variance: 0.000000e-05 .\n"
135  "Iterations: 0" ) );
136  us_setReadOnly( te_status, true );
137 
138  connect( pb_help, SIGNAL( clicked() ), SLOT( help() ) );
139  connect( pb_view, SIGNAL( clicked() ), SLOT( view() ) );
140  connect( pb_save, SIGNAL( clicked() ), SLOT( save() ) );
141 
142  pb_view ->setEnabled( false );
143  pb_save ->setEnabled( false );
144  pb_fitcntl->setEnabled( false );
145  pb_plt3d ->setEnabled( false );
146  pb_pltres ->setEnabled( false );
147  pb_exclude->setEnabled( false );
148  ct_from ->setEnabled( false );
149  ct_to ->setEnabled( false );
150 
151  edata = 0;
152  resplotd = 0;
153  eplotcd = 0;
154  analcd = 0;
155 
156  rbd_pos = this->pos() + QPoint( 100, 100 );
157  epd_pos = this->pos() + QPoint( 400, 200 );
158  acd_pos = this->pos() + QPoint( 500, 50 );
159 
160  dsets.clear();
161  dsets << &dset;
162 }
163 
164 // slot to handle the completion of a 2-D spectrum analysis stage
165 void US_2dsa::analysis_done( int updflag )
166 {
167  if ( updflag == (-1) )
168  { // fit has been aborted or reset for new fit
169  pb_view ->setEnabled( false );
170  pb_save ->setEnabled( false );
171  pb_plt3d ->setEnabled( false );
172  pb_pltres ->setEnabled( false );
173  models .clear();
174  ti_noises.clear();
175  ri_noises.clear();
176 
177  qApp->processEvents();
178  return;
179  }
180 
181  if ( updflag == (-2) )
182  { // update model,noise lists and RMSD
183  models << model;
184 
185  if ( ti_noise.count > 0 )
186  ti_noises << ti_noise;
187 
188  if ( ri_noise.count > 0 )
189  ri_noises << ri_noise;
190 
191  QString mdesc = model.description;
192  QString avari = mdesc.mid( mdesc.indexOf( "VARI=" ) + 5 );
193  double vari = avari.section( " ", 0, 0 ).toDouble();
194  double rmsd = sqrt( vari );
195  le_vari->setText( QString::number( vari ) );
196  le_rmsd->setText( QString::number( rmsd ) );
197 DbgLv(1) << "Analysis Done VARI" << vari << "model,noise counts"
198  << models.count() << ti_noises.count();
199 
200  qApp->processEvents();
201  return;
202  }
203 
204  // if here, an analysis is all done
205 
206  bool plotdata = updflag == 1;
207  bool savedata = updflag == 2;
208 
209 DbgLv(1) << "Analysis Done" << updflag;
210 DbgLv(1) << " model components size" << model.components.size();
211 DbgLv(1) << " edat0 sdat0 rdat0 tnoi0"
212  << edata->value(0,0) << sdata.value(0,0) << rdata.value(0,0)
213  << ((ti_noise.count>0)?ti_noise.values[0]:0.0);
214 
215  pb_plt3d ->setEnabled( true );
216  pb_pltres->setEnabled( true );
217  pb_view ->setEnabled( true );
218  pb_save ->setEnabled( true );
219 
220  data_plot();
221 
222  if ( plotdata )
223  {
224  open_3dplot();
225  open_resplot();
226  }
227 
228  else if ( savedata )
229  {
230  save();
231  }
232 }
233 
234 // load the experiment data, mostly thru AnalysisBase; then disable view,save
235 void US_2dsa::load( void )
236 {
237  US_AnalysisBase2::load(); // load edited experiment data
238 
239  if ( !dataLoaded ) return;
240 
241  pb_view->setEnabled( false ); // disable view,save buttons for now
242  pb_save->setEnabled( false );
243 
244  loadDB = disk_controls->db();
245  edata = &dataList[ 0 ]; // point to first loaded data
246  baserss = 0;
247  speed_steps.clear();
248  ti_noises .clear();
249  ri_noises .clear();
250 
251  // Get speed steps from disk or DB experiment
252  if ( loadDB )
253  { // Fetch the speed steps for the experiment from the database
254  US_Passwd pw;
255  US_DB2* dbP = new US_DB2( pw.getPasswd() );
256  QStringList query;
257  QString expID;
258  int idExp = 0;
259  query << "get_experiment_info_by_runID"
260  << runID
261  << QString::number( US_Settings::us_inv_ID() );
262  dbP->query( query );
263 
264  if ( dbP->lastErrno() == US_DB2::OK )
265  {
266  dbP->next();
267  idExp = dbP->value( 1 ).toInt();
269 DbgLv(1) << "SS: ss count" << speed_steps.count() << "idExp" << idExp;
270 if (speed_steps.count()>0 )
271 DbgLv(1) << "SS: ss0 w2tfirst w2tlast timefirst timelast"
272  << speed_steps[0].w2t_first << speed_steps[0].w2t_last
273  << speed_steps[0].time_first << speed_steps[0].time_last;
274  }
275  }
276 
277  else
278  { // Read run experiment file and parse out speed steps
279  QString expfpath = directory + "/" + runID + "."
280  + edata->dataType + ".xml";
281 DbgLv(1) << "LD: expf path" << expfpath;
282  QFile xfi( expfpath )
283  ;
284  if ( xfi.open( QIODevice::ReadOnly ) )
285  { // Read and parse "<speedstep>" lines in the XML
286  QXmlStreamReader xmli( &xfi );
287 
288  while ( ! xmli.atEnd() )
289  {
290  xmli.readNext();
291 
292  if ( xmli.isStartElement() && xmli.name() == "speedstep" )
293  {
294  SP_SPEEDPROFILE sp;
296  speed_steps << sp;
297 DbgLv(1) << "LD: sp: rotspeed" << sp.rotorspeed << "t1" << sp.time_first;
298  }
299  }
300 
301  xfi.close();
302  }
303  }
304 
305  exp_steps = ( speed_steps.count() > 0 ); // Flag any multi-step experiment
306 int nesc=edata->scanData.size();
307 int etm1=edata->scanData[0].seconds;
308 double eom1=edata->scanData[0].omega2t;
309 int etm2=edata->scanData[nesc-1].seconds;
310 double eom2=edata->scanData[nesc-1].omega2t;
311 int nssp=speed_steps.count();
312 int nssc=(nssp<1)?0:speed_steps[nssp-1].scans;
313 DbgLv(1) << "LD:sp: nesc nssp nssc" << nesc << nssp << nssc;
314 DbgLv(1) << "LD:sp: etm1 etm2 eom1 eom2" << etm1 << etm2 << eom1 << eom2;
315 if(nssp>0) {
316  int stm1=speed_steps[nssp-1].time_first;
317  double som1=speed_steps[nssp-1].w2t_first;
318  int stm2=speed_steps[nssp-1].time_last;
319  double som2=speed_steps[nssp-1].w2t_last;
320  DbgLv(1) << "LD:sp: stm1 stm2 som1 som2" << stm1 << stm2 << som1 << som2; }
321 
322 }
323 
324 // plot the data
325 void US_2dsa::data_plot( void )
326 {
327  // Disable base2 cyan boundary portion
328  ct_boundaryPercent->setValue( 300.0 );
329  ct_boundaryPos ->setValue( -50.0 );
330 
331 DbgLv(1) << "Data Plot by Base";
332  US_AnalysisBase2::data_plot(); // plot experiment data
333 DbgLv(1) << "Data Plot from Base";
334 
335  pb_fitcntl->setEnabled( true );
336  ct_from ->setEnabled( true );
337  ct_to ->setEnabled( true );
338 
339  if ( ! dataLoaded ||
340  sdata.scanData.size() != edata->scanData.size() )
341  return;
342 
343  // set up to plot simulation data and residuals
344  int npoints = edata->pointCount();
345  int nscans = edata->scanCount();
346  int count = ( npoints > nscans ) ? npoints : nscans;
347 
348  QVector< double > rvec( count, 0.0 );
349  QVector< double > vvec( count, 0.0 );
350  double* ra = rvec.data();
351  double* va = vvec.data();
352  QString title;
353  QwtPlotCurve* cc;
354  QPen pen_red( Qt::red );
355  QPen pen_cyan( Qt::cyan );
356  QPen pen_plot( Qt::green );
357 
358  bool have_ri = ri_noise.count > 0;
359  bool have_ti = ti_noise.count > 0;
360  double rl = edata->radius( 0 );
361  double vh = edata->value( 0, 0 ) * 0.05;
362  rl -= 0.05;
363  vh += ( vh - edata->value( 0, 0 ) ) * 0.05;
364 
365  // plot the simulation data in red on top of experiment data
366  for ( int ii = 0; ii < nscans; ii++ )
367  {
368  if ( excludedScans.contains( ii ) ) continue;
369 
370  int kk = 0;
371  double rr = 0.0;
372  double vv = 0.0;
373  double rn = have_ri ? ri_noise.values[ ii ] : 0.0;
374 
375  for ( int jj = 0; jj < npoints; jj++ )
376  {
377  double tn = have_ti ? ti_noise.values[ jj ] : 0.0;
378  rr = sdata.radius( jj );
379  vv = sdata.value( ii, jj++ ) + rn + tn;
380  if ( rr > rl )
381  {
382  ra[ kk ] = rr;
383  va[ kk++ ] = vv;
384  }
385  }
386  title = "SimCurve " + QString::number( ii );
387  cc = us_curve( data_plot2, title );
388  cc->setPen( pen_red );
389  cc->setData( ra, va, kk );
390  }
391 
392  data_plot2->replot(); // replot combined exper,simul data
393 
394  data_plot1->detachItems();
395  data_plot1->clear();
396 
397  us_grid( data_plot1 );
398  data_plot1->setAxisTitle( QwtPlot::xBottom, tr( "Radius (cm)" ) );
399  data_plot1->setAxisTitle( QwtPlot::yLeft, tr( "OD Difference" ) );
400  double vari = 0.0;
401  int kntva = 0;
402 
403  // build vector of radius values
404  for ( int jj = 0; jj < npoints; jj++ )
405  ra[ jj ] = sdata.radius( jj );
406 
407  // build and plot residual points
408  for ( int ii = 0; ii < nscans; ii++ )
409  {
410  double rinoi = have_ri ? ri_noise.values[ ii ] : 0.0;
411 
412  for ( int jj = 0; jj < npoints; jj++ )
413  {
414  double tinoi = have_ti ? ti_noise.values[ jj ] : 0.0;
415  va[ jj ] = edata->value( ii, jj ) - sdata.value( ii, jj )
416  - tinoi - rinoi;
417  vari += sq( va[ jj ] );
418  kntva++;
419  }
420 
421  // plot dots of residuals at current scan
422  title = "resids " + QString::number( ii );
423  cc = us_curve( data_plot1, title );
424  cc->setPen( pen_plot );
425  cc->setStyle( QwtPlotCurve::Dots );
426  cc->setData( ra, va, npoints );
427  }
428 
429  data_plot1->setAxisAutoScale( QwtPlot::xBottom );
430  data_plot1->setAxisAutoScale( QwtPlot::yLeft );
431  data_plot1->setTitle( tr( "Residuals" ) );
432 
433  // plot zero line through residuals plot
434  double xlo = edata->radius( 0 ) - 0.05;
435  double xhi = edata->radius( npoints - 1 ) + 0.05;
436  ra[ 0 ] = xlo;
437  ra[ 1 ] = xhi;
438  va[ 0 ] = 0.0;
439  va[ 1 ] = 0.0;
440  cc = us_curve( data_plot1, "zero-line" );
441  cc->setPen( QPen( QBrush( Qt::red ), 2 ) );
442  cc->setData( ra, va, 2 );
443 
444  // draw the plot
445  data_plot1->setAxisScale( QwtPlot::xBottom, xlo, xhi );
446  data_plot1->replot();
447 
448  // report on variance and rmsd
449  vari /= (double)( kntva );
450  rmsd = sqrt( vari );
451  le_vari->setText( QString::number( vari ) );
452  le_rmsd->setText( QString::number( rmsd ) );
453 DbgLv(1) << "Data Plot VARI" << vari;
454 }
455 
456 // view data report
457 void US_2dsa::view( void )
458 {
459  // Create the report text
460  QString rtext;
461  QTextStream ts( &rtext );
462  write_report( ts );
463 
464  // Create US_Editor and display report
465  if ( te_results == NULL )
466  {
467  te_results = new US_Editor( US_Editor::DEFAULT, true, QString(), this );
468  te_results->resize( 780, 700 );
469  QPoint p = g.global_position();
470  te_results->move( p.x() + 30, p.y() + 30 );
471  te_results->e->setFont( QFont( US_GuiSettings::fontFamily(),
473  }
474 
475  te_results->e->setHtml( rtext );
476  te_results->show();
477 }
478 
479 // Save data (model,noise), report, and PNG image files
480 void US_2dsa::save( void )
481 {
482  QString analysisDate = QDateTime::currentDateTime().toUTC()
483  .toString( "yyMMddhhmm" );
484  QString reqGUID = US_Util::new_guid();
485  QString runID = edata->runID;
486  QString editID = edata->editID.startsWith( "20" ) ?
487  edata->editID.mid( 2 ) :
488  edata->editID;
489  QString dates = "e" + editID + "_a" + analysisDate;
490  bool cusGrid = model.description.contains( "CUSTOMGRID" );
491 DbgLv(1) << "2DSA:SV: cusGrid" << cusGrid << "desc" << model.description;
492  bool fitMeni = ( model.global == US_Model::MENISCUS );
493  bool montCar = model.monteCarlo;
494  QString analysisType = QString( cusGrid ? "2DSA-CG" : "2DSA" )
495  + QString( fitMeni ? "-FM" : "" )
496  + QString( montCar ? "-MC" : "" );
497  QString requestID = "local";
498  QString tripleID = edata->cell + edata->channel + edata->wavelength;
499  QString analysisID = dates + "_" + analysisType + "_" + requestID + "_";
500  QString dext = "." + tripleID;
501  QString dext2 = ".e" + editID + "-" + dext.mid( 1 );
502  QString descbase = runID + "." + tripleID + "." + analysisID;
503 
504  QString reppath = US_Settings::reportDir();
505  QString respath = US_Settings::resultDir();
506  QString mdlpath;
507  QString noipath;
508  int nmodels = models.size(); // number of models to save
509  int knois = min( ti_noise.count, 1 )
510  + min( ri_noise.count, 1 ); // noise files per model
511  int nnoises = nmodels * knois; // number of noises to save
512  double meniscus = edata->meniscus;
513  double dwavelen = edata->wavelength.toDouble();
514  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
515 
516  // Test existence or create needed subdirectories
517  if ( ! mkdir( reppath, runID ) )
518  {
519  qDebug() << "*** Unable to create or find the report directory ***";
520  return;
521  }
522 
523  if ( ! US_Model::model_path( mdlpath ) )
524  {
525  qDebug() << "*** Unable to create or find the model directory ***";
526  return;
527  }
528 
529  if ( knois > 0 && ! US_Noise::noise_path( noipath ) )
530  {
531  qDebug() << "*** Unable to create or find the noise directory ***";
532  return;
533  }
534 
535  // Save the model and any noise file(s)
536 
537  US_Passwd pw;
538  US_DB2* dbP = loadDB ? new US_DB2( pw.getPasswd() ): NULL;
539  QDir dirm( mdlpath );
540  QDir dirn( noipath );
541  mdlpath += "/";
542  noipath += "/";
543  QStringList mfilt( "M*.xml" );
544  QStringList nfilt( "N*.xml" );
545  QStringList mdnams = dirm.entryList( mfilt, QDir::Files, QDir::Name );
546  QStringList ndnams = dirn.entryList( nfilt, QDir::Files, QDir::Name );
547  QStringList mnames;
548  QStringList nnames;
549  QStringList tnames;
550  QString mname = "M0000000.xml";
551  QString nname = "N0000000.xml";
552  QString tmppath = US_Settings::tmpDir() + "/";
553  int indx = 1;
554  int kmodels = 0;
555  int knoises = 0;
556  bool have_ti = ( ti_noises.size() > 0 );
557  bool have_ri = ( ri_noises.size() > 0 );
558 
559  while( indx > 0 )
560  { // build a list of available model file names
561  mname = "M" + QString().sprintf( "%07i", indx++ ) + ".xml";
562  if ( ! mdnams.contains( mname ) )
563  { // no name with this index exists, so add it new-name list
564  mnames << mname;
565  if ( ++kmodels >= nmodels )
566  break;
567  }
568  }
569 
570  indx = 1;
571 
572  while( indx > 0 )
573  { // build a list of available noise file names
574  nname = "N" + QString().sprintf( "%07i", indx++ ) + ".xml";
575  if ( ! ndnams.contains( nname ) )
576  { // add to the list of new-name noises
577  nnames << nname;
578  if ( ++knoises >= nnoises )
579  break;
580  }
581  }
582 //double tino = ti_noise.count > 0 ? ti_noise.values[0] : 0.0;
583 //double tini = ti_noise_in.count > 0 ? ti_noise_in.values[0] : 0.0;
584 //double rino = ri_noise.count > 0 ? ri_noise.values[0] : 0.0;
585 //double rini = ri_noise_in.count > 0 ? ri_noise_in.values[0] : 0.0;
586 //DbgLv(1) << " Pre-sum tno tni" << tino << tini << "rno rni" << rino << rini;
587 
588  for ( int jj = 0; jj < nmodels; jj++ )
589  { // loop to output models and noises
590  model = models[ jj ]; // model to output
591  QString mdesc = model.description; // description from processor
592  double variance = mdesc.mid( mdesc.indexOf( "VARI=" ) + 5 )
593  .section( ' ', 0, 0 ).toDouble();
594  // create the iteration part of model description:
595  // e.g.,"i01" normally; "i03-m60190" for meniscus; "mc017" for monte carlo
596  QString iterID = "i01";
597  int iterNum = jj + 1;
598 
599  if ( montCar )
600  iterID.sprintf( "mc%04d", iterNum );
601 
602  else if ( fitMeni )
603  {
604  meniscus = mdesc.mid( mdesc.indexOf( "MENISCUS=" ) + 9 )
605  .section( ' ', 0, 0 ).toDouble();
606  iterID.sprintf( "i%02d-m%05d", iterNum, qRound( meniscus * 10000 ) );
607  }
608 
609  // fill in actual model parameters needed for output
610  model.description = descbase + iterID + ".model";
611  mname = QString( model.description );
612  mname = montCar ?
613  tmppath + mname.replace( ".model", ".mdl.tmp" ) :
614  mdlpath + mnames[ jj ];
617  model.requestGUID = reqGUID;
619  model.variance = variance;
620  model.meniscus = meniscus;
621  model.wavelength = dwavelen;
623 
624  for ( int cc = 0; cc < model.components.size(); cc++ )
625  model.components[ cc ].name = QString().sprintf( "SC%04d", cc + 1 );
626 
627  // output the model
628  if ( dbP == NULL || montCar )
629  model.write( mname );
630  else
631  model.write( dbP );
632 
633  if ( montCar )
634  tnames << mname;
635 
636  int kk = jj * knois;
637 
638  if ( have_ti )
639  { // output a TI noise
640  ti_noise = ti_noises[ jj ];
641  ti_noise.description = descbase + iterID + ".ti_noise";
645  nname = noipath + nnames[ kk++ ];
646  int nicount = ti_noise_in.count;
647 
648  if ( nicount > 0 ) // Sum in any input noise
649  ti_noise.sum_noise( ti_noise_in, true );
650 
651  ti_noise.write( nname );
652 
653  if ( dbP != NULL )
654  ti_noise.write( dbP );
655 
656  if ( nicount > 0 ) // Remove input noise in case re-plotted
657  {
658  US_Noise noise_rmv = ti_noise_in;
659 
660  for ( int kk = 0; kk < nicount; kk++ )
661  noise_rmv.values[ kk ] *= -1.0;
662 
663  ti_noise.sum_noise( noise_rmv, true );
664  }
665  }
666 
667  if ( have_ri )
668  { // output an RI noise
669  ri_noise = ri_noises[ jj ];
670  ri_noise.description = descbase + iterID + ".ri_noise";
674  nname = noipath + nnames[ kk++ ];
675  int nicount = ri_noise_in.count;
676 
677  if ( nicount > 0 ) // Sum in any input noise
678  ri_noise.sum_noise( ri_noise_in, true );
679 
680  ri_noise.write( nname );
681 
682  if ( dbP != NULL )
683  ri_noise.write( dbP );
684 
685  if ( nicount > 0 ) // Remove input noise in case re-plotted
686  {
687  US_Noise noise_rmv = ri_noise_in;
688 
689  for ( int kk = 0; kk < nicount; kk++ )
690  noise_rmv.values[ kk ] *= -1.0;
691 
692  ri_noise.sum_noise( noise_rmv, true );
693  }
694  }
695  }
696 //tino = ti_noise.count > 0 ? ti_noise.values[0] : 0.0;
697 //rino = ri_noise.count > 0 ? ri_noise.values[0] : 0.0;
698 //DbgLv(1) << " Post-sum tno rno" << tino << rino;
699  mname = mdlpath + mnames[ 0 ];
700 
701  if ( montCar )
702  { // For Monte Carlo, create a composite of MC iteration files
703  QString tname = US_Model::composite_mc_file( tnames, true );
704  tnames[ 0 ] = tname;
705  QFile( tname ).rename( mname ); // Move/rename to model directory
706 
707  if ( dbP != NULL )
708  { // If DB, load and store in the database
709  model.load( tname );
710  model.write( dbP );
711  }
712  }
713 
714  else
715  { // For non-MC, save the model name for message to follow
716  tnames.clear();
717  tnames << mname;
718  }
719 
720  if ( dbP != NULL )
721  {
722  delete dbP;
723  dbP = NULL;
724  }
725 
726  reppath = reppath + "/" + runID + "/";
727  respath = respath + "/" + runID + "/";
728  QString analybase = fitMeni ? "2DSA-FM" : ( montCar ? "2DSA-MC" : "2DSA" );
729  QString analynode = "/" + analybase + ".";
730  QString filebase = reppath + analybase + dext + ".";
731  QString htmlFile = filebase + "report.html";
732  QString plot1File = filebase + "velocity.svgz";
733  QString plot2File = filebase + "residuals.png";
734  QString plot3File = filebase + "rbitmap.png";
735  QString fitFile = filebase + "fitmen.dat";
736  QString fresFile = respath + "2dsa-fm" + dext2 + ".fitmen.dat";
737  QString dsinfFile = QString( filebase ).replace( analynode, "/dsinfo." )
738  + "dataset_info.html";
739 
740  // Write a general dataset information html file
741  write_dset_report( dsinfFile );
742 
743  // Write HTML report file
744  QFile rep_f( htmlFile );
745 
746  if ( ! rep_f.open( QIODevice::WriteOnly | QIODevice::Text ) )
747  return;
748 
749  QTextStream ts( &rep_f );
750  write_report( ts );
751  rep_f.close();
752 
753  // Write plots
754  write_plot( plot1File, data_plot2 );
755  write_plot( plot2File, data_plot1 );
756  write_bmap( plot3File );
757 
758  // use a dialog to tell the user what we've output
759  QString wmsg = tr( "Wrote:\n" );
760 
761  mname = loadDB ? tnames[ 0 ] : mdlpath + mnames[ 0 ];
762  wmsg = wmsg + mname + "\n"; // list 1st (only?) model file
763 
764  if ( knois > 0 )
765  {
766  nname = noipath + nnames[ 0 ]; // list 1st noise file(s)
767  wmsg = wmsg + nname + "\n";
768 
769  if ( knois > 1 )
770  {
771  nname = noipath + nnames[ 1 ];
772  wmsg = wmsg + nname + "\n";
773  }
774  }
775 
776  if ( nmodels > 1 && ! montCar )
777  {
778  int kk = ( nmodels - 2 ) * ( knois + 1 );
779  wmsg = wmsg + " ... ( "
780  + QString::number( kk ) + tr( " files unshown )\n" );
781  kk = nmodels - 1;
782  mname = mdlpath + mnames[ kk ]; // list last model file
783  wmsg = wmsg + mname + "\n";
784 
785  if ( knois > 0 )
786  { // list last noise file(s)
787  kk *= knois;
788  nname = noipath + nnames[ kk++ ];
789  wmsg = wmsg + nname + "\n";
790 
791  if ( knois > 1 )
792  {
793  nname = noipath + nnames[ kk ];
794  wmsg = wmsg + nname + "\n";
795  }
796  }
797  }
798 
799  // list report and plot files
800  wmsg = wmsg + htmlFile + "\n"
801  + plot1File + "\n"
802  + plot2File + "\n"
803  + plot3File + "\n";
804  QStringList repfiles;
805  update_filelist( repfiles, htmlFile );
806  update_filelist( repfiles, plot1File );
807  update_filelist( repfiles, plot2File );
808  update_filelist( repfiles, plot3File );
809 
810  // Add fit files if fit-meniscus
811  if ( fitMeni )
812  {
813  QString fitstr = fit_meniscus_data();
814 
815  QFile rep_f( fitFile );
816  QFile res_f( fresFile );
817 
818  if ( rep_f.open( QIODevice::WriteOnly | QIODevice::Text ) )
819  {
820  QTextStream ts( &rep_f );
821  ts << fitstr;
822  rep_f.close();
823  wmsg = wmsg + fitFile + "\n";
824  update_filelist( repfiles, fitFile );
825  }
826 
827  if ( res_f.open( QIODevice::WriteOnly | QIODevice::Text ) )
828  {
829  QTextStream ts( &res_f );
830  ts << fitstr;
831  res_f.close();
832  wmsg = wmsg + fresFile + "\n";
833  }
834  }
835 
836  wmsg = wmsg + dsinfFile + "\n";
837  update_filelist( repfiles, dsinfFile );
838 
839  if ( disk_controls->db() )
840  { // Write report files to the database
841  reportFilesToDB( repfiles );
842 
843  wmsg += tr( "\nReport files were also saved to the database." );
844  }
845 
846  QApplication::restoreOverrideCursor();
847  QMessageBox::information( this, tr( "Successfully Written" ), wmsg );
848 }
849 
850 // Return pointer to main window edited data
852 {
853  int drow = lw_triples->currentRow();
854  edata = ( drow >= 0 ) ? &dataList[ drow ] : 0;
855 
856 DbgLv(1) << "(M)mw_ed" << edata;
857  return edata;
858 }
859 
860 // Return pointers to main window data and GUI elements
861 
864 QList< int >* US_2dsa::mw_excllist() { return &excludedScans;}
868 QPointer< QTextEdit > US_2dsa::mw_status_text() { return te_status; }
869 int* US_2dsa::mw_base_rss() { return &baserss; }
870 
871 // Open residuals plot window
873 {
874  if ( resplotd )
875  {
876  rbd_pos = resplotd->pos();
877  resplotd->close();
878  }
879  else
880  rbd_pos = this->pos() + QPoint( 100, 100 );
881 
882  resplotd = new US_ResidPlot2D( this );
883  resplotd->move( rbd_pos );
884  resplotd->setVisible( true );
885 }
886 
887 // Open 3-D plot control window
889 {
890  if ( eplotcd )
891  {
892  epd_pos = eplotcd->pos();
893  eplotcd->close();
894  }
895  else
896  epd_pos = this->pos() + QPoint( 400, 200 );
897 
898  eplotcd = new US_PlotControl2D( this, &model );
899  eplotcd->move( epd_pos );
900  eplotcd->show();
901 }
902 
903 // Open fit analysis control window
905 {
906  int drow = lw_triples->currentRow();
907  if ( drow < 0 ) return;
908 
909  edata = &dataList[ drow ];
910  double avTemp = edata->average_temperature();
911  double vbar20 = US_Math2::calcCommonVbar( solution_rec, 20.0 );
912  double vbartb = US_Math2::calcCommonVbar( solution_rec, avTemp );
913  double buoy = 1.0 - vbar20 * DENS_20W;
914 
915  if ( buoy <= 0.0 )
916  {
917  QMessageBox::critical( this, tr( "Negative Buoyancy Implied" ),
918  tr( "The current vbar20 value (%1) implies a buoyancy\n"
919  "value (%2) that is non-positive.\n\n"
920  "2DSA cannot proceed with this value. Click on the\n"
921  "<Solution> button and change the vbar20 value.\n"
922  "Note that the Solution may be accepted without being saved.\n"
923  "Include negative values in the sedimentation coefficient\n"
924  "range to represent floating data." ).arg( vbar20 ).arg( buoy ) );
925  return;
926  }
927 
929  sd.density = density;
930  sd.viscosity = viscosity;
931  sd.vbar20 = vbar20;
932  sd.vbar = vbartb;
933  sd.manual = manual;
934  US_Math2::data_correction( avTemp, sd );
935 DbgLv(0) << "2DSA s_corr D_corr" << sd.s20w_correction << sd.D20w_correction
936  << "manual" << sd.manual << "vbar20" << vbar20;
937 DbgLv(0) << "2DSA d_corr v vW vT d dW dT" << sd.viscosity << sd.viscosity_wt
938  << sd.viscosity_tb << sd.density << sd.density_wt << sd.density_tb;
939 
940  US_Passwd pw;
941  loadDB = disk_controls->db();
942  US_DB2* dbP = loadDB ? new US_DB2( pw.getPasswd() ) : NULL;
943 
944  // Initialize simulation parameters from data.
945  // Skip adding speed steps if this is multi-speed, initially,
946  // but set speed steps to the experiment vector.
947  dset.simparams.initFromData( dbP, dataList[ drow ], !exp_steps );
948 
949  if ( exp_steps )
950  {
951  dset.simparams.speed_step = speed_steps;
952  }
953 
954  dset.run_data = dataList[ drow ];
955  dset.viscosity = viscosity;
956  dset.density = density;
957  dset.temperature = avTemp;
958  dset.vbar20 = vbar20;
959  dset.vbartb = vbartb;
960  dset.s20w_correction = sd.s20w_correction;
961  dset.D20w_correction = sd.D20w_correction;
962  dset.manual = manual;
963 DbgLv(1) << "Bottom" << dset.simparams.bottom << "rotorcoeffs"
964  << dset.simparams.rotorcoeffs[0] << dset.simparams.rotorcoeffs[1];
965 
966 DbgLv(1) << "SimulationParameter --";
967 if(dbg_level>0) dset.simparams.debug();
968  if ( dbP != NULL )
969  {
970  delete dbP;
971  dbP = NULL;
972  }
973 
974  if ( analcd != 0 )
975  {
976  acd_pos = analcd->pos();
977  analcd->close();
978  }
979  else
980  acd_pos = this->pos() + QPoint( 500, 50 );
981 
982  analcd = new US_AnalysisControl2D( dsets, loadDB, this );
983  analcd->move( acd_pos );
984  analcd->show();
985  qApp->processEvents();
986 }
987 
988 // Distribution information HTML string
990 {
991  int ncomp = model.components.size();
992 
993  if ( ncomp == 0 )
994  return "";
995 
996  QString maDesc = model.description;
997  QString runID = edata->runID;
998 
999  if ( maDesc.startsWith( runID ) )
1000  { // Saved model: get analysis description from model description
1001  maDesc = maDesc.section( ".", -2, -2 ).section( "_", 1, -1 );
1002  }
1003 
1004  else
1005  { // No saved model yet: compose analysis description
1006  QString adate = "a" + QDateTime::currentDateTime().toUTC()
1007  .toString( "yyMMddhhmm" );
1008  bool cusGrid = model.description.contains( "CUSTOMGRID" );
1009  bool fitMeni = ( model.global == US_Model::MENISCUS );
1010  bool montCar = model.monteCarlo;
1011  QString anType = QString( cusGrid ? "2DSA-CG" : "2DSA" )
1012  + QString( fitMeni ? "-FM" : "" )
1013  + QString( montCar ? "-MC" : "" );
1014  maDesc = adate + "_" + anType + "_local_i01";
1015  }
1016 
1017  QString mstr = "\n" + indent( 4 )
1018  + tr( "<h3>Data Analysis Settings:</h3>\n" )
1019  + indent( 4 ) + "<table>\n";
1020 
1021  mstr += table_row( tr( "Model Analysis:" ),
1022  maDesc );
1023  mstr += table_row( tr( "Number of Components:" ),
1024  QString::number( ncomp ) );
1025  mstr += table_row( tr( "Residual RMS Deviation:" ),
1026  QString::number( rmsd ) );
1027 
1028  double sum_mw = 0.0;
1029  double sum_s = 0.0;
1030  double sum_D = 0.0;
1031  double sum_c = 0.0;
1032  double mink = 1e+99;
1033  double maxk = -1e+99;
1034  double minv = 1e+99;
1035  double maxv = -1e+99;
1036 
1037  for ( int ii = 0; ii < ncomp; ii++ )
1038  {
1039  double conc = model.components[ ii ].signal_concentration;
1040  sum_c += conc;
1041  sum_mw += model.components[ ii ].mw * conc;
1042  sum_s += model.components[ ii ].s * conc;
1043  sum_D += model.components[ ii ].D * conc;
1044  mink = qMin( mink, model.components[ ii ].f_f0 );
1045  maxk = qMax( maxk, model.components[ ii ].f_f0 );
1046  minv = qMin( minv, model.components[ ii ].vbar20 );
1047  maxv = qMax( maxv, model.components[ ii ].vbar20 );
1048  }
1049 
1050  bool cnstvb = ( ( maxk - mink ) / qAbs( maxk )
1051  > ( maxv - minv ) / qAbs( maxv ) );
1052 
1053  mstr += table_row( tr( "Weight Average s20,W:" ),
1054  QString().sprintf( "%6.4e", ( sum_s / sum_c ) ) );
1055  mstr += table_row( tr( "Weight Average D20,W:" ),
1056  QString().sprintf( "%6.4e", ( sum_D / sum_c ) ) );
1057  mstr += table_row( tr( "W.A. Molecular Weight:" ),
1058  QString().sprintf( "%6.4e", ( sum_mw / sum_c ) ) );
1059  mstr += table_row( tr( "Total Concentration:" ),
1060  QString().sprintf( "%6.4e", sum_c ) );
1061 
1062  if ( cnstvb )
1063  mstr += table_row( tr( "Constant Vbar at 20" ) + DEGC + ":",
1064  QString().number( maxv ) );
1065  else
1066  mstr += table_row( tr( "Constant f/f0:" ),
1067  QString().number( maxk ) );
1068 
1069  mstr += indent( 4 ) + "</table>\n\n";
1070 
1071  mstr += indent( 4 ) + tr( "<h3>Distribution Information:</h3>\n" )
1072  + indent( 4 ) + "<table>\n";
1073 
1074  if ( cnstvb )
1075  mstr += table_row( tr( "Molecular Wt." ), tr( "S Apparent" ),
1076  tr( "S 20,W" ), tr( "D Apparent" ),
1077  tr( "D 20,W" ), tr( "f / f0" ),
1078  tr( "Concentration" ) );
1079  else
1080  mstr += table_row( tr( "Molecular Wt." ), tr( "S Apparent" ),
1081  tr( "S 20,W" ), tr( "D Apparent" ),
1082  tr( "D 20,W" ), tr( "Vbar20" ),
1083  tr( "Concentration" ) );
1084 
1085  int drow = lw_triples->currentRow();
1086  edata = &dataList[ drow ];
1087  double avTemp = edata->average_temperature();
1088  double vbar20 = US_Math2::calcCommonVbar( solution_rec, 20.0 );
1089  double vbartb = US_Math2::calcCommonVbar( solution_rec, avTemp );
1091  sd.density = density;
1092  sd.viscosity = viscosity;
1093  sd.vbar20 = vbar20;
1094  sd.vbar = vbartb;
1095  sd.manual = manual;
1096 DbgLv(1) << "Data_Corr manual" << sd.manual;
1097  US_Math2::data_correction( avTemp, sd );
1098 
1099  for ( int ii = 0; ii < ncomp; ii++ )
1100  {
1101  double conc = model.components[ ii ].signal_concentration;
1102  double perc = 100.0 * conc / sum_c;
1103  double s_ap = model.components[ ii ].s;
1104  double D_ap = model.components[ ii ].D;
1105  double f_f0 = model.components[ ii ].f_f0;
1106 
1107  if ( !cnstvb )
1108  {
1109  vbar20 = model.components[ ii ].vbar20;
1110  f_f0 = vbar20;
1111  sd.vbar20 = vbar20;
1112  sd.vbar = US_Math2::adjust_vbar( vbar20, avTemp );
1113  US_Math2::data_correction( avTemp, sd );
1114  }
1115 
1116  s_ap /= sd.s20w_correction;
1117  D_ap /= sd.D20w_correction;
1118 
1119  mstr += table_row(
1120  QString().sprintf( "%10.4e", model.components[ ii ].mw ),
1121  QString().sprintf( "%10.4e", s_ap ),
1122  QString().sprintf( "%10.4e", model.components[ ii ].s ),
1123  QString().sprintf( "%10.4e", D_ap ),
1124  QString().sprintf( "%10.4e", model.components[ ii ].D ),
1125  QString().sprintf( "%10.4e", f_f0 ),
1126  QString().sprintf( "%10.4e (%5.2f %%)", conc, perc ) );
1127  }
1128 
1129  mstr += indent( 4 ) + "</table>\n";
1130 
1131  return mstr;
1132 }
1133 
1134 // Iteration information HTML string
1136 {
1137  int nmodels = models.size();
1138 
1139  if ( nmodels < 2 )
1140  return "";
1141 
1142  model = models[ nmodels - 1 ];
1143  bool fitMeni = ( model.global == US_Model::MENISCUS );
1144  bool montCar = model.monteCarlo;
1145  QString anType = montCar ? "Monte Carlo" : "Fit Meniscus";
1146 
1147  QString mstr = "\n" + indent( 4 )
1148  + tr( "<h3>Multiple Model Settings:</h3>\n" )
1149  + indent( 4 ) + "<table>\n";
1150 
1151  mstr += table_row( tr( "Number of Model Iterations:" ),
1152  QString::number( nmodels ) );
1153  mstr += table_row( tr( "Iteration Analysis Type:" ), anType );
1154 
1155  if ( fitMeni )
1156  {
1157  QString mdesc = models[ 0 ].description;
1158  QString mend1 = mdesc.mid( mdesc.indexOf( "MENISCUS=" ) + 9 );
1159  mdesc = model.description;
1160  QString mend2 = mdesc.mid( mdesc.indexOf( "MENISCUS=" ) + 9 );
1161  double bmenis = edata->meniscus;
1162  double smenis = mend1.toDouble();
1163  double emenis = mend2.toDouble();
1164  double rmenis = emenis - smenis;
1165  mstr += table_row( tr( "Meniscus Range:" ),
1166  QString::number( rmenis ) );
1167  mstr += table_row( tr( "Base Experiment Meniscus:" ),
1168  QString::number( bmenis ) );
1169  mstr += table_row( tr( "Start Fit Meniscus:" ),
1170  QString::number( smenis ) );
1171  mstr += table_row( tr( "End Fit Meniscus:" ),
1172  QString::number( emenis ) );
1173  }
1174 
1175  mstr += indent( 4 ) + "</table>\n\n";
1176 
1177  mstr += indent( 4 ) + tr( "<h3>Fit / Iteration Information:</h3>\n" )
1178  + indent( 4 ) + "<table>\n";
1179 
1180  if ( montCar )
1181  mstr += table_row( tr( "Iteration" ),
1182  tr( "Iteration ID" ),
1183  tr( "RMSD" ) );
1184 
1185  else
1186  mstr += table_row( tr( "Iteration" ),
1187  tr( "Meniscus" ),
1188  tr( "RMSD" ) );
1189 
1190  for ( int ii = 0; ii < nmodels; ii++ )
1191  {
1192  QString itnum = QString::number( ii + 1 ).rightJustified( 4, '_' );
1193  QString mdesc = models[ ii ].description;
1194  QString avari = mdesc.mid( mdesc.indexOf( "VARI=" ) + 5 );
1195  double rmsd = sqrt( avari.section( " ", 0, 0 ).toDouble() );
1196  QString armsd = QString().sprintf( "%10.8f", rmsd );
1197 
1198  if ( montCar )
1199  {
1200  QString itID = QString().sprintf( "i%04i", ii + 1 );
1201  mstr += table_row( itnum, itID, armsd );
1202  }
1203 
1204  else
1205  {
1206  QString ameni = mdesc.mid( mdesc.indexOf( "MENISCUS=" ) + 9 )
1207  .section( " ", 0, 0 );
1208  mstr += table_row( itnum, ameni, armsd );
1209  }
1210  }
1211 
1212  mstr += indent( 4 ) + "</table>\n";
1213 
1214  return mstr;
1215 }
1216 
1217 // Write HTML report file
1218 void US_2dsa::write_report( QTextStream& ts )
1219 {
1220  ts << html_header( QString( "US_2dsa" ),
1221  tr( "2-Dimensional Spectrum Analysis" ),
1222  edata );
1223  ts << distrib_info();
1224  ts << iteration_info();
1225  ts << indent( 2 ) + "</body>\n</html>\n";
1226 }
1227 
1228 // Write resids bitmap plot file
1229 void US_2dsa::write_bmap( const QString plotFile )
1230 {
1231  // Generate the residuals array
1232  bool have_ri = ri_noise.count > 0;
1233  bool have_ti = ti_noise.count > 0;
1234  int npoints = edata->pointCount();
1235  int nscans = edata->scanCount();
1236  QVector< double > resscn( npoints );
1237  QVector< QVector< double > > resids( nscans );
1238 
1239  for ( int ii = 0; ii < nscans; ii++ )
1240  {
1241  double rnoi = have_ri ? ri_noise.values[ ii ] : 0.0;
1242 
1243  for ( int jj = 0; jj < npoints; jj++ )
1244  {
1245  double tnoi = have_ti ? ti_noise.values[ jj ] : 0.0;
1246  resscn[ jj ] = edata->value( ii, jj ) - sdata.value( ii, jj )
1247  - rnoi - tnoi;
1248  }
1249 
1250  resids[ ii ] = resscn;
1251  }
1252 
1253  // Generate the residuals bitmap plot
1254  US_ResidsBitmap* resbmap = new US_ResidsBitmap( resids );
1255  QPixmap pixmap = QPixmap::grabWidget( resbmap, 0, 0,
1256  resbmap->width(), resbmap->height() );
1257 
1258  // Save the pixmap to the specified file
1259  if ( ! pixmap.save( plotFile ) )
1260  qDebug() << "*ERROR* Unable to write file" << plotFile;
1261 
1262  resbmap->close();
1263 }
1264 
1265 // New triple selected
1266 void US_2dsa::new_triple( int index )
1267 {
1268  edata = &dataList[ index ];
1269 
1270  // Restore pure data type string (other values added in 2dsa processing)
1271  edata->dataType = edata->dataType.section( " ", 0, 0 );
1272 
1273  sdata.scanData.clear(); // Clear simulation and upper plot
1274  data_plot1->detachItems();
1275  data_plot1->clear();
1276  models .clear();
1277  ti_noises .clear();
1278  ri_noises .clear();
1279 
1280  // Temporarily restore any loaded noise vectors from triples vectors
1281  ti_noise = tinoises[ index ];
1282  ri_noise = rinoises[ index ];
1283 
1284  US_AnalysisBase2::new_triple( index ); // New triple as in any analysis
1285 
1286  // Move any loaded noise vectors to the "in" versions
1293  tinoises[ index ] = ti_noise_in;
1294  rinoises[ index ] = ri_noise_in;
1295  ti_noise.values.clear();
1296  ri_noise.values.clear();
1297  ti_noise.count = 0;
1298  ri_noise.count = 0;
1299 }
1300 
1301 // Fit meniscus data table string
1303 {
1304  QString mstr = "";
1305  int nmodels = models.size();
1306 
1307  if ( nmodels < 2 )
1308  return mstr;
1309 
1310  for ( int ii = 0; ii < nmodels; ii++ )
1311  {
1312  QString mdesc = models[ ii ].description;
1313  QString avari = mdesc.mid( mdesc.indexOf( "VARI=" ) + 5 );
1314  double rmsd = sqrt( avari.section( " ", 0, 0 ).toDouble() );
1315  QString armsd = QString().sprintf( "%10.8f", rmsd );
1316  QString ameni = mdesc.mid( mdesc.indexOf( "MENISCUS=" ) + 9 )
1317  .section( " ", 0, 0 );
1318  mstr += ( ameni + " " + armsd + "\n" );
1319  }
1320 
1321  return mstr;
1322 }
1323