Pular para o conteúdo principal
Versão: 0.2.7

Conectando seu Controlador

Qualquer callable Python pode ser usado como controlador nas views 3D. Basta passar a função para o parâmetro controller=:

CartPoleView(controller=minha_funcao).run()

Assinatura esperada

controller(x: np.ndarray) -> np.ndarray | float
ArgumentoTipoDescrição
xnp.ndarray shape (n,)vetor de estado completo no instante atual
retornonp.ndarray shape (m,) ou floatação de controle (será convertida para array internamente)

O controlador é chamado a cada dt segundos na thread da UI. Para modelos pesados (> 5 ms de inferência), veja a seção Thread-safety.

dica

Quando controller=None (padrão), a lib projeta um LQR automaticamente via sim.linearize() + lqr(Q, R). O controlador customizado substitui o LQR. A perturbação interativa dos botões é adicionada depois do retorno do seu controlador.


Exemplos

Projete o LQR fora da lib e passe o ganho K como controlador:

import numpy as np
from synapsys.viz import CartPoleView
from synapsys.simulators import CartPoleSim
from synapsys.algorithms.lqr import lqr

# 1. Obter o modelo linearizado
sim = CartPoleSim(m_c=1.0, m_p=0.1, l=0.5)
sim.reset()
ss = sim.linearize(np.zeros(4), np.zeros(1))

# 2. Projetar LQR com pesos customizados
Q = np.diag([5.0, 0.1, 500.0, 50.0]) # penaliza mais o ângulo
R = 0.001 * np.eye(1) # permite forças maiores
K, _ = lqr(ss.A, ss.B, Q, R)

# 3. Passar para a view
CartPoleView(
controller=lambda x: np.clip(-K @ x, -100, 100)
).run()

Thread-safety

O controlador é executado na thread principal da UI (mesma thread do Qt). Isso significa:

  • Modelos rápidos (< 2 ms): sem problema, use diretamente.
  • Modelos médios (2–10 ms): a animação ficará um pouco lenta mas funciona.
  • Modelos lentos (> 10 ms): a UI trava. Use um thread separado com fila:
import threading
import queue
import numpy as np
from synapsys.viz import CartPoleView

# Fila para comunicação entre threads
action_queue: queue.Queue[np.ndarray] = queue.Queue(maxsize=1)
state_queue: queue.Queue[np.ndarray] = queue.Queue(maxsize=1)

def slow_model_thread():
while True:
x = state_queue.get()
u = meu_modelo_lento(x) # pode demorar
try:
action_queue.put_nowait(u)
except queue.Full:
pass

last_u = np.zeros(1)

def controller(x: np.ndarray) -> np.ndarray:
global last_u
try:
state_queue.put_nowait(x)
last_u = action_queue.get_nowait()
except (queue.Full, queue.Empty):
pass
return last_u

threading.Thread(target=slow_model_thread, daemon=True).start()
CartPoleView(controller=controller).run()

Verificando o controlador antes de rodar

Antes de passar um controlador para a view, você pode testá-lo diretamente com o simulador:

import numpy as np
from synapsys.simulators import CartPoleSim

sim = CartPoleSim()
sim.reset(x0=np.array([0, 0, 0.2, 0]))

x = sim.state
u = meu_controlador(x)
print("u =", u, "shape =", np.asarray(u).shape) # deve ser (1,)

y, info = sim.step(np.asarray(u).ravel(), dt=0.02)
print("próximo estado:", info["x"])