PennController for IBEX › Forums › Support › mathematical operations with variables
- This topic has 1 reply, 2 voices, and was last updated 3 years, 2 months ago by Jeremy.
March 27, 2020 at 10:52 am #4917crtormaParticipant
I’m wondering if there’s a way to do mathematical computations with variables, beyond .test.is(). Essentially, we have an experiment where there’s a set from which participants can select items. Each set has a maximum number of items that can be selected and still have the trial considered correct — so participants can select up to and including the maximum number. This isn’t particularly conducive to the .test.is() notation, since it would have to check multiple values, and since the maximum number is different with different sets.
I’ve been trying to use a calculation like the one below:
newVar('a_set', ((row.a_maximum_items - getVar('a_items_selected') >= 0) ? true : false)), newVar('b_set', ((row.b_maximum_items - getVar('b_items_selected') >= 0) ? true : false)) .test.is(true).and(getVar('a_set').test.is(true)
and have been trying various forms of the notation to try to get the code to recognize the arguments as the types required to evaluate, but without any luck. Would you happen to know how to make this work in the syntax / have an idea for an alternate method that would work?
(Also, thank you for your patience with all my questions on this experiment! I realize I’ve had to reach out for quite a few things regarding this experiment, so please know I really appreciate it!)
CindyMarch 27, 2020 at 1:14 pm #4918JeremyKeymaster
I have to admit that the way that Var elements work can be very confusing, and I have struggled a lot about how to best implement them when designing PennController: the current state is my best attempt at making them user-friendly, so I guess it is complicated indeed… (or maybe I’m just very bad at making things simple)
At some point (and you’ve reached that point here) it becomes important to understand how PennController commands are evaluated: the new* commands that create in-trial elements are actually evaluated when you open your experiment (ie. your experiment always starts by referencing all the elements of all your trials), but the commands that you call on the elements (print, wait, …) are evaluated upon runtime. Now, that delayed evaluation is also what makes it possible to use getVar('name') with the expectation that it will return the value at the time when the command is evaluated. However, if you try to use getVar('name') anywhere else, like inside a new* command, it will return something completely different (something that “represents the need to evaluate later,” if you will, but not the value itself)
TL;DR: you cannot refer to getVar inside the parentheses of your newVar commands (or any new* command for the matter). Here is a very minimal change to the bit of code you report that should do the trick (notice the two-step subtraction method):
newVar('a_set').set( getVar('a_items_selected') ).set( v => row.a_maximum_items-v >= 0 ), newVar('b_set').set( getVar('b_items_selected') ).set( v => row.b_maximum_items-v >= 0 ) .test.is(true).and( getVar('a_set').test.is(true) )
Here is a full example of a script that uses a slightly different method, hopefully it will give you an idea of how things work (I’m using uppercase and lowecase letters as ‘items’). It’s maybe a bit too sophisticated in some sense, but I think it’s a good illustration of how Var elements work:
PennController.ResetPrefix(null) AddTable('mytable', `U1,U2,U3,U4,l1,l2,l3,l4,max_U,max_l A,B,C,D,a,b,c,d,2,3 E,F,G,H,e,f,g,h,3,1`) // We'll call this function to create three Var, Selector and Text elements for all 8 items in a trial add_item = (row,item) => [ newVar(item, false) // The element is unselected at the beginning of the trial (=false) , newSelector(item).callback( // When the element is clicked, proceed conditionally getText('error').remove() , // Note: item below returns the first character of item, which is either U or l getVar(item).test.is(false) // Flag as true and increment U/l_selected if was unselected .success( getVar(item).set(true) , getVar(item+'_selected').set(v=>v+1) ) .failure( // If was selected, unselect, flag as false and decrement U/l_selected getSelector(item).unselect() , getVar(item).set(false) , getVar(item+'_selected').set(v=>v-1) ) ) , // Don't forget to print text element onto the page and add it to the selector newText(row[item]).selector( item ) ] Template( 'mytable' , row=> newTrial( newText("error", 'You can only select up to '+row.max_U+' uppercase letters and up to '+row.max_l+' lowercase letters!' ) , defaultText.center().print() , // These will keep track of how many items are selected in each category newVar('U_selected',0),newVar('l_selected',0) , // We use the helper function here to execute the commands defined above on all 8 items add_item(row,'U1'),add_item(row,'U2'),add_item(row,'U3'),add_item(row,'U4'), add_item(row,'l1'),add_item(row,'l2'),add_item(row,'l3'),add_item(row,'l4') , newButton("Validate").center().print().wait( // Wait will be validated only if U_selected and l_selected are below or at max_U and max_l // NOTE: the forum adds a space between < and = which should NOT be here getVar('U_selected').test.is( v => v <= row.max_U ) .and( getVar('l_selected').test.is( v => v <= row.max_l ) ) .failure( getText("error").print() ) ) ) )
Let me know if you have any questions,
- You must be logged in to reply to this topic.