Lagging issues with conditional statements in python during time-sensitive trials (Windows 10, Integrated Intel GPU, Psychopy v1.9)

I have recently done an experiment, which included a presentation of spoken digits to participants (ppt) - a concurrent load task adopted from Wills et al. (2001). Here is a brief description:

At the beginning of the trial, participants needed to remember a random list of six digits presented before the main task. After feedback on the main task, they received a probe (a random number from the list of six digits), at which point they need to enter the number that followed the probe in the list from the beginning of the trial.

In order to implement the task, I repurposed some python code from previous student experiment, for the PsychoPy Builder, that seemed to have done a reliable-enough job in presenting participants the spoken digits. Until my experiment crashed with the error message:

138.2088     WARNING     Couldn't measure a consistent frame rate.
- Is your graphics card set to sync to vertical blank?
- Are you running other processes on your computer?

138.5821     WARNING     t of last frame was 31.24ms (=1/32)
138.6133     WARNING     t of last frame was 31.24ms (=1/32)
138.6444     WARNING     t of last frame was 31.14ms (=1/32)
138.6757     WARNING     t of last frame was 31.29ms (=1/31)
138.7085     WARNING     Multiple dropped frames have occurred - I'll
stop bothering you about them!
139.5413     WARNING     psychopy.sound.backend_pyo.init could not find
microphone hardware; recording not available
Traceback (most recent call last):
  File "C:\PATH\TO\EXPERIMENT\YOUREXPERIMENT_lastrun.py", line 1813,
  in <module>
    cur_num = num_list[num_count]
IndexError: list index out of range

THE PROBLEM

This was the code I added to the Routine in the Builder View. At the time it did not seem that problematic. It worked on most machines, although they were all running Linux. There was no error message while running the pilot experiment (although with limited number of participants).

Beginning of Routine

  from numpy import arange
  import random

  full_list = range(1, 9)
  shuffle(full_list)
  num_list = full_list[0:6]
  load_timer = core.Clock()
  s_file = '1.wav'
  cur_num = 0
  num_count = 0

  p_index = randint(0,5)
  probe = num_list[p_index]

  ans = num_list[p_index+1]

Each Frame

  if load_timer.getTime() > 0.33:
     load_timer.reset()
     cur_num = num_list[num_count]
     s_file = str(cur_num) + '.wav'
     load_sound = sound.Sound(s_file,secs=-1)
     load_sound.setVolume(0.5)
     load_sound.play()
     num_count = num_count + 1

After consulting with some fellow programmers, we managed to articulate the problem. The if statement is executed at every 330 ms in a Routine lasting for 2310 ms, while Each Frame executes any code that is placed there at every 10 ms.

If you are wondering why: the media files are 330 ms long and the first starts playing from 330 ms, so (1 + 6) * 330ms = 2310 ms, therefore 2310 ms.

It picks out a member of a vector with 6 elements that is randomised at the beginning of each trial. In all cases, the error message popped up after after all the spoken digits were presented. If some other component loads slower compared to the core.Clock() - when the computer is lagging behind - Psychopy will execute the code for the 8th time. As a result, the program looks for num_list[6], whereas the last member is num_list[5] (python starts counting from 0), therefore the message: out of bounds. It seems that it only occurs on machines running Windows with an integrated Intel graphics. There is no fixing it, unless you change the OS.

So I decided to write a code that does the exact same job without an if statement running at every 10 ms. So the presentations of the spoken digits are not conditional in a programming sense.

Here is my quick solution:


from numpy import arange
import random

# Create a randomised list of numbers
full_list = range(1, 9)
shuffle(full_list)
num_list = full_list[0:6]

# Select random probe
p_index = randint(0,5)
probe = num_list[p_index]

# Store the right answer
ans = num_list[p_index+1]

# Convert number list to strings for later proc
num_list = map(str, num_list)

# Wait for monitor to update before executing code
win.flip()

# Play each element of num_list after 0.33 second
for i in range(0, 6):
  core.wait(0.33) # wait .33 s
  files = str(num_list[i]) + '.wav'           # select sound file
  load_sound = sound.Sound(files, secs=-1)    # load sound file
  load_sound.setVolume(0.5)                   # set volume
  load_sound.play()                           # play sound

This was a quick fix and I am convinced that there is a more elegant way of doing it. Let me know if you can think of a better way to do this, I will be happy to add it to the post. Or you can just edit it, thanks to Creative Commons.

P.S.

The error was only present on Windows 10 machines with Integrated Graphics and Psychopy v1.9. On Linux, the experiment runs flawlessly. So, it is possible that it is a driver issue, or the way memory is allocated for the integrated graphics in Windows.

CONCLUSION

My advice is to avoid conditional if statements in your experiment during time-sensitive trials. There are just too many variables (hardware, OS, alignment of the planets) that affect how well the program works. I am sure only the select few has enough money to upgrade the computers at every year or so. And while Linux is coming up, Windows is still the more prevalent.

References

Wills, A. J., Graham, S., Koh, Z., McLaren, I. P., & Rolland, M. D. (2011). Effects of concurrent load on feature-and rule-based generalization in human contingency learning. Journal of Experimental Psychology: Animal Behavior Processes, 37(3), 308.