"straightening" or stretching disordered residues from alphafold models
Hi folks, I’ve been playing around with AlphaFold to get protein structures and visualizing them in ChimeraX. For some proteins with a lots of disordered regions (e.g. between non-interacting, well-folded domains), the ‘noodles’ end up being a visual clutter. So I wrote a custom python function to be used in ChimeraX that would ‘straighten’ or stretch selected residues (typically the disordered or low pLDDT regions) by simply assigning torsion angles for a more ‘straight’ structure. I do this via the `Residue.set_phi()` and `Residue.set_psi()` methods. Here’s a link to my Python script: https://github.com/delnatan/colabfold_chunker_utils/blob/65f2419425de300df56... The command is run simply with `straighten #1:13-20`. Now when there are multiple chains within the same model, altering the torsion angles may actually move the relative positioning between the chains. Is there an easy way to avoid this? perhaps by passing an extra argument as ‘reference’ so one can place the structure back to its original coordinate for the reference residues. I’m not too familiar with the programming API just yet, but what is the correct way to do this? Maybe that the reference coordinates can be saved before assigning the torsion angles, and a transformation matrix can be computed only for the reference region between the original and moved structure. The altered chain can then be moved back by applying this transformation matrix? Thanks! -Daniel
Hi Daniel, I'm going to assume that in any particular chain there is only one domain you need to maintain the positioning of, because if there are more than one then straightening the loop between them is going to move them apart and there is no way to keep both domains in the correct relative positioning to the rest of the structure. You could do it the way you propose, by doing a transformation afterward to reposition the important domain, but I would suggest instead straightening the loop so that the part away from the important domain moves, and the important domain stays put. So instead of having a "move_smaller" argument to your command, I would have an argument that says to move the N-terminal or C-terminal side. If you were feeling fancy, the argument could be the domain that should stay put and the command would figure if that was the N- or C-terminal side of the loop. Now, the set_phi/psi/omega calls only have a "move_smaller" argument, so your command would need to figure out if the N- or C- side was smaller and provide the corresponding move_smaller argument. To do that you need find the residue's phi/psi/omega bond by looking through residue.atoms for one of the appropriate atoms and looking through that atom's bonds for the correct other atom (N-CA / CA-C / C-N). That bond's smaller_side property will tell you which endpoint atom is on the smaller side. A couple of tips. 1) Instead of looping through residue.atoms to find an atom with a particular name, you can use residue.find_atom(name) to get the atom. 2) atom.bonds and atom.neighbors are laid out in the same order, so if you use "for i, nb in enumerate(atom.neighbors):" to loop through the neighbor atoms, when you find the right one then atom.bonds[I] will give the corresponding bond. --Eric Eric Pettersen UCSF Computer Graphics Lab
On Dec 18, 2024, at 11:31 AM, Daniel Elnatan via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote:
Hi folks,
I’ve been playing around with AlphaFold to get protein structures and visualizing them in ChimeraX. For some proteins with a lots of disordered regions (e.g. between non-interacting, well-folded domains), the ‘noodles’ end up being a visual clutter. So I wrote a custom python function to be used in ChimeraX that would ‘straighten’ or stretch selected residues (typically the disordered or low pLDDT regions) by simply assigning torsion angles for a more ‘straight’ structure. I do this via the `Residue.set_phi()` and `Residue.set_psi()` methods. Here’s a link to my Python script: https://github.com/delnatan/colabfold_chunker_utils/blob/65f2419425de300df56...
The command is run simply with `straighten #1:13-20`.
Now when there are multiple chains within the same model, altering the torsion angles may actually move the relative positioning between the chains. Is there an easy way to avoid this? perhaps by passing an extra argument as ‘reference’ so one can place the structure back to its original coordinate for the reference residues. I’m not too familiar with the programming API just yet, but what is the correct way to do this? Maybe that the reference coordinates can be saved before assigning the torsion angles, and a transformation matrix can be computed only for the reference region between the original and moved structure. The altered chain can then be moved back by applying this transformation matrix?
Thanks! -Daniel _______________________________________________ ChimeraX-users mailing list -- chimerax-users@cgl.ucsf.edu To unsubscribe send an email to chimerax-users-leave@cgl.ucsf.edu Archives: https://mail.cgl.ucsf.edu/mailman/archives/list/chimerax-users@cgl.ucsf.edu/
Hi Eric! thanks for the tips! Being able to specify N/C terminus as the ‘mobile’ part is easier to think about! If I understand correctly, I need to pass the correct side of the bond for being smaller to make use of the “move_smaller_side” argument. So, if I’m looking at the C-alpha, then the psi angle would be on the N-terminal side and the phi angle would be on the C-terminal side? So I’ve got this written up (can’t afford to get fancy at the moment) to just assign the angles and move either the N/C terminal side: ``` def straighten_v2(session, mobile_sel, mobile_terminal, noise=5.0): """ Args: mobile_sel (selection): selection of residue regions to be moved, must be a single contiguous region mobile_terminal (str): 'N' or 'C' terminal side noise (float): gaussian standard deviation to 'jitter' angles """ import random mobile_terminal = mobile_terminal.upper() assert mobile_terminal.lower() in ( "c", "n", ), "mobile_terminal can only be N or C" for i, res in enumerate(mobile_sel): # find backbone C-alpha atom = res.find_atom("CA") # handle proline differently (for now) if res.name != "PRO": # bond.smaller_side returns the atom that is on the smaller side for nb, bond in zip(atom.neighbors, atom.bonds): # look at peptide bond atoms only (no beta-carbons) if nb.name == "N": # N-CA -> psi bond (N-terminal) move_smaller_psi = ( bond.smaller_side.name == mobile_terminal ) elif nb.name == "C": # Ca-C -> phi bond (C-terminal) move_smaller_phi = ( bond.smaller_side.name == mobile_terminal ) psi_assgn = random.normalvariate(mu=165.0, sigma=noise) phi_assgn = random.normalvariate(mu=-165.0, sigma=noise) res.set_psi(psi_assgn, move_smaller_side=move_smaller_psi) res.set_phi(phi_assgn, move_smaller_side=move_smaller_phi) ``` It seems to work correctly in some small test cases… but I’ve got a feeling that I did not implement what you meant properly. Is this roughly what you were thinking? Thanks for your help! -D On Dec 18, 2024, at 1:31 PM, Eric Pettersen <pett@cgl.ucsf.edu> wrote: On Dec 18, 2024, at 1:26 PM, Eric Pettersen via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote: so if you use "for i, nb in enumerate(atom.neighbors):" to loop through the neighbor atoms Probably even better: for nb, bond in zip(atom.neighbors, atom.bonds): --Eric
On Dec 19, 2024, at 3:51 PM, Daniel Elnatan via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote:
So, if I’m looking at the C-alpha, then the psi angle would be on the N-terminal side and the phi angle would be on the C-terminal side?
It's the other way 'round: phi is around N-CA and psi is around CA-C. For example, see the diagram for Ramachandran plot on wikipedia: <https://en.wikipedia.org/wiki/Ramachandran_plot> My personal mnemonic (not saying it's a good one) is that N->C is phi,psi ... the opposite of sci-fi (science fiction). Elaine ----- Elaine C. Meng, Ph.D. UCSF Chimera(X) team Resource for Biocomputing, Visualization, and Informatics Department of Pharmaceutical Chemistry University of California, San Francisco
Hi Daniel, Your code looks good to me except that, as Elaine pointed out, you have phi and psi inverted. —Eric
On Dec 19, 2024, at 3:51 PM, Daniel Elnatan via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote:
Hi Eric! thanks for the tips!
Being able to specify N/C terminus as the ‘mobile’ part is easier to think about!
If I understand correctly, I need to pass the correct side of the bond for being smaller to make use of the “move_smaller_side” argument.
So, if I’m looking at the C-alpha, then the psi angle would be on the N-terminal side and the phi angle would be on the C-terminal side?
So I’ve got this written up (can’t afford to get fancy at the moment) to just assign the angles and move either the N/C terminal side:
``` def straighten_v2(session, mobile_sel, mobile_terminal, noise=5.0): """ Args: mobile_sel (selection): selection of residue regions to be moved, must be a single contiguous region mobile_terminal (str): 'N' or 'C' terminal side noise (float): gaussian standard deviation to 'jitter' angles """ import random
mobile_terminal = mobile_terminal.upper()
assert mobile_terminal.lower() in ( "c", "n", ), "mobile_terminal can only be N or C"
for i, res in enumerate(mobile_sel): # find backbone C-alpha atom = res.find_atom("CA")
# handle proline differently (for now) if res.name != "PRO": # bond.smaller_side returns the atom that is on the smaller side for nb, bond in zip(atom.neighbors, atom.bonds): # look at peptide bond atoms only (no beta-carbons) if nb.name == "N": # N-CA -> psi bond (N-terminal) move_smaller_psi = ( bond.smaller_side.name == mobile_terminal ) elif nb.name == "C": # Ca-C -> phi bond (C-terminal) move_smaller_phi = ( bond.smaller_side.name == mobile_terminal )
psi_assgn = random.normalvariate(mu=165.0, sigma=noise) phi_assgn = random.normalvariate(mu=-165.0, sigma=noise) res.set_psi(psi_assgn, move_smaller_side=move_smaller_psi) res.set_phi(phi_assgn, move_smaller_side=move_smaller_phi)
```
It seems to work correctly in some small test cases… but I’ve got a feeling that I did not implement what you meant properly. Is this roughly what you were thinking?
Thanks for your help! -D
On Dec 18, 2024, at 1:31 PM, Eric Pettersen <pett@cgl.ucsf.edu> wrote:
On Dec 18, 2024, at 1:26 PM, Eric Pettersen via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote:
so if you use "for i, nb in enumerate(atom.neighbors):" to loop through the neighbor atoms
Probably even better: for nb, bond in zip(atom.neighbors, atom.bonds):
--Eric
_______________________________________________ ChimeraX-users mailing list -- chimerax-users@cgl.ucsf.edu To unsubscribe send an email to chimerax-users-leave@cgl.ucsf.edu Archives: https://mail.cgl.ucsf.edu/mailman/archives/list/chimerax-users@cgl.ucsf.edu/
Thank you for your help! From testing on a few noodly structures, the function now behaves as intended. The link to the updated script is here in case any one wants to play around with the function: <https://github.com/delnatan/colabfold_chunker_utils/blob/9a697691b9a2c4da137f1c0d2c013ab9e02da832/straighten.py> [colabfold_chunker_utils.png] colabfold_chunker_utils/straighten.py at 9a697691b9a2c4da137f1c0d2c013ab9e02da832 · delnatan/colabfold_chunker_utils<https://github.com/delnatan/colabfold_chunker_utils/blob/9a697691b9a2c4da137f1c0d2c013ab9e02da832/straighten.py> github.com<https://github.com/delnatan/colabfold_chunker_utils/blob/9a697691b9a2c4da137f1c0d2c013ab9e02da832/straighten.py> I implemented the logic of for moving either N or C-term. like this: ``` for i, res in enumerate(mobile_sel): # find backbone C-alpha atom = res.find_atom("CA") # list containing 'small side' atom w.r.t C-alpha small_side_atoms = [] # handle proline differently (for now) if res.name != "PRO": for nb, bond in zip(atom.neighbors, atom.bonds): if nb.name == "N": small_side_atoms.append(bond.smaller_side.name) elif nb.name == "C": small_side_atoms.append(bond.smaller_side.name) # if we want to move the 'N' terminus if mobile_terminal == "N": # residue is near N-term (phi & psi is the smaller side) move_smaller = set(small_side_atoms) == {"N", "CA"} elif mobile_terminal == "C": # residue is near C-term (phi & psi is the smaller side) move_smaller = set(small_side_atoms) == {"CA", "C"} psi_assgn = random.normalvariate(mu=135.0, sigma=noise) res.set_psi(psi_assgn, move_smaller_side=move_smaller) phi_assgn = random.normalvariate(mu=-139.0, sigma=noise) res.set_phi(phi_assgn, move_smaller_side=move_smaller) ``` Thanks Eric and Elaine! -Daniel On Dec 19, 2024, at 5:17 PM, Eric Pettersen <pett@cgl.ucsf.edu> wrote: Hi Daniel, Your code looks good to me except that, as Elaine pointed out, you have phi and psi inverted. —Eric On Dec 19, 2024, at 3:51 PM, Daniel Elnatan via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote: Hi Eric! thanks for the tips! Being able to specify N/C terminus as the ‘mobile’ part is easier to think about! If I understand correctly, I need to pass the correct side of the bond for being smaller to make use of the “move_smaller_side” argument. So, if I’m looking at the C-alpha, then the psi angle would be on the N-terminal side and the phi angle would be on the C-terminal side? So I’ve got this written up (can’t afford to get fancy at the moment) to just assign the angles and move either the N/C terminal side: ``` def straighten_v2(session, mobile_sel, mobile_terminal, noise=5.0): """ Args: mobile_sel (selection): selection of residue regions to be moved, must be a single contiguous region mobile_terminal (str): 'N' or 'C' terminal side noise (float): gaussian standard deviation to 'jitter' angles """ import random mobile_terminal = mobile_terminal.upper() assert mobile_terminal.lower() in ( "c", "n", ), "mobile_terminal can only be N or C" for i, res in enumerate(mobile_sel): # find backbone C-alpha atom = res.find_atom("CA") # handle proline differently (for now) if res.name != "PRO": # bond.smaller_side returns the atom that is on the smaller side for nb, bond in zip(atom.neighbors, atom.bonds): # look at peptide bond atoms only (no beta-carbons) if nb.name == "N": # N-CA -> psi bond (N-terminal) move_smaller_psi = ( bond.smaller_side.name == mobile_terminal ) elif nb.name == "C": # Ca-C -> phi bond (C-terminal) move_smaller_phi = ( bond.smaller_side.name == mobile_terminal ) psi_assgn = random.normalvariate(mu=165.0, sigma=noise) phi_assgn = random.normalvariate(mu=-165.0, sigma=noise) res.set_psi(psi_assgn, move_smaller_side=move_smaller_psi) res.set_phi(phi_assgn, move_smaller_side=move_smaller_phi) ``` It seems to work correctly in some small test cases… but I’ve got a feeling that I did not implement what you meant properly. Is this roughly what you were thinking? Thanks for your help! -D On Dec 18, 2024, at 1:31 PM, Eric Pettersen <pett@cgl.ucsf.edu> wrote: On Dec 18, 2024, at 1:26 PM, Eric Pettersen via ChimeraX-users <chimerax-users@cgl.ucsf.edu> wrote: so if you use "for i, nb in enumerate(atom.neighbors):" to loop through the neighbor atoms Probably even better: for nb, bond in zip(atom.neighbors, atom.bonds): --Eric _______________________________________________ ChimeraX-users mailing list -- chimerax-users@cgl.ucsf.edu To unsubscribe send an email to chimerax-users-leave@cgl.ucsf.edu Archives: https://mail.cgl.ucsf.edu/mailman/archives/list/chimerax-users@cgl.ucsf.edu/
participants (3)
-
Daniel Elnatan
-
Elaine Meng
-
Eric Pettersen