Code Generation

Once an equation has been derived, simplified, and optimized, the final step is turning it into code that a numerical tensor library or quantum-chemistry program can actually run. SeQuant’s export framework (SeQuant/core/export) does this by walking a binarized evaluation tree and driving a pluggable code-generation backend, so adding support for a new target language means implementing one small interface rather than writing a new tree-walker.

The Generator interface

A backend is a subclass of sequant::Generator: a set of callbacks (create, load, compute, unload, declare, …) that get invoked, in the right order, while sequant::export_expression() traverses a ResultExpr’s evaluation tree. Writing a new backend means implementing this callback interface; the tree-walking, scalar-factor bookkeeping, and intermediate-reuse logic are handled once, centrally, for every backend — documented for contributors in the developer guide. sequant::TextGenerator is a minimal, dependency-free backend that renders these callbacks as human-readable pseudocode — useful both as documentation of the callback sequence and as a debugging aid for a real backend under development:

// a small tensor equation: R^{a_1}_{i_1} = f^{a_1}_{i_1} + t^{a_1}_{i_1}
auto result = ResultExpr(Tensor(L"R", bra{L"i_1"}, ket{L"a_1"}),
                         ex<Tensor>(L"f", bra{L"i_1"}, ket{L"a_1"}) +
                             ex<Tensor>(L"t", bra{L"i_1"}, ket{L"a_1"}));

// export_expression() walks a binarized evaluation tree and invokes the
// matching Generator callback (create/load/compute/unload/...) for every
// step; TextGenerator is a dependency-free backend that renders those
// callbacks as human-readable pseudocode -- useful for prototyping a new
// Generator subclass, or for debugging what a real backend would emit
TextGenerator<TextGeneratorContext> generator;
TextGeneratorContext ctx;
export_expression(to_export_tree(result), generator, ctx);

std::cout << generator.get_generated_code() << std::endl;

Available backends

Beyond TextGenerator, SeQuant ships several backends targeting real numerical tensor libraries and domain-specific formats:

Element-wise tensor powers

An evaluation tree may contain the element-wise power of a tensor (a sequant::TensorPower, e.g. an orbital-energy denominator raised to -1). The text, Python (NumPy and PyTorch einsum) and Julia TensorOperations backends export it, spelling the exponent as follows; the operand is permuted to the result’s index order where the two differ, and a scalar prefactor, if any, is written in front of the power:

Compute R[i_1, a_1] += 2 D[a_1, i_1]^(-1)                                  (text)
R_ov += 2 * np.einsum('ai->ia', D_vo, optimize=True)**(-1)                 (Python)
R_ov .+= 2 * permutedims(D_vo, (2, 1)) .^ (-1)                             (Julia TensorOperations)

Julia spells a rational exponent as, e.g., (1//2). sequant::ItfGenerator (“ITF has no element-wise tensor power”), sequant::JuliaITensorGenerator and sequant::JuliaTensorKitGenerator (“<format> cannot express an element-wise tensor power”) have no way to express it and throw a sequant::Exception instead. A custom backend opts in by overriding sequant::Generator::represent() for sequant::TensorPower; the default throws.

Broadcast sums

The evaluation tree of a broadcast sum under a ResultExpr head (see The Expression Tree) contains sequant::EvalOp::Broadcast nodes, each adding one summand to the result over the head modes it lacks. The text, Python (NumPy and PyTorch einsum) and Julia TensorOperations backends export them; e.g., for D{i_1;a_1} = ε{;a_1} - ε{i_1;}:

Compute D[i_1, a_1] += ε[a_1] (broadcast over i_1)                         (text)
D_ov += np.einsum('a->a', ε_v, optimize=True)[None, :]                     (Python)
D_ov .+= reshape(ε_v, (1, nv))                                             (Julia TensorOperations)

Python inserts None and Julia a dimension of 1 at each mode the summand lacks, after permuting the summand’s modes to their order in the result. A scalar summand is added as is (D_ov += -1 * E), relying on the target language’s broadcasting. sequant::ItfGenerator, sequant::JuliaITensorGenerator and sequant::JuliaTensorKitGenerator throw a sequant::Exception (“<format> cannot broadcast a summand”). A custom backend opts in by overriding sequant::Generator::broadcast_add(); the default throws.

Each backend has its own Context type (e.g. ItfContext) for backend-specific settings such as naming conventions; consult its reference documentation for details. Numerically evaluating an expression directly in-process, rather than generating source code for it, is a separate concern handled by SeQuant/core/eval (see Evaluation Trace Analysis for its trace/profiling tooling).