Integração Numérica RK4 para Telemetria IoT e Controle de Baixa Latência

Análise da resposta ao degrau em sistemas LTI e integração numérica determinística em Rust para loops de controle de tempo real industrial.
Autor

Paulo Henrique Rosa

Data de Publicação

24 de junho de 2026

Português

Neste artigo, estabelecemos a formulação matemática para a resposta ao degrau de um sistema linear invariante no tempo (LTI) de segunda ordem, integrando sua dinâmica usando o método clássico de Runge-Kutta de 4ª Ordem (RK4).

A equação diferencial ordinária que descreve um sistema massa-mola-amortecedor é dada por:

\[ m \frac{d^2x}{dt^2} + c \frac{dx}{dt} + k x = F(t) \]

Convertendo para o espaço de estados, definimos \(\mathbf{y} = [x, \dot{x}]^T\):

\[ \frac{d}{dt} \begin{bmatrix} x \\ \dot{x} \end{bmatrix} = \begin{bmatrix} \dot{x} \\ -\frac{k}{m}x - \frac{c}{m}\dot{x} + \frac{1}{m}F(t) \end{bmatrix} \]

Nosso foco é implementar o algoritmo numérico em Rust garantindo Cache-Locality, utilizando arrays estáticos [f64; 2] alocados estritamente na stack.

Representação Visual (Manim)

O bloco a seguir renderiza uma abstração matemática em Python utilizando o Manim.

Manim Community v0.19.1

Implementação em Rust (Zero-Allocation)

A implementação a seguir demonstra o algoritmo RK4 com foco na localidade de cache da CPU (L1/L2), evitando a heap.

// Integrador RK4 determinístico focado em CPU Cache-Locality

type State = [f64; 2];

/// Função que descreve a dinâmica do sistema (Espaço de Estados)
fn system_dynamics(t: f64, y: &State, m: f64, c: f64, k: f64, force: f64) -> State {
    [
        y[1],
        -(k / m) * y[0] - (c / m) * y[1] + (force / m),
    ]
}

/// Passo de integração Runge-Kutta de 4ª Ordem
fn rk4_step(t: f64, y: &State, dt: f64, m: f64, c: f64, k: f64, force: f64) -> State {
    let k1 = system_dynamics(t, y, m, c, k, force);
    
    let y_k2 = [y[0] + 0.5 * dt * k1[0], y[1] + 0.5 * dt * k1[1]];
    let k2 = system_dynamics(t + 0.5 * dt, &y_k2, m, c, k, force);
    
    let y_k3 = [y[0] + 0.5 * dt * k2[0], y[1] + 0.5 * dt * k2[1]];
    let k3 = system_dynamics(t + 0.5 * dt, &y_k3, m, c, k, force);
    
    let y_k4 = [y[0] + dt * k3[0], y[1] + dt * k3[1]];
    let k4 = system_dynamics(t + dt, &y_k4, m, c, k, force);
    
    [
        y[0] + (dt / 6.0) * (k1[0] + 2.0 * k2[0] + 2.0 * k3[0] + k4[0]),
        y[1] + (dt / 6.0) * (k1[1] + 2.0 * k2[1] + 2.0 * k3[1] + k4[1]),
    ]
}

fn main() {
    let m = 1.0;
    let c = 0.5;
    let k = 2.0;
    let force = 1.0; // Degrau unitário
    
    let mut y: State = [0.0, 0.0]; // Estado inicial [posição, velocidade]
    let dt = 0.01;
    
    println!("t,x,v");
    for i in 0..1000 {
        let t = (i as f64) * dt;
        println!("{:.3},{:.6},{:.6}", t, y[0], y[1]);
        y = rk4_step(t, &y, dt, m, c, k, force);
    }
}

Como observado no código Rust, utilizamos primitivas de arrays de tamanho fixo [f64; 2] alocadas na stack. Essa abordagem garante a previsibilidade dos acessos em memória e evita a sobrecarga associada ao coletor ou ao alocador de memória na heap. O determinismo temporal e a latência de acesso à CPU tornam-se constantes, propriedades ideais para sistemas críticos de tempo real (Hard Real-Time Systems).