aGrUM 3.1.1
a C++ library for (probabilistic) graphical models
GumBNReader_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
42#pragma once
43
44#include <agrum/BN/io/GUM/GumBNReader.h> // to ease IDE parser
45#ifndef DOXYGEN_SHOULD_SKIP_THIS
46
47// to ease parsing in IDE
52
53# include <agrum/base/external/json/json.hpp>
54using json = nlohmann::json;
55
56# include <unordered_map>
57
58namespace gum {
59 template < GUM_Numeric GUM_SCALAR >
61 std::string_view filename,
62 bool binary) : BNReader< GUM_SCALAR >(bn, filename) {
63 GUM_CONSTRUCTOR(GumBNReader)
64 _bn_ = bn;
65 _streamName_ = filename;
66 _parseDone_ = false;
67 _binary_ = binary;
68 }
69
70 template < GUM_Numeric GUM_SCALAR >
71 GumBNReader< GUM_SCALAR >::GumBNReader(BayesNet< GUM_SCALAR >* bn) :
72 BNReader< GUM_SCALAR >(bn, "") {
73 GUM_CONSTRUCTOR(GumBNReader)
74 _bn_ = bn;
75 _streamName_ = "";
76 _parseDone_ = false;
77 _binary_ = false;
78 }
79
80 template < GUM_Numeric GUM_SCALAR >
81 GumBNReader< GUM_SCALAR >::~GumBNReader() {
82 GUM_DESTRUCTOR(GumBNReader)
83 }
84
85 template < GUM_Numeric GUM_SCALAR >
86 template < typename JsonType >
87 Size GumBNReader< GUM_SCALAR >::_proceedFromJson_(const JsonType& content) {
88 Size nberrors = 0;
89
90 if (content.contains("type") && content["type"].template get< std::string >() != "BN") {
91 addError("Invalid GUM file format: expected 'BN' type, got '"
92 + content["type"].template get< std::string >() + "'",
93 _streamName_,
94 0,
95 0);
96 return ++nberrors;
97 }
98
99 if (!content.contains("nodes") || !content.contains("parents") || !content.contains("cpt")) {
100 addError("Invalid GUM file format: missing 'nodes', 'parents' or 'cpt' sections",
101 _streamName_,
102 0,
103 0);
104 return ++nberrors;
105 }
106
107 try {
108 BayesNet< GUM_SCALAR > tmp;
109
110 // index cpt entries by node name once: content["cpt"].contains()/.at() are each an O(N)
111 // linear scan on nlohmann::ordered_json, so calling them per node (below) would make
112 // node reading O(N^2) on the number of nodes.
113 const auto& cptSection = content["cpt"];
114 std::unordered_map< std::string, const JsonType* > cptByName;
115 cptByName.reserve(cptSection.size());
116 for (const auto& entry: cptSection.items()) {
117 cptByName.emplace(entry.key(), &entry.value());
118 }
119
120 // iterate on nodes in json
121 for (const auto& node: content["nodes"]) {
122 auto var = fastVariable< GUM_SCALAR >(node.template get< std::string >());
123 if (var->domainSize() < 2)
124 GUM_ERROR(OperationNotAllowed, var->name() << " has a domain size <2")
125 const auto& nodeName = var->name();
126 const auto cptIt = cptByName.find(nodeName);
127 if (cptIt == cptByName.end())
128 GUM_ERROR(NotFound, "Node '" << nodeName << "' has no entry in the 'cpt' section")
129 const auto& cptEntry = *(cptIt->second);
130
131 if (cptEntry.is_object()) {
132 if (!cptEntry.contains("kind"))
133 GUM_ERROR(NotFound, "Missing 'kind' for node '" << nodeName << "'")
134 const auto kind = cptEntry.at("kind").template get< std::string >();
135 if (kind == "aggregator") {
136 if (!cptEntry.contains("name"))
137 GUM_ERROR(NotFound, "Missing 'name' for aggregator node '" << nodeName << "'")
138 const auto name = cptEntry.at("name").template get< std::string >();
139 const Idx value = cptEntry.value("value", Idx(1));
140 tmp._addAggregator_(name, *var, value);
141 } else if (kind == "ici") {
142 if (!cptEntry.contains("name") || !cptEntry.contains("externalWeight"))
144 "Missing 'name' or 'externalWeight' for ici node '" << nodeName << "'")
145 const auto name = cptEntry.at("name").template get< std::string >();
146 const auto externalWeight = cptEntry.at("externalWeight").template get< GUM_SCALAR >();
147 tmp._addICIModel_(name, *var, externalWeight);
148 } else {
149 GUM_ERROR(NotFound, "Unknown cpt kind '" << kind << "' for node " << nodeName)
150 }
151 } else {
152 tmp.add(*var);
153 }
154 }
155 // iterate on parents in json
156 for (const auto& parent: content["parents"].items()) {
157 const auto& nodeName = parent.key();
158 for (const auto& p: parent.value()) {
159 tmp.addArc(p.template get< std::string >(), nodeName);
160 }
161 }
162 // iterate on cpt in json
163 for (const auto& cpt: content["cpt"].items()) {
164 const auto& nodeName = cpt.key();
165 const auto& values = cpt.value();
166 if (values.is_object()) {
167 if (!values.contains("kind"))
168 GUM_ERROR(NotFound, "Missing 'kind' for node '" << nodeName << "'")
169 const auto kind = values.at("kind").template get< std::string >();
170 if (kind == "ici") {
171 const auto* ici = dynamic_cast< const MultiDimICIModel< GUM_SCALAR >* >(
172 tmp.cpt(nodeName).content());
173 if (ici == nullptr)
175 "Node " << nodeName << " is tagged as an ICI model but does not hold one")
176 if (!values.contains("causalWeights"))
177 GUM_ERROR(NotFound, "Missing 'causalWeights' for ici node '" << nodeName << "'")
178 for (const auto& w: values.at("causalWeights").items()) {
179 ici->causalWeight(tmp.variable(w.key()), w.value().template get< GUM_SCALAR >());
180 }
181 }
182 // aggregator nodes are already fully configured, nothing to fill
183 } else {
184 tmp.cpt(nodeName).fillWith(values.template get< std::vector< double > >());
185 }
186 }
187 // iterate on properties in json (optional section)
188 if (content.contains("properties")) {
189 for (const auto& prop: content["properties"].items()) {
190 tmp.setProperty(prop.key(), prop.value().template get< std::string >());
191 }
192 }
193 *_bn_ = std::move(tmp);
194 _parseDone_ = true;
195 } catch (const gum::Exception& e) {
196 addError(e.errorContent(), _streamName_, 0, 0);
197 return ++nberrors;
198 }
199 return nberrors;
200 }
201
202 template < GUM_Numeric GUM_SCALAR >
203 Size GumBNReader< GUM_SCALAR >::proceed() {
204 if (_parseDone_) { return 0; }
205 if (_streamName_.empty()) {
207 "GumBNReader was constructed without a filename: use proceedFromString() instead "
208 "of proceed()")
209 }
210 Size nberrors = 0;
211
212 std::ifstream file(_streamName_, _binary_ ? std::ios::binary : std::ios::in);
213 if (!file.is_open()) {
214 addException("No such file " + _streamName_, _streamName_);
215 return ++nberrors;
216 }
217 try {
218 const auto content
219 = _binary_ ? json::from_msgpack(_readVector_(file)) : json::parse(file, nullptr, false);
220 file.close();
221 if (content.is_discarded()) {
222 addException("Error parsing file", _streamName_);
223 return ++nberrors;
224 }
225 return _proceedFromJson_(content);
226 } catch (const std::exception& e) {
227 addException(std::string("Error reading binary file: ") + e.what(), _streamName_);
228 return ++nberrors;
229 }
230 }
231
232 template < GUM_Numeric GUM_SCALAR >
233 Size GumBNReader< GUM_SCALAR >::proceedFromString(std::string_view content) {
234 if (_parseDone_) { return 0; }
235 const auto j = json::parse(content, nullptr, false);
236 if (j.is_discarded()) {
237 addException("Invalid JSON string", _streamName_);
238 return 1;
239 }
240 try {
241 return _proceedFromJson_(j);
242 } catch (const std::exception& e) {
243 addException(std::string("Error reading string: ") + e.what(), _streamName_);
244 return 1;
245 }
246 }
247
248 template < GUM_Numeric GUM_SCALAR >
249 void GumBNReader< GUM_SCALAR >::showElegantErrorsAndWarnings(std::ostream& stream) const {
250 if (_parseDone_ || count() > 0) elegantErrorsAndWarnings(stream);
251 else { GUM_ERROR(OperationNotAllowed, "File not parsed yet") }
252 }
253
254 template < GUM_Numeric GUM_SCALAR >
255 void GumBNReader< GUM_SCALAR >::showErrorCounts(std::ostream& stream) const {
256 if (_parseDone_ || count() > 0) syntheticResults(stream);
257 else { GUM_ERROR(OperationNotAllowed, "File not parsed yet") }
258 }
259} // namespace gum
260
261#endif // DOXYGEN_SHOULD_SKIP_THIS
Shared binary I/O helpers for GUM (jgum/bgum) serialization.
Pure virtual class for reading a BN from a file.
Definition BNReader.h:78
Class representing a Bayesian network.
Definition BayesNet.h:99
Base class for all aGrUM's exceptions.
Definition exceptions.h:122
GUM_NODISCARD std::string errorContent() const
Returns the message content.
GumBNReader(BayesNet< GUM_SCALAR > *bn, std::string_view filename, bool binary=false)
Constructor A reader is defined for reading a defined file.
Exception : the element we looked for cannot be found.
Exception : operation not allowed.
#define GUM_ERROR(type, msg)
Definition exceptions.h:76
Abstract base class for all multi dimensionnal Causal Independency models.
gum is the global namespace for all aGrUM entities
Definition agrum.h:46
std::vector< uint8_t > _readVector_(std::istream &is)
Reads a length-prefixed byte vector from a binary stream (bgum format).
STL namespace.