aGrUM 3.2.0
a C++ library for (probabilistic) graphical models
KTBNGenerator_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
48
49#pragma once
50
51#include <cmath>
52#include <utility>
53
56
57namespace gum {
58
59 template < GUM_Numeric GUM_SCALAR >
60 template < typename T >
61 void KTBNGenerator< GUM_SCALAR >::_shuffle_(std::vector< T >& v) {
62 for (std::size_t i = v.size(); i > 1; --i)
63 std::swap(v[i - 1], v[randomValue(static_cast< Size >(i))]);
64 }
65
66 // ===========================================================================
67 // Constructors / Destructor
68 // ===========================================================================
69
70 template < GUM_Numeric GUM_SCALAR >
72 Size nbTemporal,
73 Size nbAtemporal,
74 Size maxArcs,
75 Size maxModality) :
76 _k_(k), _nbTemporal_(nbTemporal), _nbAtemporal_(nbAtemporal), _maxArcs_(maxArcs),
77 _maxModality_(maxModality) {
78 if (k == 0) GUM_ERROR(InvalidArgument, "KTBNGenerator: k must be >= 1.")
79 if (maxModality < 2) GUM_ERROR(InvalidArgument, "KTBNGenerator: maxModality must be >= 2.")
80 GUM_CONSTRUCTOR(KTBNGenerator)
81 }
82
83 template < GUM_Numeric GUM_SCALAR >
87
88 // ===========================================================================
89 // Configuration
90 // ===========================================================================
91
92 template < GUM_Numeric GUM_SCALAR >
94 if (density < 0.0 || density > 1.0)
95 GUM_ERROR(OutOfBounds, "KTBNGenerator: density must lie in [0,1].")
96 _density_ = density;
97 return *this;
98 }
99
100 template < GUM_Numeric GUM_SCALAR >
102 Size maxModality) {
103 if (minModality < 2) GUM_ERROR(InvalidArgument, "KTBNGenerator: minModality must be >= 2.")
104 if (maxModality < minModality)
105 GUM_ERROR(InvalidArgument, "KTBNGenerator: maxModality must be >= minModality.")
106 _minModality_ = minModality;
107 _maxModality_ = maxModality;
108 return *this;
109 }
110
111 template < GUM_Numeric GUM_SCALAR >
113 _maxParents_ = maxParents;
114 return *this;
115 }
116
117 template < GUM_Numeric GUM_SCALAR >
122
123 template < GUM_Numeric GUM_SCALAR >
128
129 template < GUM_Numeric GUM_SCALAR >
132 const std::string& atemporal) {
133 if (temporal.empty() || atemporal.empty())
134 GUM_ERROR(InvalidArgument, "KTBNGenerator: a name prefix cannot be empty.")
135 if (temporal == atemporal)
136 GUM_ERROR(InvalidArgument, "KTBNGenerator: the two name prefixes must differ.")
137 _temporalPrefix_ = temporal;
138 _atemporalPrefix_ = atemporal;
139 return *this;
140 }
141
142 // ===========================================================================
143 // Accessors
144 // ===========================================================================
145
146 template < GUM_Numeric GUM_SCALAR >
148 return _k_;
149 }
150
151 template < GUM_Numeric GUM_SCALAR >
153 const Size n = _nbTemporal_, m = _nbAtemporal_, k = _k_;
154 // guarded against unsigned underflow: each term is 0 when its shape is degenerate
155 const Size atemporalPairs = (m >= 2) ? m * (m - 1) / 2 : 0; // atemporal -> atemporal
156 const Size atemporalToAll = m * n * k; // atemporal -> temporal
157 const Size slicePairs = (k >= 2) ? k * (k - 1) / 2 : 0;
158 const Size crossSlice = n * n * slicePairs; // temporal, lag >= 1
159 const Size intraSlice = ((n >= 2) ? n * (n - 1) / 2 : 0) * k; // temporal, lag 0
160 return atemporalPairs + atemporalToAll + crossSlice + intraSlice;
161 }
162
163 // ===========================================================================
164 // Generation
165 // ===========================================================================
166
167 template < GUM_Numeric GUM_SCALAR >
168 std::vector< typename KTBNGenerator< GUM_SCALAR >::_Arc_ >
169 KTBNGenerator< GUM_SCALAR >::_legalArcs_(const std::vector< std::string >& temporal,
170 const std::vector< std::string >& atemporal,
171 const std::vector< Size >& tRank,
172 const std::vector< Size >& aRank) const {
173 const int k = static_cast< int >(_k_);
174 constexpr int AT = KTBN< GUM_SCALAR >::ATEMPORAL;
175 std::vector< _Arc_ > out;
176 out.reserve(nbLegalArcs());
177
178 // atemporal -> atemporal: only low rank to high rank, which keeps it acyclic
179 for (std::size_t i = 0; i < atemporal.size(); ++i)
180 for (std::size_t j = 0; j < atemporal.size(); ++j)
181 if (aRank[i] < aRank[j]) out.push_back({atemporal[i], AT, atemporal[j], AT});
182
183 // atemporal -> temporal, at any slice: never cyclic, since the k-DBN's own
184 // rules already forbid a temporal variable from parenting an atemporal one.
185 for (const auto& a: atemporal)
186 for (const auto& b: temporal)
187 for (int s = 0; s < k; ++s)
188 out.push_back({a, AT, b, s});
189
190 // temporal, lag >= 1: the slice index strictly increases, so never cyclic
191 for (int s1 = 0; s1 < k; ++s1)
192 for (int s2 = s1 + 1; s2 < k; ++s2)
193 for (const auto& b1: temporal)
194 for (const auto& b2: temporal)
195 out.push_back({b1, s1, b2, s2});
196
197 // temporal, lag 0: the other cycle-prone family, so rank-ordered as well
198 for (int s = 0; s < k; ++s)
199 for (std::size_t i = 0; i < temporal.size(); ++i)
200 for (std::size_t j = 0; j < temporal.size(); ++j)
201 if (tRank[i] < tRank[j]) out.push_back({temporal[i], s, temporal[j], s});
202
203 return out;
204 }
205
206 template < GUM_Numeric GUM_SCALAR >
207 void KTBNGenerator< GUM_SCALAR >::generateKTBN(KTBN< GUM_SCALAR >& out) {
208 out = KTBN< GUM_SCALAR >(_k_);
209
210 // ---- variables, with domain sizes drawn in [_minModality_, _maxModality_] ----
211 const Size spread = _maxModality_ - _minModality_ + 1;
212 std::vector< std::string > temporal, atemporal;
213 temporal.reserve(_nbTemporal_);
214 atemporal.reserve(_nbAtemporal_);
215
216 for (Size i = 0; i < _nbTemporal_; ++i) {
217 const std::string name = _temporalPrefix_ + std::to_string(i);
218 out.add(LabelizedVariable(name, name, _minModality_ + randomValue(spread)), true);
219 temporal.push_back(name);
220 }
221 for (Size i = 0; i < _nbAtemporal_; ++i) {
222 const std::string name = _atemporalPrefix_ + std::to_string(i);
223 out.add(LabelizedVariable(name, name, _minModality_ + randomValue(spread)), false);
224 atemporal.push_back(name);
225 }
226
227 // ---- random ranks for the two cycle-prone arc families ----
228 std::vector< Size > tPerm(_nbTemporal_), aPerm(_nbAtemporal_);
229 for (Size i = 0; i < _nbTemporal_; ++i)
230 tPerm[i] = i;
231 for (Size i = 0; i < _nbAtemporal_; ++i)
232 aPerm[i] = i;
233 _shuffle_(tPerm);
234 _shuffle_(aPerm);
235
236 std::vector< Size > tRank(_nbTemporal_), aRank(_nbAtemporal_);
237 for (Size i = 0; i < _nbTemporal_; ++i)
238 tRank[tPerm[i]] = i;
239 for (Size i = 0; i < _nbAtemporal_; ++i)
240 aRank[aPerm[i]] = i;
241
242 // ---- how many arcs to draw ----
243 const Size legalCount = nbLegalArcs();
244 Size target = (_maxArcs_ != 0)
245 ? _maxArcs_
246 : static_cast< Size >(std::llround(_density_ * double(legalCount)));
247 if (target > legalCount) target = legalCount;
248
249 Size added = 0;
250
251 // ---- the guaranteed lag-(k-1) kernel arc ----
252 // Slice 0 -> slice k-1 is the widest reach a k-slice template can express, so
253 // this alone pins the model's effective order to k. Placed before the random
254 // fill, and counted against the budget, so density still bounds the total.
255 if (_guaranteeOrder_ && _k_ >= 2 && _nbTemporal_ >= 1) {
256 const std::string& tail = temporal[randomValue(_nbTemporal_)];
257 const std::string& head = temporal[randomValue(_nbTemporal_)];
258 out.addArc(tail, 0, head, static_cast< int >(_k_) - 1);
259 ++added;
260 }
261
262 // ---- fill with random legal arcs ----
263 auto legal = _legalArcs_(temporal, atemporal, tRank, aRank);
264 _shuffle_(legal);
265 for (const auto& a: legal) {
266 if (added >= target) break;
267 if (out.existsArc(a.tailBase, a.tailSlice, a.headBase, a.headSlice)) continue;
268 if (_maxParents_ != 0
269 && static_cast< Size >(out.parents(a.headBase, a.headSlice).size()) >= _maxParents_)
270 continue;
271 out.addArc(a.tailBase, a.tailSlice, a.headBase, a.headSlice);
272 ++added;
273 }
274
275 if (_generateCPTs_) out.generateCPTs();
276 }
277
278 template < GUM_Numeric GUM_SCALAR >
280 KTBN< GUM_SCALAR > out(_k_);
281 generateKTBN(out);
282 return out;
283 }
284
285} // namespace gum
Random k-order dynamic Bayesian network generator.
Exception: at least one argument passed to a function is not what was expected.
Draws a random k-DBN template (structure and, optionally, CPTs).
KTBNGenerator< GUM_SCALAR > & setMaxParents(Size maxParents)
Caps the number of parents of any node, which bounds CPT size. 0 (default) means unlimited — a dense ...
KTBN< GUM_SCALAR > generate()
Same, returning the model by value.
KTBNGenerator(Size k, Size nbTemporal, Size nbAtemporal=0, Size maxArcs=0, Size maxModality=2)
Constructor.
std::string _atemporalPrefix_
KTBNGenerator< GUM_SCALAR > & setGenerateCPTs(bool on)
Whether to fill the CPTs with random values (default true). When false only the structure is drawn an...
void generateKTBN(KTBN< GUM_SCALAR > &out)
Fills out with a freshly drawn model (its previous content is discarded). Seed it with gum::initRando...
static void _shuffle_(std::vector< T > &v)
Fisher-Yates through gum::randomValue, so gum::initRandom() alone makes a whole generation reproducib...
KTBNGenerator< GUM_SCALAR > & setDensity(double density)
Fraction of the legal arc set to draw, in . Ignored when a non-zero maxArcs was given to the construc...
KTBNGenerator< GUM_SCALAR > & setDomainRange(Size minModality, Size maxModality)
Domain sizes are drawn uniformly in .
std::string _temporalPrefix_
std::vector< _Arc_ > _legalArcs_(const std::vector< std::string > &temporal, const std::vector< std::string > &atemporal, const std::vector< Size > &tRank, const std::vector< Size > &aRank) const
Every arc the k-DBN's rules allow, with the two cycle-prone families (lag 0, atemporal→atemporal) alr...
~KTBNGenerator()
Destructor.
KTBNGenerator< GUM_SCALAR > & setNamePrefixes(const std::string &temporal, const std::string &atemporal)
Name prefixes; variables are prefix0, prefix1, … Defaults are "X" (temporal) and "A" (atemporal).
KTBNGenerator< GUM_SCALAR > & setGuaranteeOrder(bool on)
Force one arc of lag into the kernel slice, so the model's effective order equals (see the class do...
static constexpr int ATEMPORAL
Conventional time-slice value denoting an atemporal (static) variable.
Definition KTBN.h:200
LabelizedVariable()
(protected) Default constructor
Exception : out of bound.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
std::size_t Size
In aGrUM, hashed values are unsigned long int.
Definition types.h:74
GUM_SHARED_PUBLIC Idx randomValue(const Size max=2)
Returns a random Idx between 0 and max-1 included.
Base class for labelized discrete random variables.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46