@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, ...).
119 lines (103 loc) • 4.59 kB
JavaScript
//
// 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/dae_multiple_shooting.py.
//
// Direct multiple shooting on a DAE optimal control problem:
// min int_0^10 (x0^2 + x1^2 + u^2) dt
// s.t. dot(x0) = z*x0 - x1 + u, dot(x1) = x0, 0 = x1^2 + z - 1
// x0(0)=0, x1(0)=1, x0(10)=0, x1(10)=0, -0.75 <= u <= 1
// The Python version integrates with "idas"; that DAE integrator is not
// linked in the wasm build, so this port SUBSTITUTES the "collocation"
// integrator (which also supports algebraic states).
//
// JS notes (see README.md):
// * Python's `inf` becomes JS `Infinity`.
// * The integrator is called per shooting interval; xf/qf are read from
// the returned name-keyed dict.
async function example(M, log) {
// Declare variables
const x0 = M.SX.sym("x0");
const x1 = M.SX.sym("x1");
const x = M.vertcat(x0, x1); // differential states
const z = M.SX.sym("z"); // algebraic variable
const u = M.SX.sym("u"); // control
// Differential equation
const f_x = M.vertcat(M.plus(M.minus(M.times(z, x0), x1), u), x0);
// Algebraic equation
const f_z = M.minus(M.plus(M.power(x1, 2), z), 1);
// Lagrange cost term (quadrature)
const f_q = M.plus(M.plus(M.power(x0, 2), M.power(x1, 2)), M.power(u, 2));
// Create an integrator (collocation substituted for idas), interval 0.5s
const dae = { x: x, z: z, p: u, ode: f_x, alg: f_z, quad: f_q };
const I = M.integrator("I", "collocation", dae, 0, 0.5);
// Number of intervals
const nk = 20;
// Build the NLP via direct multiple shooting
const w = []; // variables
const lbw = [];
const ubw = [];
const G = []; // constraints
let J = M.MX(0);
// Initial conditions
let Xk = M.MX.sym("X0", 2);
w.push(Xk);
lbw.push(0, 1); ubw.push(0, 1);
for (let k = 0; k < nk; ++k) {
// Local control
const Uk = M.MX.sym("U" + k);
w.push(Uk);
lbw.push(-0.75); ubw.push(1.0);
// Integrate over the interval
const Ik = I.call({ x0: Xk, p: Uk });
const X_prev = Ik["xf"];
J = M.plus(J, Ik["qf"]);
// Lift the state variable
Xk = M.MX.sym("X" + (k + 1), 2);
w.push(Xk);
lbw.push(-Infinity, -Infinity); ubw.push(Infinity, Infinity);
G.push(M.minus(X_prev, Xk));
}
// Allocate an NLP solver
const nlp = { x: M.vertcat.apply(null, w), f: J, g: M.vertcat.apply(null, G) };
const solver = M.nlpsol("solver", "ipopt", nlp,
{ "ipopt.print_level": 0, print_time: false });
const sol = solver.call({
lbx: M.DM(lbw), ubx: M.DM(ubw), lbg: M.DM(0), ubg: M.DM(0), x0: M.DM(0),
});
log("optimal cost = " + sol["f"].nonzeros()[0].toFixed(6));
// Layout per interval: [x0, x1, u]; final block has [x0, x1].
const nz = sol["x"].nonzeros();
log("interval samples (t, x0, x1, u):");
for (let k = 0; k <= nk; k += 4) {
const t = (10.0 * k) / nk;
const xa = nz[3 * k], xb = nz[3 * k + 1];
const uu = k < nk ? nz[3 * k + 2].toFixed(4) : " - ";
log(" t=" + t.toFixed(2) + " x0=" + xa.toFixed(4) + " x1=" + xb.toFixed(4) + " u=" + uu);
}
}
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;