Predicting locus release
Instead of pulling a locus with a defined force and observing the resulting trajectory (as in our original publication), one might conceive an experiment displaying a “zero-susceptibility pull”; i.e. a strong object following a predefined path and pulling the locus with it, regardless of the force necessary to do so.
As a fictional example, consider pulling literally “by hand”, i.e. using tweezers: the pN forces exerted by the locus would do nothing to change the path taken by the tweezers, since the origin of the pulling force (your hand) overcomes these forces with ease.
In a scenario like this, pulls are defined by the taken trajectory, instead of the applied force. As such, as long as locus stays attached to the pulling object, the trajectory follows the predefined path and thus does not convey much interesting information. The relevant part of the experiment is then the release/detachment and subsequent recoil of the locus.
During the pull, the polymer gets stretched and thus starts generating a restoring force. Upon release, this force is free to act on the locus and thus pulls it back; the question is now whether the recoil follows the expected behavior, given the trajectory during the pull. While this is the case exactly if the inferred force during the recoil is zero, it might be useful for visualization to actually plot this expected recoil. This is demonstrated in this example.
[1]:
import numpy as np
from matplotlib import pyplot as plt
import rousepull
np.random.seed(84989234)
[2]:
x = np.concatenate([np.arange(100), 100-np.arange(100)], dtype=float)
x += 2*np.random.normal(size=len(x))
x -= x[0] # set equilibrium position x[0] = 0
t = np.arange(len(x))
[3]:
# Plot
plt.plot(t, x)
plt.xlabel('time [frames]')
plt.ylabel('displacement')
plt.title('Example data')
plt.show()
For this example, we imagine that the locus is pulled at constant velocity for the first 100 frames and then released. We ask whether the observed behavior after release is consistent with a free recoil under the Rouse model; in that case we would expect the polymer to exert no force on the locus after recoil.
[4]:
inf = rousepull.ForceInference(t, x)
inf.populate()
[5]:
# Plot
plt.plot(t, x, label='x(t)')
fplot = inf.fpoly[list(range(len(inf.fpoly))) + [-1]]
plt.step(inf.tf, 3*fplot, where='post', label='f(t)') # rescale force for visualization
plt.axhline(0, color='k', linestyle='--', zorder=0)
plt.legend()
plt.xlabel('time [frames]')
plt.ylabel('displacement [a.u.] // force [a.u.]')
plt.title('Example data with force inference')
plt.show()
The model infers a non-zero force acting on the locus after release, meaning we are manifestly not looking at a free Rouse recoil. Shortly after the release, the inferred force is still negative, and then transitions to positive. This can be interpreted as the locus recoiling too slowly initially (such that the polymer keeps pulling on it) and too fast towards the end, such that now actually the polymer (the parts that got extended during the pull) pulls it in the forward direction.
How would the release look for a perfect Rouse recoil?
[6]:
f_rouserecoil = inf.fpoly.copy()
f_rouserecoil[100:] = 0
x_rouserecoil = inf._generate(-f_rouserecoil)
[7]:
# Plot
plt.plot(t, x, label='x(t) (original)')
fplot = inf.fpoly[list(range(len(inf.fpoly))) + [-1]]
plt.step(inf.tf, 3*fplot, where='post', label='f(t) (original)')
plt.plot(t, x_rouserecoil, label='x(t) (perfect recoil)', linewidth=2)
fplot = f_rouserecoil[list(range(len(f_rouserecoil))) + [-1]]
plt.step(inf.tf, 3*fplot, where='post', label='f(t) (perfect recoil)', linewidth=2)
plt.axhline(0, color='k', linestyle='--', zorder=0)
plt.legend()
plt.xlabel('time [frames]')
plt.ylabel('displacement [a.u.] // force [a.u.]')
plt.title('Perfect recoil vs. observed')
plt.show()
As discussed above, the expected recoil is faster initially and slows down towards long times, quite unlike the data in this example.
The key to this application of the ForceInference class is to realize that the mapping x <--> f is one-to-one (indeed it is just a linear transform, x = M.f, with some invertible matrix M): any trajectory has a unique force profile associated and vice versa. To predict the recoil, we thus simply calculate the trajectory belonging to the force profile that matches the inferred force during the pull and then drops to zero. As expected, we obtain a trajectory that reproduces exactly
the observed one during the pull (where the forces also match), but then shows a different recoil behavior.
The methods used to convert x <--> f are ForceInference._infer() for x --> f and ForceInference._generate() for f --> x.
Keeping track of the correct signs can be a bit tricky here: while ForceInference.fpoly is the force exerted by the polymer onto the locus, ForceInference._infer() returns the force necessary to move the locus as observed; these differ by a minus sign (the former is the restoring force pulling the locus back, while the latter is the force pulling the locus forward and causing movement in the first place; in the overdamped limit these have to balance exactly).