Example #1
0
class OverlayNone(Overlay):
    """No overlay.  An identity function for Solutions.

    Example usage:

      | overlay = OverlayNone()
    """
    name = "No overlay"
    required_parameters = []

    @staticmethod
    def _test(v):
        pass

    @staticmethod
    def _generate():
        yield OverlayNone()

    @accepts(Self, Solution)
    @returns(Solution)
    def apply(self, solution):
        return solution

    @accepts(Self, NDArray(d=1, t=Number))
    @returns(NDArray(d=1, t=Number))
    def apply_trajectory(self, trajectory, **kwargs):
        return trajectory
Example #2
0
class OverlayNonDecision(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)
    """
    name = "Add a non-decision by shifting the histogram"
    required_parameters = ["nondectime"]
    @staticmethod
    def _test(v):
        assert v.nondectime in Number(), "Invalid non-decision time"
    @staticmethod
    def _generate():
        yield OverlayNonDecision(nondectime=0)
        yield OverlayNonDecision(nondectime=.5)
        yield OverlayNonDecision(nondectime=-.5)
    @accepts(Self, Solution)
    @returns(Solution)
    @ensures("set(return.corr.tolist()) - set(solution.corr.tolist()).union({0.0}) == set()")
    @ensures("set(return.err.tolist()) - set(solution.err.tolist()).union({0.0}) == set()")
    @ensures("solution.prob_undecided() <= return.prob_undecided()")
    def apply(self, solution):
        corr = solution.corr
        err = solution.err
        m = solution.model
        cond = solution.conditions
        undec = solution.undec
        evolution = solution.evolution
        shifts = int(self.nondectime/m.dt) # truncate
        newcorr = np.zeros(corr.shape, dtype=corr.dtype)
        newerr = np.zeros(err.shape, dtype=err.dtype)
        if shifts > 0:
            newcorr[shifts:] = corr[:-shifts]
            newerr[shifts:] = err[:-shifts]
        elif shifts < 0:
            newcorr[:shifts] = corr[-shifts:]
            newerr[:shifts] = err[-shifts:]
        else:
            newcorr = corr
            newerr = err
        return Solution(newcorr, newerr, m, cond, undec, evolution)
    @accepts(Self, NDArray(d=1, t=Number), Unchecked)
    @returns(NDArray(d=1, t=Number))
    def apply_trajectory(self, trajectory, model, **kwargs):
        shift = int(self.nondectime/model.dt)
        if shift > 0:
            trajectory = np.append([trajectory[0]]*shift, trajectory)
        elif shift < 0:
            if len(trajectory) > abs(shift):
                trajectory = trajectory[abs(shift):]
            else:
                trajectory = np.asarray([trajectory[-1]])
        return trajectory
Example #3
0
class OverlayNonDecisionGamma(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)

    """
    name = "Add a gamma-distributed non-decision time"
    required_parameters = ["nondectime", "shape", "scale"]

    @staticmethod
    def _test(v):
        assert v.nondectime in Number(), "Invalid non-decision time"
        assert v.shape in Positive0(), "Invalid shape parameter"
        assert v.shape >= 1, "Shape parameter must be >= 1"
        assert v.scale in Positive(), "Invalid scale parameter"

    @staticmethod
    def _generate():
        yield OverlayNonDecisionGamma(nondectime=.3, shape=2, scale=.01)
        yield OverlayNonDecisionGamma(nondectime=0, shape=1.1, scale=.1)

    @accepts(Self, Solution)
    @returns(Solution)
    @ensures("np.sum(return.corr) <= np.sum(solution.corr)")
    @ensures("np.sum(return.err) <= np.sum(solution.err)")
    @ensures(
        "np.all(return.corr[0:int(self.nondectime//return.model.dt)] == 0)")
    def apply(self, solution):
        # Make sure params are within range
        assert self.shape >= 1, "Invalid shape parameter"
        assert self.scale > 0, "Invalid scale parameter"
        # Extract components of the solution object for convenience
        corr = solution.corr
        err = solution.err
        dt = solution.model.dt
        # Create the weights for different timepoints
        times = np.asarray(list(range(-len(corr), len(corr)))) * dt
        weights = scipy.stats.gamma(a=self.shape,
                                    scale=self.scale,
                                    loc=self.nondectime).pdf(times)
        if np.sum(weights) > 0:
            weights /= np.sum(weights)  # Ensure it integrates to 1
        newcorr = np.convolve(corr, weights,
                              mode="full")[len(corr):(2 * len(corr))]
        newerr = np.convolve(err, weights,
                             mode="full")[len(corr):(2 * len(corr))]
        return Solution(newcorr, newerr, solution.model, solution.conditions,
                        solution.undec, solution.evolution)

    @accepts(Self, NDArray(d=1, t=Number), Unchecked)
    @returns(NDArray(d=1, t=Number))
    def apply_trajectory(self, trajectory, model, **kwargs):
        ndtime = scipy.stats.gamma(a=self.shape,
                                   scale=self.scale,
                                   loc=self.nondectime).rvs()
        shift = int(ndtime / model.dt)
        if shift > 0:
            np.append([trajectory[0]] * shift, trajectory)
        elif shift < 0:
            if len(trajectory) > abs(shift):
                trajectory = trajectory[abs(shift):]
            else:
                trajectory = np.asarray([trajectory[-1]])
        return trajectory
Example #4
0
class OverlayNonDecisionUniform(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)

    """
    name = "Uniformly-distributed non-decision time"
    required_parameters = ["nondectime", "halfwidth"]

    @staticmethod
    def _test(v):
        assert v.nondectime in Number(), "Invalid non-decision time"
        assert v.halfwidth in Positive0(), "Invalid halfwidth parameter"

    @staticmethod
    def _generate():
        yield OverlayNonDecisionUniform(nondectime=.3, halfwidth=.01)
        yield OverlayNonDecisionUniform(nondectime=0, halfwidth=.1)

    @accepts(Self, Solution)
    @returns(Solution)
    @ensures("np.sum(return.corr) <= np.sum(solution.corr)")
    @ensures("np.sum(return.err) <= np.sum(solution.err)")
    def apply(self, solution):
        # Make sure params are within range
        assert self.halfwidth >= 0, "Invalid st parameter"
        # Extract components of the solution object for convenience
        corr = solution.corr
        err = solution.err
        m = solution.model
        cond = solution.conditions
        undec = solution.undec
        evolution = solution.evolution
        # Describe the width and shift of the uniform distribution in
        # terms of list indices
        shift = int(self.nondectime / m.dt)  # Discretized non-decision time
        width = int(self.halfwidth /
                    m.dt)  # Discretized uniform distribution half-width
        offsets = list(range(shift - width, shift + width + 1))
        # Create new correct and error distributions and iteratively
        # add shifts of each distribution to them.  Use this over the
        # np.convolution because it handles negative non-decision
        # times.
        newcorr = np.zeros(corr.shape, dtype=corr.dtype)
        newerr = np.zeros(err.shape, dtype=err.dtype)
        for offset in offsets:
            if offset > 0:
                newcorr[offset:] += corr[:-offset] / len(offsets)
                newerr[offset:] += err[:-offset] / len(offsets)
            elif offset < 0:
                newcorr[:offset] += corr[-offset:] / len(offsets)
                newerr[:offset] += err[-offset:] / len(offsets)
            else:
                newcorr += corr / len(offsets)
                newerr += err / len(offsets)
        return Solution(newcorr, newerr, m, cond, undec, evolution)

    @accepts(Self, NDArray(d=1, t=Number), Unchecked)
    @returns(NDArray(d=1, t=Number))
    def apply_trajectory(self, trajectory, model, **kwargs):
        ndtime = np.random.rand() * 2 * self.halfwidth + (self.nondectime -
                                                          self.halfwidth)
        shift = int(ndtime / model.dt)
        if shift > 0:
            np.append([trajectory[0]] * shift, trajectory)
        elif shift < 0:
            if len(trajectory) > abs(shift):
                trajectory = trajectory[abs(shift):]
            else:
                trajectory = np.asarray([trajectory[-1]])
        return trajectory
Example #5
0
class OverlayChain(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
    """
    name = "Chain overlay"
    required_parameters = ["overlays"]

    @staticmethod
    def _test(v):
        assert v.overlays in List(
            Overlay), "overlays must be a list of Overlay objects"

    @staticmethod
    def _generate():
        yield OverlayChain(overlays=[OverlayNone()])
        yield OverlayChain(overlays=[
            OverlayUniformMixture(umixturecoef=.3),
            OverlayPoissonMixture(pmixturecoef=.2, rate=.7)
        ])
        yield OverlayChain(overlays=[
            OverlayNonDecision(nondectime=.1),
            OverlayPoissonMixture(pmixturecoef=.1, rate=1),
            OverlayUniformMixture(umixturecoef=.1)
        ])

    def __init__(self, **kwargs):
        Overlay.__init__(self, **kwargs)
        object.__setattr__(self, "required_parameters", [])
        object.__setattr__(self, "required_conditions", [])
        for o in self.overlays:
            self.required_parameters.extend(o.required_parameters)
            self.required_conditions.extend(o.required_conditions)
        assert len(self.required_parameters) == len(
            set(self.required_parameters)
        ), "Two overlays in chain cannot have the same parameter names"
        object.__setattr__(self, "required_conditions",
                           list(set(
                               self.required_conditions)))  # Avoid duplicates

    def __setattr__(self, name, value):
        if "required_parameters" in self.__dict__:
            if name in self.required_parameters:
                for o in self.overlays:
                    if name in o.required_parameters:
                        return setattr(o, name, value)
        return Overlay.__setattr__(self, name, value)

    def __getattr__(self, name):
        if name in self.required_parameters:
            for o in self.overlays:
                if name in o.required_parameters:
                    return getattr(o, name)
        else:
            return Overlay.__getattribute__(self, name)

    def __repr__(self):
        overlayreprs = list(map(repr, self.overlays))
        return "OverlayChain(overlays=[" + ", ".join(overlayreprs) + "])"

    @accepts(Self, Solution)
    @returns(Solution)
    def apply(self, solution):
        assert isinstance(solution, Solution)
        newsol = solution
        for o in self.overlays:
            newsol = o.apply(newsol)
        return newsol

    @accepts(Self, NDArray(d=1, t=Number))
    @returns(NDArray(d=1, t=Number))
    @paranoidconfig(unit_test=False)
    def apply_trajectory(self, trajectory, **kwargs):
        for o in self.overlays:
            trajectory = o.apply_trajectory(trajectory=trajectory, **kwargs)
        return trajectory