• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / docc / 28793626242

06 Jul 2026 01:04PM UTC coverage: 62.962% (+0.2%) from 62.801%
28793626242

Pull #740

github

web-flow
Merge b7e03d389 into 23e67a4ab
Pull Request #740: Expand pass

594 of 962 new or added lines in 31 files covered. (61.75%)

36 existing lines in 10 files now uncovered.

40557 of 64415 relevant lines covered (62.96%)

972.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.62
/sdfg/src/data_flow/library_nodes/math/tensor/einsum_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/einsum_node.h"
2

3
#include <cstddef>
4
#include <memory>
5
#include <nlohmann/json_fwd.hpp>
6
#include <sstream>
7
#include <string>
8
#include <unordered_map>
9
#include <unordered_set>
10
#include <vector>
11

12
#include "sdfg/analysis/analysis.h"
13
#include "sdfg/builder/structured_sdfg_builder.h"
14
#include "sdfg/data_flow/access_node.h"
15
#include "sdfg/data_flow/data_flow_graph.h"
16
#include "sdfg/data_flow/data_flow_node.h"
17
#include "sdfg/data_flow/library_node.h"
18
#include "sdfg/data_flow/library_nodes/math/math_node.h"
19
#include "sdfg/data_flow/memlet.h"
20
#include "sdfg/data_flow/tasklet.h"
21
#include "sdfg/element.h"
22
#include "sdfg/exceptions.h"
23
#include "sdfg/function.h"
24
#include "sdfg/graph/graph.h"
25
#include "sdfg/serializer/json_serializer.h"
26
#include "sdfg/structured_control_flow/block.h"
27
#include "sdfg/structured_control_flow/control_flow_node.h"
28
#include "sdfg/structured_control_flow/map.h"
29
#include "sdfg/structured_control_flow/sequence.h"
30
#include "sdfg/structured_control_flow/structured_loop.h"
31
#include "sdfg/symbolic/symbolic.h"
32
#include "sdfg/types/scalar.h"
33
#include "sdfg/types/type.h"
34
#include "symengine/symbol.h"
35

36
namespace sdfg {
37
namespace math {
38
namespace tensor {
39

40
EinsumNode::EinsumNode(
41
    size_t element_id,
42
    const DebugInfo& debug_info,
43
    const graph::Vertex vertex,
44
    data_flow::DataFlowGraph& parent,
45
    const std::vector<std::string>& inputs,
46
    const std::vector<EinsumDimension>& dims,
47
    const data_flow::Subset& out_indices,
48
    const std::vector<data_flow::Subset>& in_indices,
49
    bool rename_indvars
50
)
51
    : math::MathNode(
54✔
52
          element_id,
54✔
53
          debug_info,
54✔
54
          vertex,
54✔
55
          parent,
54✔
56
          LibraryNodeType_Einsum,
54✔
57
          {"__einsum_out"},
54✔
58
          inputs,
54✔
59
          data_flow::ImplementationType_NONE
54✔
60
      ),
54✔
61
      dims_(dims), out_indices_(out_indices), in_indices_(in_indices) {
54✔
62
    // Check list sizes
63
    if (inputs.size() != in_indices.size()) {
54✔
64
        throw InvalidSDFGException("EinsumNode: Number of input containers != number of input indices");
×
65
    }
×
66

67
    // Rename indvars to internal symbols (only for fresh construction, not clone/deserialize)
68
    if (rename_indvars) {
54✔
69
        // Build mapping from original indvars to internal symbols
70
        // Format: _einsum_node_{element_id}_{original_indvar_name}
71
        std::string prefix = "_einsum_node_" + std::to_string(element_id) + "_";
40✔
72
        std::vector<std::pair<symbolic::Symbol, symbolic::Symbol>> indvar_renames;
40✔
73
        for (const auto& dim : this->dims_) {
40✔
74
            auto old_indvar = dim.indvar;
22✔
75
            auto old_name = SymEngine::rcp_static_cast<const SymEngine::Symbol>(old_indvar)->get_name();
22✔
76
            auto new_indvar = symbolic::symbol(prefix + old_name);
22✔
77
            indvar_renames.push_back({old_indvar, new_indvar});
22✔
78
        }
22✔
79

80
        // Apply all substitutions
81
        for (size_t idx = 0; idx < indvar_renames.size(); idx++) {
62✔
82
            auto old_indvar = indvar_renames[idx].first;
22✔
83
            auto new_indvar = indvar_renames[idx].second;
22✔
84

85
            // Replace in all dims' init, bound, and indvar
86
            for (auto& d : this->dims_) {
56✔
87
                if (symbolic::eq(d.indvar, old_indvar)) {
56✔
88
                    d.indvar = new_indvar;
22✔
89
                }
22✔
90
                d.init = symbolic::subs(d.init, old_indvar, new_indvar);
56✔
91
                d.bound = symbolic::subs(d.bound, old_indvar, new_indvar);
56✔
92
            }
56✔
93

94
            // Replace in out_indices
95
            for (size_t i = 0; i < this->out_indices_.size(); i++) {
57✔
96
                this->out_indices_[i] = symbolic::subs(this->out_indices_[i], old_indvar, new_indvar);
35✔
97
            }
35✔
98

99
            // Replace in in_indices
100
            for (size_t i = 0; i < this->in_indices_.size(); i++) {
62✔
101
                for (size_t j = 0; j < this->in_indices_[i].size(); j++) {
111✔
102
                    this->in_indices_[i][j] = symbolic::subs(this->in_indices_[i][j], old_indvar, new_indvar);
71✔
103
                }
71✔
104
            }
40✔
105
        }
22✔
106
    }
40✔
107

108
    // Append output at the end
109
    this->inputs_.push_back("__einsum_out");
54✔
110
    this->in_indices_.push_back(this->out_indices_);
54✔
111
}
54✔
112

113
const std::vector<EinsumDimension>& EinsumNode::dims() const { return this->dims_; }
165✔
114

115
const EinsumDimension& EinsumNode::dim(size_t index) const { return this->dims_.at(index); }
7✔
116

117
const symbolic::Symbol& EinsumNode::indvar(size_t index) const { return this->dims_.at(index).indvar; }
127✔
118

119
const symbolic::Expression& EinsumNode::init(size_t index) const { return this->dims_.at(index).init; }
40✔
120

121
const symbolic::Expression& EinsumNode::bound(size_t index) const { return this->dims_.at(index).bound; }
58✔
122

123
const data_flow::Subset& EinsumNode::out_indices() const { return this->out_indices_; }
137✔
124

125
const symbolic::Expression& EinsumNode::out_index(size_t index) const { return this->out_indices_.at(index); }
37✔
126

127
const std::vector<data_flow::Subset>& EinsumNode::in_indices() const { return this->in_indices_; }
77✔
128

129
const data_flow::Subset& EinsumNode::in_indices(size_t index) const { return this->in_indices_.at(index); }
96✔
130

131
const symbolic::Expression& EinsumNode::in_index(size_t index1, size_t index2) const {
82✔
132
    return this->in_indices_.at(index1).at(index2);
82✔
133
}
82✔
134

135
symbolic::SymbolSet EinsumNode::internal_symbols() const {
4✔
136
    symbolic::SymbolSet result;
4✔
137
    for (auto& dim : this->dims()) {
9✔
138
        result.insert(dim.indvar);
9✔
139
    }
9✔
140
    return result;
4✔
141
}
4✔
142

143
passes::LibNodeExpander::ExpandOutcome EinsumNode::
NEW
144
    expand(passes::LibNodeExpander::ExpandContext& context, structured_control_flow::Block& block) {
×
NEW
145
    return context.unapplicable();
×
NEW
146
}
×
147

148
bool EinsumNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
6✔
149
    // Get data flow graph and block
150
    auto& dfg = this->get_parent();
6✔
151
    auto* block = dynamic_cast<structured_control_flow::Block*>(dfg.get_parent());
6✔
152
    if (!block) {
6✔
153
        return false;
×
154
    }
×
155

156
    // Get parent sequence
157
    auto* sequence = dynamic_cast<structured_control_flow::Sequence*>(block->get_parent());
6✔
158
    if (!sequence) {
6✔
159
        return false;
×
160
    }
×
161

162
    // Create block after this block
163
    auto& block_after = builder.add_block_after(*sequence, *block, {}, block->debug_info());
6✔
164

165
    // Collect and transfer nodes after the EinsumNode
166
    bool before = true;
6✔
167
    std::unordered_map<data_flow::DataFlowNode*, data_flow::DataFlowNode*> nodes_after;
6✔
168
    for (auto* node : dfg.topological_sort()) {
34✔
169
        if (before) {
34✔
170
            if (node == this) {
23✔
171
                before = false;
6✔
172
            }
6✔
173
            continue;
23✔
174
        }
23✔
175
        data_flow::DataFlowNode* node_after = nullptr;
11✔
176
        if (auto* constant_node = dynamic_cast<data_flow::ConstantNode*>(node)) {
11✔
177
            node_after =
×
178
                &builder
×
179
                     .add_constant(block_after, constant_node->data(), constant_node->type(), constant_node->debug_info());
×
180
        } else if (auto* access_node = dynamic_cast<data_flow::AccessNode*>(node)) {
11✔
181
            if (dfg.out_degree(*access_node) == 0 && dfg.in_degree(*access_node) == 1 &&
9✔
182
                &(*dfg.in_edges(*access_node).begin()).src() == this) {
9✔
183
                continue;
5✔
184
            }
5✔
185
            node_after = &builder.add_access(block_after, access_node->data(), access_node->debug_info());
4✔
186
        } else if (auto* code_node = dynamic_cast<data_flow::CodeNode*>(node)) {
4✔
187
            node_after = &builder.copy_node(block_after, *code_node);
2✔
188
        } else {
2✔
189
            return false;
×
190
        }
×
191
        nodes_after.insert({node, node_after});
6✔
192
        if (dynamic_cast<data_flow::Tasklet*>(node) || dynamic_cast<data_flow::LibraryNode*>(node)) {
6✔
193
            for (auto& iedge : dfg.in_edges(*node)) {
3✔
194
                if (!nodes_after.contains(&iedge.src())) {
3✔
195
                    if (auto* constant_node = dynamic_cast<data_flow::ConstantNode*>(&iedge.src())) {
×
196
                        nodes_after.insert(
×
197
                            {constant_node,
×
198
                             &builder.add_constant(
×
199
                                 block_after, constant_node->data(), constant_node->type(), constant_node->debug_info()
×
200
                             )}
×
201
                        );
×
202
                    } else if (auto* access_node = dynamic_cast<data_flow::AccessNode*>(&iedge.src())) {
×
203
                        nodes_after.insert(
×
204
                            {access_node,
×
205
                             &builder.add_access(block_after, access_node->data(), access_node->debug_info())}
×
206
                        );
×
207
                    } else {
×
208
                        return false;
×
209
                    }
×
210
                }
×
211
            }
3✔
212
        }
2✔
213
    }
6✔
214

215
    // Transfer memlets after the EinsumNode
216
    for (auto& edge : dfg.edges()) {
28✔
217
        if (!nodes_after.contains(&edge.src()) || !nodes_after.contains(&edge.dst())) {
28✔
218
            continue;
23✔
219
        }
23✔
220
        builder.add_memlet(
5✔
221
            block_after,
5✔
222
            *nodes_after[&edge.src()],
5✔
223
            edge.src_conn(),
5✔
224
            *nodes_after[&edge.dst()],
5✔
225
            edge.dst_conn(),
5✔
226
            edge.subset(),
5✔
227
            edge.base_type(),
5✔
228
            edge.debug_info()
5✔
229
        );
5✔
230
    }
5✔
231

232
    // Delete transferred data flow in the original block
233
    std::unordered_set<data_flow::Memlet*> edges_for_removal;
6✔
234
    for (auto& edge : dfg.edges()) {
28✔
235
        if (nodes_after.contains(&edge.src()) && nodes_after.contains(&edge.dst())) {
28✔
236
            edges_for_removal.insert(&edge);
5✔
237
        }
5✔
238
    }
28✔
239
    for (auto* edge : edges_for_removal) {
6✔
240
        builder.remove_memlet(*block, *edge);
5✔
241
    }
5✔
242
    std::unordered_set<data_flow::DataFlowNode*> nodes_for_removal;
6✔
243
    for (auto& node : dfg.nodes()) {
34✔
244
        if (dfg.in_degree(node) == 0 && dfg.out_degree(node) == 0) {
34✔
245
            nodes_for_removal.insert(&node);
5✔
246
        }
5✔
247
    }
34✔
248
    for (auto* node : nodes_for_removal) {
6✔
249
        builder.remove_node(*block, *node);
5✔
250
    }
5✔
251

252
    // Add containers for loop induction variables (symbols already renamed in constructor)
253
    for (size_t i = 0; i < this->dims().size(); i++) {
17✔
254
        auto indvar = this->indvar(i);
11✔
255
        auto indvar_name = SymEngine::rcp_static_cast<const SymEngine::Symbol>(indvar)->get_name();
11✔
256
        if (builder.subject().exists(indvar_name)) {
11✔
257
            continue;
5✔
258
        }
5✔
259
        builder.add_container(indvar_name, types::Scalar(types::PrimitiveType::Int64));
6✔
260
    }
6✔
261

262
    // Add loops
263
    structured_control_flow::Sequence* current_sequence = nullptr;
6✔
264
    bool map = true;
6✔
265
    for (size_t i = 0; i < this->dims().size(); i++) {
17✔
266
        if (map) {
11✔
267
            if (i >= this->out_indices().size() || !symbolic::uses(this->out_index(i), this->indvar(i))) {
11✔
268
                map = false;
6✔
269
            } else {
6✔
270
                for (size_t j = 0; j < i; j++) {
7✔
271
                    if (symbolic::uses(this->init(i), this->indvar(j)) ||
2✔
272
                        symbolic::uses(this->bound(i), this->indvar(j))) {
2✔
273
                        map = false;
×
274
                        break;
×
275
                    }
×
276
                }
2✔
277
            }
5✔
278
        }
11✔
279
        auto indvar = this->indvar(i);
11✔
280
        auto condition = symbolic::Lt(indvar, this->bound(i));
11✔
281
        auto init = this->init(i);
11✔
282
        auto update = symbolic::add(indvar, symbolic::one());
11✔
283
        if (current_sequence) {
11✔
284
            structured_control_flow::StructuredLoop* loop;
5✔
285
            if (map) {
5✔
286
                loop = &builder.add_map(
2✔
287
                    *current_sequence,
2✔
288
                    indvar,
2✔
289
                    condition,
2✔
290
                    init,
2✔
291
                    update,
2✔
292
                    ScheduleType_Sequential::create(),
2✔
293
                    {},
2✔
294
                    this->debug_info()
2✔
295
                );
2✔
296
            } else {
3✔
297
                loop = &builder.add_for(*current_sequence, indvar, condition, init, update, {}, this->debug_info());
3✔
298
            }
3✔
299
            current_sequence = &loop->root();
5✔
300
        } else {
6✔
301
            structured_control_flow::StructuredLoop* loop;
6✔
302
            if (map) {
6✔
303
                loop = &builder.add_map_after(
3✔
304
                    *sequence,
3✔
305
                    *block,
3✔
306
                    indvar,
3✔
307
                    condition,
3✔
308
                    init,
3✔
309
                    update,
3✔
310
                    ScheduleType_Sequential::create(),
3✔
311
                    {},
3✔
312
                    this->debug_info()
3✔
313
                );
3✔
314
            } else {
3✔
315
                loop =
3✔
316
                    &builder.add_for_after(*sequence, *block, indvar, condition, init, update, {}, this->debug_info());
3✔
317
            }
3✔
318
            current_sequence = &loop->root();
6✔
319
        }
6✔
320
    }
11✔
321

322
    // Add new block
323
    structured_control_flow::Block* new_block;
6✔
324
    if (current_sequence) {
6✔
325
        new_block = &builder.add_block(*current_sequence);
6✔
326
    } else {
6✔
327
        new_block = &builder.add_block_after(*sequence, *block, {}, this->debug_info());
×
328
    }
×
329

330
    // Transfer the access nodes of the EinsumNode
331
    std::unordered_map<std::string, data_flow::AccessNode*> new_in_accesses;
6✔
332
    std::unordered_map<std::string, const types::IType&> in_types;
6✔
333
    for (auto& iedge : dfg.in_edges(*this)) {
15✔
334
        in_types.insert({iedge.dst_conn(), iedge.base_type()});
15✔
335
        if (auto* constant_node = dynamic_cast<data_flow::ConstantNode*>(&iedge.src())) {
15✔
336
            new_in_accesses.insert(
×
337
                {iedge.dst_conn(),
×
338
                 &builder
×
339
                      .add_constant(*new_block, constant_node->data(), constant_node->type(), constant_node->debug_info())
×
340
                }
×
341
            );
×
342
        } else if (auto* access_node = dynamic_cast<data_flow::AccessNode*>(&iedge.src())) {
15✔
343
            data_flow::AccessNode* new_access_node = nullptr;
15✔
344
            for (auto [conn, other_access_node] : new_in_accesses) {
15✔
345
                if (access_node->data() == other_access_node->data()) {
13✔
346
                    new_access_node = other_access_node;
×
347
                    break;
×
348
                }
×
349
            }
13✔
350
            if (!new_access_node) {
15✔
351
                new_access_node = &builder.add_access(*new_block, access_node->data(), access_node->debug_info());
15✔
352
            }
15✔
353
            new_in_accesses.insert({iedge.dst_conn(), new_access_node});
15✔
354
        } else {
15✔
355
            return false;
×
356
        }
×
357
    }
15✔
358
    data_flow::AccessNode* new_out_access;
6✔
359
    const types::IType* out_type;
6✔
360
    {
6✔
361
        auto& oedge = *dfg.out_edges(*this).begin();
6✔
362
        out_type = &oedge.base_type();
6✔
363
        if (auto* access_node = dynamic_cast<data_flow::AccessNode*>(&oedge.dst())) {
6✔
364
            new_out_access = &builder.add_access(*new_block, access_node->data(), access_node->debug_info());
6✔
365
        } else {
6✔
366
            return false;
×
367
        }
×
368
    }
6✔
369

370
    // Add computations to the block
371
    if (this->inputs().size() == 1) {
6✔
372
        auto& tasklet =
×
373
            builder.add_tasklet(*new_block, data_flow::TaskletCode::assign, {"_out"}, {"_in0"}, this->debug_info());
×
374
        builder.add_memlet(
×
375
            *new_block,
×
376
            *new_in_accesses.at(this->input(0)),
×
377
            "void",
×
378
            tasklet,
×
379
            "_in0",
×
380
            this->in_indices(0),
×
381
            in_types.at(this->input(0)),
×
382
            this->debug_info()
×
383
        );
×
384
        builder.add_memlet(
×
385
            *new_block, tasklet, "_out", *new_out_access, "void", this->out_indices(), *out_type, this->debug_info()
×
386
        );
×
387
    } else if (this->inputs().size() == 2) {
6✔
388
        auto& tasklet =
4✔
389
            builder
4✔
390
                .add_tasklet(*new_block, data_flow::TaskletCode::fp_add, {"_out"}, {"_in0", "_in1"}, this->debug_info());
4✔
391
        builder.add_memlet(
4✔
392
            *new_block,
4✔
393
            *new_in_accesses.at(this->input(0)),
4✔
394
            "void",
4✔
395
            tasklet,
4✔
396
            "_in0",
4✔
397
            this->in_indices(0),
4✔
398
            in_types.at(this->input(0)),
4✔
399
            this->debug_info()
4✔
400
        );
4✔
401
        builder.add_memlet(
4✔
402
            *new_block,
4✔
403
            *new_in_accesses.at(this->input(1)),
4✔
404
            "void",
4✔
405
            tasklet,
4✔
406
            "_in1",
4✔
407
            this->in_indices(1),
4✔
408
            in_types.at(this->input(1)),
4✔
409
            this->debug_info()
4✔
410
        );
4✔
411
        builder.add_memlet(
4✔
412
            *new_block, tasklet, "_out", *new_out_access, "void", this->out_indices(), *out_type, this->debug_info()
4✔
413
        );
4✔
414
    } else {
4✔
415
        // Build a mapping from original connector names to internal names and indices
416
        std::unordered_map<std::string, data_flow::Subset> in_indices;
2✔
417
        std::unordered_map<std::string, std::string> conn_to_internal;
2✔
418
        for (size_t i = 0; i < this->inputs().size(); i++) {
9✔
419
            in_indices.insert({this->input(i), this->in_indices(i)});
7✔
420
            conn_to_internal.insert({this->input(i), "_in" + std::to_string(i)});
7✔
421
        }
7✔
422
        long long inp;
2✔
423
        for (inp = 0; inp < (long long) this->inputs().size() - 3; inp++) {
3✔
424
            auto tmp = builder.find_new_name();
1✔
425
            auto& tmp_type = builder.add_container(tmp, types::Scalar(in_types.at(this->input(inp)).primitive_type()));
1✔
426
            auto& tmp_access = builder.add_access(*new_block, tmp);
1✔
427
            std::string int_conn0 = conn_to_internal.at(this->input(inp));
1✔
428
            std::string int_conn1 = conn_to_internal.at(this->input(inp + 1));
1✔
429
            auto& tasklet = builder.add_tasklet(
1✔
430
                *new_block, data_flow::TaskletCode::fp_mul, {"_out"}, {int_conn0, int_conn1}, this->debug_info()
1✔
431
            );
1✔
432
            builder.add_memlet(
1✔
433
                *new_block,
1✔
434
                *new_in_accesses.at(this->input(inp)),
1✔
435
                "void",
1✔
436
                tasklet,
1✔
437
                int_conn0,
1✔
438
                in_indices.at(this->input(inp)),
1✔
439
                in_types.at(this->input(inp)),
1✔
440
                this->debug_info()
1✔
441
            );
1✔
442
            builder.add_memlet(
1✔
443
                *new_block,
1✔
444
                *new_in_accesses.at(this->input(inp + 1)),
1✔
445
                "void",
1✔
446
                tasklet,
1✔
447
                int_conn1,
1✔
448
                in_indices.at(this->input(inp + 1)),
1✔
449
                in_types.at(this->input(inp + 1)),
1✔
450
                this->debug_info()
1✔
451
            );
1✔
452
            builder.add_memlet(*new_block, tasklet, "_out", tmp_access, "void", {}, tmp_type, this->debug_info());
1✔
453
            new_in_accesses[this->input(inp + 1)] = &tmp_access;
1✔
454
            in_indices[this->input(inp + 1)].clear();
1✔
455
            in_types.erase(this->input(inp + 1));
1✔
456
            in_types.insert({this->input(inp + 1), tmp_type});
1✔
457
        }
1✔
458
        std::string int_conn0 = conn_to_internal.at(this->input(inp));
2✔
459
        std::string int_conn1 = conn_to_internal.at(this->input(inp + 1));
2✔
460
        std::string int_conn2 = conn_to_internal.at(this->input(inp + 2));
2✔
461
        auto& tasklet = builder.add_tasklet(
2✔
462
            *new_block, data_flow::TaskletCode::fp_fma, {"_out"}, {int_conn0, int_conn1, int_conn2}, this->debug_info()
2✔
463
        );
2✔
464
        builder.add_memlet(
2✔
465
            *new_block,
2✔
466
            *new_in_accesses.at(this->input(inp)),
2✔
467
            "void",
2✔
468
            tasklet,
2✔
469
            int_conn0,
2✔
470
            in_indices.at(this->input(inp)),
2✔
471
            in_types.at(this->input(inp)),
2✔
472
            this->debug_info()
2✔
473
        );
2✔
474
        builder.add_memlet(
2✔
475
            *new_block,
2✔
476
            *new_in_accesses.at(this->input(inp + 1)),
2✔
477
            "void",
2✔
478
            tasklet,
2✔
479
            int_conn1,
2✔
480
            in_indices.at(this->input(inp + 1)),
2✔
481
            in_types.at(this->input(inp + 1)),
2✔
482
            this->debug_info()
2✔
483
        );
2✔
484
        builder.add_memlet(
2✔
485
            *new_block,
2✔
486
            *new_in_accesses.at(this->input(inp + 2)),
2✔
487
            "void",
2✔
488
            tasklet,
2✔
489
            int_conn2,
2✔
490
            in_indices.at(this->input(inp + 2)),
2✔
491
            in_types.at(this->input(inp + 2)),
2✔
492
            this->debug_info()
2✔
493
        );
2✔
494
        builder.add_memlet(
2✔
495
            *new_block, tasklet, "_out", *new_out_access, "void", this->out_indices(), *out_type, this->debug_info()
2✔
496
        );
2✔
497
    }
2✔
498

499
    // Remove EinsumNode and its access nodes and memlets
500
    std::unordered_set<data_flow::AccessNode*> old_accesses;
6✔
501
    while (dfg.in_edges(*this).begin() != dfg.in_edges(*this).end()) {
21✔
502
        auto& iedge = *dfg.in_edges(*this).begin();
15✔
503
        old_accesses.insert(dynamic_cast<data_flow::AccessNode*>(&iedge.src()));
15✔
504
        builder.remove_memlet(*block, iedge);
15✔
505
    }
15✔
506
    while (dfg.out_edges(*this).begin() != dfg.out_edges(*this).end()) {
12✔
507
        auto& oedge = *dfg.out_edges(*this).begin();
6✔
508
        old_accesses.insert(dynamic_cast<data_flow::AccessNode*>(&oedge.dst()));
6✔
509
        builder.remove_memlet(*block, oedge);
6✔
510
    }
6✔
511
    for (auto* old_access : old_accesses) {
21✔
512
        if (dfg.in_degree(*old_access) == 0 && dfg.out_degree(*old_access) == 0) {
21✔
513
            builder.remove_node(*block, *old_access);
20✔
514
        }
20✔
515
    }
21✔
516
    builder.remove_node(*block, *this);
6✔
517

518
    // Remove block before loops if empty
519
    size_t block_index = sequence->index(*block);
6✔
520
    if (dfg.nodes().size() == 0 && sequence->at(block_index).second.empty()) {
6✔
521
        builder.remove_child(*sequence, sequence->index(*block));
5✔
522
    }
5✔
523

524
    // Remove block after loops if empty
525
    if (block_after.dataflow().nodes().size() == 0) {
6✔
526
        builder.remove_child(*sequence, sequence->index(block_after));
5✔
527
    }
5✔
528

529
    return true;
6✔
530
}
6✔
531

532
symbolic::SymbolSet EinsumNode::symbols() const {
4✔
533
    symbolic::SymbolSet result;
4✔
534
    symbolic::SymbolSet internal = this->internal_symbols();
4✔
535

536
    // Collect only external symbols from bounds and init expressions
537
    for (auto& dim : this->dims()) {
9✔
538
        for (auto& symbol : symbolic::atoms(dim.init)) {
9✔
539
            if (!internal.count(symbol)) {
×
540
                result.insert(symbol);
×
541
            }
×
542
        }
×
543
        for (auto& symbol : symbolic::atoms(dim.bound)) {
9✔
544
            if (!internal.count(symbol)) {
9✔
545
                result.insert(symbol);
9✔
546
            }
9✔
547
        }
9✔
548
    }
9✔
549

550
    // Note: indices only contain internal indvars, so skip them
551

552
    return result;
4✔
553
}
4✔
554

555
void EinsumNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
556
    // Skip if old_expression is an internal symbol (indvar)
557
    for (auto& dim : this->dims()) {
×
558
        if (symbolic::eq(dim.indvar, old_expression)) {
×
559
            return; // Internal symbol - do not replace
×
560
        }
×
561
    }
×
562

563
    // Only replace external symbols in bounds/init expressions
564
    for (auto& dim : this->dims_) {
×
565
        dim.init = symbolic::subs(dim.init, old_expression, new_expression);
×
566
        dim.bound = symbolic::subs(dim.bound, old_expression, new_expression);
×
567
    }
×
568

569
    // Note: indices only contain internal indvars, so no substitution needed
570
}
×
571

572
void EinsumNode::replace(const symbolic::ExpressionMapping& replacements) {
×
573
    // Filter out replacements whose key is an internal symbol (indvar)
574
    symbolic::ExpressionMapping filtered;
×
575
    for (auto& pair : replacements) {
×
576
        bool is_internal = false;
×
577
        for (auto& dim : this->dims_) {
×
578
            if (symbolic::eq(dim.indvar, pair.first)) {
×
579
                is_internal = true;
×
580
                break;
×
581
            }
×
582
        }
×
583
        if (!is_internal) {
×
584
            filtered[pair.first] = pair.second;
×
585
        }
×
586
    }
×
587

588
    if (filtered.empty()) {
×
589
        return;
×
590
    }
×
591

592
    // Only replace external symbols in bounds/init expressions
593
    for (auto& dim : this->dims_) {
×
594
        dim.init = symbolic::subs(dim.init, filtered);
×
595
        dim.bound = symbolic::subs(dim.bound, filtered);
×
596
    }
×
597

598
    // Note: indices only contain internal indvars, so no substitution needed
599
}
×
600

601
std::string EinsumNode::toStr() const {
4✔
602
    std::stringstream stream;
4✔
603

604
    stream << this->output(0);
4✔
605
    for (auto& index : this->out_indices()) {
5✔
606
        stream << "[" << index->__str__() << "]";
5✔
607
    }
5✔
608
    stream << " = ";
4✔
609
    size_t num_inputs = this->inputs().size();
4✔
610
    if (num_inputs > 1) {
4✔
611
        for (size_t i = 0; i < num_inputs - 1; i++) {
10✔
612
            if (i > 0) {
6✔
613
                stream << " * ";
2✔
614
            }
2✔
615
            stream << this->input(i);
6✔
616
            for (auto& index : this->in_indices(i)) {
11✔
617
                stream << "[" << index->__str__() << "]";
11✔
618
            }
11✔
619
        }
6✔
620
        stream << " + ";
4✔
621
    }
4✔
622
    stream << this->input(num_inputs - 1);
4✔
623
    for (auto& index : this->in_indices(num_inputs - 1)) {
5✔
624
        stream << "[" << index->__str__() << "]";
5✔
625
    }
5✔
626

627
    for (auto& dim : this->dims()) {
9✔
628
        stream << " for " << dim.indvar->__str__() << " = " << dim.init->__str__() << " : " << dim.bound->__str__();
9✔
629
    }
9✔
630

631
    return stream.str();
4✔
632
}
4✔
633

634
symbolic::Expression EinsumNode::flop() const {
4✔
635
    symbolic::SymbolMap dim_map;
4✔
636
    symbolic::Expression result = symbolic::one();
4✔
637

638
    for (size_t i = 0; i < this->dims().size(); i++) {
13✔
639
        symbolic::Expression dim_expr = symbolic::sub(this->bound(i), this->init(i));
9✔
640
        for (size_t j = 0; j < i; j++) {
16✔
641
            for (auto& symbol : symbolic::atoms(dim_expr)) {
7✔
642
                if (symbolic::eq(symbol, this->indvar(j))) {
7✔
643
                    dim_expr =
×
644
                        symbolic::subs(dim_expr, symbol, symbolic::div(dim_map.at(symbol), symbolic::integer(2)));
×
645
                }
×
646
            }
7✔
647
        }
7✔
648
        dim_map.insert({this->indvar(i), dim_expr});
9✔
649
        result = symbolic::mul(result, dim_expr);
9✔
650
    }
9✔
651

652
    return symbolic::mul(result, symbolic::integer(this->inputs().size() - 1));
4✔
653
}
4✔
654

655
std::unique_ptr<data_flow::DataFlowNode> EinsumNode::
656
    clone(size_t element_id, const graph::Vertex vertex, data_flow::DataFlowGraph& parent) const {
×
657
    return std::make_unique<EinsumNode>(
×
658
        element_id,
×
659
        this->debug_info(),
×
660
        vertex,
×
661
        parent,
×
662
        std::vector<std::string>(this->inputs().begin(), this->inputs().end() - 1),
×
663
        this->dims(),
×
664
        this->out_indices(),
×
665
        std::vector<data_flow::Subset>(this->in_indices().begin(), this->in_indices().end() - 1),
×
666
        false // skip renaming - already internal symbols
×
667
    );
×
668
}
×
669

670
void EinsumNode::validate(const Function& function) const {
22✔
671
    // Check inputs
672
    size_t inputs_size = this->inputs().size();
22✔
673
    if (inputs_size == 0) {
22✔
674
        throw InvalidSDFGException("EinsumNode: Inputs of EinsumNode must not be empty");
×
675
    }
×
676
    for (size_t i = 0; i < inputs_size - 1; i++) {
60✔
677
        if (this->input(i) == "__einsum_out") {
38✔
678
            throw InvalidSDFGException("EinsumNode: Input '__einsum_out' at wrong position");
×
679
        }
×
680
    }
38✔
681
    if (this->input(inputs_size - 1) != "__einsum_out") {
22✔
682
        throw InvalidSDFGException("EinsumNode: Last input of EinsumNode must be '__einsum_out'");
×
683
    }
×
684

685
    // Check last in indices
686
    if (this->out_indices().size() != this->in_indices(inputs_size - 1).size()) {
22✔
687
        throw InvalidSDFGException("EinsumNode: Out indices and last in indices have different sizes");
×
688
    }
×
689
    for (size_t i = 0; i < this->out_indices().size(); i++) {
40✔
690
        if (!symbolic::eq(this->out_index(i), this->in_index(inputs_size - 1, i))) {
18✔
691
            throw InvalidSDFGException("EinsumNode: Out indices and last in indices do not match");
×
692
        }
×
693
    }
18✔
694

695
    // Check input containers
696
    auto& dfg = this->get_parent();
22✔
697
    auto& oedge = *dfg.out_edges(*this).begin();
22✔
698
    std::string out_container = dynamic_cast<const data_flow::AccessNode&>(oedge.dst()).data();
22✔
699
    for (auto& iedge : dfg.in_edges(*this)) {
60✔
700
        auto& src = dynamic_cast<const data_flow::AccessNode&>(iedge.src());
60✔
701
        if (src.data() != out_container && iedge.dst_conn() == "__einsum_out") {
60✔
702
            throw InvalidSDFGException("EinsumNode: Out container must occur as a summation in the inputs");
×
703
        }
×
704
    }
60✔
705

706
    // Check if dimensions index variables occur at least once as in/out indices
707
    for (size_t i = 0; i < this->dims().size(); i++) {
45✔
708
        bool unused = true;
23✔
709
        for (auto& index : this->out_indices()) {
30✔
710
            for (auto& symbol : symbolic::atoms(index)) {
30✔
711
                if (symbolic::eq(this->indvar(i), symbol)) {
30✔
712
                    unused = false;
13✔
713
                    break;
13✔
714
                }
13✔
715
            }
30✔
716
            if (!unused) {
30✔
717
                break;
13✔
718
            }
13✔
719
        }
30✔
720
        if (!unused) {
23✔
721
            continue;
13✔
722
        }
13✔
723
        for (auto& indices : this->in_indices()) {
12✔
724
            for (auto& index : indices) {
17✔
725
                for (auto& symbol : symbolic::atoms(index)) {
17✔
726
                    if (symbolic::eq(this->indvar(i), symbol)) {
17✔
727
                        unused = false;
10✔
728
                        break;
10✔
729
                    }
10✔
730
                }
17✔
731
                if (!unused) {
17✔
732
                    break;
10✔
733
                }
10✔
734
            }
17✔
735
            if (!unused) {
12✔
736
                break;
10✔
737
            }
10✔
738
        }
12✔
739
        if (unused) {
10✔
740
            throw InvalidSDFGException(
×
741
                "EinsumNode: Dimension indvar does not occur in the in/out indices: " + this->indvar(i)->__str__()
×
742
            );
×
743
        }
×
744
    }
10✔
745
}
22✔
746

747
nlohmann::json EinsumSerializer::serialize(const data_flow::LibraryNode& libnode) {
1✔
748
    if (libnode.code() != LibraryNodeType_Einsum) {
1✔
749
        throw InvalidSDFGException("EinsumSerializer: Invalid library node type");
×
750
    }
×
751

752
    const auto& einsum_node = static_cast<const EinsumNode&>(libnode);
1✔
753
    serializer::JSONSymbolicPrinter printer;
1✔
754

755
    nlohmann::json j;
1✔
756
    j["type"] = "library_node";
1✔
757
    j["code"] = std::string(LibraryNodeType_Einsum.value());
1✔
758
    j["side_effect"] = einsum_node.side_effect();
1✔
759

760
    j["output"] = einsum_node.output(0);
1✔
761

762
    j["inputs"] = nlohmann::json::array();
1✔
763
    for (auto& input : einsum_node.inputs()) {
3✔
764
        j["inputs"].push_back(input);
3✔
765
    }
3✔
766

767
    j["dims"] = nlohmann::json::array();
1✔
768
    for (auto& dim : einsum_node.dims()) {
3✔
769
        nlohmann::json dimj;
3✔
770
        dimj["indvar"] = printer.apply(dim.indvar);
3✔
771
        dimj["init"] = printer.apply(dim.init);
3✔
772
        dimj["bound"] = printer.apply(dim.bound);
3✔
773
        j["dims"].push_back(dimj);
3✔
774
    }
3✔
775

776
    j["out_indices"] = nlohmann::json::array();
1✔
777
    for (auto& index : einsum_node.out_indices()) {
2✔
778
        j["out_indices"].push_back(printer.apply(index));
2✔
779
    }
2✔
780

781
    j["in_indices"] = nlohmann::json::array();
1✔
782
    for (auto& indices : einsum_node.in_indices()) {
3✔
783
        nlohmann::json indicesj = nlohmann::json::array();
3✔
784
        for (auto& index : indices) {
6✔
785
            indicesj.push_back(printer.apply(index));
6✔
786
        }
6✔
787
        j["in_indices"].push_back(indicesj);
3✔
788
    }
3✔
789

790
    return j;
1✔
791
}
1✔
792

793
data_flow::LibraryNode& EinsumSerializer::deserialize(
794
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
795
) {
1✔
796
    assert(j.contains("type"));
1✔
797
    assert(j["type"].is_string());
1✔
798
    assert(j.contains("code"));
1✔
799
    assert(j["code"].is_string());
1✔
800
    assert(j.contains("side_effect"));
1✔
801
    assert(j["side_effect"].is_boolean());
1✔
802
    assert(j.contains("output"));
1✔
803
    assert(j["output"].is_string());
1✔
804
    assert(j.contains("inputs"));
1✔
805
    assert(j["inputs"].is_array());
1✔
806
    assert(j.contains("dims"));
1✔
807
    assert(j["dims"].is_array());
1✔
808
    assert(j.contains("out_indices"));
1✔
809
    assert(j["out_indices"].is_array());
1✔
810
    assert(j.contains("in_indices"));
1✔
811
    assert(j["in_indices"].is_array());
1✔
812
    assert(j["inputs"].size() == j["in_indices"].size());
1✔
813

814
    auto type = j["type"].get<std::string>();
1✔
815
    if (type != "library_node") {
1✔
816
        throw InvalidSDFGException("EinsumSerializer: Invalid library node type");
×
817
    }
×
818

819
    auto code = j["code"].get<std::string>();
1✔
820
    if (code != LibraryNodeType_Einsum.value()) {
1✔
821
        throw InvalidSDFGException("EinsumSerializer: Invalid library node code");
×
822
    }
×
823

824
    auto side_effect = j["side_effect"].get<bool>();
1✔
825
    if (side_effect) {
1✔
826
        throw InvalidSDFGException("EinsumSerializer: EinsumNodes must be free of side effects");
×
827
    }
×
828

829
    auto output = j["output"].get<std::string>();
1✔
830
    if (output != "__einsum_out") {
1✔
831
        throw InvalidSDFGException("EinsumSerializer: Output of EinsumNode must be '__einsum_out'");
×
832
    }
×
833

834
    auto inputs = j["inputs"].get<std::vector<std::string>>();
1✔
835
    size_t inputs_size = inputs.size();
1✔
836
    if (inputs_size == 0) {
1✔
837
        throw InvalidSDFGException("EinsumSerializer: Inputs of EinsumNode must not be empty");
×
838
    }
×
839
    if (inputs[inputs_size - 1] != "__einsum_out") {
1✔
840
        throw InvalidSDFGException("EinsumSerializer: Last input of EinsumNode must be '__einsum_out'");
×
841
    }
×
842

843
    std::vector<EinsumDimension> dims;
1✔
844
    for (size_t i = 0; i < j["dims"].size(); i++) {
4✔
845
        auto& dimj = j["dims"][i];
3✔
846
        assert(dimj.is_object());
3✔
847
        assert(dimj.contains("indvar"));
3✔
848
        assert(dimj["indvar"].is_string());
3✔
849
        assert(dimj.contains("init"));
3✔
850
        assert(dimj["init"].is_string());
3✔
851
        assert(dimj.contains("bound"));
3✔
852
        assert(dimj["bound"].is_string());
3✔
853

854
        EinsumDimension dim;
3✔
855
        dim.indvar = symbolic::symbol(dimj["indvar"]);
3✔
856
        dim.init = symbolic::parse(dimj["init"]);
3✔
857
        dim.bound = symbolic::parse(dimj["bound"]);
3✔
858
        dims.push_back(dim);
3✔
859
    }
3✔
860

861
    data_flow::Subset out_indices;
1✔
862
    auto out_indices_str = j["out_indices"].get<std::vector<std::string>>();
1✔
863
    for (auto& index_str : out_indices_str) {
2✔
864
        out_indices.push_back(symbolic::parse(index_str));
2✔
865
    }
2✔
866

867
    std::vector<data_flow::Subset> in_indices;
1✔
868
    for (size_t i = 0; i < j["in_indices"].size(); i++) {
4✔
869
        assert(j["in_indices"][i].is_array());
3✔
870

871
        data_flow::Subset indices;
3✔
872
        auto indices_str = j["in_indices"][i].get<std::vector<std::string>>();
3✔
873
        for (auto& index_str : indices_str) {
6✔
874
            indices.push_back(symbolic::parse(index_str));
6✔
875
        }
6✔
876
        in_indices.push_back(indices);
3✔
877
    }
3✔
878
    if (out_indices.size() != in_indices[inputs_size - 1].size()) {
1✔
879
        throw InvalidSDFGException("EinsumSerializer: Out indices and last in indices have different sizes");
×
880
    }
×
881
    for (size_t i = 0; i < out_indices.size(); i++) {
3✔
882
        if (!symbolic::eq(out_indices[i], in_indices[inputs_size - 1][i])) {
2✔
883
            throw InvalidSDFGException("EinsumSerializer: Out indices and last in indices do not match");
×
884
        }
×
885
    }
2✔
886

887
    auto& einsum_node = builder.add_library_node<
1✔
888
        EinsumNode,
1✔
889
        const std::vector<std::string>&,
1✔
890
        const std::vector<EinsumDimension>&,
1✔
891
        const data_flow::Subset&,
1✔
892
        const std::vector<data_flow::Subset>&,
1✔
893
        bool>(
1✔
894
        parent,
1✔
895
        DebugInfo(),
1✔
896
        std::vector<std::string>(inputs.begin(), inputs.end() - 1),
1✔
897
        dims,
1✔
898
        out_indices,
1✔
899
        std::vector<data_flow::Subset>(in_indices.begin(), in_indices.end() - 1),
1✔
900
        false // skip renaming - already internal symbols from serialization
1✔
901
    );
1✔
902

903
    return einsum_node;
1✔
904
}
1✔
905

906
} // namespace tensor
907
} // namespace math
908
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc