@@ -124,9 +124,14 @@ object NegativeBinomial:
124124 * @param tol
125125 * convergence tolerance for parameter 'a'
126126 * @return
127- * Named tuple with `dist`: the fitted NegativeBinomial distribution, and `converged`: whether the optimizer converged within maxIter
127+ * Named tuple with `dist`: the fitted NegativeBinomial distribution, and `converged`: whether the optimizer
128+ * converged within maxIter
128129 */
129- def mle (observations : Array [Int ], maxIter : Int = 500 , tol : Double = 1e-8 ): (dist : NegativeBinomial , converged : Boolean ) =
130+ def mle (
131+ observations : Array [Int ],
132+ maxIter : Int = 500 ,
133+ tol : Double = 1e-8
134+ ): (dist : NegativeBinomial , converged : Boolean ) =
130135 require(observations.nonEmpty, " observations must not be empty" )
131136 require(observations.forall(_ >= 0 ), " all observations must be non-negative" )
132137
@@ -209,6 +214,7 @@ object NegativeBinomial:
209214
210215 if aNew <= 0 || ! aNew.isFinite then a = a / 2.0
211216 else a = aNew
217+ end if
212218
213219 converged = math.abs(step * delta) < tol * math.abs(a)
214220 iter += 1
@@ -221,25 +227,21 @@ object NegativeBinomial:
221227
222228 /** Maximum likelihood estimation for the volume-adjusted Negative Binomial.
223229 *
224- * We observe pairs $(n_j, v_j)$ where $n_j$ is the count and $v_j$ is the volume ratio (historical volume / modeled volume).
225- * With parameters $(r, \beta)$ and $p = 1/(1+\beta v_j)$ the likelihood is
226- * $$
227- * L(r,\beta) = \prod_j \frac{\Gamma(r+n_j)}{\Gamma(r)\,\Gamma(n_j+1)} \left(\frac{\beta v_j}{1+\beta v_j}\right)^{n_j} \left(\frac{1}{1+\beta v_j}\right)^r.
228- * $$
229- * The log-likelihood is
230- * $$
231- * \ell(r,\beta) = \sum_j \big[\log\Gamma(r+n_j) - \log\Gamma(r) - \log\Gamma(n_j+1) + n_j(\log(\beta v_j) - \log(1+\beta v_j)) - r\,\log(1+\beta v_j)\big].
232- * $$
233- * Gradient components:
234- * $$\partial_\beta \ell = \sum_j \Big( \frac{n_j}{\beta(1+\beta v_j)} - \frac{r v_j}{1+\beta v_j} \Big),\quad
235- * \partial_r \ell = \sum_j \big[\psi(r+n_j) - \psi(r) - \log(1+\beta v_j)\big],$$
236- * and Hessian entries:
237- * $$\partial^2_{\beta\beta} \ell = \sum_j \Big( \frac{r v_j}{(1+\beta v_j)^2} - \frac{n_j(1+2\beta v_j)}{\beta^2(1+\beta v_j)^2} \Big),$$
238- * $$\partial^2_{rr} \ell = \sum_j \big[\psi'(r+n_j) - \psi'(r)\big],\quad \partial^2_{\beta r} \ell = -\sum_j \frac{v_j}{1+\beta v_j}.$$
230+ * We observe pairs $(n_j, v_j)$ where $n_j$ is the count and $v_j$ is the volume ratio (historical volume / modeled
231+ * volume). With parameters $(r, \beta)$ and $p = 1/(1+\beta v_j)$ the likelihood is $$ L(r,\beta) = \prod_j
232+ * \frac{\Gamma(r+n_j)}{\Gamma(r)\,\Gamma(n_j+1)} \left(\frac{\beta v_j}{1+\beta v_j}\right)^{n_j}
233+ * \left(\frac{1}{1+\beta v_j}\right)^r. $$ The log-likelihood is $$ \ell(r,\beta) = \sum_j \big[\log\Gamma(r+n_j) -
234+ * \log\Gamma(r) - \log\Gamma(n_j+1) + n_j(\log(\beta v_j) - \log(1+\beta v_j)) - r\,\log(1+\beta v_j)\big]. $$
235+ * Gradient components: $$\partial_\beta \ell = \sum_j \Big( \frac{n_j}{\beta(1+\beta v_j)} - \frac{r v_j}{1+\beta
236+ * v_j} \Big),\quad \partial_r \ell = \sum_j \big[\psi(r+n_j) - \psi(r) - \log(1+\beta v_j)\big],$$ and Hessian
237+ * entries: $$\partial^2_{\beta\beta} \ell = \sum_j \Big( \frac{r v_j}{(1+\beta v_j)^2} - \frac{n_j(1+2\beta
238+ * v_j)}{\beta^2(1+\beta v_j)^2} \Big),$$ $$\partial^2_{rr} \ell = \sum_j \big[\psi'(r+n_j) - \psi'(r)\big],\quad
239+ * \partial^2_{\beta r} \ell = -\sum_j \frac{v_j}{1+\beta v_j}.$$
239240 *
240241 * Implementation details:
241242 * - Initialize from method of moments on rates $n_j / v_j$; if underdispersed, start at a small $\beta$.
242- * - Newton updates solve the $2\times2$ system from the gradient/Hessian; a tiny ridge is added to keep the Hessian invertible.
243+ * - Newton updates solve the $2\times2$ system from the gradient/Hessian; a tiny ridge is added to keep the
244+ * Hessian invertible.
243245 * - Step halving is applied to enforce positivity of $r$ and $\beta$.
244246 *
245247 * @param observations
@@ -253,7 +255,12 @@ object NegativeBinomial:
253255 * @return
254256 * tuple of fitted `NegativeBinomial(r, beta)` and a convergence flag
255257 */
256- def volweightedMle (observations : Array [Int ], volumes : Array [Double ], maxIter : Int = 500 , tol : Double = 1e-8 ): (dist : NegativeBinomial , converged : Boolean ) =
258+ def volweightedMle (
259+ observations : Array [Int ],
260+ volumes : Array [Double ],
261+ maxIter : Int = 500 ,
262+ tol : Double = 1e-8
263+ ): (dist : NegativeBinomial , converged : Boolean ) =
257264 require(observations.nonEmpty, " observations must not be empty" )
258265 require(observations.length == volumes.length, " observations and volumes must have the same length" )
259266 require(observations.forall(_ >= 0 ), " all observations must be non-negative" )
@@ -324,8 +331,7 @@ object NegativeBinomial:
324331 val hrrAdj = hrr + ridge
325332 val det = hbbAdj * hrrAdj - hbr * hbr
326333
327- if det.isNaN || det.isInfinite || math.abs(det) < 1e-18 then
328- iter = maxIter
334+ if det.isNaN || det.isInfinite || math.abs(det) < 1e-18 then iter = maxIter
329335 else
330336 val deltaBeta = (gBeta * hrrAdj - gR * hbr) / det
331337 val deltaR = (hbbAdj * gR - hbr * gBeta) / det
@@ -343,17 +349,23 @@ object NegativeBinomial:
343349 if newBeta > 0 && newR > 0 && newBeta.isFinite && newR.isFinite then
344350 beta = newBeta
345351 r = newR
346- converged =
347- math.abs(step * deltaBeta) <= tol * math.abs(beta) &&
348- math.abs(step * deltaR) <= tol * math.abs(r)
352+ converged = math.abs(step * deltaBeta) <= tol * math.abs(beta) &&
353+ math.abs(step * deltaR) <= tol * math.abs(r)
349354 else iter = maxIter
355+ end if
350356 end if
351357
352358 iter += 1
353359 end while
354360
355361 (NegativeBinomial (r, beta), converged)
356-
357- inline def mleVolumeWeighted (observations : Array [Int ], volumes : Array [Double ], maxIter : Int = 100 , tol : Double = 1e-8 ): (dist : NegativeBinomial , converged : Boolean ) = volweightedMle(observations, volumes, maxIter, tol)
362+ end volweightedMle
363+
364+ inline def mleVolumeWeighted (
365+ observations : Array [Int ],
366+ volumes : Array [Double ],
367+ maxIter : Int = 100 ,
368+ tol : Double = 1e-8
369+ ): (dist : NegativeBinomial , converged : Boolean ) = volweightedMle(observations, volumes, maxIter, tol)
358370
359371end NegativeBinomial
0 commit comments