{"action":"created","issue":{"url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/104","labels_url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/104/labels{/name}","comments_url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/104/comments","events_url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/104/events","html_url":"https://github.com/gentnerlab/pyoperant/issues/104","id":128023754,"number":104,"title":"refactor behavior to isolate intra-trial logic from trial selection logic","user":{"login":"neuromusic","id":1245615,"avatar_url":"https://avatars.githubusercontent.com/u/1245615?v=3","gravatar_id":"","url":"https://api.github.com/users/neuromusic","html_url":"https://github.com/neuromusic","followers_url":"https://api.github.com/users/neuromusic/followers","following_url":"https://api.github.com/users/neuromusic/following{/other_user}","gists_url":"https://api.github.com/users/neuromusic/gists{/gist_id}","starred_url":"https://api.github.com/users/neuromusic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/neuromusic/subscriptions","organizations_url":"https://api.github.com/users/neuromusic/orgs","repos_url":"https://api.github.com/users/neuromusic/repos","events_url":"https://api.github.com/users/neuromusic/events{/privacy}","received_events_url":"https://api.github.com/users/neuromusic/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/gentnerlab/pyoperant/milestones/1","html_url":"https://github.com/gentnerlab/pyoperant/milestones/behavior%20refactor","labels_url":"https://api.github.com/repos/gentnerlab/pyoperant/milestones/1/labels","id":910827,"number":1,"title":"behavior refactor","description":"","creator":{"login":"neuromusic","id":1245615,"avatar_url":"https://avatars.githubusercontent.com/u/1245615?v=3","gravatar_id":"","url":"https://api.github.com/users/neuromusic","html_url":"https://github.com/neuromusic","followers_url":"https://api.github.com/users/neuromusic/followers","following_url":"https://api.github.com/users/neuromusic/following{/other_user}","gists_url":"https://api.github.com/users/neuromusic/gists{/gist_id}","starred_url":"https://api.github.com/users/neuromusic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/neuromusic/subscriptions","organizations_url":"https://api.github.com/users/neuromusic/orgs","repos_url":"https://api.github.com/users/neuromusic/repos","events_url":"https://api.github.com/users/neuromusic/events{/privacy}","received_events_url":"https://api.github.com/users/neuromusic/received_events","type":"User","site_admin":false},"open_issues":6,"closed_issues":0,"state":"open","created_at":"2014-12-20T03:24:40Z","updated_at":"2016-01-21T21:30:42Z","due_on":null,"closed_at":null},"comments":0,"created_at":"2016-01-21T21:30:25Z","updated_at":"2016-01-21T21:47:44Z","closed_at":null,"body":"\r\nevery `Trial` instance should be a self-contained object that runs one trial of an experiment.\r\n\r\neach trial will be initialized with the minimal set of conditions needed to provide a stimulus and consequate a response.\r\n\r\nfor example, a trial that plays stimulus 'a.wav' and provides a feed would be initialized with something like...\r\n``` Python\r\ntrial = Trial(panel=panel,\r\n              stimulus='a.wav',\r\n              consequences={\r\n                  'left': {\r\n                      'feed': 2.0,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  'right': {\r\n                      'feed': False,\r\n                      'flash': False,\r\n                      'timeout': 10.0\r\n                      },\r\n                  },\r\n              )\r\n```\r\n\r\nThe `consequence` argument takes a nested dictionary of the form `{response:{consequence:value}}`\r\n\r\nThis design relies on a couple of assumptions\r\n- there are a limited number of possible responses and these are known to the Trial\r\n- there are a limited number of possible consequences and these are known to the Trial\r\n\r\nThis means that the Trial object needs to know:\r\n\r\n- what to do with the stimulus\r\n- what to do with each response's consequence:value pair.\r\n\r\nOnce a trial is initialized, it would be run with `trial.run()`, which would execute the trial and save the relevant outcomes to trial attributes. The rest of the script would then access these values in order to save the trial.\r\n\r\nThis structure has a few advantages:\r\n\r\n1. This structure moves the logic of checking the trial \"type\" (correction, probe, etc) OUT of the trial execution and into the experimental trial generation logic. This means that we will need to know how we will want to consequate *this* trial before *this* trial starts.\r\n2. Isolating the intra-trial execution should allow us to more readily design new Trial classes for more complex interactions while using existing trial generation logic or visa versa.\r\n3. Further down the line, this may even allow a Trial object to simply send of relevant parameters to an Arduino or other embedded system that maintains the trial logic.\r\n\r\nPushing the consequation logic out of the trial execution changes the way we think about consequences a little bit. For example, a \"correction\" trial (where there is no feed for a correct response, but still a secondary reinforcer) would be initialized like...\r\n\r\n``` Python\r\ntrial = Trial(panel=panel,\r\n              stimulus='a.wav',\r\n              consequences={\r\n                  'left': {\r\n                      'feed': False,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  'right': {\r\n                      'feed': False,\r\n                      'flash': False,\r\n                      'timeout': 10.0\r\n                      },\r\n                  },\r\n              )\r\n```\r\n\r\non the other hand, a probe trial might be reinforced with...\r\n\r\n``` Python\r\ntrial = Trial(panel=panel,\r\n              stimulus='a.wav',\r\n              consequences={\r\n                  'left': {\r\n                      'feed': 2.0,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  'right': {\r\n                      'feed': 2.0,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  },\r\n              )\r\n```\r\n\r\nMore complex Trial objects might need a more complex set of arguments than just \"stimulus\"\r\n\r\n``` Python\r\ntrial = Trial(panel=panel,\r\n              stim='a.wav',\r\n              target='b.wav',\r\n              cue='blue',\r\n              consequences={\r\n                  'left': {\r\n                      'feed': 2.0,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  'right': {\r\n                      'feed': 2.0,\r\n                      'flash': 1.0,\r\n                      'timeout': False,\r\n                      },\r\n                  },\r\n              )\r\n```\r\n\r\nConcerns:\r\n\r\n@MarvinT wants callbacks. I really think that the trial object should not be calling back out of the trial, but there might be a need, e.g., to make a feed duration depend on reaction time in a graded fashion.\r\n\r\nThis could work by allowing a callback that takes the trial as an argument to be passed.\r\n\r\n``` Python\r\nNORMAL_CORRECT = {'feed': 2.0,'flash': 1.0,'timeout': False}\r\nNORMAL_WRONG = {'feed': False,'flash': False,'timeout': 10.0}\r\n\r\ndef normal_left_consequences(response,trial):\r\n    if response =='left':\r\n        return NORMAL_CORRECT\r\n    elif response=='right':\r\n        return NORMAL_WRONG\r\n\r\nclass Trial(object):\r\n    def __init__(self, panel, stimulus, consequences):\r\n        self.consequences = consequences\r\n    def consequate(self):\r\n        return self.consequences(self.response,self)\r\n```\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/comments/173719703","html_url":"https://github.com/gentnerlab/pyoperant/issues/104#issuecomment-173719703","issue_url":"https://api.github.com/repos/gentnerlab/pyoperant/issues/104","id":173719703,"user":{"login":"neuromusic","id":1245615,"avatar_url":"https://avatars.githubusercontent.com/u/1245615?v=3","gravatar_id":"","url":"https://api.github.com/users/neuromusic","html_url":"https://github.com/neuromusic","followers_url":"https://api.github.com/users/neuromusic/followers","following_url":"https://api.github.com/users/neuromusic/following{/other_user}","gists_url":"https://api.github.com/users/neuromusic/gists{/gist_id}","starred_url":"https://api.github.com/users/neuromusic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/neuromusic/subscriptions","organizations_url":"https://api.github.com/users/neuromusic/orgs","repos_url":"https://api.github.com/users/neuromusic/repos","events_url":"https://api.github.com/users/neuromusic/events{/privacy}","received_events_url":"https://api.github.com/users/neuromusic/received_events","type":"User","site_admin":false},"created_at":"2016-01-21T21:47:44Z","updated_at":"2016-01-21T21:47:44Z","body":"The Trial object needs to know about the possible \"responses\"\r\n\r\nIf they are discrete & named, then we can do something like...\r\n\r\n```Python\r\nCORRECT = {'feed': 2.0,'flash': 1.0,'timeout': False}\r\nWRONG = {'feed': False,'flash': False,'timeout': 10.0}\r\n\r\ntrial = Trial(panel=panel,\r\n              stimulus='a.wav',\r\n              on_left=CORRECT,\r\n              on_right=WRONG,\r\n              )\r\n```\r\n\r\nor we could pass in a single callback that takes the trial as its sole argument:\r\n\r\n``` Python\r\ndef correct(trial):\r\n    if trial.reaction_time > 0.5:\r\n        return CORRECT.update({'feed'=1.0})\r\n    else\r\n        return CORRECT\r\n\r\ntrial = Trial(panel=panel,\r\n              stimulus='a.wav',\r\n              on_left=correct,\r\n              on_right=WRONG,\r\n              )\r\n```\r\nI know @MarvinT doesn't like this, but I find it to be much more readable and composable.\r\n\r\nThis will definitely break down if responses are not named (i.e. floats based on a joystick moving or pressure sensor), but the dictionary approach won't work there either.\r\n"}}