Derivation of R

This section discusses how I used Python’s sympy (Symbolic Python) module to start with the first equation below (introduced in Mannige, Kundu, Whitelam, 2016) and end with the second, much simpler equation (introduced in Mannige, 2018). I.e., this section proves that \(R(\phi,\psi) = (\phi+\psi+2\pi)/(4\pi)\)

The first paper that discussed the Ramachandran number (Mannige, Kundu, Whitelam, 2016) reported an estimation of the \(R\) number as such (the numerator and denominator are simple summands, not matrices): image.png

Here, [\(x\)] rounds \(x\) to the closest integer value, \(\sigma\) is a scaling factor (the bigger the more accurate the estimate for \(R\)) and \(\lambda\) is the expeccted range of an angle (i.e., \(\lambda = \phi_{max} - \phi_{min} = \psi_{max} - \psi_{min}\)).

The first step to simplifying the equation is to recognize that, for each implementation of [\(x\)], we effectively introduce an if then else evaluation (if floor(\(x\)) == round(\(x\)), then return floor(\(x\)) else return ceil(\(x\))), which is easier treated as int(round(\(x,0\))). However both types of expressions are not amenable to treatment in SymPy (the round function is not interpretable by SymPy, when applied to a symbolic python symbol \(x\)). When looking at the equation above, it is worth recognizing that there are only 7 unique expressions to which the [\(x\)] function is applied (when going from the numerator to the denominator and reading from left to right, the first expression is \((\phi-\psi+\lambda)\sigma/\sqrt(2)\) and the last expression is \((\phi_{min}+\psi_{min}+\lambda)\sigma/\sqrt(2)\)). Therefore, we can discretely model the equation above by treating each [\(x\)] function as either ceil(\(x\)) or floor(\(x\)), leading to \(2^7=128\) unique equations.

Feeding each of those 128 equations into the SymPy, and applying a limit of \(\sigma \to \infty\) shows that every one of the 128 equations simplify to the much simpler equation (reported in Mannige, 2018):

image-2.png

This is if we are dealing with radians, otherwise, \(\pi\) converts to 180 degrees.

Here is how we can prove this relationship, by putting \(\sigma\to\infty\) limits on each of the combinatorially generated equations:

[1]:
import sympy
import numpy as np
import itertools
import random
sympy.init_printing()

# Here, we are generating a all possible binary series of length N.
def generate_binary_sequences_itertools(N):
    """Generates all binary sequences of length N using itertools.product."""
    return [p for p in itertools.product((0,1), repeat=N)]

# A simple function that applies either floor or a ceil, based on it's mode
def ceil_or_float(v,mode=0):
    '''
    Returns either a floor or a ceiling of the inputed v,
    based on mode (0: floor, !0: ceiling)
    '''
    if mode == 0:
        return sympy.floor(v)
    else:
        return sympy.ceiling(v)

# Defining the relevant peptide backbone angles
phi,psi = sympy.symbols("phi psi")
# Sigma tunes the accuracy of R (which we will later take the limit to infinity)
sigma = sympy.symbols("sigma")

# Defining the angle domain [-180,180] or [-pi,pi]
phi_min, psi_min, phi_max, psi_max = sympy.symbols("phi_min psi_min phi_max psi_max")

# If you want the more general equation (where the ranges of
# phi and psi are free to be set by the user, then set the following
# to 'False')
set_ramachandran_boundaries = True
if set_ramachandran_boundaries:
    phi_min,psi_min = sympy.pi*-1,sympy.pi*-1
    phi_max,psi_max = sympy.pi   ,sympy.pi

# L for lambda (an angle's range, which is 2pi radians or 360 degrees)
#L = 2*sympy.pi #, or:
L = phi_max-phi_min


# Going through all possible treatments of the original ramachandran number, where the
# [] function becomes either a ceil or a float, resulting in len(generate_binary_sequences_itertools(N=7))
# number of possible equations.
ix = 0
final_ramachandran_equations = []
binary_sequences = generate_binary_sequences_itertools(N=7)
for f1,f2,f3,f4,f5,f6,f7 in binary_sequences:
    ix += 1
    print(f'Testing equation {ix} of {len(binary_sequences)}',end='\r')

    numerator  =                                         ceil_or_float((phi-psi+L)*sigma/sympy.sqrt(2),f1)
    numerator += ceil_or_float(sympy.sqrt(2)*L*sigma,f2)*ceil_or_float((phi+psi+L)*sigma/sympy.sqrt(2),f3)
    numerator -=                                         ceil_or_float((phi_min-psi_min+L)*sigma/sympy.sqrt(2),f4)
    numerator -= ceil_or_float(sympy.sqrt(2)*L*sigma,f2)*ceil_or_float((phi_min+psi_min+L)*sigma/sympy.sqrt(2),f5)

    denominator  =                                         ceil_or_float((phi_max-psi_max+L)*sigma/sympy.sqrt(2),f6)
    denominator += ceil_or_float(sympy.sqrt(2)*L*sigma,f2)*ceil_or_float((phi_max+psi_max+L)*sigma/sympy.sqrt(2),f7)
    denominator -=                                         ceil_or_float((phi_min-psi_min+L)*sigma/sympy.sqrt(2),f4)
    denominator -= ceil_or_float(sympy.sqrt(2)*L*sigma,f2)*ceil_or_float((phi_min+psi_min+L)*sigma/sympy.sqrt(2),f5)

    R = numerator/denominator
    final_ramachandran_equations.append(sympy.limit(R,sigma,sympy.oo).simplify())

print("Testing if all equations collapse to one (phi+psi+2pi)/(2pi)",end='\r')
assert set([(phi+psi+2*sympy.pi)/(4*sympy.pi)]) == set(final_ramachandran_equations)
print("\nSUCCESS!")
print("\nHere it is, the final, simple ramachandran number!")
list(final_ramachandran_equations)[0]
Testing if all equations collapse to one (phi+psi+2pi)/(2pi)
SUCCESS!

Here it is, the final, simple ramachandran number!
[1]:
$\displaystyle \frac{\phi + \psi + 2 \pi}{4 \pi}$