"""
Created on Fri Nov 7 19:15:57 2025
@author: davide
"""
import numpy as np
from scipy import interpolate
from scipy.integrate import odeint
from scipy.optimize import curve_fit
def deltaC_einarsson15(r,Rep,dir='',IC=None,time=None,gammadot=None):
"""returns rotational dinamics from initial condition IC along time at Reynolds Rep for the spheroid with aspect ratio"""
b1,b2,b3,b4 = load_betas(dir,req=r,Rep=Rep)
nnn,ttt = Einarsson_interface(IC=IC,time=time,gammadot=gammadot,r=r,b1=b1,b2=b2,b3=b3,b4=b4)
return split_orbits_and_get_C(nnn[:,0],nnn[:,1],nnn[:,2],r)
def load_betas(folder,req,Rep):
"""Loads the graphically extrapolated coefficients from Einarsson et al., PoF 2015, Fig. 2"""
B1data = np.loadtxt(folder+'B1.txt')
B2data = np.loadtxt(folder+'B2.txt')
B3data = np.loadtxt(folder+'B3.txt')
B4data = np.loadtxt(folder+'B4.txt')
f1 = interpolate.interp1d(B1data[:,0], B1data[:,1])
f2 = interpolate.interp1d(B2data[:,0], B2data[:,1])
f3 = interpolate.interp1d(B3data[:,0], B3data[:,1])
f4 = interpolate.interp1d(B4data[:,0], B4data[:,1])
betas = np.array([f1(req),f2(req),f3(req),f4(req)])* Rep
return betas
def rotation_Einarsson(p,t,beta,gammadot,b1,b2,b3,b4):
"""Calculates the rotation rate according to Einarsson et al., PoF, 2015: eq. 39"""
Omega = np.array([[0, 0.5, 0],[-0.5, 0, 0],[ 0, 0, 0]]) * gammadot
E = np.array([[0, 0.5, 0],[0.5, 0, 0],[ 0, 0, 0]]) * gammadot
pEp=np.dot(p,np.dot(E,p))
pOEp=np.dot(p,np.dot(Omega,np.dot(E,p)))
pEEp=np.dot(p,np.dot(E,np.dot(E,p)))
b1/=gammadot;b2/=gammadot;b3/=gammadot;b4/=gammadot
return np.dot(Omega+beta*E,p)-beta*pEp*p+b1*pEp*(np.dot(E,p)-p*pEp)+b2*pEp*np.dot(Omega,p)+b3*(np.dot(Omega,np.dot(E,p))-pOEp*p)+b4*(np.dot(E,np.dot(E,p))-pEEp*p)
def Einarsson_interface(IC,time,gammadot,r,b1,b2,b3,b4):
"""Manages the integration of the Einarsson rotation rate from a given initial_value"""
beta = (r**2-1)/(r**2+1)
simtime = time
pinit=np.array([IC[0],IC[1],IC[2]])
ptheory = odeint(rotation_Einarsson,pinit,simtime,args=(beta,gammadot,b1,b2,b3,b4))
return ptheory,simtime
def jeffery_model(phi, C, r):
return C * r / np.sqrt(np.cos(phi)**2 + r**2 * np.sin(phi)**2)
def fit_C(theta,phi,r):
def model(phi, C):
return jeffery_model(phi, C, r)
try:
C_fit, cov = curve_fit(model, phi, np.tan(theta), bounds=[0, np.inf], p0=[1.0])
C_std = np.sqrt(np.diag(cov))[0] if cov.size else np.nan
return C_fit[0], C_std
except Exception:
return np.nan, np.nan
def split_orbits_and_get_C(n1,n2,n3,r):
"""Method to split orbits in semi-periods"""
if r > 1.0:
sign_change = np.diff(np.sign(n1)) != 0
else:
sign_change = np.diff(np.sign(n2)) != 0
orbit_labels = np.zeros_like(n1, dtype=int)
orbit_labels[1:] = np.cumsum(sign_change)
num_orbits = orbit_labels.max() + 1
C = []
for i in range(num_orbits):
orbit_inds = np.where(orbit_labels[:]==i)
phi = np.arctan2(n1[orbit_inds],n2[orbit_inds])
theta = np.arccos(n3[orbit_inds])
C.append(fit_C(theta,phi,r))
return np.array(C).T