← Documentation
ADS-IDAC Engine 5.0.0
compiler.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <limits>
4#include <numeric>
5#include <set>
6#include <stdexcept>
7#include <string>
8
9namespace {
10void validate_commercial_license() {
11 // Intentionally disabled for local/HCL integration builds.
12}
13} // namespace
14
15namespace bncore {
16
17std::vector<std::vector<NodeId>>
19 std::size_t n = graph.num_variables();
20 std::vector<std::set<NodeId>> adj(n);
21
22 for (std::size_t i = 0; i < n; ++i) {
23 const auto &parents = graph.get_parents(i);
24 const auto &children = graph.get_children(i);
25
26 // Add directed edges as undirected
27 for (NodeId p : parents) {
28 adj[i].insert(p);
29 adj[p].insert(i);
30 }
31 for (NodeId c : children) {
32 adj[i].insert(c);
33 adj[c].insert(i);
34 }
35
36 // Marry the parents
37 for (std::size_t p1 = 0; p1 < parents.size(); ++p1) {
38 for (std::size_t p2 = p1 + 1; p2 < parents.size(); ++p2) {
39 adj[parents[p1]].insert(parents[p2]);
40 adj[parents[p2]].insert(parents[p1]);
41 }
42 }
43 }
44
45 std::vector<std::vector<NodeId>> moral_graph(n);
46 for (std::size_t i = 0; i < n; ++i) {
47 moral_graph[i].assign(adj[i].begin(), adj[i].end());
48 }
49 return moral_graph;
50}
51
52std::unique_ptr<JunctionTree>
54 const std::string &heuristic) {
55 validate_commercial_license();
56 auto jt = std::make_unique<JunctionTree>(&graph);
57
58 // Step 1: Moralize
59 auto moral_graph = moralize(graph);
60
61 // Step 2: Triangulate using min_fill
62 std::size_t n = graph.num_variables();
63 std::vector<bool> eliminated(n, false);
64 std::vector<std::vector<NodeId>> adj = moral_graph;
65 std::vector<std::vector<NodeId>> cliques;
66
67 for (std::size_t step = 0; step < n; ++step) {
68 std::size_t best_node = n;
69 // P4: min-weight heuristic — score = product of state counts of the new
70 // clique (node + active neighbours). For equal weights we break ties by
71 // fewer fill edges, then by smaller clique size. This avoids selecting
72 // high-cardinality nodes that would create enormous clique tables.
73 std::size_t best_weight = std::numeric_limits<std::size_t>::max();
74 std::size_t best_fill = std::numeric_limits<std::size_t>::max();
75 std::size_t best_size = std::numeric_limits<std::size_t>::max();
76
77 for (std::size_t i = 0; i < n; ++i) {
78 if (eliminated[i]) continue;
79
80 std::vector<NodeId> neighbors;
81 for (NodeId neighbor : adj[i])
82 if (!eliminated[neighbor]) neighbors.push_back(neighbor);
83
84 std::size_t fill_edges = 0;
85 for (std::size_t u = 0; u < neighbors.size(); ++u)
86 for (std::size_t v = u + 1; v < neighbors.size(); ++v)
87 if (std::find(adj[neighbors[u]].begin(), adj[neighbors[u]].end(),
88 neighbors[v]) == adj[neighbors[u]].end())
89 fill_edges++;
90
91 // Compute clique weight = product of state counts (cap at ULLONG_MAX/2
92 // to avoid overflow — just needs to be a consistent ordering).
93 std::size_t weight = graph.get_variable(i).states.size();
94 for (NodeId nb : neighbors) {
95 std::size_t ns = graph.get_variable(nb).states.size();
96 if (weight > (std::numeric_limits<std::size_t>::max() / 2) / ns)
97 weight = std::numeric_limits<std::size_t>::max() / 2; // saturate
98 else
99 weight *= ns;
100 }
101 std::size_t clique_sz = neighbors.size() + 1;
102
103 if (weight < best_weight ||
104 (weight == best_weight && fill_edges < best_fill) ||
105 (weight == best_weight && fill_edges == best_fill && clique_sz < best_size)) {
106 best_weight = weight;
107 best_fill = fill_edges;
108 best_size = clique_sz;
109 best_node = i;
110 }
111 }
112
113 eliminated[best_node] = true;
114 std::vector<NodeId> neighbors;
115 for (NodeId neighbor : adj[best_node]) {
116 if (!eliminated[neighbor])
117 neighbors.push_back(neighbor);
118 }
119
120 std::vector<NodeId> clique = neighbors;
121 clique.push_back(best_node);
122 std::sort(clique.begin(), clique.end());
123 cliques.push_back(clique);
124
125 for (std::size_t u = 0; u < neighbors.size(); ++u) {
126 for (std::size_t v = u + 1; v < neighbors.size(); ++v) {
127 auto nu = neighbors[u], nv = neighbors[v];
128 if (std::find(adj[nu].begin(), adj[nu].end(), nv) == adj[nu].end()) {
129 adj[nu].push_back(nv);
130 adj[nv].push_back(nu);
131 }
132 }
133 }
134 }
135
136 // Step 3: Extract maximum cliques
137 std::vector<std::vector<NodeId>> max_cliques;
138 std::sort(cliques.begin(), cliques.end(),
139 [](const auto &a, const auto &b) { return a.size() > b.size(); });
140 for (const auto &clique : cliques) {
141 bool is_subset = false;
142 for (const auto &max_clq : max_cliques) {
143 if (std::includes(max_clq.begin(), max_clq.end(), clique.begin(),
144 clique.end())) {
145 is_subset = true;
146 break;
147 }
148 }
149 if (!is_subset)
150 max_cliques.push_back(clique);
151 }
152
153 for (const auto &clique : max_cliques) {
154 std::vector<std::size_t> state_sizes;
155 for (NodeId id : clique)
156 state_sizes.push_back(graph.get_variable(id).states.size());
157 jt->add_clique(clique, state_sizes);
158 }
159
160 // Step 4: Build Spanning Tree (Kruskal's)
161 struct Edge {
162 std::size_t u, v, weight;
163 std::vector<NodeId> sepset;
164 };
165 std::vector<Edge> edges;
166 for (std::size_t i = 0; i < max_cliques.size(); ++i) {
167 for (std::size_t j = i + 1; j < max_cliques.size(); ++j) {
168 std::vector<NodeId> intersection;
169 std::set_intersection(max_cliques[i].begin(), max_cliques[i].end(),
170 max_cliques[j].begin(), max_cliques[j].end(),
171 std::back_inserter(intersection));
172 if (!intersection.empty()) {
173 edges.push_back({i, j, intersection.size(), intersection});
174 }
175 }
176 }
177
178 std::sort(edges.begin(), edges.end(),
179 [](const Edge &a, const Edge &b) { return a.weight > b.weight; });
180
181 std::vector<std::size_t> parent(max_cliques.size());
182 std::iota(parent.begin(), parent.end(), 0);
183 auto find = [&](std::size_t i) {
184 std::size_t root = i;
185 while (root != parent[root])
186 root = parent[root];
187 std::size_t curr = i;
188 while (curr != root) {
189 std::size_t nxt = parent[curr];
190 parent[curr] = root;
191 curr = nxt;
192 }
193 return root;
194 };
195 auto unite = [&](std::size_t i, std::size_t j) {
196 std::size_t root_i = find(i);
197 std::size_t root_j = find(j);
198 if (root_i != root_j) {
199 parent[root_i] = root_j;
200 return true;
201 }
202 return false;
203 };
204
205 for (const auto &edge : edges) {
206 if (unite(edge.u, edge.v))
207 jt->add_separator(edge.u, edge.v, edge.sepset);
208 }
209
210 // Step 5: Initialize Base Potentials with CPTs
211 for (auto &clq : jt->get_mutable_cliques()) {
212 clq.base_potential.tensor().fill(1.0);
213 }
214
215 for (std::size_t i = 0; i < graph.num_variables(); ++i) {
216 std::vector<NodeId> family;
217 for (NodeId p : graph.get_parents(i))
218 family.push_back(p);
219 family.push_back(static_cast<NodeId>(i));
220
221 for (auto &clq : jt->get_mutable_cliques()) {
222 bool contains_family = true;
223 for (NodeId n : family) {
224 if (std::find(clq.scope.begin(), clq.scope.end(), n) ==
225 clq.scope.end()) {
226 contains_family = false;
227 break;
228 }
229 }
230 if (contains_family) {
231 clq.assigned_cpts.push_back(i);
232 break;
233 }
234 }
235 }
236
237 return jt;
238}
239
240} // namespace bncore
Directed graphical model over discrete variables.
Definition: graph.hpp:28
const std::vector< NodeId > & get_parents(NodeId id) const
Parents of a variable, in the order their edges were added.
Definition: graph.cpp:105
std::size_t num_variables() const
Number of variables added so far.
Definition: graph.cpp:117
const std::vector< NodeId > & get_children(NodeId id) const
Children of a variable, in the order their edges were added.
Definition: graph.cpp:111
const VariableMetadata & get_variable(NodeId id) const
Metadata of a variable.
Definition: graph.cpp:92
static std::vector< std::vector< NodeId > > moralize(const Graph &graph)
Moral graph as adjacency lists indexed by NodeId, each sorted ascending.
Definition: compiler.cpp:18
static std::unique_ptr< JunctionTree > compile(const Graph &graph, const std::string &heuristic="min_fill")
Compiles a junction tree.
Definition: compiler.cpp:53
bncore::JunctionTreeCompiler, which turns a Graph into the JunctionTree that the inference engine run...
std::uint32_t NodeId
Handle of a variable within one Graph.
Definition: node.hpp:20
std::vector< std::string > states
Definition: node.hpp:28