Inquisit supports a variety logical and arithmetic expressions. Expressions are a powerful feature that enable you to create sophisticated and flexible scripts that dynamically adapt based on a wide range of inputs such as the participant's responses, elapsed time, the computer's capabilities, and more. Best of all, expressions allow you to express adaptive scripts in a natural, intuitive way, making it much easier than before to program adaptive tasks.
If you are familiar with imperative programming languages like JavaScript, Java, C#, PHP, or C, Inquisit's expression language syntax will be immediately recognizable to you. Inquisit's basic expression syntax follows the same rules as these other languages.
If an expression consists of a set of statements, each statement must be delimited by a ";". For example, the following to statements keep a running total of the sum of all response latencies and the total correct responses:
values.sumlatency = values.sumlatency + trial.test.latency; values.totalcorrect = values.totalcorrect + trial.test.correct;
In addition to simple statements, Inquisit expressions can also consist of compound statements such as "if", "else if", and "else". For example, the following statement updates the sum of latencies only if the response latency falls within a certain range:
if (trial.test.latency > 300 && trial.test.latency < 10000) values.sumlatency = values.sumlatency + trial.test.latency;
Multiple statements can be nested under and an "if" condition by placing the statements inside "{" and "}". In the following example, the sum of latencies and total correct are updated only if the latency falls within the designated range:
if (trial.test.latency > 300 && trial.test.latency < 10000) { values.sumlatency = values.sumlatency + trial.test.latency; values.totalcorrect = values.totalcorrect + trial.test.correct; }
"If" conditionals can be accompanied by "else" statements to designated statements to be executed only if the "if" conditional fails. If the following example, if the response latency falls outside the given range, a value tracking the number of discarded trials is incremented by 1:
if (trial.test.latency > 300 && trial.test.latency < 10000) { values.sumlatency = values.sumlatency + trial.test.latency; values.totalcorrect = values.totalcorrect + trial.test.correct; } else { values.discardedtrialscount += 1; }
"Else if" statements allow you to include multiple conditionals. For example, the following updates a points counter based on speed at which a response that was given, rewarding faster responses with more points:
if (trial.test.latency < 500) { values.points += 10; } else if (trial.test.latency >= 500 && trial.test.latency < 1000) { values.points += 5; } else if (trial.test.latency >= 100 && trial.test.latency < 1500 ) { values.points += 1; } else { values.points -= 1; }
Conditional statements can also be nested within each other. The following updates a points counter based on speed at which a response that was given, but only if the response was correct:
if (trial.response.correct ) { if (trial.test.latency > 500) { values.points += 10; } else if (trial.test.latency >= 500 && trial.test.latency < 1000) { values.points += 5; } else if (trial.test.latency >= 100 && trial.test.latency < 1500 ) { values.points += 1; } else { values.points -= 1; } }
Operator reference. This topic covers the syntax for mathematical operations such as addition, subtraction, multiplication, equality. It also covers logical operations such as tests for equality, inequality, greater than, less than, logical AND, logical OR, and so on.
Function Reference. Inquisit contains numerous built in functions that can be used by expressions. Examples include functions for returning the maximum of two values, rounding real numbers to integers, computing the natural logarithm of a value, and more.
There are a number of different Inquisit elements and attributes that make use of expressions. The following provides an overview of how expressions can be used in a script. The reference topics elements and attributes provide more detailed information on where expressions are valid.
Simple attributes are just regular old attributes that can be set to a single numeric value, or possibly a set of numeric values. For example, the timeout attribute consists of a single numeric value, for example /timeout = 1000. The size attribute consists of two numeric attributes, for example /size = (200, 300). Inquisit allows you to set these attribute values to either a constant number such as "5" or a numeric expression such as "sqrt(5) + 22 - text.target.currentitemnumber".
As an example, imagine you are running a task in which subjects try to answer as many questions as possible in 5 minutes. When their time is up, the current trial is interrupted and no more trials are run. One way to achieve this is to set the timeout of each trial as a function of the time elapsed since the task started. This might look like the following:
<trial timedtaska> / stimulustimes = [1=question] / validresponse = ("a", "b") / correctresponse = ("a") / timeout = 18000000 - block.timedtask.elapsedtime </trial>
The above example computes the number of milliseconds remaining in the 5 minute period by subtracting the number of milliseconds that have elapsed since the start of the block from 18000000, which is the number of millisecond in a 5 minute interval.
There is a problem with this expression however, namely that it might return a negative number, which is not a valid timeout value, or it might return 0, which Inquisit interprets to mean no timeout at all. We can fix this by uses Inquisit's "max" function, which returns the maximum value of two expressions. The following ensures that the timeout is never set to a value less than 1 millisecond.
<trial timedtaska> / stimulustimes = [1=question] / validresponse = ("a", "b") / correctresponse = ("a") / timeout = max(18000000 - block.timedtask.elapsedtime, 1) </trial>
Inquisit 6 introduces a set of event attributes that allow you to execute logic at a particular time in the experiment, such as at the beginning or end of a certain type of trial or block. These attributes allow you to configure aspects of the script based on prior performance or tally up custom scores and metrics. Event attributes include ontrialbegin, ontrialend, onblockbegin, onblockend, onexptbegin, and onexptend.
As an example, imagine a decision making task in which you award points based on correct decisions. There are two types of decisions, a relative easy type worth 2 points and a harder type worth 5 points. Using the ontrialend attribute, this is easily accomplished.
First, we'll define a custom value called "score" used to store respondent's running score.
<values> / score = 0 </values>
Next, we'll define the ontrialend attribute on our easy trial to add 2 points to the score with each correct response.
<trial easydecision> / stimulusframes = [1 = easydecisions] / validresponse = ("a", "b") / ontrialend=[values.score = values.score + 2 * trial.easydecision.correct] </trial>
Note that the trial.easydecision.correct property is set to 1 for a correct response and 0 for an incorrect response, so we can simply multiply this property by 2 and add the result to the score. Another way to accomplish the same thing would be to use conditional logic as follows.
<trial easydecision> / stimulusframes = [1 = easydecisions] / validresponse = ("a", "b") / ontrialend= [ if (trial.easydecision.correct == 1) values.score = values.score + 2; ] </trial>
Here, we check if the response was correct, and if so, we add two 2 points.
The last step is to define the ontrialend attribute on our difficult trial, which adds 5 points for each correct response.
<trial harddecision> / stimulusframes = [1 = harddecisions] / validresponse = ("a", "b") / ontrialend = [ values.score = values.score + 5 * trial.harddecision.correct; ] </trial>
Task flow attributes allow you to conditionally determine the flow of a procedure based on prior performance. For example, a script might repeat a practice block of trials until the percent of correct responses meets some criteria before moving on to the critical blocks. Another script may skip a certain type of trial based on the response to a previous question. Flow attributes include branch, skip, repeat, and stop.
As an example, imagine you wish to provide adaptive feedback to participants based on how well they perform a task. Specifically, if they make 5 correct responses in a row, they are shown a message of encouragement. If they make 3 incorrect responses in a row, they are shown a message warning them to slow down and concentrate on the task.
First, we'll define the two trials used to display the encouragement and warning feedback. Note that since these are just feedback trials, we use the recorddata command to prevent the data from these trials from being added to the data file.
<trial encouragement> / stimulusframes = [1 = encouragingwords] / validresponse = (" ") / recorddata = false </trial>
<trial warning> / stimulusframes = [1 = warningwords] / validresponse = (" ") / recorddata = false </trial>
Next, we'll define the trial that runs the main task. This trial includes two branch commands, the first of which runs the encouraging feedback trial and the second that runs the warning.
<trial maintask> / stimulusframes = [1 = taskstimulus] / validresponse = ("a", "b") / branch = [ if (mod(trial.maintask.correctstreak, 5) == 0 && trial.maintask.correctstreak != 0) trial.encouragement; ] / branch = [ if (mod(trial.maintask.errorstreak, 3) == 0 && trial.maintask.errorstreak 1 != 0) trial.warning; ] </trial>
The first branch uses the mod function, which returns the remainder when the first argument, trial.maintask.correctstreak, is divided by the second argument, 5. If the correctstreak is a multiple of 5 (e.g., 5, 10, 15, ...), the remainder is 0 and the first statement in the condition is true. However, the mod function also returns 0 if the correctstreak is 0, in which case we don't want to show the encouragement trial. So, a second statement checking whether the correctstreak is 0 has been added. Now the encouragement trial is run after ever streak of 5 correct responses.
The second branch is similar, except that it shows a warning trial for every streak of 3 incorrect responses.
Expressions can also be incorporated into stimulus items in order to be displayed on the screen. For example, a text stimulus might include expressions representing their performance, a score, or the amount of time remaining in a task. In order to distinguish regular text from text that should be evaluated as an expression, expressions must appear between <% and %>. Anything appearing between these delimiters will be evaluated as an expression. Otherwise, the text is displayed as is.
The following example shows a text item that displays a participant's total score on a judgment and decision making task by adding subscores from phase 1 and 2:
<text score> / items = ("Total score: <% values.phase1score + values.phase2score %>") / position = (50%, 5%) </text>
The following text shows the number of minutes that have passed since the start of the session. Since the script.elapsedtime property returns the duration in millisecond, the value is divided by 60000 and rounded to the nearest integer to convert it to minutes:
<text elapsedminutes> / items = ("Total minutes: <% round(script.elapsedtime / 60000) %>") / position = (50%, 5%) </text>
Just like stimulus items, expressions can also be inserted into instruction pages. Again, anything appearing between expressions must appear between <% and %> is evaluated as an expression. Otherwise the text or html is displayed as it is.
The following instruction page shows the mean latency on a block rounded to the nearest integer.
<page feedback> Your average response time was <% round(block.test.meanlatency) %> </page>