This is a read-only mirror of pymolwiki.org

Difference between revisions of "Consistent View/ Map Inspect"

From PyMOL Wiki
Jump to navigation Jump to search
Line 7: Line 7:
 
Three components are needed:
 
Three components are needed:
  
* A Python module, support.py, giving matrix algebra & Kabsch-style structure alignment.  This module must be in the current working directory, and PyMol started from the command line, e.g., on MacOSX:
+
* A Python module, support.py, giving matrix algebra & Kabsch-style structure alignment (source code given below).  This module must be in the current working directory, and PyMol started from the command line, e.g., on MacOSX:
  
 
<pre>
 
<pre>

Revision as of 19:19, 7 April 2010

DESCRIPTION

This is a toolkit for rapidly inspecting multiple maps and models. The right & left arrow keys navigate sequentially through the amino acid chain, but alternating between two structures (could be NCS-related chains or models solved in different space groups). Each view is rendered in a consistent orientation (the default is centered on alpha carbon, with nitrogen right, carbonyl left & beta carbon up). The view can be customized. It is necessary to edit the script to define the behavior for the problem at hand.

INSTALLATION

Three components are needed:

  • A Python module, support.py, giving matrix algebra & Kabsch-style structure alignment (source code given below). This module must be in the current working directory, and PyMol started from the command line, e.g., on MacOSX:
/Applications/MacPyMol.app/Contents/MacOS/MacPyMOL
  • Atomic coordinates must be loaded first, and if appropriate, maps and isomesh levels. An example .pml file is shown here, consisting of two structural models solved in different space groups:
load sand2b_001_2mFo-DFc_map.xplor, map2
isomesh msh2,map2,2.0
color blue, msh2

load sand3_rotfix_001_2mFo-DFc_map.xplor, map3
isomesh msh3,map3,2.0
color marine, msh3

load sand2b_001.pdb, mod02
load sand3_rotfix_001.pdb, mod03
set antialias, 2
set line_width, 3
  • A final pml is then loaded (@consistent.pml) to define the viewing sequence for the problem at hand. An example is shown here. (The python / python end construct will not work in PyMOL versions below 1.0.)
python

from support import matrix,residue,std_residue,kabsch_rotation
from support import view_matrix, new_set_view

#  This section is the generic preset view--DO NOT EDIT #########################
#  Centered on CA, CB up, N left, C=O right
preset_view = view_matrix((
     0.295077115,    0.440619737,   -0.847830832,
    -0.408159286,    0.860427082,    0.305112839,
     0.863915384,    0.256014407,    0.433732271,
     0.,             0.,           -30.,
     0.,             0.,             0.,
    26.,            33.,             0.
))
preset_view.difference_to_this_CA = matrix.col((0.,0.,0.,))

preset_std_residue = std_residue()
preset_std_residue.N = matrix.col((16.1469993591, 33.0660018921, -20.0760002136))
preset_std_residue.CA = matrix.col((15.4309997559, 34.2599983215, -20.4640007019))
preset_std_residue.C = matrix.col((15.2889995575, 34.1189994812, -21.9699993134))
preset_std_residue.CB = matrix.col((16.2469997406, 35.516998291, -20.1380004883))
#  END GENERIC VIEW #############################################################

def view_preset():
  cmd.current_std_view = preset_view
  cmd.current_std_residue = preset_std_residue

view_preset()

#  EDIT THIS SECTION TO REFLECT THE PROBLEM AT HAND #############################
#  In this example, there are two objects: structure mod02 & mod03
#  We will be looking at chain B in each structure, each having 262 residues,
#  for a total of 524 amino acids to view. 
#  For each of 524 views, we select the appropriate map and isomesh

cmd.no_of_structures = 2
cmd.global_ptr = 1 * cmd.no_of_structures
cmd.last_residue = preset_std_residue

def seqwalk():
  chain_id = "B"
  atoms = cmd.get_model("object mod03 and chain %s"%chain_id)
  
  wait=""
  if cmd.global_ptr<524:
  
    resid = cmd.global_ptr/2
    this_object = cmd.global_ptr%2
    if this_object==0:
      atoms = cmd.get_model("object mod03 and chain %s"%chain_id)
      obj_id = "mod03"
      map = "map3"
      mesh = "msh3"
    else:
      atoms = cmd.get_model("object mod02 and chain %s"%chain_id)
      obj_id = "mod02"
      map = "map2"
      mesh = "msh2"
    
    
    this = residue(chain_id,resid,atoms)
    cmd.last_residue = this #remember this residue in case reset command is issued
    print "Centering on /%s//%s/%s %d/CA; waiting for right mouse key"%(
      obj_id,chain_id,this.residue_type,resid,)
    cmd.center("/%s//%s/%d/CA"%(obj_id,chain_id,resid))
    cmd.isomesh(
     name=mesh,map=map,level= 2.0, selection="object %s and chain %s and resid %d"%(obj_id,chain_id,resid),buffer=8)
#  No more edits below this line
    
    kr = kabsch_rotation(cmd.current_std_residue.sites(),this.sites())
    view_matrix = new_set_view(cmd.current_std_view,kr,this,verbose=False).view_matrix()
    cmd.set_view(view_matrix)
    cmd.global_ptr=cmd.global_ptr+1

#  END OF PROBLEM-SPECIFIC SECTION TO EDIT  #####################################

def seqwalk2():
  cmd.global_ptr-=2
  seqwalk()
    
cmd.set_key("right",seqwalk)
cmd.set_key("left",seqwalk2)

def view_reset():
  cmd.current_std_view = view_matrix(cmd.get_view()).set_diff_to_CA(cmd.last_residue)
  cmd.current_std_residue = cmd.last_residue

def view_goto(number):
  cmd.global_ptr = cmd.no_of_structures*number

cmd.extend("view_reset",view_reset)  # After customizing the view with GUI mouse & clicks, make the
                                     # view persistent by typing "view reset"
cmd.extend("view_preset",view_preset)# Forget the user-customized view, go back to the generic view
                                     # defined at the beginning of the program
cmd.extend("view_goto",view_goto)    # Example: view_goto(36) --resets at residue 36

python end

USAGE

Once the code is set up as described above, you must mouse-click in PyMOL's 3D display window to enable the right & left arrow keys. These keys will navigate through the amino acid sequence, displaying each residue in a predefined orientation. Maps, if defined, will be redrawn for each view.

Three commands can be issued at the prompt:

  • view_goto(N) -- will go to residue N. The view will not actually change until you mouse click in the 3D display window and hit the right arrow key.
  • view_reset -- will accept the current view (in relation to the present alpha carbon) as the default. This is extremely useful for preparing views for presentation, which compare sidechains or ligands in two homologous structures. For example, if you are interested in a Mg++ ion adjacent to residue 56, you will first use the arrow keys to center on the residue 56 alpha carbon. Then recenter on the Mg++ ion, and rotate to the exact view desired. Typing view_reset will then produce the same view of the Mg++ ion in both structures.
  • view_preset -- revert back to the generic view centered on the alpha carbon.

METHODOLOGY

Four atoms of the current residue (N, CA, CB, and C) are used for a Kabsch-style alignment against a reference orientation. For glycines, a hypothetical beta-carbon is modelled (but not shown) based on tetrahedral geometry. A known limitation of the approach is that the alignment is very local, i.e., neighboring residues may not precisely align between structures.

Adaptation of PyMOL's set_view command to display aligned views of separate structures was suggested by Herb Klei.

SUPPORTING MODULE

The following Python module, support.py, must be placed in the current working directory. Code is based on CCTBX (cctbx.sf.net) and is released under the cctbx license.

import math

#####################################################################
# This code has been adapted from cctbx.sf.net, file scitbx/matrix.py
flex = None
numpy = None

class rec(object):

  container_type = tuple

  def __init__(self, elems, n):
    assert len(n) == 2
    if (not isinstance(elems, self.container_type)):
      elems = self.container_type(elems)
    assert len(elems) == n[0] * n[1]
    self.elems = elems
    self.n = tuple(n)

  def n_rows(self):
    return self.n[0]

  def n_columns(self):
    return self.n[1]

  def __neg__(self):
    return rec([-e for e in self.elems], self.n)

  def __add__(self, other):
    assert self.n == other.n
    a = self.elems
    b = other.elems
    return rec([a[i] + b[i] for i in xrange(len(a))], self.n)

  def __sub__(self, other):
    assert self.n == other.n
    a = self.elems
    b = other.elems
    return rec([a[i] - b[i] for i in xrange(len(a))], self.n)

  def __mul__(self, other):
    if (not hasattr(other, "elems")):
      if (not isinstance(other, (list, tuple))):
        return rec([x * other for x in self.elems], self.n)
      other = col(other)
    a = self.elems
    ar = self.n_rows()
    ac = self.n_columns()
    b = other.elems
    if (other.n_rows() != ac):
      raise RuntimeError(
        "Incompatible matrices:\n"
        "  self.n:  %s\n"
        "  other.n: %s" % (str(self.n), str(other.n)))
    bc = other.n_columns()
    if (ac == 0):
      # Roy Featherstone, Springer, New York, 2007, p. 53 footnote
      return rec((0,)*(ar*bc), (ar,bc))
    result = []
    for i in xrange(ar):
      for k in xrange(bc):
        s = 0
        for j in xrange(ac):
          s += a[i * ac + j] * b[j * bc + k]
        result.append(s)
    if (ar == bc):
      return sqr(result)
    return rec(result, (ar, bc))

  def __rmul__(self, other):
    "scalar * matrix"
    if (isinstance(other, rec)): # work around odd Python 2.2 feature
      return other.__mul__(self)
    return self * other

  def transpose_multiply(self, other=None):
    a = self.elems
    ar = self.n_rows()
    ac = self.n_columns()
    if (other is None):
      result = [0] * (ac * ac)
      jac = 0
      for j in xrange(ar):
        ik = 0
        for i in xrange(ac):
          for k in xrange(ac):
            result[ik] += a[jac + i] * a[jac + k]
            ik += 1
        jac += ac
      return sqr(result)
    b = other.elems
    assert other.n_rows() == ar, "Incompatible matrices."
    bc = other.n_columns()
    result = [0] * (ac * bc)
    jac = 0
    jbc = 0
    for j in xrange(ar):
      ik = 0
      for i in xrange(ac):
        for k in xrange(bc):
          result[ik] += a[jac + i] * b[jbc + k]
          ik += 1
      jac += ac
      jbc += bc
    if (ac == bc):
      return sqr(result)
    return rec(result, (ac, bc))

  def __div__(self, other):
    return rec([e/other for e in self.elems], self.n)

  def __truediv__(self, other):
    return rec([e/other for e in self.elems], self.n)

  def __floordiv__(self, other):
    return rec([e//other for e in self.elems], self.n)

  def __mod__(self, other):
    return rec([ e % other for e in self.elems], self.n)

  def __call__(self, ir, ic):
    return self.elems[ir * self.n_columns() + ic]

  def __len__(self):
    return len(self.elems)

  def __getitem__(self, i):
    return self.elems[i]

  def as_float(self):
    return rec([float(e) for e in self.elems], self.n)

  def as_int(self, rounding=True):
    if rounding:
      return rec([int(round(e)) for e in self.elems], self.n)
    else:
      return rec([int(e) for e in self.elems], self.n)

  def each_abs(self):
    return rec([abs(e) for e in self.elems], self.n)

  def min(self):
    result = None
    for e in self.elems:
      if (result is None or result > e):
        result = e
    return result

  def max(self):
    result = None
    for e in self.elems:
      if (result is None or result < e):
        result = e
    return result

  def min_index(self):
    result = None
    for i in xrange(len(self.elems)):
      if (result is None or self.elems[result] > self.elems[i]):
        result = i
    return result

  def max_index(self):
    result = None
    for i in xrange(len(self.elems)):
      if (result is None or self.elems[result] < self.elems[i]):
        result = i
    return result

  def sum(self):
    result = 0
    for e in self.elems:
      result += e
    return result

  def product(self):
    result = 1
    for e in self.elems:
      result *= e
    return result

  def trace(self):
    assert self.n_rows() == self.n_columns()
    n = self.n_rows()
    result = 0
    for i in xrange(n):
      result += self.elems[i*n+i]
    return result

  def norm_sq(self):
    result = 0
    for e in self.elems:
      result += e*e
    return result

  def round(self, digits):
    return rec([ round(x, digits) for x in self.elems ], self.n)

  def __abs__(self):
    assert self.n_rows() == 1 or self.n_columns() == 1
    return math.sqrt(self.norm_sq())

  length_sq = norm_sq # for compatibility with scitbx/vec3.h
  length = __abs__

  def normalize(self):
    return self / abs(self)

  def dot(self, other=None):
    result = 0
    a = self.elems
    if (other is None):
      for i in xrange(len(a)):
        v = a[i]
        result += v * v
    else:
      assert len(self.elems) == len(other.elems)
      b = other.elems
      for i in xrange(len(a)):
        result += a[i] * b[i]
    return result

  def cross(self, other):
    assert self.n in ((3,1), (1,3))
    assert self.n == other.n
    a = self.elems
    b = other.elems
    return rec((
      a[1] * b[2] - b[1] * a[2],
      a[2] * b[0] - b[2] * a[0],
      a[0] * b[1] - b[0] * a[1]), self.n)

  def is_r3_rotation_matrix_rms(self):
    if (self.n != (3,3)): raise RuntimeError("Not a 3x3 matrix.")
    rtr = self.transpose_multiply()
    return (rtr - identity(n=3)).norm_sq()**0.5

  def is_r3_rotation_matrix(self, rms_tolerance=1e-8):
    return self.is_r3_rotation_matrix_rms() < rms_tolerance

  def unit_quaternion_as_r3_rotation_matrix(self):
    assert self.n in [(1,4), (4,1)]
    q0,q1,q2,q3 = self.elems
    return sqr((
      2*(q0*q0+q1*q1)-1, 2*(q1*q2-q0*q3),   2*(q1*q3+q0*q2),
      2*(q1*q2+q0*q3),   2*(q0*q0+q2*q2)-1, 2*(q2*q3-q0*q1),
      2*(q1*q3-q0*q2),   2*(q2*q3+q0*q1),   2*(q0*q0+q3*q3)-1))

  def r3_rotation_matrix_as_unit_quaternion(self):
    # Based on work by:
    #   Shepperd (1978), J. Guidance and Control, 1, 223-224.
    #   Sam Buss, http://math.ucsd.edu/~sbuss/MathCG
    #   Robert Hanson, jmol/Jmol/src/org/jmol/util/Quaternion.java
    if (self.n != (3,3)): raise RuntimeError("Not a 3x3 matrix.")
    m00,m01,m02,m10,m11,m12,m20,m21,m22 = self.elems
    trace = m00 + m11 + m22
    if (trace >= 0.5):
      w = (1 + trace)**0.5
      d = w + w
      w *= 0.5
      x = (m21 - m12) / d
      y = (m02 - m20) / d
      z = (m10 - m01) / d
    else:
      if (m00 > m11):
        if (m00 > m22): mx = 0
        else:           mx = 2
      elif (m11 > m22): mx = 1
      else:             mx = 2
      invalid_cutoff = 0.8 # not critical; true value is closer to 0.83
      invalid_message = "Not a r3_rotation matrix."
      if (mx == 0):
        x_sq = 1 + m00 - m11 - m22
        if (x_sq < invalid_cutoff): raise RuntimeError(invalid_message)
        x = x_sq**0.5
        d = x + x
        x *= 0.5
        w = (m21 - m12) / d
        y = (m10 + m01) / d
        z = (m20 + m02) / d
      elif (mx == 1):
        y_sq = 1 + m11 - m00 - m22
        if (y_sq < invalid_cutoff): raise RuntimeError(invalid_message)
        y = y_sq**0.5
        d = y + y
        y *= 0.5
        w = (m02 - m20) / d
        x = (m10 + m01) / d
        z = (m21 + m12) / d
      else:
        z_sq = 1 + m22 - m00 - m11
        if (z_sq < invalid_cutoff): raise RuntimeError(invalid_message)
        z = z_sq**0.5
        d = z + z
        z *= 0.5
        w = (m10 - m01) / d
        x = (m20 + m02) / d
        y = (m21 + m12) / d
    return col((w, x, y, z))

  def unit_quaternion_product(self, other):
    assert self.n in [(1,4), (4,1)]
    assert other.n in [(1,4), (4,1)]
    q0,q1,q2,q3 = self.elems
    o0,o1,o2,o3 = other.elems
    return col((
      q0*o0 - q1*o1 - q2*o2 - q3*o3,
      q0*o1 + q1*o0 + q2*o3 - q3*o2,
      q0*o2 - q1*o3 + q2*o0 + q3*o1,
      q0*o3 + q1*o2 - q2*o1 + q3*o0))

  def axis_and_angle_as_unit_quaternion(self, angle, deg=False):
    assert self.n in ((3,1), (1,3))
    if (deg): angle *= math.pi/180
    h = angle * 0.5
    c, s = math.cos(h), math.sin(h)
    u,v,w = self.normalize().elems
    return col((c, u*s, v*s, w*s))

  def axis_and_angle_as_r3_rotation_matrix(self, angle, deg=False):
    uq = self.axis_and_angle_as_unit_quaternion(angle=angle, deg=deg)
    return uq.unit_quaternion_as_r3_rotation_matrix()

  def rt_for_rotation_around_axis_through(self, point, angle, deg=False):
    assert self.n in ((3,1), (1,3))
    assert point.n in ((3,1), (1,3))
    r = (point - self).axis_and_angle_as_r3_rotation_matrix(
      angle=angle, deg=deg)
    return rt((r, self-r*self))

  def ortho(self):
    assert self.n in ((3,1), (1,3))
    x, y, z = self.elems
    a, b, c = abs(x), abs(y), abs(z)
    if c <= a and c <= b:
      return col((-y, x, 0))
    if b <= a and b <= c:
      return col((-z, 0, x))
    return col((0, -z, y))

  def rotate_around_origin(self, axis, angle, deg=False):
    assert self.n in ((3,1), (1,3))
    assert axis.n == self.n
    if deg: angle *= math.pi/180
    n = axis.normalize()
    x = self
    c, s = math.cos(angle), math.sin(angle)
    return x*c + n*n.dot(x)*(1-c) + n.cross(x)*s

  def rotate(self, axis, angle, deg=False):
    import warnings
    warnings.warn(
      message=
        "The .rotate() method has been renamed to .rotate_around_origin()"
        " for clarity. Please update the code calling this method.",
      category=DeprecationWarning,
      stacklevel=2)
    return self.rotate_around_origin(axis=axis, angle=angle, deg=deg)

  def vector_to_001_rotation(self,
        sin_angle_is_zero_threshold=1.e-10,
        is_normal_vector_threshold=1.e-10):
    assert self.n in ((3,1), (1,3))
    x,y,c = self.elems
    xxyy = x*x + y*y
    if (abs(xxyy + c*c - 1) > is_normal_vector_threshold):
      raise RuntimeError("self is not a normal vector.")
    s = (xxyy)**0.5
    if (s < sin_angle_is_zero_threshold):
      if (c > 0):
        return sqr((1,0,0,0,1,0,0,0,1))
      return sqr((1,0,0,0,-1,0,0,0,-1))
    us = y
    vs = -x
    u = us / s
    v = vs / s
    oc = 1-c
    return sqr((c + u*u*oc, u*v*oc, vs, u*v*oc, c + v*v*oc, -us, -vs, us, c))

  def outer_product(self, other=None):
    if (other is None): other = self
    assert self.n[0] == 1 or self.n[1] == 1
    assert other.n[0] == 1 or other.n[1] == 1
    result = []
    for a in self.elems:
      for b in other.elems:
        result.append(a*b)
    return rec(result, (len(self.elems), len(other.elems)))

  def cos_angle(self, other, value_if_undefined=None):
    self_norm_sq = self.norm_sq()
    if (self_norm_sq == 0): return value_if_undefined
    other_norm_sq = other.norm_sq()
    if (other_norm_sq == 0): return value_if_undefined
    d = self_norm_sq * other_norm_sq
    if (d == 0): return value_if_undefined
    return self.dot(other) / math.sqrt(d)

  def angle(self, other, value_if_undefined=None, deg=False):
    cos_angle = self.cos_angle(other=other)
    if (cos_angle is None): return value_if_undefined
    result = math.acos(max(-1,min(1,cos_angle)))
    if (deg): result *= 180/math.pi
    return result

  def accute_angle(self, other, value_if_undefined=None, deg=False):
    cos_angle = self.cos_angle(other=other)
    if (cos_angle is None): return value_if_undefined
    if (cos_angle < 0): cos_angle *= -1
    result = math.acos(min(1,cos_angle))
    if (deg): result *= 180/math.pi
    return result

  def is_square(self):
    return self.n[0] == self.n[1]

  def determinant(self):
    assert self.is_square()
    m = self.elems
    n = self.n[0]
    if (n == 1):
      return m[0]
    if (n == 2):
      return m[0]*m[3] - m[1]*m[2]
    if (n == 3):
      return   m[0] * (m[4] * m[8] - m[5] * m[7]) \
             - m[1] * (m[3] * m[8] - m[5] * m[6]) \
             + m[2] * (m[3] * m[7] - m[4] * m[6])
    if (flex is not None):
      m = flex.double(m)
      m.resize(flex.grid(self.n))
      return m.matrix_determinant_via_lu()
    return determinant_via_lu(m=self)

  def co_factor_matrix_transposed(self):
    n = self.n
    if (n == (0,0)):
      return rec(elems=(), n=n)
    if (n == (1,1)):
      return rec(elems=(1,), n=n)
    m = self.elems
    if (n == (2,2)):
      return rec(elems=(m[3], -m[1], -m[2], m[0]), n=n)
    if (n == (3,3)):
      return rec(elems=(
         m[4] * m[8] - m[5] * m[7],
        -m[1] * m[8] + m[2] * m[7],
         m[1] * m[5] - m[2] * m[4],
        -m[3] * m[8] + m[5] * m[6],
         m[0] * m[8] - m[2] * m[6],
        -m[0] * m[5] + m[2] * m[3],
         m[3] * m[7] - m[4] * m[6],
        -m[0] * m[7] + m[1] * m[6],
         m[0] * m[4] - m[1] * m[3]), n=n)
    assert self.is_square()
    raise RuntimeError("Not implemented.")

  def inverse(self):
    assert self.is_square()
    n = self.n
    if (n[0] < 4):
      determinant = self.determinant()
      assert determinant != 0
      return self.co_factor_matrix_transposed() / determinant
    if (flex is not None):
      m = flex.double(self.elems)
      m.resize(flex.grid(n))
      m.matrix_inversion_in_place()
      return rec(elems=m, n=n)
    if (numpy is not None):
      m = numpy.asarray(self.elems)
      m.shape = n
      m = numpy.ravel(numpy.linalg.inv(m))
      return rec(elems=m, n=n)
    return inverse_via_lu(m=self)

  def transpose(self):
    elems = []
    for j in xrange(self.n_columns()):
      for i in xrange(self.n_rows()):
        elems.append(self(i,j))
    return rec(elems, (self.n_columns(), self.n_rows()))

  def _mathematica_or_matlab_form(self,
        outer_open, outer_close,
        inner_open, inner_close, inner_close_follow,
        label,
        one_row_per_line,
        format,
        prefix):
    nr = self.n_rows()
    nc = self.n_columns()
    s = prefix
    indent = prefix
    if (label):
      s += label + "="
      indent += " " * (len(label) + 1)
    s += outer_open
    if (nc != 0):
      for ir in xrange(nr):
        s += inner_open
        for ic in xrange(nc):
          if (format is None):
            s += str(self(ir, ic))
          else:
            s += format % self(ir, ic)
          if (ic+1 != nc): s += ", "
          elif (ir+1 != nr or len(inner_open) != 0): s += inner_close
        if (ir+1 != nr):
          s += inner_close_follow
          if (one_row_per_line):
            s += "\n"
            s += indent
          s += " "
    return s + outer_close

  def mathematica_form(self,
        label="",
        one_row_per_line=False,
        format=None,
        prefix="",
        matrix_form=False):
    result = self._mathematica_or_matlab_form(
      outer_open="{", outer_close="}",
      inner_open="{", inner_close="}", inner_close_follow=",",
      label=label,
      one_row_per_line=one_row_per_line,
      format=format,
      prefix=prefix)
    if matrix_form: result += "//MatrixForm"
    result = result.replace('e', '*^')
    return result

  def matlab_form(self,
        label="",
        one_row_per_line=False,
        format=None,
        prefix=""):
    return self._mathematica_or_matlab_form(
      outer_open="[", outer_close="]",
      inner_open="", inner_close=";", inner_close_follow="",
      label=label,
      one_row_per_line=one_row_per_line,
      format=format,
      prefix=prefix)

  def __repr__(self):
    n0, n1 = self.n
    e = self.elems
    if (len(e) <= 3):
      e = str(e)
    else:
      e = "(%s, ..., %s)" % (str(e[0]), str(e[-1]))
    return "matrix.rec(elems=%s, n=(%d,%d))" % (e, n0, n1)

  def __str__(self):
    return self.mathematica_form(one_row_per_line=True)

  def as_list_of_lists(self):
    result = []
    nr,nc = self.n
    for ir in xrange(nr):
      result.append(list(self.elems[ir*nc:(ir+1)*nc]))
    return result

  def as_sym_mat3(self):
    assert self.n == (3,3)
    m = self.elems
    return (m[0],m[4],m[8],
            (m[1]+m[3])/2.,
            (m[2]+m[6])/2.,
            (m[5]+m[7])/2.)

  def as_mat3(self):
    assert self.n == (3,3)
    return self.elems

  def as_flex_double_matrix(self):
    assert flex is not None
    result = flex.double(self.elems)
    result.reshape(flex.grid(self.n))
    return result

  def as_flex_int_matrix(self):
    assert flex is not None
    result = flex.int(self.elems)
    result.reshape(flex.grid(self.n))
    return result

  def extract_block(self, stop, start=(0,0), step=(1,1)):
    assert 0 <= stop[0] <= self.n[0]
    assert 0 <= stop[1] <= self.n[1]
    i_rows = range(start[0], stop[0], step[0])
    i_colums = range(start[1], stop[1], step[1])
    result = []
    for ir in i_rows:
      for ic in i_colums:
        result.append(self(ir,ic))
    return rec(result, (len(i_rows),len(i_colums)))

  def __eq__(self, other):
    if self is other: return True
    if other is None: return False
    if issubclass(type(other), rec):
      return self.elems == other.elems
    for ir in xrange(self.n_rows()):
      for ic in xrange(self.n_columns()):
        if self(ir,ic) != other[ir,ic]: return False
    return True

  def resolve_partitions(self):
    nr,nc = self.n
    result_nr = 0
    for ir in xrange(nr):
      part_nr = 0
      for ic in xrange(nc):
        part = self(ir,ic)
        assert isinstance(part, rec)
        if (ic == 0): part_nr = part.n[0]
        else: assert part.n[0] == part_nr
      result_nr += part_nr
    result_nc = 0
    for ic in xrange(nc):
      part_nc = 0
      for ir in xrange(nr):
        part = self(ir,ic)
        if (ir == 0): part_nc = part.n[1]
        else: assert part.n[1] == part_nc
      result_nc += part_nc
    result_elems = [0] * (result_nr * result_nc)
    result_ir = 0
    for ir in xrange(nr):
      result_ic = 0
      for ic in xrange(nc):
        part = self(ir,ic)
        part_nr,part_nc = part.n
        i_part = 0
        for part_ir in xrange(part_nr):
          i_result = (result_ir + part_ir) * result_nc + result_ic
          for part_ic in xrange(part_nc):
            result_elems[i_result + part_ic] = part[i_part]
            i_part += 1
        result_ic += part_nc
      assert result_ic == result_nc
      result_ir += part_nr
    assert result_ir == result_nr
    return rec(elems=result_elems, n=(result_nr, result_nc))

class mutable_rec(rec):
  container_type = list

  def __setitem__(self, i, x):
    self.elems[i] = x

class row_mixin(object):

  def __init__(self, elems):
    super(row_mixin, self).__init__(elems, (1, len(elems)))

class row(row_mixin, rec): pass
class mutable_row(row_mixin, mutable_rec): pass

class col_mixin(object):

  def __init__(self, elems):
    super(col_mixin, self).__init__(elems, (len(elems), 1))

  def random(cls, n, a, b):
    uniform = random.uniform
    return cls([ uniform(a,b) for i in xrange(n) ])
  random = classmethod(random)

class col(col_mixin, rec): pass
class mutable_col(col_mixin, mutable_rec): pass

class sqr(rec):

  def __init__(self, elems):
    l = len(elems)
    n = int(l**(.5) + 0.5)
    assert l == n * n
    rec.__init__(self, elems, (n,n))

################################################################################
# This code has been adapted from cctbx.sf.net, file scitbx/matrix/eigensystem.h
class real_symmetric:
  def __init__(self,symmat3):
    m = symmat3
    self.relative_epsilon = 1.e-10
    self.absolute_epsilon = 0
    
    self.a = [symmat3[0],symmat3[3],symmat3[1],symmat3[4],symmat3[5],symmat3[2]]
    self.vectors_ = [0.,0.,0.,0.,0.,0.,0.,0.,0.]
    self.values_ = [0.,0.,0.];
    self.min_abs_pivot_ = self.real_symmetric_given_lower_triangle(
       self.a,
       3,
       self.vectors_,
       self.values_,
       self.relative_epsilon,
       self.absolute_epsilon);
  def real_symmetric_given_lower_triangle(self,
       a,  # size of memory pointed to by a must be n*(n+1)/2
       n,  
       eigenvectors,
       eigenvalues,
       relative_epsilon,
       absolute_epsilon):
      assert (relative_epsilon >= 0);
      assert (absolute_epsilon >= 0);
      if (n == 0): return 0;
      # The matrix that will hold the results is initially = I.

      for x in xrange(n*n):
        eigenvectors[x] = 0.0;

      for x in xrange(0,(n*n),(n+1)):
        eigenvectors[x] = 1.0;

      # Setup variables
      #std::size_t il, ilq, ilr, im, imq, imr, ind, iq;
      #std::size_t j, k, km, l, ll, lm, lq, m, mm, mq;
      #FloatType am, anorm, anrmx, cosx, cosx2, sincs, sinx, sinx2, thr, x, y;
      # Initial and final norms (anorm & anrmx).
      anorm=0.0;
      iq=0;
      for i in xrange(n):
        for j in xrange(i+1):
          if (j!=i): anorm+=a[iq]*a[iq];
          iq+=1
      
      anorm=math.sqrt(2.0*anorm);
      anrmx=relative_epsilon*anorm/n;
      if (anrmx < absolute_epsilon): anrmx = absolute_epsilon;
      if (anorm>0.0):
        # Compute threshold and initialise flag.
        thr=anorm;
        while (thr>anrmx): # Compare threshold with final norm
          thr/=n;
          ind=1;
          while (ind):
            ind=0;
            l=0;
            while (l != n-1): # Test for l beyond penultimate column
              lq=l*(l+1)/2;
              ll=l+lq;
              m=l+1;
              ilq=n*l;
              while (m != n): # Test for m beyond last column
                # Compute sin & cos.
                mq=m*(m+1)/2;
                lm=l+mq;
                if (a[lm]*a[lm]>thr*thr):
                  ind=1;
                  mm=m+mq;
                  x=0.5*(a[ll]-a[mm]);
                  denominator=math.sqrt(a[lm]*a[lm]+x*x);
                  assert (denominator != 0);
                  y=-a[lm]/denominator;
                  if (x<0.0): y=-y;
                  sinx=y/math.sqrt(2.0*(1.0+(math.sqrt(1.0-y*y))));
                  sinx2=sinx*sinx;
                  cosx=math.sqrt(1.0-sinx2);
                  cosx2=cosx*cosx;
                  sincs=sinx*cosx;
                  # Rotate l & m columns.
                  imq=n*m;
                  for i in xrange(n):
                    iq=i*(i+1)/2;
                    if (i!=l and i!=m):
                      if (i<m): im=i+mq;
                      else:     im=m+iq;
                      if (i<l): il=i+lq;
                      else:     il=l+iq;
                      x=a[il]*cosx-a[im]*sinx;
                      a[im]=a[il]*sinx+a[im]*cosx;
                      a[il]=x;
                    
                    ilr=ilq+i;
                    imr=imq+i;
                    x = (eigenvectors[ilr]*cosx) \
                      - (eigenvectors[imr]*sinx);
                    eigenvectors[imr] = (eigenvectors[ilr]*sinx) \
                                        + (eigenvectors[imr]*cosx);
                    eigenvectors[ilr] = x;
                  
                  x=2.0*a[lm]*sincs;
                  y=a[ll]*cosx2+a[mm]*sinx2-x;
                  x=a[ll]*sinx2+a[mm]*cosx2+x;
                  a[lm]=(a[ll]-a[mm])*sincs+a[lm]*(cosx2-sinx2);
                  a[ll]=y;
                  a[mm]=x;
                
                m+=1;
              
              l+=1;
            
          
        
      
      # Sort eigenvalues & eigenvectors in order of descending eigenvalue.
      k=0;
      for i in xrange(n-1):
        im=i;
        km=k;
        am=a[k];
        l=0;
        for j in xrange(n):
          if (j>i and a[l]>am):
            im=j;
            km=l;
            am=a[l];
          
          l+=j+2;
        
        if (im!=i):
          a[km]=a[k];
          a[k]=am;
          l=n*i;
          m=n*im;
          for jj in xrange(n):
            am=eigenvectors[l];
            eigenvectors[l] = eigenvectors[m];
	    l+=1;
            eigenvectors[m] = am;
	    m+=1
          
        
        k+=i+2;
      
      # place sorted eigenvalues into the matrix_vector structure
      k = 0
      for j in xrange(n):
        eigenvalues[j]=a[k];
        k+=j+2;
      
      return anrmx;

  def vectors(self):
    return matrix.sqr(self.vectors_)
  def values(self):
    return matrix.col(self.values_)


class module: pass

eigensystem = module()
eigensystem.real_symmetric = real_symmetric

matrix = module()
matrix.sqr = sqr
matrix.col = col
matrix.rec = rec

######################################################################
#This code is adapted from cctbx.sf.net, file scitbx/math/superpose.py
def kabsch_rotation(reference_sites, other_sites):
  """
Kabsch, W. (1976). Acta Cryst. A32, 922-923.
A solution for the best rotation to relate two sets of vectors

Based on a prototype by Erik McKee and Reetal K. Pai.

  """
  assert len(reference_sites) == len(other_sites)
  sts = matrix.sqr(other_sites.transpose_multiply(reference_sites))
  sym_mat3_input = (sts * sts.transpose()).as_sym_mat3()
  eigs = eigensystem.real_symmetric(sym_mat3_input)
  vals = list(eigs.values())
  vecs = list(eigs.vectors())
  a3 = list(matrix.col(vecs[:3]).cross(matrix.col(vecs[3:6])))
  a = matrix.sqr(list(vecs[:6])+a3)
  b = list(a * sts)
  for i in xrange(3):
    d = math.sqrt(math.fabs(vals[i]))
    if (d > 0):
      for j in xrange(3):
        b[i*3+j] /= d
  b3 = list(matrix.col(b[:3]).cross(matrix.col(b[3:6])))
  b = matrix.sqr(b[:6]+b3)
  return b.transpose() * a

#######################################
#This code is new for this application:
class residue:
  def __init__(self,chain,resid,atoms):
    self.C = None
    self.CA = None
    self.CB = None
    self.N = None
    for atom in atoms.atom:
      if chain != str(atom.chain): continue
      if str(resid) != str(atom.resi): continue
      if atom.name=="N":
	self.N = matrix.col((atom.coord[0],atom.coord[1],atom.coord[2]))
      if atom.name=="CA":
        self.residue_type = atom.resn
	self.CA = matrix.col((atom.coord[0],atom.coord[1],atom.coord[2]))
      if atom.name=="C":
	self.C = matrix.col((atom.coord[0],atom.coord[1],atom.coord[2]))
      if atom.name=="CB":
	self.CB = matrix.col((atom.coord[0],atom.coord[1],atom.coord[2]))
    assert self.N != None
    assert self.CA!= None
    assert self.C != None
    if self.CB==None:  #generate a CB position for Glycine
      self.CB = self.constructed_CB()
      
  def constructed_CB(self):
    #refer to the documentation for geometrical construction
    unit_N = (self.N - self.CA).normalize()
    assert abs(unit_N.length() - 1.0) < 0.0001
    unit_C = (self.C - self.CA).normalize()
    on_bisector = (unit_N + unit_C).normalize()
    unit_rotation_axis = (unit_N - unit_C).normalize()
    expected_angle_bisector_to_CB = math.acos (-1./math.sqrt(3.))
    #Use Euler-Rodrigues formula for rotation
    unit_on_CB = self.rotation_formula(on_bisector,unit_rotation_axis,
                                       expected_angle_bisector_to_CB)
    O_to_CB = 1.53 * unit_on_CB
    return self.CA + O_to_CB
    
  def rotation_formula(self,vector,axis,theta):
    return math.cos(theta)*vector + \
           math.sin(theta)*(axis.cross(vector)) + \
	   (1.-math.cos(theta))*((axis.dot(vector))*axis)
    
  def sites(self):
    diff_N = self.N - self.CA
    diff_C = self.C - self.CA
    diff_CB= self.CB- self.CA
    all = []
    for site in [diff_N,diff_C,diff_CB]:
      for coord in [0,1,2]:
        all.append(site[coord])
    return matrix.rec(all,(3,3))

class std_residue(residue):
  def __init__(self): pass

class view_matrix:
  def __init__(self,getview_output):
    #3x3 rotation matrix which transforms model to camera space
    self.rotmat = matrix.sqr(getview_output[0:9])
    #camera position in model space and relative to the origin of rotation
    self.camera_position = matrix.col(getview_output[9:12])
    #origin of rotation in model space
    self.origin_of_rotation = matrix.col(getview_output[12:15])
    #front plane distance from camera
    self.front_plane = getview_output[15]
    self.back_plane = getview_output[16]
    self.orthoscopic_flag = getview_output[17]
    
  def set_diff_to_CA(self,residue):
    self.difference_to_this_CA = self.origin_of_rotation - residue.CA
    return self


class new_set_view:
  def __init__(self,std_view,kr,residue, verbose=True):
    R_prime = (kr.inverse() * std_view.rotmat)
    
    print "delta",std_view.origin_of_rotation - residue.CA
    test = residue.CA + (kr.inverse() * std_view.difference_to_this_CA)
#		  list(residue.CA.elems)+\
    
    if verbose:
      print "set_view ( ",
      for x in R_prime.elems:  print "%10.4f,"%x,
      for x in std_view.camera_position.elems:  print "%10.4f,"%x,
      for x in residue.CA.elems:  print "%10.7f,"%x,
      for x in [std_view.front_plane,std_view.back_plane,std_view.orthoscopic_flag]:
        print "%10.4f,"%x,
      print ")"    
    
    self.result = list(R_prime.elems)+\
                  list(std_view.camera_position.elems)+\
		  list(test.elems)+\
		  [std_view.front_plane,std_view.back_plane,std_view.orthoscopic_flag]

  def view_matrix(self): return self.result

"Author: Nick Sauter"