diff --git a/lib/include/otagrum/otagrum.hxx b/lib/include/otagrum/otagrum.hxx
index d632f9bd..485b018d 100644
--- a/lib/include/otagrum/otagrum.hxx
+++ b/lib/include/otagrum/otagrum.hxx
@@ -35,6 +35,7 @@
#include "otagrum/JunctionTreeBernsteinCopulaFactory.hxx"
#include "otagrum/ContinuousBayesianNetwork.hxx"
#include "otagrum/ContinuousBayesianNetworkFactory.hxx"
+#include "otagrum/DistributionBayesianNetwork.hxx"
#include "otagrum/StratifiedCache.hxx"
#endif // OTAGRUM_HXX
diff --git a/lib/src/CMakeLists.txt b/lib/src/CMakeLists.txt
index 6b94d741..5613886b 100644
--- a/lib/src/CMakeLists.txt
+++ b/lib/src/CMakeLists.txt
@@ -13,6 +13,7 @@ ot_add_source_file ( ContinuousBayesianNetwork.cxx )
ot_add_source_file ( ContinuousBayesianNetworkFactory.cxx )
ot_add_source_file ( ContinuousPC.cxx )
ot_add_source_file ( ContinuousMIIC.cxx )
+ot_add_source_file ( DistributionBayesianNetwork.cxx )
ot_add_source_file ( TabuList.cxx )
ot_add_source_file ( JunctionTreeBernsteinCopula.cxx )
ot_add_source_file ( JunctionTreeBernsteinCopulaFactory.cxx )
@@ -29,6 +30,7 @@ ot_install_header_file ( ContinuousBayesianNetwork.hxx )
ot_install_header_file ( ContinuousBayesianNetworkFactory.hxx )
ot_install_header_file ( ContinuousPC.hxx )
ot_install_header_file ( ContinuousMIIC.hxx )
+ot_install_header_file ( DistributionBayesianNetwork.hxx )
ot_install_header_file ( TabuList.hxx )
ot_install_header_file ( JunctionTreeBernsteinCopula.hxx )
ot_install_header_file ( JunctionTreeBernsteinCopulaFactory.hxx )
diff --git a/lib/src/DistributionBayesianNetwork.cxx b/lib/src/DistributionBayesianNetwork.cxx
new file mode 100644
index 00000000..170c949f
--- /dev/null
+++ b/lib/src/DistributionBayesianNetwork.cxx
@@ -0,0 +1,296 @@
+// -*- C++ -*-
+/**
+ * @brief The DistributionBayesianNetwork distribution
+ *
+ *
+ * Copyright 2010-2024 Airbus-LIP6-Phimeca
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library. If not, see .
+ *
+ */
+
+#include "otagrum/DistributionBayesianNetwork.hxx"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace OT;
+
+namespace OTAGRUM
+{
+
+CLASSNAMEINIT(DistributionBayesianNetwork)
+
+static const Factory
+Factory_DistributionBayesianNetwork;
+
+/* Default constructor */
+DistributionBayesianNetwork::DistributionBayesianNetwork()
+ : ContinuousDistribution()
+ , dag_()
+ , joints_(0)
+{
+ setName("DistributionBayesianNetwork");
+ setDAGAndJoints(dag_, joints_);
+}
+
+/* Parameters constructor */
+DistributionBayesianNetwork::DistributionBayesianNetwork(const NamedDAG &dag,
+ const DistributionCollection & joints)
+ : ContinuousDistribution()
+ , dag_(dag)
+ , joints_(0)
+{
+ setName("DistributionBayesianNetwork");
+ setDAGAndJoints(dag, joints);
+}
+
+/* Comparison operator */
+Bool DistributionBayesianNetwork::
+operator==(const DistributionBayesianNetwork &other) const
+{
+ if (this == &other)
+ return true;
+ return (dag_ == other.dag_) &&
+ (joints_ == other.joints_);
+}
+
+Bool DistributionBayesianNetwork::equals(
+ const DistributionImplementation &other) const
+{
+ const DistributionBayesianNetwork *p_other =
+ dynamic_cast(&other);
+ return p_other && (*this == *p_other);
+}
+
+/* String converter */
+String DistributionBayesianNetwork::__repr__() const
+{
+ OSS oss(true);
+ oss << "class=" << DistributionBayesianNetwork::GetClassName()
+ << " name=" << getName() << " dimension=" << getDimension()
+ << " dag=" << dag_ << " joints=" << joints_;
+ return oss;
+}
+
+String DistributionBayesianNetwork::__str__(const String &offset) const
+{
+ OSS oss(false);
+ oss << offset << getClassName() << "(dag=" << dag_
+ << ", joints=" << joints_ << ")";
+ return oss;
+}
+
+/* Virtual constructor */
+DistributionBayesianNetwork *DistributionBayesianNetwork::clone() const
+{
+ return new DistributionBayesianNetwork(*this);
+}
+
+/* Compute the numerical range of the distribution given the parameters values
+ */
+void DistributionBayesianNetwork::computeRange()
+{
+ const UnsignedInteger dimension = dag_.getSize();
+ setDimension(dimension);
+ Point lower(dimension);
+ Point upper(dimension);
+ for (UnsignedInteger i = 0; i < dimension; ++i)
+ {
+ const Interval rangeI(joints_[i].getRange());
+ const UnsignedInteger dimI = joints_[i].getDimension();
+ // Check if the current node is a root node
+ lower[i] = rangeI.getLowerBound()[dimI - 1];
+ upper[i] = rangeI.getUpperBound()[dimI - 1];
+ } // i
+ setRange(Interval(lower, upper));
+}
+
+/* Get one realization of the distribution */
+Point DistributionBayesianNetwork::getRealization() const
+{
+ const UnsignedInteger dimension = getDimension();
+ Point result(dimension);
+ const Indices order(dag_.getTopologicalOrder());
+
+ // The generation works this way:
+ // + go through the nodes according to a topological order wrt the dag
+ // + the ith node in this order has a global index order[i]
+ // + its parents have global indices parents[i]
+ // + for the ith node, sample the conditional joint corresponding
+ // to the multivariate joint linked to this node.
+ // The convention is that the (d-1) first components of this distribution
+ // is the distribution of the parents of the node IN THE CORRECT ORDER
+ // whild the d-th component is the current node.
+ for (UnsignedInteger i = 0; i < order.getSize(); ++i)
+ {
+ const UnsignedInteger globalI = order[i];
+ const Distribution joint(joints_[globalI]);
+ const Indices parents(dag_.getParents(globalI));
+ const UnsignedInteger conditioningDimension(parents.getSize());
+ if (conditioningDimension == 0)
+ {
+ result[globalI] = joint.getRealization()[0];
+ }
+ else
+ {
+ Point y(conditioningDimension);
+ for (UnsignedInteger j = 0; j < conditioningDimension; ++j)
+ y[j] = result[parents[j]];
+ result[globalI] = joint.computeConditionalQuantile(
+ RandomGenerator::Generate(), y);
+ }
+ } // i
+ return result;
+}
+
+/* Get the PDF of the distribution */
+Scalar DistributionBayesianNetwork::computePDF(const Point &point) const
+{
+ const Indices order(dag_.getTopologicalOrder());
+ Scalar pdf = 1.0;
+ for (UnsignedInteger i = 0; i < order.getSize(); ++i)
+ {
+ const UnsignedInteger globalI = order[i];
+ const Distribution joint(joints_[globalI]);
+ const Indices parents(dag_.getParents(globalI));
+ const UnsignedInteger conditioningDimension(parents.getSize());
+ const Scalar x = point[globalI];
+ if (conditioningDimension == 0)
+ {
+ pdf *= joint.computePDF(x);
+ }
+ else
+ {
+ Point y(conditioningDimension);
+ for (UnsignedInteger j = 0; j < conditioningDimension; ++j)
+ y[j] = point[parents[j]];
+ const Scalar conditionalPDF =
+ joint.computeConditionalPDF(x, y);
+ pdf *= conditionalPDF;
+ if (!(pdf > 0.0)) return 0.0;
+ }
+ } // i
+ return pdf;
+}
+
+/* Get the log-PDF of the distribution */
+Scalar DistributionBayesianNetwork::computeLogPDF(const Point &point) const
+{
+ const Indices order(dag_.getTopologicalOrder());
+ Scalar logPDF = 0.0;
+ for (UnsignedInteger i = 0; i < order.getSize(); ++i)
+ {
+ const UnsignedInteger globalI = order[i];
+ const Distribution joint(joints_[globalI]);
+ const Indices parents(dag_.getParents(globalI));
+ const UnsignedInteger conditioningDimension(parents.getSize());
+ const Scalar x = point[globalI];
+ if (conditioningDimension == 0)
+ {
+ logPDF += joint.computeLogPDF(x);
+ }
+ else
+ {
+ Point y(conditioningDimension);
+ for (UnsignedInteger j = 0; j < conditioningDimension; ++j)
+ y[j] = point[parents[j]];
+ const Scalar conditionalPDF =
+ joint.computeConditionalPDF(x, y);
+ if (!(conditionalPDF > 0.0))
+ return -SpecFunc::MaxScalar;
+ logPDF += std::log(conditionalPDF);
+ }
+ } // i
+ return logPDF;
+}
+
+/* DAG and joints accessor */
+void DistributionBayesianNetwork::setDAGAndJoints(const NamedDAG &dag,
+ const DistributionCollection & joints)
+{
+ const Indices order(dag.getTopologicalOrder());
+ const UnsignedInteger size = order.getSize();
+ if (joints.getSize() != size) throw InvalidArgumentException(HERE) << "Error: expected a collection of joints of size=" << size << ", got size=" << joints.getSize();
+ for (UnsignedInteger i = 0; i < order.getSize(); ++i)
+ {
+ const UnsignedInteger globalIndex = order[i];
+ if (joints[globalIndex].getDimension() != dag.getParents(globalIndex).getSize() + 1)
+ throw InvalidArgumentException(HERE)
+ << "Error: expected a joint of dimension="
+ << dag.getParents(globalIndex).getSize() + 1 << " for node=" << globalIndex
+ << " and its parents=" << dag.getParents(globalIndex)
+ << ", got dimension=" << joints[globalIndex].getDimension();
+ }
+ dag_ = dag;
+ joints_ = joints;
+ computeRange();
+ setDescription(dag.getDescription());
+}
+
+gum::DAG DistributionBayesianNetwork::getDAG() const
+{
+ return dag_.getDAG();
+}
+
+Indices DistributionBayesianNetwork::getParents(const UnsignedInteger nodeId) const
+{
+ return dag_.getParents(nodeId);
+}
+
+Distribution
+DistributionBayesianNetwork::getMarginal(const UnsignedInteger i) const
+{
+ if (i >= joints_.getSize()) throw InvalidArgumentException(HERE) << "The index of a marginal distribution must be in the range [0, dim-1]";
+ if (joints_[i].getDimension() == 1)
+ return joints_[i];
+ throw NotYetImplementedException(HERE) << "In getMarginal";
+}
+
+DistributionBayesianNetwork::DistributionCollection
+DistributionBayesianNetwork::getJoints() const
+{
+ return joints_;
+}
+
+Distribution
+DistributionBayesianNetwork::getJointAtNode(const UnsignedInteger i) const
+{
+ if (i >= joints_.getSize()) throw InvalidArgumentException(HERE) << "The index of a joint distribution must be in the range [0, dim-1]";
+ return joints_[i];
+}
+
+/* Method save() stores the object through the StorageManager */
+void DistributionBayesianNetwork::save(Advocate &adv) const
+{
+ ContinuousDistribution::save(adv);
+ adv.saveAttribute("dag_", dag_);
+ adv.saveAttribute("joints_", joints_);
+}
+
+/* Method load() reloads the object from the StorageManager */
+void DistributionBayesianNetwork::load(Advocate &adv)
+{
+ ContinuousDistribution::load(adv);
+ adv.loadAttribute("dag_", dag_);
+ adv.loadAttribute("joints_", joints_);
+ computeRange();
+}
+
+} // namespace OTAGRUM
diff --git a/lib/src/otagrum/DistributionBayesianNetwork.hxx b/lib/src/otagrum/DistributionBayesianNetwork.hxx
new file mode 100644
index 00000000..e6c74787
--- /dev/null
+++ b/lib/src/otagrum/DistributionBayesianNetwork.hxx
@@ -0,0 +1,121 @@
+// -*- C++ -*-
+/**
+ * @brief The DistributionBayesianNetwork distribution
+ *
+ * Copyright 2010-2024 Airbus-LIP6-Phimeca
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library. If not, see .
+ *
+ */
+#ifndef OTAGRUM_DISTRIBUTIONBAYESIANNETWORK_HXX
+#define OTAGRUM_DISTRIBUTIONBAYESIANNETWORK_HXX
+
+#include
+#include
+
+#include "otagrum/NamedDAG.hxx"
+#include "otagrum/otagrumprivate.hxx"
+
+namespace OTAGRUM
+{
+
+/**
+ * @class DistributionBayesianNetwork
+ *
+ * The DistributionBayesianNetwork distribution.
+ */
+class OTAGRUM_API DistributionBayesianNetwork
+ : public OT::ContinuousDistribution
+{
+ CLASSNAME
+public:
+
+ typedef OT::Collection< OT::Distribution > DistributionCollection;
+ typedef OT::PersistentCollection< OT::Distribution > DistributionPersistentCollection;
+
+ /** Default constructor */
+ DistributionBayesianNetwork();
+
+ /** Parameters constructor */
+ DistributionBayesianNetwork(const NamedDAG & dag,
+ const DistributionCollection & joints);
+
+public:
+ /** Comparison operator */
+ using OT::ContinuousDistribution::operator ==;
+ OT::Bool operator ==(const DistributionBayesianNetwork & other) const;
+protected:
+ OT::Bool equals(const OT::DistributionImplementation & other) const override;
+public:
+
+ /** String converter */
+ OT::String __repr__() const override;
+ OT::String __str__(const OT::String & offset = "") const override;
+
+
+
+ /* Interface inherited from Distribution */
+
+ /** Virtual constructor */
+ DistributionBayesianNetwork * clone() const override;
+
+ /** Get one realization of the distribution */
+ OT::Point getRealization() const override;
+
+ /** Get the PDF of the distribution, i.e. P(point < X < point+dx) = PDF(point)dx + o(dx) */
+ using OT::ContinuousDistribution::computePDF;
+ OT::Scalar computePDF(const OT::Point & point) const override;
+
+ /** Get the log-PDF of the distribution */
+ using OT::ContinuousDistribution::computeLogPDF;
+ OT::Scalar computeLogPDF(const OT::Point & point) const override;
+
+ /** DAG, marginals and copulas accessor */
+ void setDAGAndJoints(const NamedDAG & dag,
+ const DistributionCollection & joints);
+
+ gum::DAG getDAG() const;
+ OT::Indices getParents(const OT::UnsignedInteger nodeId) const;
+ /** One joint per node */
+ DistributionCollection getJoints() const;
+ OT::Distribution getJointAtNode(const OT::UnsignedInteger i) const;
+
+ using OT::ContinuousDistribution::getMarginal;
+ OT::Distribution getMarginal(const OT::UnsignedInteger i) const override;
+
+ /** Method save() stores the object through the StorageManager */
+ void save(OT::Advocate & adv) const override;
+
+ /** Method load() reloads the object from the StorageManager */
+ void load(OT::Advocate & adv) override;
+
+protected:
+
+private:
+ /** Compute the range */
+ void computeRange() override;
+
+ /** The main parameter set of the distribution */
+
+ /** Underlying junction tree */
+ NamedDAG dag_;
+
+ /** Collection of distributions, one per node */
+ DistributionPersistentCollection joints_;
+
+}; /* class DistributionBayesianNetwork */
+
+} /* namespace OTAGRUM */
+
+#endif /* OTAGRUM_DISTRIBUTIONBAYESIANNETWORK_HXX */
diff --git a/lib/test/t_NamedDAG_std.cxx b/lib/test/t_NamedDAG_std.cxx
index e49785e8..a1587aff 100644
--- a/lib/test/t_NamedDAG_std.cxx
+++ b/lib/test/t_NamedDAG_std.cxx
@@ -53,7 +53,7 @@ void testSerialisation()
std::cout << "\n------------------\n";
OT::Study study2("tmp.xml");
study2.load();
- std::cout << "Labels=" << study2.printLabels() << std::endl;
+ std::cout << "Labels=" << study2.getLabels() << std::endl;
std::cout << "\n------------------\n";
OTAGRUM::NamedDAG dag;
diff --git a/lib/test/t_NamedDAG_std.expout b/lib/test/t_NamedDAG_std.expout
index 108f3b7e..fa9f0b02 100644
--- a/lib/test/t_NamedDAG_std.expout
+++ b/lib/test/t_NamedDAG_std.expout
@@ -38,7 +38,7 @@ todo : digraph {
Before saving, NamedDAG, ndagFromTest
------------------
-Labels=ndag
+Labels=[ndag]
------------------
Before fill, NamedDAG, Unnamed
diff --git a/python/doc/user_manual/user_manual.rst b/python/doc/user_manual/user_manual.rst
index cee4155b..dd1ded8b 100644
--- a/python/doc/user_manual/user_manual.rst
+++ b/python/doc/user_manual/user_manual.rst
@@ -19,3 +19,4 @@ API
NamedDAG
ContinuousBayesianNetwork
ContinuousBayesianNetworkFactory
+ DistributionBayesianNetwork
diff --git a/python/src/CMakeLists.txt b/python/src/CMakeLists.txt
index 57d53b55..5b09e5d0 100644
--- a/python/src/CMakeLists.txt
+++ b/python/src/CMakeLists.txt
@@ -71,6 +71,7 @@ ot_add_python_module (${PACKAGE_NAME} ${PACKAGE_NAME}_module.i otagrum_agrum.i
JunctionTreeBernsteinCopulaFactory.i JunctionTreeBernsteinCopulaFactory_doc.i
ContinuousBayesianNetwork.i ContinuousBayesianNetwork_doc.i
ContinuousBayesianNetworkFactory.i ContinuousBayesianNetworkFactory_doc.i
+ DistributionBayesianNetwork.i DistributionBayesianNetwork_doc.i
)
diff --git a/python/src/DistributionBayesianNetwork.i b/python/src/DistributionBayesianNetwork.i
new file mode 100644
index 00000000..ed140124
--- /dev/null
+++ b/python/src/DistributionBayesianNetwork.i
@@ -0,0 +1,10 @@
+// SWIG file DistributionBayesanNetwork.i
+
+%{
+#include "otagrum/DistributionBayesianNetwork.hxx"
+%}
+
+%include DistributionBayesianNetwork_doc.i
+
+%copyctor OTAGRUM::DistributionBayesianNetwork;
+%include "otagrum/DistributionBayesianNetwork.hxx"
diff --git a/python/src/DistributionBayesianNetwork_doc.i b/python/src/DistributionBayesianNetwork_doc.i
new file mode 100644
index 00000000..1c7f4364
--- /dev/null
+++ b/python/src/DistributionBayesianNetwork_doc.i
@@ -0,0 +1,106 @@
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork
+"DistributionBayesianNetwork class represents a continuous bayesian network (CBN),
+that is a bayesian network parameterized by marginal distributions and local
+copulas for each node from which conditional copulas are
+extracted. The scope of each copula is the node and its parents.
+
+Available constructor:
+ DistributionBayesianNetwork(*dag, marginals, copulas*)
+
+Parameters
+----------
+dag : :class:`~otagrum.NamedDAG`
+ The structure of the CBN
+joints : sequence of :py:class:`openturns.Distribution`
+ Collection of local joint distribution from which the conditional
+ distributions are extracted.
+
+Examples
+--------
+Create a CBN from a DAG structure and a collection of distributions
+
+>>> import otagrum
+>>> import openturns as ot
+>>> import pyAgrum as gum
+>>> bn = gum.BayesNet.fastPrototype('A->C->B;')
+>>> ndag = otagrum.NamedDAG(bn)
+>>> joints = []
+>>> for i in range(ndag.getSize()):
+... dim = 1 + ndag.getParents(i).getSize()
+... R = ot.CorrelationMatrix(dim)
+... for j in range(dim):
+... for k in range(j):
+... R[j, k] = 0.8
+... joints.append(ot.Normal([0.0]*dim, [1.0]*dim, R))
+>>> cbn = otagrum.DistributionBayesianNetwork(ndag, joints)
+
+Draw a sample from the joint distribution encoded by the CBN
+
+>>> sample = cbn.getSample(100)"
+
+// ----------------------------------------------------------------------------
+
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork::getDAG
+"Accessor to the CBN structure.
+
+Returns
+-------
+dag : :py:class:`pyAgrum.DAG`
+ The underlying DAG"
+
+// ----------------------------------------------------------------------------
+
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork::setDAGAndJoints
+"Accessor to the DAG and the local joints collection.
+
+Parameters
+----------
+dag : :class:`~otagrum.NamedDAG`
+ The underlying NamedDAG
+joints : sequence of :py:class:`openturns.Distribution`
+ The collection of local joints from which the conditional
+ distributions are extracted."
+
+// ----------------------------------------------------------------------------
+
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork::getJoints
+"The collection of local conditional joints.
+
+Returns
+-------
+joints : sequence of :py:class:`openturns.Distribution`
+ Collection of local joint distributions from which the marginal
+ distributions are extracted.
+"
+
+// ----------------------------------------------------------------------------
+
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork::getJointAtNode
+"Accessor to the local conditional distributions.
+
+Parameters
+----------
+index : int
+ Collection index
+
+Returns
+-------
+copula : `py:class:`openturns.Distribution`
+ Local conditional distribution
+"
+
+// ----------------------------------------------------------------------------
+
+%feature("docstring") OTAGRUM::DistributionBayesianNetwork::getParents
+"Get node parents.
+
+Parameters
+----------
+nodeId : int
+ Node index
+
+Returns
+-------
+parents : :py:class:`openturns.Indices`
+ Parent nodes
+"
diff --git a/python/src/otagrum_module.i b/python/src/otagrum_module.i
index 35e572d1..89fb5a4f 100644
--- a/python/src/otagrum_module.i
+++ b/python/src/otagrum_module.i
@@ -35,3 +35,4 @@
%include TabuList.i
%include ContinuousBayesianNetwork.i
%include ContinuousBayesianNetworkFactory.i
+%include DistributionBayesianNetwork.i