From 1a09ccca4c6da3fc92892fb06d20ed088baa5312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana?= Date: Sat, 5 Aug 2023 14:40:42 +0200 Subject: [PATCH] Add KDBNew fix computeCPT error --- Makefile | 3 + sample/sample.cc | 112 +++++++++++++++++------------------ src/BayesNet/CMakeLists.txt | 2 +- src/BayesNet/Classifier.cc | 18 +++--- src/BayesNet/Classifier.h | 1 + src/BayesNet/KDB.cc | 1 + src/BayesNet/KDBNew.cc | 48 +++++++++++++++ src/BayesNet/KDBNew.h | 20 +++++++ src/BayesNet/Network.cc | 46 +++++++------- src/BayesNet/Network.h | 3 +- src/BayesNet/Node.cc | 5 +- src/BayesNet/Node.h | 2 +- src/BayesNet/TANNew.cc | 14 +++-- src/Platform/Experiment.cc | 5 ++ src/Platform/Models.h | 1 + src/Platform/modelRegister.h | 2 + tests/BayesNetwork.cc | 20 ++----- 17 files changed, 192 insertions(+), 111 deletions(-) create mode 100644 src/BayesNet/KDBNew.cc create mode 100644 src/BayesNet/KDBNew.h diff --git a/Makefile b/Makefile index f841342..1307544 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,9 @@ setup: ## Install dependencies for tests and coverage dependency: ## Create a dependency graph diagram of the project (build/dependency.png) cd build && cmake .. --graphviz=dependency.dot && dot -Tpng dependency.dot -o dependency.png +build: ## Build the main and BayesNetSample + cmake --build build -t main -t BayesNetSample -j 32 + debug: ## Build a debug version of the project @echo ">>> Building Debug BayesNet ..."; @if [ -d ./build ]; then rm -rf ./build; fi diff --git a/sample/sample.cc b/sample/sample.cc index ef81546..7da318d 100644 --- a/sample/sample.cc +++ b/sample/sample.cc @@ -178,61 +178,59 @@ int main(int argc, char** argv) cout << "end." << endl; auto score = clf->score(Xd, y); cout << "Score: " << score << endl; - auto graph = clf->graph(); - auto dot_file = model_name + "_" + file_name; - ofstream file(dot_file + ".dot"); - file << graph; - file.close(); - cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl; - cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl; - string stratified_string = stratified ? " Stratified" : ""; - cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl; - cout << "==========================================" << endl; - torch::Tensor Xt = torch::zeros({ static_cast(Xd.size()), static_cast(Xd[0].size()) }, torch::kInt32); - torch::Tensor yt = torch::tensor(y, torch::kInt32); - for (int i = 0; i < features.size(); ++i) { - Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32)); - } - float total_score = 0, total_score_train = 0, score_train, score_test; - Fold* fold; - if (stratified) - fold = new StratifiedKFold(nFolds, y, seed); - else - fold = new KFold(nFolds, y.size(), seed); - for (auto i = 0; i < nFolds; ++i) { - auto [train, test] = fold->getFold(i); - cout << "Fold: " << i + 1 << endl; - if (tensors) { - auto ttrain = torch::tensor(train, torch::kInt64); - auto ttest = torch::tensor(test, torch::kInt64); - torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain); - torch::Tensor ytraint = yt.index({ ttrain }); - torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest); - torch::Tensor ytestt = yt.index({ ttest }); - clf->fit(Xtraint, ytraint, features, className, states); - auto temp = clf->predict(Xtraint); - score_train = clf->score(Xtraint, ytraint); - score_test = clf->score(Xtestt, ytestt); - } else { - auto [Xtrain, ytrain] = extract_indices(train, Xd, y); - auto [Xtest, ytest] = extract_indices(test, Xd, y); - clf->fit(Xtrain, ytrain, features, className, states); - - score_train = clf->score(Xtrain, ytrain); - score_test = clf->score(Xtest, ytest); - } - if (dump_cpt) { - cout << "--- CPT Tables ---" << endl; - clf->dump_cpt(); - } - total_score_train += score_train; - total_score += score_test; - cout << "Score Train: " << score_train << endl; - cout << "Score Test : " << score_test << endl; - cout << "-------------------------------------------------------------------------------" << endl; - } - cout << "**********************************************************************************" << endl; - cout << "Average Score Train: " << total_score_train / nFolds << endl; - cout << "Average Score Test : " << total_score / nFolds << endl; - return 0; + // auto graph = clf->graph(); + // auto dot_file = model_name + "_" + file_name; + // ofstream file(dot_file + ".dot"); + // file << graph; + // file.close(); + // cout << "Graph saved in " << model_name << "_" << file_name << ".dot" << endl; + // cout << "dot -Tpng -o " + dot_file + ".png " + dot_file + ".dot " << endl; + // string stratified_string = stratified ? " Stratified" : ""; + // cout << nFolds << " Folds" << stratified_string << " Cross validation" << endl; + // cout << "==========================================" << endl; + // torch::Tensor Xt = torch::zeros({ static_cast(Xd.size()), static_cast(Xd[0].size()) }, torch::kInt32); + // torch::Tensor yt = torch::tensor(y, torch::kInt32); + // for (int i = 0; i < features.size(); ++i) { + // Xt.index_put_({ i, "..." }, torch::tensor(Xd[i], torch::kInt32)); + // } + // float total_score = 0, total_score_train = 0, score_train, score_test; + // Fold* fold; + // if (stratified) + // fold = new StratifiedKFold(nFolds, y, seed); + // else + // fold = new KFold(nFolds, y.size(), seed); + // for (auto i = 0; i < nFolds; ++i) { + // auto [train, test] = fold->getFold(i); + // cout << "Fold: " << i + 1 << endl; + // if (tensors) { + // auto ttrain = torch::tensor(train, torch::kInt64); + // auto ttest = torch::tensor(test, torch::kInt64); + // torch::Tensor Xtraint = torch::index_select(Xt, 1, ttrain); + // torch::Tensor ytraint = yt.index({ ttrain }); + // torch::Tensor Xtestt = torch::index_select(Xt, 1, ttest); + // torch::Tensor ytestt = yt.index({ ttest }); + // clf->fit(Xtraint, ytraint, features, className, states); + // auto temp = clf->predict(Xtraint); + // score_train = clf->score(Xtraint, ytraint); + // score_test = clf->score(Xtestt, ytestt); + // } else { + // auto [Xtrain, ytrain] = extract_indices(train, Xd, y); + // auto [Xtest, ytest] = extract_indices(test, Xd, y); + // clf->fit(Xtrain, ytrain, features, className, states); + // score_train = clf->score(Xtrain, ytrain); + // score_test = clf->score(Xtest, ytest); + // } + // if (dump_cpt) { + // cout << "--- CPT Tables ---" << endl; + // clf->dump_cpt(); + // } + // total_score_train += score_train; + // total_score += score_test; + // cout << "Score Train: " << score_train << endl; + // cout << "Score Test : " << score_test << endl; + // cout << "-------------------------------------------------------------------------------" << endl; + // } + // cout << "**********************************************************************************" << endl; + // cout << "Average Score Train: " << total_score_train / nFolds << endl; + // cout << "Average Score Test : " << total_score / nFolds << endl;return 0; } \ No newline at end of file diff --git a/src/BayesNet/CMakeLists.txt b/src/BayesNet/CMakeLists.txt index 2d4d729..86d9660 100644 --- a/src/BayesNet/CMakeLists.txt +++ b/src/BayesNet/CMakeLists.txt @@ -1,4 +1,4 @@ include_directories(${BayesNet_SOURCE_DIR}/lib/mdlp) include_directories(${BayesNet_SOURCE_DIR}/lib/Files) -add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANNew.cc Mst.cc Proposal.cc) +add_library(BayesNet bayesnetUtils.cc Network.cc Node.cc BayesMetrics.cc Classifier.cc KDB.cc TAN.cc SPODE.cc Ensemble.cc AODE.cc TANNew.cc KDBNew.cc Mst.cc Proposal.cc) target_link_libraries(BayesNet mdlp ArffFiles "${TORCH_LIBRARIES}") \ No newline at end of file diff --git a/src/BayesNet/Classifier.cc b/src/BayesNet/Classifier.cc index 26dce1d..4d9a4c7 100644 --- a/src/BayesNet/Classifier.cc +++ b/src/BayesNet/Classifier.cc @@ -36,14 +36,18 @@ namespace bayesnet { yv = vector(y.data_ptr(), y.data_ptr() + y.size(0)); return build(features, className, states); } + void Classifier::generateTensorXFromVector() + { + X = torch::zeros({ static_cast(Xv.size()), static_cast(Xv[0].size()) }, kInt32); + for (int i = 0; i < Xv.size(); ++i) { + X.index_put_({ i, "..." }, torch::tensor(Xv[i], kInt32)); + } + } // X is nxm where n is the number of features and m the number of samples Classifier& Classifier::fit(vector>& X, vector& y, vector& features, string className, map>& states) { - this->X = torch::zeros({ static_cast(X.size()), static_cast(X[0].size()) }, kInt32); Xv = X; - for (int i = 0; i < X.size(); ++i) { - this->X.index_put_({ i, "..." }, torch::tensor(X[i], kInt32)); - } + generateTensorXFromVector(); this->y = torch::tensor(y, kInt32); yv = y; return build(features, className, states); @@ -112,11 +116,9 @@ namespace bayesnet { { // Add all nodes to the network for (const auto& feature : features) { - model.addNode(feature, states[feature].size()); - cout << "-Adding node " << feature << " with " << states[feature].size() << " states" << endl; + model.addNode(feature); } - model.addNode(className, states[className].size()); - cout << "*Adding class " << className << " with " << states[className].size() << " states" << endl; + model.addNode(className); } int Classifier::getNumberOfNodes() { diff --git a/src/BayesNet/Classifier.h b/src/BayesNet/Classifier.h index e10a523..03c46c6 100644 --- a/src/BayesNet/Classifier.h +++ b/src/BayesNet/Classifier.h @@ -25,6 +25,7 @@ namespace bayesnet { string className; map> states; void checkFitParameters(); + void generateTensorXFromVector(); virtual void train() = 0; public: Classifier(Network model); diff --git a/src/BayesNet/KDB.cc b/src/BayesNet/KDB.cc index a0ab434..c1611c5 100644 --- a/src/BayesNet/KDB.cc +++ b/src/BayesNet/KDB.cc @@ -35,6 +35,7 @@ namespace bayesnet { } // 2. Compute class conditional mutual information I(Xi;XjIC), f or each auto conditionalEdgeWeights = metrics.conditionalEdge(); + cout << "Conditional edge weights: " << conditionalEdgeWeights << endl; // 3. Let the used variable list, S, be empty. vector S; // 4. Let the DAG network being constructed, BN, begin with a single diff --git a/src/BayesNet/KDBNew.cc b/src/BayesNet/KDBNew.cc new file mode 100644 index 0000000..8adf3b4 --- /dev/null +++ b/src/BayesNet/KDBNew.cc @@ -0,0 +1,48 @@ +#include "KDBNew.h" + +namespace bayesnet { + using namespace std; + KDBNew::KDBNew(int k) : KDB(k), Proposal(KDB::Xv, KDB::yv, features, className) {} + KDBNew& KDBNew::fit(torch::Tensor& X_, torch::Tensor& y_, vector& features_, string className_, map>& states_) + { + // This first part should go in a Classifier method called fit_local_discretization o fit_float... + features = features_; + className = className_; + Xf = X_; + y = y_; + model.initialize(); + // Fills vectors Xv & yv with the data from tensors X_ (discretized) & y + fit_local_discretization(states, y); + generateTensorXFromVector(); + // We have discretized the input data + // 1st we need to fit the model to build the normal TAN structure, TAN::fit initializes the base Bayesian network + cout << "KDBNew: Fitting model" << endl; + KDB::fit(KDB::Xv, KDB::yv, features, className, states); + cout << "KDBNew: Model fitted" << endl; + localDiscretizationProposal(states, model); + generateTensorXFromVector(); + Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1); + samples = torch::cat({ X, ytmp }, 0); + model.fit(KDB::Xv, KDB::yv, features, className); + return *this; + } + void KDBNew::train() + { + KDB::train(); + } + Tensor KDBNew::predict(Tensor& X) + { + auto Xtd = torch::zeros_like(X, torch::kInt32); + for (int i = 0; i < X.size(0); ++i) { + auto Xt = vector(X[i].data_ptr(), X[i].data_ptr() + X.size(1)); + auto Xd = discretizers[features[i]]->transform(Xt); + Xtd.index_put_({ i }, torch::tensor(Xd, torch::kInt32)); + } + cout << "KDBNew Xtd: " << Xtd.sizes() << endl; + return KDB::predict(Xtd); + } + vector KDBNew::graph(const string& name) + { + return KDB::graph(name); + } +} \ No newline at end of file diff --git a/src/BayesNet/KDBNew.h b/src/BayesNet/KDBNew.h new file mode 100644 index 0000000..2bcd213 --- /dev/null +++ b/src/BayesNet/KDBNew.h @@ -0,0 +1,20 @@ +#ifndef KDBNEW_H +#define KDBNEW_H +#include "KDB.h" +#include "Proposal.h" + +namespace bayesnet { + using namespace std; + class KDBNew : public KDB, public Proposal { + private: + public: + KDBNew(int k); + virtual ~KDBNew() = default; + KDBNew& fit(torch::Tensor& X, torch::Tensor& y, vector& features, string className, map>& states) override; + vector graph(const string& name = "KDB") override; + Tensor predict(Tensor& X) override; + void train() override; + static inline string version() { return "0.0.1"; }; + }; +} +#endif // !KDBNew_H \ No newline at end of file diff --git a/src/BayesNet/Network.cc b/src/BayesNet/Network.cc index 3f304a4..2a37de4 100644 --- a/src/BayesNet/Network.cc +++ b/src/BayesNet/Network.cc @@ -31,21 +31,18 @@ namespace bayesnet { { return samples; } - void Network::addNode(const string& name, int numStates) + void Network::addNode(const string& name) { if (name == "") { throw invalid_argument("Node name cannot be empty"); } + if (nodes.find(name) != nodes.end()) { + return; + } if (find(features.begin(), features.end(), name) == features.end()) { features.push_back(name); } - if (nodes.find(name) != nodes.end()) { - // if node exists update its number of states and remove parents, children and CPT - nodes[name]->clear(); - nodes[name]->setNumStates(numStates); - return; - } - nodes[name] = std::make_unique(name, numStates); + nodes[name] = std::make_unique(name); } vector Network::getFeatures() { @@ -128,14 +125,20 @@ namespace bayesnet { } } } + void Network::setStates() + { + // Set states to every Node in the network + for (int i = 0; i < features.size(); ++i) { + nodes[features[i]]->setNumStates(static_cast(torch::max(samples.index({ i, "..." })).item()) + 1); + } + classNumStates = nodes[className]->getNumStates(); + } // X comes in nxm, where n is the number of features and m the number of samples void Network::fit(torch::Tensor& X, torch::Tensor& y, const vector& featureNames, const string& className) { checkFitData(X.size(1), X.size(0), y.size(0), featureNames, className); this->className = className; dataset.clear(); - // Specific part - classNumStates = torch::max(y).item() + 1; Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1); samples = torch::cat({ X , ytmp }, 0); for (int i = 0; i < featureNames.size(); ++i) { @@ -151,8 +154,6 @@ namespace bayesnet { checkFitData(input_data[0].size(), input_data.size(), labels.size(), featureNames, className); this->className = className; dataset.clear(); - // Specific part - classNumStates = *max_element(labels.begin(), labels.end()) + 1; // Build dataset & tensor of samples (nxm) (n+1 because of the class) samples = torch::zeros({ static_cast(input_data.size() + 1), static_cast(input_data[0].size()) }, torch::kInt32); for (int i = 0; i < featureNames.size(); ++i) { @@ -165,6 +166,7 @@ namespace bayesnet { } void Network::completeFit() { + setStates(); int maxThreadsRunning = static_cast(std::thread::hardware_concurrency() * maxThreads); if (maxThreadsRunning < 1) { maxThreadsRunning = 1; @@ -213,7 +215,7 @@ namespace bayesnet { auto sample = samples.index({ "...", i }); auto psample = predict_sample(sample); auto temp = torch::tensor(psample, torch::kFloat64); -// result.index_put_({ i, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64)); + // result.index_put_({ i, "..." }, torch::tensor(predict_sample(sample), torch::kFloat64)); result.index_put_({ i, "..." }, temp); } if (proba) @@ -325,17 +327,17 @@ namespace bayesnet { vector threads; mutex mtx; for (int i = 0; i < classNumStates; ++i) { -// threads.emplace_back([this, &result, &evidence, i, &mtx]() { - auto completeEvidence = map(evidence); - completeEvidence[getClassName()] = i; + threads.emplace_back([this, &result, &evidence, i, &mtx]() { + auto completeEvidence = map(evidence); + completeEvidence[getClassName()] = i; double factor = computeFactor(completeEvidence); -// lock_guard lock(mtx); + lock_guard lock(mtx); result[i] = factor; -// }); + }); + } + for (auto& thread : threads) { + thread.join(); } -// for (auto& thread : threads) { -// thread.join(); -// } // Normalize result double sum = accumulate(result.begin(), result.end(), 0.0); transform(result.begin(), result.end(), result.begin(), [sum](double& value) { return value / sum; }); @@ -421,7 +423,7 @@ namespace bayesnet { void Network::dump_cpt() { for (auto& node : nodes) { - cout << "* " << node.first << ": " << node.second->getCPT() << endl; + cout << "* " << node.first << ": (" << node.second->getNumStates() << ") : " << node.second->getCPT().sizes() << endl; } } } diff --git a/src/BayesNet/Network.h b/src/BayesNet/Network.h index 49ebcfb..b27125c 100644 --- a/src/BayesNet/Network.h +++ b/src/BayesNet/Network.h @@ -27,6 +27,7 @@ namespace bayesnet { double mutualInformation(torch::Tensor&, torch::Tensor&); void completeFit(); void checkFitData(int n_features, int n_samples, int n_samples_y, const vector& featureNames, const string& className); + void setStates(); public: Network(); explicit Network(float, int); @@ -34,7 +35,7 @@ namespace bayesnet { explicit Network(Network&); torch::Tensor& getSamples(); float getmaxThreads(); - void addNode(const string&, int); + void addNode(const string&); void addEdge(const string&, const string&); map>& getNodes(); vector getFeatures(); diff --git a/src/BayesNet/Node.cc b/src/BayesNet/Node.cc index 095cff7..3fca064 100644 --- a/src/BayesNet/Node.cc +++ b/src/BayesNet/Node.cc @@ -2,8 +2,8 @@ namespace bayesnet { - Node::Node(const std::string& name, int numStates) - : name(name), numStates(numStates), cpTable(torch::Tensor()), parents(vector()), children(vector()) + Node::Node(const std::string& name) + : name(name), numStates(0), cpTable(torch::Tensor()), parents(vector()), children(vector()) { } void Node::clear() @@ -86,6 +86,7 @@ namespace bayesnet { } void Node::computeCPT(map>& dataset, const int laplaceSmoothing) { + dimensions.clear(); // Get dimensions of the CPT dimensions.push_back(numStates); transform(parents.begin(), parents.end(), back_inserter(dimensions), [](const auto& parent) { return parent->getNumStates(); }); diff --git a/src/BayesNet/Node.h b/src/BayesNet/Node.h index 3a5bbe6..b923dec 100644 --- a/src/BayesNet/Node.h +++ b/src/BayesNet/Node.h @@ -16,7 +16,7 @@ namespace bayesnet { vector dimensions; // dimensions of the cpTable public: vector> combinations(const vector&); - Node(const string&, int); + explicit Node(const string&); void clear(); void addParent(Node*); void addChild(Node*); diff --git a/src/BayesNet/TANNew.cc b/src/BayesNet/TANNew.cc index abee474..e0e1f0b 100644 --- a/src/BayesNet/TANNew.cc +++ b/src/BayesNet/TANNew.cc @@ -2,22 +2,26 @@ namespace bayesnet { using namespace std; - TANNew::TANNew() : TAN(), Proposal(TAN::Xv, TAN::yv, TAN::features, TAN::className) {} + TANNew::TANNew() : TAN(), Proposal(TAN::Xv, TAN::yv, features, className) {} TANNew& TANNew::fit(torch::Tensor& X_, torch::Tensor& y_, vector& features_, string className_, map>& states_) { // This first part should go in a Classifier method called fit_local_discretization o fit_float... - TAN::features = features_; - TAN::className = className_; + features = features_; + className = className_; Xf = X_; y = y_; + // Fills vectors Xv & yv with the data from tensors X_ (discretized) & y fit_local_discretization(states, y); + generateTensorXFromVector(); // We have discretized the input data // 1st we need to fit the model to build the normal TAN structure, TAN::fit initializes the base Bayesian network cout << "TANNew: Fitting model" << endl; - TAN::fit(TAN::Xv, TAN::yv, TAN::features, TAN::className, states); + TAN::fit(TAN::Xv, TAN::yv, features, className, states); cout << "TANNew: Model fitted" << endl; localDiscretizationProposal(states, model); - addNodes(); + generateTensorXFromVector(); + Tensor ytmp = torch::transpose(y.view({ y.size(0), 1 }), 0, 1); + samples = torch::cat({ X, ytmp }, 0); model.fit(TAN::Xv, TAN::yv, features, className); return *this; } diff --git a/src/Platform/Experiment.cc b/src/Platform/Experiment.cc index a79216c..261b1c5 100644 --- a/src/Platform/Experiment.cc +++ b/src/Platform/Experiment.cc @@ -146,6 +146,11 @@ namespace platform { auto y_test = y.index({ test_t }); cout << nfold + 1 << ", " << flush; clf->fit(X_train, y_train, features, className, states); + cout << endl; + auto lines = clf->show(); + for (auto line : lines) { + cout << line << endl; + } nodes[item] = clf->getNumberOfNodes(); edges[item] = clf->getNumberOfEdges(); num_states[item] = clf->getNumberOfStates(); diff --git a/src/Platform/Models.h b/src/Platform/Models.h index ae675ea..9616734 100644 --- a/src/Platform/Models.h +++ b/src/Platform/Models.h @@ -7,6 +7,7 @@ #include "KDB.h" #include "SPODE.h" #include "TANNew.h" +#include "KDBNew.h" namespace platform { class Models { private: diff --git a/src/Platform/modelRegister.h b/src/Platform/modelRegister.h index e6788cb..b3f4bd0 100644 --- a/src/Platform/modelRegister.h +++ b/src/Platform/modelRegister.h @@ -8,6 +8,8 @@ static platform::Registrar registrarS("SPODE", [](void) -> bayesnet::BaseClassifier* { return new bayesnet::SPODE(2);}); static platform::Registrar registrarK("KDB", [](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDB(2);}); +static platform::Registrar registrarKN("KDBNew", + [](void) -> bayesnet::BaseClassifier* { return new bayesnet::KDBNew(2);}); static platform::Registrar registrarA("AODE", [](void) -> bayesnet::BaseClassifier* { return new bayesnet::AODE();}); #endif \ No newline at end of file diff --git a/tests/BayesNetwork.cc b/tests/BayesNetwork.cc index 6750741..613392e 100644 --- a/tests/BayesNetwork.cc +++ b/tests/BayesNetwork.cc @@ -9,29 +9,21 @@ TEST_CASE("Test Bayesian Network") { auto [Xd, y, features, className, states] = loadFile("iris"); - SECTION("Test Update Nodes") - { - auto net = bayesnet::Network(); - net.addNode("A", 3); - REQUIRE(net.getStates() == 3); - net.addNode("A", 5); - REQUIRE(net.getStates() == 5); - } SECTION("Test get features") { auto net = bayesnet::Network(); - net.addNode("A", 3); - net.addNode("B", 5); + net.addNode("A"); + net.addNode("B"); REQUIRE(net.getFeatures() == vector{"A", "B"}); - net.addNode("C", 2); + net.addNode("C"); REQUIRE(net.getFeatures() == vector{"A", "B", "C"}); } SECTION("Test get edges") { auto net = bayesnet::Network(); - net.addNode("A", 3); - net.addNode("B", 5); - net.addNode("C", 2); + net.addNode("A"); + net.addNode("B"); + net.addNode("C"); net.addEdge("A", "B"); net.addEdge("B", "C"); REQUIRE(net.getEdges() == vector>{ {"A", "B"}, { "B", "C" } });