UltraScan III
us_eqreporter.cpp
Go to the documentation of this file.
1 
3 #include "us_eqreporter.h"
4 #include "us_settings.h"
5 #include "us_gui_settings.h"
6 #include "us_math2.h"
7 #include "us_editor.h"
8 #include "us_constants.h"
9 #include "us_widgets.h"
10 
11 // Main constructor
13  QVector< US_DataIO::EditedData >& dataList,
14  QVector< ScanEdit >& scedits,
15  QVector< EqScanFit >& scanfits,
16  EqRunFit& runfit,
17  QWidget* wparent )
18  : QObject(),
19  dataList ( dataList ),
20  scedits ( scedits ),
21  scanfits ( scanfits ),
22  runfit ( runfit ),
23  wparent ( wparent )
24 {
25 
27  asters = QString().fill( '*', 80 ).append( "\n" );
28 }
29 
30 // Scan Diagnostics and display a report in a text dialog
32 {
33 DbgLv(1) << "SCAN_DIAGS()";
34 
35  QString plsrd = tr( "PLEASE READ THIS!" );
36  QString rs;
37 
38  // Compose opening general notes
39  rs = "\n" + centerInLine( plsrd, 80, false, ' ' ) + "\n\n";
40  rs += tr( "Below is a listing of the ratios of slopes in the endpoints"
41  " of each indicated\n" );
42  rs += tr( "scan. If the ratios are less than 30, then there is little"
43  " information content\n" );
44  rs += tr( "in the scan and chances are that the experiment was"
45  " improperly set up\nand should be repeated.\n\n" );
46  rs += tr( "Additional warnings will be generated if the scan does not"
47  " contain enough\n" );
48  rs += tr( "data points or if the experimenter did not use the majority"
49  " of the linear\n" );
50  rs += tr( "absorbance range available (at least 0.6 OD between 0.0 OD"
51  " and 0.9 OD).\n\n" );
52  rs += tr( "These warnings are for your information only; they have no"
53  " effect on the\n" );
54  rs += tr( "rest of the program, since there are valid exceptions to"
55  " these warnings\n" );
56  rs += tr( "when including such scans is appropriate. Please refer to"
57  " the global\n" );
58  rs += tr( "equilibrium analysis tutorial for more information.\n\n" );
59 
60  bool scprobs = false;
61  int dimvs = dataList[ 0 ].pointCount() * 3 / 2;
62  QVector< double > xvec( dimvs );
63  QVector< double > yvec( dimvs );
64  double* xx = xvec.data();
65  double* yy = yvec.data();
66 
67  // Compose notes on each scan
68  for ( int jes = 0; jes < scedits.size(); jes++ )
69  {
70  int jdx = scedits[ jes ].dsindex; // data set index
71  double radlo = scedits[ jes ].rad_lo; // radius low value
72  double radhi = scedits[ jes ].rad_hi; // radius high value
73 
74  // Scan information header
75  rs += scanInfoHeader( jes, jdx );
76 
77  // Point to data, scan and this scan's data range
78  US_DataIO::EditedData* sdata = &dataList[ jdx ];
79  int ivstx = index_radius( sdata, radlo );
80  int ivenx = index_radius( sdata, radhi );
81  int endx = scanfits[ jes ].stop_ndx;
82  ivenx = min( ivenx, endx );
83  int ivenn = ivenx + 1;
84  int npts = ivenn - ivstx;
85 
86  if ( npts > dimvs )
87  { // If need be (unlikely), resize the work x,y vectors
88  dimvs = npts + 10;
89  xvec.resize( dimvs );
90  yvec.resize( dimvs );
91  xx = xvec.data();
92  yy = yvec.data();
93  }
94 DbgLv(1) << "SDiag: jes" << jes << "ivstx ivenx npts" << ivstx << ivenx << npts;
95 DbgLv(1) << "SDiag: radlo radhi" << radlo << radhi
96  << " rs re" << sdata->radius(ivstx) << sdata->radius(ivenx)
97  << " r0 rn" << sdata->radius(0) << sdata->radius(sdata->pointCount()-1);
98 
99  int nwarns = 0; // Initialize for scan analysis
100 
101  if ( npts > 50 )
102  { // If sufficient points, analyze slopes and ratios
103  int nspts = npts / 5;
104  int knt = 0;
105  double slope1 = 0.0;
106  double slope2 = 0.0;
107  double icept = 0.0;
108  double sigma = 0.0;
109  double corr = 0.0;
110 
111  for ( int jj = ivstx; jj < ivstx + nspts; jj++ )
112  { // Accumulate work arrays of beginning points
113  xx[ knt ] = scanfits[ jes ].xvs[ jj ];
114  yy[ knt++ ] = scanfits[ jes ].yvs[ jj ];
115  }
116 
117  // Get the slope at the beginning, then get ystart
118  US_Math2::linefit( &xx, &yy, &slope1, &icept, &sigma, &corr, knt );
119 DbgLv(1) << "SDiag: knt slope1 icept" << knt << slope1 << icept;
120  double xstart = xx[ 0 ];
121  double ystart = slope1 * xstart + icept;
122 
123  knt = 0;
124  nspts = npts / 10;
125 
126  for ( int jj = ivenn - nspts; jj < ivenn; jj++ )
127  { // Accumulate work arrays of ending points
128  xx[ knt ] = scanfits[ jes ].xvs[ jj ];
129  yy[ knt++ ] = scanfits[ jes ].yvs[ jj ];
130  }
131 
132  // Get the slope at the end, then get yend
133  US_Math2::linefit( &xx, &yy, &slope2, &icept, &sigma, &corr, knt );
134  double xend = xx[ knt - 1 ];
135  double yend = slope2 * xend + icept;
136 
137  // Get slope ratio and absorbance range
138  slope1 = ( slope1 == 0.0 ) ? 9.999999e-21 : slope1;
139  double ratio = slope2 / slope1;
140  double rangea = yend - ystart;
141 DbgLv(1) << "SDiag: knt slope2 icept" << knt << slope2 << icept;
142 DbgLv(1) << "SDiag: y0 y1 ym yn" << yy[0] << yy[1] << yy[knt-2] << yy[knt-1];
143 DbgLv(1) << "SDiag: xend yend" << xend << yend;
144 DbgLv(1) << "SDiag: ratio rangea" << ratio << rangea;
145 
146  rs += tr( "Slope at beginning: %1, Slope at end %2, "
147  "Ratio: %3\n\n" ).arg( slope1 ).arg( slope2 ).arg( ratio );
148 
149  // Determine and add notes for any warnings
150 
151  if ( ratio > 0.0 && ratio < 1.5 )
152  {
153  rs += tr( "Warning: The ratio is very small - there is"
154  " probably not enough\n"
155  "information in this scan.\n"
156  "Suggestion: use a higher speed. Also, check"
157  " for aggregation!\n\n" );
158  nwarns++;
159  }
160 
161  if ( slope1 < 0.0 )
162  {
163  rs += tr( "Warning: The start point slope for this scan"
164  " is negative!\n"
165  "Possible reasons: excessive noise in the data,"
166  " or time invariant noise\n"
167  "from interference data has not been subtracted.\n\n" );
168  nwarns++;
169  }
170 
171  if ( slope2 < 0.0 )
172  {
173  rs += tr( "Warning: The end point slope for this scan"
174  " is negative!\n"
175  "Possible reasons: excessive noise in the data,"
176  " or time invariant noise\n"
177  "from interference data has not been subtracted.\n\n" );
178  nwarns++;
179  }
180 
181  if ( rangea < 0.4 )
182  {
183  rs += tr( "Warning: This scan only spans %1 OD of the"
184  " possible\n" ). arg( rangea );
185  rs += tr( "0.9 - 1.0 OD range the instrument allows.\n\n" );
186  nwarns++;
187  }
188 
189  if ( yend < 0.6 )
190  {
191  rs += tr( "Warning: This scan's maximum absorbance is only "
192  " %1 OD.\n" ).arg( yend );
193  rs += tr( "This is lower than the linear range of the XL-A"
194  " which generally extends\n"
195  "up to ~0.9 OD. You may be discarding information."
196  " Check for Aggregation!\n\n" );
197  nwarns++;
198  }
199  }
200 
201  // Add final notes on points and warnings
202 
203  rs += tr( "Number of points in this scan: %1" ).arg( npts );
204 
205  if ( npts >= 100 )
206  rs += tr( "\n\n" );
207 
208  else
209  {
210  if ( npts >= 50 )
211  rs += tr( " (low!)\n\n" );
212  else
213  rs += tr( " (too low! Are the data below the OD cutoff?)\n\n" );
214  nwarns++;
215  }
216 
217  if ( nwarns == 1 )
218  rs += tr( "There was 1 warning generated for this scan.\n" );
219 
220  else
221  rs += tr( "There were %1 warnings generated for this scan.\n" )
222  .arg( nwarns );
223 
224  if ( nwarns > 2 )
225  rs += tr( "Please check the scan to make sure it is appropriate"
226  " for inclusion in a global fit!\n" );
227 
228  rs += "\n";
229 
230  if ( nwarns == 0 )
231  { // Mark scan as fit/non-excluded
232  scanfits[ jes ].scanFit = true;
233  scanfits[ jes ].autoExcl = false;
234  }
235 
236  else
237  { // Mark scan as non-fit/excluded
238  scanfits[ jes ].scanFit = false;
239  scanfits[ jes ].autoExcl = true;
240  scprobs = true;
241  }
242  } // End: scans loop
243 
244  if ( scprobs )
245  { // Pop up dialog warning of potential problems
246  QMessageBox::warning( wparent, tr( "Scan Problem(s)" ),
247  tr( "One or more scans have been excluded from the fit.\n"
248  "The Diagnostics report will help you to determine\n"
249  "which problems occurred. You can manually override\n"
250  "scan exclusions and include them, once you identify\n"
251  "the reasons for the exclusion." ) );
252 
253  }
254 
255  // Display scan diagnostics in an editor text dialog
256  US_Editor* ediag = new US_Editor( US_Editor::DEFAULT, true, "*.res",
257  wparent );
258  ediag->setWindowTitle( tr( "Scan Diagnostics" ) );
259  QFont efont( US_GuiSettings::fontFamily(),
260  US_GuiSettings::fontSize() - 2 );
261  ediag->e->setFont( efont );
262  ediag->e->setText( rs );
263  QFontMetrics fm( efont );
264  int dwid = maxLineWidth( fm, rs ) + fm.width( "WWW" );
265 DbgLv(1) << "dwid" << dwid;
266  int dhgt = fm.lineSpacing() * 50;
267  dwid = ( ( dwid / 12 + 2 ) * 12 );
268  dhgt = ( ( dhgt / 12 + 2 ) * 12 );
269  ediag->resize( dwid, dhgt );
270  ediag->move( wparent->pos() + QPoint( 400, 100 ) );
271  ediag->show();
272 
273  // Output the text, also, to a reports file
274  QString basedir = US_Settings::reportDir();
275  QString repdir = dataList[ 0 ].runID;
276  QDir folder( basedir );
277 
278  if ( ! folder.exists( repdir ) )
279  {
280  if ( ! folder.mkdir( repdir ) )
281  {
282  QMessageBox::warning( wparent, tr( "File Error" ),
283  tr( "Could not create the directory:\n" )
284  + basedir + "/" + repdir );
285  return;
286  }
287  }
288 
289  QString filename = basedir + "/" + repdir + "/globeq.diagnostics.rpt";
290  QFile drf( filename );
291 
292  if ( ! drf.open( QIODevice::WriteOnly | QIODevice::Text ) )
293  {
294  QMessageBox::warning( wparent, tr( "File Error" ),
295  tr( "Unable to open the file:\n" ) + filename );
296  return;
297  }
298 
299  QTextStream ts( &drf ); // Write report text to file
300  ts << rs;
301  drf.close();
302 }
303 
304 // Check scan fit
306 {
307 DbgLv(1) << " EqRep:CHECK_SCAN_FIT()";
308  int ffitx = 0;
309  bool critical = false;
310 
311  while ( ffitx < scanfits.size() && ! scanfits[ ffitx ].scanFit )
312  ffitx++;
313 
314 DbgLv(1) << " EqRep:CSF: ffitx" << ffitx;
315  QString rs;
316 
317  rs += tr( "In order to assure that the proper parameters are used"
318  " for the fitting process,\n" );
319  rs += tr( "the following information has been compiled about the"
320  " components in your\n" );
321  rs += tr( "model and the scans included in the fit --\n\n"
322  "Component Information:\n\n" );
323 
324  for ( int jj = 0; jj < runfit.nbr_comps; jj++ )
325  {
326  int cnum = jj + 1;
327 
328  if ( runfit.mw_fits[ jj ] )
329  {
330  if ( runfit.mw_vals[ jj ] == 0.0 )
331  {
332  rs += tr( "Although the molecular weight parameter for"
333  " component %1 has been floated,\n" ).arg( cnum );
334  rs += tr( "the value for this parameter is equal to zero"
335  " - fitting aborted!\n" );
336  critical = true;
337  }
338 
339  if ( runfit.mw_rngs[ jj ] == 0.0 )
340  {
341  rs += tr( "Although the molecular weight parameter for"
342  " component %1 has been floated,\n" ).arg( cnum );
343  rs += tr( "the range for this parameter is equal to zero"
344  " - fitting aborted!\n" );
345  critical = true;
346  }
347  }
348 
349  else
350  {
351  rs += tr( "The molecular weight parameter for component %1"
352  " has been fixed.\n" ).arg( cnum );
353  }
354 
355  if ( runfit.vbar_fits[ jj ] )
356  {
357  if ( runfit.vbar_vals[ jj ] == 0.0 )
358  {
359  rs += tr( "Although the vbar parameter for"
360  " component %1 has been floated,\n" ).arg( cnum );
361  rs += tr( "the value for this parameter is equal to zero"
362  " - fitting aborted!\n" );
363  critical = true;
364  }
365 
366  if ( runfit.vbar_rngs[ jj ] == 0.0 )
367  {
368  rs += tr( "Although the vbar parameter for"
369  " component %1 has been floated,\n" ).arg( cnum );
370  rs += tr( "the range for this parameter is equal to zero"
371  " - fitting aborted!\n" );
372  critical = true;
373  }
374  }
375 
376  else
377  {
378  rs += tr( "The vbar parameter for component %1"
379  " has been fixed.\n" ).arg( cnum );
380  }
381 
382  if ( (int)( 1000000.0 * runfit.vbar_vals[ jj ] + 0.5 ) == 720000 )
383  {
384  rs += tr( "The vbar value for component %1 has been"
385  " left at 0.72,\n" ).arg( cnum );
386  rs += tr( "which is the default value - are you sure"
387  " you want to use this value?\n" );
388  }
389 
390  }
391 
392  for ( int jj = 0; jj < runfit.nbr_assocs; jj++ )
393  {
394  int anum = jj + 1;
395 
396  if ( runfit.eq_fits[ jj ] )
397  {
398  if ( runfit.eq_vals[ jj ] == 0.0 )
399  {
400  rs += tr( "Although the equilibrium constant %1 has"
401  " been floated,\n" ).arg( anum );
402  rs += tr( "the value for this parameter is equal to zero"
403  " - fitting aborted!\n" );
404  critical = true;
405  }
406 
407  if ( runfit.eq_rngs[ jj ] == 0.0 )
408  {
409  rs += tr( "Although the equilibrium constant %1 has"
410  " been floated,\n" ).arg( anum );
411  rs += tr( "the range for this parameter is equal to zero"
412  " - fitting aborted!\n" );
413  critical = true;
414  }
415  }
416 
417  else
418  {
419  rs += tr( "The equilibrium constant %1 has been fixed"
420  " - are you sure you want to do that?\n" ).arg( anum );
421  }
422  }
423 
424  if ( modelx > 3 )
425  {
426  bool same_waveln = true;
427  bool same_extinc = true;
428  int test_waveln = scanfits[ ffitx ].wavelen;
429 DbgLv(1) << " EqRep:CSF: fx extsz" << ffitx << scanfits[ffitx].extincts.size();
430 DbgLv(1) << " EqRep:CSF: test_waveln" << test_waveln;
431  double test_extinc = scanfits[ ffitx ].extincts[ 0 ];
432 DbgLv(1) << " EqRep:CSF: test_extinc" << test_extinc;
433 
434  for ( int jj = 0; jj < scanfits.size(); jj++ )
435  {
436  if ( scanfits[ jj ].scanFit )
437  {
438  if ( scanfits[ jj ].wavelen != test_waveln )
439  same_waveln = false;
440 
441  if ( scanfits[ jj ].extincts[ 0 ] != test_extinc )
442  same_extinc = false;
443  }
444  }
445 
446  if ( ! same_waveln && same_extinc )
447  rs += tr( "\nWarning:\n"
448  "Your project contains scans with different wavelengths\n"
449  "but identical extinction coefficients!\n" );
450 
451  else if ( ! same_waveln && ! same_extinc )
452  rs += tr( "\nWarning:\n"
453  "Your project contains scans with different wavelengths;\n"
454  "make sure that the extinction coefficients match!\n" );
455  }
456 
457  // Compose notes on each scan
458  for ( int jes = 0; jes < scedits.size(); jes++ )
459  {
460  int jdx = scedits[ jes ].dsindex; // data set index
461 
462  // Scan information header
463  rs += scanInfoHeader( jes, jdx );
464 
465  if ( scanfits[ jes ].scanFit )
466  {
467  for ( int jj = 0; jj < runfit.nbr_comps; jj++ )
468  {
469  int cnum = jj + 1;
470 
471  if ( scanfits[ jes ].amp_fits[ jj ] )
472  {
473  if ( scanfits[ jes ].amp_vals[ jj ] == 0.0 )
474  {
475  rs += tr( "Although the amplitude for component %1 has"
476  " been floated,\nthe value for this parameter"
477  " is equal to zero - fitting aborted!\n" );
478  critical = true;
479  }
480 
481  if ( scanfits[ jes ].amp_rngs[ jj ] == 0.0 )
482  {
483  rs += tr( "Although the amplitude for component %1 has"
484  " been floated,\nthe range for this parameter"
485  " is equal to zero - fitting aborted!\n" );
486  critical = true;
487  }
488 
489  }
490 
491  else
492  {
493  rs += tr( "The amplitude for component %1 has been fixed -"
494  " are you sure you want to do that?\n" ).arg( cnum );
495  }
496 
497  if ( runfit.nbr_assocs > 0 )
498  {
499  if ( scanfits[ jes ].extincts[ jj ] == 0.0 )
500  {
501  rs += tr( "The extinction coefficient for component %1 is"
502  " equal to zero - are you sure?\n" ).arg( cnum );
503  rs += tr( "(This could be valid if this component does"
504  " not absort at %1 nm)\n" )
505  .arg( scanfits[ jes ].wavelen );
506  }
507  }
508  }
509 
510  if ( scanfits[ jes ].baseln_fit )
511  {
512  if ( scanfits[ jes ].baseln_rng == 0.0 )
513  {
514  rs += tr( "Although the baseline for this scan has"
515  " been floated,\nthe range for this parameter"
516  " is equal to zero - fitting aborted!\n" );
517  critical = true;
518  }
519  }
520 
521  else
522  {
523  rs += tr( "The baseline for this scan has been fixed -"
524  " are you sure you want to do that?\n" );
525  }
526 
527  if ( (int)( 1000000.0 * scanfits[ jes ].density + 0.5 ) == 998234 )
528  {
529  rs += tr( "The density setting corresponds to pure water -"
530  " are you sure you want to use that?\n" );
531  }
532 
533  rs += "\n";
534  }
535 
536  else
537  {
538  rs += tr( "This scan has been excluded from the fit.\n" );
539  }
540  }
541 
542  // Display scan fit checks in an editor text dialog
543  US_Editor* ediag = new US_Editor( US_Editor::DEFAULT, true, "*.res",
544  wparent );
545  ediag->setWindowTitle( tr( "Scan Fit Check" ) );
546  QFont efont( US_GuiSettings::fontFamily(),
547  US_GuiSettings::fontSize() - 2 );
548  ediag->e->setFont( efont );
549  ediag->e->setText( rs );
550  QFontMetrics fm( efont );
551  int dwid = maxLineWidth( fm, rs ) + fm.width( "WWW" );
552 DbgLv(1) << " dwid" << dwid << " astw" << fm.width( asters );
553  int dhgt = fm.lineSpacing() * 50;
554  dwid = ( ( dwid / 12 + 2 ) * 12 );
555  dhgt = ( ( dhgt / 12 + 2 ) * 12 );
556 DbgLv(1) << " dwid" << dwid;
557  ediag->resize( dwid, dhgt );
558  ediag->move( wparent->pos() + QPoint( 450, 150 ) );
559  ediag->show();
560 
561  // Output the text, also, to a reports file
562  QString basedir = US_Settings::reportDir();
563  QString repdir = dataList[ 0 ].runID;
564  QDir folder( basedir );
565 
566  if ( ! folder.exists( repdir ) )
567  {
568  if ( ! folder.mkdir( repdir ) )
569  {
570  QMessageBox::warning( wparent, tr( "File Error" ),
571  tr( "Could not create the directory:\n" )
572  + basedir + "/" + repdir );
573  return true;
574  }
575  }
576 
577  QString filename = basedir + "/" + repdir + "/globeq.scan_check.rpt";
578  QFile drf( filename );
579 
580  if ( ! drf.open( QIODevice::WriteOnly | QIODevice::Text ) )
581  {
582  QMessageBox::warning( wparent, tr( "File Error" ),
583  tr( "Unable to open the file:\n" ) + filename );
584  return true;
585  }
586 
587  QTextStream ts( &drf ); // Write report text to file
588  ts << rs;
589  drf.close();
590 
591  return critical;
592 }
593 
594 // Create fit report
595 QString US_EqReporter::fit_report( FitCtrlPar& fitpars, bool opengui,
596  bool wrreport, QString& filename )
597 {
598  QString rs;
599 DbgLv(1) << " EqRep:FIT_REPORT()";
600  QString mtitl = tr( "Global Equilibrium Fit Analysis" );
601  QString fitd = tr( " (fitted)" );
602  QString fixd = tr( " (fixed) " );
603 
604  // Compose opening header and notes
605  rs = asters + centerInLine( mtitl, 80, false, ' ' ) + "\n" + asters + "\n";
606  rs += tr( "Data Report for Project \"%1\"" ).arg( runfit.projname ) + "\n";
607  rs += tr( "Fitted Model: " ) + runfit.modlname + "\n\n";
608  rs += tr( "Parameters for this model:" ) + "\n\n";
609 
610  for ( int ii = 0; ii < runfit.nbr_comps; ii++ )
611  {
612  rs += tr( "For component %1:\n" ).arg( ii + 1 );
613  rs += tr( " Molecular Weight" );
614  rs += runfit.mw_fits[ ii ] ? fitd : fixd;
615  rs += tr( ": %1 dalton\n" ).arg( runfit.mw_vals[ ii ] );
616  rs += tr( " Partial Specific Volume" );
617  rs += runfit.vbar_fits[ ii ] ? fitd : fixd;
618  rs += tr( ": %1 (at 20" ).arg( runfit.vbar_vals[ ii ] ) + DEGC + ")\n";
619 
620  for ( int jj = 0; jj < runfit.nbr_assocs; jj++ )
621  {
622  double eqval = exp( runfit.eq_vals[ jj ] );
623  rs += tr( " Association (Dissociation) Constant %1: %2" )
624  .arg( jj + 1 ).arg( eqval );
625  rs += runfit.eq_fits[ jj ] ? fitd : fixd + "\n";
626  }
627  }
628 
629  rs += tr( "\nGlobal Fitting Statistics:\n\n" );
630  rs += tr( "Variance: %1\n" )
631  .arg( fitpars.variance );
632  rs += tr( "Standard Deviation: %1\n" )
633  .arg( fitpars.std_dev );
634  rs += tr( "Number of floated Parameters: %1\n" )
635  .arg( fitpars.nfpars );
636  rs += tr( "Number of Datasets (scans): %1\n" )
637  .arg( fitpars.ndsets );
638  rs += tr( "Number of total Datapoints: %1\n" )
639  .arg( fitpars.ntpts );
640 
641  double totneg = 0.0;
642  double totpos = 0.0;
643  int niscns = 0;
644 
645  for ( int ii = 0; ii < scanfits.size(); ii++ )
646  if ( scanfits[ ii ].scanFit ) niscns++;
647 
648  double ptfac = (double)niscns / (double)fitpars.ntpts;
649 
650  for ( int ii = 0; ii < scanfits.size(); ii++ )
651  {
652  EqScanFit* scnf = &scanfits[ ii ];
653 
654  if ( ! scnf->scanFit ) continue;
655 
656  int jdax = scedits[ ii ].dsindex;
657  int jscx = scedits[ ii ].scannbr - 1;
658 
659  double deltar = dataList[ jdax ].scanData[ jscx ].delta_r;
660  double ptdens = (double)scnf->points * ptfac;
661  double dpscale = deltar / ptdens;
662  totneg += ( (double)scnf->nbr_negr * dpscale );
663  totpos += ( (double)scnf->nbr_posr * dpscale );
664  }
665 
666  double totprod = totneg * totpos;
667  double totsum = totneg + totpos;
668  double totprod2 = 2.0 * totprod;
669 DbgLv(1) << " EqRep:FITREP: nfruns" << runfit.nbr_runs;
670  runfit.runs_percent = ( runfit.nbr_runs * 100.0 ) / totsum;
671  runfit.runs_expect = (double)( qRound( 1.0 + ( totprod2 / totsum ) ) );
672  runfit.runs_vari = ( totprod2 * ( totprod2 - totsum ) )
673  / ( sq( totprod ) * ( totsum - 1.0 ) );
674  //rs += tr( "Average Datapoint concentration: %1\n" ).arg( conc_avg );
675  rs += tr( "Average Datapoint concentration: Not determined\n" );
676  rs += tr( "Number of Degrees of Freedom: %1\n" )
677  .arg( fitpars.ntpts - fitpars.nfpars );
678  rs += tr( "Number of Runs (corrected): %1 (%2 %)\n" )
679  .arg( runfit.nbr_runs ).arg( runfit.runs_percent );
680  rs += tr( "Expected Number of Runs (corrected): %1\n" )
681  .arg( runfit.runs_expect );
682  rs += tr( "Run Variance (corrected): %1\n" )
683  .arg( runfit.runs_vari );
684 
685  rs += tr( "\nAccording to these statistical tests, this model is " );
686 
687  if ( runfit.runs_percent <= 30.0 )
688  {
689  if ( runfit.runs_percent < 26.0 )
690  rs += tr( "either inappropriate for\n" );
691 
692  else
693  rs += tr( "either a poor candidate for\n" );
694 
695  rs += tr( "the experimental data, or the fitting process has not yet"
696  " converged. Please try to reduce\n" );
697  rs += tr( "the variance by additional nonlinear least-squares"
698  " minimization of the data.\n" );
699  rs += tr( "This fit cannot be used for a Monte Carlo Analysis.\n" );
700  }
701 
702  else if ( runfit.runs_percent <= 35.0 )
703  {
704  rs += tr( "an acceptable candidate for\nthe experimental data.\n" );
705  rs += tr( "This fit can be used for a Monte Carlo Analysis"
706  " with reservations.\n" );
707  }
708 
709  else
710  {
711  rs += tr( "a good candidate for\nthe experimental data.\n" );
712  rs += tr( "This fit is recommended for Monte Carlo Analysis.\n" );
713  }
714 
715  rs += tr( "\nDetailed Information for fitted Scans:\n" );
716 
717  for ( int ii = 0; ii < scanfits.size(); ii++ )
718  {
719  EqScanFit* scnf = &scanfits[ ii ];
720 
721  // Scan information header
722  rs += scanInfoHeader( ii, scedits[ ii ].dsindex );
723 
724  if ( ! scnf->scanFit )
725  {
726  rs += tr( "This scan has been excluded from the Fit.\n" );
727  continue;
728  }
729 
730  rs += tr( "Baseline" );
731  rs += scnf->baseln_fit ? fitd : fixd;
732  rs += QString( ": %1 OD\n" )
733  .arg( scnf->baseline );
734  rs += tr( "Meniscus: %1\n" )
735  .arg( scnf->meniscus );
736  rs += tr( "Bottom: %1\n" )
737  .arg( scnf->bottom );
738  double tempera = scnf->tempera;
739  double density = scnf->density;
740  rs += tr( "Density setting: %1 g/ccm\n" )
741  .arg( density );
742  rs += tr( "Temperature setting: %1" )
743  .arg( tempera ) + DEGC + "\n";
744 
745  for ( int jj = 0; jj < runfit.nbr_comps; jj++ )
746  {
747  int compn = jj + 1;
748  rs += tr( " Amplitude of component %1" ).arg( compn );
749  rs += scnf->amp_fits[ jj ] ? fitd : fixd;
750  rs += QString( ": %1 OD\n" ).arg( scnf->amp_vals[ jj ] );
751  rs += tr( " Integral of component: %2\n" )
752  .arg( scnf->integral[ jj ] );
753  rs += tr( " Extinction Coefficient of component: %2\n" )
754  .arg( scnf->extincts[ jj ] );
755  double vbar20 = US_Math2::adjust_vbar20( runfit.vbar_vals[ jj ],
756  tempera );
757  double buoy = ( 1.0 - vbar20 * density );
758  rs += tr( " Partial Specific Volume of component: %2 (20W)\n" )
759  .arg( vbar20 );
760  rs += tr( " Buoyancy (20W) of component: %2\n" )
761  .arg( buoy );
762  }
763 
764  rs += tr( "Pathlength: %1 cm\n" )
765  .arg( scnf->pathlen );
766  rs += tr( "\nFitting Statistics for this Scan:\n" );
767  rs += tr( "Number of Runs (corrected): %1\n" )
768  .arg( scnf->runs );
769  double runprod = scnf->nbr_posr * scnf->nbr_negr;
770  double runsum = scnf->nbr_posr + scnf->nbr_negr;
771  double runpro2 = runprod * 2.0;
772  double expectr = 1.0 + runpro2 / runsum;
773  double varirun = ( runpro2 * ( runpro2 - runsum ) )
774  / ( sq( runsum ) * ( runsum - 1.0 ) );
775  rs += tr( "Expected Number of Runs (corrected): %1\n" )
776  .arg( qRound( expectr ) );
777  rs += tr( "Run Variance (corrected): %1\n" )
778  .arg( varirun );
779  } // END: scans information composition loop
780 
781  if ( opengui )
782  {
783  US_Editor* ediag = new US_Editor( US_Editor::DEFAULT, true, "*.res",
784  wparent );
785  ediag->setWindowTitle( mtitl );
786  QFont efont( US_Widgets::fixedFont().family(),
787  US_GuiSettings::fontSize() - 2 );
788  ediag->e->setFont( efont );
789  ediag->e->setText( rs );
790  QFontMetrics fm( efont );
791  int dwid = maxLineWidth( fm, rs ) + fm.width( "WWW" );
792  int dhgt = fm.lineSpacing() * 50;
793  dwid = ( ( dwid / 12 + 2 ) * 12 );
794  dhgt = ( ( dhgt / 12 + 2 ) * 12 );
795  ediag->resize( dwid, dhgt );
796  ediag->move( wparent->pos() + QPoint( 400, 100 ) );
797  ediag->show();
798  }
799 
800  if ( wrreport )
801  {
802  filename = US_Settings::reportDir() + "/" + dataList[ 0 ].runID
803  + "/globeq." + runfit.projname + ".eqfit.rpt";
804  QFile drf( filename );
805 
806  if ( ! drf.open( QIODevice::WriteOnly | QIODevice::Text ) )
807  {
808  QMessageBox::warning( wparent, tr( "File Error" ),
809  tr( "Unable to open the file:\n" ) + filename );
810  }
811 
812  else
813  {
814  QTextStream ts( &drf );
815  ts << rs;
816  drf.close();
817  }
818  }
819 
820  return rs;
821 }
822 
823 // Determine the index in the radius vector of a given radius
825 {
826  int l_index = edat->pointCount() - 1;
827  int r_index = -1;
828 
829  while ( ++r_index < l_index )
830  {
831  if ( radius <= edat->radius( r_index ) )
832  break;
833  }
834 
835  return r_index;
836 }
837 
838 // Compose a string that centers a title string within a line
839 QString US_EqReporter::centerInLine( const QString& titl_text, int linelen,
840  bool rightPad, const QChar end_char )
841 {
842  int tlen = titl_text.length(); // title text length
843  int plen = ( linelen - tlen ) / 2; // pad characters length
844  bool have_end = ( ! end_char.isNull() && end_char != ' ' ); // end char?
845 
846  // Pad to the left in order to center the given text
847  QString linestr = QString().fill( ' ', plen ) + titl_text;
848 
849  if ( rightPad )
850  { // If also right-padding, add pad spaces to the right
851  plen = linelen - tlen - plen;
852  linestr += QString().fill( ' ', plen );
853 
854  // If an end character was given, use it at the end of the line
855  if ( have_end )
856  linestr.replace( linestr.length() - 1, 1, end_char );
857  }
858 
859  // If an end character was given, use it at the beginning of the line
860  if ( have_end )
861  linestr.replace( 0, 1, end_char );
862 
863  return linestr;
864 }
865 
866 // Compose a scan information header string
867 QString US_EqReporter::scanInfoHeader( int jes, int jdx )
868 {
869  return ( "\n" + asters
870  + tr( "Information for Scan " ) + QString::number( jes + 1 )
871  + tr( ", Cell " ) + dataList[ jdx ].cell
872  + tr( ", Channel " ) + dataList[ jdx ].channel
873  + tr( ", Wavelength " ) + dataList[ jdx ].wavelength
874  + " nm, " + QString::number( scanfits[ jes ].rpm ) + tr( " rpm" )
875  + "\n [ " + scanfits[ jes ].descript + " ]\n" + asters );
876 }
877 
878 // Return the maximum line in a report string
879 int US_EqReporter::maxLineWidth( QFontMetrics& fm, const QString& repstr )
880 {
881  QStringList rlines = repstr.split( "\n" );
882  int mxlen = 0;
883  int lnlen = 0;
884 
885  for ( int ii = 0; ii < rlines.size(); ii++ )
886  {
887  if ( ( lnlen = fm.width( rlines[ ii ] ) ) > mxlen )
888  {
889  mxlen = lnlen;
890 DbgLv(1) << " mLW: ii lnlen" << ii << lnlen << " line:" << rlines[ii];
891  }
892  }
893 
894  return mxlen;
895 }
896