New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simulation mode for experiments #1886
Conversation
This is awesome! I'm hoping to add a simulation mode like this fairly soon (before the end of November at the latest). I'm sorry I missed this PR when you first submitted it. Are you interested in collaborating on updating this for version 7.0, and perhaps tweaking some of the implementation? |
Yes, I am happy to collaborate on this. I've continually used this in my own projects (which was the main purpose I developed it) and so far it has been spotless. Even in the most customized projects I am doing right now, it still holds up. The only issue I have encountered, unfortunately, is that it is not fully backwards compatible with older jsPsych versions (due to updates to event listeners) but not difficult to tweak. I've been keeping an eye out on the work on modernising the code in jsPsych, so hopefully I can be of help. Let me know how you would like to proceed. |
@bjoluc and @becky-gilbert also curious to get your perspectives on what the final API should look like here. I think so much of this already makes perfect sense. A few things that I thought of after a first read through:
|
Hi @nikbpetrov and @jodeleeuw,
|
I'm coming back to this with the intent to start diving in more. One design decision that I noticed in your implementation @nikbpetrov is that the simulation mode actually interacts with the real trial content. That's neat! My initial instinct was that a simulation mode could just skip the whole trial and call An advantage of your approach is that the actual trial methods would be called so folks could observe the experiment directly, which seems useful for checking for visual errors among other things. When you use the simulation mode in your own work, which mode do you think would be more useful? |
@jodeleeuw That's actually a feature I use quite often when I am still testing. The benefit of this approach -- especially during local testing - is that I can call the simulation mode with a speed of 1s per trial and just watch what would happen in the experiment (making sure that logic runs as expected and everything displays as I want). If I just want the data, I can just run the same thing with a speed of 100ms (or less) and get the data in 3-4 seconds or so. The simulation mode where the time per trial is set to 1s can also be useful for collaborators who just want to run through and see how the experiment looks like. Another useful feature is that if I use this visual simulation mode I can set Hope this helps! |
Hey @nikbpetrov I'm going to close this because it can't be merged with the 7.0 changes and #2287 accomplishes this now. However, I think it would be worth posting your guide in the discussions for anyone who wants to enable simulation features with v6. Maybe that will be a little easier to find than a closed PR. Thanks again for all of the inspiration here that made #2287 possible! |
Abstract (tl;dr)
Want to automatically run through your experiment, simulate a real participant and save the data? The simulate method does exactly this.
To use:
jspsych.js
with the new onejspsych.js
jspsych.js
and set the location 2 new scripts (the same source as you would add to yourindex.html
)Essentially, you will be adding the two new scripts in your
index.html
but you will do that withinjspsych.js
. For example, if your folder structure looks like this---index.html
---js
------jspsych.js
------get_simulate_method.js
------modify_trial_options_for_simulation.js
then the source for the scripts will need to be set to (this is the default):
Add
simulate: true
on thejsPsych.init
method like this:NB The current implementation of the simulate method is likely not backwards compatible with older versions of jsPsych. Ensure you are using >v6.3 if possible.
For details, additional parameters and more, read on.
1. Functionality
This PR implements a simulate method for all available plugins (see #647). This method simulates random user behaviour on all trials in the experiment and allows to quickly go through an experiment, potentially spotting problems, and generates random data in the end.
1.1. Running the experiment in simulation mode
The simulate method can be run by setting the
simulate
parameter on thejsPsych.init
method totrue
(boolean), which is a sufficient condition to run the experiment in simulation mode. This would look like this:It is also possible to simulate a specific trial by setting its
simulate
parameter (which is a universal parameter across all plugins) totrue
. Ifsimulate
is set on any specific trial, the trial-specific definition will take precedence (see more about specificity below). For example, in this case the globalsimulate
is set tofalse
(it could very well be left undefined, it does not matter) on thejsPsych.init
method, but the trial'ssimulate
parameter is set totrue
. Hence, this specific trial will be run in simulation mode (example fromdemo-simple-rt-task.html
):1.2. Simulation options
When simulation mode is activated, each trial's parameters can be modified during the simulation. The simulations parameters can be controlled either via the
jsPsych.init
method or by explicitly defining them on any individual trial. Timeline variables and functions can also be used, just like they would be set outside of simulation mode.If a trial's parameter is not set to a specific value in simulation mode, the trial-defined value will be used.
1.2.1. Simulation options defined in the
jsPsych.init
methodSimulation options on the jsPsych.init method can be defined in two different ways, depending on the level of specificity needed.
They can be defined in a
simulate_all_trials_opts
parameter. This parameter takes in an object as its value that overwrites all trials' parameters during simulation mode. For instance, we can overwrite all trials' stimulus parameter during simulation mode like so:Thus, our
welcome
trial will have the text defined insimulate_all_trials_opts.stimulus
as its value. The original stimulus will display outside of simulation mode.Simulations options can also be defined in the
jsPsych.init
method in thesimulate_trial_type_opts
parameter. This parameter takes in key-value pairs as its values, whereby the key is a specific trial type (i.e. plugin name, e.g.html-keyboard-response
,image-button-response
,survey-multi-choice
), and the values of the keys are objects that overwrite the parameters of the specific trial type during simulation mode. This would look like this:1.2.2. Simulation options defined on a specific trial
In addition to defining simulation options within the
jsPsych.init
method, all trials take a universalsimulate_opts
parameter, which accepts an object that defines the the trial's parameters to overwrite. For example:Note that this parameter is updated during runtime in order to show specifically which trial's parameters are changed during the simulation mode -- see the "A trial's simulation_opts values" section below.
1.2.3. Simulation response time
An additional parameter can be set during simulation mode, namely
simulate_response_time
. This is a universal plugin that can be set on any one specific trial. Here are example of how this can be set successfully:1.2.4. Simulation options precedence
Given that simulation options can be set in a few different ways, it is possible that a trial's parameter is defined in a few different places. In the cases of conflicts, the more specific trial parameter definition takes over. Hence, the simulation trial specificity is as follows:
Simulation trial parameters defined on a specific trial (
simulate_opts
for any given trial) >>>Simulation trial parameters defined for a specific trial type (
simulate_trial_type_opts
on thejsPsych.init
method) >>>Simulation trial parameters defined for all trials (
simulate_all_trials_opts
on thejsPsych.init
method)1.2.5. A trial's simulation_opts values
During runtime the
simulation_opts
of an individual trial are overwritten to let the user know which trial's parameters are set for the simulation and where they are coming from. Thesimulation_opts
value is a nested object looking like this:where each key tells you where a specific parameter is set from. Here's an example of a trial object. Note how during trial definition there is conflict between trial durations: the trial parameter is set to 2000, but during simulation, the trial duration is set to 1500 for all trials, to 1200 for all
html-keyboard-response
s, and to 1000 for the specific trial. In the end, only the trial duration withinsimulate_opts
of the specific trial gets successfully applied during simulation as this is the most specific one. This is then reflected in the trial object during runtime.And the evaluated trial object would look like this during simulation mode:
1.2.6. The 'same_as_simulate_response_time' keyword
Simulation options can also be set using the
same_as_simulate_response_time
keyword. This allows to standardize runtime for some plugins. During runtime, the keyword is evaluated to be the same as the set response time -- see default values below for an example.1.2.7. Simulation options default values
1.2.7.1. Default values on the
jsPsych.init
methodThe entire experiment will not run in simulate mode by default (
simulate: false
). But if it does run, the simulated response will occur in 500ms (simulate_response_time': 500
). It is possible that the trial duration is shorter than thesimulate_response_time
-- in this case the trial will continue without a response.Some trial-type specific parameters are also set to be the same as the
simulate_response_time
, using thesame_as_simulate_response_time
to ensure smooth and quick simulation running of the experiment by default, but these can be overwritten by the user. The defaultpost_tral_gap
is also set to 0 on all trials (note that it's a universal parameter so it will apply everywhere) to ensure smoother default running.NB Setting the
post_trial_gap
parameter means that thedefault_iti
on thejsPsych.init
method will never be evaluated. Revert the value ofpost_trial_gap
tonull
manually if you want thedefault_iti
to be evaluated during simulation mode.1.2.7.2. Default values of the trial-specific simulation-related parameters
As already mentioned there are 3 new universal parameters for all plugins.
The first one is
simulate
, which is a boolean with a default value ofnull
assimulate
is, by default, set from thesimulate_all_trials_opts
of thejsPsych.init
method.The second one is
simulate_response_time
, which is an integer with a default value ofnull
assimulate_response_time
is, by default, set from thesimulate_all_trials_opts
of thejsPsych.init
method.The third one is
simulate_opts
, which takes in an object whose default value is{}
-- this defines trial-specific parameters to modify during runtime (but also see the "A trial's simulation_opts values" section above).2. Overview of code changes
Note that the current PR is purposely highly modularised as the goal is to make as FEW changes to the existing
jsPsych
code base as possible. This not only allows easier code review/troubleshooting, but also allows users to use the functionality by adding it to their own projects while the PR is being reviewed. For each change below, I will suggest where it might land after code review to be integrated within jsPsych but that is up to the reviewers.2.1. Updates to
jsPsych
core moduleindex.html
of each one. During code review, change the directory filepath to start with..
instead ofjs
to locate the files. This will obviously not be preserved at all in the final product.Object_assign_nested
which is an upgraded recursive version of the built-inObject.asign
. This is then used to assign defaults to the options in thejsPsych.init
method on line 206. This is needed because the default simulation-related variables are nested.simulate
,simulate_all_trials_opts
andsimulate_trial_type_opts
parameters of the core module (lines 131-191).doTrial
function (starts at line 1059).setDefaultValues
function call, the options for the current trial are modified based on the user specifications if simulation is requested. Notably, timeline variables and function parameters are re-evaluated. This will likely need to be modified to prevent 2 repeated function calls but would likely require changes to the infrastructure either of thedoTrial
function or a newdoSimulation
functiontrial
method is still executed even in simulation mode as there is a lot of necessary handling within thetrial
method (even listeners, timeout handling etc.)trial
method,on_load
functions, extensions etc), then, if we are in simulation mode, the trial'ssimulate
method is set and executed.simulate
,simulate_response_time
andsimulate_opts
) - lines 1409-14262.2. Adding
modify_trial_options_for_simulation.js
preload
,call-function
,external-html
,virtual-chinrest
,webgazer-calibrate
,webgazer-init-camera
,webgazer-validate
,free-sort
) and send a warn message in the console.free-sort
is expected to have a simulate method but does not have one yet.same_as_simulate_response_time
keyword and saving the options in the trial'ssimulate_opts
parameter so that the user can see/troubleshoot easier2.3. Adding
get_simulate_method.js
trial.simulate
method and return the plugin.simulate_all_trials_opts
if preferred.2.4. examples-with-simulate folder
This folder is a copy-paste of the examples folder with the only difference that all
.html
files now havesimulate: true
in theirjsPsych.init
calls3. Some technical notes
There are a lot of notes (in the form of comments) throughout the code on what I think needs to be reviewed, improved etc, but here I make some more big-picture notes.
get_simulate_method.js
it is possible to replace the repetition of the timeout at the end of each simulate method by abstracting it in a global function but would make it less readablesame-different-html
andsame-different-image
) but it's better to repeat it as this is likely to go in separate files later (as mentioned above)3.1. Potential bugs (mostly with custom setups)
disabled
attribute set todisabled
) in an on_load function. The simulate method is then expected to behaviour weirdly and not to take that into account. This could later be addressed by adding some additional handling.3.2. Potential list of to-do’s later on
simulation_response_time
parameter is and have a very tiny variance. For people who want to quickly simulate data and do mock analysis, that might be suoptimal. A new universal parameter can be added (something likesimulate_random_rt_data
set to true/false) that will overwrite the data object at the end such that all RT values are random numbers in some range.Disclaimer
I am not an expert on using git so hopefully I've done this correctly.
Not a coding expert either, just an enthusiast that googles profusely.