Skip to content

Commit 9fc0987

Browse files
committed
Add and test StratMetropolis functions that take GeneralAgeData inputs
1 parent 2978a8e commit 9fc0987

File tree

7 files changed

+218
-37
lines changed

7 files changed

+218
-37
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Chron"
22
uuid = "68885b1f-77b5-52a7-b2e7-6a8014c56b98"
33
authors = ["C. Brenhin Keller <cbkeller@dartmouth.edu>"]
4-
version = "0.6.0"
4+
version = "0.6.1"
55

66
[deps]
77
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"

src/StratMetropolis.jl

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
```julia
66
StratMetropolis(smpl::ChronAgeData, [hiatus::HiatusData,] config::StratAgeModelConfiguration)
7+
StratMetropolis(smpl::GeneralAgeData, [hiatus::HiatusData,] config::StratAgeModelConfiguration)
78
```
89
Runs the main Chron.jl age-depth model routine for a stratigraphic set of
910
samples defined by sample heights and simple Gaussian age constraints in the
@@ -39,7 +40,7 @@
3940
Height = copy(smpl.Height)::Vector{Float64}
4041
Height_sigma = smpl.Height_sigma::Vector{Float64} .+ 1E-9 # Avoid divide-by-zero issues
4142
Age_Sidedness = copy(smpl.Age_Sidedness)::Vector{Float64} # Bottom is a maximum age and top is a minimum age
42-
Chronometer = smpl.Chronometer
43+
chronometer = smpl.Chronometer
4344
(bottom, top) = extrema(Height)
4445
model_heights = bottom:resolution:top
4546

@@ -60,7 +61,7 @@
6061
Height_sigma = [0; Height_sigma; 0] .+ 1E-9 # Avoid divide-by-zero issues
6162
Age_Sidedness = [-1.0; Age_Sidedness; 1.0;] # Bottom is a maximum age and top is a minimum age
6263
model_heights = (bottom-offset):resolution:(top+offset)
63-
Chronometer = (:None, Chronometer..., :None)
64+
chronometer = (:None, chronometer..., :None)
6465
end
6566
active_height_t = bottom .<= model_heights .<= top
6667

@@ -77,7 +78,7 @@
7778

7879
# Run the Markov chain
7980
ages = Normal.(Age, Age_sigma)
80-
agedist, lldist = stratmetropolis(Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve, Chronometer, systematic)
81+
agedist, lldist = stratmetropolis(Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve, chronometer, systematic)
8182

8283
# Crop the result
8384
agedist = agedist[active_height_t,:]
@@ -86,7 +87,7 @@
8687

8788
return mdl, agedist, lldist
8889
end
89-
function StratMetropolis(smpl::ChronAgeData, hiatus::HiatusData, config::StratAgeModelConfiguration)
90+
function StratMetropolis(smpl::ChronAgeData, hiatus::HiatusData, config::StratAgeModelConfiguration, systematic=nothing)
9091
# Run stratigraphic MCMC model, with hiata
9192
@info "Generating stratigraphic age-depth model..."
9293

@@ -103,6 +104,7 @@
103104
Height = copy(smpl.Height)::Vector{Float64}
104105
Height_sigma = smpl.Height_sigma::Vector{Float64} .+ 1E-9 # Avoid divide-by-zero issues
105106
Age_Sidedness = copy(smpl.Age_Sidedness)::Vector{Float64} # Bottom is a maximum age and top is a minimum age
107+
chronometer = smpl.Chronometer
106108
(bottom, top) = extrema(Height)
107109
model_heights = bottom:resolution:top
108110

@@ -123,6 +125,7 @@
123125
Height_sigma = [0; Height_sigma; 0] .+ 1E-9 # Avoid divide-by-zero issues
124126
Age_Sidedness = [-1.0; Age_Sidedness; 1.0;] # Bottom is a maximum age and top is a minimum age
125127
model_heights = (bottom-offset):resolution:(top+offset)
128+
chronometer = (:None, chronometer..., :None)
126129
end
127130
active_height_t = bottom .<= model_heights .<= top
128131
npoints = length(model_heights)
@@ -140,7 +143,133 @@
140143

141144
# Run the Markov chain
142145
ages = Normal.(Age, Age_sigma)
143-
agedist, lldist, hiatusdist = stratmetropolis(hiatus, Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve)
146+
agedist, lldist, hiatusdist = stratmetropolis(hiatus, Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve, chronometer, systematic)
147+
148+
# Crop the result
149+
agedist = agedist[active_height_t,:]
150+
model_heights = model_heights[active_height_t]
151+
mdl = StratAgeModel(model_heights, agedist)
152+
153+
return mdl, agedist, hiatusdist, lldist
154+
end
155+
function StratMetropolis(smpl::GeneralAgeData, config::StratAgeModelConfiguration, systematic=nothing)
156+
# Run stratigraphic MCMC model
157+
@info "Generating stratigraphic age-depth model..."
158+
159+
# Model configuration -- read from struct
160+
resolution = config.resolution
161+
burnin = config.burnin
162+
nsteps = config.nsteps
163+
sieve = config.sieve
164+
bounding = config.bounding
165+
166+
# Stratigraphic age constraints
167+
ages = unionize(smpl.Age_Distribution)::Vector{<:Union{<:Distribution{Univariate, Continuous}}}
168+
Height = copy(smpl.Height)::Vector{Float64}
169+
Height_sigma = smpl.Height_sigma::Vector{Float64} .+ 1E-9 # Avoid divide-by-zero issues
170+
Age_Sidedness = copy(smpl.Age_Sidedness)::Vector{Float64} # Bottom is a maximum age and top is a minimum age
171+
chronometer = smpl.Chronometer
172+
(bottom, top) = extrema(Height)
173+
model_heights = bottom:resolution:top
174+
175+
aveuncert = nanmean(std.(ages))
176+
absdiff = diff(sort!(mean.(ages[Age_Sidedness.==0])))
177+
maxdiff = isempty(absdiff) ? 0.0 : nanmaximum(absdiff)
178+
proposal_sigma = sqrt(aveuncert^2 + (maxdiff/10)^2)
179+
180+
if bounding>0
181+
# If bounding is requested, add extrapolated top and bottom bounds to avoid
182+
# issues with the stratigraphic markov chain wandering off to +/- infinity
183+
(youngest, oldest) = extrema(mean.(ages))
184+
dt_dH = (oldest-youngest)/(top-bottom)
185+
offset = round((top-bottom)*bounding/resolution)*resolution
186+
ages = unionize([Normal(oldest+offset*dt_dH, aveuncert/10);
187+
ages;
188+
Normal(youngest-offset*dt_dH, aveuncert/10)])
189+
Height = [bottom-offset; Height; top+offset]
190+
Height_sigma = [0; Height_sigma; 0] .+ 1E-9 # Avoid divide-by-zero issues
191+
Age_Sidedness = [-1.0; Age_Sidedness; 1.0;] # Bottom is a maximum age and top is a minimum age
192+
model_heights = (bottom-offset):resolution:(top+offset)
193+
chronometer = (:None, chronometer..., :None)
194+
end
195+
active_height_t = bottom .<= model_heights .<= top
196+
197+
# Start with a linear fit as an initial proposal
198+
(a,b) = hcat(fill!(similar(Height), 1), Height) \ mean.(ages)
199+
model_ages = a .+ b .* collect(model_heights)
200+
201+
# Select sidedness method
202+
sidedness = if smpl.Sidedness_Method === :fast || all(iszero, smpl.Age_Sidedness)
203+
FastSidedness(Age_Sidedness)
204+
else
205+
CDFSidedness(Age_Sidedness)
206+
end
207+
208+
# Run the Markov chain
209+
agedist, lldist = stratmetropolis(Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve, chronometer, systematic)
210+
211+
# Crop the result
212+
agedist = agedist[active_height_t,:]
213+
model_heights = model_heights[active_height_t]
214+
mdl = StratAgeModel(model_heights, agedist)
215+
216+
return mdl, agedist, lldist
217+
end
218+
function StratMetropolis(smpl::GeneralAgeData, hiatus::HiatusData, config::StratAgeModelConfiguration, systematic=nothing)
219+
# Run stratigraphic MCMC model
220+
@info "Generating stratigraphic age-depth model..."
221+
222+
# Model configuration -- read from struct
223+
resolution = config.resolution
224+
burnin = config.burnin
225+
nsteps = config.nsteps
226+
sieve = config.sieve
227+
bounding = config.bounding
228+
229+
# Stratigraphic age constraints
230+
ages = unionize(smpl.Age_Distribution)::Vector{<:Union{<:Distribution{Univariate, Continuous}}}
231+
Height = copy(smpl.Height)::Vector{Float64}
232+
Height_sigma = smpl.Height_sigma::Vector{Float64} .+ 1E-9 # Avoid divide-by-zero issues
233+
Age_Sidedness = copy(smpl.Age_Sidedness)::Vector{Float64} # Bottom is a maximum age and top is a minimum age
234+
chronometer = smpl.Chronometer
235+
(bottom, top) = extrema(Height)
236+
model_heights = bottom:resolution:top
237+
238+
aveuncert = nanmean(std.(ages))
239+
absdiff = diff(sort!(mean.(ages[Age_Sidedness.==0])))
240+
maxdiff = isempty(absdiff) ? 0.0 : nanmaximum(absdiff)
241+
proposal_sigma = sqrt(aveuncert^2 + (maxdiff/10)^2)
242+
243+
if bounding>0
244+
# If bounding is requested, add extrapolated top and bottom bounds to avoid
245+
# issues with the stratigraphic markov chain wandering off to +/- infinity
246+
(youngest, oldest) = extrema(mean.(ages))
247+
dt_dH = (oldest-youngest)/(top-bottom)
248+
offset = round((top-bottom)*bounding/resolution)*resolution
249+
ages = unionize([Normal(oldest+offset*dt_dH, aveuncert/10);
250+
ages;
251+
Normal(youngest-offset*dt_dH, aveuncert/10)])
252+
Height = [bottom-offset; Height; top+offset]
253+
Height_sigma = [0; Height_sigma; 0] .+ 1E-9 # Avoid divide-by-zero issues
254+
Age_Sidedness = [-1.0; Age_Sidedness; 1.0;] # Bottom is a maximum age and top is a minimum age
255+
model_heights = (bottom-offset):resolution:(top+offset)
256+
chronometer = (:None, chronometer..., :None)
257+
end
258+
active_height_t = bottom .<= model_heights .<= top
259+
260+
# Start with a linear fit as an initial proposal
261+
(a,b) = hcat(fill!(similar(Height), 1), Height) \ mean.(ages)
262+
model_ages = a .+ b .* collect(model_heights)
263+
264+
# Select sidedness method
265+
sidedness = if smpl.Sidedness_Method === :fast || all(iszero, smpl.Age_Sidedness)
266+
FastSidedness(Age_Sidedness)
267+
else
268+
CDFSidedness(Age_Sidedness)
269+
end
270+
271+
# Run the Markov chain
272+
agedist, lldist, hiatusdist = stratmetropolis(hiatus, Height, Height_sigma, model_heights, sidedness, ages, model_ages, proposal_sigma, burnin, nsteps, sieve, chronometer, systematic)
144273

145274
# Crop the result
146275
agedist = agedist[active_height_t,:]
@@ -401,9 +530,6 @@
401530

402531
return mdl, agedist, lldist
403532
end
404-
405-
## --- Stratigraphic MCMC model with hiatus, for radiocarbon ages # # # # # #
406-
407533
function StratMetropolis14C(smpl::ChronAgeData, hiatus::HiatusData, config::StratAgeModelConfiguration)
408534
# Run stratigraphic MCMC model, with hiata
409535
@info "Generating stratigraphic age-depth model..."

test/runtests.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ using Test, Statistics, Distributions
44
const make_plots = get(ENV, "MAKE_PLOTS", false) == "true"
55
@testset "Utilities" begin include("testUtilities.jl") end
66
@testset "Eruption / deposition age distributions" begin include("testDist.jl") end
7-
@testset "Strat only" begin include("testStratOnly.jl") end
7+
@testset "Strat only" begin
8+
include("testStratOnly.jl")
9+
include("testStratOnlyGeneral.jl")
10+
end
811
@testset "Radiocarbon" begin include("testRadiocarbon.jl") end
912
@testset "Coupled model" begin include("testCoupled.jl") end

test/testCoupled.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
## --- Standard coupled eruption-deposition modelling
22

3-
nSamples = 5 # The number of samples you have data for
4-
smpl = ChronAgeData(nSamples)
3+
nsamples = 5 # The number of samples you have data for
4+
smpl = ChronAgeData(nsamples)
55
smpl.Name = ("KJ08-157", "KJ04-75", "KJ09-66", "KJ04-72", "KJ04-70",)
66
smpl.Height .= [ -52.0, 44.0, 54.0, 82.0, 93.0,]
77
smpl.Height_sigma .= [ 3.0, 1.0, 3.0, 3.0, 3.0,]
8-
smpl.Age_Sidedness .= zeros(nSamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
8+
smpl.Age_Sidedness .= zeros(nsamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
99
smpl.Path = abspath("../examples/DenverUPbExampleData/") # Where are the data files?
1010
smpl.inputSigmaLevel = 2 # i.e., are the data files 1-sigma or 2-sigma. Integer.
1111
smpl.Age_Unit = "Ma" # Unit of measurement for ages and errors in the data files
@@ -124,11 +124,11 @@ println("StratMetropolisDist with fitted Gaussians:")
124124

125125
## -- Test coupled with Isoplot.jl Pb-loss-aware eruption ages
126126

127-
nSamples = 3 # The number of samples you have data for
128-
smpl = ChronAgeData(nSamples)
127+
nsamples = 3 # The number of samples you have data for
128+
smpl = ChronAgeData(nsamples)
129129
smpl.Name = ("KR18-04", "KR18-01", "KR18-05")
130130
smpl.Height .= [ 0.0, 100.0, 200.0] # Arbitrary heights
131-
smpl.Age_Sidedness .= zeros(nSamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
131+
smpl.Age_Sidedness .= zeros(nsamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
132132
smpl.Path = abspath("../examples/ConcordiaExampleData/") # Where are the data files?
133133
smpl.inputSigmaLevel = 1 # i.e., are the data files 1-sigma or 2-sigma. Integer.
134134
smpl.Age_Unit = "Ma" # Unit of measurement for ages and errors in the data files

test/testRadiocarbon.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
## --- Test age-depth model
88

99
# Input the number of samples we wish to model (must match below)
10-
nSamples = 4
11-
# Make an instance of a ChronSection object for nSamples
12-
smpl = ChronAgeData(nSamples)
10+
nsamples = 4
11+
# Make an instance of a ChronSection object for nsamples
12+
smpl = ChronAgeData(nsamples)
1313
smpl.Name = ("Sample 1", "Sample 2", "Sample 3", "Sample 4") # Et cetera
1414
smpl.Age_14C .= [ 6991, 7088, 7230, 7540,] # Measured ages
1515
smpl.Age_14C_sigma .= [ 30, 70, 50, 50,] # Measured 1-σ uncertainties
1616
smpl.Height .= [ -355, -380,-397.0,-411.5,] # Depths below surface should be negative
17-
smpl.Height_sigma .= fill(0.01, nSamples) # Usually assume little or no sample height uncertainty
18-
smpl.Age_Sidedness .= zeros(nSamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
17+
smpl.Height_sigma .= fill(0.01, nsamples) # Usually assume little or no sample height uncertainty
18+
smpl.Age_Sidedness .= zeros(nsamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
1919
smpl.Age_Unit = "Years BP" # Unit of measurement for ages
2020
smpl.Height_Unit = "m" # Unit of measurement for Height and Height_sigma
2121

@@ -24,8 +24,8 @@ smpl.Height_Unit = "m" # Unit of measurement for Height and Height_sigma
2424
calibration = intcal13
2525

2626
# Calculate calendar age PDFs for each sample
27-
smpl.Params = fill(NaN, length(calibration.Age_Calendar), nSamples)
28-
for i = 1:nSamples
27+
smpl.Params = fill(NaN, length(calibration.Age_Calendar), nsamples)
28+
for i = 1:nsamples
2929
# The likelihood that a measured 14C age could result from a sample of
3030
# a given calendar age is proportional to the intergral of the product
3131
# of the two respective distributions
@@ -98,8 +98,8 @@ hiatus.Duration_sigma = [ 30.5, 20.0 ]
9898
calibration = intcal20
9999

100100
# Calculate calendar age PDFs for each sample
101-
smpl.Params = fill(NaN, length(calibration.Age_Calendar), nSamples)
102-
for i = 1:nSamples
101+
smpl.Params = fill(NaN, length(calibration.Age_Calendar), nsamples)
102+
for i = 1:nsamples
103103
# The likelihood that a measured 14C age could result from a sample of
104104
# a given calendar age is proportional to the intergral of the product
105105
# of the two respective distributions

test/testStratOnly.jl

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# Make an instance of a ChronAgeData object for nSamples
2-
nSamples = 6
3-
smpl = ChronAgeData(nSamples)
1+
# Make an instance of a ChronAgeData object for nsamples
2+
nsamples = 6
3+
smpl = ChronAgeData(nsamples)
44
@test smpl isa ChronAgeData
55
smpl.Name = ("minimum age", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "maximum age") # Et cetera
66
smpl.Age .= [ 690.0, 699.1, 708.8, 723.0, 754.0, 812.0] # Measured ages
77
smpl.Age_sigma .= [ 7.0, 3.0, 7.0, 5.0, 5.0, 6.0] # Measured 1-σ uncertainties
88
smpl.Height .= [-350.0, -355.0, -380.0, -397.0, -411.5, -420.0] # Depths below surface should be negative
9-
smpl.Height_sigma .= fill(0.01, nSamples) # Usually assume little or no sample height uncertainty
10-
smpl.Age_Sidedness .= zeros(nSamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
9+
smpl.Height_sigma .= fill(0.01, nsamples) # Usually assume little or no sample height uncertainty
10+
smpl.Age_Sidedness .= zeros(nsamples) # Sidedness (zeros by default: geochron constraints are two-sided). Use -1 for a maximum age and +1 for a minimum age, 0 for two-sided
1111
smpl.Age_Sidedness[1] = 1. # Minimum age
1212
smpl.Age_Sidedness[end] = -1. # Maximum age
1313
smpl.Age_Unit = "Years BP" # Unit of measurement for ages
@@ -60,10 +60,3 @@ hiatus.Duration_sigma = [ 3.1, 2.0 ]
6060
@test size(hiatusdist) == (nHiatuses, config.nsteps)
6161
@test mean(hiatusdist, dims=2) [10.580012942504894; 18.96167245288326;;] atol=2
6262
@test -Inf < mean(lldist) < 0
63-
64-
## --- Strat only, general Distributions-based case
65-
66-
# Make an instance of a ChronAgeData object for nSamples
67-
nSamples = 4
68-
smpl = GeneralAgeData(nSamples)
69-
@test smpl isa GeneralAgeData

0 commit comments

Comments
 (0)