The Lua "version a" of Settle was much longer than the original. Including blank lines, the Lua recipe was 61 lines, where the original GUI was only 17 commands. By taking advantage of some additional programming features, the size of the Lua version can be reduced significantly.
During testing of "version a", it became apparent that the Lua version behaved differently that the GUI original. During the Local Wiggle Sequence command in the GUI, all the segments between a pair of frozen segments would pulsate, indicating they were being wiggled.
In contrast, the Lua recipe was written to wiggle only a single segment between the frozen segments. This doesn't match the undocumented way that the GUI version wiggles multiple segments. The way the GUI version works actually makes sense, however, so the Lua version should do the same thing. Fortunately, it's easy to do.
The first step in refining "version a" is to look for repeated groups of similar statements.
Repetition[edit | edit source]
As seen in the image on this page, the first part of the GUI version consists of repetitions of the following commands:
- Freeze Segments by stride
- Local Wiggle Sequence Segments by stride
- Unfreeze All segments
Each repetition works with a different group of segments.
One repetition works slightly differently:
- Freeze All segments
- Unfreeze Segments by stride
- Local Wiggle Sequence Segments by stride
- Unfreeze All segments
It's not clear why the recipe used that variation. The same results can be accomplished by unfreezing all, then freezing specific segments.
Each set of repeated commands has three things that change:
- the segment index to start freezing by stride
- the segment index to start local wiggle sequence by stride
- the number of segments involved
In a programming language like Lua, it's easy to handle this type of situation.
Lua version[edit | edit source]
From Lua "version a" of Settle, the first two repetitions involve statements 1 to 2 and statements 3 to 5 from the GUI. For consistency, the recipe really should have started with "freeze.UnfreezeAll", just in case there were frozen segments left over from previous work.
Some slight changes to first two sets of repeated commands can help show what they have in common:
-- -- repetition 1 -- startFreeze = 2 -- segment to start freezing startLWS = 1 -- segment to start local wiggle inc = 4 -- segments to process for ii = startFreeze, segCnt, in do -- statement 1 freeze.Freeze ( ii, true, true ) -- statement 1 end -- statement 1 for ii = startLWS, segCnt, inc do -- statement 2 selection.DeselectAll () -- statement 2 selection.Select ( ii ) -- statement 2 structure.LocalWiggleSelected ( 3 ) -- statement 2 end -- statement 2 -- -- repetition 2 -- startFreeze = 1 -- segment to start freezing startLWS = 2 -- segment to start local wiggle inc = 2 -- segments to process freeze.UnfreezeAll () -- statement 3 for ii = startFreeze, segCnt, inc do -- statement 4 freeze.Freeze ( ii, true, true ) -- statement 4 end -- statement 4 for ii = startLWS, segCnt, inc do -- statement 5 selection.DeselectAll () -- statement 5 selection.Select ( ii ) -- statement 5 structure.LocalWiggleSelected ( 3 ) -- statement 5 end -- statement 5
Now the only difference between the two repetitions is the value of the variables "startFreeze", "startLWS", and "inc". (As before, the variable "segCnt" is the total number of segments in the puzzle, which is set earlier.)
The next step is to reuse the repeated statements, replacing each group with just one statement.
Function! (first cut)[edit | edit source]
In Lua, the "function" statement can be used to deal with the type of repetition that's present in Settle. Functions are a feature in most programming languages, but they also have different names, like "subroutine" or "method", depending on the language.
Lua "version a" of settle already used several functions. The only difference is that now Settle is going to include its own function.
A function doesn't do anything until it's "called" from another spot. Functions can be "passed" a list of "arguments" or "parameters" which contain the information the function needs to do its job. In this example, the variables "startFreeze", "startLWS", and "inc" will be the arguments.
The initial version of a function for Settle looks like this:
function FreezeLWS ( startFreeze, startLWS, inc ) freeze.UnfreezeAll () for ii = startFreeze, segCnt, inc do freeze.Freeze ( ii, true, true ) end for ii = startLWS, segCnt, inc do selection.DeselectAll () selection.Select ( ii ) structure.LocalWiggleSelected ( 3 ) end end
"FreezeLWS" is the name of the function, which will be used to call the function later on.
The arguments "startFreeze", "startLWS", and "inc" appear in parenthesis. These names can be used as variables in the function. These argument names override any other variable names. For example, if you already have a variable "inc", it won't affect FreezeLWS. FreezeLWS will only use its argument "inc". Any changes that FreezeLWS makes to "inc" won't affect any other "inc" in the program.
As before, the variable "segCnt" is a global variable, which can be seen anywhere in the program. Many Foldit recipes set this variable as one of their first steps. The only requirement is that "segCnt" should be set before FreezeLWS is called.
It's not shown here, but functions can also "return" values to their callers. Lua functions can return multiple values, which is somewhat unique. Most other languages allow only a single return value. The "return" statement can be used to return values and end the function. This example doesn't return anything, so it doesn't include a "return". The function ends with the last "end" statement.
Function! (second cut)[edit | edit source]
The previous example showed how to make a function for Lua "version a" of Settle.
Based on some careful observation of pulsating segments, "version a" didn't quite match what the GUI version of Settle was doing.
This version of the function is a better match to the GUI Settle:
function FreezeLWS ( startFreeze, startLWS, inc ) freeze.UnfreezeAll () for ii = startFreeze, segCnt, inc do freeze.Freeze ( ii, true, true ) end for ii = startLWS, segCnt, inc do selection.DeselectAll () local jj = ii + inc - 1 jj = math.min ( jj, segCnt ) selection.SelectRange ( ii, jj ) structure.LocalWiggleSelected ( 3 ) end end
It uses the same arguments as the first version. Instead of just selecting a single segment, it calls selection.SelectRange to select one or more segments.
The selection still starts with the segment specified by "ii". The ending segment is calculated by these statements:
local jj = ii + inc - 1 jj = math.min ( jj, segCnt )
The ending segment is "jj", a local variable. This variable will only exist inside the for loop. When the for loop reaches the "end" statement, the value of "jj" vanishes.
The "math.min" is a built-in Lua function that returns the minimum of two numbers. Here, it's used to make sure that we don't try to select segments beyond the end of the protein. That's the sort of thing that GUI recipes take care of automatically, but require care in Lua.
As an example, if "ii" is 3, "inc" is 4, and "segCnt" is 80, the calculations are:
local jj = 3 + 4 - 1 (jj is now 6) jj = math.min ( 6, 80) (jj is still 6)
So segments 3 ("ii") to 6 ("jj") will be selected in this example.
Function call![edit | edit source]
The next step is to convert the repeated statements into calls to FreezeLWS.
The first two repetitions are simple:
FreezeLWS ( 2, 1, 4 ) -- statements 1-2 FreezeLWS ( 1, 2, 2 ) -- statements 4-5
Here, we're just passing numbers to FreezeLWS. We could also use variables, but there's no real need at this point. The three numbers we're passing become the arguments "startFreeze", "startLWS", and "inc" in FreezeLWS.
We must pass three values, or an error occurs, which ends the recipe. Also, all three arguments must be numbers, or different errors will occur, also ending the recipe.
The third repetition was different. It froze all segments, then unfroze every second segment, starting at segment 1. So segments 1, 3, 5, 7, and so on would be unfrozen, and these segments would then be local wiggled.
This is the same as starting the freeze and segment 2, and the local wiggle at segment 1, so FreezeLWS can still be used:
FreezeLWS ( 2, 1, 2 ) -- statements 7-8
The fourth and final repetion is simple:
FreezeLWS ( 2, 1, 3 ) -- statements 10-11
All in one[edit | edit source]
Here is the refined version of Settle. The 61 lines of Lua in "version a" have been reduced to 39 lines in this version, again counting blank lines.
With a few added comments and some extra code to provide timings, this version of Settle has been shared as Co lapse's Settle Lua version c.
segCnt = structure.GetCount () -- get number of segments, save in "segCnt" function FreezeLWS ( startFreeze, startLWS, inc ) freeze.UnfreezeAll () for ii = startFreeze, segCnt, inc do freeze.Freeze ( ii, true, true ) end for ii = startLWS, segCnt, inc do selection.DeselectAll () local jj = ii + inc - 1 jj = math.min ( jj, segCnt ) selection.SelectRange ( ii, jj ) structure.LocalWiggleSelected ( 3 ) end end FreezeLWS ( 2, 1, 4 ) -- statements 1-2 FreezeLWS ( 1, 2, 2 ) -- statements 4-5 FreezeLWS ( 2, 1, 2 ) -- statements 7-8 FreezeLWS ( 2, 1, 3 ) -- statements 10-11 freeze.UnfreezeAll () -- statement 12 for ii = 1, segCnt, 2 do -- statement 13 freeze.Freeze ( ii, true, true ) -- statement 13 end -- statement 13 structure.WiggleAll ( 30 ) -- statement 14 freeze.UnfreezeAll () -- statement 15 structure.ShakeSidechainsAll ( 1 ) -- statement 16 structure.WiggleAll ( 30 ) -- statement 17
Testing[edit | edit source]
The by stride option has not been working for some time in Foldit. This version of Settle was tested on 20180411-0d2a98333f-win_x86-devprev, which includes a working version of "by stride". It produced similar results to the original GUI version of Settle.