Open neuromusic opened 8 years ago
The Trial object needs to know about the possible "responses"
If they are discrete & named, then we can do something like...
CORRECT = {'feed': 2.0,'flash': 1.0,'timeout': False}
WRONG = {'feed': False,'flash': False,'timeout': 10.0}
trial = Trial(panel=panel,
stimulus='a.wav',
on_left=CORRECT,
on_right=WRONG,
)
or we could pass in a single callback that takes the trial as its sole argument:
def correct(trial):
if trial.reaction_time > 0.5:
return CORRECT.update({'feed'=1.0})
else
return CORRECT
trial = Trial(panel=panel,
stimulus='a.wav',
on_left=correct,
on_right=WRONG,
)
I know @MarvinT doesn't like this, but I find it to be much more readable and composable.
This 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.
Other Trial examples...
Additional parameters
trial = Trial(panel=panel,
stimulus='a.wav',
on_left=CORRECT,
on_right=WRONG,
response_window=3.0
)
Shape Trials
trial = Shape2ACBlock3Trial(panel=panel,
response_window=None, # wait forever
on_left=CORRECT,
on_right=CORRECT,
)
...
trial = Shape2ACBlock4Trial(panel=panel,
response_window=None, # wait forever
stimulus='left'
on_left=CORRECT,
)
I definitely need more time to sit with the ideas you proposed, but am I correct in reading that you are wanting to move away from the Behavior being the commonly customized and subclassed object and toward the Trial being customized and subclassed?
I guess "common" is relative.
If you want to change what constitutes a trial, you subclass the Trial object. If you want to change logic about which stimuli get presented with which consequences in what order, you subclass the Behavior object.
In the past two years, we've had around a half dozen people with minimal programming understanding want to develop new behaviors. 80% of the time, this has simply meant putting new logic in the "get_stimuli" method. The other 20% of the time, the changes have to do with trial selection: block structures, etc.
So the goal is to draw a stronger line between within-trial logic and across-trial logic.
The proximal need is that @MarvinT needs to integrate probe trials, where reinforcement is non-differential. Perhaps he can comment more on why the current structuring would need refactoring to make this work.
here's a sketch (without the full Behavior class) of how I envision this working
note: this should also help with #73, #14, #84
every
Trial
instance should be a self-contained object that runs one trial of an experiment.each trial will be initialized with the minimal set of conditions needed to provide a stimulus and consequate a response.
for example, a trial that plays stimulus 'a.wav' and provides a feed would be initialized with something like...
The
consequence
argument takes a nested dictionary of the form{response:{consequence:value}}
This design relies on a couple of assumptions
This means that the Trial object needs to know:
Once 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.This structure has a few advantages:
Pushing 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...
on the other hand, a probe trial might be reinforced with...
More complex Trial objects might need a more complex set of arguments than just "stimulus"
Concerns:
@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.
This could work by allowing a callback that takes the trial as an argument to be passed.