← Documentation
ADS-IDAC Engine 5.0.0
engine.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <stdexcept>
4
5namespace {
6void validate_commercial_license() {
7 // Intentionally disabled for local/HCL integration builds.
8}
9} // namespace
10
11namespace bncore {
12
14 std::size_t num_threads,
15 std::size_t chunk_size)
16 : jt_(jt), num_threads_(num_threads),
17 chunk_size_(std::max<std::size_t>(1, chunk_size)) {
18 if (num_threads_ == 0)
19 num_threads_ = std::thread::hardware_concurrency();
20 if (num_threads_ == 0)
21 num_threads_ = 1;
22}
23
24void BatchExecutionEngine::evaluate(const int *evidence_data,
25 std::size_t batch_size,
26 std::size_t num_vars, double *output_data,
27 std::size_t query_var) {
28 const std::size_t num_states =
29 jt_.graph()->get_variable(query_var).states.size();
30 const std::size_t query_vars[1] = {query_var};
31 const std::size_t offsets[2] = {0, num_states};
32 evaluate_multi(evidence_data, batch_size, num_vars, query_vars, 1, offsets,
33 output_data);
34}
35
36void BatchExecutionEngine::evaluate_multi(const int *evidence_data,
37 std::size_t batch_size,
38 std::size_t num_vars,
39 const std::size_t *query_vars,
40 std::size_t num_queries,
41 const std::size_t *output_offsets,
42 double *output_data) {
43 validate_commercial_license();
44 if (!query_vars || !output_offsets || !output_data) {
45 throw std::invalid_argument(
46 "BatchExecutionEngine::evaluate_multi received null query/output buffers.");
47 }
48 if (num_queries == 0 || batch_size == 0) {
49 return;
50 }
51 const std::size_t total_states = output_offsets[num_queries];
52 if (total_states == 0) {
53 return;
54 }
55
56 std::size_t num_chunks = (batch_size + chunk_size_ - 1) / chunk_size_;
57
58 // P2: Use query_marginals_multi — single clique scan for all queries,
59 // with pre-baked state_of_dim maps (no Factor::marginalize allocation).
60 // Convert size_t query_vars to NodeId (uint32_t) once here.
61 std::vector<bncore::NodeId> qvars_nodeid(num_queries);
62 for (std::size_t i = 0; i < num_queries; ++i)
63 qvars_nodeid[i] = static_cast<bncore::NodeId>(query_vars[i]);
64
65 auto write_query_outputs = [&](BatchWorkspace &workspace,
66 std::size_t current_batch_start) {
67 double *out_row = output_data + current_batch_start * total_states;
68 workspace.query_marginals_multi(qvars_nodeid.data(), num_queries,
69 output_offsets, out_row);
70 };
71
72
73 // Fast path for the most common Python/HCL usage pattern:
74 // single context query repeatedly from the same engine instance.
75 if (num_chunks == 1) {
76 const std::size_t current_chunk_size = batch_size;
78 single_workspace_batch_size_ != current_chunk_size) {
80 std::make_unique<BatchWorkspace>(jt_, current_chunk_size);
81 single_workspace_batch_size_ = current_chunk_size;
82 } else {
83 single_workspace_cache_->reset(current_chunk_size, 0);
84 }
85
87 const int *chunk_evidence = evidence_data;
88 if (chunk_evidence) {
89 workspace.set_evidence_matrix(chunk_evidence, num_vars);
90 } else {
91 workspace.clear_evidence();
92 }
93 apply_soft_evidence_to_workspace(workspace, 0, current_chunk_size);
94 workspace.calibrate();
95 write_query_outputs(workspace, /*current_batch_start=*/0);
96 return;
97 }
98
99 auto worker = [&](std::size_t start_chunk, std::size_t end_chunk) {
100 BatchWorkspace workspace(jt_, chunk_size_);
101
102 for (std::size_t c = start_chunk; c < end_chunk; ++c) {
103 std::size_t current_batch_start = c * chunk_size_;
104 std::size_t current_chunk_size =
105 std::min(chunk_size_, batch_size - current_batch_start);
106
107 workspace.reset(current_chunk_size, current_batch_start);
108
109 const int *chunk_evidence =
110 evidence_data ? (evidence_data + current_batch_start * num_vars)
111 : nullptr;
112 if (chunk_evidence)
113 workspace.set_evidence_matrix(chunk_evidence, num_vars);
114
115 apply_soft_evidence_to_workspace(workspace, current_batch_start, current_chunk_size);
116 workspace.calibrate();
117 write_query_outputs(workspace, current_batch_start);
118 }
119 };
120
121 const std::size_t effective_threads =
122 std::max<std::size_t>(1, std::min(num_threads_, num_chunks));
123 if (effective_threads == 1 || num_chunks <= 1) {
124 worker(0, num_chunks);
125 return;
126 }
127
128 std::vector<std::future<void>> futures;
129 std::size_t chunks_per_thread =
130 (num_chunks + effective_threads - 1) / effective_threads;
131
132 for (std::size_t t = 0; t < effective_threads; ++t) {
133 std::size_t start_chunk = t * chunks_per_thread;
134 std::size_t end_chunk =
135 std::min(start_chunk + chunks_per_thread, num_chunks);
136 if (start_chunk < end_chunk) {
137 futures.push_back(
138 std::async(std::launch::async, worker, start_chunk, end_chunk));
139 }
140 }
141
142 for (auto &f : futures) {
143 f.get();
144 }
145}
146
147void BatchExecutionEngine::evaluate_map(const int *evidence_data,
148 std::size_t batch_size,
149 std::size_t num_vars,
150 int *output_states) {
151 validate_commercial_license();
152 if (!output_states) {
153 throw std::invalid_argument(
154 "BatchExecutionEngine::evaluate_map received null output buffer.");
155 }
156 if (batch_size == 0) {
157 return;
158 }
159
160 const std::size_t model_vars = jt_.graph()->num_variables();
161 if (evidence_data && num_vars < model_vars) {
162 throw std::invalid_argument(
163 "BatchExecutionEngine::evaluate_map evidence width is smaller than "
164 "the model variable count.");
165 }
166
167 const std::size_t num_chunks = (batch_size + chunk_size_ - 1) / chunk_size_;
168
169 // Fast path for repeated scalar MAP queries (B=1).
170 if (num_chunks == 1 && batch_size == 1) {
172 single_workspace_cache_ = std::make_unique<BatchWorkspace>(jt_, 1);
174 } else {
175 single_workspace_cache_->reset(1, 0);
176 }
177
179 if (evidence_data) {
180 workspace.set_evidence_matrix(evidence_data, num_vars);
181 } else {
182 workspace.clear_evidence();
183 }
184
185 apply_soft_evidence_to_workspace(workspace, 0, 1);
186 workspace.max_calibrate();
187 workspace.query_map(output_states, model_vars);
188 return;
189 }
190
191 auto worker = [&](std::size_t start_chunk, std::size_t end_chunk) {
192 BatchWorkspace workspace(jt_, 1);
193
194 for (std::size_t c = start_chunk; c < end_chunk; ++c) {
195 const std::size_t chunk_start = c * chunk_size_;
196 const std::size_t chunk_end =
197 std::min(chunk_start + chunk_size_, batch_size);
198
199 for (std::size_t row = chunk_start; row < chunk_end; ++row) {
200 workspace.reset(1, row);
201 const int *row_evidence =
202 evidence_data ? (evidence_data + row * num_vars) : nullptr;
203 if (row_evidence) {
204 workspace.set_evidence_matrix(row_evidence, num_vars);
205 } else {
206 workspace.clear_evidence();
207 }
208
209 apply_soft_evidence_to_workspace(workspace, row, 1);
210 workspace.max_calibrate();
211 workspace.query_map(output_states + row * model_vars, model_vars);
212 }
213 }
214 };
215
216 const std::size_t effective_threads =
217 std::max<std::size_t>(1, std::min(num_threads_, num_chunks));
218 if (effective_threads == 1 || num_chunks <= 1) {
219 worker(0, num_chunks);
220 return;
221 }
222
223 std::vector<std::future<void>> futures;
224 const std::size_t chunks_per_thread =
225 (num_chunks + effective_threads - 1) / effective_threads;
226
227 for (std::size_t t = 0; t < effective_threads; ++t) {
228 const std::size_t start_chunk = t * chunks_per_thread;
229 const std::size_t end_chunk =
230 std::min(start_chunk + chunks_per_thread, num_chunks);
231 if (start_chunk < end_chunk) {
232 futures.push_back(
233 std::async(std::launch::async, worker, start_chunk, end_chunk));
234 }
235 }
236
237 for (auto &f : futures) {
238 f.get();
239 }
240}
241
245}
246
247void BatchExecutionEngine::set_soft_evidence(bncore::NodeId var, const double *likelihoods, std::size_t n_states) {
248 soft_evidence_scalar_[var].assign(likelihoods, likelihoods + n_states);
249 soft_evidence_matrix_.erase(var);
250}
251
252void BatchExecutionEngine::set_soft_evidence_matrix(bncore::NodeId var, const double *likelihoods_matrix, std::size_t total_n_states) {
253 soft_evidence_matrix_[var].assign(likelihoods_matrix, likelihoods_matrix + total_n_states);
254 soft_evidence_scalar_.erase(var);
255}
256
258 soft_evidence_scalar_.clear();
259 soft_evidence_matrix_.clear();
260}
261
263 std::size_t current_batch_start,
264 std::size_t current_chunk_size) const {
265 workspace.clear_soft_evidence();
266 for (const auto &[nid, l_vec] : soft_evidence_scalar_) {
267 workspace.set_soft_evidence(nid, l_vec.data(), l_vec.size());
268 }
269 for (const auto &[nid, l_mat] : soft_evidence_matrix_) {
270 const std::size_t n_states = l_mat.size() / soft_evidence_matrix_.at(nid).size(); // Wait, this is wrong. Total states is what is stored.
271 // The matrix has shape (batch_size, n_states), so size = B * n_states
272 // Actually, we must derive n_states
273 const std::size_t total_elements = l_mat.size();
274 // In evaluate_multi, batch_size isn't cached. Wait, the Python side provides a (total_batch, n_states) array.
275 // How do we find n_states? It's the variable's cardinality.
276 const std::size_t n_states_var = jt_.graph()->get_variable(nid).states.size();
277 if (l_mat.size() < (current_batch_start + current_chunk_size) * n_states_var) {
278 throw std::invalid_argument("Soft evidence matrix size is too small for the batch dimensions.");
279 }
280 workspace.set_soft_evidence_matrix(nid, l_mat.data() + current_batch_start * n_states_var, n_states_var);
281 }
282}
283
284
285} // namespace bncore
void apply_soft_evidence_to_workspace(BatchWorkspace &workspace, std::size_t current_batch_start, std::size_t current_chunk_size) const
Definition: engine.cpp:262
const JunctionTree & jt_
Definition: engine.hpp:184
void evaluate_multi(const int *evidence_data, std::size_t batch_size, std::size_t num_vars, const std::size_t *query_vars, std::size_t num_queries, const std::size_t *output_offsets, double *output_data)
Posterior marginals of several variables for every batch row, from one sum-product calibration per ch...
Definition: engine.cpp:36
void evaluate_map(const int *evidence_data, std::size_t batch_size, std::size_t num_vars, int *output_states)
Most probable state of every variable for every batch row, from max-product propagation.
Definition: engine.cpp:147
std::unique_ptr< BatchWorkspace > single_workspace_cache_
reused when a batch is a single chunk
Definition: engine.hpp:187
std::size_t single_workspace_batch_size_
batch size the cache was built for
Definition: engine.hpp:188
void invalidate_workspace_cache()
Discards the workspace kept for single-chunk evaluations.
Definition: engine.cpp:242
void clear_soft_evidence()
Removes all stored soft evidence, scalar and matrix.
Definition: engine.cpp:257
std::unordered_map< bncore::NodeId, std::vector< double > > soft_evidence_scalar_
Definition: engine.hpp:190
void set_soft_evidence_matrix(bncore::NodeId var, const double *likelihoods_matrix, std::size_t total_n_states)
Sets a separate likelihood vector per batch row for a variable.
Definition: engine.cpp:252
std::unordered_map< bncore::NodeId, std::vector< double > > soft_evidence_matrix_
Definition: engine.hpp:192
BatchExecutionEngine(const JunctionTree &jt, std::size_t num_threads, std::size_t chunk_size)
Definition: engine.cpp:13
void set_soft_evidence(bncore::NodeId var, const double *likelihoods, std::size_t n_states)
Sets one likelihood vector for a variable, used for every batch row.
Definition: engine.cpp:247
void evaluate(const int *evidence_data, std::size_t batch_size, std::size_t num_vars, double *output_data, std::size_t query_var)
Posterior marginal of one variable for every batch row.
Definition: engine.cpp:24
Message passing state for one junction tree and one batch size.
Definition: workspace.hpp:110
void clear_soft_evidence()
Removes all soft evidence, scalar and matrix.
Definition: workspace.cpp:552
void set_soft_evidence_matrix(NodeId var, const double *likelihoods_matrix, std::size_t n_states)
Sets a separate likelihood vector per batch row for a variable.
Definition: workspace.cpp:532
void calibrate()
Runs sum-product message passing with the current evidence.
Definition: workspace.cpp:682
void clear_evidence()
Forgets the hard evidence matrix. Soft evidence is not affected.
Definition: workspace.cpp:507
void max_calibrate()
Runs max-product propagation with the current evidence, for query_map().
Definition: workspace.cpp:1160
void set_soft_evidence(NodeId var, const double *likelihoods, std::size_t n_states)
Sets one likelihood vector for a variable, shared by all batch rows.
Definition: workspace.cpp:514
void set_evidence_matrix(const int *evidence_matrix, std::size_t num_vars)
Sets the hard evidence for the next calibration.
Definition: workspace.cpp:504
void query_marginals_multi(const NodeId *query_vars, std::size_t num_queries, const std::size_t *offsets, double *out) const
Marginals of several variables from the last calibrate(), with one scan of each clique involved.
Definition: workspace.cpp:987
void reset(std::size_t new_batch_size, std::size_t cpt_batch_offset=0)
Prepares the workspace for a new evidence case.
Definition: workspace.cpp:475
void query_map(int *out_states, std::size_t n_vars) const
Decodes a state for each variable from the max-product beliefs.
Definition: workspace.cpp:1257
std::size_t num_variables() const
Number of variables added so far.
Definition: graph.cpp:117
const VariableMetadata & get_variable(NodeId id) const
Metadata of a variable.
Definition: graph.cpp:92
Cliques and separators compiled from one Graph.
const Graph * graph() const
The graph given at construction.
bncore::BatchExecutionEngine, the query interface of BNCore: exact posterior marginals and MAP states...
std::uint32_t NodeId
Handle of a variable within one Graph.
Definition: node.hpp:20
std::vector< std::string > states
Definition: node.hpp:28