Closed dshimaoka closed 2 years ago
This solution leaves the problem that someone has to set the manualSequence (which duplicates .list) but then also the mode. I was thinking of something like function shuffle(o,manualList) if nargin > 1 % List was provided. o.randomization = MANUAL o.list = manualList else % The old code end end
This requires no new internal members to keep track of the manual list.
Thank you so much for the suggestion. I will rewrite shuffle
with another input listManual
. Now the question is how we deal with listManual
. When we actually call shuffle
, should we store this variable in RSVP struct:
addRSVP(s,design,varargin)
...
p.addParameter('manualList', [], @(x) isnumeric(x));
p.parse(design,varargin{:});
flds = fieldnames(p.Results);
for i=1:numel(flds)
s.rsvp.(flds{i}) = p.Results.(flds{i});
end
%Elaborate the factorial design into (sub)condition lists for RSVP
if ~isempty(p.Results.manualList)
s.rsvp.design.shuffle(p.Results.manualList);
else
s.rsvp.design.shuffle;
end
or should we not save listManual
as in:
addRSVP(s,design,varargin)
...
manualList = [];
for i=1:numel(flds)
if ~strcmp(flds{i}, 'manualList')
s.rsvp.(flds{i}) = p.Results.(flds{i});
else
manualList = p.Results.(flds{i});
end
end
If there is a way to retrieve o.list, I guess the latter is better to reduce overhead?
It's not clear to me how this approach can work. As it stands, shuffle() is called in several places (block
, stimulus.rsvp
) without any arguments and uses stored properties of design
to build and re-build (when exhausted) the condition-by-trial list as the experiment progresses. The user doesn't call shuffle()
directly.
Perhaps the different list-generator modes (sequential, randomwithreplacement, manual etc.) should be in separate functions and one of them is assigned as the "active" generator function depending on randomization
(e.g. its handle is assigned to a new property named shuffleFun
). For manual mode, the user can pass a handle to an arbitrary function that returns the list. That way, it can also support reactive changes in trial order based on results of earlier blocks.
Thanks for the comment. The way the user pass a handle to an arbitrary function would be via addRSVP
as:
addRSVP(... 'manualList', list)
.
This is done once at the beginning of the experiment. When shuffle
is subsequently called without any arguments in baseBeforeTrial
or in updateRSVP
, then o.list
is recycled. The recycling of o.list
in baseBeforeTrial
is precisely what we aim to achieve. The recycling in updateRSVP
would be what a user would expect, I suppose. My understanding of block is pretty shallow as I have not used block with rsvp. If you can suggest an example code, I can dig and think more deeply.
I guess a support for reactive changes in trial order based on results is bit too advanced at this stage, unless there is a demand for it.
I can upload another pull request implementing what I wrote and checked with my stimulus code.
The design
object is meant to contain all the information about conditions, their repeats, and ordering etc. The same object, with the same code for specifying those details, is used whether you are defining a factorial design for an experiment (i.e. by using it to create a block
and then calling c.run(myBlock)
, or for an RSVP stream by passing the design object to addRSVP(myDesign)
. The only difference between the two is whether it steps through the list across or within trials.
For this reason, any option for manual specification of condition order has to be stand-alone, built into design
, not as an additional argument to addRSVP()
As I said above, I don't think the committed version is a good solution. It solves the problem you have now (manual rsvp) but not the one we will get asked about tomorrow (and have had in the past): "how can I set the order of trials in my experiment to an arbitrary order?".
Similar to @bartkrekelberg's suggestion, could it perhaps be achieved instead by a function in design.m named setConditionOrder(condOrder), which overrides value of o.randomization to manual and sets o.list. then, make shuffle() do nothing to o.list for that mode.
So list remains frozen until someone next calls setCondOrder() (if a new order is desired), which could be done in a beforeBlock() callback, for example.
This is essentially the same solution Bart suggested but it's wrapped in a more intuitive function name and would allow shuffle() to remain protected (is it?).
This allows you to set up the design, including order, and then hand that to addRSVP(). Cleaner because it solves both problems and keeps design functionality in design.m and out of stimulus.m
Thanks, another pull request reflecting the Adam's suggestion will be uploaded.
Followed the suggestion on my previous request https://github.com/klabhub/neurostim/pull/175#discussion_r709294389. Instead of changing the access to list, a new listMod property was added with SetAccess =public.