psychopy.org | Reference | Downloads | Github

Psychopy/pavlovia freezing, we *think* possibly getting stuck in an infinite code loop

Hi,
I’m running into psychopy/pavlovia freezing mid block and think it may be a result of processing constraints, but am not explicitly sure where or why.

We have an attentional capture task that has two conditions:

  • an experimental condition where a distractor-present trial appears every 3 trials (this works fine)
  • and then a control condition where distractor-present trials appear randomly but at the same 33% proportion as the experimental condition.

We are randomizing conditions within a single exp trials loop in builder, so the control condition took a little more code to make sure it would randomly select each trial from the 27-row spreadsheet once before starting it over (the spreadsheet instills the 1/3 distractor-present/absent ratio). However, we noticed that this would often present 3-4 distractor-present trials in a row, which I would like to mitigate as best as possible.

My RA and I just put together the below code to designate what each trial selection needs to respect (i.e., no more than two distractors in a row and every row of the spreadsheet before restarting). We had gotten the every row of spreadsheet aspect working, but then psychopy/pavlovia is now freezing/becoming unresponsive at seemingly random points when running through the control condition trials after we tried to implement control over the next trial if the last two trials were already distractor-present. We think that the program could be getting stuck in an infinite loop somewhere in here, but are unsure where.

URL of experiment: https://pavlovia.org/quirkm/attentional_historyv3

# this is all defined in the begin routine of a trials loop
trial_id += 1 
runTrials = [] #this is defined in the beginning of experiment
duplicateCheck = False;
repeatedDistractor = False;
rand_trial = str(randint(0,27))
numTrials = len(runTrials)

#decide distractor trial type
if expInfo['condition'] == "experimental":
    if trial_id % 3 == 0 :
        #distractor present
        select_trial = str(randint(0,9))
    else:
        #distractor absent
        select_trial = str(randint(9,27))
else:
    if rand_trial in runTrials:
        duplicateCheck = True      
        print("Already Ran")
    else:
        #if two trials have ran
        if (numTrials > 1):
            if (int(rand_trial) >= 0 and int(rand_trial) <= 8):
                if (0 <= int(runTrials[numTrials - 1]) <= 8):
                    if (0 <= int(runTrials[numTrials - 2]) <= 8):
                        repeatedDistractor = True

        if (repeatedDistractor == False):    
            #using current randTrial
            #and adding it to run_trials
            select_trial = rand_trial
            runTrials.append(rand_trial)
    
    while(duplicateCheck or repeatedDistractor):
        if (repeatedDistractor):
            rand_trial = str(randint(9,27))
            repeatedDistractor = False
        else:
            rand_trial = str(randint(0,27))
            
        if (rand_trial not in runTrials):
            
            #if two trials have ran
            if (int(rand_trial) >= 0 and int(rand_trial) <= 8):
                if (0 <= int(runTrials[numTrials - 1]) <= 8):
                    if (0 <= int(runTrials[numTrials - 2]) <= 8):
                        repeatedDistractor = True
                    else:
                        repeatedDistractor = False
                else:
                    repeatedDistractor = False
            else:
                repeatedDistractor = False
            
            if (duplicateCheck == False):    
                #using current randTrial
                #and adding it to run_trials
                select_trial = rand_trial
                runTrials.append(rand_trial)
            
        else:
            duplicateCheck = True
        
        #generate non distractor if repeatedDistractor
        if (repeatedDistractor):
            rand_trial = str(randint(9,27))
        else:
            rand_trial = str(randint(0,27))
            
             
    #if 27th run
    if len(runTrials) == 27:
        #resetting list
        runTrials = []
    else:
        print("Current # of Ran Trials: " + str(len(runTrials)))
        print("Haven't reached 27 trials")

I’ve seen other threads suggest taking out the print statements to limit the output, but I don’t see that being the reason for the whole program to freeze up. If anyone has a suggestion about what in the code might not be closed correctly or a better way to implement control over the next trial following two distractors, I’d be really appreciative!!

Thanks,
Maddie

You enter a while loop that will only be exited if duplicateCheck and repeatedDistractor are both false. You have some code to handle the case where repeatedDistractor is true to begin with, and to resolve this. However, it looks to me like you’re not really handling the scenario where duplicateCheck starts out as True. You just end up going to an else-block that sets duplicateCheck=True again, but I don’t see any code that sets this variable to False. This would get you stuck in an infinite loop, and explain why your experiment halts.

On the whole, I would recommend a different approach from what you’re currently doing. Your current method is to pick a trial totally at random and then see whether it satisfies your constraints (and if not, try again). If implemented correctly, this should work, but it’s a bit clunky and more error-prone (also it may work for your current experiment, but it will scale poorly to more complicated sets of constraints which are harder to satisfy by chance with a random draw). Instead, I would consider if you can’t program the random draw so it satisfies your constraints by construction. So e.g. keep a list of trials that have not yet been picked (striking trials off when they have been), and sample the next trial only from trials that remain on this list. And check beforehand whether you’ve had too many distractors in a row in the last N trials. If so, sample the next trial only from the distractor-absent indices.

2 Likes

Hi, thanks so much for this response! We closed the original loop that was open and then it worked fine locally, but would start freezing again randomly through Pavlovia. So with that, we then worked to condense the code to do the duplicate checking prior to selecting a trial like you suggested and it certainly did help limit the processing power needed. I’ve pasted our new code below. It looked really promising when running locally again, but much to our dismay, ends up freezing again and then killing my pavlovia page (unresponsive - kill page). At this point, I’m not really sure what might be causing this or if there is a way to even better limit our computing requirements here. This code really doesn’t seem like it should be crashing my entire pavlovia page. (It won’t even run on Safari either - unspecified JavaScript error)… Any further insight would be so appreciated! We’re somewhat a loss right now and are working against the clock to collect some data for a grant!

Begin Experiment:

trial_id = 0
#runTrials = []
runTrials = 0
distractorPresent = [0,1,2,3,4,5,6,7]
distractorAbsent = [8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 
24, 25, 26]
trialTracker = []

Begin Routine:

trial_id += 1 
rand_array = str(randint(0,2))
trialTracker.append(rand_array)
runTrials += 1
duplicateCheck = False
trackerSize = len(trialTracker)


#decide distractor trial type - experimental condition works fine
if expInfo['condition'] == "experimental": 
    if trial_id % 3 == 0 :
        #distractor present
        select_trial = str(randint(0,9))
    else:
        #distractor absent
        select_trial = str(randint(9,27))
else:
    #duplicate check
    if (trackerSize >= 2):
        if (trialTracker[trackerSize - 1] == "0" and
            trialTracker[trackerSize - 2] == "0"):
            duplicateCheck = True
            
    #traditional trial selection   
    if (len(distractorPresent) == 0):
        shuffle(distractorAbsent)
        select_trial = str(distractorAbsent[0])
        del distractorAbsent[0]
    elif (len(distractorAbsent) == 0  and duplicateCheck == False):
        shuffle(distractorPresent)
        select_trial = str(distractorPresent[0])
        del distractorPresent[0]      
    elif (rand_array == "0" and duplicateCheck == False):
        shuffle(distractorPresent)
        select_trial = str(distractorPresent[0])
        del distractorPresent[0]
    else:
        shuffle(distractorAbsent)
        select_trial = str(distractorAbsent[0])
        del distractorAbsent[0]
              
    #if 27th run
    if runTrials == 27:
        #resetting list
        distractPresent = [0,1,2,3,4,5,6,7]
        distractorAbsent = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
        runTrials = 0

Update: I changed the delete function to .pop(0) and set this function to call .shift() in JS as per the crib sheet - Array.prototype.pop = [].shift; . I was told that delete would just set an index to ‘undefined’ rather than actually take it out, so I hoped that undefined was causing our freezing issue. That doesn’t seem to be the case. Along with the actual experiment freezing and becoming unresponsive, whatever this error is is also causing my other two open pavlovia dashboard pages to become unresponsive and force me to kill them.