Download globmp/problem.py from GlobalMotionPlanning/GlobMP: direct link, hf CLI and curl.
- Browser
- Download file 9.87 kB
-
https://huggingface.co/GlobalMotionPlanning/GlobMP/resolve/main/globmp/problem.py
- Command line
-
hf download hf://GlobalMotionPlanning/GlobMP/globmp/problem.py
-
curl -L -o problem.py https://huggingface.co/GlobalMotionPlanning/GlobMP/resolve/main/globmp/problem.py
9.87 kB
| """The program (P) over piecewise polynomial curves. | |
| min sum_a int ||gamma_a^(k)||^2 (or a tracking cost, see set_tracking) | |
| s.t. gamma(0) = start, gamma(1) = goal, derivatives 1..ell zero at both ends, | |
| C^{ell_j} continuity at every junction, | |
| every block p_j(t) >= 0 for all t (blocks/) | |
| The curve has n coordinates (all robots stacked) and M segments of degree d; Gamma in R^{n x M(d+1)} | |
| holds the Bernstein coefficients. The boundary and junction conditions are linear, Gamma A = B, so every | |
| admissible Gamma is Gamma0 + Z N^T with N a basis of the null space of A^T: Z in R^{n x d_f} is the | |
| chart, and the cost is the quadratic cost(Z) = c0 + 2<Z, G0> + <Z H, Z>. | |
| """ | |
| import math | |
| import numpy as np | |
| import torch | |
| from . import spline as bern | |
| from .spline import DTYPE as DT, elev_mat | |
| from .blocks import BlocksMixin | |
| from .restore import RestoreMixin | |
| from .gpa import GPAMixin | |
| from .certificate import CertificateMixin | |
| from .polish import PolishMixin | |
| _CHART_CACHE = {} | |
| class Problem(BlocksMixin, RestoreMixin, GPAMixin, CertificateMixin, PolishMixin): | |
| """ | |
| n, d, k, ell, M : coordinates, segment degree, cost derivative order, boundary order, segments | |
| start, goal : (n,) end positions | |
| ell_j : continuity order at junctions (default ell) | |
| delta : inflation margin added to every sphere, separation and moving-sphere radius | |
| mode : 'cone' exact membership in K at every t (GlobMP) | |
| 'sampled' nonnegativity at Ns sample points only (the inexact sampled check) | |
| 'orthant' all Bernstein coefficients of degree De nonnegative (control-polygon restriction) | |
| whiten : use the chart N H^{-1/2}, so the cost Hessian in the chart is the identity | |
| tol_min : feasibility tolerance of blocks without an inflation margin | |
| bb : Barzilai-Borwein steps in Algorithm 1 (False: monotone gradient steps with a Polyak cap) | |
| stop_rel : Algorithm 2 stops when the cost drops by less than stop_rel (relative) over 50 accepted steps | |
| """ | |
| def __init__(self, n, d, k, ell, M, start, goal, ell_j=None, delta=0.0, mode='cone', Ns=None, De=None, | |
| whiten=False, tol_min=1e-12, bb=True, stop_rel=2e-4): | |
| self.mode, self.Ns, self.De = mode, Ns, De | |
| self.n, self.d, self.k, self.ell, self.M = n, d, k, ell, M | |
| self.ell_j = ell if ell_j is None else ell_j | |
| self.delta = float(delta) | |
| self.tol_min, self.bb, self.stop_rel = float(tol_min), bool(bb), float(stop_rel) | |
| P = M * (d + 1); self.P = P | |
| Dq = [bern.Dk(d, q) for q in range(max(ell, self.ell_j) + 1)] | |
| b0 = [Dq[q][:, 0] for q in range(len(Dq))]; b1 = [Dq[q][:, -1] for q in range(len(Dq))] | |
| cols, Bc = [], [] | |
| start = torch.as_tensor(start, dtype=DT); goal = torch.as_tensor(goal, dtype=DT) | |
| for q in range(ell + 1): | |
| c = torch.zeros(P, dtype=DT); c[:d + 1] = b0[q]; cols.append(c); Bc.append(start if q == 0 else torch.zeros(n, dtype=DT)) | |
| for q in range(ell + 1): | |
| c = torch.zeros(P, dtype=DT); c[(M - 1) * (d + 1):] = b1[q]; cols.append(c); Bc.append(goal if q == 0 else torch.zeros(n, dtype=DT)) | |
| for i in range(M - 1): | |
| for q in range(self.ell_j + 1): | |
| c = torch.zeros(P, dtype=DT); c[i * (d + 1):(i + 1) * (d + 1)] = b1[q]; c[(i + 1) * (d + 1):(i + 2) * (d + 1)] = -b0[q] | |
| cols.append(c); Bc.append(torch.zeros(n, dtype=DT)) | |
| self.A = torch.stack(cols, 1); self.B = torch.stack(Bc, 1) | |
| nc = self.A.shape[1] | |
| # The chart basis and the pseudoinverse of A depend only on (d, ell, ell_j, M), not on the end | |
| # values, so they are cached: rebuilding them costs an SVD that dominates short solves. | |
| ck = ('chart', d, ell, self.ell_j, M) | |
| if ck in _CHART_CACHE: | |
| Api, self.N = _CHART_CACHE[ck] | |
| else: | |
| assert int(torch.linalg.matrix_rank(self.A)) == nc | |
| Api = torch.linalg.inv(self.A.T @ self.A) @ self.A.T | |
| _, _, Vt = torch.linalg.svd(self.A.T, full_matrices=True); self.N = Vt[-(P - nc):, :].T.contiguous() | |
| _CHART_CACHE[ck] = (Api, self.N) | |
| self.Gamma0 = self.B @ Api | |
| self.d_f = P - nc | |
| self.Gk = torch.block_diag(*[bern.cost_gram(d, k)] * M) | |
| self.u = bern.ones_u(d); self.uu = torch.outer(self.u, self.u) | |
| self.H = self.N.T @ self.Gk @ self.N | |
| # Whitened chart: any basis of the null space of A^T is a valid chart, so take N <- N H^{-1/2}. | |
| # The cost Hessian becomes the identity and Algorithm 2 no longer crawls along the stiff | |
| # high-derivative directions. Multipliers and the sign of the certificate are chart-invariant. | |
| if whiten: | |
| ev, V = torch.linalg.eigh(0.5 * (self.H + self.H.T)); c = float(ev.clamp_min(1e-12).log().mean().exp().sqrt()) | |
| self.N = (self.N @ V) * (c / ev.clamp_min(1e-12).sqrt()); self.N = self.N.contiguous() | |
| self.H = self.N.T @ self.Gk @ self.N | |
| ev = torch.linalg.eigvalsh(self.H); self.alpha = 2 * float(ev.min()); self.beta = 2 * float(ev.max()) | |
| # Cost as an explicit chart quadratic. Evaluating the Gram form on the Bernstein coefficients loses | |
| # ~12 digits by cancellation on long instances; the chart form is exact to rounding. | |
| self.H = 0.5 * (self.H + self.H.T); self.G0 = (self.Gamma0 @ self.Gk) @ self.N | |
| self.c0 = float(torch.einsum('pq,ip,iq->', self.Gk, self.Gamma0, self.Gamma0)) | |
| self.D1 = bern.Dk(d, 1) # gamma' = Gamma_seg D1 b_{d-1} | |
| self.E1 = elev_mat(2 * (d - 1), 2 * d) | |
| self._init_blocks() | |
| if mode == 'sampled': | |
| self.s_grid = torch.linspace(0, 1, Ns, dtype=DT); self.Bs = bern.bernstein_basis_eval(2 * d, self.s_grid) | |
| if mode == 'orthant': | |
| self.Eo = elev_mat(2 * d, De) | |
| # ---- tracking costs | |
| def set_tracking(self, wp, w_deriv=0.0): | |
| """Cost = int ||gamma(t) - gamma_ref(t)||^2 dt, gamma_ref the piecewise-linear interpolant of the M+1 | |
| waypoints wp (segment endpoints), plus w_deriv times the derivative cost. The reference is linear | |
| on each segment, hence exactly a degree-d Bernstein polynomial.""" | |
| wp = torch.as_tensor(wp, dtype=DT) # (M+1,n) | |
| ss = torch.linspace(0, 1, self.d + 1, dtype=DT) | |
| R = torch.stack([wp[a].view(-1, 1) * (1 - ss) + wp[a + 1].view(-1, 1) * ss for a in range(self.M)], 0) | |
| R = R.permute(1, 0, 2).reshape(self.n, -1) # (n,M(d+1)) | |
| Gk = torch.block_diag(*[bern.cost_gram(self.d, 0)] * self.M) | |
| if w_deriv > 0: Gk = Gk + float(w_deriv) * torch.block_diag(*[bern.cost_gram(self.d, self.k)] * self.M) | |
| A = self.Gamma0 - R | |
| self.Gk = Gk; self.ref = R | |
| self.H = self.N.T @ Gk @ self.N; self.H = 0.5 * (self.H + self.H.T) | |
| self.G0 = (A @ Gk) @ self.N; self.c0 = float(torch.einsum('pq,ip,iq->', Gk, A, A)) | |
| ev = torch.linalg.eigvalsh(self.H); self.alpha = 2 * float(ev.min()); self.beta = 2 * float(ev.max()) | |
| return self | |
| def set_tracking_fine(self, wpf, w_deriv=0.0, nq=None): | |
| """Tracking cost against the piecewise-linear interpolant of L+1 waypoints wpf (L >= M), so the | |
| spline may use fewer segments than the reference has pieces. Gauss-Legendre with | |
| ceil((d+2)/2) nodes per sub-interval is exact.""" | |
| wpf = torch.as_tensor(wpf, dtype=DT); L = wpf.shape[0] - 1; n = self.n; d = self.d; M = self.M | |
| assert wpf.shape[1] == n | |
| nq = int(math.ceil((d + 2) / 2)) if nq is None else int(nq) | |
| xg, wg = np.polynomial.legendre.leggauss(nq) | |
| xg = torch.as_tensor(0.5 * (xg + 1.0), dtype=DT); wg = torch.as_tensor(0.5 * wg, dtype=DT) | |
| Rd = torch.zeros(n, M * (d + 1), dtype=DT); cref = 0.0 | |
| tb = torch.linspace(0, 1, L + 1, dtype=DT) | |
| for a in range(M): | |
| t0, t1 = a / M, (a + 1) / M | |
| cuts = torch.unique(torch.cat([torch.tensor([t0, t1], dtype=DT), tb[(tb > t0) & (tb < t1)]])) | |
| acc = torch.zeros(n, d + 1, dtype=DT) | |
| for q in range(len(cuts) - 1): | |
| c0, c1 = float(cuts[q]), float(cuts[q + 1]); h = c1 - c0 | |
| tq = c0 + h * xg | |
| u = (tq - t0) / (t1 - t0) | |
| bu = bern.bernstein_basis_eval(d, u) | |
| i = torch.clamp((tq * L).floor().long(), 0, L - 1); f = (tq * L - i).view(1, -1) | |
| gq = wpf[i].T * (1 - f) + wpf[i + 1].T * f | |
| acc += (gq * (wg * h).view(1, -1)) @ bu.T | |
| cref += float(((gq ** 2).sum(0) * (wg * h)).sum()) | |
| Rd[:, a * (d + 1):(a + 1) * (d + 1)] = acc | |
| Gk = torch.block_diag(*[bern.cost_gram(d, 0)] * M) | |
| if w_deriv > 0: Gk = Gk + float(w_deriv) * torch.block_diag(*[bern.cost_gram(d, self.k)] * M) | |
| A = self.Gamma0 | |
| self.Gk = Gk; self.ref = None | |
| self.H = self.N.T @ Gk @ self.N; self.H = 0.5 * (self.H + self.H.T) | |
| self.G0 = (A @ Gk - Rd * M) @ self.N | |
| self.c0 = float(torch.einsum('pq,ip,iq->', Gk, A, A)) - 2.0 * float((Rd * M * A).sum()) + cref * M | |
| ev = torch.linalg.eigvalsh(self.H); self.alpha = 2 * float(ev.min()); self.beta = 2 * float(ev.max()) | |
| return self | |
| # ---- chart | |
| def Gamma(self, Z): return self.Gamma0 + Z @ self.N.T | |
| def segs(self, Z): return self.Gamma(Z).view(self.n, self.M, self.d + 1).permute(1, 0, 2) # (M,n,d+1) | |
| def eval_curve(self, Z, s): | |
| bd = bern.bernstein_basis_eval(self.d, s); return torch.einsum('mnp,ps->mns', self.segs(Z), bd) # (M,n,S) | |
| def cost(self, Z): return self.c0 + 2 * (Z * self.G0).sum() + torch.einsum('pq,ip,iq->', self.H, Z, Z) | |
| def cost_grad(self, Z): return 2 * (self.G0 + Z @ self.H) | |