In [1]:
from transitions import Machine, State

import random
import numpy as np
from collections import Counter

In [2]:
class TrialSeq(object):
    def __init__(self,N):    
        _CueTypes = [5,6]
        _nCueTypes = len(_CueTypes)
        _GoalIDs = [3,4,5,6]
        _GoalIDsByCue = {5:[3,4],6:[5,6]}
        _nGoalsByCue = {k: len(v) for k, v in _GoalIDsByCue.items()}
        
        # increase N if not divisible by number of conditions 
        _nConds = 4
        while N%_nConds != 0:
            N = N+1
        
        # seq refers to the cue sequence, indicating trial type.
        _nTrialsPerCue = int(N/_nCueTypes)
        _seq = _CueTypes*_nTrialsPerCue # create sequence of length N for CueTypes
        _seq = np.array(np.random.permutation(_seq))
        
        # assign goal to cues  
        _GoalSeq = np.zeros(N)-1 
        for cue in _CueTypes: 
            _sublist = _seq==cue
            _nTrialsPerCueGoal = int(_nTrialsPerCue/_nGoalsByCue[cue])
            _cuegoalseq = np.array(_GoalIDsByCue[cue]*_nTrialsPerCueGoal)
            _cuegoalseq = np.random.permutation(_cuegoalseq)
            _GoalSeq[_sublist] = _cuegoalseq

        # another implementation of goal allocation, no restrain for # of goal
#         for ii in range(N):
#             for jj in _CueTypes:
#                 rr = random.random(): 
#                 for kk in range(_GoalIDsByCue[jj]):
#                     if rr < (kk+1)*(1/_nGoalsByCue[jj]):
#                         self.GoalSeq[ii] = _GoalIDsByCue[jj][kk]
#                         break
        # del temp variables
        del _sublist, _cuegoalseq
        
        # visible variables
        self.N = N
        self.CueSeq = _seq
        self.GoalSeq = _GoalSeq.astype(int)
        self.CueIDs = _CueTypes
        self.GoalIDsByCue = _GoalIDsByCue
        self.CurrentTrial = 0
        
    def CueID(self):
        return self.CueSeq[self.CurrentTrial] 
    def GoalID(self):
        return self.GoalSeq[self.CurrentTrial] 
    def NextTrial(self):
        self.CurrentTrial = self.CurrentTrial + 1
    
    def SeqCounts(self):
        print("Number of trials = ", self.N)
        print("Trial Cue Counts: ")
        print(Counter(self.CueSeq))
        print("Trial Goal Counts: ")
        print(Counter(self.GoalSeq))

In [12]:
ts = TrialSeq(303)
ts.GoalSeq


Out[12]:
array([6, 5, 5, 4, 6, 4, 5, 6, 3, 5, 4, 4, 6, 5, 4, 5, 4, 6, 4, 3, 4, 5, 6,
       5, 6, 6, 4, 6, 6, 5, 3, 4, 3, 5, 3, 4, 3, 6, 6, 3, 5, 3, 5, 4, 6, 5,
       4, 6, 4, 5, 3, 6, 6, 3, 5, 3, 5, 4, 5, 3, 6, 6, 6, 3, 4, 6, 5, 4, 4,
       4, 5, 5, 6, 3, 6, 6, 6, 4, 5, 6, 4, 6, 4, 5, 6, 5, 6, 5, 5, 4, 6, 4,
       3, 3, 4, 5, 4, 4, 5, 4, 6, 4, 3, 3, 3, 4, 5, 3, 3, 3, 6, 5, 5, 4, 6,
       6, 3, 4, 6, 4, 5, 3, 3, 4, 4, 4, 4, 3, 3, 5, 4, 3, 5, 3, 3, 3, 3, 6,
       4, 3, 5, 4, 4, 4, 5, 3, 5, 5, 4, 3, 5, 4, 3, 6, 6, 4, 4, 3, 5, 3, 6,
       5, 4, 5, 6, 6, 5, 6, 5, 5, 4, 5, 3, 3, 3, 3, 3, 6, 3, 4, 5, 5, 3, 4,
       4, 6, 3, 6, 5, 4, 5, 3, 3, 4, 3, 4, 5, 6, 6, 4, 6, 5, 4, 5, 3, 6, 6,
       5, 6, 4, 3, 4, 5, 4, 4, 3, 3, 6, 5, 4, 6, 5, 6, 5, 6, 4, 4, 3, 3, 3,
       5, 3, 6, 6, 6, 3, 5, 6, 6, 3, 3, 6, 6, 5, 5, 4, 3, 5, 4, 5, 5, 5, 3,
       4, 3, 4, 6, 3, 4, 6, 6, 5, 6, 6, 3, 4, 3, 4, 3, 6, 3, 5, 5, 6, 6, 4,
       5, 5, 3, 6, 5, 5, 6, 4, 3, 3, 5, 4, 3, 6, 3, 6, 6, 4, 3, 4, 5, 3, 5,
       5, 6, 6, 4, 3])

In [2]:
class Maze(object):
    nWells = 6
    nCues = 6
    def __init__(self):
        self.Act_Well = np.zeros(self.nWells,dtype=bool)
        self.PrevAct_Well = np.zeros(self.nWells,dtype=bool)
        self.Act_Cue  = 0
        self.DetectedWellNum = 0
        self.WellDetectSeq = []
        self.ValidWellDetectSeq = []
        self.StateChangeFlag = 0
        
    def activate_cue(self):
        # send command to activate relevant cue
        #self.Act_Cue
        pass
    def inactivate_cue(self):
        # send command to inactivate cue
        self.Act_Cue = 0

    def activate_well(self,well):
        if self.Act_Well[well]:
            print('activated well', well+1)
            #send command to arduino
            
    def detect(self,well):
        #well = event.kwargs.get('well',0)
        well = well-1 # zero indexing the wells
        self.DetectedWellNum = well
        self.WellDetectSeq.append(well+1)
        if self.Act_Well[well]==True:
            self.Act_Well[well]=False
            self.ValidWellDetectSeq.append(well+1)
        
            
    def update_states(self):
        well = int(self.state[2:])
        self.PrevAct_Well = np.array(self.Act_Well)
        
        if (well==99): #activate all
            if all(self.Act_Well)==False:
                self.Act_Well[:] = True
        elif (well>=1 and well<=6):
            if self.Act_Well[well-1] == False:
                self.Act_Well[well-1] = True
        elif (well==34):
            if (self.Act_Well[2]==False):
                self.Act_Well[2]=True
            if (self.Act_Well[3]==False):
                self.Act_Well[3]=True
        elif (well==56):
            if (self.Act_Well[4]==False):
                self.Act_Well[4]=True
            if (self.Act_Well[5]==False):
                self.Act_Well[5]=True
        else: # inactivate all
            if any(self.Act_Well)==True:
                self.Act_Well[:] = False
        
        change_wells = np.array((self.PrevAct_Well != self.Act_Well).nonzero()).flatten()
        for ii in change_wells: 
            self.activate_well(ii)


    def next_trial(self):
        pass
    def G34(self):
        if self.Act_Cue==5:
            return True
        return False
    def G56(self):
        if self.Act_Cue==6:
            return True
        return False
    def G3(self):
        pass
        # if sequence.goal(current trial) == 3
        # return true
    def G4(self): pass
    def G5(self): pass
    def G6(self): pass

In [28]:
N = 100;

GoalSeq = []

RightCues = [1,2,5]
LeftCues  = [3,4,6]  

trialTypes = ['']
states = [State(name='AW0',on_enter=['inactivate_cue'],ignore_invalid_triggers=True),
          State(name='AW1',on_enter='next_trial',on_exit=['activate_cue'],ignore_invalid_triggers=True),
          State(name='AW2',ignore_invalid_triggers=True),
          State(name='AW3',ignore_invalid_triggers=True),
          State(name='AW4',ignore_invalid_triggers=True),
          State(name='AW5',ignore_invalid_triggers=True),
          State(name='AW6',ignore_invalid_triggers=True),
          State(name='AW34',ignore_invalid_triggers=True),
          State(name='AW56',ignore_invalid_triggers=True)
         ]

conditions = ['G3','G4','G5','G6','G34','G56']
transitions = [ 
    {'trigger':'D1','source':'AW1','dest':'AW2'},
    {'trigger':'D2','source':'AW2','dest':'AW34', 'conditions':'G34','after':'inactivate_cue'},
    {'trigger':'D2','source':'AW2','dest':'AW56', 'conditions':'G56','after':'inactivate_cue'},
    {'trigger':'D2','source':'AW2','dest':'AW0'},
    
    {'trigger':'D3','source':'AW34','dest':'AW4'},
    {'trigger':'D3','source':'AW3','dest':'AW1'},
    {'trigger':'D4','source':'AW34','dest':'AW3'},
    {'trigger':'D4','source':'AW4','dest':'AW1'},

    {'trigger':'D5','source':'AW5','dest':'AW1'},
    {'trigger':'D5','source':'AW56','dest':'AW6'},
    {'trigger':'D6','source':'AW6','dest':'AW1'},
    {'trigger':'D6','source':'AW56','dest':'AW5'},

    {'trigger':'stop','source':'*','dest':'AW0'}]

In [55]:
MS = Maze()
machine = Machine(MS,states,transitions=transitions, ignore_invalid_triggers=True ,initial='AW0',
           after_state_change='update_states')

# list of detections
dlist = [1,2,3,4,1,2,4,3,1,2,3,5,6]
goals = [1,2,3,4,5,6]
cuelist = [5,5,6,6,5,6]
detect_callback = []
MS.Act_Cue=5
for ii in goals:
    detect_callback.append(getattr(MS,'D'+str(ii)))


MS.to_AW1()
trialcount =0
for ii in dlist:
    print(MS.state)
    if ii==1 and MS.state==1:
        MS.activate_cue=cuelist(trialcount)
        trialcount=trialcount+1
    MS.detect(ii)
    detect_callback[ii-1]()


activated well 1
AW1
activated well 2
AW2
activated well 3
activated well 4
AW34
AW4
activated well 1
AW1
activated well 2
AW2
AW0
AW0
AW0
AW0
AW0
AW0
AW0

In [54]:
help(MS.trigger)


Help on partial object:

class partial(builtins.object)
 |  partial(func, *args, **keywords) - new function with partial application
 |  of the given arguments and keywords.
 |  
 |  Methods defined here:
 |  
 |  __call__(self, /, *args, **kwargs)
 |      Call self as a function.
 |  
 |  __delattr__(self, name, /)
 |      Implement delattr(self, name).
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  __reduce__(...)
 |      helper for pickle
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __setattr__(self, name, value, /)
 |      Implement setattr(self, name, value).
 |  
 |  __setstate__(...)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |  
 |  args
 |      tuple of arguments to future partial calls
 |  
 |  func
 |      function object to use in future partial calls
 |  
 |  keywords
 |      dictionary of keyword arguments to future partial calls


In [34]:
# thread 1
# gets arduino data
# runs continouesly, no printing to command line
# writes events codes and timing to text file

# thread 2
# get user input data
# if there is user input trigger a flag
# store user commands

# thread 3
# ongoing loop getting detect events from GPIO
# act on user inputs, if any (checks input flag), turn off flag
# x = dected well
# f=getattr(machine,'D'+str(x));
# f(x); triggers the transition and passes which well was detected.

In [36]:



Out[36]:
[functools.partial(<bound method Event.trigger of <Event('D1')@1700805775768>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D2')@1700805775824>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D3')@1700805775936>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D1')@1700805775768>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D2')@1700805775824>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D4')@1700805776272>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D1')@1700805775768>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D2')@1700805775824>>, <__main__.Maze object at 0x0000018BFFE9B390>),
 functools.partial(<bound method Event.trigger of <Event('D3')@1700805775936>>, <__main__.Maze object at 0x0000018BFFE9B390>)]

In [38]:



Out[38]:
False