Skip to content

About

MIT-licensed standalone CasADi reader for JavaScript, Python, C++, C, MATLAB and Julia

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@casadi/casadi-reader

Read CasADi serialization into typed fields, values, containers and shared references, without installing CasADi or loading its plugins.

The reader does not interpret SX/MX instructions, name mathematical operations, reconstruct entry mappings, or build visualization graphs. Those tasks belong in casadi-viz. An ONNX-backed function is read as serialized configuration and model bytes; the reader never runs ONNX.

JavaScript, Python, C, C++, MATLAB and Julia expose the same structural document contract. Each implementation is native to its language and generated by the same Python tool. CI builds and tests packages for all six languages.

Development happens on generate. CI generates and tests all six readers, then commits the complete, reproducible snapshot to main. Generated source is committed on main, not on generate. generation.json identifies the source commit. Release workflows and ZIP packaging are maintained on generate and included in the generated snapshot.

API

import {decode, open} from '@casadi/casadi-reader';

const document = decode(serializedText);
const fromFile = await open(file, {lazy: true}); // browser File/Blob
const root = document.objects[document.root];
console.log(root.type, root.fields);

decode() accepts encoded .casadi text. open() accepts a File/Blob. decodeCasadi remains an alias for decode; both return structural documents.

The output is ordinary JavaScript data:

{
  format: 'casadi_serialization',
  version: 1,
  serializationProtocol: 3,
  root: 8,
  roots: [{$ref: 8}],
  objects: [
    // ... shared objects ...
    {
      type: 'Function',
      fields: [
        {name: 'Function::null', type: 'bool', value: false},
        // Serialized field order and duplicate field names are preserved.
      ],
      layouts: ['MXFunction::serialize_body', /* base layouts ... */]
    }
  ]
}

The example's indices are illustrative. $ref values are zero-based indices into objects. Shared definitions appear once, even when referenced by several functions. Inline structures have their own type and fields. Vectors are arrays, pairs are two-element arrays, and maps are {$map: [[key, value], ...]} so arbitrary key types and field order survive. Repeated serializer fields remain repeated entries, not overwritten properties. 64-bit integers outside JavaScript's exact range use {$integer: "..."}; nonfinite floating-point values use {$float: "..."}. Records and fields contain no source byte ranges.

A file can contain several roots; roots retains them in order. root is a convenience index for a single shared-object root, otherwise null.

Lazy bytes

const document = await open(resourceFile, {type: 'Resource', lazy: true});
const root = document.objects[document.root];
const blob = root.fields.find(f => f.name === 'ZipMemResource::blob').value;
const firstBytes = await blob.read(0, 64);

type selects a raw SerializingStream root instead of FileSerializer framing. Resource streams use the same archive representation embedded inside FMUs; complete saved FMU functions are not yet a validated layout family.

Lazy byte handles expose offset, byteLength and read(offset, length). Text-input reads are synchronous; File/Blob reads are asynchronous. JSON output contains a small descriptor and requires the original source to retrieve bytes.

Opaque streams are deferred in lazy mode. Large serialized strings are also deferred (default lazyThreshold: 65536 bytes). Smaller strings are decoded as UTF-8 where possible; other strings retain their bytes as {$bytes: [...]} in eager mode or a lazy byte handle in lazy mode. No archive extraction occurs.

Lazy File/Blob opening loads metadata pages and skips payload pages by their declared lengths. It requires unpadded encoded files, as emitted by CasADi. Text input accepts surrounding whitespace but necessarily retains the supplied encoded string. Graph fields and containers are currently eager; lazy mode specifically concerns opaque byte payloads.

Browser and CLI

Published packages contain bundled browser ESM with generated reading functions. They do not fetch or parse the source scheme JSON at runtime. A browser can import a pinned https://unpkg.com/@casadi/casadi-reader@VERSION/dist/index.js from a module script without a bundler or an import map.

npx @casadi/casadi-reader model.casadi model.json
# Or, for a raw Resource stream:
npx @casadi/casadi-reader --type Resource --lazy resource.casadi

There are no runtime dependencies. Building the npm package needs Node 22+, Python 3.9+ and the development dependencies in package-lock.json.

Scheme and coverage

CasADi's misc/generate_serialization_scheme.py produces the vendored schemes/serialization_scheme.json: decoding rules and validation metadata, without source bodies, pack expressions, source locations or extraction offsets. npm run generate runs one Python generator for all six languages. Each layout becomes a reading function. Fields become direct primitive or object reads; conditions, repetitions and discriminators become native control flow. Calls to other layouts remain function calls, so shared layouts are not expanded at each call site. The emitted code grows proportionally to the scheme.

The common compiler traversal lives in scripts/codegen/base.py; the language backends alongside it emit syntax. scripts/reader_generators.py combines that output with small runtime templates from scripts/templates. Those templates handle binary primitives, buffers, reference bookkeeping and structural records. No runtime interprets scheme instructions, predicates or type expressions, and no reader needs the generator or the source scheme installed.

Use --scheme PATH --output-root DIR with scripts/generate-reader-assets.py to generate readers for another extracted scheme without changing this checkout. Custom schemes are a build-time input; decoding does not accept runtime scheme injection.

The extractor includes inline serializers, inheritance and tensor metadata helpers. Inherited serializers and template aliases are explicit layouts with call instructions; readers perform no C++ inheritance lookup. It derives operation dispatch families from CasADi's native dispatcher and plugin registrations from the source. No mathematical evaluation occurs.

Coverage is still experimental. Native plain/debug fixture pairs validate MX, SX, nested calls, mappings, constants, ONNX-backed functions and Resource streams. The debug files independently check serialized field names and primitive tags; ordinary undecorated files use the same layouts. Tests also exercise a new function discriminator added to the scheme, followed by regeneration and execution of all six implementations on ordinary and debug input.

This is not yet a guarantee that every CasADi class/version can be read. Unsupported lowering, missing field types, absent layouts and unknown discriminators fail explicitly. Current fixtures target the CasADi 3.8.1 source snapshot, little-endian protocol 3. Expanding coverage belongs in the scheme extractor and fixtures, followed by regeneration of the readers.

npm ci
npm run generate
npm run check:generated
npm test
npm run test:browser
npm pack

CI checks deterministic generation and tests the emitted assets before updating main. Browser tests serve the extracted npm tarball over HTTP and verify one JavaScript request with no scheme JSON or CasADi runtime request. Native CasADi is used only when regenerating the fixtures, via scripts/generate-fixtures.py and the Resource fixture generator. Julia additionally ships a broad corpus generated by scripts/generate-julia-fixtures.py, with JSON references checked by ordinary Pkg.test(). CI measures runtime and generated-decoder coverage and uploads it to Codecov; see Julia tests and coverage.

Other languages

Python is dependency-free: PYTHONPATH=python python3 -m casadi_reader model.casadi. casadi_reader.read_casadi(path) returns the same typed records as JavaScript; to_json(document) makes them portable to the viewer. Use lazy=True for byte handles and keep their source open while reading them.

C and C++ have independent implementations in c/ and cpp/. Build them with cmake -S . -B build, cmake --build build, and ctest --test-dir build. The separate source archives require only their respective compiler. Platform archives include the library, CLI and headers.

Install the pure Python reader with pip install casadi-reader.

Julia is a native implementation requiring only Julia’s standard library:

using Pkg
Pkg.add(url="https://github.com/casadi/casadi-reader", subdir="julia", rev="main")
using CasADiReader
record = read_casadi("function.casadi")

For a standalone download, extract casadi-reader-julia.zip and call push!(LOAD_PATH, "/path/to/casadi-reader-julia") before using CasADiReader. No package-manager call or dependency download is needed.

MATLAB is implemented in .m files. Extract the MATLAB ZIP, add its directory to the MATLAB path, and call casadi_reader.read('function.casadi'). It needs no MEX file, compiler, shared library, Python, or CasADi runtime. Use casadi_reader.Document(path, '', true) for lazy byte payloads.

The generator consumes only the current prototype scheme format. There is no compatibility layer for earlier incarnations. CasADi resolves template types and member references; this project compiles those explicit layouts into code.

CI compares all languages against the same ordinary/debug fixtures, tests installed Python wheels and sdists, and tests the standalone Julia/MATLAB archives. C and C++ are built and tested separately on Linux, macOS and Windows.

Standalone downloads

CI attaches these ZIPs to each release:

  • casadi-reader-python.zip: extract and add its directory to PYTHONPATH; run python -m casadi_reader model.casadi. No pip installation is needed.
  • casadi-reader-javascript.zip: import dist/index.js directly, or run node bin/casadi-reader.js model.casadi. No npm installation is needed.
  • casadi-reader-julia.zip: add the extracted directory to Julia's LOAD_PATH.
  • casadi-reader-matlab.zip: add the extracted directory to the MATLAB path.
  • casadi-reader-c.zip and casadi-reader-cpp.zip: independent source packages.
  • casadi-reader-c-PLATFORM.zip and casadi-reader-cpp-PLATFORM.zip: built command-line tools, shared libraries and headers for each supported platform.

Python wheels/sdists and npm publication remain available alongside these ZIPs.

Publishing

publish.yml is dispatched by TagBot at the shared vX.Y.Z tag after Julia registration merges. Before building or publishing, it verifies that all language versions match and that the tagged Julia source tree matches the version registered in General. All language releases therefore wait for Julia registration.

The npm Trusted Publisher configuration is GitHub organization casadi, repository casadi-reader, workflow publish.yml, with no environment name and with direct npm publish allowed. Publishing uses OIDC and provenance, with no npm token secret. See npm's documentation.

The reader implementation is MIT licensed. The vendored scheme is derived from CasADi; upstream attribution and license texts are retained in NOTICE and LICENSES.

Python and other language releases

The publish.yml workflow builds every distribution in CI. It publishes the Python wheel and sdist with PyPI Trusted Publishing using environment pypi, and attaches Julia, MATLAB, C and C++ packages plus checksums to the GitHub release. The PyPI publisher identifies owner casadi, repository casadi-reader, workflow publish.yml, and environment pypi.

All language metadata must match package.json; scripts/check-version.py checks this before publication. scripts/package-bindings.py builds source archives without requiring CasADi or a native compiler.

Julia registration and tags

The Julia package is named CasADiReader. All languages share one version and one vX.Y.Z tag. Existing tags stay unchanged; the next release is v0.2.3.

Bump all language versions on generate and let CI produce the tested, generated snapshot on main. Request registration on that exact main commit with @JuliaRegistrator register subdir=julia (or use JuliaHub's Registrator with subdirectory julia). Register the renamed package as version 0.2.3; the pending registration under the old name is replaced by a new registry PR.

After registration merges, TagBot.yml creates the shared tag and GitHub release. Do not create the tag beforehand. TagBot dispatches publish.yml at that tag, which tests and publishes the other language packages and attaches the release assets. New packages normally wait three days in General; subsequent versions normally wait 15 minutes, provided the registration checks pass and there are no blockers.

TagBot uses GITHUB_TOKEN and explicitly dispatches publishing because releases created with that token do not trigger another release workflow. No extra deploy key or personal token is needed. To retry publishing, manually dispatch publish.yml at the release tag; its registration gate still applies. Publishing also runs for manually published GitHub releases, with the same gate.

About

MIT-licensed standalone CasADi reader for JavaScript, Python, C++, C, MATLAB and Julia

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages