Skip to content

Commit 7cff1f3

Browse files
author
Aaron Meyer
committed
Implement analytical gradient for gamma_estimator
1 parent fa39bc3 commit 7cff1f3

1 file changed

Lines changed: 43 additions & 5 deletions

File tree

‎lineage/states/stateCommon.py‎

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ def bern_estimator(bern_obs: np.ndarray, gammas: np.ndarray):
6767
addr = get_cython_function_address("scipy.special.cython_special", "gammaln")
6868
gammaln = CFUNCTYPE(c_double, c_double)(addr)
6969

70+
addr = get_cython_function_address("scipy.special.cython_special", "__pyx_fuse_0psi")
71+
psi = CFUNCTYPE(c_double, c_double)(addr)
72+
7073

7174
@njit
7275
def gamma_LL(logX: arr_type, gamma_obs: arr_type, time_cen: arr_type, gammas: arr_type, param_idx):
@@ -92,6 +95,43 @@ def gamma_LL(logX: arr_type, gamma_obs: arr_type, time_cen: arr_type, gammas: ar
9295
return outt
9396

9497

98+
@njit
99+
def gamma_LL_grad(logX: arr_type, gamma_obs: arr_type, time_cen: arr_type, gammas: arr_type, param_idx) -> arr_type:
100+
"""Analytical gradient of gamma_LL with respect to logX."""
101+
x = np.exp(logX)
102+
a = x[0]
103+
glnA = gammaln(a)
104+
psiA = psi(a)
105+
gobs = gamma_obs / x[param_idx]
106+
107+
grad = np.zeros_like(logX)
108+
109+
# Uncensored contribution for theta_0 (log shape):
110+
grad[0] = -a * np.dot(gammas * time_cen, np.log(gobs) - psiA)
111+
112+
# Scale parameter contributions for theta_k (log scales):
113+
for i in range(len(gamma_obs)):
114+
k = param_idx[i]
115+
if time_cen[i] == 1:
116+
grad[k] += gammas[i] * (a - gobs[i])
117+
else:
118+
gamP = gammaincc(a, gobs[i])
119+
gamP = np.maximum(gamP, 1e-35)
120+
pdf_term = np.exp(a * np.log(gobs[i]) - gobs[i] - glnA)
121+
grad[k] -= gammas[i] * (pdf_term / gamP)
122+
123+
# Censored contribution for theta_0:
124+
h = 1e-6
125+
for jj, cen in enumerate(time_cen):
126+
if cen == 0:
127+
gamP_plus = np.maximum(gammaincc(a * np.exp(h), gobs[jj]), 1e-35)
128+
gamP_minus = np.maximum(gammaincc(a * np.exp(-h), gobs[jj]), 1e-35)
129+
dlogQ_dtheta0 = (np.log(gamP_plus) - np.log(gamP_minus)) / (2 * h)
130+
grad[0] -= gammas[jj] * dlogQ_dtheta0
131+
132+
return grad
133+
134+
95135
def gamma_estimator(
96136
gamma_obs: arr_type,
97137
time_cen: arr_type,
@@ -101,10 +141,8 @@ def gamma_estimator(
101141
phase: Literal["all", "G1", "G2"],
102142
) -> arr_type:
103143
"""
104-
This is a weighted, closed-form estimator for two parameters
105-
of the Gamma distribution for estimating shared shape and separate scale parameters of several drug concentrations at once.
106-
In the phase-specific case, we have 3 linear constraints: scale1 > scale2, scale2 > scale3, scale3 > scale 4.
107-
In the non-specific case, we have only 1 constraint: scale1 > scale2 ==> A = np.array([1, 3])
144+
This is a weighted estimator for the parameters of the Gamma distribution,
145+
estimating shared shape and separate scale parameters across drug concentrations.
108146
"""
109147
arrgs = (
110148
gamma_obs,
@@ -126,7 +164,7 @@ def gamma_estimator(
126164

127165
res = minimize(
128166
gamma_LL,
129-
jac="2-point",
167+
jac=gamma_LL_grad,
130168
x0=np.log(x0),
131169
args=arrgs,
132170
bounds=bnd,

0 commit comments

Comments
 (0)