Skip to content

Commit

Permalink
Possible fix for plone/Products.CMFPlone#391. Adaptive values were be…
Browse files Browse the repository at this point in the history
…ing updated in afterTrial(), making get(...,'atTrialTime',Inf) return the wrong (counterintuitive) value for the trial. In this version, the criterion correct/incorrect function is evaluated in afterTrial(), but update() is not called until beforeTrial() in the next trial.
  • Loading branch information
adammorrissirrommada committed Dec 9, 2019
1 parent 3c93ddc commit 6b6e581
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 35 deletions.
34 changes: 23 additions & 11 deletions +neurostim/+plugins/adaptive.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
end

methods (Abstract)
% update(s) should change the internal state of the adaptive object using the outcome of the current
% trial (result = TRUE/Correct or FALSE/Incorrect).
update(s,result);
% update(s) should change the internal state of the adaptive object
% using the value (o.lastValue) and outcome of the just-completed trial
% (i.e. o.lastOutcome = TRUE/Correct or FALSE/Incorrect).
update(s);
% getAdaptValue returns the current parameter value from the adaptive algorithm.
v= getAdaptValue(s);
end

properties (SetAccess=private)
overruleValue = []; %used to manually set the adaptive parameter value (for the rest of current trial) to something other than that returned by the adaptive algorithm. Ensures update() is based on the actual tested value.
lastOutcome = []; %True/false, the outcome of the most recent trial
lastValue = []; %The value of the adaptive parameter at the time the outcome was evaluated
end

methods
Expand Down Expand Up @@ -199,30 +202,39 @@ function overrule(x1,newValue)
function afterTrial(o)
% This is called after cic sends the AFTERTRIAL event
% (in cic.run)
% An emply o.design means that the adaptive parameter was
% An empty o.design means that the adaptive parameter was
% assigned directly to a plugin property (probably a jitter)
% and not part of a design. These get updated after every
% trial, irrespective of the current condition/design.
% Adaptive parameters defined as part of a design only get
% updated when "their" condition/design is the currently active
% one.

if isempty(o.design) || (strcmpi(o.cic.design,o.design) && ismember(o.cic.condition,o.conditions))% Check that this adaptive belongs to the current condition
if o.cic.trial > o.ignoreN
% Call the derived class function to update it
correct = o.trialOutcome; % Evaluate the function that the user provided.
if numel(correct)>1
error(['Your ''correct'' function in the adaptive parameter ' o.name ' does not evaluate to true or false']);
% Evaluate the function that the user provided (returning correct/incorrect) and store it.
%We'll use it to update the adaptive value in beforeTrial()
o.lastOutcome = o.trialOutcome;
if numel(o.lastOutcome)>1
error(['Your ''trialOutcome'' function in the adaptive parameter ' o.name ' does not evaluate to true or false']);
end
update(o,correct); % Pass it to the derived class to update
o.overruleValue = [];

%Also store the value used
o.lastValue = o.getValue;
else
% Ignoring this trial
end
end
end

function beforeTrial(o)

if ~isempty(o.lastOutcome)
% Allow the derived class to update (based on lastValue and lastOutcome)
update(o);
o.lastOutcome = [];
o.lastValue = [];
end

%Reset the overruled value.
o.overruleValue = [];
end
Expand Down
2 changes: 1 addition & 1 deletion +neurostim/+plugins/jitter.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
update(o); % Call it once now to initialize.
end

function update(o,~)
function update(o)
% The abstract adaptive parent class requires that we implement this
% This is called after each trial. Update the internal value. The second arg is the success of the current trial, irrelevant here.
if isa(o.distribution,'function_handle')
Expand Down
9 changes: 4 additions & 5 deletions +neurostim/+plugins/nDown1UpStaircase.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,11 @@
o.value = startValue;
end

function update(o,correct)
function update(o)
% calculate and return the updated property value

% current value
v = o.getValue;
if correct
v = o.lastValue;

if o.lastOutcome
% increment correct count
o.cnt = o.cnt + 1;
if o.cnt >= o.n
Expand Down
10 changes: 3 additions & 7 deletions +neurostim/+plugins/psyBayes.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,10 @@

end

function update(o,response)
function update(o)
% The abstract adaptive parent class requires that we implement this
% This is called after each trial. Update the internal value. The second arg is the success of the current trial, as determined
% in the parent class, using the trialResullt function
% specified by the user when constructing an object of this
% class.
parmValue = getValue(o); % This is the value that we used previously
[~,o.psy] = psybayes(o.psy, o.method, o.vars,parmValue,response); % Call to update.
% This is called after each trial. Update the internal value.
[~,o.psy] = psybayes(o.psy, o.method, o.vars,o.lastValue,o.lastOutcome); % Call to update.
end

function v =getAdaptValue(o)
Expand Down
14 changes: 4 additions & 10 deletions +neurostim/+plugins/quest.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,11 @@
o.momentFun = funs{ismember({'QUANTILE','MEAN','MODE'},p.Results.pdfMoment)};
end

function update(o,correct)
function update(o)
% The abstract adaptive parent class requires that we implement this
% This is called after each trial. Update the internal value. The second arg is the success of the current trial, as determined
% in the parent class, using the trialResullt function
% specified by the user when constructing an object of this
% class.
parmValue = o.getValue; % This is the value that was used previously
intensity = o.p2i(parmValue); % Converti it to Quest intensity
if ~isempty(correct)
o.Q=QuestUpdate(o.Q,intensity,correct); % Add the new datum .
end
% This is called after each trial. Update the internal value.
intensity = o.p2i(o.lastValue); % Converti it to Quest intensity
o.Q=QuestUpdate(o.Q,intensity,o.lastOutcome); % Add the new datum .
end

function v =getAdaptValue(o)
Expand Down
2 changes: 1 addition & 1 deletion demos/adaptiveDemo.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@

% Create a block for this design and specify the repeats per design
myBlock=block('myBlock',d);
myBlock.nrRepeats = 10; % Because the design has 2 conditions, this results in 2*nrRepeats trials.
myBlock.nrRepeats = 50; % Because the design has 2 conditions, this results in 2*nrRepeats trials.
c.run(myBlock);

%% Do some analysis on the data
Expand Down

0 comments on commit 6b6e581

Please sign in to comment.