rmeddis@0: function [nextStep, msg]=Levitt2 (decision, currentValue) rmeddis@0: global LevittControl withinRuns rmeddis@0: % descision is a string: 'hit' or 'miss' rmeddis@0: % current value is the value of the variable parameter (e.g. level) rmeddis@0: % msg: rmeddis@0: % msg='' indicates trial progressing successfully rmeddis@0: % msg='done' indicates that a threshold estimate has been found rmeddis@0: % msg='maximum level exceeded' rmeddis@0: % msg='maximum no of trials exceeded' rmeddis@0: rmeddis@0: % Initialize by running Levitt2 without arguments rmeddis@0: % later LevittControl will be set in aReadAndCheckParameterBoxes in expGUI_MT rmeddis@0: if nargin==0 rmeddis@0: rmeddis@0: LevittControl.peakTroughValues=[]; rmeddis@0: LevittControl.trialRunning=0; rmeddis@0: LevittControl.sequence='****'; rmeddis@0: LevittControl.LevittValuesUsed=[NaN NaN NaN NaN]; rmeddis@0: LevittControl.TurnsToSmallSteps=4; % 2 peaks and 2 troughs rmeddis@0: LevittControl.direction='same'; rmeddis@0: LevittControl.prevDirection='easier'; rmeddis@0: LevittControl.Nreversals=0; rmeddis@0: rmeddis@0: LevittControl.trialRunning=1; rmeddis@0: LevittControl.meanPeakTrough=NaN; rmeddis@0: LevittControl.sd=NaN; rmeddis@0: msg=''; rmeddis@0: return rmeddis@0: end rmeddis@0: rmeddis@0: % apply Levitt rules to find next stimulus value rmeddis@0: rule=LevittControl.rule; rmeddis@0: sequence=LevittControl.sequence; rmeddis@0: meanPT=LevittControl.meanPeakTrough; rmeddis@0: sdPT=LevittControl.sd; rmeddis@0: rmeddis@0: % response sequence: '+' is a hit and '-' is a miss. rmeddis@0: if strcmp(decision,'hit') rmeddis@0: sequence=[sequence '+']; rmeddis@0: else rmeddis@0: sequence=[sequence '-']; rmeddis@0: end rmeddis@0: LevittControl.LevittValuesUsed=[LevittControl.LevittValuesUsed withinRuns.levelList(end)]; rmeddis@0: rmeddis@0: switch rule rmeddis@0: case '++' rmeddis@0: peakCriterion='-++'; rmeddis@0: troughCriteria={'++ -', '++ +-'}; rmeddis@0: case '+++' rmeddis@0: peakCriterion='-+++'; rmeddis@0: troughCriteria={'+++ -', '+++ +-', '+++ ++-'}; rmeddis@0: otherwise rmeddis@0: error('Levitt:') rmeddis@0: end rmeddis@0: rmeddis@0: troughs=[]; allTroughPtrs=[]; rmeddis@0: for i=1:length(troughCriteria) % do all trough criteria rmeddis@0: troughCriterion=char(troughCriteria(i));% one criterion at a time rmeddis@0: % identify the location of a trough rmeddis@0: troughPtrs=findstr(sequence, troughCriterion) + length(troughCriterion)-1; rmeddis@0: % identify the level at which it occurred rmeddis@0: troughLevels=LevittControl.LevittValuesUsed(troughPtrs); rmeddis@0: % archive the list rmeddis@0: withinRuns.troughs=troughLevels; rmeddis@0: troughs=[troughs troughLevels]; rmeddis@0: allTroughPtrs=[allTroughPtrs troughPtrs]; rmeddis@0: end rmeddis@0: % only one peak criterion used rmeddis@0: withinRuns.troughs=troughs; rmeddis@0: peakPtrs=findstr(sequence,peakCriterion)+length(peakCriterion)-1; rmeddis@0: peakLevels=LevittControl.LevittValuesUsed(peakPtrs); rmeddis@0: withinRuns.peaks=peakLevels; rmeddis@0: rmeddis@0: % almagamate and sort into date order rmeddis@0: peakTroughList=[peakLevels troughs]; rmeddis@0: peakTroughPtrs=[peakPtrs allTroughPtrs]; rmeddis@0: [peakTroughPtrs idx]=sort(peakTroughPtrs); rmeddis@0: % it needs to be sequenced so that the algorithm can take the last peaks rmeddis@0: % and troughs rmeddis@0: peakTroughList=peakTroughList(idx); rmeddis@0: rmeddis@0: % adjust step size as the trial progresses rmeddis@0: % a positive step size indicates the 'harder' direction rmeddis@0: if length(peakTroughList)>=LevittControl.TurnsToSmallSteps rmeddis@0: currentStep=LevittControl.steadyLevittStep; rmeddis@0: else rmeddis@0: currentStep=LevittControl.startLevelStep; rmeddis@0: end rmeddis@0: rmeddis@0: % base next stimulus on the basis of the sequence rmeddis@0: % any miss requires an 'easier' stimulus next time. rmeddis@0: if strcmp(sequence(end),'-') rmeddis@0: nextStep= -currentStep; rmeddis@0: rmeddis@0: % success requires 2 or more successive hits rmeddis@0: elseif strcmp(sequence(end-length(rule)+1:end),rule) rmeddis@0: sequence=[sequence ' ']; % add space to prevent success on successive trials rmeddis@0: LevittControl.LevittValuesUsed=[LevittControl.LevittValuesUsed NaN]; rmeddis@0: nextStep=currentStep; rmeddis@0: rmeddis@0: % not enough hits to provoke a change rmeddis@0: else rmeddis@0: nextStep=0; rmeddis@0: end rmeddis@0: rmeddis@0: LevittControl.sequence=sequence; rmeddis@0: rmeddis@0: LevittControl.Nreversals=length(peakTroughList); rmeddis@0: % compute threshold estimate rmeddis@0: % only if minReversals exceeded and even number of peaks and troughs rmeddis@0: if LevittControl.Nreversals>=LevittControl.minReversals && rem(length(peakTroughList),2)==0 rmeddis@0: % use only the peaks and troughs at the end of the sequence rmeddis@0: peaksAndTroughs=peakTroughList(end-LevittControl.useLastNturns+1:end); rmeddis@0: disp(['peak/trough sequence= ' num2str(peaksAndTroughs, '%6.0f') ] ) rmeddis@0: rmeddis@0: meanPT=mean(peaksAndTroughs); rmeddis@0: sdPT=std(peaksAndTroughs); rmeddis@0: LevittControl.meanPeakTrough=meanPT; rmeddis@0: LevittControl.sd=sdPT; rmeddis@0: fprintf('Levitt, mean, sd= %6.1f,%6.1f\n', meanPT, sdPT) rmeddis@0: % final check that the sd is low enough rmeddis@0: if sdPTLevittControl.maxLevittValue rmeddis@0: msg='maximum level exceeded' rmeddis@0: return rmeddis@0: end rmeddis@0: rmeddis@0: % required for later use rmeddis@0: LevittControl.trialRunning=LevittControl.trialRunning+1; rmeddis@0: LevittControl.peakTroughValues=peakTroughList; rmeddis@0: rmeddis@0: if LevittControl.trialRunning>LevittControl.maxTrials rmeddis@0: msg='maximum no of trials exceeded' rmeddis@0: return rmeddis@0: end rmeddis@0: rmeddis@0: % Trial continues rmeddis@0: msg='';