Skip to content

SQ2.50 Procedural Framework: Part 7b (Common Pb correction)

sbodorkos edited this page Aug 2, 2019 · 5 revisions

After completing the "Part 7a" block, Ludwig has some old code that Sorts the rows, and places some of the WtdMeanA parameters directly on each Grouped-sample sheet. I doubt that any of it is relevant to Squid3; so the next step is to commence the process of calculating (or applying specified) common Pb ratios to the Grouped-sample-specific radiogenic isotopic ratios.

Recall that for reference, SQUID 2.50 retains a master-list of Group Date Types, indexed by the integer piGrpDateType as follows:

0 = Total 206Pb/238U Age (by default, this is not available to users)
1 = 204corr 206Pb/238U Age
2 = 207corr 206Pb/238U Age
3 = 208corr 206Pb/238U Age
4 = 204corr 207Pb/206Pb Age
5 = 204corr 208Pb/232Th Age
6 = 207corr 208Pb/232Th Age
7 = 208corr 207Pb/206Pb Age

Ludwig's code for spot-specific common Pb corrections is muddied by poor logic. In essence, it can be written as:

If (pbExtractAgeGroups = TRUE)
  If (pbGrpCommPbSpecific = FALSE)
    --Option 1: Apply SPOT-SPECIFIC common-Pb ratios based on spot ages as dictated by 
    --piGrpDateType, in conjunction with Stacey & Kramers (1975) common Pb model 
  Else
    --Option 2: Apply ONE CONSTANT SET of common-Pb ratios (manually defined by user) to   
    --every spot, with NO reference to piGrpDateType or Stacey & Kramers (1975) model 
  End If --(pbGrpCommPbSpecific = FALSE)
Else
  --Option 3: Retain THE CONSTANT DEFAULT SET of common-Pb ratios (i.e. sComm64, sComm76,  
  --etc) for every spot, with NO reference to piGrpDateType or Stacey & Kramers (1975)
End If --(pbExtractAgeGroups = TRUE)

Basically, there are very few real-life scenarios where Option 3 is best. So in Ludwig's model, if the user wants to customise the common Pb compositions (used in the common Pb correction) in ANY way, either for all the Grouped-samples as a whole (Option 2), or for each spot individually (Option 1), then the user must set pbExtractAgeGroups = TRUE. But the thing is, customising the common Pb compositions, and extracting a statistically coherent age-population based on a chosen piGrpDateType, are two quite different processes, and those processes are not necessarily strongly associated in an isotopic/geological sense.

For example, in a sample comprising analyses of detrital zircons with profound age heterogeneity, Option 1 is by far the (geologically) best way to select common Pb compositions for enacting the correction, because Option 1 caters for the fact that age varies widely from grain to grain, therefore contemporaneous bulk-Earth common Pb (as modelled by Stacey & Kramers, 1975) displays similar grain-to-grain variation. However, Ludwig's model requires pbExtractAgeGroups = TRUE before users can access Option 1, and that doesn't necessarily make much sense, because determining the largest statistically-coherent population is of limited relevance in studies of detrital zircons. This is because for provenance spectra, the traditional null hypothesis (all analyses are equal, with dispersion explained by analytical uncertainty) is known a priori to be false.

(In contrast, Ludwig's structure works well for samples dominated by analyses of cogenetic magmatic grains, where pbExtractAgeGroups = TRUE is useful and relevant, as are Option 1 and Option 2 for the common Pb correction.)

Essentially, I contend that the Booleans pbExtractAgeGroups and pbGrpCommPbSpecific are best modelled as independent. Furthermore, I suggest that Option 3 is simply a special case of Option 2, and could be framed as the 'default' case of Option 2 (albeit with values editable by users interested in genuine choice. So in terms of a 'truth-table':

(pbExtractAgeGroups = TRUE) AND (pbGrpCommPbSpecific = FALSE)

  • Geologically often relevant (magmatics)
  • Very common user-mode
  • Well handled by Ludwig
  • Squid3 to implement unchanged

(pbExtractAgeGroups = TRUE) AND (pbGrpCommPbSpecific = TRUE)

  • Geologically sometimes relevant (well-characterised magmatics)
  • Uncommon user-mode (not as flexible as above)
  • Well handled by Ludwig [although Option 2 default values are Stacey-Kramers common Pb at 0 Ma, not super-helpful].
  • Squid3 to implement mostly unchanged [although Option 2 default values would be Option 3 values, should be better].

(pbExtractAgeGroups = FALSE) AND (pbGrpCommPbSpecific = FALSE)

  • Geologically often relevant (detritals)
  • WOULD BE common user-mode
  • NOT handled by Ludwig AT ALL
  • Squid3 would allow it!

(pbExtractAgeGroups = FALSE) AND (pbGrpCommPbSpecific = TRUE)

  • Geologically rarely relevant (detritals in which surface Pb is much more prevalent than initial Pb?)
  • Uncommon user-mode
  • INDIRECTLY handled by Ludwig [via Option 3 only, not editable].
  • Squid3 to implement full Option 2 functionality [albeit with 'default' values derived from Option 3].

So the following code represents a rearrangement of Ludwig's logic into something that would make more sense for users (i.e. it breaks links between Booleans that should not have been linked in the first place). But the replacement logic comes straight "out of my head", so please don't waste time puzzling over fundamental-looking flaws; just point them out to me and I will revisit.

If (pbExtractAgeGroups = TRUE) --I believe this clause is spurious; the following code
--ought to be enacted irrespective of the value of this Boolean. so my code-indenting
--for the remainder of this "If clause" does not honour the existence of this first line

If (piPb46col = 0) AND (piPb76col = 0) AND (piPb86col = 0) --i.e. if none of ["204/206"],
--["207/206"], ["208/206"] exist! Should always be FALSE, as these ratios are all mandated 
--whenever their constituent mass-stations are present.

  piGrpDateType = 0
  
End If

--SQUID 2.50 places the ~4 new (currently non-existent) columns related to row-by-row 
--common-Pb corrections immediately to the left of the first appearing age-type in its 
--SampleData column-headers. I have skipped that code, and moved on to some remodelled
--logic governing the existence of the new columns (there will be up to 4 of them):

If (piGrpDateType > 0) --i.e. for any user-defined choice of Group Date Type

  --Leftmost most is the Stacey-Kramers age of the common Pb, which is only applicable
  --when using Stacey-Kramers ratios to perform the correction: 
  If (pbGrpCommPbSpecific = FALSE) --i.e. common Pb defined by Stacey-Kramers:
    --Create a NEW column with column-index SKageCol, and name ["Age S-K ComPb"]
    --Note there is no dependence on the existence of any particular ratio(s).
  End If
  
  --Second from left is the SPOT-SPECIFIC 206/204 of the common Pb.
  --This is essentially a row-by-row replacement of the previous 'global' sComm64.
  --It is defined whenever ["204/206"] exists for the Sample (which is nearly always).
  If (piPb46col > 0) --i.e. if ["204/206"] exists
    --Create a NEW column with column-index piSK64col, and name ["ComPb 206/204"]
    --Note there is no dependence on the value of Boolean pbGrpCommPbSpecific.
  End If
  
  --Third from left is the SPOT-SPECIFIC 207/206 of the common Pb.
  --This is essentially a row-by-row replacement of the previous 'global' sComm76.
  --It is defined whenever ["207/206"] exists for the Sample (which is nearly always).
  If (piPb76col > 0) --i.e. if ["207/206"] exists
    --Create a NEW column with column-index piSK76col, and name ["ComPb 207/206"]
    --Note there is no dependence on the value of Boolean pbGrpCommPbSpecific.
  End If      
  
  --Fourth from left is the SPOT-SPECIFIC 208/206 of the common Pb.
  --This is essentially a row-by-row replacement of the previous 'global' sComm86.
  --It is defined whenever ["208/206"] exists for the Sample (which is very often).
  If (piPb86col > 0) --i.e. if ["208/206"] exists
    --Create a NEW column with column-index piSK86col, and name ["ComPb 208/206"]
    --Note there is no dependence on the value of Boolean pbGrpCommPbSpecific.
  End If            
  
End If --(piGrpDateType > 0) i.e. for any user-defined choice of Group Date Type

Now that the various common-Pb related columns have been created and defined, SQUID 2.50 next populates them with spot-specific Stacey-Kramers values, corresponding to the case pbGrpCommPbSpecific = FALSE. Assuming that a user-defined piGrpDate (> 0) has been selected by the user, GrpAgeTypeCol is its column-index:

If (GrpAgeTypeCol > 0) AND (pbGrpCommPbSpecific = FALSE) --i.e. chosen GrpDateType
--column exists, and common-Pb compositions are to be determined spot-by-spot:

  AgeVal = 0
  
  --Subroutine SetSKageForCPb (trivial) documented separately; FIRST OF TWO USES!
  SetSKageForCPb Frw, Lrw, GrpAgeTypeCol, SKageCol, 1 
  
  For i = Frw To Lrw
  
    If piSK64col > 0 --if column ["ComPb 206/204"] exists, evaluate SPOT-SPECIFIC
    --206/204 corresponding to SKageCol[i]
      
      --Subroutine SingleStagePbR documented separately
      SK64col[i] = " =SingleStagePbR( " & SKageCol[i] & " , 0) "
     
      If piSK76col > 0 --if column ["ComPb 207/206"] exists, evaluate SPOT-SPECIFIC 
      --207/206 corresponding to SKageCol[i], defined as (207/204) / (206/204)
      
        --Subroutine SingleStagePbR documented separately
        SK76col[i] = " =SingleStagePbR( " & SKageCol[i] & " , 1)/ " & SK64col[i]
      
      End If
    
    
      If piSK86col > 0 --if column ["ComPb 208/206"] exists, evaluate SPOT-SPECIFIC 
      --208/206 corresponding to SKageCol[i], defined as (208/204) / (206/204)
      
        --Subroutine SingleStagePbR documented separately
        SK86col[i] = " =SingleStagePbR( " & SKageCol[i] & " , 2)/ " & SK64col[i]
      
      End If --piSK86col > 0
      
    End If --piSK64col > 0
            
  Next i

This first section of the If clause has recognised the need for row-specific common-Pb ratio values (because pbGrpCommPbSpecific = FALSE), calculated those with reference to the user-preferred GrpAgeType, and placed each row-specific SKNumDen[i] value. Note that in the SQUID 2.50 version of this code, the foregoing If also included a For loop (executed only if pbGroupCommPbSpecific = FALSE) to Replace (for Grouped-samples only) all expression-references of the form 'sCommNumDen' with the relevant row-specific reference SKNumDen[i]. However, in this documentation, I have removed that For loop to the end of the current If clause, because the business of Replacing expression-references of the form 'sCommNumDen' is nor a universal consideration (i.e. it no longer depends on pbGroupCommPbSpecific). The above If clause is unclosed, and the following ElseIf performs the analogous operations where pbGrpCommPbSpecific = TRUE:

ElseIf (piAgePb6U8_4col > 0) OR (piAgePb6U8_7col > 0) OR (piAgePb6U8_8col > 0)
--This ElseIf embodies pbGrpCommPbSpecific = TRUE, so implements row-invariant common-Pb
--ratios, pending existence of at least one 206Pb/238U Age (index isotope independent).
--Seems a very trivial condition; could have been "ElseIf (pbGrpCommPbSpecific = TRUE)",
--or even just "Else"!

--SQUID 2.50 uses a single, constant set of common-Pb ratios across ALL Groups when 
--pbGrpCommPbSpecific = TRUE. A Squid3 enhancement would be to enable the user OPTION
--of specifying the following values on a Grouped-sample by Grouped-sample basis...

  --First, set the constant S-K 206/204, 207/206 and 208/206 values to those manually
  --specified by the user in the 'Group Me' Form (see Grouping video):

  Com64grp = foUser("GrpCPb64") 
  Com76grp = foUser("GrpCPb76")
  Com86grp = foUser("GrpCPb86")

  --Check each value to see if it lies in an "acceptable" range:
  Com64grp = MINMAX( Val( Com64grp ), 8, 1000 )
  Com76grp = MINMAX( Val( Com76grp ), 0.04, 1.5)
  Com86grp = MINMAX( Val( Com86grp ), 0.0001, 1000)

  --Deal with zero/NULL values:
  If (Com64grp = 0) OR (Com64grp = "") 
    Com64grp = 18.3 --this corresponds to S-K 206/204 at 0 Ma. I don't like this;
    --if Com64grp needs a default value here, I think we should re-use sComm64
  End If

  If (Com76grp = 0) OR (Com76grp = "") 
    Com76grp = 0.8536 --this corresponds to S-K 207/206 at 0 Ma. I don't like this;
    --if Com76grp needs a default value here, I think we should re-use sComm76
  End If

  If (Com86grp = 0) OR (Com86grp = "") 
    Com86grp = 2.093 --this corresponds to S-K 208/206 at 0 Ma. I don't like this;
    --if Com86grp needs a default value here, I think we should re-use sComm86
  End If

  --Determine the remaining two S-K ratios (207/204 and 208/204) algebraically:
  Com74grp = Com64grp * Com76grp
  Com84grp = Com64grp * Com86grp

  --Place these constants in every row of the Grouped-sample sheet:
  For i = Frw To Lrw
  
    If piSK64col > 0 --if column ["ComPb 206/204"] exists, set constant value:
      SK64col[i] = Com64grp
    End If
     
    If piSK76col > 0 --if column ["ComPb 207/206"] exists, set constant value:
      SK76col[i] = Com76grp
    End If
     
    If piSK86col > 0 --if column ["ComPb 208/206"] exists, set constant value:
      SK86col[i] = Com86grp
    End If
           
  Next i
  
End If --(GrpAgeTypeCol > 0) AND (pbGrpCommPbSpecific = FALSE)

Having evaluated and placed row-specific values of SK64col[i], SK76col[i] and SK86col[i] throughout, for both states of pbGrpCommPbSpecific, the next step in each Grouped-sample sheet is to replace all previous references to the global constant values sComm64, sComm74, sComm84, sComm76, sComm86 (which were employed to generate SQUID 2.50's StandardData and SampleData sheets) with the appropriate SKNumDen[i] value:

If (GrpAgeTypeCol > 0) --i.e. if some SKNumDen[i] values exist
  
  For i = Frw To Lrw
    
    With frSr(i, FirstAcol, , Lcol) --In row i, look at all columns containing EXPRESSIONS
      
      If piSK64col > 0 --Replace global sComm64 with row-specific SK64 value
        .Replace sComm64, SK64col[i]
      End If
      
      If piSK76col > 0 --Replace global sComm76 with row-specific SK76 value
        .Replace sComm76, SK76col[i]

        If piSK64col > 0 --Algebraically replace global sComm74 with row-specific SK74
          .Replace sComm74, "(" & SK64col[i] & "*" & SK76col[i] & ")"
        End If
      End If
      
      If piSK86col > 0 --Replace global sComm64 with row-specific SK86 value
        .Replace sComm86, SK86col[i]

        If piSK64col > 0 --Algebraically replace global sComm84 with row-specific SK84
          .Replace sComm84, "(" & SK64col[i] & "*" & SK86col[i] & ")"
        End If
      End If

    End With
            
  Next i
  
End If --(GrpAgeTypeCol > 0)  

If (pbGrpCommPbSpecific = FALSE) --common Pb ratios vary from row to row, with S-K Age
  
  --Subroutine SetSKageForCPb (trivial) documented separately; SECOND OF TWO USES!
  SetSKageForCPb Frw, Lrw, GrpAgetypeCol, SKageCol, 2 
  --Two iterations converge row-specific S-K Age and GrpAgeType Age satisfactorily

End If --(pbGrpCommPbSpecific = FALSE)

[APPLICATION.CALCULATE/EVALUATE] --Refresh all expression-calculations

--Note that the starting, spurious If (pbExtractAgeGroups = TRUE) clause remains 
--nominally unclosed at this point.

As far as I can tell, this brings the Grouped-sample common-Pb machinations to an end. Please note (sas I have, above) that this code is reorganised relative to Ludwig's original implementation; I believe the logic is correct, but as I am working solely with blocks of text, I'm not testing anything. If you find the code described here clearly nonsensical, please don't waste any time on it - just advise me of the issue and I will re-examine and address it.

Clone this wiki locally