def test_OverlayPoissonMixture(self): """Poisson mixture model overlay: an exponential distribution plus the model's solved distribution""" # Do nothing with mixture coef 0 s = ddm.Model(drift=ddm.models.DriftConstant(drift=1)).solve() smix = ddm.models.OverlayPoissonMixture(pmixturecoef=0, rate=1).apply(s) assert s == smix # With mixture coef 1, integrate to 1 s = ddm.Model(drift=ddm.models.DriftConstant(drift=2), noise=ddm.models.NoiseConstant(noise=3)).solve() smix = ddm.models.OverlayPoissonMixture(pmixturecoef=1, rate=10).apply(s) assert np.isclose(np.sum(smix.corr) + np.sum(smix.err), 1) # Should be monotonic decreasing on uniform distribution s = self.FakeUniformModel(dt=.001).solve() smix = ddm.models.OverlayPoissonMixture(pmixturecoef=.2, rate=1).apply(s) assert np.all([ smix.corr[i - 1] - smix.corr[i] > 0 for i in range(1, len(smix.corr)) ]) assert np.all([ smix.err[i - 1] - smix.err[i] > 0 for i in range(1, len(smix.err)) ]) # Don't change total probability s = ddm.Model(ddm.models.DriftConstant(drift=1)).solve() smix = ddm.models.OverlayPoissonMixture(pmixturecoef=.2, rate=7).apply(s) assert np.isclose( np.sum(s.corr) + np.sum(s.err), np.sum(smix.corr) + np.sum(smix.err))
def setUp(self): self.basic = ddm.Model(dx=.005, dt=.01, T_dur=2, drift=ddm.DriftConstant(drift=.4), noise=ddm.NoiseConstant(noise=1), bound=ddm.BoundConstant(B=1)) class NoiseCond(ddm.Noise): name = "Noise with a condition" required_conditions = ['cond'] required_parameters = [] def get_noise(self, conditions, **kwargs): return conditions["cond"] self.withcond = ddm.Model(noise=NoiseCond()) class FancyBounds(ddm.Bound): name = "Increasing/decreasing bounds" required_conditions = [] required_parameters = [] def get_bound(self, conditions, t, **kwargs): if t <= 1: return 1 + t if t > 1: return 2 / t self.bound = ddm.Model(bound=FancyBounds())
def setUp(self): class DriftSimple(ddm.Drift): name = "Test drift" required_conditions = ['coher'] required_parameters = [] def get_drift(self, conditions, **kwargs): return conditions["coher"] class DriftSimpleStringArg(ddm.Drift): name = "Test drift" required_conditions = ['type'] required_parameters = [] def get_drift(self, conditions, **kwargs): if conditions['type'] == "a": return .3 else: return .1 class DriftSimpleTupleArg(ddm.Drift): name = "Test drift" required_conditions = ['cohs'] required_parameters = [] def get_drift(self, conditions, **kwargs): return conditions['cohs'][0] # No undecided self.quick_ana = ddm.Model(T_dur=2, dt=.02).solve_analytical() # Includes undecided self.quick_cn = ddm.Model(T_dur=.5).solve_numerical_cn() # Includes undecided self.quick_imp = ddm.Model(T_dur=.5).solve_numerical_implicit() # No undecided, with parameters self.params_ana = ddm.Model(drift=DriftSimple(), T_dur=2.5, dt=.005).solve_analytical({"coher": .3}) # Includes undecided, with parameters self.params_cn = ddm.Model( drift=DriftSimple(), T_dur=.5).solve_numerical_cn(conditions={"coher": .1}) # Includes undecided, with parameters self.params_imp = ddm.Model( drift=DriftSimple(), T_dur=.5).solve_numerical_implicit(conditions={"coher": .1}) # Dependence with a string argument self.params_strarg = ddm.Model( drift=DriftSimpleStringArg(), T_dur=.5).solve_analytical(conditions={"type": "a"}) # Dependence with a tuple argument self.params_tuplearg = ddm.Model( drift=DriftSimpleTupleArg(), T_dur=.5).solve_analytical(conditions={"cohs": (.2, 1, 2)}) self.all_sols = [ self.quick_ana, self.quick_cn, self.quick_imp, self.params_ana, self.params_cn, self.params_imp, self.params_strarg, self.params_tuplearg ]
def setUp(self): from integration_test_models import DriftCond self.DriftCond = DriftCond self.cond_m = ddm.Model(drift=self.DriftCond(param=1)) self.cond_s = self.cond_m.solve(conditions={"cond": .1}).resample(4000) + \ self.cond_m.solve(conditions={"cond": 1}).resample(4000) + \ self.cond_m.solve(conditions={"cond": 2}).resample(4000)
def test_analytic_lin_collapse(self): """Make sure linearly collapsing bounds stops at 0""" # Will collapse to 0 by t=1 b = ddm.models.bound.BoundCollapsingLinear(B=1, t=1) m = ddm.Model(bound=b, T_dur=2) s = m.solve() assert len(s.pdf_corr()) == len(m.t_domain())
def test_OverlaySimplePause(self): """Pause at some point in the trial and then continue, leaving 0 probability in the gap""" # Should do nothing with no shift s = ddm.Model().solve() assert s == ddm.models.OverlaySimplePause(pausestart=.4, pausestop=.4).apply(s) # Shift should make a gap in the uniform model s = self.FakeUniformModel().solve() smix = ddm.models.OverlaySimplePause(pausestart=.3, pausestop=.6).apply(s) assert len(set(smix.corr).union(set(smix.err))) == 2 assert len(list(groupby( smix.corr))) == 3 # Looks like ----____---------- # Should start with 0 and then go to constant with pausestart=.3 s = self.FakeUniformModel(dt=.01).solve() smix = ddm.models.OverlaySimplePause(pausestart=0, pausestop=.05).apply(s) assert len(set(smix.corr).union(set(smix.err))) == 2 assert len(list(groupby(smix.corr))) == 2 # Looks like ____---------- assert np.all(smix.corr[0:5] == 0) and smix.corr[6] != 0 # Truncate when time bin doesn't align s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlaySimplePause(pausestart=.01, pausestop=.029).apply(s) assert s.corr[1] == sshift.corr[2] assert s.err[1] == sshift.err[2]
def test_fit_drift(self): """A simple one-parameter fit""" m = ddm.Model(name="DDM", drift=ddm.DriftConstant(drift=2)) s = m.solve() sample = s.resample(10000) mfit = ddm.Model( name="DDM", drift=ddm.DriftConstant(drift=ddm.Fittable(minval=0, maxval=10))) ddm.fit_adjust_model(model=mfit, sample=sample) # Within 10% if SHOW_PLOTS: mfit.name = "Fitted solution" sfit = mfit.solve() plot_compare_solutions(s, sfit) plt.show() _verify_param_match("drift", "drift", m, mfit)
def test_OverlayNonDecisionUniform(self): """Uniform-distributed non-decision time shifts the histogram""" # Should give the same results as OverlayNonDecision when halfwidth=0 s = ddm.Model().solve() for nondectime in [0, -.1, .01, .0099, .011111, 1]: ndunif = ddm.models.OverlayNonDecisionUniform( nondectime=nondectime, halfwidth=0).apply(s) ndpoint = ddm.models.OverlayNonDecision( nondectime=nondectime).apply(s) assert np.all(np.isclose(ndunif.corr, ndpoint.corr)), (nondectime, list(ndunif.corr), list(ndpoint.corr)) assert np.all(np.isclose(ndunif.err, ndpoint.err)) # Simple shift example s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayNonDecisionUniform(nondectime=.02, halfwidth=.01).apply(s) assert sshift.corr[2] == sshift.corr[3] == sshift.corr[4] assert sshift.err[2] == sshift.err[3] == sshift.err[4] assert sshift.corr[0] == sshift.corr[1] == sshift.corr[5] == 0 assert sshift.err[0] == sshift.err[1] == sshift.err[5] == 0 # Off-boundary and behind 0 example s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayNonDecisionUniform( nondectime=.021111, halfwidth=.033333).apply(s) assert sshift.corr[0] == sshift.corr[1] assert sshift.err[0] == sshift.err[1] assert len(set(sshift.corr)) == 2 assert len(set(sshift.err)) == 2
def test_OverlayBlurredPause(self): """Like OverlaySimplePause but with a gamma distribution on delay times""" # Don't change total probability when there are no undecided responses s = ddm.Model(drift=ddm.models.DriftConstant(drift=1), T_dur=10).solve() smix = ddm.models.OverlayBlurredPause(pausestart=.3, pausestop=.6, pauseblurwidth=.1).apply(s) assert np.isclose( np.sum(s.corr) + np.sum(s.err), np.sum(smix.corr) + np.sum(smix.err)) # Make sure responses before the pause aren't affected s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayBlurredPause(pausestart=.02, pausestop=.03, pauseblurwidth=.002).apply(s) assert s.corr[1] == sshift.corr[1] != 0 assert s.err[1] == sshift.err[1] != 0 # Make sure responses after look like a gamma distribution s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayBlurredPause(pausestart=0, pausestop=.05, pauseblurwidth=.01).apply(s) positive = (sshift.corr[2:] > sshift.err[1:-1]).astype( int ) # Excluding first 0 point, should go from + to - slope only once assert positive[0] == 1 and positive[-1] == 0 and len( set(positive)) == 2
def test_OverlayNone(self): """No overlay""" s = ddm.Model().solve() assert s == ddm.models.OverlayNone().apply(s) s = self.FakeUniformModel().solve() assert s == ddm.models.OverlayNone().apply(s) s = self.FakePointModel().solve() assert s == ddm.models.OverlayNone().apply(s)
def setUp(self): self.basic = ddm.Model(dx=.005, dt=.01, T_dur=2, drift=ddm.DriftConstant(drift=.4), noise=ddm.NoiseConstant(noise=1), bound=ddm.BoundConstant(B=1)) class NoiseCond(ddm.Noise): name = "Noise with a condition" required_conditions = ['cond'] required_parameters = [] def get_noise(self, conditions, **kwargs): return conditions["cond"] self.withcond = ddm.Model(noise=NoiseCond())
def test_collapsing_bounds(self): """Bounds collapse to zero""" m = ddm.Model(bound=ddm.BoundCollapsingLinear(B=1, t=2)) _modeltest_numerical_vs_analytical(m, method="implicit", max_diff=.3, mean_diff=.2, prob_diff=.05)
def test_OverlayUniformMixture(self): """Uniform mixture model overlay: a uniform distribution plus the model's solved distribution""" # Do nothing with 0 probability s = ddm.Model(drift=ddm.models.DriftConstant(drift=1)).solve() smix = ddm.models.OverlayUniformMixture(umixturecoef=0).apply(s) assert s == smix # With mixture coef 1, integrate to 1 s = ddm.Model(drift=ddm.models.DriftConstant(drift=2), noise=ddm.models.NoiseConstant(noise=3)).solve() smix = ddm.models.OverlayUniformMixture(umixturecoef=1).apply(s) assert np.isclose(np.sum(smix.corr) + np.sum(smix.err), 1) # Should not change uniform distribution s = self.FakeUniformModel(dt=.001).solve() assert s == ddm.models.OverlayUniformMixture(umixturecoef=.2).apply(s) # Don't change total probability s = ddm.Model(drift=ddm.models.DriftConstant(drift=1)).solve() smix = ddm.models.OverlayUniformMixture(umixturecoef=.2).apply(s) assert np.isclose(np.sum(s.corr) + np.sum(s.err), np.sum(smix.corr) + np.sum(smix.err))
def test_ICPoint_collapsing_bounds(self): m = ddm.Model(name='ICPoint_BCollapsingLin_test', drift=ddm.DriftConstant(drift=2), noise=ddm.NoiseConstant(noise=1.5), bound=ddm.BoundCollapsingLinear(B=1, t=0.5), IC=ddm.ICPoint(x0=-.25)) _modeltest_numerical_vs_analytical(m, method="implicit", max_diff=.3, mean_diff=.2, prob_diff=.05)
def test_ICPoint(self): """Arbitrary pointwise initial condition""" m = ddm.Model(name='ICPoint_test', drift=ddm.DriftConstant(drift=2), noise=ddm.NoiseConstant(noise=1.5), bound=ddm.BoundConstant(B=1), IC=ddm.ICPoint(x0=-.25)) _modeltest_numerical_vs_analytical(m, method="implicit", max_diff=.3, mean_diff=.2, prob_diff=.05)
def test_get_set_parameters_functions(self): """Test get_parameters, set_parameters, and get_parameter_names""" p1 = ddm.Fittable(minval=0, maxval=1) p2 = ddm.Fittable(minval=.3, maxval=.9, default=.4) m = ddm.Model(drift=ddm.DriftConstant(drift=p1), noise=ddm.NoiseLinear(noise=p2, x=.2, t=p1)) print(m.get_model_parameters()) assert all(id(a) == id(b) for a,b in zip(m.get_model_parameters(), [p1, p2])) assert all(a == b for a,b in zip(m.get_model_parameter_names(), ["drift/t", "noise"])) m.set_model_parameters(m.get_model_parameters()) assert all(id(a) == id(b) for a,b in zip(m.get_model_parameters(), [p1, p2])) m.set_model_parameters([.5, .5]) assert all(a == b for a,b in zip(m.get_model_parameters(), [.5, .5]))
def test_overlay_chain_distribution_integrates_to_1(self): """Overlays integrate to 1""" m = ddm.Model(name="Overlay_test", drift=ddm.DriftConstant(drift=2), T_dur=5, overlay=ddm.OverlayChain(overlays=[ ddm.OverlayPoissonMixture(pmixturecoef=.2, rate=2), ddm.OverlayUniformMixture(umixturecoef=.2), ddm.OverlayNonDecision(nondectime=.2) ])) s = m.solve() distsum = s.prob_correct() + s.prob_error() assert .99 < distsum < 1.0001, "Distribution doesn't sum to 1"
def test_fit_with_condition(self): """A simple one-parameter fit with conditions""" m = self.cond_m s = self.cond_s mfit = ddm.Model(drift=self.DriftCond( param=ddm.Fittable(minval=.1, maxval=3))) ddm.fit_adjust_model(model=mfit, sample=s) # Within 10% if SHOW_PLOTS: mfit.name = "Fitted solution" sfit = mfit.solve() plot_compare_solutions(s, sfit) plt.show() _verify_param_match("drift", "param", m, mfit)
def test_ICArbitrary(self): """Arbitrary starting conditions from a distribution""" # Make sure we get out the same distribution we put in m = ddm.Model() unif = ddm.models.ICUniform() unif_a = ddm.models.ICArbitrary(unif.get_IC(m.x_domain({}))) assert np.all(unif.get_IC(m.x_domain({})) == unif_a.get_IC(m.x_domain({}))) point = ddm.models.ICPointSourceCenter() point_a = ddm.models.ICArbitrary(point.get_IC(m.x_domain({}))) assert np.all(point.get_IC(m.x_domain({})) == point_a.get_IC(m.x_domain({}))) # Make sure the distribution integrates to 1 fails(lambda : ddm.models.ICArbitrary(aa([.1, .1, 0, 0, 0]))) fails(lambda : ddm.models.ICArbitrary(aa([0, .6, .6, 0]))) assert ddm.models.ICArbitrary(aa([1]))
def __init__(self, ndt='gaussian'): overlay = (ddm.OverlayNonDecisionUniform(nondectime=ddm.Fittable(minval=0, maxval=0.5), halfwidth=ddm.Fittable(minval=0.001, maxval=0.3)) if ndt=='uniform' else self.OverlayNonDecisionGaussian(nondectime=ddm.Fittable(minval=0, maxval=0.6), ndsigma=ddm.Fittable(minval=0.001, maxval=0.3))) self.model = ddm.Model(name='5 TTA- and d-dependent drift and bounds and uniformly distributed nondecision time', drift=self.DriftTtaDistance(alpha=ddm.Fittable(minval=0.1, maxval=3), beta=ddm.Fittable(minval=0, maxval=1), theta=ddm.Fittable(minval=4, maxval=40)), noise=ddm.NoiseConstant(noise=1), bound=self.BoundCollapsingTta(b_0=ddm.Fittable(minval=0.5, maxval=5), k=ddm.Fittable(minval=0.1, maxval=2), tta_crit=ddm.Fittable(minval=3, maxval=6)), overlay=overlay, T_dur=self.T_dur)
def test_OverlayNonDecision(self): """Non-decision time shifts the histogram""" # Should do nothing with no shift s = ddm.Model().solve() assert s == ddm.models.OverlayNonDecision(nondectime=0).apply(s) # Shifts a single point distribution s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayNonDecision(nondectime=.01).apply(s) assert s.corr[1] == sshift.corr[2] assert s.err[1] == sshift.err[2] # Shift the other way s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayNonDecision(nondectime=-.01).apply(s) assert s.corr[1] == sshift.corr[0] assert s.err[1] == sshift.err[0] # Truncate when time bin doesn't align s = self.FakePointModel(dt=.01).solve() sshift = ddm.models.OverlayNonDecision(nondectime=.019).apply(s) assert s.corr[1] == sshift.corr[2] assert s.err[1] == sshift.err[2]
def test_double_fit(self): """Fit different parameters in the same (or a different) model using a single Fittable object""" class NoiseDouble(ddm.Noise): name = "time-varying noise" required_parameters = ["noise1", "noise2"] def get_noise(self, **kwargs): if np.random.rand() > .5: return self.noise1 else: return self.noise2 class NoiseConstantButNot(ddm.Noise ): # To avoid the numerical simulations name = "almost noise constant" required_parameters = ["noise"] def get_noise(self, **kwargs): return self.noise # Generate data m = ddm.Model(name="DDM", drift=ddm.DriftConstant(drift=1), noise=ddm.NoiseConstant(noise=1.7)) s = m.solve_numerical( ) # Solving analytical and then fitting numerical may give a bias sample = s.resample(10000) mone = ddm.fit_model( sample, drift=ddm.DriftConstant(drift=1), noise=NoiseConstantButNot(noise=ddm.Fittable(minval=.5, maxval=3))) sigs = ddm.Fittable(minval=.5, maxval=3) msam = ddm.fit_model(sample, drift=ddm.DriftConstant(drift=1), noise=NoiseDouble(noise1=sigs, noise2=sigs)) assert msam._noisedep.noise1 == msam._noisedep.noise2, "Fitting to be the same failed" assert abs(msam._noisedep.noise1 - mone._noisedep.noise) < 0.1 * mone._noisedep.noise
m = ddm.Model( drift=DriftDip( snr=snr, noise=noise, t1=t1, t1slope=t1slope, leak=leak, maxcoh=70, leaktarget=x0, leaktargramp=leaktargramp, dipstart=dipstart, dipstop=dipstop, diptype=diptype, dipparam=dipparam, ), noise=NoiseDip( noise=noise, t1=t1, t1slope=t1slope, dipstart=dipstart, dipstop=dipstop, diptype=diptype, ), IC=ICPoint(x0=x0), bound=BoundDip(B=1, dipstart=dipstart, dipstop=dipstop, diptype=diptype), overlay=ddm.OverlayChain(overlays=[ ddm.OverlayNonDecision(nondectime=nondectime), OverlayDipRatio(detect=detect, diptype=diptype), ddm.OverlayPoissonMixture(pmixturecoef=pmixturecoef, rate=rate) ]), dx=0.002, dt=0.002, T_dur=3.0)