From cd364eee8a337189d825e813bb8d5167414e903c Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 21 Jun 2023 16:26:06 -0700 Subject: [PATCH 01/43] pdf for Ratcliff DDM first pass --- src/RatcliffDDM.jl | 554 +++++++++++++------------------- src/SequentialSamplingModels.jl | 2 + 2 files changed, 226 insertions(+), 330 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index efeba124..c5a478fa 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -1,357 +1,251 @@ -# """ -# RatcliffDDM - -# Model object for the Ratcliff Diffusion Model. - -# # Fields -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. - -# # Example - -# ````julia -# using SequentialSamplingModels -# dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) -# choice,rt = rand(dist, 10) -# like = pdf.(dist, choice, rt) -# loglike = logpdf.(dist, choice, rt) -# ```` +""" + RatcliffDDM + + Model object for the Ratcliff Diffusion Model. + +# Fields + - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 + - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 + - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 + - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. + - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. + - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. + - `σ`: diffusion noise constant. Default is 1. + +# Example + +````julia +using SequentialSamplingModels +dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) +choice,rt = rand(dist, 10) +like = pdf.(dist, choice, rt) +loglike = logpdf.(dist, choice, rt) +```` -# # References +# References -# Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. -# Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 - -# """ -# mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel -# ν::T1 -# α::T2 -# τ::T3 -# z::T4 -# η::T5 -# sz::T6 -# st::T7 -# σ::T8 -# end - -# """ -# RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0 -# ) - -# Constructor for the Ratcliff Diffusion Model. +Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. +Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 +""" +mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel + ν::T1 + α::T2 + τ::T3 + z::T4 + η::T5 + sz::T6 + st::T7 + σ::T8 +end + +Base.broadcastable(x::RatcliffDDM) = Ref(x) + +function params(d::RatcliffDDM) + (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) +end + +loglikelihood(d::RatcliffDDM, data) = sum(logpdf.(d, data...)) + +""" +RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + +Constructor for the Ratcliff Diffusion Model. -# # Keywords -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. -# """ -# function RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0) -# return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -# end - -# function params(d::RatcliffDDM) -# (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -# end - -# #uses analytic integration of the likelihood function for variability in drift-rate +# Keywords +- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 +- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 +- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 +- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). +- `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. +- `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. +- `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. +- `σ`: diffusion noise constant. Default is 1. + +# Example + +```julia +using SequentialSamplingModels +dist = RatcliffDDM(;ν = 1.00,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = 0.10,σ = 1.0) +``` +""" +function RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0) + return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) +end + +# probability density function over the lower boundary + +# uses analytic integration of the likelihood function for variability in drift-rate # function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# (ν, α, τ, z, η, sz, st) = params(d) - -# if choice == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ) -# end -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) + ν*α*z + (ν^2)*rt*0.5 -# end -# return pdf(DDM(ν, α, τ, z), choice, rt; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) - ν*α*(1-z) + (ν^2)*rt*0.5 -# end - -# function pdf_sv(x::Real, c::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real; ϵ::Real = 1.0e-12) - -# if c == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), c, x; ϵ) -# end -# return pdf(DDM(-ν, α, τ, 1-z), c, x; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*x ) / (2*(η^2)*x+2) ) - log(sqrt((η^2)*x+1)) + ν*α*z + (ν^2)*x*0.5 -# end -# return pdf(DDM(ν, α, τ, z), c, x; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*x ) / (2*(η^2)*x+2) ) - log(sqrt((η^2)*x+1)) - ν*α*(1-z) + (ν^2)*x*0.5 -# end - -# #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -# function pdf_full(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) -# (ν, α, τ, z, η, sz, st) = params(d) - -# # transform ν, z if choice is other bound response # if choice == 1 +# (ν, α, τ, z, η, sz, st, σ) = params(d) # ν = -ν -# z = 1 -z -# end - -# if st < 1.0e-3 -# st = 0 -# end -# if sz < 1.0e-3 -# sz = 0 -# end - -# if sz==0 -# if st==0 #sv=0,sz=0,st=0 -# return pdf_sv(d, choice, rt; ϵ::Real = 1.0e-12) -# else #sv=0,sz=0,st=$ -# return _simpson_1D(rt, choice, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) -# end -# else #sz=$ -# if st==0 #sv=0,sz=$,st=0 -# return _simpson_1D(rt, choice, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) -# else #sv=0,sz=$,st=$ -# return _simpson_2D(rt, choice, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) +# z = 1 - z +# return pdf_sv(RatcliffDDM(ν, α, τ, z, η, sz, st, σ), rt; ϵ) # end +# return pdf_sv(d, rt; ϵ) # end -# """ - -# _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - -# Numerical Integration with Simpson's Method -# see: https://en.wikipedia.org/wiki/Simpson%27s_rule - -# # Arguments -# - `x`::Real: response time -# - `ν`::Real: response time -# - `η`::Real: response time -# - `α`::Real: response time -# - `z`::Real: response time -# - `τ`::Real: response time -# - `ϵ`::Real: response time -# - `lb_z`::Real: response time -# - `ub_z`::Real: response time -# - `n_sz`::Int: response time -# - `lb_t`::Real: response time -# - `ub_t`::Real: response time -# - `n_st`::Int: response time - -# """ -# function _simpson_1D(x::Real, c::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) +function pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if η == 0 + return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + end + # if isless(ν,0) + # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 + # end + # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 + return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 +end + +function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) + if choice == 1 + (ν, α, τ, z, η, sz, st, σ) = params(d) + return pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + end + return pdf(d, rt; ϵ) +end + +#use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) +function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if τ ≥ rt + return T(NaN) + end + + if st < 1.0e-3 + st = 0 + end + if sz < 1.0e-3 + sz = 0 + end + + if sz==0 + if st==0 #sv=0,sz=0,st=0 + return pdf_sv(d, rt; ϵ) + else #sv=0,sz=0,st=$ + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) + end + else #sz=$ + if st==0 #sv=0,sz=$,st=0 + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) + else #sv=0,sz=$,st=$ + return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) + end + end +end + +##################################################### +# Numerical Integration with Simpson's Method # +# https://en.wikipedia.org/wiki/Simpson%27s_rule # +##################################################### + +# Simpson's Method one dimentional case +function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -# @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - -# n = max(n_st, n_sz) - -# if n_st == 0 #integration over z -# hz = (ub_z-lb_z)/n -# ht = 0 -# lb_t = t -# ub_t = t -# else #integration over t -# hz = 0 -# ht = (ub_t-lb_t)/n -# lb_z = z -# ub_z = z -# end - -# S = pdf_sv(x - lb_t, c, ν, η, α, lb_z, τ; ϵ) + # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even + + n = max(n_st, n_sz) + + if n_st == 0 #integration over z + hz = (ub_z-lb_z)/n + ht = 0 + lb_t = τ + ub_t = τ + else #integration over t + hz = 0 + ht = (ub_t-lb_t)/n + lb_z = z + ub_z = z + end + + S = pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) -# for i in 1:n -# z_tag = lb_z + hz * i -# t_tag = lb_t + ht * i -# y = pdf_sv(x - t_tag, c, ν, η, α, z_tag, τ; ϵ) - -# if isodd(i) -# S += 4 * y -# else -# S += 2 * y -# end - -# end + y = 0 + z_tag = 0 + t_tag = 0 + + for i in 1:n + z_tag = lb_z + hz * i + t_tag = lb_t + ht * i + + y = pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + + if isodd(i) + S += 4 * y + else + S += 2 * y + end + + end -# S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y -# S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st -# return (ht + hz) * S / 3 + return (ht + hz) * S / 3 -# end - -# """ -# RatcliffDDM - -# Model object for the Ratcliff Diffusion Model. - -# # Fields -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. - -# # Example - -# ````julia -# using SequentialSamplingModels -# dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) -# choice,rt = rand(dist, 10) -# like = pdf.(dist, choice, rt) -# loglike = logpdf.(dist, choice, rt) -# ```` - -# # References - -# Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. - -# Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 - -# """ -# @concrete mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel -# ν::T1 -# α::T2 -# τ::T3 -# z::T4 -# η::T5 -# sz::T6 -# st::T7 -# σ::T8 -# end - -# """ -# RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0 -# ) - -# Constructor for the Ratcliff Diffusion Model. - -# # Keywords -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. -# """ -# function RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0) -# return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -# end +end -# function params(d::RatcliffDDM) -# (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -# end - -# #uses analytic integration of the likelihood function for variability in drift-rate -# function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# (ν, α, τ, z, η, sz, st) = params(d) +# Simpson's Method two dimentional case +function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even + # @assert (ub_t-lb_t)*(ub_z-lb_z)>0 && (n_sz*n_st)>0 # 2D-integration only -# if choice == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ::Real = 1.0e-12) -# end -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ::Real = 1.0e-12) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) + ν*α*z + (ν^2)*rt*0.5 -# end -# return pdf(DDM(ν, α, τ, z), choice, rt; ϵ::Real = 1.0e-12) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) - ν*α*(1-z) + (ν^2)*rt*0.5 -# end + ht = (ub_t-lb_t)/n_st + S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) -# #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -# function pdf_full(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) -# (ν, α, τ, z, η, sz, st) = params(d) + t_tag = 0 + y = 0 + for i_t in 1:n_st + t_tag = lb_t + ht * i_t + y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) -# # transform ν, z if choice is other bound response -# if choice == 1 -# ν = -ν -# z = 1 -z -# end + if isodd(i_t) + S += 4 * y + else + S += 2 * y + end + end -# if st < 1.0e-3 -# st = 0 -# end -# if sz < 1.0e-3 -# sz = 0 -# end + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / (ub_t - lb_t) -# if sz==0 -# if st==0 #sv=0,sz=0,st=0 -# return pdf_sv(d, choice, rt; ϵ::Real = 1.0e-12) -# else #sv=0,sz=0,st=$ -# return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) -# end -# else #sz=$ -# if st==0 #sv=0,sz=$,st=0 -# return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) -# else #sv=0,sz=$,st=$ -# return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) -# end -# end + return ht * S / 3 -# """ +end -# _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - -# Numerical Integration with Simpson's Method -# see: https://en.wikipedia.org/wiki/Simpson%27s_rule - -# # Arguments -# - `x`::Real: response time -# - `ν`::Real: response time -# - `η`::Real: response time -# - `α`::Real: response time -# - `z`::Real: response time -# - `τ`::Real: response time -# - `ϵ`::Real: response time -# - `lb_z`::Real: response time -# - `ub_z`::Real: response time -# - `n_sz`::Int: response time -# - `lb_t`::Real: response time -# - `ub_t`::Real: response time -# - `n_st`::Int: response time +logpdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -# """ -# function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -# #assert ((n_sz&1)==0 and (n_st&1)==0), "n_st and n_sz have to be even" +function logpdf(d::RatcliffDDM, data::T) where {T<:NamedTuple} + return sum(logpdf.(d, data...)) +end -# end +function logpdf(dist::RatcliffDDM, data::Array{<:Tuple,1}) + LL = 0.0 + for d in data + LL += logpdf(dist, d...) + end + return LL +end -# function _simpson_2D() - -# end +logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) # """ # cdf_full() @@ -368,4 +262,4 @@ # in the diffusion model, Behavior Research Methods, # Instruments, & Computers, 36 (4), 702-716. -# """ +# """ \ No newline at end of file diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index 4c6ad16e..cf971293 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -33,6 +33,7 @@ module SequentialSamplingModels export LCA export LNR export maaDDM + export RatcliffDDM export Wald export WaldMixture @@ -56,4 +57,5 @@ module SequentialSamplingModels include("KDE.jl") include("LCA.jl") include("DDM.jl") + include("RatcliffDDM.jl") end \ No newline at end of file From 342f6bb649b289280ee62646684a0e7d555f86b2 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 21 Jun 2023 20:17:19 -0700 Subject: [PATCH 02/43] added rand simulators for Ratcliff Diffusion Model --- src/RatcliffDDM.jl | 187 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index c5a478fa..076ab2f1 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -17,7 +17,7 @@ ````julia using SequentialSamplingModels -dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) +dist = RatcliffDDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) choice,rt = rand(dist, 10) like = pdf.(dist, choice, rt) loglike = logpdf.(dist, choice, rt) @@ -262,4 +262,187 @@ logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) # in the diffusion model, Behavior Research Methods, # Instruments, & Computers, 36 (4), 702-716. -# """ \ No newline at end of file +# """ + +""" + rand(dist::RatcliffDDM) + +Generate a random choice and rt for the Ratcliff Diffusion Model + +# Arguments +- `dist`: model object for Ratcliff Diffusion Model. +- `method`: method simulating the diffusion process. + "rejection" uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process + "stochastic" uses the stochastic Euler method to directly simulate the stochastic differential equation + +# References + + Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). + A comparison of four methods for simulating the diffusion process. + Behavior Research Methods, Instruments, & Computers, 33, 443-456. + + Converted from Rhddmjagsutils.R R script by Kianté Fernandez + + See also https://github.com/kiante-fernandez/Rhddmjags. +""" +function rand(rng::AbstractRNG, d::RatcliffDDM) + # method::Char = "rejection" + return _rand_rejection(rng, d) + # method::Char = "stochastic" +# return _rand_stochastic(rng, d) +end + +function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if (ν < -5) || (ν > 5) + ν = sign(ν) * 5 + warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") + end + + if η > 3 + warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") + η = 3 + end + + if η == 0 + η = 1e-16 + end + + # Initialize output vectors + result = zeros(N) + T = zeros(N) + XX = zeros(N) + + # Called sigma in 2001 paper + D = σ^2 / 2 + + # Program specifications + ϵ = 2.220446049250313e-16 # precision from 1.0 to next double-precision number + Δ = ϵ + + for n in 1:N + r1 = randn() + μ = ν + r1 * η + bb = z - sz / 2 + sz * rand() + zz = bb * α + finish = 0 + totaltime = 0 + startpos = 0 + Aupper = α - zz + Alower = -zz + radius = min(abs(Aupper), abs(Alower)) + + while finish == 0 + λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 + # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 + F = D * π / (radius * μ) + F = F^2 / (1 + F^2) + # formula p447 in 2001 paper + prob = exp(radius * μ / D) + prob = prob / (1 + prob) + dir_ = 2 * (rand() < prob) - 1 + l = -1 + s2 = 0 + s1 = 0 + while s2 > l + s2 = rand() + s1 = rand() + tnew = 0 + told = 0 + uu = 0 + while abs(tnew - told) > ϵ || uu == 0 + told = tnew + uu += 1 + tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) + # infinite sum in formula (16) in BRMIC,2001 + end + l = 1 + s1^(-F) * tnew + end + # rest of formula (16) + t = abs(log(s1)) / λ + # is the negative of t* in (14) in BRMIC,2001 + totaltime += t + dir_ = startpos + dir_ * radius + ndt = τ - st / 2 + st * rand() + if (dir_ + Δ) > Aupper + T[n] = ndt + totaltime + XX[n] = 1 + finish = 1 + elseif (dir_ - Δ) < Alower + T[n] = ndt + totaltime + XX[n] = 2 + finish = 1 + else + startpos = dir_ + radius = minimum(abs.([Aupper, Alower] .- startpos)) + end + end + end + return (choice=XX,rt=T) +end + +function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if (ν < -5) || (ν > 5) + ν = sign(ν) * 5 + warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") + end + + if η > 3 + warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") + η = 3 + end + + if η == 0 + η = 1e-16 + end + + # Initialize output vectors + rts = zeros(N) + choice = zeros(N) + + for n in 1:N + random_walk = Array{Float64}(undef, nsteps) + start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() + ndt = (τ - st/2) + ((τ + st/2) - (τ - st/2)) * rand() + drift = rand(Distributions.Normal(ν, η)) + random_walk[1] = start_point * α + for s in 2:nsteps + random_walk[s] = random_walk[s-1] + rand(Distributions.Normal(drift * step_length, σ * sqrt(step_length))) + if random_walk[s] >= α + random_walk[s:end] .= α + rts[n] = s * step_length + ndt + choice[n] = 1 + break + elseif random_walk[s] <= 0 + random_walk[s:end] .= 0 + rts[n] = s * step_length + ndt + choice[n] = 2 + break + elseif s == nsteps + rts[n] = NaN + choice[n] = NaN + break + end + end + end + return (choice=choice,rt=rts) +end + +""" + rand(dist::DDM, n_sim::Int) + +Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. + +# Arguments +- `dist`: model object for the Drift Diffusion Model. +- `n_sim::Int`: the number of simulated rts +""" + +function rand(rng::AbstractRNG, d::RatcliffDDM, n_sim::Int) + return _rand_rejection(rng, d, N = n_sim) +end + +sampler(rng::AbstractRNG, d::RatcliffDDM) = rand(rng::AbstractRNG, d::RatcliffDDM) From b5cca7dc84fd37b64b63f789c1bb3141ab995c02 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 14:48:10 +0200 Subject: [PATCH 03/43] changes to Ratcliff to for SSM format --- src/RatcliffDDM.jl | 88 ++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 57 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 076ab2f1..ccd124c5 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -1,9 +1,9 @@ """ - RatcliffDDM + RatcliffDDM{T<:Real} <: SSM2D Model object for the Ratcliff Diffusion Model. -# Fields +# Parameters - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 @@ -13,6 +13,20 @@ - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. - `σ`: diffusion noise constant. Default is 1. +# Constructors + + RatcliffDDM(ν, α, τ, z, η, sz, st, σ) + + RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + # Example ````julia @@ -28,55 +42,25 @@ loglike = logpdf.(dist, choice, rt) Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ -mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel - ν::T1 - α::T2 - τ::T3 - z::T4 - η::T5 - sz::T6 - st::T7 - σ::T8 +mutable struct RatcliffDDM{T<:Real} <: SSM2D + ν::T + α::T + τ::T + z::T + η::T + sz::T + st::T + σ::T end -Base.broadcastable(x::RatcliffDDM) = Ref(x) +function RatcliffDDM(ν, α, τ, z, η, sz, st, σ) + return RatcliffDDM(promote(ν, α, τ, z, η, sz, st, σ)...) +end function params(d::RatcliffDDM) (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) end -loglikelihood(d::RatcliffDDM, data) = sum(logpdf.(d, data...)) - -""" -RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0 - ) - -Constructor for the Ratcliff Diffusion Model. - -# Keywords -- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -- `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -- `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -- `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -- `σ`: diffusion noise constant. Default is 1. - -# Example - -```julia -using SequentialSamplingModels -dist = RatcliffDDM(;ν = 1.00,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = 0.10,σ = 1.0) -``` -""" function RatcliffDDM(; ν = 1.00, α = 0.80, τ = 0.30, @@ -318,7 +302,7 @@ function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) D = σ^2 / 2 # Program specifications - ϵ = 2.220446049250313e-16 # precision from 1.0 to next double-precision number + ϵ = eps() # precision from 1.0 to next double-precision number Δ = ϵ for n in 1:N @@ -385,23 +369,13 @@ end function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) (ν, α, τ, z, η, sz, st, σ) = params(d) - if (ν < -5) || (ν > 5) - ν = sign(ν) * 5 - warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") - end - - if η > 3 - warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") - η = 3 - end - if η == 0 η = 1e-16 end # Initialize output vectors - rts = zeros(N) - choice = zeros(N) + choice = fill(0, N) + rt = fill(0.0, N) for n in 1:N random_walk = Array{Float64}(undef, nsteps) From 244a24de6e8e3f7c48c1f5d29cdb3240c91198fd Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 14:59:05 +0200 Subject: [PATCH 04/43] cdf fast dm implementation --- src/RatcliffDDM.jl | 272 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 16 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index ccd124c5..64a23d0c 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -231,22 +231,262 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) -# """ -# cdf_full() - -# The orignial algorithm was written on 09/01/06 by Joachim Vandekerckhove -# Then converted from c to julia by Kianté Fernandez - -# Computes Cumulative Distribution Function for the Diffusion model with random trial to trial mean drift (normal), -# starting point and non-decision (ter) time (both rectangular). Uses 6 quadrature points for drift and 6 for the others. - -# Based on methods described in: -# Tuerlinckx, F. (2004). The efficient computation of the -# cumulative distribution and probability density functions -# in the diffusion model, Behavior Research Methods, -# Instruments, & Computers, 36 (4), 702-716. - -# """ +######################################################################################################################################################################## +# Calculate Drift-diffusion Probability Density +# +# compute the densities g- and g+ of the first exit time. `cdf` implement A1 to A4 equations in Voss, Rothermund, and Voss (2004). These equations calculate Ratcliff's +# drift-diffusion model (1978). This source codes are derived from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). +# +# * ------------------------------------------------------------------------ +# * A verbatim copy of Jochen Voss & Andreas Voss's copyright. +# * ------------------------------------------------------------------------ +# * Copyright (C) 2012 Andreas Voss, Jochen Voss. +# * +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License as +# * published by the Free Software Foundation; either version 2 of the +# * License, or (at your option) any later version. +# * +# * This program is distributed in the hope that it will be useful, but +# * WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * General Public License for more details. +# * +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# * 02110-1301 USA. +# +# # References +# +# - Singmann H, Brown S, Gretton M, Heathcote A (2022). _rtdists: Response Time Distributions_. Rpackage version 0.11-5, . +# - Voss, A., Rothermund, K., & Voss, J. (2004). Interpreting the parameters of the diffusion model: An empirical validation. *Memory and Cognition, 32(7)*, 1206-1220. +# - Ratcliff, R. (1978). A theory of memory retrieval. *Psychology Review, 85(2)*, 59-108. +# - Voss, A., Voss, J., & Lerche, V. (2015). Assessing cognitive processes with diffusion model analyses: A tutorial based on fast-dm-30. *Frontiers in Psychology, 6*, Article 336. https://doi.org/10.3389/fpsyg.2015.00336 +# +################################################################################################################################################################ + +TUNE_PDE_DT_MIN = 1e-6 +TUNE_PDE_DT_MAX = 1e-6 +TUNE_PDE_DT_SCALE = 0.0 + +TUNE_DZ = 0.0 +TUNE_DV = 0.0 +TUNE_DT0 = 0.0 + +TUNE_INT_T0 = 0 +TUNE_INT_Z = 0 + +precision_set = 0 + +function cdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, precision::Real = 3) + if choice == 1 + (ν, α, τ, z, η, sz, st, σ) = params(d) + return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ, precision) #over the upper boundary (g_plus) + end + + return cdf(d, rt; ϵ, precision) +end + +# cumulative density function over the lower boundary (g_minus) +function cdf(d::RatcliffDDM{T}, x::Real; ϵ::Real= 1.0e-6, precision::Real = 3) where {T<:Real} + if d.τ ≥ t + return T(NaN) + end + _set_precision(precision) + DT = x - τ - 0.5 * z + return _integral_τ_g_minus(x, d) +end + +function _set_precision(precision::Real = 3) + """ + Precision of calculation. + Corresponds roughly to the number of decimals of the predicted CDFs that are calculated accurately. Default is 3. + The function adjusts various parameters used in the calculations based on the precision value. + """ + global TUNE_PDE_DT_MIN = (-0.400825*precision-1.422813)^10 + global TUNE_PDE_DT_MAX = (-0.627224*precision+0.492689)^10 + global TUNE_PDE_DT_SCALE = (-1.012677*precision+2.261668)^10 + global TUNE_DZ = (-0.5*precision-0.033403)^10 + global TUNE_DV = (-1.0*precision+1.4)^10 + global TUNE_DT0 = (-0.5*precision-0.323859)^10 + + global TUNE_INT_T0 = 0.089045 * exp(-1.037580*precision) + global TUNE_INT_Z = 0.508061 * exp(-1.022373*precision) + + global precision_set = 1 +end + +function _integrate(F::Function, d::RatcliffDDM, a::Int, b::Int, step_width::Real) + """ + These functions perform numerical integration using a specified function, range, and step width. The integrate function performs the integration sequentially + """ + width = b - a # integration width + N = max(4, Int(width / step_width)) # N at least equals 4 + step = width / N + x = a + 0.5 * step + out = 0 + while x < b + out += step * F(x, d) + x += step + end + return out +end + +function _g_minus_small_time(x::Real, d::RatcliffDDM, N::Int) + """ + calculate the densities g- for the first exit time for small time + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + sum = 0.0 + for i = -N:N÷2 + d = 2*i + z + sum += exp(-d*d / (2*DT)) * d + end + return sum / sqrt(2π*DT*DT*DT) +end + +function _g_minus_large_time(x::Real, d::RatcliffDDM, N::Int) + """ + calculate the densities g- for the first exit time for large time values + """ + DT = x - τ #make into decision time + + sum = 0.0 + for i = 1:N + d = i * π + sum += exp(-0.5 * d*d * DT) * sin(d*z) * i + end + return sum * π +end + +function _g_minus_no_var(x::Real, d::RatcliffDDM) + """ + calculates the density g- when there is no variability in the input parameters. + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + N_small = 0 + N_large = 0 + simple = 0.0 + factor = exp(-α*z*ν - 0.5*ν*ν*DT) / (α*α) # Front term in A3 + ϵ = ϵ / factor + + ta = x / (α*α) + + N_large = ceil(1 / (π*sqrt(DT))) + if π*ta*ϵ < 1 + N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + end + + if 2*sqrt(2*π*ta)*ϵ < 1 + N_small = ceil(max(sqrt(ta) + 1, 2 + sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) + else + N_small = 2 + end + + if N_small < N_large + simple = _g_minus_small_time(x / (α*α), z, N_small) + else + simple = _g_minus_large_time(x / (α*α), z, N_large) + end + + out = isinf(factor) ? 0 : (factor * simple) + return out +end + +function _integral_v_g_minus(Real::x, d::RatcliffDDM; ϵ::Real = 1e-6) + """ + calculates the integral of the density g- over the variable ν for a given set of input parameters. It takes into account variability in the parameters η + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + N_small = 0 + N_large = 0 + simple = 0.0 + factor = 1 / (α*α * sqrt(DT * η*η + 1)) * + exp(-0.5 * (ν*ν*DT + 2*ν*α*z - α*z*α*z*η*η) / (DT*η*η+1)) + ϵ = ϵ / factor + + ta = DT / (α*α) + + N_large = ceil(1 / (π*sqrt(DT))) + if π*ta*ϵ < 1 + N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + end + + if 2*sqrt(2*π*ta)*ϵ < 1 + N_small = ceil(max(sqrt(ta)+1, 2+sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) + else + N_small = 2 + end + + if isinf(factor) + out = 0 + elseif η == 0 + out = _g_minus_no_var(x, d) + elseif N_small < N_large + simple = _g_minus_small_time(x/(α*α), d, N_small) + out = factor * simple + else + simple = _g_minus_large_time(x/(a*a), d, N_large) + out = factor * simple + end + + return out +end + +function _integral_z_g_minus(x::Real, d::RatcliffDDM) + """ + calculate the integral of integral_v_g_minus over the variable zr for a given set of input parameters. They handle variability in the parameter sz + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + out = 0.0 + + if DT <= 0 # if DT <= 0 + out = 0 + elseif sz == 0 # this should be sz + out = _integral_v_g_minus(x, d) + else + a = z - 0.5*sz # zr - 0.5*szr; uniform variability + b = z + 0.5*sz # zr + 0.5*szr + step_width = TUNE_INT_Z + out = _integrate(integral_v_g_minus, d, a, b, step_width) / sz + end + + return out +end + +function _integral_τ_g_minus(x::Real, d::RatcliffDDM) + """ + calculate the integral of integral_z_g_minus over the variable τ for a given set of input parameters. They handle variability in the parameter st + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + out = 0.0 + if st == 0 + out = _integral_z_g_minus(x, d) # should send t as RT + else + a = DT - 0.5*st # DT - 0.5*st + b = DT + 0.5*st # DT + 0.5*st + step_width = TUNE_INT_T0 + out = _integrate(integral_z_g_minus, d, a, b, step_width) / sts + end + + return out +end """ rand(dist::RatcliffDDM) From 8014ab0a0858fe57f3e8bb519e179195e4c01705 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 15:20:54 +0200 Subject: [PATCH 05/43] _pdf change --- src/RatcliffDDM.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 64a23d0c..d25c0be4 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -85,29 +85,29 @@ end # return pdf_sv(d, rt; ϵ) # end -function pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} +function _pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} (ν, α, τ, z, η, sz, st, σ) = params(d) if η == 0 - return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) end # if isless(ν,0) # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 # end # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 - return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 end function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) if choice == 1 (ν, α, τ, z, η, sz, st, σ) = params(d) - return pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + return _pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) end - return pdf(d, rt; ϵ) + return _pdf(d, rt; ϵ) end #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} +function _pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} (ν, α, τ, z, η, sz, st, σ) = params(d) if τ ≥ rt @@ -123,7 +123,7 @@ function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2 if sz==0 if st==0 #sv=0,sz=0,st=0 - return pdf_sv(d, rt; ϵ) + return _pdf_sv(d, rt; ϵ) else #sv=0,sz=0,st=$ return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) end @@ -160,7 +160,7 @@ function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ub_z = z end - S = pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) + S = _pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) y = 0 z_tag = 0 @@ -170,7 +170,7 @@ function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, z_tag = lb_z + hz * i t_tag = lb_t + ht * i - y = pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + y = _pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) if isodd(i) S += 4 * y From c8f1c9dba766bea148648e12cd6f52a4eec8d478 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 18:51:22 +0200 Subject: [PATCH 06/43] cdf code must be redone. Wrong in a few ways --- src/RatcliffDDM.jl | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index d25c0be4..a6324a84 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -232,30 +232,9 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) ######################################################################################################################################################################## -# Calculate Drift-diffusion Probability Density +# Calculate Cumulative Distribution Function # -# compute the densities g- and g+ of the first exit time. `cdf` implement A1 to A4 equations in Voss, Rothermund, and Voss (2004). These equations calculate Ratcliff's -# drift-diffusion model (1978). This source codes are derived from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). -# -# * ------------------------------------------------------------------------ -# * A verbatim copy of Jochen Voss & Andreas Voss's copyright. -# * ------------------------------------------------------------------------ -# * Copyright (C) 2012 Andreas Voss, Jochen Voss. -# * -# * This program is free software; you can redistribute it and/or -# * modify it under the terms of the GNU General Public License as -# * published by the Free Software Foundation; either version 2 of the -# * License, or (at your option) any later version. -# * -# * This program is distributed in the hope that it will be useful, but -# * WITHOUT ANY WARRANTY; without even the implied warranty of -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# * General Public License for more details. -# * -# * You should have received a copy of the GNU General Public License -# * along with this program; if not, write to the Free Software -# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# * 02110-1301 USA. +# This source codes are adpated from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). # # # References # @@ -519,16 +498,6 @@ end function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) (ν, α, τ, z, η, sz, st, σ) = params(d) - if (ν < -5) || (ν > 5) - ν = sign(ν) * 5 - warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") - end - - if η > 3 - warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") - η = 3 - end - if η == 0 η = 1e-16 end From dcccdf66c13fdc494ec0e3bd6e10526f9fb96abc Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 18:38:39 +0200 Subject: [PATCH 07/43] added non license breaking implementation of cdf --- .vscode/settings.json | 3 + src/DDM.jl | 2 +- src/RatcliffDDM.jl | 406 ++++++++++++++++++++---------------------- 3 files changed, 194 insertions(+), 217 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ca783c47 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "julia.environmentPath": "/Users/kiantefernandez/Documents/Julia/SequentialSamplingModels.jl" +} \ No newline at end of file diff --git a/src/DDM.jl b/src/DDM.jl index 47975a13..f451e6c2 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -267,7 +267,7 @@ end """ rand(dist::DDM) -Generate a random rt for the Diffusion Decision Model (negative coding) +Generate a random rt for the Diffusion Decision Model # Arguments - `dist`: model object for the Diffusion Decision Model. diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index a6324a84..1ad58d14 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -234,237 +234,215 @@ logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) ######################################################################################################################################################################## # Calculate Cumulative Distribution Function # -# This source codes are adpated from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). +# Computes Cumulative Distribution Function for the Ratcliff Diffusion model +# using 6 Gaussian quadrature for numerical integration # -# # References -# -# - Singmann H, Brown S, Gretton M, Heathcote A (2022). _rtdists: Response Time Distributions_. Rpackage version 0.11-5, . -# - Voss, A., Rothermund, K., & Voss, J. (2004). Interpreting the parameters of the diffusion model: An empirical validation. *Memory and Cognition, 32(7)*, 1206-1220. -# - Ratcliff, R. (1978). A theory of memory retrieval. *Psychology Review, 85(2)*, 59-108. -# - Voss, A., Voss, J., & Lerche, V. (2015). Assessing cognitive processes with diffusion model analyses: A tutorial based on fast-dm-30. *Frontiers in Psychology, 6*, Article 336. https://doi.org/10.3389/fpsyg.2015.00336 +# References +# Tuerlinckx, F. (2004). The efficient computation of the +# cumulative distribution and probability density functions +# in the diffusion model, Behavior Research Methods, +# Instruments, & Computers, 36 (4), 702-716.# # +# +# Converted from cdfdif.c C script by Joachim Vandekerckhove +# See also https://ppw.kuleuven.be/okp/software/dmat/ ################################################################################################################################################################ +function cdf(d::RatcliffDDM,choice,rt,p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) -TUNE_PDE_DT_MIN = 1e-6 -TUNE_PDE_DT_MAX = 1e-6 -TUNE_PDE_DT_SCALE = 0.0 - -TUNE_DZ = 0.0 -TUNE_DV = 0.0 -TUNE_DT0 = 0.0 - -TUNE_INT_T0 = 0 -TUNE_INT_Z = 0 - -precision_set = 0 - -function cdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, precision::Real = 3) - if choice == 1 - (ν, α, τ, z, η, sz, st, σ) = params(d) - return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ, precision) #over the upper boundary (g_plus) - end - - return cdf(d, rt; ϵ, precision) -end - -# cumulative density function over the lower boundary (g_minus) -function cdf(d::RatcliffDDM{T}, x::Real; ϵ::Real= 1.0e-6, precision::Real = 3) where {T<:Real} - if d.τ ≥ t - return T(NaN) - end - _set_precision(precision) - DT = x - τ - 0.5 * z - return _integral_τ_g_minus(x, d) -end - -function _set_precision(precision::Real = 3) - """ - Precision of calculation. - Corresponds roughly to the number of decimals of the predicted CDFs that are calculated accurately. Default is 3. - The function adjusts various parameters used in the calculations based on the precision value. - """ - global TUNE_PDE_DT_MIN = (-0.400825*precision-1.422813)^10 - global TUNE_PDE_DT_MAX = (-0.627224*precision+0.492689)^10 - global TUNE_PDE_DT_SCALE = (-1.012677*precision+2.261668)^10 - global TUNE_DZ = (-0.5*precision-0.033403)^10 - global TUNE_DV = (-1.0*precision+1.4)^10 - global TUNE_DT0 = (-0.5*precision-0.323859)^10 - - global TUNE_INT_T0 = 0.089045 * exp(-1.037580*precision) - global TUNE_INT_Z = 0.508061 * exp(-1.022373*precision) - - global precision_set = 1 -end - -function _integrate(F::Function, d::RatcliffDDM, a::Int, b::Int, step_width::Real) - """ - These functions perform numerical integration using a specified function, range, and step width. The integrate function performs the integration sequentially - """ - width = b - a # integration width - N = max(4, Int(width / step_width)) # N at least equals 4 - step = width / N - x = a + 0.5 * step - out = 0 - while x < b - out += step * F(x, d) - x += step - end - return out -end - -function _g_minus_small_time(x::Real, d::RatcliffDDM, N::Int) - """ - calculate the densities g- for the first exit time for small time - """ (ν, α, τ, z, η, sz, st, σ) = params(d) - DT = x - τ #make into decision time - - sum = 0.0 - for i = -N:N÷2 - d = 2*i + z - sum += exp(-d*d / (2*DT)) * d - end - return sum / sqrt(2π*DT*DT*DT) -end + #check arguments + # if p_outlier > 0 + # @assert maximum(abs.(x)) < (1./(2*w_outlier)) "1. / (2*w_outlier) must be smaller than RT" + # end -function _g_minus_large_time(x::Real, d::RatcliffDDM, N::Int) - """ - calculate the densities g- for the first exit time for large time values - """ - DT = x - τ #make into decision time + # if (η < 0) || (α <=0 ) || (z < 0) || (z > 1) || (sz < 0) || (sz > 1) || (z+sz/2.>1) || \ + # (z-sz/2.<0) || (τ-st/2.<0) || (τ<0) || (st < 0) || !_p_outlier_in_range(p_outlier) + # error("At least one of the parameters is out of the support") + # end - sum = 0.0 - for i = 1:N - d = i * π - sum += exp(-0.5 * d*d * DT) * sin(d*z) * i + size = length(rt) + y = zeros(Float64, size) + epsi = 1e-10 + + #transform parameters + α = α/10. + τ = τ + η = η/10. + epsi + z = z*(α/10.) + sz = sz*(α/10.) + epsi + st = st + epsi + ν = ν/10. + + p_boundary = 0.0 + + for i in 1:size + y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) + y[i] = (1 - p_boundary) + rt[i]*y[i] + #add p_outlier probability? + #y[i] = _add_outlier_cdf(y[i], x[i], p_outlier, w_outlier) end - return sum * π + + return y end -function _g_minus_no_var(x::Real, d::RatcliffDDM) - """ - calculates the density g- when there is no variability in the input parameters. - """ +function _cdf(d::RatcliffDDM{T}, choice,rt; ϵ::Real = 1e-7) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - N_small = 0 - N_large = 0 - simple = 0.0 - factor = exp(-α*z*ν - 0.5*ν*ν*DT) / (α*α) # Front term in A3 - ϵ = ϵ / factor - - ta = x / (α*α) - - N_large = ceil(1 / (π*sqrt(DT))) - if π*ta*ϵ < 1 - N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) + #note we need to make sure this is consistent in the all the relative bound models + if choice == 2 #lower + choice = 0 + else if choice == 1 #upper + choice = 1 end - if 2*sqrt(2*π*ta)*ϵ < 1 - N_small = ceil(max(sqrt(ta) + 1, 2 + sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) - else - N_small = 2 + # Initializing variables + a2 = α*α + Z_U = (1-choice)*z+choice*(α-z)+sz/2 + Z_L = (1-choice)*z+choice*(α-z)-sz/2 + lower_t = τ-st/2 + upper_t = 0.0 + Δ = 1e-29 + min_rt=0.001 + v_max = 5000 # maximum number of terms in a partial sum approximating infinite series + + Fnew = 0.0 + sum_z=0.0 + sum_ν=0.0 + p1 = 0.0 + p0 = 0.0 + sum_hist = zeros(3) + denom = 0.0 + sifa = 0.0 + upp = 0.0 + low = 0.0 + fact = 0.0 + exdif = 0.0 + su = 0.0 + sl = 0.0 + zzz = 0.0 + ser = 0.0 + nr_ν = 6 + nr_z = 6 + + # Defining Gauss-Hermite abscissae and weights for numerical integration + gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] + w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] + gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] + w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] + + # Adjusting Gauss-Hermite abscissae and weights + for i=1:nr_ν + gk[i] = 1.41421356237309505*gk[i]*η+ν + w_gh[i] = w_gh[i]/1.772453850905515882 end - - if N_small < N_large - simple = _g_minus_small_time(x / (α*α), z, N_small) - else - simple = _g_minus_large_time(x / (α*α), z, N_large) + for i=1:nr_z + gz[i] = (.5*sz*gz[i])+z end - out = isinf(factor) ? 0 : (factor * simple) - return out -end - -function _integral_v_g_minus(Real::x, d::RatcliffDDM; ϵ::Real = 1e-6) - """ - calculates the integral of the density g- over the variable ν for a given set of input parameters. It takes into account variability in the parameters η - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - N_small = 0 - N_large = 0 - simple = 0.0 - factor = 1 / (α*α * sqrt(DT * η*η + 1)) * - exp(-0.5 * (ν*ν*DT + 2*ν*α*z - α*z*α*z*η*η) / (DT*η*η+1)) - ϵ = ϵ / factor - - ta = DT / (α*α) - - N_large = ceil(1 / (π*sqrt(DT))) - if π*ta*ϵ < 1 - N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + # numerical integration + for i=1:nr_z + sum_ν=0.0 + # numerical integration + for m=1:nr_ν + if abs(gk[m])>ϵ + sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] + else + sum_ν+=gz[i]/α*w_gh[m] + end + end + sum_z+=sum_ν*w_g[i]/2 end + prob = sum_z + + if (rt-τ+st/2 > min_RT) # is t larger than lower boundary τ distribution? + upper_t = min(rt, τ+st/2) + p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t + p0 = (1-prob)*(upper_t-lower_t)/st + if rt > τ+st/2 # is t larger than upper boundary Ter distribution? + sum_hist = zeros(3) + for v in 1:v_max # infinite series + sum_hist = circshift(sum_hist, 1) + sum_ν = 0 + sifa = π*v/α + for m in 1:nr_ν # numerical integration with respect to xi + denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) + upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - + low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) + exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) + sum_ν += fact*exdif + end + sum_hist[3] = sum_hist[2] + v*sum_ν + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end - if 2*sqrt(2*π*ta)*ϵ < 1 - N_small = ceil(max(sqrt(ta)+1, 2+sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) - else - N_small = 2 + Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) + # cumulative distribution function for t and x + elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? + sum_ν = 0 + for m in 1:nr_ν + if abs(gk[m]) > ϵ + sum_z = 0 + for i in 1:nr_z + zzz = (α - gz[i])*choice + gz[i]*(1 - choice) + ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ + (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + + (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ + sinh((1 - 2*choice)*gk[m]*α/.01) + sum_hist = zeros(3) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) + sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) + end + else + sum_hist = zeros(3) + su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) + sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (π*v)*(π*v)/(a2*100) + sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* + exp(-.5*denom*(rt - lower_t)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) + end + sum_ν += sum_z*w_gh[m] + end + Fnew = (p0*(1 - choice) + p1*choice) - sum_ν + end + elseif rt - τ + st/2 <= min_RT # is t lower than lower boundary Ter distr? + Fnew = 0 end + + Fnew = Fnew > Δ ? Fnew : 0 - if isinf(factor) - out = 0 - elseif η == 0 - out = _g_minus_no_var(x, d) - elseif N_small < N_large - simple = _g_minus_small_time(x/(α*α), d, N_small) - out = factor * simple - else - simple = _g_minus_large_time(x/(a*a), d, N_large) - out = factor * simple - end + return Fnew - return out end -function _integral_z_g_minus(x::Real, d::RatcliffDDM) - """ - calculate the integral of integral_v_g_minus over the variable zr for a given set of input parameters. They handle variability in the parameter sz - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - out = 0.0 - - if DT <= 0 # if DT <= 0 - out = 0 - elseif sz == 0 # this should be sz - out = _integral_v_g_minus(x, d) - else - a = z - 0.5*sz # zr - 0.5*szr; uniform variability - b = z + 0.5*sz # zr + 0.5*szr - step_width = TUNE_INT_Z - out = _integrate(integral_v_g_minus, d, a, b, step_width) / sz - end - - return out +function _add_outlier_cdf(y::Real, x::Real, p_outlier::Real; w_outlier::Real = 0.1) + #Ratcliff and Tuerlinckx, 2002 containment process + return y * (1 - p_outlier) + (x + (1. / (2 * w_outlier))) * w_outlier * p_outlier end -function _integral_τ_g_minus(x::Real, d::RatcliffDDM) - """ - calculate the integral of integral_z_g_minus over the variable τ for a given set of input parameters. They handle variability in the parameter st - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - out = 0.0 - if st == 0 - out = _integral_z_g_minus(x, d) # should send t as RT - else - a = DT - 0.5*st # DT - 0.5*st - b = DT + 0.5*st # DT + 0.5*st - step_width = TUNE_INT_T0 - out = _integrate(integral_z_g_minus, d, a, b, step_width) / sts - end - - return out +function _p_outlier_in_range(p_outlier) + return (p_outlier >= 0) & (p_outlier <= 1) end """ @@ -485,7 +463,6 @@ Generate a random choice and rt for the Ratcliff Diffusion Model Behavior Research Methods, Instruments, & Computers, 33, 443-456. Converted from Rhddmjagsutils.R R script by Kianté Fernandez - See also https://github.com/kiante-fernandez/Rhddmjags. """ function rand(rng::AbstractRNG, d::RatcliffDDM) @@ -503,9 +480,8 @@ function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) end # Initialize output vectors - result = zeros(N) - T = zeros(N) - XX = zeros(N) + choice = fill(0, N) + rt = fill(0.0, N) # Called sigma in 2001 paper D = σ^2 / 2 @@ -559,12 +535,12 @@ function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) dir_ = startpos + dir_ * radius ndt = τ - st / 2 + st * rand() if (dir_ + Δ) > Aupper - T[n] = ndt + totaltime - XX[n] = 1 + rt[n] = ndt + totaltime + choice[n] = 1 finish = 1 elseif (dir_ - Δ) < Alower - T[n] = ndt + totaltime - XX[n] = 2 + rt[n] = ndt + totaltime + choice[n] = 2 finish = 1 else startpos = dir_ @@ -572,7 +548,7 @@ function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) end end end - return (choice=XX,rt=T) + return (choice=choice,rt=rt) end function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) @@ -627,5 +603,3 @@ Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. function rand(rng::AbstractRNG, d::RatcliffDDM, n_sim::Int) return _rand_rejection(rng, d, N = n_sim) end - -sampler(rng::AbstractRNG, d::RatcliffDDM) = rand(rng::AbstractRNG, d::RatcliffDDM) From 1560760e19e07772f703bfcb28a7a2c983efcd5c Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 18:57:21 +0200 Subject: [PATCH 08/43] bugs --- src/RatcliffDDM.jl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 1ad58d14..2e706f12 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -231,23 +231,23 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) -######################################################################################################################################################################## -# Calculate Cumulative Distribution Function -# -# Computes Cumulative Distribution Function for the Ratcliff Diffusion model -# using 6 Gaussian quadrature for numerical integration -# -# References -# Tuerlinckx, F. (2004). The efficient computation of the -# cumulative distribution and probability density functions -# in the diffusion model, Behavior Research Methods, -# Instruments, & Computers, 36 (4), 702-716.# -# -# -# Converted from cdfdif.c C script by Joachim Vandekerckhove -# See also https://ppw.kuleuven.be/okp/software/dmat/ -################################################################################################################################################################ -function cdf(d::RatcliffDDM,choice,rt,p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) +################################################################################ +# Calculate Cumulative Distribution Function # +# # +# Computes Cumulative Distribution Function for the Ratcliff Diffusion model # +# using 6 Gaussian quadrature for numerical integration # +# # +# References # +# Tuerlinckx, F. (2004). The efficient computation of the # +# cumulative distribution and probability density functions # +# in the diffusion model, Behavior Research Methods, # +# Instruments, & Computers, 36 (4), 702-716. # +# # +# Converted from cdfdif.c C script by Joachim Vandekerckhove # +# See also https://ppw.kuleuven.be/okp/software/dmat/ # +################################################################################ + +function cdf(d::RatcliffDDM, choice, rt, p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) (ν, α, τ, z, η, sz, st, σ) = params(d) @@ -293,7 +293,7 @@ function _cdf(d::RatcliffDDM{T}, choice,rt; ϵ::Real = 1e-7) where {T<:Real} #note we need to make sure this is consistent in the all the relative bound models if choice == 2 #lower choice = 0 - else if choice == 1 #upper + elseif choice == 1 #upper choice = 1 end @@ -591,12 +591,12 @@ function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps:: end """ - rand(dist::DDM, n_sim::Int) + rand(dist::RatcliffDDM, n_sim::Int) -Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. +Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. # Arguments -- `dist`: model object for the Drift Diffusion Model. +- `dist`: model object for the Ratcliff DDM. - `n_sim::Int`: the number of simulated rts """ From 1e7669cad659e6d4549cea4edbc4d4e5042f2177 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:16:48 +0200 Subject: [PATCH 09/43] minor bugs --- src/RatcliffDDM.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 2e706f12..b0fb1109 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -286,7 +286,7 @@ function cdf(d::RatcliffDDM, choice, rt, p_outlier; w_outlier::Real = 0.1, ϵ::R return y end -function _cdf(d::RatcliffDDM{T}, choice,rt; ϵ::Real = 1e-7) where {T<:Real} +function _cdf(d::RatcliffDDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} (ν, α, τ, z, η, sz, st, σ) = params(d) #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) @@ -356,7 +356,7 @@ function _cdf(d::RatcliffDDM{T}, choice,rt; ϵ::Real = 1e-7) where {T<:Real} end prob = sum_z - if (rt-τ+st/2 > min_RT) # is t larger than lower boundary τ distribution? + if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? upper_t = min(rt, τ+st/2) p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t p0 = (1-prob)*(upper_t-lower_t)/st @@ -426,7 +426,7 @@ function _cdf(d::RatcliffDDM{T}, choice,rt; ϵ::Real = 1e-7) where {T<:Real} end Fnew = (p0*(1 - choice) + p1*choice) - sum_ν end - elseif rt - τ + st/2 <= min_RT # is t lower than lower boundary Ter distr? + elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? Fnew = 0 end From dc8cc16b79a0e6f87af2b3a90fb94d734adde786 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:43:39 +0200 Subject: [PATCH 10/43] rm vscode settings --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ca783c47..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "julia.environmentPath": "/Users/kiantefernandez/Documents/Julia/SequentialSamplingModels.jl" -} \ No newline at end of file From 6aa64a2a0af0d1df9f5ebceb849ed456e1ab76ae Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:44:20 +0200 Subject: [PATCH 11/43] settings change --- .gitignore | 1 + .vscode/settings.json | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 0a29f2eb..f80bf72d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /Manifest.toml /docs/Manifest.toml /temp +./vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ca783c47 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "julia.environmentPath": "/Users/kiantefernandez/Documents/Julia/SequentialSamplingModels.jl" +} \ No newline at end of file From 1e6f3160fcde70d08cdd47a5e82b16d8ee8698e4 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:47:14 +0200 Subject: [PATCH 12/43] rm vscode settings --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ca783c47..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "julia.environmentPath": "/Users/kiantefernandez/Documents/Julia/SequentialSamplingModels.jl" -} \ No newline at end of file From 0d96d73cf3fc407879b4eb74778c699ed7b03e66 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:47:34 +0200 Subject: [PATCH 13/43] change vscode settings --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f80bf72d..e025c355 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /Manifest.toml /docs/Manifest.toml /temp -./vscode \ No newline at end of file +/.vscode \ No newline at end of file From 50d3acba5fb3304b1f7b24a58287ac2eea2dd16e Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 5 Jul 2023 18:58:18 +0200 Subject: [PATCH 14/43] adding Ratcliff DDM --- docs/src/Ratcliff_DDM.md | 2 +- src/SequentialSamplingModels.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/Ratcliff_DDM.md b/docs/src/Ratcliff_DDM.md index f6cd3ff4..2465cbf0 100644 --- a/docs/src/Ratcliff_DDM.md +++ b/docs/src/Ratcliff_DDM.md @@ -72,7 +72,7 @@ sz = 0.05 Now that values have been assigned to the parameters, we will pass them to `RatcliffDDM` to generate the model object. ```@example RatcliffDDM -dist = DDM(ν, α, τ, z) +dist = RatcliffDDM(ν, α, τ, z, η, sz, st, σ) ``` ## Simulate Model diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index 49332bf4..e0ec4fe8 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -36,6 +36,7 @@ module SequentialSamplingModels export LNR export maaDDM export MixedMultivariateDistribution + export RatcliffDDM export SSM1D export SSM2D export Wald From f8de1415ef1169a5f5a13ba7401d7be606ab8ba5 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 15:42:20 +0200 Subject: [PATCH 15/43] Merge ratcliff and DDM --- src/DDM.jl | 888 ++++++++++++++++++++++++-------- src/RatcliffDDM.jl | 605 ---------------------- src/SequentialSamplingModels.jl | 2 - src/utilities.jl | 4 +- 4 files changed, 675 insertions(+), 824 deletions(-) delete mode 100644 src/RatcliffDDM.jl diff --git a/src/DDM.jl b/src/DDM.jl index 1df44e27..744a3fa5 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -1,80 +1,179 @@ """ DDM{T<:Real} <: SSM2D -Model object for the standard Drift Diffusion Model. + Model object for the Ratcliff (Full) Diffusion Decision Model. # Parameters -- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 + - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 + - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 + - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. + - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. + - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. + - `σ`: diffusion noise constant. Default is 1. # Constructors - DDM(ν, α, τ, z) + DDM(ν, α, τ, z, η, sz, st, σ) - DDM(; ν = 1.0, - α = 0.8, - τ = 0.3 - z = 0.25) - + DDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + # Example -```julia +````julia using SequentialSamplingModels -dist = DDM(ν = 1.0, α = 0.8, τ = 0.3, z = 0.25) +dist = DDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) choice,rt = rand(dist, 10) like = pdf.(dist, choice, rt) loglike = logpdf.(dist, choice, rt) -``` - -# References +```` +# References + Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. +Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ mutable struct DDM{T<:Real} <: SSM2D ν::T α::T τ::T z::T + η::T + sz::T + st::T + σ::T end -function DDM(ν, α, τ, z) - return DDM(promote(ν, α, τ, z)...) +function DDM(ν, α, τ, z, η, sz, st, σ) + return DDM(promote(ν, α, τ, z, η, sz, st, σ)...) end function params(d::DDM) - (d.ν, d.α, d.τ, d.z) + (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) end function DDM(; ν = 1.00, α = 0.80, τ = 0.30, - z = 0.50) + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0) + return DDM(ν, α, τ, z, η, sz, st, σ) +end - return DDM(ν, α, τ, z) +function pdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) + if choice == 1 + (ν, α, τ, z, η, sz, st, σ) = params(d) + return _pdf_Full(DDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + end + return _pdf_Full(d, rt; ϵ) end -################################################################################ -# Converted from WienerDiffusionModel.jl repository orginally by Tobias Alfers# -# See https://github.com/t-alfers/WienerDiffusionModel.jl # -################################################################################ -##################################### -# Probability density function # -# Navarro & Fuss (2009) # -# Wabersich & Vandekerckhove (2014) # -##################################### +""" + _pdf_Full(d::DDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} -function pdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z) = params(d) - return _pdf(DDM(-ν, α, τ, 1-z), rt; ϵ) +Calculate the probability density function (PDF) for a Diffusion Decision Model (DDM) object. This +function applies numerical integration to account for variability in non-decision time and bias, as suggested +by Ratcliff and Tuerlinckx (2002). + +# Arguments +- `d::DDM{T}`: a DDM distribution object +- `rt`: reaction time. + +# Optional arguments +- `ϵ::Real`: a small constant to prevent divide by zero errors, default is 1.0e-12. +- `n_st::Int`: specifies the number of subintervals in the Simpson's rule for the integration associated with non-decision time variability. Default is 2. +- `n_sz::Int`: specifies the number of subintervals in the Simpson's rule for the integration associated with starting point variability. Default is 2. + +""" +function _pdf_Full(d::DDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} + + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if τ ≥ rt + return T(NaN) + end + + if st < 1.0e-3 + st = 0 + end + if sz < 1.0e-3 + sz = 0 + end + + if sz==0 + if st==0 #sv=0,sz=0,st=0 + return _pdf_sv(d, rt; ϵ) + else #sv=0,sz=0,st=$ + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) + end + else #sz=$ + if st==0 #sv=0,sz=$,st=0 + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) + else #sv=0,sz=$,st=$ + return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) + end end - return _pdf(d, rt; ϵ) end -# probability density function over the lower boundary +""" + _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) + +Computes the Probability Density Function (PDF) for a given Diffusion Decision Model +with across-trial variability in drift-rate. This function uses analytic integration of the likelihood function +for variability in drift-rate. + +# Arguments +- `d::DDM`: a DDM distribution constructor object +- `rt`: Reaction time for which the PDF is to be computed. + +# Returns +- Returns the computed PDF value. + +""" +function _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if η == 0 + return _pdf(DDM(ν, α, τ, z), rt; ϵ) + end + + return _pdf(DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 +end + +""" + _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} + +Computes the Probability Density Function (PDF) for a given Drift Diffusion Model (DDM). +The function uses normalized time and applies an infinite sum algorithm. The implementation +is based on the work of Navarro & Fuss (2009) and Wabersich & Vandekerckhove (2014). + +# Arguments +- `d::DDM{T}`: A DDM object containing the parameters of the Diffusion Model. +- `t::Real`: Time for which the PDF is to be computed. + +# Optional Arguments +- `ϵ::Real = 1.0e-12`: A very small number representing machine epsilon. + +# Returns +- Returns the computed PDF value. + +# See also: + - Converted from WienerDiffusionModel.jl repository orginally by Tobias Alfers: https://github.com/t-alfers/WienerDiffusionModel.jl +""" + function _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} (ν, α, τ, z) = params(d) if τ ≥ t @@ -126,230 +225,465 @@ function _large_time_pdf(u::T, z::T, K::Int) where {T<:Real} return π * inf_sum end -logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -#logpdf(d::DDM, t::Real; ϵ::Real = 1.0e-12) = log(pdf(d, t; ϵ)) +""" + _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -function logpdf(d::DDM, data::T) where {T<:NamedTuple} - return sum(logpdf.(d, data...)) -end +Calculate the 1-dimensional Simpson's numerical integration for a drift diffusion model with given parameters. This function is used for integrating over either the starting point or the non-decision time. -function logpdf(dist::DDM, data::Array{<:Tuple,1}) - LL = 0.0 - for d in data - LL += logpdf(dist, d...) - end - return LL -end +# Arguments +- `x::Real`: Reaction time for which the probability density function is being computed. +- `ν::Real`, `η::Real`, `α::Real`, `z::Real`, `τ::Real`: Parameters of the Ratcliff Drift Diffusion Model. +- `ϵ::Real`: A small constant to prevent divide by zero errors. +- `lb_z::Real`, `ub_z::Real`: Lower and upper bounds for z (starting point). +- `n_sz::Int`: Specifies the number of subintervals for Simpson's rule in the z dimension. +- `lb_t::Real`, `ub_t::Real`: Lower and upper bounds for t (non-decision time). +- `n_st::Int`: Specifies the number of subintervals for Simpson's rule in the t dimension. -logpdf(d::DDM, data::Tuple) = logpdf(d, data...) +# Returns +- Returns the Simpson's numerical integration of the PDF of the Ratcliff Drift Diffusion Model at the given reaction time `x`, over the specified bounds and subintervals. -######################################### -# Cumulative density function # -# Blurton, Kesselmeier, & Gondan (2012) # -######################################### - -function cdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z) = params(d) - return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ) - end +# Note +- If `n_st` is 0, the function integrates over z (starting point). If `n_sz` is 0, the function integrates over t (non-decision time). - return cdf(d, rt; ϵ) -end +# References -# cumulative density function over the lower boundary -function cdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - if d.τ ≥ t - return T(NaN) +https://en.wikipedia.org/wiki/Simpson%27s_rule + +""" +# Simpson's Method one dimentional case +function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + + n = max(n_st, n_sz) + + if n_st == 0 #integration over z + hz = (ub_z-lb_z)/n + ht = 0 + lb_t = τ + ub_t = τ + else #integration over t + hz = 0 + ht = (ub_t-lb_t)/n + lb_z = z + ub_z = z end - K_l = _K_large(d, t; ϵ) - K_s = _K_small(d, t; ϵ) + S = _pdf_sv(DDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) + + y = 0 + z_tag = 0 + t_tag = 0 + + for i in 1:n + z_tag = lb_z + hz * i + t_tag = lb_t + ht * i + + y = _pdf_sv(DDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + + if isodd(i) + S += 4 * y + else + S += 2 * y + end - if K_l < 10*K_s - return _Fl_lower(d, K_l, t) end - return _Fs_lower(d, K_s, t) -end + + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st + + return (ht + hz) * S / 3 -# Large time representation of lower subdistribution -function _Fl_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (ν, α, τ, z) = params(d) - F = zero(T) - K_series = K:-1:1 - for k in K_series - F -= (k/(ν^2 + k^2*π^2/(α^2)) * - exp(-ν*α*z - 0.5*ν^2*(t-τ) - 0.5*k^2*π^2/(α^2)*(t-τ)) * - sin(π * k * z)) - end - return _P_upper(ν, α, z) + 2*π/(α^2) * F end -# Small time representation of the upper subdistribution -function _Fs_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (ν, α, τ, z) = params(d) - if abs(ν) < sqrt(eps(T)) - return _Fs0_lower(d, K, t) - end +""" + _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + +Calculate the 2-dimensional Simpson's numerical integration for a drift diffusion model with given parameters. This function is used for integrating over both the starting point and the non-decision time. - sqt = sqrt(t-τ) +# Arguments +- `x::Real`: Reaction time for which the probability density function is being computed. +- `ν::Real`, `η::Real`, `α::Real`, `z::Real`, `τ::Real`: Parameters of the Ratcliff Drift Diffusion Model. +- `ϵ::Real`: A small constant to prevent divide by zero errors. +- `lb_z::Real`, `ub_z::Real`: Lower and upper bounds for z (starting point). +- `n_sz::Int`: Specifies the number of subintervals for Simpson's rule in the z dimension. +- `lb_t::Real`, `ub_t::Real`: Lower and upper bounds for t (non-decision time). +- `n_st::Int`: Specifies the number of subintervals for Simpson's rule in the t dimension. + +# Returns +- Returns the Simpson's numerical integration of the PDF of the Ratcliff Drift Diffusion Model at the given reaction time `x`, over the specified bounds and subintervals in both dimensions. + +# Note +- The function calls `_simpson_1D` to perform the 1-dimensional integrations over each dimension. - S1 = zero(T) - S2 = zero(T) - K_series = K:-1:1 +""" +# Simpson's Method two dimentional case +function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + + ht = (ub_t-lb_t)/n_st + S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - for k in K_series - S1 += (_exp_pnorm(2*ν*α*k, -sign(ν)*(2*α*k+α*z+ν*(t-τ))/sqt) - - _exp_pnorm(-2*ν*α*k-2*ν*α*z, sign(ν)*(2*α*k+α*z-ν*(t-τ))/sqt)) + t_tag = 0 + y = 0 + for i_t in 1:n_st + t_tag = lb_t + ht * i_t + y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - S2 += (_exp_pnorm(-2*ν*α*k, sign(ν)*(2*α*k-α*z-ν*(t-τ))/sqt) - - _exp_pnorm(2*ν*α*k-2*ν*α*z, -sign(ν)*(2*α*k-α*z+ν*(t-τ))/sqt)) + if isodd(i_t) + S += 4 * y + else + S += 2 * y + end end - return _P_upper(ν, α, z) + sign(ν) * ((cdf(Normal(), -sign(ν) * (α*z+ν*(t-τ))/sqt) - - _exp_pnorm(-2*ν*α*z, sign(ν) * (α*z-ν*(t-τ)) / sqt)) + S1 + S2) + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / (ub_t - lb_t) + + return ht * S / 3 + end -# Zero drift version -function _Fs0_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (_, α, τ, z) = params(d) - F = zero(T) - K_series = K:-1:0 - for k in K_series - F -= (cdf(Distributions.Normal(), (-2*k - 2+z) * α / sqrt(t-τ)) + cdf(Distributions.Normal(), (-2*k -z) * α / sqrt(t-τ))) +logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) + +# function logpdf(d::DDM, data::T) where {T<:NamedTuple} +# return sum(logpdf.(d, data...)) +# end + +# function logpdf(dist::DDM, data::Array{<:Tuple,1}) +# LL = 0.0 +# for d in data +# LL += logpdf(dist, d...) +# end +# return LL +# end + +# logpdf(d::DDM, data::Tuple) = logpdf(d, data...) + +""" + cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) + +Compute the Cumulative Distribution Function (CDF) for the Ratcliff Diffusion model. This function uses 6 Gaussian quadrature for numerical integration. + +# Arguments +- `d`: an instance of DDM Constructor +- `choice`: an input representing the choice. +- `rt`: response time. +- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. + +# Returns +- `y`: an array representing the CDF of the Ratcliff Diffusion model. + +# Reference +Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution and probability density functions in the diffusion model, Behavior Research Methods, Instruments, & Computers, 36 (4), 702-716. + +# See also +- Converted from cdfdif.c C script by Joachim Vandekerckhove: https://ppw.kuleuven.be/okp/software/dmat/ +""" +function cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) + + (ν, α, τ, z, η, sz, st, σ) = params(d) + + size = length(rt) + y = zeros(Float64, size) + epsi = 1e-10 + + #transform parameters + α = α/10. + τ = τ + η = η/10. + epsi + z = z*(α/10.) + sz = sz*(α/10.) + epsi + st = st + epsi + ν = ν/10. + + p_boundary = 0.0 + + for i in 1:size + y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) + y[i] = (1 - p_boundary) + rt[i]*y[i] end - return 2*F -end -# Number of terms required for large time representation -function _K_large(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) - x = t-τ - sqrtL1 = sqrt(1/x) * α/π - sqrtL2 = sqrt(max(1, -2/x*α*α/π/π * (log(ϵ*π*x/2 * (ν*ν + π*π/α/α)) + ν*α*z + ν*ν*x/2))) - return ceil(Int, max(sqrtL1, sqrtL2)) + + return y end +""" + _cdf(d::DDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} -# Number of terms required for small time representation -function _K_small(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) - if abs(ν) < sqrt(eps(T)) - return ceil(Int, max(0, z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ/(2-2*z)))))) +A helper function to compute the Cumulative Distribution Function (CDF) for the Full Diffusion model. + +# Arguments +- `d`: an instance of DDM Constructor +- `choice`: an input representing the choice. +- `rt`: response time. +- `prob`: a probability value. +- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. + +# Returns +- `Fnew`: the computed CDF for the given parameters. + +""" +function _cdf(d::DDM, choice, rt, prob; ϵ::Real = 1e-7) + + (ν, α, τ, z, η, sz, st, σ) = params(d) + #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) + #note we need to make sure this is consistent in the all the relative bound models + if choice == 2 #lower + choice = 0 + elseif choice == 1 #upper + choice = 1 end - if ν > 0 - return _K_small(DDM(-ν, α, τ, z), t; ϵ = exp(-2*α*z*ν)*ϵ) + + # Initializing variables + a2 = α*α + Z_U = (1-choice)*z+choice*(α-z)+sz/2 + Z_L = (1-choice)*z+choice*(α-z)-sz/2 + lower_t = τ-st/2 + upper_t = 0.0 + Δ = 1e-29 + min_rt=0.001 + v_max = 5000 # maximum number of terms in a partial sum approximating infinite series + + Fnew = 0.0 + sum_z=0.0 + sum_ν=0.0 + p1 = 0.0 + p0 = 0.0 + sum_hist = zeros(3) + denom = 0.0 + sifa = 0.0 + upp = 0.0 + low = 0.0 + fact = 0.0 + exdif = 0.0 + su = 0.0 + sl = 0.0 + zzz = 0.0 + ser = 0.0 + nr_ν = 6 + nr_z = 6 + + # Defining Gauss-Hermite abscissae and weights for numerical integration + gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] + w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] + gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] + w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] + + # Adjusting Gauss-Hermite abscissae and weights + for i=1:nr_ν + gk[i] = 1.41421356237309505*gk[i]*η+ν + w_gh[i] = w_gh[i]/1.772453850905515882 end - S2 = z - 1 + 1/(2*ν*α) * log(ϵ/2 * (1-exp(2*ν*α))) - S3 = (0.535 * sqrt(2*(t-τ)) + ν*(t-τ) + α*z)/(2*α) - S4 = z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ*α/(0.3 * sqrt(2*π*(t-τ))) * exp(ν^2*(t-τ)/2 + ν*α*z) ))) - return ceil(Int, max(0, S2, S3, S4)) -end -# Probability for absorption at upper barrier -function _P_upper(ν::T, α::T, z::T) where {T<:Real} - e = exp(-2 * ν * α * (1-z)) - if isinf(e) - return 1.0 + for i=1:nr_z + gz[i] = (.5*sz*gz[i])+z end - if abs(e-1) < sqrt(eps(T)) - return 1-z + + # numerical integration + for i=1:nr_z + sum_ν=0.0 + # numerical integration + for m=1:nr_ν + if abs(gk[m])>ϵ + sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] + else + sum_ν+=gz[i]/α*w_gh[m] + end + end + sum_z+=sum_ν*w_g[i]/2 end - return (1-e)/(exp(2*ν*α*z) - e) -end + prob = sum_z + + if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? + upper_t = min(rt, τ+st/2) + p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t + p0 = (1-prob)*(upper_t-lower_t)/st + if rt > τ+st/2 # is t larger than upper boundary Ter distribution? + sum_hist = zeros(3) + for v in 1:v_max # infinite series + sum_hist = circshift(sum_hist, 1) + sum_ν = 0 + sifa = π*v/α + for m in 1:nr_ν # numerical integration with respect to xi + denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) + upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - + low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) + exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) + sum_ν += fact*exdif + end + sum_hist[3] = sum_hist[2] + v*sum_ν + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end -# Calculates exp(a) * pnorm(b) using an approximation by Kiani et al. (2008) -function _exp_pnorm(a::T, b::T) where {T<:Real} - r = exp(a) * cdf(Distributions.Normal(), b) - if isnan(r) && b < -5.5 - r = (1/sqrt(2)) * exp(a - b^2/2) * (0.5641882/(b^3) - 1/(b * sqrt(π))) + Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) + # cumulative distribution function for t and x + elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? + sum_ν = 0 + for m in 1:nr_ν + if abs(gk[m]) > ϵ + sum_z = 0 + for i in 1:nr_z + zzz = (α - gz[i])*choice + gz[i]*(1 - choice) + ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ + (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + + (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ + sinh((1 - 2*choice)*gk[m]*α/.01) + sum_hist = zeros(3) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) + sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) + end + else + sum_hist = zeros(3) + su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) + sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (π*v)*(π*v)/(a2*100) + sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* + exp(-.5*denom*(rt - lower_t)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) + end + sum_ν += sum_z*w_gh[m] + end + Fnew = (p0*(1 - choice) + p1*choice) - sum_ν + end + elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? + Fnew = 0 end - return r + + Fnew = Fnew > Δ ? Fnew : 0 + + return Fnew + end """ rand(dist::DDM) -Generate a random rt for the Diffusion Decision Model +Generate a random choice and rt for the Ratcliff Diffusion Model # Arguments -- `dist`: model object for the Diffusion Decision Model. +- `dist`: model object for Ratcliff Diffusion Model. + +method simulating the diffusion process: +_rand_rejection uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process + +# References + + Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). + A comparison of four methods for simulating the diffusion process. + Behavior Research Methods, Instruments, & Computers, 33, 443-456. + + Converted from Rhddmjagsutils.R R script by Kianté Fernandez + See also https://github.com/kiante-fernandez/Rhddmjags. """ function rand(rng::AbstractRNG, d::DDM) return _rand_rejection(rng, d) end -# Rejection-based Method for the Symmetric Wiener Process(Tuerlinckx et al., 2001 based on Lichters et al., 1995) -# adapted from the RWiener R package, note, here σ = 0.1 -function _rand_rejection(rng::AbstractRNG, d::DDM) - (ν, α, τ, z) = params(d) - - ϵ = 1.0e-15 - - D = .005 # = 2*σ^2 => 1/200 - zn = (z*α) / 10 # absolute bias! - αn = α / 10 - νn = ν / 10 - - total_time = 0.0 - start_pos = 0.0 - Aupper = αn - zn - Alower = -zn - radius = min(abs(Aupper), abs(Alower)) - λ = 0.0 - F = 0.0 - prob = 0.0 - - while true - if νn == 0 - λ = (0.25D*π^2) / (radius^2) - F = 1.0 - prob = .5 - else - λ = ((0.25*νn^2)/D) + ((0.25*D*π^2) / (radius^2)) - F = (D*π) / (radius*νn) - F = F^2 / (1+F^2) - prob = exp((radius*νn)/D) - prob = prob / (1+prob) - end - - r = rand(rng) - dir = r < prob ? 1 : -1 - l = -1.0 - s1 = 0.0 - s2 = 0.0 - - # Tuerlinckx et al. (2001; eq. 16) - while s2 > l - s1 = rand(rng) - s2 = rand(rng) - tnew = 0.0 - tδ = 0.0 - uu = zero(Int) - - while (abs(tδ) > ϵ) || (uu == 0) - uu += 1 - tt = 2*uu + 1 - tδ = tt * (uu % 2 == 0 ? 1 : -1) * (s1 ^ (F * tt^2)) - tnew += tδ - end +function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) + (ν, α, τ, z, η, sz, st, σ) = params(d) - l = 1 + (s1^(-F)) * tnew - end + if η == 0 + η = 1e-16 + end - total_time += abs(log(s1)) / λ - dir = start_pos + dir * radius - - if (dir + ϵ) > Aupper - rt = total_time + τ - choice = 1 - return (;choice,rt) - elseif (dir - ϵ) < Alower - rt = total_time + τ - choice = 2 - return (;choice,rt) - else - start_pos = dir - radius = min(abs(Aupper - start_pos), (abs(Alower - start_pos))) + # Initialize output vectors + choice = fill(0, N) + rt = fill(0.0, N) + + # Called sigma in 2001 paper + D = σ^2 / 2 + + # Program specifications + ϵ = eps() # precision from 1.0 to next double-precision number + Δ = ϵ + + for n in 1:N + r1 = randn() + μ = ν + r1 * η + bb = z - sz / 2 + sz * rand() + zz = bb * α + finish = 0 + totaltime = 0 + startpos = 0 + Aupper = α - zz + Alower = -zz + radius = min(abs(Aupper), abs(Alower)) + + while finish == 0 + λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 + # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 + F = D * π / (radius * μ) + F = F^2 / (1 + F^2) + # formula p447 in 2001 paper + prob = exp(radius * μ / D) + prob = prob / (1 + prob) + dir_ = 2 * (rand() < prob) - 1 + l = -1 + s2 = 0 + s1 = 0 + while s2 > l + s2 = rand() + s1 = rand() + tnew = 0 + told = 0 + uu = 0 + while abs(tnew - told) > ϵ || uu == 0 + told = tnew + uu += 1 + tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) + # infinite sum in formula (16) in BRMIC,2001 + end + l = 1 + s1^(-F) * tnew + end + # rest of formula (16) + t = abs(log(s1)) / λ + # is the negative of t* in (14) in BRMIC,2001 + totaltime += t + dir_ = startpos + dir_ * radius + ndt = τ - st / 2 + st * rand() + if (dir_ + Δ) > Aupper + rt[n] = ndt + totaltime + choice[n] = 1 + finish = 1 + elseif (dir_ - Δ) < Alower + rt[n] = ndt + totaltime + choice[n] = 2 + finish = 1 + else + startpos = dir_ + radius = minimum(abs.([Aupper, Alower] .- startpos)) + end end end + return (choice=choice,rt=rt) +end + +""" + rand(dist::DDM, n_sim::Int) + +Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. + +# Arguments +- `dist`: model object for the Ratcliff DDM. +- `n_sim::Int`: the number of simulated rts +""" + +function rand(rng::AbstractRNG, d::DDM, n_sim::Int) + return _rand_rejection(rng, d, N = n_sim) end """ @@ -361,4 +695,128 @@ Returns 2 for the number of choice options - `d::DDM`: a model object for the drift diffusion model """ -n_options(d::DDM) = 2 \ No newline at end of file +n_options(d::DDM) = 2 + + +####### +# old cdf code for debug checking +######################################### +# Cumulative density function # +# Blurton, Kesselmeier, & Gondan (2012) # +######################################### + +# function cdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) +# if choice == 1 +# (ν, α, τ, z) = params(d) +# return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ) +# end + +# return cdf(d, rt; ϵ) +# end + +# # cumulative density function over the lower boundary +# function cdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# if d.τ ≥ t +# return T(NaN) +# end + +# K_l = _K_large(d, t; ϵ) +# K_s = _K_small(d, t; ϵ) + +# if K_l < 10*K_s +# return _Fl_lower(d, K_l, t) +# end +# return _Fs_lower(d, K_s, t) +# end + +# # Large time representation of lower subdistribution +# function _Fl_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (ν, α, τ, z) = params(d) +# F = zero(T) +# K_series = K:-1:1 +# for k in K_series +# F -= (k/(ν^2 + k^2*π^2/(α^2)) * +# exp(-ν*α*z - 0.5*ν^2*(t-τ) - 0.5*k^2*π^2/(α^2)*(t-τ)) * +# sin(π * k * z)) +# end +# return _P_upper(ν, α, z) + 2*π/(α^2) * F +# end + +# # Small time representation of the upper subdistribution +# function _Fs_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (ν, α, τ, z) = params(d) +# if abs(ν) < sqrt(eps(T)) +# return _Fs0_lower(d, K, t) +# end + +# sqt = sqrt(t-τ) + +# S1 = zero(T) +# S2 = zero(T) +# K_series = K:-1:1 + +# for k in K_series +# S1 += (_exp_pnorm(2*ν*α*k, -sign(ν)*(2*α*k+α*z+ν*(t-τ))/sqt) - +# _exp_pnorm(-2*ν*α*k-2*ν*α*z, sign(ν)*(2*α*k+α*z-ν*(t-τ))/sqt)) + +# S2 += (_exp_pnorm(-2*ν*α*k, sign(ν)*(2*α*k-α*z-ν*(t-τ))/sqt) - +# _exp_pnorm(2*ν*α*k-2*ν*α*z, -sign(ν)*(2*α*k-α*z+ν*(t-τ))/sqt)) +# end + +# return _P_upper(ν, α, z) + sign(ν) * ((cdf(Normal(), -sign(ν) * (α*z+ν*(t-τ))/sqt) - +# _exp_pnorm(-2*ν*α*z, sign(ν) * (α*z-ν*(t-τ)) / sqt)) + S1 + S2) +# end + +# # Zero drift version +# function _Fs0_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (_, α, τ, z) = params(d) +# F = zero(T) +# K_series = K:-1:0 +# for k in K_series +# F -= (cdf(Distributions.Normal(), (-2*k - 2+z) * α / sqrt(t-τ)) + cdf(Distributions.Normal(), (-2*k -z) * α / sqrt(t-τ))) +# end +# return 2*F +# end +# # Number of terms required for large time representation +# function _K_large(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# (ν, α, τ, z) = params(d) +# x = t-τ +# sqrtL1 = sqrt(1/x) * α/π +# sqrtL2 = sqrt(max(1, -2/x*α*α/π/π * (log(ϵ*π*x/2 * (ν*ν + π*π/α/α)) + ν*α*z + ν*ν*x/2))) +# return ceil(Int, max(sqrtL1, sqrtL2)) +# end + +# # Number of terms required for small time representation +# function _K_small(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# (ν, α, τ, z) = params(d) +# if abs(ν) < sqrt(eps(T)) +# return ceil(Int, max(0, z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ/(2-2*z)))))) +# end +# if ν > 0 +# return _K_small(DDM(-ν, α, τ, z), t; ϵ = exp(-2*α*z*ν)*ϵ) +# end +# S2 = z - 1 + 1/(2*ν*α) * log(ϵ/2 * (1-exp(2*ν*α))) +# S3 = (0.535 * sqrt(2*(t-τ)) + ν*(t-τ) + α*z)/(2*α) +# S4 = z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ*α/(0.3 * sqrt(2*π*(t-τ))) * exp(ν^2*(t-τ)/2 + ν*α*z) ))) +# return ceil(Int, max(0, S2, S3, S4)) +# end +# # Probability for absorption at upper barrier +# function _P_upper(ν::T, α::T, z::T) where {T<:Real} +# e = exp(-2 * ν * α * (1-z)) +# if isinf(e) +# return 1.0 +# end +# if abs(e-1) < sqrt(eps(T)) +# return 1-z +# end +# return (1-e)/(exp(2*ν*α*z) - e) +# end + +# # Calculates exp(a) * pnorm(b) using an approximation by Kiani et al. (2008) +# function _exp_pnorm(a::T, b::T) where {T<:Real} +# r = exp(a) * cdf(Distributions.Normal(), b) +# if isnan(r) && b < -5.5 +# r = (1/sqrt(2)) * exp(a - b^2/2) * (0.5641882/(b^3) - 1/(b * sqrt(π))) +# end +# return r +# end \ No newline at end of file diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl deleted file mode 100644 index b0fb1109..00000000 --- a/src/RatcliffDDM.jl +++ /dev/null @@ -1,605 +0,0 @@ -""" - RatcliffDDM{T<:Real} <: SSM2D - - Model object for the Ratcliff Diffusion Model. - -# Parameters - - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 - - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 - - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). - - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. - - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. - - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. - - `σ`: diffusion noise constant. Default is 1. - -# Constructors - - RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - - RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0 - ) - -# Example - -````julia -using SequentialSamplingModels -dist = RatcliffDDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) -choice,rt = rand(dist, 10) -like = pdf.(dist, choice, rt) -loglike = logpdf.(dist, choice, rt) -```` - -# References - -Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. -Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 -""" -mutable struct RatcliffDDM{T<:Real} <: SSM2D - ν::T - α::T - τ::T - z::T - η::T - sz::T - st::T - σ::T -end - -function RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - return RatcliffDDM(promote(ν, α, τ, z, η, sz, st, σ)...) -end - -function params(d::RatcliffDDM) - (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -end - -function RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0) - return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -end - -# probability density function over the lower boundary - -# uses analytic integration of the likelihood function for variability in drift-rate -# function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# if choice == 1 -# (ν, α, τ, z, η, sz, st, σ) = params(d) -# ν = -ν -# z = 1 - z -# return pdf_sv(RatcliffDDM(ν, α, τ, z, η, sz, st, σ), rt; ϵ) -# end -# return pdf_sv(d, rt; ϵ) -# end - -function _pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) - end - # if isless(ν,0) - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 - # end - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 -end - -function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z, η, sz, st, σ) = params(d) - return _pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) - end - return _pdf(d, rt; ϵ) -end - -#use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -function _pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if τ ≥ rt - return T(NaN) - end - - if st < 1.0e-3 - st = 0 - end - if sz < 1.0e-3 - sz = 0 - end - - if sz==0 - if st==0 #sv=0,sz=0,st=0 - return _pdf_sv(d, rt; ϵ) - else #sv=0,sz=0,st=$ - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) - end - else #sz=$ - if st==0 #sv=0,sz=$,st=0 - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) - else #sv=0,sz=$,st=$ - return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) - end - end -end - -##################################################### -# Numerical Integration with Simpson's Method # -# https://en.wikipedia.org/wiki/Simpson%27s_rule # -##################################################### - -# Simpson's Method one dimentional case -function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - - # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - - n = max(n_st, n_sz) - - if n_st == 0 #integration over z - hz = (ub_z-lb_z)/n - ht = 0 - lb_t = τ - ub_t = τ - else #integration over t - hz = 0 - ht = (ub_t-lb_t)/n - lb_z = z - ub_z = z - end - - S = _pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) - - y = 0 - z_tag = 0 - t_tag = 0 - - for i in 1:n - z_tag = lb_z + hz * i - t_tag = lb_t + ht * i - - y = _pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) - - if isodd(i) - S += 4 * y - else - S += 2 * y - end - - end - - S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y - S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st - - return (ht + hz) * S / 3 - -end - -# Simpson's Method two dimentional case -function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - # @assert (ub_t-lb_t)*(ub_z-lb_z)>0 && (n_sz*n_st)>0 # 2D-integration only - - ht = (ub_t-lb_t)/n_st - S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - - t_tag = 0 - y = 0 - for i_t in 1:n_st - t_tag = lb_t + ht * i_t - y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - - if isodd(i_t) - S += 4 * y - else - S += 2 * y - end - end - - S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y - S = S / (ub_t - lb_t) - - return ht * S / 3 - -end - -logpdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) - -function logpdf(d::RatcliffDDM, data::T) where {T<:NamedTuple} - return sum(logpdf.(d, data...)) -end - -function logpdf(dist::RatcliffDDM, data::Array{<:Tuple,1}) - LL = 0.0 - for d in data - LL += logpdf(dist, d...) - end - return LL -end - -logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) - -################################################################################ -# Calculate Cumulative Distribution Function # -# # -# Computes Cumulative Distribution Function for the Ratcliff Diffusion model # -# using 6 Gaussian quadrature for numerical integration # -# # -# References # -# Tuerlinckx, F. (2004). The efficient computation of the # -# cumulative distribution and probability density functions # -# in the diffusion model, Behavior Research Methods, # -# Instruments, & Computers, 36 (4), 702-716. # -# # -# Converted from cdfdif.c C script by Joachim Vandekerckhove # -# See also https://ppw.kuleuven.be/okp/software/dmat/ # -################################################################################ - -function cdf(d::RatcliffDDM, choice, rt, p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) - - (ν, α, τ, z, η, sz, st, σ) = params(d) - - #check arguments - # if p_outlier > 0 - # @assert maximum(abs.(x)) < (1./(2*w_outlier)) "1. / (2*w_outlier) must be smaller than RT" - # end - - # if (η < 0) || (α <=0 ) || (z < 0) || (z > 1) || (sz < 0) || (sz > 1) || (z+sz/2.>1) || \ - # (z-sz/2.<0) || (τ-st/2.<0) || (τ<0) || (st < 0) || !_p_outlier_in_range(p_outlier) - # error("At least one of the parameters is out of the support") - # end - - size = length(rt) - y = zeros(Float64, size) - epsi = 1e-10 - - #transform parameters - α = α/10. - τ = τ - η = η/10. + epsi - z = z*(α/10.) - sz = sz*(α/10.) + epsi - st = st + epsi - ν = ν/10. - - p_boundary = 0.0 - - for i in 1:size - y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) - y[i] = (1 - p_boundary) + rt[i]*y[i] - #add p_outlier probability? - #y[i] = _add_outlier_cdf(y[i], x[i], p_outlier, w_outlier) - end - - return y -end - -function _cdf(d::RatcliffDDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} - - (ν, α, τ, z, η, sz, st, σ) = params(d) - #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) - #note we need to make sure this is consistent in the all the relative bound models - if choice == 2 #lower - choice = 0 - elseif choice == 1 #upper - choice = 1 - end - - # Initializing variables - a2 = α*α - Z_U = (1-choice)*z+choice*(α-z)+sz/2 - Z_L = (1-choice)*z+choice*(α-z)-sz/2 - lower_t = τ-st/2 - upper_t = 0.0 - Δ = 1e-29 - min_rt=0.001 - v_max = 5000 # maximum number of terms in a partial sum approximating infinite series - - Fnew = 0.0 - sum_z=0.0 - sum_ν=0.0 - p1 = 0.0 - p0 = 0.0 - sum_hist = zeros(3) - denom = 0.0 - sifa = 0.0 - upp = 0.0 - low = 0.0 - fact = 0.0 - exdif = 0.0 - su = 0.0 - sl = 0.0 - zzz = 0.0 - ser = 0.0 - nr_ν = 6 - nr_z = 6 - - # Defining Gauss-Hermite abscissae and weights for numerical integration - gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] - w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] - gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] - w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] - - # Adjusting Gauss-Hermite abscissae and weights - for i=1:nr_ν - gk[i] = 1.41421356237309505*gk[i]*η+ν - w_gh[i] = w_gh[i]/1.772453850905515882 - end - for i=1:nr_z - gz[i] = (.5*sz*gz[i])+z - end - - # numerical integration - for i=1:nr_z - sum_ν=0.0 - # numerical integration - for m=1:nr_ν - if abs(gk[m])>ϵ - sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] - else - sum_ν+=gz[i]/α*w_gh[m] - end - end - sum_z+=sum_ν*w_g[i]/2 - end - prob = sum_z - - if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? - upper_t = min(rt, τ+st/2) - p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t - p0 = (1-prob)*(upper_t-lower_t)/st - if rt > τ+st/2 # is t larger than upper boundary Ter distribution? - sum_hist = zeros(3) - for v in 1:v_max # infinite series - sum_hist = circshift(sum_hist, 1) - sum_ν = 0 - sifa = π*v/α - for m in 1:nr_ν # numerical integration with respect to xi - denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) - upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - - low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) - exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) - sum_ν += fact*exdif - end - sum_hist[3] = sum_hist[2] + v*sum_ν - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - - Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) - # cumulative distribution function for t and x - elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? - sum_ν = 0 - for m in 1:nr_ν - if abs(gk[m]) > ϵ - sum_z = 0 - for i in 1:nr_z - zzz = (α - gz[i])*choice + gz[i]*(1 - choice) - ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ - (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + - (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ - sinh((1 - 2*choice)*gk[m]*α/.01) - sum_hist = zeros(3) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) - sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) - end - else - sum_hist = zeros(3) - su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) - sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (π*v)*(π*v)/(a2*100) - sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* - exp(-.5*denom*(rt - lower_t)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) - end - sum_ν += sum_z*w_gh[m] - end - Fnew = (p0*(1 - choice) + p1*choice) - sum_ν - end - elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? - Fnew = 0 - end - - Fnew = Fnew > Δ ? Fnew : 0 - - return Fnew - -end - -function _add_outlier_cdf(y::Real, x::Real, p_outlier::Real; w_outlier::Real = 0.1) - #Ratcliff and Tuerlinckx, 2002 containment process - return y * (1 - p_outlier) + (x + (1. / (2 * w_outlier))) * w_outlier * p_outlier -end - -function _p_outlier_in_range(p_outlier) - return (p_outlier >= 0) & (p_outlier <= 1) -end - -""" - rand(dist::RatcliffDDM) - -Generate a random choice and rt for the Ratcliff Diffusion Model - -# Arguments -- `dist`: model object for Ratcliff Diffusion Model. -- `method`: method simulating the diffusion process. - "rejection" uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process - "stochastic" uses the stochastic Euler method to directly simulate the stochastic differential equation - -# References - - Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). - A comparison of four methods for simulating the diffusion process. - Behavior Research Methods, Instruments, & Computers, 33, 443-456. - - Converted from Rhddmjagsutils.R R script by Kianté Fernandez - See also https://github.com/kiante-fernandez/Rhddmjags. -""" -function rand(rng::AbstractRNG, d::RatcliffDDM) - # method::Char = "rejection" - return _rand_rejection(rng, d) - # method::Char = "stochastic" -# return _rand_stochastic(rng, d) -end - -function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - η = 1e-16 - end - - # Initialize output vectors - choice = fill(0, N) - rt = fill(0.0, N) - - # Called sigma in 2001 paper - D = σ^2 / 2 - - # Program specifications - ϵ = eps() # precision from 1.0 to next double-precision number - Δ = ϵ - - for n in 1:N - r1 = randn() - μ = ν + r1 * η - bb = z - sz / 2 + sz * rand() - zz = bb * α - finish = 0 - totaltime = 0 - startpos = 0 - Aupper = α - zz - Alower = -zz - radius = min(abs(Aupper), abs(Alower)) - - while finish == 0 - λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 - # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 - F = D * π / (radius * μ) - F = F^2 / (1 + F^2) - # formula p447 in 2001 paper - prob = exp(radius * μ / D) - prob = prob / (1 + prob) - dir_ = 2 * (rand() < prob) - 1 - l = -1 - s2 = 0 - s1 = 0 - while s2 > l - s2 = rand() - s1 = rand() - tnew = 0 - told = 0 - uu = 0 - while abs(tnew - told) > ϵ || uu == 0 - told = tnew - uu += 1 - tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) - # infinite sum in formula (16) in BRMIC,2001 - end - l = 1 + s1^(-F) * tnew - end - # rest of formula (16) - t = abs(log(s1)) / λ - # is the negative of t* in (14) in BRMIC,2001 - totaltime += t - dir_ = startpos + dir_ * radius - ndt = τ - st / 2 + st * rand() - if (dir_ + Δ) > Aupper - rt[n] = ndt + totaltime - choice[n] = 1 - finish = 1 - elseif (dir_ - Δ) < Alower - rt[n] = ndt + totaltime - choice[n] = 2 - finish = 1 - else - startpos = dir_ - radius = minimum(abs.([Aupper, Alower] .- startpos)) - end - end - end - return (choice=choice,rt=rt) -end - -function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - η = 1e-16 - end - - # Initialize output vectors - choice = fill(0, N) - rt = fill(0.0, N) - - for n in 1:N - random_walk = Array{Float64}(undef, nsteps) - start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() - ndt = (τ - st/2) + ((τ + st/2) - (τ - st/2)) * rand() - drift = rand(Distributions.Normal(ν, η)) - random_walk[1] = start_point * α - for s in 2:nsteps - random_walk[s] = random_walk[s-1] + rand(Distributions.Normal(drift * step_length, σ * sqrt(step_length))) - if random_walk[s] >= α - random_walk[s:end] .= α - rts[n] = s * step_length + ndt - choice[n] = 1 - break - elseif random_walk[s] <= 0 - random_walk[s:end] .= 0 - rts[n] = s * step_length + ndt - choice[n] = 2 - break - elseif s == nsteps - rts[n] = NaN - choice[n] = NaN - break - end - end - end - return (choice=choice,rt=rts) -end - -""" - rand(dist::RatcliffDDM, n_sim::Int) - -Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. - -# Arguments -- `dist`: model object for the Ratcliff DDM. -- `n_sim::Int`: the number of simulated rts -""" - -function rand(rng::AbstractRNG, d::RatcliffDDM, n_sim::Int) - return _rand_rejection(rng, d, N = n_sim) -end diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index 1f0cc47a..e89f8923 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -36,7 +36,6 @@ module SequentialSamplingModels export LNR export maaDDM export MixedMultivariateDistribution - export RatcliffDDM export SSM1D export SSM2D export Wald @@ -65,5 +64,4 @@ module SequentialSamplingModels include("maaDDM.jl") include("LCA.jl") include("DDM.jl") - include("RatcliffDDM.jl") end \ No newline at end of file diff --git a/src/utilities.jl b/src/utilities.jl index 6c504f2c..f886367d 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -67,8 +67,8 @@ Computes the likelihood for a 2D sequential sampling model. - `d::SSM2D`: an object for a 2D sequential sampling model - `data::NamedTuple`: a NamedTuple of data containing choice and reaction time """ -logpdf(d::SSM2D, data::NamedTuple) = logpdf.(d, data.choice, data.rt) -logpdf(d::SSM2D, data::Vector{Real}) = logpdf(d, Int(data[1]), data[2]) +logpdf(d::SSM2D, data::NamedTuple; kwargs...) = logpdf.(d, data.choice, data.rt; kwargs...) +logpdf(d::SSM2D, data::Vector{Real}; kwargs...) = logpdf(d, Int(data[1]), data[2]; kwargs...) """ loglikelihood(d::SSM2D, data::NamedTuple) From 14006ca26fefe161062080de68de418f1d00c9d3 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 16:52:22 +0200 Subject: [PATCH 16/43] bugs in integration --- src/DDM.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 744a3fa5..6b305809 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -147,10 +147,10 @@ function _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) (ν, α, τ, z, η, sz, st, σ) = params(d) if η == 0 - return _pdf(DDM(ν, α, τ, z), rt; ϵ) + return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) end - return _pdf(DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 end """ @@ -175,7 +175,8 @@ is based on the work of Navarro & Fuss (2009) and Wabersich & Vandekerckhove (20 """ function _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) + (ν, α, τ, z, η, sz, st, σ) = params(d) + if τ ≥ t return T(NaN) end From 9ba7ade8251e6f4d844a96da7c34f31f649e0a2e Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 16:57:52 +0200 Subject: [PATCH 17/43] slights changes for merge --- src/DDM.jl | 90 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 7df8a203..53488636 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -698,6 +698,51 @@ Returns 2 for the number of choice options """ n_options(d::DDM) = 2 +""" + simulate(model::DDM; Δt=.001) + +Returns a matrix containing evidence samples of the drift diffusion model decision process. In the matrix, rows +represent samples of evidence per time step and columns represent different accumulators. + +# Arguments + +- `model::DDM`: an drift diffusion model object + +# Keywords + +- `Δt=.001`: size of time step of decision process in seconds +""" +function simulate(model::DDM; Δt=.001) + (;ν, α, τ, z, η, sz, st, σ) = model + + x = α * z + t = 0.0 + evidence = [x] + time_steps = [t] + while (x < α) && (x > 0) + t += Δt + x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) + push!(evidence, x) + push!(time_steps, t) + end + return time_steps,evidence +end + +# function rand(dist::DDM) +# (;ν,α,z,σ,τ, Δt,η,st,sz) = dist +# t = 0.0 +# srΔt = √Δt +# x = rand(Uniform(z - sz / 2, z + sz / 2)) +# ν′ = rand(Normal(ν, η)) +# while (x < α) && (x > 0) +# ϵ = rand(Normal(0, σ)) +# x += ν′* Δt + ϵ * srΔt +# t += Δt +# end +# choice = (x < α) + 1 +# t += rand(Uniform(τ - st / 2, τ + st / 2)) +# return choice,t +# end ####### # old cdf code for debug checking @@ -821,48 +866,3 @@ n_options(d::DDM) = 2 # end # return r # end - -""" - simulate(model::DDM; Δt=.001) - -Returns a matrix containing evidence samples of the drift diffusion model decision process. In the matrix, rows -represent samples of evidence per time step and columns represent different accumulators. - -# Arguments - -- `model::DDM`: an drift diffusion model object - -# Keywords - -- `Δt=.001`: size of time step of decision process in seconds -""" -function simulate(model::DDM; Δt=.001) - (;ν,α,z) = model - x = α * z - t = 0.0 - evidence = [x] - time_steps = [t] - while (x < α) && (x > 0) - t += Δt - x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) - push!(evidence, x) - push!(time_steps, t) - end - return time_steps,evidence -end - -# function rand(dist::DDM) -# (;ν,α,z,σ,τ, Δt,η,st,sz) = dist -# t = 0.0 -# srΔt = √Δt -# x = rand(Uniform(z - sz / 2, z + sz / 2)) -# ν′ = rand(Normal(ν, η)) -# while (x < α) && (x > 0) -# ϵ = rand(Normal(0, σ)) -# x += ν′* Δt + ϵ * srΔt -# t += Δt -# end -# choice = (x < α) + 1 -# t += rand(Uniform(τ - st / 2, τ + st / 2)) -# return choice,t -# end \ No newline at end of file From 43697e077f677d96f99175ff631de8b041e58e26 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 17:11:46 +0200 Subject: [PATCH 18/43] merged Ratcliff docs --- docs/src/DDM.md | 23 ++++++- docs/src/Ratcliff_DDM.md | 141 --------------------------------------- 2 files changed, 21 insertions(+), 143 deletions(-) delete mode 100644 docs/src/Ratcliff_DDM.md diff --git a/docs/src/DDM.md b/docs/src/DDM.md index 25ae32ff..023ef6c9 100644 --- a/docs/src/DDM.md +++ b/docs/src/DDM.md @@ -1,9 +1,13 @@ # Diffusion Decision Model -The Diffusion Decision Model (DDM; Ratcliff et al., 2016) is a model of speeded decision-making in two-choice tasks. The DDM assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). Like other Sequential Sampling Models, the DDM comprises psychologically interpretable parameters that collectively form a generative model for reaction time distributions of both responses. +The Diffusion Decision Model (DDM; Ratcliff et al., 2016) is a model of speeded decision-making in two-choice tasks. The DDM assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). Like other Sequential Sampling Models, the DDM comprises psychologically interpretable parameters that collectively form a generative model for reaction time distributions of both responses. The drift rate (ν) determines the rate at which the accumulation process approaches a decision boundary, representing the relative evidence for or against a specific response. The distance between the two decision boundaries (referred to as the evidence threshold, α) influences the amount of evidence required before executing a response. Non-decision-related components, including perceptual encoding, movement initiation, and execution, are accounted for in the DDM and reflected in the τ parameter. Lastly, the model incorporates a bias in the evidence accumulation process through the parameter z, affecting the starting point of the drift process in relation to the two boundaries. The z parameter in DDM is relative to a (i.e. it ranges from 0 to 1). +However, in the Ratcliff Diffusion Decision Model, we also include across-trial variability parameters. These parameters were developed to explain specific discrepancies between the DDM and experimental data (Anderson, 1960; Laming, 1968; Blurton et al., 2017). The data exhibited a difference in mean RT between correct and error responses that could not be captured by the DDM. As a result, two parameters for across-trial variability were introduced to explain this difference: across-trial variability in the starting point (sz) to explain fast errors (Laming, 1968), and across-trial variability in drift rate (η) to explain slow errors (Ratcliff, 1978; Ratcliff and Rouder, 1998). Additionally, the DDM also showed a sharper rise in the leading edge of the response time distribution than observed in the data. To capture this leading edge effect, across-trial variability in non-decision time (st) was introduced. + +Previous work has validated predictions of these across-trial variability parameters (Wagenmakers et al., 2009). When compared to the DDM, the Ratcliff DDM improves the fit to the data. Researchers now often assume that the core parameters of sequential sampling models, such as drift rates, non-decision times, and starting points vary between trials. + One last parameter is the within-trial variability in drift rate (σ), or the diffusion coefficient. The diffusion coefficient is the standard deviation of the evidence accumulation process within one trial. It is a scaling parameter and by convention it is kept fixed. Following Navarro & Fuss, (2009), we use the σ = 1 version. # Example @@ -27,8 +31,11 @@ In the code below, we will define parameters for the DDM and create a model obje The average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 +Across-trial-variability of drift rate. Standard deviation of a normal distribution with mean v describing the distribution of actual drift rates from specific trials. Values different from 0 can predict slow errors. Typical range: 0 < η < 2. Default is 0. + ```@example DDM ν=1.0 +η = 0.16 ``` ### Boundary Separation @@ -43,16 +50,22 @@ The amount of information that is considered for a decision. Large values indica The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 +Across-trial-variability of non-decisional components. Range of a uniform distribution with mean τ + st/2 describing the distribution of actual τ values across trials. Accounts for response times below t0. Reduces skew of predicted RT distributions. Typical range: 0 < τ < 0.2. Default is 0. + ```@example DDM τ = 0.30 +st = 0.10 ``` ### Starting Point An indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). +Across-trial-variability of starting point. Range of a uniform distribution with mean z describing the distribution of actual starting points from specific trials. Values different from 0 can predict fast errors. Typical range: 0 < sz < 0.5. Default is 0. + ```@example DDM z = 0.50 +sz = 0.05 ``` ### DDM Constructor @@ -60,7 +73,7 @@ z = 0.50 Now that values have been assigned to the parameters, we will pass them to `DDM` to generate the model object. ```@example DDM -dist = DDM(ν, α, τ, z) +dist = DDM(ν, α, τ, z, η, sz, st, σ) ``` ## Simulate Model @@ -94,6 +107,10 @@ plot!(dist; t_range=range(.301, 1, length=100)) # References +Blurton, S. P., Kesselmeier, M., & Gondan, M. (2017). The first-passage time distribution for the diffusion model with variable drift. Journal of Mathematical Psychology, 76, 7–12. https://doi.org/10.1016/j.jmp.2016.11.003 + +Laming, D. R. J. (1968). Information theory of choice-reaction times. Academic Press. + Navarro, D., & Fuss, I. (2009). Fast and accurate calculations for first-passage times in Wiener diffusion models. https://doi.org/10.1016/J.JMP.2009.02.003 Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. https://doi.org/10.1162/neco.2008.12-06-420 @@ -103,3 +120,5 @@ Ratcliff, R., & Rouder, J. N. (1998). Modeling Response Times for Two-Choice Dec Ratcliff, R., & Smith, P. L. (2004). A comparison of sequential sampling models for two-choice reaction time. Psychological Review, 111 2, 333–367. https://doi.org/10.1037/0033-295X.111.2.333 Ratcliff, R., Smith, P. L., Brown, S. D., & McKoon, G. (2016). Diffusion Decision Model: Current Issues and History. Trends in Cognitive Sciences, 20(4), 260–281. https://doi.org/10.1016/j.tics.2016.01.007 + +Wagenmakers, E.-J. (2009). Methodological and empirical developments for the Ratcliff diffusion model of response times and accuracy. European Journal of Cognitive Psychology, 21(5), 641-671. diff --git a/docs/src/Ratcliff_DDM.md b/docs/src/Ratcliff_DDM.md deleted file mode 100644 index 2465cbf0..00000000 --- a/docs/src/Ratcliff_DDM.md +++ /dev/null @@ -1,141 +0,0 @@ -# Ratcliff Diffusion Model - -The Ratcliff Diffusion Model (Ratcliff DDM; Ratcliff et al., 2016) is similar to the DDM. Like the DDM, the model assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). The drift rate (ν) determines the rate at which the accumulation process approaches a decision boundary, representing the relative evidence for or against a specific response. The distance between the two decision boundaries (referred to as the evidence threshold, α) influences the amount of evidence required before executing a response. Non-decision-related components, including perceptual encoding, movement initiation, and execution, are accounted for in the DDM and reflected in the τ parameter. Lastly, the model incorporates a bias in the evidence accumulation process through the parameter z, affecting the starting point of the drift process in relation to the two boundaries. The z parameter in DDM is relative to a (i.e. it ranges from 0 to 1). - -However, the model differs in the inclusion of across-trial variability parameters. These parameters were developed to explain specific discrepancies between the DDM and experimental data (Anderson, 1960; Laming, 1968; Blurton et al., 2017). The data exhibited a difference in mean RT between correct and error responses that could not be captured by the DDM. As a result, two parameters for across-trial variability were introduced to explain this difference: across-trial variability in the starting point to explain fast errors (Laming, 1968), and across-trial variability in drift rate to explain slow errors (Ratcliff, 1978; Ratcliff and Rouder, 1998). Additionally, the DDM also showed a sharper rise in the leading edge of the response time distribution than observed in the data. To capture this leading edge effect, across-trial variability in non-decision time was introduced. - -Previous work has validated predictions of these across-trial variability parameters (Wagenmakers et al., 2009). When compared to the DDM, the Ratcliff DDM improves the fit to the data. Researchers now often assume that the core parameters of sequential sampling models, such as drift rates, non-decision times, and starting points vary between trials. - -One last parameter is the within-trial variability in drift rate (σ), or the diffusion coefficient. The diffusion coefficient is the standard deviation of the evidence accumulation process within one trial. It is a scaling parameter and by convention it is kept fixed. Following Navarro & Fuss, (2009), we use the σ = 1 version. - -# Example -In this example, we will demonstrate how to use the DDM in a generic two alternative forced choice task. - -## Load Packages -The first step is to load the required packages. - -```@example RatcliffDDM -using SequentialSamplingModels -using Plots -using Random - -Random.seed!(8741) -``` - -## Create Model Object -In the code below, we will define parameters for the DDM and create a model object to store the parameter values. - -### Drift Rate - -The average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - -Across-trial-variability of drift rate. Standard deviation of a normal distribution with mean v describing the distribution of actual drift rates from specific trials. Values different from 0 can predict slow errors. Typical range: 0 < η < 2. Default is 0. - -```@example RatcliffDDM -ν=1.0 -η = 0.16 -``` - -### Boundary Separation - -The amount of information that is considered for a decision. Large values indicates response caution. Typical range: 0.5 < α < 2 - - -```@example RatcliffDDM -α = 0.80 -``` - -### Non-Decision Time - -The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 - -Across-trial-variability of non-decisional components. Range of a uniform distribution with mean τ + st/2 describing the distribution of actual τ values across trials. Accounts for response times below t0. Reduces skew of predicted RT distributions. Typical range: 0 < τ < 0.2. Default is 0. - -```@example RatcliffDDM -τ = 0.30 -st = 0.10 -``` - -### Starting Point - -An indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). - -Across-trial-variability of starting point. Range of a uniform distribution with mean z describing the distribution of actual starting points from specific trials. Values different from 0 can predict fast errors. Typical range: 0 < sz < 0.5. Default is 0. - -```@example RatcliffDDM -z = 0.25 -sz = 0.05 -``` - -### Ratcliff Diffusion Model Constructor - -Now that values have been assigned to the parameters, we will pass them to `RatcliffDDM` to generate the model object. - -```@example RatcliffDDM -dist = RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -``` - -## Simulate Model - -Now that the model is defined, we will generate $10,000$ choices and reaction times using `rand`. - - ```@example RatcliffDDM - choices,rts = rand(dist, 10_000) -``` - -## Compute PDF -The PDF for each observation can be computed as follows: - - ```@example RatcliffDDM -pdf.(dist, choices, rts) -``` -## Compute Log PDF -Similarly, the log PDF for each observation can be computed as follows: - - ```@example RatcliffDDM -logpdf.(dist, choices, rts) -``` - -## Plot Simulation -The code below overlays the PDF on reaction time histograms for each option. - - ```@example RatcliffDDM -# rts for option 1 -rts1 = rts[choices .== 1] -# rts for option 2 -rts2 = rts[choices .== 2] -# probability of choosing 1 -p1 = length(rts1) / length(rts) -t_range = range(.30, 2, length=100) -# pdf for choice 1 -pdf1 = pdf.(dist, (1,), t_range) -# pdf for choice 2 -pdf2 = pdf.(dist, (2,), t_range) -# histogram of retrieval times -hist = histogram(layout=(2,1), leg=false, grid=false, - xlabel="Reaction Time", ylabel="Density", xlims = (0,1.5)) -histogram!(rts1, subplot=1, color=:grey, bins = 200, norm=true, title="Choice 1") -plot!(t_range, pdf1, subplot=1, color=:darkorange, linewidth=2) -histogram!(rts2, subplot=2, color=:grey, bins = 150, norm=true, title="Choice 2") -plot!(t_range, pdf2, subplot=2, color=:darkorange, linewidth=2) -# weight histogram according to choice probability -hist[1][1][:y] *= p1 -hist[2][1][:y] *= (1 - p1) -hist -``` - -# References - -Navarro, D., & Fuss, I. (2009). Fast and accurate calculations for first-passage times in Wiener diffusion models. https://doi.org/10.1016/J.JMP.2009.02.003 - -Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. https://doi.org/10.1162/neco.2008.12-06-420 - -Ratcliff, R., & Rouder, J. N. (1998). Modeling Response Times for Two-Choice Decisions. Psychological Science, 9(5), 347–356. https://doi.org/10.1111/1467-9280.00067 - -Ratcliff, R., & Smith, P. L. (2004). A comparison of sequential sampling models for two-choice reaction time. Psychological Review, 111 2, 333–367. https://doi.org/10.1037/0033-295X.111.2.333 - -Ratcliff, R., Smith, P. L., Brown, S. D., & McKoon, G. (2016). Diffusion Decision Model: Current Issues and History. Trends in Cognitive Sciences, 20(4), 260–281. https://doi.org/10.1016/j.tics.2016.01.007 - -Wagenmakers, E.-J. (2009). Methodological and empirical developments for the Ratcliff diffusion model of response times and accuracy. European Journal of Cognitive Psychology, 21(5), 641-671. - - From 5cf0ae3e7635c40fd6f52ed0c12d13d5ab4cd1a4 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 17:21:32 +0200 Subject: [PATCH 19/43] make simulate use across trial parameters --- src/DDM.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 53488636..1ceacd57 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -715,13 +715,17 @@ represent samples of evidence per time step and columns represent different accu function simulate(model::DDM; Δt=.001) (;ν, α, τ, z, η, sz, st, σ) = model - x = α * z + start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() + drift = rand(Distributions.Normal(ν, η)) + + x = α * start_point t = 0.0 + evidence = [x] time_steps = [t] while (x < α) && (x > 0) t += Δt - x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) + x += drift * Δt + rand(Normal(0.0, 1.0)) * √(Δt) push!(evidence, x) push!(time_steps, t) end From aa245d55b8a0c56cc274bfbaaca397164cff4f9b Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 19:40:59 +0200 Subject: [PATCH 20/43] working on test changes --- test/ddm_tests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ddm_tests.jl b/test/ddm_tests.jl index aae27db0..889c330f 100644 --- a/test/ddm_tests.jl +++ b/test/ddm_tests.jl @@ -6,7 +6,7 @@ Random.seed!(654) include("KDE.jl") - dist = DDM(ν=1.0, α = .8, z = .5, τ = .3) + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^6) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -31,7 +31,7 @@ include("KDE.jl") Random.seed!(750) - dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30) + dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^6) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -56,7 +56,7 @@ using Random Random.seed!(7540) - dist = DDM(ν=1.0, α = .8, z = .5, τ = .3) + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^5) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -81,7 +81,7 @@ using Random Random.seed!(2200) - dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30) + dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^5) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) From 9f9500e0aa1548f3066a0fdec426c8d05d527d09 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 21 Jun 2023 16:26:06 -0700 Subject: [PATCH 21/43] pdf for Ratcliff DDM first pass --- src/RatcliffDDM.jl | 554 +++++++++++++------------------- src/SequentialSamplingModels.jl | 2 + 2 files changed, 226 insertions(+), 330 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index efeba124..c5a478fa 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -1,357 +1,251 @@ -# """ -# RatcliffDDM - -# Model object for the Ratcliff Diffusion Model. - -# # Fields -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. - -# # Example - -# ````julia -# using SequentialSamplingModels -# dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) -# choice,rt = rand(dist, 10) -# like = pdf.(dist, choice, rt) -# loglike = logpdf.(dist, choice, rt) -# ```` +""" + RatcliffDDM + + Model object for the Ratcliff Diffusion Model. + +# Fields + - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 + - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 + - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 + - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. + - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. + - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. + - `σ`: diffusion noise constant. Default is 1. + +# Example + +````julia +using SequentialSamplingModels +dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) +choice,rt = rand(dist, 10) +like = pdf.(dist, choice, rt) +loglike = logpdf.(dist, choice, rt) +```` -# # References +# References -# Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. -# Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 - -# """ -# mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel -# ν::T1 -# α::T2 -# τ::T3 -# z::T4 -# η::T5 -# sz::T6 -# st::T7 -# σ::T8 -# end - -# """ -# RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0 -# ) - -# Constructor for the Ratcliff Diffusion Model. +Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. +Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 +""" +mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel + ν::T1 + α::T2 + τ::T3 + z::T4 + η::T5 + sz::T6 + st::T7 + σ::T8 +end + +Base.broadcastable(x::RatcliffDDM) = Ref(x) + +function params(d::RatcliffDDM) + (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) +end + +loglikelihood(d::RatcliffDDM, data) = sum(logpdf.(d, data...)) + +""" +RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + +Constructor for the Ratcliff Diffusion Model. -# # Keywords -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. -# """ -# function RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0) -# return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -# end - -# function params(d::RatcliffDDM) -# (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -# end - -# #uses analytic integration of the likelihood function for variability in drift-rate +# Keywords +- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 +- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 +- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 +- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). +- `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. +- `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. +- `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. +- `σ`: diffusion noise constant. Default is 1. + +# Example + +```julia +using SequentialSamplingModels +dist = RatcliffDDM(;ν = 1.00,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = 0.10,σ = 1.0) +``` +""" +function RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0) + return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) +end + +# probability density function over the lower boundary + +# uses analytic integration of the likelihood function for variability in drift-rate # function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# (ν, α, τ, z, η, sz, st) = params(d) - -# if choice == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ) -# end -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) + ν*α*z + (ν^2)*rt*0.5 -# end -# return pdf(DDM(ν, α, τ, z), choice, rt; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) - ν*α*(1-z) + (ν^2)*rt*0.5 -# end - -# function pdf_sv(x::Real, c::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real; ϵ::Real = 1.0e-12) - -# if c == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), c, x; ϵ) -# end -# return pdf(DDM(-ν, α, τ, 1-z), c, x; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*x ) / (2*(η^2)*x+2) ) - log(sqrt((η^2)*x+1)) + ν*α*z + (ν^2)*x*0.5 -# end -# return pdf(DDM(ν, α, τ, z), c, x; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*x ) / (2*(η^2)*x+2) ) - log(sqrt((η^2)*x+1)) - ν*α*(1-z) + (ν^2)*x*0.5 -# end - -# #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -# function pdf_full(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) -# (ν, α, τ, z, η, sz, st) = params(d) - -# # transform ν, z if choice is other bound response # if choice == 1 +# (ν, α, τ, z, η, sz, st, σ) = params(d) # ν = -ν -# z = 1 -z -# end - -# if st < 1.0e-3 -# st = 0 -# end -# if sz < 1.0e-3 -# sz = 0 -# end - -# if sz==0 -# if st==0 #sv=0,sz=0,st=0 -# return pdf_sv(d, choice, rt; ϵ::Real = 1.0e-12) -# else #sv=0,sz=0,st=$ -# return _simpson_1D(rt, choice, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) -# end -# else #sz=$ -# if st==0 #sv=0,sz=$,st=0 -# return _simpson_1D(rt, choice, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) -# else #sv=0,sz=$,st=$ -# return _simpson_2D(rt, choice, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) +# z = 1 - z +# return pdf_sv(RatcliffDDM(ν, α, τ, z, η, sz, st, σ), rt; ϵ) # end +# return pdf_sv(d, rt; ϵ) # end -# """ - -# _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - -# Numerical Integration with Simpson's Method -# see: https://en.wikipedia.org/wiki/Simpson%27s_rule - -# # Arguments -# - `x`::Real: response time -# - `ν`::Real: response time -# - `η`::Real: response time -# - `α`::Real: response time -# - `z`::Real: response time -# - `τ`::Real: response time -# - `ϵ`::Real: response time -# - `lb_z`::Real: response time -# - `ub_z`::Real: response time -# - `n_sz`::Int: response time -# - `lb_t`::Real: response time -# - `ub_t`::Real: response time -# - `n_st`::Int: response time - -# """ -# function _simpson_1D(x::Real, c::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) +function pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if η == 0 + return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + end + # if isless(ν,0) + # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 + # end + # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 + return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 +end + +function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) + if choice == 1 + (ν, α, τ, z, η, sz, st, σ) = params(d) + return pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + end + return pdf(d, rt; ϵ) +end + +#use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) +function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if τ ≥ rt + return T(NaN) + end + + if st < 1.0e-3 + st = 0 + end + if sz < 1.0e-3 + sz = 0 + end + + if sz==0 + if st==0 #sv=0,sz=0,st=0 + return pdf_sv(d, rt; ϵ) + else #sv=0,sz=0,st=$ + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) + end + else #sz=$ + if st==0 #sv=0,sz=$,st=0 + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) + else #sv=0,sz=$,st=$ + return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) + end + end +end + +##################################################### +# Numerical Integration with Simpson's Method # +# https://en.wikipedia.org/wiki/Simpson%27s_rule # +##################################################### + +# Simpson's Method one dimentional case +function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -# @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - -# n = max(n_st, n_sz) - -# if n_st == 0 #integration over z -# hz = (ub_z-lb_z)/n -# ht = 0 -# lb_t = t -# ub_t = t -# else #integration over t -# hz = 0 -# ht = (ub_t-lb_t)/n -# lb_z = z -# ub_z = z -# end - -# S = pdf_sv(x - lb_t, c, ν, η, α, lb_z, τ; ϵ) + # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even + + n = max(n_st, n_sz) + + if n_st == 0 #integration over z + hz = (ub_z-lb_z)/n + ht = 0 + lb_t = τ + ub_t = τ + else #integration over t + hz = 0 + ht = (ub_t-lb_t)/n + lb_z = z + ub_z = z + end + + S = pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) -# for i in 1:n -# z_tag = lb_z + hz * i -# t_tag = lb_t + ht * i -# y = pdf_sv(x - t_tag, c, ν, η, α, z_tag, τ; ϵ) - -# if isodd(i) -# S += 4 * y -# else -# S += 2 * y -# end - -# end + y = 0 + z_tag = 0 + t_tag = 0 + + for i in 1:n + z_tag = lb_z + hz * i + t_tag = lb_t + ht * i + + y = pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + + if isodd(i) + S += 4 * y + else + S += 2 * y + end + + end -# S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y -# S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st -# return (ht + hz) * S / 3 + return (ht + hz) * S / 3 -# end - -# """ -# RatcliffDDM - -# Model object for the Ratcliff Diffusion Model. - -# # Fields -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. - -# # Example - -# ````julia -# using SequentialSamplingModels -# dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) -# choice,rt = rand(dist, 10) -# like = pdf.(dist, choice, rt) -# loglike = logpdf.(dist, choice, rt) -# ```` - -# # References - -# Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. - -# Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 - -# """ -# @concrete mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel -# ν::T1 -# α::T2 -# τ::T3 -# z::T4 -# η::T5 -# sz::T6 -# st::T7 -# σ::T8 -# end - -# """ -# RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0 -# ) - -# Constructor for the Ratcliff Diffusion Model. - -# # Keywords -# - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -# - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -# - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -# - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -# - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -# - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -# - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -# - `σ`: diffusion noise constant. Default is 1. -# """ -# function RatcliffDDM(; ν = 1.00, -# α = 0.80, -# τ = 0.30, -# z = 0.25, -# η = 0.16, -# sz = 0.05, -# st = 0.10, -# σ = 1.0) -# return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -# end +end -# function params(d::RatcliffDDM) -# (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -# end - -# #uses analytic integration of the likelihood function for variability in drift-rate -# function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# (ν, α, τ, z, η, sz, st) = params(d) +# Simpson's Method two dimentional case +function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even + # @assert (ub_t-lb_t)*(ub_z-lb_z)>0 && (n_sz*n_st)>0 # 2D-integration only -# if choice == 1 -# if η == 0 -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ::Real = 1.0e-12) -# end -# return pdf(DDM(-ν, α, τ, 1-z), choice, rt; ϵ::Real = 1.0e-12) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) + ν*α*z + (ν^2)*rt*0.5 -# end -# return pdf(DDM(ν, α, τ, z), choice, rt; ϵ::Real = 1.0e-12) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*rt ) / (2*(η^2)*rt+2) ) - log(sqrt((η^2)*rt+1)) - ν*α*(1-z) + (ν^2)*rt*0.5 -# end + ht = (ub_t-lb_t)/n_st + S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) -# #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -# function pdf_full(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) -# (ν, α, τ, z, η, sz, st) = params(d) + t_tag = 0 + y = 0 + for i_t in 1:n_st + t_tag = lb_t + ht * i_t + y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) -# # transform ν, z if choice is other bound response -# if choice == 1 -# ν = -ν -# z = 1 -z -# end + if isodd(i_t) + S += 4 * y + else + S += 2 * y + end + end -# if st < 1.0e-3 -# st = 0 -# end -# if sz < 1.0e-3 -# sz = 0 -# end + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / (ub_t - lb_t) -# if sz==0 -# if st==0 #sv=0,sz=0,st=0 -# return pdf_sv(d, choice, rt; ϵ::Real = 1.0e-12) -# else #sv=0,sz=0,st=$ -# return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) -# end -# else #sz=$ -# if st==0 #sv=0,sz=$,st=0 -# return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) -# else #sv=0,sz=$,st=$ -# return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) -# end -# end + return ht * S / 3 -# """ +end -# _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - -# Numerical Integration with Simpson's Method -# see: https://en.wikipedia.org/wiki/Simpson%27s_rule - -# # Arguments -# - `x`::Real: response time -# - `ν`::Real: response time -# - `η`::Real: response time -# - `α`::Real: response time -# - `z`::Real: response time -# - `τ`::Real: response time -# - `ϵ`::Real: response time -# - `lb_z`::Real: response time -# - `ub_z`::Real: response time -# - `n_sz`::Int: response time -# - `lb_t`::Real: response time -# - `ub_t`::Real: response time -# - `n_st`::Int: response time +logpdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -# """ -# function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -# #assert ((n_sz&1)==0 and (n_st&1)==0), "n_st and n_sz have to be even" +function logpdf(d::RatcliffDDM, data::T) where {T<:NamedTuple} + return sum(logpdf.(d, data...)) +end -# end +function logpdf(dist::RatcliffDDM, data::Array{<:Tuple,1}) + LL = 0.0 + for d in data + LL += logpdf(dist, d...) + end + return LL +end -# function _simpson_2D() - -# end +logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) # """ # cdf_full() @@ -368,4 +262,4 @@ # in the diffusion model, Behavior Research Methods, # Instruments, & Computers, 36 (4), 702-716. -# """ +# """ \ No newline at end of file diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index e2ff9122..3c4ae116 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -47,6 +47,7 @@ module SequentialSamplingModels export SSM1D export SSM2D export ContinuousMultivariateSSM + export RatcliffDDM export Wald export WaldMixture @@ -86,4 +87,5 @@ module SequentialSamplingModels include("CircularDDM.jl") include("ext_functions.jl") include("ex_gaussian.jl") + include("RatcliffDDM.jl") end \ No newline at end of file From 11eff78a4993ab0c19474f4f7c8bc693f7790ee1 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 21 Jun 2023 20:17:19 -0700 Subject: [PATCH 22/43] added rand simulators for Ratcliff Diffusion Model --- src/RatcliffDDM.jl | 187 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index c5a478fa..076ab2f1 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -17,7 +17,7 @@ ````julia using SequentialSamplingModels -dist = RatcliffDDM(ν = 0.50,α = 0.08,τ = 0.30,z = 0.04,η = 0.10,sz = 0.02,st = .02,σ = 0.10) +dist = RatcliffDDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) choice,rt = rand(dist, 10) like = pdf.(dist, choice, rt) loglike = logpdf.(dist, choice, rt) @@ -262,4 +262,187 @@ logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) # in the diffusion model, Behavior Research Methods, # Instruments, & Computers, 36 (4), 702-716. -# """ \ No newline at end of file +# """ + +""" + rand(dist::RatcliffDDM) + +Generate a random choice and rt for the Ratcliff Diffusion Model + +# Arguments +- `dist`: model object for Ratcliff Diffusion Model. +- `method`: method simulating the diffusion process. + "rejection" uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process + "stochastic" uses the stochastic Euler method to directly simulate the stochastic differential equation + +# References + + Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). + A comparison of four methods for simulating the diffusion process. + Behavior Research Methods, Instruments, & Computers, 33, 443-456. + + Converted from Rhddmjagsutils.R R script by Kianté Fernandez + + See also https://github.com/kiante-fernandez/Rhddmjags. +""" +function rand(rng::AbstractRNG, d::RatcliffDDM) + # method::Char = "rejection" + return _rand_rejection(rng, d) + # method::Char = "stochastic" +# return _rand_stochastic(rng, d) +end + +function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if (ν < -5) || (ν > 5) + ν = sign(ν) * 5 + warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") + end + + if η > 3 + warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") + η = 3 + end + + if η == 0 + η = 1e-16 + end + + # Initialize output vectors + result = zeros(N) + T = zeros(N) + XX = zeros(N) + + # Called sigma in 2001 paper + D = σ^2 / 2 + + # Program specifications + ϵ = 2.220446049250313e-16 # precision from 1.0 to next double-precision number + Δ = ϵ + + for n in 1:N + r1 = randn() + μ = ν + r1 * η + bb = z - sz / 2 + sz * rand() + zz = bb * α + finish = 0 + totaltime = 0 + startpos = 0 + Aupper = α - zz + Alower = -zz + radius = min(abs(Aupper), abs(Alower)) + + while finish == 0 + λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 + # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 + F = D * π / (radius * μ) + F = F^2 / (1 + F^2) + # formula p447 in 2001 paper + prob = exp(radius * μ / D) + prob = prob / (1 + prob) + dir_ = 2 * (rand() < prob) - 1 + l = -1 + s2 = 0 + s1 = 0 + while s2 > l + s2 = rand() + s1 = rand() + tnew = 0 + told = 0 + uu = 0 + while abs(tnew - told) > ϵ || uu == 0 + told = tnew + uu += 1 + tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) + # infinite sum in formula (16) in BRMIC,2001 + end + l = 1 + s1^(-F) * tnew + end + # rest of formula (16) + t = abs(log(s1)) / λ + # is the negative of t* in (14) in BRMIC,2001 + totaltime += t + dir_ = startpos + dir_ * radius + ndt = τ - st / 2 + st * rand() + if (dir_ + Δ) > Aupper + T[n] = ndt + totaltime + XX[n] = 1 + finish = 1 + elseif (dir_ - Δ) < Alower + T[n] = ndt + totaltime + XX[n] = 2 + finish = 1 + else + startpos = dir_ + radius = minimum(abs.([Aupper, Alower] .- startpos)) + end + end + end + return (choice=XX,rt=T) +end + +function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if (ν < -5) || (ν > 5) + ν = sign(ν) * 5 + warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") + end + + if η > 3 + warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") + η = 3 + end + + if η == 0 + η = 1e-16 + end + + # Initialize output vectors + rts = zeros(N) + choice = zeros(N) + + for n in 1:N + random_walk = Array{Float64}(undef, nsteps) + start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() + ndt = (τ - st/2) + ((τ + st/2) - (τ - st/2)) * rand() + drift = rand(Distributions.Normal(ν, η)) + random_walk[1] = start_point * α + for s in 2:nsteps + random_walk[s] = random_walk[s-1] + rand(Distributions.Normal(drift * step_length, σ * sqrt(step_length))) + if random_walk[s] >= α + random_walk[s:end] .= α + rts[n] = s * step_length + ndt + choice[n] = 1 + break + elseif random_walk[s] <= 0 + random_walk[s:end] .= 0 + rts[n] = s * step_length + ndt + choice[n] = 2 + break + elseif s == nsteps + rts[n] = NaN + choice[n] = NaN + break + end + end + end + return (choice=choice,rt=rts) +end + +""" + rand(dist::DDM, n_sim::Int) + +Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. + +# Arguments +- `dist`: model object for the Drift Diffusion Model. +- `n_sim::Int`: the number of simulated rts +""" + +function rand(rng::AbstractRNG, d::RatcliffDDM, n_sim::Int) + return _rand_rejection(rng, d, N = n_sim) +end + +sampler(rng::AbstractRNG, d::RatcliffDDM) = rand(rng::AbstractRNG, d::RatcliffDDM) From 4c37f711138a8470899e2828867fcc04c77ccfb9 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 14:48:10 +0200 Subject: [PATCH 23/43] changes to Ratcliff to for SSM format --- src/RatcliffDDM.jl | 88 ++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 57 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 076ab2f1..ccd124c5 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -1,9 +1,9 @@ """ - RatcliffDDM + RatcliffDDM{T<:Real} <: SSM2D Model object for the Ratcliff Diffusion Model. -# Fields +# Parameters - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 @@ -13,6 +13,20 @@ - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. - `σ`: diffusion noise constant. Default is 1. +# Constructors + + RatcliffDDM(ν, α, τ, z, η, sz, st, σ) + + RatcliffDDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + # Example ````julia @@ -28,55 +42,25 @@ loglike = logpdf.(dist, choice, rt) Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ -mutable struct RatcliffDDM{T1,T2,T3,T4,T5,T6,T7,T8} <: SequentialSamplingModel - ν::T1 - α::T2 - τ::T3 - z::T4 - η::T5 - sz::T6 - st::T7 - σ::T8 +mutable struct RatcliffDDM{T<:Real} <: SSM2D + ν::T + α::T + τ::T + z::T + η::T + sz::T + st::T + σ::T end -Base.broadcastable(x::RatcliffDDM) = Ref(x) +function RatcliffDDM(ν, α, τ, z, η, sz, st, σ) + return RatcliffDDM(promote(ν, α, τ, z, η, sz, st, σ)...) +end function params(d::RatcliffDDM) (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) end -loglikelihood(d::RatcliffDDM, data) = sum(logpdf.(d, data...)) - -""" -RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0 - ) - -Constructor for the Ratcliff Diffusion Model. - -# Keywords -- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). -- `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. -- `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. -- `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. -- `σ`: diffusion noise constant. Default is 1. - -# Example - -```julia -using SequentialSamplingModels -dist = RatcliffDDM(;ν = 1.00,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = 0.10,σ = 1.0) -``` -""" function RatcliffDDM(; ν = 1.00, α = 0.80, τ = 0.30, @@ -318,7 +302,7 @@ function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) D = σ^2 / 2 # Program specifications - ϵ = 2.220446049250313e-16 # precision from 1.0 to next double-precision number + ϵ = eps() # precision from 1.0 to next double-precision number Δ = ϵ for n in 1:N @@ -385,23 +369,13 @@ end function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) (ν, α, τ, z, η, sz, st, σ) = params(d) - if (ν < -5) || (ν > 5) - ν = sign(ν) * 5 - warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") - end - - if η > 3 - warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") - η = 3 - end - if η == 0 η = 1e-16 end # Initialize output vectors - rts = zeros(N) - choice = zeros(N) + choice = fill(0, N) + rt = fill(0.0, N) for n in 1:N random_walk = Array{Float64}(undef, nsteps) From 63739577ca2feec350bbf59bf3fb01a68aae9a0a Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 14:59:05 +0200 Subject: [PATCH 24/43] cdf fast dm implementation --- src/RatcliffDDM.jl | 272 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 256 insertions(+), 16 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index ccd124c5..64a23d0c 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -231,22 +231,262 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) -# """ -# cdf_full() - -# The orignial algorithm was written on 09/01/06 by Joachim Vandekerckhove -# Then converted from c to julia by Kianté Fernandez - -# Computes Cumulative Distribution Function for the Diffusion model with random trial to trial mean drift (normal), -# starting point and non-decision (ter) time (both rectangular). Uses 6 quadrature points for drift and 6 for the others. - -# Based on methods described in: -# Tuerlinckx, F. (2004). The efficient computation of the -# cumulative distribution and probability density functions -# in the diffusion model, Behavior Research Methods, -# Instruments, & Computers, 36 (4), 702-716. - -# """ +######################################################################################################################################################################## +# Calculate Drift-diffusion Probability Density +# +# compute the densities g- and g+ of the first exit time. `cdf` implement A1 to A4 equations in Voss, Rothermund, and Voss (2004). These equations calculate Ratcliff's +# drift-diffusion model (1978). This source codes are derived from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). +# +# * ------------------------------------------------------------------------ +# * A verbatim copy of Jochen Voss & Andreas Voss's copyright. +# * ------------------------------------------------------------------------ +# * Copyright (C) 2012 Andreas Voss, Jochen Voss. +# * +# * This program is free software; you can redistribute it and/or +# * modify it under the terms of the GNU General Public License as +# * published by the Free Software Foundation; either version 2 of the +# * License, or (at your option) any later version. +# * +# * This program is distributed in the hope that it will be useful, but +# * WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# * General Public License for more details. +# * +# * You should have received a copy of the GNU General Public License +# * along with this program; if not, write to the Free Software +# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# * 02110-1301 USA. +# +# # References +# +# - Singmann H, Brown S, Gretton M, Heathcote A (2022). _rtdists: Response Time Distributions_. Rpackage version 0.11-5, . +# - Voss, A., Rothermund, K., & Voss, J. (2004). Interpreting the parameters of the diffusion model: An empirical validation. *Memory and Cognition, 32(7)*, 1206-1220. +# - Ratcliff, R. (1978). A theory of memory retrieval. *Psychology Review, 85(2)*, 59-108. +# - Voss, A., Voss, J., & Lerche, V. (2015). Assessing cognitive processes with diffusion model analyses: A tutorial based on fast-dm-30. *Frontiers in Psychology, 6*, Article 336. https://doi.org/10.3389/fpsyg.2015.00336 +# +################################################################################################################################################################ + +TUNE_PDE_DT_MIN = 1e-6 +TUNE_PDE_DT_MAX = 1e-6 +TUNE_PDE_DT_SCALE = 0.0 + +TUNE_DZ = 0.0 +TUNE_DV = 0.0 +TUNE_DT0 = 0.0 + +TUNE_INT_T0 = 0 +TUNE_INT_Z = 0 + +precision_set = 0 + +function cdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, precision::Real = 3) + if choice == 1 + (ν, α, τ, z, η, sz, st, σ) = params(d) + return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ, precision) #over the upper boundary (g_plus) + end + + return cdf(d, rt; ϵ, precision) +end + +# cumulative density function over the lower boundary (g_minus) +function cdf(d::RatcliffDDM{T}, x::Real; ϵ::Real= 1.0e-6, precision::Real = 3) where {T<:Real} + if d.τ ≥ t + return T(NaN) + end + _set_precision(precision) + DT = x - τ - 0.5 * z + return _integral_τ_g_minus(x, d) +end + +function _set_precision(precision::Real = 3) + """ + Precision of calculation. + Corresponds roughly to the number of decimals of the predicted CDFs that are calculated accurately. Default is 3. + The function adjusts various parameters used in the calculations based on the precision value. + """ + global TUNE_PDE_DT_MIN = (-0.400825*precision-1.422813)^10 + global TUNE_PDE_DT_MAX = (-0.627224*precision+0.492689)^10 + global TUNE_PDE_DT_SCALE = (-1.012677*precision+2.261668)^10 + global TUNE_DZ = (-0.5*precision-0.033403)^10 + global TUNE_DV = (-1.0*precision+1.4)^10 + global TUNE_DT0 = (-0.5*precision-0.323859)^10 + + global TUNE_INT_T0 = 0.089045 * exp(-1.037580*precision) + global TUNE_INT_Z = 0.508061 * exp(-1.022373*precision) + + global precision_set = 1 +end + +function _integrate(F::Function, d::RatcliffDDM, a::Int, b::Int, step_width::Real) + """ + These functions perform numerical integration using a specified function, range, and step width. The integrate function performs the integration sequentially + """ + width = b - a # integration width + N = max(4, Int(width / step_width)) # N at least equals 4 + step = width / N + x = a + 0.5 * step + out = 0 + while x < b + out += step * F(x, d) + x += step + end + return out +end + +function _g_minus_small_time(x::Real, d::RatcliffDDM, N::Int) + """ + calculate the densities g- for the first exit time for small time + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + sum = 0.0 + for i = -N:N÷2 + d = 2*i + z + sum += exp(-d*d / (2*DT)) * d + end + return sum / sqrt(2π*DT*DT*DT) +end + +function _g_minus_large_time(x::Real, d::RatcliffDDM, N::Int) + """ + calculate the densities g- for the first exit time for large time values + """ + DT = x - τ #make into decision time + + sum = 0.0 + for i = 1:N + d = i * π + sum += exp(-0.5 * d*d * DT) * sin(d*z) * i + end + return sum * π +end + +function _g_minus_no_var(x::Real, d::RatcliffDDM) + """ + calculates the density g- when there is no variability in the input parameters. + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + N_small = 0 + N_large = 0 + simple = 0.0 + factor = exp(-α*z*ν - 0.5*ν*ν*DT) / (α*α) # Front term in A3 + ϵ = ϵ / factor + + ta = x / (α*α) + + N_large = ceil(1 / (π*sqrt(DT))) + if π*ta*ϵ < 1 + N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + end + + if 2*sqrt(2*π*ta)*ϵ < 1 + N_small = ceil(max(sqrt(ta) + 1, 2 + sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) + else + N_small = 2 + end + + if N_small < N_large + simple = _g_minus_small_time(x / (α*α), z, N_small) + else + simple = _g_minus_large_time(x / (α*α), z, N_large) + end + + out = isinf(factor) ? 0 : (factor * simple) + return out +end + +function _integral_v_g_minus(Real::x, d::RatcliffDDM; ϵ::Real = 1e-6) + """ + calculates the integral of the density g- over the variable ν for a given set of input parameters. It takes into account variability in the parameters η + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + N_small = 0 + N_large = 0 + simple = 0.0 + factor = 1 / (α*α * sqrt(DT * η*η + 1)) * + exp(-0.5 * (ν*ν*DT + 2*ν*α*z - α*z*α*z*η*η) / (DT*η*η+1)) + ϵ = ϵ / factor + + ta = DT / (α*α) + + N_large = ceil(1 / (π*sqrt(DT))) + if π*ta*ϵ < 1 + N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + end + + if 2*sqrt(2*π*ta)*ϵ < 1 + N_small = ceil(max(sqrt(ta)+1, 2+sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) + else + N_small = 2 + end + + if isinf(factor) + out = 0 + elseif η == 0 + out = _g_minus_no_var(x, d) + elseif N_small < N_large + simple = _g_minus_small_time(x/(α*α), d, N_small) + out = factor * simple + else + simple = _g_minus_large_time(x/(a*a), d, N_large) + out = factor * simple + end + + return out +end + +function _integral_z_g_minus(x::Real, d::RatcliffDDM) + """ + calculate the integral of integral_v_g_minus over the variable zr for a given set of input parameters. They handle variability in the parameter sz + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + out = 0.0 + + if DT <= 0 # if DT <= 0 + out = 0 + elseif sz == 0 # this should be sz + out = _integral_v_g_minus(x, d) + else + a = z - 0.5*sz # zr - 0.5*szr; uniform variability + b = z + 0.5*sz # zr + 0.5*szr + step_width = TUNE_INT_Z + out = _integrate(integral_v_g_minus, d, a, b, step_width) / sz + end + + return out +end + +function _integral_τ_g_minus(x::Real, d::RatcliffDDM) + """ + calculate the integral of integral_z_g_minus over the variable τ for a given set of input parameters. They handle variability in the parameter st + """ + (ν, α, τ, z, η, sz, st, σ) = params(d) + + DT = x - τ #make into decision time + + out = 0.0 + if st == 0 + out = _integral_z_g_minus(x, d) # should send t as RT + else + a = DT - 0.5*st # DT - 0.5*st + b = DT + 0.5*st # DT + 0.5*st + step_width = TUNE_INT_T0 + out = _integrate(integral_z_g_minus, d, a, b, step_width) / sts + end + + return out +end """ rand(dist::RatcliffDDM) From fabd699d928b5971b00f30e569f31152887b67d7 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 15:20:54 +0200 Subject: [PATCH 25/43] _pdf change --- src/RatcliffDDM.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index 64a23d0c..d25c0be4 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -85,29 +85,29 @@ end # return pdf_sv(d, rt; ϵ) # end -function pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} +function _pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} (ν, α, τ, z, η, sz, st, σ) = params(d) if η == 0 - return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) end # if isless(ν,0) # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 # end # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 - return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 end function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) if choice == 1 (ν, α, τ, z, η, sz, st, σ) = params(d) - return pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + return _pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) end - return pdf(d, rt; ϵ) + return _pdf(d, rt; ϵ) end #use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} +function _pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} (ν, α, τ, z, η, sz, st, σ) = params(d) if τ ≥ rt @@ -123,7 +123,7 @@ function pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2 if sz==0 if st==0 #sv=0,sz=0,st=0 - return pdf_sv(d, rt; ϵ) + return _pdf_sv(d, rt; ϵ) else #sv=0,sz=0,st=$ return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) end @@ -160,7 +160,7 @@ function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ub_z = z end - S = pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) + S = _pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) y = 0 z_tag = 0 @@ -170,7 +170,7 @@ function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, z_tag = lb_z + hz * i t_tag = lb_t + ht * i - y = pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + y = _pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) if isodd(i) S += 4 * y From d91c218ca87644f766f6594ea83e141ef1ed6cbf Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 3 Jul 2023 18:51:22 +0200 Subject: [PATCH 26/43] cdf code must be redone. Wrong in a few ways --- src/RatcliffDDM.jl | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index d25c0be4..a6324a84 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -232,30 +232,9 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) ######################################################################################################################################################################## -# Calculate Drift-diffusion Probability Density +# Calculate Cumulative Distribution Function # -# compute the densities g- and g+ of the first exit time. `cdf` implement A1 to A4 equations in Voss, Rothermund, and Voss (2004). These equations calculate Ratcliff's -# drift-diffusion model (1978). This source codes are derived from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). -# -# * ------------------------------------------------------------------------ -# * A verbatim copy of Jochen Voss & Andreas Voss's copyright. -# * ------------------------------------------------------------------------ -# * Copyright (C) 2012 Andreas Voss, Jochen Voss. -# * -# * This program is free software; you can redistribute it and/or -# * modify it under the terms of the GNU General Public License as -# * published by the Free Software Foundation; either version 2 of the -# * License, or (at your option) any later version. -# * -# * This program is distributed in the hope that it will be useful, but -# * WITHOUT ANY WARRANTY; without even the implied warranty of -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# * General Public License for more details. -# * -# * You should have received a copy of the GNU General Public License -# * along with this program; if not, write to the Free Software -# * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# * 02110-1301 USA. +# This source codes are adpated from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). # # # References # @@ -519,16 +498,6 @@ end function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) (ν, α, τ, z, η, sz, st, σ) = params(d) - if (ν < -5) || (ν > 5) - ν = sign(ν) * 5 - warn("ν is not in the range [-5, 5], bounding drift rate to $Nu...") - end - - if η > 3 - warn("Standard deviation of drift rate is out of bounds, bounding drift rate to 3") - η = 3 - end - if η == 0 η = 1e-16 end From e2bc8f4b0dc860c54f2c16cdf81d34de38a4b3e3 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 18:38:39 +0200 Subject: [PATCH 27/43] added non license breaking implementation of cdf --- .vscode/settings.json | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file From f53378c189f82a2e2fb67f1ab99b3358a1f9b6c4 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 18:57:21 +0200 Subject: [PATCH 28/43] bugs --- src/RatcliffDDM.jl | 351 +++++++++++++++++++++------------------------ 1 file changed, 160 insertions(+), 191 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index a6324a84..b536149e 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -231,91 +231,24 @@ end logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) -######################################################################################################################################################################## -# Calculate Cumulative Distribution Function -# -# This source codes are adpated from Henrik Singmann's Density.h (rtdists) & Voss & Voss's density.c (fast-dm). -# -# # References -# -# - Singmann H, Brown S, Gretton M, Heathcote A (2022). _rtdists: Response Time Distributions_. Rpackage version 0.11-5, . -# - Voss, A., Rothermund, K., & Voss, J. (2004). Interpreting the parameters of the diffusion model: An empirical validation. *Memory and Cognition, 32(7)*, 1206-1220. -# - Ratcliff, R. (1978). A theory of memory retrieval. *Psychology Review, 85(2)*, 59-108. -# - Voss, A., Voss, J., & Lerche, V. (2015). Assessing cognitive processes with diffusion model analyses: A tutorial based on fast-dm-30. *Frontiers in Psychology, 6*, Article 336. https://doi.org/10.3389/fpsyg.2015.00336 -# -################################################################################################################################################################ - -TUNE_PDE_DT_MIN = 1e-6 -TUNE_PDE_DT_MAX = 1e-6 -TUNE_PDE_DT_SCALE = 0.0 - -TUNE_DZ = 0.0 -TUNE_DV = 0.0 -TUNE_DT0 = 0.0 - -TUNE_INT_T0 = 0 -TUNE_INT_Z = 0 - -precision_set = 0 - -function cdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12, precision::Real = 3) - if choice == 1 - (ν, α, τ, z, η, sz, st, σ) = params(d) - return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ, precision) #over the upper boundary (g_plus) - end - - return cdf(d, rt; ϵ, precision) -end - -# cumulative density function over the lower boundary (g_minus) -function cdf(d::RatcliffDDM{T}, x::Real; ϵ::Real= 1.0e-6, precision::Real = 3) where {T<:Real} - if d.τ ≥ t - return T(NaN) - end - _set_precision(precision) - DT = x - τ - 0.5 * z - return _integral_τ_g_minus(x, d) -end - -function _set_precision(precision::Real = 3) - """ - Precision of calculation. - Corresponds roughly to the number of decimals of the predicted CDFs that are calculated accurately. Default is 3. - The function adjusts various parameters used in the calculations based on the precision value. - """ - global TUNE_PDE_DT_MIN = (-0.400825*precision-1.422813)^10 - global TUNE_PDE_DT_MAX = (-0.627224*precision+0.492689)^10 - global TUNE_PDE_DT_SCALE = (-1.012677*precision+2.261668)^10 - global TUNE_DZ = (-0.5*precision-0.033403)^10 - global TUNE_DV = (-1.0*precision+1.4)^10 - global TUNE_DT0 = (-0.5*precision-0.323859)^10 - - global TUNE_INT_T0 = 0.089045 * exp(-1.037580*precision) - global TUNE_INT_Z = 0.508061 * exp(-1.022373*precision) - - global precision_set = 1 -end - -function _integrate(F::Function, d::RatcliffDDM, a::Int, b::Int, step_width::Real) - """ - These functions perform numerical integration using a specified function, range, and step width. The integrate function performs the integration sequentially - """ - width = b - a # integration width - N = max(4, Int(width / step_width)) # N at least equals 4 - step = width / N - x = a + 0.5 * step - out = 0 - while x < b - out += step * F(x, d) - x += step - end - return out -end +################################################################################ +# Calculate Cumulative Distribution Function # +# # +# Computes Cumulative Distribution Function for the Ratcliff Diffusion model # +# using 6 Gaussian quadrature for numerical integration # +# # +# References # +# Tuerlinckx, F. (2004). The efficient computation of the # +# cumulative distribution and probability density functions # +# in the diffusion model, Behavior Research Methods, # +# Instruments, & Computers, 36 (4), 702-716. # +# # +# Converted from cdfdif.c C script by Joachim Vandekerckhove # +# See also https://ppw.kuleuven.be/okp/software/dmat/ # +################################################################################ + +function cdf(d::RatcliffDDM, choice, rt, p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) -function _g_minus_small_time(x::Real, d::RatcliffDDM, N::Int) - """ - calculate the densities g- for the first exit time for small time - """ (ν, α, τ, z, η, sz, st, σ) = params(d) DT = x - τ #make into decision time @@ -347,124 +280,160 @@ function _g_minus_no_var(x::Real, d::RatcliffDDM) calculates the density g- when there is no variability in the input parameters. """ (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - N_small = 0 - N_large = 0 - simple = 0.0 - factor = exp(-α*z*ν - 0.5*ν*ν*DT) / (α*α) # Front term in A3 - ϵ = ϵ / factor - - ta = x / (α*α) - - N_large = ceil(1 / (π*sqrt(DT))) - if π*ta*ϵ < 1 - N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) + #note we need to make sure this is consistent in the all the relative bound models + if choice == 2 #lower + choice = 0 + elseif choice == 1 #upper + choice = 1 end - if 2*sqrt(2*π*ta)*ϵ < 1 - N_small = ceil(max(sqrt(ta) + 1, 2 + sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) - else - N_small = 2 + # Initializing variables + a2 = α*α + Z_U = (1-choice)*z+choice*(α-z)+sz/2 + Z_L = (1-choice)*z+choice*(α-z)-sz/2 + lower_t = τ-st/2 + upper_t = 0.0 + Δ = 1e-29 + min_rt=0.001 + v_max = 5000 # maximum number of terms in a partial sum approximating infinite series + + Fnew = 0.0 + sum_z=0.0 + sum_ν=0.0 + p1 = 0.0 + p0 = 0.0 + sum_hist = zeros(3) + denom = 0.0 + sifa = 0.0 + upp = 0.0 + low = 0.0 + fact = 0.0 + exdif = 0.0 + su = 0.0 + sl = 0.0 + zzz = 0.0 + ser = 0.0 + nr_ν = 6 + nr_z = 6 + + # Defining Gauss-Hermite abscissae and weights for numerical integration + gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] + w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] + gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] + w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] + + # Adjusting Gauss-Hermite abscissae and weights + for i=1:nr_ν + gk[i] = 1.41421356237309505*gk[i]*η+ν + w_gh[i] = w_gh[i]/1.772453850905515882 end - - if N_small < N_large - simple = _g_minus_small_time(x / (α*α), z, N_small) - else - simple = _g_minus_large_time(x / (α*α), z, N_large) + for i=1:nr_z + gz[i] = (.5*sz*gz[i])+z end - out = isinf(factor) ? 0 : (factor * simple) - return out -end - -function _integral_v_g_minus(Real::x, d::RatcliffDDM; ϵ::Real = 1e-6) - """ - calculates the integral of the density g- over the variable ν for a given set of input parameters. It takes into account variability in the parameters η - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - N_small = 0 - N_large = 0 - simple = 0.0 - factor = 1 / (α*α * sqrt(DT * η*η + 1)) * - exp(-0.5 * (ν*ν*DT + 2*ν*α*z - α*z*α*z*η*η) / (DT*η*η+1)) - ϵ = ϵ / factor - - ta = DT / (α*α) - - N_large = ceil(1 / (π*sqrt(DT))) - if π*ta*ϵ < 1 - N_large = max(N_large, ceil(sqrt(-2*log(π*ta*ϵ) / (π*π*ta)))) + # numerical integration + for i=1:nr_z + sum_ν=0.0 + # numerical integration + for m=1:nr_ν + if abs(gk[m])>ϵ + sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] + else + sum_ν+=gz[i]/α*w_gh[m] + end + end + sum_z+=sum_ν*w_g[i]/2 end + prob = sum_z + + if (rt-τ+st/2 > min_RT) # is t larger than lower boundary τ distribution? + upper_t = min(rt, τ+st/2) + p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t + p0 = (1-prob)*(upper_t-lower_t)/st + if rt > τ+st/2 # is t larger than upper boundary Ter distribution? + sum_hist = zeros(3) + for v in 1:v_max # infinite series + sum_hist = circshift(sum_hist, 1) + sum_ν = 0 + sifa = π*v/α + for m in 1:nr_ν # numerical integration with respect to xi + denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) + upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - + low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) + exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) + sum_ν += fact*exdif + end + sum_hist[3] = sum_hist[2] + v*sum_ν + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end - if 2*sqrt(2*π*ta)*ϵ < 1 - N_small = ceil(max(sqrt(ta)+1, 2+sqrt(-2*ta*log(2*ϵ*sqrt(2*π*ta))))) - else - N_small = 2 + Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) + # cumulative distribution function for t and x + elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? + sum_ν = 0 + for m in 1:nr_ν + if abs(gk[m]) > ϵ + sum_z = 0 + for i in 1:nr_z + zzz = (α - gz[i])*choice + gz[i]*(1 - choice) + ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ + (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + + (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ + sinh((1 - 2*choice)*gk[m]*α/.01) + sum_hist = zeros(3) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) + sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) + end + else + sum_hist = zeros(3) + su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) + sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (π*v)*(π*v)/(a2*100) + sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* + exp(-.5*denom*(rt - lower_t)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) + end + sum_ν += sum_z*w_gh[m] + end + Fnew = (p0*(1 - choice) + p1*choice) - sum_ν + end + elseif rt - τ + st/2 <= min_RT # is t lower than lower boundary Ter distr? + Fnew = 0 end + + Fnew = Fnew > Δ ? Fnew : 0 - if isinf(factor) - out = 0 - elseif η == 0 - out = _g_minus_no_var(x, d) - elseif N_small < N_large - simple = _g_minus_small_time(x/(α*α), d, N_small) - out = factor * simple - else - simple = _g_minus_large_time(x/(a*a), d, N_large) - out = factor * simple - end + return Fnew - return out end -function _integral_z_g_minus(x::Real, d::RatcliffDDM) - """ - calculate the integral of integral_v_g_minus over the variable zr for a given set of input parameters. They handle variability in the parameter sz - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - out = 0.0 - - if DT <= 0 # if DT <= 0 - out = 0 - elseif sz == 0 # this should be sz - out = _integral_v_g_minus(x, d) - else - a = z - 0.5*sz # zr - 0.5*szr; uniform variability - b = z + 0.5*sz # zr + 0.5*szr - step_width = TUNE_INT_Z - out = _integrate(integral_v_g_minus, d, a, b, step_width) / sz - end - - return out +function _add_outlier_cdf(y::Real, x::Real, p_outlier::Real; w_outlier::Real = 0.1) + #Ratcliff and Tuerlinckx, 2002 containment process + return y * (1 - p_outlier) + (x + (1. / (2 * w_outlier))) * w_outlier * p_outlier end -function _integral_τ_g_minus(x::Real, d::RatcliffDDM) - """ - calculate the integral of integral_z_g_minus over the variable τ for a given set of input parameters. They handle variability in the parameter st - """ - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - out = 0.0 - if st == 0 - out = _integral_z_g_minus(x, d) # should send t as RT - else - a = DT - 0.5*st # DT - 0.5*st - b = DT + 0.5*st # DT + 0.5*st - step_width = TUNE_INT_T0 - out = _integrate(integral_z_g_minus, d, a, b, step_width) / sts - end - - return out +function _p_outlier_in_range(p_outlier) + return (p_outlier >= 0) & (p_outlier <= 1) end """ @@ -615,12 +584,12 @@ function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps:: end """ - rand(dist::DDM, n_sim::Int) + rand(dist::RatcliffDDM, n_sim::Int) -Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. +Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. # Arguments -- `dist`: model object for the Drift Diffusion Model. +- `dist`: model object for the Ratcliff DDM. - `n_sim::Int`: the number of simulated rts """ From b3b5687f48d6e7bbbf39bb89f68419fa4f94482f Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:16:48 +0200 Subject: [PATCH 29/43] minor bugs --- src/RatcliffDDM.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl index b536149e..0206c205 100644 --- a/src/RatcliffDDM.jl +++ b/src/RatcliffDDM.jl @@ -275,10 +275,8 @@ function _g_minus_large_time(x::Real, d::RatcliffDDM, N::Int) return sum * π end -function _g_minus_no_var(x::Real, d::RatcliffDDM) - """ - calculates the density g- when there is no variability in the input parameters. - """ +function _cdf(d::RatcliffDDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} + (ν, α, τ, z, η, sz, st, σ) = params(d) #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) #note we need to make sure this is consistent in the all the relative bound models @@ -347,7 +345,7 @@ function _g_minus_no_var(x::Real, d::RatcliffDDM) end prob = sum_z - if (rt-τ+st/2 > min_RT) # is t larger than lower boundary τ distribution? + if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? upper_t = min(rt, τ+st/2) p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t p0 = (1-prob)*(upper_t-lower_t)/st @@ -417,7 +415,7 @@ function _g_minus_no_var(x::Real, d::RatcliffDDM) end Fnew = (p0*(1 - choice) + p1*choice) - sum_ν end - elseif rt - τ + st/2 <= min_RT # is t lower than lower boundary Ter distr? + elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? Fnew = 0 end From d9f85ed7c702017701c74800bb394e265a8a8c41 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:44:20 +0200 Subject: [PATCH 30/43] settings change --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b7bdae0b..595173b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ Manifest.toml /temp /benchmark/*.json -/benchmark/*.md \ No newline at end of file +/benchmark/*.md./vscode \ No newline at end of file From 44ef7c2f9b62fb277b58d6b12dc04d4b250ce3a9 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:47:14 +0200 Subject: [PATCH 31/43] rm vscode settings --- docs/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.gitignore b/docs/.gitignore index 93584992..1fa74a1a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,5 @@ build/ site/ docs/build/ -Manifest.toml \ No newline at end of file +Manifest.toml +settings.json \ No newline at end of file From 2e58140f7c07423725a960513a554eb6ae587da3 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 4 Jul 2023 19:47:34 +0200 Subject: [PATCH 32/43] change vscode settings --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 595173b1..474a45ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ Manifest.toml /temp -/benchmark/*.json -/benchmark/*.md./vscode \ No newline at end of file +/.vscode \ No newline at end of file From 83ca3770d604c8310e2c082266f85faf191a35f8 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 5 Jul 2023 18:58:18 +0200 Subject: [PATCH 33/43] adding Ratcliff DDM --- docs/src/Ratcliff_DDM.md | 2 +- src/SequentialSamplingModels.jl | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/Ratcliff_DDM.md b/docs/src/Ratcliff_DDM.md index f6cd3ff4..2465cbf0 100644 --- a/docs/src/Ratcliff_DDM.md +++ b/docs/src/Ratcliff_DDM.md @@ -72,7 +72,7 @@ sz = 0.05 Now that values have been assigned to the parameters, we will pass them to `RatcliffDDM` to generate the model object. ```@example RatcliffDDM -dist = DDM(ν, α, τ, z) +dist = RatcliffDDM(ν, α, τ, z, η, sz, st, σ) ``` ## Simulate Model diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index 3c4ae116..59611b6d 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -44,6 +44,8 @@ module SequentialSamplingModels export LCA export LNR export maaDDM + export MixedMultivariateDistribution + export RatcliffDDM export SSM1D export SSM2D export ContinuousMultivariateSSM From 7925430d4c66193989a4f115d64af7b0e90b405c Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 15:42:20 +0200 Subject: [PATCH 34/43] Merge ratcliff and DDM --- src/DDM.jl | 914 +++++++++++++++++++++++--------- src/SequentialSamplingModels.jl | 2 - src/utilities.jl | 138 +++++ 3 files changed, 802 insertions(+), 252 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 6711a092..744a3fa5 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -1,79 +1,179 @@ """ DDM{T<:Real} <: SSM2D -Model object for the standard Drift Diffusion Model. + Model object for the Ratcliff (Full) Diffusion Decision Model. # Parameters -- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 -- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 -- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 -- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 + - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 + - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 + - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). + - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. + - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. + - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. + - `σ`: diffusion noise constant. Default is 1. # Constructors - DDM(ν, α, τ, z) + DDM(ν, α, τ, z, η, sz, st, σ) - DDM(; ν = 1.0, - α = 0.8, - τ = 0.3 - z = 0.25) - + DDM(; ν = 1.00, + α = 0.80, + τ = 0.30, + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0 + ) + # Example -```julia +````julia using SequentialSamplingModels -dist = DDM(ν = 1.0, α = 0.8, τ = 0.3, z = 0.25) +dist = DDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) choice,rt = rand(dist, 10) like = pdf.(dist, choice, rt) loglike = logpdf.(dist, choice, rt) -``` - -# References +```` +# References + Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. +Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ mutable struct DDM{T<:Real} <: SSM2D ν::T α::T τ::T z::T + η::T + sz::T + st::T + σ::T end -function DDM(ν, α, τ, z) - return DDM(promote(ν, α, τ, z)...) +function DDM(ν, α, τ, z, η, sz, st, σ) + return DDM(promote(ν, α, τ, z, η, sz, st, σ)...) end function params(d::DDM) - (d.ν, d.α, d.τ, d.z) + (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) end function DDM(; ν = 1.00, α = 0.80, τ = 0.30, - z = 0.50) - return DDM(ν, α, τ, z) + z = 0.25, + η = 0.16, + sz = 0.05, + st = 0.10, + σ = 1.0) + return DDM(ν, α, τ, z, η, sz, st, σ) end -################################################################################ -# Converted from WienerDiffusionModel.jl repository orginally by Tobias Alfers# -# See https://github.com/t-alfers/WienerDiffusionModel.jl # -################################################################################ - -##################################### -# Probability density function # -# Navarro & Fuss (2009) # -# Wabersich & Vandekerckhove (2014) # -##################################### - function pdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) if choice == 1 - (ν, α, τ, z) = params(d) - return _pdf(DDM(-ν, α, τ, 1-z), rt; ϵ) + (ν, α, τ, z, η, sz, st, σ) = params(d) + return _pdf_Full(DDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) + end + return _pdf_Full(d, rt; ϵ) +end + + +""" + _pdf_Full(d::DDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} + +Calculate the probability density function (PDF) for a Diffusion Decision Model (DDM) object. This +function applies numerical integration to account for variability in non-decision time and bias, as suggested +by Ratcliff and Tuerlinckx (2002). + +# Arguments +- `d::DDM{T}`: a DDM distribution object +- `rt`: reaction time. + +# Optional arguments +- `ϵ::Real`: a small constant to prevent divide by zero errors, default is 1.0e-12. +- `n_st::Int`: specifies the number of subintervals in the Simpson's rule for the integration associated with non-decision time variability. Default is 2. +- `n_sz::Int`: specifies the number of subintervals in the Simpson's rule for the integration associated with starting point variability. Default is 2. + +""" +function _pdf_Full(d::DDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} + + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if τ ≥ rt + return T(NaN) + end + + if st < 1.0e-3 + st = 0 + end + if sz < 1.0e-3 + sz = 0 + end + + if sz==0 + if st==0 #sv=0,sz=0,st=0 + return _pdf_sv(d, rt; ϵ) + else #sv=0,sz=0,st=$ + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) + end + else #sz=$ + if st==0 #sv=0,sz=$,st=0 + return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) + else #sv=0,sz=$,st=$ + return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) + end end - return _pdf(d, rt; ϵ) end -# probability density function over the lower boundary +""" + _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) + +Computes the Probability Density Function (PDF) for a given Diffusion Decision Model +with across-trial variability in drift-rate. This function uses analytic integration of the likelihood function +for variability in drift-rate. + +# Arguments +- `d::DDM`: a DDM distribution constructor object +- `rt`: Reaction time for which the PDF is to be computed. + +# Returns +- Returns the computed PDF value. + +""" +function _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if η == 0 + return _pdf(DDM(ν, α, τ, z), rt; ϵ) + end + + return _pdf(DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 +end + +""" + _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} + +Computes the Probability Density Function (PDF) for a given Drift Diffusion Model (DDM). +The function uses normalized time and applies an infinite sum algorithm. The implementation +is based on the work of Navarro & Fuss (2009) and Wabersich & Vandekerckhove (2014). + +# Arguments +- `d::DDM{T}`: A DDM object containing the parameters of the Diffusion Model. +- `t::Real`: Time for which the PDF is to be computed. + +# Optional Arguments +- `ϵ::Real = 1.0e-12`: A very small number representing machine epsilon. + +# Returns +- Returns the computed PDF value. + +# See also: + - Converted from WienerDiffusionModel.jl repository orginally by Tobias Alfers: https://github.com/t-alfers/WienerDiffusionModel.jl +""" + function _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} (ν, α, τ, z) = params(d) if τ ≥ t @@ -125,230 +225,465 @@ function _large_time_pdf(u::T, z::T, K::Int) where {T<:Real} return π * inf_sum end -logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -#logpdf(d::DDM, t::Real; ϵ::Real = 1.0e-12) = log(pdf(d, t; ϵ)) +""" + _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -function logpdf(d::DDM, data::T) where {T<:NamedTuple} - return sum(logpdf.(d, data...)) -end +Calculate the 1-dimensional Simpson's numerical integration for a drift diffusion model with given parameters. This function is used for integrating over either the starting point or the non-decision time. -function logpdf(dist::DDM, data::Array{<:Tuple,1}) - LL = 0.0 - for d in data - LL += logpdf(dist, d...) - end - return LL -end +# Arguments +- `x::Real`: Reaction time for which the probability density function is being computed. +- `ν::Real`, `η::Real`, `α::Real`, `z::Real`, `τ::Real`: Parameters of the Ratcliff Drift Diffusion Model. +- `ϵ::Real`: A small constant to prevent divide by zero errors. +- `lb_z::Real`, `ub_z::Real`: Lower and upper bounds for z (starting point). +- `n_sz::Int`: Specifies the number of subintervals for Simpson's rule in the z dimension. +- `lb_t::Real`, `ub_t::Real`: Lower and upper bounds for t (non-decision time). +- `n_st::Int`: Specifies the number of subintervals for Simpson's rule in the t dimension. -logpdf(d::DDM, data::Tuple) = logpdf(d, data...) +# Returns +- Returns the Simpson's numerical integration of the PDF of the Ratcliff Drift Diffusion Model at the given reaction time `x`, over the specified bounds and subintervals. -######################################### -# Cumulative density function # -# Blurton, Kesselmeier, & Gondan (2012) # -######################################### - -function cdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z) = params(d) - return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ) - end +# Note +- If `n_st` is 0, the function integrates over z (starting point). If `n_sz` is 0, the function integrates over t (non-decision time). - return cdf(d, rt; ϵ) -end +# References -# cumulative density function over the lower boundary -function cdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - if d.τ ≥ t - return T(NaN) +https://en.wikipedia.org/wiki/Simpson%27s_rule + +""" +# Simpson's Method one dimentional case +function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + + n = max(n_st, n_sz) + + if n_st == 0 #integration over z + hz = (ub_z-lb_z)/n + ht = 0 + lb_t = τ + ub_t = τ + else #integration over t + hz = 0 + ht = (ub_t-lb_t)/n + lb_z = z + ub_z = z end - K_l = _K_large(d, t; ϵ) - K_s = _K_small(d, t; ϵ) + S = _pdf_sv(DDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) + + y = 0 + z_tag = 0 + t_tag = 0 + + for i in 1:n + z_tag = lb_z + hz * i + t_tag = lb_t + ht * i + + y = _pdf_sv(DDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) + + if isodd(i) + S += 4 * y + else + S += 2 * y + end - if K_l < 10*K_s - return _Fl_lower(d, K_l, t) end - return _Fs_lower(d, K_s, t) -end + + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st + + return (ht + hz) * S / 3 -# Large time representation of lower subdistribution -function _Fl_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (ν, α, τ, z) = params(d) - F = zero(T) - K_series = K:-1:1 - for k in K_series - F -= (k/(ν^2 + k^2*π^2/(α^2)) * - exp(-ν*α*z - 0.5*ν^2*(t-τ) - 0.5*k^2*π^2/(α^2)*(t-τ)) * - sin(π * k * z)) - end - return _P_upper(ν, α, z) + 2*π/(α^2) * F end -# Small time representation of the upper subdistribution -function _Fs_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (ν, α, τ, z) = params(d) - if abs(ν) < sqrt(eps(T)) - return _Fs0_lower(d, K, t) - end +""" + _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) + +Calculate the 2-dimensional Simpson's numerical integration for a drift diffusion model with given parameters. This function is used for integrating over both the starting point and the non-decision time. + +# Arguments +- `x::Real`: Reaction time for which the probability density function is being computed. +- `ν::Real`, `η::Real`, `α::Real`, `z::Real`, `τ::Real`: Parameters of the Ratcliff Drift Diffusion Model. +- `ϵ::Real`: A small constant to prevent divide by zero errors. +- `lb_z::Real`, `ub_z::Real`: Lower and upper bounds for z (starting point). +- `n_sz::Int`: Specifies the number of subintervals for Simpson's rule in the z dimension. +- `lb_t::Real`, `ub_t::Real`: Lower and upper bounds for t (non-decision time). +- `n_st::Int`: Specifies the number of subintervals for Simpson's rule in the t dimension. + +# Returns +- Returns the Simpson's numerical integration of the PDF of the Ratcliff Drift Diffusion Model at the given reaction time `x`, over the specified bounds and subintervals in both dimensions. - sqt = sqrt(t-τ) +# Note +- The function calls `_simpson_1D` to perform the 1-dimensional integrations over each dimension. - S1 = zero(T) - S2 = zero(T) - K_series = K:-1:1 +""" +# Simpson's Method two dimentional case +function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - for k in K_series - S1 += (_exp_pnorm(2*ν*α*k, -sign(ν)*(2*α*k+α*z+ν*(t-τ))/sqt) - - _exp_pnorm(-2*ν*α*k-2*ν*α*z, sign(ν)*(2*α*k+α*z-ν*(t-τ))/sqt)) + ht = (ub_t-lb_t)/n_st + S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - S2 += (_exp_pnorm(-2*ν*α*k, sign(ν)*(2*α*k-α*z-ν*(t-τ))/sqt) - - _exp_pnorm(2*ν*α*k-2*ν*α*z, -sign(ν)*(2*α*k-α*z+ν*(t-τ))/sqt)) + t_tag = 0 + y = 0 + for i_t in 1:n_st + t_tag = lb_t + ht * i_t + y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) + + if isodd(i_t) + S += 4 * y + else + S += 2 * y + end end - return _P_upper(ν, α, z) + sign(ν) * ((cdf(Normal(), -sign(ν) * (α*z+ν*(t-τ))/sqt) - - _exp_pnorm(-2*ν*α*z, sign(ν) * (α*z-ν*(t-τ)) / sqt)) + S1 + S2) + S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y + S = S / (ub_t - lb_t) + + return ht * S / 3 + end -# Zero drift version -function _Fs0_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} - (_, α, τ, z) = params(d) - F = zero(T) - K_series = K:-1:0 - for k in K_series - F -= (cdf(Distributions.Normal(), (-2*k - 2+z) * α / sqrt(t-τ)) + cdf(Distributions.Normal(), (-2*k -z) * α / sqrt(t-τ))) +logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) + +# function logpdf(d::DDM, data::T) where {T<:NamedTuple} +# return sum(logpdf.(d, data...)) +# end + +# function logpdf(dist::DDM, data::Array{<:Tuple,1}) +# LL = 0.0 +# for d in data +# LL += logpdf(dist, d...) +# end +# return LL +# end + +# logpdf(d::DDM, data::Tuple) = logpdf(d, data...) + +""" + cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) + +Compute the Cumulative Distribution Function (CDF) for the Ratcliff Diffusion model. This function uses 6 Gaussian quadrature for numerical integration. + +# Arguments +- `d`: an instance of DDM Constructor +- `choice`: an input representing the choice. +- `rt`: response time. +- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. + +# Returns +- `y`: an array representing the CDF of the Ratcliff Diffusion model. + +# Reference +Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution and probability density functions in the diffusion model, Behavior Research Methods, Instruments, & Computers, 36 (4), 702-716. + +# See also +- Converted from cdfdif.c C script by Joachim Vandekerckhove: https://ppw.kuleuven.be/okp/software/dmat/ +""" +function cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) + + (ν, α, τ, z, η, sz, st, σ) = params(d) + + size = length(rt) + y = zeros(Float64, size) + epsi = 1e-10 + + #transform parameters + α = α/10. + τ = τ + η = η/10. + epsi + z = z*(α/10.) + sz = sz*(α/10.) + epsi + st = st + epsi + ν = ν/10. + + p_boundary = 0.0 + + for i in 1:size + y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) + y[i] = (1 - p_boundary) + rt[i]*y[i] end - return 2*F -end -# Number of terms required for large time representation -function _K_large(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) - x = t-τ - sqrtL1 = sqrt(1/x) * α/π - sqrtL2 = sqrt(max(1, -2/x*α*α/π/π * (log(ϵ*π*x/2 * (ν*ν + π*π/α/α)) + ν*α*z + ν*ν*x/2))) - return ceil(Int, max(sqrtL1, sqrtL2)) + + return y end +""" + _cdf(d::DDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} -# Number of terms required for small time representation -function _K_small(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) - if abs(ν) < sqrt(eps(T)) - return ceil(Int, max(0, z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ/(2-2*z)))))) +A helper function to compute the Cumulative Distribution Function (CDF) for the Full Diffusion model. + +# Arguments +- `d`: an instance of DDM Constructor +- `choice`: an input representing the choice. +- `rt`: response time. +- `prob`: a probability value. +- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. + +# Returns +- `Fnew`: the computed CDF for the given parameters. + +""" +function _cdf(d::DDM, choice, rt, prob; ϵ::Real = 1e-7) + + (ν, α, τ, z, η, sz, st, σ) = params(d) + #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) + #note we need to make sure this is consistent in the all the relative bound models + if choice == 2 #lower + choice = 0 + elseif choice == 1 #upper + choice = 1 end - if ν > 0 - return _K_small(DDM(-ν, α, τ, z), t; ϵ = exp(-2*α*z*ν)*ϵ) + + # Initializing variables + a2 = α*α + Z_U = (1-choice)*z+choice*(α-z)+sz/2 + Z_L = (1-choice)*z+choice*(α-z)-sz/2 + lower_t = τ-st/2 + upper_t = 0.0 + Δ = 1e-29 + min_rt=0.001 + v_max = 5000 # maximum number of terms in a partial sum approximating infinite series + + Fnew = 0.0 + sum_z=0.0 + sum_ν=0.0 + p1 = 0.0 + p0 = 0.0 + sum_hist = zeros(3) + denom = 0.0 + sifa = 0.0 + upp = 0.0 + low = 0.0 + fact = 0.0 + exdif = 0.0 + su = 0.0 + sl = 0.0 + zzz = 0.0 + ser = 0.0 + nr_ν = 6 + nr_z = 6 + + # Defining Gauss-Hermite abscissae and weights for numerical integration + gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] + w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] + gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] + w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] + + # Adjusting Gauss-Hermite abscissae and weights + for i=1:nr_ν + gk[i] = 1.41421356237309505*gk[i]*η+ν + w_gh[i] = w_gh[i]/1.772453850905515882 end - S2 = z - 1 + 1/(2*ν*α) * log(ϵ/2 * (1-exp(2*ν*α))) - S3 = (0.535 * sqrt(2*(t-τ)) + ν*(t-τ) + α*z)/(2*α) - S4 = z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ*α/(0.3 * sqrt(2*π*(t-τ))) * exp(ν^2*(t-τ)/2 + ν*α*z) ))) - return ceil(Int, max(0, S2, S3, S4)) -end -# Probability for absorption at upper barrier -function _P_upper(ν::T, α::T, z::T) where {T<:Real} - e = exp(-2 * ν * α * (1-z)) - if isinf(e) - return 1.0 + for i=1:nr_z + gz[i] = (.5*sz*gz[i])+z end - if abs(e-1) < sqrt(eps(T)) - return 1-z + + # numerical integration + for i=1:nr_z + sum_ν=0.0 + # numerical integration + for m=1:nr_ν + if abs(gk[m])>ϵ + sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] + else + sum_ν+=gz[i]/α*w_gh[m] + end + end + sum_z+=sum_ν*w_g[i]/2 end - return (1-e)/(exp(2*ν*α*z) - e) -end + prob = sum_z + + if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? + upper_t = min(rt, τ+st/2) + p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t + p0 = (1-prob)*(upper_t-lower_t)/st + if rt > τ+st/2 # is t larger than upper boundary Ter distribution? + sum_hist = zeros(3) + for v in 1:v_max # infinite series + sum_hist = circshift(sum_hist, 1) + sum_ν = 0 + sifa = π*v/α + for m in 1:nr_ν # numerical integration with respect to xi + denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) + upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) + fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - + low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) + exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) + sum_ν += fact*exdif + end + sum_hist[3] = sum_hist[2] + v*sum_ν + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end -# Calculates exp(a) * pnorm(b) using an approximation by Kiani et al. (2008) -function _exp_pnorm(a::T, b::T) where {T<:Real} - r = exp(a) * cdf(Distributions.Normal(), b) - if isnan(r) && b < -5.5 - r = (1/sqrt(2)) * exp(a - b^2/2) * (0.5641882/(b^3) - 1/(b * sqrt(π))) + Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) + # cumulative distribution function for t and x + elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? + sum_ν = 0 + for m in 1:nr_ν + if abs(gk[m]) > ϵ + sum_z = 0 + for i in 1:nr_z + zzz = (α - gz[i])*choice + gz[i]*(1 - choice) + ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ + (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + + (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ + sinh((1 - 2*choice)*gk[m]*α/.01) + sum_hist = zeros(3) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) + sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) + end + else + sum_hist = zeros(3) + su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) + sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) + for v in 1:v_max + sum_hist = circshift(sum_hist, 1) + sifa = π*v/α + denom = (π*v)*(π*v)/(a2*100) + sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* + exp(-.5*denom*(rt - lower_t)) + if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 + break + end + end + sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) + end + sum_ν += sum_z*w_gh[m] + end + Fnew = (p0*(1 - choice) + p1*choice) - sum_ν + end + elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? + Fnew = 0 end - return r + + Fnew = Fnew > Δ ? Fnew : 0 + + return Fnew + end """ rand(dist::DDM) -Generate a random rt for the Diffusion Decision Model (negative coding) +Generate a random choice and rt for the Ratcliff Diffusion Model # Arguments -- `dist`: model object for the Diffusion Decision Model. +- `dist`: model object for Ratcliff Diffusion Model. + +method simulating the diffusion process: +_rand_rejection uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process + +# References + + Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). + A comparison of four methods for simulating the diffusion process. + Behavior Research Methods, Instruments, & Computers, 33, 443-456. + + Converted from Rhddmjagsutils.R R script by Kianté Fernandez + See also https://github.com/kiante-fernandez/Rhddmjags. """ function rand(rng::AbstractRNG, d::DDM) return _rand_rejection(rng, d) end -# Rejection-based Method for the Symmetric Wiener Process(Tuerlinckx et al., 2001 based on Lichters et al., 1995) -# adapted from the RWiener R package, note, here σ = 0.1 -function _rand_rejection(rng::AbstractRNG, d::DDM) - (ν, α, τ, z) = params(d) - - ϵ = 1.0e-15 - - D = .005 # = 2*σ^2 => 1/200 - zn = (z*α) / 10 # absolute bias! - αn = α / 10 - νn = ν / 10 - - total_time = 0.0 - start_pos = 0.0 - Aupper = αn - zn - Alower = -zn - radius = min(abs(Aupper), abs(Alower)) - λ = 0.0 - F = 0.0 - prob = 0.0 - - while true - if νn == 0 - λ = (0.25D*π^2) / (radius^2) - F = 1.0 - prob = .5 - else - λ = ((0.25*νn^2)/D) + ((0.25*D*π^2) / (radius^2)) - F = (D*π) / (radius*νn) - F = F^2 / (1+F^2) - prob = exp((radius*νn)/D) - prob = prob / (1+prob) - end - - r = rand(rng) - dir = r < prob ? 1 : -1 - l = -1.0 - s1 = 0.0 - s2 = 0.0 - - # Tuerlinckx et al. (2001; eq. 16) - while s2 > l - s1 = rand(rng) - s2 = rand(rng) - tnew = 0.0 - tδ = 0.0 - uu = zero(Int) - - while (abs(tδ) > ϵ) || (uu == 0) - uu += 1 - tt = 2*uu + 1 - tδ = tt * (uu % 2 == 0 ? 1 : -1) * (s1 ^ (F * tt^2)) - tnew += tδ - end +function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) + (ν, α, τ, z, η, sz, st, σ) = params(d) - l = 1 + (s1^(-F)) * tnew - end + if η == 0 + η = 1e-16 + end - total_time += abs(log(s1)) / λ - dir = start_pos + dir * radius - - if (dir + ϵ) > Aupper - rt = total_time + τ - choice = 1 - return (;choice,rt) - elseif (dir - ϵ) < Alower - rt = total_time + τ - choice = 2 - return (;choice,rt) - else - start_pos = dir - radius = min(abs(Aupper - start_pos), (abs(Alower - start_pos))) + # Initialize output vectors + choice = fill(0, N) + rt = fill(0.0, N) + + # Called sigma in 2001 paper + D = σ^2 / 2 + + # Program specifications + ϵ = eps() # precision from 1.0 to next double-precision number + Δ = ϵ + + for n in 1:N + r1 = randn() + μ = ν + r1 * η + bb = z - sz / 2 + sz * rand() + zz = bb * α + finish = 0 + totaltime = 0 + startpos = 0 + Aupper = α - zz + Alower = -zz + radius = min(abs(Aupper), abs(Alower)) + + while finish == 0 + λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 + # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 + F = D * π / (radius * μ) + F = F^2 / (1 + F^2) + # formula p447 in 2001 paper + prob = exp(radius * μ / D) + prob = prob / (1 + prob) + dir_ = 2 * (rand() < prob) - 1 + l = -1 + s2 = 0 + s1 = 0 + while s2 > l + s2 = rand() + s1 = rand() + tnew = 0 + told = 0 + uu = 0 + while abs(tnew - told) > ϵ || uu == 0 + told = tnew + uu += 1 + tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) + # infinite sum in formula (16) in BRMIC,2001 + end + l = 1 + s1^(-F) * tnew + end + # rest of formula (16) + t = abs(log(s1)) / λ + # is the negative of t* in (14) in BRMIC,2001 + totaltime += t + dir_ = startpos + dir_ * radius + ndt = τ - st / 2 + st * rand() + if (dir_ + Δ) > Aupper + rt[n] = ndt + totaltime + choice[n] = 1 + finish = 1 + elseif (dir_ - Δ) < Alower + rt[n] = ndt + totaltime + choice[n] = 2 + finish = 1 + else + startpos = dir_ + radius = minimum(abs.([Aupper, Alower] .- startpos)) + end end end + return (choice=choice,rt=rt) +end + +""" + rand(dist::DDM, n_sim::Int) + +Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. + +# Arguments +- `dist`: model object for the Ratcliff DDM. +- `n_sim::Int`: the number of simulated rts +""" + +function rand(rng::AbstractRNG, d::DDM, n_sim::Int) + return _rand_rejection(rng, d, N = n_sim) end """ @@ -362,47 +697,126 @@ Returns 2 for the number of choice options """ n_options(d::DDM) = 2 -""" - simulate(model::DDM; Δt=.001) -Returns a matrix containing evidence samples of the drift diffusion model decision process. In the matrix, rows -represent samples of evidence per time step and columns represent different accumulators. +####### +# old cdf code for debug checking +######################################### +# Cumulative density function # +# Blurton, Kesselmeier, & Gondan (2012) # +######################################### + +# function cdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) +# if choice == 1 +# (ν, α, τ, z) = params(d) +# return cdf(DDM(-ν, α, τ, 1-z), rt; ϵ) +# end -# Arguments +# return cdf(d, rt; ϵ) +# end -- `model::DDM`: an drift diffusion model object +# # cumulative density function over the lower boundary +# function cdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# if d.τ ≥ t +# return T(NaN) +# end -# Keywords +# K_l = _K_large(d, t; ϵ) +# K_s = _K_small(d, t; ϵ) -- `Δt=.001`: size of time step of decision process in seconds -""" -function simulate(model::DDM; Δt=.001) - (;ν,α,z) = model - x = α * z - t = 0.0 - evidence = [x] - time_steps = [t] - while (x < α) && (x > 0) - t += Δt - x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) - push!(evidence, x) - push!(time_steps, t) - end - return time_steps,evidence -end +# if K_l < 10*K_s +# return _Fl_lower(d, K_l, t) +# end +# return _Fs_lower(d, K_s, t) +# end + +# # Large time representation of lower subdistribution +# function _Fl_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (ν, α, τ, z) = params(d) +# F = zero(T) +# K_series = K:-1:1 +# for k in K_series +# F -= (k/(ν^2 + k^2*π^2/(α^2)) * +# exp(-ν*α*z - 0.5*ν^2*(t-τ) - 0.5*k^2*π^2/(α^2)*(t-τ)) * +# sin(π * k * z)) +# end +# return _P_upper(ν, α, z) + 2*π/(α^2) * F +# end + +# # Small time representation of the upper subdistribution +# function _Fs_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (ν, α, τ, z) = params(d) +# if abs(ν) < sqrt(eps(T)) +# return _Fs0_lower(d, K, t) +# end + +# sqt = sqrt(t-τ) + +# S1 = zero(T) +# S2 = zero(T) +# K_series = K:-1:1 + +# for k in K_series +# S1 += (_exp_pnorm(2*ν*α*k, -sign(ν)*(2*α*k+α*z+ν*(t-τ))/sqt) - +# _exp_pnorm(-2*ν*α*k-2*ν*α*z, sign(ν)*(2*α*k+α*z-ν*(t-τ))/sqt)) + +# S2 += (_exp_pnorm(-2*ν*α*k, sign(ν)*(2*α*k-α*z-ν*(t-τ))/sqt) - +# _exp_pnorm(2*ν*α*k-2*ν*α*z, -sign(ν)*(2*α*k-α*z+ν*(t-τ))/sqt)) +# end -# function rand(dist::DDM) -# (;ν,α,z,σ,τ, Δt,η,st,sz) = dist -# t = 0.0 -# srΔt = √Δt -# x = rand(Uniform(z - sz / 2, z + sz / 2)) -# ν′ = rand(Normal(ν, η)) -# while (x < α) && (x > 0) -# ϵ = rand(Normal(0, σ)) -# x += ν′* Δt + ϵ * srΔt -# t += Δt +# return _P_upper(ν, α, z) + sign(ν) * ((cdf(Normal(), -sign(ν) * (α*z+ν*(t-τ))/sqt) - +# _exp_pnorm(-2*ν*α*z, sign(ν) * (α*z-ν*(t-τ)) / sqt)) + S1 + S2) +# end + +# # Zero drift version +# function _Fs0_lower(d::DDM{T}, K::Int, t::Real) where {T<:Real} +# (_, α, τ, z) = params(d) +# F = zero(T) +# K_series = K:-1:0 +# for k in K_series +# F -= (cdf(Distributions.Normal(), (-2*k - 2+z) * α / sqrt(t-τ)) + cdf(Distributions.Normal(), (-2*k -z) * α / sqrt(t-τ))) +# end +# return 2*F +# end +# # Number of terms required for large time representation +# function _K_large(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# (ν, α, τ, z) = params(d) +# x = t-τ +# sqrtL1 = sqrt(1/x) * α/π +# sqrtL2 = sqrt(max(1, -2/x*α*α/π/π * (log(ϵ*π*x/2 * (ν*ν + π*π/α/α)) + ν*α*z + ν*ν*x/2))) +# return ceil(Int, max(sqrtL1, sqrtL2)) +# end + +# # Number of terms required for small time representation +# function _K_small(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} +# (ν, α, τ, z) = params(d) +# if abs(ν) < sqrt(eps(T)) +# return ceil(Int, max(0, z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ/(2-2*z)))))) +# end +# if ν > 0 +# return _K_small(DDM(-ν, α, τ, z), t; ϵ = exp(-2*α*z*ν)*ϵ) +# end +# S2 = z - 1 + 1/(2*ν*α) * log(ϵ/2 * (1-exp(2*ν*α))) +# S3 = (0.535 * sqrt(2*(t-τ)) + ν*(t-τ) + α*z)/(2*α) +# S4 = z/2 - sqrt(t-τ)/(2*α) * quantile(Normal(), max(0, min(1, ϵ*α/(0.3 * sqrt(2*π*(t-τ))) * exp(ν^2*(t-τ)/2 + ν*α*z) ))) +# return ceil(Int, max(0, S2, S3, S4)) +# end +# # Probability for absorption at upper barrier +# function _P_upper(ν::T, α::T, z::T) where {T<:Real} +# e = exp(-2 * ν * α * (1-z)) +# if isinf(e) +# return 1.0 +# end +# if abs(e-1) < sqrt(eps(T)) +# return 1-z +# end +# return (1-e)/(exp(2*ν*α*z) - e) +# end + +# # Calculates exp(a) * pnorm(b) using an approximation by Kiani et al. (2008) +# function _exp_pnorm(a::T, b::T) where {T<:Real} +# r = exp(a) * cdf(Distributions.Normal(), b) +# if isnan(r) && b < -5.5 +# r = (1/sqrt(2)) * exp(a - b^2/2) * (0.5641882/(b^3) - 1/(b * sqrt(π))) # end -# choice = (x < α) + 1 -# t += rand(Uniform(τ - st / 2, τ + st / 2)) -# return choice,t +# return r # end \ No newline at end of file diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index 59611b6d..369d0490 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -45,7 +45,6 @@ module SequentialSamplingModels export LNR export maaDDM export MixedMultivariateDistribution - export RatcliffDDM export SSM1D export SSM2D export ContinuousMultivariateSSM @@ -89,5 +88,4 @@ module SequentialSamplingModels include("CircularDDM.jl") include("ext_functions.jl") include("ex_gaussian.jl") - include("RatcliffDDM.jl") end \ No newline at end of file diff --git a/src/utilities.jl b/src/utilities.jl index a7ee21c5..335bd2ac 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -1,3 +1,141 @@ +abstract type Mixed <: ValueSupport end + +""" + SSM2D = Distribution{Multivariate, Mixed} + +An abstract type for sequential sampling models characterized by a multivariate choice-reaction time distribution. +Sub-types of `SSM2D` output a `NamedTuple` consisting of a vector of choices and reaction times. +""" +const SSM2D = Distribution{Multivariate, Mixed} + +""" + SSM1D <: ContinuousUnivariateDistribution + +An abstract type for sequential sampling models characterized by a single choice reaction time distribution. +Sub-types of `SSM1D` output a vector of reaction times. +""" +abstract type SSM1D <: ContinuousUnivariateDistribution end + +abstract type AbstractWald <: SSM1D end + +abstract type AbstractaDDM <: SSM2D end + +abstract type PDFType end + +""" + Exact <: PDFType + +Has closed-form PDF. +""" +struct Exact <: PDFType end + +""" + Approximate <: PDFType + +Has approximate PDF based on kernel density estimator. +""" +struct Approximate <: PDFType end + +get_pdf_type(d::SSM1D) = Exact +get_pdf_type(d::SSM2D) = Exact + +minimum(d::SSM1D) = 0.0 +maximum(d::SSM1D) = Inf + +minimum(d::SSM2D) = 0.0 +maximum(d::SSM2D) = Inf + +insupport(d::SSM2D, data) = data.rt ≥ minimum(d) && data.rt ≤ maximum(d) + +Base.broadcastable(x::SSM1D) = Ref(x) +Base.broadcastable(x::SSM2D) = Ref(x) + +vectorize(d::SSM2D, r::NamedTuple) = [r...] + +Base.length(d::SSM2D) = 2 + +rand(d::SSM2D) = rand(Random.default_rng(), d) +rand(d::SSM2D, n::Int) = rand(Random.default_rng(), d, n) + +""" + logpdf(d::SSM2D, data::NamedTuple) + +Computes the likelihood for a 2D sequential sampling model. + +# Arguments + +- `d::SSM2D`: an object for a 2D sequential sampling model +- `data::NamedTuple`: a NamedTuple of data containing choice and reaction time +""" +logpdf(d::SSM2D, data::NamedTuple; kwargs...) = logpdf.(d, data.choice, data.rt; kwargs...) +logpdf(d::SSM2D, data::Vector{Real}; kwargs...) = logpdf(d, Int(data[1]), data[2]; kwargs...) + +""" + loglikelihood(d::SSM2D, data::NamedTuple) + +Computes the summed log likelihood for a 2D sequential sampling model. + +# Arguments + +- `d::SSM2D`: an object for a 2D sequential sampling model +- `data::NamedTuple`: a NamedTuple of data containing choice and reaction time +""" +loglikelihood(d::SSM2D, data::NamedTuple) = sum(logpdf.(d, data...)) + +""" + pdf(d::SSM2D, data::NamedTuple) + +Computes the probability density for a 2D sequential sampling model. + +# Arguments + +- `d::SSM2D`: an object for a 2D sequential sampling model +- `data::NamedTuple`: a NamedTuple of data containing choice and reaction time +""" +pdf(d::SSM2D, data::NamedTuple) = pdf.(d, data.choice, data.rt) + + +""" + rand(rng::AbstractRNG, d::SSM2D, N::Int) + +Default method for Generating `n_sim` random choice-rt pairs from a sequential sampling model +with more than one choice option. + +# Arguments +- `d::SSM2D`: a 2D sequential sampling model. +- `n_sim::Int`: the number of simulated choices and rts +""" +function rand(rng::AbstractRNG, d::SSM2D, N::Int) + choice = fill(0, N) + rt = fill(0.0, N) + for i in 1:N + choice[i],rt[i] = rand(rng, d) + end + return (;choice,rt) +end + +""" + n_options(dist::SSM2D) + +Returns the number of choice options based on the length of the drift rate vector `ν`. + +# Arguments + +- `d::SSM2D`: a sub-type of `SSM2D` +""" +n_options(d::SSM2D) = length(d.ν) + +""" + n_options(dist::SSM1D) + +Returns 1 for the number of choice options + +# Arguments + +- `d::SSM1D`: a sub-type of `SSM1D` +""" +n_options(d::SSM1D) = 1 + function Base.show(io::IO, ::MIME"text/plain", model::SSM1D) return _show(io::IO, model) end From efadc3509f43bc7d4782ba13c0a47a3fa5075d12 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 16:52:22 +0200 Subject: [PATCH 35/43] bugs in integration --- src/DDM.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 744a3fa5..6b305809 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -147,10 +147,10 @@ function _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) (ν, α, τ, z, η, sz, st, σ) = params(d) if η == 0 - return _pdf(DDM(ν, α, τ, z), rt; ϵ) + return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) end - return _pdf(DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 end """ @@ -175,7 +175,8 @@ is based on the work of Navarro & Fuss (2009) and Wabersich & Vandekerckhove (20 """ function _pdf(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z) = params(d) + (ν, α, τ, z, η, sz, st, σ) = params(d) + if τ ≥ t return T(NaN) end From e86074cf1708d39cff730edf7518a068ef8db728 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 16:57:52 +0200 Subject: [PATCH 36/43] slights changes for merge --- src/DDM.jl | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/DDM.jl b/src/DDM.jl index 6b305809..53488636 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -698,6 +698,51 @@ Returns 2 for the number of choice options """ n_options(d::DDM) = 2 +""" + simulate(model::DDM; Δt=.001) + +Returns a matrix containing evidence samples of the drift diffusion model decision process. In the matrix, rows +represent samples of evidence per time step and columns represent different accumulators. + +# Arguments + +- `model::DDM`: an drift diffusion model object + +# Keywords + +- `Δt=.001`: size of time step of decision process in seconds +""" +function simulate(model::DDM; Δt=.001) + (;ν, α, τ, z, η, sz, st, σ) = model + + x = α * z + t = 0.0 + evidence = [x] + time_steps = [t] + while (x < α) && (x > 0) + t += Δt + x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) + push!(evidence, x) + push!(time_steps, t) + end + return time_steps,evidence +end + +# function rand(dist::DDM) +# (;ν,α,z,σ,τ, Δt,η,st,sz) = dist +# t = 0.0 +# srΔt = √Δt +# x = rand(Uniform(z - sz / 2, z + sz / 2)) +# ν′ = rand(Normal(ν, η)) +# while (x < α) && (x > 0) +# ϵ = rand(Normal(0, σ)) +# x += ν′* Δt + ϵ * srΔt +# t += Δt +# end +# choice = (x < α) + 1 +# t += rand(Uniform(τ - st / 2, τ + st / 2)) +# return choice,t +# end ####### # old cdf code for debug checking @@ -820,4 +865,4 @@ n_options(d::DDM) = 2 # r = (1/sqrt(2)) * exp(a - b^2/2) * (0.5641882/(b^3) - 1/(b * sqrt(π))) # end # return r -# end \ No newline at end of file +# end From f7f29ad12708970afe4da96d9cd13ecdb98df506 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 17:11:46 +0200 Subject: [PATCH 37/43] merged Ratcliff docs --- docs/src/DDM.md | 23 ++++++- docs/src/Ratcliff_DDM.md | 141 --------------------------------------- 2 files changed, 21 insertions(+), 143 deletions(-) delete mode 100644 docs/src/Ratcliff_DDM.md diff --git a/docs/src/DDM.md b/docs/src/DDM.md index e19491fb..3ce4a44c 100644 --- a/docs/src/DDM.md +++ b/docs/src/DDM.md @@ -1,9 +1,13 @@ # Diffusion Decision Model -The Diffusion Decision Model (DDM; Ratcliff et al., 2016) is a model of speeded decision-making in two-choice tasks. The DDM assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). Like other Sequential Sampling Models, the DDM comprises psychologically interpretable parameters that collectively form a generative model for reaction time distributions of both responses. +The Diffusion Decision Model (DDM; Ratcliff et al., 2016) is a model of speeded decision-making in two-choice tasks. The DDM assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). Like other Sequential Sampling Models, the DDM comprises psychologically interpretable parameters that collectively form a generative model for reaction time distributions of both responses. The drift rate (ν) determines the rate at which the accumulation process approaches a decision boundary, representing the relative evidence for or against a specific response. The distance between the two decision boundaries (referred to as the evidence threshold, α) influences the amount of evidence required before executing a response. Non-decision-related components, including perceptual encoding, movement initiation, and execution, are accounted for in the DDM and reflected in the τ parameter. Lastly, the model incorporates a bias in the evidence accumulation process through the parameter z, affecting the starting point of the drift process in relation to the two boundaries. The z parameter in DDM is relative to a (i.e. it ranges from 0 to 1). +However, in the Ratcliff Diffusion Decision Model, we also include across-trial variability parameters. These parameters were developed to explain specific discrepancies between the DDM and experimental data (Anderson, 1960; Laming, 1968; Blurton et al., 2017). The data exhibited a difference in mean RT between correct and error responses that could not be captured by the DDM. As a result, two parameters for across-trial variability were introduced to explain this difference: across-trial variability in the starting point (sz) to explain fast errors (Laming, 1968), and across-trial variability in drift rate (η) to explain slow errors (Ratcliff, 1978; Ratcliff and Rouder, 1998). Additionally, the DDM also showed a sharper rise in the leading edge of the response time distribution than observed in the data. To capture this leading edge effect, across-trial variability in non-decision time (st) was introduced. + +Previous work has validated predictions of these across-trial variability parameters (Wagenmakers et al., 2009). When compared to the DDM, the Ratcliff DDM improves the fit to the data. Researchers now often assume that the core parameters of sequential sampling models, such as drift rates, non-decision times, and starting points vary between trials. + One last parameter is the within-trial variability in drift rate (σ), or the diffusion coefficient. The diffusion coefficient is the standard deviation of the evidence accumulation process within one trial. It is a scaling parameter and by convention it is kept fixed. Following Navarro & Fuss, (2009), we use the σ = 1 version. # Example @@ -33,8 +37,11 @@ In the code below, we will define parameters for the DDM and create a model obje The average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 +Across-trial-variability of drift rate. Standard deviation of a normal distribution with mean v describing the distribution of actual drift rates from specific trials. Values different from 0 can predict slow errors. Typical range: 0 < η < 2. Default is 0. + ```@example DDM ν=1.0 +η = 0.16 ``` ### Boundary Separation @@ -49,16 +56,22 @@ The amount of information that is considered for a decision. Large values indica The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 +Across-trial-variability of non-decisional components. Range of a uniform distribution with mean τ + st/2 describing the distribution of actual τ values across trials. Accounts for response times below t0. Reduces skew of predicted RT distributions. Typical range: 0 < τ < 0.2. Default is 0. + ```@example DDM τ = 0.30 +st = 0.10 ``` ### Starting Point An indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). +Across-trial-variability of starting point. Range of a uniform distribution with mean z describing the distribution of actual starting points from specific trials. Values different from 0 can predict fast errors. Typical range: 0 < sz < 0.5. Default is 0. + ```@example DDM z = 0.50 +sz = 0.05 ``` ### DDM Constructor @@ -66,7 +79,7 @@ z = 0.50 Now that values have been assigned to the parameters, we will pass them to `DDM` to generate the model object. ```@example DDM -dist = DDM(ν, α, τ, z) +dist = DDM(ν, α, τ, z, η, sz, st, σ) ``` ## Simulate Model @@ -100,6 +113,10 @@ plot!(dist; t_range=range(.301, 1, length=100)) # References +Blurton, S. P., Kesselmeier, M., & Gondan, M. (2017). The first-passage time distribution for the diffusion model with variable drift. Journal of Mathematical Psychology, 76, 7–12. https://doi.org/10.1016/j.jmp.2016.11.003 + +Laming, D. R. J. (1968). Information theory of choice-reaction times. Academic Press. + Navarro, D., & Fuss, I. (2009). Fast and accurate calculations for first-passage times in Wiener diffusion models. https://doi.org/10.1016/J.JMP.2009.02.003 Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. https://doi.org/10.1162/neco.2008.12-06-420 @@ -109,3 +126,5 @@ Ratcliff, R., & Rouder, J. N. (1998). Modeling Response Times for Two-Choice Dec Ratcliff, R., & Smith, P. L. (2004). A comparison of sequential sampling models for two-choice reaction time. Psychological Review, 111 2, 333–367. https://doi.org/10.1037/0033-295X.111.2.333 Ratcliff, R., Smith, P. L., Brown, S. D., & McKoon, G. (2016). Diffusion Decision Model: Current Issues and History. Trends in Cognitive Sciences, 20(4), 260–281. https://doi.org/10.1016/j.tics.2016.01.007 + +Wagenmakers, E.-J. (2009). Methodological and empirical developments for the Ratcliff diffusion model of response times and accuracy. European Journal of Cognitive Psychology, 21(5), 641-671. diff --git a/docs/src/Ratcliff_DDM.md b/docs/src/Ratcliff_DDM.md deleted file mode 100644 index 2465cbf0..00000000 --- a/docs/src/Ratcliff_DDM.md +++ /dev/null @@ -1,141 +0,0 @@ -# Ratcliff Diffusion Model - -The Ratcliff Diffusion Model (Ratcliff DDM; Ratcliff et al., 2016) is similar to the DDM. Like the DDM, the model assumes that evidence accumulates over time, starting from a certain position, until it crosses one of two boundaries and triggers the corresponding response (Ratcliff & McKoon, 2008; Ratcliff & Rouder, 1998; Ratcliff & Smith, 2004). The drift rate (ν) determines the rate at which the accumulation process approaches a decision boundary, representing the relative evidence for or against a specific response. The distance between the two decision boundaries (referred to as the evidence threshold, α) influences the amount of evidence required before executing a response. Non-decision-related components, including perceptual encoding, movement initiation, and execution, are accounted for in the DDM and reflected in the τ parameter. Lastly, the model incorporates a bias in the evidence accumulation process through the parameter z, affecting the starting point of the drift process in relation to the two boundaries. The z parameter in DDM is relative to a (i.e. it ranges from 0 to 1). - -However, the model differs in the inclusion of across-trial variability parameters. These parameters were developed to explain specific discrepancies between the DDM and experimental data (Anderson, 1960; Laming, 1968; Blurton et al., 2017). The data exhibited a difference in mean RT between correct and error responses that could not be captured by the DDM. As a result, two parameters for across-trial variability were introduced to explain this difference: across-trial variability in the starting point to explain fast errors (Laming, 1968), and across-trial variability in drift rate to explain slow errors (Ratcliff, 1978; Ratcliff and Rouder, 1998). Additionally, the DDM also showed a sharper rise in the leading edge of the response time distribution than observed in the data. To capture this leading edge effect, across-trial variability in non-decision time was introduced. - -Previous work has validated predictions of these across-trial variability parameters (Wagenmakers et al., 2009). When compared to the DDM, the Ratcliff DDM improves the fit to the data. Researchers now often assume that the core parameters of sequential sampling models, such as drift rates, non-decision times, and starting points vary between trials. - -One last parameter is the within-trial variability in drift rate (σ), or the diffusion coefficient. The diffusion coefficient is the standard deviation of the evidence accumulation process within one trial. It is a scaling parameter and by convention it is kept fixed. Following Navarro & Fuss, (2009), we use the σ = 1 version. - -# Example -In this example, we will demonstrate how to use the DDM in a generic two alternative forced choice task. - -## Load Packages -The first step is to load the required packages. - -```@example RatcliffDDM -using SequentialSamplingModels -using Plots -using Random - -Random.seed!(8741) -``` - -## Create Model Object -In the code below, we will define parameters for the DDM and create a model object to store the parameter values. - -### Drift Rate - -The average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - -Across-trial-variability of drift rate. Standard deviation of a normal distribution with mean v describing the distribution of actual drift rates from specific trials. Values different from 0 can predict slow errors. Typical range: 0 < η < 2. Default is 0. - -```@example RatcliffDDM -ν=1.0 -η = 0.16 -``` - -### Boundary Separation - -The amount of information that is considered for a decision. Large values indicates response caution. Typical range: 0.5 < α < 2 - - -```@example RatcliffDDM -α = 0.80 -``` - -### Non-Decision Time - -The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 - -Across-trial-variability of non-decisional components. Range of a uniform distribution with mean τ + st/2 describing the distribution of actual τ values across trials. Accounts for response times below t0. Reduces skew of predicted RT distributions. Typical range: 0 < τ < 0.2. Default is 0. - -```@example RatcliffDDM -τ = 0.30 -st = 0.10 -``` - -### Starting Point - -An indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). - -Across-trial-variability of starting point. Range of a uniform distribution with mean z describing the distribution of actual starting points from specific trials. Values different from 0 can predict fast errors. Typical range: 0 < sz < 0.5. Default is 0. - -```@example RatcliffDDM -z = 0.25 -sz = 0.05 -``` - -### Ratcliff Diffusion Model Constructor - -Now that values have been assigned to the parameters, we will pass them to `RatcliffDDM` to generate the model object. - -```@example RatcliffDDM -dist = RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -``` - -## Simulate Model - -Now that the model is defined, we will generate $10,000$ choices and reaction times using `rand`. - - ```@example RatcliffDDM - choices,rts = rand(dist, 10_000) -``` - -## Compute PDF -The PDF for each observation can be computed as follows: - - ```@example RatcliffDDM -pdf.(dist, choices, rts) -``` -## Compute Log PDF -Similarly, the log PDF for each observation can be computed as follows: - - ```@example RatcliffDDM -logpdf.(dist, choices, rts) -``` - -## Plot Simulation -The code below overlays the PDF on reaction time histograms for each option. - - ```@example RatcliffDDM -# rts for option 1 -rts1 = rts[choices .== 1] -# rts for option 2 -rts2 = rts[choices .== 2] -# probability of choosing 1 -p1 = length(rts1) / length(rts) -t_range = range(.30, 2, length=100) -# pdf for choice 1 -pdf1 = pdf.(dist, (1,), t_range) -# pdf for choice 2 -pdf2 = pdf.(dist, (2,), t_range) -# histogram of retrieval times -hist = histogram(layout=(2,1), leg=false, grid=false, - xlabel="Reaction Time", ylabel="Density", xlims = (0,1.5)) -histogram!(rts1, subplot=1, color=:grey, bins = 200, norm=true, title="Choice 1") -plot!(t_range, pdf1, subplot=1, color=:darkorange, linewidth=2) -histogram!(rts2, subplot=2, color=:grey, bins = 150, norm=true, title="Choice 2") -plot!(t_range, pdf2, subplot=2, color=:darkorange, linewidth=2) -# weight histogram according to choice probability -hist[1][1][:y] *= p1 -hist[2][1][:y] *= (1 - p1) -hist -``` - -# References - -Navarro, D., & Fuss, I. (2009). Fast and accurate calculations for first-passage times in Wiener diffusion models. https://doi.org/10.1016/J.JMP.2009.02.003 - -Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. https://doi.org/10.1162/neco.2008.12-06-420 - -Ratcliff, R., & Rouder, J. N. (1998). Modeling Response Times for Two-Choice Decisions. Psychological Science, 9(5), 347–356. https://doi.org/10.1111/1467-9280.00067 - -Ratcliff, R., & Smith, P. L. (2004). A comparison of sequential sampling models for two-choice reaction time. Psychological Review, 111 2, 333–367. https://doi.org/10.1037/0033-295X.111.2.333 - -Ratcliff, R., Smith, P. L., Brown, S. D., & McKoon, G. (2016). Diffusion Decision Model: Current Issues and History. Trends in Cognitive Sciences, 20(4), 260–281. https://doi.org/10.1016/j.tics.2016.01.007 - -Wagenmakers, E.-J. (2009). Methodological and empirical developments for the Ratcliff diffusion model of response times and accuracy. European Journal of Cognitive Psychology, 21(5), 641-671. - - From a025829be0e15a59614b0d07354da3d2ff8956ef Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 17:21:32 +0200 Subject: [PATCH 38/43] make simulate use across trial parameters --- src/DDM.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 53488636..1ceacd57 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -715,13 +715,17 @@ represent samples of evidence per time step and columns represent different accu function simulate(model::DDM; Δt=.001) (;ν, α, τ, z, η, sz, st, σ) = model - x = α * z + start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() + drift = rand(Distributions.Normal(ν, η)) + + x = α * start_point t = 0.0 + evidence = [x] time_steps = [t] while (x < α) && (x > 0) t += Δt - x += ν * Δt + rand(Normal(0.0, 1.0)) * √(Δt) + x += drift * Δt + rand(Normal(0.0, 1.0)) * √(Δt) push!(evidence, x) push!(time_steps, t) end From 538dbad710c2de8e354670276f1d2770cdfaf419 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 17 Jul 2023 19:40:59 +0200 Subject: [PATCH 39/43] working on test changes --- test/ddm_tests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ddm_tests.jl b/test/ddm_tests.jl index aae27db0..889c330f 100644 --- a/test/ddm_tests.jl +++ b/test/ddm_tests.jl @@ -6,7 +6,7 @@ Random.seed!(654) include("KDE.jl") - dist = DDM(ν=1.0, α = .8, z = .5, τ = .3) + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^6) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -31,7 +31,7 @@ include("KDE.jl") Random.seed!(750) - dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30) + dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^6) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -56,7 +56,7 @@ using Random Random.seed!(7540) - dist = DDM(ν=1.0, α = .8, z = .5, τ = .3) + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^5) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) @@ -81,7 +81,7 @@ using Random Random.seed!(2200) - dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30) + dist = DDM(ν=2.0, α = 1.5, z = .5, τ = .30, η = 0.00, sz = 0.00, st = 0.00, σ = 1.0) choice,rt = rand(dist, 10^5) rt1 = rt[choice .== 1] p1 = mean(choice .== 1) From c5303536c807608953af30dab7c7d7c7ea8d47e3 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 25 Dec 2023 17:18:34 -0500 Subject: [PATCH 40/43] Add random number generator to DDM simulation --- src/DDM.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 1ceacd57..6b31dacf 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -712,11 +712,11 @@ represent samples of evidence per time step and columns represent different accu - `Δt=.001`: size of time step of decision process in seconds """ -function simulate(model::DDM; Δt=.001) +function simulate(rng::AbstractRNG, model::DDM; Δt=.001) (;ν, α, τ, z, η, sz, st, σ) = model start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() - drift = rand(Distributions.Normal(ν, η)) + drift = rand(rng, Distributions.Normal(ν, η)) x = α * start_point t = 0.0 @@ -725,7 +725,7 @@ function simulate(model::DDM; Δt=.001) time_steps = [t] while (x < α) && (x > 0) t += Δt - x += drift * Δt + rand(Normal(0.0, 1.0)) * √(Δt) + x += drift * Δt + rand(rng, Distributions.Normal(0.0, 1.0)) * √(Δt) push!(evidence, x) push!(time_steps, t) end From bd36ea81fd773c252bcaf2ea3dbb80e9f1b72061 Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Tue, 26 Dec 2023 21:24:40 -0500 Subject: [PATCH 41/43] Add AbstractDDM and remove/port RatcliffDDM --- src/DDM.jl | 408 +++++++++-------- src/RatcliffDDM.jl | 754 -------------------------------- src/SequentialSamplingModels.jl | 5 +- src/type_system.jl | 7 + test/ddm_tests.jl | 312 ++++++++++--- 5 files changed, 471 insertions(+), 1015 deletions(-) delete mode 100644 src/RatcliffDDM.jl diff --git a/src/DDM.jl b/src/DDM.jl index 6b31dacf..3f0ee8db 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -1,5 +1,5 @@ """ - DDM{T<:Real} <: SSM2D + DDM{T<:Real} <: AbstractDDM Model object for the Ratcliff (Full) Diffusion Decision Model. @@ -42,7 +42,7 @@ loglike = logpdf.(dist, choice, rt) Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ -mutable struct DDM{T<:Real} <: SSM2D +mutable struct DDM{T<:Real} <: AbstractDDM ν::T α::T τ::T @@ -344,19 +344,6 @@ end logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -# function logpdf(d::DDM, data::T) where {T<:NamedTuple} -# return sum(logpdf.(d, data...)) -# end - -# function logpdf(dist::DDM, data::Array{<:Tuple,1}) -# LL = 0.0 -# for d in data -# LL += logpdf(dist, d...) -# end -# return LL -# end - -# logpdf(d::DDM, data::Tuple) = logpdf(d, data...) """ cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) @@ -370,7 +357,7 @@ Compute the Cumulative Distribution Function (CDF) for the Ratcliff Diffusion mo - `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. # Returns -- `y`: an array representing the CDF of the Ratcliff Diffusion model. +- `y`: an array representing the CDF of the Diffusion Decision model. # Reference Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution and probability density functions in the diffusion model, Behavior Research Methods, Instruments, & Computers, 36 (4), 702-716. @@ -378,36 +365,36 @@ Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution # See also - Converted from cdfdif.c C script by Joachim Vandekerckhove: https://ppw.kuleuven.be/okp/software/dmat/ """ -function cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) +# function cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) - (ν, α, τ, z, η, sz, st, σ) = params(d) +# (ν, α, τ, z, η, sz, st, σ) = params(d) - size = length(rt) - y = zeros(Float64, size) - epsi = 1e-10 +# size = length(rt) +# y = zeros(Float64, size) +# epsi = 1e-10 - #transform parameters - α = α/10. - τ = τ - η = η/10. + epsi - z = z*(α/10.) - sz = sz*(α/10.) + epsi - st = st + epsi - ν = ν/10. +# #transform parameters +# α = α/10. +# τ = τ +# η = η/10. + epsi +# z = z*(α/10.) +# sz = sz*(α/10.) + epsi +# st = st + epsi +# ν = ν/10. - p_boundary = 0.0 +# p_boundary = 0.0 - for i in 1:size - y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) - y[i] = (1 - p_boundary) + rt[i]*y[i] - end +# for i in 1:size +# y[i] = _cdf(d,rt[i],choice[i],p_boundary; ϵ) +# y[i] = (1 - p_boundary) + rt[i]*y[i] +# end - return y -end +# return y +# end """ _cdf(d::DDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} -A helper function to compute the Cumulative Distribution Function (CDF) for the Full Diffusion model. +A helper function to compute the Cumulative Distribution Function (CDF) for the Diffusion Decision model. # Arguments - `d`: an instance of DDM Constructor @@ -420,166 +407,167 @@ A helper function to compute the Cumulative Distribution Function (CDF) for the - `Fnew`: the computed CDF for the given parameters. """ -function _cdf(d::DDM, choice, rt, prob; ϵ::Real = 1e-7) +# function _cdf(d::DDM, choice, rt, prob; ϵ::Real = 1e-7) - (ν, α, τ, z, η, sz, st, σ) = params(d) - #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) - #note we need to make sure this is consistent in the all the relative bound models - if choice == 2 #lower - choice = 0 - elseif choice == 1 #upper - choice = 1 - end - - # Initializing variables - a2 = α*α - Z_U = (1-choice)*z+choice*(α-z)+sz/2 - Z_L = (1-choice)*z+choice*(α-z)-sz/2 - lower_t = τ-st/2 - upper_t = 0.0 - Δ = 1e-29 - min_rt=0.001 - v_max = 5000 # maximum number of terms in a partial sum approximating infinite series - - Fnew = 0.0 - sum_z=0.0 - sum_ν=0.0 - p1 = 0.0 - p0 = 0.0 - sum_hist = zeros(3) - denom = 0.0 - sifa = 0.0 - upp = 0.0 - low = 0.0 - fact = 0.0 - exdif = 0.0 - su = 0.0 - sl = 0.0 - zzz = 0.0 - ser = 0.0 - nr_ν = 6 - nr_z = 6 - - # Defining Gauss-Hermite abscissae and weights for numerical integration - gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] - w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] - gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] - w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] - - # Adjusting Gauss-Hermite abscissae and weights - for i=1:nr_ν - gk[i] = 1.41421356237309505*gk[i]*η+ν - w_gh[i] = w_gh[i]/1.772453850905515882 - end - for i=1:nr_z - gz[i] = (.5*sz*gz[i])+z - end +# (ν, α, τ, z, η, sz, st, σ) = params(d) +# #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) +# #note we need to make sure this is consistent in the all the relative bound models +# if choice == 2 #lower +# choice = 0 +# elseif choice == 1 #upper +# choice = 1 +# end - # numerical integration - for i=1:nr_z - sum_ν=0.0 - # numerical integration - for m=1:nr_ν - if abs(gk[m])>ϵ - sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] - else - sum_ν+=gz[i]/α*w_gh[m] - end - end - sum_z+=sum_ν*w_g[i]/2 - end - prob = sum_z - - if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? - upper_t = min(rt, τ+st/2) - p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t - p0 = (1-prob)*(upper_t-lower_t)/st - if rt > τ+st/2 # is t larger than upper boundary Ter distribution? - sum_hist = zeros(3) - for v in 1:v_max # infinite series - sum_hist = circshift(sum_hist, 1) - sum_ν = 0 - sifa = π*v/α - for m in 1:nr_ν # numerical integration with respect to xi - denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) - upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - - low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) - exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) - sum_ν += fact*exdif - end - sum_hist[3] = sum_hist[2] + v*sum_ν - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end +# # Initializing variables +# a2 = α*α +# Z_U = (1-choice)*z+choice*(α-z)+sz/2 +# Z_L = (1-choice)*z+choice*(α-z)-sz/2 +# lower_t = τ-st/2 +# upper_t = 0.0 +# Δ = 1e-29 +# min_rt=0.001 +# v_max = 5000 # maximum number of terms in a partial sum approximating infinite series + +# Fnew = 0.0 +# sum_z=0.0 +# sum_ν=0.0 +# p1 = 0.0 +# p0 = 0.0 +# sum_hist = zeros(3) +# denom = 0.0 +# sifa = 0.0 +# upp = 0.0 +# low = 0.0 +# fact = 0.0 +# exdif = 0.0 +# su = 0.0 +# sl = 0.0 +# zzz = 0.0 +# ser = 0.0 +# nr_ν = 6 +# nr_z = 6 + +# # Defining Gauss-Hermite abscissae and weights for numerical integration +# gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] +# w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] +# gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] +# w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] + +# # Adjusting Gauss-Hermite abscissae and weights +# for i=1:nr_ν +# gk[i] = 1.41421356237309505*gk[i]*η+ν +# w_gh[i] = w_gh[i]/1.772453850905515882 +# end +# for i=1:nr_z +# gz[i] = (.5*sz*gz[i])+z +# end - Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) - # cumulative distribution function for t and x - elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? - sum_ν = 0 - for m in 1:nr_ν - if abs(gk[m]) > ϵ - sum_z = 0 - for i in 1:nr_z - zzz = (α - gz[i])*choice + gz[i]*(1 - choice) - ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ - (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + - (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ - sinh((1 - 2*choice)*gk[m]*α/.01) - sum_hist = zeros(3) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) - sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) - end - else - sum_hist = zeros(3) - su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) - sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (π*v)*(π*v)/(a2*100) - sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* - exp(-.5*denom*(rt - lower_t)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) - end - sum_ν += sum_z*w_gh[m] - end - Fnew = (p0*(1 - choice) + p1*choice) - sum_ν - end - elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? - Fnew = 0 - end +# # numerical integration +# for i=1:nr_z +# sum_ν=0.0 +# # numerical integration +# for m=1:nr_ν +# if abs(gk[m])>ϵ +# sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] +# else +# sum_ν+=gz[i]/α*w_gh[m] +# end +# end +# sum_z+=sum_ν*w_g[i]/2 +# end +# prob = sum_z + +# if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? +# upper_t = min(rt, τ+st/2) +# p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t +# p0 = (1-prob)*(upper_t-lower_t)/st +# if rt > τ+st/2 # is t larger than upper boundary Ter distribution? +# sum_hist = zeros(3) +# for v in 1:v_max # infinite series +# sum_hist = circshift(sum_hist, 1) +# sum_ν = 0 +# sifa = π*v/α +# for m in 1:nr_ν # numerical integration with respect to xi +# denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) +# upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) +# low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) +# fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - +# low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) +# exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) +# sum_ν += fact*exdif +# end +# sum_hist[3] = sum_hist[2] + v*sum_ν +# if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 +# break +# end +# end + +# Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) +# # cumulative distribution function for t and x +# elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? +# sum_ν = 0 +# for m in 1:nr_ν +# if abs(gk[m]) > ϵ +# sum_z = 0 +# for i in 1:nr_z +# zzz = (α - gz[i])*choice + gz[i]*(1 - choice) +# ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ +# (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + +# (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ +# sinh((1 - 2*choice)*gk[m]*α/.01) +# sum_hist = zeros(3) +# for v in 1:v_max +# sum_hist = circshift(sum_hist, 1) +# sifa = π*v/α +# denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) +# sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) +# if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 +# break +# end +# end +# sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) +# end +# else +# sum_hist = zeros(3) +# su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) +# sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) +# for v in 1:v_max +# sum_hist = circshift(sum_hist, 1) +# sifa = π*v/α +# denom = (π*v)*(π*v)/(a2*100) +# sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* +# exp(-.5*denom*(rt - lower_t)) +# if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 +# break +# end +# end +# sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) +# end +# sum_ν += sum_z*w_gh[m] +# end +# Fnew = (p0*(1 - choice) + p1*choice) - sum_ν +# end +# elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? +# Fnew = 0 +# end - Fnew = Fnew > Δ ? Fnew : 0 +# Fnew = Fnew > Δ ? Fnew : 0 - return Fnew +# return Fnew -end +# end """ rand(dist::DDM) -Generate a random choice and rt for the Ratcliff Diffusion Model +Generate a random choice and rt for the Diffusion Decision Model # Arguments -- `dist`: model object for Ratcliff Diffusion Model. +- `dist`: model object for Diffusion Decision Model. method simulating the diffusion process: _rand_rejection uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process +_rand_stochastic uses the stochastic Euler method to simulate the stochastic differential equation # References @@ -592,6 +580,7 @@ _rand_rejection uses Tuerlinckx et al., 2001 rejection-based method for the gene """ function rand(rng::AbstractRNG, d::DDM) return _rand_rejection(rng, d) + # return _rand_stochastic(rng, d) end function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) @@ -613,9 +602,9 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) Δ = ϵ for n in 1:N - r1 = randn() + r1 = randn(rng) μ = ν + r1 * η - bb = z - sz / 2 + sz * rand() + bb = z - sz / 2 + sz * rand(rng) zz = bb * α finish = 0 totaltime = 0 @@ -632,13 +621,13 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) # formula p447 in 2001 paper prob = exp(radius * μ / D) prob = prob / (1 + prob) - dir_ = 2 * (rand() < prob) - 1 + dir_ = 2 * (rand(rng) < prob) - 1 l = -1 s2 = 0 s1 = 0 while s2 > l - s2 = rand() - s1 = rand() + s2 = rand(rng) + s1 = rand(rng) tnew = 0 told = 0 uu = 0 @@ -655,7 +644,7 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) # is the negative of t* in (14) in BRMIC,2001 totaltime += t dir_ = startpos + dir_ * radius - ndt = τ - st / 2 + st * rand() + ndt = τ - st / 2 + st * rand(rng) if (dir_ + Δ) > Aupper rt[n] = ndt + totaltime choice[n] = 1 @@ -673,10 +662,49 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) return (choice=choice,rt=rt) end +function _rand_stochastic(rng::AbstractRNG, d::DDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) + (ν, α, τ, z, η, sz, st, σ) = params(d) + + if η == 0 + η = 1e-16 + end + + # Initialize output vectors + choice = fill(0, N) + rt = fill(0.0, N) + + for n in 1:N + random_walk = Array{Float64}(undef, nsteps) + start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand(rng) + ndt = (τ - st/2) + ((τ + st/2) - (τ - st/2)) * rand(rng) + drift = rand(rng, Distributions.Normal(ν, η)) + random_walk[1] = start_point * α + for s in 2:nsteps + random_walk[s] = random_walk[s-1] + rand(Distributions.Normal(drift * step_length, σ * sqrt(step_length))) + if random_walk[s] >= α + random_walk[s:end] .= α + rts[n] = s * step_length + ndt + choice[n] = 1 + break + elseif random_walk[s] <= 0 + random_walk[s:end] .= 0 + rts[n] = s * step_length + ndt + choice[n] = 2 + break + elseif s == nsteps + rts[n] = NaN + choice[n] = NaN + break + end + end + end + return (choice=choice,rt=rts) +end + """ rand(dist::DDM, n_sim::Int) -Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. +Generate `n_sim` random choice-rt pairs for the Diffusion Decision Model. # Arguments - `dist`: model object for the Ratcliff DDM. @@ -827,7 +855,7 @@ end # end # return 2*F # end -# # Number of terms required for large time representation +# Number of terms required for large time representation # function _K_large(d::DDM{T}, t::Real; ϵ::Real = 1.0e-12) where {T<:Real} # (ν, α, τ, z) = params(d) # x = t-τ diff --git a/src/RatcliffDDM.jl b/src/RatcliffDDM.jl deleted file mode 100644 index 9a572435..00000000 --- a/src/RatcliffDDM.jl +++ /dev/null @@ -1,754 +0,0 @@ -""" - RatcliffDDM{T<:Real} <: SSM2D - - Model object for the Ratcliff Diffusion Model. - -# Parameters - - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 - - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 - - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). - - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. - - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. - - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. - - `σ`: diffusion noise constant. Default is 1. - -# Constructors - - RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - - RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0 - ) - -# Example -# Constructors - - RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - - RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0 - ) - -# Example - -````julia -using SequentialSamplingModels -dist = RatcliffDDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) -choice,rt = rand(dist, 10) -like = pdf.(dist, choice, rt) -loglike = logpdf.(dist, choice, rt) -```` -````julia -using SequentialSamplingModels -dist = RatcliffDDM(ν = 1.0,α = 0.80,τ = 0.30,z = 0.25,η = 0.16,sz = 0.05,st = .10,σ = 1) -choice,rt = rand(dist, 10) -like = pdf.(dist, choice, rt) -loglike = logpdf.(dist, choice, rt) -```` - -# References -# References - -Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. -Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 -""" -mutable struct RatcliffDDM{T<:Real} <: SSM2D - ν::T - α::T - τ::T - z::T - η::T - sz::T - st::T - σ::T -end - -function RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - return RatcliffDDM(promote(ν, α, τ, z, η, sz, st, σ)...) -end - -function params(d::RatcliffDDM) - (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -end - -function RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0) - return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -end -Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. -Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 -""" -mutable struct RatcliffDDM{T<:Real} <: SSM2D - ν::T - α::T - τ::T - z::T - η::T - sz::T - st::T - σ::T -end - -function RatcliffDDM(ν, α, τ, z, η, sz, st, σ) - return RatcliffDDM(promote(ν, α, τ, z, η, sz, st, σ)...) -end - -function params(d::RatcliffDDM) - (d.ν, d.α, d.τ, d.z,d.η, d.sz, d.st, d.σ) -end - -function RatcliffDDM(; ν = 1.00, - α = 0.80, - τ = 0.30, - z = 0.25, - η = 0.16, - sz = 0.05, - st = 0.10, - σ = 1.0) - return RatcliffDDM(ν, α, τ, z, η, sz, st, σ) -end - -# probability density function over the lower boundary -# probability density function over the lower boundary - -# uses analytic integration of the likelihood function for variability in drift-rate -# uses analytic integration of the likelihood function for variability in drift-rate -# function pdf_sv(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) -# if choice == 1 -# (ν, α, τ, z, η, sz, st, σ) = params(d) -# (ν, α, τ, z, η, sz, st, σ) = params(d) -# ν = -ν -# z = 1 - z -# return pdf_sv(RatcliffDDM(ν, α, τ, z, η, sz, st, σ), rt; ϵ) -# end -# return pdf_sv(d, rt; ϵ) -# end - -function _pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) - end - # if isless(ν,0) - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 - # end - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 -end - -function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z, η, sz, st, σ) = params(d) - return _pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) - end - return _pdf(d, rt; ϵ) -end - -#use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -function _pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if τ ≥ rt - return T(NaN) - end - - if st < 1.0e-3 - st = 0 - end - if sz < 1.0e-3 - sz = 0 - end - - if sz==0 - if st==0 #sv=0,sz=0,st=0 - return _pdf_sv(d, rt; ϵ) - else #sv=0,sz=0,st=$ - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) - end - else #sz=$ - if st==0 #sv=0,sz=$,st=0 - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) - else #sv=0,sz=$,st=$ - return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) - end - end -end - -##################################################### -# Numerical Integration with Simpson's Method # -# https://en.wikipedia.org/wiki/Simpson%27s_rule # -##################################################### - -# Simpson's Method one dimentional case -function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) -# z = 1 - z -# return pdf_sv(RatcliffDDM(ν, α, τ, z, η, sz, st, σ), rt; ϵ) -# end -# return pdf_sv(d, rt; ϵ) -# end - -function _pdf_sv(d::RatcliffDDM{T}, rt::Real; ϵ::Real = 1.0e-12) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) - end - # if isless(ν,0) - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) + ν*α*z + (ν^2)*t*0.5 - # end - # return pdf(SequentialSamplingModels.DDM(ν, α, τ, z), t; ϵ) + ( ( (α*(1-z)*η)^2 + 2*ν*α*(1-z) - (ν^2)*t ) / (2*(η^2)*t+2) ) - log(sqrt((η^2)*t+1)) - ν*α*(1-z) + (ν^2)*t*0.5 - return _pdf(SequentialSamplingModels.DDM(ν, α, τ, z), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 -end - -function pdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) - if choice == 1 - (ν, α, τ, z, η, sz, st, σ) = params(d) - return _pdf(RatcliffDDM(-ν, α, τ, 1-z, η, sz, st, σ), rt; ϵ) - end - return _pdf(d, rt; ϵ) -end - -#use numerical integration for variability in non-decision time and bias (Ratcliff and Tuerlinckx, 2002) -function _pdf(d::RatcliffDDM{T}, rt; ϵ::Real = 1.0e-12, n_st::Int=2, n_sz::Int=2) where {T<:Real} - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if τ ≥ rt - return T(NaN) - end - - if st < 1.0e-3 - st = 0 - end - if sz < 1.0e-3 - sz = 0 - end - - if sz==0 - if st==0 #sv=0,sz=0,st=0 - return _pdf_sv(d, rt; ϵ) - else #sv=0,sz=0,st=$ - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z, z, 0, τ-st/2., τ+st/2., n_st) - end - else #sz=$ - if st==0 #sv=0,sz=$,st=0 - return _simpson_1D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ, τ , 0) - else #sv=0,sz=$,st=$ - return _simpson_2D(rt, ν, η, α, z, τ, ϵ, z-sz/2., z+sz/2., n_sz, τ-st/2., τ+st/2., n_st) - end - end -end - -##################################################### -# Numerical Integration with Simpson's Method # -# https://en.wikipedia.org/wiki/Simpson%27s_rule # -##################################################### - -# Simpson's Method one dimentional case -function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - - # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - - n = max(n_st, n_sz) - n = max(n_st, n_sz) - - if n_st == 0 #integration over z - hz = (ub_z-lb_z)/n - ht = 0 - lb_t = τ - ub_t = τ - else #integration over t - hz = 0 - ht = (ub_t-lb_t)/n - lb_z = z - ub_z = z - end - if n_st == 0 #integration over z - hz = (ub_z-lb_z)/n - ht = 0 - lb_t = τ - ub_t = τ - else #integration over t - hz = 0 - ht = (ub_t-lb_t)/n - lb_z = z - ub_z = z - end - - S = _pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) - S = _pdf_sv(RatcliffDDM(ν, α, lb_t, lb_z, η, 0, 0, 1), x; ϵ) - - y = 0 - z_tag = 0 - t_tag = 0 - - for i in 1:n - z_tag = lb_z + hz * i - t_tag = lb_t + ht * i - - y = _pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) - - if isodd(i) - S += 4 * y - else - S += 2 * y - end - y = 0 - z_tag = 0 - t_tag = 0 - - for i in 1:n - z_tag = lb_z + hz * i - t_tag = lb_t + ht * i - - y = _pdf_sv(RatcliffDDM(ν, α, t_tag, z_tag, η, 0, 0, 1), x; ϵ) - - if isodd(i) - S += 4 * y - else - S += 2 * y - end - - end - end - - S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y - S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st - S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y - S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st - - return (ht + hz) * S / 3 - return (ht + hz) * S / 3 - -end -end - -# Simpson's Method two dimentional case -function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) - # @assert (n_sz & 1) == 0 && (n_st & 1) == 0 # n_st and n_sz have to be even - # @assert (ub_t-lb_t)*(ub_z-lb_z)>0 && (n_sz*n_st)>0 # 2D-integration only - - ht = (ub_t-lb_t)/n_st - S = _simpson_1D(x, ν, η, α, z, τ, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - - t_tag = 0 - y = 0 - for i_t in 1:n_st - t_tag = lb_t + ht * i_t - y = _simpson_1D(x, ν, η, α, z, t_tag, ϵ, lb_z, ub_z, n_sz, 0, 0, 0) - - if isodd(i_t) - S += 4 * y - else - S += 2 * y - end - end - - S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y - S = S / (ub_t - lb_t) - - return ht * S / 3 - -end - -logpdf(d::RatcliffDDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) - -function logpdf(d::RatcliffDDM, data::T) where {T<:NamedTuple} - return sum(logpdf.(d, data...)) -end - -function logpdf(dist::RatcliffDDM, data::Array{<:Tuple,1}) - LL = 0.0 - for d in data - LL += logpdf(dist, d...) - end - return LL -end - -logpdf(d::RatcliffDDM, data::Tuple) = logpdf(d, data...) - -################################################################################ -# Calculate Cumulative Distribution Function # -# # -# Computes Cumulative Distribution Function for the Ratcliff Diffusion model # -# using 6 Gaussian quadrature for numerical integration # -# # -# References # -# Tuerlinckx, F. (2004). The efficient computation of the # -# cumulative distribution and probability density functions # -# in the diffusion model, Behavior Research Methods, # -# Instruments, & Computers, 36 (4), 702-716. # -# # -# Converted from cdfdif.c C script by Joachim Vandekerckhove # -# See also https://ppw.kuleuven.be/okp/software/dmat/ # -################################################################################ - -function cdf(d::RatcliffDDM, choice, rt, p_outlier; w_outlier::Real = 0.1, ϵ::Real = 1e-7) - - (ν, α, τ, z, η, sz, st, σ) = params(d) - - DT = x - τ #make into decision time - - sum = 0.0 - for i = -N:N÷2 - d = 2*i + z - sum += exp(-d*d / (2*DT)) * d - end - return sum / sqrt(2π*DT*DT*DT) -end - -function _g_minus_large_time(x::Real, d::RatcliffDDM, N::Int) - """ - calculate the densities g- for the first exit time for large time values - """ - DT = x - τ #make into decision time - - sum = 0.0 - for i = 1:N - d = i * π - sum += exp(-0.5 * d*d * DT) * sin(d*z) * i - end - return sum * π -end - -function _cdf(d::RatcliffDDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} - - (ν, α, τ, z, η, sz, st, σ) = params(d) - #Explcit recode of the choice from 2(lower) & 1(upper) to 0(lower) and 1(upper) - #note we need to make sure this is consistent in the all the relative bound models - if choice == 2 #lower - choice = 0 - elseif choice == 1 #upper - choice = 1 - end - - # Initializing variables - a2 = α*α - Z_U = (1-choice)*z+choice*(α-z)+sz/2 - Z_L = (1-choice)*z+choice*(α-z)-sz/2 - lower_t = τ-st/2 - upper_t = 0.0 - Δ = 1e-29 - min_rt=0.001 - v_max = 5000 # maximum number of terms in a partial sum approximating infinite series - - Fnew = 0.0 - sum_z=0.0 - sum_ν=0.0 - p1 = 0.0 - p0 = 0.0 - sum_hist = zeros(3) - denom = 0.0 - sifa = 0.0 - upp = 0.0 - low = 0.0 - fact = 0.0 - exdif = 0.0 - su = 0.0 - sl = 0.0 - zzz = 0.0 - ser = 0.0 - nr_ν = 6 - nr_z = 6 - - # Defining Gauss-Hermite abscissae and weights for numerical integration - gk = [-2.3506049736744922818,-1.3358490740136970132,-.43607741192761650950,.43607741192761650950,1.3358490740136970132,2.3506049736744922818] - w_gh = [.45300099055088421593e-2,.15706732032114842368,.72462959522439207571,.72462959522439207571,.15706732032114842368,.45300099055088421593e-2] - gz = [-.93246951420315193904,-.66120938646626381541,-.23861918608319693247,.23861918608319712676,.66120938646626459256,.93246951420315160597] - w_g = [.17132449237917049545,.36076157304813916138,.46791393457269092604,.46791393457269092604,.36076157304813843973,.17132449237917132812] - - # Adjusting Gauss-Hermite abscissae and weights - for i=1:nr_ν - gk[i] = 1.41421356237309505*gk[i]*η+ν - w_gh[i] = w_gh[i]/1.772453850905515882 - end - for i=1:nr_z - gz[i] = (.5*sz*gz[i])+z - end - - # numerical integration - for i=1:nr_z - sum_ν=0.0 - # numerical integration - for m=1:nr_ν - if abs(gk[m])>ϵ - sum_ν+=(exp(-200*gz[i]*gk[m])-1)/(exp(-200*α*gk[m])-1)*w_gh[m] - else - sum_ν+=gz[i]/α*w_gh[m] - end - end - sum_z+=sum_ν*w_g[i]/2 - end - prob = sum_z - - if (rt-τ+st/2 > min_rt) # is t larger than lower boundary τ distribution? - upper_t = min(rt, τ+st/2) - p1 = prob*(upper_t-lower_t)/st # integrate probability with respect to t - p0 = (1-prob)*(upper_t-lower_t)/st - if rt > τ+st/2 # is t larger than upper boundary Ter distribution? - sum_hist = zeros(3) - for v in 1:v_max # infinite series - sum_hist = circshift(sum_hist, 1) - sum_ν = 0 - sifa = π*v/α - for m in 1:nr_ν # numerical integration with respect to xi - denom = (100*gk[m]*gk[m] + (π*π)*(v*v)/(100*a2)) - upp = exp((2*choice-1)*Z_U*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - low = exp((2*choice-1)*Z_L*gk[m]*100 - 3*log(denom) + log(w_gh[m]) - 2*log(100)) - fact = upp*((2*choice-1)*gk[m]*sin(sifa*Z_U)*100 - sifa*cos(sifa*Z_U)) - - low*((2*choice-1)*gk[m]*sin(sifa*Z_L)*100 - sifa*cos(sifa*Z_L)) - exdif = exp((-.5*denom*(rt-upper_t)) + log(1-exp(-.5*denom*(upper_t-lower_t)))) - sum_ν += fact*exdif - end - sum_hist[3] = sum_hist[2] + v*sum_ν - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - - Fnew = (p0*(1-choice) + p1*choice) - sum_hist[3]*4*π/(a2*sz*st) - # cumulative distribution function for t and x - elseif t <= τ+st/2 # is t lower than upper boundary Ter distribution? - sum_ν = 0 - for m in 1:nr_ν - if abs(gk[m]) > ϵ - sum_z = 0 - for i in 1:nr_z - zzz = (α - gz[i])*choice + gz[i]*(1 - choice) - ser = -((α*a2)/((1 - 2*choice)*gk[m]*π*.01))*sinh(zzz*(1 - 2*x)*gk[m]/.01)/ - (sinh((1 - 2*choice)*gk[m]*α/.01)^2) + - (zzz*a2)/((1 - 2*choice)*gk[m]*π*.01)*cosh((α - zzz)*(1 - 2*choice)*gk[m]/.01)/ - sinh((1 - 2*choice)*gk[m]*α/.01) - sum_hist = zeros(3) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (gk[m]*gk[m]*100 + (π*v)*(π*v)/(a2*100)) - sum_hist[3] = sum_hist[2] + v*sin(sifa*zzz)*exp(-.5*denom*(rt - lower_t) - 2*log(denom)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z += .5*w_g[i]*(ser - 4*sum_hist[3])*(π/100)/(a2*st)*exp((2*choice - 1)*zzz*gk[m]*100) - end - else - sum_hist = zeros(3) - su = -(Z_U*Z_U)/(12*a2) + (Z_U*Z_U*Z_U)/(12*α*a2) - (Z_U*Z_U*Z_U*Z_U)/(48*a2*a2) - sl = -(Z_L*Z_L)/(12*a2) + (Z_L*Z_L*Z_L)/(12*α*a2) - (Z_L*Z_L*Z_L*Z_L)/(48*a2*a2) - for v in 1:v_max - sum_hist = circshift(sum_hist, 1) - sifa = π*v/α - denom = (π*v)*(π*v)/(a2*100) - sum_hist[3] = sum_hist[2] + 1/(π*π*π*π*v*v*v*v)*(cos(sifa*Z_L) - cos(sifa*Z_U))* - exp(-.5*denom*(rt - lower_t)) - if abs(sum_hist[1] - sum_hist[2]) < Δ && abs(sum_hist[2] - sum_hist[3]) < Δ && sum_hist[3] > 0 - break - end - end - sum_z = 400*a2*α*(sl - su - sum_hist[3])/(st*sz) - end - sum_ν += sum_z*w_gh[m] - end - Fnew = (p0*(1 - choice) + p1*choice) - sum_ν - end - elseif rt - τ + st/2 <= min_rt # is t lower than lower boundary Ter distr? - Fnew = 0 - end - - Fnew = Fnew > Δ ? Fnew : 0 - - return Fnew - -end - -function _add_outlier_cdf(y::Real, x::Real, p_outlier::Real; w_outlier::Real = 0.1) - #Ratcliff and Tuerlinckx, 2002 containment process - return y * (1 - p_outlier) + (x + (1. / (2 * w_outlier))) * w_outlier * p_outlier -end - -function _p_outlier_in_range(p_outlier) - return (p_outlier >= 0) & (p_outlier <= 1) -end - -""" - rand(dist::RatcliffDDM) - -Generate a random choice and rt for the Ratcliff Diffusion Model - -# Arguments -- `dist`: model object for Ratcliff Diffusion Model. -- `method`: method simulating the diffusion process. - "rejection" uses Tuerlinckx et al., 2001 rejection-based method for the general wiener process - "stochastic" uses the stochastic Euler method to directly simulate the stochastic differential equation - -# References - - Tuerlinckx, F., Maris, E., Ratcliff, R., & De Boeck, P. (2001). - A comparison of four methods for simulating the diffusion process. - Behavior Research Methods, Instruments, & Computers, 33, 443-456. - - Converted from Rhddmjagsutils.R R script by Kianté Fernandez - - See also https://github.com/kiante-fernandez/Rhddmjags. -""" -function rand(rng::AbstractRNG, d::RatcliffDDM) - # method::Char = "rejection" - return _rand_rejection(rng, d) - # method::Char = "stochastic" -# return _rand_stochastic(rng, d) -end - -function _rand_rejection(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1) - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - η = 1e-16 - end - - # Initialize output vectors - result = zeros(N) - T = zeros(N) - XX = zeros(N) - - # Called sigma in 2001 paper - D = σ^2 / 2 - - # Program specifications - ϵ = eps() # precision from 1.0 to next double-precision number - Δ = ϵ - - for n in 1:N - r1 = randn() - μ = ν + r1 * η - bb = z - sz / 2 + sz * rand() - zz = bb * α - finish = 0 - totaltime = 0 - startpos = 0 - Aupper = α - zz - Alower = -zz - radius = min(abs(Aupper), abs(Alower)) - - while finish == 0 - λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 - # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 - F = D * π / (radius * μ) - F = F^2 / (1 + F^2) - # formula p447 in 2001 paper - prob = exp(radius * μ / D) - prob = prob / (1 + prob) - dir_ = 2 * (rand() < prob) - 1 - l = -1 - s2 = 0 - s1 = 0 - while s2 > l - s2 = rand() - s1 = rand() - tnew = 0 - told = 0 - uu = 0 - while abs(tnew - told) > ϵ || uu == 0 - told = tnew - uu += 1 - tnew = told + (2 * uu + 1) * (-1)^uu * s1^(F * (2 * uu + 1)^2) - # infinite sum in formula (16) in BRMIC,2001 - end - l = 1 + s1^(-F) * tnew - end - # rest of formula (16) - t = abs(log(s1)) / λ - # is the negative of t* in (14) in BRMIC,2001 - totaltime += t - dir_ = startpos + dir_ * radius - ndt = τ - st / 2 + st * rand() - if (dir_ + Δ) > Aupper - T[n] = ndt + totaltime - XX[n] = 1 - finish = 1 - elseif (dir_ - Δ) < Alower - T[n] = ndt + totaltime - XX[n] = 2 - finish = 1 - else - startpos = dir_ - radius = minimum(abs.([Aupper, Alower] .- startpos)) - end - end - end - return (choice=XX,rt=T) -end - -function _rand_stochastic(rng::AbstractRNG, d::RatcliffDDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) - (ν, α, τ, z, η, sz, st, σ) = params(d) - - if η == 0 - η = 1e-16 - end - - # Initialize output vectors - choice = fill(0, N) - rt = fill(0.0, N) - - for n in 1:N - random_walk = Array{Float64}(undef, nsteps) - start_point = (z - sz/2) + ((z + sz/2) - (z - sz/2)) * rand() - ndt = (τ - st/2) + ((τ + st/2) - (τ - st/2)) * rand() - drift = rand(Distributions.Normal(ν, η)) - random_walk[1] = start_point * α - for s in 2:nsteps - random_walk[s] = random_walk[s-1] + rand(Distributions.Normal(drift * step_length, σ * sqrt(step_length))) - if random_walk[s] >= α - random_walk[s:end] .= α - rts[n] = s * step_length + ndt - choice[n] = 1 - break - elseif random_walk[s] <= 0 - random_walk[s:end] .= 0 - rts[n] = s * step_length + ndt - choice[n] = 2 - break - elseif s == nsteps - rts[n] = NaN - choice[n] = NaN - break - end - end - end - return (choice=choice,rt=rts) -end - -""" - rand(dist::RatcliffDDM, n_sim::Int) - -Generate `n_sim` random choice-rt pairs for the Ratcliff Diffusion Decision Model. - -# Arguments -- `dist`: model object for the Ratcliff DDM. -- `n_sim::Int`: the number of simulated rts -""" - -function rand(rng::AbstractRNG, d::RatcliffDDM, n_sim::Int) - return _rand_rejection(rng, d, N = n_sim) -end - -sampler(rng::AbstractRNG, d::RatcliffDDM) = rand(rng::AbstractRNG, d::RatcliffDDM) diff --git a/src/SequentialSamplingModels.jl b/src/SequentialSamplingModels.jl index bd19bd2c..357f941c 100644 --- a/src/SequentialSamplingModels.jl +++ b/src/SequentialSamplingModels.jl @@ -36,6 +36,7 @@ module SequentialSamplingModels export AbstractLNR export AbstractPoissonRace export AbstractRDM + export AbstractDDM export AbstractWald export aDDM export CDDM @@ -50,7 +51,7 @@ module SequentialSamplingModels export SSM1D export SSM2D export ContinuousMultivariateSSM - export RatcliffDDM + # export RatcliffDDM export Wald export WaldMixture @@ -92,5 +93,5 @@ module SequentialSamplingModels include("ext_functions.jl") include("ex_gaussian.jl") include("poisson_race.jl") - include("RatcliffDDM.jl") + # include("RatcliffDDM.jl") end \ No newline at end of file diff --git a/src/type_system.jl b/src/type_system.jl index f2fa1dc1..06f2a694 100644 --- a/src/type_system.jl +++ b/src/type_system.jl @@ -72,6 +72,13 @@ An abstract type for the racing diffusion model. """ abstract type AbstractRDM <: SSM2D end +""" + AbstractDDM <: SSM2D + +An abstract type for the diffusion decision model. +""" +abstract type AbstractDDM <: SSM2D end + abstract type PDFType end """ diff --git a/test/ddm_tests.jl b/test/ddm_tests.jl index 889c330f..4104e76e 100644 --- a/test/ddm_tests.jl +++ b/test/ddm_tests.jl @@ -49,6 +49,106 @@ @test y′ ≈ y rtol = .05 end + @safetestset "Full DDM pdf 1 (η)" begin + using SequentialSamplingModels + using Test + using Random + Random.seed!(654) + include("KDE.jl") + + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.08, sz = 0.00, st = 0.00, σ = 1.0) + choice,rt = rand(dist, 10^6) + rt1 = rt[choice .== 1] + p1 = mean(choice .== 1) + p2 = 1 - p1 + approx_pdf = kernel(rt1) + x = range(.301, 1.5, length=100) + y′ = pdf(approx_pdf, x) * p1 + y = pdf.(dist, (1,), x) + @test y′ ≈ y rtol = .05 + + rt2 = rt[choice .== 2] + approx_pdf = kde(rt2) + y′ = pdf(approx_pdf, x) * p2 + y = pdf.(dist, (2,), x) + @test y′ ≈ y rtol = .05 + end + + @safetestset "Full DDM pdf 2 (sz)" begin + using SequentialSamplingModels + using Test + using Random + Random.seed!(654) + include("KDE.jl") + + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.10, st = 0.00, σ = 1.0) + choice,rt = rand(dist, 10^6) + rt1 = rt[choice .== 1] + p1 = mean(choice .== 1) + p2 = 1 - p1 + approx_pdf = kernel(rt1) + x = range(.301, 1.5, length=100) + y′ = pdf(approx_pdf, x) * p1 + y = pdf.(dist, (1,), x) + @test y′ ≈ y rtol = .05 + + rt2 = rt[choice .== 2] + approx_pdf = kde(rt2) + y′ = pdf(approx_pdf, x) * p2 + y = pdf.(dist, (2,), x) + @test y′ ≈ y rtol = .05 + end + + @safetestset "Full DDM pdf 3 (st)" begin + using SequentialSamplingModels + using Test + using Random + Random.seed!(654) + include("KDE.jl") + + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.00, sz = 0.00, st = 0.10, σ = 1.0) + choice,rt = rand(dist, 10^6) + rt1 = rt[choice .== 1] + p1 = mean(choice .== 1) + p2 = 1 - p1 + approx_pdf = kernel(rt1) + x = range(.301 + .10, 1.5, length=100) # τ and st start the lower bound + y′ = pdf(approx_pdf, x) * p1 + y = pdf.(dist, (1,), x) + @test y′ ≈ y rtol = .05 + + rt2 = rt[choice .== 2] + approx_pdf = kde(rt2) + y′ = pdf(approx_pdf, x) * p2 + y = pdf.(dist, (2,), x) + @test y′ ≈ y rtol = .05 + end + + @safetestset "Full DDM pdf 4 (η,sz,st)" begin + using SequentialSamplingModels + using Test + using Random + Random.seed!(654) + include("KDE.jl") + + dist = DDM(ν=1.0, α = .8, z = .5, τ = .3, η = 0.08, sz = 0.10, st = 0.10, σ = 1.0) + choice,rt = rand(dist, 10^6) + rt1 = rt[choice .== 1] + p1 = mean(choice .== 1) + p2 = 1 - p1 + approx_pdf = kernel(rt1) + x = range(.301 + .10, 1.5, length=100) # τ and st start the lower bound + y′ = pdf(approx_pdf, x) * p1 + y = pdf.(dist, (1,), x) + @test y′ ≈ y rtol = .05 + + rt2 = rt[choice .== 2] + approx_pdf = kde(rt2) + y′ = pdf(approx_pdf, x) * p2 + y = pdf.(dist, (2,), x) + @test y′ ≈ y rtol = .05 + end + @safetestset "DDM cdf 1" begin using SequentialSamplingModels using Test @@ -96,21 +196,21 @@ ecdf2 = ecdf(rt2) y′ = ecdf1.(x) * p2 y = cdf.(dist, (2,), x) - @test y′ ≈ y rtol = .01 + @test y′ ≈ y rtol = .02 #note the change in tol end - @safetestset "_exp_pnorm" begin - using SequentialSamplingModels: _exp_pnorm - using Test - true_values = [0.003078896, 0.021471654, 0.067667642, 0.113863630, 0.132256388, 0.008369306, 0.058366006, 0.183939721, 0.309513435, 0.359510135, 0.022750132, - 0.158655254, 0.500000000, 0.841344746, 0.977249868, 0.061841270, 0.431269694, 1.359140914, 2.287012135, 2.656440558, 0.168102001, 1.172312572, - 3.694528049, 6.216743527, 7.220954098] - cnt = 0 - for v1 ∈ -2:2, v2 ∈ -2:2 - cnt += 1 - @test true_values[cnt] ≈ _exp_pnorm(v1, v2) atol = 1e-5 - end - end + # @safetestset "_exp_pnorm" begin + # using SequentialSamplingModels: _exp_pnorm + # using Test + # true_values = [0.003078896, 0.021471654, 0.067667642, 0.113863630, 0.132256388, 0.008369306, 0.058366006, 0.183939721, 0.309513435, 0.359510135, 0.022750132, + # 0.158655254, 0.500000000, 0.841344746, 0.977249868, 0.061841270, 0.431269694, 1.359140914, 2.287012135, 2.656440558, 0.168102001, 1.172312572, + # 3.694528049, 6.216743527, 7.220954098] + # cnt = 0 + # for v1 ∈ -2:2, v2 ∈ -2:2 + # cnt += 1 + # @test true_values[cnt] ≈ _exp_pnorm(v1, v2) atol = 1e-5 + # end + # end # exp_pnorm = function(a, b) # { # r = exp(a) * pnorm(b) @@ -127,17 +227,17 @@ # } # } - @safetestset "K_large" begin - using SequentialSamplingModels - using SequentialSamplingModels: _K_large - using Test + # @safetestset "K_large" begin + # using SequentialSamplingModels + # using SequentialSamplingModels: _K_large + # using Test - test_val1 = _K_large(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .75; ϵ = 1e-10) - @test test_val1 ≈ 3.0 + # test_val1 = _K_large(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .75; ϵ = 1e-10) + # @test test_val1 ≈ 3.0 - test_val2 = _K_large(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .501; ϵ = 1e-10) - @test test_val2 ≈ 36 - end + # test_val2 = _K_large(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .501; ϵ = 1e-10) + # @test test_val2 ≈ 36 + # end # K_large = function(t, v, a, w, epsilon) # { # sqrtL1 = sqrt(1/t) * a/pi @@ -147,21 +247,21 @@ # K_large(.25, 1.5, .5, .25, 1e-10) # K_large(.001, 1.5, .5, .25, 1e-10) - @safetestset "K_small" begin - using SequentialSamplingModels - using SequentialSamplingModels: _K_small - using Test + # @safetestset "K_small" begin + # using SequentialSamplingModels + # using SequentialSamplingModels: _K_small + # using Test - # function(t, v, a, w, epsilon) - test_val1 = _K_small(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .75; ϵ = 1e-10) - @test test_val1 ≈ 16 + # # function(t, v, a, w, epsilon) + # test_val1 = _K_small(DDM(;ν=1.5, α=.50, z=.25, τ=.50, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), .75; ϵ = 1e-10) + # @test test_val1 ≈ 16 - test_val2 = _K_small(DDM(;ν=1.5, α=.50, z=.25, τ=.50), .501; ϵ = 1e-10) - @test test_val2 ≈ 16 + # test_val2 = _K_small(DDM(;ν=1.5, α=.50, z=.25, τ=.50, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), .501; ϵ = 1e-10) + # @test test_val2 ≈ 16 - test_val3 = _K_small(DDM(;ν=0.50, α=2.50, z=.50, τ=.30), .400; ϵ = 1e-10) - @test test_val3 ≈ 10 - end + # test_val3 = _K_small(DDM(;ν=0.50, α=2.50, z=.50, τ=.30, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), .400; ϵ = 1e-10) + # @test test_val3 ≈ 10 + # end # K_small = function(t, v, a, w, epsilon) # { # if(abs(v) < sqrt(.Machine$double.eps)) # zero drift case @@ -178,35 +278,35 @@ # K_small(.001, 1.5, .5, .25, 1e-10) # K_small(.10, .50, 2.5, .50, 1e-10) - @safetestset "_P_upper" begin - using SequentialSamplingModels - using SequentialSamplingModels: _P_upper - using Test + # @safetestset "_P_upper" begin + # using SequentialSamplingModels + # using SequentialSamplingModels: _P_upper + # using Test - test_val1 = _P_upper(1., .5, .5) - @test test_val1 ≈ 0.3775407 atol = 1e-5 + # test_val1 = _P_upper(1., .5, .5) + # @test test_val1 ≈ 0.3775407 atol = 1e-5 - test_val2 = _P_upper(-1., .75, .25) - @test test_val2 ≈ .8693188 atol = 1e-5 + # test_val2 = _P_upper(-1., .75, .25) + # @test test_val2 ≈ .8693188 atol = 1e-5 - test_val3 = _P_upper(-1e10, .75, .25) - @test test_val3 ≈ 1 + # test_val3 = _P_upper(-1e10, .75, .25) + # @test test_val3 ≈ 1 - test_val4 = _P_upper(eps(), .75, .25) - @test test_val4 ≈ .75 - end + # test_val4 = _P_upper(eps(), .75, .25) + # @test test_val4 ≈ .75 + # end - @safetestset "_Fs_lower" begin - using SequentialSamplingModels - using SequentialSamplingModels: _Fs_lower - using Test + # @safetestset "_Fs_lower" begin + # using SequentialSamplingModels + # using SequentialSamplingModels: _Fs_lower + # using Test - test_val1 = _Fs_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .75) - @test test_val1 ≈ 0.5955567 atol = 1e-5 + # test_val1 = _Fs_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .75) + # @test test_val1 ≈ 0.5955567 atol = 1e-5 - test_val2 = _Fs_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .501) - @test test_val2 ≈ 6.393096e-05 atol = 1e-8 - end + # test_val2 = _Fs_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .501) + # @test test_val2 ≈ 6.393096e-05 atol = 1e-8 + # end # Fs_lower = function(t, v, a, w, K) # { @@ -228,17 +328,17 @@ # Fs_lower(.25, 1.5, .5, .25, 10) # Fs_lower(.001, 1.5, .5, .25, 10) - @safetestset "_Fl_lower" begin - using SequentialSamplingModels - using SequentialSamplingModels: _Fl_lower - using Test + # @safetestset "_Fl_lower" begin + # using SequentialSamplingModels + # using SequentialSamplingModels: _Fl_lower + # using Test - test_val1 = _Fl_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .75) - @test test_val1 ≈ 0.5955567 atol = 1e-5 + # test_val1 = _Fl_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .75) + # @test test_val1 ≈ 0.5955567 atol = 1e-5 - test_val2 = _Fl_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .501) - @test test_val2 ≈ 0.001206451 atol = 1e-8 - end + # test_val2 = _Fl_lower(DDM(;ν=1.5, α=.50, z=.25, τ=.50), 10, .501) + # @test test_val2 ≈ 0.001206451 atol = 1e-8 + # end # Fl_lower = function(t, v, a, w, K) # { # F = numeric(length(t)) @@ -254,19 +354,93 @@ using SequentialSamplingModels using Test # tested against rtdists - test_val1 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3), 1, .5) + test_val1 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), 1, .5) @test test_val1 ≈ 2.131129 atol = 1e-5 - test_val2 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3), 2, .5) + test_val2 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), 2, .5) @test test_val2 ≈ 0.2884169 atol = 1e-5 - test_val3 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2), 1, .35) + test_val3 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), 1, .35) @test test_val3 ≈ 0.6635714 atol = 1e-5 - test_val4 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2), 2, .35) + test_val4 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.00, sz = 0.0, st = 0.0, σ = 1.0), 2, .35) @test test_val4 ≈ 0.4450956 atol = 1e-5 end + @safetestset "full pdf" begin + using SequentialSamplingModels + using Test + # tested against rtdists + ## eta + test_val1 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .5) + @test test_val1 ≈ 2.129834 atol = 1e-1 + + test_val2 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .5) + @test test_val2 ≈ 0.2889796 atol = 1e-1 + + test_val3 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .35) + @test test_val3 ≈ 0.6633653 atol = 1e-1 + + test_val4 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .35) + @test test_val4 ≈ 0.4449858 atol = 1e-1 + #try a bunch of combinations from values from rtdists + sz_values = [0.00,0.05, 0.1, 0.2, 0.3] + st_values = [0.00,0.05, 0.1, 0.2, 0.3] + η_values = [0.00,0.08, 0.9, 0.13, 0.16] + combinations = [(sz, st, η) for sz in sz_values for st in st_values for η in η_values] + + test_vals_upper = [2.131129, 2.129384, 2.124152, 2.101896, 2.065028, 2.540625, + 2.538263, 2.531186, 2.501185, 2.45186, 3.025843, 3.022579, 3.01281, + 2.971536, 2.90416, 2.866027, 2.865954, 2.865732, 2.864744, 2.862934, + 1.910684, 1.910636, 1.910488, 1.909829, 1.908623, 2.129834, 2.128092, + 2.122868, 2.100647, 2.063835, 2.539411, 2.537052, 2.529984, 2.500024, + 2.450762, 3.024903, 3.021643, 3.011883, 2.970646, 2.903329, 2.865857, + 2.865785, 2.865565, 2.864583, 2.862783, 1.910572, 1.910523, 1.910377, + 1.909722, 1.908522, 1.983902, 1.98245, 1.978094, 1.95952, 1.9286, + 2.399602, 2.397588, 2.391552, 2.365916, 2.323594, 2.913938, 2.911015, + 2.902262, 2.86524, 2.804655, 2.846349, 2.846329, 2.846268, 2.845959, + 2.845273, 1.897566, 1.897553, 1.897512, 1.897306, 1.896849, 2.127715, + 2.125978, 2.120768, 2.098603, 2.061881, 2.537423, 2.535069, 2.528017, + 2.498121, 2.448964, 3.023364, 3.020108, 3.010363, 2.969186, 2.901967, + 2.86558, 2.865508, 2.865291, 2.864319, 2.862536, 1.910387, 1.910339, + 1.910194, 1.909546, 1.908358, 2.125965, 2.124231, 2.119032, 2.096913, + 2.060266, 2.53578, 2.53343, 2.526391, 2.496549, 2.447477, 3.02209, + 3.018839, 3.009105, 2.967979, 2.900839, 2.865351, 2.86528, 2.865064, + 2.864101, 2.862332, 1.910234, 1.910187, 1.910043, 1.909401, 1.908221] + + test_vals_lower = [0.288417, 0.288327, 0.288055, 0.286843, 0.284638, 0.343836, + 0.343781, 0.343611, 0.342803, 0.341161, 0.409503, 0.409585, 0.409821, + 0.410666, 0.411506, 0.387875, 0.389054, 0.3926, 0.407855, 0.43373, + 0.258583, 0.25937, 0.261734, 0.271903, 0.289153, 0.28898, 0.288889, + 0.288615, 0.287391, 0.285169, 0.344436, 0.34438, 0.344207, 0.343387, + 0.341724, 0.410134, 0.410215, 0.410447, 0.411278, 0.412097, 0.388387, + 0.389566, 0.393111, 0.408359, 0.434222, 0.258925, 0.259711, 0.262074, + 0.272239, 0.289481, 0.354834, 0.354647, 0.354081, 0.351634, 0.34743, + 0.415484, 0.415315, 0.414806, 0.412568, 0.408615, 0.486035, 0.485984, + 0.485823, 0.484998, 0.483146, 0.451208, 0.452322, 0.455669, 0.470067, + 0.494477, 0.300805, 0.301548, 0.303779, 0.313378, 0.329651, 0.289902, + 0.28981, 0.289531, 0.28829, 0.286039, 0.345418, 0.34536, 0.345183, + 0.344343, 0.342648, 0.411169, 0.411247, 0.411475, 0.412283, 0.413065, + 0.389227, 0.390406, 0.393948, 0.409185, 0.435029, 0.259485, 0.26027, + 0.262632, 0.27279, 0.290019, 0.290664, 0.290571, 0.290289, 0.289034, + 0.286759, 0.346231, 0.346172, 0.34599, 0.345134, 0.343412, 0.412025, + 0.412102, 0.412325, 0.413114, 0.413866, 0.389923, 0.391101, 0.394641, + 0.409869, 0.435697, 0.259949, 0.260734, 0.263094, 0.273246, 0.290465] + + for (i, (sz, st, η)) in enumerate(combinations) + # Compute the pdf for the current values of sz, st, and η + pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=η, sz=sz, st=st, σ = 1.0), 1, .5) + @test test_vals_upper[i] ≈ pdf_value atol = 1e-1 + pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=η, sz=sz, st=st, σ = 1.0), 2, .5) + @test test_vals_lower[i] ≈ pdf_value atol = 1e-1 + end + + # 0.05, 0.05, 0.08 + # pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=.08, sz=.05, st=.05, σ = 1.0), 1, .5) + # @test 2.537052 ≈ pdf_value atol = 1e-1 + + end + @safetestset "simulate" begin using SequentialSamplingModels using Test From b74b0a16b98eedcb14009bd0364db8a20c58413c Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Wed, 27 Dec 2023 21:08:34 -0500 Subject: [PATCH 42/43] Remove .vscode directory --- .vscode/settings.json | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7a73a41b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file From 85156123beed5ce7cab9eda2136258b67c167d6c Mon Sep 17 00:00:00 2001 From: Kiante Fernandez Date: Mon, 1 Jan 2024 13:50:09 -0500 Subject: [PATCH 43/43] Refactor DDM tests and debug/ID bug --- src/DDM.jl | 132 +++++++++++++++++++++++----------------------- test/ddm_tests.jl | 118 ++++++++++++++++++++++------------------- 2 files changed, 131 insertions(+), 119 deletions(-) diff --git a/src/DDM.jl b/src/DDM.jl index 3f0ee8db..7648f9e0 100644 --- a/src/DDM.jl +++ b/src/DDM.jl @@ -4,14 +4,14 @@ Model object for the Ratcliff (Full) Diffusion Decision Model. # Parameters - - `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 - - `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 - - `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 - - `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). - - `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. - - `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. - - `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. - - `σ`: diffusion noise constant. Default is 1. +- `ν`: drift rate. Average slope of the information accumulation process. The drift gives information about the speed and direction of the accumulation of information. Typical range: -5 < ν < 5 +- `α`: boundary threshold separation. The amount of information that is considered for a decision. Typical range: 0.5 < α < 2 +- `τ`: non-decision time. The duration for a non-decisional processes (encoding and response execution). Typical range: 0.1 < τ < 0.5 +- `z`: starting point. Indicator of an an initial bias towards a decision. The z parameter is relative to a (i.e. it ranges from 0 to 1). +- `η`: across-trial-variability of drift rate. Typical range: 0 < η < 2. Default is 0. +- `sz`: across-trial-variability of starting point. Typical range: 0 < sz < 0.5. Default is 0. +- `st`: across-trial-variability of non-decision time. Typical range: 0 < st < 0.2. Default is 0. +- `σ`: diffusion noise constant. Default is 1. # Constructors @@ -40,6 +40,7 @@ loglike = logpdf.(dist, choice, rt) # References Ratcliff, R., & McKoon, G. (2008). The Diffusion Decision Model: Theory and Data for Two-Choice Decision Tasks. Neural Computation, 20(4), 873–922. + Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85, 59–108. https://doi.org/10.1037/0033-295X.85.2.59 """ mutable struct DDM{T<:Real} <: AbstractDDM @@ -150,7 +151,13 @@ function _pdf_sv(d::DDM, rt; ϵ::Real = 1.0e-12) return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) end - return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + # α *= 2.0 + # return _pdf(DDM(0, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ( ( (α*z*η)^2 - 2*ν*α*z - (ν^2)*(rt-τ) ) / (2*(η^2)*(rt-τ)+2) ) - log(sqrt((η^2)*(rt-τ)+1)) + ν*α*z + (ν^2)*(rt-τ)*0.5 + #based on:https://github.com/AGhaderi/spatial_attenNCM/blob/main/Neuro-Cognitive%20Models/Models/Hier_Model/lambda_modelt.stan + XX = (rt - τ) / α^2 #use normalized time + + return _pdf(DDM(ν, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ( ( (α*(1-z)*η)^2 - 2*ν*α*(1-z) - (ν^2)*XX ) / (2*(η^2)*XX+2) ) - log(sqrt((η^2)*XX+1)) + ν*α*(1-z) + (ν^2)*XX*0.5 + # return _pdf(DDM(0, α, τ, z, 0, 0, 0, σ), rt; ϵ) + ((α * z * η)^2 - 2 * α * ν * z - (ν^2) * (rt-τ) ) ./ (2 * (η^2) * (rt-τ) .+ 2) - 0.5 * sqrt(η^2 * (rt-τ) .+ 1) - 2 * α end """ @@ -251,7 +258,6 @@ Calculate the 1-dimensional Simpson's numerical integration for a drift diffusio https://en.wikipedia.org/wiki/Simpson%27s_rule """ -# Simpson's Method one dimentional case function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) n = max(n_st, n_sz) @@ -291,7 +297,7 @@ function _simpson_1D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y S = S / ((ub_t - lb_t) + (ub_z - lb_z)) # the right function if pdf_sv()/sz or pdf_sv()/st - return (ht + hz) * S / 3 + return ((ht + hz) * S / 3) end @@ -316,7 +322,6 @@ Calculate the 2-dimensional Simpson's numerical integration for a drift diffusio - The function calls `_simpson_1D` to perform the 1-dimensional integrations over each dimension. """ -# Simpson's Method two dimentional case function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, ϵ::Real, lb_z::Real, ub_z::Real, n_sz::Int, lb_t::Real, ub_t::Real, n_st::Int) ht = (ub_t-lb_t)/n_st @@ -338,33 +343,33 @@ function _simpson_2D(x::Real, ν::Real, η::Real, α::Real, z::Real, τ::Real, S = S - y # the last term should be f(b) and not 2*f(b) so we subtract y S = S / (ub_t - lb_t) - return ht * S / 3 + return (ht * S / 3) end logpdf(d::DDM, choice, rt; ϵ::Real = 1.0e-12) = log(pdf(d, choice, rt; ϵ)) -""" - cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) +# """ +# cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) -Compute the Cumulative Distribution Function (CDF) for the Ratcliff Diffusion model. This function uses 6 Gaussian quadrature for numerical integration. +# Compute the Cumulative Distribution Function (CDF) for the Ratcliff Diffusion model. This function uses 6 Gaussian quadrature for numerical integration. -# Arguments -- `d`: an instance of DDM Constructor -- `choice`: an input representing the choice. -- `rt`: response time. -- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. +# # Arguments +# - `d`: an instance of DDM Constructor +# - `choice`: an input representing the choice. +# - `rt`: response time. +# - `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. -# Returns -- `y`: an array representing the CDF of the Diffusion Decision model. +# # Returns +# - `y`: an array representing the CDF of the Diffusion Decision model. -# Reference -Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution and probability density functions in the diffusion model, Behavior Research Methods, Instruments, & Computers, 36 (4), 702-716. +# # Reference +# Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution and probability density functions in the diffusion model, Behavior Research Methods, Instruments, & Computers, 36 (4), 702-716. -# See also -- Converted from cdfdif.c C script by Joachim Vandekerckhove: https://ppw.kuleuven.be/okp/software/dmat/ -""" +# # See also +# - Converted from cdfdif.c C script by Joachim Vandekerckhove: https://ppw.kuleuven.be/okp/software/dmat/ +# """ # function cdf(d::DDM, choice, rt; ϵ::Real = 1e-7) # (ν, α, τ, z, η, sz, st, σ) = params(d) @@ -391,22 +396,22 @@ Tuerlinckx, F. (2004). The efficient computation of the cumulative distribution # return y # end -""" - _cdf(d::DDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} +# """ +# _cdf(d::DDM{T}, choice, rt, prob; ϵ::Real = 1e-7) where {T<:Real} -A helper function to compute the Cumulative Distribution Function (CDF) for the Diffusion Decision model. +# A helper function to compute the Cumulative Distribution Function (CDF) for the Diffusion Decision model. -# Arguments -- `d`: an instance of DDM Constructor -- `choice`: an input representing the choice. -- `rt`: response time. -- `prob`: a probability value. -- `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. +# # Arguments +# - `d`: an instance of DDM Constructor +# - `choice`: an input representing the choice. +# - `rt`: response time. +# - `prob`: a probability value. +# - `ϵ`: a small constant to avoid division by zero, defaults to 1e-7. -# Returns -- `Fnew`: the computed CDF for the given parameters. +# # Returns +# - `Fnew`: the computed CDF for the given parameters. -""" +# """ # function _cdf(d::DDM, choice, rt, prob; ϵ::Real = 1e-7) # (ν, α, τ, z, η, sz, st, σ) = params(d) @@ -584,36 +589,33 @@ function rand(rng::AbstractRNG, d::DDM) end function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) - (ν, α, τ, z, η, sz, st, σ) = params(d) + (;ν, α, τ, z, η, sz, st, σ) = d - if η == 0 - η = 1e-16 - end + η = η == 0 ? eps() : η # Initialize output vectors - choice = fill(0, N) - rt = fill(0.0, N) + T = zeros(N) + XX = fill(0, N) # Called sigma in 2001 paper D = σ^2 / 2 # Program specifications ϵ = eps() # precision from 1.0 to next double-precision number - Δ = ϵ for n in 1:N r1 = randn(rng) μ = ν + r1 * η bb = z - sz / 2 + sz * rand(rng) zz = bb * α - finish = 0 - totaltime = 0 - startpos = 0 + finish = false + totaltime = 0.0 + startpos = 0.0 Aupper = α - zz Alower = -zz radius = min(abs(Aupper), abs(Alower)) - while finish == 0 + while !finish λ = 0.25 * μ^2 / D + 0.25 * D * π^2 / radius^2 # eq. formula (13) in 2001 paper with D = sigma^2/2 and radius = Alpha/2 F = D * π / (radius * μ) @@ -622,14 +624,14 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) prob = exp(radius * μ / D) prob = prob / (1 + prob) dir_ = 2 * (rand(rng) < prob) - 1 - l = -1 - s2 = 0 - s1 = 0 + l = -1.0 + s2 = 0.0 + s1 = 0.0 while s2 > l s2 = rand(rng) s1 = rand(rng) - tnew = 0 - told = 0 + tnew = 0.0 + told = 0.0 uu = 0 while abs(tnew - told) > ϵ || uu == 0 told = tnew @@ -645,21 +647,21 @@ function _rand_rejection(rng::AbstractRNG, d::DDM; N::Int = 1) totaltime += t dir_ = startpos + dir_ * radius ndt = τ - st / 2 + st * rand(rng) - if (dir_ + Δ) > Aupper - rt[n] = ndt + totaltime - choice[n] = 1 - finish = 1 - elseif (dir_ - Δ) < Alower - rt[n] = ndt + totaltime - choice[n] = 2 - finish = 1 + if (dir_ + ϵ) > Aupper + T[n] = ndt + totaltime + XX[n] = 1 + finish = true + elseif (dir_ - ϵ) < Alower + T[n] = ndt + totaltime + XX[n] = 2 + finish = true else startpos = dir_ radius = minimum(abs.([Aupper, Alower] .- startpos)) end end end - return (choice=choice,rt=rt) + return (choice=XX,rt=T) end function _rand_stochastic(rng::AbstractRNG, d::DDM; N::Int = 1, nsteps::Int=300, step_length::Int=0.01) diff --git a/test/ddm_tests.jl b/test/ddm_tests.jl index 4104e76e..3d26a152 100644 --- a/test/ddm_tests.jl +++ b/test/ddm_tests.jl @@ -372,68 +372,78 @@ using Test # tested against rtdists ## eta - test_val1 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .5) - @test test_val1 ≈ 2.129834 atol = 1e-1 + # test_val1 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .5) + # @test test_val1 ≈ 2.129834 atol = 1e-1 - test_val2 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .5) - @test test_val2 ≈ 0.2889796 atol = 1e-1 + # test_val2 = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .5) + # @test test_val2 ≈ 0.2889796 atol = 1e-1 - test_val3 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .35) - @test test_val3 ≈ 0.6633653 atol = 1e-1 + # test_val3 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 1, .35) + # @test test_val3 ≈ 0.6633653 atol = 1e-1 - test_val4 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .35) - @test test_val4 ≈ 0.4449858 atol = 1e-1 + # test_val4 = pdf(DDM(;ν=.8, α=.5, z=.3, τ=.2, η = 0.08, sz = 0.0, st = 0.0, σ = 1.0), 2, .35) + # @test test_val4 ≈ 0.4449858 atol = 1e-1 #try a bunch of combinations from values from rtdists - sz_values = [0.00,0.05, 0.1, 0.2, 0.3] - st_values = [0.00,0.05, 0.1, 0.2, 0.3] - η_values = [0.00,0.08, 0.9, 0.13, 0.16] + sz_values = [0.00, 0.05, 0.10, 0.30] + st_values = [0.00, 0.05, 0.10, 0.30] + η_values = [0.00, 0.08, 0.16] + combinations = [(sz, st, η) for sz in sz_values for st in st_values for η in η_values] - test_vals_upper = [2.131129, 2.129384, 2.124152, 2.101896, 2.065028, 2.540625, - 2.538263, 2.531186, 2.501185, 2.45186, 3.025843, 3.022579, 3.01281, - 2.971536, 2.90416, 2.866027, 2.865954, 2.865732, 2.864744, 2.862934, - 1.910684, 1.910636, 1.910488, 1.909829, 1.908623, 2.129834, 2.128092, - 2.122868, 2.100647, 2.063835, 2.539411, 2.537052, 2.529984, 2.500024, - 2.450762, 3.024903, 3.021643, 3.011883, 2.970646, 2.903329, 2.865857, - 2.865785, 2.865565, 2.864583, 2.862783, 1.910572, 1.910523, 1.910377, - 1.909722, 1.908522, 1.983902, 1.98245, 1.978094, 1.95952, 1.9286, - 2.399602, 2.397588, 2.391552, 2.365916, 2.323594, 2.913938, 2.911015, - 2.902262, 2.86524, 2.804655, 2.846349, 2.846329, 2.846268, 2.845959, - 2.845273, 1.897566, 1.897553, 1.897512, 1.897306, 1.896849, 2.127715, - 2.125978, 2.120768, 2.098603, 2.061881, 2.537423, 2.535069, 2.528017, - 2.498121, 2.448964, 3.023364, 3.020108, 3.010363, 2.969186, 2.901967, - 2.86558, 2.865508, 2.865291, 2.864319, 2.862536, 1.910387, 1.910339, - 1.910194, 1.909546, 1.908358, 2.125965, 2.124231, 2.119032, 2.096913, - 2.060266, 2.53578, 2.53343, 2.526391, 2.496549, 2.447477, 3.02209, - 3.018839, 3.009105, 2.967979, 2.900839, 2.865351, 2.86528, 2.865064, - 2.864101, 2.862332, 1.910234, 1.910187, 1.910043, 1.909401, 1.908221] - - test_vals_lower = [0.288417, 0.288327, 0.288055, 0.286843, 0.284638, 0.343836, - 0.343781, 0.343611, 0.342803, 0.341161, 0.409503, 0.409585, 0.409821, - 0.410666, 0.411506, 0.387875, 0.389054, 0.3926, 0.407855, 0.43373, - 0.258583, 0.25937, 0.261734, 0.271903, 0.289153, 0.28898, 0.288889, - 0.288615, 0.287391, 0.285169, 0.344436, 0.34438, 0.344207, 0.343387, - 0.341724, 0.410134, 0.410215, 0.410447, 0.411278, 0.412097, 0.388387, - 0.389566, 0.393111, 0.408359, 0.434222, 0.258925, 0.259711, 0.262074, - 0.272239, 0.289481, 0.354834, 0.354647, 0.354081, 0.351634, 0.34743, - 0.415484, 0.415315, 0.414806, 0.412568, 0.408615, 0.486035, 0.485984, - 0.485823, 0.484998, 0.483146, 0.451208, 0.452322, 0.455669, 0.470067, - 0.494477, 0.300805, 0.301548, 0.303779, 0.313378, 0.329651, 0.289902, - 0.28981, 0.289531, 0.28829, 0.286039, 0.345418, 0.34536, 0.345183, - 0.344343, 0.342648, 0.411169, 0.411247, 0.411475, 0.412283, 0.413065, - 0.389227, 0.390406, 0.393948, 0.409185, 0.435029, 0.259485, 0.26027, - 0.262632, 0.27279, 0.290019, 0.290664, 0.290571, 0.290289, 0.289034, - 0.286759, 0.346231, 0.346172, 0.34599, 0.345134, 0.343412, 0.412025, - 0.412102, 0.412325, 0.413114, 0.413866, 0.389923, 0.391101, 0.394641, - 0.409869, 0.435697, 0.259949, 0.260734, 0.263094, 0.273246, 0.290465] + # for (sz, st, η) in combinations println("$sz, $st, $η") end + + test_vals_upper = [1.81597, 1.814461, 1.809956, 1.974649, 1.973321, 1.969353, + 2.123612, 2.122583, 2.119502, 1.624452, 1.624509, 1.62468, 1.815153, + 1.813646, 1.809147, 1.973663, 1.972337, 1.968375, 2.122466, 2.121439, + 2.118364, 1.624535, 1.624593, 1.624764, 1.812704, 1.811203, 1.806723, + 1.970707, 1.969387, 1.965443, 2.119034, 2.118012, 2.114953, 1.624786, + 1.624843, 1.625014, 1.785586, 1.784153, 1.779875, 1.938027, 1.936776, + 1.933035, 2.081122, 2.080158, 2.077274, 1.627546, 1.627602, 1.627769] + + test_vals_lower = [0.074023, 0.074416, 0.075599, 0.080491, 0.080889, 0.082087, + 0.086563, 0.08696, 0.088155, 0.066216, 0.066472, 0.067242, 0.074072, + 0.074465, 0.075648, 0.080556, 0.080954, 0.082151, 0.086653, 0.087051, + 0.088245, 0.066413, 0.066669, 0.067439, 0.074218, 0.074611, 0.075792, + 0.080751, 0.081149, 0.082345, 0.086923, 0.08732, 0.088513, 0.067004, + 0.06726, 0.068031, 0.075803, 0.076192, 0.077358, 0.082871, 0.083265, + 0.084447, 0.089867, 0.09026, 0.091442, 0.073773, 0.074034, 0.074818] for (i, (sz, st, η)) in enumerate(combinations) - # Compute the pdf for the current values of sz, st, and η - pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=η, sz=sz, st=st, σ = 1.0), 1, .5) - @test test_vals_upper[i] ≈ pdf_value atol = 1e-1 - pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=η, sz=sz, st=st, σ = 1.0), 2, .5) - @test test_vals_lower[i] ≈ pdf_value atol = 1e-1 - end + # Print out the current parameter set + println("Parameter set [$i]: sz = $sz, st = $st, η = $η") + # Compute the pdf for the current values of sz, st, and η + pdf_value = pdf(DDM(;ν=2.0, α=1.6, z=.5, τ=.2, η=η, sz=sz, st=st, σ = 1.0), 1, .5) + println("Test value upper[$i]: ", test_vals_upper[i]) + println("PDF value upper: ", pdf_value) + @test test_vals_upper[i] ≈ pdf_value atol = 1e-1 + pdf_value = pdf(DDM(;ν=2.0, α=1.6, z=.5, τ=.2, η=η, sz=sz, st=st, σ = 1.0), 2, .5) + println("Test value lower[$i]: ", test_vals_lower[i]) + println("PDF value lower: ", pdf_value) + @test test_vals_lower[i] ≈ pdf_value atol = 1e-1 + end + +# sz_values = [0.00] +# st_values = [0.00] +# η_values = [0.00, 0.08, 0.16] + +# combinations = [(sz, st, η) for sz in sz_values for st in st_values for η in η_values] + +# test_vals_upper = [1.81597, 1.814461, 1.809956] +# test_vals_lower = [0.074023, 0.074416, 0.075599] + +# for (i, (sz, st, η)) in enumerate(combinations) +# # Print out the current parameter set +# println("Parameter set [$i]: sz = $sz, st = $st, η = $η") +# # Compute the pdf for the current values of sz, st, and η +# pdf_value = pdf(DDM(;ν=2.0, α=1.6, z=.5, τ=.2, η=η, sz=sz, st=st, σ = 1.0), 1, .5) +# println("Test value upper[$i]: ", test_vals_upper[i]) +# println("PDF value upper: ", pdf_value) +# @test test_vals_upper[i] ≈ pdf_value atol = 1e-3 +# pdf_value = pdf(DDM(;ν=2.0, α=1.6, z=.5, τ=.2, η=η, sz=sz, st=st, σ = 1.0), 2, .5) +# println("Test value lower[$i]: ", test_vals_lower[i]) +# println("PDF value lower: ", pdf_value) +# @test test_vals_lower[i] ≈ pdf_value atol = 1e-3 +# end # 0.05, 0.05, 0.08 # pdf_value = pdf(DDM(;ν=2.0, α=1.0, z=.5, τ=.3, η=.08, sz=.05, st=.05, σ = 1.0), 1, .5)