-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeatures.py
More file actions
124 lines (94 loc) · 3.61 KB
/
Copy pathfeatures.py
File metadata and controls
124 lines (94 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
Basis functions φ(x) and Jacobian ∇φ(x) for value-function approximation.
**Quadratic monomial basis** (required):
φ(x) = [1, x_1, …, x_n, x_1², x_1 x_2, …, x_n²]
Dimension: p = 1 + n + n(n+1)/2.
For n = 10: p = 1 + 10 + 55 = 66.
Convention
----------
∇φ(x) has shape **(n, p)** so that
∇V = ∇φ @ W ∈ ℝⁿ (gradient of value function)
σ = ∇φᵀ @ f ∈ ℝᵖ (basis-projected dynamics)
**RBF basis** (optional, kept for experiments):
φ_k(x) = exp(−‖x − c_k‖² / (2 σ_k²))
"""
import numpy as np
# ═══════════════════════════════════════════════════════════════════════
# Quadratic monomials
# ═══════════════════════════════════════════════════════════════════════
def quadratic_basis_dim(n: int) -> int:
"""Return dimensionality *p* of the quadratic monomial basis."""
return 1 + n + n * (n + 1) // 2
def phi_quadratic(x: np.ndarray) -> np.ndarray:
"""Evaluate quadratic monomial basis.
Parameters
----------
x : (n,)
Returns
-------
phi : (p,)
"""
n = x.shape[0]
p = quadratic_basis_dim(n)
phi = np.empty(p)
# --- constant ---
phi[0] = 1.0
# --- linear ---
phi[1: n + 1] = x
# --- quadratic x_i x_j (i ≤ j) ---
idx = n + 1
for i in range(n):
for j in range(i, n):
phi[idx] = x[i] * x[j]
idx += 1
return phi
def grad_phi_quadratic(x: np.ndarray) -> np.ndarray:
"""Analytically compute the Jacobian ∇φ(x).
Parameters
----------
x : (n,)
Returns
-------
grad : (n, p) — entry [m, k] = ∂φ_k / ∂x_m
"""
n = x.shape[0]
p = quadratic_basis_dim(n)
grad = np.zeros((n, p))
# ∂(1)/∂x_m = 0 → already zero
# ∂(x_i)/∂x_m = δ_{im}
for m in range(n):
grad[m, 1 + m] = 1.0
# ∂(x_i x_j)/∂x_m
idx = n + 1
for i in range(n):
for j in range(i, n):
if i == j:
grad[i, idx] = 2.0 * x[i]
else:
grad[i, idx] = x[j]
grad[j, idx] = x[i]
idx += 1
return grad
# ═══════════════════════════════════════════════════════════════════════
# Radial-basis functions (optional)
# ═══════════════════════════════════════════════════════════════════════
def phi_rbf(
x: np.ndarray,
centers: np.ndarray,
widths: np.ndarray,
) -> np.ndarray:
"""RBF basis. ``centers`` (p, n), ``widths`` (p,) → φ (p,)."""
diff = x[np.newaxis, :] - centers # (p, n)
return np.exp(-np.sum(diff ** 2, axis=1) / (2.0 * widths ** 2))
def grad_phi_rbf(
x: np.ndarray,
centers: np.ndarray,
widths: np.ndarray,
) -> np.ndarray:
"""Jacobian of RBF basis. Shape (n, p)."""
phi_val = phi_rbf(x, centers, widths) # (p,)
diff = x[np.newaxis, :] - centers # (p, n)
# ∂φ_k/∂x_m = φ_k · (c_{km} − x_m) / σ_k²
# = −φ_k · diff[k, m] / σ_k²
grad_pxn = -phi_val[:, np.newaxis] * diff / (widths[:, np.newaxis] ** 2)
return grad_pxn.T # (n, p)