Icon representing a recipe

Recipe: Atom Explorer v1.0

created by LociOiling

Profile


Name
Atom Explorer v1.0
ID
102531
Shared with
Public
Parent
None
Children
None
Created on
November 07, 2017 at 03:03 AM UTC
Updated on
November 07, 2017 at 03:03 AM UTC
Description

Use spacebands to identify the atoms of a ligand or a regular segment. Save the user's picks, output them in Lua table format for recipe use.

Best for


Code


--[[ Atom Explorer version 1.0 - 2017/11/05 - LociOiling -- systematically add spacebands to each atom of a ligand or selected segment -- record user's input regarding the atom, dump as table ]]-- -- -- globals section -- Recipe = "Atom Explorer" Version = "1.0" ReVersion = Recipe .. " v" .. Version ligandy = false segCnt = 0 segCnt2 = 0 atomChart = {} isCarbon = true isNitrogen = false isOxygen = false isHydrogen = false isSulfur = false isMetal = false isDonor = false isAcceptor = false isHybrid = false atomNote = "" firstHydrogen = false restHydrogen = false -- -- end of globals section -- -- -- Ident - print identifying information at beginning and end of recipe -- -- slugline - first line to print - normally recipe name and version -- function Ident ( slugline ) local function round ( ii ) return ii - ii % 0.001 end print ( slugline ) print ( "Puzzle: " .. puzzle.GetName () .. " (" .. puzzle.GetPuzzleID () .. ")" ) print ( "Track: " .. ui.GetTrackName () ) gname = user.GetGroupName () if gname ~= nil then gname = " (" .. gname .. ")" else gname = "" end print ( "User: " .. user.GetPlayerName () .. gname ) local scoretype = scoreboard.GetScoreType () local scort = "" if scoretype == 0 then scort = "soloist" elseif scoretype == 1 then scort = "evolver" elseif scoretype == 2 then scort = "all hands" elseif scoretype == 3 then scort = "no score" else scort = "unknown/error" end print ( "Rank: " .. scoreboard.GetRank ( scoretype ) .. " (" .. scort .. ")" ) local sGroup = scoreboard.GetGroupScore () if sGroup ~= nil then print ( "Group rank / score: " .. scoreboard.GetGroupRank () .. " / " .. round ( 10 * ( 800 - sGroup ) ) ) end end function random ( n1, n2 ) --random function returns int or float depends on input vars if n2 == nil and n1 == nil then return math.random () --float else if n2 == nil then if n1 % 1 == 0 then return math.random ( n1 ) --integer else return math.random () * n1 --float end else if n1 % 1 == 0 and n2 % 1 == 0 then return math.random ( n1, n2 ) --integer between else return math.random () * ( n2 - n1 ) + n1 --float between end end end end function GetBonding ( text, element, segment, atom ) local dlg = dialog.CreateDialog ( text ) dlg.l01 = dialog.AddLabel ( "Confirm bondable atom type" ) local lmsg = "Segment " .. segment .. ", atom " .. atom .. ", " if element == "N" then lmsg = lmsg .. ", element = Nitrogen" elseif element == "O" then lmsg = lmsg .. ", element = Oxygen" else lmsg = lmsg .. ", element = " .. element end dlg.l06 = dialog.AddLabel ( lmsg ) dlg.l21 = dialog.AddLabel ( "" ) dlg.l50 = dialog.AddLabel ( "" ) dlg.l51 = dialog.AddLabel ( "With \"Show Bondable Atoms\" selected, " ) dlg.l56 = dialog.AddLabel ( "hydrogen bond donor and acceptors are " ) dlg.l57 = dialog.AddLabel ( "shown with red, blue, or purple spheres." ) dlg.l61 = dialog.AddLabel ( "Donors, normally nitrogen, are blue." ) dlg.l61 = dialog.AddLabel ( "Acceptors, normally oxygen, are red, " ) dlg.l63 = dialog.AddLabel ( "Donor/acceptors are purple, normally" ) dlg.l64 = dialog.AddLabel ( "an oxygen with hydrgen attached." ) dlg.l65 = dialog.AddLabel ( "" ) dlg.isDonor = dialog.AddCheckbox ( "donor", isDonor ) dlg.isAcceptor = dialog.AddCheckbox ( "acceptor", isAcceptor ) dlg.isHybrid = dialog.AddCheckbox ( "donor/acceptor", isHybrid ) dlg.l86 = dialog.AddLabel ( "" ) dlg.ok = dialog.AddButton ( "OK", 1 ) dlg.cancel = dialog.AddButton ( "Cancel", -1 ) rc = dialog.Show ( dlg ) if rc > 0 then isDonor = dlg.isDonor.value isAcceptor = dlg.isAcceptor.value isHybrid = dlg.isHybrid.value -- -- radio buttons the hard way if isAcceptor then isDonor = false end if isHybrid then isDonor = false isAcceptor = false end end return rc end function ShowAtom ( text, segment, atom ) isCarbon = true isNitrogen = false isOxygen = false isHydrogen = false isSulfur = false isMetal = false isDonor = false isAcceptor = false isHybrid = false atomNote = "" if firstHydrogen then isCarbon = false isHydrogen = true end local dlg = dialog.CreateDialog ( text ) dlg.l01 = dialog.AddLabel ( "Tip: use CPK, EnzDes, or Ligand Specific coloring," ) dlg.l06 = dialog.AddLabel ( "which give certain atoms a colored sleeve" ) dlg.l07 = dialog.AddLabel ( "Nitrogen is blue, oxygen is red, sulfur is yellow" ) dlg.l08 = dialog.AddLabel ( "hydrogen is white, carbon has no sleeve," ) dlg.l16 = dialog.AddLabel ( "and metal atoms have a different color." ) dlg.l21 = dialog.AddLabel ( "" ) if firstHydrogen then dlg.l26 = dialog.AddLabel ( "First hydrogen atom found, so the" ) dlg.l27 = dialog.AddLabel ( "remaining atoms are likely all hydrogen." ) dlg.restHydrogen = dialog.AddCheckbox ( "Mark all remaining atoms as hydrogen", restHydrogen ) dlg.l28 = dialog.AddLabel ( "" ) end dlg.isCarbon = dialog.AddCheckbox ( "carbon", isCarbon ) dlg.isNitrogen = dialog.AddCheckbox ( "nitrogen", isNitrogen ) dlg.isOxygen = dialog.AddCheckbox ( "oxygen", isOxygen ) dlg.isHydrogen = dialog.AddCheckbox ( "hydrogen", isHydrogen ) dlg.isSulfur = dialog.AddCheckbox ( "sulfur", isSulfur ) dlg.isMetal = dialog.AddCheckbox ( "metal", isMetal ) dlg.l76 = dialog.AddLabel ( "" ) dlg.atomNote = dialog.AddTextbox ( "notes", atomNote ) dlg.l86 = dialog.AddLabel ( "" ) dlg.ok = dialog.AddButton ( "OK", 1 ) dlg.cancel = dialog.AddButton ( "Cancel", -1 ) rc = dialog.Show ( dlg ) if rc > 0 then isCarbon = dlg.isCarbon.value isNitrogen = dlg.isNitrogen.value isOxygen = dlg.isOxygen.value isHydrogen = dlg.isHydrogen.value isSulfur = dlg.isSulfur.value isMetal = dlg.isMetal.value atomNote = dlg.atomNote.value if firstHydrogen then restHydrogen = dlg.restHydrogen.value end -- -- radio buttons the hard way -- if isNitrogen then isCarbon = false end if isOxygen then isCarbon = false isNitrogen = false end if isHydrogen then isCarbon = false isNitrogen = false isOxygen = false firstHydrogen = true end if isSulfur then isCarbon = false isNitrogen = false isOxygen = false firstHydrogen = false isHydrogen = false end if isMetal then isCarbon = false isNitrogen = false isOxygen = false firstHydrogen = false isHydrogen = false isSulfur = false end local element = "C" if isNitrogen then element = "N" elseif isOxygen then element = "O" elseif isHydrogen then element = "H" elseif isSulfur then element = "S" elseif isMetal then element = "mx" end if isNitrogen or isOxygen then if isNitrogen then isDonor = true isAcceptor = false isHybrid = false end if isOxygen then isDonor = false isAcceptor = true isHybrid = false end GetBonding ( text, element, segment, atom ) end local bonding = "" if isAcceptor then bonding = "A" elseif isDonor then bonding = "D" elseif isHybrid then bonding = "B" end atomChart [ #atomChart + 1 ] = { segment, atom, element, bonding, atomNote, } end return rc end function ConfirmTarget ( target ) local otarget = target local changed = false local uerror = "" local rc local atom1 = 1 local atomn = structure.GetAtomCount ( otarget ) repeat local dlg = dialog.CreateDialog ( ReVersion ) local atomCnt = structure.GetAtomCount ( otarget ) local ssType = structure.GetSecondaryStructure ( otarget ) local aaName = structure.GetAminoAcid ( otarget ) changed = false dlg.l01 = dialog.AddLabel ( "Starting target segment: " .. otarget ) dlg.l02 = dialog.AddLabel ( "Atom count: " .. atomCnt ) dlg.l03 = dialog.AddLabel ( "Secondary structure: " .. ssType ) dlg.l04 = dialog.AddLabel ( "Amino acid code: " .. aaName ) dlg.l04 = dialog.AddLabel ( "Max segment: " .. segCnt ) dlg.l06 = dialog.AddLabel ( "" ) dlg.target = dialog.AddTextbox ( "Target segment: ", tostring ( target ) ) dlg.atom1 = dialog.AddTextbox ( "First atom: ", tostring ( atom1 ) ) dlg.atomn = dialog.AddTextbox ( "Last atom: ", tostring ( atomn ) ) dlg.l11 = dialog.AddLabel ( "" ) if uerror:len () > 0 then dlg.lerr1 = dialog.AddLabel ( "" ) dlg.lerr2 = dialog.AddLabel ( "ERROR: " .. uerror ) end dlg.ok = dialog.AddButton ( "OK", 1 ) dlg.cancel = dialog.AddButton ( "Cancel", -1 ) rc = dialog.Show ( dlg ) uerror = "" if rc > 0 then target = tonumber ( dlg.target.value ) if target == nil then uerror = "target must be numeric" target = otarget elseif target < 0 or target > segCnt then uerror = "target out of range" else atomCnt = structure.GetAtomCount ( target ) if target ~= otarget then changed = true uerror = "target changed, confirm atom range" atom1 = 1 atomn = structure.GetAtomCount ( target ) end otarget = target end if uerror:len () == 0 then atom1 = tonumber ( dlg.atom1.value ) if atom1 == nil then uerror = "first atom must be numeric" atom1 = 1 elseif atom1 < 0 or atom1 > atomCnt then uerror = "first atom out of range" end end if uerror:len () == 0 then atomn = tonumber ( dlg.atomn.value ) if atomn == nil then uerror = "last atom must be numeric" atomn = atomCnt elseif atomn < 0 or atomn > atomCnt then uerror = "last atom out of range" end end if atom1 > atomn then atom1, atomn = atomn, atom1 uerror = "first and last atoms swapped, please confirm" end end until rc < 0 or ( not changed and uerror:len () == 0 ) return rc, target, atom1, atomn end function AtomExplorer ( target ) local rc, target, atom1, atomn = ConfirmTarget ( target, ac ) if rc < 0 then return end local ac = structure.GetAtomCount ( target ) print ( "target segment " .. target .. " atom count = " .. ac ) print ( "atom range to scan = " .. atom1 .. " - " .. atomn ) for ii = atom1, atomn do local text = "atom " .. ii if restHydrogen == false then local str = 2.5 local len = 5 local theta = math.acos ( math.random () ) local phi = 2 * math.pi * math.random() local segmentXAxis = 0 local segmentYAxis = 0 while true do -- need target plus two more segments, all must be different segmentXAxis = random ( segCnt ) segmentYAxis = random ( segCnt ) if segmentXAxis ~= target and segmentYAxis ~= target and segmentXAxis ~= segmentYAxis then break end end local bc = band.Add ( target, segmentXAxis, segmentYAxis, len, theta, phi, ii ) if bc ~= nil and bc ~= 0 then band.SetStrength ( bc, str ) end local nomore = ShowAtom ( text, target, ii ) band.DeleteAll () if nomore < 0 then break end else atomChart [ #atomChart + 1 ] = { target, ii, "H", "", "", } end end return target end function main () Ident ( ReVersion ) segCnt = structure.GetCount() segCnt2 = segCnt while structure.GetSecondaryStructure ( segCnt2 ) == "M" do segCnt2 = segCnt2 - 1 end print ( "initial segment count: " .. segCnt ) if segCnt ~= segCnt2 then ligandy = true print ( "ligand found, adjusted segment count: " .. segCnt2 ) end local seed = os.time() seed = 1 / seed while seed < 10000000 do seed = seed * 10 end seed = seed - seed % 1 print ( "Seed is: " .. seed ) math.randomseed ( seed ) math.random () math.random () local targetAtom = segCnt2 if ligandy then targetAtom = segCnt end targetAtom = AtomExplorer ( targetAtom ) -- -- print out the results -- print ( #atomChart .. " atoms analyzed" ) if #atomChart > 0 then local cCnt = 0 local hCnt = 0 local nCnt = 0 local oCnt = 0 local sCnt = 0 local mCnt = 0 local frstH = 0 local nAccept = 0 local nDonor = 0 local nHybrid = 0 print ( "--" ) print ( "-- AtomChart indexes" ) print ( "-- ATCSEG = 1 -- segment" ) print ( "-- ATCATM = 2 -- atom" ) print ( "-- ATCELE = 3 -- element (user pick)" ) print ( "-- ATCBND = 4 -- bonding (user pick, D = donor, A = acceptor, B = both)" ) print ( "-- ATCCOM = 5 -- comment (user input)" ) print ( "--" ) print ( "atomChart = {" ) for ii = 1, #atomChart do if atomChart [ ii ] [ 3 ] == "C" then cCnt = cCnt + 1 elseif atomChart [ ii ] [ 3 ] == "H" then if frstH == 0 then frstH = ii end hCnt = hCnt + 1 elseif atomChart [ ii ] [ 3 ] == "N" then nCnt = nCnt + 1 elseif atomChart [ ii ] [ 3 ] == "O" then oCnt = oCnt + 1 elseif atomChart [ ii ] [ 3 ] == "S" then sCnt = sCnt + 1 else mCnt = mCnt + 1 end if atomChart [ ii ] [ 4 ] == "A" then nAccept = nAccept + 1 elseif atomChart [ ii ] [ 4 ] == "D" then nDonor = nDonor + 1 elseif atomChart [ ii ] [ 4 ] == "B" then nHybrid = nHybrid + 1 end print ( " { " .. atomChart [ ii ] [ 1 ] .. ", " .. atomChart [ ii ] [ 2 ] .. ", \"" .. atomChart [ ii ] [ 3 ] .. "\", \"" .. atomChart [ ii ] [ 4 ] .. "\", \"" .. atomChart [ ii ] [ 5 ] .. "\", }," ) end print ( "}" ) -- -- formulate a formula -- local form = "" if cCnt > 0 then form = form .. "C" .. cCnt end if hCnt > 0 then form = form .. "H" .. hCnt end if nCnt > 0 then form = form .. "N" .. nCnt end if oCnt > 0 then form = form .. "O" .. oCnt end if sCnt > 0 then form = form .. "S" .. sCnt end if mCnt > 0 then form = form .. " {metal}" .. mCnt end if form:len () > 0 then print ( "formula: " .. form ) if mCnt > 0 then print ( "{metal} stands for any metal atoms identified" ) end end if frstH ~= 0 then if frstH > 1 then print ( "heavy atoms 1 - " .. frstH - 1 ) end print ( "hydrogen atoms " .. frstH .. " - " .. structure.GetAtomCount ( targetAtom ) ) end print ( "hydrogen bonding summary" ) print ( "h-bond donors = " .. nDonor ) print ( "h-bond acceptors = " .. nAccept ) print ( "h-bond donor/acceptors = " .. nHybrid ) end cleanup () end function cleanup ( errmsg ) if CLEANUPENTRY ~= nil then return end CLEANUPENTRY = true print ( "---" ) local reason local start, stop, line, msg if errmsg == nil then reason = "complete" else -- -- model 120 - civilized error reporting, -- thanks to Bruno K. and Jean-Bob -- start, stop, line, msg = errmsg:find ( ":(%d+):%s()" ) if msg ~= nil then errmsg = errmsg:sub ( msg, #errmsg ) end if errmsg:find ( "Cancelled" ) ~= nil then reason = "cancelled" else reason = "error" end end -- -- model 100 - print recipe name, puzzle, track, time, score, and gain -- Ident ( ReVersion .. " " .. reason ) if reason == "error" then print ( "Unexpected error detected" ) print ( "Error line: " .. line ) print ( "Error: \"" .. errmsg .. "\"" ) end -- -- model 130 - reset clash importance, clear selections, restore structures, etc. -- selection.DeselectAll () band.DeleteAll () behavior.SetFiltersDisabled ( false ) end xpcall ( main, cleanup )

Comments


LociOiling Lv 1

Several recent puzzles have involved mystery ligands, small molecules floating in space somewhere near the protein. Unlike the familiar 20 amino acids, the exact makeup of these ligands can be hard to determine.

Atom Explorer draws spacebands to each atom of a specified segment. By default, it starts with the last ligand segment in a puzzle, but you can point it to any segment if desired.

The recipe prompts for chemical element of each atom. Using one of the "CPK" coloring options helps to identify the elements.

For nitrogen and oxygen, the recipe also prompts for whether the atom is a hydrogen bond donor, acceptor, or both. Normally nitrogen is a donor, and oxygen can be either an acceptor, or a donor/acceptor.

The recipe records the user picks, and, at the end, outputs them to the scriptlog in table format. The table can be imported into another recipe.

setup and view options

Some puzzles, like 1446, may allow you to draw the ligand away from the protein. If so, use the move tool to get some space between the ligand and the protein.

Otherwise, the "X-ray tunnel for ligand" view option may help you to see what's going on. You can drag on the black border of the tunnel to make it wider or narrower. Fading the background (via control-shift-drag on background) or the foreground (via control-alt-drag on background) may also help.

For a ligand puzzle, the "Ligand Specific" color option and the "Cartoon Ligand" view protein option probably produce the best results.

Otherwise, be sure to use "EnzDes" coloring or one of the "CPK" options.

These options put a blue sleeve around nitrogen atoms, a red sleeve around oxygen atoms, a white sleeve around hydrogens, and a yellow sleeve around sulfurs. Carbons have a light blue-gray color, the same color as the bonds between atoms. Metals like zinc or calcium have an indistinct light color, such as a kind of light green for calcium.

In addition to the CPK sleeve colors, the appropriate "show bonds" option and "show bondable atoms" highlight whether an atom is a hydrogen donor, acceptor, or both an acceptor and a donor.

For example, with a ligand, use "show bonds (non-protein)" and "show bondable atoms" to highlight the bondable atoms. Usually, nitrogen is a donor. Oxygen can be an acceptor, but it can also be a donor/acceptor if it's bonded to a hydrogen atom.

Running the recipe

Once you have the correct view options set and the ligand or segment is visible, run the recipe. The recipe selects the last segment to analyze by default. The segment selected and the range of atom numbers to look at can be changed in the first dialog.

Once the segment number is confirmed, the recipe starts by drawing a heavy band from the first atom specified to a point in space. The free end of the band can be moved around to help clarify which atom the band is attached to. The recipe prompts for the element of the atom, which can be identified by the CPK colors described above. A comment about the atom can also be entered.

For nitrogen and oxygen, the recipe prompts for donor/acceptor/both. These can be identified by the spheres drawn when "show bonds" and "show bondable atoms" are selected.

The atoms analyzed are written to the scriptlog in Lua table format. The resulting table can be used in another Lua script.

The scriptlog output also includes an approximate chemical formula and a count of hydrogen bond donors, acceptors, and donor/acceptors.

heavy versus light

Foldit follows the convention that all "light" atoms (hydrogens) appear after the other "heavy" atoms. Once you identify the first hydrogen, all the remaining atoms should be hydrogen. The recipe offers to identify all the remaining atoms as hydrogen, which can speed things up considerably. The default atom choice also becomes hydrogen instead of carbon after the first hydrogen.

The recipe reports the heavy and light atom ranges to the scriptlog.

regular amino acids

The recipe can also be used with the amino acid segments (residues) that make up a protein.

Using a "cartoon" view option, you won't be able to see the backbone atoms of an amino acid residue. Use "line+H" or "line+polarH" to help reveal these atoms.

The atom numbers for the first and last amino acid residue in a protein differ slightly from the ones in the middle. The first segment (or "N terminal") has an extra hydrogen, but that's not usually a concern.

The last segment (or "C terminal") has an extra -OH group. The O from the -OH group is counted before any of the sidechain atoms. As a result, the sidechain atoms of the last segment of a protein are all numbered one higher than the sidechain atoms of the same AA residue in the middle of the protein. When banding to a specific atom, recipes need to account for this difference in the last atom.

The nice part about the usual 20 amino acids is that they're always the same, aside from the quirks of atom numbering, so you don't absolutely need this recipe to identify the atoms.

metal atoms

Metals like zinc and calcium are hard to identify by color. So far, it's easiest for figure them out by examining certain Foldit configuration files, which use poorly documented formats associated with the underlying Rosetta software.

Atom Explorer just has a "metal" pick for element, and puts "mx" in the element column of the output table.

table output

The recipe outputs a table called AtomChart in Lua source format. The table can be copied into another recipe for use in banding.

The table has five columns:

  • segment number
  • atom number
  • chemical element (as identified by the user)
  • hydrogen bonding (user-pick, blank or D for donor, A for acceptor, and B for donor/acceptor)
  • optional user comment