The basic idea of the code is to calculate the push or pull force at a vertex, given the number of "push causing molecules" and "pull causing molecules" at a polygon vertex.
The code thus mainly performs arithmetic (including the function it calls, calculate_edge_force
), and has a couple of if
-statements. The function is called many times since it is required in order to compute the derivatives, which is later used by scipy.odeint
for integration.
Here is my code, in plain Python (note that import numpy as np
has been put in somewhere higher up in the file, and that the functions are inside a class, hence there are references to this as self.<something>
):
def calculate_point_forces(self, PushMolecules, PushMolecules_iPlus1, PushMolecules_iMinus1, PullMolecules, PullMolecules_iPlus1, PullMolecules_iMinus1, point_index, num_nodes, polygon, polygon_compression, point_interpolygon_contact, push_vector, pull_vector, a1, a2):
max_push_force = self.max_push_force
max_pull_force = self.max_pull_force
tangent_factor = self.tangent_factor
rac_force_exponent = self.rac_force_exponent
rho_force_exponent = self.rho_force_exponent
avg_PushMolecules_plus1 = (PushMolecules + PushMolecules_iPlus1)*0.5
avg_PullMolecules_plus1 = (PullMolecules + PullMolecules_iPlus1)*0.5
avg_PushMolecules_minus1 = (PushMolecules + PushMolecules_iMinus1)*0.5
avg_PullMolecules_minus1 = (PullMolecules + PullMolecules_iMinus1)*0.5
if self.enable_compression_stiffening == True:
if polygon_compression < 1:
F_internal = ((1-polygon_compression)+self.cytosolic_baseline)*self.cytosolic_pressure
else:
F_internal = 0
else:
F_internal = 0
if PushMolecules > PullMolecules:
Fpush = int(not point_interpolygon_contact)*max_push_force*(1 - (1/(1 + (PushMolecules/a2)**rac_force_exponent)))*push_vector
Fpull = 0
Fe_pull = 0
elif PushMolecules < PullMolecules:
pull_magnitude = max_pull_force*(1 - (1/(1 + (PullMolecules/a1)**rho_force_exponent)))
Fpull = (1-tangent_factor)*pull_magnitude*pull_vector
Fe_pull = 0.5*tangent_factor*pull_magnitude
Fpush = 0
else:
Fpush = 0
Fpull = 0
Fe_pull = 0
Fe_pull_minus1 = 0
Fe_pull_plus1 = 0
if PushMolecules_iMinus1 < PullMolecules_iMinus1:
pull_magnitude_iMinus1 = max_pull_force*(1 - (1/(1 + (PullMolecules_iMinus1/a1)**rho_force_exponent)))
Fe_pull_minus1 = 0.5*tangent_factor*pull_magnitude_iMinus1
if PushMolecules_iPlus1 < PullMolecules_iPlus1:
pull_magnitude_iPlus1 = max_pull_force*(1 - (1/(1 + (PullMolecules_iPlus1/a1)**rho_force_exponent)))
Fe_pull_plus1 = 0.5*tangent_factor*pull_magnitude_iPlus1
Fe_pull = np.max([Fe_pull_minus1, Fe_pull_plus1, Fe_pull])
Fe_plus, Fe_plus_ = self.calculate_edge_force(
avg_PushMolecules_plus1, avg_PullMolecules_plus1, point_index,
(point_index+1) % num_nodes, polygon)
Fe_minus, Fe_minus_ = self.calculate_edge_force(
avg_PushMolecules_minus1, avg_PullMolecules_minus1, point_index,
(point_index-1) % num_nodes, polygon)
F = Fpush + Fpull + Fe_plus + Fe_minus + Fe_pull*Fe_plus_ + Fe_pull*Fe_minus_ + push_vector*F_internal
return F
When I profile this, I get that the function itself takes about 3 seconds. Out of this, 1 second is spent in calls to the calculate_edge_force
and 1 second is spent by np.max
.
If I "naively" put in float
and int
wherever I can (I don't know how to type the NumPy arrays, e.g. push_vector
or pull_vector
, so I only do it for things like PushMolecules
, max_push_force
or point_index
, which I know will be float
or int
), I actually get a very mild slowdown in speed (about 0.2 seconds)!
Clearly then, that's not the way to go about "Cythonizing" my code. What should I be doing instead?