####################### PVTdemo.py Description ###############################
# PVTdemo.py 10-09-06 replicating Psychomotor Vigilance Task (PVT) model by
# Gunzelmann, G.Gluck, K., Van Dongen, H., & Dinges, D.F. in press (2006)
# Decreased Arousal as a Result of Sleep Deprivation: The Unraveling of 
# Cognitive Control (chapter in Modeling Integrated Cognitive Systems)
# reaction time model for letter X response X with trial interval 2-10 sec
# experimental data collected 10 minutes every 2 hours for 88 hours
# (missing latency feedback to the display)
# simulating total sleep deprivation (TSD) for 0 1 2 3 days
# PythonActor code by Terry Stewart and Bruce Landon
#note: description from Loh,Laland,Dorrian,Roach and Dawson (2004) describing
#their replication of Dinges and Powell (1985) which is not easily avialable.
#PVT ran for a period of 10min. Subjects were required to reapond to a visual
#stimulus presented at a variable interval (2000-10000ms) by pressing a button
#with the thumb on the dominant hand. The visual stimulus was a four-digit
#LED counter turning on and incrementing from 0 to 60s at 1ms.  Button press
#stopped the LED incrmenting allowing the subject 1s to read their reaction 
#time before the counter restarted.  If a response was not made within 60s
#the clock was reset and the counter restarted. If a response was made prior
#to the presentaiton of the stimulus, a "False Start" message was displayed
#If the button was not released after 3s a reminder message ("Release Button")
#was displayed.(there is now a Palm verison of the PVT Saxena & George (2005) 
######################## Define task setup ##################################
goal_threshold = 1.98 #enable ccm simulation 1.98 1.80 1.66 1.58 TSD 0 1 2 3 
utility_threshold = 1.84 #enable simulation  1.84 1.78 1.70 1.64 TSD 0 1 2 3
cycle_noise = 0.0125 # variability in fundamental cycle time of 50ms
adjust_utility_threshold = 0.035 # reduce utility threshold after idle cycle
repetitions=100 # the number of stimuli to show
####################### Editable parameters above ###########################
from ccm.env.objects import *#http://www.carleton.ca/ics/ccmlab/ccmsuite.html
import random
import math

class MatchEnvironment(ObjectEnvironment):
    def start(self):     # define internal variables
        self.count=0
        self.falseAlarm=0
        self.lapse=0
        self.sleepAttack=0
        self.letter=Object(isa='letter',x=0.5,y=0.5,value=None) # initialize
        self.numrt=0     # for calculating okrtaverage
        self.sumrt=0     # for calculating okrtaverage
        self.rtsumX=0    # for calculating self.okrtsd
        self.rtsumXsqr=0 # for calculating self.okrtsd
        self.rtnum=0     # for calculating self.okrtsd
        yield random.uniform(2,10) # random wait 2-10 seconds
        self.letter.value='X'
        self.target=self.letter.value
        self.cueTime=self.now()    # get the current time

    @ccm.parallelize    
    def press(self,key):
        rt=self.now()-self.cueTime  # calculate reaction time
        log.rt=rt    # record the reaction time to the log
        if rt<0.150 or self.letter.value==None: # response before stimulus
            self.falseAlarm+=1
        elif rt>30: 
            self.sleepAttack+=1
        elif rt>0.5:
            self.lapse+=1
        else:
            self.numrt+=1    # ok rt between >0.15 (falseAlarm) <0.5 (lapse)
            self.sumrt+=rt
            self.rtsumX+=rt
            self.rtsumXsqr+=(rt * rt)
            self.rtnum+=1
        self.letter.value=None
        self.count+=1
        if self.count==repetitions:
            # convert to proportions
            scale=1.0/repetitions
            log.falseAlarm=self.falseAlarm*scale
            log.lapse=self.lapse*scale
            log.sleepAttack=self.sleepAttack*scale
            log.oktraverage=self.sumrt/self.numrt # over 150ms under 500ms
            log.self.okrtsd= math.sqrt((self.rtnum * self.rtsumXsqr
                                        -self.rtsumX * self.rtsumX)/
                                       (self.rtnum * (self.rtnum-1)))
            self.stop()
        else:
            self.model.pgc.goal=goal_threshold # reset goal at start of trial
            yield random.uniform(2,10) # random inter-trial interval 2-10s
            self.letter.value='X' # the cue stimuls (for rt counter digits)
            self.target=self.letter.value
            self.cueTime=self.now() # start trial reaction timer
            
from ccm.lib.actr import * # http://www.carleton.ca/ics/ccmlab/ccmsuite.html
class PMpgcGoalReducer(PMpgc): # enables falling asleep or micro sleeps
   def below_threshold(self):
       if env.letter.value==None: return  # don't reduce G if no stimuli
       self.goal-=adjust_utility_threshold # reduce threshold after idle cycle
       if self.goal < 0 :    # self.goal is G which is motivational goal value
            self.goal=0.0001 #keep G positive even during sleep attack

################# Define the ACT-R Model Components ##########################   
class MyModel: # minimal model (with no memory,mood,feeling,pain or alertness) 
  goal=Buffer()
  visual=Buffer()
  env=DirectEnvironment()
  vision=SOSVision(visual,delay=0.085, delay_sd=0.01)
  procedural=Procedural(threshold=utility_threshold, delay_sd=cycle_noise)
  pgc=PMpgcGoalReducer(procedural,goal=goal_threshold) #sleepless adjust G
  pnoise=PMNoise(procedural,0.25) # add in the noise part of the utility 
  
################# Define ACT-R Model Production Memories #####################
  def attend(goal='attend',vision='busy:False'): # looking for stimulus X
    vision.request('isa:letter')
    goal.set('attend')
    
  def respond(goal='attend',visual='isa:letter value:?letter'): #see X
    env.press(letter) # model presses key X to stop the reaction time clock
    visual.clear()
    
  def just_click(goal='attend',vision='busy:False'): # enables false alarms
    env.press('X') # press X before stimulus appears due to model noise
    visual.clear()

####################### Define Environment setup ############################
log=ccm.log()            # enable log to print out results
env=MatchEnvironment()   # don't automatically try to log things
env.model=ACTR(MyModel,logging=False) #turn off logging internal cycle states
env.model.goal.set('attend') # set starting goal buffer
# needed to set the P value for 'just_click' to zero 
env.model.pgc.set('just_click',successes=0,failures=1,lock=True)
# turn on visual display or just run env.run()
#import ccm.display
#ccm.display.Display(env)
#env.run(rate=1)  # to run at real-time    
env.run() # run simulation with model

