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
Tensorarithmetic 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 |