11import numpy as np
22from 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
159246def calculate_exponent (t_start , t_end , U , a , b ):
0 commit comments