## network.nim — MLP and ActorCritic forward pass (inference only, no autograd). import arraymancer import std/[math, random] const STATE_DIM* = 56 ACTION_DIM* = 6 var logStdFloor*: float32 = -3.0'f32 # overridden by PPOB_LOG_STD_FLOOR logStdCeiling*: float32 = 0.5'f32 # overridden by PPOB_LOG_STD_CEILING initialLogStd*: float32 = 0.0'f32 # overridden by PPOB_INITIAL_LOG_STD type MLP* = object w1*, b1*: Tensor[float32] # [hidden, input], [hidden] w2*, b2*: Tensor[float32] # [hidden, hidden], [hidden] w3*, b3*: Tensor[float32] # [output, hidden], [output] ActorCritic* = object actor*: MLP critic*: MLP logStd*: Tensor[float32] # [ACTION_DIM] — one per action dim proc initMLP*(inputDim, hiddenDim, outputDim: int): MLP = # Xavier/He-style init: scale weights by sqrt(2/fan_in) result.w1 = randomNormalTensor[float32]([hiddenDim, inputDim]) *. sqrt(2.0'f32 / inputDim.float32) result.b1 = zeros[float32](hiddenDim) result.w2 = randomNormalTensor[float32]([hiddenDim, hiddenDim]) *. sqrt(2.0'f32 / hiddenDim.float32) result.b2 = zeros[float32](hiddenDim) result.w3 = randomNormalTensor[float32]([outputDim, hiddenDim]) *. sqrt(1.0'f32 / hiddenDim.float32) result.b3 = zeros[float32](outputDim) proc initActorCritic*(): ActorCritic = result.actor = initMLP(STATE_DIM, 64, ACTION_DIM) result.critic = initMLP(STATE_DIM, 64, 1) result.logStd = newTensor[float32](ACTION_DIM).map(proc(v: float32): float32 = initialLogStd) proc forward*(mlp: MLP, x: Tensor[float32]): Tensor[float32] = ## x shape: [inputDim] (1D vector) let h1 = tanh(mlp.w1 * x + mlp.b1) let h2 = tanh(mlp.w2 * h1 + mlp.b2) result = mlp.w3 * h2 + mlp.b3 proc actorForward*(ac: ActorCritic, state: Tensor[float32]): tuple[actions: Tensor[float32], logProb: float32] = ## state: [STATE_DIM]. Returns sampled actions [ACTION_DIM] and sum log-prob. let mean = ac.actor.forward(state) # Floor logStd at logStdFloor before exp → min std ≈ exp(logStdFloor) var clampedLogStd = ac.logStd.map(proc(v: float32): float32 = clamp(v, logStdFloor, logStdCeiling)) let std = clampedLogStd.map(proc(v: float32): float32 = exp(v)) var actions = newTensor[float32](ACTION_DIM) var logP = 0.0'f32 for i in 0..