import util
import basic_io
import settings
import motif
import numpy as np
import uuid
import exceptions
[docs]class Basepair(object):
"""
:param res1: First residue in basepair
:param res2: Second residue in basepair
:param r: Reference frame of basepair
:param bp_type: X3dna basepair type, default "c..."
:type res1: residue.Residue
:type res2: residue.Residue
:type r: np.array
:type bp_type: str
:attributes:
`res1` : residue.Residue
First residue in basepair
`res2` : residue.Residue
Second residue in basepair
`bp_type`: str
X3dna basepair type
`atoms`: list of atom.Atoms
All atoms from both res1 and res2. Grouped together for easy manipulation
`bp_state`: BasepairState
Contains the orientation, origin and sugar. This is all information
required to align to this basepair and can be used independently from
the reset of the basepair.
`uuid`: uuid.uuid1()
unique id to indentify this basepair when locating it in a motif or
pose
:examples:
.. code-block:: python
# build basepair from stratch
>>> from rnamake.unittests import instances
>>> from rnamake import basepair
>>> import numpy as np
>>> s = instances.structure()
>>> b = basepair.Basepair(s.get_residue(num=103),
s.get_residue(num=104),
np.eye(3))
>>> print b
<Basepair(A103-A104)>
# loading test basepair
>>> b = instances.basepair()
>>> print b
<Basepair(A13-A12)>
# primary axis of orientation, used to align to this basepair
>>> print b.r()
[[ 1.00000001e+00 1.00000001e-04 -9.99999990e-09]
[ -1.00000002e-04 1.00000001e+00 -9.99999990e-05]
[ -1.99999995e-08 9.99999955e-05 1.00000000e+00]]
# center of mass of basepair
>>> print b.d()
[ 0.1956032 0.69256601 0.0930465 ]
"""
__slots__ = ["res1", "res2", "r", "bp_type", "atoms", "bp_state", "uuid"]
def __init__(self, res1, res2, r, bp_type="c..."):
self.res1, self.res2 = res1, res2
self.atoms = self._get_atoms()
self.bp_state = self._get_state(r)
self.bp_type = bp_type
self.uuid = uuid.uuid1()
def __repr__(self):
return "<Basepair("+self.res1.chain_id+str(self.res1.num)+str(self.res1.i_code) +\
"-" + self.res2.chain_id+str(self.res2.num)+str(self.res2.i_code) + ")>"
def _get_atoms(self):
"""
helper function to collect all non null atoms from both resiudes in
basepair
:return: all atoms that belong to basepair
:rtype: list of atom.Atoms
"""
atoms = []
for a in self.res1.atoms:
if a is not None:
atoms.append(a)
for a in self.res2.atoms:
if a is not None:
atoms.append(a)
return atoms
def _get_state(self, r):
"""
helper function to setup basepair state object that keeps track of the
rotation, center and c1' sugar atoms used to align to this basepair
:param r: orientation matrix defining principle axes of coordinate
system
:type r: np.array
:return: basepair state object for this basepair
:rtype: BasepairState
"""
d = util.center(self.atoms)
sugars = [self.res1.get_atom("C1'").coords,
self.res2.get_atom("C1'").coords]
return BasepairState(r, d, sugars)
[docs] def residues(self):
"""
returns both residues for easy iteration
:returns: both residues in base
:rtype: list of residue.Residues
"""
return [self.res1, self.res2]
[docs] def c1_prime_coords(self):
"""
gets the c1' atom coordinates for both residues. These coordinates are
used to fine tune the alignment between basepairs
:return: list of c1' coords
:rtype: list of np.arrays
"""
return [self.res1.get_atom("C1'").coords,
self.res2.get_atom("C1'").coords]
[docs] def partner(self, res):
"""
get the other basepairing partner of a residue will throw an error
if the supplied residue is not contained within this basepair
:param res: the residue that you want to get the partner of
:type res: Residue object
"""
if res == self.res1:
return self.res2
elif res == self.res2:
return self.res1
else:
raise exceptions.BasepairException(
"called get_partner with residue not in this not in this "
"basepair")
# TODO make sure smaller number is actually first, doesnt always work
[docs] def name(self):
"""
get name of basepair: which is the combined name of both residues
seperated by a "-". The residue with the lower res number should
come first
:return: name of basepair
:rtype: str
:examples:
.. code-block:: python
# build basepair from stratch
>>> from rnamake.unittests import instances
>>> b = instances.basepair()
>>> print b.res1
<Residue('G13 chain A')>
>>> print b.res2
<Residue('C12 chain A')>
>>> print b.name()
A12-A13
"""
str1 = self.res1.chain_id+str(self.res1.num)+str(self.res1.i_code)
str2 = self.res2.chain_id+str(self.res2.num)+str(self.res2.i_code)
if str1 < str2:
return str1+"-"+str2
else:
return str2+"-"+str1
[docs] def flip(self):
"""
wrapper for BasepairState flip. There is probably no reasons to ever
call this. See the implementation in BasepairState
:return: None
"""
self.bp_state.flip()
[docs] def copy(self):
"""
creates a deep copy of this basepair object
"""
cbp = Basepair(self.res1, self.res2, self.bp_state.r)
cbp.bp_state = self.bp_state.copy()
cbp.bp_state.sugars = [self.res1.get_atom("C1'").coords,
self.res2.get_atom("C1'").coords]
cbp.bp_type = self.bp_type
cbp.uuid = self.uuid
return cbp
[docs] def state(self):
"""
gets the state of this basepair and makes sure that the
center and sugar positions are updated
"""
d = util.center(self.atoms)
self.bp_state.d = d
self.bp_state.sugars = self.c1_prime_coords()
return self.bp_state
[docs] def r(self):
"""
gets the orientation matrix of this basepair which is stored in the
the BasepairState instance.
:return: orientation matrix of basepair
:rtype: np.array
"""
return self.bp_state.r
[docs] def d(self):
"""
gets the center of the atoms in this basepair.
:return: center of mass of basepair
:rtype: np.array
"""
return util.center(self.atoms)
[docs] def to_str(self):
"""
stringify basepair object
"""
str1 = self.res1.chain_id+str(self.res1.num)+str(self.res1.i_code)
str2 = self.res2.chain_id+str(self.res2.num)+str(self.res2.i_code)
s = str1+"-"+str2 + "," + self.bp_state.to_str() + "," + \
self.bp_type
return s
[docs] def to_pdb_str(self, acount=1, return_acount=0):
"""
creates a PDB string formatted verision of this Basepair object.
:param acount: current atom index, default: 1
:param return_acount: final atom index after current atoms, default: 0
:type acount: int
:type return_acount: int
:return: str
"""
s = ""
for r in self.residues():
r_str, acount = r.to_pdb_str(acount, 1)
s += r_str
if return_acount:
return s, acount
else:
return s
[docs] def to_pdb(self, fname="basepair.pdb"):
"""
write basepair object to pdb file
:param fname" the file you want to write the basepair too
:type fname: str
"""
f = open(fname, "w")
f.write ( self.to_pdb_str() )
f.close()
[docs]class BasepairState(object):
"""
A small container class to hold the "State" of a basepair for finding
matches in the database. The critical features are:
:params r: Reference Frame of basepair
:type r: np.array
:params d: Center of mass of basepair
:type d: np.array
:params sugars: C1` atom coords for both residues in basepair
:type sugars: List of Np.Arrays
:attributes:
`r` : np.Matrix
Reference Frame of basepair
`d` : Np.Array
Center of mass of basepair
`sugars` : List of Np.Arrays
C1` atom coords for both residues in basepair
:examples:
.. code-block:: python
>>> from rnamake.unittests import instances
>>> bp_state_1 = instances.basepairstate_random()
>>> bp_state_2 = instances.basepairstate_random()
# get rotation and translation defining the transformation between the
# two states
>>> r, t = bp_state_1.get_transforming_r_and_t_w_state(bp_state_2)
>>> t += bp_state_1.d
# align end2 to end1, notice this is two steps, this is done so
# r and t can be applied to many states at the same time so they can
# be all aligned together
>>> new_r, new_d, new_sug = bp_state_2.get_transformed_state(r, t)
>>> bp_state_2.set(new_r, new_d, new_sug)
# both the orientation matrix and origins are nearly indentical now
>>> print bp_state_1.r
[[-0.43155336 0.24291737 0.86876513]
[-0.61066224 -0.78751428 -0.08314381]
[ 0.66396787 -0.56640305 0.4881949 ]]
>>> print bp_state_2.r
[[-0.43155336 0.24291737 0.86876513]
[-0.61066224 -0.78751428 -0.08314381]
[ 0.66396787 -0.56640305 0.4881949 ]]
>>> print bp_state_1.d
[ 5.45492979 45.17079568 43.80163295]
>>> print bp_state_2.d
[ 5.45492979 45.17079568 43.80163295]
"""
__slots__ = ["r", "d", "sugars"]
def __init__(self, r, d, sugars):
self.r, self.d, self.sugars = r, d, sugars
def flip(self):
self.r[1] = -self.r[1]
self.r[2] = -self.r[2]
[docs] def set(self, r, d, sug):
"""
sets a new orientation matrx, origin and new c1' sugar coordinates,
overriding the existing values stored in this instance.
:param r: new orientation matrix
:param d: new origin point
:param sug: new c1' sugar coordinates
:type r: np.array
:type d: np.array
:type sug: list of 2 np.arrays
"""
self.r = r
self.d = d
self.sugars = sug
[docs] def copy(self):
"""
returns a deep copy of this BasepairState object
"""
return BasepairState(np.array(self.r, copy=True),
np.array(self.d, copy=True),
[coord[:] for coord in self.sugars])
[docs] def diff(self, state):
"""
calculate the difference between this basepair and another. Difference
is defined currently as euclidean distance between their origins and
two times the matrix difference between the orientations of both
states.
:param state: the other state to compare the difference too
:type state: BasepairState
:return: difference between the states
:rtype: float
"""
diff = util.distance(self.d, state.d)
diff += self._rot_diff(state) * 2
#diff += self._sugar_diff(state)
return diff
def _rot_diff(self, state):
r_diff = util.matrix_distance(self.r, state.r)
state.flip()
r_diff_2 = util.matrix_distance(self.r, state.r)
state.flip()
if r_diff > r_diff_2:
r_diff = r_diff_2
return r_diff
def _sugar_diff(self, state):
diff_1 = util.distance(self.sugars[0], state.sugars[0]) + \
util.distance(self.sugars[1], state.sugars[1])
diff_2 = util.distance(self.sugars[1], state.sugars[0]) + \
util.distance(self.sugars[0], state.sugars[1])
if diff_1 > diff_2:
diff_1 = diff_2
return diff_1
[docs] def to_str(self):
"""
converts basepairstate into a string
"""
s = basic_io.point_to_str(self.d) + ";" + \
basic_io.matrix_to_str(self.r) + ";" +\
basic_io.matrix_to_str(self.sugars)
return s
[docs]def str_to_basepairstate(s):
"""
convert stringified basepair back to a basepair object see
basepairstate.to_str()
"""
spl = s.split(";")
d = basic_io.str_to_point(spl[0])
r = basic_io.str_to_matrix(spl[1])
sugars = basic_io.str_to_matrix(spl[2])
return BasepairState(r, d, sugars)