UNPKG

@casadi/casadi-wasm

Version:

CasADi — symbolic framework for algorithmic differentiation and numerical optimization, compiled to WebAssembly. Runs in Node.js with on-demand solver plugins (ipopt, fatrop, sundials, ...).

140 lines (127 loc) 5.64 kB
// // MIT No Attribution // // Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl, KU Leuven. // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, // merge, publish, distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // // JS port of docs/examples/python/vdp_dynamic_programming.py. // // Dynamic programming over a discretized state/control grid for the Van der // Pol problem. The Python original is pure numpy (no casadi symbolics); this // port keeps it as plain JS array math. // // JS notes (see README.md): // * numpy meshgrid/linspace become flat Float64Array grids. // * matplotlib output is dropped; we log the optimal cost and trajectory. function linspace(a, b, n) { const r = new Array(n); for (let i = 0; i < n; i++) r[i] = a + (b - a) * i / (n - 1); return r; } async function example(M, log) { const inf = Infinity; const T = 10.0; // End time const N = 20; // Number of control intervals const NK = 20; // RK4 steps per interval const DT = T / (N * NK); const NU = 101; // Number of discrete control values const NX = 101; // Number of discrete state values per axis // System dynamics const f = (x1, x2, u) => [ (1 - x2 * x2) * x1 - x2 + u, // x1_dot x1, // x2_dot x1 * x1 + x2 * x2 + u * u, // q_dot ]; const U = linspace(-1, 1, NU); const x1 = linspace(-1, 1, NX); const x2 = linspace(-1, 1, NX); const NG = NX * NX; // total grid points (row=x2, col=x1) const idx = (i2, i1) => i2 * NX + i1; // For each control action, precompute next-state index and stage cost const stage_J = [], next_x1 = [], next_x2 = []; for (let uind = 0; uind < NU; uind++) { const u = U[uind]; const nx1 = new Int32Array(NG), nx2 = new Int32Array(NG), Q = new Float64Array(NG); for (let i2 = 0; i2 < NX; i2++) { for (let i1 = 0; i1 < NX; i1++) { let X1 = x1[i1], X2 = x2[i2], Qk = 0; for (let k = 0; k < NK; k++) { const [a1, a2, aq] = f(X1, X2, u); const [b1, b2, bq] = f(X1 + DT / 2 * a1, X2 + DT / 2 * a2, u); const [c1, c2, cq] = f(X1 + DT / 2 * b1, X2 + DT / 2 * b2, u); const [d1, d2, dq] = f(X1 + DT * c1, X2 + DT * c2, u); X1 += DT / 6 * (a1 + 2 * b1 + 2 * c1 + d1); X2 += DT / 6 * (a2 + 2 * b2 + 2 * c2 + d2); Qk += DT / 6 * (aq + 2 * bq + 2 * cq + dq); } // Round to nearest grid index let r1 = Math.round((X1 + 1) / 2 * (NX - 1)); let r2 = Math.round((X2 + 1) / 2 * (NX - 1)); // Infinite cost if out-of-bounds if (r1 < 0 || r1 >= NX || r2 < 0 || r2 >= NX) { Qk = inf; r1 = 0; r2 = 0; } const g = idx(i2, i1); nx1[g] = r1; nx2[g] = r2; Q[g] = Qk; } } next_x1.push(nx1); next_x2.push(nx2); stage_J.push(Q); } // Cost-to-go (no end cost) and optimal control let J = new Float64Array(NG); const U_opt = []; for (let k = N - 1; k >= 0; k--) { const J_prev = new Float64Array(NG).fill(inf); const u_prev = new Int32Array(NG).fill(-1); for (let uind = 0; uind < NU; uind++) { const nx1 = next_x1[uind], nx2 = next_x2[uind], sj = stage_J[uind]; for (let g = 0; g < NG; g++) { const test = J[idx(nx2[g], nx1[g])] + sj[g]; if (test < J_prev[g]) { J_prev[g] = test; u_prev[g] = uind; } } } J = J_prev; U_opt.push(u_prev); } U_opt.reverse(); // Optimal control starting at x1=0, x2=1 let i1 = Math.floor(NX / 2); let i2 = NX - 1; const u_opt = [], x1_opt = [x1[i1]], x2_opt = [x2[i2]]; let cost = 0; for (let k = 0; k < N; k++) { const u_ind = U_opt[k][idx(i2, i1)]; cost += stage_J[u_ind][idx(i2, i1)]; const ni1 = next_x1[u_ind][idx(i2, i1)], ni2 = next_x2[u_ind][idx(i2, i1)]; i1 = ni1; i2 = ni2; u_opt.push(U[u_ind]); x1_opt.push(x1[i1]); x2_opt.push(x2[i2]); } log("-----"); log("Minimal cost: " + cost); // Consistency check (cf. Python assert) if (Math.abs(cost - J[idx(NX - 1, Math.floor(NX / 2))]) >= 1e-8) throw new Error("consistency check failed"); log("x1 trajectory = " + x1_opt.map((v) => v.toFixed(4)).join(" ")); log("x2 trajectory = " + x2_opt.map((v) => v.toFixed(4)).join(" ")); log("u trajectory = " + u_opt.map((v) => v.toFixed(4)).join(" ")); } if (typeof require !== "undefined" && typeof module !== "undefined" && require.main === module) { const path = require("path"); const casadiPath = process.env.CASADI_JS || path.resolve(__dirname, "../../../build-wasm/swig/wasm-js/casadi.js"); require(casadiPath)() .then((M) => example(M, (...a) => console.log(...a))) .catch((e) => { console.error("FATAL:", e.message || e); process.exit(1); }); } if (typeof module !== "undefined" && module.exports) module.exports = example;