Introducing CJC-Lang: Why I’m Building a Deterministic Scientific Computing Language

What CJC-Lang is, why it exists, what it can already do, and where it stands today.

CJC-Lang
compilers
scientific-computing
A deep dive into CJC-Lang — a deterministic scientific computing language built from scratch in Rust.
Author

Adam Ezzat

Published

March 21, 2026

Modified

March 22, 2026

I didn’t set out to build a programming language. I set out to solve a problem.

I spent years working as a lab engineer running experiments on audio devices — collecting data, cleaning it, troubleshooting when results didn’t look right. Was the anomaly real, or was it an environment issue? A script issue? A sensor calibration drift? I learned firsthand how messy data — or messy handling of data — introduces problems that cascade through every stage of analysis. A mistake in the initial data cleaning phase doesn’t just cost time. It costs weeks of downstream work, delays shipping, and erodes confidence in the results.

That experience stuck with me as I moved into data science. In most scientific computing environments, running the same program twice can produce slightly different results. Floating-point reduction order changes between runs. Hash maps iterate in random order. Thread scheduling affects accumulation. RNG state leaks across boundaries. These aren’t bugs — they’re properties of systems that weren’t designed with determinism as a constraint.

I wanted a system where the same program with the same seed produces bit-identical output every time, on every platform. Not approximately the same — exactly the same.

That constraint turned out to touch everything: memory layout, collection types, floating-point arithmetic, random number generation, parallelism. The deeper I went, the clearer it became that bolting determinism onto an existing language would mean fighting the language at every turn. So I built one where determinism is structural.


What Is CJC-Lang?

CJC-Lang — Computation Jacobian Core — is a programming language I’m building from scratch for deterministic scientific computing. It’s written in Rust, organized as a 20-crate workspace, with nearly all functionality implemented from scratch using only the Rust standard library.

CJC-Lang is currently in beta. The core compiler pipeline and runtime are functional and backed by 5,300+ passing tests, but the language is still under active development and is not production-ready. I’m sharing it now because the ideas behind it are worth discussing even before everything is polished.

20Rust crates
6.4 MBsingle binary
~300built-in functions
5,300+tests passing

The language covers the full compilation and execution pipeline: a lexer, recursive descent parser, type checker, two intermediate representations (HIR and MIR), an optimizer, two execution engines, and a runtime with ~300 built-in functions for math, statistics, linear algebra, machine learning, signal processing, and data manipulation.


Core Ideas

Determinism as Architecture

CJC-Lang enforces determinism at every layer:

  • Ordered collections everywhere. BTreeMap and BTreeSet instead of hash-based containers. Iteration order is always defined.
  • Stable floating-point reductions. All summations use Kahan or binned accumulators — order-invariant superaccumulators that produce identical results regardless of evaluation order.
  • No FMA in SIMD kernels. Fused multiply-add produces different rounding than separate multiply and add. CJC-Lang avoids FMA to keep results identical across CPU architectures.
  • Seeded, explicit RNG. The random number generator is SplitMix64 with explicit seed threading. No global state, no entropy sources.
  • Deterministic parallelism. Parallel operations are structured so that results don’t depend on thread count or scheduling.

This isn’t a mode you opt into. It’s how the language works. The determinism guarantee is verified in the test suite — for example, the SciML tests train a physics-informed model twice with the same seed and assert that every parameter is bit-identical:

// From tests/sciml_determinism.rs
let r1 = piml_heat_1d_train(6, 20, 30, 0.01, 1000, 1e-3, 1.0, 10.0, 42);
let r2 = piml_heat_1d_train(6, 20, 30, 0.01, 1000, 1e-3, 1.0, 10.0, 42);
for (i, (&a, &b)) in r1.final_params.iter()
    .zip(r2.final_params.iter()).enumerate()
{
    assert_eq!(a.to_bits(), b.to_bits(),
        "Param {} not bit-identical", i);
}

Not approximately equal. Bit-identical. to_bits() compares the raw IEEE 754 representation.

Nearly Zero External Dependencies

The entire system compiles to a 6.4 MB single binary. That one file contains everything: lexer, parser, type checker, two execution engines, optimizer, ~300 builtins, tensor library, linear algebra kernels, regex engine, AD engine, data manipulation DSL, serialization, visualization, and 15+ CLI subcommands.1

This is a deliberate choice. External dependencies introduce version churn, platform variance, and complexity that’s hard to audit. For a system that promises bit-identical results, every component needs to be under direct control.

Dual Execution Engines

CJC-Lang has two execution paths:

  1. AST interpreter (cjc-eval): walks the syntax tree directly. Simple, easy to debug, good for prototyping.
  2. MIR executor (cjc-mir-exec): lowers through HIR → MIR, applies optimization passes (constant folding, dead code elimination), and runs on a register machine.

Both engines must produce identical results for every program. Parity tests enforce this — if the engines disagree, that’s a bug, and the test suite catches it.

NoGC Verification

CJC-Lang includes a static verifier that can prove code paths are free of GC allocations. The NoGC verifier performs transitive call-graph analysis to ensure that annotated functions — and everything they call — operate within reference-counted, copy-on-write memory without triggering GC. This matters for latency-sensitive workloads where GC pauses are unacceptable.


What CJC-Lang Can Do Today

Every example below comes from real files in the repository — demo programs, test fixtures, or integration tests.

Closures and Higher-Order Functions

CJC-Lang supports lambda expressions with capture analysis. The compiler performs lambda lifting during HIR lowering, converting closures into top-level functions with explicit environment parameters:

Lambda with capture, passed as higher-order function
fn apply(f: fn(i64) -> i64, x: i64) -> i64 {
    f(x)
}

fn main() -> i64 {
    let double = |x: i64| x * 2;
    let result: i64 = apply(double, 21);
    print(f"result: {result}");
    result
}

The capture analysis feeds into the NoGC verifier — if a closure captures a reference that would require GC, the verifier flags it.

Structs, Enums, and Pattern Matching

CJC-Lang supports algebraic data types with structural destructuring:

struct Point {
    x: f64,
    y: f64
}

fn distance(p: Any) -> f64 {
    sqrt(p.x * p.x + p.y * p.y)
}

let p = Point { x: 3.0, y: 4.0 };
print(distance(p));

Match expressions support literal patterns, wildcards, and structural destructuring for tuples and structs. There’s also an Option type with Some/None and unwrap/unwrap_or.

Tensors and Linear Algebra

This is where CJC-Lang starts to show its numerical computing focus. Tensors are N-dimensional arrays with zero-copy COW (copy-on-write) buffers:

Matrix multiplication with shape verification
let A = Tensor.from_vec(
    [1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
    [2, 3]
);
let B = Tensor.from_vec(
    [7.0,  8.0,
     9.0,  10.0,
     11.0, 12.0],
    [3, 2]
);

let C = matmul(A, B);
print(C.shape());        // [2, 2]
assert_eq(C.get([0, 0]), 58.0);

The runtime provides the full suite: SVD, QR, Cholesky, eigenvalue decomposition, matrix exponential, Schur decomposition, condition number, and multiple matrix norms. All floating-point reductions go through Kahan or binned summation.

Neural Network Forward Pass

CJC-Lang can express multi-layer neural networks with struct-based layer definitions and a pipe operator (|>) for readable data flow:

struct Layer {
    weights: Tensor,
    bias: Tensor
}

fn forward(layer: Layer, input: Tensor) -> Tensor {
    matmul(input, layer.weights) |> add_bias(layer.bias)
}

The runtime provides built-in ML operations: batch_norm, dropout_mask, sigmoid, relu, leaky_relu, attention, softmax, learning rate schedulers, loss functions, and regularization. I’ll go deeper into the neural network architectures I’ve built and verified in CJC-Lang in the next post.

Gradient Descent and Automatic Differentiation

A simple but complete optimization loop — gradient descent on f(x) = (x - 3)^2:

Manual gradient descent converging to x = 3.0
fn f(x: f64) -> f64 {
    (x - 3.0) * (x - 3.0)
}

fn grad_f(x: f64) -> f64 {
    2.0 * (x - 3.0)
}

let x = 0.0;
let lr = 0.1;
let i = 0;

while i < 100 {
    let g = grad_f(x);
    x = x - lr * g;
    i = i + 1;
}

print("Final x:", x);          // ~3.0
assert(abs(x - 3.0) < 0.001);

For more complex cases, CJC-Lang has a full automatic differentiation engine — forward-mode via dual numbers and reverse-mode via tape-based gradient computation.

Statistics and Distributions

The runtime includes 35+ statistical functions, 10 probability distributions, and 12 hypothesis tests:

let data = [23.1, 25.4, 22.8, 24.5, 26.1, 23.9, 24.2, 25.0, 22.5, 24.8];

let data_t = Tensor.from_vec(data, [10]);
print(data_t.mean());
print(median(data));
print(sd(data));

// Normal distribution
print(normal_cdf(1.96));        // P(Z < 1.96)
print(normal_ppf(0.975));       // quantile function

// Hypothesis testing
let test_result = t_test(data, 24.0);
print(test_result);

Beyond the basics: robust statistics (trimmed_mean, winsorize, mad), rank correlations (spearman_cor, kendall_cor, partial_cor), weighted statistics, and non-parametric tests (wilcoxon_signed_rank, kruskal_wallis, mann_whitney).

Distributions include normal, beta, gamma, exponential, F, Weibull, Poisson, binomial, chi-squared, and Student’s t — each with PDF/PMF, CDF, and where applicable, quantile functions.

Data Manipulation

CJC-Lang includes a tidyverse-inspired data DSL with 73+ operations:

let csv_data = "name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,NYC\n";
let df = Csv.parse(csv_data);
let view = df.view();

// Filter and select
let pred = dexpr_binop(">", col("age"), 28);
let filtered = view.filter(pred);
let selected = filtered.select(["name", "city"]);

The data layer supports typed columns, joins (inner, left, right, anti, semi), pivoting, window functions, and lazy evaluation with COW semantics.


Beyond Neural Networks

CJC-Lang isn’t just a machine learning language. The numerical infrastructure supports a broader range of scientific computing — and this is where the determinism guarantee pays the biggest dividends. When you’re debugging a failed ODE integration or a clustering result that doesn’t match your colleague’s run, knowing the computation is reproducible eliminates an entire class of “was it the code or was it the environment?” questions.

Domain Key Capabilities
ODE/PDE Solvers Euler, RK4, adaptive RK45, 1D Laplacian, diffusion stepping, adjoint sensitivity
Optimization Gradient descent, BFGS, L-BFGS, Nelder-Mead, bisection, Brent, Newton, secant
Regression OLS, weighted least squares, logistic regression, polynomial fitting
Clustering K-means (k-means++ init), DBSCAN, agglomerative (4 linkage methods), PCA
Time Series ACF, PACF, EWMA, seasonal decomposition, stationarity tests (ADF, KPSS, Phillips-Perron)
Interpolation Cubic natural splines, linear, nearest-neighbor, Horner polynomial evaluation
Adaptive RK45 integration with step size control
let result = ode_solve_rk45(
    |t: f64, y: f64| -0.5 * y,   // dy/dt = -0.5y (exponential decay)
    0.0,                           // t_start
    10.0,                          // t_end
    1.0,                           // y_initial
    1e-6                           // tolerance
);
print("Final y:", result);         // ~0.00674 (matches exp(-5))

What’s Native — and Why That Matters

Everything mentioned above runs inside a single binary with nearly zero external dependencies. Every component is under direct control, which means determinism guarantees extend through the entire stack — from CSV parsing through model training through serialization.

Component What It Does Why Native Matters
Regex (cjc-regex) Thompson NFA engine — O(n) linear time, no backtracking Eliminates ReDoS vulnerabilities and supply chain risk
RNG (SplitMix64) Seeded, explicit state, fork() for parallel streams Same seed = same output on every platform
Visualization (cjc-vizor) Grammar-of-graphics with SVG/BMP output No subprocess, no JS engine, no network calls
DataFrame (cjc-data) 73+ tidyverse-style ops, lazy eval, typed columns Grouping and aggregation inherit determinism automatically
Serialization (cjc-snap) Canonical binary format with SHA-256 content hashing Checkpoints are exact; snap_hash() enables verification across runs
Accumulators (cjc-repro) Kahan compensated and binned superaccumulators Order-invariant reductions — the mechanism behind bit-identical floating-point results

No pip install. No npm install. No version conflicts. Just the binary, the source file, and the seed.


The CLI Toolchain

CJC-Lang ships with 15+ CLI subcommands:

cjcl run program.cjcl              # execute a program
cjcl run program.cjcl --seed 42    # explicit RNG seed
cjcl run program.cjcl --mir-opt    # MIR-optimized execution
cjcl check program.cjcl            # type-check without running
cjcl repl                         # interactive REPL
cjcl trace program.cjcl            # execution tracing
cjcl bench program.cjcl            # benchmarking
cjcl doctor                       # health/validation checks

Additional commands include inspect, schema, flow (control flow graphs), drift (determinism drift detection), mem (memory profiling), pack (binary serialization), and forge (code generation).


Why CJC-Lang Is Still in Beta

The core is solid: the compiler pipeline works end-to-end, both execution engines agree, the test suite is extensive, and the runtime covers a wide surface area. But several areas need more work:

  • Type inference is limited — most types require explicit annotation
  • Generics are parsed but not fully enforced at the type level
  • The module system exists but needs refinement for multi-file projects
  • REPL lacks readline support
  • Optimizer has room for more passes beyond constant folding and DCE
  • Documentation is sparse outside of test coverage

I’m not rushing to call this production-ready. The point is to build something correct first, then make it convenient.

What Comes Next

The near-term roadmap includes:

  • If-expressions: converting if from a statement to an expression (let x = if cond { a } else { b })
  • Default parameters and variadic functions: making function signatures more flexible
  • MIR-integrated autodiff: ensuring gradient computation works through the optimized execution path
  • Module system improvements: reliable multi-file program support
  • Sparse linear algebra: eigenvalue solvers (Lanczos, Arnoldi) with deterministic iteration

Why Share This Now?

CJC-Lang isn’t finished, but the ideas behind it are clear enough to discuss. Deterministic scientific computing is a real problem — I’ve seen it firsthand, from the lab bench to the data pipeline. Building the entire stack from scratch is an unusual approach, but the results so far suggest it’s viable.

If you’re interested in compiler design, numerical methods, reproducible computing, or just the experience of building a language from the ground up, I’ll be writing more here as the project develops.

Next up: I’ll walk through the neural network architectures I’ve built and verified in CJC-Lang — REINFORCE agents, physics-informed networks, CNNs, and attention mechanisms, all running deterministically without any ML framework.

Follow the project

CJC-Lang is open source and under active development. The project is in beta — functional and tested, but not yet production-ready. Follow @adamezzat for updates as new features land.

Footnotes

  1. The only external dependencies are in the optional language server (cjc-analyzer) and the rayon crate for parallel tensor operations. The core 18 crates have zero external dependencies.↩︎