Profile


Name
iwdn HBNW 1.3
ID
105175
Shared with
Public
Parent
iwdn HBNW 1.1
Children
Created on
April 28, 2022 at 20:48 PM UTC
Updated on
April 28, 2022 at 20:48 PM UTC
Description

Finds H-Bond networks

Best for


Code


--iwdn hbnw --version 1.3 28.02.2022 --H-Bond NetWorker: Script to semi-automatic find H-Bond networks. --What it does: --This recipe tries to extend the excellent work of spvincent's H-Network Probe. --In hbnw not the H-Bond network-bonus is used as criterion to valuate if a net has formed but the function "structure.GetHBonds()" is used instead. --See the post "https://fold.it/portal/node/2007744" for more details. --This recipe will try to bond sidechains together as much as possible. --It uses direct mutation and then shaking and wiggling for this. --In order to use this you should first freeze all sidechains (you can use for example the public recipe "iwdn ufreeze SC 1.0" for this) and then de-freeze the ones that you want this recipe to act on. --Basically, the unfrozen sidechains should be mostly core-residues. Especially in symmetry-puzzles, these should be the core-residues at the interface between monomer and symmetric copies (plus maybe some surrounding additional sidechains). --Since version 1.2 there are different modes which can be used. The previous mode in older versions is now called single-mode and the new mode is called Auto-Grow (AG). --I recommend using AG in the future. Especially for symmetry-puzzles with HBond-bonus there is a better chance to find HBbonus-nets than for single-mode. Got astonishing results with AG. --In AG-mode it is tried in multiple iterations (iter) to first find a basic net and then to extend it in further iterations. Bonds are only accepted and incorporated into the net when they are connecting to the net from the previous iteration. --Like this, the net will grow in AG-mode all over the structure. --Be aware though that there is no valuation of the net done by hbnw. So it may find you nets with open ends. You will have to manually optimize your net by removing unwanted elements from the net. This is possible in most cases and leads to higher-quality nets which then give good HBbonus. --But in any case the AG-mode will give you a good impression of where it is promising to build up your net. --In single-mode it is gone randomly through all possible sidechain-mutation-poses and detected (after shake&wiggle) with "structure.GetHBonds()" if one or more new HBonds have formed. Duplicate poses are avoided. --The nice thing is then that it is checked if such a net was already found. This is done by comparing the residues on which it has formed and also the atoms of the sidechains on which it has formed. --This will reduce the number of found nets but it increases dramatically the quality of the found nets. --Be aware though that some nets may not be kept. There are sidechains which have the same atoms at the same sidechain-position and only ONE of these nets will be kept. --So it might be worthwile to play around with the found nets and maybe change one or more sidechains in it to an "equivalent" mutations. --The stored nets are quick-saved and after the recipe has finished they are loaded into undo-stack so that you can flip through them via ctrl+z/y (thanks again spvincent for this nice function!). --The ultimate goal of this recipe for me is to find AND EXTEND nets automatically. This is now implemented in a first step with the AG-mode. --In single-mode, you should run the recipe on a few residues. Then check the found nets and re-run this recipe on non-bonded residues in the vicinity of the found net. Like this your net will grow. --In AG-mode, this is done automatically for you. You just need to specify an initial center-segment around which your net shall grow (or you can leave the option "AG center seg(s)" set to 0 and hbnw will randomly choose a center-segment for you. You can watch the net grow then in the further iterations. --I got very good results in AG-mode on puzzle 1980. But you will probably have to hand-optimize the found nets and your protein in general to get rid of bad-scoring configurations in the found net. --It should be noted though that it may be better to fist find some initial nets and then try to extend them by re-running this recipe. In AG-mode you have the option "AG init base seg(s)" for this. In this you can specify the segments of an already present net and this will be used in further iterations to connect to. So this initial net will be extended. So if you have a net with e.g. HBbonus in a symmetry puzzle, you can use this to extend this net and get better HBbonus. In this case, the center segment should be chosen to one of the initial net segments. --This recipe is general so it can be used on any kind of puzzle and not only ones with H-Bond bonus! This is very nice I think and I thank the developers very much for implementing "structure.GetHBonds()"! --Be aware that some parts of this recipe still need speed-optimization. Sometimes it looks like it may have stalled but it should go on after a while. In single-mode, the duplicate-check may take quite a bit of time especially if already many nets were found. --The function structure.getHBonds() itself is not the fastest but that is nothing I can do something about. --Please be aware that there may be bugs in this recipe since due to the increased number of options now it was not possible to debug and test everything in detail. Getting this core-functionality going was already quite painsome due to its complexity and the plan is to improve this recipe over time. --Nevertheless I got very good and promising results especially on symmetry-puzzles with HBbonus. --version history --1.0: --Initial release. Base-functionality and GUI. Nets are not extended yet automatically. You have to run the recipe multiple times to extend nets. --1.1: --Smarter routine to set mutation-poses. This does not require any pre-stored large matrix anymore. --Therefore you can now select almost an arbitrary number of unfrozen segments. --Better ending-sequence. Even when the program is cancelled all the good found nets are loaded to undo-stack. --Fixed a bug where the recipe would halt on puzzles which do not have HB-bonus --Fixed a bug where wrong quicksaves were loaded upon end of the recipe --Improved speed of duplicate-net detection. Only nets with the same order (bonds count) are compared in-depth. --Fixed numerous bugs and did some further improvements. --1.2: --Added settings for behavior.SetSidechainHBondImportance(3) and behavior.SetBackboneHBondImportance(3) since we want to create as many bonds as possible and loose as little as possible. --Added setting AAs to test via GUI --Added option to keep only nets with added HBbonus --Added option for linker puzzles: Sidechains on locked segments are mutable. --Implemented "Auto-grow" modus. Found nets are stored and reloaded. --Auto-grow: found nets are extended in multiple turns until stall. Like this large nets can quickly be found which often have good HBbonus. --Fixed a bug in the setting of poses (data-format-limit). There is now no limit on the number of sidechains anymore. For large number of unfrozen sidechain direct mutation is used in AG with no stored poses. --Added settings to limit the time to work on a single net in AG-mode (give-up-criterions: maximum number of poses to try or/and maximum number of iterations to try) --Added option to set random center-segment for AG. Set "0" in center segment list! --Added auto-grow chaos mode. All found bonds are kept. The found nets will in most cases be far less nice than without this option but maybe it can help in some instances. --The HBfilter is now always active if it is available. --Fixed a serious bug in single-mode where only few or no new nets were found when there were already bonds between the sidechains to mutate. Now uses netDiff instead of simple bond-count check --Added verbosity and timestamps for a bit more detailed status-output. --Modified distance-check to use bands in every case because bands need anyway to be used for symmetric puzzles. --Exchanged "structure.LocalWiggleSelected()" with "structure.WiggleSelected()" because the Local function did not behave properly with the sidechain/backbone only option. --Now in every case the initial solution before any modification is stored in the first quickstart-slot. This is necessary for AG and also helpful to identify the start when flipping through the results. --Added mode to extend a given net with additional auto-grow. The given net must be specified in in the GUI in "AG init base seg(s)". --Heavy bugfixing and performance improvement. --30.04.2021: --Fixed a small bug where the last iteration was not fully completed in AG-mode when the iteration-limit was reached. --Added a text-output when all poses are tried and the recipe ends. --For AG-mode the initial, unused AAs are restored before the solution is quicksaved. This is helpful if you want to directly use the result to go on. --Mutable Cysteines are now detected as segments to work on. Since they have no sidechain, they cannot be flagged to NOT being used. If you don't want to use them, then mutate them to something else and freeze the sidechain before running this recipe. --09.05.2021: --Added second dialog for more AG options: --It can now be chosen if already existing bonds of a secondary net (which gets connected via a bond to the main net) should get incorporated into the net or not. This was not done previously and it lead to not incorporating bonds that never change (e.g. on binder) --Special mode for ligands etc: the center never changes and connections are only accepted to it. Like this you can quickly bind (as much as possible) to a ligand when you specify it as center. But the net will not grow further beyound segments that are connected to the center directly. --It can now be chosen if for AG the unused segments (the ones not connecting to the net) should be reverted to their inital AA before storing. This gives much cleaner/nicer looking saves/results. --Added option to input the segments to work on as space-separated text-list instead of unfrozen elements. --1.3: --06.09.2021: --Prepared for public release --Implemented the possibility to specify CI for shake and wiggle. Initial CI before running this recipe is restored when the recipe is done. This is also done for Sidechain- and BackboneImportance. --28.04.2022: --Added the option to leave all filters completely untouched. Like this, you can choose for yourself which filters you want to have active while running this recipe by en- or disabling them before executing this recipe. --Variables and Constants --************* Change if desired ******************* local qs_idx_start = 3 --startindex for quick-saves. Is overwritten from value in GUI --*********** End: Change if desired **************** --Internal global variables local version = "1.3" local dia = {} --Global GUI local dia2 = {} local extNets = {} --Stores all found nets local qs_idx = qs_idx_start local posesDone = {} local guardInt01 = 1 --in secs local turnFiltersOn = false --Can be used to keep filters off. May make thinks more stable? local grow_iters = 1 --needs quasi-global access! local CI_initial = 1 --CI before running this recipe. This is overwritten at start of the recipe local CI_SC_initial = 1 local CI_BB_initial = 1 --Function definitions function wait(seconds) local start = os.time() repeat until os.time() > start + seconds end function mysplit(inputstr, sep) if sep == nil then sep = "%s" end local t={} for str in string.gmatch(inputstr, "([^"..sep.."]+)") do table.insert(t, str) end return t end function round(inNum) return inNum-inNum%0.001 end function getScore() return current.GetEnergyScore() --This function is newer and should be used instead of GetScore() end function checkFilters() --Check the objectives of interest and store for later reference. Currently this is only HBbonus. local currFilters = {} local filterNames = filter.GetNames() local filterBonus_tmp = nil if (filterNames[1] ~= nil) then for m=1,#filterNames do local filterName = filterNames[m] if (filter.HasBonus(filterName)) then --Store here filters that may not degrade in score. These will be protected with the save/restore-scheme. if (string.find(string.lower(filterName), "hydrogen") ~= nil and string.find(string.lower(filterName), "network") ~= nil) then filterBonus_tmp = filter.GetBonus(filterName) table.insert(currFilters, {filterBonus_tmp, filterName}) end end end end return currFilters end function checkMutables(cm_toWorkOn) local outArr = {} for m=1,#cm_toWorkOn do outArr[m] = {} end for m=1,#cm_toWorkOn do if (structure.IsLocked(cm_toWorkOn[m]) == true) then --Strange: On binder-puzzles the sidechains on the target cannot mutate but CanMutate states that it can?! if (dia.cbx00.value == false) then --This is not very nice. The dialog is accessed from within this function. Clean it up someday. outArr[m][1] = structure.GetAminoAcid(cm_toWorkOn[m]) else --This means that non-locked sidechains on locked segments are mutable. This is the case for linker-puzzles. for n=1,#aasAll do if (structure.CanMutate(cm_toWorkOn[m], aasAll[n]) == true) then table.insert(outArr[m], aasAll[n]) else end end end else for n=1,#aasAll do if (structure.CanMutate(cm_toWorkOn[m], aasAll[n]) == true) then table.insert(outArr[m], aasAll[n]) end end end end return outArr end function getDistances(gd_testArr, gd_center) --Determines reliably the minimal distances between the sidechain of the center-element and ALL others --Distance refers to the beta-carbon. Only bands can be used in symmetry puzzles for this since structure.GetDistance() cannot be used for symmetric copies. --Therefore I use bands in every case. --For symmetry puzzles, only the minimum distance from center to any of the copies is output. So only one value for each residue in gd_testArr. local gd_symCount = structure.GetSymCount() --0... --local tmpBdIdxs = {} --local tmpBdsIdxs = {} local tmpDists = {} --local tmpDist2 = {} local outArr = {} --local tmpBdIdx_last = {} for m=1,#gd_testArr do if (gd_testArr[m] == gd_center) then outArr[m] = -1 --Just set an illegal value if center is in gd_testArr so that it will be ignored. else local tmpBdIdxs = {} local tmpDists = {} for n=1,gd_symCount+1 do local tmpBdIdx = band.AddBetweenSegments(gd_center, gd_testArr[m], 5, 5, n-1) --Distance-check to beta-carbon. Should work also for Glycine which has no beta-carbon. table.insert(tmpBdIdxs, tmpBdIdx) local tmpDist = band.GetLength(tmpBdIdx) table.insert(tmpDists, tmpDist) end --Now all bands from center to the symmetric copies of the current segment are drawn. --Determine the shortest one and output this as distance. local tmpMinDist = tmpDists[1] local tmpMinDistIdx = 1 for n=2,#tmpDists do if (tmpDists[n] < tmpMinDist) then tmpMinDist = tmpDists[n] tmpMinDistIdx = n end end --local tmpString = "" --for n=1,#tmpDists do -- tmpString = tmpString .. round(tmpDists[n]) .. " " --end --wait(5) --for debug --Delete all bands. Somehow this needs to be done in reverse since the indices need to be consecutive? for n=#tmpDists,1,-1 do band.Delete(tmpBdIdxs[n]) end --Debug-check: --local tmpBdIdx = band.AddBetweenSegments(gd_center, gd_testArr[m], 5, 5, tmpMinDistIdx-1) --Distance-check to beta-carbon. Attention! Glycine will be ignored here! --print("Seg " .. gd_testArr[m] .. " dists: " .. tmpString .. " taken: " .. round(band.GetLength(tmpBdIdx))) --wait(5) --for debug --band.Delete(tmpBdIdx) outArr[m] = tmpDists[tmpMinDistIdx] end end return outArr end function rand() return math.random() --0...1 (boarder-values are INCLUSIVE!) end function netDiff(nd_old, nd_new) --Compares 2 results of structure.GetHBonds() for differences. --ATTENTION! Only the TYPE of net is detected (same atom-positions). --But basically there are different types of mutations possible. local oldDoneVec = {} local newDoneVec = {} for m=1,#nd_old do oldDoneVec[m] = 0 --Done indicator end for m=1,#nd_new do newDoneVec[m] = 0 end --Go through the nets and see where there is a difference. local tmp_nd_old = {} local tmp_nd_new = {} for m=1,#nd_old do tmp_nd_old = nd_old[m] --prestore this vector for speed. for n=1,#nd_new do --Check if HBond of nd_old is present also in nd_new. --This is the case if res1, res2, atom1 and atom2 are the same! --The subsequent "interlaced" scheme is WAY faster than testing each condition in a single if!!! tmp_nd_new = nd_new[n] --prestore this vector for speed. if (newDoneVec[n] == 0) then if (tmp_nd_old["res1"] == tmp_nd_new["res1"]) then if (tmp_nd_old["res2"] == tmp_nd_new["res2"]) then if (tmp_nd_old["atom1"] == tmp_nd_new["atom1"]) then if (tmp_nd_old["atom2"] == tmp_nd_new["atom2"]) then oldDoneVec[m] = 1 --flags that this item was done/found. newDoneVec[n] = 1 --flags that this item was done/found. break end end end end end end end --Go through the DoneVecs and build return-Array local outArr = {} outArr[1] = {} --This would only be needed if we want to detect if a bond out of the old net was broken. This is not used at the moment. outArr[2] = {} for m=1,#oldDoneVec do if (oldDoneVec[m] == 0) then --outArr[1][#(outArr[1])+1] = nd_old[m] --We lost this bond in the old net! table.insert(outArr[1], nd_old[m]) end end for m=1,#newDoneVec do if (newDoneVec[m] == 0) then --outArr[2][#(outArr[2])+1] = nd_new[m] --We gained this bond in the new net! table.insert(outArr[2], nd_new[m]) end end --print ("netDiff size: " .. " " .. #(outArr[1]) .. " " .. #(outArr[2])) return outArr end function filtDisAllExcl(fdae_excludedNames) --Disables all filters excluding the ones with names in table fdae_excludedNames. --Useful if for example HBnet bonus shall be always on. if (#fdae_excludedNames == 0) then filter.DisableAll() else local filterNames = filter.GetNames() local found = false for m=1,#filterNames do found = false for n=1,#fdae_excludedNames do if (filterNames[m] == fdae_excludedNames[n]) then found = true break end end if (found == false) then filter.Disable(filterNames[m]) end end end end function getTimestamp() return os.date("%a %X") --Abbreviated weekday (e.g. Wed) and time in format 23:12:01 end function cleanup(err) if (string.find(string.lower(err), "cancelled") ~= nil) then print("User Cancelled. Cleaning up...") --Debug: print posesDone. There may be NO jumps in it! local debug = 0 if (debug == 1) then print("posesDone: ") for m=1,#posesDone do print(posesDone[m]) end end print("Resetting importances and filters...") if (dia2.cbx04.value == false) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end behavior.SetSidechainHBondImportance(CI_SC_initial) behavior.SetBackboneHBondImportance(CI_BB_initial) behavior.SetClashImportance(CI_initial) if (grow_iters >= 1) then print("Loading all up to now found Nets...") undo.SetUndo(true) for n=1,qs_idx-qs_idx_start do save.Quickload(qs_idx_start+n-1) --Puts found nets into restore. Use ctrl+z/y to flip through. if (dia2.cbx04.value == false) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end print("Net in qs" .. qs_idx_start+n-1 .. " (" .. n .. "/" .. qs_idx-qs_idx_start .. ") loaded.") end end else print("ERROR: " .. err) end print("iwdn HBNW " .. version .. " has finished!") end --***** MAIN MAIN MAIN ***** function main() print("Running iwdn HBNW " .. version .. "...") --Store initial CIs before running this recipe CI_initial = behavior.GetClashImportance() CI_SC_initial = behavior.GetSidechainHBondImportance() CI_BB_initial = behavior.GetBackboneHBondImportance() --Show GUI dia = dialog.CreateDialog("HBNW " .. version) dia.lbl01 = dialog.AddLabel("Settings for global operations:") dia.txb02 = dialog.AddTextbox("AAs to try", "wdetsyhqn") --Def.: wdetsyhqn. Only promising ones: hdeqn dia.cbx00a = dialog.AddCheckbox("Use text-input instead of unfrozen segs:", false) dia.txb01 = dialog.AddTextbox("ToWorkOn segs", "0") dia.cbx00 = dialog.AddCheckbox("SCs on locked segs are mutable", false) dia.sli01 = dialog.AddSlider("Shake Iters", 1, 0, 20, 0) dia.sli02 = dialog.AddSlider("Wiggle Iters", 2, 0, 20, 0) dia.cbx00b = dialog.AddCheckbox("Wiggle backbone", false) dia.sli04 = dialog.AddSlider("Max Nets to find", 15, 1, 50, 0) dia.sli04b = dialog.AddSlider("Qs startslot", 3, 1, 50, 0) dia.cbx00c = dialog.AddCheckbox("Turn filters on", true) dia.sli04c = dialog.AddSlider("Verbosity", 0, 0, 2, 0) dia.lbl01b = dialog.AddLabel("") dia.lbl02 = dialog.AddLabel("Settings for Single:") dia.sli03 = dialog.AddSlider("Min Order", 1, 1, 10, 0) dia.cbx01 = dialog.AddCheckbox("Store only HBbonus nets", false) dia.lbl02b = dialog.AddLabel("") dia.lbl03 = dialog.AddLabel("Settings for Auto-Grow (AG):") dia.cbx02 = dialog.AddCheckbox("Auto-grow nets", true) dia.sli05 = dialog.AddSlider("AG radius (segs)", 30, 1, 100, 0) dia.sli05b = dialog.AddSlider("AG max iters", 15, 2, 50, 0) --can be used to limit the max. complexity of the found net and to save time. dia.sli06 = dialog.AddSlider("AG maxPoses", 20, 5, 200, 0) dia.txb03 = dialog.AddTextbox("AG center seg(s)", "0") dia.txb03a = dialog.AddTextbox("AG init base seg(s)", "0") dia.cbx04 = dialog.AddCheckbox("AG store additional HB bonus nets", true) dia.but01 = dialog.AddButton("OK", 1) --In Foldit-lua a pressed button will always hide the current window! dia.but02 = dialog.AddButton("Cancel", 0) dia.but03 = dialog.AddButton("More", 2) --Secondary window for further AG-options: dia2 = dialog.CreateDialog("HBNW " .. version .. " further options") dia2.cbx00 = dialog.AddCheckbox("AG chaos mode", false) dia2.cbx01 = dialog.AddCheckbox("AG revert unused segments", true) dia2.cbx02 = dialog.AddCheckbox("AG add already connected segs", true) dia2.cbx03 = dialog.AddCheckbox("AG connect only to fixed center", false) dia2.sli07 = dialog.AddSlider("Shake CI", CI_initial, 0, 1, 2) dia2.sli08 = dialog.AddSlider("Wiggle CI", CI_initial, 0, 1, 2) dia2.cbx04 = dialog.AddCheckbox("Leave all filters untouched", false) dia2.but01 = dialog.AddButton("OK", 0) --Window management local tmpBack = dialog.Show(dia) while true do local tmpBack2 = {} if (tmpBack == 2) then --Secondary options dialog tmpBack2 = dialog.Show(dia2) tmpBack = dialog.Show(dia) end if (tmpBack == 0) then print("iwdn HBNW " .. version .. " has finished.") return elseif (tmpBack == 1) then break --continue with normal operation. end end --set verbosity from GUI local verbosity = dia.sli04c.value --0: minimal output --Handling of filters turnFiltersOn = dia.cbx00c.value --If true then only for score-readout all filters are turned on. This will get more accurate score because filter bonuses are included then. --Set random seed math.randomseed(os.time()-1000*os.clock()) --Disable this line if you want to have always the same result/sequence for each run. --Set AAs from GUI aasAll = {} local aasAllString = "ivlfcmagtswyphdeqnkr" local aasAccString = "" --stores the accepted identifiers for m=1,string.len(dia.txb02.value) do if (string.find(aasAllString, string.lower(string.sub(dia.txb02.value, m, m))) ~= nil) then --Ignore erroneous strings aasAll[m] = string.sub(dia.txb02.value, m, m) --print(aasAll[m]) aasAccString = aasAccString .. aasAll[m] end end --Convert initial centers to num array local ag_tmpCenter = mysplit(dia.txb03.value, " ") for m=1,#ag_tmpCenter do ag_tmpCenter[m] = tonumber(ag_tmpCenter[m]) -- print("ag_tmpCenter: " .. ag_tmpCenter[m]) end --Convert initial base-segs to num array local ag_initialBase = mysplit(dia.txb03a.value, " ") for m=1,#ag_initialBase do ag_initialBase[m] = tonumber(ag_initialBase[m]) -- print("ag_initialBase: " .. ag_initialBase[m]) --Since the user may have forgotten to freeze these base-residues we simply do this so that these residues are neglected in the determination of toWorkOn if ag_initialBase[m] ~= 0 then freeze.Freeze(ag_initialBase[m], false, true) --backbone, sidechain end end --Text-input of segments toWorkOn local txtInp_toWorkOn = {} if (dia.cbx00a.value == true) then --Convert initial base-segs to num array txtInp_toWorkOn = mysplit(dia.txb01.value, " ") for m=1,#txtInp_toWorkOn do txtInp_toWorkOn[m] = tonumber(txtInp_toWorkOn[m]) end end --Check if HBond-Bonus is available. local tmpFilters = checkFilters() local filterHBname = {} local hasHBbonus = false local filt2exclArr = {} local HBbonus_start = 0 if (#tmpFilters > 0) then if (#(tmpFilters[1]) > 1) then hasHBbonus = true HBbonus_start = tmpFilters[1][1] filterHBname = tmpFilters[1][2] print("Puzzle has H-Bond Bonus. Current HB bonus: " .. round(HBbonus_start) .. " Pts.") filt2exclArr[1] = filterHBname end end --Check for symmetric copies local symCount = structure.GetSymCount() local residues = structure.GetCount() print("Puzzle has " .. residues .. " residues and " .. symCount .. " symmetric copies.") --List all settings from the GUI here at the start. print("") print("Selected global settings:") print("Mutations to try: " .. aasAccString) print("Use text-input instead of unfrozen segs:" .. tostring(dia.cbx00a.value)) print("ToWorkOn segments (space-delimited):" .. dia.txb01.value) print("Sidechains on locked segments are mutable (linker/interface): " .. tostring(dia.cbx00.value)) print("Shake iterations: " .. dia.sli01.value) print("Wiggle iters: " .. dia.sli02.value) print("Wiggle backbone(s) in addition to sidechain(s): " .. tostring(dia.cbx00b.value)) print("Maximum number of nets to find: " .. dia.sli04.value) print("Quicksave slot to start from: " .. dia.sli04b.value) print("Turn filters on (more exact score-readout. Leaving it off may lead to better stability (?) and a bit of time-saving.): " .. tostring(dia.cbx00c.value)) print("Verbosity-level for output information: " .. dia.sli04c.value) print("") if (dia.cbx02.value == false) then --Non auto-grow print("Selected mode: Single. Every pose with more new bonds than specified in 'Min order' will be stored as new Net.") print("Nets to keep must have this minimum order: " .. dia.sli03.value) print("Store only HB-Bonus-nets: " .. tostring(dia.cbx01.value)) else --Auto-Grow print("Selected mode: Auto-Grow (AG). Net will be grown in multiple iters around center.") print("AG radius (segments): " .. dia.sli05.value) print("AG maximum interations (give-up criterion): " .. dia.sli05b.value) print("AG max poses per iteration (give-up criterion): " .. dia.sli06.value) print("AG initial center segments to cycle (set 0 for random or e.g. 11 31 45 for specific starters.): " .. dia.txb03.value) print("AG initial base segments (use these to try to extend to an already existing net): " .. dia.txb03a.value) print("AG store additional HB-Bonus-nets (requires more qs-slots than 'max number of nets'): " .. tostring(dia.cbx04.value)) --Second dialog print("AG chaos mode (keep all bonds even if they are not connected to center): " .. tostring(dia2.cbx00.value)) print("AG revert unused segments (which do not connect to the net) to original AA: " .. tostring(dia2.cbx01.value)) print("AG add already connected segs to the net: " .. tostring(dia2.cbx02.value)) print("AG connect only to fixed center: " .. tostring(dia2.cbx03.value)) print("Shake CI: " .. tostring(dia2.sli07.value)) print("Wiggle CI: " .. tostring(dia2.sli08.value)) print("Leave all filters untouched: " .. tostring(dia2.cbx04.value)) end print("") --Find unfrozen sidechains. User has to freeze all other sidechains beforehand! local toWorkOn = {} local toWorkOnString = "" if (dia.cbx00a.value == false) then --Normal input: detection of unfrozen segments for m=1,residues do --IsLocked cannot really be used because this will be true for EVERY locked backbone. --Therefore a segment will be detected as locked even when it's sidechain is mutable. --This can be circumvented by checking available rotamers. Locked segments have only one. But cysteine and alanine and maybe others under certain circumstances have only one as well! local frzback, frzside = freeze.IsFrozen(m) if (frzside == false) then --We only need to consider unfrozen sidechains. All others are irrelevant in this case. if (structure.IsMutable(m) == true) then --We need to consider this segment in every case because it can mutate to different sidechains. So it can potentially be part of a net when it gets mutated and bonded to. --This will now include freely mutable cysteines as well even though they have no sidechain! table.insert(toWorkOn, m) toWorkOnString = toWorkOnString .. m .. " " else --In this case the sidechain is non-frozen and non-mutable. So it can be a completely locked segment. This needs to be detected. if (rotamer.GetCount(m) > 1) then --This means we have a non-locked sidechain. The bb may or may not be locked - we don't have to care about it. This segment we also want to use because it can shake and wiggle and therefore potentially connect to the net (if it has open atoms). table.insert(toWorkOn, m) toWorkOnString = toWorkOnString .. m .. " " else --This means we have a non-frozen, non-mutable AA with single rotamer. --This can be a fully locked segment (bb and sc). --But this can also be a cysteine, an alanine or another AA with only one sidechain. if (structure.IsLocked(m) == false) then --If it is also non-locked, then this could potentially be a usable sidechain. But only if it has at least one open atom --Therefore check for open atom(s) here. local tmpAA = structure.GetAminoAcid(m) if (string.find("wdetsyhqnkr", string.lower(tmpAA)) ~= nil) then table.insert(toWorkOn, m) toWorkOnString = toWorkOnString .. m .. " " end end end end end end print(#toWorkOn .. " unfrozen segments toWorkOn: " .. toWorkOnString) else --Text-input of segments toWorkOn --Convert initial base-segs to num array local warn = false for m=1,#txtInp_toWorkOn do if (txtInp_toWorkOn[m] >= 1 and txtInp_toWorkOn[m] <= residues and math.floor(txtInp_toWorkOn[m]) == txtInp_toWorkOn[m]) then --includes check for integer table.insert(toWorkOn, txtInp_toWorkOn[m]) toWorkOnString = toWorkOnString .. txtInp_toWorkOn[m] .. " " else warn = true end end if (warn == true) then print("Warning: Please check text-input for segments in toWorkOn because at least one index could not be read!") end print(#toWorkOn .. " text-input segments in toWorkOn: " .. toWorkOnString) if (#toWorkOn == 0) then print("Error: no segments toWorkOn were found! This recipe is terminating.") cleanup("No segments in toWorkOn found Error") end --freeze ALL sidechains not specified here (this may not be necessary?!) local tmpFound = {} for m=1,residues do tmpFound = false for n=1,#toWorkOn do if (m == toWorkOn[n]) then tmpFound = true break end end if (tmpFound == false) then freeze.Freeze(m, false, true) --backbone, sidechain end end end --tmpBase0 = getDistances(toWorkOn, 60) --Attention! The result of getDistances() is unsorted. --wait(1000) --Determine current AAs of toWorkOn in order to restore the ones where no net was found before storing it to file. --This may be helpful because you may not have to run mutate after finishing this recipe. Like this the result will be as close as possible to the initial configuration. --There may still be differences because it is not possible to EXACTLY reset the initial structure unfortunately. local toWorkOnAAs = {} for m=1,#toWorkOn do toWorkOnAAs[m] = structure.GetAminoAcid(toWorkOn[m]) end --Auto-grow(AG): the subsequent steps need to be run in a loop. local ag_looper = 1 --Is used for signalling that for autogrow multiple nets should be found and stored. if (dia.cbx02.value == true) then grow_iters = dia.sli05b.value --Set this to a very high value. Basically this should be infinite. But >10 should be ok. ag_looper = dia.sli04.value --Set this here to max number of nets to find. end --Store initial configuration for restore in AG-loops qs_idx_start = dia.sli04b.value save.Quicksave(qs_idx_start) qs_idx = qs_idx_start+1 print("Storing initial in qs" .. qs_idx_start .. " for later reference.") --Disable all filters. Otherwise this is too slow. behavior.SetSidechainHBondImportance(3) --Set this as high as possible since we want to find sidechain-bonds at any cost. behavior.SetBackboneHBondImportance(3) --We don't want to loose backbone-H-Bonds if (dia2.cbx04.value == false) then filtDisAllExcl(filt2exclArr) --if filterHBname is empty then it will not lead to an error. end for aa=1,ag_looper do if (aa > 1) then --Means we are in auto-grow --reload initial net because we always want to start from the same start-condition. --otherwise there would be unwanted clashes etc right from the start. save.Quickload(qs_idx_start) end local ag_net_breaker = false --used to flag that a net shall be finished. local foundNetRes = {} --included residues in the found net as indices. local HBbonusArr = {} --stores the diverse HB bonus values found in all iterations for AG. local tmpCenter0 = {} --local ag_tmpCenterMutate = true for a=1,grow_iters do --Out of all segments to work on select a region in which to look for HB-nets local tmpCenter = {} local tmpBase = {} --All residues in this array are mutated randomly in the current iteration. All others are not. local tmpBase0 = {} --Temporarily required for distance-check. if (a == 1) then if (grow_iters == 1) then --No auto-grow --Simply choose ALL unfrozen segments. No distance-check is required. tmpBase = toWorkOn print("Non-auto-grow mode (Single) started...") else --Auto-grow is chosen. Select a center and find closest residues. if (ag_tmpCenter[1] ~= 0) then tmpCenter = ag_tmpCenter[(aa-1) % #ag_tmpCenter + 1] --Get residue-number of center from GUI. Needs to be converted from string to number! else --This means we want to start with a random segment out of all toWorkOn local tmpVal = rand() * #toWorkOn if (tmpVal == #toWorkOn) then tmpVal = #toWorkOn-1 --Special treatment for rand()==1 end tmpCenter = toWorkOn[math.floor(tmpVal)+1] end if (ag_initialBase[1] == 0) then tmpBase[1] = tmpCenter --In the first turn the center needs to be in the base. Because we want to mutate it as well to get a HBond. else --Special mode: connect to a given net. --check if current tmpCenter is in ag_initialBase. If this is the case then it may not be mutated and therefore not be put into tmpBase. local tmpFound = false for m=1,#ag_initialBase do if (tmpCenter == ag_initialBase[m]) then --In this case the tmpCenter shall NOT be mutated because it is part of the net already. tmpFound = true break end end if (tmpFound == false) then --tmpCenter is not in initBase. It may be mutated. tmpBase[1] = tmpCenter end end tmpCenter0 = tmpCenter --Needed for summary in AG --Find closest residues and put them in base. tmpBase0 = getDistances(toWorkOn, tmpCenter) --Attention! The result of getDistances() is unsorted. local tmpBase_start = #tmpBase for m=1,dia.sli05.value-tmpBase_start do --There may already be segments assigned to tmpBase. Therefore -tmpBase_start here. --Go through the distances and find smallest ones local tmpMinDistIdx = {} local tmpMinDist = -2 for n=1,#tmpBase0 do if (tmpBase0[n] > 0) then --Protection against invalid distances if (tmpMinDist == -2) then --Initial tmpMinDist = tmpBase0[n] tmpMinDistIdx = n else if (tmpBase0[n] < tmpMinDist) then tmpMinDist = tmpBase0[n] tmpMinDistIdx = n end end end end --It can happen that more residues than there are available shall be worked on. --Avoid this here by breaking this loop. if (tmpMinDist == -2) then --no more distance-segment was found! break end --Store found minimum table.insert(tmpBase, toWorkOn[tmpMinDistIdx]) --The smallest distance corresponds to a segment stored in toWorkOn! tmpBase0[tmpMinDistIdx] = -1 --set to a useless value so that this value is not considered anymore in the next turn. end print("Auto-growing Net " .. aa .. "/" .. dia.sli04.value .. " center: " .. tmpCenter .. ". Please wait...") if (verbosity >= 1) then print(getTimestamp() .. ": Auto-grow iter 1 started on " .. #tmpBase .. " segments...") end if (verbosity >= 2) then local tmpBase_string = "" for m=1,#tmpBase do tmpBase_string = tmpBase_string .. tmpBase[m] .. " " end tmpBase_string = string.sub(tmpBase_string, 1, #tmpBase_string-1) print("Center (init/curr): " .. tmpCenter0 .. "/" .. tmpCenter .. " tmpBase: " .. tmpBase_string) end end else --For further loops the center needs to be chosen out of the already found net. Prefereably the last found segment?! --TODO: Avoid taking a center residue twice. --Check the residues of the net for a new center for this turn. Simply pick the last entry. This may not be smart. --It should be detected which residue is actually the open end. But that will take more doing. if (dia2.cbx03.value == true) then --In this case/mode the center shall always stay the same! This is always the first element in foundNetRes! tmpCenter = foundNetRes[1] else tmpCenter = foundNetRes[#foundNetRes] --Standard mode: the center will vary end --Find closest residues and put them in base. local toWorkOn_tmp = {} --subtract the already found net and store it in this variable. local foundEntry = 0 for m=1,#toWorkOn do foundEntry = 0 for n=1,#foundNetRes do if (toWorkOn[m] == foundNetRes[n]) then foundEntry = 1 end end if (foundEntry == 0) then table.insert(toWorkOn_tmp, toWorkOn[m]) end end foundEntry = nil --invalidate local debug = 0 if (debug == 1) then local toWorkOn_tmpString = "" for m=1,#toWorkOn_tmp do toWorkOn_tmpString = toWorkOn_tmpString .. toWorkOn_tmp[m] .. " " end --print("Unfrozen segments toWorkOn: " .. toWorkOn_tmpString) end tmpBase0 = getDistances(toWorkOn_tmp, tmpCenter) --Attention! The result of getDistances() is unsorted. for m=1,dia.sli05.value do --There cannot be segments assigned to tmpBase already in this case! --Go through the distances and find smallest ones local tmpMinDistIdx = {} local tmpMinDist = -2 for n=1,#tmpBase0 do if (tmpBase0[n] > 0) then --Protection against invalid distances if (tmpMinDist == -2) then --Initial tmpMinDist = tmpBase0[n] tmpMinDistIdx = n else if (tmpBase0[n] < tmpMinDist) then tmpMinDist = tmpBase0[n] tmpMinDistIdx = n end end end end --It can happen that more residues than there are available shall be worked on. --Avoid this here by breaking this loop. if (tmpMinDist == -2) then --no more distance-segment was found! break end --Store found minimum table.insert(tmpBase, toWorkOn_tmp[tmpMinDistIdx]) --The smallest distance corresponds to a segment stored in toWorkOn_tmp! tmpBase0[tmpMinDistIdx] = -1 --set to a useless value so that this value is not considered anymore in the next turn. end if (verbosity >= 1) then print(getTimestamp() .. ": Auto-grow iter " .. a .. " started on " .. #tmpBase .. " segments...") end if (verbosity >= 2) then local tmpBase_string = "" for m=1,#tmpBase do tmpBase_string = tmpBase_string .. tmpBase[m] .. " " end tmpBase_string = string.sub(tmpBase_string, 1, #tmpBase_string-1) print("Center (init/curr): " .. tmpCenter0 .. "/" .. tmpCenter .. " tmpBase: " .. tmpBase_string) local tmpFoundNetRes_string = "" for m=1,#foundNetRes do tmpFoundNetRes_string = tmpFoundNetRes_string .. foundNetRes[m] .. " " end tmpFoundNetRes_string = string.sub(tmpFoundNetRes_string, 1, #tmpFoundNetRes_string-1) print("Found net so far (" .. #foundNetRes .. " segs): " .. tmpFoundNetRes_string) end end --End: if (a == 1) then --Now the base-residues are determined. Find the mutation-pose for them. --For auto-grow it is not necessary to checkMutables again. Do this only in the first turn for all residues in toWorkOn. --This saves some time. local tmpBase_mutables = {} if (dia.cbx02.value == false) then --non-auto-grow tmpBase_mutables = checkMutables(tmpBase) --tmpBase is ALWAYS toWorkOn in this case! else --For auto-grow use this... if (aa == 1 and a == 1) then toWorkOn_mutables = checkMutables(toWorkOn) --find mutables for ALL possible residues to be used in the future. end --Go through toWorkOn_mutables and find the corresponding mutables for the current tmpBase. for m=1,#tmpBase do --tmpBase_mutables[m] = {} --init 2-dimensional array. Is this necessary? for n=1,#toWorkOn do if (tmpBase[m] == toWorkOn[n]) then table.insert(tmpBase_mutables, toWorkOn_mutables[n]) break end end end end --calculate the maximum number of poses and display it local maxPoses = 1 --Initialize to 1 because we will do multiplication --local mutables_string = "" --For debug only for m=1,#tmpBase_mutables do maxPoses = maxPoses * #(tmpBase_mutables[m]) --calculate max number of combinations --mutables_string = mutables_string .. #(tmpBase_mutables[m]) .. " " end --print("maxPoses= " .. maxPoses) --print("mutables_string: " .. mutables_string) --Determine the number of HBonds BEFORE the search begins local startBonds = structure.GetHBonds() selection.DeselectAll() for m=1,#tmpBase do selection.Select(tmpBase[m]) --select residues in the vicinity end local extNetsMinOrder = dia.sli03.value local extNetsBondsNr = {} --Stores the number of bonds for each found net in extNets. --Basic approach: Don't prestore anything. Just store the poses that were done and randomly try new ones. posesDone = {} --Stores the already done poses. This holds just a decimal value for each pose. Sorted with rising values. local ag_breaker = false --necessary for auto-grow-mode to end a single grow-iteration. for m=1,maxPoses do --In each turn of m, another pose is tried with random indexing. --Also it is checked if some new network has formed. local tmpRand = math.floor(rand() * maxPoses)+1 --Random integer index from 1...maxPoses if (tmpRand >= maxPoses) then --clipping to avoid value > maxPoses tmpRand = maxPoses end --print("maxPoses: " .. maxPoses) --print("currPose: " .. tmpRand) local tmpComb = {} if (maxPoses < 10^10 and dia.cbx02.value == true) then --Treatment for huge number of residues to try. Since we would reach the data-format-limit. For single-mode we NEED to use the first scheme because we need to fill the posesDone array. --The subsequent if-condition and internals are a BEAST! Gave me some headache to figure it out. --I would be thankful if someone could hint me to a simpler solution. if (#posesDone == 0) then posesDone[1] = tmpRand --First initialization. else --Check if this random pose was already used. If not then find free slot. for n=1,#posesDone do --Run once from start to end over posesDone-Array --Special case: first element if (n == 1) then if (tmpRand < posesDone[1]) then --Insert at front. Other cases will be treated in following steps. table.insert(posesDone, 1, tmpRand) break end end --Special case: we hit the last element. No element follows in posesDone! if (n == #posesDone) then if (posesDone[n] == maxPoses) then --In this case we NEED to wrap! This case is: tmpRand = posesDone[#posesDone]! --wrap around and find free slot! for o=1,#posesDone-1 do --minus 1 because we can omit the current (last) slot. if (posesDone[o+1] > posesDone[o]+1) then table.insert(posesDone, o+1, posesDone[o]+1) tmpRand = posesDone[o]+1 break end end break --Break the loop over n since we placed the item. else --this means: last element in posesDone is < maxPoses if (tmpRand == posesDone[n]) then posesDone[n+1] = posesDone[n]+1 --This slot MUST be free because we are smaller than maxPoses. tmpRand = posesDone[n]+1 break else --tmpRand is larger than the last element. Smaller is not possible since this case is treated in the next steps. posesDone[n+1] = tmpRand break end end end --Now the probably most common and difficult case: --tmpRand lies either on a value (and another value follows in posesDone) or tmpRand lies between two values. if (tmpRand >= posesDone[n] and tmpRand < posesDone[n+1]) then if (tmpRand > posesDone[n] and tmpRand < posesDone[n+1]) then --we have found a free slot. Store the new element in between these values in posesDone. table.insert(posesDone, n+1, tmpRand) break else --This means that tmpRand lies ON posesDone[n]. We have to increment to find a free slot. --This is the most difficult case because it may happen that we will have to wrap around the end of posesDone. local wrapOffset = 0 for o=1,#posesDone-1 do --minus 1 because we can omit the current (last) slot. local tmpIdx = n+o-1+wrapOffset --first index is posesDone[n]! --Treat the case where tmpIdx points to the FIRST element in posesDone. This can happen due to wrapping! if (tmpIdx == 1) then if (posesDone[1] > 1) then --Insert at front table.insert(posesDone, 1, posesDone[1]-1) tmpRand = posesDone[1]-1 --Step back break else --Nothing needs to be done here? This is covered by subsequent cases? end end if (tmpIdx == #posesDone) then --We have reached the end of posesDone. Either there is a free slot or we will have to wrap. if (posesDone[tmpIdx] == maxPoses) then --In this case we NEED to wrap! --wrap around and find free slot! wrapOffset = -#posesDone --All subsequent actions need to be executed shifted! --Is this action sufficient or is there something else to be done? else --We have found a free slot at the end of posesDone table.insert(posesDone, posesDone[tmpIdx]+1) tmpRand = posesDone[tmpIdx]+1 break end else --If this point is reached then there is still an element following the current one in posesDone. --We can safely use the next element. if (posesDone[tmpIdx+1] > posesDone[tmpIdx]+1) then --We found a free slot. table.insert(posesDone, tmpIdx+1, posesDone[tmpIdx]+1) tmpRand = posesDone[tmpIdx]+1 break else --Do nothing. Wait for next turn. end end end --End: internal for-loop (o) break --Break loop over n end end --End: interval-case --There is no other case that needs to be treated. If the above conditions are not satisfied then we just have to --do another turn for n. end --End: for n=1,#posesDone do end --End: if (#posesDone == 0) --Decode the current pose and apply --Strange: this routine cannot take more than about 15 segments. Then some strange clipping occurs. Maybe the data-format? --Yes, seems like it. So I should use for >15 a different scheme. I should not store anything but set the residues directly random. --This is because it is VERY unlikely that with so many residues I will get the same pose twice. tmpRand = tmpRand-1 --0...maxPoses-1 --tmpRand = maxPoses-1-5*9^8-2*9^3 --debug local tmpIdx = 0 local tmpVal = tmpRand local tmpVal2 = 0 for n=1,#tmpBase_mutables do tmpVal2 = math.floor(tmpVal/#(tmpBase_mutables[n])) tmpIdx = tmpVal-tmpVal2*#(tmpBase_mutables[n]) --Integer-index to the current slot in tmpBase_mutables. --print(#(tmpBase_mutables[n]) .. " " .. tmpVal2 .. " " .. tmpIdx) tmpVal = tmpVal2 --Prepare for next turn tmpComb[n] = tmpBase_mutables[n][tmpIdx+1] end else --Huge number of residues to try in auto-grow. Workaround to avoid data-format limitation --Simply set each residue directly and don't store anything. for n=1,#tmpBase_mutables do if (#(tmpBase_mutables[n]) == 1) then tmpComb[n] = tmpBase_mutables[n][1] else tmpRand = math.floor(rand() * #(tmpBase_mutables[n])) if ( tmpRand > #(tmpBase_mutables[n])-1 ) then --special treatment of "1" in rand() tmpRand = #(tmpBase_mutables[n])-1 end tmpComb[n] = tmpBase_mutables[n][tmpRand+1] end end --For compatibility: fill posesDone. This is used for debug output table.insert(posesDone, 0) end --local tmpString = "" --for n=1,#tmpComb do -- print(tmpComb[n]) -- tmpString = tmpString .. tmpComb[n] .. " " --end --print ("tmpComb: " .. tmpString) for n=1,#tmpComb do structure.SetAminoAcid(tmpBase[n], tmpComb[n]) end if (dia.sli01.value > 0) then behavior.SetClashImportance(dia2.sli07.value) --Set shake-CI according to what is chosen/set in the GUI structure.ShakeSidechainsSelected(dia.sli01.value) --Shake really helps to pre-order things. I think it should be used. end if (dia.sli02.value > 0) then --Very strange! LocalWiggleSelected does not seem to work right with the backbone and sidechain options. If backbone is false than it does not execute. --Submit this as bug or investigate further later. Workaround: WiggleSelected. This does work with these options. Strange! --structure.LocalWiggleSelected(dia.sli02.value, dia.cbx00b.value, true) --backbone, sidechains <== DO NOT USE THIS!!! behavior.SetClashImportance(dia2.sli08.value) --Set wiggle-CI according to what is chosen/set in the GUI structure.WiggleSelected(dia.sli02.value, dia.cbx00b.value, true) end --If the puzzle has HBbonus and it shall be incorporated then we can just check for this here. --This jumper probably only makes sense in single-mode since for auto-grow in many cases you can get HBbonus by modifiying the net manually. So better store them for auto-grow anyway. --This should save quite some time in single-mode since GetHBonds does not need to be called. local jumper = false --lua has no continue so this workaround is necessary. if (hasHBbonus == true and dia.cbx02.value == false) then --Only for single-mode! if (dia.cbx01.value == true) then local tmpFilters = checkFilters() if (tmpFilters[1][1] <= HBbonus_start) then jumper = true --skip the rest of this turn since no gain in HBnet-bonus was achieved. end end end if (jumper == false) then --check if some HBond has formed tmpBonds = structure.GetHBonds() --In non-autogrow-mode we can NOT simply check the difference in bond-count. --This is because we may have bonds in the initial pose before the first mutation. These bonds will rip off due to pose-mutations and we would then have to find MORE new bonds than the ones that ripped off. --This is not desired and therefore we will have to use netDiff here instead as it is done in the auto-grow-routine. local diffBack = netDiff(startBonds, tmpBonds) --We need this for single- AND auto-grow mode! if (dia.cbx02.value == false) then --This means we are in non-auto-grow mode! if (#(diffBack[2]) >= extNetsMinOrder) then if (#extNets == 0) then extNets[1] = tmpBonds --We need to store each new found net for later reference to compare for duplicate. This is only necessary in single-mode. extNetsBondsNr[1] = #tmpBonds if (hasHBbonus == false) then if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end if (verbosity == 0) then print("NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts.") else print(getTimestamp() .. ": NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts.") end save.Quicksave(qs_idx) qs_idx = qs_idx+1 if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end else local tmpFilters = checkFilters() if (dia.cbx01.value == false or (dia.cbx01.value == true and tmpFilters[1][1] > HBbonus_start)) then if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end if (verbosity == 0) then print("NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts.") else print(getTimestamp() .. ": NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts.") end save.Quicksave(qs_idx) qs_idx = qs_idx+1 if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end end end else --Non-auto-grow (single): further turns for n=1,#extNets do --Only nets with the same number of bonds need to be compared more in-depth. --This will save a large amount of time. if (#tmpBonds == extNetsBondsNr[n]) then local diffBack = netDiff(extNets[n], tmpBonds) if (#(diffBack[1]) == 0 and #(diffBack[2]) == 0) then --This means we have found a duplicate net with the same bonds as it is stored in one of extNets already. Ignore. --print("Found duplicate network to Nr " .. n .. ". Ignoring it.") break end end if (n == #extNets) then --Only evaluate in the last turn! table.insert(extNets, tmpBonds) table.insert(extNetsBondsNr, #tmpBonds) if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end if (hasHBbonus == false) then if (verbosity == 0) then print("NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts.") else print(getTimestamp() .. ": NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts.") end save.Quicksave(qs_idx) qs_idx = qs_idx+1 else local tmpFilters = checkFilters() if (dia.cbx01.value == false or (dia.cbx01.value == true and tmpFilters[1][1] > HBbonus_start)) then if (verbosity == 0) then print("NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts.") else print(getTimestamp() .. ": NET " .. #extNets .. ": +" .. #(diffBack[2]) .. " bds in qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts.") end save.Quicksave(qs_idx) qs_idx = qs_idx+1 end end if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end end end --End: for n end end if (#extNets >= dia.sli04.value) then --stop criterion break end else --Auto grow mode!!! --The order may not simply be derived by subtracting #startBonds from #tmpBonds. --This is because in previous stages some bonds probably have formed on the other, irrelevant sidechains. --These will break down and form new ones in further turns. But the broken ones will lead to an error in this equation. --This will probably lead to ignoring promising new nets! --It would be better to use netDiff for this because that function "splits" broken and new bonds. --Simply accept ANY order in this mode. Anyway only bonds connected to the center/base-net will be kept! if (#(diffBack[2]) >= 1) then --This means at least one new bond has formed. --local foundNet_tmp = diffBack[2] --check for connections of diffBack to foundNet. Since multi-connections via multiple segments in diffBack are possible, we need an infinite loop here. if (dia2.cbx00.value == true) then --this means we are in chaos-mode. --Keep external nets. This should be very fast but chaotic. We will see. --Simply store ALL found new bonds and freeze the connected sidechains --The center is not important in this mode. ONLY for determining the base. So this does not matter here. --We need to check for duplicate residues in foundNetRes especially for symm puzzles. for c=1,#foundNet_tmp do if (#foundNetRes == 0) then --Special treatment for first element. Simply put in the first found one since we want to accept ALL bonds foundNetRes[1] = foundNet_tmp[c]["res1"]%(residues+1) end local tmpFound = false for d=1,#foundNetRes do --Go through this array and see if we already found this residue. Store only new ones. if (foundNet_tmp[c]["res1"]%(residues+1) == foundNetRes[d]) then tmpFound = true break end end if (tmpFound == false) then table.insert(foundNetRes, foundNet_tmp[c]["res1"]%(residues+1)) freeze.Freeze(foundNet_tmp[c]["res1"]%(residues+1), false, true) --backbone, sidechain end tmpFound = false for d=1,#foundNetRes do --Go through this array and see if we already found this residue. Store only new ones. if (foundNet_tmp[c]["res2"]%(residues+1) == foundNetRes[d]) then tmpFound = true break end end if (tmpFound == false) then table.insert(foundNetRes, foundNet_tmp[c]["res2"]%(residues+1)) freeze.Freeze(foundNet_tmp[c]["res2"]%(residues+1), false, true) --backbone, sidechain end end ag_breaker = true --this flags that the current auto-grow iteration shall be ended because a new bond has been found. else --This means we are in AG-non-chaos mode --Unfortunately it is possible that some bonds may not be considered in this simple scheme. Because as it is, only new-formed bonds which are connected somehow to the net will be kept. --But especially for binder-puzzles it can happen that there are bonds that never change and still get connected to the net via a new-formed bond. These will also have to be incorporated! --This can be done by checking ALL current bonds for connections to the net. But we have to exclude BB to BB bonds. if (dia2.cbx02.value == true) then foundNet_tmp = tmpBonds --Add already connected bonds on a secondary net to the net when it gets connected via a bond to the main net. else foundNet_tmp = diffBack[2] --Only newly formed bonds can get added to the net. This may leave out some. end if (a == 1) then --special treatment in first turn. --If it has not been done already then the first item in foundNetRes must be set to the initial center. --Like this the same routine can be used as in further loops. if (#foundNetRes == 0) then --Isn't this always the case for a==1? if ag_initialBase[1] == 0 then foundNetRes[1] = tmpCenter --print("Setting tmpCenter seg " .. tmpCenter .. " as first element in foundNetRes.") else --Special mode: connect to given net. for d=1,#ag_initialBase do table.insert(foundNetRes, ag_initialBase[d]) end end end end --Only new bonds that are CONNECTED to the old net may be kept! --This is the most prominent and difficult routine for the auto-grow modus while true do --210331: This is strange! The symmetric copies seem to start at residues+2! I have read that this is some Foldit-internal feature connected with Rosetta?! --I will have to investigate this further! --This is gonna get ugly. It can happen that a sidechain bonds to the backbone. This will also be detected as net-extension. --Avoid this in the first step by detecting that the bond goes at least to the beta-carbon. ==> Removed this to see if it is problematic or helps. Uncomment below line for re-activation. local c_ender = false local foundNetRes_maxIdx = {} if (dia2.cbx03.value == true) then foundNetRes_maxIdx = 1 --Spechial mode: the center is fixed and we may ONLY connect to IT. else foundNetRes_maxIdx = #foundNetRes --Normal mode: We may connect to ANY already found net segment. end for c=1,#foundNet_tmp do for d=1,foundNetRes_maxIdx do --if (foundNet_tmp[c]["res1"]%(residues+1) == foundNetRes[d] and (foundNet_tmp[c]["atom1"] >= 5 and foundNet_tmp[c]["atom2"] >= 5)) then --Means that we found an extended connection. Between sidechains only! if (foundNet_tmp[c]["res1"]%(residues+1) == foundNetRes[d] and (foundNet_tmp[c]["atom1"] >= 5 or foundNet_tmp[c]["atom2"] >= 5)) then --Allows for connections from BB to SC and vice versa. Butt BB to BB is excluded. --if (foundNet_tmp[c]["res1"]%(residues+1) == foundNetRes[d]) then --This accepts bonds to the backbone as well. Also BB to BB! --Check if second residue is also in foundNetRes. This can happen for symm-puzzles and it must be avoided that they are stored twice. local tmpFound = false for e=1,#foundNetRes do if (foundNetRes[e] == foundNet_tmp[c]["res2"]%(residues+1)) then tmpFound = true break end end if (tmpFound == true) then --This means that we have found a duplicate. Invalidate it an do NOT store it in foundNet table.remove(foundNet_tmp, c) --print("found duplicate1") c_ender = true break --break loop over foundNetRes. But also the loop over foundNet_tmp must be broken! Simply go through foundNet_tmp again in the while-loop else if (a == 1 and #foundNetRes == 1) then freeze.Freeze(tmpCenter, false, true) --Freeze the initial center. end table.insert(foundNetRes, foundNet_tmp[c]["res2"]%(residues+1)) --The second atom is the new one. The first is already in the foundNet. freeze.Freeze(foundNet_tmp[c]["res2"]%(residues+1), false, true) --backbone, sidechain table.remove(foundNet_tmp, c) --invalidate ag_breaker = true --this flags that the current auto-grow iteration shall be ended because a new bond has been found. c_ender = true break end --elseif (foundNet_tmp[c]["res2"]%(residues+1) == foundNetRes[d] and (foundNet_tmp[c]["atom1"] >= 5 and foundNet_tmp[c]["atom2"] >= 5)) then elseif (foundNet_tmp[c]["res2"]%(residues+1) == foundNetRes[d] and (foundNet_tmp[c]["atom1"] >= 5 or foundNet_tmp[c]["atom2"] >= 5)) then --elseif (foundNet_tmp[c]["res2"]%(residues+1) == foundNetRes[d]) then --Accepts bonds to BB as well. --Check if first residue is also in foundNetRes. This can happen for symm-puzzles and it must be avoided that they are stored twice. local tmpFound = false for e=1,#foundNetRes do if (foundNetRes[e] == foundNet_tmp[c]["res1"]%(residues+1)) then tmpFound = true break end end if (tmpFound == true) then --This means that we have found a duplicate. Invalidate it an do NOT store it foundNet table.remove(foundNet_tmp, c) --print("found duplicate2") c_ender = true break --break loop over foundNetRes. But also the loop over foundNet_tmp must be broken! Simply go through foundNet_tmp again in the while-loop else if (a == 1 and #foundNetRes == 1) then freeze.Freeze(tmpCenter, false, true) --Freeze the initial center. end table.insert(foundNetRes, foundNet_tmp[c]["res1"]%(residues+1)) --The second atom is the new one. The first is already in the foundNet. freeze.Freeze(foundNet_tmp[c]["res1"]%(residues+1), false, true) --backbone, sidechain table.remove(foundNet_tmp, c) --invalidate ag_breaker = true --this flags that the current auto-grow iteration shall be ended because a new bond has been found. c_ender = true break end end end --End: d if (c_ender == true) then break --Break loop over c end end --End: c if (c_ender == false) then break --break infinite while loop end end --End: Infinite while loop end --End: keep external nets end --End: if (#(diffBack[2]) >= 1) then end --End: auto-grow mode!!! end --End: if (jumper == false) then --print out the number of completed poses for auto-grow-loop if (verbosity >=1) then if (ag_looper > 1) then if (#posesDone % 5 == 0) then print(getTimestamp() .. ": AG posesDone in this iter: " .. #posesDone .. "/" .. dia.sli06.value) end end end if (ag_looper > 1 and #posesDone >= dia.sli06.value) then --AG Giveup-criterion: Max number of poses to try reached. --This means that the net is not growing anymore. --So we can store the net and start a new net in the next turn about (aa) ag_breaker = true --break the loop about grow_iters ag_net_breaker = true end if (a >= grow_iters and grow_iters > 1 and ag_breaker == true) then --AG Giveup-criterion: Max number of iters to try reached; grow_iters > 1: this is an override for single-mode. Leave it in! ag_net_breaker = true end if (ag_net_breaker == true) then --Restore of AAs --Only do this for non-HBbonus-puzzles since residues outside of the found net may get this bonus! --As of 210505 this can be set via GUI. The user should be aware that this should be deactivated probably on puzzles with HB-bonus! if (dia2.cbx01.value == true and dia.cbx02.value == true) then --Only do this in AG-mode at the moment. if (verbosity > 0) then print("Restoring AAs of non-used segments in toWorkOn for qs...") end selection.DeselectAll() --Only act on the residues which are NOT in foundNetRes but are in toWorkOn. --The corresponding AAs to restore were stored in toWorkOnAAs. local foundEntry = 0 for n=1,#toWorkOn do foundEntry = 0 for o=1,#foundNetRes do if (toWorkOn[n] == foundNetRes[o]) then foundEntry = 1 break end end if (foundEntry == 0) then --Reset the initially saved AA for this segment structure.SetAminoAcid(toWorkOn[n], toWorkOnAAs[n]) selection.Select(toWorkOn[n]) end end foundEntry = nil --invalidate --Optional: Shake/wiggle the restored sidechains to get back to how they were placed before?! structure.ShakeSidechainsSelected(1) structure.WiggleSelected(1, false, true) --backbone, sidechains selection.DeselectAll() end --store the net in extNets. tmpBonds = structure.GetHBonds() table.insert(extNets, tmpBonds) table.insert(extNetsBondsNr, #tmpBonds) if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end if (hasHBbonus == false) then print("NET " .. aa .. ": qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts center " .. tmpCenter0 .. " net " .. #foundNetRes .. " segs " .. a .. " iters.") save.Quicksave(qs_idx) qs_idx = qs_idx+1 else local tmpFilters = checkFilters() if (dia.cbx01.value == false or (dia.cbx01.value == true and tmpFilters[1][1] > HBbonus_start)) then print("NET " .. aa .. ": qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts center " .. tmpCenter0 .. " net " .. #foundNetRes .. " segs " .. a .. " iters.") save.Quicksave(qs_idx) qs_idx = qs_idx+1 end end if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end else --If the puzzle has HBbonus and it shall be incorporated then we can just check for this here. --As it is, EVERY pose that has a HBbonus is stored as long as its bonus differs from the already found bonusses for this net. So as little as possible bonus-nets are probably lost. if (hasHBbonus == true and ag_net_breaker == false and dia.cbx02.value == true) then --Only execute in intermediate iters and NOT at end. Only needs to be executed in auto-grow mode! local tmpFilters = checkFilters() if (tmpFilters[1][1] > 0 and tmpFilters[1][1] ~= HBbonus_start) then --Search in HBbonusArr if this bonus was already achieved in previous iterations if (#HBbonusArr == 0) then if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end HBbonusArr[1] = tmpFilters[1][1] print("NET " .. aa .. "_" .. #HBbonusArr .. ": qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts center " .. tmpCenter0 .. " net " .. #foundNetRes .. " segs " .. a .. " iters.") save.Quicksave(qs_idx) qs_idx = qs_idx+1 if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end else for o=1,#HBbonusArr do if (tmpFilters[1][1] == HBbonusArr[o]) then break end if (o == #HBbonusArr) then --We found a new HB bonus. Store this as new quicksave! table.insert(HBbonusArr, tmpFilters[1][1]) if (dia.cbx04.value == true) then if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end end print("NET " .. aa .. "_" .. #HBbonusArr .. ": qs" .. qs_idx .. " Score " .. round(getScore()) .. " Pts HB " .. round(tmpFilters[1][1]) .. " Pts center " .. tmpCenter0 .. " net " .. #foundNetRes .. " segs " .. a .. " iters.") save.Quicksave(qs_idx) qs_idx = qs_idx+1 if (dia2.cbx04.value == false) then if (turnFiltersOn == true) then filtDisAllExcl(filt2exclArr) end end end end end end end end end if (m == maxPoses) then print("Warning: All possible poses were tried. Provide more unfrozen sidechains to avoid this.") end if (ag_breaker == true) then break --Break loop over maxPoses (m) end end --End: for m=1,maxPoses do --Debug: print posesDone. There may be NO jumps in it! local debug = 0 if (debug == 1) then print("posesDone: ") for m=1,#posesDone do print(posesDone[m]) end end if (ag_net_breaker == true) then break end end --END: for a=1,grow_iters do if (dia.cbx02.value == true and #extNets >= dia.sli04.value) then --stop criterion break end end --END: for aa=1,ag_looper do print("Resetting importances and filters...") behavior.SetSidechainHBondImportance(CI_SC_initial) behavior.SetBackboneHBondImportance(CI_BB_initial) behavior.SetClashImportance(CI_initial) if (dia2.cbx04.value == false) then filter.EnableAll() end print("Restoring saves to undo...") undo.SetUndo(true) for n=1,qs_idx-qs_idx_start do save.Quickload(qs_idx_start+n-1) --Puts found nets into restore. Use ctrl+z/y to flip through. if (dia2.cbx04.value == false) then filter.EnableAll() wait(guardInt01) --This seems to be necessary in order for things to settle. end print("Net in qs" .. qs_idx_start+n-1 .. " (" .. n .. "/" .. qs_idx-qs_idx_start .. ") loaded.") end print("iwdn HBNW " .. version .. " has finished!") end --main xpcall(main, cleanup) --For debug: disable this line and instead enable the line below! --main()

Comments


ichwilldiesennamen Lv 1

This recipe is meant for finding semi-automatically H-Bond networks. (HBNW: H-Bond-Networker). It builds on the excellent work of spvincent's H-Network Probe (HNP).
The general idea of HBNW is quite similar to HNP but HBNW does not depend on the H-Bond network filter to identify and store nets. Instead all bonds are evaluated and especially in the Auto-Grow (AG) mode a found net is tried to be extended in further iterations. Like this, a net will in many cases grow all over the structure and very huge nets are possible. This recipe was very helpful for me in order to understand how H-Bond networks should be found and formed and I used this recipe for example to find the network described in Lab report #20.
The basic procedure is described in the header of this recipe and you will know most of it already from HNP. It works on unfrozen sidechains and these are randomly mutated in order to find H-Bond-connections. Shake and wiggle are used to sort out clashes and thereby it is tried to find stable bonds. This is done in multiple iterations in AG-mode and for puzzles with H-Bond bonus this often leads to quickly finding (in a matter of minutes to hours) a multitude of bonus-nets. There is no limit on the number of unfrozen sidechains to work on but of course things get slower the more sidechains you leave unfrozen.
Be aware though that the found nets are often "raw" nets which need manual optimization. Especially BUNs in the net should be removed or further bonded in order to get really good bonus. Further manual shakes and wiggles need to be done to stabilize a net. And sometimes whole branches of a net should be removed or "cleaned up" in order to get a good-scoring and nice-looking net. So it is no "wonder-tool" but it can show you how networks could look like and where on the structure they are likely to form.
There are two main modes (single- and Auto-Grow) and multiple options in this recipe. I recommend using only AG-mode which leads to larger nets in shorter time. The default settings should work well for most cases (AG-mode is active by default). If it is desired, I can release a short description of each option. For the start just leave the default options active and then try to play around with the options if you are interested.
This recipe can be used on every kind of puzzle. Especially on binder-puzzles it can be used to find interface-bonds or even whole networks.
For ligands there is the option to accept only bonds to a single segment ("AG connect only to fixed center") and with this all kinds of bonds to the ligand can be found in relatively short time.
This recipe has taught me how and where networks should be formed and it enabled me to create them manually and quickly now. I hope that this recipe can be useful to whomever is interested in finding and understanding H-Bond networks.

Thanks again to spvincent for his excellent pioneer's work with H Network Probe! Also many thanks go to Jeff101 for the good discussions we had on functions and features for this recipe. This has been very helpful even though I did not find the time to implement it all.

This recipe is quite a complicated "beast" and it gave me quite a headache while implementing some routines. But after all it was still fun to figure it out. Bugs are likely to still be in there even though I tried to test it as thoroughly as possible.

If you have questions, feel free to contact me. And as always: Happy folding and greetings to all, iwdn

ichwilldiesennamen Lv 1

On request of Alcor29, I added the option to leave all filters untouched. You find this as the last item in the second dialog.
Otherwise this recipe is pretty much the same as before.