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:
sequant::ItfGeneratorgenerates ITF (Integrated Tensor Framework) code, a domain-specific tensor-contraction language used by parts of the Molpro quantum chemistry program.sequant::PythonEinsumGeneratorBaseand its NumPy/PyTorch specializations generate Python code usingeinsum-style tensor contractions.sequant::JuliaTensorOperationsGenerator,sequant::JuliaITensorGenerator, andsequant::JuliaTensorKitGeneratorgenerate Julia code targeting, respectively, the TensorOperations.jl, ITensors.jl, and TensorKit.jl packages.
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).