Recipes for Collapsing Bounds

General use of bounds

Both linearly collapsing bounds (BoundCollapsingLinear) and exponentially collapsing bounds (BoundCollapsingExponential) already exist in PyDDM. For example:

from pyddm import Model
from pyddm.models import BoundCollapsingLinear, BoundCollapsingExponential
model1 = Model(bound=BoundCollapsingExponential(B=1, tau=2))
model2 = Model(bound=BoundCollapsingLinear(B=1, t=.2))

Step function collapsing bounds

It is also possible to make collapsing bounds of any shape. For example, the following describes bounds which collapse in discrete steps of a particular length:

from pyddm.models import Bound
class BoundCollapsingStep(Bound):
    name = "Step collapsing bounds"
    required_conditions = []
    required_parameters = ["B0", "stepheight", "steplength"]
    def get_bound(self, t, **kwargs):
        stepnum = t//self.steplength
        step = self.B0 - stepnum * self.stepheight
        return max(step, 0)

Try it out with:

from pyddm import Model, Fittable
from pyddm.plot import model_gui
model = Model(bound=BoundCollapsingStep(B0=Fittable(minval=.5, maxval=1.5),
                                        stepheight=Fittable(minval=0, maxval=.49),
                                        steplength=Fittable(minval=0, maxval=2)),
              dx=.01, dt=.01)
model_gui(model)

Weibull CDF collapsing bounds

The Weibull function is a popular choice for collapsing bounds. (See, e.g., Hawkins et al. (2015).) This can be implemented using:

import numpy as np
from pyddm import Bound
class BoundCollapsingWeibull(Bound):
    name = "Weibull CDF collapsing bounds"
    required_parameters = ["a", "aprime", "lam", "k"]
    def get_bound(self, t, **kwargs):
        l = self.lam
        a = self.a
        aprime = self.aprime
        k = self.k
        return a - (1 - np.exp(-(t/l)**k)) * (a - aprime)

Try it out with:

from pyddm import Model, Fittable
from pyddm.plot import model_gui
model = Model(bound=BoundCollapsingWeibull(a=Fittable(minval=1, maxval=2),
                                           aprime=Fittable(minval=0, maxval=1),
                                           lam=Fittable(minval=0, maxval=2),
                                           k=Fittable(minval=0, maxval=5)),
              dx=.01, dt=.01)
model_gui(model)

(Note that in Hakwins et al. (2015), diffusion goes from [0,1], whereas our diffusion goes from [-1,1]. Thus, the 0.5 term was removed.)

Delayed collapsing bounds

The following implements exponentially collapsing bounds with a delay:

class BoundCollapsingExponentialDelay(Bound):
    """Bound collapses exponentially over time.

    Takes three parameters: 

    `B` - the bound at time t = 0.
    `tau` - the time constant for the collapse, should be greater than
    zero.
    `t1` - the time at which the collapse begins, in seconds
    """
    name = "Delayed exponential collapsing bound"
    required_parameters = ["B", "tau", "t1"]
    def get_bound(self, t, conditions, **kwargs):
        if t <= self.t1:
            return self.B
        if t > self.t1:
            return self.B * np.exp(-self.tau*(t-self.t1))

Bounds which depend on task conditions (e.g. speed vs accuracy tradeoff)

As an example of bounds which depend on a task conditions, we assume a task in which a subject is cued before the stimulus about whether to prioritize speed or accuracy. The following model could test the hypothesis that the subject changes their integration bound to be high for the accuracy condition and low for the speed condition.

from pyddm import Bound
class BoundSpeedAcc(Bound):
    name = "constant"
    required_parameters = ["Bacc", "Bspeed"]
    required_conditions = ['speed_trial']
    def get_bound(self, conditions, *args, **kwargs):
        assert self.Bacc > 0
        assert self.Bspeed > 0
        if conditions['speed_trial'] == 1:
            return self.Bspeed
        else:
            return self.Bacc

Try it out with:

from pyddm import Model, Fittable
from pyddm.plot import model_gui
model = Model(bound=BoundSpeedAcc(Bacc=Fittable(minval=.5, maxval=1.5),
                                  Bspeed=Fittable(minval=0, maxval=1)),
              dx=.01, dt=.01)
model_gui(model, conditions={"speed_trial": [0, 1]})

Increasing bounds

In addition to collapsing bounds, PyDDM also supports increasing bounds, or bounds which both increase and decrease over time. Note that performance is proportional to the maximum size of the bounds, so very large bounds should be avoided.

For example, the following bounds are constant from t=0 until t=1, increase from t=1 until t=1.2, decrease from t=1.2 until t=1.4, and then are again constant:

import pyddm as ddm
class BoundIncreasingAndDecreasing(ddm.Bound):
    name = "Increasing bound"
    required_conditions = []
    required_parameters = []
    def get_bound(self, t, *args, **kwargs):
        if t > 1 and t < 1.4:
            return 1 + (.2-abs(t-1.2))*3
        else:
            return 1