Start

Calling from C#

The generated classes, the value types they take, and how a formula decides where it runs.

Every .hb file becomes one public static class named after the file, in the project’s namespace. Every fn becomes a set of PascalCase methods on it: fn toRadians(a) in Angles.hb is Angles.ToRadians(...). Definitions (def) never appear in C#. Everything below the doorway is ordinary .NET — the only package you reference at run time is MLambda.Hilbert.Runtime.

The value types

Hilbert value C# type How to make one
real double 2.0; int and float widen
complex System.Numerics.Complex new Complex(1, 2)
vector MLambda.Hilbert.Runtime.Vector new[] { 1d, 2d }, a List<double>, Vector.Of([1d, 2d])
matrix MLambda.Hilbert.Runtime.Matrix new double[,] { { 1, 2 }, { 3, 4 } } or a jagged double[][]
tensor MLambda.Hilbert.Runtime.Tensor any array (new double[,,] { … }), Tensor.Of(new Shape(2, 3), values), Tensor.Filled(shape, v)

Vector and Matrix are tensors (rank 1 and rank 2). A Tensor exposes Shape (with Dims, Rank and Count) and Values, the elements flattened in row-major order. Tensors support the elementwise operators + − · / with broadcasting, so small bits of glue code stay short.

One formula, many overloads

For fn sum(x, y) ↦ x + y the compiler generates:

double  Sum(double x,  double y)
Complex Sum(Complex x, Complex y)
Vector  Sum(Vector x,  Vector y)     Vector Sum(double x, Vector y)     Vector Sum(Vector x, double y)
Matrix  Sum(Matrix x,  Matrix y)     Matrix Sum(double x, Matrix y)     Matrix Sum(Matrix x, double y)
Tensor  Sum(Tensor x,  Tensor y)     Tensor Sum(double x, Tensor y)     Tensor Sum(Tensor x, double y)

The real overload is plain C# arithmetic. The Complex overload is generated only when every operation admits complex numbers (min, max and abs do not). Every shaped overload runs the formula’s ONNX graph, which the generated class also exposes as a value — Angles.SumGraph — that you can inspect or print.

A formula that is tensor-valued by nature — it calls einsum, reads rows(x), or names a tensor literal — gets a single Tensor overload:

Tensor mean = Summaries.Average(new[] { 1d, 2d, 3d, 4d });   // 2.5, a rank-0 tensor
double m = mean.Values[0];

Formulas that change rank: pass tensors. When a formula maps vectors to a matrix (an outer product, einsum("i,j->ij", u, v)) or a matrix to a vector (einsum("ij->j", a)), the Vector and Matrix overloads cannot hold the result and throw InvalidCastException. Call the Tensor overload by typing the arguments as Tensor:

var outer = Contractions.OuterOf((Tensor)new[] { 1d, 2d }, (Tensor)new[] { 3d, 4d, 5d });
// shape [2, 3], values [3, 4, 5, 6, 8, 10]

Where a formula runs

A shaped overload calls Programs.Run(graph, inputs). The runtime has two executors for the same graph:

  • Host — an interpreter over Tensor arithmetic on the calling thread. No start-up cost; best for small inputs.
  • Onnx — ONNX Runtime, with the session cached per graph. Pays off on large inputs.

By default (Executor.Auto) the choice is made by the number of elements in the inputs: below the crossover the host runs the graph, above it ONNX Runtime does. The crossover is measured once per machine on first use (the default when it cannot measure is 4,096 elements).

using MLambda.Hilbert.Runtime;

Programs.Executor = Executor.Host;      // always interpret on this thread
Programs.Executor = Executor.Onnx;      // always use ONNX Runtime
Programs.Executor = Executor.Auto;      // decide by volume (default)

Programs.Crossover = 10_000;            // pin the crossover in code
Console.WriteLine(Programs.Chosen(1_000_000));   // which executor would run 1M elements

The environment variable HILBERT_ONNX_ABOVE pins the crossover without code (a negative value turns ONNX Runtime off). Both executors run the same graph and give the same answers; one exception is noted in einsum in practice: the host interpreter does not take a diagonal with a repeated index such as "ii->".

Templates

A doorway with free variables returns a template — a formula specialised at run time:

fn line(a, b) ↦ x ↦ a + b · x
fn wave(a, b) ↦ x ↦ a + b · cos(x)
Template wave = Families.Wave(1, 3);         // graph "wave(1, 3)", 5 nodes
double atZero = wave.At(0);                  // 4

Func<double, double> line = Families.Line(0, 1);   // reduces to one Identity node
double y = line(7);                          // 7

double direct = Families.Line(2, 3, 10);     // 32 — the fully applied overload

Binding substitutes the coefficients into the graph as constants, folds what can be folded, removes identities such as · 1 and + 0, and caches the result per set of arguments, so calling Families.Wave(1, 3) in a loop specialises once. Coefficients may be tensors as well as reals. Template.At(double) returns a number and refuses a program that yields more than one; At(Tensor) returns the tensor.

Models, processes and frames

A model, process or frame declaration compiles to a sealed class with a constructor, properties for its weights and methods for its members — see Models and processes.

Diagnostics

The compiler reports problems as build errors in the form File.hb(line): error HB0001: …, and writes nothing when any file fails. The ones customers meet most:

Code / message Cause Fix
HB0001 'x' is already declared in File.hb a name you declared exists in an opened module rename your definition
expected an expression, found the end of the line a def body wrapped onto a second line keep it on one line; name sub-terms
a parameter named pi or e these are constants choose another name
operand 0 has rank 0 from einsum a scalar reached a contraction pass a vector/matrix; build zeros as shaped values