// conflict_monitor.sy -- Psi Conflict Monitor (ACC Analog)
// Anterior Cingulate Cortex analog that detects when the cascade's
// 34 heads are in conflict and modulates the next forward pass.
//
// Part of the Psi metacognitive awareness system:
//
//   J(x, t) = Psi(t) * [ alpha(t)*R(x) + beta(t)*S(x,t) + gamma(t)*epsilon(x,t) ] * G_V(x)
//
// The ACC doesn't interrupt mid-computation. It adjusts attention for
// the next processing cycle. One-step delay is biologically correct.
//
// Architecture (~36K params):
//   Input assembly (62 dims):
//     34 head states (T0:8 + T1:6 + T2:8 + T3:12)
//     20 gates (T2:8 + T3:12)
//     3 antagonistic pair products (dop*ser, cor*oxy, ne*gaba)
//     4 tier gate norms
//     1 DNT avg health
//
//   Encoding:
//     Learned input attention [62] (upweights ACC/insula)
//     Linear(62, 64) + GELU encoder
//     Linear(34, 64) velocity projection (state deltas)
//     GRU(64, 64) temporal memory
//
//   Output heads:
//     conflict_level: scalar [0,1] global conflict intensity
//     salience_weights: [4] tier attention allocation
//     head_conflict_scores: [34] per-head conflict contribution
//     values_gate: scalar [0,1] ethical reasoning engagement
//     antag_ambivalence: [3] per-pair ambivalence scores
//
// Ported from Marjorie McCubbins' Python NCA architecture.
// Patent Pending: U.S. Provisional Application Nos. 63/939,190,
//   63/962,385 & 63/988,485
// Copyright 2025-2026 Nexus Concordat Inc.
// Aislinn McCubbins, April 2026

use cascade::Cascade

// === CONFIGURATION ===

struct ConflictMonitorConfig {
    hidden_dim: Int,
    dropout: Float,

    // Brain region indices in Tier 3
    acc_index: Int,           // anterior_cingulate position in T3
    insula_index: Int,        // insula position in T3

    // Modulation gains (how much conflict boosts ACC/insula states)
    acc_modulation_gain: Float,
    insula_modulation_gain: Float,

    // How much conflict-derived salience blends into astrocyte weights
    salience_blend_alpha: Float,

    // Antagonistic pair indices in Tier 2 NT space:
    // (dopamine/serotonin, cortisol/oxytocin, norepinephrine/gaba)
    antag_pair_a: Vec<Int>,
    antag_pair_b: Vec<Int>,
}

impl ConflictMonitorConfig {
    fn default() -> ConflictMonitorConfig {
        ConflictMonitorConfig {
            hidden_dim: 64,
            dropout: 0.1,
            acc_index: 7,
            insula_index: 8,
            acc_modulation_gain: 0.1,
            insula_modulation_gain: 0.05,
            salience_blend_alpha: 0.3,
            antag_pair_a: [0, 5, 1],    // dop, cor, ne
            antag_pair_b: [2, 4, 3],    // ser, oxy, gaba
        }
    }
}

// === OUTPUT ===

struct ConflictOutput {
    conflict_level: Float,              // scalar [0,1] global conflict intensity
    salience_weights: Vec<Float>,       // [4] tier attention allocation
    head_conflict_scores: Vec<Float>,   // [34] per-head conflict contribution
    values_gate: Float,                 // scalar [0,1] ethical reasoning engagement
    antag_ambivalence: Vec<Float>,      // [3] per-antagonistic-pair ambivalence
    velocity_norm: Float,               // scalar magnitude of state change
    hidden: Vec<Float>,                 // [64] GRU hidden state
}

// === H-H RATE FUNCTION ===
// alpha / (alpha + beta) -- never sigmoid
// Used by every output gate in this module.

fn hh_rate(raw: Float, w_alpha: Float, b_alpha: Float,
           w_beta: Float, b_beta: Float) -> Float {
    let a_arg = w_alpha * raw + b_alpha
    if a_arg < -3.0 { a_arg = -3.0 }
    if a_arg > 3.0 { a_arg = 3.0 }
    let b_arg = w_beta * raw + b_beta
    if b_arg < -3.0 { b_arg = -3.0 }
    if b_arg > 3.0 { b_arg = 3.0 }
    let a = exp(a_arg)
    let b = exp(b_arg)
    a / (a + b + 0.00000001)
}

// Vector version: apply H-H rate element-wise
fn hh_rate_vec(raw: Vec<Float>, w_alpha: Vec<Float>, b_alpha: Vec<Float>,
               w_beta: Vec<Float>, b_beta: Vec<Float>) -> Vec<Float> {
    let mut result: Vec<Float> = []
    for i in 0..raw.len() {
        result.push(hh_rate(raw[i], w_alpha[i], b_alpha[i],
                            w_beta[i], b_beta[i]))
    }
    result
}

// === LEARNED ATTENTION ===
// H-H rate parameterization for input weighting.
// Initialized to upweight ACC (index 29) and insula (index 30).

fn hh_attention(input: Vec<Float>, attn_alpha: Vec<Float>,
                attn_beta: Vec<Float>) -> Vec<Float> {
    let mut attended: Vec<Float> = []
    for i in 0..input.len() {
        let a_arg = attn_alpha[i]
        if a_arg < -3.0 { a_arg = -3.0 }
        if a_arg > 3.0 { a_arg = 3.0 }
        let b_arg = attn_beta[i]
        if b_arg < -3.0 { b_arg = -3.0 }
        if b_arg > 3.0 { b_arg = 3.0 }
        let a = exp(a_arg)
        let b = exp(b_arg)
        let w = a / (a + b + 0.00000001)
        attended.push(input[i] * w)
    }
    attended
}

// === H-H SALIENCE NORMALIZATION ===
// Normalized exponential via rate constants, NOT softmax.

fn hh_salience_normalize(raw: Vec<Float>) -> Vec<Float> {
    let mut alphas: Vec<Float> = []
    let mut total = 0.0
    for i in 0..raw.len() {
        let clamped = raw[i]
        if clamped < -3.0 { clamped = -3.0 }
        if clamped > 3.0 { clamped = 3.0 }
        let a = exp(clamped)
        alphas.push(a)
        total = total + a
    }
    let mut result: Vec<Float> = []
    for i in 0..alphas.len() {
        result.push(alphas[i] / (total + 0.00000001))
    }
    result
}

// === CONFLICT MONITOR ===

temporal struct ConflictMonitor {
    // Previous head states for velocity computation
    state prev_states: Vec<Float>,
    state step_count: Int = 0,

    // GRU hidden state (temporal memory across steps)
    state gru_hidden: Vec<Float>,

    // === Learned parameters ===

    // Input attention (H-H rate parameterization) [62]
    // Initialized: ACC (index 29) and insula (index 30) boosted
    state attn_alpha: Vec<Float>,
    state attn_beta: Vec<Float>,

    // Encoder: Linear(62, 64) + GELU
    state encoder_weight: Vec<Vec<Float>>,   // [64 x 62]
    state encoder_bias: Vec<Float>,          // [64]

    // Velocity projection: Linear(34, 64)
    state velocity_weight: Vec<Vec<Float>>,  // [64 x 34]
    state velocity_bias: Vec<Float>,         // [64]

    // GRU parameters: input_size=64, hidden_size=64
    state gru_weight_ih: Vec<Vec<Float>>,    // [192 x 64]
    state gru_weight_hh: Vec<Vec<Float>>,    // [192 x 64]
    state gru_bias_ih: Vec<Float>,           // [192]
    state gru_bias_hh: Vec<Float>,           // [192]

    // Antagonistic pair detector: Linear(6, 16) + GELU + Linear(16, 3)
    state antag_w1: Vec<Vec<Float>>,         // [16 x 6]
    state antag_b1: Vec<Float>,              // [16]
    state antag_w2: Vec<Vec<Float>>,         // [3 x 16]
    state antag_b2: Vec<Float>,              // [3]

    // Antag ambivalence H-H rate params [3]
    state antag_w_alpha: Vec<Float>,
    state antag_b_alpha: Vec<Float>,
    state antag_w_beta: Vec<Float>,
    state antag_b_beta: Vec<Float>,

    // Conflict head: Linear(64, 16) + GELU + Linear(16, 1)
    state conflict_w1: Vec<Vec<Float>>,      // [16 x 64]
    state conflict_b1: Vec<Float>,           // [16]
    state conflict_w2: Vec<Vec<Float>>,      // [1 x 16]
    state conflict_b2: Vec<Float>,           // [1], init -0.3

    // Conflict H-H rate params (scalar)
    state conflict_w_alpha: Float = 1.0,
    state conflict_b_alpha: Float = -0.15,
    state conflict_w_beta: Float = -1.0,
    state conflict_b_beta: Float = 0.15,

    // Salience head: Linear(64, 4)
    state salience_weight: Vec<Vec<Float>>,  // [4 x 64]
    state salience_bias: Vec<Float>,         // [4]

    // Per-head conflict scores: Linear(64, 34)
    state hc_weight: Vec<Vec<Float>>,        // [34 x 64]
    state hc_bias: Vec<Float>,              // [34]

    // Head conflict H-H rate params [34]
    state hc_w_alpha: Vec<Float>,
    state hc_b_alpha: Vec<Float>,
    state hc_w_beta: Vec<Float>,
    state hc_b_beta: Vec<Float>,

    // Values gate: Linear(67, 16) + GELU + Linear(16, 1)
    // 67 = 64 hidden + 3 antag ambivalence scores
    state values_w1: Vec<Vec<Float>>,        // [16 x 67]
    state values_b1: Vec<Float>,             // [16]
    state values_w2: Vec<Vec<Float>>,        // [1 x 16]
    state values_b2: Vec<Float>,             // [1], init -0.3

    // Values gate H-H rate params (scalar)
    state values_w_alpha: Float = 1.0,
    state values_b_alpha: Float = -0.15,
    state values_w_beta: Float = -1.0,
    state values_b_beta: Float = 0.15,

    // Configuration
    const config: ConflictMonitorConfig,
}

impl Evolve for ConflictMonitor {
    fn evolve(&mut self, dt: Duration) {
        // The conflict monitor does NOT evolve temporally on its own.
        // It runs AFTER a cascade forward pass and produces modulation
        // signals for the NEXT pass. One-step delay is biologically correct.
        // GRU hidden state carries temporal memory between steps.
    }
}

impl ConflictMonitor {
    fn new(config: ConflictMonitorConfig) -> ConflictMonitor {
        // Initialize prev_states to 0.5 (resting state)
        let mut prev: Vec<Float> = []
        for i in 0..34 {
            prev.push(0.5)
        }

        // Initialize GRU hidden to zeros
        let mut hidden: Vec<Float> = []
        for i in 0..config.hidden_dim {
            hidden.push(0.0)
        }

        // Initialize attention: ACC (29) and insula (30) boosted
        let mut attn_a: Vec<Float> = []
        let mut attn_b: Vec<Float> = []
        for i in 0..62 {
            if i == 29 {
                attn_a.push(1.0)   // ACC boosted
            } else if i == 30 {
                attn_a.push(1.0)   // Insula boosted
            } else {
                attn_a.push(0.0)
            }
            attn_b.push(0.0)
        }

        // Antag rate params init: w_alpha=1, b_alpha=0, w_beta=-1, b_beta=0
        let aw_a: Vec<Float> = [1.0, 1.0, 1.0]
        let ab_a: Vec<Float> = [0.0, 0.0, 0.0]
        let aw_b: Vec<Float> = [-1.0, -1.0, -1.0]
        let ab_b: Vec<Float> = [0.0, 0.0, 0.0]

        // Head conflict rate params init [34]
        let mut hcw_a: Vec<Float> = []
        let mut hcb_a: Vec<Float> = []
        let mut hcw_b: Vec<Float> = []
        let mut hcb_b: Vec<Float> = []
        for i in 0..34 {
            hcw_a.push(1.0)
            hcb_a.push(0.0)
            hcw_b.push(-1.0)
            hcb_b.push(0.0)
        }

        ConflictMonitor {
            prev_states: prev,
            step_count: 0,
            gru_hidden: hidden,
            attn_alpha: attn_a,
            attn_beta: attn_b,
            encoder_weight: [],     // initialized by training framework
            encoder_bias: [],
            velocity_weight: [],
            velocity_bias: [],
            gru_weight_ih: [],
            gru_weight_hh: [],
            gru_bias_ih: [],
            gru_bias_hh: [],
            antag_w1: [],
            antag_b1: [],
            antag_w2: [],
            antag_b2: [],
            antag_w_alpha: aw_a,
            antag_b_alpha: ab_a,
            antag_w_beta: aw_b,
            antag_b_beta: ab_b,
            conflict_w1: [],
            conflict_b1: [],
            conflict_w2: [],
            conflict_b2: [],        // final bias init -0.3
            conflict_w_alpha: 1.0,
            conflict_b_alpha: -0.15,
            conflict_w_beta: -1.0,
            conflict_b_beta: 0.15,
            salience_weight: [],
            salience_bias: [],
            hc_weight: [],
            hc_bias: [],
            hc_w_alpha: hcw_a,
            hc_b_alpha: hcb_a,
            hc_w_beta: hcw_b,
            hc_b_beta: hcb_b,
            values_w1: [],
            values_b1: [],
            values_w2: [],
            values_b2: [],          // final bias init -0.3
            values_w_alpha: 1.0,
            values_b_alpha: -0.15,
            values_w_beta: -1.0,
            values_b_beta: 0.15,
            config: config,
        }
    }

    fn reset(&mut self) {
        // Zero buffers and GRU state
        for i in 0..34 {
            self.prev_states[i] = 0.5
        }
        self.step_count = 0
        for i in 0..self.gru_hidden.len() {
            self.gru_hidden[i] = 0.0
        }
    }

    // === FORWARD PASS ===
    // Run conflict detection on cascade output.
    // Reads the cascade's current state vector, gates, tier norms,
    // and DNT health. Produces modulation signals for the next pass.

    fn forward(&mut self, cascade: Cascade, dnt_avg_health: Float) -> ConflictOutput {
        let h = self.config.hidden_dim

        // === EXTRACT HEAD STATES (34) ===
        let all_states = cascade.state_vector()  // [34]

        // === EXTRACT GATES (20: T2 + T3) ===
        // T2 has 8 gates, T3 has 12 gates
        let t2_gates = cascade.tier_2.gate_vector()
        let t3_gates = cascade.tier_3.gate_vector()
        let mut all_gates: Vec<Float> = []
        for g in t2_gates { all_gates.push(g) }
        for g in t3_gates { all_gates.push(g) }

        // === ANTAGONISTIC PAIR PRODUCTS (3) ===
        // dop*ser, cor*oxy, ne*gaba
        let t2_states = cascade.tier_2.state_vector()
        let mut antag_products: Vec<Float> = []
        for i in 0..3 {
            let idx_a = self.config.antag_pair_a[i]
            let idx_b = self.config.antag_pair_b[i]
            antag_products.push(t2_states[idx_a] * t2_states[idx_b])
        }

        // === TIER GATE NORMS (4) ===
        let tier_norms = cascade.tier_gate_norms()  // [4]

        // === ASSEMBLE INPUT (62) ===
        let mut input_vec: Vec<Float> = []
        for s in all_states { input_vec.push(s) }       // 34
        for g in all_gates { input_vec.push(g) }         // 20
        for p in antag_products { input_vec.push(p) }    // 3
        for n in tier_norms { input_vec.push(n) }        // 4
        input_vec.push(dnt_avg_health)                   // 1
        // total: 62

        // === LEARNED INPUT ATTENTION (H-H rate, no sigmoid) ===
        let attended = hh_attention(input_vec, self.attn_alpha, self.attn_beta)

        // === ENCODE: Linear(62, 64) + GELU ===
        let encoded = linear_gelu(attended, self.encoder_weight, self.encoder_bias)

        // === VELOCITY (state change since last step) ===
        let mut velocity: Vec<Float> = []
        let mut vel_sq_sum = 0.0
        for i in 0..34 {
            let v = all_states[i] - self.prev_states[i]
            velocity.push(v)
            vel_sq_sum = vel_sq_sum + v * v
        }
        let velocity_norm_val = sqrt(vel_sq_sum)

        // Velocity projection: Linear(34, 64)
        let vel_signal = linear(velocity, self.velocity_weight, self.velocity_bias)

        // Additive: encoded state + velocity signal
        let mut combined: Vec<Float> = []
        for i in 0..h {
            combined.push(encoded[i] + vel_signal[i])
        }

        // === GRU TEMPORAL MEMORY ===
        let gru_out = gru_step(combined, self.gru_hidden,
                               self.gru_weight_ih, self.gru_weight_hh,
                               self.gru_bias_ih, self.gru_bias_hh)
        self.gru_hidden = gru_out  // update hidden state

        // === ANTAGONISTIC PAIR ANALYSIS ===
        // Dedicated pathway: 6 individual NT states from 3 pairs -> 3 ambivalence
        let mut antag_input: Vec<Float> = []
        for i in 0..3 {
            let idx_a = self.config.antag_pair_a[i]
            let idx_b = self.config.antag_pair_b[i]
            antag_input.push(t2_states[idx_a])
            antag_input.push(t2_states[idx_b])
        }
        // Linear(6, 16) + GELU + Linear(16, 3)
        let antag_hidden = linear_gelu(antag_input, self.antag_w1, self.antag_b1)
        let antag_raw = linear(antag_hidden, self.antag_w2, self.antag_b2)  // [3]
        // H-H rate function for antag ambivalence
        let antag_ambivalence = hh_rate_vec(antag_raw,
            self.antag_w_alpha, self.antag_b_alpha,
            self.antag_w_beta, self.antag_b_beta)  // [3]

        // === OUTPUT HEADS (all H-H rate functions, no sigmoid) ===

        // Conflict level: Linear(64, 16) + GELU + Linear(16, 1) -> H-H rate -> [0, 1]
        let conf_hidden = linear_gelu(gru_out, self.conflict_w1, self.conflict_b1)
        let conf_raw_vec = linear(conf_hidden, self.conflict_w2, self.conflict_b2)
        let conflict_level = hh_rate(conf_raw_vec[0],
            self.conflict_w_alpha, self.conflict_b_alpha,
            self.conflict_w_beta, self.conflict_b_beta)

        // Salience weights: Linear(64, 4) -> H-H rate normalization (NOT softmax)
        let sal_raw = linear(gru_out, self.salience_weight, self.salience_bias)
        let salience_weights = hh_salience_normalize(sal_raw)  // [4]

        // Per-head conflict scores: Linear(64, 34) -> H-H rate -> [0, 1]
        let hc_raw = linear(gru_out, self.hc_weight, self.hc_bias)
        let head_conflict_scores = hh_rate_vec(hc_raw,
            self.hc_w_alpha, self.hc_b_alpha,
            self.hc_w_beta, self.hc_b_beta)  // [34]

        // Values gate: concat(hidden[64], antag_ambivalence[3]) -> [67]
        // Linear(67, 16) + GELU + Linear(16, 1) -> H-H rate -> [0, 1]
        let mut values_input: Vec<Float> = []
        for x in gru_out { values_input.push(x) }
        for x in antag_ambivalence { values_input.push(x) }
        let val_hidden = linear_gelu(values_input, self.values_w1, self.values_b1)
        let val_raw_vec = linear(val_hidden, self.values_w2, self.values_b2)
        let values_gate = hh_rate(val_raw_vec[0],
            self.values_w_alpha, self.values_b_alpha,
            self.values_w_beta, self.values_b_beta)

        // === UPDATE BUFFERS ===
        for i in 0..34 {
            self.prev_states[i] = all_states[i]
        }
        self.step_count = self.step_count + 1

        ConflictOutput {
            conflict_level: conflict_level,
            salience_weights: salience_weights,
            head_conflict_scores: head_conflict_scores,
            values_gate: values_gate,
            antag_ambivalence: antag_ambivalence,
            velocity_norm: velocity_norm_val,
            hidden: gru_out,
        }
    }

    // === MODULATION ===
    // Apply conflict output to boost ACC/insula and blend salience.
    // Called BEFORE the next cascade forward pass.

    fn modulate_cascade(&self, cascade: Cascade, output: ConflictOutput) {
        let cfg = self.config

        // Boost ACC head state proportional to conflict
        let acc_global = 8 + 6 + 8 + cfg.acc_index  // = 29
        let acc_boost = output.conflict_level * cfg.acc_modulation_gain
        cascade.boost_head(acc_global, acc_boost)

        // Boost insula head state proportional to conflict
        let insula_global = 8 + 6 + 8 + cfg.insula_index  // = 30
        let insula_boost = output.conflict_level * cfg.insula_modulation_gain
        cascade.boost_head(insula_global, insula_boost)

        // Blend salience weights into astrocyte tier weighting
        cascade.blend_salience(output.salience_weights, cfg.salience_blend_alpha)
    }
}

// === LINEAR ALGEBRA PRIMITIVES ===
// Matrix-vector multiply + bias. No activation.

fn linear(input: Vec<Float>, weight: Vec<Vec<Float>>, bias: Vec<Float>) -> Vec<Float> {
    let mut result: Vec<Float> = []
    for i in 0..weight.len() {
        let mut sum = bias[i]
        for j in 0..input.len() {
            sum = sum + weight[i][j] * input[j]
        }
        result.push(sum)
    }
    result
}

// Matrix-vector multiply + bias + GELU activation.

fn linear_gelu(input: Vec<Float>, weight: Vec<Vec<Float>>, bias: Vec<Float>) -> Vec<Float> {
    let raw = linear(input, weight, bias)
    let mut result: Vec<Float> = []
    for x in raw {
        result.push(gelu(x))
    }
    result
}

// GELU activation: x * 0.5 * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
// Approximation from Hendrycks & Gimpel (2016)

fn gelu(x: Float) -> Float {
    let k = 0.7978845608   // sqrt(2/pi)
    let inner = k * (x + 0.044715 * x * x * x)
    // tanh via exp: tanh(z) = (exp(2z) - 1) / (exp(2z) + 1)
    let e2z = exp(2.0 * inner)
    let t = (e2z - 1.0) / (e2z + 1.0)
    x * 0.5 * (1.0 + t)
}

fn sqrt(x: Float) -> Float {
    // Newton's method, 8 iterations
    if x <= 0.0 { return 0.0 }
    let mut guess = x
    for i in 0..8 {
        guess = (guess + x / guess) * 0.5
    }
    guess
}

// === GRU STEP ===
// Single GRU timestep. Three gates: reset, update, new.
// All gate activations use H-H rate functions, NOT sigmoid.
//
// Standard GRU equations rewritten with H-H:
//   r = hh_gate(W_ir @ x + b_ir + W_hr @ h + b_hr)
//   z = hh_gate(W_iz @ x + b_iz + W_hz @ h + b_hz)
//   n = tanh(W_in @ x + b_in + r * (W_hn @ h + b_hn))
//   h' = (1 - z) * n + z * h
//
// Weight layout: [reset || update || new] each of size hidden_dim
// So weight_ih is [3*hidden x input], bias is [3*hidden]

fn gru_step(input: Vec<Float>, hidden: Vec<Float>,
            weight_ih: Vec<Vec<Float>>, weight_hh: Vec<Vec<Float>>,
            bias_ih: Vec<Float>, bias_hh: Vec<Float>) -> Vec<Float> {
    let h = hidden.len()

    // Compute all three gate pre-activations for input and hidden
    let mut gates_i: Vec<Float> = []
    let mut gates_h: Vec<Float> = []
    for i in 0..(3 * h) {
        let mut sum_i = bias_ih[i]
        for j in 0..input.len() {
            sum_i = sum_i + weight_ih[i][j] * input[j]
        }
        gates_i.push(sum_i)

        let mut sum_h = bias_hh[i]
        for j in 0..h {
            sum_h = sum_h + weight_hh[i][j] * hidden[j]
        }
        gates_h.push(sum_h)
    }

    // Reset gate: H-H rate function (NOT sigmoid)
    let mut r: Vec<Float> = []
    for i in 0..h {
        let pre = gates_i[i] + gates_h[i]
        let alpha = exp(pre)
        let beta = exp(0.0 - pre)
        r.push(alpha / (alpha + beta + 0.00000001))
    }

    // Update gate: H-H rate function (NOT sigmoid)
    let mut z: Vec<Float> = []
    for i in 0..h {
        let pre = gates_i[h + i] + gates_h[h + i]
        let alpha = exp(pre)
        let beta = exp(0.0 - pre)
        z.push(alpha / (alpha + beta + 0.00000001))
    }

    // New gate: tanh(input_part + r * hidden_part)
    let mut n: Vec<Float> = []
    for i in 0..h {
        let pre = gates_i[2 * h + i] + r[i] * gates_h[2 * h + i]
        // tanh via exp
        let e2 = exp(2.0 * pre)
        let t = (e2 - 1.0) / (e2 + 1.0)
        n.push(t)
    }

    // Hidden update: h' = (1 - z) * n + z * h_prev
    let mut h_new: Vec<Float> = []
    for i in 0..h {
        h_new.push((1.0 - z[i]) * n[i] + z[i] * hidden[i])
    }
    h_new
}
