Newer
Older
adaptive-nback / generators / nb_gm_005.py
import random
import scipy.stats

import benchmarks.common as common


class SequenceGenerator:
    """nb_gm_005:
        - pseudo-random sampling.
        - fixed number of targets,lures, and distractors.
        - all stimuli presented equally as target, lure, or distractor.

    """

    def __init__(
        self,
        choices,
        n
    ):
        self.trials, self.targets_ratio, self.choices, self.n = None, None, choices, n
        self.seq = []

        # create norm distributions for cost functions
        self.skewness_norm      = None
        self.lures_ratio_norm   = None
        self.targets_ratio_norm = None

    def reset(self, trials, targets, lures):
        self.trials = trials

        self.targets_ratio_norm = scipy.stats.norm(targets/trials, 0.2)
        self.lures_ratio_norm   = scipy.stats.norm(lures/trials, 0.2)
        self.skewness_norm      = scipy.stats.norm(0      , trials/len(self.choices))

        print(self.skewness_norm.pdf(0))
        print(self.targets_ratio_norm.pdf(1.0/3.0))
        print(self.lures_ratio_norm.pdf(1.0/6.0))

        self.seq = []

    def generate(self, trials, targets, lures):
        self.reset(trials, targets, lures)
        while not self.seq or len(self.seq) < self.trials:
            #self.seq += self.best_choice()
            chunk_size = 3 if len(self.seq) + 3 <= self.trials else self.trials-len(self.seq)
            self.seq += self.best_chunk(chunk_size)
        return self.seq

    def best_chunk(self, chunk_size) -> list:
        from itertools import permutations
        min_cost, best_chunk = None, None
        best_chunk_size = min(len(self.choices), chunk_size)
        chunks = list(permutations(self.choices, best_chunk_size))
        random.shuffle(chunks)
        for chunk in chunks:
            cost = self.cost(self.seq + list(chunk))
            if min_cost is None or cost < min_cost:
                min_cost, best_chunk = cost, chunk
        return list(best_chunk)

    def best_choice(self) -> list:
        best_choice, min_cost = None, None
        random.shuffle(self.choices)  # to avoid ordering effect
        for choice in self.choices:
            cost = self.cost(self.seq + [choice])
            if min_cost is None or cost < min_cost:
                min_cost, best_choice = cost, choice
        return [best_choice]

    def cost(self, seq):
        targets, lures = common.count_targets_and_lures(seq, n)
        targets_ratio, lures_ratio = targets/len(seq), lures/len(seq)
        c1, c2, c3 = self.skewness_cost(seq), self.targets_cost(targets_ratio), self.lures_cost(lures_ratio)
        # print(c1,c2,c3)
        return c1+c2+c3

    def skewness_cost(self, seq):
        even_ratio = self.trials / len(self.choices)
        costs = {c: abs(seq.count(c) - even_ratio) for c in self.choices}
        max_deviation_from_even_dist = max(list(costs.values()))
        return 1.0 - self.skewness_norm.pdf(max_deviation_from_even_dist)

    def targets_cost(self, targets_ratio):
        return 1.0 - self.targets_ratio_norm.pdf(targets_ratio)

    def lures_cost(self, lures_ratio) -> float:
        return 1.0 - self.lures_ratio_norm.pdf(lures_ratio)


if __name__ == '__main__':
    import time
    import numpy as np

    trials, targets, lures = 24, 8, 4
    choices = ['1', '2', '3', '4', '5', '6', '7', '8']
    print('alg,n,trials,time,targets,lures')
    for s in range(1, common.sample_size):
        n = np.random.randint(2, 8)
        gen = SequenceGenerator(choices, n)
        st = time.time()
        s = gen.generate(trials, targets, lures)
        st = time.time() - st
        t, l = common.count_targets_and_lures(s, n)
        print(f"nb_gm_004,{n},{trials},{st:0.2f},{t},{l}")