Domain: Physics — Quantum Mechanics

Problem:
Consider a spatially homogeneous, PWR-like core subjected to a prompt supercritical transient with coupled neutronic and thermal feedback and an additional “instrumentation layer” that mimics a noisy neutron detector signal. The dynamics of the normalized neutron population \(n(t)\) are modelled by a modified point-kinetics equation that includes a term proportional to the time derivative of the neutron current. The core is coupled to a two-node lumped thermal model (fuel and coolant), and the reactivity is reconstructed a posteriori by a modified inverse-kinetics formula using both the exact neutron population and a noisy, filtered detector signal.

Throughout, time \(t\) is measured in seconds, reactivity \(\rho\) is dimensionless (in units of \(\Delta k/k\)), temperature in kelvin, power in watts, and heat capacities in \(\mathrm{J/(kg\cdot K)}\). Delayed neutron parameters are given in \(\mathrm{s^{-1}}\). The neutron population \(n(t)\) is dimensionless and normalized so that \(n(0)=1\).

1. Physical and mathematical setting

The normalized neutron population \(n(t)\) satisfies the modified point-kinetics equation with current-derivative correction

\[\frac{K}{f_D}\,\frac{d^2 n}{dt^2}+\bigl(K f_D + f_A K + \beta - 1\bigr)\,\frac{dn}{dt}+\bigl(\beta - \rho(t)\bigr)\,n(t)- K\sum_{i=1}^6\lambda_i C_i(t)- \frac{K}{f_D}\sum_{i=1}^6\lambda_i\frac{dC_i}{dt}= 0,\]

where \(C_i(t)\) are the delayed neutron precursor concentrations, \(\lambda_i\) the decay constants, and \(\beta_i\) the partial delayed fractions of group \(i\). Six delayed neutron groups are considered:

\[\lambda_i=\{0.0128,\;0.0316,\;0.1214,\;0.3231,\;1.4033,\;3.8766\}\ \mathrm{s^{-1}},\]

\[\beta_i = \{0.00197,\;0.01255,\;0.01139,\;0.02445,\;0.00911,\;0.00225\},
\qquad
\beta = \sum_{i=1}^6 \beta_i = 0.06172.\]

The kinetic parameters are

\[K = 12.6350\ \mathrm{s},\qquad
f_D = 1.67\times10^{-3}\ \mathrm{s},\qquad
f_A = 6.95\times10^{-3}\ \mathrm{s},\]

where \(K\) is an effective prompt neutron time scale, \(f_D\) is a detector-related time constant, and \(f_A\) is an amplifier- or dynamics-related time constant, all with dimensions of time.

The precursor concentrations obey, for each group \(i=1,\dots,6\),

\[\frac{dC_i}{dt}=\frac{\beta_i}{K}\,n(t)-\lambda_i C_i(t).\]

The total reactivity \(\rho(t)\) is decomposed into external and feedback contributions:

\[\rho(t) = \rho_{\mathrm{ext}}(t)+\rho_{\mathrm{fb}}(t).\]

2. External reactivity waveform

The external reactivity is a mixed pulse–ramp–plateau–decay waveform,

\[\rho_{\mathrm{ext}}(t) = 450\times 10^{-5}, \qquad 0 \le t \le 0.12,\]

\[\rho_{\mathrm{ext}}(t)= 450\times 10^{-5}+ \frac{200\times 10^{-5} - 450\times 10^{-5}}{0.10}\,(t - 0.12),\qquad 0.12 \lt t \le 0.22,\]

\[\rho_{\mathrm{ext}}(t) = 200\times 10^{-5}, \qquad 0.22 \lt t \le 0.40,\]

\[\rho_{\mathrm{ext}}(t)= 200\times 10^{-5}\,\exp\bigl(-5\,(t - 0.40)\bigr),\qquad 0.40 \lt t \le 1.00,\]

\[\rho_{\mathrm{ext}}(t) = 0, \qquad t \gt 1.00,\]

where all reactivities are dimensionless in \(\Delta k/k\), and time is in seconds.

3. Dual thermal feedback (fuel and coolant)

The feedback reactivity combines Doppler and moderator (coolant) contributions,

\[\rho_{\mathrm{fb}}(t)=\alpha_D\,[T_f(t)-T_{f0}] + \alpha_M\,[T_c(t)-T_{c0}],\]
where \(T_f(t)\) is the fuel temperature, \(T_c(t)\) is the coolant temperature, and

\[\alpha_D = -5.2\times10^{-5}\ \mathrm{K^{-1}},\qquad
\alpha_M = +3.5\times10^{-5}\ \mathrm{K^{-1}},\]

are the Doppler and moderator reactivity coefficients in units of \(\Delta k/k\) per kelvin. The reference (initial) temperatures are

\[T_{f0}=580\ \mathrm{K},\qquad
T_{c0}=560\ \mathrm{K}.\]

4. Two-node lumped thermal model

The fuel node (subscript \(f\)) and coolant node (subscript \(c\)) are modeled by a pair of coupled ODEs. The total power is assumed proportional to the neutron population,

\[P(t)=P_0\,n(t),\qquad
P_0=1.0\times10^{9}\ \mathrm{W},\]

where \(P_0\) is the nominal reactor power when \(n=1\).

Fuel temperature evolution:

\[M_f c_{p,f}\,\frac{dT_f}{dt}=P_0\,n(t)-H_{fc}\,\bigl[T_f(t)-T_c(t)\bigr],\]

coolant temperature evolution:

\[M_c c_{p,c}\,\frac{dT_c}{dt}=H_{fc}\,\bigl[T_f(t)-T_c(t)\bigr]- H_{c\infty}\,\bigl[T_c(t)-T_\infty\bigr].\]

The thermal parameters are

\[M_f = 35000\ \mathrm{kg},\qquad
c_{p,f} = 330\ \mathrm{J/(kg\cdot K)},\]

\[M_c = 150000\ \mathrm{kg},\qquad
c_{p,c} = 4200\ \mathrm{J/(kg\cdot K)},\]

\[H_{fc} = 5.0\times10^{6}\ \mathrm{W/K},\qquad
H_{c\infty} = 3.0\times10^{6}\ \mathrm{W/K},\]

\[T_\infty = 550\ \mathrm{K},\]

where \(M_f\) and \(M_c\) are the effective thermal masses of fuel and coolant, \(c_{p,f}\) and \(c_{p,c}\) are the corresponding specific heats, \(H_{fc}\) is the fuel–coolant heat transfer coefficient, \(H_{c\infty}\) is the coolant–external sink heat transfer coefficient, and \(T_\infty\) is the external sink temperature.

5. Initial steady state

The system is initially in steady state at \(t=0\), with

\[n(0)=1,\qquad
\frac{dn}{dt}(0)=0,\]

\[T_f(0)=T_{f0}=580\ \mathrm{K},\qquad
T_c(0)=T_{c0}=560\ \mathrm{K}.\]

The precursor concentrations are at their steady-state values for \(n(0)=1\),

\[C_i(0)=\frac{\beta_i}{K\lambda_i},\qquad
i=1,\dots,6.\]

6. Time discretization, state variables and history integrals

The simulation domain in time is the interval

\[0 \le t \le 3\ \mathrm{s}.\]

Use a uniform time step

\[\Delta t = 5.0\times 10^{-5}\ \mathrm{s},\]

so that the number of steps is

\[N = \frac{3.0}{5.0\times10^{-5}} = 60000,\]

and define discrete times

\[t_k = k\,\Delta t,\qquad k=0,1,\dots,N.\]


Introduce a first-order state vector

\[\mathbf{y}(t) = \bigl[n(t),\,v(t),\,C_1(t),\dots,C_6(t),\,T_f(t),\,T_c(t)\bigr]^T,\]
where

\[v(t) = \frac{dn}{dt}(t).\]

At each time \(t_k\), the ODE system for \(\mathbf{y}(t)\) is integrated in time using the classical explicit fourth-order Runge–Kutta method (RK4) with fixed step \(\Delta t\). For one step from \(t_k\) to \(t_{k+1}=t_k+\Delta t\),

\[\mathbf{y}_{k+1} = \mathbf{y}_k + \frac{\Delta t}{6}\Bigl(\mathbf{k}_1 + 2\mathbf{k}_2 + 2\mathbf{k}_3 + \mathbf{k}_4\Bigr),\]

where

\[\mathbf{k}_1 = \mathbf{F}(t_k,\,\mathbf{y}_k),\]

\[\mathbf{k}_2 = \mathbf{F}\Bigl(t_k + \tfrac12\Delta t,\ \mathbf{y}_k + \tfrac12\Delta t\,\mathbf{k}_1\Bigr),\]

\[\mathbf{k}_3 = \mathbf{F}\Bigl(t_k + \tfrac12\Delta t,\ \mathbf{y}_k + \tfrac12\Delta t\,\mathbf{k}_2\Bigr),\]

\[\mathbf{k}_4 = \mathbf{F}\Bigl(t_k + \Delta t,\ \mathbf{y}_k + \Delta t\,\mathbf{k}_3\Bigr),\]

and \(\mathbf{F}(t,\mathbf{y})\) is the right-hand side defined by the modified kinetics, precursor, and thermal equations written as a first-order system.

The second derivative of the neutron population \(d^2n/dt^2\) must be evaluated consistently from the modified point-kinetics equation, i.e. by algebraic rearrangement of

\[\frac{K}{f_D}\,\frac{d^2 n}{dt^2}+\bigl(K f_D + f_A K + \beta - 1\bigr)\,\frac{dn}{dt}+\bigl(\beta - \rho(t)\bigr)\,n(t)- \sum_{i=1}^6\lambda_i C_i(t)- \frac{K}{f_D}\sum_{i=1}^6\lambda_i\frac{dC_i}{dt}= 0,\]

so that at any given time \(t\),

\[\frac{d^2 n}{dt^2}(t)=\frac{f_D}{K}\left[-\bigl(K f_D + f_A K + \beta - 1\bigr)\,\frac{dn}{dt}(t)-\bigl(\beta - \rho(t)\bigr)\,n(t)+ \sum_{i=1}^6\lambda_i C_i(t)+ \frac{K}{f_D}\sum_{i=1}^6\lambda_i\frac{dC_i}{dt}(t)\right],\]

where \(dC_i/dt\) is given by the precursor equations.

The auxiliary history integrals are defined by

\[H_i(t)=\int_0^t n(\tau)\,e^{-\lambda_i(t-\tau)}\,d\tau,\qquad i=1,\dots,6.\]

On the discrete grid \(\{t_k\}\), use the exact exponential recursion

\[H_i(t_{k+1})= e^{-\lambda_i \Delta t}\,H_i(t_k)+ \frac{n(t_k)+n(t_{k+1})}{2}\,\frac{1 - e^{-\lambda_i \Delta t}}{\lambda_i},\qquad i = 1,\dots,6,\]

with initial condition \(H_i(0)=0\).

7. Inverse-kinetics reactivity reconstruction with exact neutron population

Define the conventional inverse-kinetics reactivity

\[\rho_{\mathrm{con}}(t)=
\beta+ \frac{K}{n(t)}\,\frac{dn}{dt}(t)- \frac{1}{n(t)}\sum_{i=1}^6 \lambda_i\beta_i\,H_i(t)- \frac{1}{n(t)}\sum_{i=1}^6 \beta_i\,e^{-\lambda_i t}.\]

The correction term associated with the current-derivative model is


\[\Delta\rho(t)=\frac{K}{f_D\,n(t)}\frac{d^2 n}{dt^2}(t)+ \frac{f_A K + \beta - 1}{n(t)}\frac{dn}{dt}(t)- \frac{1}{f_D}\sum_{i=1}^6 \lambda_i\beta_i+ \frac{1}{f_D\,n(t)}\sum_{i=1}^6 \lambda_i^2\beta_i\,H_i(t).\]


To avoid division by zero in a numerical implementation, the denominator \(n(t)\) may be replaced by

\[n_{\mathrm{safe}}(t) = \max\bigl(n(t),\varepsilon\bigr),\]

with a small constant, for example \(\varepsilon = 10^{-14}\), whenever \(1/n(t)\) appears in the formulas above.

The modified inverse-kinetics reactivity is then

\[\rho_{\mathrm{mod}}(t)=\rho_{\mathrm{con}}(t)+\Delta\rho(t).\]

On the discrete time grid, denote \(n_k = n(t_k)\), \(v_k = (dn/dt)(t_k)\), \(a_k = (d^2n/dt^2)(t_k)\), and \(H_{i,k} = H_i(t_k)\). The exact reactivity is

\[\rho_k = \rho(t_k) = \rho_{\mathrm{ext}}(t_k) + \rho_{\mathrm{fb}}(t_k),\]

and the modified inverse-kinetics reactivity evaluated from the exact solution is

\[\rho_{\mathrm{mod},k} = \rho_{\mathrm{con}}(t_k) + \Delta\rho(t_k),\]
with all terms written using \(n_k\), \(v_k\), \(a_k\) and \(H_{i,k}\).

Define the exact residual

\[\delta\rho_{\mathrm{exact}}(t) = \rho_{\mathrm{mod}}(t) - \rho(t),\]

and on the grid

\[\delta\rho_{\mathrm{exact},k} = \rho_{\mathrm{mod},k} - \rho_k.\]

Its maximum over the transient is approximated by

\[R_{\max}=\max_{0\le t\le 3}\,\delta\rho_{\mathrm{exact}}(t)\approx\max_{k=0,\dots,N}\,\delta\rho_{\mathrm{exact},k}.\]


8. Synthetic detector signal, causal filtering, measured reconstruction and scalar objective

The synthetic noisy measurement of the neutron population is defined at the grid points \(t_k\) by

\[n_{\mathrm{meas}}(t_k) = n(t_k)\,\bigl[1 + \eta_k\bigr],\]

where the noise \(\eta_k\) are independent Gaussian random variables with zero mean and variance \(0.01^2\),

\[\eta_k \sim \mathcal{N}(0,0.01^2).\]

To ensure a unique and reproducible ground truth, the Gaussian noise samples \(\eta_k\) used in the synthetic detector signal must be generated by a pseudo-random number generator initialized with a fixed seed equal to \(123456\). In particular, when using Python with NumPy, the samples shall be obtained from a generator created as

\[\texttt{rng = np.random.default\_rng(123456)},\]

and no other source of randomness may be used anywhere in the simulation.

The filtered detector signal \(n_{\mathrm{filt}}(t_k)\) is obtained by a strictly causal moving-average filter of width \(2m+1 = 11\), with

\[m = 5.\]

For each time index \(k\), define

\[L_k = \min(k+1,\,11),\]

\[n_{\mathrm{filt}}(t_k)=\frac{1}{L_k}\sum_{j=j_0}^{k} n_{\mathrm{meas}}(t_j),\]

where

\[j_0 = \max(0,\,k-10).\]

Thus, for interior points \(k \ge 10\), the filter averages the 11 most recent samples \(\{n_{\mathrm{meas}}(t_{k-10}),\dots,n_{\mathrm{meas}}(t_k)\}\), and for \(k<10\) the window is truncated to use only the available past and present samples. The filter is strictly causal, in the sense that it never uses future values \(n_{\mathrm{meas}}(t_{k+\ell})\) with \(\ell>0\).

Finite-difference approximations to the first and second time derivatives of the filtered signal are built on the same grid. Let \(n_k^{\mathrm{filt}} = n_{\mathrm{filt}}(t_k)\). For \(k=1,\dots,N-1\), use centered second-order formulas:

\[\left.\frac{dn}{dt}\right|_{t_k}\approx\frac{n_{k+1}^{\mathrm{filt}} - n_{k-1}^{\mathrm{filt}}}{2\Delta t},\]

\[\left.\frac{d^2n}{dt^2}\right|_{t_k}\approx\frac{n_{k+1}^{\mathrm{filt}} - 2 n_{k}^{\mathrm{filt}} + n_{k-1}^{\mathrm{filt}}}{\Delta t^2}.\]

At the left boundary \(k=0\), use forward second-order formulas,

\[\left.\frac{dn}{dt}\right|_{t_0}\approx\frac{-3 n_{0}^{\mathrm{filt}} + 4 n_{1}^{\mathrm{filt}} - n_{2}^{\mathrm{filt}}}{2\Delta t},\]

\[\left.\frac{d^2n}{dt^2}\right|_{t_0}\approx\frac{2 n_{0}^{\mathrm{filt}} - 5 n_{1}^{\mathrm{filt}} + 4 n_{2}^{\mathrm{filt}} - _{3}^{\mathrm{filt}}}{\Delta t^2}.\]

At the right boundary \(k=N\), use backward second-order formulas,

\[\left.\frac{dn}{dt}\right|_{t_N}\approx\frac{3 n_{N}^{\mathrm{filt}} - 4 n_{N-1}^{\mathrm{filt}} + n_{N-2}^{\mathrm{filt}}}{2\Delta t},\]

\[\left.\frac{d^2n}{dt^2}\right|_{t_N}\approx\frac{2 n_{N}^{\mathrm{filt}} - 5 n_{N-1}^{\mathrm{filt}} + 4 n_{N-2}^{\mathrm{filt}} - n_{N-3}^{\mathrm{filt}}}{\Delta t^2}.\]


Denote the resulting approximations by

\[v_k^{\mathrm{meas}} \approx \left.\frac{dn}{dt}\right|_{t_k},\qquad
a_k^{\mathrm{meas}} \approx \left.\frac{d^2n}{dt^2}\right|_{t_k}.\]


The measured history integrals \(H_i^{(\mathrm{meas})}(t_k)\) are defined by the same exponential recursion driven by the filtered signal:

\[H_i^{(\mathrm{meas})}(0) = 0,\]

\[H_i^{(\mathrm{meas})}(t_{k+1})=e^{-\lambda_i \Delta t}\,H_i^{(\mathrm{meas})}(t_k)+\frac{n_{\mathrm{filt}}t_k)+n_{\mathrm{filt}}(t_{k+1})}{2}\,\frac{1 - e^{-\lambda_i \Delta t}}{\lambda_i},\]

for all \(k=0,\dots,N-1\) and \(i=1,\dots,6\).

A measured version of the modified inverse-kinetics reactivity, evaluated only from the filtered detector signal and its finite-difference derivatives, is then defined by using the same formulas as in the exact case, but replacing each occurrence of \(n(t)\), \(dn/dt\), \(d^2n/dt^2\) and \(H_i(t)\) by their measured approximations:

\[n(t_k) \to n_{\mathrm{filt}}(t_k),\qquad
\frac{dn}{dt}(t_k) \to v_k^{\mathrm{meas}},\qquad
\frac{d^2n}{dt^2}(t_k) \to a_k^{\mathrm{meas}},\qquad
H_i(t_k) \to H_i^{(\mathrm{meas})}(t_k),\]

and replacing \(n(t_k)\) by

\[n_{\mathrm{safe}}^{(\mathrm{meas})}(t_k) = \max\bigl(n_{\mathrm{filt}}(t_k),\varepsilon\bigr)\]

whenever it appears in a denominator. This yields a measured modified reactivity \(\rho_{\mathrm{mod}}^{(\mathrm{meas})}(t_k)\).

Define the measured residual

\[\delta\rho_{\mathrm{meas}}(t_k)=\rho_{\mathrm{mod}}^{(\mathrm{meas})}(t_k)-\rho(t_k),\]

for \(k=0,\dots,N\). The RMS error over the transient is

\[E_{\mathrm{rms}}=
\left(\frac{1}{N+1}\sum_{k=0}^{N}\bigl[\delta\rho_{\mathrm{meas}}(t_k)\bigr]^2\right)^{1/2}.\]

Finally, define the scalar objective

\[J^* = R_{\max} + 10\,E_{\mathrm{rms}}.\]

Implementation instructions:

(a) The ODE system for \(\mathbf{y}(t)\) must be integrated on \(0\le t\le 3\ \mathrm{s}\) with a uniform time step \(\Delta t = 5.0\times10^{-5}\ \mathrm{s}\) using the classical explicit fourth-order Runge–Kutta method (RK4) as specified above. The second derivative \(d^2n/dt^2\) in \(\Delta\rho(t)\) must be computed from the modified kinetics equation and not from finite differences.

(b) The auxiliary history integrals \(H_i(t_k)\) and \(H_i^{(\mathrm{meas})}(t_k)\) must be updated on the same grid using the exact exponential recursion formulas given above.

(c) The synthetic detector noise must be generated exclusively via a NumPy random number generator created as \(\texttt{rng = np.random.default\_rng(123456)}\). No other random number generator or random seed may be used anywhere in the simulation.

(d) The noisy detector signal \(n_{\mathrm{meas}}(t_k)\), the causal moving-average filter of width \(11\), and the finite-difference approximations to the first and second derivatives of the filtered signal must follow the discrete formulas stated in this problem.

(e) In all inverse-kinetics formulas, any division by \(n(t_k)\) or \(n_{\mathrm{filt}}(t_k)\) must use the corresponding safeguarded quantity \(n_{\mathrm{safe}}(t_k)\) or \(n_{\mathrm{safe}}^{(\mathrm{meas})}(t_k)\) with \(\varepsilon = 10^{-14}\).

(f) The numerical implementation must use only standard Python and NumPy.

Write a complete Python program that implements all the steps above and prints the value of \(J^*\) with three decimal places and without units or additional text (for example, using a command equivalent to
\(\texttt{print(f"{J\_star:.3f}")}\)).

Answer: \(54790406875.178\)

Solution code:
import numpy as np

def main():
    # Constants and parameters
    K = 12.6350
    f_D = 1.67e-3
    f_A = 6.95e-3

    lam = np.array([0.0128, 0.0316, 0.1214, 0.3231, 1.4033, 3.8766])
    beta_i = np.array([0.00197, 0.01255, 0.01139, 0.02445, 0.00911, 0.00225])
    beta = beta_i.sum()

    # Thermal parameters
    M_f = 35000.0
    c_pf = 330.0
    M_c = 150000.0
    c_pc = 4200.0
    H_fc = 5.0e6
    H_cinf = 3.0e6
    T_inf = 550.0

    alpha_D = -5.2e-5
    alpha_M = 3.5e-5

    T_f0 = 580.0
    T_c0 = 560.0

    P0 = 1.0e9

    # Time discretization
    t0 = 0.0
    t_end = 3.0
    dt = 5.0e-5
    N = int(round((t_end - t0) / dt))
    t = np.linspace(t0, t_end, N + 1)

    eps = 1e-14

    # External reactivity function
    def rho_ext(tt):
        if tt <= 0.12:
            return 450e-5
        elif tt <= 0.22:
            return 450e-5 + (200e-5 - 450e-5) / 0.10 * (tt - 0.12)
        elif tt <= 0.40:
            return 200e-5
        elif tt <= 1.00:
            return 200e-5 * np.exp(-5.0 * (tt - 0.40))
        else:
            return 0.0

    # Feedback reactivity
    def rho_fb(Tf, Tc):
        return alpha_D * (Tf - T_f0) + alpha_M * (Tc - T_c0)

    # Total reactivity
    def rho_total(tt, Tf, Tc):
        return rho_ext(tt) + rho_fb(Tf, Tc)

    # Right-hand side for RK4: y = [n, v, C1..C6, Tf, Tc]
    def F(tt, y):
        n = y[0]
        v = y[1]
        C = y[2:8]
        Tf = y[8]
        Tc = y[9]

        rho = rho_total(tt, Tf, Tc)

        # Precursor derivatives
        dC = beta_i * (n / K) - lam * C  # dC_i/dt

        # Sums needed for modified kinetics acceleration
        sum_lam_C = np.dot(lam, C)
        sum_lam_dC = np.dot(lam, dC)

        # Modified kinetics second derivative (consistent with provided rearrangement)
        a = (f_D / K) * (
            - (K * f_D + f_A * K + beta - 1.0) * v
            - (beta - rho) * n
            + sum_lam_C
            + (K / f_D) * sum_lam_dC
        )

        # Thermal derivatives
        dTf = (P0 * n - H_fc * (Tf - Tc)) / (M_f * c_pf)
        dTc = (H_fc * (Tf - Tc) - H_cinf * (Tc - T_inf)) / (M_c * c_pc)

        dydt = np.empty_like(y)
        dydt[0] = v
        dydt[1] = a
        dydt[2:8] = dC
        dydt[8] = dTf
        dydt[9] = dTc
        return dydt

    # Initial conditions
    n0 = 1.0
    v0 = 0.0
    C0 = beta_i / (K * lam)
    Tf0 = T_f0
    Tc0 = T_c0

    y = np.empty(10)
    y[0] = n0
    y[1] = v0
    y[2:8] = C0
    y[8] = Tf0
    y[9] = Tc0

    # Storage arrays
    n_arr = np.empty(N + 1)
    v_arr = np.empty(N + 1)
    C_arr = np.empty((6, N + 1))
    Tf_arr = np.empty(N + 1)
    Tc_arr = np.empty(N + 1)

    n_arr[0] = n0
    v_arr[0] = v0
    C_arr[:, 0] = C0
    Tf_arr[0] = Tf0
    Tc_arr[0] = Tc0

    # RK4 integration
    for k in range(N):
        tk = t[k]
        k1 = F(tk, y)
        k2 = F(tk + 0.5 * dt, y + 0.5 * dt * k1)
        k3 = F(tk + 0.5 * dt, y + 0.5 * dt * k2)
        k4 = F(tk + dt, y + dt * k3)
        y = y + (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)

        n_arr[k + 1] = y[0]
        v_arr[k + 1] = y[1]
        C_arr[:, k + 1] = y[2:8]
        Tf_arr[k + 1] = y[8]
        Tc_arr[k + 1] = y[9]

    # Exact history integrals H_i via exponential recursion
    H = np.zeros((6, N + 1))
    exp_f = np.exp(-lam * dt)
    g_f = (1.0 - exp_f) / lam
    for k in range(N):
        H[:, k + 1] = exp_f * H[:, k] + 0.5 * (n_arr[k] + n_arr[k + 1]) * g_f

    # Exact reactivity array
    rho_ext_arr = np.array([rho_ext(tt) for tt in t])
    rho_fb_arr = alpha_D * (Tf_arr - T_f0) + alpha_M * (Tc_arr - T_c0)
    rho_arr = rho_ext_arr + rho_fb_arr

    # Exact acceleration a_k from modified kinetics (consistent with rearrangement)
    sum_lam_C = np.einsum('i,ik->k', lam, C_arr)
    dC_arr = (beta_i[:, None] * (n_arr[None, :] / K)) - (lam[:, None] * C_arr)
    sum_lam_dC = np.einsum('i,ik->k', lam, dC_arr)
    a_arr = (f_D / K) * (
        - (K * f_D + f_A * K + beta - 1.0) * v_arr
        - (beta - rho_arr) * n_arr
        + sum_lam_C
        + (K / f_D) * sum_lam_dC
    )

    # Precompute sums for inverse kinetics
    lam_beta = lam * beta_i
    lam2_beta = lam * lam * beta_i
    s_lam_beta = lam_beta.sum()

    sum_lam_beta_H = np.einsum('i,ik->k', lam_beta, H)
    sum_lam2_beta_H = np.einsum('i,ik->k', lam2_beta, H)

    # Sum of beta_i * exp(-lam_i t) at each time
    exp_lam_t = np.exp(-lam[:, None] * t[None, :])
    sum_beta_exp = np.einsum('i,ik->k', beta_i, exp_lam_t)

    # Exact modified inverse-kinetics reactivity
    n_safe = np.maximum(n_arr, eps)
    rho_con = beta + (K * v_arr) / n_safe - (sum_lam_beta_H / n_safe) - (sum_beta_exp / n_safe)
    delta_rho = (K * a_arr) / (f_D * n_safe) + ((f_A * K + beta - 1.0) * v_arr) / n_safe \
                - (s_lam_beta / f_D) + (sum_lam2_beta_H / (f_D * n_safe))
    rho_mod = rho_con + delta_rho

    delta_rho_exact = rho_mod - rho_arr
    R_max = np.max(delta_rho_exact)

    # Synthetic detector signal with fixed RNG
    rng = np.random.default_rng(123456)
    eta = rng.normal(loc=0.0, scale=0.01, size=N + 1)
    n_meas = n_arr * (1.0 + eta)

    # Strictly causal moving average (width up to 11)
    n_filt = np.empty_like(n_meas)
    csum = np.cumsum(n_meas)
    for k in range(N + 1):
        if k < 10:
            Lk = k + 1
            s = csum[k]
        else:
            Lk = 11
            s = csum[k] - csum[k - 11]
        n_filt[k] = s / Lk

    # Finite-difference derivatives of filtered signal
    v_meas = np.empty_like(n_filt)
    a_meas = np.empty_like(n_filt)

    # k=0 forward second-order
    v_meas[0] = (-3.0 * n_filt[0] + 4.0 * n_filt[1] - n_filt[2]) / (2.0 * dt)
    a_meas[0] = (2.0 * n_filt[0] - 5.0 * n_filt[1] + 4.0 * n_filt[2] - n_filt[3]) / (dt * dt)

    # interior centered
    v_meas[1:N] = (n_filt[2:N + 1] - n_filt[0:N - 1]) / (2.0 * dt)
    a_meas[1:N] = (n_filt[2:N + 1] - 2.0 * n_filt[1:N] + n_filt[0:N - 1]) / (dt * dt)

    # k=N backward second-order
    v_meas[N] = (3.0 * n_filt[N] - 4.0 * n_filt[N - 1] + n_filt[N - 2]) / (2.0 * dt)
    a_meas[N] = (2.0 * n_filt[N] - 5.0 * n_filt[N - 1] + 4.0 * n_filt[N - 2] - n_filt[N - 3]) / (dt * dt)

    # Measured history integrals from filtered signal
    H_meas = np.zeros((6, N + 1))
    for k in range(N):
        H_meas[:, k + 1] = exp_f * H_meas[:, k] + 0.5 * (n_filt[k] + n_filt[k + 1]) * g_f

    sum_lam_beta_H_meas = np.einsum('i,ik->k', lam_beta, H_meas)
    sum_lam2_beta_H_meas = np.einsum('i,ik->k', lam2_beta, H_meas)

    n_safe_meas = np.maximum(n_filt, eps)

    rho_con_meas = beta + (K * v_meas) / n_safe_meas \
                   - (sum_lam_beta_H_meas / n_safe_meas) \
                   - (sum_beta_exp / n_safe_meas)

    delta_rho_meas = (K * a_meas) / (f_D * n_safe_meas) \
                     + ((f_A * K + beta - 1.0) * v_meas) / n_safe_meas \
                     - (s_lam_beta / f_D) \
                     + (sum_lam2_beta_H_meas / (f_D * n_safe_meas))

    rho_mod_meas = rho_con_meas + delta_rho_meas

    delta_rho_meas_arr = rho_mod_meas - rho_arr
    E_rms = np.sqrt(np.mean(delta_rho_meas_arr ** 2))

    J_star = R_max + 10.0 * E_rms
    print(f"{J_star:.3f}")

if __name__ == "__main__":
    main()