|
158 | 158 | Base.:+(d::BilinearExponential{T}, c::Real) where {T} = BilinearExponential{T}(d.A, d.loc + c, d.scl, d.shp, d.skw) |
159 | 159 | Base.:*(d::BilinearExponential{T}, c::Real) where {T} = BilinearExponential{T}(d.A-log(abs(c)), d.loc * c, d.scl*abs(c), d.shp, d.skw) |
160 | 160 |
|
161 | | -## --- Radiocarbon distribution type |
162 | | - |
163 | | - struct Radiocarbon{T<:Real} <: ContinuousUnivariateDistribution |
164 | | - μ::T |
165 | | - σ::T |
166 | | - dist::Vector{T} |
167 | | - ldist::Vector{T} |
168 | | - lcdist::Vector{T} |
169 | | - lccdist::Vector{T} |
170 | | - end |
171 | | - |
172 | | - function Radiocarbon(μ::T, σ::T, ldist::Vector{T}) where {T<:Real} |
173 | | - # Ensure normalization |
174 | | - dist = exp.(ldist) |
175 | | - normconst = sum(dist) * one(T) |
176 | | - dist ./= normconst |
177 | | - ldist .-= normconst |
178 | | - |
179 | | - # Cumulative distributions |
180 | | - lcdist = nanlogcumsumexp(ldist) |
181 | | - lccdist = nanlogcumsumexp(ldist, reverse=true) |
182 | 161 |
|
183 | | - return Radiocarbon{T}(μ, σ, dist, ldist, lcdist, lccdist) |
184 | | - end |
185 | | - |
186 | | - function Radiocarbon(Age_14C::Real, Age_14C_sigma::Real, calibration::NamedTuple=intcal13) |
187 | | - @assert calibration.Age_Calendar == 1:1:length(calibration.Age_14C) |
188 | | - @assert step(calibration.Age_Calendar) == calibration.dt == 1 |
189 | | - |
190 | | - ldist = normlogproduct.(Age_14C, Age_14C_sigma, calibration.Age_14C, calibration.Age_sigma) |
191 | | - |
192 | | - # Ensure normalization |
193 | | - dist = exp.(ldist) |
194 | | - normconst = sum(dist) * calibration.dt |
195 | | - dist ./= normconst |
196 | | - ldist .-= normconst |
197 | | - |
198 | | - μ = histmean(dist, calibration.Age_Calendar) |
199 | | - σ = histstd(dist, calibration.Age_Calendar, corrected=false) |
200 | | - |
201 | | - # Cumulative distributions |
202 | | - lcdist = nanlogcumsumexp(ldist) |
203 | | - lccdist = nanlogcumsumexp(ldist, reverse=true) |
204 | | - |
205 | | - return Radiocarbon(μ, σ, dist, ldist, lcdist, lccdist) |
206 | | - end |
207 | | - |
208 | | - ## Conversions |
209 | | - Base.convert(::Type{Radiocarbon{T}}, d::Radiocarbon) where {T<:Real} = Radiocarbon{T}(T(d.μ), T(d.σ), T.(d.ldist)) |
210 | | - Base.convert(::Type{Radiocarbon{T}}, d::Radiocarbon{T}) where {T<:Real} = d |
211 | | - |
212 | | - ## Parameters |
213 | | - Distributions.params(d::Radiocarbon) = (d.dist, d.ldist) |
214 | | - @inline Distributions.partype(d::Radiocarbon{T}) where {T<:Real} = T |
215 | | - |
216 | | - Distributions.location(d::Radiocarbon) = d.μ |
217 | | - Distributions.scale(d::Radiocarbon) = d.σ |
218 | | - |
219 | | - Base.eltype(::Type{Radiocarbon{T}}) where {T} = T |
220 | | - |
221 | | - ## Evaluation |
222 | | - @inline Distributions.pdf(d::Radiocarbon, x::Real) = exp(logpdf(d, x)) |
223 | | - @inline function Distributions.pdf(d::Radiocarbon{T}, x::Real) where {T} |
224 | | - if firstindex(d.ldist) <= x <= lastindex(d.ldist) |
225 | | - linterp_at_index(d.dist, x, -maxintfloat(T)) |
226 | | - else |
227 | | - # Treat as a single Normal distrbution if outside of the calibration range |
228 | | - pdf(Normal(d.μ, d.σ), x) |
229 | | - end |
230 | | - end |
231 | | - @inline function Distributions.logpdf(d::Radiocarbon{T}, x::Real) where {T} |
232 | | - if firstindex(d.ldist) <= x <= lastindex(d.ldist) |
233 | | - linterp_at_index(d.ldist, x, -maxintfloat(T)) |
234 | | - else |
235 | | - # Treat as a single Normal distrbution if outside of the calibration range |
236 | | - logpdf(Normal(d.μ, d.σ), x) |
237 | | - end |
238 | | - end |
239 | | - @inline Distributions.cdf(d::Radiocarbon, x::Real) = exp(logcdf(d, x)) |
240 | | - @inline function Distributions.logcdf(d::Radiocarbon{T}, x::Real) where {T} |
241 | | - if firstindex(d.lcdist) <= x <= lastindex(d.lcdist) |
242 | | - linterp_at_index(d.lcdist, x, -maxintfloat(T)) |
243 | | - else |
244 | | - # Treat as a single Normal distrbution if outside of the calibration range |
245 | | - logcdf(Normal(d.μ, d.σ), x) |
246 | | - end |
247 | | - end |
248 | | - @inline Distributions.ccdf(d::Radiocarbon, x::Real) = exp(logccdf(d, x)) |
249 | | - @inline function Distributions.logccdf(d::Radiocarbon{T}, x::Real) where {T} |
250 | | - if firstindex(d.lccdist) <= x <= lastindex(d.lccdist) |
251 | | - linterp_at_index(d.lccdist, x, -maxintfloat(T)) |
252 | | - else |
253 | | - # Treat as a single Normal distrbution if outside of the calibration range |
254 | | - logccdf(Normal(d.μ, d.σ), x) |
255 | | - end |
256 | | - end |
257 | | - |
258 | | - ## Statistics |
259 | | - Distributions.mean(d::Radiocarbon) = d.μ |
260 | | - Distributions.var(d::Radiocarbon) = d.σ * d.σ |
261 | | - Distributions.std(d::Radiocarbon) = d.σ |
262 | | - Distributions.skewness(d::Radiocarbon) = histskewness(d.dist, eachindex(d.dist), corrected=false) |
263 | | - Distributions.kurtosis(d::Radiocarbon) = histkurtosis(d.dist, eachindex(d.dist), corrected=false) |
264 | | - |
265 | 162 | ## --- Extend `normpdf_ll` to deal with Distributions.Normal |
266 | 163 |
|
267 | 164 | function StatGeochemBase.normpdf_ll(x::AbstractVector, ages::AbstractVector{Normal{T}}) where T |
|
0 commit comments