aGrUM 3.2.0
a C++ library for (probabilistic) graphical models
IKTBNLearner_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
47
48#pragma once
49
51
52namespace gum {
53
54 namespace learning {
55
56 template < GUM_Numeric GUM_SCALAR >
57 std::string IKTBNLearner< GUM_SCALAR >::_encode_(std::string_view base, int slice) const {
58 if (slice == KTBN< GUM_SCALAR >::ATEMPORAL) return std::string{base};
59 return std::string{base} + '[' + std::to_string(slice) + ']';
60 }
61
62 template < GUM_Numeric GUM_SCALAR >
63 std::pair< std::string, int >
64 IKTBNLearner< GUM_SCALAR >::_determineNode_(const std::string& name) const {
65 // check atemporal set first: a name registered as atemporal always maps
66 // to ATEMPORAL, even if it syntactically looks like "base[t]"
68
69 // syntactic parse: look for a trailing "[digits]" suffix
70 const std::size_t bracketPos = name.rfind('[');
71 if (bracketPos == std::string::npos) return {name, KTBN< GUM_SCALAR >::ATEMPORAL};
72
73 const std::string_view bracketContent{name.c_str() + bracketPos + 1,
74 name.size() - bracketPos - 1};
75 if (bracketContent.empty() || bracketContent.back() != ']')
76 return {name, KTBN< GUM_SCALAR >::ATEMPORAL};
77
78 const std::string_view digits = bracketContent.substr(0, bracketContent.size() - 1);
79 if (digits.empty()) return {name, KTBN< GUM_SCALAR >::ATEMPORAL};
80 for (const char c: digits)
81 if (std::isdigit(static_cast< unsigned char >(c)) == 0)
82 return {name, KTBN< GUM_SCALAR >::ATEMPORAL};
83
84 int slice{};
85 try {
86 slice = std::stoi(std::string{digits});
87 } catch (const std::out_of_range&) {
89 "Node name '" << name << "' has a slice index too large to represent as int.")
90 }
91 return {name.substr(0, bracketPos), slice};
92 }
93
94 template < GUM_Numeric GUM_SCALAR >
96 std::string_view head,
97 std::string_view action) const {
98 const int tailSlice = _determineNode_(std::string{tail}).second;
99 const int headSlice = _determineNode_(std::string{head}).second;
100 if (tailSlice != KTBN< GUM_SCALAR >::ATEMPORAL && headSlice == KTBN< GUM_SCALAR >::ATEMPORAL)
102 "cannot " << action << " " << tail << " -> " << head
103 << ": a temporal variable can never be a parent of an atemporal one; "
104 "this constraint is part of the k-TBN definition")
105 if (headSlice < tailSlice)
107 "cannot " << action << " " << tail << " -> " << head
108 << ": its head is at an earlier time slice than its tail, which "
109 "violates temporal causality")
110 }
111
112 template < GUM_Numeric GUM_SCALAR >
113 std::unordered_set< std::string > IKTBNLearner< GUM_SCALAR >::_scanConstantColumns_(
114 std::string_view dirPath,
115 std::string_view csvBaseName,
116 Size nbSamples,
117 const std::vector< std::string >& missingSymbols) {
118 namespace fs = std::filesystem;
119 const fs::path dir{dirPath};
120 const std::string stem{csvBaseName};
121 const std::unordered_set< std::string > missing(missingSymbols.begin(), missingSymbols.end());
122
123 std::vector< std::string > header; // captured from trajectory 1
124 std::vector< std::size_t > candidates; // column indices not yet falsified; shrinks only
125 std::vector< std::string > firstSeen; // per column: first non-missing value THIS trajectory
126 std::vector< bool >
127 haveFirstSeen; // per column: whether firstSeen[c] is set THIS trajectory
128
129 for (Size i = 0; i < nbSamples; ++i) {
130 // once every column has been falsified, nothing left to test can
131 // ever become atemporal again, so remaining trajectories are never
132 // even opened. Not part of the for-condition: `candidates` does not
133 // exist yet before trajectory 1 populates it.
134 if (i > 0 && candidates.empty()) break;
135
136 const fs::path file = dir / (stem + std::to_string(i + 1) + ".csv");
137 std::ifstream is(file, std::ifstream::in);
138 if (!is.is_open()) GUM_ERROR(IOError, "Cannot open " << file.string());
139
140 CSVParser parser(is, file.string());
141 parser.next();
142 const auto& raw = parser.current();
143
144 if (i == 0) {
145 header.assign(raw.begin(), raw.end());
146 candidates.resize(header.size());
147 std::iota(candidates.begin(), candidates.end(), std::size_t{0});
148 } else {
149 bool same = (raw.size() == header.size());
150 for (std::size_t c = 0; same && c < header.size(); ++c)
151 same = (raw[c] == header[c]);
152 if (!same)
154 "Header of " << file.string() << " differs from trajectory 1");
155 }
156
157 haveFirstSeen.assign(header.size(), false);
158 firstSeen.assign(header.size(), {});
159
160 while (parser.next()) {
161 const auto& tokens = parser.current();
162 if (tokens.size() != header.size())
164 "Trajectory " << (i + 1) << ", row " << parser.nbLine() << ": expected "
165 << header.size() << " columns, got " << tokens.size());
166 // iterate only the still-live candidates, swap-erasing any just
167 // falsified so later rows (and later trajectories) never revisit it
168 for (std::size_t idx = 0; idx < candidates.size();) {
169 const std::size_t c = candidates[idx];
170 if (missing.contains(tokens[c])) {
171 ++idx; // uninformative row for this column, still a candidate
172 continue;
173 }
174 if (!haveFirstSeen[c]) {
175 firstSeen[c] = tokens[c];
176 haveFirstSeen[c] = true;
177 ++idx;
178 } else if (tokens[c] != firstSeen[c]) {
179 candidates[idx] = candidates.back(); // falsified: drop, O(1)
180 candidates.pop_back();
181 } else {
182 ++idx;
183 }
184 }
185 if (candidates.empty()) break; // nothing left to test in this file either
186 }
187 }
188
189 std::unordered_set< std::string > atemporalVars;
190 for (const std::size_t c: candidates)
191 atemporalVars.insert(header[c]);
192 return atemporalVars;
193 }
194
195 template < GUM_Numeric GUM_SCALAR >
197 std::string_view context) const {
198 const std::string b{base};
199 if (!_isKnownBase_(b))
201 "unknown base variable '" << base
202 << "': it is not one of this learner's variables")
205 "atemporal variable '" << base << "' cannot appear in " << context
206 << ": it has no per-slice instance")
207 }
208
209 template < GUM_Numeric GUM_SCALAR >
210 void IKTBNLearner< GUM_SCALAR >::_checkMinimalOrder_(Size order, std::string_view label) {
211 if (order < 2)
213 "a k-TBN learner requires "
214 << label << " >= 2: k=1 is a static Bayesian network, use BNLearner instead")
215 }
216
217 } /* namespace learning */
218} /* namespace gum */
Common configuration interface for k-TBN learners.
Exception : input/output problem.
Exception: at least one argument passed to a function is not what was expected.
static constexpr int ATEMPORAL
Conventional time-slice value denoting an atemporal (static) variable.
Definition KTBN.h:200
Class for fast parsing of CSV file (never more than one line in application memory).
Definition CSVParser.h:78
bool next()
gets the next line of the csv stream and parses it
std::size_t nbLine() const
returns the current line number within the stream
const std::vector< std::string > & current() const
returns the current parsed line
virtual bool _isKnownBase_(std::string_view base) const =0
Whether base is one of this learner's variables, temporal or atemporal. The second subclass-specific ...
void _checkBaseIsTemporal_(std::string_view base, std::string_view context) const
Throw InvalidArgument unless base is a known temporal base. context completes "cannot appear in <cont...
void _checkArcTemporallyFeasible_(std::string_view tail, std::string_view head, std::string_view action) const
Reject an arc the k-TBN definition can never contain, so eraseForbiddenArc and addMandatoryArc both f...
static std::unordered_set< std::string > _scanConstantColumns_(std::string_view dirPath, std::string_view csvBaseName, Size nbSamples, const std::vector< std::string > &missingSymbols)
Scans every trajectory and returns the base names classified atemporal: those whose value never chang...
virtual const std::unordered_set< std::string > & _atemporalVarNames_() const =0
The base names of the atemporal (static) variables. The only subclass-specific input to determineNode...
static void _checkMinimalOrder_(Size order, std::string_view label)
Throw InvalidArgument unless order is at least 2, label naming the offending parameter ("k" for the f...
std::string _encode_(std::string_view base, int slice) const
(base, slice) -> engine name ("A[1]" / atemporal engine name). Pure function, shared by every learner...
std::pair< std::string, int > _determineNode_(const std::string &name) const
engine name -> (base, slice); atemporal names map to KTBN::ATEMPORAL. Shared by every learner; only t...
#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 bool contains(std::string_view s, std::string_view needle)
true if needle in s
include the inlined functions if necessary
Definition CSVParser.h:55
gum is the global namespace for all aGrUM entities
Definition agrum.h:46