Skip to content

Commit 7079485

Browse files
committed
Add first calculateB_hri function
1 parent fd6ecfb commit 7079485

1 file changed

Lines changed: 89 additions & 2 deletions

File tree

Bvalcalc/core/calculateB.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import numpy as np
22
from Bvalcalc.utils.dfe_helper import get_DFE_params
3+
from scipy.optimize import root_scalar
4+
from scipy.integrate import trapezoid
35

46
_params_cache: dict | None = None
57
_cache_args: tuple[str | None, bool, bool] | None = None
@@ -14,8 +16,8 @@ def get_params(
1416
Caches on (params_path, gamma_dfe, constant_dfe) and rebuilds whenever
1517
any of those three inputs change.
1618
"""
17-
global _params_cache#, _cache_args
18-
key = (params_path, gamma_dfe, constant_dfe)
19+
global _params_cache#, _cache_args # COMMENTED OUT CACHING FOR API USAGE, CAN RE-IMPLEMENT FOR CLI IF IT SLOWS IT DOWN
20+
# key = (params_path, gamma_dfe, constant_dfe)
1921
# if _cache_args != key:
2022
_params_cache = get_DFE_params(params_path, gamma_dfe, constant_dfe)
2123
# _cache_args = key
@@ -154,6 +156,91 @@ def calculateB_unlinked(unlinked_L: int, params: dict | None = None):
154156

155157
return unlinked_B
156158

159+
160+
##
161+
162+
163+
def calculateB_hri(f1,f2,u,interfering_L,h,N0,t1,t2,t3,t_constant = None):
164+
# Mutation rates for f1 and f2
165+
u1 = f1 * u # f1 mut rate
166+
u2 = f2 * u # f2 mut rate
167+
168+
# DFE ranges in terms of 2Ns
169+
a1, b1 = t1, t2 # f1: uniform 2Ns ∈ [1, 10]
170+
a2, b2 = t2, t3 # f2: uniform 2Ns ∈ [10, 100]
171+
172+
# Compute E[(2Ns)^2] for each uniform DFE: E[X^2] = (b^2 + b*a + a^2)/3
173+
E_X2_f1 = (b1**2 + b1*a1 + a1**2) / 3
174+
E_X2_f2 = (b2**2 + b2*a2 + a2**2) / 3
175+
176+
# Convert to E[s^2]: s = h * (2Ns)/(2N0) => s^2 = h^2 * X^2 / (4 * N0^2)
177+
t_sq1 = (h**2 * E_X2_f1) / (4 * N0**2)
178+
t_sq2 = (h**2 * E_X2_f2) / (4 * N0**2)
179+
180+
# Total HRI related mutation rate per site
181+
u = u1 + u2
182+
183+
# RMS selection coefficient over both DFEs
184+
t = np.sqrt((u1 * t_sq1 + u2 * t_sq2) / u)
185+
186+
kappa = 1 # Mutational bias parameter
187+
188+
# Scaling parameters
189+
gamma = 2 * N0 * t
190+
U = u * interfering_L
191+
alpha2 = 2 * N0 * U
192+
193+
# ======================== EQ4: Solve for B ========================
194+
def eq4(B):
195+
return (
196+
-np.log(B)
197+
- (0.5 * U * (1 - np.exp(-gamma * B))**3)
198+
/ (t * (1 + kappa * np.exp(-gamma * B))**3)
199+
)
200+
201+
sol = root_scalar(eq4, bracket=[1e-10, 1], method='bisect')
202+
Bval = sol.root
203+
204+
# ======================== EQ5: Vectorized double-trapz for B' ========================
205+
def eq5(B, Tmax=100.0, n_steps=2000):
206+
# precompute coefficients
207+
f1 = 1 - np.exp(-gamma * B)
208+
f2 = 1 + kappa * np.exp(-gamma * B)
209+
A = f1 / f2
210+
c = 0.5 * alpha2 / gamma * A**3
211+
d = 2 * gamma * B * (f2 / f1)
212+
213+
# grid from 0 to Tmax
214+
x = np.linspace(0, Tmax, n_steps)
215+
216+
# inner integrand g(x)
217+
gx = np.exp(c * (1 - np.exp(-d * x))**2)
218+
219+
# cumulative ∫₀ˣ g(t) dt via trapezoid rule
220+
cumI = np.concatenate((
221+
[0.0],
222+
np.cumsum((gx[:-1] + gx[1:]) * 0.5 * np.diff(x))
223+
))
224+
225+
# outer integrand and final trapezoid
226+
hx = np.exp(-B * cumI)
227+
return B * trapezoid(hx, x)
228+
229+
# ======================== RUN & PRINT ========================
230+
Bprime = eq5(Bval)
231+
232+
print(f"Total per-site u = {u:.2e}")
233+
print(f"E[s^2]_f1 = {t_sq1:.2e}")
234+
print(f"E[s^2]_f2 = {t_sq2:.2e}")
235+
print(f"RMS selection s_eff = {t:.2e}")
236+
print(f"Dip B (from eq4) = {Bval:.5f}")
237+
print(f"Dip B' (from eq5) = {Bprime:.5f}")
238+
239+
return Bprime
240+
241+
##
242+
243+
157244
## Helper functions
158245

159246
def calculate_exponent(t_start, t_end, U, a, b):

0 commit comments

Comments
 (0)