DOC 7 — MATEMATIKA & FISIKA
// DOC 07 / 07

MATEMATIKA &
FISIKA

Matematika & fisika lengkap untuk robotika: transformasi homogen, quaternion, SO3/SE3, Jacobian, Lagrangian dynamics, Kalman Filter, EKF, PID, state-space LQR, optimasi, Python tools.

Linear AlgebraQuaternionKalman/EKFLQRSymPy
01
Linear Algebra

MATRIKS TRANSFORMASI HOMOGEN

Transformasi homogen adalah representasi seragam untuk translasi + rotasi dalam satu matriks 4×4. Fundamental untuk semua perhitungan pose robot.

Homogeneous Transform T ∈ SE(3): T = | R p | R ∈ SO(3): rotation matrix 3×3 | 0 1 | p ∈ ℝ³: translation vector Rotation matrices (basic): |1 0 0 | | cθ 0 sθ | | cθ -sθ 0 | Rx(θ) = |0 cθ -sθ | Ry(θ)= | 0 1 0 | Rz(θ)= | sθ cθ 0 | |0 sθ cθ | |-sθ 0 cθ | | 0 0 1 | Composition: T_AC = T_AB · T_BC Inverse: T^-1 = | R^T -R^T·p | | 0 1 |
import numpy as np

def Rx(t): c,s=np.cos(t),np.sin(t); return np.array([[1,0,0],[0,c,-s],[0,s,c]])
def Ry(t): c,s=np.cos(t),np.sin(t); return np.array([[c,0,s],[0,1,0],[-s,0,c]])
def Rz(t): c,s=np.cos(t),np.sin(t); return np.array([[c,-s,0],[s,c,0],[0,0,1]])

def T_mat(R, p):
    T = np.eye(4); T[:3,:3]=R; T[:3,3]=p; return T

def T_inv(T):
    R=T[:3,:3]; p=T[:3,3]
    Ti=np.eye(4); Ti[:3,:3]=R.T; Ti[:3,3]=-R.T@p
    return Ti

# Contoh: transformasi sensor → base frame
R_sensor = Rz(np.radians(90)) @ Rx(np.radians(-10))
p_sensor = np.array([0.15, 0, 0.30])
T_sensor = T_mat(R_sensor, p_sensor)

# Point di frame sensor → transformasi ke frame robot
p_s = np.array([1.5, 0, 0, 1])  # homogeneous
p_robot = T_sensor @ p_s
print(f"Point in robot frame: {p_robot[:3]}")
02
Rotasi

QUATERNION & ROTASI 3D

Quaternion q = w + xi + yj + zk = (w, x, y, z) w = cos(θ/2) x = sin(θ/2)·nx, y = sin(θ/2)·ny, z = sin(θ/2)·nz n̂ = (nx, ny, nz) = unit rotation axis Unit quaternion: w² + x² + y² + z² = 1 Quaternion multiplication (Hamilton product): q₁⊗q₂ = (w₁w₂ - x₁x₂ - y₁y₂ - z₁z₂, w₁x₂ + x₁w₂ + y₁z₂ - z₁y₂, w₁y₂ - x₁z₂ + y₁w₂ + z₁x₂, w₁z₂ + x₁y₂ - y₁x₂ + z₁w₂) Quaternion conjugate q* = (w, -x, -y, -z) Rotation of vector v: v' = q ⊗ (0,v) ⊗ q* Kelebihan vs Euler angles: no gimbal lock, smooth interpolation (SLERP)
import numpy as np

def quat_mult(q1, q2):
    w1,x1,y1,z1 = q1; w2,x2,y2,z2 = q2
    return np.array([
        w1*w2 - x1*x2 - y1*y2 - z1*z2,
        w1*x2 + x1*w2 + y1*z2 - z1*y2,
        w1*y2 - x1*z2 + y1*w2 + z1*x2,
        w1*z2 + x1*y2 - y1*x2 + z1*w2])

def quat_rotate(q, v):
    qv = np.array([0, *v])
    q_conj = np.array([q[0], -q[1], -q[2], -q[3]])
    return quat_mult(quat_mult(q, qv), q_conj)[1:]

def quat_from_axis_angle(axis, angle_rad):
    n = np.array(axis) / np.linalg.norm(axis)
    s = np.sin(angle_rad/2)
    return np.array([np.cos(angle_rad/2), *n*s])

def slerp(q1, q2, t):
    """Spherical linear interpolation, t in [0,1]"""
    dot = np.clip(np.dot(q1,q2), -1,1)
    if dot < 0: q2=-q2; dot=-dot
    if dot > 0.9995: return (q1+t*(q2-q1)) / np.linalg.norm(q1+t*(q2-q1))
    theta = np.arccos(dot)
    return (np.sin((1-t)*theta)*q1 + np.sin(t*theta)*q2) / np.sin(theta)

def quat_to_euler(q):
    w,x,y,z = q
    roll  = np.arctan2(2*(w*x+y*z), 1-2*(x*x+y*y))
    pitch = np.arcsin(np.clip(2*(w*y-z*x),-1,1))
    yaw   = np.arctan2(2*(w*z+x*y), 1-2*(y*y+z*z))
    return np.degrees([roll, pitch, yaw])
03
Lie Groups

SO(3) & SE(3)

Lie Groups untuk rotasi & pose: SO(3) — Special Orthogonal Group (pure rotation): R ∈ SO(3) iff R^T·R = I dan det(R) = +1 Lie algebra so(3): skew-symmetric matrices ω̂ = [ 0 -ωz ωy ] [ ωz 0 -ωx ] [-ωy ωx 0 ] Rodrigues formula (axis-angle → rotation matrix): R = I + sin(θ)·ω̂ + (1-cos(θ))·ω̂² θ = |ω|, ω̂ = ω/θ (unit axis) SE(3) — Special Euclidean Group (rotation + translation): T = [R|p; 0|1] ∈ ℝ^(4×4) Lie algebra se(3): [ω̂, v; 0, 0]
import numpy as np

def skew(w):
    """Skew-symmetric matrix dari vektor 3D"""
    return np.array([[0,-w[2],w[1]],[w[2],0,-w[0]],[-w[1],w[0],0]])

def rodrigues(omega):
    """Axis-angle vector omega → rotation matrix (Rodrigues)"""
    theta = np.linalg.norm(omega)
    if theta < 1e-8: return np.eye(3)
    k = omega / theta
    K = skew(k)
    return np.eye(3) + np.sin(theta)*K + (1-np.cos(theta))*(K@K)

def log_SO3(R):
    """Rotation matrix → axis-angle vector"""
    theta = np.arccos(np.clip((np.trace(R)-1)/2,-1,1))
    if abs(theta) < 1e-8: return np.zeros(3)
    w = np.array([R[2,1]-R[1,2], R[0,2]-R[2,0], R[1,0]-R[0,1]])
    return theta / (2*np.sin(theta)) * w
04
Kinematika

JACOBIAN & ANALISIS KECEPATAN

Jacobian J memetakan joint velocities → end-effector velocities: ẋ = J(q) · q̇ ẋ = [vx, vy, vz, ωx, ωy, ωz]^T (6×1 spatial velocity) q̇ = [q̇₁, q̇₂, ..., q̇ₙ]^T (n×1 joint velocities) J = 6×n matrix (geometric Jacobian) Kolom i Jacobian untuk revolute joint: J_i = [ z_{i-1} × (p_n - p_{i-1}) ] (linear velocity) [ z_{i-1} ] (angular velocity) Joint velocity dari task velocity: q̇ = J⁺ · ẋ (J⁺ = pseudo-inverse, least-norm solution) q̇ = J⁺ · ẋ + (I - J⁺J) · z (null-space projection)
import numpy as np

def geometric_jacobian(T_list, joint_types=None):
    """Compute 6×n geometric Jacobian from list of transforms T_0_i"""
    n = len(T_list) - 1
    if joint_types is None: joint_types = ['R']*n  # all revolute
    p_ee = T_list[-1][:3,3]  # end-effector position
    J = np.zeros((6, n))
    for i in range(n):
        z = T_list[i][:3,2]  # z-axis of frame i
        p = T_list[i][:3,3]  # origin of frame i
        if joint_types[i] == 'R':
            J[:3,i] = np.cross(z, p_ee - p)
            J[3:,i] = z
        else:  # Prismatic
            J[:3,i] = z
            J[3:,i] = np.zeros(3)
    return J

def damped_pseudoinverse(J, lam=0.01):
    """Damped least squares: lebih stabil di singularity"""
    return J.T @ np.linalg.inv([email protected] + lam**2*np.eye(J.shape[0]))
05
Dinamika

LAGRANGIAN MECHANICS

Lagrangian L = T - V T = kinetic energy = ½ · q̇^T · M(q) · q̇ V = potential energy = Σ mᵢ · g · hᵢ(q) Euler-Lagrange equations: d/dt(∂L/∂q̇ᵢ) - ∂L/∂qᵢ = τᵢ Result: M(q)·q̈ + C(q,q̇)·q̇ + G(q) = τ Inertia matrix M(q) element: Mᵢⱼ = Σₖ(mₖ · (∂pₖ/∂qᵢ)^T · (∂pₖ/∂qⱼ) + ωₖᵢ^T · Iₖ · ωₖⱼ) Gravity vector: Gᵢ = -∂V/∂qᵢ = -Σₖ mₖ · g^T · (∂pₖ/∂qᵢ)
import numpy as np

def robot_dynamics_2dof(q, qdot, tau,
                          m1=1.0, m2=0.5,
                          l1=0.3, l2=0.2,
                          lc1=0.15, lc2=0.10,
                          I1=0.05, I2=0.02, g=9.81):
    """2-DOF planar robot dynamics via Lagrangian"""
    q1,q2 = q; dq1,dq2 = qdot
    c2 = np.cos(q2)

    # Inertia matrix M(q)
    M11 = m1*lc1**2 + I1 + m2*(l1**2 + lc2**2 + 2*l1*lc2*c2) + I2
    M12 = m2*(lc2**2 + l1*lc2*c2) + I2
    M22 = m2*lc2**2 + I2
    M = np.array([[M11,M12],[M12,M22]])

    # Coriolis + centripetal C(q,qdot)
    h = -m2*l1*lc2*np.sin(q2)
    C = np.array([[h*dq2, h*(dq1+dq2)],[-h*dq1, 0]])

    # Gravity G(q)
    G1 = (m1*lc1 + m2*l1)*g*np.cos(q1) + m2*g*lc2*np.cos(q1+q2)
    G2 = m2*g*lc2*np.cos(q1+q2)
    G = np.array([G1,G2])

    # Acceleration: M·qddot = tau - C·qdot - G
    qddot = np.linalg.solve(M, np.array(tau) - C@np.array(qdot) - G)
    return qddot, M, C, G
06
Estimasi

KALMAN FILTER

Kalman Filter: optimal linear state estimator State model: x_k = F·x_{k-1} + B·u_k + w_k (w ~ N(0,Q)) Obs model: z_k = H·x_k + v_k (v ~ N(0,R)) Predict step: x̂_k⁻ = F·x̂_{k-1} + B·u_k P_k⁻ = F·P_{k-1}·F^T + Q Update step: K_k = P_k⁻·H^T · (H·P_k⁻·H^T + R)^{-1} ← Kalman gain x̂_k = x̂_k⁻ + K_k·(z_k - H·x̂_k⁻) ← posterior mean P_k = (I - K_k·H)·P_k⁻ ← posterior covariance K_k → 0 jika R besar (sensor noise tinggi) → lebih percaya prediksi K_k → H^{-1} jika Q besar (model noise tinggi) → lebih percaya sensor
import numpy as np

class KalmanFilter:
    def __init__(self, F, H, Q, R, x0, P0):
        self.F=np.array(F); self.H=np.array(H)
        self.Q=np.array(Q); self.R=np.array(R)
        self.x=np.array(x0,dtype=float)
        self.P=np.array(P0,dtype=float)
        self.n=self.x.shape[0]

    def predict(self, u=None, B=None):
        self.x = self.F @ self.x
        if u is not None and B is not None:
            self.x += np.array(B) @ np.array(u)
        self.P = self.F @ self.P @ self.F.T + self.Q

    def update(self, z):
        z = np.array(z)
        S = self.H @ self.P @ self.H.T + self.R
        K = self.P @ self.H.T @ np.linalg.inv(S)
        self.x = self.x + K @ (z - self.H @ self.x)
        self.P = (np.eye(self.n) - K @ self.H) @ self.P

# Contoh: estimasi posisi 1D dari GPS noisy
dt=0.1
kf=KalmanFilter(
  F=[[1,dt],[0,1]], H=[[1,0]],
  Q=[[0.01,0],[0,0.1]], R=[[0.5]],
  x0=[0,0], P0=np.eye(2)*1.0)
for z in [0.1,0.25,0.51,0.78,1.02]:
  kf.predict(); kf.update([z])
  print(f"pos={kf.x[0]:.3f}, vel={kf.x[1]:.3f}")
07
Estimasi

EXTENDED KALMAN FILTER (EKF)

EKF: Kalman Filter untuk sistem non-linear f(x,u) = state transition (non-linear) h(x) = observation model (non-linear) Linearisasi via Jacobian: F_k = ∂f/∂x |_{x̂_{k-1}} (Jacobian of f w.r.t. x) H_k = ∂h/∂x |_{x̂_k⁻} (Jacobian of h w.r.t. x) Predict: x̂_k⁻ = f(x̂_{k-1}, u_k) P_k⁻ = F_k · P_{k-1} · F_k^T + Q Update: K_k = P_k⁻ · H_k^T · (H_k · P_k⁻ · H_k^T + R)^{-1} x̂_k = x̂_k⁻ + K_k · (z_k - h(x̂_k⁻)) P_k = (I - K_k · H_k) · P_k⁻
import numpy as np

class RobotEKF:
    """EKF untuk lokalisasi mobile robot: state=[x,y,θ,v,ω]"""
    def __init__(self, dt=0.02):
        self.dt=dt; self.x=np.zeros(5)
        self.P=np.diag([0.1,0.1,0.05,0.01,0.01])
        self.Q=np.diag([1e-4,1e-4,1e-4,0.001,0.001])

    def f(self, x, u=None):
        dt=self.dt; xn=x.copy()
        xn[0]+=x[3]*np.cos(x[2])*dt
        xn[1]+=x[3]*np.sin(x[2])*dt
        xn[2]+=x[4]*dt
        return xn

    def F_jac(self, x):
        dt=self.dt; th=x[2]; v=x[3]
        F=np.eye(5)
        F[0,2]=-v*np.sin(th)*dt; F[0,3]=np.cos(th)*dt
        F[1,2]= v*np.cos(th)*dt; F[1,3]=np.sin(th)*dt
        F[2,4]=dt
        return F

    def predict(self):
        F=self.F_jac(self.x)
        self.x=self.f(self.x)
        [email protected]@F.T+self.Q

    def update_gps(self, gx, gy):
        H=np.zeros((2,5)); H[0,0]=H[1,1]=1
        R=np.diag([0.5,0.5])
        z=np.array([gx,gy]); [email protected]
        [email protected]@H.T+R; [email protected]@np.linalg.inv(S)
        self.x+=K@y; self.P=(np.eye(5)-K@H)@self.P
08
Kontrol

PID & TUNING

PID Controller: u(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt Discrete (sampling time dt): P = Kp · e I = I_prev + Ki · e · dt (clamp: -I_max ≤ I ≤ I_max) D = Kd · (e - e_prev) / dt (atau low-pass filter D) u = P + I + D Ziegler-Nichols tuning (ultimate gain method): 1. Set Ki=Kd=0, naikkan Kp sampai osilasi stabil → Ku 2. Catat periode osilasi Pu 3. PID: Kp=0.6·Ku, Ki=1.2·Ku/Pu, Kd=0.075·Ku·Pu
class PID:
    def __init__(self, kp, ki, kd, i_max=100, dt=0.02, d_filter=0.1):
        self.kp=kp; self.ki=ki; self.kd=kd
        self.i_max=i_max; self.dt=dt
        self.i=0; self.e_prev=0; self.d_filt=0
        self.alpha=d_filter  # D term low-pass coeff

    def compute(self, setpoint, measured):
        e = setpoint - measured
        self.i += e * self.dt
        self.i  = max(-self.i_max, min(self.i_max, self.i))
        d_raw = (e - self.e_prev) / self.dt
        self.d_filt = self.alpha*d_raw + (1-self.alpha)*self.d_filt
        out = self.kp*e + self.ki*self.i + self.kd*self.d_filt
        self.e_prev = e
        return out

    def reset(self): self.i=0; self.e_prev=0; self.d_filt=0

# Contoh: velocity PID untuk robot wheel
vel_pid = PID(kp=200, ki=15, kd=8, i_max=80)
pwm = vel_pid.compute(target_vel_ms, actual_vel_ms)

Cascade Control (Position + Velocity)

Cascade: outer loop (position) → setpoint untuk inner loop (velocity) e_pos = x_desired - x_actual v_cmd = PID_outer(e_pos) ← output = velocity setpoint u = PID_inner(v_cmd, v_actual) ← output = motor PWM Inner loop harus 3-10× lebih cepat dari outer loop
09
Kontrol

STATE SPACE CONTROL

State Space Representation: ẋ = Ax + Bu (continuous) y = Cx + Du Discrete (ZOH, sampling time dt): x_{k+1} = Ad·x_k + Bd·u_k Ad = e^(A·dt) ≈ I + A·dt + (A·dt)²/2 + ... Bd = A^{-1}(Ad-I)·B State Feedback Control: u = -K·x + r (K = gain matrix, r = reference) Pole Placement: pilih desired poles → solve for K K = place(A, B, desired_poles) LQR: optimal K yang minimasi J = ∫(x^T·Q·x + u^T·R·u)dt Solve: A^T·P + P·A - P·B·R^{-1}·B^T·P + Q = 0 (Riccati) K = R^{-1}·B^T·P
import numpy as np
from scipy.linalg import solve_continuous_are, expm
from scipy.signal import place_poles

def lqr(A, B, Q, R):
    """Linear Quadratic Regulator — optimal state feedback gain"""
    P = solve_continuous_are(A, B, Q, R)
    K = np.linalg.inv(R) @ B.T @ P
    return K, P

# Contoh: inverted pendulum balancing (linearized)
g=9.81; L=0.3; m=0.1; M=0.5
A=np.array([[0,1,0,0],[0,0,-m*g/M,0],[0,0,0,1],[0,0,g*(M+m)/(M*L),0]])
B=np.array([[0],[1/M],[0],[-1/(M*L)]])
Q=np.diag([1,1,10,1])
R=np.array([[0.1]])
K,P = lqr(A,B,Q,R)
print("LQR gain K:", K)

# Discrete simulation:
dt=0.01
Ad = expm(A*dt)
Bd = np.linalg.solve(A, (Ad-np.eye(4))) @ B
x=np.array([0,0,0.1,0])  # initial: 0.1 rad tilt
for _ in range(500):
    u = -K @ x
    x = Ad @ x + Bd @ u
print(f"Final angle: {np.degrees(x[2]):.4f}°")
010
Optimasi

GRADIENT DESCENT & OPTIMASI

Gradient Descent: θ_{k+1} = θ_k - α·∇L(θ_k) α = learning rate Variasi: SGD: update dari satu sampel Momentum: v = β·v - α·∇L; θ += v Adam: adaptive learning rate per parameter m = β₁·m + (1-β₁)·g (1st moment) v = β₂·v + (1-β₂)·g² (2nd moment) θ -= α · m̂/(√v̂ + ε) Untuk trajectory optimization: min_q J(q) = ∫(||q̈||² + w·||f_collision||²) dt subject to: kinematics, joint limits, collision constraints
import numpy as np

class Adam:
    def __init__(self, params, lr=0.001, b1=0.9, b2=0.999, eps=1e-8):
        self.p=np.array(params,dtype=float)
        self.lr=lr; self.b1=b1; self.b2=b2; self.eps=eps
        self.m=np.zeros_like(self.p); self.v=np.zeros_like(self.p); self.t=0

    def step(self, grad):
        self.t+=1; g=np.array(grad)
        self.m=self.b1*self.m+(1-self.b1)*g
        self.v=self.b2*self.v+(1-self.b2)*g**2
        mh=self.m/(1-self.b1**self.t)
        vh=self.v/(1-self.b2**self.t)
        self.p-=self.lr*mh/(np.sqrt(vh)+self.eps)
        return self.p.copy()

# Nelder-Mead untuk PID tuning otomatis:
from scipy.optimize import minimize

def pid_cost(params):
    kp,ki,kd = params
    if any(x < 0 for x in params): return 1e9
    sim = simulate_robot(kp, ki, kd)
    overshoot = max(0, sim.max()-1) * 100
    settle = sim.settle_time()
    sse = np.sum((sim.response-1)**2)
    return overshoot*0.5 + settle*2 + sse*0.1

result = minimize(pid_cost, [100,10,5], method='Nelder-Mead')
print("Optimal PID:", result.x)
011
Python

PYTHON MATH TOOLS ROBOTIKA

Library Ekosistem

LibraryFungsiInstallUse Case Robot
NumPyArray, linear algebrapip install numpySemua komputasi matrix, FK/IK
SciPyOptimasi, signal, ODEpip install scipyLQR (linalg), filter sinyal, integrasi
SymPySymbolic mathpip install sympyDerive Jacobian simbolik, Lagrangian otomatis
Robotics ToolboxRobot kinematics/dynamicspip install roboticstoolbox-pythonFK, IK, Jacobian, trajectory, visualisasi
PyBulletPhysics simulationpip install pybulletSimulasi robot 3D, reinforcement learning
PinocchioRigid body dynamicsconda install pinocchioDynamics cepat, MPC robot
controlControl systemspip install controlBode plot, LQR, pole placement, sisotool
import sympy as sp

# Symbolic Jacobian derivation
q1, q2, L1, L2 = sp.symbols('q1 q2 L1 L2')

# Forward kinematics 2-DOF planar
x = L1*sp.cos(q1) + L2*sp.cos(q1+q2)
y = L1*sp.sin(q1) + L2*sp.sin(q1+q2)

# Symbolic Jacobian
J = sp.Matrix([[sp.diff(x,q1), sp.diff(x,q2)],
               [sp.diff(y,q1), sp.diff(y,q2)]])
J_simplified = sp.simplify(J)
print(J_simplified)

# Determinant (untuk singularity analysis)
det_J = sp.det(J_simplified)
print("det(J) =", sp.simplify(det_J))
# det = 0 → singularity condition

# Convert to numpy function:
J_func = sp.lambdify([q1,q2,L1,L2], J_simplified, 'numpy')
import numpy as np
J_num = J_func(np.radians(30), np.radians(45), 0.15, 0.12)
print("Numeric J:", J_num)
MATEMATIKA & FISIKA ROBOTIKA — DOC 07 / 07
Antonius - bluedragonsec.com