@@ -67,6 +67,9 @@ def bern_estimator(bern_obs: np.ndarray, gammas: np.ndarray):
6767addr = get_cython_function_address ("scipy.special.cython_special" , "gammaln" )
6868gammaln = 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
7275def 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+
95135def 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