Model components

Drift rate

class pyddm.models.drift.Drift(**kwargs)[source]

Bases: pyddm.models.base.Dependence

Subclass this to specify how drift rate varies with position and time.

This abstract class provides the methods which define a dependence of drift on x and t. To subclass it, implement get_drift. Since it inherits from Dependence, subclasses must also assign a name and required_parameters (see documentation for Dependence.)

get_drift(t, x, conditions, **kwargs)[source]

Calculate the instantaneous drift rate.

This function must be redefined in subclasses.

It may take several arguments:

  • t - The time at which drift should be calculated
  • x - The particle position (or 1-dimensional NDArray of particle positions) at which drift should be calculated
  • conditions - A dictionary describing the task conditions

It should return a number or an NDArray (the same as x) indicating the drift rate at that particular time, position(s), and task conditions.

Definitions of this method in subclasses should only have arguments for needed variables and should always be followed by “**kwargs”. For example, if the function does not depend on t or x but does depend on task conditions, this should be:

def get_drift(self, conditions, **kwargs):

Of course, the function would still work properly if x were included as an argument, but this convention allows PyDDM to automatically select the best simulation methods for the model.

If a function depends on x, it should return a scalar if x is a scalar, or an NDArray of the same size as x if x is an NDArray. If the function does not depend on x, it should return a scalar. (The purpose of this is a dramatic speed increase by using numpy vectorization.)

get_flux(x_bound, t, dx, dt, conditions, **kwargs)[source]

The drift component of flux across the boundary at position x_bound at time t.

Flux here is essentially the amount of the mass of the PDF that is past the boundary point x_bound.

There is generally no need to redefine this method in subclasses.

get_matrix(x, t, dx, dt, conditions, implicit=False, **kwargs)[source]

The drift component of the implicit method diffusion matrix across the domain x at time t.

x should be a length N ndarray of all positions in the grid. t should be the time in seconds at which to calculate drift. dt and dx should be the simulations timestep and grid step conditions should be the conditions at which to calculate drift

Returns a sparse NxN matrix as a PyDDM TriDiagMatrix object.

There is generally no need to redefine this method in subclasses.

class pyddm.models.drift.DriftConstant(**kwargs)[source]

Bases: pyddm.models.drift.Drift

Drift dependence: drift rate is constant throughout the simulation.

Only take one parameter: drift, the constant drift rate.

Note that this is a special case of DriftLinear.

Example usage:

drift = DriftConstant(drift=0.3)
class pyddm.models.drift.DriftLinear(**kwargs)[source]

Bases: pyddm.models.drift.Drift

Drift dependence: drift rate varies linearly with position and time.

Take three parameters:

  • drift - The starting drift rate
  • x - The coefficient by which drift varies with x
  • t - The coefficient by which drift varies with t

Example usage:

drift = DriftLinear(drift=0.5, t=0, x=-1) # Leaky integrator
drift = DriftLinear(drift=0.8, t=0, x=0.4) # Unstable integrator
drift = DriftLinear(drift=0, t=1, x=0.4) # Urgency function

Noise

class pyddm.models.noise.Noise(**kwargs)[source]

Bases: pyddm.models.base.Dependence

Subclass this to specify how noise level varies with position and time.

This abstract class provides the methods which define a dependence of noise on x and t. To subclass it, implement get_noise. Since it inherits from Dependence, subclasses must also assign a name and required_parameters (see documentation for Dependence.)

get_flux(x_bound, t, dx, dt, conditions, **kwargs)[source]

The diffusion component of flux across the boundary at position x_bound at time t.

Flux here is essentially the amount of the mass of the PDF that is past the boundary point x_bound at time t (in seconds).

Note that under the central scheme we want to use x at half-grid from the boundary. This is however cleaner and justifiable using forward/backward scheme.

There is generally no need to redefine this method in subclasses.

get_matrix(x, t, dx, dt, conditions, implicit=False, **kwargs)[source]

The diffusion component of the implicit method diffusion matrix across the domain x at time t.

x should be a length N ndarray of all positions in the grid. t should be the time in seconds at which to calculate noise. dt and dx should be the simulations timestep and grid step conditions should be the conditions at which to calculate noise

Returns a sparse NxN matrix as a PyDDM TriDiagMatrix object.

There is generally no need to redefine this method in subclasses.

get_noise(conditions, **kwargs)[source]

Calculate the instantaneous noise (standard deviation of noise).

This function must be redefined in subclasses.

It may take several arguments:

  • t - The time at which noise should be calculated
  • x - The particle position (or 1-dimensional NDArray of particle positions) at which noise should be calculated
  • conditions - A dictionary describing the task conditions

It should return a number or an NDArray (the same as x) indicating the standard deviation of the noise at that particular time, position(s), and task conditions.

Definitions of this method in subclasses should only have arguments for needed variables and should always be followed by “**kwargs”. For example, if the function does not depend on t or x but does depend on task conditions, this should be:

def get_noise(self, conditions, **kwargs):

Of course, the function would still work properly if x were included as an argument, but this convention allows PyDDM to automatically select the best simulation methods for the model.

If a function depends on x, it should return a scalar if x is a scalar, or an NDArray of the same size as x if x is an NDArray. If the function does not depend on x, it should return a scalar. (The purpose of this is a dramatic speed increase by using numpy vectorization.)

class pyddm.models.noise.NoiseConstant(**kwargs)[source]

Bases: pyddm.models.noise.Noise

Noise level is constant over time.

Only take one parameter: noise, the standard deviation of the noise.

Note that this is a special case of NoiseLinear.

Example usage:

noise = NoiseConstant(noise=0.5)
class pyddm.models.noise.NoiseLinear(**kwargs)[source]

Bases: pyddm.models.noise.Noise

Noise level varies linearly with position and time.

Take three parameters:

  • noise - The inital noise standard deviation
  • x - The coefficient by which noise standard deviation varies with x
  • t - The coefficient by which noise standard deviation varies with t

Example usage:

noise = NoiseLinear(noise=0.5, x=0, t=.1) # Noise increases over time

Bounds

class pyddm.models.bound.Bound(**kwargs)[source]

Bases: pyddm.models.base.Dependence

Subclass this to specify how bounds vary with time.

This abstract class provides the methods which define a dependence of the bounds on t. To subclass it, implement get_bound. All bounds must be symmetric, so the lower bound is -get_bound.

Also, since it inherits from Dependence, subclasses must also assign a name and required_parameters (see documentation for Dependence.)

get_bound(t, conditions, **kwargs)[source]

Calculate the bounds which particles cross to determine response time.

This function must be redefined in subclasses.

It may take up to two arguments:

  • t - The time at which bound should be calculated
  • conditions - A dictionary describing the task conditions

It should return a non-negative number indicating the upper bound at that particular time, and task conditions. The lower bound is taken to be the negative of the upper bound.

Definitions of this method in subclasses should only have arguments for needed variables and should always be followed by “**kwargs”. For example, if the function does not depend on task conditions but does depend on time, this should be:

def get_bound(self, t, **kwargs):

Of course, the function would still work properly if conditions were included as an argument, but this convention allows PyDDM to automatically select the best simulation methods for the model.

class pyddm.models.bound.BoundConstant(**kwargs)[source]

Bases: pyddm.models.bound.Bound

Bound dependence: bound is constant throuhgout the simulation.

Takes only one parameter: B, the constant bound.

Example usage:

bound = BoundConstant(B=1.5) # Bound at 1.5 and -1.5
class pyddm.models.bound.BoundCollapsingLinear(**kwargs)[source]

Bases: pyddm.models.bound.Bound

Bound dependence: bound collapses linearly over time.

Takes two parameters:

  • B - the bound at time t = 0.
  • t - the slope, i.e. the coefficient of time, should be greater than zero.

Example usage:

bound = BoundCollapsingLinear(B=1, t=.5) # Collapsing at .5 units per second
class pyddm.models.bound.BoundCollapsingExponential(**kwargs)[source]

Bases: pyddm.models.bound.Bound

Bound dependence: bound collapses exponentially over time.

Takes two parameters:

  • B - the bound at time t = 0.
  • tau - one divided by the time constant for the collapse. 0 gives constant bounds.

Example usage:

bound = BoundCollapsingExponential(B=1, tau=2.1) # Collapsing with time constant 1/2.1

Initial Conditions (IC)

class pyddm.models.ic.InitialCondition(**kwargs)[source]

Bases: pyddm.models.base.Dependence

Subclass this to compute the initial conditions of the simulation.

This abstract class describes initial PDF at the beginning of a simulation. To subclass it, implement get_IC(x).

Also, since it inherits from Dependence, subclasses must also assign a name and required_parameters (see documentation for Dependence.)

get_IC(x, dx, **kwargs)[source]

Get the initial conditions (a PDF) withsupport x.

This function must be redefined in subclasses.

x is a length N ndarray representing the support of the initial condition PDF, i.e. the x-domain. This returns a length N ndarray describing the distribution.

class pyddm.models.ic.ICPointSourceCenter(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: a dirac delta function in the center of the domain.

Example usage:

ic = ICPointSourceCenter()
class pyddm.models.ic.ICPoint(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: any point.

Example usage:

ic = ICPoint(x0=.2)
class pyddm.models.ic.ICPointRatio(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: any point expressed as a ratio between bounds, from -1 to 1.

Example usage:

ic = ICPointRatio(x0=-.2)

The advantage of ICPointRatio over ICPoint is that, as long as x0 is greater than -1 and less than 1, the starting point will always stay within the bounds, even when bounds are being fit.

class pyddm.models.ic.ICUniform(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: a uniform distribution.

Example usage:

ic = ICUniform()
class pyddm.models.ic.ICRange(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: a bounded uniform distribution with range from -sz to sz.

Example usage:

ic = ICRange(sz=.3)
class pyddm.models.ic.ICGaussian(**kwargs)[source]

Bases: pyddm.models.ic.InitialCondition

Initial condition: a Gaussian distribution with a specified standard deviation.

Example usage:

ic = ICRange(sz=.3)
pyddm.models.ic.ICArbitrary(dist)[source]

Generate an IC object from an arbitrary distribution.

dist should be a 1 dimensional numpy array which sums to 1.

Note that ICArbitrary is a function, not an InitialCondition object, so it cannot be passed directly. It returns an instance of a an InitialCondition object which can be passed. So in place of, e.g. ICUniform(). In practice, the user should not notice a difference, and this function can thus be used in place of an InitialCondition object.

Example usage:

import scipy.stats
ic = ICArbitrary(dist=scipy.stats.binom.pmf(n=200, p=.4, k=range(0, 201))) # Binomial distribution
import numpy as np
ic = ICArbitrary(dist=np.asarray([0]*100+[1]+[0]*100)) # Equivalent to ICPointSourceCenter for dx=.01

Overlay

class pyddm.models.overlay.Overlay(**kwargs)[source]

Bases: pyddm.models.base.Dependence

Subclasses can modify distributions after they have been generated.

This abstract class provides the methods which define how a distribution should be modified after solving the model, for example for a mixture model. To subclass it, implement apply.

Also, since it inherits from Dependence, subclasses must also assign a name and required_parameters (see documentation for Dependence.)

apply(solution)[source]

Apply the overlay to a Solution object.

This function must be redefined in subclasses.

This function takes a Solution object as its argument and returns a Solution object which was modified in some way. Often times, this will be by modifying solution.corr and solution.choice_lower. See the documentation for Solution for more information about this object.

Note that while this does not take conditions as an argument, conditions may still be accessed via solution.conditions.

Conceptually, this function performs some transformation on the simulated response time (first passage time) distributions. It is especially useful for non-decision times and mixture models, potentially in a parameter-dependent or condition-dependent manner.

apply_trajectory(trajectory, model, rk4, seed, conditions={})[source]

Apply the overlay to a simulated decision variable trajectory.

This function is optional and may be redefined in subclasses. It is expected to implement the same mechanism as the method “apply”, but to do so on simulated trajectories (i.e. from Model.simulate_trial) instead of on a Solution object.

This function takes the t domain, the trajectory itself, and task conditions. It returns the modified trajectory.

class pyddm.models.overlay.OverlayNone(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

No overlay. An identity function for Solutions.

Example usage:

overlay = OverlayNone()
class pyddm.models.overlay.OverlayChain(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

Join together multiple overlays.

Unlike other model components, Overlays are not mutually exclusive. It is possible to transform the output solution many times. Thus, this allows joining together multiple Overlay objects into a single object.

It accepts one parameter: overlays. This should be a list of Overlay objects, in the order which they should be applied to the Solution object.

One key technical caveat is that the overlays which are chained together may not have the same parameter names. Parameter names must be given different names in order to be a part of the same overlay. This allows those parameters to be accessed by their name inside of an OverlayChain object.

Example usage:

overlay = OverlayChain(overlays=[OverlayNone(), OverlayNone(), OverlayNone()]) # Still equivalent to OverlayNone
overlay = OverlayChain(overlays=[OverlayPoissonMixture(pmixturecoef=.01, rate=1),
OverlayUniformMixture(umixturecoef=.01)]) # Apply a Poission mixture and then a Uniform mixture
class pyddm.models.overlay.OverlayUniformMixture(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

A uniform mixture distribution.

The output distribution should be umixturecoef*100 percent uniform distribution and (1-umixturecoef)*100 percent of the distribution to which this overlay is applied.

A mixture with the uniform distribution can be used to confer robustness when fitting using likelihood.

Example usage:

overlay = OverlayUniformMixture(umixturecoef=.01)
class pyddm.models.overlay.OverlayExponentialMixture(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

An exponential mixture distribution.

The output distribution should be pmixturecoef*100 percent exponential distribution and (1-umixturecoef)*100 percent of the distribution to which this overlay is applied.

A mixture with the exponential distribution can be used to confer robustness when fitting using likelihood.

Note that this is called OverlayPoissonMixture and not OverlayExponentialMixture because the exponential distribution is formed from a Poisson process, i.e. modeling a uniform lapse rate.

Example usage:

overlay = OverlayPoissonMixture(pmixturecoef=.02, rate=1)
class pyddm.models.overlay.OverlayNonDecision(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

Add a non-decision time

This shifts the reaction time distribution by nondectime seconds in order to create a non-decision time.

Example usage:

overlay = OverlayNonDecision(nondectime=.2)

This can also be subclassed to allow easily shifting the non-decision time. When subclassing, override the get_nondecision_time(self, conditions) method to be any function you wish, using both conditions and parameters.

class pyddm.models.overlay.OverlayNonDecisionGamma(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

Add a gamma-distributed non-decision time

This shifts the reaction time distribution by an amount of time specified by the gamma distribution with shape parameter shape (sometimes called “k”) and scale parameter scale (sometimes called “theta”). The distribution is then further shifted by nondectime seconds.

Example usage:

overlay = OverlayNonDecisionGamma(nondectime=.2, shape=1.5, scale=.05)

This can also be subclassed to allow easily shifting the non-decision time. When subclassing, override the get_nondecision_time(self, conditions) method to be any function you wish, using both conditions and parameters.

class pyddm.models.overlay.OverlayNonDecisionUniform(**kwargs)[source]

Bases: pyddm.models.overlay.Overlay

Add a uniformly-distributed non-decision time.

The center of the distribution of non-decision times is at nondectime, and it extends halfwidth on each side.

Example usage:

overlay = OverlayNonDecisionUniform(nondectime=.2, halfwidth=.02)

This can also be subclassed to allow easily shifting the non-decision time. When subclassing, override the get_nondecision_time(self, conditions) method to be any function you wish, using both conditions and parameters.

Loss function (for optimization)

class pyddm.models.loss.LossFunction(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: object

An abstract class for a function to assess goodness of fit.

This is an abstract class for describing how well data fits a model.

When subclasses are initialized, they will be initialized with the Sample object to which the model should be fit. Because the data will not change but the model will change, this is specified with initialization.

The optional required_conditions argument limits the stratification of sample by conditions to only the conditions mentioned in required_conditions. This decreases computation time by only solving the model for the condition names listed in required_conditions. For example, a simple DDM with no drift and constant variaince would mean required_conditions is an empty list.

The optional method argument can be “analytical”, “numerical”, “cn”, “implicit”, or “explicit”.

This will automatically parallelize if set_N_cpus() has been called.

cache_by_conditions(model)[source]

Solve the model for all relevant conditions.

Solve model for each combination of conditions found within the dataset. For example, if required_conditions is [“hand”, “color”], and hand can be left or right and color can be blue or green, solves the model for: hand=left and color=blue; hand=right and color=blue; hand=left and color=green, hand=right and color=green.

This is a convenience function for defining new loss functions. There is generally no need to redefine this function in subclasses.

loss(model)[source]

Compute the value of the loss function for the given model.

This function must be redefined in subclasses.

model should be a Model object. This should return a floating point value, where smaller values mean a better fit of the model to the data.

setup(**kwargs)[source]

Initialize the loss function.

The optional setup function is executed at the end of the initializaiton. It is executed only once at the beginning of the fitting procedure.

This function may optionally be redefined in subclasses.

class pyddm.models.loss.LossSquaredError(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: pyddm.models.loss.LossFunction

Squared-error loss function

class pyddm.models.loss.LossLikelihood(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: pyddm.models.loss.LossFunction

Likelihood loss function

class pyddm.models.loss.LossBIC(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: pyddm.models.loss.LossLikelihood

BIC loss function, functionally equivalent to LossLikelihood

class pyddm.models.loss.LossRobustLikelihood(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: pyddm.models.loss.LossLikelihood

Likelihood loss function which will not fail for infinite likelihoods.

Usually you will want to use LossLikelihood instead. See the FAQs in the documentation for more information on how this differs from LossLikelihood.

class pyddm.models.loss.LossRobustBIC(sample, required_conditions=None, method=None, **kwargs)[source]

Bases: pyddm.models.loss.LossBIC

BIC loss function which will not fail for infinite likelihoods.

Usually you will want to use LossBIC instead. See the FAQs in the documentation for more information on how this differs from LossBIC.

Base

class pyddm.models.base.Dependence(**kwargs)[source]

Bases: object

An abstract class describing how one variable depends on other variables.

This is an abstract class which is inherrited by other abstract classes only, and has the highest level machinery for describing how one variable depends on others. For example, an abstract class that inherits from Dependence might describe how the drift rate may change throughout the simulation depending on the value of x and t, and then this class would be inherited by a concrete class describing an implementation. For example, the relationship between drift rate and x could be linear, exponential, etc., and each of these would be a subsubclass of Dependence.

In order to subclass Dependence, you must set the (static) class variable depname, which gives an alpha-numeric string describing which variable could potentially depend on other variables.

Each subsubclass of dependence must also define two (static) class variables. First, it must define name, which is an alpha-numeric plus underscores name of what the algorithm is, and also required_parameters, a python list of names (strings) for the parameters that must be passed to this algorithm. (This does not include globally-relevant variables like dt, it only includes variables relevant to a particular instance of the algorithm.) An optional (static) class variable is default_parameters, which is a dictionary indexed by the parameter names from required_parameters. Any parameters referenced here will be given a default value.

Dependence will check to make sure all of the required parameters have been supplied, with the exception of those which have default versions. It also provides other convenience and safety features, such as allowing tests for equality of derived algorithms and for ensuring extra parameters were not assigned.