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, ...).

206 lines (167 loc) 9.18 kB
# CasADi examples in JavaScript / WebAssembly These are JavaScript ports of [`docs/examples/python`](../python), running against the **wasm-js** build of CasADi: the C++ library compiled to WebAssembly with a JavaScript API generated by SWIG. The same examples run two ways: - **Node** — `node rosenbrock.js` (headless, scriptable, CI-friendly) - **Browser** — open `rosenbrock.html`, no install, CasADi runs client-side Every example is **one file shared by both**: a small `example(M, log)` function containing nothing but CasADi calls. A Node entry point at the bottom runs it from the command line; the matching `.html` page runs the *identical* function in the browser. Read any `.js` to learn the syntax; read this file first to learn the handful of ways JS differs from Python. --- ## Running under Node ```bash node rosenbrock.js ``` The script finds the wasm build at `../../../build-wasm/swig/wasm-js/casadi.js`. Point elsewhere with an env var: ```bash CASADI_JS=/path/to/wasm-js/casadi.js node rosenbrock.js ``` ## Running in a browser The page must be **served over HTTP** — not opened as a `file://` URL. This is the only "CORS-ish" gotcha, and it is *not* a cross-origin problem: browsers simply refuse to `fetch()` a sibling `.wasm` over `file://`. Serve from a real (even local) web server and everything is same-origin. The browser also needs the build artifacts reachable next to the page. From this directory: ```bash # 1. make the four wasm artifacts available here (symlink or copy) ln -sf ../../../build-wasm/swig/wasm-js/casadi.js \ ../../../build-wasm/swig/wasm-js/casadi_wasm.js \ ../../../build-wasm/swig/wasm-js/casadi_wasm.wasm \ ../../../build-wasm/swig/wasm-js/casadi_wasm.data . # 2. serve and open python3 -m http.server 8000 # -> http://localhost:8000/rosenbrock.html ``` (The symlinks are `.gitignore`d.) If the artifacts live elsewhere, skip the symlinks and pass their URL: `rosenbrock.html?casadi=/some/path/`. How the browser loads a Node-targeted module: the generated `casadi.js` wrapper is CommonJS, but only its first ~10 lines use Node APIs (`require`, `__dirname`); the other ~25k lines are plain ECMAScript. [`_casadi_browser.js`](./_casadi_browser.js) evaluates it inside a tiny sandbox that supplies just those few symbols — so the unmodified file runs in the browser. You never call it directly; the HTML does. --- ## CasADi in JavaScript: what differs from Python The C++ API is the same. Only the *language surface* changes. | Concept | Python | JavaScript | |---|---|---| | Load the library | `import casadi as ca` (sync) | `const M = await loadCasadi(...)` (async) | | Construct symbol | `ca.SX.sym("x")` | `M.SX.sym("x")` | | Construct, no `new` | `ca.SX(2)` | `M.SX(2)` (classes are callable; `new` also works) | | Add / multiply | `a + b`, `a * b` | `M.plus(a, b)`, `M.times(a, b)` — **no operator overloading** | | Power | `a ** 2` | `M.power(a, 2)` | | Number literal in op | `2 * x` | `M.times(2, x)` — bare numbers/arrays coerce automatically | | Matrix product | `A @ b` | `M.mtimes(A, b)` | | Element index | `x[k]`, `x[k] = v` | `x[k]`, `x[k] = v` (same!) | | 2-D index | `A[i, j]` | `A[[i, j]]` — note the inner array (`A[i,j]` is the comma operator in JS) | | Slice | `x[1:3]`, `A[:, 1]` | `x["1:3"]`, `A[[":", 1]]` (string slices) | | Nonzeros | `x.nz[k]` | `x.nz[k]`, `x.nz[k] = v` (same!) | | Concatenate (variadic) | `ca.vertcat(x, y, z)` | `M.vertcat(x, y, z)` | | Concatenate (list) | `ca.vertcat(*xs)` | `M.vcat(xs)` | | Solver IO | dict `{"x0": ...}` | object `{ x0: ... }` | | Read DM values | `np.array(dm)` / `dm.full()` | `dm.nonzeros()``number[]` | | Integer args | `f.size1()``int` | `f.size1()``bigint` (note the `n` suffix) | | Function repr | `str(f)` / `print(f)` | `String(f)`, `` `${f}` ``, `console.log(f)`, or `f.str()` | | Const MX → value | `mx.to_DM()` | `M.evalf(mx)` → DM (MX has no `to_DM`) | | Opcode enums | `ca.OP_ADD` | `M.Operation.OP_ADD` (`bigint`, matches `instruction_id()` — compare with `===`) | The single biggest adjustment is **no operator overloading**: write `x*x` as `M.times(x, x)`. Bare number/array literals *are* coerced into the matching matrix type, so `M.plus(2, x)`, `M.times(2.5, y)`, and `M.plus(M.DM(1), 4)` all work — no need to wrap `2` as `M.SX(2)`. ### The example shape ```js // foo.js — the body is pure CasADi and environment-agnostic async function example(M, log) { const x = M.SX.sym("x"); const f = new M.Function("f", [x], [M.sin(x)]); log("f(0.5) = " + f.call([M.DM(0.5)])[0].nonzeros()[0]); } // Node entry point (skipped in the browser) if (typeof require !== "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, console.log)); } if (typeof module !== "undefined" && module.exports) module.exports = example; ``` `log` is the output sink: `console.log` under Node, a DOM writer in the browser. Keep `example()` free of `require`, `process`, and DOM access so both runners can share it. ### Editor autocomplete `jsconfig.json` wires up the generated `casadi.d.ts` (symlinked as part of the browser setup above; or symlink it directly). Annotate the module parameter to get full completion: ```js /** @param {typeof import("casadi")} M */ async function example(M, log) { /* M. <-- autocompletes the whole API */ } ``` --- ## Adding a new example 1. Copy the corresponding `docs/examples/python/<name>.py` mentally and translate it into `<name>.js` using the example shape above. 2. Make the page: `sed 's/__NAME__/<name>/g' _template.html > <name>.html`. 3. Test: `node <name>.js`, then serve and open `<name>.html`. ## Coverage Ported from `docs/examples/python`, alphabetically — **25 of 45**. Each ported `.js` runs under Node (and in the browser via its `.html`). Examples needing capabilities not present in this wasm build are skipped with the reason; see also the [capability matrix](#plugin-availability). ### Ported (25) | Example | Notes | |---|---| | `accessing_mx_algorithm` | MX algorithm introspection | | `accessing_sx_algorithm` | SX algorithm introspection | | `biegler_10_1` | collocation NLP (ipopt) | | `c_code_generation` | `CodeGenerator.dump()` C text only (no compile in wasm) | | `chain_qp` | hanging-chain QP (qpoases) | | `dae_collocation` | hand collocation OCP (ipopt) | | `dae_multiple_shooting` | integrator `idas``collocation` | | `dae_single_shooting` | integrator `idas``collocation` | | `direct_collocation` | flagship; hand collocation OCP | | `direct_multiple_shooting` | RK4 multiple shooting | | `direct_single_shooting` | RK4 single shooting | | `implicit_runge-kutta` | IRK via `newton` rootfinder | | `multipoint_simulation` | integrator `cvodes``rk` | | `nlp_codegen` | ipopt solve + `CodeGenerator.dump()` (no compile) | | `nlp_sensitivities` | sqpmethod + qrqp; fwd/rev/FD sensitivities | | `parallel_map` | serial `Function.map` (no threads in wasm) | | `race_car` | Opti min-time OCP | | `rocket` | NLP OCP | | `rosenbrock` | NLP (ipopt) | | `sensitivity_analysis` | integrator `cvodes``rk`/`collocation` | | `simple_lp` | LP via conic (qpoases) | | `simple_nlp` | NLP (ipopt) | | `vdp_collocation` | hand Radau collocation NLP | | `vdp_dynamic_programming` | numeric DP over a grid | | `vdp_indirect_multiple_shooting` | indirect method, `cvodes`→`rk` | ### Skipped (20) — capability not in the wasm build | Example | Reason | |---|---| | `bouncing_ball` | cvodes event/zero-crossing detection (no rk/collocation equivalent) | | `bouncing_ball_daebuilder` | FMU export + compile | | `breaking_spring` | DaeBuilder + cvodes events | | `callback` | Callback director numeric `eval` aborts (`DM: arg-type mismatch`) | | `daebuilder` | `DaeBuilder.der()` const overload throws in the binding | | `dae_reduced_index` | `dae_reduce_index` aborts (by-ref dict marshalling) | | `debug_fatrop` | matplotlib sparsity-spy of `.mtx` files; no numeric output | | `fmu_collocation` `fmu_demo` `fmu_demo2` `fmu_export` `modelica_fmu_import` | FMU / DaeBuilder-FMI runtime | | `ipopt_nl` | reads an AMPL `.nl` from disk (no JS-accessible FS) | | `lotka_volterra_minlp` | MINLP (bonmin) | | `lqr_control` `mhe_spring_damper` `simulation` `vdp_collocation2` | `casadi.tools.struct` / scipy (Python-only) | | `sysid` | ipopt fatal on NaN line-search trial (no backtracking recovery in this build) | | `vdp_indirect_single_shooting` | unstable augmented ODE needs a stiff integrator (idas/cvodes) | ### Plugin availability Works: `nlpsol` (ipopt, fatrop, sqpmethod), `conic` (qrqp, qpoases, osqp, highs, ipqp), `integrator` (rk, collocation), `rootfinder` (newton, fast_newton), `CodeGenerator.dump()`. Not available: cvodes/idas/kinsol (sundials), FMU/DaeBuilder-FMI, compiling generated C, `casadi.tools`, MINLP solvers, threads. > Known binding rough edges (candidates for a future pass): `expand=true` > can crash teardown; Callback director numeric eval; ipopt aborting on > NaN trial points.