aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
DBRowGeneratorEM_tpl.h
Go to the documentation of this file.
1/****************************************************************************
2 * This file is part of the aGrUM/pyAgrum library. *
3 * *
4 * Copyright (c) 2005-2026 by *
5 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
6 * - Christophe GONZALES(_at_AMU) *
7 * *
8 * The aGrUM/pyAgrum library is free software; you can redistribute it *
9 * and/or modify it under the terms of either : *
10 * *
11 * - the GNU Lesser General Public License as published by *
12 * the Free Software Foundation, either version 3 of the License, *
13 * or (at your option) any later version, *
14 * - the MIT license (MIT), *
15 * - or both in dual license, as here. *
16 * *
17 * (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html) *
18 * *
19 * This aGrUM/pyAgrum library is distributed in the hope that it will be *
20 * useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
21 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
22 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
25 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
26 * OTHER DEALINGS IN THE SOFTWARE. *
27 * *
28 * See LICENCES for more details. *
29 * *
30 * SPDX-FileCopyrightText: Copyright 2005-2026 *
31 * - Pierre-Henri WUILLEMIN(_at_LIP6) *
32 * - Christophe GONZALES(_at_AMU) *
33 * SPDX-License-Identifier: LGPL-3.0-or-later OR MIT *
34 * *
35 * Contact : info_at_agrum_dot_org *
36 * homepage : http://agrum.gitlab.io *
37 * gitlab : https://gitlab.com/agrumery/agrum *
38 * *
39 ****************************************************************************/
40
41#pragma once
42
43
49#include <agrum/base/database/DBRowGeneratorEM.h> // to ease IDE parser
51
52#ifndef DOXYGEN_SHOULD_SKIP_THIS
53
54namespace gum {
55
56 namespace learning {
57
59 template < GUM_Numeric GUM_SCALAR >
61 const std::vector< DBTranslatedValueType >& column_types,
62 const BayesNet< GUM_SCALAR >& bn,
63 const Bijection< NodeId, std::size_t >& nodeId2columns) :
64 DBRowGeneratorWithBN< GUM_SCALAR >(column_types,
65 bn,
67 nodeId2columns),
68 _filled_row1_(bn.size(), 1.0), _filled_row2_(bn.size(), 1.0) {
69 setBayesNet(bn);
70
71 GUM_CONSTRUCTOR(DBRowGeneratorEM);
72 }
73
75 template < GUM_Numeric GUM_SCALAR >
76 DBRowGeneratorEM< GUM_SCALAR >::DBRowGeneratorEM(const DBRowGeneratorEM< GUM_SCALAR >& from) :
77 DBRowGeneratorWithBN< GUM_SCALAR >(from), _input_row_(from._input_row_),
78 _missing_cols_(from._missing_cols_), _nb_miss_(from._nb_miss_),
79 _joint_proba_(from._joint_proba_), _filled_row1_(from._filled_row1_),
80 _filled_row2_(from._filled_row2_), _use_filled_row1_(from._use_filled_row1_),
81 _original_weight_(from._original_weight_) {
82 if (from._joint_inst_ != nullptr) {
83 _joint_inst_ = new Instantiation(_joint_proba_);
84 const auto& var_seq = _joint_inst_->variablesSequence();
85 const std::size_t size = var_seq.size();
86 for (std::size_t i = std::size_t(0); i < size; ++i) {
87 _joint_inst_->chgVal(Idx(i), from._joint_inst_->val(i));
88 }
89 }
90
91 GUM_CONS_CPY(DBRowGeneratorEM);
92 }
93
95 template < GUM_Numeric GUM_SCALAR >
96 DBRowGeneratorEM< GUM_SCALAR >::DBRowGeneratorEM(DBRowGeneratorEM< GUM_SCALAR >&& from) noexcept
97 :
98 DBRowGeneratorWithBN< GUM_SCALAR >(std::move(from)), _input_row_(from._input_row_),
99 _missing_cols_(std::move(from._missing_cols_)), _nb_miss_(from._nb_miss_),
100 _joint_proba_(std::move(from._joint_proba_)), _filled_row1_(std::move(from._filled_row1_)),
101 _filled_row2_(std::move(from._filled_row2_)), _use_filled_row1_(from._use_filled_row1_),
102 _original_weight_(from._original_weight_) {
103 if (from._joint_inst_ != nullptr) {
104 _joint_inst_ = new Instantiation(_joint_proba_);
105 const auto& var_seq = _joint_inst_->variablesSequence();
106 const std::size_t size = var_seq.size();
107 for (std::size_t i = std::size_t(0); i < size; ++i) {
108 _joint_inst_->chgVal(Idx(i), from._joint_inst_->val(i));
109 }
110 }
111
112 GUM_CONS_MOV(DBRowGeneratorEM);
113 }
114
116 template < GUM_Numeric GUM_SCALAR >
117 DBRowGeneratorEM< GUM_SCALAR >* DBRowGeneratorEM< GUM_SCALAR >::clone() const {
118 return new DBRowGeneratorEM< GUM_SCALAR >(*this);
119 }
120
122 template < GUM_Numeric GUM_SCALAR >
123 DBRowGeneratorEM< GUM_SCALAR >::~DBRowGeneratorEM() {
124 if (_joint_inst_ != nullptr) delete _joint_inst_;
125 GUM_DESTRUCTOR(DBRowGeneratorEM);
126 }
127
129 template < GUM_Numeric GUM_SCALAR >
130 DBRowGeneratorEM< GUM_SCALAR >&
131 DBRowGeneratorEM< GUM_SCALAR >::operator=(const DBRowGeneratorEM< GUM_SCALAR >& from) {
132 if (this != &from) {
133 DBRowGeneratorWithBN< GUM_SCALAR >::operator=(from);
134 _input_row_ = from._input_row_;
135 _missing_cols_ = from._missing_cols_;
136 _nb_miss_ = from._nb_miss_;
137 _joint_proba_ = from._joint_proba_;
138 _filled_row1_ = from._filled_row1_;
139 _filled_row2_ = from._filled_row2_;
140 _use_filled_row1_ = from._use_filled_row1_;
141 _original_weight_ = from._original_weight_;
142
143 if (_joint_inst_ != nullptr) {
144 delete _joint_inst_;
145 _joint_inst_ = nullptr;
146 }
147
148 if (from._joint_inst_ != nullptr) {
149 _joint_inst_ = new Instantiation(_joint_proba_);
150 const auto& var_seq = _joint_inst_->variablesSequence();
151 const std::size_t size = var_seq.size();
152 for (std::size_t i = std::size_t(0); i < size; ++i) {
153 _joint_inst_->chgVal(Idx(i), from._joint_inst_->val(i));
154 }
155 }
156 }
157
158 return *this;
159 }
160
162 template < GUM_Numeric GUM_SCALAR >
163 DBRowGeneratorEM< GUM_SCALAR >&
164 DBRowGeneratorEM< GUM_SCALAR >::operator=(DBRowGeneratorEM< GUM_SCALAR >&& from) {
165 if (this != &from) {
166 DBRowGeneratorWithBN< GUM_SCALAR >::operator=(std::move(from));
167 _input_row_ = from._input_row_;
168 _missing_cols_ = std::move(from._missing_cols_);
169 _nb_miss_ = from._nb_miss_;
170 _joint_proba_ = std::move(from._joint_proba_);
171 _filled_row1_ = std::move(from._filled_row1_);
172 _filled_row2_ = std::move(from._filled_row2_);
173 _use_filled_row1_ = from._use_filled_row1_;
174 _original_weight_ = from._original_weight_;
175
176 if (_joint_inst_ != nullptr) {
177 delete _joint_inst_;
178 _joint_inst_ = nullptr;
179 }
180
181 if (from._joint_inst_ != nullptr) {
182 _joint_inst_ = new Instantiation(_joint_proba_);
183 const auto& var_seq = _joint_inst_->variablesSequence();
184 const std::size_t size = var_seq.size();
185 for (std::size_t i = std::size_t(0); i < size; ++i) {
186 _joint_inst_->chgVal(Idx(i), from._joint_inst_->val(i));
187 }
188 }
189 }
190
191 return *this;
192 }
193
195 template < GUM_Numeric GUM_SCALAR >
196 const DBRow< DBTranslatedValue >& DBRowGeneratorEM< GUM_SCALAR >::generate() {
197 this->decreaseRemainingRows();
198
199 // if everything is observed, return the input row
200 if (_input_row_ != nullptr) return *_input_row_;
201
202 if (_use_filled_row1_) {
203 // get the weight of the row from the joint probability
204 _filled_row1_.setWeight(_joint_proba_.get(*_joint_inst_) * _original_weight_);
205
206 // fill the values of the row
207 for (std::size_t i = std::size_t(0); i < _nb_miss_; ++i)
208 _filled_row1_[_missing_cols_[i]].discr_val = _joint_inst_->val(i);
209
210 _joint_inst_->inc();
211 _use_filled_row1_ = false;
212
213 return _filled_row1_;
214 } else {
215 // get the weight of the row from the joint probability
216 _filled_row2_.setWeight(_joint_proba_.get(*_joint_inst_) * _original_weight_);
217
218 // fill the values of the row
219 for (std::size_t i = std::size_t(0); i < _nb_miss_; ++i)
220 _filled_row2_[_missing_cols_[i]].discr_val = _joint_inst_->val(i);
221
222 _joint_inst_->inc();
223 _use_filled_row1_ = true;
224
225 return _filled_row2_;
226 }
227 }
228
230 template < GUM_Numeric GUM_SCALAR >
231 std::size_t
232 DBRowGeneratorEM< GUM_SCALAR >::computeRows_(const DBRow< DBTranslatedValue >& row) {
233 // check if there are unobserved values among the columns of interest.
234 // If this is the case, save in _missing_cols_ all the columns with unobserved values
235 bool found_unobserved = false;
236 const auto& xrow = row.row();
237 for (const auto col: this->columns_of_interest_) {
238 switch (this->column_types_[col]) {
239 case DBTranslatedValueType::DISCRETE :
240 if (xrow[col].discr_val == std::numeric_limits< std::size_t >::max()) {
241 if (!found_unobserved) {
242 _missing_cols_.clear();
243 found_unobserved = true;
244 }
245 _missing_cols_.push_back(col);
246 }
247 break;
248
249 case DBTranslatedValueType::CONTINUOUS :
251 "The BDRowGeneratorEM does not handle yet continuous "
252 << "variables. But the variable in column" << col << " is continuous.");
253 break;
254
255 default :
257 "DBTranslatedValueType " << int(this->column_types_[col])
258 << " is not supported yet");
259 }
260 }
261
262 // if there is no unobserved value, make the _input_row_ point to the row
263 if (!found_unobserved) {
264 _input_row_ = &row;
265 return std::size_t(1);
266 }
267
268 _input_row_ = nullptr;
269 _nb_miss_ = _missing_cols_.size();
270 _original_weight_ = row.weight();
271
272 // here, there are missing symbols, so we should compute the distribution
273 // of the missing values. For this purpose, we use Variable Elimination
274 VariableElimination< GUM_SCALAR > ve(this->bn_);
275
276 // add the targets and fill the output row with the observed values
277 NodeSet target_set(_nb_miss_);
278 if (this->nodeId2columns_.empty()) {
279 std::size_t i = std::size_t(0);
280 bool end_miss = false;
281 for (const auto col: this->columns_of_interest_) {
282 if (!end_miss && (col == _missing_cols_[i])) {
283 target_set.insert(NodeId(col));
284 ++i;
285 if (i == _nb_miss_) end_miss = true;
286 } else {
287 _filled_row1_[col].discr_val = xrow[col].discr_val;
288 _filled_row2_[col].discr_val = xrow[col].discr_val;
289 }
290 }
291 } else {
292 std::size_t i = std::size_t(0);
293 bool end_miss = false;
294 for (const auto col: this->columns_of_interest_) {
295 if (!end_miss && (col == _missing_cols_[i])) {
296 target_set.insert(this->nodeId2columns_.first(col));
297 ++i;
298 if (i == _nb_miss_) end_miss = true;
299 } else {
300 _filled_row1_[col].discr_val = xrow[col].discr_val;
301 _filled_row2_[col].discr_val = xrow[col].discr_val;
302 }
303 }
304 }
305
306 ve.addJointTarget(target_set);
307
308 // add the evidence and the target into variable elimination
309 const std::size_t row_size = xrow.size();
310 if (this->nodeId2columns_.empty()) {
311 for (std::size_t col = std::size_t(0); col < row_size; ++col) {
312 switch (this->column_types_[col]) {
313 case DBTranslatedValueType::DISCRETE :
314 // only observed values are evidence
315 if (xrow[col].discr_val != std::numeric_limits< std::size_t >::max()) {
316 ve.addEvidence(NodeId(col), xrow[col].discr_val);
317 }
318 break;
319
320 case DBTranslatedValueType::CONTINUOUS :
322 "The BDRowGeneratorEM does not handle yet continuous "
323 << "variables. But the variable in column" << col << " is continuous.");
324 break;
325
326 default :
328 "DBTranslatedValueType " << int(this->column_types_[col])
329 << " is not supported yet");
330 }
331 }
332 } else {
333 for (std::size_t col = std::size_t(0); col < row_size; ++col) {
334 switch (this->column_types_[col]) {
335 case DBTranslatedValueType::DISCRETE :
336 // only observed values are evidence
337 if (xrow[col].discr_val != std::numeric_limits< std::size_t >::max()) {
338 ve.addEvidence(this->nodeId2columns_.first(col), xrow[col].discr_val);
339 }
340 break;
341
342 case DBTranslatedValueType::CONTINUOUS :
344 "The BDRowGeneratorEM does not handle yet continuous "
345 << "variables. But the variable in column" << col << " is continuous.");
346 break;
347
348 default :
350 "DBTranslatedValueType " << int(this->column_types_[col])
351 << " is not supported yet");
352 }
353 }
354 }
355
356 // get the tensor of the target set
357 Tensor< GUM_SCALAR >& pot
358 = const_cast< Tensor< GUM_SCALAR >& >(ve.jointPosterior(target_set));
359 _joint_proba_ = std::move(pot);
360 if (_joint_inst_ != nullptr) delete _joint_inst_;
361 _joint_inst_ = new Instantiation(_joint_proba_);
362
363 // get the mapping between variables of the joint proba and the
364 // columns in the database
365 const auto& var_sequence = _joint_proba_.variablesSequence();
366 if (this->nodeId2columns_.empty()) {
367 for (std::size_t i = std::size_t(0); i < _nb_miss_; ++i) {
368 _missing_cols_[i] = std::size_t(this->bn_->nodeId(*(var_sequence[i])));
369 }
370 } else {
371 for (std::size_t i = std::size_t(0); i < _nb_miss_; ++i) {
372 _missing_cols_[i] = this->nodeId2columns_.second(this->bn_->nodeId(*(var_sequence[i])));
373 }
374 }
375
376 return std::size_t(_joint_proba_.domainSize());
377 }
378
380 template < GUM_Numeric GUM_SCALAR >
381 void DBRowGeneratorEM< GUM_SCALAR >::setBayesNet(const BayesNet< GUM_SCALAR >& new_bn) {
382 // check that if nodeId2columns is not empty, then all the columns
383 // correspond to nodes of the BN
384 if (!this->nodeId2columns_.empty()) {
385 const DAG& dag = new_bn.internalDag();
386 for (auto iter = this->nodeId2columns_.begin(); iter != this->nodeId2columns_.end();
387 ++iter) {
388 if (!dag.existsNode(iter.first())) {
390 "Column " << iter.second() << " of the database is associated to Node ID "
391 << iter.first()
392 << ", which does not belong to the Bayesian network");
393 }
394 }
395 }
396
397 DBRowGeneratorWithBN< GUM_SCALAR >::setBayesNet(new_bn);
398
399 // we determine the size of the filled rows
400 std::size_t size = std::size_t(0);
401 if (this->nodeId2columns_.empty()) {
402 // here, we know that the DAG's node IDs correspond precisely to column indices
403 // in the database. So we can safely set the size of _filled_row_ to the highest
404 // node ID in the graph
405 for (auto node: new_bn.internalDag())
406 if (std::size_t(node) > size) size = std::size_t(node);
407 } else {
408 for (auto iter = this->nodeId2columns_.begin(); iter != this->nodeId2columns_.end();
409 ++iter) {
410 if (iter.second() > size) size = iter.second();
411 }
412 }
413 _filled_row1_.resize(size + 1);
414 _filled_row2_.resize(size + 1);
415 }
416
417 } /* namespace learning */
418
419} /* namespace gum */
420
421#endif /* DOXYGEN_SHOULD_SKIP_THIS */
A DBRowGenerator class that returns incomplete rows as EM would do.
A DBRowGenerator class that returns exactly the rows it gets in input.
Exception : node does not exist.
Exception : there is something wrong with an implementation.
DBRowGeneratorEM(const std::vector< DBTranslatedValueType > &column_types, const BayesNet< GUM_SCALAR > &bn, const Bijection< NodeId, std::size_t > &nodeId2columns=Bijection< NodeId, std::size_t >())
default constructor
Base class for DBRowGenerator classes that use a BN for computing their outputs.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
Set< NodeId > NodeSet
Some typdefs and define for shortcuts ...
DBRowGeneratorGoal
the type of things that a DBRowGenerator is designed for
include the inlined functions if necessary
Definition CSVParser.h:55
gum is the global namespace for all aGrUM entities
Definition agrum.h:46