Skip to content

Sq2.50 Procedural Framework: Part 2

sbodorkos edited this page May 30, 2017 · 3 revisions

The next stage of the VBA code deals with 'looping' each spot through a set of procedures aimed at validating the analysis in itself, validating it against the governing Task, and evaluating Custom expressions and Built-in expression in a logical sequence, before proceeding to the Task-independent 'central processing of the U-Pb data.

Unfortunately, in the early sections of the code, Ludwig has made extensive use of the numeric values assigned to Booleans (in VBA, TRUE = -1 and FALSE = 0), as array-references (for a series of two-column, zero-addressed arrays). He sought to exploit the fact that, numerically, FALSE = -FALSE, and that -TRUE = 1. No doubt this is extremely 'clever', but it makes the code unnecessarily difficult to read and interpret. In this wiki, the original 'Booleans-as-indices' usage has been retained (to guard against the introduction of errors during translation), but wherever possible, plain-English comments have been added in order to clarify what is going on.

Some Definitions

Most of these appear in the VBA code, although they have been extended and reworded in places, for clarity.

  • Integer piSpotNum (range from 1 to total number of spots analysed) is the index of the total vector of Spots, in time sequence.
  • Integer vector piaNumSpots is a zero-addressed, two-element vector containing counts of the number of analyses by type. piaNumSpots[0] = number of Sample spots in the run (91 in demo XML); piaNumSpots[1] = number of Reference 206/238 material spots in the run (23 in demo XML).
  • Integer array piaSpots is a two-row array in which the rows are zero-addressed (range 0 to 1) but the columns are not (range 1 to max(piaNumSpots) i.e. 91 in demo XML). The first row (i.e. piaSpots[0, x]) contains the index numbers (i.e piSpotNum values) for each Sample spot in the run; the second row (i.e. piaSpots[1, x]) contains the index numbers (i.e piSpotNum values) for each Reference 206/238 material spot in the run. Unused elements in the shorter of the two rows default to zero.
  • Integer vector piaSpotIndx is a zero-addressed, two-element vector containing the index of piaSpots[w, x] of the current spot being processed. piaSpotIndx[0] = piaSpots[0, x] when a Sample spot is being processed, and piaSpotIndx[1] = piaSpots[1, x] when a Reference 206/238 material spot is being processed.
  • Integer vector piaSpotCt is a zero-addressed, two-element vector containing counts of the number of analyses processed so far. piaSpotCt[0] = number of Sample spots processed so far; piaSpotCt[1] = number of Reference 206/238 material spots processed so far.

Paraphrased Code

pbStd = FALSE  

"Start of Reference Material-Sample Loop":

Do --Start of Loop A  
  pbStd = (pbStd = FALSE) Or (pbStdsOnly = TRUE)  
  --The first time through Loop A, this sets pbStd = TRUE,
  --in which case integer/index "[-pbStd]" = 1  
  
  pbDone = FALSE  
  
  Do --Start of Loop B ("first loop of spots")  

    Do --Start of Loop C (counting, and "basic" parsing of spot)  
      
      piaSpotCt[-pbStd] = 1 + piaSpotCt[-pbStd]  
      piaSpotIndx[-pbStd] = 1 + piaSpotIndx[-pbStd]  
      piSpotNum = piaSpots[-pbStd, piaSpotIndx[-pbStd] ]  
      
      ParseRawData piSpotNum, TRUE, IgnoredChangedRunTable, DateStr, TRUE, FALSE, TRUE  

See separately documented subroutine ParseRawData for the argument list. Note that (1) FirstPass = TRUE here (as opposed to the prior call of ParseRawData in GetConcStdData, documented in Sq2.50 Procedure Pt 1, and (2) the prior value of IgnoredChangedRunTable is not specified, but this is ultimately irrelevant as it is set to FALSE near the start of ParseRawData anyway. The incomplete Loop C proceeds as follows:

      If (IgnoredChangedRunTable = TRUE) And (SpotNscans > 1)  
        MsgBox("Run Table changes at spot number " & StR[piSpotNum] & " -- terminating.")
        CrashEnd  
      End If  
      
    Loop Until IgnoredChangedRunTable = FALSE --End of Loop C

Loop C usually only runs once because IgnoredChangedRunTable is usually FALSE throughout. When it does get set to TRUE (via failure of a ParseRawData test), it still only runs once, because CrashEnd is immediately triggered in the vast majority of cases. But note that there seems to be a gap in the VBA code logic here, in the case where an analysis with SpotNscans = 1 failed a ParseRawData test, resulting in IgnoredChangedRunTable = TRUE. It looks like such an analysis could never escape Loop C... but I don't have any 'real data' to test this.

The incomplete Loop B proceeds as follows:

    DateStr = psaSpotDateTime[piSpotNum]
    ParseTimedate DateStr, Seconds 

ParseTimeDate is a Ludwig function designed to convert a date-string (DateStr) into its representation as a number of seconds (Seconds) elapsed since the commencement of calendar 1990 (seems arbitrary; presumably chosen because SHRIMP output was not computerised prior to that). The incomplete Loop B proceeds as follows:

    If FirstSecond = 0
      FirstSecond = Seconds
    End If
    
    plSpotOutputRw = 1 + plSpotOutputRw

In the following, 'CFs' reflects a Ludwig function best considered as 'Cell Fill with String', and 'CF' reflects 'Cell Fill'. In each case, the function appears to have three arguments: the row the cursor is to be placed in, the column the cursor is to be placed in, and the value to be placed. The incomplete Loop B proceeds as follows:

    CFs plSpotOutputRw, 1, psSpotName
    CFs plSpotOutputRw, piDateTimeCol, DateStr
    CF plSpotOutputRw, piHoursCol, Excel.Fixed((Seconds - FirstSecond) / 3600, 3)

Excel.Fixed invokes the Excel spreadsheet function FIXED, which returns a text representation of a number rounded to a specified number (3 here) of decimal places (e.g. https://www.techonthenet.com/excel/formulas/fixed.php). The third of these three statements calculates the 'Hours' value, as shown in the third column of processed SQUID-books, as a double-precision number (to 3 decimal places) defining the time elapsed in the analytical session since the commencement of the first Reference 206/238 material analysis.

At the time the code was written, this was presumably supposed to rigorously reflect the first analysis collected, chronologically. However, at Geoscience Australia (and probably elsewhere), sessions rarely commence with an analysis of the Reference 206/238 material, with the result that a small handful of spots have negative Hours values. This is not an operational problem: the primary function of the Hours column is to provide easy chronological sorting of any set of analyses and to provide an easy 'axis' for time-dependent representations and calculations (e.g. X-Y plots where X is analytical session time, and calculation of any associated X-Y regressions).

The incomplete Loop B proceeds as follows:

    GetRatios Ratios(), RatioFractErrs(), False, BadSbm()  
    PlaceRawRatios plSpotOutputRw, Ratios, RatioFractErrs

This pair of statements 'gets and places' the 'isotope ratios' for this row (i.e. in the initial instance, the first (chronological) analysis of the reference 206Pb/238U material). In addition to the Spot Name, Spot Date/Time and Hours data placed previously by the CFs and CF statements above, this pair of statements results in the placement of:

  • All spot-specific data harvested directly from the XML file (in SQUID 2.50, these are the QT1Y and QT1Z bit-values, the Stage X, Stage Y and Stage Z positions, and the primary beam intensity in nA).
  • BackgroundCps and all TotCps values calculated at the end of SHRIMP: Step 2
  • All RatioVal and RatioFractErr (the latter as percentage) values, as calculated for each 'ratio of interest' by SHRIMP: Step 4. Note that this does not include the results of any Task expressions, as these calculations have not yet been performed!

The procedure then turns firstly to the custom expressions of the governing Task. This first pass through the expressions is (I think) primarily aimed at finding which of the 'custom' expressions requires evaluation as input to any of the 'built-in' expressions. The incomplete Loop B proceeds as follows:

    For EqNum = 1 to Task.Neqns --Remember 'built-ins' have EqNum < 0

      If Switch.LA = FALSE And Switch.SC = FALSE
    
        If Switch.FO = TRUE Or Switch.AR = TRUE Or piaEqnRats[EqNum] = 0
          --Second condition is redundant, I think

          Formulae Task.EqnAsString[EqNum], EqNum, pbStd, piaEqCol[pbStd, EqNum]
          --to be documented/paraphrased separately
          
          If Switch.FO = FALSE
            EqnResu = Cells[SpotOutputRw, piaEqCol[pbStd, EqNum] ]
          End If
      
        Else --essentially equivalent to "If Switch.NU = TRUE"
      
          EqnInterp Task.EqnAsString[EqNum], EqNum, EqnRes, EqnFerr, 1, 0, TRUE  
          --See separate documentation of Sub EqnInterp for argument list 
      
          EqnResu = StR( EqnRes ) --double-to-string conversion 
          EqnFerro = StR( 100 * EqnFerr ) --formal construction of %err from Ferr
          CFs plSpotOutputRw, piaEqCol[pbStd, EqNum], EqnResu
          CFs plSpotOutputRw, piaEqECol[pbStd, EqNum], EqnFerro
          --Note "piaEqCol" vs "piaEqECol" in the last two lines
          
        End If
    
      End If
    
    Next EqNum
    
  Loop Until piaSpotIndx[-pbStd] = piaNumSpots[-pbStd] --End of Loop B

This marks the end of the first loop through a set of spots (i.e either reference 206Pb/238U materials or samples, depending on the stage of the processing. Note that Loop A is not yet closed, and the next stage of the processing revisits the Task's Custom expressions, this time looking at 'single-cell' (or 'single-array') results generated from columns of pre-existing data. The incomplete Loop A proceeds as follows:

  plHdrRw = flHeaderRow[pbStd] 
  --Fixed index-number of header-row: 6 for ref 206/238 material, 2 for samples
  Frw = FirstDatRw[-pbStd]
  --First data-row: essentially = 1 + plHdrRw 
  --(i.e. 7 for ref 206/238 material, 3 for samples)
  Lrw = LastDatRw[-pbStd]
  --Last data-row: essentially = piaNumSpots[-pbStd] + plHdrRw (i.e. for demo XML,
  --ref 206/238 material = 23 analyses, plHdrRw = 6, so Lrw = 29, and for
  --samples = 91 analyses, plHdrRw = 2, so Lrw = 93.

With Frw and Lrw defined, the next step is to place any 'single-cell' (or 'single-array') expressions for which Switch.LA (i.e. 'LAst') = FALSE. The incomplete Loop A proceeds as follows:

  For EqNum = 1 to Task.Neqns --Custom expressions only!
  
    If Switch.LA = FALSE And ( (Switch.AR = TRUE And Switch.ARrows = 1) Or 
      (Switch.SC = TRUE) ) --logic repaired from Ludwig; see below
      
      Formulae Task.EqnAsString[EqNum], EqNum, pbStd, Frw, piaEqCol[pbStd, EqNum]
      --to be documented/paraphrased separately
    End If
  
  Next EqNum

The above If statement incorporates repairs to the conditions originally imposed by Ludwig: his VBA code omitted "Switch.AR = TRUE And" under the assumption that this Boolean was implied by a non-zero value for Switch.ARrows. Unfortunately, a bug in the Task Editor permits the proliferation of non-zero values for Switch.ARrows and Switch.ARcols, even when Switch.AR = FALSE, and the real presence of this prohibited combination permitted expressions with Switch.AR = FALSE to satisfy the If condition (it is safe to say this was unintended!). Consequences included 're-evaluation' of NU-switched expressions as FO-switched (in the first data-row only), as observed by Jim Bowring in the 29 April 2017 iteration of the 100142_ShowcaseTask... output.

Loop A remains unfinished, but now that the first loop through the spot-groups has been completed (via Loop B and subsequent), it is time to reset the counters and prepare for the second loop through the spots.

plSpotOutputRw = plHdrRw
piaSpotIndx[-pbStd] = piaStartSpotIndx[-pbStd] - 1
piaSpotCt[-pbStd] = 0

The second loop through the spot-groups encompasses the calculation and placement, row-by-row, of the Daughter-Parent ratios/constants, using the built-in expressions. This will be documented in Procedural Framework: Part 3.

Clone this wiki locally